shithub: cstory

Download patch

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);
-}