The s3eSound API

If you want to add spot sound effects to your game, such as laser bolts and explosions, the s3eSound API is what you need to use. This API allows multiple sound samples to be played simultaneously at different volumes and pitch by mixing them together into a single output.

To make use of the s3eSound API, simply include the file s3eSound.h in your source code.

The API expects all sound effects to be supplied as uncompressed 16-bit signed PCM. File formats such as WAV are not supported by the API, so you must write your own code to load and extract the sample data from such files.

As you read through this section you may start to think that there's an awful lot to do in order to play some sound effects. While this may appear to be the case, s3eSound is actually a very low-level API and provides enough flexibility to allow you to code your own complex sound routines.

Later in this chapter we will be covering the SoundEngine module, which comes with Marmalade to provide a wrapper for the s3eSound API. The SoundEngine module takes care of most of the hard work involved in using the s3eSound API for us and also includes the ability to load WAV files directly from a GROUP file.

Starting sound playback

In order to play a sound sample using s3eSound, the first thing we have to do is allocate a free sound channel. The s3eSound API provides a limited number of channels (we'll see later how to determine exactly how many are available) that allow us to specify a sound sample, volume, and playback rate. The sound data for all currently active channels is then mixed together in the inner workings of s3eSound into a single waveform and this is what is played through the device's sound hardware. To allocate a free channel, we make the following function call:

int32 lChannel = s3eSoundGetFreeChannel();

This will return the ID number of a free channel, or -1 if no channel is available. Most of the time it is unlikely that a free channel will not be available, but if we are playing a lot of sound effects we might want to consider tagging each of our sound effects with a priority value and maintaining a list of currently active sounds. When we run out of channels, we can check the list of sounds and reclaim the channel being used by the lowest priority sound effect, assuming that it is at a lower priority than the sound we wish to start of course!

Assuming a channel is available we must set up the playback rate of our sample data, which is done like this:

s3eSoundChannelSetInt(lChannel, S3E_CHANNEL_RATE, lFrequency);

The first parameter is the sound channel ID we just allocated. The second parameter indicates that we want to set the playback rate for that channel, and the third parameter is the actual desired playback rate in Hertz (Hz). The maximum frequency that can be set is specified by the define S3E_SOUND_MAX_FREQ.

We should also set the volume that we want the sound to be played at, which is also done using the s3eSoundChannelSetInt function:

s3eSoundChannelSetInt(lChannel, S3E_CHANNEL_VOLUME, lVolume);

The valid values for the lVolume parameter are from 0 to the define S3E_SOUND_MAX_VOLUME.

Note

It is possible to change the volume and playback rate at any time once the sound has started playing. This makes it possible to implement effects such as volume fades or pitch shifts.

Now we can start playing our sound sample. We do this with the following call:

s3eSoundChannelPlay(lChannel, lSampleData, lNumSamples, lRepeatCount,
lLoopIndex);

Unsurprisingly, we first pass in the channel ID we are using, followed by the address in memory where the 16-bit PCM sample data can be found in the lSampleData parameter. The lNumSamples parameter is the number of actual sound samples in our waveform (not the number of bytes), and lRepeatCount indicates how often we want the sound to repeat. A value of 0 will play the sound forever. Finally the lLoopIndex parameter allows us to specify which sample to start at if the sound repeats. This makes it possible to use sounds that only need to repeat a portion of the sample data.

Pausing, resuming, and stopping playback

Once a sound channel has started playing a sound sample, we might want to temporarily suspend its playback or stop it entirely. To pause a sound channel we use the function s3eSoundChannelPause, and we can start playing it again from the paused position using s3eSoundChannelResume. To stop a sound channel entirely we call s3eSoundChannelStop. Each of these functions takes a single parameter, which is the channel ID we want to affect.

To determine the current playback status of a particular sound channel we can use the s3eSoundChannelGetInt function as follows:

if (s3eSoundChannelGetInt(lChannel, S3E_CHANNEL_STATUS) == 1)
{
  // Sound channel is currently playing
}

if (s3eSoundChannelGetInt(lChannel, S3E_CHANNEL_PAUSED) == 1)
{
  // Sound channel is currently active, but paused
}

Note that this function can also be used with the S3E_CHANNEL_RATE and S3E_CHANNEL_VOLUME properties to discover the current sample rate and volume for a particular channel.

Finally, it is also possible to affect all currently active sound channels at once using the functions s3eSoundPauseAllChannels, s3eSoundResumeAllChannels, and s3eSoundStopAllChannels. These functions take no inputs and are extremely useful for handling situations like going in and out of pause mode, or when switching from one part of the game to another (for example, when exiting the title screen and entering the main game).

Global sound settings

As well as being able to read and write settings on a per channel basis, we can also make settings that affect sound support globally. To do this we use the s3eSoundSetInt and s3eSoundGetInt functions as follows:

// To read a global sound setting
int32 lValue = s3eSoundGetInt(lProperty);

// To change a global sound setting
s3eSoundSetInt(lProperty, lValue);

Here are some of the more useful values for the lProperty parameter:

Property

Description

S3E_SOUND_VOLUME

Can be used to read or write the current master sound volume. This will scale the volumes of each individual channel appropriately. The maximum value is determined by the define S3E_SOUND_MAX_VOLUME.

S3E_SOUND_DEFAULT_FREQ

This is the default frequency that will be used when starting playback on a sound channel. If all our sound waveforms have the same sample rate, it is possible to write to this property once and not have to set the sample rate explicitly when playing each individual sound.

S3E_SOUND_NUM_CHANNELS

A read-only value indicating the maximum number of simultaneous sounds that can be played.

S3E_SOUND_USED_CHANNELS

A read-only value that shows which sound channels are currently in use. This is returned as a bit mask with the least significant bit relating to sound channel 0. This value could be used to determine an available sound channel, but for future compatibility using s3eSoundGetFreeChannel to do this is recommended.

S3E_SOUND_AVAILABLE

A read-only value that returns 1 if s3eSound is available on the device.

S3E_SOUND_VOLUME_DEFAULT

A read-only value that is used as the default value for the global sound volume. It can vary from device to device and is intended to allow sound output to be at a similar volume across all devices.

There are other values described in the Marmalade documentation, but we won't cover them here as they are used for purposes such as custom sound stream generation, which are beyond the scope of this book.

Sound notifications

We have already seen how to use a polled method of detecting whether or not a sound channel is currently playing, but sometimes it is useful to know exactly when a sound sample has finished playing, for example, so we can immediately start playing back a new sound effect.

The s3eSound API allows us to set several different callback functions on a per channel basis and we use the functions s3eSoundChannelRegister and s3eSoundChannelUnRegister to enable and disable them as follows:

// To set up a sound channel callback
s3eSoundChannelRegister(lChannel, lCallbackType, (s3eCallback)
              CallbackFunction, lpUserData);

// To disable a sound channel callback
s3eSoundChannelUnRegister(lChannel, lCallbackType);

As with all other Marmalade callbacks, we specify the code for the callback function by passing in a pointer to the function itself, and we can also register a block of user data that will be passed into this function when it is triggered. There are four different callback types called S3E_CHANNEL_END_SAMPLE, S3E_CHANNEL_STOP_AUDIO, S3E_CHANNEL_GEN_AUDIO, and S3E_CHANNEL_GEN_AUDIO_STEREO. We will only take a look at the first two of them here, as the latter two are concerned with generating custom audio streams and are beyond the scope of this book. For an example of how to use these callback types, take a look at the source code for the SoundEngine module, which we'll be covering in the next section.

First let's look at the S3E_CHANNEL_END_SAMPLE callback, which allows us to loop sounds and join different sounds together as a sequence. The registered callback function is passed a pointer to an s3eSoundEndSampleInfo structure as its first parameter. The structure indicates which sound channel has ended by using its m_Channel member.

If we want to start a completely new sound playing on this channel, we can set the m_NewData member of the s3eSoundEndSampleInfo structure to the start address of the new sample data, and the m_NumSamples member to the number of samples in the new waveform.

The structure also contains a member called m_RepsRemaining, which allows us to change the number of repetitions of the sample data we want on this sound channel. Note, though, that this callback will still be triggered every time the end of the sample data has been reached.

If we wish the channel to continue playing sample data, be it the original data or a new sample specified using the m_NewData and m_NumSamples members of the s3eSoundEndSampleInfo structure, we must return a non-zero value from the callback function. If zero is returned, the sound channel will stop playing.

The following code example puts the functionality described previosuly into practice:

// Simple structure used to indicate the next sound sample to play
typedef struct
{
  void* mSampleData;
  uint32 mSampleCount;
} NewSoundData;

// Sample callback function that will start a new sound effect
// playing if one has been specified when registering the
// callback function
int32 SoundEndCallback(s3eSoundEndSampleInfo* apInfo,
   NewSoundData* apSound)
{
  if (apSound)
  {
     apInfo->m_NewData = apSound->mSampleData;
     apInfo->m_NumSamples = apSound->mSampleCount;
     apInfo->m_RepsRemaining = 1;
  }
  return apInfo->m_RepsRemaining;
}

// Register the callback function to play a new sound when
// current sound completes
s3eSoundChannelRegister(lChannel, S3E_CHANNEL_END_SAMPLE,
   (s3eCallback) SoundEndCallback,
   &lNewSoundDataInstance);

The second callback type we'll consider is S3E_CHANNEL_STOP_AUDIO. This callback will occur whenever a sound channel finishes playing a sound completely (for example, if we have an S3E_CHANNEL_END_SAMPLE callback set and we return zero from it to end all playback). It is passed a pointer to an s3eSoundEndSampleInfo structure, but the only valid field is the m_Channel member.

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

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