diff --git a/src/external/mini_al.h b/src/external/mini_al.h index 98e68c1b..22ec1ac5 100644 --- a/src/external/mini_al.h +++ b/src/external/mini_al.h @@ -233,8 +233,14 @@ extern "C" { #define MAL_SUPPORT_OSS #endif #endif -#define MAL_SUPPORT_OPENAL // All platforms support OpenAL (at least for now). -#define MAL_SUPPORT_NULL // All platforms support the null device. + +// Explicitly disable OpenAL for Emscripten - prefer SDL for this. +#if !defined(MAL_EMSCRIPTEN) +#define MAL_SUPPORT_OPENAL +#endif + +#define MAL_SUPPORT_SDL // All platforms support SDL. +#define MAL_SUPPORT_NULL // All platforms support the null backend. #if !defined(MAL_NO_WASAPI) && defined(MAL_SUPPORT_WASAPI) @@ -261,6 +267,9 @@ extern "C" { #if !defined(MAL_NO_OPENAL) && defined(MAL_SUPPORT_OPENAL) #define MAL_ENABLE_OPENAL #endif +#if !defined(MAL_NO_SDL) && defined(MAL_SUPPORT_SDL) + #define MAL_ENABLE_SDL +#endif #if !defined(MAL_NO_NULL) && defined(MAL_SUPPORT_NULL) #define MAL_ENABLE_NULL #endif @@ -469,7 +478,8 @@ typedef enum mal_backend_alsa, mal_backend_oss, mal_backend_opensl, - mal_backend_openal + mal_backend_openal, + mal_backend_sdl } mal_backend; typedef enum @@ -522,6 +532,9 @@ typedef union #ifdef MAL_SUPPORT_OPENAL char openal[256]; // OpenAL seems to use human-readable device names as the ID. #endif +#ifdef MAL_SUPPORT_SDL + int sdl; // +#endif #ifdef MAL_SUPPORT_NULL int nullbackend; // Always 0. #endif @@ -844,11 +857,17 @@ struct mal_context mal_uint32 isMCFormatsSupported : 1; } openal; #endif +#ifdef MAL_SUPPORT_SDL + struct + { + int _unused; + } sdl; +#endif #ifdef MAL_SUPPORT_NULL struct { int _unused; - } null_device; + } null_backend; #endif }; @@ -1017,6 +1036,12 @@ struct mal_device mal_bool32 breakFromMainLoop; } openal; #endif +#ifdef MAL_SUPPORT_SDL + struct + { + mal_uint32 deviceID; + } sdl; +#endif #ifdef MAL_SUPPORT_NULL struct { @@ -1575,12 +1600,26 @@ void mal_pcm_convert(void* pOut, mal_format formatOut, const void* pIn, mal_form #ifdef MAL_ENABLE_OPENAL #define MAL_HAS_OPENAL // mini_al inlines the necessary OpenAL stuff. #endif +#ifdef MAL_ENABLE_SDL + #define MAL_HAS_SDL + #ifdef __has_include + #ifdef MAL_EMSCRIPTEN + #if !__has_include() + #undef MAL_HAS_SDL + #endif + #else + #if !__has_include() + #undef MAL_HAS_SDL + #endif + #endif + #endif +#endif #ifdef MAL_ENABLE_NULL #define MAL_HAS_NULL // Everything supports the null backend. #endif // Disable run-time linking on certain backends. -#if defined(MAL_ANDROID) || defined(MAL_EMSCRIPTED) +#if defined(MAL_ANDROID) || defined(MAL_EMSCRIPTEN) #define MAL_NO_RUNTIME_LINKING #endif @@ -7768,7 +7807,7 @@ mal_result mal_enumerate_devices__openal(mal_context* pContext, mal_device_type return MAL_SUCCESS; } -static void mal_device_uninit__openal(mal_device* pDevice) +void mal_device_uninit__openal(mal_device* pDevice) { mal_assert(pDevice != NULL); @@ -7784,7 +7823,7 @@ static void mal_device_uninit__openal(mal_device* pDevice) mal_free(pDevice->openal.pIntermediaryBuffer); } -static mal_result mal_device_init__openal(mal_context* pContext, mal_device_type type, mal_device_id* pDeviceID, const mal_device_config* pConfig, mal_device* pDevice) +mal_result mal_device_init__openal(mal_context* pContext, mal_device_type type, mal_device_id* pDeviceID, const mal_device_config* pConfig, mal_device* pDevice) { if (pDevice->periods > MAL_MAX_PERIODS_OPENAL) { pDevice->periods = MAL_MAX_PERIODS_OPENAL; @@ -8167,6 +8206,196 @@ static mal_result mal_device__main_loop__openal(mal_device* pDevice) #endif // OpenAL + +/////////////////////////////////////////////////////////////////////////////// +// +// SDL Backend +// +/////////////////////////////////////////////////////////////////////////////// +#ifdef MAL_HAS_SDL +#define SDL_MAIN_HANDLED +#ifdef MAL_EMSCRIPTEN +#include +#else +#include +#endif + +SDL_AudioFormat mal_format_to_sdl(mal_format format) +{ + switch (format) + { + case mal_format_unknown: return 0; + case mal_format_u8: return AUDIO_U8; + case mal_format_s16: return AUDIO_S16; + case mal_format_s24: return AUDIO_S32; // Closest match. + case mal_format_s32: return AUDIO_S32; + default: return 0; + } +} + +mal_format mal_format_from_sdl(SDL_AudioFormat format) +{ + switch (format) + { + case AUDIO_U8: return mal_format_u8; + case AUDIO_S16: return mal_format_s16; + case AUDIO_S32: return mal_format_s32; + case AUDIO_F32: return mal_format_f32; + default: return mal_format_unknown; + } +} + + +mal_result mal_context_init__sdl(mal_context* pContext) +{ + mal_assert(pContext != NULL); + + int resultSDL = SDL_InitSubSystem(SDL_INIT_AUDIO); + if (resultSDL != 0) { + return MAL_ERROR; + } + + return MAL_SUCCESS; +} + +mal_result mal_context_uninit__sdl(mal_context* pContext) +{ + mal_assert(pContext != NULL); + mal_assert(pContext->backend == mal_backend_sdl); + + SDL_QuitSubSystem(SDL_INIT_AUDIO); + return MAL_SUCCESS; +} + +mal_result mal_enumerate_devices__sdl(mal_context* pContext, mal_device_type type, mal_uint32* pCount, mal_device_info* pInfo) +{ + (void)pContext; + + + mal_uint32 infoSize = *pCount; + *pCount = 0; + + // For now I'm restricting the SDL backend to default devices. + if (pInfo != NULL) { + if (infoSize > 0) { + if (type == mal_device_type_playback) { + pInfo->id.sdl = 0; + mal_strncpy_s(pInfo->name, sizeof(pInfo->name), "Default Playback Device", (size_t)-1); + } else { + pInfo->id.sdl = 0; + mal_strncpy_s(pInfo->name, sizeof(pInfo->name), "Default Capture Device", (size_t)-1); + } + + pInfo += 1; + *pCount += 1; + } + } else { + *pCount += 1; + } + + return MAL_SUCCESS; +} + +void mal_device_uninit__sdl(mal_device* pDevice) +{ + mal_assert(pDevice != NULL); + +#if 1 + SDL_CloseAudioDevice(pDevice->sdl.deviceID); +#else + SDL_CloseAudio(); +#endif +} + + +static void mal_audio_callback__sdl(void *pUserData, Uint8 *pBuffer, int bufferSizeInBytes) +{ + mal_device* pDevice = (mal_device*)pUserData; + mal_assert(pDevice != NULL); + + mal_uint32 bufferSizeInFrames = (mal_uint32)bufferSizeInBytes / mal_get_sample_size_in_bytes(pDevice->format) / pDevice->channels; + + if (pDevice->type == mal_device_type_playback) { + mal_device__read_frames_from_client(pDevice, bufferSizeInFrames, pBuffer); + } else { + mal_device__send_frames_to_client(pDevice, bufferSizeInFrames, pBuffer); + } +} + +mal_result mal_device_init__sdl(mal_context* pContext, mal_device_type type, mal_device_id* pDeviceID, const mal_device_config* pConfig, mal_device* pDevice) +{ + mal_assert(pContext != NULL); + mal_assert(pConfig != NULL); + mal_assert(pDevice != NULL); + + SDL_AudioSpec desiredSpec, obtainedSpec; + mal_zero_memory(&desiredSpec, sizeof(desiredSpec)); + desiredSpec.freq = (int)pConfig->sampleRate; + desiredSpec.format = mal_format_to_sdl(pConfig->format); + desiredSpec.channels = (Uint8)pConfig->channels; + desiredSpec.samples = (Uint16)mal_next_power_of_2(pConfig->bufferSizeInFrames * pConfig->periods * pConfig->channels); + desiredSpec.callback = mal_audio_callback__sdl; + desiredSpec.userdata = pDevice; + + // Fall back to f32 if we don't have an appropriate mapping between mini_al and SDL. + if (desiredSpec.format == 0) { + desiredSpec.format = AUDIO_F32; + } + + // For now, only using the default device. + (void)pDeviceID; + +#if 1 + pDevice->sdl.deviceID = SDL_OpenAudioDevice(NULL, (type == mal_device_type_playback) ? 0 : 1, &desiredSpec, &obtainedSpec, 0); + if (pDevice->sdl.deviceID == 0) { + return mal_post_error(pDevice, "Failed to open SDL device.", MAL_FAILED_TO_OPEN_BACKEND_DEVICE); + } +#else + pDevice->sdl.deviceID = SDL_OpenAudio(&desiredSpec, &obtainedSpec); + if (pDevice->sdl.deviceID != 0) { + return mal_post_error(pDevice, "Failed to open SDL device.", MAL_FAILED_TO_OPEN_BACKEND_DEVICE); + } +#endif + + pDevice->internalFormat = mal_format_from_sdl(obtainedSpec.format); + pDevice->internalChannels = obtainedSpec.channels; + pDevice->internalSampleRate = (mal_uint32)obtainedSpec.freq; + pDevice->bufferSizeInFrames = obtainedSpec.samples / obtainedSpec.channels; + pDevice->periods = 1; // SDL doesn't seem to tell us what the period count is. Just set this 1. + + return MAL_SUCCESS; +} + +static mal_result mal_device__start_backend__sdl(mal_device* pDevice) +{ + mal_assert(pDevice != NULL); + +#if 1 + SDL_PauseAudioDevice(pDevice->sdl.deviceID, 0); +#else + SDL_PauseAudio(0); +#endif + + return MAL_SUCCESS; +} + +static mal_result mal_device__stop_backend__sdl(mal_device* pDevice) +{ + mal_assert(pDevice != NULL); + +#if 1 + SDL_PauseAudioDevice(pDevice->sdl.deviceID, 1); +#else + SDL_PauseAudio(1); +#endif + + return MAL_SUCCESS; +} +#endif // SDL + + + + mal_bool32 mal__is_channel_map_valid(const mal_channel* channelMap, mal_uint32 channels) { mal_assert(channels > 0); @@ -8585,6 +8814,7 @@ mal_result mal_context_init(mal_backend backends[], mal_uint32 backendCount, con mal_backend_oss, mal_backend_opensl, mal_backend_openal, + mal_backend_sdl, mal_backend_null }; @@ -8642,6 +8872,12 @@ mal_result mal_context_init(mal_backend backends[], mal_uint32 backendCount, con result = mal_context_init__openal(pContext); } break; #endif + #ifdef MAL_HAS_SDL + case mal_backend_sdl: + { + result = mal_context_init__sdl(pContext); + } break; + #endif #ifdef MAL_HAS_NULL case mal_backend_null: { @@ -8710,6 +8946,12 @@ mal_result mal_context_uninit(mal_context* pContext) return mal_context_uninit__openal(pContext); } break; #endif + #ifdef MAL_HAS_SDL + case mal_backend_sdl: + { + return mal_context_uninit__sdl(pContext); + } break; + #endif #ifdef MAL_HAS_NULL case mal_backend_null: { @@ -8775,6 +9017,12 @@ mal_result mal_enumerate_devices(mal_context* pContext, mal_device_type type, ma return mal_enumerate_devices__openal(pContext, type, pCount, pInfo); } break; #endif + #ifdef MAL_HAS_SDL + case mal_backend_sdl: + { + return mal_enumerate_devices__sdl(pContext, type, pCount, pInfo); + } break; + #endif #ifdef MAL_HAS_NULL case mal_backend_null: { @@ -8934,6 +9182,12 @@ mal_result mal_device_init(mal_context* pContext, mal_device_type type, mal_devi result = mal_device_init__openal(pContext, type, pDeviceID, &config, pDevice); } break; #endif + #ifdef MAL_HAS_SDL + case mal_backend_sdl: + { + result = mal_device_init__sdl(pContext, type, pDeviceID, &config, pDevice); + } break; + #endif #ifdef MAL_HAS_NULL case mal_backend_null: { @@ -8979,7 +9233,7 @@ mal_result mal_device_init(mal_context* pContext, mal_device_type type, mal_devi // Some backends don't require the worker thread. - if (pContext->backend != mal_backend_opensl) { + if (pContext->backend != mal_backend_opensl && pContext->backend != mal_backend_sdl) { // The worker thread. if (mal_thread_create(pContext, &pDevice->thread, mal_worker_thread, pDevice) != MAL_SUCCESS) { mal_device_uninit(pDevice); @@ -9012,7 +9266,7 @@ void mal_device_uninit(mal_device* pDevice) mal_device__set_state(pDevice, MAL_STATE_UNINITIALIZED); // Wake up the worker thread and wait for it to properly terminate. - if (pDevice->pContext->backend != mal_backend_opensl) { + if (pDevice->pContext->backend != mal_backend_opensl && pDevice->pContext->backend != mal_backend_sdl) { mal_event_signal(&pDevice->wakeupEvent); mal_thread_wait(&pDevice->thread); } @@ -9057,6 +9311,11 @@ void mal_device_uninit(mal_device* pDevice) mal_device_uninit__openal(pDevice); } #endif +#ifdef MAL_HAS_SDL + if (pDevice->pContext->backend == mal_backend_sdl) { + mal_device_uninit__sdl(pDevice); + } +#endif #ifdef MAL_HAS_NULL if (pDevice->pContext->backend == mal_backend_null) { mal_device_uninit__null(pDevice); @@ -9117,6 +9376,12 @@ mal_result mal_device_start(mal_device* pDevice) mal_device__start_backend__opensl(pDevice); mal_device__set_state(pDevice, MAL_STATE_STARTED); } else +#endif +#ifdef MAL_HAS_SDL + if (pDevice->pContext->backend == mal_backend_sdl) { + mal_device__start_backend__sdl(pDevice); + mal_device__set_state(pDevice, MAL_STATE_STARTED); + } else #endif // Synchronous backends. { @@ -9167,6 +9432,11 @@ mal_result mal_device_stop(mal_device* pDevice) if (pDevice->pContext->backend == mal_backend_opensl) { mal_device__stop_backend__opensl(pDevice); } else +#endif +#ifdef MAL_HAS_SDL + if (pDevice->pContext->backend == mal_backend_sdl) { + mal_device__stop_backend__sdl(pDevice); + } else #endif // Synchronous backends. {