C++

Multi-Threaded Audio System

Simple Interface

The main goal for this audio system was to create a simple and easy to use interface for the user. The audio system uses the XAudio2 interface, which is not a simple API to use. The API supports surround sound and other 3D effects, but this system was to support only stereo. By not disclosing these features, and wrapping many of the functions into simpler calls, the user only had to worry about calls like Play(), Stop(), and SetPan(). The interface includes a return status for its handle, which the user can either check or ignore depending on their use case. A list of simple and easy to call functions available to the user can be seen below.

class Snd
{
	//...
public:
	Handle::Status Start();
	Handle::Status Stop();
	Handle::Status Pause();
	Handle::Status Resume();

	Handle::Status SetPriority(unsigned int);
	Handle::Status SetVol(float);
	Handle::Status SetPan(float);
	Handle::Status SetPitch(float);
	
	Handle::Status GetVol(float&);
	Handle::Status GetPan(float&);
	Handle::Status GetPitch(float&);
	Handle::Status GetPriority(unsigned int&);
	Handle::Status GetState(State&);
	
	Handle::Status IsReady(bool&);
	Handle::Status IsRunning(bool&);
	
	Handle::Status GetPlayTimeS(float&);
	Handle::Status GetPlayTimeMS(int&);
	//...
}

Thread Protection

For any object that is being accessed or modified in multiple threads, a handle system was created and used for protection in conjunction with mutex locks. This protected the system when an object has been deleted but a command to modify the now-deleted object is still in flight. If the handle was no longer valid (no longer in the handle table), the command would simply be ignored and not execute.

In the user thread(s), the user never had direct access to the internal audio system's threads. Any command being sent (such as LoadWAV() or Play()) was sent to the audio system via a unique handleID.

Multiple Internal Threads

The audio system itself runs on multiple threads for distributed load and reduced bottlenecking. The main threads are the master AudioThread for coordinating and divvying out commands to the correct helper thread, the FileThread for loading in audio files, the AuxThread for miscellanous work (such as recording when a voice starts/stops playing), and the StreamingThread for streaming a sound file (compared to loading it all in at once before playback). Smaller helper threads are also used (for actions such as panning for X seconds) to ensure the system has no delay on receiving and executing commands in its queue.

Callback support

Within the system, the user can provide a user callback to hook into the audio system. The user can provide their own FileCallBack and be notified OnLoad() and OnError(). The user can also create their own AudioCallBack and be notified OnStart(), OnStop(), OnPause(), and OnEnd().

Synchronous/Asychronous

The file loading API supports the ability to load audio files either asynchronously or synchronously. This allows the user to decide if their calling thread should block until the file is loaded or to continue. Using the synchronous option, the user can include an optional user-defined callback to be notified when the file has been loaded.

Priority Sounds

Within the audio system is a priority table to limit the amount of sounds playing at any one time. Each sound object can be given a priority value. The priority system allows for early-termination of lower priority sounds in order to play a newer more important sound. It also supports ignoring lesser important sounds if the table is already full of more important active sounds.

The system keeps track of stats, such as how long a sound has been playing for. This stat tracking allows for termination of the oldest playing sound if there are multiple sounds with the same priority level.