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