diff --git a/Makefile.wiiu b/Makefile.wiiu index df1d16f8b..9e5542c60 100644 --- a/Makefile.wiiu +++ b/Makefile.wiiu @@ -19,7 +19,7 @@ SOURCES = \ src/*.c \ src/atomic/*.c \ src/audio/*.c \ - src/audio/dummy/*.c \ + src/audio/wiiu/*.c \ src/cpuinfo/*.c \ src/events/*.c \ src/file/*.c \ diff --git a/include/SDL_config_wiiu.h b/include/SDL_config_wiiu.h index ca417e5a9..3acb693cf 100644 --- a/include/SDL_config_wiiu.h +++ b/include/SDL_config_wiiu.h @@ -136,8 +136,8 @@ /* Wii U joystick driver (src/joystick/wiiu/\*.c) */ #define SDL_JOYSTICK_WIIU 1 -/* Enable the stub audio driver (src/audio/dummy/\*.c) */ -#define SDL_AUDIO_DRIVER_DUMMY 1 +/* Enable the wiiu audio driver (src/audio/wiiu/\*.c) */ +#define SDL_AUDIO_DRIVER_WIIU 1 /* Wii U video dirver */ #define SDL_VIDEO_DRIVER_WIIU 1 diff --git a/src/audio/SDL_audio.c b/src/audio/SDL_audio.c index f4999f136..5ea0f1835 100644 --- a/src/audio/SDL_audio.c +++ b/src/audio/SDL_audio.c @@ -101,6 +101,9 @@ static const AudioBootStrap *const bootstrap[] = { #if SDL_AUDIO_DRIVER_JACK &JACK_bootstrap, #endif +#if SDL_AUDIO_DRIVER_WIIU + &WIIUAUDIO_bootstrap, +#endif #if SDL_AUDIO_DRIVER_DISK &DISKAUDIO_bootstrap, #endif diff --git a/src/audio/SDL_sysaudio.h b/src/audio/SDL_sysaudio.h index 579dea5b4..4bb8ada5b 100644 --- a/src/audio/SDL_sysaudio.h +++ b/src/audio/SDL_sysaudio.h @@ -207,6 +207,7 @@ extern AudioBootStrap FUSIONSOUND_bootstrap; extern AudioBootStrap ANDROIDAUDIO_bootstrap; extern AudioBootStrap PSPAUDIO_bootstrap; extern AudioBootStrap EMSCRIPTENAUDIO_bootstrap; +extern AudioBootStrap WIIUAUDIO_bootstrap; #endif /* SDL_sysaudio_h_ */ diff --git a/src/audio/wiiu/SDL_wiiuaudio.c b/src/audio/wiiu/SDL_wiiuaudio.c new file mode 100644 index 000000000..410f9410e --- /dev/null +++ b/src/audio/wiiu/SDL_wiiuaudio.c @@ -0,0 +1,318 @@ +/* + Simple DirectMedia Layer + Copyright (C) 2018-2018 Ash Logan + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ +#include "../../SDL_internal.h" + +#if SDL_AUDIO_DRIVER_WIIU + +#include + +#include "SDL_audio.h" +#include "SDL_error.h" +#include "SDL_timer.h" +#include "../SDL_audio_c.h" +#include "../SDL_audiodev_c.h" +#include "../SDL_sysaudio.h" +#include "SDL_wiiuaudio.h" + +#include +#include +#include +#include +#include +#include + +#define WIIUAUDIO_DRIVER_NAME "wiiu" + +#define AX_MAIN_AFFINITY OS_THREAD_ATTRIB_AFFINITY_CPU1 + +static void _WIIUAUDIO_framecallback(); +static SDL_AudioDevice* cb_this; +#define cb_hidden cb_this->hidden + +/* Some helpers for AX-related math */ +/* Absolute address to an AXVoiceOffsets offset */ +#define calc_ax_offset(offs, addr) (((void*)addr - offs.data) \ + / (offs.dataType == AX_VOICE_FORMAT_LPCM8 ? 1 : 2)) + +/* AX will play the offset *at* endOffset, so we need to subtract 1 sample for + end offsets */ +#define calc_buf_endaddr(offs, buf, sz) (buf + sz \ + - (offs.dataType == AX_VOICE_FORMAT_LPCM8 ? 1 : 2)) + +/* +1, but never goes above NUM_BUFFERS */ +#define next_id(id) (id + 1) % NUM_BUFFERS + +static int WIIUAUDIO_OpenDevice(_THIS, void* handle, const char* devname, int iscapture) { + this->hidden = (struct SDL_PrivateAudioData*)SDL_malloc(sizeof(*this->hidden)); + if (this->hidden == NULL) return SDL_OutOfMemory(); + SDL_zerop(this->hidden); + +/* We *must not* change cores when setting stuff up */ + uint32_t old_affinity = OSGetThreadAffinity(OSGetCurrentThread()); + OSSetThreadAffinity(OSGetCurrentThread(), AX_MAIN_AFFINITY); + +/* Take a quick aside to init the wiiu audio */ + if (!AXIsInit()) { + /* Init the AX audio engine */ + AXInitParams initparams = { + .renderer = AX_INIT_RENDERER_48KHZ, + .pipeline = AX_INIT_PIPELINE_SINGLE, + }; + AXInitWithParams(&initparams); + } else printf("DEBUG: AX already up?\n"); + +/* Get a voice, top priority (we only need one) */ + this->hidden->voice = AXAcquireVoice(31, NULL, NULL); + if (!this->hidden->voice) { + AXQuit(); + printf("DEBUG: couldn't get voice\n"); + return SDL_OutOfMemory(); + } + +/* Start messing with it */ + AXVoiceBegin(this->hidden->voice); + + AXSetVoiceType(this->hidden->voice, 0); + +/* Set the voice's volume */ + AXVoiceVeData vol = { + .volume = 0x8000, + }; + AXSetVoiceVe(this->hidden->voice, &vol); + + AXVoiceDeviceMixData drcmix = { + .bus = { + { .volume = 0x8000 }, //bus 0 + { .volume = 0x0000 }, //bus 1 + { .volume = 0x0000 }, //bus 2 + { .volume = 0x0000 }, //bus 3 + } + }; + AXSetVoiceDeviceMix(this->hidden->voice, AX_DEVICE_TYPE_DRC, 0, &drcmix); + +/* Set the samplerate conversion ratio + / */ + float srcratio = (float)this->spec.freq / (float)AXGetInputSamplesPerSec(); + AXSetVoiceSrcRatio(this->hidden->voice, srcratio); + AXSetVoiceSrcType(this->hidden->voice, AX_VOICE_SRC_TYPE_LINEAR); + +/* Okay, we're good */ + AXVoiceEnd(this->hidden->voice); + +/* Force wiiu-compatible audio formats. + TODO verify - unsigned or signed? */ + switch (SDL_AUDIO_BITSIZE(this->spec.format)) { + case 8: + /* TODO 8-bit audio sounds broken */ + /*this->spec.format = AUDIO_S8; + break;*/ + case 16: + default: + this->spec.format = AUDIO_S16MSB; + break; + } + + //TODO + this->spec.channels = 1; + //TODO maybe round this->spec.samples up even when >? + //maybe even force at least 2* so we get more frame callbacks to think + if (this->spec.samples < AXGetInputSamplesPerFrame()) { + this->spec.samples = AXGetInputSamplesPerFrame(); + } + +/* We changed channels and samples, so recalculate the spec */ + SDL_CalculateAudioSpec(&this->spec); + +/* Allocate buffers for double-buffering and samples */ + for (int i = 0; i < NUM_BUFFERS; i++) { + this->hidden->mixbufs[i] = SDL_malloc(this->spec.size); + if (this->hidden->mixbufs[i] == NULL) { + AXQuit(); + printf("DEBUG: Couldn't allocate buffer"); + return SDL_SetError("Couldn't allocate buffer"); + } + + memset(this->hidden->mixbufs[i], 0, this->spec.size); + DCFlushRange(this->hidden->mixbufs[i], this->spec.size); + } + +/* Start messing with the voice again */ + AXVoiceBegin(this->hidden->voice); + +/* Set up the offsets for the first mixbuf */ + AXVoiceOffsets offs; + switch (SDL_AUDIO_BITSIZE(this->spec.format)) { + case 8: + offs.dataType = AX_VOICE_FORMAT_LPCM8; + offs.endOffset = this->spec.size - 1; + break; + case 16: + default: + offs.dataType = AX_VOICE_FORMAT_LPCM16; + offs.endOffset = (this->spec.size / 2) - 1; + break; + } + offs.loopingEnabled = AX_VOICE_LOOP_ENABLED; + offs.loopOffset = 0; + offs.currentOffset = 0; + offs.data = this->hidden->mixbufs[0]; + AXSetVoiceOffsets(this->hidden->voice, &offs); + +/* Set the last good loopcount */ + this->hidden->last_loopcount = AXGetVoiceLoopCount(this->hidden->voice); + +/* Offsets are set for playing the first mixbuf, so we should render the second */ + this->hidden->playingid = 0; + this->hidden->renderingid = 1; + +/* Start playing. */ + AXSetVoiceState(this->hidden->voice, AX_VOICE_STATE_PLAYING); + +/* ..alright! */ + AXVoiceEnd(this->hidden->voice); + + cb_this = this; //wish there was a better way + AXRegisterAppFrameCallback(_WIIUAUDIO_framecallback); + +/* Put the thread affinity back to normal - we won't call any more AX funcs */ + OSSetThreadAffinity(OSGetCurrentThread(), old_affinity); + return 0; +} + +/* Called every 3ms before a frame of audio is rendered. Keep it fast! */ +static void _WIIUAUDIO_framecallback() { + if (!cb_hidden->voice) { + printf("DEBUG: aaah!"); + return; + } + + AXVoiceOffsets offs; + AXGetVoiceOffsets(cb_hidden->voice, &offs); + +/* Figure out which buffer is being played by the hardware */ + int playing_buffer = -1; + for (int i = 0; i < NUM_BUFFERS; i++) { + void* buf = cb_hidden->mixbufs[i]; + void* bufEnd = calc_buf_endaddr(offs, buf, cb_this->spec.size); + uint32_t startOffset = calc_ax_offset(offs, buf); + uint32_t endOffset = calc_ax_offset(offs, bufEnd); + + /* NOTE endOffset definitely needs to be <= (AX plays the sample at + endOffset), dunno about startOffset */ + if (offs.currentOffset >= startOffset && + offs.currentOffset <= endOffset) { + playing_buffer = i; + break; + } + } + + if (playing_buffer < 0 || playing_buffer >= NUM_BUFFERS) { + /* UM */ + /* Uncomment for craploads of debug info */ + /*printf("bad buffer %d\n" "|> %08X, %08X-%08X\n" \ + "0: xxxxxxxx, %08X-%08X (%08X@%08X)\n" \ + "1: xxxxxxxx, %08X-%08X (%08X@%08X)\n", \ + playing_buffer, offs.currentOffset, offs.loopOffset, offs.endOffset, + calc_ax_offset(offs, (void*)cb_hidden->mixbufs[0]), + calc_ax_offset(offs, (void*)cb_hidden->mixbufs[0] + cb_this->spec.size), + cb_this->spec.size, (void*)cb_hidden->mixbufs[0], + calc_ax_offset(offs, (void*)cb_hidden->mixbufs[1]), + calc_ax_offset(offs, (void*)cb_hidden->mixbufs[1] + cb_this->spec.size), + cb_this->spec.size, (void*)cb_hidden->mixbufs[1]);*/ + printf("DEBUG: Playing an invalid buffer? This is not a good sign.\n"); + playing_buffer = 0; + } + +/* Make sure playingid is in sync with the hardware */ + cb_hidden->playingid = playing_buffer; + +/* Make sure the end offset is correct for the playing buffer */ + void* endaddr = cb_hidden->mixbufs[cb_hidden->playingid] + cb_this->spec.size - 2; + AXSetVoiceEndOffset(cb_hidden->voice, calc_ax_offset(offs, endaddr)); + +/* The next buffer is good to go, set the loop offset */ + if (cb_hidden->renderingid != next_id(cb_hidden->playingid)) { + void* loopaddr = cb_hidden->mixbufs[next_id(cb_hidden->playingid)]; + AXSetVoiceLoopOffset(cb_hidden->voice, calc_ax_offset(offs, loopaddr)); +/* Otherwise, make sure the loop offset is correct for the playing buffer */ + } else { + void* loopaddr = cb_hidden->mixbufs[cb_hidden->playingid]; + AXSetVoiceLoopOffset(cb_hidden->voice, calc_ax_offset(offs, loopaddr)); + } +} + +static void WIIUAUDIO_PlayDevice(_THIS) { +/* Comment this out for broken-record mode ;3 */ + DCFlushRange(this->hidden->mixbufs[this->hidden->renderingid], this->spec.size); +/* Signal we're no longer rendering this buffer, AX callback will notice later */ + this->hidden->renderingid = next_id(this->hidden->renderingid); +} + +static void WIIUAUDIO_WaitDevice(_THIS) { +/* TODO use real thread sync stuff */ + while (this->hidden->renderingid == this->hidden->playingid) { + OSSleepTicks(OSMillisecondsToTicks(3)); + } +} + +static Uint8* WIIUAUDIO_GetDeviceBuf(_THIS) { +/* SDL will write audio samples into this buffer */ + return this->hidden->mixbufs[this->hidden->renderingid]; +} + +static void WIIUAUDIO_CloseDevice(_THIS) { + if (AXIsInit()) { + AXFreeVoice(this->hidden->voice); + this->hidden->voice = NULL; + AXQuit(); + } + for (int i = 0; i < NUM_BUFFERS; i++) { + if (this->hidden->mixbufs[i]) SDL_free(this->hidden->mixbufs[i]); + } + SDL_free(this->hidden); +} + +static void WIIUAUDIO_ThreadInit(_THIS) { +/* Bump our thread's priority a bit */ + OSThread* currentThread = OSGetCurrentThread(); + int32_t priority = OSGetThreadPriority(currentThread); + priority -= 1; + OSSetThreadPriority(currentThread, priority); +} + +static int WIIUAUDIO_Init(SDL_AudioDriverImpl* impl) { + impl->OpenDevice = WIIUAUDIO_OpenDevice; + impl->PlayDevice = WIIUAUDIO_PlayDevice; + impl->WaitDevice = WIIUAUDIO_WaitDevice; + impl->GetDeviceBuf = WIIUAUDIO_GetDeviceBuf; + impl->CloseDevice = WIIUAUDIO_CloseDevice; + impl->ThreadInit = WIIUAUDIO_ThreadInit; + + impl->OnlyHasDefaultOutputDevice = 1; + + return 1; +} + +AudioBootStrap WIIUAUDIO_bootstrap = { + WIIUAUDIO_DRIVER_NAME, "Wii U AX Audio Driver", WIIUAUDIO_Init, 0, +}; + +#endif //SDL_AUDIO_DRIVER_WIIU diff --git a/src/audio/wiiu/SDL_wiiuaudio.h b/src/audio/wiiu/SDL_wiiuaudio.h new file mode 100644 index 000000000..371e10410 --- /dev/null +++ b/src/audio/wiiu/SDL_wiiuaudio.h @@ -0,0 +1,47 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2018 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ + +#ifndef SDL_wiiuaudio_h_ +#define SDL_wiiuaudio_h_ + +#include "../SDL_sysaudio.h" + +#include + +/* Hidden "this" pointer for the audio functions */ +#define _THIS SDL_AudioDevice *this + +#define NUM_BUFFERS 2 + +struct SDL_PrivateAudioData { + /* xasxa */ + AXVoice* voice; + /* The raw allocated mixing buffer. */ + Uint8 *rawbuf; + /* Individual mixing buffers. */ + Uint8 *mixbufs[NUM_BUFFERS]; + + int renderingid; + int playingid; + uint32_t last_loopcount; +}; + +#endif /* SDL_wiiuaudio_h_ */