ref: be6f46fabdb59659a051369053d06991f066b556
parent: ab09dc67ebe3f87e6d9879979e49fe660fecf006
author: Clownacy <Clownacy@users.noreply.github.com>
date: Thu Sep 3 15:00:24 EDT 2020
Refactor audio software mixer Now the various backends have far less duplicate code, and are part of a separate backend system specifically for the software mixer. Now, any modifications to the MixSoundsAndUpdateOrganya function will apply to all backends, instead of needing to manually be applied to each one.
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -316,15 +316,19 @@
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-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
--- a/src/Backends/Audio/SDL2.cpp
+++ /dev/null
@@ -1,249 +1,0 @@
-#include "../Audio.h"
-
-#include <stddef.h>
-#include <string.h>
-#include <string>
-
-#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);
-}
--- a/src/Backends/Audio/SoftwareMixer.cpp
+++ b/src/Backends/Audio/SoftwareMixer.cpp
@@ -1,236 +1,214 @@
-#include "SoftwareMixer.h"
+#include "../Audio.h"
-#include <math.h>
#include <stddef.h>
-#include <stdlib.h>
-#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
+static unsigned long output_frequency;
-struct Mixer_Sound
+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)
{
- 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
+ SoftwareMixerBackend_LockOrganyaMutex();
- struct Mixer_Sound *next;
-};
+ if (organya_callback_milliseconds == 0)
+ {
+ SoftwareMixerBackend_LockMixerMutex();
+ Mixer_MixSounds(stream, frames_total);
+ SoftwareMixerBackend_UnlockMixerMutex();
+ }
+ 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;
-static Mixer_Sound *sound_list_head;
+ // 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);
-static unsigned long output_frequency;
+ if (frames_to_do != 0)
+ {
+ SoftwareMixerBackend_LockMixerMutex();
+ Mixer_MixSounds(stream, frames_to_do);
+ SoftwareMixerBackend_UnlockMixerMutex();
-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);
-}
+ frames_done += frames_to_do;
+ organya_sleep_timer -= frames_to_do;
+ }
-void Mixer_Init(unsigned long frequency)
-{
- output_frequency = frequency;
-}
+ while (frames_done != frames_total)
+ {
+ static unsigned long organya_countdown;
-Mixer_Sound* Mixer_CreateSound(unsigned int frequency, const unsigned char *samples, size_t length)
-{
- Mixer_Sound *sound = (Mixer_Sound*)malloc(sizeof(Mixer_Sound));
+ 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();
+ }
- if (sound == NULL)
- return NULL;
+ const unsigned int frames_to_do = MIN(organya_countdown, frames_total - frames_done);
- // 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
+ SoftwareMixerBackend_LockMixerMutex();
+ Mixer_MixSounds(stream + frames_done * 2, frames_to_do);
+ SoftwareMixerBackend_UnlockMixerMutex();
- if (sound->samples == NULL)
- {
- free(sound);
- return NULL;
+ frames_done += frames_to_do;
+ organya_countdown -= frames_to_do;
+ }
}
-#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;
+ SoftwareMixerBackend_UnlockOrganyaMutex();
+}
- sound->samples += LANCZOS_KERNEL_RADIUS - 1;
-#endif
+bool AudioBackend_Init(void)
+{
+ output_frequency = SoftwareMixerBackend_Init(MixSoundsAndUpdateOrganya);
- for (size_t i = 0; i < length; ++i)
- sound->samples[i] = samples[i] - 0x80; // Convert from unsigned 8-bit PCM to signed
+ if (output_frequency != 0)
+ {
+ Mixer_Init(output_frequency);
- sound->frames = length;
- sound->playing = false;
- sound->position = 0;
- sound->position_subsample = 0;
+ if (SoftwareMixerBackend_Start())
+ return true;
- Mixer_SetSoundFrequency(sound, frequency);
- Mixer_SetSoundVolume(sound, 0);
- Mixer_SetSoundPan(sound, 0);
+ SoftwareMixerBackend_Deinit();
+ }
- sound->next = sound_list_head;
- sound_list_head = sound;
+ return false;
+}
- return sound;
+void AudioBackend_Deinit(void)
+{
+ return SoftwareMixerBackend_Deinit();
}
-void Mixer_DestroySound(Mixer_Sound *sound)
+AudioBackend_Sound* AudioBackend_CreateSound(unsigned int frequency, const unsigned char *samples, size_t length)
{
- 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;
- }
- }
+ SoftwareMixerBackend_LockMixerMutex();
+
+ Mixer_Sound *sound = Mixer_CreateSound(frequency, samples, length);
+
+ SoftwareMixerBackend_UnlockMixerMutex();
+
+ return (AudioBackend_Sound*)sound;
}
-void Mixer_PlaySound(Mixer_Sound *sound, bool looping)
+void AudioBackend_DestroySound(AudioBackend_Sound *sound)
{
- sound->playing = true;
- sound->looping = looping;
+ if (sound == NULL)
+ return;
- // 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
+ SoftwareMixerBackend_LockMixerMutex();
+
+ Mixer_DestroySound((Mixer_Sound*)sound);
+
+ SoftwareMixerBackend_UnlockMixerMutex();
}
-void Mixer_StopSound(Mixer_Sound *sound)
+void AudioBackend_PlaySound(AudioBackend_Sound *sound, bool looping)
{
- sound->playing = false;
+ if (sound == NULL)
+ return;
+
+ SoftwareMixerBackend_LockMixerMutex();
+
+ Mixer_PlaySound((Mixer_Sound*)sound, looping);
+
+ SoftwareMixerBackend_UnlockMixerMutex();
}
-void Mixer_RewindSound(Mixer_Sound *sound)
+void AudioBackend_StopSound(AudioBackend_Sound *sound)
{
- sound->position = 0;
- sound->position_subsample = 0;
+ if (sound == NULL)
+ return;
+
+ SoftwareMixerBackend_LockMixerMutex();
+
+ Mixer_StopSound((Mixer_Sound*)sound);
+
+ SoftwareMixerBackend_UnlockMixerMutex();
}
-void Mixer_SetSoundFrequency(Mixer_Sound *sound, unsigned int frequency)
+void AudioBackend_RewindSound(AudioBackend_Sound *sound)
{
- sound->advance_delta = (frequency << 16) / output_frequency;
+ if (sound == NULL)
+ return;
+
+ SoftwareMixerBackend_LockMixerMutex();
+
+ Mixer_RewindSound((Mixer_Sound*)sound);
+
+ SoftwareMixerBackend_UnlockMixerMutex();
}
-void Mixer_SetSoundVolume(Mixer_Sound *sound, long volume)
+void AudioBackend_SetSoundFrequency(AudioBackend_Sound *sound, unsigned int frequency)
{
- sound->volume = MillibelToScale(volume);
+ if (sound == NULL)
+ return;
- sound->volume_l = (sound->pan_l * sound->volume) >> 8;
- sound->volume_r = (sound->pan_r * sound->volume) >> 8;
+ SoftwareMixerBackend_LockMixerMutex();
+
+ Mixer_SetSoundFrequency((Mixer_Sound*)sound, frequency);
+
+ SoftwareMixerBackend_UnlockMixerMutex();
}
-void Mixer_SetSoundPan(Mixer_Sound *sound, long pan)
+void AudioBackend_SetSoundVolume(AudioBackend_Sound *sound, long volume)
{
- sound->pan_l = MillibelToScale(-pan);
- sound->pan_r = MillibelToScale(pan);
+ if (sound == NULL)
+ return;
- sound->volume_l = (sound->pan_l * sound->volume) >> 8;
- sound->volume_r = (sound->pan_r * sound->volume) >> 8;
+ SoftwareMixerBackend_LockMixerMutex();
+
+ Mixer_SetSoundVolume((Mixer_Sound*)sound, volume);
+
+ SoftwareMixerBackend_UnlockMixerMutex();
}
-// 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)
+void AudioBackend_SetSoundPan(AudioBackend_Sound *sound, long pan)
{
- for (Mixer_Sound *sound = sound_list_head; sound != NULL; sound = sound->next)
- {
- if (sound->playing)
- {
- long *stream_pointer = stream;
+ if (sound == NULL)
+ return;
- for (size_t frames_done = 0; frames_done < frames_total; ++frames_done)
- {
- #ifdef LANCZOS_RESAMPLER
- // Perform Lanczos resampling
- float output_sample = 0;
+ SoftwareMixerBackend_LockMixerMutex();
- for (int i = -LANCZOS_KERNEL_RADIUS + 1; i <= LANCZOS_KERNEL_RADIUS; ++i)
- {
- const signed char input_sample = sound->samples[sound->position + i];
+ Mixer_SetSoundPan((Mixer_Sound*)sound, pan);
- const float kernel_input = ((float)sound->position_subsample / 0x10000) - i;
+ SoftwareMixerBackend_UnlockMixerMutex();
+}
- if (kernel_input == 0.0f)
- {
- output_sample += input_sample;
- }
- else
- {
- const float nx = 3.14159265358979323846f * kernel_input;
- const float nxa = nx / LANCZOS_KERNEL_RADIUS;
+void AudioBackend_SetOrganyaCallback(void (*callback)(void))
+{
+ SoftwareMixerBackend_LockOrganyaMutex();
- output_sample += input_sample * (sin(nx) * sin(nxa) / (nx * nxa));
- }
- }
+ organya_callback = callback;
- // 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;
+ SoftwareMixerBackend_UnlockOrganyaMutex();
+}
- const signed char output_sample = (sound->samples[sound->position] * (0x100 - interpolation_scale)
- + sound->samples[sound->position + 1] * interpolation_scale) >> 8;
+void AudioBackend_SetOrganyaTimer(unsigned int milliseconds)
+{
+ SoftwareMixerBackend_LockOrganyaMutex();
- // Mix, and apply volume
- *stream_pointer++ += output_sample * sound->volume_l;
- *stream_pointer++ += output_sample * sound->volume_r;
- #endif
+ organya_callback_milliseconds = milliseconds;
- // 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;
+ SoftwareMixerBackend_UnlockOrganyaMutex();
+}
- // 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;
- }
- }
- }
- }
- }
+void AudioBackend_SleepOrganya(unsigned int milliseconds)
+{
+ SoftwareMixerBackend_LockOrganyaMutex();
+
+ organya_sleep_timer = (milliseconds * output_frequency) / 1000;
+
+ SoftwareMixerBackend_UnlockOrganyaMutex();
}
--- a/src/Backends/Audio/SoftwareMixer.h
+++ /dev/null
@@ -1,16 +1,0 @@
-#pragma once
-
-#include <stddef.h>
-
-typedef struct Mixer_Sound Mixer_Sound;
-
-void Mixer_Init(unsigned long frequency);
-Mixer_Sound* Mixer_CreateSound(unsigned int frequency, const unsigned char *samples, size_t length);
-void Mixer_DestroySound(Mixer_Sound *sound);
-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(long *stream, size_t frames_total);
--- /dev/null
+++ b/src/Backends/Audio/SoftwareMixer/Backend.h
@@ -1,0 +1,14 @@
+#pragma once
+
+#include <stddef.h>
+
+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);
--- /dev/null
+++ b/src/Backends/Audio/SoftwareMixer/Mixer.cpp
@@ -1,0 +1,236 @@
+#include "Mixer.h"
+
+#include <math.h>
+#include <stddef.h>
+#include <stdlib.h>
+
+#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;
+ }
+ }
+ }
+ }
+ }
+}
--- /dev/null
+++ b/src/Backends/Audio/SoftwareMixer/Mixer.h
@@ -1,0 +1,16 @@
+#pragma once
+
+#include <stddef.h>
+
+typedef struct Mixer_Sound Mixer_Sound;
+
+void Mixer_Init(unsigned long frequency);
+Mixer_Sound* Mixer_CreateSound(unsigned int frequency, const unsigned char *samples, size_t length);
+void Mixer_DestroySound(Mixer_Sound *sound);
+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(long *stream, size_t frames_total);
--- /dev/null
+++ b/src/Backends/Audio/SoftwareMixer/SDL2.cpp
@@ -1,0 +1,120 @@
+#include "Backend.h"
+
+#include <stddef.h>
+#include <string.h>
+#include <string>
+
+#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);
+}
--- /dev/null
+++ b/src/Backends/Audio/SoftwareMixer/WiiU-Software.cpp
@@ -1,0 +1,234 @@
+#include "Backend.h"
+
+#include <math.h>
+#include <stddef.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <coreinit/cache.h>
+#include <coreinit/mutex.h>
+#include <coreinit/thread.h>
+#include <sndcore2/core.h>
+#include <sndcore2/voice.h>
+#include <sndcore2/drcvs.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 (*parent_callback)(long *stream, size_t frames_total);
+
+static OSMutex sound_list_mutex;
+static OSMutex organya_mutex;
+
+static AXVoice *voices[2];
+
+static short *stream_buffers[2];
+static long *stream_buffer_long;
+static size_t buffer_length;
+
+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.
+ // The buffer is 10ms long in total, and this function runs every 3ms.
+
+ // Just assume both voices are in-sync, and only check the first one
+ AXVoiceOffsets offsets;
+ AXGetVoiceOffsets(voices[0], &offsets);
+
+ unsigned int current_buffer = offsets.currentOffset / buffer_length;
+
+ static unsigned int last_buffer = 1;
+
+ if (current_buffer != last_buffer)
+ {
+ // Clear the mixer buffer
+ memset(stream_buffer_long, 0, buffer_length * sizeof(long) * 2);
+
+ // Fill mixer buffer
+ 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];
+ short *right_output_buffer = &stream_buffers[1][buffer_length * last_buffer];
+
+ long *mixer_buffer_pointer = stream_buffer_long;
+ short *left_output_buffer_pointer = left_output_buffer;
+ short *right_output_buffer_pointer = right_output_buffer;
+
+ for (unsigned int i = 0; i < buffer_length; ++i)
+ {
+ const long left_sample = *mixer_buffer_pointer++;
+ const long right_sample = *mixer_buffer_pointer++;
+
+ // Clamp samples to sane limits, convert to S16, and store in double-buffers
+ if (left_sample > 0x7FFF)
+ *left_output_buffer_pointer++ = 0x7FFF;
+ else if (left_sample < -0x7FFF)
+ *left_output_buffer_pointer++ = -0x7FFF;
+ else
+ *left_output_buffer_pointer++ = (short)left_sample;
+
+ if (right_sample > 0x7FFF)
+ *right_output_buffer_pointer++ = 0x7FFF;
+ else if (right_sample < -0x7FFF)
+ *right_output_buffer_pointer++ = -0x7FFF;
+ else
+ *right_output_buffer_pointer++ = (short)right_sample;
+ }
+
+ // Make sure the sound hardware can see our data
+ DCStoreRange(left_output_buffer, buffer_length * sizeof(short));
+ DCStoreRange(right_output_buffer, buffer_length * sizeof(short));
+
+ last_buffer = current_buffer;
+ }
+}
+
+unsigned long SoftwareMixerBackend_Init(void (*callback)(long *stream, size_t frames_total))
+{
+ if (!AXIsInit())
+ {
+ AXInitParams initparams = {
+ .renderer = AX_INIT_RENDERER_48KHZ,
+ .pipeline = AX_INIT_PIPELINE_SINGLE,
+ };
+
+ AXInitWithParams(&initparams);
+ }
+
+ OSInitMutex(&sound_list_mutex);
+ OSInitMutex(&organya_mutex);
+
+ unsigned long output_frequency = AXGetInputSamplesPerSec();
+
+ buffer_length = output_frequency / 100; // 10ms buffer
+
+ // Create and initialise two 'voices': each one will stream its own
+ // audio - one for the left speaker, and one for the right.
+
+ // The software-mixer outputs interlaced samples into a buffer of `long`s,
+ // so create a buffer for it here.
+ stream_buffer_long = (long*)malloc(buffer_length * sizeof(long) * 2); // `* 2` because it's an interlaced stereo buffer
+
+ if (stream_buffer_long != NULL)
+ {
+ stream_buffers[0] = (short*)malloc(buffer_length * sizeof(short) * AUDIO_BUFFERS);
+
+ if (stream_buffers[0] != NULL)
+ {
+ stream_buffers[1] = (short*)malloc(buffer_length * sizeof(short) * AUDIO_BUFFERS);
+
+ if (stream_buffers[1] != NULL)
+ {
+ voices[0] = AXAcquireVoice(31, NULL, NULL);
+
+ if (voices[0] != NULL)
+ {
+ voices[1] = AXAcquireVoice(31, NULL, NULL);
+
+ if (voices[1] != NULL)
+ {
+ for (unsigned int i = 0; i < 2; ++i)
+ {
+ AXVoiceBegin(voices[i]);
+
+ AXSetVoiceType(voices[i], 0);
+
+ AXVoiceVeData vol = {.volume = 0x8000};
+ AXSetVoiceVe(voices[i], &vol);
+
+ AXVoiceDeviceMixData mix_data[6];
+ memset(mix_data, 0, sizeof(mix_data));
+ mix_data[i].bus[0].volume = 0x8000; // Voice 1 goes on the left speaker - voice 2 goes on the right speaker
+
+ AXSetVoiceDeviceMix(voices[i], AX_DEVICE_TYPE_DRC, 0, mix_data);
+ AXSetVoiceDeviceMix(voices[i], AX_DEVICE_TYPE_TV, 0, mix_data);
+
+ AXSetVoiceSrcRatio(voices[i], 1.0f); // We use the native sample rate
+ AXSetVoiceSrcType(voices[i], AX_VOICE_SRC_TYPE_NONE);
+
+ AXVoiceOffsets offs = {
+ .dataType = AX_VOICE_FORMAT_LPCM16,
+ .loopingEnabled = AX_VOICE_LOOP_ENABLED,
+ .loopOffset = 0,
+ .endOffset = (buffer_length * AUDIO_BUFFERS) - 1, // -1 or else you'll get popping!
+ .currentOffset = 0,
+ .data = stream_buffers[i]
+ };
+ AXSetVoiceOffsets(voices[i], &offs);
+
+ 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 output_frequency;
+ }
+
+ AXFreeVoice(voices[0]);
+ }
+
+ free(stream_buffers[1]);
+ }
+
+ free(stream_buffers[0]);
+ }
+
+ free(stream_buffer_long);
+ }
+
+ AXQuit();
+
+ return 0;
+}
+
+void SoftwareMixerBackend_Deinit(void)
+{
+ AXRegisterAppFrameCallback(NULL);
+
+ AXFreeVoice(voices[1]);
+ AXFreeVoice(voices[0]);
+
+ free(stream_buffers[1]);
+ free(stream_buffers[0]);
+
+ free(stream_buffer_long);
+
+ AXQuit();
+}
+
+bool SoftwareMixerBackend_Start(void)
+{
+ AXSetVoiceState(voices[0], AX_VOICE_STATE_PLAYING);
+ AXSetVoiceState(voices[1], AX_VOICE_STATE_PLAYING);
+
+ return true;
+}
+
+void SoftwareMixerBackend_LockMixerMutex(void)
+{
+ OSLockMutex(&sound_list_mutex);
+}
+
+void SoftwareMixerBackend_UnlockMixerMutex(void)
+{
+ OSUnlockMutex(&sound_list_mutex);
+}
+
+void SoftwareMixerBackend_LockOrganyaMutex(void)
+{
+ OSLockMutex(&organya_mutex);
+}
+
+void SoftwareMixerBackend_UnlockOrganyaMutex(void)
+{
+ OSUnlockMutex(&organya_mutex);
+}
--- /dev/null
+++ b/src/Backends/Audio/SoftwareMixer/miniaudio.cpp
@@ -1,0 +1,168 @@
+#include "Backend.h"
+
+#include <stddef.h>
+#include <string.h>
+
+#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);
+}
--- a/src/Backends/Audio/WiiU-Software.cpp
+++ /dev/null
@@ -1,367 +1,0 @@
-#include "../Audio.h"
-
-#include <math.h>
-#include <stddef.h>
-#include <stdlib.h>
-#include <string.h>
-
-#include <coreinit/cache.h>
-#include <coreinit/mutex.h>
-#include <coreinit/thread.h>
-#include <sndcore2/core.h>
-#include <sndcore2/voice.h>
-#include <sndcore2/drcvs.h>
-
-#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 OSMutex sound_list_mutex;
-static OSMutex organya_mutex;
-
-static AXVoice *voices[2];
-
-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.
- // The buffer is 10ms long in total, and this function runs every 3ms.
-
- // Just assume both voices are in-sync, and only check the first one
- AXVoiceOffsets offsets;
- AXGetVoiceOffsets(voices[0], &offsets);
-
- unsigned int current_buffer = offsets.currentOffset / buffer_length;
-
- static unsigned int last_buffer = 1;
-
- if (current_buffer != last_buffer)
- {
- // Clear the mixer buffer
- memset(stream_buffer_long, 0, buffer_length * sizeof(long) * 2);
-
- // Fill mixer buffer
- MixSoundsAndUpdateOrganya(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];
- short *right_output_buffer = &stream_buffers[1][buffer_length * last_buffer];
-
- long *mixer_buffer_pointer = stream_buffer_long;
- short *left_output_buffer_pointer = left_output_buffer;
- short *right_output_buffer_pointer = right_output_buffer;
-
- for (unsigned int i = 0; i < buffer_length; ++i)
- {
- const long left_sample = *mixer_buffer_pointer++;
- const long right_sample = *mixer_buffer_pointer++;
-
- // Clamp samples to sane limits, convert to S16, and store in double-buffers
- if (left_sample > 0x7FFF)
- *left_output_buffer_pointer++ = 0x7FFF;
- else if (left_sample < -0x7FFF)
- *left_output_buffer_pointer++ = -0x7FFF;
- else
- *left_output_buffer_pointer++ = (short)left_sample;
-
- if (right_sample > 0x7FFF)
- *right_output_buffer_pointer++ = 0x7FFF;
- else if (right_sample < -0x7FFF)
- *right_output_buffer_pointer++ = -0x7FFF;
- else
- *right_output_buffer_pointer++ = (short)right_sample;
- }
-
- // Make sure the sound hardware can see our data
- DCStoreRange(left_output_buffer, buffer_length * sizeof(short));
- DCStoreRange(right_output_buffer, buffer_length * sizeof(short));
-
- last_buffer = current_buffer;
- }
-}
-
-bool AudioBackend_Init(void)
-{
- if (!AXIsInit())
- {
- AXInitParams initparams = {
- .renderer = AX_INIT_RENDERER_48KHZ,
- .pipeline = AX_INIT_PIPELINE_SINGLE,
- };
-
- AXInitWithParams(&initparams);
- }
-
- OSInitMutex(&sound_list_mutex);
- OSInitMutex(&organya_mutex);
-
- output_frequency = AXGetInputSamplesPerSec();
-
- Mixer_Init(output_frequency);
-
- buffer_length = output_frequency / 100; // 10ms buffer
-
- // Create and initialise two 'voices': each one will stream its own
- // audio - one for the left speaker, and one for the right.
-
- // The software-mixer outputs interlaced samples into a buffer of `long`s,
- // so create a buffer for it here.
- stream_buffer_long = (long*)malloc(buffer_length * sizeof(long) * 2); // `* 2` because it's an interlaced stereo buffer
-
- if (stream_buffer_long != NULL)
- {
- stream_buffers[0] = (short*)malloc(buffer_length * sizeof(short) * AUDIO_BUFFERS);
-
- if (stream_buffers[0] != NULL)
- {
- stream_buffers[1] = (short*)malloc(buffer_length * sizeof(short) * AUDIO_BUFFERS);
-
- if (stream_buffers[1] != NULL)
- {
- voices[0] = AXAcquireVoice(31, NULL, NULL);
-
- if (voices[0] != NULL)
- {
- voices[1] = AXAcquireVoice(31, NULL, NULL);
-
- if (voices[1] != NULL)
- {
- for (unsigned int i = 0; i < 2; ++i)
- {
- AXVoiceBegin(voices[i]);
-
- AXSetVoiceType(voices[i], 0);
-
- AXVoiceVeData vol = {.volume = 0x8000};
- AXSetVoiceVe(voices[i], &vol);
-
- AXVoiceDeviceMixData mix_data[6];
- memset(mix_data, 0, sizeof(mix_data));
- mix_data[i].bus[0].volume = 0x8000; // Voice 1 goes on the left speaker - voice 2 goes on the right speaker
-
- AXSetVoiceDeviceMix(voices[i], AX_DEVICE_TYPE_DRC, 0, mix_data);
- AXSetVoiceDeviceMix(voices[i], AX_DEVICE_TYPE_TV, 0, mix_data);
-
- AXSetVoiceSrcRatio(voices[i], 1.0f); // We use the native sample rate
- AXSetVoiceSrcType(voices[i], AX_VOICE_SRC_TYPE_NONE);
-
- AXVoiceOffsets offs = {
- .dataType = AX_VOICE_FORMAT_LPCM16,
- .loopingEnabled = AX_VOICE_LOOP_ENABLED,
- .loopOffset = 0,
- .endOffset = (buffer_length * AUDIO_BUFFERS) - 1, // -1 or else you'll get popping!
- .currentOffset = 0,
- .data = stream_buffers[i]
- };
- AXSetVoiceOffsets(voices[i], &offs);
-
- AXSetVoiceState(voices[i], AX_VOICE_STATE_PLAYING);
-
- AXVoiceEnd(voices[i]);
- }
-
- // Register the frame callback.
- // Apparently, this fires every 3ms - we will use
- // it to update the stream buffers when needed.
- AXRegisterAppFrameCallback(FrameCallback);
-
- return true;
- }
-
- AXFreeVoice(voices[0]);
- }
-
- free(stream_buffers[1]);
- }
-
- free(stream_buffers[0]);
- }
-
- free(stream_buffer_long);
- }
-
- AXQuit();
-
- return false;
-}
-
-void AudioBackend_Deinit(void)
-{
- AXRegisterAppFrameCallback(NULL);
-
- AXFreeVoice(voices[1]);
- AXFreeVoice(voices[0]);
-
- free(stream_buffers[1]);
- free(stream_buffers[0]);
-
- free(stream_buffer_long);
-
- AXQuit();
-}
-
-AudioBackend_Sound* AudioBackend_CreateSound(unsigned int frequency, const unsigned char *samples, size_t length)
-{
- OSLockMutex(&sound_list_mutex);
-
- Mixer_Sound *sound = Mixer_CreateSound(frequency, samples, length);
-
- OSUnlockMutex(&sound_list_mutex);
-
- return (AudioBackend_Sound*)sound;
-}
-
-void AudioBackend_DestroySound(AudioBackend_Sound *sound)
-{
- if (sound == NULL)
- return;
-
- OSLockMutex(&sound_list_mutex);
-
- Mixer_DestroySound((Mixer_Sound*)sound);
-
- 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)
-{
- OSLockMutex(&organya_mutex);
-
- organya_callback_milliseconds = milliseconds;
-
- OSUnlockMutex(&organya_mutex);
-}
--- a/src/Backends/Audio/miniaudio.cpp
+++ /dev/null
@@ -1,332 +1,0 @@
-#include "../Audio.h"
-
-#include <stddef.h>
-#include <string.h>
-
-#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);
-}