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.
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
.
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.
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).
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:
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.
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.
18.223.213.238