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.
