// ----------------------------------------------------------------
// From Game Programming in C++ by Sanjay Madhav
// Copyright (C) 2017 Sanjay Madhav. All rights reserved.
//
// Released under the BSD License
// See LICENSE in root directory for full details.
// ----------------------------------------------------------------
#include "AudioSystem.h"
#include
#include
#include
#include
unsigned int AudioSystem::sNextID = 0;
AudioSystem::AudioSystem(Game* game)
:mGame(game)
,mSystem(nullptr)
,mLowLevelSystem(nullptr)
{
}
AudioSystem::~AudioSystem()
{
}
bool AudioSystem::Initialize()
{
// Initialize debug logging
FMOD::Debug_Initialize(
FMOD_DEBUG_LEVEL_ERROR, // Log only errors
FMOD_DEBUG_MODE_TTY // Output to stdout
);
// Create FMOD studio system object
FMOD_RESULT result;
result = FMOD::Studio::System::create(&mSystem);
if (result != FMOD_OK)
{
SDL_Log("Failed to create FMOD system: %s", FMOD_ErrorString(result));
return false;
}
// Initialize FMOD studio system
result = mSystem->initialize(
512, // Max number of concurrent sounds
FMOD_STUDIO_INIT_NORMAL, // Use default settings
FMOD_INIT_NORMAL, // Use default settings
nullptr // Usually null
);
if (result != FMOD_OK)
{
SDL_Log("Failed to initialize FMOD system: %s", FMOD_ErrorString(result));
return false;
}
// Save the low-level system pointer
mSystem->getLowLevelSystem(&mLowLevelSystem);
// Load the master banks (strings first)
LoadBank("Assets/Master Bank.strings.bank");
LoadBank("Assets/Master Bank.bank");
return true;
}
void AudioSystem::Shutdown()
{
// Unload all banks
UnloadAllBanks();
// Shutdown FMOD system
if (mSystem)
{
mSystem->release();
}
}
void AudioSystem::LoadBank(const std::string& name)
{
// Prevent double-loading
if (mBanks.find(name) != mBanks.end())
{
return;
}
// Try to load bank
FMOD::Studio::Bank* bank = nullptr;
FMOD_RESULT result = mSystem->loadBankFile(
name.c_str(), // File name of bank
FMOD_STUDIO_LOAD_BANK_NORMAL, // Normal loading
&bank // Save pointer to bank
);
const int maxPathLength = 512;
if (result == FMOD_OK)
{
// Add bank to map
mBanks.emplace(name, bank);
// Load all non-streaming sample data
bank->loadSampleData();
// Get the number of events in this bank
int numEvents = 0;
bank->getEventCount(&numEvents);
if (numEvents > 0)
{
// Get list of event descriptions in this bank
std::vector<:studio::eventdescription> events(numEvents);
bank->getEventList(events.data(), numEvents, &numEvents);
char eventName[maxPathLength];
for (int i = 0; i < numEvents; i++)
{
FMOD::Studio::EventDescription* e = events[i];
// Get the path of this event (like event:/Explosion2D)
e->getPath(eventName, maxPathLength, nullptr);
// Add to event map
mEvents.emplace(eventName, e);
}
}
// Get the number of buses in this bank
int numBuses = 0;
bank->getBusCount(&numBuses);
if (numBuses > 0)
{
// Get list of buses in this bank
std::vector<:studio::bus> buses(numBuses);
bank->getBusList(buses.data(), numBuses, &numBuses);
char busName[512];
for (int i = 0; i < numBuses; i++)
{
FMOD::Studio::Bus* bus = buses[i];
// Get the path of this bus (like bus:/SFX)
bus->getPath(busName, 512, nullptr);
// Add to buses map
mBuses.emplace(busName, bus);
}
}
}
}
void AudioSystem::UnloadBank(const std::string& name)
{
// Ignore if not loaded
auto iter = mBanks.find(name);
if (iter == mBanks.end())
{
return;
}
// First we need to remove all events from this bank
FMOD::Studio::Bank* bank = iter->second;
int numEvents = 0;
bank->getEventCount(&numEvents);
if (numEvents > 0)
{
// Get event descriptions for this bank
std::vector<:studio::eventdescription> events(numEvents);
// Get list of events
bank->getEventList(events.data(), numEvents, &numEvents);
char eventName[512];
for (int i = 0; i < numEvents; i++)
{
FMOD::Studio::EventDescription* e = events[i];
// Get the path of this event
e->getPath(eventName, 512, nullptr);
// Remove this event
auto eventi = mEvents.find(eventName);
if (eventi != mEvents.end())
{
mEvents.erase(eventi);
}
}
}
// Get the number of buses in this bank
int numBuses = 0;
bank->getBusCount(&numBuses);
if (numBuses > 0)
{
// Get list of buses in this bank
std::vector<:studio::bus> buses(numBuses);
bank->getBusList(buses.data(), numBuses, &numBuses);
char busName[512];
for (int i = 0; i < numBuses; i++)
{
FMOD::Studio::Bus* bus = buses[i];
// Get the path of this bus (like bus:/SFX)
bus->getPath(busName, 512, nullptr);
// Remove this bus
auto busi = mBuses.find(busName);
if (busi != mBuses.end())
{
mBuses.erase(busi);
}
}
}
// Unload sample data and bank
bank->unloadSampleData();
bank->unload();
// Remove from banks map
mBanks.erase(iter);
}
void AudioSystem::UnloadAllBanks()
{
for (auto& iter : mBanks)
{
// Unload the sample data, then the bank itself
iter.second->unloadSampleData();
iter.second->unload();
}
mBanks.clear();
// No banks means no events
mEvents.clear();
}
SoundEvent AudioSystem::PlayEvent(const std::string& name)
{
unsigned int retID = 0;
auto iter = mEvents.find(name);
if (iter != mEvents.end())
{
// Create instance of event
FMOD::Studio::EventInstance* event = nullptr;
iter->second->createInstance(&event);
if (event)
{
// Start the event instance
event->start();
// Get the next id, and add to map
sNextID++;
retID = sNextID;
mEventInstances.emplace(retID, event);
}
}
return SoundEvent(this, retID);
}
void AudioSystem::Update(float deltaTime)
{
// Find any stopped event instances
std::vector done;
for (auto& iter : mEventInstances)
{
FMOD::Studio::EventInstance* e = iter.second;
// Get the state of this event
FMOD_STUDIO_PLAYBACK_STATE state;
e->getPlaybackState(&state);
if (state == FMOD_STUDIO_PLAYBACK_STOPPED)
{
// Release the event and add id to done
e->release();
done.emplace_back(iter.first);
}
}
// Remove done event instances from map
for (auto id : done)
{
mEventInstances.erase(id);
}
// Update FMOD
mSystem->update();
}
namespace
{
FMOD_VECTOR VecToFMOD(const Vector3& in)
{
// Convert from our coordinates (+x forward, +y right, +z up)
// to FMOD (+z forward, +x right, +y up)
FMOD_VECTOR v;
v.x = in.y;
v.y = in.z;
v.z = in.x;
return v;
}
}
void AudioSystem::SetListener(const Matrix4& viewMatrix)
{
// Invert the view matrix to get the correct vectors
Matrix4 invView = viewMatrix;
invView.Invert();
FMOD_3D_ATTRIBUTES listener;
// Set position, forward, up
listener.position = VecToFMOD(invView.GetTranslation());
// In the inverted view, third row is forward
listener.forward = VecToFMOD(invView.GetZAxis());
// In the inverted view, second row is up
listener.up = VecToFMOD(invView.GetYAxis());
// Set velocity to zero (fix if using Doppler effect)
listener.velocity = {0.0f, 0.0f, 0.0f};
// Send to FMOD
mSystem->setListenerAttributes(0, &listener);
}
float AudioSystem::GetBusVolume(const std::string& name) const
{
float retVal = 0.0f;
const auto iter = mBuses.find(name);
if (iter != mBuses.end())
{
iter->second->getVolume(&retVal);
}
return retVal;
}
bool AudioSystem::GetBusPaused(const std::string & name) const
{
bool retVal = false;
const auto iter = mBuses.find(name);
if (iter != mBuses.end())
{
iter->second->getPaused(&retVal);
}
return retVal;
}
void AudioSystem::SetBusVolume(const std::string& name, float volume)
{
auto iter = mBuses.find(name);
if (iter != mBuses.end())
{
iter->second->setVolume(volume);
}
}
void AudioSystem::SetBusPaused(const std::string & name, bool pause)
{
auto iter = mBuses.find(name);
if (iter != mBuses.end())
{
iter->second->setPaused(pause);
}
}
FMOD::Studio::EventInstance* AudioSystem::GetEventInstance(unsigned int id)
{
FMOD::Studio::EventInstance* event = nullptr;
auto iter = mEventInstances.find(id);
if (iter != mEventInstances.end())
{
event = iter->second;
}
return event;
}