Custom Audio Engine
Status: In development
Overview
This is a custom implementation of a sound engine made in my custom game engine. This implementation is inspired by the way FMOD and XAudio2 work. It was also inspired by a Guy Somberg’s conference video on the CppCon.
The main classes of this implementation are:
- Audio
- It’s the place where my audio data is contained. Here I have my buffer of samples.
- Channel
- This is the abstraction of my sound voices. Channels can be routed to the main output or to submixes.
- Submix
- Used to group channels,for volume control, and digital signal processing.
- Master
- Is the main output, the endpoint that communicates with the sound card.
- Audio API
- The place where XAudio2 objects are created. It’s like a factory class.
- Sound Engine
- The place where actions are done such as play or pause.
- Audio Component
- The interface between the sound engine and scene objects from the game engine.
AudioAPI
My AudioAPI is like a factory that creates Audio, Channel, Submix and Master objects.
SPtr<Audio> CreateAudio(const String& name,
const String& filepath,
const SPtr<VoiceCallback>& pCallback = nullptr);
SPtr<Channel> CreateChannel(const SPtr<Audio>& pAudio = nullptr,
uint32 inputChannels = 2,
uint32 inputSampleRate = 48000);
SPtr<Submix> CreateSubmix(uint32 inputChannels = 2,
uint32 inputSampleRate = 48000);
SPtr<Master> CreateMaster(uint32 inChannels = XAUDIO2_DEFAULT_CHANNELS,
uint32 inSampleRate = XAUDIO2_DEFAULT_SAMPLERATE,
uint32 flags = 0);
Those are XAudio2 objects underneath, it’s basically a wrapper class.
#pragma once
#include "HelperMacros.h"
#include <xaudio2.h>
#include <xaudio2fx.h>
class Submix;
class VoiceCallback;
class Audio;
class Channel
{
public:
Channel(unsigned int inputChannels,
unsigned int inputSampleRate);
~Channel();
inline unsigned int getInputChannels() const {
return m_inputChannels;
}
inline unsigned int getInputSampleRate() const {
return m_inputSampleRate;
}
void SubmitAudio(const WPtr<Audio>& pAudio);
void Route(const WPtr<Submix>& channel,
unsigned int flags = 0);
bool isPlaying();
private:
friend class AudioAPI;
friend class SoundEngine;
IXAudio2SourceVoice* m_pSourceVoice = nullptr;
unsigned int m_inputChannels;
unsigned int m_inputSampleRate;
XAUDIO2_VOICE_STATE m_voiceState;
//Sends
XAUDIO2_VOICE_SENDS m_sends{ 0 };
Vector<XAUDIO2_SEND_DESCRIPTOR> m_sendList;
//FX
XAUDIO2_EFFECT_CHAIN m_fxs{ 0 };
Vector<XAUDIO2_EFFECT_DESCRIPTOR> m_fxsList;
};
Sound Engine
Those objects are then managed by the Sound Engine where two maps live, one for Channels and the other one for Audios.
#pragma once
#include "Module.h"
#include "AudioDevice.h"
#include "AudioAPI.h"
using ChannelMap = Map<String, SPtr<Channel>>;
using AudioMap = Map<String, SPtr<Audio>>;
using SubmixMap = Map<String, SPtr<Submix>>;
class SoundEngine : public Module<SoundEngine>
{
public:
SoundEngine() = default;
virtual ~SoundEngine() = default;
virtual void OnStartUp() override;
virtual void OnShutdown() override;
void Play(const WPtr<Channel>& channel, float volume = 0.0f);
void Update();
const ChannelMap& GetChannels() const
{
return m_mapChannels;
}
const AudioMap& GetAudios() const
{
return m_mapAudios;
}
bool CreateAudio(const String& name,
const String& filePath);
bool CreateChannel(const String& name,
const SPtr<Audio>& pAudio,
int32 numChannels,
int32 inSampleRate);
protected:
SPtr<Master> m_pMasterOutput;
ChannelMap m_mapChannels;
AudioMap m_mapAudios;
SubmixMap m_mapSubmixes;
};
SoundEngine& g_soundEngine();
In the Sound Engine we define methods to play, but they’re not used here.
void SoundEngine::Play(const WPtr<Channel>& channel, float volume)
{
if (channel.expired())
{
return;
}
auto CHANNEL = channel.lock();
CHANNEL->m_pSourceVoice->SetVolume(volume);
CHANNEL->m_pSourceVoice->Start(0);
}
Audio Component
Here we can call the methods defined in the Sound Engine to play audios.
bool AudioComponent::Play()
{
if (m_channel.expired() && m_audio.expired())
{
return false;
}
g_soundEngine().Play(m_channel,1.0f);
return true;
}
You can find the code in my repo here!
Enjoy Reading This Article?
Here are some more articles you might like to read next: