diff --git a/Makefile b/Makefile index c08f3d3e8..7d109db14 100644 --- a/Makefile +++ b/Makefile @@ -350,7 +350,7 @@ else ifeq ($(TARGET_NX),1) # Nintendo Switch STRIP := $(CROSS)strip OPT_FLAGS := -ffunction-sections -fdata-sections -march=armv8-a+crc+crypto+simd -mtune=cortex-a57 -mtp=soft -ftls-model=local-exec -fwrapv -fPIC - DEFINES += __SWITCH__=1 __CONSOLE__=1 MA_NO_RUNTIME_LINKING=1 USE_GLES=1 + DEFINES += __SWITCH__=1 __CONSOLE__=1 USE_GLES=1 ifeq ($(BUILD_NRO),1) DEFINES += BUILD_NRO=1 diff --git a/src/pc/lua/utils/smlua_audio_utils.c b/src/pc/lua/utils/smlua_audio_utils.c index b7c329a4c..63324f623 100644 --- a/src/pc/lua/utils/smlua_audio_utils.c +++ b/src/pc/lua/utils/smlua_audio_utils.c @@ -1,5 +1,3 @@ -#define MINIAUDIO_IMPLEMENTATION // required by miniaudio - // enable Vorbis decoding (provides ogg audio decoding support) for miniaudio #define STB_VORBIS_HEADER_ONLY #include "pc/utils/stb_vorbis.c" @@ -158,19 +156,15 @@ void smlua_audio_utils_replace_sequence(u8 sequenceId, u8 bankId, u8 defaultVolu // mod audio // /////////////// -// Optimization: disable spatialization for everything as it's not used -#define MA_SOUND_STREAM_FLAGS (MA_SOUND_FLAG_NO_SPATIALIZATION | MA_SOUND_FLAG_STREAM) -#define MA_SOUND_SAMPLE_FLAGS (MA_SOUND_FLAG_NO_SPATIALIZATION | MA_SOUND_FLAG_NO_PITCH | MA_SOUND_FLAG_DECODE) // No pitch, pre-decode audio samples - static ma_engine sModAudioEngine; static struct DynamicPool *sModAudioPool; static void smlua_audio_custom_init(void) { sModAudioPool = dynamic_pool_init(); - ma_result result = ma_engine_init(NULL, &sModAudioEngine); + ma_result result = ma_initalize(NULL, &sModAudioEngine); if (result != MA_SUCCESS) { - LOG_ERROR("failed to init Miniaudio: %d", result); + LOG_ERROR("Failed to init Miniaudio: %d", result); } } @@ -291,23 +285,11 @@ struct ModAudio* audio_load_internal(const char* filename, bool isStream) { } f_close(f); f_delete(f); - - // decode the audio buffer - ma_result result = ma_decoder_init_memory(buffer, size, NULL, &audio->decoder); + + ma_result result = ma_sound_from_buffer(&sModAudioEngine, &audio->sound, &audio->decoder, buffer, size, isStream); if (result != MA_SUCCESS) { free(buffer); - LOG_ERROR("failed to load audio file '%s': failed to decode raw audio: %d", filename, result); - return NULL; - } - - result = ma_sound_init_from_data_source( - &sModAudioEngine, &audio->decoder, - isStream ? MA_SOUND_STREAM_FLAGS : MA_SOUND_SAMPLE_FLAGS, - NULL, &audio->sound - ); - if (result != MA_SUCCESS) { - free(buffer); - LOG_ERROR("failed to load audio file '%s': %d", filename, result); + LOG_ERROR("Failed to load audio file '%s': Failed to decode raw audio: %d", filename, result); return NULL; } @@ -531,9 +513,7 @@ void audio_sample_play(struct ModAudio* audio, Vec3f position, f32 volume) { ma_sound *sound = &audio->sound; if (ma_sound_is_playing(sound)) { struct ModAudioSampleCopies* copy = calloc(1, sizeof(struct ModAudioSampleCopies)); - ma_result result = ma_decoder_init_memory(audio->buffer, audio->bufferSize, NULL, ©->decoder); - if (result != MA_SUCCESS) { return; } - result = ma_sound_init_from_data_source(&sModAudioEngine, ©->decoder, MA_SOUND_SAMPLE_FLAGS, NULL, ©->sound); + ma_result result = ma_sound_from_buffer(&sModAudioEngine, ©->sound, ©->decoder, audio->buffer, audio->bufferSize, false); if (result != MA_SUCCESS) { return; } ma_sound_set_end_callback(©->sound, audio_sample_copy_end_callback, copy); copy->parent = audio; @@ -615,7 +595,7 @@ void smlua_audio_custom_deinit(void) { if (sModAudioPool) { audio_custom_shutdown(); free(sModAudioPool); - ma_engine_uninit(&sModAudioEngine); + ma_uninitalize(&sModAudioEngine); sModAudioPool = NULL; } } diff --git a/src/pc/lua/utils/smlua_audio_utils.h b/src/pc/lua/utils/smlua_audio_utils.h index 4fc276548..a3337218b 100644 --- a/src/pc/lua/utils/smlua_audio_utils.h +++ b/src/pc/lua/utils/smlua_audio_utils.h @@ -1,7 +1,7 @@ #ifndef SMLUA_AUDIO_UTILS_H #define SMLUA_AUDIO_UTILS_H -#include "pc/utils/miniaudio.h" +#include "pc/utils/miniaudio_api.h" /* |description|Resets all custom sequences back to vanilla|descriptionEnd| */ void smlua_audio_utils_reset_all(void); diff --git a/src/pc/utils/miniaudio_api.c b/src/pc/utils/miniaudio_api.c new file mode 100644 index 000000000..b3c4766d7 --- /dev/null +++ b/src/pc/utils/miniaudio_api.c @@ -0,0 +1,119 @@ +#define MINIAUDIO_IMPLEMENTATION // required by miniaudio +#define MA_NO_SDL // We don't use this because it conflicts with audio_sdl2. + +#include "types.h" +#include "pc/utils/miniaudio_api.h" +#include "pc/utils/miniaudio_nx.inl" +#include "pc/utils/miniaudio_sdl2.inl" + +typedef struct { + union { + ma_context context; + ma_context_sdl sdl_context; + }; +} ma_context_ex; + +typedef struct { + union { + ma_device device; + ma_device_sdl sdl_device; + }; +} ma_device_ex; + +void data_callback(ma_device* pDevice, void* pOutput, const void* pInput, ma_uint32 frameCount) +{ + if (pDevice->type == ma_device_type_playback) { + ma_waveform_read_pcm_frames((ma_waveform*)pDevice->pUserData, pOutput, frameCount, NULL); + } + + if (pDevice->type == ma_device_type_duplex) { + ma_copy_pcm_frames(pOutput, pInput, frameCount, pDevice->playback.format, pDevice->playback.channels); + } +} + +/* +This is our custom backend "loader". All this does is attempts to initialize our custom backends in the order they are listed. The first +one to successfully initialize is the one that's chosen. In this example we're just listing them statically, but you can use whatever logic +you want to handle backend selection. + +This is used as the onContextInit() callback in the context config. +*/ +static ma_result ma_context_init__custom_loader(ma_context *pContext, const ma_context_config *pConfig, ma_backend_callbacks *pCallbacks) { + ma_result result = MA_NO_BACKEND; + + /* Silence some unused parameter warnings just in case no custom backends are enabled. */ + (void)pContext; + (void)pCallbacks; + + /* NX. */ +#if !defined(MA_NO_NX) + if (result != MA_SUCCESS) { + result = ma_context_init__nx(pContext, pConfig, pCallbacks); + } +#endif + + /* SDL. */ +#if !defined(MA_NO_SDL) + if (result != MA_SUCCESS) { + result = ma_context_init__sdl(pContext, pConfig, pCallbacks); + } +#endif + + /* If we have a success result we have initialized a backend. Otherwise we need to tell miniaudio about the error so it can skip over our custom backends. */ + return result; +} + +ma_context_ex global_context = { 0 }; +ma_backend global_backends[] = { + ma_backend_wasapi, + ma_backend_dsound, + ma_backend_winmm, + ma_backend_coreaudio, + ma_backend_sndio, + ma_backend_audio4, + ma_backend_oss, + ma_backend_pulseaudio, + ma_backend_alsa, + ma_backend_jack, + ma_backend_aaudio, + ma_backend_opensl, + ma_backend_webaudio, + ma_backend_custom, + ma_backend_null +}; + +ma_result ma_initalize(const ma_engine_config *pConfig, ma_engine *pEngine) { + ma_result result = MA_SUCCESS; + ma_engine_config engineConfig; + + /* The config is allowed to be NULL in which case we use defaults for everything. */ + if (pConfig != NULL) { + engineConfig = *pConfig; + } else { + engineConfig = ma_engine_config_init(); + } + engineConfig.pContext = (ma_context *)&global_context; + + ma_context_config contextConfig = ma_context_config_init(); + contextConfig.custom.onContextInit = ma_context_init__custom_loader; + + result = ma_context_init(global_backends, sizeof(global_backends) / sizeof(global_backends[0]), &contextConfig, (ma_context *)&global_context); + if (result != MA_SUCCESS) { return result; } + + result = ma_engine_init(&engineConfig, pEngine); + return result; +} + +ma_result ma_uninitalize(ma_engine *pEngine) { + ma_engine_uninit(pEngine); + ma_context_uninit((ma_context *)&global_context); +} + +ma_result ma_sound_from_buffer(ma_engine *engine, ma_sound *sound, ma_decoder *decoder, void *buffer, u32 size, bool stream) { + // Decode the audio buffer. + ma_result result = ma_decoder_init_memory(buffer, size, NULL, decoder); + if (result != MA_SUCCESS) { return result; } + + result = ma_sound_init_from_data_source(engine, decoder, stream ? MA_SOUND_STREAM_FLAGS : MA_SOUND_SAMPLE_FLAGS, NULL, sound); + return result; +} \ No newline at end of file diff --git a/src/pc/utils/miniaudio_api.h b/src/pc/utils/miniaudio_api.h new file mode 100644 index 000000000..1248ed872 --- /dev/null +++ b/src/pc/utils/miniaudio_api.h @@ -0,0 +1,20 @@ +#ifndef miniaudio_api_h +#define miniaudio_api_h + +#ifdef __SWITCH__ +#define MA_NO_RUNTIME_LINKING 1 +#define MA_DEBUG_OUTPUT 1 +#endif + +#include "pc/utils/miniaudio.h" + +// Optimization: disable spatialization for everything as it's not used +#define MA_SOUND_STREAM_FLAGS (MA_SOUND_FLAG_NO_SPATIALIZATION | MA_SOUND_FLAG_STREAM) +#define MA_SOUND_SAMPLE_FLAGS (MA_SOUND_FLAG_NO_SPATIALIZATION | MA_SOUND_FLAG_NO_PITCH | MA_SOUND_FLAG_DECODE) // No pitch, pre-decode audio samples + +ma_result ma_initalize(const ma_engine_config *pConfig, ma_engine *pEngine); +ma_result ma_uninitalize(ma_engine *pEngine); + +ma_result ma_sound_from_buffer(ma_engine *engine, ma_sound *sound, ma_decoder *decoder, void *buffer, u32 size, bool stream); + +#endif // miniaudio_api_h \ No newline at end of file diff --git a/src/pc/utils/miniaudio_conf.h b/src/pc/utils/miniaudio_conf.h new file mode 100644 index 000000000..f8b739316 --- /dev/null +++ b/src/pc/utils/miniaudio_conf.h @@ -0,0 +1,9 @@ +#ifndef miniaudio_conf_h +#define miniaudio_conf_h + +#ifdef __SWITCH__ +#define MA_NO_RUNTIME_LINKING 1 +#define MA_DEBUG_OUTPUT 1 +#endif + +#endif // miniaudio_conf_h \ No newline at end of file diff --git a/src/pc/utils/miniaudio_nx.inl b/src/pc/utils/miniaudio_nx.inl new file mode 100644 index 000000000..b7597e764 --- /dev/null +++ b/src/pc/utils/miniaudio_nx.inl @@ -0,0 +1,199 @@ +#ifndef miniaudio_nx_inl +#define miniaudio_nx_inl + +#include +#include + +/* Only enable MA_SUPPORT_NX on Nintendo Switch */ +#ifdef __SWITCH__ +#define MA_SUPPORT_NX +#endif + +/* +Only enable NX if it's hasn't been explicitly disabled (MA_NO_NX) or enabled (MA_ENABLE_NX with +MA_ENABLE_ONLY_SPECIFIC_BACKENDS) and it's supported at compile time (MA_SUPPORT_NX). +*/ +#if defined(MA_SUPPORT_NX) && !defined(MA_NO_NX) && (!defined(MA_ENABLE_ONLY_SPECIFIC_BACKENDS) || defined(MA_ENABLE_NX)) + #define MA_HAS_NX +#endif + +#if defined(MA_HAS_NX) + +ma_result ma_context_init__nx(ma_context *pContext, const ma_context_config *pConfig, ma_backend_callbacks *pCallbacks); +ma_result ma_context_uninit__nx(ma_context *pContext); +ma_result ma_device_init__nx(ma_device *pDevice, const ma_device_config *pConfig, ma_device_descriptor *pDescriptorPlayback, ma_device_descriptor *pDescriptorCapture); +ma_result ma_device_uninit__nx(ma_device *pDevice); +ma_result ma_device_start__nx(ma_device *pDevice); +ma_result ma_device_stop__nx(ma_device *pDevice); + +ma_result ma_context_enumerate_devices__nx(ma_context *pContext, ma_enum_devices_callback_proc callback, void *pUserData); +ma_result ma_context_get_device_info__nx(ma_context *pContext, ma_device_type deviceType, const ma_device_id *pDeviceID, ma_device_info *pDeviceInfo); + +#endif // MA_HAS_NX + +#if defined(MA_HAS_NX) + +#include + +AudioOutBuffer audout_buf = { 0 }; +AudioOutBuffer *released_out_buffer = NULL; +u32 released_out_count = 0; + +PcmFormat ma_format_to_nx(ma_format format) { + switch (format) + { + case ma_format_u8: return PcmFormat_Int8; + case ma_format_s16: return PcmFormat_Int16; + case ma_format_s24: return PcmFormat_Int24; + case ma_format_s32: return PcmFormat_Int32; + case ma_format_f32: return PcmFormat_Float; + default: return PcmFormat_Invalid; + } +} + +ma_format ma_format_from_nx(PcmFormat format) { + switch (format) + { + case PcmFormat_Int8: return ma_format_u8; + case PcmFormat_Int16: return ma_format_s16; + case PcmFormat_Int24: return ma_format_s24; + case PcmFormat_Int32: return ma_format_s32; + case PcmFormat_Float: return ma_format_f32; + default: return ma_format_unknown; + } +} + +ma_result ma_context_enumerate_devices__nx(UNUSED ma_context *pContext, UNUSED ma_enum_devices_callback_proc callback, UNUSED void *pUserData) { + return MA_SUCCESS; +} + +ma_result ma_context_get_device_info__nx(UNUSED ma_context *pContext, UNUSED ma_device_type deviceType, UNUSED const ma_device_id *pDeviceID, ma_device_info *pDeviceInfo) { + pDeviceInfo->id.custom.i = 0; + pDeviceInfo->isDefault = MA_TRUE; + ma_strncpy_s(pDeviceInfo->name, sizeof(pDeviceInfo->name), MA_DEFAULT_PLAYBACK_DEVICE_NAME, (size_t)-1); + + pDeviceInfo->nativeDataFormatCount = 1; + pDeviceInfo->nativeDataFormats[0].format = ma_format_s16; + pDeviceInfo->nativeDataFormats[0].channels = 2; + pDeviceInfo->nativeDataFormats[0].sampleRate = 48000; + pDeviceInfo->nativeDataFormats[0].flags = 0; + + /* If miniaudio does not support the format, just use f32 as the native format (SDL will do the necessary conversions for us). */ + if (pDeviceInfo->nativeDataFormats[0].format == ma_format_unknown) { + pDeviceInfo->nativeDataFormats[0].format = ma_format_f32; + } + + return MA_SUCCESS; +} + +ma_result ma_device_init__nx(ma_device *pDevice, const ma_device_config *pConfig, UNUSED ma_device_descriptor *pDescriptorPlayback, UNUSED ma_device_descriptor *pDescriptorCapture) { + /* NX does not support loopback mode, so must return MA_DEVICE_TYPE_NOT_SUPPORTED if it's requested. */ + if (pConfig->deviceType == ma_device_type_loopback) { + return MA_DEVICE_TYPE_NOT_SUPPORTED; + } + + // Initialize the default audio output device. + Result rc = audoutInitialize(); + if (!R_SUCCEEDED(rc)) { + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[NX] audoutInitialize() returned 0x%x", rc); + return MA_ERROR; + } + + // Make sure the sample buffer size is aligned to 0x1000 bytes. + u32 data_size = ((audoutGetSampleRate() / 30) * audoutGetChannelCount() * 2); + u32 buffer_size = (data_size + 0xfff) & ~0xfff; + + // Allocate the buffers. + u8 *out_buf_data = memalign(0x1000, buffer_size); + + // Ensure buffers were properly allocated. + if (out_buf_data == NULL) { + rc = MAKERESULT(Module_Libnx, LibnxError_OutOfMemory); + } + + if (!R_SUCCEEDED(rc)) { + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[NX] ma_device_init__nx() failed to allocate buffer with error 0x%x", rc); + audoutExit(); + free(out_buf_data); + return MA_ERROR; + } + + memset(out_buf_data, 0, buffer_size); + + // Prepare the output buffer. + audout_buf.next = NULL; + audout_buf.buffer = out_buf_data; + audout_buf.buffer_size = buffer_size; + audout_buf.data_size = data_size; + audout_buf.data_offset = 0; + + // Prepare pointers and counters for released buffers. + released_out_buffer = NULL; + released_out_count = 0; + + // Append the initial output buffer. + rc = audoutAppendAudioOutBuffer(&audout_buf); + if (!R_SUCCEEDED(rc)) { + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[NX] audoutAppendAudioOutBuffer() returned 0x%x", rc); + audoutExit(); + free(out_buf_data); + return MA_ERROR; + } + + return MA_SUCCESS; +} + +ma_result ma_device_uninit__nx(UNUSED ma_device *pDevice) { + audoutExit(); + + if (audout_buf.buffer) { free(audout_buf.buffer); } + memset(&audout_buf, 0, sizeof(audout_buf)); + released_out_buffer = NULL; + released_out_count = 0; + return MA_SUCCESS; +} + +ma_result ma_device_start__nx(ma_device *pDevice) { + Result rc = audoutStartAudioOut(); + if (!R_SUCCEEDED(rc)) { + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[NX] audoutStartAudioOut() returned 0x%x", rc); + return MA_ERROR; + } + + return MA_SUCCESS; +} + +ma_result ma_device_stop__nx(ma_device *pDevice) { + Result rc = audoutStopAudioOut(); + if (!R_SUCCEEDED(rc)) { + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[NX] audoutStopAudioOut() returned 0x%x", rc); + return MA_ERROR; + } + + return MA_SUCCESS; +} + +ma_result ma_context_uninit__nx(UNUSED ma_context *pContext) { + return MA_SUCCESS; +} + +ma_result ma_context_init__nx(UNUSED ma_context *pContext, UNUSED const ma_context_config *pConfig, ma_backend_callbacks *pCallbacks) { + /* + The last step is to make sure the callbacks are set properly in pCallbacks. Internally, miniaudio will copy these callbacks into the + context object and then use them for then on for calling into our custom backend. + */ + pCallbacks->onContextInit = ma_context_init__nx; + pCallbacks->onContextUninit = ma_context_uninit__nx; + pCallbacks->onContextEnumerateDevices = ma_context_enumerate_devices__nx; + pCallbacks->onContextGetDeviceInfo = ma_context_get_device_info__nx; + pCallbacks->onDeviceInit = ma_device_init__nx; + pCallbacks->onDeviceUninit = ma_device_uninit__nx; + pCallbacks->onDeviceStart = ma_device_start__nx; + pCallbacks->onDeviceStop = ma_device_stop__nx; + + return MA_SUCCESS; +} + +#endif // MA_HAS_NX + +#endif // miniaudio_nx_inl \ No newline at end of file diff --git a/src/pc/utils/miniaudio_sdl2.inl b/src/pc/utils/miniaudio_sdl2.inl new file mode 100644 index 000000000..c6602a153 --- /dev/null +++ b/src/pc/utils/miniaudio_sdl2.inl @@ -0,0 +1,535 @@ +#ifndef miniaudio_sdl_inl +#define miniaudio_sdl_inl + +/* Support SDL on everything. */ +#define MA_SUPPORT_SDL + +/* +Only enable SDL if it's hasn't been explicitly disabled (MA_NO_SDL) or enabled (MA_ENABLE_SDL with +MA_ENABLE_ONLY_SPECIFIC_BACKENDS) and it's supported at compile time (MA_SUPPORT_SDL). +*/ +#if defined(MA_SUPPORT_SDL) && !defined(MA_NO_SDL) && (!defined(MA_ENABLE_ONLY_SPECIFIC_BACKENDS) || defined(MA_ENABLE_SDL)) + #define MA_HAS_SDL +#endif + +#if defined(MA_HAS_SDL) + /* SDL headers are necessary if using compile-time linking. */ + #ifdef MA_NO_RUNTIME_LINKING + #ifdef __has_include + #ifdef MA_EMSCRIPTEN + #if !__has_include() + #undef MA_HAS_SDL + #endif + #else + #if !__has_include() + #undef MA_HAS_SDL + #endif + #endif + #endif + #endif +#endif + +typedef struct { + ma_context context; /* Make this the first member so we can cast between ma_context and ma_context_sdl. */ +#if defined(MA_SUPPORT_SDL) + struct { + ma_handle hSDL; /* A handle to the SDL2 shared object. We dynamically load function pointers at runtime so we can avoid linking. */ + ma_proc SDL_InitSubSystem; + ma_proc SDL_QuitSubSystem; + ma_proc SDL_GetNumAudioDevices; + ma_proc SDL_GetAudioDeviceName; + ma_proc SDL_CloseAudioDevice; + ma_proc SDL_OpenAudioDevice; + ma_proc SDL_PauseAudioDevice; + } sdl; +#endif +} ma_context_sdl; + +typedef struct { + ma_device device; /* Make this the first member so we can cast between ma_device and ma_device_sdl. */ +#if defined(MA_SUPPORT_SDL) + struct { + int deviceIDPlayback; + int deviceIDCapture; + } sdl; +#endif +} ma_device_sdl; + +#if defined(MA_HAS_SDL) +#define MA_SDL_INIT_AUDIO 0x00000010 +#define MA_AUDIO_U8 0x0008 +#define MA_AUDIO_S16 0x8010 +#define MA_AUDIO_S32 0x8020 +#define MA_AUDIO_F32 0x8120 +#define MA_SDL_AUDIO_ALLOW_FREQUENCY_CHANGE 0x00000001 +#define MA_SDL_AUDIO_ALLOW_FORMAT_CHANGE 0x00000002 +#define MA_SDL_AUDIO_ALLOW_CHANNELS_CHANGE 0x00000004 +#define MA_SDL_AUDIO_ALLOW_ANY_CHANGE (MA_SDL_AUDIO_ALLOW_FREQUENCY_CHANGE | MA_SDL_AUDIO_ALLOW_FORMAT_CHANGE | MA_SDL_AUDIO_ALLOW_CHANNELS_CHANGE) + +ma_result ma_context_init__sdl(ma_context *pContext, const ma_context_config *pConfig, ma_backend_callbacks *pCallbacks); +ma_result ma_context_uninit__sdl(ma_context *pContext); +ma_result ma_device_init__sdl(ma_device *pDevice, const ma_device_config *pConfig, ma_device_descriptor *pDescriptorPlayback, ma_device_descriptor *pDescriptorCapture); +ma_result ma_device_uninit__sdl(ma_device *pDevice); +ma_result ma_device_start__sdl(ma_device *pDevice); +ma_result ma_device_stop__sdl(ma_device *pDevice); + +ma_result ma_context_enumerate_devices__sdl(ma_context *pContext, ma_enum_devices_callback_proc callback, void *pUserData); +ma_result ma_context_get_device_info__sdl(ma_context *pContext, ma_device_type deviceType, const ma_device_id *pDeviceID, ma_device_info *pDeviceInfo); + +#endif // MA_HAS_SDL + +#if defined(MA_HAS_SDL) + +/* If we are linking at compile time we'll just #include SDL.h. Otherwise we can just redeclare some stuff to avoid the need for development packages to be installed. */ +#ifdef MA_NO_RUNTIME_LINKING + #define SDL_MAIN_HANDLED + #ifdef MA_EMSCRIPTEN + #include + #else + #include + #endif + + typedef SDL_AudioCallback MA_SDL_AudioCallback; + typedef SDL_AudioSpec MA_SDL_AudioSpec; + typedef SDL_AudioFormat MA_SDL_AudioFormat; + typedef SDL_AudioDeviceID MA_SDL_AudioDeviceID; +#else + typedef void (* MA_SDL_AudioCallback)(void* userdata, ma_uint8* stream, int len); + typedef ma_uint16 MA_SDL_AudioFormat; + typedef ma_uint32 MA_SDL_AudioDeviceID; + + typedef struct MA_SDL_AudioSpec + { + int freq; + MA_SDL_AudioFormat format; + ma_uint8 channels; + ma_uint8 silence; + ma_uint16 samples; + ma_uint16 padding; + ma_uint32 size; + MA_SDL_AudioCallback callback; + void* userdata; + } MA_SDL_AudioSpec; +#endif + +typedef int (* MA_PFN_SDL_InitSubSystem)(ma_uint32 flags); +typedef void (* MA_PFN_SDL_QuitSubSystem)(ma_uint32 flags); +typedef int (* MA_PFN_SDL_GetNumAudioDevices)(int iscapture); +typedef const char* (* MA_PFN_SDL_GetAudioDeviceName)(int index, int iscapture); +typedef void (* MA_PFN_SDL_CloseAudioDevice)(MA_SDL_AudioDeviceID dev); +typedef MA_SDL_AudioDeviceID (* MA_PFN_SDL_OpenAudioDevice)(const char* device, int iscapture, const MA_SDL_AudioSpec* desired, MA_SDL_AudioSpec* obtained, int allowed_changes); +typedef void (* MA_PFN_SDL_PauseAudioDevice)(MA_SDL_AudioDeviceID dev, int pause_on); + +MA_SDL_AudioFormat ma_format_to_sdl(ma_format format) { + switch (format) + { + case ma_format_unknown: return 0; + case ma_format_u8: return MA_AUDIO_U8; + case ma_format_s16: return MA_AUDIO_S16; + case ma_format_s24: return MA_AUDIO_S32; /* Closest match. */ + case ma_format_s32: return MA_AUDIO_S32; + case ma_format_f32: return MA_AUDIO_F32; + default: return 0; + } +} + +ma_format ma_format_from_sdl(MA_SDL_AudioFormat format) { + switch (format) + { + case MA_AUDIO_U8: return ma_format_u8; + case MA_AUDIO_S16: return ma_format_s16; + case MA_AUDIO_S32: return ma_format_s32; + case MA_AUDIO_F32: return ma_format_f32; + default: return ma_format_unknown; + } +} + +ma_result ma_context_enumerate_devices__sdl(ma_context *pContext, ma_enum_devices_callback_proc callback, void* pUserData) { + ma_context_sdl *pContextEx = (ma_context_sdl *)pContext; + ma_bool32 isTerminated = MA_FALSE; + + /* Playback */ + if (!isTerminated) { + int deviceCount = ((MA_PFN_SDL_GetNumAudioDevices)pContextEx->sdl.SDL_GetNumAudioDevices)(0); + for (int iDevice = 0; iDevice < deviceCount; ++iDevice) { + ma_device_info deviceInfo; + MA_ZERO_OBJECT(&deviceInfo); + + deviceInfo.id.custom.i = iDevice; + ma_strncpy_s(deviceInfo.name, sizeof(deviceInfo.name), ((MA_PFN_SDL_GetAudioDeviceName)pContextEx->sdl.SDL_GetAudioDeviceName)(iDevice, 0), (size_t)-1); + + if (iDevice == 0) { + deviceInfo.isDefault = MA_TRUE; + } + + ma_bool32 cbResult = callback(pContext, ma_device_type_playback, &deviceInfo, pUserData); + if (cbResult == MA_FALSE) { + isTerminated = MA_TRUE; + break; + } + } + } + + /* Capture */ + if (!isTerminated) { + int deviceCount = ((MA_PFN_SDL_GetNumAudioDevices)pContextEx->sdl.SDL_GetNumAudioDevices)(1); + for (int iDevice = 0; iDevice < deviceCount; ++iDevice) { + ma_device_info deviceInfo; + MA_ZERO_OBJECT(&deviceInfo); + + deviceInfo.id.custom.i = iDevice; + ma_strncpy_s(deviceInfo.name, sizeof(deviceInfo.name), ((MA_PFN_SDL_GetAudioDeviceName)pContextEx->sdl.SDL_GetAudioDeviceName)(iDevice, 1), (size_t)-1); + + if (iDevice == 0) { + deviceInfo.isDefault = MA_TRUE; + } + + ma_bool32 cbResult = callback(pContext, ma_device_type_capture, &deviceInfo, pUserData); + if (cbResult == MA_FALSE) { + isTerminated = MA_TRUE; + break; + } + } + } + + return MA_SUCCESS; +} + +ma_result ma_context_get_device_info__sdl(ma_context *pContext, ma_device_type deviceType, const ma_device_id *pDeviceID, ma_device_info *pDeviceInfo) { + ma_context_sdl *pContextEx = (ma_context_sdl *)pContext; + +#if !defined(__EMSCRIPTEN__) + MA_SDL_AudioSpec desiredSpec; + MA_SDL_AudioSpec obtainedSpec; + MA_SDL_AudioDeviceID tempDeviceID; + const char* pDeviceName; +#endif + + if (pDeviceID == NULL) { + if (deviceType == ma_device_type_playback) { + pDeviceInfo->id.custom.i = 0; + ma_strncpy_s(pDeviceInfo->name, sizeof(pDeviceInfo->name), MA_DEFAULT_PLAYBACK_DEVICE_NAME, (size_t)-1); + } else { + pDeviceInfo->id.custom.i = 0; + ma_strncpy_s(pDeviceInfo->name, sizeof(pDeviceInfo->name), MA_DEFAULT_CAPTURE_DEVICE_NAME, (size_t)-1); + } + } else { + pDeviceInfo->id.custom.i = pDeviceID->custom.i; + ma_strncpy_s(pDeviceInfo->name, sizeof(pDeviceInfo->name), ((MA_PFN_SDL_GetAudioDeviceName)pContextEx->sdl.SDL_GetAudioDeviceName)(pDeviceID->custom.i, (deviceType == ma_device_type_playback) ? 0 : 1), (size_t)-1); + } + + if (pDeviceInfo->id.custom.i == 0) { + pDeviceInfo->isDefault = MA_TRUE; + } + + /* + To get an accurate idea on the backend's native format we need to open the device. Not ideal, but it's the only way. An + alternative to this is to report all channel counts, sample rates and formats, but that doesn't offer a good representation + of the device's _actual_ ideal format. + + Note: With Emscripten, it looks like non-zero values need to be specified for desiredSpec. Whatever is specified in + desiredSpec will be used by SDL since it uses it just does its own format conversion internally. Therefore, from what + I can tell, there's no real way to know the device's actual format which means I'm just going to fall back to the full + range of channels and sample rates on Emscripten builds. + */ +#if defined(__EMSCRIPTEN__) + /* Good practice to prioritize the best format first so that the application can use the first data format as their chosen one if desired. */ + pDeviceInfo->nativeDataFormatCount = 3; + pDeviceInfo->nativeDataFormats[0].format = ma_format_s16; + pDeviceInfo->nativeDataFormats[0].channels = 0; /* All channel counts supported. */ + pDeviceInfo->nativeDataFormats[0].sampleRate = 0; /* All sample rates supported. */ + pDeviceInfo->nativeDataFormats[0].flags = 0; + pDeviceInfo->nativeDataFormats[1].format = ma_format_s32; + pDeviceInfo->nativeDataFormats[1].channels = 0; /* All channel counts supported. */ + pDeviceInfo->nativeDataFormats[1].sampleRate = 0; /* All sample rates supported. */ + pDeviceInfo->nativeDataFormats[1].flags = 0; + pDeviceInfo->nativeDataFormats[2].format = ma_format_u8; + pDeviceInfo->nativeDataFormats[2].channels = 0; /* All channel counts supported. */ + pDeviceInfo->nativeDataFormats[2].sampleRate = 0; /* All sample rates supported. */ + pDeviceInfo->nativeDataFormats[2].flags = 0; +#else + MA_ZERO_MEMORY(&desiredSpec, sizeof(desiredSpec)); + + pDeviceName = NULL; + if (pDeviceID != NULL) { + pDeviceName = ((MA_PFN_SDL_GetAudioDeviceName)pContextEx->sdl.SDL_GetAudioDeviceName)(pDeviceID->custom.i, (deviceType == ma_device_type_playback) ? 0 : 1); + } + + tempDeviceID = ((MA_PFN_SDL_OpenAudioDevice)pContextEx->sdl.SDL_OpenAudioDevice)(pDeviceName, (deviceType == ma_device_type_playback) ? 0 : 1, &desiredSpec, &obtainedSpec, MA_SDL_AUDIO_ALLOW_ANY_CHANGE); + if (tempDeviceID == 0) { + ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_ERROR, "Failed to open SDL device."); + return MA_FAILED_TO_OPEN_BACKEND_DEVICE; + } + + ((MA_PFN_SDL_CloseAudioDevice)pContextEx->sdl.SDL_CloseAudioDevice)(tempDeviceID); + + /* Only reporting a single native data format. It'll be whatever SDL decides is the best. */ + pDeviceInfo->nativeDataFormatCount = 1; + pDeviceInfo->nativeDataFormats[0].format = ma_format_from_sdl(obtainedSpec.format); + pDeviceInfo->nativeDataFormats[0].channels = obtainedSpec.channels; + pDeviceInfo->nativeDataFormats[0].sampleRate = obtainedSpec.freq; + pDeviceInfo->nativeDataFormats[0].flags = 0; + + /* If miniaudio does not support the format, just use f32 as the native format (SDL will do the necessary conversions for us). */ + if (pDeviceInfo->nativeDataFormats[0].format == ma_format_unknown) { + pDeviceInfo->nativeDataFormats[0].format = ma_format_f32; + } +#endif /* __EMSCRIPTEN__ */ + + return MA_SUCCESS; +} + +void ma_audio_callback_capture__sdl(void* pUserData, ma_uint8* pBuffer, int bufferSizeInBytes) { + ma_device_sdl *pDeviceEx = (ma_device_sdl *)pUserData; + ma_device_handle_backend_data_callback((ma_device *)pDeviceEx, NULL, pBuffer, (ma_uint32)bufferSizeInBytes / ma_get_bytes_per_frame(pDeviceEx->device.capture.internalFormat, pDeviceEx->device.capture.internalChannels)); +} + +void ma_audio_callback_playback__sdl(void* pUserData, ma_uint8* pBuffer, int bufferSizeInBytes) { + ma_device_sdl *pDeviceEx = (ma_device_sdl *)pUserData; + ma_device_handle_backend_data_callback((ma_device *)pDeviceEx, pBuffer, NULL, (ma_uint32)bufferSizeInBytes / ma_get_bytes_per_frame(pDeviceEx->device.playback.internalFormat, pDeviceEx->device.playback.internalChannels)); +} + +static ma_result ma_device_init_internal__sdl(ma_device_sdl *pDeviceEx, const ma_device_config *pConfig, ma_device_descriptor *pDescriptor) { + ma_context_sdl *pContextEx = (ma_context_sdl *)pDeviceEx->device.pContext; + + /* + SDL is a little bit awkward with specifying the buffer size, You need to specify the size of the buffer in frames, but since we may + have requested a period size in milliseconds we'll need to convert, which depends on the sample rate. But there's a possibility that + the sample rate just set to 0, which indicates that the native sample rate should be used. There's no practical way to calculate this + that I can think of right now so I'm just using MA_DEFAULT_SAMPLE_RATE. + */ + if (pDescriptor->sampleRate == 0) { + pDescriptor->sampleRate = MA_DEFAULT_SAMPLE_RATE; + } + + /* + When determining the period size, you need to take defaults into account. This is how the size of the period should be determined. + + 1) If periodSizeInFrames is not 0, use periodSizeInFrames; else + 2) If periodSizeInMilliseconds is not 0, use periodSizeInMilliseconds; else + 3) If both periodSizeInFrames and periodSizeInMilliseconds is 0, use the backend's default. If the backend does not allow a default + buffer size, use a default value of MA_DEFAULT_PERIOD_SIZE_IN_MILLISECONDS_LOW_LATENCY or + MA_DEFAULT_PERIOD_SIZE_IN_MILLISECONDS_CONSERVATIVE depending on the value of pConfig->performanceProfile. + + Note that options 2 and 3 require knowledge of the sample rate in order to convert it to a frame count. You should try to keep the + calculation of the period size as accurate as possible, but sometimes it's just not practical so just use whatever you can. + + A helper function called ma_calculate_buffer_size_in_frames_from_descriptor() is available to do all of this for you which is what + we'll be using here. + */ + pDescriptor->periodSizeInFrames = ma_calculate_buffer_size_in_frames_from_descriptor(pDescriptor, pDescriptor->sampleRate, pConfig->performanceProfile); + + /* SDL wants the buffer size to be a power of 2 for some reason. */ + if (pDescriptor->periodSizeInFrames > 32768) { + pDescriptor->periodSizeInFrames = 32768; + } else { + pDescriptor->periodSizeInFrames = ma_next_power_of_2(pDescriptor->periodSizeInFrames); + } + + + /* We now have enough information to set up the device. */ + MA_SDL_AudioSpec desiredSpec; + MA_ZERO_OBJECT(&desiredSpec); + desiredSpec.freq = (int)pDescriptor->sampleRate; + desiredSpec.format = ma_format_to_sdl(pDescriptor->format); + desiredSpec.channels = (ma_uint8)pDescriptor->channels; + desiredSpec.samples = (ma_uint16)pDescriptor->periodSizeInFrames; + desiredSpec.callback = (pConfig->deviceType == ma_device_type_capture) ? ma_audio_callback_capture__sdl : ma_audio_callback_playback__sdl; + desiredSpec.userdata = pDeviceEx; + + /* We'll fall back to f32 if we don't have an appropriate mapping between SDL and miniaudio. */ + if (desiredSpec.format == 0) { + desiredSpec.format = MA_AUDIO_F32; + } + + const char *pDeviceName = NULL; + if (pDescriptor->pDeviceID != NULL) { + pDeviceName = ((MA_PFN_SDL_GetAudioDeviceName)pContextEx->sdl.SDL_GetAudioDeviceName)(pDescriptor->pDeviceID->custom.i, (pConfig->deviceType == ma_device_type_playback) ? 0 : 1); + } + + MA_SDL_AudioSpec obtainedSpec; + int deviceID = ((MA_PFN_SDL_OpenAudioDevice)pContextEx->sdl.SDL_OpenAudioDevice)(pDeviceName, (pConfig->deviceType == ma_device_type_playback) ? 0 : 1, &desiredSpec, &obtainedSpec, MA_SDL_AUDIO_ALLOW_ANY_CHANGE); + if (deviceID == 0) { + ma_log_postf(ma_device_get_log((ma_device*)pDeviceEx), MA_LOG_LEVEL_ERROR, "Failed to open SDL2 device."); + return MA_FAILED_TO_OPEN_BACKEND_DEVICE; + } + + if (pConfig->deviceType == ma_device_type_playback) { + pDeviceEx->sdl.deviceIDPlayback = deviceID; + } else { + pDeviceEx->sdl.deviceIDCapture = deviceID; + } + + /* The descriptor needs to be updated with our actual settings. */ + pDescriptor->format = ma_format_from_sdl(obtainedSpec.format); + pDescriptor->channels = obtainedSpec.channels; + pDescriptor->sampleRate = (ma_uint32)obtainedSpec.freq; + ma_channel_map_init_standard(ma_standard_channel_map_default, pDescriptor->channelMap, ma_countof(pDescriptor->channelMap), pDescriptor->channels); + pDescriptor->periodSizeInFrames = obtainedSpec.samples; + pDescriptor->periodCount = 1; /* SDL doesn't use the notion of period counts, so just set to 1. */ + + return MA_SUCCESS; +} + +ma_result ma_device_init__sdl(ma_device *pDevice, const ma_device_config *pConfig, ma_device_descriptor *pDescriptorPlayback, ma_device_descriptor *pDescriptorCapture) { + ma_device_sdl *pDeviceEx = (ma_device_sdl *)pDevice; + ma_context_sdl *pContextEx = (ma_context_sdl *)pDevice->pContext; + ma_result result; + + /* SDL does not support loopback mode, so must return MA_DEVICE_TYPE_NOT_SUPPORTED if it's requested. */ + if (pConfig->deviceType == ma_device_type_loopback) { + return MA_DEVICE_TYPE_NOT_SUPPORTED; + } + + if (pConfig->deviceType == ma_device_type_capture || pConfig->deviceType == ma_device_type_duplex) { + result = ma_device_init_internal__sdl(pDeviceEx, pConfig, pDescriptorCapture); + if (result != MA_SUCCESS) { + return result; + } + } + + if (pConfig->deviceType == ma_device_type_playback || pConfig->deviceType == ma_device_type_duplex) { + result = ma_device_init_internal__sdl(pDeviceEx, pConfig, pDescriptorPlayback); + if (result != MA_SUCCESS) { + if (pConfig->deviceType == ma_device_type_duplex) { + ((MA_PFN_SDL_CloseAudioDevice)pContextEx->sdl.SDL_CloseAudioDevice)(pDeviceEx->sdl.deviceIDCapture); + } + + return result; + } + } + + return MA_SUCCESS; +} + +ma_result ma_device_uninit__sdl(ma_device *pDevice) { + ma_device_sdl *pDeviceEx = (ma_device_sdl *)pDevice; + ma_context_sdl *pContextEx = (ma_context_sdl *)pDevice->pContext; + + if (pDevice->type == ma_device_type_capture || pDevice->type == ma_device_type_duplex) { + ((MA_PFN_SDL_CloseAudioDevice)pContextEx->sdl.SDL_CloseAudioDevice)(pDeviceEx->sdl.deviceIDCapture); + } + + if (pDevice->type == ma_device_type_playback || pDevice->type == ma_device_type_duplex) { + ((MA_PFN_SDL_CloseAudioDevice)pContextEx->sdl.SDL_CloseAudioDevice)(pDeviceEx->sdl.deviceIDCapture); + } + + return MA_SUCCESS; +} + +ma_result ma_device_start__sdl(ma_device *pDevice) { + ma_device_sdl *pDeviceEx = (ma_device_sdl *)pDevice; + ma_context_sdl *pContextEx = (ma_context_sdl *)pDevice->pContext; + + if (pDevice->type == ma_device_type_capture || pDevice->type == ma_device_type_duplex) { + ((MA_PFN_SDL_PauseAudioDevice)pContextEx->sdl.SDL_PauseAudioDevice)(pDeviceEx->sdl.deviceIDCapture, 0); + } + + if (pDevice->type == ma_device_type_playback || pDevice->type == ma_device_type_duplex) { + ((MA_PFN_SDL_PauseAudioDevice)pContextEx->sdl.SDL_PauseAudioDevice)(pDeviceEx->sdl.deviceIDPlayback, 0); + } + + return MA_SUCCESS; +} + +ma_result ma_device_stop__sdl(ma_device *pDevice) { + ma_device_sdl *pDeviceEx = (ma_device_sdl *)pDevice; + ma_context_sdl *pContextEx = (ma_context_sdl *)pDevice->pContext; + + if (pDevice->type == ma_device_type_capture || pDevice->type == ma_device_type_duplex) { + ((MA_PFN_SDL_PauseAudioDevice)pContextEx->sdl.SDL_PauseAudioDevice)(pDeviceEx->sdl.deviceIDCapture, 1); + } + + if (pDevice->type == ma_device_type_playback || pDevice->type == ma_device_type_duplex) { + ((MA_PFN_SDL_PauseAudioDevice)pContextEx->sdl.SDL_PauseAudioDevice)(pDeviceEx->sdl.deviceIDPlayback, 1); + } + + return MA_SUCCESS; +} + +ma_result ma_context_uninit__sdl(ma_context *pContext) { + ma_context_sdl *pContextEx = (ma_context_sdl *)pContext; + + ((MA_PFN_SDL_QuitSubSystem)pContextEx->sdl.SDL_QuitSubSystem)(MA_SDL_INIT_AUDIO); + + /* Close the handle to the SDL shared object last. */ +#ifndef MA_NO_RUNTIME_LINKING + ma_dlclose(ma_context_get_log(pContext), pContextEx->sdl.hSDL); +#endif + pContextEx->sdl.hSDL = NULL; + + return MA_SUCCESS; +} + +ma_result ma_context_init__sdl(ma_context *pContext, const ma_context_config *pConfig, ma_backend_callbacks *pCallbacks) { + ma_context_sdl *pContextEx = (ma_context_sdl *)pContext; + +#ifndef MA_NO_RUNTIME_LINKING + /* We'll use a list of possible shared object names for easier extensibility. */ + const char* pSDLNames[] = { +#if defined(_WIN32) + "SDL2.dll" +#elif defined(__APPLE__) + "SDL2.framework/SDL2" +#else + "libSDL2-2.0.so.0" +#endif + }; + + (void)pConfig; + + /* Check if we have SDL2 installed somewhere. If not it's not usable and we need to abort. */ + for (size_t iName = 0; iName < ma_countof(pSDLNames); iName += 1) { + pContextEx->sdl.hSDL = ma_dlopen(ma_context_get_log(pContext), pSDLNames[iName]); + if (pContextEx->sdl.hSDL != NULL) { + break; + } + } + + if (pContextEx->sdl.hSDL == NULL) { + return MA_NO_BACKEND; /* SDL2 could not be loaded. */ + } + + /* Now that we have the handle to the shared object we can go ahead and load some function pointers. */ + pContextEx->sdl.SDL_InitSubSystem = ma_dlsym(ma_context_get_log(pContext), pContextEx->sdl.hSDL, "SDL_InitSubSystem"); + pContextEx->sdl.SDL_QuitSubSystem = ma_dlsym(ma_context_get_log(pContext), pContextEx->sdl.hSDL, "SDL_QuitSubSystem"); + pContextEx->sdl.SDL_GetNumAudioDevices = ma_dlsym(ma_context_get_log(pContext), pContextEx->sdl.hSDL, "SDL_GetNumAudioDevices"); + pContextEx->sdl.SDL_GetAudioDeviceName = ma_dlsym(ma_context_get_log(pContext), pContextEx->sdl.hSDL, "SDL_GetAudioDeviceName"); + pContextEx->sdl.SDL_CloseAudioDevice = ma_dlsym(ma_context_get_log(pContext), pContextEx->sdl.hSDL, "SDL_CloseAudioDevice"); + pContextEx->sdl.SDL_OpenAudioDevice = ma_dlsym(ma_context_get_log(pContext), pContextEx->sdl.hSDL, "SDL_OpenAudioDevice"); + pContextEx->sdl.SDL_PauseAudioDevice = ma_dlsym(ma_context_get_log(pContext), pContextEx->sdl.hSDL, "SDL_PauseAudioDevice"); +#else + pContextEx->sdl.SDL_InitSubSystem = (ma_proc)SDL_InitSubSystem; + pContextEx->sdl.SDL_QuitSubSystem = (ma_proc)SDL_QuitSubSystem; + pContextEx->sdl.SDL_GetNumAudioDevices = (ma_proc)SDL_GetNumAudioDevices; + pContextEx->sdl.SDL_GetAudioDeviceName = (ma_proc)SDL_GetAudioDeviceName; + pContextEx->sdl.SDL_CloseAudioDevice = (ma_proc)SDL_CloseAudioDevice; + pContextEx->sdl.SDL_OpenAudioDevice = (ma_proc)SDL_OpenAudioDevice; + pContextEx->sdl.SDL_PauseAudioDevice = (ma_proc)SDL_PauseAudioDevice; +#endif /* MA_NO_RUNTIME_LINKING */ + + int resultSDL = ((MA_PFN_SDL_InitSubSystem)pContextEx->sdl.SDL_InitSubSystem)(MA_SDL_INIT_AUDIO); + if (resultSDL != 0) { + ma_dlclose(ma_context_get_log(pContext), pContextEx->sdl.hSDL); + return MA_ERROR; + } + + /* + The last step is to make sure the callbacks are set properly in pCallbacks. Internally, miniaudio will copy these callbacks into the + context object and then use them for then on for calling into our custom backend. + */ + pCallbacks->onContextInit = ma_context_init__sdl; + pCallbacks->onContextUninit = ma_context_uninit__sdl; + pCallbacks->onContextEnumerateDevices = ma_context_enumerate_devices__sdl; + pCallbacks->onContextGetDeviceInfo = ma_context_get_device_info__sdl; + pCallbacks->onDeviceInit = ma_device_init__sdl; + pCallbacks->onDeviceUninit = ma_device_uninit__sdl; + pCallbacks->onDeviceStart = ma_device_start__sdl; + pCallbacks->onDeviceStop = ma_device_stop__sdl; + + return MA_SUCCESS; +} + +#endif /* MA_HAS_SDL */ + +#endif // miniaudio_sdl_inl \ No newline at end of file