diff --git a/CMakeLists.txt b/CMakeLists.txt index b8182f71..d41b069b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -316,15 +316,19 @@ endif() if(BACKEND_AUDIO MATCHES "SDL2") target_sources(CSE2 PRIVATE - "src/Backends/Audio/SDL2.cpp" "src/Backends/Audio/SoftwareMixer.cpp" - "src/Backends/Audio/SoftwareMixer.h" + "src/Backends/Audio/SoftwareMixer/Mixer.cpp" + "src/Backends/Audio/SoftwareMixer/Mixer.h" + "src/Backends/Audio/SoftwareMixer/Backend.h" + "src/Backends/Audio/SoftwareMixer/SDL2.cpp" ) elseif(BACKEND_AUDIO MATCHES "miniaudio") target_sources(CSE2 PRIVATE - "src/Backends/Audio/miniaudio.cpp" "src/Backends/Audio/SoftwareMixer.cpp" - "src/Backends/Audio/SoftwareMixer.h" + "src/Backends/Audio/SoftwareMixer/Mixer.cpp" + "src/Backends/Audio/SoftwareMixer/Mixer.h" + "src/Backends/Audio/SoftwareMixer/Backend.h" + "src/Backends/Audio/SoftwareMixer/miniaudio.cpp" ) # Link libdl, libm, and libpthread @@ -347,9 +351,11 @@ elseif(BACKEND_AUDIO MATCHES "WiiU-Hardware") ) elseif(BACKEND_AUDIO MATCHES "WiiU-Software") target_sources(CSE2 PRIVATE - "src/Backends/Audio/WiiU-Software.cpp" "src/Backends/Audio/SoftwareMixer.cpp" - "src/Backends/Audio/SoftwareMixer.h" + "src/Backends/Audio/SoftwareMixer/Mixer.cpp" + "src/Backends/Audio/SoftwareMixer/Mixer.h" + "src/Backends/Audio/SoftwareMixer/Backend.h" + "src/Backends/Audio/SoftwareMixer/WiiU-Software.cpp" ) elseif(BACKEND_AUDIO MATCHES "Null") target_sources(CSE2 PRIVATE diff --git a/src/Backends/Audio/SDL2.cpp b/src/Backends/Audio/SDL2.cpp deleted file mode 100644 index c737a582..00000000 --- a/src/Backends/Audio/SDL2.cpp +++ /dev/null @@ -1,249 +0,0 @@ -#include "../Audio.h" - -#include -#include -#include - -#include "SDL.h" - -#include "../Misc.h" - -#include "SoftwareMixer.h" - -#define MIN(a, b) ((a) < (b) ? (a) : (b)) - -static SDL_AudioDeviceID device_id; - -static unsigned long output_frequency; - -static void (*organya_callback)(void); -static unsigned int organya_callback_milliseconds; - -static void MixSoundsAndUpdateOrganya(long *stream, size_t frames_total) -{ - if (organya_callback_milliseconds == 0) - { - Mixer_MixSounds(stream, frames_total); - } - else - { - // Synchronise audio generation with Organya. - // In the original game, Organya ran asynchronously in a separate thread, - // firing off commands to DirectSound in realtime. To match that, we'd - // need a very low-latency buffer, otherwise we'd get mistimed instruments. - // Instead, we can just do this. - unsigned int frames_done = 0; - - while (frames_done != frames_total) - { - static unsigned long organya_countdown; - - if (organya_countdown == 0) - { - organya_countdown = (organya_callback_milliseconds * output_frequency) / 1000; // organya_timer is in milliseconds, so convert it to audio frames - organya_callback(); - } - - const unsigned int frames_to_do = MIN(organya_countdown, frames_total - frames_done); - - Mixer_MixSounds(stream + frames_done * 2, frames_to_do); - - frames_done += frames_to_do; - organya_countdown -= frames_to_do; - } - } -} - -static void Callback(void *user_data, Uint8 *stream_uint8, int len) -{ - (void)user_data; - - short *stream = (short*)stream_uint8; - const size_t frames_total = len / sizeof(short) / 2; - - size_t frames_done = 0; - - while (frames_done != frames_total) - { - long mix_buffer[0x800 * 2]; // 2 because stereo - - size_t subframes = MIN(0x800, frames_total - frames_done); - - memset(mix_buffer, 0, subframes * sizeof(long) * 2); - - MixSoundsAndUpdateOrganya(mix_buffer, subframes); - - for (size_t i = 0; i < subframes * 2; ++i) - { - if (mix_buffer[i] > 0x7FFF) - *stream++ = 0x7FFF; - else if (mix_buffer[i] < -0x7FFF) - *stream++ = -0x7FFF; - else - *stream++ = mix_buffer[i]; - } - - frames_done += subframes; - } -} - -bool AudioBackend_Init(void) -{ - if (SDL_InitSubSystem(SDL_INIT_AUDIO) < 0) - { - std::string errorMessage = std::string("'SDL_InitSubSystem(SDL_INIT_AUDIO)' failed: ") + SDL_GetError(); - Backend_ShowMessageBox("Fatal error (SDL2 audio backend)", errorMessage.c_str()); - return false; - } - - Backend_PrintInfo("Available SDL audio drivers:"); - - for (int i = 0; i < SDL_GetNumAudioDrivers(); ++i) - Backend_PrintInfo("%s", SDL_GetAudioDriver(i)); - - SDL_AudioSpec specification; - specification.freq = 48000; - specification.format = AUDIO_S16; - specification.channels = 2; - specification.samples = 0x400; // Roughly 10 milliseconds for 48000Hz - specification.callback = Callback; - specification.userdata = NULL; - - SDL_AudioSpec obtained_specification; - device_id = SDL_OpenAudioDevice(NULL, 0, &specification, &obtained_specification, SDL_AUDIO_ALLOW_FREQUENCY_CHANGE); - if (device_id == 0) - { - std::string error_message = std::string("'SDL_OpenAudioDevice' failed: ") + SDL_GetError(); - Backend_ShowMessageBox("Fatal error (SDL2 audio backend)", error_message.c_str()); - return false; - } - - output_frequency = obtained_specification.freq; - Mixer_Init(obtained_specification.freq); - - SDL_PauseAudioDevice(device_id, 0); - - Backend_PrintInfo("Selected SDL audio driver: %s", SDL_GetCurrentAudioDriver()); - - return true; -} - -void AudioBackend_Deinit(void) -{ - SDL_CloseAudioDevice(device_id); - - SDL_QuitSubSystem(SDL_INIT_AUDIO); -} - -AudioBackend_Sound* AudioBackend_CreateSound(unsigned int frequency, const unsigned char *samples, size_t length) -{ - SDL_LockAudioDevice(device_id); - - Mixer_Sound *sound = Mixer_CreateSound(frequency, samples, length); - - SDL_UnlockAudioDevice(device_id); - - return (AudioBackend_Sound*)sound; -} - -void AudioBackend_DestroySound(AudioBackend_Sound *sound) -{ - if (sound == NULL) - return; - - SDL_LockAudioDevice(device_id); - - Mixer_DestroySound((Mixer_Sound*)sound); - - SDL_UnlockAudioDevice(device_id); -} - -void AudioBackend_PlaySound(AudioBackend_Sound *sound, bool looping) -{ - if (sound == NULL) - return; - - SDL_LockAudioDevice(device_id); - - Mixer_PlaySound((Mixer_Sound*)sound, looping); - - SDL_UnlockAudioDevice(device_id); -} - -void AudioBackend_StopSound(AudioBackend_Sound *sound) -{ - if (sound == NULL) - return; - - SDL_LockAudioDevice(device_id); - - Mixer_StopSound((Mixer_Sound*)sound); - - SDL_UnlockAudioDevice(device_id); -} - -void AudioBackend_RewindSound(AudioBackend_Sound *sound) -{ - if (sound == NULL) - return; - - SDL_LockAudioDevice(device_id); - - Mixer_RewindSound((Mixer_Sound*)sound); - - SDL_UnlockAudioDevice(device_id); -} - -void AudioBackend_SetSoundFrequency(AudioBackend_Sound *sound, unsigned int frequency) -{ - if (sound == NULL) - return; - - SDL_LockAudioDevice(device_id); - - Mixer_SetSoundFrequency((Mixer_Sound*)sound, frequency); - - SDL_UnlockAudioDevice(device_id); -} - -void AudioBackend_SetSoundVolume(AudioBackend_Sound *sound, long volume) -{ - if (sound == NULL) - return; - - SDL_LockAudioDevice(device_id); - - Mixer_SetSoundVolume((Mixer_Sound*)sound, volume); - - SDL_UnlockAudioDevice(device_id); -} - -void AudioBackend_SetSoundPan(AudioBackend_Sound *sound, long pan) -{ - if (sound == NULL) - return; - - SDL_LockAudioDevice(device_id); - - Mixer_SetSoundPan((Mixer_Sound*)sound, pan); - - SDL_UnlockAudioDevice(device_id); -} - -void AudioBackend_SetOrganyaCallback(void (*callback)(void)) -{ - SDL_LockAudioDevice(device_id); - - organya_callback = callback; - - SDL_UnlockAudioDevice(device_id); -} - -void AudioBackend_SetOrganyaTimer(unsigned int milliseconds) -{ - SDL_LockAudioDevice(device_id); - - organya_callback_milliseconds = milliseconds; - - SDL_UnlockAudioDevice(device_id); -} diff --git a/src/Backends/Audio/SoftwareMixer.cpp b/src/Backends/Audio/SoftwareMixer.cpp index 11e30567..6320dad0 100644 --- a/src/Backends/Audio/SoftwareMixer.cpp +++ b/src/Backends/Audio/SoftwareMixer.cpp @@ -1,236 +1,214 @@ -#include "SoftwareMixer.h" +#include "../Audio.h" -#include #include -#include -#include "../../Attributes.h" +#include "SoftwareMixer/Backend.h" +#include "SoftwareMixer/Mixer.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)) - -#define LANCZOS_KERNEL_RADIUS 2 - -struct Mixer_Sound -{ - signed char *samples; - size_t frames; - size_t position; - unsigned short position_subsample; - unsigned long advance_delta; // 16.16 fixed-point - bool playing; - bool looping; - short volume; // 8.8 fixed-point - short pan_l; // 8.8 fixed-point - short pan_r; // 8.8 fixed-point - short volume_l; // 8.8 fixed-point - short volume_r; // 8.8 fixed-point - - struct Mixer_Sound *next; -}; - -static Mixer_Sound *sound_list_head; static unsigned long output_frequency; -static unsigned short MillibelToScale(long volume) +static void (*organya_callback)(void); +static unsigned int organya_callback_milliseconds; +static unsigned int organya_sleep_timer; + +static void MixSoundsAndUpdateOrganya(long *stream, size_t frames_total) { - // Volume is in hundredths of a decibel, from 0 to -10000 - volume = CLAMP(volume, -10000, 0); - return (unsigned short)(pow(10.0, volume / 2000.0) * 256.0f); -} + SoftwareMixerBackend_LockOrganyaMutex(); -void Mixer_Init(unsigned long frequency) -{ - output_frequency = frequency; -} - -Mixer_Sound* Mixer_CreateSound(unsigned int frequency, const unsigned char *samples, size_t length) -{ - Mixer_Sound *sound = (Mixer_Sound*)malloc(sizeof(Mixer_Sound)); - - if (sound == NULL) - return NULL; - - // Both interpolators will read outside the array's bounds, so allocate some extra room -#ifdef LANCZOS_RESAMPLER - sound->samples = (signed char*)malloc(LANCZOS_KERNEL_RADIUS - 1 + length + LANCZOS_KERNEL_RADIUS); -#else - sound->samples = (signed char*)malloc(length + 1); -#endif - - if (sound->samples == NULL) + if (organya_callback_milliseconds == 0) { - free(sound); - return NULL; + SoftwareMixerBackend_LockMixerMutex(); + Mixer_MixSounds(stream, frames_total); + SoftwareMixerBackend_UnlockMixerMutex(); } - -#ifdef LANCZOS_RESAMPLER - // Blank samples outside the array bounds (we'll deal with the other half later) - for (size_t i = 0; i < LANCZOS_KERNEL_RADIUS - 1; ++i) - sound->samples[i] = 0; - - sound->samples += LANCZOS_KERNEL_RADIUS - 1; -#endif - - for (size_t i = 0; i < length; ++i) - sound->samples[i] = samples[i] - 0x80; // Convert from unsigned 8-bit PCM to signed - - sound->frames = length; - sound->playing = false; - sound->position = 0; - sound->position_subsample = 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; - #ifdef LANCZOS_RESAMPLER - sound->samples -= LANCZOS_KERNEL_RADIUS - 1; - #endif - free(sound->samples); - free(sound); - break; - } - } -} - -void Mixer_PlaySound(Mixer_Sound *sound, bool looping) -{ - sound->playing = true; - sound->looping = looping; - - // Fill the out-of-bounds part of the buffer with - // either blank samples or repeated samples -#ifdef LANCZOS_RESAMPLER - if (looping) - for (size_t i = 0; i < LANCZOS_KERNEL_RADIUS; ++i) - sound->samples[sound->frames + i] = sound->samples[i]; else - for (size_t i = 0; i < LANCZOS_KERNEL_RADIUS; ++i) - sound->samples[sound->frames + i] = 0; -#else - sound->samples[sound->frames] = looping ? sound->samples[0] : 0; -#endif -} - -void Mixer_StopSound(Mixer_Sound *sound) -{ - sound->playing = false; -} - -void Mixer_RewindSound(Mixer_Sound *sound) -{ - sound->position = 0; - sound->position_subsample = 0; -} - -void Mixer_SetSoundFrequency(Mixer_Sound *sound, unsigned int frequency) -{ - sound->advance_delta = (frequency << 16) / output_frequency; -} - -void Mixer_SetSoundVolume(Mixer_Sound *sound, long volume) -{ - sound->volume = MillibelToScale(volume); - - sound->volume_l = (sound->pan_l * sound->volume) >> 8; - sound->volume_r = (sound->pan_r * sound->volume) >> 8; -} - -void Mixer_SetSoundPan(Mixer_Sound *sound, long pan) -{ - sound->pan_l = MillibelToScale(-pan); - sound->pan_r = MillibelToScale(pan); - - sound->volume_l = (sound->pan_l * sound->volume) >> 8; - sound->volume_r = (sound->pan_r * sound->volume) >> 8; -} - -// Most CPU-intensive function in the game (2/3rd CPU time consumption in my experience), so marked with ATTRIBUTE_HOT so the compiler considers it a hot spot (as it is) when optimizing -ATTRIBUTE_HOT void Mixer_MixSounds(long *stream, size_t frames_total) -{ - for (Mixer_Sound *sound = sound_list_head; sound != NULL; sound = sound->next) { - if (sound->playing) + // Synchronise audio generation with Organya. + // In the original game, Organya ran asynchronously in a separate thread, + // firing off commands to DirectSound in realtime. To match that, we'd + // need a very low-latency buffer, otherwise we'd get mistimed instruments. + // Instead, we can just do this. + unsigned int frames_done = 0; + + // Don't process Organya when it's meant to be sleeping + const unsigned int frames_to_do = MIN(organya_sleep_timer, frames_total - frames_done); + + if (frames_to_do != 0) { - long *stream_pointer = stream; + SoftwareMixerBackend_LockMixerMutex(); + Mixer_MixSounds(stream, frames_to_do); + SoftwareMixerBackend_UnlockMixerMutex(); - for (size_t frames_done = 0; frames_done < frames_total; ++frames_done) + frames_done += frames_to_do; + organya_sleep_timer -= frames_to_do; + } + + while (frames_done != frames_total) + { + static unsigned long organya_countdown; + + if (organya_countdown == 0) { - #ifdef LANCZOS_RESAMPLER - // Perform Lanczos resampling - float output_sample = 0; - - for (int i = -LANCZOS_KERNEL_RADIUS + 1; i <= LANCZOS_KERNEL_RADIUS; ++i) - { - const signed char input_sample = sound->samples[sound->position + i]; - - const float kernel_input = ((float)sound->position_subsample / 0x10000) - i; - - if (kernel_input == 0.0f) - { - output_sample += input_sample; - } - else - { - const float nx = 3.14159265358979323846f * kernel_input; - const float nxa = nx / LANCZOS_KERNEL_RADIUS; - - output_sample += input_sample * (sin(nx) * sin(nxa) / (nx * nxa)); - } - } - - // Mix, and apply volume - *stream_pointer++ += (short)(output_sample * sound->volume_l); - *stream_pointer++ += (short)(output_sample * sound->volume_r); - #else - // Perform linear interpolation - const unsigned char interpolation_scale = sound->position_subsample >> 8; - - const signed char output_sample = (sound->samples[sound->position] * (0x100 - interpolation_scale) - + sound->samples[sound->position + 1] * interpolation_scale) >> 8; - - // Mix, and apply volume - *stream_pointer++ += output_sample * sound->volume_l; - *stream_pointer++ += output_sample * sound->volume_r; - #endif - - // Increment sample - const unsigned long next_position_subsample = sound->position_subsample + sound->advance_delta; - sound->position += next_position_subsample >> 16; - sound->position_subsample = next_position_subsample & 0xFFFF; - - // Stop or loop sample once it's reached its end - if (sound->position >= sound->frames) - { - if (sound->looping) - { - sound->position %= sound->frames; - } - else - { - sound->playing = false; - sound->position = 0; - sound->position_subsample = 0; - break; - } - } + organya_countdown = (organya_callback_milliseconds * output_frequency) / 1000; // organya_timer is in milliseconds, so convert it to audio frames + organya_callback(); } + + const unsigned int frames_to_do = MIN(organya_countdown, frames_total - frames_done); + + SoftwareMixerBackend_LockMixerMutex(); + Mixer_MixSounds(stream + frames_done * 2, frames_to_do); + SoftwareMixerBackend_UnlockMixerMutex(); + + frames_done += frames_to_do; + organya_countdown -= frames_to_do; } } + + SoftwareMixerBackend_UnlockOrganyaMutex(); +} + +bool AudioBackend_Init(void) +{ + output_frequency = SoftwareMixerBackend_Init(MixSoundsAndUpdateOrganya); + + if (output_frequency != 0) + { + Mixer_Init(output_frequency); + + if (SoftwareMixerBackend_Start()) + return true; + + SoftwareMixerBackend_Deinit(); + } + + return false; +} + +void AudioBackend_Deinit(void) +{ + return SoftwareMixerBackend_Deinit(); +} + +AudioBackend_Sound* AudioBackend_CreateSound(unsigned int frequency, const unsigned char *samples, size_t length) +{ + SoftwareMixerBackend_LockMixerMutex(); + + Mixer_Sound *sound = Mixer_CreateSound(frequency, samples, length); + + SoftwareMixerBackend_UnlockMixerMutex(); + + return (AudioBackend_Sound*)sound; +} + +void AudioBackend_DestroySound(AudioBackend_Sound *sound) +{ + if (sound == NULL) + return; + + SoftwareMixerBackend_LockMixerMutex(); + + Mixer_DestroySound((Mixer_Sound*)sound); + + SoftwareMixerBackend_UnlockMixerMutex(); +} + +void AudioBackend_PlaySound(AudioBackend_Sound *sound, bool looping) +{ + if (sound == NULL) + return; + + SoftwareMixerBackend_LockMixerMutex(); + + Mixer_PlaySound((Mixer_Sound*)sound, looping); + + SoftwareMixerBackend_UnlockMixerMutex(); +} + +void AudioBackend_StopSound(AudioBackend_Sound *sound) +{ + if (sound == NULL) + return; + + SoftwareMixerBackend_LockMixerMutex(); + + Mixer_StopSound((Mixer_Sound*)sound); + + SoftwareMixerBackend_UnlockMixerMutex(); +} + +void AudioBackend_RewindSound(AudioBackend_Sound *sound) +{ + if (sound == NULL) + return; + + SoftwareMixerBackend_LockMixerMutex(); + + Mixer_RewindSound((Mixer_Sound*)sound); + + SoftwareMixerBackend_UnlockMixerMutex(); +} + +void AudioBackend_SetSoundFrequency(AudioBackend_Sound *sound, unsigned int frequency) +{ + if (sound == NULL) + return; + + SoftwareMixerBackend_LockMixerMutex(); + + Mixer_SetSoundFrequency((Mixer_Sound*)sound, frequency); + + SoftwareMixerBackend_UnlockMixerMutex(); +} + +void AudioBackend_SetSoundVolume(AudioBackend_Sound *sound, long volume) +{ + if (sound == NULL) + return; + + SoftwareMixerBackend_LockMixerMutex(); + + Mixer_SetSoundVolume((Mixer_Sound*)sound, volume); + + SoftwareMixerBackend_UnlockMixerMutex(); +} + +void AudioBackend_SetSoundPan(AudioBackend_Sound *sound, long pan) +{ + if (sound == NULL) + return; + + SoftwareMixerBackend_LockMixerMutex(); + + Mixer_SetSoundPan((Mixer_Sound*)sound, pan); + + SoftwareMixerBackend_UnlockMixerMutex(); +} + +void AudioBackend_SetOrganyaCallback(void (*callback)(void)) +{ + SoftwareMixerBackend_LockOrganyaMutex(); + + organya_callback = callback; + + SoftwareMixerBackend_UnlockOrganyaMutex(); +} + +void AudioBackend_SetOrganyaTimer(unsigned int milliseconds) +{ + SoftwareMixerBackend_LockOrganyaMutex(); + + organya_callback_milliseconds = milliseconds; + + SoftwareMixerBackend_UnlockOrganyaMutex(); +} + +void AudioBackend_SleepOrganya(unsigned int milliseconds) +{ + SoftwareMixerBackend_LockOrganyaMutex(); + + organya_sleep_timer = (milliseconds * output_frequency) / 1000; + + SoftwareMixerBackend_UnlockOrganyaMutex(); } diff --git a/src/Backends/Audio/SoftwareMixer/Backend.h b/src/Backends/Audio/SoftwareMixer/Backend.h new file mode 100644 index 00000000..daf7ec38 --- /dev/null +++ b/src/Backends/Audio/SoftwareMixer/Backend.h @@ -0,0 +1,14 @@ +#pragma once + +#include + +unsigned long SoftwareMixerBackend_Init(void (*callback)(long *stream, size_t frames_total)); +void SoftwareMixerBackend_Deinit(void); + +bool SoftwareMixerBackend_Start(void); + +void SoftwareMixerBackend_LockMixerMutex(void); +void SoftwareMixerBackend_UnlockMixerMutex(void); + +void SoftwareMixerBackend_LockOrganyaMutex(void); +void SoftwareMixerBackend_UnlockOrganyaMutex(void); diff --git a/src/Backends/Audio/SoftwareMixer/Mixer.cpp b/src/Backends/Audio/SoftwareMixer/Mixer.cpp new file mode 100644 index 00000000..6ff49696 --- /dev/null +++ b/src/Backends/Audio/SoftwareMixer/Mixer.cpp @@ -0,0 +1,236 @@ +#include "Mixer.h" + +#include +#include +#include + +#include "../../../Attributes.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)) + +#define LANCZOS_KERNEL_RADIUS 2 + +struct Mixer_Sound +{ + signed char *samples; + size_t frames; + size_t position; + unsigned short position_subsample; + unsigned long advance_delta; // 16.16 fixed-point + bool playing; + bool looping; + short volume; // 8.8 fixed-point + short pan_l; // 8.8 fixed-point + short pan_r; // 8.8 fixed-point + short volume_l; // 8.8 fixed-point + short volume_r; // 8.8 fixed-point + + struct Mixer_Sound *next; +}; + +static Mixer_Sound *sound_list_head; + +static unsigned long output_frequency; + +static unsigned short MillibelToScale(long volume) +{ + // Volume is in hundredths of a decibel, from 0 to -10000 + volume = CLAMP(volume, -10000, 0); + return (unsigned short)(pow(10.0, volume / 2000.0) * 256.0f); +} + +void Mixer_Init(unsigned long frequency) +{ + output_frequency = frequency; +} + +Mixer_Sound* Mixer_CreateSound(unsigned int frequency, const unsigned char *samples, size_t length) +{ + Mixer_Sound *sound = (Mixer_Sound*)malloc(sizeof(Mixer_Sound)); + + if (sound == NULL) + return NULL; + + // Both interpolators will read outside the array's bounds, so allocate some extra room +#ifdef LANCZOS_RESAMPLER + sound->samples = (signed char*)malloc(LANCZOS_KERNEL_RADIUS - 1 + length + LANCZOS_KERNEL_RADIUS); +#else + sound->samples = (signed char*)malloc(length + 1); +#endif + + if (sound->samples == NULL) + { + free(sound); + return NULL; + } + +#ifdef LANCZOS_RESAMPLER + // Blank samples outside the array bounds (we'll deal with the other half later) + for (size_t i = 0; i < LANCZOS_KERNEL_RADIUS - 1; ++i) + sound->samples[i] = 0; + + sound->samples += LANCZOS_KERNEL_RADIUS - 1; +#endif + + for (size_t i = 0; i < length; ++i) + sound->samples[i] = samples[i] - 0x80; // Convert from unsigned 8-bit PCM to signed + + sound->frames = length; + sound->playing = false; + sound->position = 0; + sound->position_subsample = 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; + #ifdef LANCZOS_RESAMPLER + sound->samples -= LANCZOS_KERNEL_RADIUS - 1; + #endif + free(sound->samples); + free(sound); + break; + } + } +} + +void Mixer_PlaySound(Mixer_Sound *sound, bool looping) +{ + sound->playing = true; + sound->looping = looping; + + // Fill the out-of-bounds part of the buffer with + // either blank samples or repeated samples +#ifdef LANCZOS_RESAMPLER + if (looping) + for (size_t i = 0; i < LANCZOS_KERNEL_RADIUS; ++i) + sound->samples[sound->frames + i] = sound->samples[i]; + else + for (size_t i = 0; i < LANCZOS_KERNEL_RADIUS; ++i) + sound->samples[sound->frames + i] = 0; +#else + sound->samples[sound->frames] = looping ? sound->samples[0] : 0; +#endif +} + +void Mixer_StopSound(Mixer_Sound *sound) +{ + sound->playing = false; +} + +void Mixer_RewindSound(Mixer_Sound *sound) +{ + sound->position = 0; + sound->position_subsample = 0; +} + +void Mixer_SetSoundFrequency(Mixer_Sound *sound, unsigned int frequency) +{ + sound->advance_delta = (frequency << 16) / output_frequency; +} + +void Mixer_SetSoundVolume(Mixer_Sound *sound, long volume) +{ + sound->volume = MillibelToScale(volume); + + sound->volume_l = (sound->pan_l * sound->volume) >> 8; + sound->volume_r = (sound->pan_r * sound->volume) >> 8; +} + +void Mixer_SetSoundPan(Mixer_Sound *sound, long pan) +{ + sound->pan_l = MillibelToScale(-pan); + sound->pan_r = MillibelToScale(pan); + + sound->volume_l = (sound->pan_l * sound->volume) >> 8; + sound->volume_r = (sound->pan_r * sound->volume) >> 8; +} + +// Most CPU-intensive function in the game (2/3rd CPU time consumption in my experience), so marked with ATTRIBUTE_HOT so the compiler considers it a hot spot (as it is) when optimizing +ATTRIBUTE_HOT void Mixer_MixSounds(long *stream, size_t frames_total) +{ + for (Mixer_Sound *sound = sound_list_head; sound != NULL; sound = sound->next) + { + if (sound->playing) + { + long *stream_pointer = stream; + + for (size_t frames_done = 0; frames_done < frames_total; ++frames_done) + { + #ifdef LANCZOS_RESAMPLER + // Perform Lanczos resampling + float output_sample = 0; + + for (int i = -LANCZOS_KERNEL_RADIUS + 1; i <= LANCZOS_KERNEL_RADIUS; ++i) + { + const signed char input_sample = sound->samples[sound->position + i]; + + const float kernel_input = ((float)sound->position_subsample / 0x10000) - i; + + if (kernel_input == 0.0f) + { + output_sample += input_sample; + } + else + { + const float nx = 3.14159265358979323846f * kernel_input; + const float nxa = nx / LANCZOS_KERNEL_RADIUS; + + output_sample += input_sample * (sin(nx) * sin(nxa) / (nx * nxa)); + } + } + + // Mix, and apply volume + *stream_pointer++ += (short)(output_sample * sound->volume_l); + *stream_pointer++ += (short)(output_sample * sound->volume_r); + #else + // Perform linear interpolation + const unsigned char interpolation_scale = sound->position_subsample >> 8; + + const signed char output_sample = (sound->samples[sound->position] * (0x100 - interpolation_scale) + + sound->samples[sound->position + 1] * interpolation_scale) >> 8; + + // Mix, and apply volume + *stream_pointer++ += output_sample * sound->volume_l; + *stream_pointer++ += output_sample * sound->volume_r; + #endif + + // Increment sample + const unsigned long next_position_subsample = sound->position_subsample + sound->advance_delta; + sound->position += next_position_subsample >> 16; + sound->position_subsample = next_position_subsample & 0xFFFF; + + // Stop or loop sample once it's reached its end + if (sound->position >= sound->frames) + { + if (sound->looping) + { + sound->position %= sound->frames; + } + else + { + sound->playing = false; + sound->position = 0; + sound->position_subsample = 0; + break; + } + } + } + } + } +} diff --git a/src/Backends/Audio/SoftwareMixer.h b/src/Backends/Audio/SoftwareMixer/Mixer.h similarity index 100% rename from src/Backends/Audio/SoftwareMixer.h rename to src/Backends/Audio/SoftwareMixer/Mixer.h diff --git a/src/Backends/Audio/SoftwareMixer/SDL2.cpp b/src/Backends/Audio/SoftwareMixer/SDL2.cpp new file mode 100644 index 00000000..843fd3f9 --- /dev/null +++ b/src/Backends/Audio/SoftwareMixer/SDL2.cpp @@ -0,0 +1,120 @@ +#include "Backend.h" + +#include +#include +#include + +#include "SDL.h" + +#include "../../Misc.h" + +#define MIN(a, b) ((a) < (b) ? (a) : (b)) + +static void (*parent_callback)(long *stream, size_t frames_total); + +static SDL_AudioDeviceID device_id; + +static void Callback(void *user_data, Uint8 *stream_uint8, int len) +{ + (void)user_data; + + short *stream = (short*)stream_uint8; + const size_t frames_total = len / sizeof(short) / 2; + + size_t frames_done = 0; + + while (frames_done != frames_total) + { + long mix_buffer[0x800 * 2]; // 2 because stereo + + size_t subframes = MIN(0x800, frames_total - frames_done); + + memset(mix_buffer, 0, subframes * sizeof(long) * 2); + + parent_callback(mix_buffer, subframes); + + for (size_t i = 0; i < subframes * 2; ++i) + { + if (mix_buffer[i] > 0x7FFF) + *stream++ = 0x7FFF; + else if (mix_buffer[i] < -0x7FFF) + *stream++ = -0x7FFF; + else + *stream++ = mix_buffer[i]; + } + + frames_done += subframes; + } +} + +unsigned long SoftwareMixerBackend_Init(void (*callback)(long *stream, size_t frames_total)) +{ + if (SDL_InitSubSystem(SDL_INIT_AUDIO) < 0) + { + std::string errorMessage = std::string("'SDL_InitSubSystem(SDL_INIT_AUDIO)' failed: ") + SDL_GetError(); + Backend_ShowMessageBox("Fatal error (SDL2 audio backend)", errorMessage.c_str()); + return 0; + } + + Backend_PrintInfo("Available SDL audio drivers:"); + + for (int i = 0; i < SDL_GetNumAudioDrivers(); ++i) + Backend_PrintInfo("%s", SDL_GetAudioDriver(i)); + + SDL_AudioSpec specification; + specification.freq = 48000; + specification.format = AUDIO_S16; + specification.channels = 2; + specification.samples = 0x400; // Roughly 10 milliseconds for 48000Hz + specification.callback = Callback; + specification.userdata = NULL; + + SDL_AudioSpec obtained_specification; + device_id = SDL_OpenAudioDevice(NULL, 0, &specification, &obtained_specification, SDL_AUDIO_ALLOW_FREQUENCY_CHANGE); + if (device_id == 0) + { + std::string error_message = std::string("'SDL_OpenAudioDevice' failed: ") + SDL_GetError(); + Backend_ShowMessageBox("Fatal error (SDL2 audio backend)", error_message.c_str()); + return 0; + } + + Backend_PrintInfo("Selected SDL audio driver: %s", SDL_GetCurrentAudioDriver()); + + parent_callback = callback; + + return obtained_specification.freq; +} + +void SoftwareMixerBackend_Deinit(void) +{ + SDL_CloseAudioDevice(device_id); + + SDL_QuitSubSystem(SDL_INIT_AUDIO); +} + +bool SoftwareMixerBackend_Start(void) +{ + SDL_PauseAudioDevice(device_id, 0); + + return true; +} + +void SoftwareMixerBackend_LockMixerMutex(void) +{ + SDL_LockAudioDevice(device_id); +} + +void SoftwareMixerBackend_UnlockMixerMutex(void) +{ + SDL_UnlockAudioDevice(device_id); +} + +void SoftwareMixerBackend_LockOrganyaMutex(void) +{ + SDL_LockAudioDevice(device_id); +} + +void SoftwareMixerBackend_UnlockOrganyaMutex(void) +{ + SDL_UnlockAudioDevice(device_id); +} diff --git a/src/Backends/Audio/WiiU-Software.cpp b/src/Backends/Audio/SoftwareMixer/WiiU-Software.cpp similarity index 58% rename from src/Backends/Audio/WiiU-Software.cpp rename to src/Backends/Audio/SoftwareMixer/WiiU-Software.cpp index fe6e7915..2c53024d 100644 --- a/src/Backends/Audio/WiiU-Software.cpp +++ b/src/Backends/Audio/SoftwareMixer/WiiU-Software.cpp @@ -1,4 +1,4 @@ -#include "../Audio.h" +#include "Backend.h" #include #include @@ -12,16 +12,13 @@ #include #include -#include "SoftwareMixer.h" - #define AUDIO_BUFFERS 2 // Double-buffer #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)) -static void (*organya_callback)(void); -static unsigned int organya_callback_milliseconds; +static void (*parent_callback)(long *stream, size_t frames_total); static OSMutex sound_list_mutex; static OSMutex organya_mutex; @@ -32,51 +29,6 @@ static short *stream_buffers[2]; static long *stream_buffer_long; static size_t buffer_length; -static unsigned long output_frequency; - -static void MixSoundsAndUpdateOrganya(long *stream, size_t frames_total) -{ - OSLockMutex(&organya_mutex); - - if (organya_callback_milliseconds == 0) - { - OSLockMutex(&sound_list_mutex); - Mixer_MixSounds(stream, frames_total); - OSUnlockMutex(&sound_list_mutex); - } - else - { - // Synchronise audio generation with Organya. - // In the original game, Organya ran asynchronously in a separate thread, - // firing off commands to DirectSound in realtime. To match that, we'd - // need a very low-latency buffer, otherwise we'd get mistimed instruments. - // Instead, we can just do this. - unsigned int frames_done = 0; - - while (frames_done != frames_total) - { - static unsigned long organya_countdown; - - if (organya_countdown == 0) - { - organya_countdown = (organya_callback_milliseconds * output_frequency) / 1000; // organya_timer is in milliseconds, so convert it to audio frames - organya_callback(); - } - - const unsigned int frames_to_do = MIN(organya_countdown, frames_total - frames_done); - - OSLockMutex(&sound_list_mutex); - Mixer_MixSounds(stream + frames_done * 2, frames_to_do); - OSUnlockMutex(&sound_list_mutex); - - frames_done += frames_to_do; - organya_countdown -= frames_to_do; - } - } - - OSUnlockMutex(&organya_mutex); -} - static void FrameCallback(void) { // We use a double-buffer: while the Wii U is busy playing one half of the buffer, we update the other. @@ -96,7 +48,7 @@ static void FrameCallback(void) memset(stream_buffer_long, 0, buffer_length * sizeof(long) * 2); // Fill mixer buffer - MixSoundsAndUpdateOrganya(stream_buffer_long, buffer_length); + parent_callback(stream_buffer_long, buffer_length); // Deinterlate samples, convert them to S16, and write them to the double-buffers short *left_output_buffer = &stream_buffers[0][buffer_length * last_buffer]; @@ -135,7 +87,7 @@ static void FrameCallback(void) } } -bool AudioBackend_Init(void) +unsigned long SoftwareMixerBackend_Init(void (*callback)(long *stream, size_t frames_total)) { if (!AXIsInit()) { @@ -150,9 +102,7 @@ bool AudioBackend_Init(void) OSInitMutex(&sound_list_mutex); OSInitMutex(&organya_mutex); - output_frequency = AXGetInputSamplesPerSec(); - - Mixer_Init(output_frequency); + unsigned long output_frequency = AXGetInputSamplesPerSec(); buffer_length = output_frequency / 100; // 10ms buffer @@ -210,17 +160,17 @@ bool AudioBackend_Init(void) }; AXSetVoiceOffsets(voices[i], &offs); - AXSetVoiceState(voices[i], AX_VOICE_STATE_PLAYING); - AXVoiceEnd(voices[i]); } + parent_callback = callback; + // Register the frame callback. // Apparently, this fires every 3ms - we will use // it to update the stream buffers when needed. AXRegisterAppFrameCallback(FrameCallback); - return true; + return output_frequency; } AXFreeVoice(voices[0]); @@ -237,10 +187,10 @@ bool AudioBackend_Init(void) AXQuit(); - return false; + return 0; } -void AudioBackend_Deinit(void) +void SoftwareMixerBackend_Deinit(void) { AXRegisterAppFrameCallback(NULL); @@ -255,113 +205,30 @@ void AudioBackend_Deinit(void) AXQuit(); } -AudioBackend_Sound* AudioBackend_CreateSound(unsigned int frequency, const unsigned char *samples, size_t length) +bool SoftwareMixerBackend_Start(void) { - OSLockMutex(&sound_list_mutex); + AXSetVoiceState(voices[0], AX_VOICE_STATE_PLAYING); + AXSetVoiceState(voices[1], AX_VOICE_STATE_PLAYING); - Mixer_Sound *sound = Mixer_CreateSound(frequency, samples, length); - - OSUnlockMutex(&sound_list_mutex); - - return (AudioBackend_Sound*)sound; + return true; } -void AudioBackend_DestroySound(AudioBackend_Sound *sound) +void SoftwareMixerBackend_LockMixerMutex(void) { - if (sound == NULL) - return; - OSLockMutex(&sound_list_mutex); +} - Mixer_DestroySound((Mixer_Sound*)sound); - +void SoftwareMixerBackend_UnlockMixerMutex(void) +{ OSUnlockMutex(&sound_list_mutex); } -void AudioBackend_PlaySound(AudioBackend_Sound *sound, bool looping) -{ - if (sound == NULL) - return; - - OSLockMutex(&sound_list_mutex); - - Mixer_PlaySound((Mixer_Sound*)sound, looping); - - OSUnlockMutex(&sound_list_mutex); -} - -void AudioBackend_StopSound(AudioBackend_Sound *sound) -{ - if (sound == NULL) - return; - - OSLockMutex(&sound_list_mutex); - - Mixer_StopSound((Mixer_Sound*)sound); - - OSUnlockMutex(&sound_list_mutex); -} - -void AudioBackend_RewindSound(AudioBackend_Sound *sound) -{ - if (sound == NULL) - return; - - OSLockMutex(&sound_list_mutex); - - Mixer_RewindSound((Mixer_Sound*)sound); - - OSUnlockMutex(&sound_list_mutex); -} - -void AudioBackend_SetSoundFrequency(AudioBackend_Sound *sound, unsigned int frequency) -{ - if (sound == NULL) - return; - - OSLockMutex(&sound_list_mutex); - - Mixer_SetSoundFrequency((Mixer_Sound*)sound, frequency); - - OSUnlockMutex(&sound_list_mutex); -} - -void AudioBackend_SetSoundVolume(AudioBackend_Sound *sound, long volume) -{ - if (sound == NULL) - return; - - OSLockMutex(&sound_list_mutex); - - Mixer_SetSoundVolume((Mixer_Sound*)sound, volume); - - OSUnlockMutex(&sound_list_mutex); -} - -void AudioBackend_SetSoundPan(AudioBackend_Sound *sound, long pan) -{ - if (sound == NULL) - return; - - OSLockMutex(&sound_list_mutex); - - Mixer_SetSoundPan((Mixer_Sound*)sound, pan); - - OSUnlockMutex(&sound_list_mutex); -} - -void AudioBackend_SetOrganyaCallback(void (*callback)(void)) -{ - // As far as thread-safety goes - this is guarded by - // `organya_milliseconds`, which is guarded by `organya_mutex`. - organya_callback = callback; -} - -void AudioBackend_SetOrganyaTimer(unsigned int milliseconds) +void SoftwareMixerBackend_LockOrganyaMutex(void) { OSLockMutex(&organya_mutex); +} - organya_callback_milliseconds = milliseconds; - +void SoftwareMixerBackend_UnlockOrganyaMutex(void) +{ OSUnlockMutex(&organya_mutex); } diff --git a/src/Backends/Audio/SoftwareMixer/miniaudio.cpp b/src/Backends/Audio/SoftwareMixer/miniaudio.cpp new file mode 100644 index 00000000..1e6241c1 --- /dev/null +++ b/src/Backends/Audio/SoftwareMixer/miniaudio.cpp @@ -0,0 +1,168 @@ +#include "Backend.h" + +#include +#include + +#define MINIAUDIO_IMPLEMENTATION +#define MA_NO_DECODING +#define MA_NO_ENCODING +#define MA_NO_WAV +#define MA_NO_FLAC +#define MA_NO_MP3 +#define MA_API static +#include "../../../../external/miniaudio.h" + +#include "../../Misc.h" + +#define MIN(a, b) ((a) < (b) ? (a) : (b)) + +static void (*parent_callback)(long *stream, size_t frames_total); + +static ma_context context; +static ma_device device; +static ma_mutex mutex; +static ma_mutex organya_mutex; + +static void Callback(ma_device *device, void *output_stream, const void *input_stream, ma_uint32 frames_total) +{ + (void)device; + (void)input_stream; + + short *stream = (short*)output_stream; + + size_t frames_done = 0; + + while (frames_done != frames_total) + { + long mix_buffer[0x800 * 2]; // 2 because stereo + + size_t subframes = MIN(0x800, frames_total - frames_done); + + memset(mix_buffer, 0, subframes * sizeof(long) * 2); + + parent_callback(mix_buffer, subframes); + + for (size_t i = 0; i < subframes * 2; ++i) + { + if (mix_buffer[i] > 0x7FFF) + *stream++ = 0x7FFF; + else if (mix_buffer[i] < -0x7FFF) + *stream++ = -0x7FFF; + else + *stream++ = mix_buffer[i]; + } + + frames_done += subframes; + } +} + +unsigned long SoftwareMixerBackend_Init(void (*callback)(long *stream, size_t frames_total)) +{ + ma_device_config config = ma_device_config_init(ma_device_type_playback); + config.playback.pDeviceID = NULL; + config.playback.format = ma_format_s16; + config.playback.channels = 2; + config.sampleRate = 0; // Let miniaudio decide what sample rate to use + config.dataCallback = Callback; + config.pUserData = NULL; + + ma_result return_value; + + return_value = ma_context_init(NULL, 0, NULL, &context); + + if (return_value == MA_SUCCESS) + { + return_value = ma_device_init(&context, &config, &device); + + if (return_value == MA_SUCCESS) + { + return_value = ma_mutex_init(&mutex); + + if (return_value == MA_SUCCESS) + { + return_value = ma_mutex_init(&organya_mutex); + + if (return_value == MA_SUCCESS) + { + parent_callback = callback; + + return device.sampleRate; + } + else + { + Backend_PrintError("Failed to create organya mutex: %s", ma_result_description(return_value)); + } + + ma_mutex_uninit(&mutex); + } + else + { + Backend_PrintError("Failed to create mutex: %s", ma_result_description(return_value)); + } + + ma_device_uninit(&device); + } + else + { + Backend_PrintError("Failed to initialize playback device: %s", ma_result_description(return_value)); + } + + ma_context_uninit(&context); + } + else + { + Backend_PrintError("Failed to initialize context: %s", ma_result_description(return_value)); + } + + + return 0; +} + +void SoftwareMixerBackend_Deinit(void) +{ + ma_result return_value = ma_device_stop(&device); + + if (return_value != MA_SUCCESS) + Backend_PrintError("Failed to stop playback device: %s", ma_result_description(return_value)); + + ma_mutex_uninit(&organya_mutex); + + ma_mutex_uninit(&mutex); + + ma_device_uninit(&device); + + ma_context_uninit(&context); +} + +bool SoftwareMixerBackend_Start(void) +{ + ma_result return_value = ma_device_start(&device); + + if (return_value != MA_SUCCESS) + { + Backend_PrintError("Failed to start playback device: %s", ma_result_description(return_value)); + return false; + } + + return true; +} + +void SoftwareMixerBackend_LockMixerMutex(void) +{ + ma_mutex_lock(&mutex); +} + +void SoftwareMixerBackend_UnlockMixerMutex(void) +{ + ma_mutex_unlock(&mutex); +} + +void SoftwareMixerBackend_LockOrganyaMutex(void) +{ + ma_mutex_lock(&organya_mutex); +} + +void SoftwareMixerBackend_UnlockOrganyaMutex(void) +{ + ma_mutex_unlock(&organya_mutex); +} diff --git a/src/Backends/Audio/miniaudio.cpp b/src/Backends/Audio/miniaudio.cpp deleted file mode 100644 index 8a142857..00000000 --- a/src/Backends/Audio/miniaudio.cpp +++ /dev/null @@ -1,332 +0,0 @@ -#include "../Audio.h" - -#include -#include - -#define MINIAUDIO_IMPLEMENTATION -#define MA_NO_DECODING -#define MA_NO_ENCODING -#define MA_NO_WAV -#define MA_NO_FLAC -#define MA_NO_MP3 -#define MA_API static -#include "../../../external/miniaudio.h" - -#include "../Misc.h" - -#include "SoftwareMixer.h" - -#define MIN(a, b) ((a) < (b) ? (a) : (b)) - -static ma_context context; -static ma_device device; -static ma_mutex mutex; -static ma_mutex organya_mutex; - -static unsigned long output_frequency; - -static void (*organya_callback)(void); -static unsigned int organya_callback_milliseconds; -static unsigned int organya_sleep_timer; - -static void MixSoundsAndUpdateOrganya(long *stream, size_t frames_total) -{ - ma_mutex_lock(&organya_mutex); - - if (organya_callback_milliseconds == 0) - { - ma_mutex_lock(&mutex); - Mixer_MixSounds(stream, frames_total); - ma_mutex_unlock(&mutex); - } - else - { - // Synchronise audio generation with Organya. - // In the original game, Organya ran asynchronously in a separate thread, - // firing off commands to DirectSound in realtime. To match that, we'd - // need a very low-latency buffer, otherwise we'd get mistimed instruments. - // Instead, we can just do this. - unsigned int frames_done = 0; - - // Don't process Organya when it's meant to be sleeping - const unsigned int frames_to_do = MIN(organya_sleep_timer, frames_total - frames_done); - - if (frames_to_do != 0) - { - ma_mutex_lock(&mutex); - Mixer_MixSounds(stream, frames_to_do); - ma_mutex_unlock(&mutex); - - frames_done += frames_to_do; - organya_sleep_timer -= frames_to_do; - } - - while (frames_done != frames_total) - { - static unsigned long organya_countdown; - - if (organya_countdown == 0) - { - organya_countdown = (organya_callback_milliseconds * output_frequency) / 1000; // organya_timer is in milliseconds, so convert it to audio frames - organya_callback(); - } - - const unsigned int frames_to_do = MIN(organya_countdown, frames_total - frames_done); - - 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; - } - } - - ma_mutex_unlock(&organya_mutex); -} - -static void Callback(ma_device *device, void *output_stream, const void *input_stream, ma_uint32 frames_total) -{ - (void)device; - (void)input_stream; - - short *stream = (short*)output_stream; - - size_t frames_done = 0; - - while (frames_done != frames_total) - { - long mix_buffer[0x800 * 2]; // 2 because stereo - - size_t subframes = MIN(0x800, frames_total - frames_done); - - memset(mix_buffer, 0, subframes * sizeof(long) * 2); - - MixSoundsAndUpdateOrganya(mix_buffer, subframes); - - for (size_t i = 0; i < subframes * 2; ++i) - { - if (mix_buffer[i] > 0x7FFF) - *stream++ = 0x7FFF; - else if (mix_buffer[i] < -0x7FFF) - *stream++ = -0x7FFF; - else - *stream++ = mix_buffer[i]; - } - - frames_done += subframes; - } -} - -bool AudioBackend_Init(void) -{ - ma_device_config config = ma_device_config_init(ma_device_type_playback); - config.playback.pDeviceID = NULL; - config.playback.format = ma_format_s16; - config.playback.channels = 2; - config.sampleRate = 0; // Let miniaudio decide what sample rate to use - config.dataCallback = Callback; - config.pUserData = NULL; - - ma_result return_value; - - return_value = ma_context_init(NULL, 0, NULL, &context); - - if (return_value == MA_SUCCESS) - { - return_value = ma_device_init(&context, &config, &device); - - if (return_value == MA_SUCCESS) - { - return_value = ma_mutex_init(&mutex); - - if (return_value == MA_SUCCESS) - { - return_value = ma_mutex_init(&organya_mutex); - - if (return_value == MA_SUCCESS) - { - return_value = ma_device_start(&device); - - if (return_value == MA_SUCCESS) - { - output_frequency = device.sampleRate; - - Mixer_Init(device.sampleRate); - - return true; - } - else - { - Backend_PrintError("Failed to start playback device: %s", ma_result_description(return_value)); - } - - ma_mutex_uninit(&organya_mutex); - } - else - { - Backend_PrintError("Failed to create organya mutex: %s", ma_result_description(return_value)); - } - - ma_mutex_uninit(&mutex); - } - else - { - Backend_PrintError("Failed to create mutex: %s", ma_result_description(return_value)); - } - - ma_device_uninit(&device); - } - else - { - Backend_PrintError("Failed to initialize playback device: %s", ma_result_description(return_value)); - } - - ma_context_uninit(&context); - } - else - { - Backend_PrintError("Failed to initialize context: %s", ma_result_description(return_value)); - } - - - return false; -} - -void AudioBackend_Deinit(void) -{ - ma_result return_value = ma_device_stop(&device); - - if (return_value != MA_SUCCESS) - Backend_PrintError("Failed to stop playback device: %s", ma_result_description(return_value)); - - ma_mutex_uninit(&organya_mutex); - - ma_mutex_uninit(&mutex); - - ma_device_uninit(&device); - - ma_context_uninit(&context); -} - -AudioBackend_Sound* AudioBackend_CreateSound(unsigned int frequency, const unsigned char *samples, size_t length) -{ - ma_mutex_lock(&mutex); - - Mixer_Sound *sound = Mixer_CreateSound(frequency, samples, length); - - ma_mutex_unlock(&mutex); - - return (AudioBackend_Sound*)sound; -} - -void AudioBackend_DestroySound(AudioBackend_Sound *sound) -{ - if (sound == NULL) - return; - - ma_mutex_lock(&mutex); - - Mixer_DestroySound((Mixer_Sound*)sound); - - ma_mutex_unlock(&mutex); -} - -void AudioBackend_PlaySound(AudioBackend_Sound *sound, bool looping) -{ - if (sound == NULL) - return; - - ma_mutex_lock(&mutex); - - Mixer_PlaySound((Mixer_Sound*)sound, looping); - - ma_mutex_unlock(&mutex); -} - -void AudioBackend_StopSound(AudioBackend_Sound *sound) -{ - if (sound == NULL) - return; - - ma_mutex_lock(&mutex); - - Mixer_StopSound((Mixer_Sound*)sound); - - ma_mutex_unlock(&mutex); -} - -void AudioBackend_RewindSound(AudioBackend_Sound *sound) -{ - if (sound == NULL) - return; - - ma_mutex_lock(&mutex); - - Mixer_RewindSound((Mixer_Sound*)sound); - - ma_mutex_unlock(&mutex); -} - -void AudioBackend_SetSoundFrequency(AudioBackend_Sound *sound, unsigned int frequency) -{ - if (sound == NULL) - return; - - ma_mutex_lock(&mutex); - - Mixer_SetSoundFrequency((Mixer_Sound*)sound, frequency); - - ma_mutex_unlock(&mutex); -} - -void AudioBackend_SetSoundVolume(AudioBackend_Sound *sound, long volume) -{ - if (sound == NULL) - return; - - ma_mutex_lock(&mutex); - - Mixer_SetSoundVolume((Mixer_Sound*)sound, volume); - - ma_mutex_unlock(&mutex); -} - -void AudioBackend_SetSoundPan(AudioBackend_Sound *sound, long pan) -{ - if (sound == NULL) - return; - - ma_mutex_lock(&mutex); - - Mixer_SetSoundPan((Mixer_Sound*)sound, pan); - - ma_mutex_unlock(&mutex); -} - -void AudioBackend_SetOrganyaCallback(void (*callback)(void)) -{ - ma_mutex_lock(&organya_mutex); - - organya_callback = callback; - - ma_mutex_unlock(&organya_mutex); -} - -void AudioBackend_SetOrganyaTimer(unsigned int milliseconds) -{ - ma_mutex_lock(&organya_mutex); - - organya_callback_milliseconds = milliseconds; - - ma_mutex_unlock(&organya_mutex); -} - -void AudioBackend_SleepOrganya(unsigned int milliseconds) -{ - ma_mutex_lock(&organya_mutex); - - organya_sleep_timer = (milliseconds * output_frequency) / 1000; - - ma_mutex_unlock(&organya_mutex); -}