The Soundpool class

The SoundPool class allows us to hold and manipulate a collection of sound FX; literally, a pool of sounds. The class handles everything from decompressing a sound file such as a .wav or a .ogg, keeping an identifying reference to it via an integer id and, of course, playing the sound. When the sound is played, it is done so in a non-blocking manner (using a thread behind the scenes) that does not interfere with the smooth running of our app or our user's interaction with it.

The first thing we need to do is add the sound effects to a folder called assets in the main folder of the game project. We will do this for real shortly.

Next, in our Java code, declare an object of the SoundPool type and an int identifier for each and every sound effect we intend to use. We also need to declare another int called nowPlaying, which we can use to track which sound is currently playing. We will see how we do this shortly:

// create an identifier
SoundPool sp;
int nowPlaying =-1;
int idFX1 = -1;
float volume = 1;// Volumes rage from 0 through 1

Now, we will look at the two different ways we initialize a SoundPool, depending upon the version of Android the device is using. This is the perfect opportunity to use our method of writing different code for different versions of Android.

Initializing SoundPool the new way

The new way of initializing SoundPool involves us using an AudioAttributes object to set the attributes of the pool of sound we want.

The first block of code uses chaining and calls four separate methods on one object that initializes our AudioAttributes object (audioAttributes). Also note that it is fine to have comments in-between parts of the chained method calls as these are ignored entirely by the compiler:

// Instantiate a SoundPool dependent on Android version
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
   
   // The new way
   // Build an AudioAttributes object
   AudioAttributes audioAttributes = 
               // First method call
        new AudioAttributes.Builder()
              // Second method call
              .setUsage
             (AudioAttributes.USAGE_ASSISTANCE_SONIFICATION)
             // Third method call 
            .setContentType
            (AudioAttributes.CONTENT_TYPE_SONIFICATION)
            // Fourth method call
      .build();// Yay! A semicolon

   // Initialize the SoundPool
   sp = new SoundPool.Builder()
              .setMaxStreams(5)
              .setAudioAttributes(audioAttributes)
              .build();
}

In the preceding code, we used chaining and the Builder method of this class to initialize an AudioAttributes object to let it know that it will be used for user interface interaction with USAGE_ASSISTANCE_SONIFICATION.

We also used CONTENT_TYPE_SONIFICATION, which lets the class know it is for responsive sounds, for example, button clicks, a collision, or similar.

Now, we can initialize the SoundPool (sp) itself by passing in the AudioAttributes object (audioAttributes) and the maximum number of simultaneous sounds we are likely to want to play.

The second block of code chains another four methods to initialize sp, including a call to setAudioAttributes that uses the audioAttributes object that we initialized in the earlier block of chained methods.

Now, we can write an else block of code that will, of course, have the code for the old way of doing things.

Initializing SoundPool the old way

There is no need for an AudioAttributes object. Simply initialize the SoundPool (sp) by passing in the number of simultaneous sounds. The final parameter is for sound quality, and passing zero is all we need to do. This is much simpler than the new way, but also less flexible regarding the choices we can make:

else {
   // The old way
   sp = new SoundPool(5, AudioManager.STREAM_MUSIC, 0);
}

Note

We could use the old way, and the newer versions of Android would handle this. However, we get a warning about using deprecated methods. This is what the official documentation says about it:

Initializing SoundPool the old way

Furthermore, the new way gives access to more features, as we saw previously. And anyway, it's a good excuse to look at some simple code to handle different versions of Android.

Now, we can go ahead and load up (decompress) the sound files into our SoundPool.

Loading sound files into memory

As with our thread control, we are required to wrap our code in try-catch blocks. This makes sense because reading a file can fail for reasons beyond our control, but also because we are forced to because the method that we use throws an exception and the code we write will not compile otherwise.

Inside the try block, we declare and initialize objects of the AssetManager and AssetFileDescriptor types.

The AssetFileDescriptor is initialized by using the openFd method of the AssetManager object that actually decompresses the sound file. We then initialize our id (idFX1) at the same time as we load the contents of the AssetFileDescriptor into our SoundPool.

The catch block simply outputs a message to the console to let us know whether something has gone wrong. Note that this code is the same, regardless of the Android version:

try{
   
   // Create objects of the 2 required classes
   AssetManager assetManager = this.getAssets();
   AssetFileDescriptor descriptor;

   // Load our fx in memory ready for use
   descriptor = assetManager.openFd("fx1.ogg");
   idFX1 = sp.load(descriptor, 0);
}catch(IOException e){
   
   // Print an error message to the console
   Log.d("error", "failed to load sound files");
}

We are ready to make some noise.

Playing a sound

At this point, there is a sound effect in our SoundPool, and we have an id by which we can refer to it.

This code is the same regardless of how we built the SoundPool object, and this is how we play the sound. Notice in the following line of code that we initialize the nowPlaying variable with the return value from the same method that actually plays the sound.

The following code therefore simultaneously plays a sound and loads the value of the id that is being played into nowPlaying:

nowPlaying = sp.play(idFX1, volume, volume, 0, repeats, 1);

Note

It is not necessary to store the id in nowPlaying in order to play a sound, but it has its uses, as we will now see.

The parameters of the play method are as follows:

  • The id of the sound effect
  • The left and right speaker volumes
  • The priority over other sounds
  • The number of times the sound is repeated
  • The rate/speed it is played at (1 being the normal rate)

There's just one more thing we need to do before we make the sound demo app.

Stopping a sound

It is also very trivial to stop a sound when it is still playing with the stop method. Note that there might be more than one sound effect playing at any given time, so the stop method needs the ID of the sound effect to stop:

sp.stop(nowPlaying);

When you call play, you only need to store the ID of the currently playing sound if you want to track it so that you can interact with it at a later time.

Now, we can make the Sound Demo app.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.147.55.42