diff --git a/CMakeLists.txt b/CMakeLists.txt index 580142ad..4547a3ca 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -296,7 +296,7 @@ endif() if(BACKEND_AUDIO MATCHES "SDL2") target_sources(CSE2 PRIVATE "src/Backends/Audio/SDL2.cpp") elseif(BACKEND_AUDIO MATCHES "miniaudio") - target_sources(CSE2 PRIVATE "src/Backends/Audio/miniaudio.cpp") + target_sources(CSE2 PRIVATE "src/Backends/Audio/miniaudio.cpp" "src/Backends/Audio/SoftwareMixer.cpp") # Link libdl, libm, and libpthread include(CheckLibraryExists) diff --git a/src/Backends/Audio/SoftwareMixer.cpp b/src/Backends/Audio/SoftwareMixer.cpp new file mode 100644 index 00000000..afae1b4b --- /dev/null +++ b/src/Backends/Audio/SoftwareMixer.cpp @@ -0,0 +1,185 @@ +#include "SoftwareMixer.h" + +#include +#include +#include +#include +#include + +#define MIN(a, b) ((a) < (b) ? (a) : (b)) +#define MAX(a, b) ((a) > (b) ? (a) : (b)) +#define CLAMP(x, y, z) MIN(MAX((x), (y)), (z)) + +#ifdef __GNUC__ +#define ATTR_HOT __attribute__((hot)) +#else +#define ATTR_HOT +#endif + +struct Mixer_Sound +{ + unsigned char *samples; + size_t frames; + double position; + double advance_delta; + bool playing; + bool looping; + unsigned int frequency; + float volume; + float pan_l; + float pan_r; + float volume_l; + float volume_r; + + struct Mixer_Sound *next; +}; + +static Mixer_Sound *sound_list_head; + +static unsigned long output_frequency; + +static double MillibelToScale(long volume) +{ + // Volume is in hundredths of a decibel, from 0 to -10000 + volume = CLAMP(volume, -10000, 0); + return pow(10.0, volume / 2000.0); +} + +void Mixer_Init(unsigned long frequency) +{ + output_frequency = frequency; +} + +Mixer_Sound* Mixer_CreateSound(unsigned int frequency, size_t frames) +{ + Mixer_Sound *sound = (Mixer_Sound*)malloc(sizeof(Mixer_Sound)); + + if (sound == NULL) + return NULL; + + sound->samples = (unsigned char*)malloc(frames + 1); + + if (sound->samples == NULL) + { + free(sound); + return NULL; + } + + sound->frames = frames; + sound->playing = false; + sound->position = 0.0; + + Mixer_SetSoundFrequency(sound, frequency); + Mixer_SetSoundVolume(sound, 0); + Mixer_SetSoundPan(sound, 0); + + sound->next = sound_list_head; + sound_list_head = sound; + + return sound; +} + +void Mixer_DestroySound(Mixer_Sound *sound) +{ + for (Mixer_Sound **sound_pointer = &sound_list_head; *sound_pointer != NULL; sound_pointer = &(*sound_pointer)->next) + { + if (*sound_pointer == sound) + { + *sound_pointer = sound->next; + free(sound->samples); + free(sound); + break; + } + } +} + +unsigned char* Mixer_LockSound(Mixer_Sound *sound, size_t *size) +{ + if (size != NULL) + *size = sound->frames; + + return sound->samples; +} + +void Mixer_PlaySound(Mixer_Sound *sound, bool looping) +{ + sound->playing = true; + sound->looping = looping; + + sound->samples[sound->frames] = looping ? sound->samples[0] : 0x80; // For the linear interpolator +} + +void Mixer_StopSound(Mixer_Sound *sound) +{ + sound->playing = false; + sound->position = 0.0; +} + +void Mixer_RewindSound(Mixer_Sound *sound) +{ + sound->position = 0.0; +} + +void Mixer_SetSoundFrequency(Mixer_Sound *sound, unsigned int frequency) +{ + sound->frequency = frequency; + sound->advance_delta = (double)frequency / (double)output_frequency; +} + +void Mixer_SetSoundVolume(Mixer_Sound *sound, long volume) +{ + sound->volume = (float)MillibelToScale(volume); + + sound->volume_l = sound->pan_l * sound->volume; + sound->volume_r = sound->pan_r * sound->volume; +} + +void Mixer_SetSoundPan(Mixer_Sound *sound, long pan) +{ + sound->pan_l = (float)MillibelToScale(-pan); + sound->pan_r = (float)MillibelToScale(pan); + + sound->volume_l = sound->pan_l * sound->volume; + sound->volume_r = sound->pan_r * sound->volume; +} + +// Most CPU-intensive function in the game (2/3rd CPU time consumption in my experience), so marked with attrHot so the compiler considers it a hot spot (as it is) when optimizing +ATTR_HOT void Mixer_MixSounds(float *stream, unsigned int frames_total) +{ + for (Mixer_Sound *sound = sound_list_head; sound != NULL; sound = sound->next) + { + if (sound->playing) + { + float *steam_pointer = stream; + + for (unsigned int frames_done = 0; frames_done < frames_total; ++frames_done) + { + // Get two samples, and normalise them to 0-1 + const float sample1 = (sound->samples[(size_t)sound->position] - 128.0f) / 128.0f; + const float sample2 = (sound->samples[(size_t)sound->position + 1] - 128.0f) / 128.0f; + + // Perform linear interpolation + const float interpolated_sample = sample1 + ((sample2 - sample1) * fmod((float)sound->position, 1.0f)); + + *steam_pointer++ += interpolated_sample * sound->volume_l; + *steam_pointer++ += interpolated_sample * sound->volume_r; + + sound->position += sound->advance_delta; + + if (sound->position >= sound->frames) + { + if (sound->looping) + { + sound->position = fmod(sound->position, (double)sound->frames); + } + else + { + sound->playing = false; + sound->position = 0.0; + break; + } + } + } + } + } +} diff --git a/src/Backends/Audio/SoftwareMixer.h b/src/Backends/Audio/SoftwareMixer.h new file mode 100644 index 00000000..da762fcf --- /dev/null +++ b/src/Backends/Audio/SoftwareMixer.h @@ -0,0 +1,17 @@ +#pragma once + +#include + +typedef struct Mixer_Sound Mixer_Sound; + +void Mixer_Init(unsigned long frequency); +Mixer_Sound* Mixer_CreateSound(unsigned int frequency, size_t frames); +void Mixer_DestroySound(Mixer_Sound *sound); +unsigned char* Mixer_LockSound(Mixer_Sound *sound, size_t *size); +void Mixer_PlaySound(Mixer_Sound *sound, bool looping); +void Mixer_StopSound(Mixer_Sound *sound); +void Mixer_RewindSound(Mixer_Sound *sound); +void Mixer_SetSoundFrequency(Mixer_Sound *sound, unsigned int frequency); +void Mixer_SetSoundVolume(Mixer_Sound *sound, long volume); +void Mixer_SetSoundPan(Mixer_Sound *sound, long pan); +void Mixer_MixSounds(float *stream, unsigned int frames_total); diff --git a/src/Backends/Audio/miniaudio.cpp b/src/Backends/Audio/miniaudio.cpp index 7b0b554d..7b374819 100644 --- a/src/Backends/Audio/miniaudio.cpp +++ b/src/Backends/Audio/miniaudio.cpp @@ -1,11 +1,5 @@ #include "../Audio.h" -#include -#include -#include -#include -#include - #define MINIAUDIO_IMPLEMENTATION #define MA_NO_DECODING #define MA_API static @@ -14,35 +8,10 @@ #include "../../Organya.h" #include "../../WindowsWrapper.h" +#include "SoftwareMixer.h" + #define MIN(a, b) ((a) < (b) ? (a) : (b)) -#define MAX(a, b) ((a) > (b) ? (a) : (b)) -#define CLAMP(x, y, z) MIN(MAX((x), (y)), (z)) -#ifdef __GNUC__ -#define ATTR_HOT __attribute__((hot)) -#else -#define ATTR_HOT -#endif - -struct AudioBackend_Sound -{ - unsigned char *samples; - size_t frames; - double position; - double advance_delta; - BOOL playing; - BOOL looping; - unsigned int frequency; - float volume; - float pan_l; - float pan_r; - float volume_l; - float volume_r; - - struct AudioBackend_Sound *next; -}; - -static AudioBackend_Sound *sound_list_head; static ma_device device; static ma_mutex mutex; static ma_mutex organya_mutex; @@ -51,81 +20,6 @@ static unsigned long output_frequency; static unsigned short organya_timer; -static double MillibelToScale(long volume) -{ - // Volume is in hundredths of decibels, from 0 to -10000 - volume = CLAMP(volume, -10000, 0); - return pow(10.0, volume / 2000.0); -} - -static void SetSoundFrequency(AudioBackend_Sound *sound, unsigned int frequency) -{ - sound->frequency = frequency; - sound->advance_delta = (double)frequency / (double)output_frequency; -} - -static void SetSoundVolume(AudioBackend_Sound *sound, long volume) -{ - sound->volume = (float)MillibelToScale(volume); - - sound->volume_l = sound->pan_l * sound->volume; - sound->volume_r = sound->pan_r * sound->volume; -} - -static void SetSoundPan(AudioBackend_Sound *sound, long pan) -{ - sound->pan_l = (float)MillibelToScale(-pan); - sound->pan_r = (float)MillibelToScale(pan); - - sound->volume_l = sound->pan_l * sound->volume; - sound->volume_r = sound->pan_r * sound->volume; -} - -// Most CPU-intensive function in the game (2/3rd CPU time consumption in my experience), so marked with attrHot so the compiler considers it a hot spot (as it is) when optimizing -ATTR_HOT static void MixSounds(float *stream, unsigned int frames_total) -{ - ma_mutex_lock(&mutex); - - for (AudioBackend_Sound *sound = sound_list_head; sound != NULL; sound = sound->next) - { - if (sound->playing) - { - float *steam_pointer = stream; - - for (unsigned int frames_done = 0; frames_done < frames_total; ++frames_done) - { - // Get two samples, and normalise them to 0-1 - const float sample1 = (sound->samples[(size_t)sound->position] - 128.0f) / 128.0f; - const float sample2 = (sound->samples[(size_t)sound->position + 1] - 128.0f) / 128.0f; - - // Perform linear interpolation - const float interpolated_sample = sample1 + ((sample2 - sample1) * fmod((float)sound->position, 1.0f)); - - *steam_pointer++ += interpolated_sample * sound->volume_l; - *steam_pointer++ += interpolated_sample * sound->volume_r; - - sound->position += sound->advance_delta; - - if (sound->position >= sound->frames) - { - if (sound->looping) - { - sound->position = fmod(sound->position, (double)sound->frames); - } - else - { - sound->playing = FALSE; - sound->position = 0.0; - break; - } - } - } - } - } - - ma_mutex_unlock(&mutex); -} - static void Callback(ma_device *device, void *output_stream, const void *input_stream, ma_uint32 frames_total) { (void)device; @@ -137,7 +31,9 @@ static void Callback(ma_device *device, void *output_stream, const void *input_s if (organya_timer == 0) { - MixSounds(stream, frames_total); + ma_mutex_lock(&mutex); + Mixer_MixSounds(stream, frames_total); + ma_mutex_unlock(&mutex); } else { @@ -160,7 +56,9 @@ static void Callback(ma_device *device, void *output_stream, const void *input_s const unsigned int frames_to_do = MIN(organya_countdown, frames_total - frames_done); - MixSounds(stream + frames_done * 2, frames_to_do); + ma_mutex_lock(&mutex); + Mixer_MixSounds(stream + frames_done * 2, frames_to_do); + ma_mutex_unlock(&mutex); frames_done += frames_to_do; organya_countdown -= frames_to_do; @@ -182,14 +80,18 @@ BOOL AudioBackend_Init(void) if (ma_device_init(NULL, &config, &device) == MA_SUCCESS) { - output_frequency = device.sampleRate; - if (ma_mutex_init(device.pContext, &mutex) == MA_SUCCESS) { if (ma_mutex_init(device.pContext, &organya_mutex) == MA_SUCCESS) { if (ma_device_start(&device) == MA_SUCCESS) + { + output_frequency = device.sampleRate; + + Mixer_Init(device.sampleRate); + return TRUE; + } ma_mutex_uninit(&organya_mutex); } @@ -216,35 +118,13 @@ void AudioBackend_Deinit(void) AudioBackend_Sound* AudioBackend_CreateSound(unsigned int frequency, size_t frames) { - AudioBackend_Sound *sound = (AudioBackend_Sound*)malloc(sizeof(AudioBackend_Sound)); - - if (sound == NULL) - return NULL; - - sound->samples = (unsigned char*)malloc(frames + 1); - - if (sound->samples == NULL) - { - free(sound); - return NULL; - } - - sound->frames = frames; - sound->playing = FALSE; - sound->position = 0.0; - - SetSoundFrequency(sound, frequency); - SetSoundVolume(sound, 0); - SetSoundPan(sound, 0); - ma_mutex_lock(&mutex); - sound->next = sound_list_head; - sound_list_head = sound; + Mixer_Sound *sound = Mixer_CreateSound(frequency, frames); ma_mutex_unlock(&mutex); - return sound; + return (AudioBackend_Sound*)sound; } void AudioBackend_DestroySound(AudioBackend_Sound *sound) @@ -254,16 +134,7 @@ void AudioBackend_DestroySound(AudioBackend_Sound *sound) ma_mutex_lock(&mutex); - for (AudioBackend_Sound **sound_pointer = &sound_list_head; *sound_pointer != NULL; sound_pointer = &(*sound_pointer)->next) - { - if (*sound_pointer == sound) - { - *sound_pointer = sound->next; - free(sound->samples); - free(sound); - break; - } - } + Mixer_DestroySound((Mixer_Sound*)sound); ma_mutex_unlock(&mutex); } @@ -275,10 +146,7 @@ unsigned char* AudioBackend_LockSound(AudioBackend_Sound *sound, size_t *size) ma_mutex_lock(&mutex); - if (size != NULL) - *size = sound->frames; - - return sound->samples; + return Mixer_LockSound((Mixer_Sound*)sound, size); } void AudioBackend_UnlockSound(AudioBackend_Sound *sound) @@ -296,10 +164,7 @@ void AudioBackend_PlaySound(AudioBackend_Sound *sound, BOOL looping) ma_mutex_lock(&mutex); - sound->playing = TRUE; - sound->looping = looping; - - sound->samples[sound->frames] = looping ? sound->samples[0] : 0x80; // For the linear interpolator + Mixer_PlaySound((Mixer_Sound*)sound, looping); ma_mutex_unlock(&mutex); } @@ -311,8 +176,7 @@ void AudioBackend_StopSound(AudioBackend_Sound *sound) ma_mutex_lock(&mutex); - sound->playing = FALSE; - sound->position = 0.0; + Mixer_StopSound((Mixer_Sound*)sound); ma_mutex_unlock(&mutex); } @@ -324,7 +188,7 @@ void AudioBackend_RewindSound(AudioBackend_Sound *sound) ma_mutex_lock(&mutex); - sound->position = 0.0; + Mixer_RewindSound((Mixer_Sound*)sound); ma_mutex_unlock(&mutex); } @@ -336,7 +200,7 @@ void AudioBackend_SetSoundFrequency(AudioBackend_Sound *sound, unsigned int freq ma_mutex_lock(&mutex); - SetSoundFrequency(sound, frequency); + Mixer_SetSoundFrequency((Mixer_Sound*)sound, frequency); ma_mutex_unlock(&mutex); } @@ -348,7 +212,7 @@ void AudioBackend_SetSoundVolume(AudioBackend_Sound *sound, long volume) ma_mutex_lock(&mutex); - SetSoundVolume(sound, volume); + Mixer_SetSoundVolume((Mixer_Sound*)sound, volume); ma_mutex_unlock(&mutex); } @@ -360,7 +224,7 @@ void AudioBackend_SetSoundPan(AudioBackend_Sound *sound, long pan) ma_mutex_lock(&mutex); - SetSoundPan(sound, pan); + Mixer_SetSoundPan((Mixer_Sound*)sound, pan); ma_mutex_unlock(&mutex); }