Skip to content

Instantly share code, notes, and snippets.

@DearthDev
Created August 29, 2023 10:10
Show Gist options
  • Save DearthDev/cda7c0fe37914cd9604fda792d501746 to your computer and use it in GitHub Desktop.
Save DearthDev/cda7c0fe37914cd9604fda792d501746 to your computer and use it in GitHub Desktop.
// Audio library made in a single header library style for my game engine.
#pragma once
struct Sound {
u32 frameCount;
i16 *samples;
};
enum class AudioBus {
Music,
Sfx,
Voice,
};
class AudioPlayer {
public:
AudioPlayer(Sound *sound, AudioBus audioBus);
~AudioPlayer();
AudioBus GetAudioBus() const { return m_audioBus; }
void Play();
void PlayOneshot();
void Pause();
bool IsPlaying() const {return m_playing;}
bool IsOneshot() const {return m_oneshot;}
void SetOneshot(bool oneshot) {m_oneshot = oneshot;}
f32 GetAmplitude() const {return m_amplitude;}
void SetAmplitude(f32 amplitude) {m_amplitude = amplitude;}
f32 GetPitch() const {return m_pitch;}
void SetPitch(f32 pitch) {m_pitch = pitch;}
bool IsPositional() const {return m_positional;}
void SetPositional(bool positional) {m_positional = positional;}
v3 GetPosition() const {return m_position;}
void SetPosition(const v3 &position) {m_position = position;}
f32 GetRadius() const {return m_radius;}
void SetRadius(f32 radius) {m_radius = radius;}
// Getthe next audio player in the linked list.
AudioPlayer *GetNext() const {return m_next;}
// Set the next audio player in the linked list.
void SetNext(AudioPlayer *next) {m_next = next;}
// Get the previous audio player in the linked list.
AudioPlayer *GetPrev() const {return m_prev;}
// Set the previous audio player in the linked list.
void SetPrev(AudioPlayer *prev) {m_prev = prev;}
// Write audio samples to the output buffer.
void Write(const f32 modulate, const v3 &listenerPosition, const f32 listenerYaw, const u32 frameCount, f32 *output);
AudioPlayer(AudioPlayer const &) = delete;
void operator=(AudioPlayer const &) = delete;
private:
// Helper function to check and reset if the audio source is oneshot
// and finished.
bool CheckOneshotDone(u32 frame);
// Helper function to calculate playback amplitude.
f32 CalcPlayAmp(const v3 &listenerPosition);
// Helper function to calculate the pan of the audio when played.
f32 CalcPlayPan(const v3 &listenerPosition, f32 listenerYaw);
// Helper function to write unpitched audio samples to the output buffer.
void WriteUnpitched(f32 amp, f32 pan, f32 *output, u32 frameCount);
// Helper function to write pitched audio samples to the output buffer.
void WritePitched(f32 amp, f32 pan, f32 *output, u32 frameCount);
Sound *m_sound;
AudioBus m_audioBus;
bool m_playing = false;
bool m_oneshot = false;
f32 m_amplitude = 1.0f;
f32 m_pitch = 1.0f;
bool m_positional = false;
v3 m_position = {};
f32 m_radius = 10.0f;
AudioPlayer *m_next = 0;
AudioPlayer *m_prev = 0;
u32 m_currentFrame = 0;
};
class AudioManager {
public:
// Adds an audio player to the manager's linked list.
void AddAudioPlayer(AudioPlayer *audioPlayer);
// Removes the audio player to the manager's linked list.
void RemoveAudioPlayer(AudioPlayer *audioPlayer);
f32 GetMasterVolume() const {return m_masterVolume;}
void SetMasterVolume(f32 volume) {m_masterVolume = volume;}
f32 GetMusicVolume() const {return m_musicVolume;}
void SetMusicVolume(f32 volume) {m_musicVolume = volume;}
f32 GetSfxVolume() const {return m_sfxVolume;}
void SetSfxVolume(f32 volume) {m_sfxVolume = volume;}
f32 GetVoiceVolume() const {return m_voiceVolume;}
void SetVoiceVolume(f32 volume) {m_voiceVolume = volume;}
// Sets the position of the listener for positional audio.
void SetListenerPosition(const v3 &position) {m_listenerPosition = position;};
// Sets the yaw (rotation along the y axis) of the listener for positional audio.
void SetListenerYaw(f32 yaw) {m_listenerYaw = yaw;}
// Has each audio player in the manager's linked list write audio samples
// to the output buffer.
void Write(const ma_uint32 frameCount, void *pOutput);
static AudioManager *Singleton() {
static AudioManager instance;
return &instance;
}
AudioManager(AudioManager const &) = delete;
void operator=(AudioManager const &) = delete;
private:
AudioManager() {}
f32 m_masterVolume = 1.0f;
f32 m_musicVolume = 1.0f;
f32 m_sfxVolume = 1.0f;
f32 m_voiceVolume = 1.0f;
v3 m_listenerPosition = {};
f32 m_listenerYaw = 0.0f;
AudioPlayer *m_head = 0;
f32 m_audioOutput[MA_DATA_CONVERTER_STACK_BUFFER_SIZE] = {};
};
AudioPlayer::AudioPlayer(Sound *sound, AudioBus audioBus) : m_sound(sound), m_audioBus(audioBus) {
AudioManager::Singleton()->AddAudioPlayer(this);
}
AudioPlayer::~AudioPlayer() {
AudioManager::Singleton()->RemoveAudioPlayer(this);
}
void AudioPlayer::Play() {
m_playing = true;
}
void AudioPlayer::PlayOneshot() {
m_playing = true;
m_oneshot = true;
m_currentFrame = 0;
}
void AudioPlayer::Pause() {
m_playing = false;
}
void AudioPlayer::Write(const f32 modulate, const v3 &listenerPosition, const f32 listenerYaw, const u32 frameCount, f32 *output) {
if (!m_sound || !m_playing)
return;
// Calculate the amplitude based on the listener position and modulation value.
const f32 amp = CalcPlayAmp(listenerPosition) * modulate;
// If the amplitude is zero, calculate the next frame index and return
// without writing any data.
if (amp == 0.0) {
u32 frame = static_cast<u32>(m_currentFrame + m_pitch * frameCount);
if (CheckOneshotDone(frame)) {
return;
}
m_currentFrame = frame % m_sound->frameCount;
return;
}
// Calculate the panning value based on the listener position and yaw.
const f32 pan = CalcPlayPan(listenerPosition, listenerYaw);
if (m_pitch == 1.0f)
WriteUnpitched(amp, pan, output, frameCount);
else
WritePitched(amp, pan, output, frameCount);
}
bool AudioPlayer::CheckOneshotDone(u32 frame) {
if (m_oneshot && frame >= m_sound->frameCount) {
m_playing = false;
m_currentFrame = 0;
return true;
}
return false;
}
f32 AudioPlayer::CalcPlayAmp(const v3 &listenerPosition) {
// If this audio player is not positional, return the amplitude as is.
if (!m_positional)
return m_amplitude;
// obtain the squared distance between the listener and the audio source.
const f32 l2 = Length2(m_position - listenerPosition);
// If the listener is outside the audio source's radius, return 0.
const f32 r2 = m_radius * m_radius;
if (l2 > r2)
return 0.0;
// Calculate the amplitude as a function of distance from the audio source,
// using the inverse square.
return m_amplitude * (1.0f - l2 / r2);
}
f32 AudioPlayer::CalcPlayPan(const v3 &listenerPosition, f32 listenerYaw) {
// If this audio player is not positional, it should be played with equal
// power in both speakers.
if (!m_positional)
return 0.5f;
// Calculate the relative offset from the listener to this player in
// the x,z plane.
const v2 offset = V2(m_position.x - listenerPosition.x, m_position.z - listenerPosition.z);
if (offset == V2(0.0f, 0.0f))
return 0.5f;
// Get the dot product of the listener's right unit vector and the offset
// vector, then map the [-1, 1] range to [0, 1].
return Dot(Normalize(offset), V2Rotate(Wrap(listenerYaw + PIO2, -PI, PI))) * 0.5f + 0.5f;
}
void AudioPlayer::WriteUnpitched(f32 amp, f32 pan, f32 *output, u32 frameCount) {
for (u32 i = 0; i < frameCount; i++) {
m_currentFrame += 1;
// If this is a one-shot sound and we've reached the end of the sound
// data, stop writing samples.
if (CheckOneshotDone(m_currentFrame))
return;
m_currentFrame = m_currentFrame % m_sound->frameCount;
f32 sample = m_sound->samples[m_currentFrame];
sample *= amp;
// Write the sample to the left and right channel based on the panning.
output[i * 2] += sample * Cos(pan * PI / 2.0f);
output[i * 2 + 1] += sample * Sin(pan * PI / 2.0f);
}
}
void AudioPlayer::WritePitched(f32 amp, f32 pan, f32 *output, u32 frameCount) {
// Get the current frame as a float.
f32 frame = static_cast<float>(m_currentFrame);
for (u32 i = 0; i < frameCount; i++) {
// Increase the frame by the pitch which is a non-whole amount.
frame += m_pitch;
// If this is a one-shot sound and we've reached the end of the sound
// data, stop writing samples.
if (CheckOneshotDone(static_cast<u32>(Ceil(frame))))
return;
// Get the two frames that will be lerped between.
const u32 prevFrame = static_cast<u32>(Floor(frame)) % m_sound->frameCount;
const u32 nextFrame = static_cast<u32>(Ceil(frame)) % m_sound->frameCount;
// Interpolate between the previous and next frames based on how far
// between the two frames our current frame is.
f32 sample = (Lerp(m_sound->samples[prevFrame], m_sound->samples[nextFrame], frame - Floor(frame)));
sample *= amp;
// Write the sample to the left and right channel based on the panning.
output[i * 2] += sample * Cos(pan * PI / 2.0f);
output[i * 2 + 1] += sample * Sin(pan * PI / 2.0f);
}
// Set the current frame to the closest whole value to the new frame value.
m_currentFrame = static_cast<u32>(frame) % m_sound->frameCount;
}
void AudioManager::AddAudioPlayer(AudioPlayer *audioPlayer) {
Assert(audioPlayer);
if (m_head) {
m_head->SetPrev(audioPlayer);
}
audioPlayer->SetNext(m_head);
m_head = audioPlayer;
}
void AudioManager::RemoveAudioPlayer(AudioPlayer *audioPlayer) {
Assert(audioPlayer);
if (audioPlayer == m_head)
m_head = audioPlayer->GetNext();
if (audioPlayer->GetPrev())
audioPlayer->GetPrev()->SetNext(audioPlayer->GetNext());
if (audioPlayer->GetNext())
audioPlayer->GetNext()->SetPrev(audioPlayer->GetPrev());
}
void AudioManager::Write(const ma_uint32 frameCount, void *pOutput) {
i16 *output = static_cast<i16 *>(pOutput);
memset(m_audioOutput, 0, sizeof(m_audioOutput));
// Write audio for each audio player.
AudioPlayer *audioPlayer = m_head;
while (audioPlayer) {
// Calculate the volume for this audio player.
f32 modulate = m_masterVolume;
switch (audioPlayer->GetAudioBus()) {
case AudioBus::Music:
modulate *= m_musicVolume;
break;
case AudioBus::Sfx:
modulate *= m_sfxVolume;
break;
case AudioBus::Voice:
modulate *= m_voiceVolume;
break;
}
audioPlayer->Write(modulate, m_listenerPosition, m_listenerYaw, frameCount, m_audioOutput);
audioPlayer = audioPlayer->GetNext();
}
// For both channels, apply limiting and copy the samples to the output buffer.
for (u32 i = 0; i < frameCount * 2; i++) {
f32 a = m_audioOutput[i];
if (a > 29500.0f)
a = Min(32767.0f, (a - 29500.0f) * 0.5f + 29500.0f);
else if (a < -29500.0f)
a = Max(-32767.0f, (a + 29500.0f) * 0.5f - 29500.0f);
output[i] = static_cast<i16>(a);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment