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.

diff: cannot open b/src/Backends/Audio/SoftwareMixer//null: file does not exist: 'b/src/Backends/Audio/SoftwareMixer//null'
--- 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);
-}