The audio system under X-Forge core is a facility which supports regular audio buffers as well as programmable audio streams. The system uses channels to limit the number of sounds being played. Using channels, the programmer can easily handle the performance issues of audio output.
An audio system such as this requires internal structures which need to be controlled and guarded by the audio system itself. This is to protect the audio system from breaking itself due to possible errors in the user's code. Therefore, all operations which handle the structures of audio buffers or streams which are required by the audio system can only be modified through the audio library.
The audio system has to be initialized before it can be used. XFcAudio a static setAudioFormat()-method for this use. The function returns an XFcAudioFormat object which holds information of the format the hardware was capable to initialize. The method tries to initialize a format in the hardware as close as possible to the given format. If NULL is given, X-Forge will automatically try the preferred audio format. The application can ask for the preferred format with the static getPreferredAudioFormat() method.
XFcAudioFormat getPreferredAudioFormat(); XFcAudioFormat setAudioFormat(XFcAudioFormat *aFormat, INT32 aChannelCount, UINT32 aInterpolation);
The aInterpolation parameter takes one of these values:
XFCAUDIO_INTERPOLATION_NONE // No interpolation is used XFCAUDIO_INTERPOLATION_LINEAR // Linear interpolation
The XFcAudioFormat structure contains sample rate, audio block size and the type of data in the format. The data type is handled by the follwing flags (which can be found from XFcAudioFlags.h):
XFCAUDIO_16BIT // Audio is 16bit audio XFCAUDIO_STEREO // Audio is stereo audio XFCAUDIO_SIGNED // Audio is signed data
The flags can be combined by OR-ing the different flags together:
XFCAUDIO_16BIT | XFCAUDIO_STEREO // Audio is 16bit stereo audio
An example:
// Audio system is initialized to 44100kHz/16bit/stereo/signed/4096 // samples/8 channels/linear interpolation XFcAudioFormat audioformat = XFcAudioFormat(44100, XFCAUDIO_16BIT |XFCAUDIO_STEREO | XFCAUDIO_SIGNED, 4096); XFcAudio::setAudioFormat(&audioformat, 8, XFCAUDIO_INTERPOLATION_LINEAR);
If the audio initialization fails for the given values, the system will default to the preferred ones.
Audio buffers are created as objects of the XFcAudioBuffer class. One can procedurally generate a sound into an audio buffer but commonly audio buffers are created from audio files. An application has two basic ways to create XFcAudioBuffer objects, either by using one of the create()-methods or by using the xfuLoadWav()-function.
There are multiple ways to create an XFcAudioBuffer class, the simplest one having only the audio format as the single parameter.
To simply create an audio buffer with a particular audio format one can use one of two methods. First one of the methods uses an XFcAudioFormat object as the format and the other one needs a sampling rate, different flags and sample count as parameters:
XFcAudioBuffer * create(XFcAudioFormat aFormat); XFcAudioBuffer * create(FLOAT32 aSampleRate, UINT32 aFlags, INT32 aSamples);
The flags which can be used for aFlags can be found from XFcAudioFlags.h.
All create()-mehotds will return NULL if they fail.
The sample count in the audio format is truly sample count, not byte count.
Typically audio buffers are created directly from audio files and for that one can use the xfuLoadWav()-function.
An audio buffer can be created from an audio file with the following function:
XFcAudioBuffer * xfuLoadWav(const CHAR *filename);
As of this writing, xfuLoadWav() only handles 8-bit mono files. Using a different format in the flags-parameter does not convert the audio buffer to that format but only sets the internal flags to a false format effectively breaking the audio buffer.
To be able to write audio data into an audio buffer, the audio buffer has to be locked for writing via the audio library, XFcAudio. The static lock()-method in XFcAudio returns a pointer to actual audio data inside the buffer.
A simple example of writing audio data into an audio buffer:
// Create an audio buffer, 44100kHz/8bit/mono with 1000 samples XFcAudioBuffer *buf = XFcAudioBuffer::create(44100.0f, 0, 1000); // Lock the audio buffer for writing UINT8 *ptr = XFcAudio::lock(buf); for (INT i = 0; i < 1000; ++i) { // Generate white noise ptr[i] = (rand() * 255 / 32768); } // Unlock the audio buffer XFcAudio::unlock(buf);
One should always remember to unlock the audio buffer after locking, otherwise the audio system can not play it.
In X-Forge, audio streams are created by inheriting the XFcAudioStream class. XFcAudioStream objects can not be instantiated, only inherited. An example of an inherited stream class is the XM-player class, XFuXMPlayer.
The XFcAudioStream class introduces methods which the inheriting class should override. Only the streaming function is required to be overriden, the rest can be left alone.
The inheriting classes are also required to call the initialization method, initialize(), of the base class to set their internal structures properly for the audio system to use.
An example of a simple streaming audio class:
class MyStream : public XFcAudioStream { public: virtual UINT32 stream(void *aBuf, INT32 aSamples); MyStream(FLOAT32 aSamplingRate, UINT32 aFlags); virtual ~MyStream(); }; UINT32 MyStream::stream(void *aBuf, INT32 aSamples) { // fill aSamples of noise as sampledata into aBuf for (INT32 i = 0; i < aSamples; ++i) { aBuf[i] = (rand() * 255 / 32768); } } MyStream::MyStream(FLOAT32 aSamplingRate, UINT32 aFlags) { initialize(aSamplingRate, aFlags, REALf(1.0), REALf(0.5), 0); // e.g. create wavetables } MyStream::~MyStream() { // clean up }
Other methods the inheriting class can override are play(), stop(), pause() and resume(). The inheriting class does not have to call the parent class methods in its overriden methods.
The audio library handles all aspects of audio output and parameters. To play an audio buffer or to change the sampling rate of an audio stream, one has to use the interface provided by the audio library.
The audio library introduces different ways to play an audio buffer or stream. One can simply ask audio library to play the sound as it is or for example ask it to play the sound with a sampling rate other than the original.
A simple example of playing an audio buffer with a specific sampling rate, volume and panning:
// Create an audio buffer from a file XFcAudioBuffer *sound = xfuLoadWav("mysound.wav"); // Play the audio buffer with a sampling rate of 22050kHz, // 70% volume of the original volume, centered panning UINT32 id = XFcAudio::play(sound, 22050.0f, REALf(0.7), REALf(0.5));
All play()-methods return a unique ID. This ID can be used to control the sound after it has been set to play. One doesn't however need to save the ID for later use if there is no need to control the behaviour of the sound after it has been initially set to play.
An audio buffer or stream which has been set to play using the play()-method of the XFcAudio-interface have to be stopped and paused using the same interface. A paused buffer or stream can also be resumed after it has been paused.
To stop a specific buffer or stream which is playing, one has to use the unique ID returned by the audio library to identify which sound it should stop:
// Create an audio buffer from a file XFcAudioBuffer *sound = xfuLoadWav("mysound.wav"); // Start playing the first sound UINT32 firstId = XFcAudio::play(sound); // Start a second instance of the same audio buffer which will play concurrently with the first UINT32 secondId = XFcAudio::play(sound); // Stop the first instance XFcAudio::stop(firstId);
One can also stop all instances of a certain audio buffer or stream with a single method call by using the audio buffer or stream object as the argument in the stop()-call:
// Create an audio buffer from a file XFcAudioBuffer *sound = xfuLoadWav("mysound.wav"); // Start playing the first sound UINT32 firstId = XFcAudio::play(sound); // Start a second instance of the same audio buffer which will play concurrently with the first UINT32 secondId = XFcAudio::play(sound); // Stop all instances of "sound"-object XFcAudio::stop(sound);
Pausing and resuming audio buffers and stream can be used with the pause() and resume() -methods respectively:
// Create an audio buffer from a file XFcAudioBuffer *sound = xfuLoadWav("mysound.wav"); // Start playing a sound UINT32 id = XFcAudio::play(sound); // Pause the sound XFcAudio::pause(id); // Resume the sound XFcAudio::resume(id);
Pausing and resuming can also be used for all instances by using the audio buffer or stream object as the argument in the method-calls.
A paused sound cannot be resumed if a higher priority sound has occupied its audio channel after the sound was paused.
To change parameters on audio buffers or streams, one has to use the interface provided by the audio library. For example to change the sampling rate of a particular audio buffer, one must use:
// Create an audio buffer from a file XFcAudioBuffer *sound = xfuLoadWav("mysound.wav"); // Change sampling rate on audio buffer XFcAudio::setSampleRate(sound, 44100.0f);
To change parameters of a sound which has already been set to play, one has to use the unique ID returned by the audio library when the sample was set to play:
// Create an audio buffer from a file XFcAudioBuffer *sound = xfuLoadWav("mysound.wav"); // Play the audio buffer UINT32 id = XFcAudio::play(sound); // Change sampling rate on the sound instance which was just set to play XFcAudio::setSampleRate(id, 44100.0f);
The effect of the previous example can of course be achieved with the first example as well, and is the preferred way because of the latency issues in audio output.
Various parameters can be used when playing sounds so their ranges and meanings are explained here.
Multiplier of the original volume.
Panning of an audio buffer or stream, only applicable to mono audio.
Priority of a sound is used when the audio library is trying to determine which channel to use for the sound. A higher priority sound can override a lower priority sound if a free channel can not be found. The audio library first searches for a free channel, then a channel with a lower priority sound, then a channel with the same priority and overrides its sound if such was found.