diff --git a/CMakeLists.txt b/CMakeLists.txt index c2499008..bf1ac391 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -18,7 +18,7 @@ option(DEBUG_SAVE "Re-enable the ability to drag-and-drop save files onto the wi option(BUILD_DOCONFIG "Compile a DoConfig clone tool - not useful for console ports" ON) set(BACKEND_RENDERER "SDLTexture" CACHE STRING "Which renderer the game should use: 'OpenGL3' for an OpenGL 3.2 renderer, 'OpenGLES2' for an OpenGL ES 2.0 renderer, 'SDLTexture' for SDL2's hardware-accelerated Texture API, 'SDLSurface' for SDL2's software-rendered Surface API, or 'Software' for a handwritten software renderer") -set(BACKEND_AUDIO "SDL2" CACHE STRING "Which audio backend the game should use: 'SDL2', 'miniaudio', or 'Null'") +set(BACKEND_AUDIO "SDL2" CACHE STRING "Which audio backend the game should use: 'SDL2', 'miniaudio', 'WiiU', or 'Null'") set(BACKEND_PLATFORM "SDL2" CACHE STRING "Which platform backend the game should use: 'SDL2', 'GLFW3', 'WiiU', or 'Null'") option(LTO "Enable link-time optimisation" OFF) @@ -330,6 +330,10 @@ elseif(BACKEND_AUDIO MATCHES "miniaudio") endif() target_link_libraries(CSE2 PRIVATE ${CMAKE_DL_LIBS}) +elseif(BACKEND_AUDIO MATCHES "WiiU") + target_sources(CSE2 PRIVATE + "src/Backends/Audio/WiiU.cpp" + ) elseif(BACKEND_AUDIO MATCHES "Null") target_sources(CSE2 PRIVATE "src/Backends/Audio/Null.cpp" diff --git a/README.md b/README.md index c2a6678c..d9d54583 100644 --- a/README.md +++ b/README.md @@ -75,6 +75,7 @@ Name | Function `-DBACKEND_RENDERER=Software` | Use the handwritten software renderer `-DBACKEND_AUDIO=SDL2` | Use the SDL2-driven software audio-mixer `-DBACKEND_AUDIO=miniaudio` | Use the miniaudio-driven software audio-mixer +`-DBACKEND_AUDIO=WiiU` | Use the hardware-accelerated audio backend for the Wii U `-DBACKEND_AUDIO=Null` | Use the dummy audio backend (doesn't produce any sound) `-DBACKEND_PLATFORM=SDL2` | Use SDL2 for windowing and OS-abstraction `-DBACKEND_PLATFORM=GLFW3` | Use GLFW3 for windowing and OS-abstraction diff --git a/src/Backends/Audio/WiiU.cpp b/src/Backends/Audio/WiiU.cpp new file mode 100644 index 00000000..57f7f240 --- /dev/null +++ b/src/Backends/Audio/WiiU.cpp @@ -0,0 +1,170 @@ +#include "../Audio.h" + +#include <math.h> +#include <stddef.h> +#include <stdlib.h> +#include <string.h> + +#include <coreinit/cache.h> + +#include <sndcore2/core.h> +#include <sndcore2/voice.h> +#include <sndcore2/drcvs.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)) + +struct AudioBackend_Sound +{ + unsigned char *samples; + AXVoice *voice; +}; + +static double MillibelToScale(long volume) +{ + // Volume is in hundredths of a decibel, from 0 to -10000 + volume = CLAMP(volume, -10000, 0); + return pow(10.0, volume / 2000.0); +} + +bool AudioBackend_Init(void) +{ + if (!AXIsInit()) + { + static AXInitParams initparams = + { + .renderer = AX_INIT_RENDERER_48KHZ, + .pipeline = AX_INIT_PIPELINE_SINGLE, + }; + + AXInitWithParams(&initparams); + } + + return true; +} + +void AudioBackend_Deinit(void) +{ + AXQuit(); +} + +AudioBackend_Sound* AudioBackend_CreateSound(unsigned int frequency, const unsigned char *samples, size_t length) +{ + AudioBackend_Sound *sound = (AudioBackend_Sound*)malloc(sizeof(AudioBackend_Sound)); + + if (sound != NULL) + { + unsigned char *samples_copy = (unsigned char*)malloc(length); + + if (samples_copy != NULL) + { + for (size_t i = 0; i < length; ++i) + samples_copy[i] = samples[i] - 0x80; + + DCStoreRange(samples_copy, length); + + AXVoice *voice = AXAcquireVoice(31, NULL, NULL); + + if (voice != NULL) + { + AXVoiceOffsets offs; + AXVoiceVeData vol = { + .volume = 0x8000, + }; + + AXVoiceBegin(voice); + + AXSetVoiceType(voice, 0); + AXSetVoiceVe(voice, &vol); + + static AXVoiceDeviceMixData mix_data[1][6]; + mix_data[0][0].bus[0].volume = 0x8000; + mix_data[0][1].bus[0].volume = 0x8000; + + AXSetVoiceDeviceMix(voice, AX_DEVICE_TYPE_DRC, 0, mix_data[0]); + AXSetVoiceDeviceMix(voice, AX_DEVICE_TYPE_TV, 0, mix_data[0]); + + float srcratio = (float)frequency / (float)AXGetInputSamplesPerSec(); + AXSetVoiceSrcRatio(voice, srcratio); + AXSetVoiceSrcType(voice, AX_VOICE_SRC_TYPE_LINEAR); + + offs.dataType = AX_VOICE_FORMAT_LPCM8; + offs.endOffset = length; + offs.loopingEnabled = AX_VOICE_LOOP_DISABLED; + offs.loopOffset = 0; + offs.currentOffset = 0; + offs.data = samples_copy; + AXSetVoiceOffsets(voice, &offs); + + AXVoiceEnd(voice); + + sound->samples = samples_copy; + sound->voice = voice; + + return sound; + } + + free(samples_copy); + } + + free(sound); + } + + return NULL; +} + +void AudioBackend_DestroySound(AudioBackend_Sound *sound) +{ + AXFreeVoice(sound->voice); + free(sound->samples); + free(sound); +} + +void AudioBackend_PlaySound(AudioBackend_Sound *sound, bool looping) +{ + AXSetVoiceLoop(sound->voice, looping ? AX_VOICE_LOOP_ENABLED : AX_VOICE_LOOP_DISABLED); + AXSetVoiceState(sound->voice, AX_VOICE_STATE_PLAYING); +} + +void AudioBackend_StopSound(AudioBackend_Sound *sound) +{ + AXSetVoiceState(sound->voice, AX_VOICE_STATE_STOPPED); + AXSetVoiceCurrentOffset(sound->voice, 0); +} + +void AudioBackend_RewindSound(AudioBackend_Sound *sound) +{ + AXSetVoiceCurrentOffset(sound->voice, 0); +} + +void AudioBackend_SetSoundFrequency(AudioBackend_Sound *sound, unsigned int frequency) +{ + float srcratio = (float)frequency / (float)AXGetInputSamplesPerSec(); + AXSetVoiceSrcRatio(sound->voice, srcratio); +} + +void AudioBackend_SetSoundVolume(AudioBackend_Sound *sound, long volume) +{ + AXVoiceVeData vol = { + .volume = (unsigned short)(0x8000 * MillibelToScale(volume)), + }; + + AXSetVoiceVe(sound->voice, &vol); +} + +void AudioBackend_SetSoundPan(AudioBackend_Sound *sound, long pan) +{ + static AXVoiceDeviceMixData mix_data[1][6]; + mix_data[0][0].bus[0].volume = (unsigned short)(0x8000 * MillibelToScale(-pan)); + mix_data[0][1].bus[0].volume = (unsigned short)(0x8000 * MillibelToScale(pan)); + + AXSetVoiceDeviceMix(sound->voice, AX_DEVICE_TYPE_DRC, 0, mix_data[0]); + AXSetVoiceDeviceMix(sound->voice, AX_DEVICE_TYPE_TV, 0, mix_data[0]); +} + +void AudioBackend_SetOrganyaCallback(void (*callback)(void), unsigned int milliseconds) +{ + (void)callback; + (void)milliseconds; +}