Audio System for 2D and 3D spatial audio

The Prodigy Engine audio system implemented uses FMOD and is a fairly simple implementation. The audio system takes sounds, handles playback options, sets listeners in scene and supports audio channel groups.

Below is an implementation of 3D audio in-game:

3D Audio implementation in Prodigy Engine

Below is the implementation used for the AudioSytem in Prodigy Engine:

class AudioSystem
{
public:
	AudioSystem();
	virtual ~AudioSystem();

public:
	virtual void				BeginFrame();
	virtual void				EndFrame();

	virtual SoundID				CreateOrGetSound( const std::string& soundFilePath );
	virtual SoundID				CreateOrGetSound3D(const std::string& soundFilePath);
	virtual SoundPlaybackID		PlaySound( SoundID soundID, bool isLooped=false, float volume=1.f, float balance=0.0f, float speed=1.0f, bool isPaused=false );
	virtual ChannelGroupID		CreateOrGetChannelGroup(const std::string& channelName);
	virtual SoundPlaybackID		Play3DSound( SoundID soundID, const Vec3& position, ChannelGroupID channelID, bool isLooped=false, float volume=1.f, float balance=0.0f, float speed=1.0f, bool isPaused=false );

	virtual void				StopSound( SoundPlaybackID soundPlaybackID );
	virtual void				SetSoundPlaybackVolume( SoundPlaybackID soundPlaybackID, float volume );	// volume is in [0,1]
	virtual void				SetSoundPlaybackBalance( SoundPlaybackID soundPlaybackID, float balance );	// balance is in [-1,1], where 0 is L/R centered
	virtual void				SetSoundPlaybackSpeed( SoundPlaybackID soundPlaybackID, float speed );		// speed is frequency multiplier (1.0 == normal)

	virtual void				SetAudioListener(const Vec3& position, const Vec3& forward, const Vec3& up);

	//FMOD Result validation
	virtual void				ValidateResult( FMOD_RESULT result );

protected:
	FMOD::System*						m_fmodSystem;
	//2D audio
	std::map< std::string, SoundID >	m_registeredSoundIDs;
	std::vector< FMOD::Sound* >			m_registeredSounds;
	//3D audio
	std::map< std::string, SoundID >	m_registered3DSoundIDs;
	std::vector< FMOD::Sound* >			m_registered3DSounds;
	//Channel Groups (Mixers)
	std::map< std::string, ChannelGroupID >		m_registeredChannelIDs;
	std::vector< FMOD::ChannelGroup* >			m_registeredChannels;

};

With this interface, I was able to call into the required functions from Game and allow for audio playback.

Below are some code implementations for the various functions highlighted above:

VIRTUAL SoundPlaybackID AudioSystem::Play3DSound(SoundID soundID, const Vec3& position, ChannelGroupID channelID, bool isLooped/*=false*/, float volume/*=1.f*/, float balance/*=0.0f*/, float speed/*=1.0f*/, bool isPaused/*=false */)
{
	size_t numSounds = m_registered3DSounds.size();
	if (soundID < 0 || soundID >= numSounds)
		return MISSING_SOUND_ID;

	FMOD::Sound* sound = m_registered3DSounds[soundID];
	if (!sound)
		return MISSING_SOUND_ID;

	FMOD::Channel* channelAssignedToSound = nullptr;

	FMOD_VECTOR fpos;
	fpos.x = position.x;
	fpos.y = position.y;
	fpos.z = position.z;

	FMOD::ChannelGroup* channelGroup = m_registeredChannels[channelID];

	m_fmodSystem->playSound(sound, channelGroup, isPaused, &channelAssignedToSound);
	channelAssignedToSound->set3DAttributes(&fpos, nullptr);
	if (channelAssignedToSound)
	{
		int loopCount = isLooped ? -1 : 0;
		unsigned int playbackMode = isLooped ? FMOD_LOOP_NORMAL : FMOD_LOOP_OFF;
		float frequency;
		channelAssignedToSound->setMode(playbackMode);
		channelAssignedToSound->getFrequency(&frequency);
		channelAssignedToSound->setFrequency(frequency * speed);
		channelAssignedToSound->setVolume(volume);
		channelAssignedToSound->setPan(balance);
		channelAssignedToSound->setLoopCount(loopCount);
	}

	return (SoundPlaybackID)channelAssignedToSound;
}
VIRTUAL SoundPlaybackID AudioSystem::PlaySound( SoundID soundID, bool isLooped, float volume, float balance, float speed, bool isPaused )
{
	size_t numSounds = m_registeredSounds.size();
	if( soundID < 0 || soundID >= numSounds )
		return MISSING_SOUND_ID;

	FMOD::Sound* sound = m_registeredSounds[ soundID ];
	if( !sound )
		return MISSING_SOUND_ID;

	FMOD::Channel* channelAssignedToSound = nullptr;
	m_fmodSystem->playSound( sound, nullptr, isPaused, &channelAssignedToSound );
	if( channelAssignedToSound )
	{
		int loopCount = isLooped ? -1 : 0;
		unsigned int playbackMode = isLooped ? FMOD_LOOP_NORMAL : FMOD_LOOP_OFF;
		float frequency;
		channelAssignedToSound->setMode(playbackMode);
		channelAssignedToSound->getFrequency( &frequency );
		channelAssignedToSound->setFrequency( frequency * speed );
		channelAssignedToSound->setVolume( volume );
		channelAssignedToSound->setPan( balance );
		channelAssignedToSound->setLoopCount( loopCount );
	}

	return (SoundPlaybackID) channelAssignedToSound;	
}
VIRTUAL ChannelGroupID AudioSystem::CreateOrGetChannelGroup(const std::string& channelName)
{
	std::map<std::string, ChannelGroupID>::iterator itr;
	itr = m_registeredChannelIDs.find(channelName);

	if (itr != m_registeredChannelIDs.end())
	{
		return itr->second;
	}
	else
	{
		//Channel needs to be created
		FMOD::ChannelGroup* newChannelGroup = nullptr;
		m_fmodSystem->createChannelGroup(channelName.c_str(), &newChannelGroup);
		if (newChannelGroup)
		{
			FMOD::ChannelGroup* master = nullptr;
			m_fmodSystem->getMasterChannelGroup(&master);
			master->addGroup(newChannelGroup);

			ChannelGroupID channelID = m_registeredChannelIDs.size();
			m_registeredChannelIDs[channelName] = channelID;
			m_registeredChannels.push_back(newChannelGroup);
			return channelID;
		}
		else
		{
			ASSERT("Channel group not created in FMOD");
			return (ChannelGroupID)-1;
		}
	}
}
void AudioSystem::SetAudioListener( const Vec3& position, const Vec3& forward, const Vec3& up)
{
	FMOD_VECTOR fpos;
	fpos.x = position.x;
	fpos.y = position.y;
	fpos.z = position.z;

	FMOD_VECTOR fforward;
	fforward.x = forward.x;
	fforward.y = forward.y;
	fforward.z = forward.z;

	FMOD_VECTOR fup;
	fup.x = up.x;
	fup.y = up.y;
	fup.z = up.z;

	FMOD_VECTOR zero;
	zero.x = 0;
	zero.y = 0;
	zero.z = 0;

	//We are assuming we only have 1 listener for our system
	m_fmodSystem->set3DListenerAttributes(0, &fpos, &zero, &fforward, &fup);
}

This simple audio system provided all the functionality needed to play audio in specific locations/times in-game.