Chapter 10. Audio

Table of Contents

Overview
Initializing the Audio System
Audio Buffers
XFcAudioBuffer::create()
xfuLoadWav()
Generating Audio Data into an Audio Buffer
Audio Streams
XFcAudioStream
Audio Library
Playing Audio Buffers and Streams
Stopping and Pausing Audio Buffers and Streams
Changing Parameters on Audio Buffers and Streams
Explanation of Parameters

Overview

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.

Initializing the Audio System

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

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.

XFcAudioBuffer::create()

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.

Note

The sample count in the audio format is truly sample count, not byte count.

xfuLoadWav()

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);

Note

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.

Generating Audio Data into an 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);

Note

One should always remember to unlock the audio buffer after locking, otherwise the audio system can not play it.

Audio Streams

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.

XFcAudioStream

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.

Audio Library

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.

Playing Audio Buffers and Streams

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.

Stopping and Pausing Audio Buffers and Streams

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.

Note

A paused sound cannot be resumed if a higher priority sound has occupied its audio channel after the sound was paused.

Changing Parameters on Audio Buffers and Streams

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.

Explanation of Parameters

Various parameters can be used when playing sounds so their ranges and meanings are explained here.

Sample Rate

Sampling rate of an audio buffer or stream.

  • range: positive floating point values.

Volume

Multiplier of the original volume.

  • range: positive floating point values.
  • values: 0.0 is silent, 1.0 is original volume, values above 1.0 amplify the volume.

Panning

Panning of an audio buffer or stream, only applicable to mono audio.

  • range: 0.0 - 1.0
  • values: 0.0 is left, 0.5 center, 1.0 right

Priority

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.

  • range: 0 - 65535
  • values: 0 is highest priority, 65535 lowest priority