ref: 07ee64818196cbe91528e90b51ce8172d990120a
parent: 745783a025a5d4c34b6e7a28349334e1c8007d47
author: Clownacy <[email protected]>
date: Sun Apr 19 15:03:00 EDT 2020
Add Wii U software audio mixer The hardware-accelerated one is suffering from a bizarre-ass bug that I can't fix for the life of me.
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -332,7 +332,9 @@
target_link_libraries(CSE2 PRIVATE ${CMAKE_DL_LIBS})
elseif(BACKEND_AUDIO MATCHES "WiiU")
target_sources(CSE2 PRIVATE
- "src/Backends/Audio/WiiU.cpp"
+ "src/Backends/Audio/WiiU-Software.cpp"
+ "src/Backends/Audio/SoftwareMixer.cpp"
+ "src/Backends/Audio/SoftwareMixer.h"
)
elseif(BACKEND_AUDIO MATCHES "Null")
target_sources(CSE2 PRIVATE
--- /dev/null
+++ b/src/Backends/Audio/WiiU-Software.cpp
@@ -1,0 +1,340 @@
+#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 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 unsigned long ticks_per_second;
+
+static OSMutex sound_list_mutex;
+static OSMutex organya_mutex;
+
+static AXVoice *voice;
+
+static short *stream_buffer;
+static float *stream_buffer_float;
+static size_t buffer_length;
+static unsigned long output_frequency;
+
+static unsigned long GetTicksMilliseconds(void)
+{
+ static uint64_t accumulator;
+
+ static unsigned long last_tick;
+
+ unsigned long current_tick = OSGetTick();
+
+ accumulator += current_tick - last_tick;
+
+ last_tick = current_tick;
+
+ return (accumulator * 1000) / ticks_per_second;
+}
+
+static void Callback(void *output_stream, size_t frames_total)
+{
+
+ float *stream = (float*)output_stream;
+
+ 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 int ThreadFunction(int argc, const char *argv[])
+{
+ for (;;)
+ {
+ OSTestThreadCancel();
+
+ static int last_half = 1;
+
+ unsigned int half;
+
+ AXVoiceOffsets offsets;
+
+ AXGetVoiceOffsets(voice, &offsets);
+
+ if (offsets.currentOffset > (buffer_length / 2))
+ {
+ half = 1;
+ }
+ else
+ {
+ half = 0;
+ }
+
+ if (half != last_half)
+ {
+ for (unsigned int i = 0; i < buffer_length / 2; ++i)
+ {
+ stream_buffer_float[i * 2 + 0] = 0.0f;
+ stream_buffer_float[i * 2 + 1] = 0.0f;
+ }
+
+ Callback(stream_buffer_float, buffer_length / 2);
+
+ for (unsigned int i = 0; i < buffer_length / 2; ++i)
+ {
+ float sample = stream_buffer_float[i * 2];
+
+ if (sample < -1.0f)
+ sample = -1.0f;
+ else if (sample > 1.0f)
+ sample = 1.0f;
+
+ stream_buffer[((buffer_length / 2) * last_half) + i] = sample * 32767.0f;
+ }
+
+ DCStoreRange(&stream_buffer[(buffer_length / 2) * last_half], buffer_length / 2 * sizeof(short));
+
+ last_half = half;
+ }
+ }
+
+ return 0;
+}
+
+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);
+
+ voice = AXAcquireVoice(31, NULL, NULL);
+
+ if (voice != NULL)
+ {
+ AXVoiceBegin(voice);
+
+ AXSetVoiceType(voice, 0);
+
+ AXVoiceVeData vol = {.volume = 0x8000};
+ AXSetVoiceVe(voice, &vol);
+
+ AXVoiceDeviceMixData mix_data[6];
+ memset(mix_data, 0, sizeof(mix_data));
+ mix_data[0].bus[0].volume = 0x8000;
+ mix_data[1].bus[0].volume = 0x8000;
+
+ AXSetVoiceDeviceMix(voice, AX_DEVICE_TYPE_DRC, 0, mix_data);
+ AXSetVoiceDeviceMix(voice, AX_DEVICE_TYPE_TV, 0, mix_data);
+
+ AXSetVoiceSrcRatio(voice, 1.0f); // We use the native sample rate
+ AXSetVoiceSrcType(voice, AX_VOICE_SRC_TYPE_NONE);
+
+ buffer_length = output_frequency / 100; // 10ms buffer
+
+ stream_buffer = (short*)malloc(buffer_length * sizeof(short));
+ stream_buffer_float = (float*)malloc(buffer_length * sizeof(float) * 2);
+
+ AXVoiceOffsets offs;
+ offs.dataType = AX_VOICE_FORMAT_LPCM16;
+ offs.endOffset = buffer_length;
+ offs.loopingEnabled = AX_VOICE_LOOP_ENABLED;
+ offs.loopOffset = 0;
+ offs.currentOffset = 0;
+ offs.data = stream_buffer;
+ AXSetVoiceOffsets(voice, &offs);
+
+ AXSetVoiceState(voice, AX_VOICE_STATE_PLAYING);
+
+ AXVoiceEnd(voice);
+
+ //AXRegisterAppFrameCallback(FrameCallback);
+
+ OSRunThread(OSGetDefaultThread(0), ThreadFunction, 0, NULL);
+
+ return true;
+ }
+
+ return false;
+}
+
+void AudioBackend_Deinit(void)
+{
+ OSCancelThread(OSGetDefaultThread(0));
+
+ OSJoinThread(OSGetDefaultThread(0), NULL);
+
+ AXFreeVoice(voice);
+
+ 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);
+}