From 360786bef24bf10c01ee43eb939bf4ed538619f0 Mon Sep 17 00:00:00 2001 From: Ethan Lee Date: Sat, 13 Apr 2024 23:14:48 -0400 Subject: [PATCH] Initial FAUDIO_SDL3_PLATFORM --- CMakeLists.txt | 53 ++- src/FAudio_internal.h | 7 + src/FAudio_platform_sdl2.c | 4 +- src/FAudio_platform_sdl3.c | 712 +++++++++++++++++++++++++++++++++++++ 4 files changed, 769 insertions(+), 7 deletions(-) create mode 100644 src/FAudio_platform_sdl3.c diff --git a/CMakeLists.txt b/CMakeLists.txt index 92aefb7a0..862f3a994 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -6,8 +6,9 @@ project(FAudio C) # Options option(BUILD_UTILS "Build utils/ folder" OFF) option(BUILD_TESTS "Build tests/ folder for unit tests to be executed on the host against FAudio" OFF) +option(BUILD_SDL3 "Build against SDL 3.0" OFF) if(WIN32) -option(PLATFORM_WIN32 "Enable native Win32 platform instead of SDL2" OFF) +option(PLATFORM_WIN32 "Enable native Win32 platform instead of SDL" OFF) endif() option(XNASONG "Build with XNA_Song.c" ON) option(LOG_ASSERTIONS "Bind FAudio_assert to log, instead of platform's assert" OFF) @@ -98,6 +99,7 @@ add_library(FAudio src/FAudio_internal_simd.c src/FAudio_operationset.c src/FAudio_platform_sdl2.c + src/FAudio_platform_sdl3.c src/FAudio_platform_win32.c # Optional source files src/XNA_Song.c @@ -110,7 +112,12 @@ if(PLATFORM_WIN32) set(PLATFORM_CFLAGS "-DFAUDIO_WIN32_PLATFORM") set(XNASONG OFF) else() - set(PLATFORM_CFLAGS) + if(BUILD_SDL3) + target_compile_definitions(FAudio PUBLIC FAUDIO_SDL3_PLATFORM) + set(PLATFORM_CFLAGS "-DFAUDIO_SDL3_PLATFORM") + else() + set(PLATFORM_CFLAGS) + endif() endif() # Only disable DebugConfiguration in release builds @@ -150,9 +157,41 @@ if(DUMP_VOICES) target_compile_definitions(FAudio PRIVATE FAUDIO_DUMP_VOICES) endif() -# SDL2 Dependency +# SDL Dependency if (PLATFORM_WIN32) - message(STATUS "not using SDL2") + message(STATUS "not using SDL") +elseif (BUILD_SDL3) + if (DEFINED SDL3_INCLUDE_DIRS AND DEFINED SDL3_LIBRARIES) + message(STATUS "using pre-defined SDL3 variables SDL3_INCLUDE_DIRS and SDL3_LIBRARIES") + target_include_directories(FAudio PUBLIC "$") + target_link_libraries(FAudio PUBLIC ${SDL3_LIBRARIES}) + if(INSTALL_MINGW_DEPENDENCIES) + install_shared_libs(${SDL3_LIBRARIES} DESTINATION bin NO_INSTALL_SYMLINKS) + endif() + else() + # Only try to autodetect if both SDL3 variables aren't explicitly set + find_package(SDL3 CONFIG) + if (TARGET SDL3::SDL3) + message(STATUS "using TARGET SDL3::SDL3") + target_link_libraries(FAudio PUBLIC SDL3::SDL3) + if(INSTALL_MINGW_DEPENDENCIES) + install_shared_libs(TARGETS SDL3::SDL3 DESTINATION bin NO_INSTALL_SYMLINKS REQUIRED) + endif() + elseif (TARGET SDL3) + message(STATUS "using TARGET SDL3") + target_link_libraries(FAudio PUBLIC SDL3) + if(INSTALL_MINGW_DEPENDENCIES) + install_shared_libs(TARGETS SDL3 DESTINATION bin NO_INSTALL_SYMLINKS REQUIRED) + endif() + else() + message(STATUS "no TARGET SDL3::SDL3, or SDL3, using variables") + target_include_directories(FAudio PUBLIC "$") + target_link_libraries(FAudio PUBLIC ${SDL3_LIBRARIES}) + if(INSTALL_MINGW_DEPENDENCIES) + install_shared_libs(${SDL3_LIBRARIES} DESTINATION bin NO_INSTALL_SYMLINKS) + endif() + endif() + endif() elseif (DEFINED SDL2_INCLUDE_DIRS AND DEFINED SDL2_LIBRARIES) message(STATUS "using pre-defined SDL2 variables SDL2_INCLUDE_DIRS and SDL2_LIBRARIES") target_include_directories(FAudio PUBLIC "$") @@ -300,7 +339,11 @@ join_paths(FAUDIO_PKGCONF_LIBDIR "\${prefix}" "${CMAKE_INSTALL_LIBDIR}") join_paths(FAUDIO_PKGCONF_INCLUDEDIR "\${prefix}" "${CMAKE_INSTALL_INCLUDEDIR}") if(NOT PLATFORM_WIN32) - set(PC_REQUIRES_PRIVATE "Requires.private: sdl2") + if(BUILD_SDL3) + set(PC_REQUIRES_PRIVATE "Requires.private: sdl3") + else() + set(PC_REQUIRES_PRIVATE "Requires.private: sdl2") + endif() endif() configure_file( diff --git a/src/FAudio_internal.h b/src/FAudio_internal.h index 700ab96f8..faba52226 100644 --- a/src/FAudio_internal.h +++ b/src/FAudio_internal.h @@ -109,10 +109,17 @@ extern void FAudio_Log(char const *msg); ((x << 24) & 0x00FF000000000000) | \ ((x << 32) & 0xFF00000000000000) #else +#ifdef FAUDIO_SDL3_PLATFORM +#include +#include +#include +#include +#else #include #include #include #include +#endif #define FAudio_malloc SDL_malloc #define FAudio_realloc SDL_realloc diff --git a/src/FAudio_platform_sdl2.c b/src/FAudio_platform_sdl2.c index 067428554..dade7f9b3 100644 --- a/src/FAudio_platform_sdl2.c +++ b/src/FAudio_platform_sdl2.c @@ -24,7 +24,7 @@ * */ -#ifndef FAUDIO_WIN32_PLATFORM +#if !defined(FAUDIO_WIN32_PLATFORM) && !defined(FAUDIO_SDL3_PLATFORM) #include "FAudio_internal.h" @@ -671,4 +671,4 @@ void FAudio_UTF8_To_UTF16(const char *src, uint16_t *dst, size_t len) extern int this_tu_is_empty; -#endif /* FAUDIO_WIN32_PLATFORM */ +#endif /* !defined(FAUDIO_WIN32_PLATFORM) && !defined(FAUDIO_SDL3_PLATFORM) */ diff --git a/src/FAudio_platform_sdl3.c b/src/FAudio_platform_sdl3.c new file mode 100644 index 000000000..7c0ee4030 --- /dev/null +++ b/src/FAudio_platform_sdl3.c @@ -0,0 +1,712 @@ +/* FAudio - XAudio Reimplementation for FNA + * + * Copyright (c) 2011-2024 Ethan Lee, Luigi Auriemma, and the MonoGame Team + * + * 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. + * + * Ethan "flibitijibibo" Lee + * + */ + +#ifdef FAUDIO_SDL3_PLATFORM + +#include "FAudio_internal.h" + +#include + +typedef struct SDLAudioDevice +{ + FAudio *audio; + SDL_AudioStream *stream; + float *stagingBuffer; + size_t stagingLen; +} SDLAudioDevice; + +/* Mixer Thread */ + +static void FAudio_INTERNAL_MixCallback( + void *userdata, + SDL_AudioStream *stream, + int additional_amount, + int total_amount +) { + SDLAudioDevice *dev = (SDLAudioDevice*) userdata; + + if (!dev->audio->active) + { + /* Nothing to do, SDL will fill in for us */ + return; + } + + while (additional_amount > 0) + { + FAudio_zero(dev->stagingBuffer, dev->stagingLen); + FAudio_INTERNAL_UpdateEngine(dev->audio, dev->stagingBuffer); + SDL_PutAudioStreamData( + stream, + dev->stagingBuffer, + dev->stagingLen + ); + additional_amount -= dev->stagingLen; + } +} + +/* Platform Functions */ + +static void FAudio_INTERNAL_PrioritizeDirectSound() +{ + int numdrivers, i, wasapi, directsound; + void *dll, *proc; + + if (SDL_GetHint("SDL_AUDIO_DRIVER") != NULL) + { + /* Already forced to something, ignore */ + return; + } + + /* Windows 10+ decided to break version detection, so instead of doing + * it the right way we have to do something dumb like search for an + * export that's only in Windows 10 or newer. + * -flibit + */ + if (SDL_strcmp(SDL_GetPlatform(), "Windows") != 0) + { + return; + } + dll = SDL_LoadObject("USER32.DLL"); + if (dll == NULL) + { + return; + } + proc = SDL_LoadFunction(dll, "SetProcessDpiAwarenessContext"); + SDL_UnloadObject(dll); /* We aren't really using this, unload now */ + if (proc != NULL) + { + /* OS is new enough to trust WASAPI, bail */ + return; + } + + /* Check to see if we have both Windows drivers in the list */ + numdrivers = SDL_GetNumAudioDrivers(); + wasapi = -1; + directsound = -1; + for (i = 0; i < numdrivers; i += 1) + { + const char *driver = SDL_GetAudioDriver(i); + if (SDL_strcmp(driver, "wasapi") == 0) + { + wasapi = i; + } + else if (SDL_strcmp(driver, "directsound") == 0) + { + directsound = i; + } + } + + /* We force if and only if both drivers exist and wasapi is earlier */ + if ((wasapi > -1) && (directsound > -1)) + { + if (wasapi < directsound) + { + SDL_SetHint("SDL_AUDIODRIVER", "directsound"); + } + } +} + +void FAudio_PlatformAddRef() +{ + FAudio_INTERNAL_PrioritizeDirectSound(); + + /* SDL tracks ref counts for each subsystem */ + if (SDL_InitSubSystem(SDL_INIT_AUDIO) < 0) + { + SDL_Log("SDL_INIT_AUDIO failed: %s", SDL_GetError()); + } + FAudio_INTERNAL_InitSIMDFunctions( + SDL_HasSSE2(), + SDL_HasNEON() + ); +} + +void FAudio_PlatformRelease() +{ + /* SDL tracks ref counts for each subsystem */ + SDL_QuitSubSystem(SDL_INIT_AUDIO); +} + +void FAudio_PlatformInit( + FAudio *audio, + uint32_t flags, + uint32_t deviceIndex, + FAudioWaveFormatExtensible *mixFormat, + uint32_t *updateSize, + void** platformDevice +) { + SDLAudioDevice *result; + SDL_AudioDeviceID devID; + SDL_AudioSpec spec; + int wantSamples; + + FAudio_assert(mixFormat != NULL); + FAudio_assert(updateSize != NULL); + + /* Build the device spec */ + spec.freq = mixFormat->Format.nSamplesPerSec; + spec.format = SDL_AUDIO_F32; + spec.channels = mixFormat->Format.nChannels; + if (flags & FAUDIO_1024_QUANTUM) + { + /* Get the sample count for a 21.33ms frame. + * For 48KHz this should be 1024. + */ + wantSamples = (int) ( + spec.freq / (1000.0 / (64.0 / 3.0)) + ); + } + else + { + wantSamples = spec.freq / 100; + } + + if (deviceIndex == 0) + { + devID = SDL_AUDIO_DEVICE_DEFAULT_OUTPUT; + } + else + { + int devcount; + SDL_AudioDeviceID *devs = SDL_GetAudioOutputDevices(&devcount); + + /* Bounds checking is done before this function is called */ + devID = devs[deviceIndex - 1]; + + SDL_free(devs); + } + + result = (SDLAudioDevice*) SDL_malloc(sizeof(SDLAudioDevice)); + result->audio = audio; + result->stagingLen = wantSamples * spec.channels * sizeof(float); + result->stagingBuffer = (float*) SDL_malloc(result->stagingLen); + + /* Open the device (or at least try to) */ +iosretry: + result->stream = SDL_OpenAudioDeviceStream( + devID, + &spec, + FAudio_INTERNAL_MixCallback, + result + ); + + /* Write up the received format for the engine */ + WriteWaveFormatExtensible( + mixFormat, + spec.channels, + spec.freq, + &DATAFORMAT_SUBTYPE_IEEE_FLOAT + ); + *updateSize = wantSamples; + + /* SDL_AudioDeviceID is a Uint32, anybody using a 16-bit PC still? */ + *platformDevice = result; + + /* Start the thread! */ + SDL_ResumeAudioDevice(devID); +} + +void FAudio_PlatformQuit(void* platformDevice) +{ + SDLAudioDevice *dev = (SDLAudioDevice*) platformDevice; + SDL_AudioDeviceID devID = SDL_GetAudioStreamDevice(dev->stream); + SDL_DestroyAudioStream(dev->stream); + SDL_CloseAudioDevice(devID); + SDL_free(dev->stagingBuffer); + SDL_free(dev); +} + +uint32_t FAudio_PlatformGetDeviceCount() +{ + int devcount; + SDL_free(SDL_GetAudioOutputDevices(&devcount)); + if (devcount == 0) + { + return 0; + } + SDL_assert(devcount > 0); + return devcount + 1; /* Add one for "Default Device" */ +} + +void FAudio_UTF8_To_UTF16(const char *src, uint16_t *dst, size_t len); + +uint32_t FAudio_PlatformGetDeviceDetails( + uint32_t index, + FAudioDeviceDetails *details +) { + const char *name, *envvar; + int channels, rate; + SDL_AudioSpec spec; + int devcount; + SDL_AudioDeviceID *devs; + + FAudio_zero(details, sizeof(FAudioDeviceDetails)); + + devs = SDL_GetAudioOutputDevices(&devcount); + if (index >= devcount) + { + SDL_free(devs); + return FAUDIO_E_INVALID_CALL; + } + + details->DeviceID[0] = L'0' + index; + if (index == 0) + { + name = "Default Device"; + details->Role = FAudioGlobalDefaultDevice; + + /* This variable will look like a DSound GUID or WASAPI ID, i.e. + * "{0.0.0.00000000}.{FD47D9CC-4218-4135-9CE2-0C195C87405B}" + */ + envvar = SDL_getenv("FAUDIO_FORCE_DEFAULT_DEVICEID"); + if (envvar != NULL) + { + FAudio_UTF8_To_UTF16( + envvar, + (uint16_t*) details->DeviceID, + sizeof(details->DeviceID) + ); + } + } + else + { + name = SDL_GetAudioDeviceName(devs[index - 1]); + details->Role = FAudioNotDefaultDevice; + } + FAudio_UTF8_To_UTF16( + name, + (uint16_t*) details->DisplayName, + sizeof(details->DisplayName) + ); + + /* Environment variables take precedence over all possible values */ + envvar = SDL_getenv("SDL_AUDIO_FREQUENCY"); + if (envvar != NULL) + { + rate = SDL_atoi(envvar); + } + else + { + rate = 0; + } + envvar = SDL_getenv("SDL_AUDIO_CHANNELS"); + if (envvar != NULL) + { + channels = SDL_atoi(envvar); + } + else + { + channels = 0; + } + + /* Get the device format from the OS */ + if (index == 0) + { + if (SDL_GetAudioDeviceFormat(SDL_AUDIO_DEVICE_DEFAULT_OUTPUT, &spec, NULL) < 0) + { + SDL_zero(spec); + } + } + else + { + if (SDL_GetAudioDeviceFormat(devs[index - 1], &spec, NULL) < 0) + { + SDL_zero(spec); + } + } + if ((spec.freq > 0) && (rate <= 0)) + { + rate = spec.freq; + } + if ((spec.channels > 0) && (spec.channels < 9) && (channels <= 0)) + { + channels = spec.channels; + } + + /* If we make it all the way here with no format, hardcode a sane one */ + if (rate <= 0) + { + rate = 48000; + } + if (channels <= 0) + { + channels = 2; + } + + /* Write the format, finally. */ + WriteWaveFormatExtensible( + &details->OutputFormat, + channels, + rate, + &DATAFORMAT_SUBTYPE_PCM + ); + return 0; +} + +/* Threading */ + +FAudioThread FAudio_PlatformCreateThread( + FAudioThreadFunc func, + const char *name, + void* data +) { + return (FAudioThread) SDL_CreateThread( + (SDL_ThreadFunction) func, + name, + data + ); +} + +void FAudio_PlatformWaitThread(FAudioThread thread, int32_t *retval) +{ + SDL_WaitThread((SDL_Thread*) thread, retval); +} + +void FAudio_PlatformThreadPriority(FAudioThreadPriority priority) +{ + SDL_SetThreadPriority((SDL_ThreadPriority) priority); +} + +uint64_t FAudio_PlatformGetThreadID(void) +{ + return (uint64_t) SDL_GetCurrentThreadID(); +} + +FAudioMutex FAudio_PlatformCreateMutex() +{ + return (FAudioMutex) SDL_CreateMutex(); +} + +void FAudio_PlatformDestroyMutex(FAudioMutex mutex) +{ + SDL_DestroyMutex((SDL_Mutex*) mutex); +} + +void FAudio_PlatformLockMutex(FAudioMutex mutex) +{ + SDL_LockMutex((SDL_Mutex*) mutex); +} + +void FAudio_PlatformUnlockMutex(FAudioMutex mutex) +{ + SDL_UnlockMutex((SDL_Mutex*) mutex); +} + +void FAudio_sleep(uint32_t ms) +{ + SDL_Delay(ms); +} + +/* Time */ + +uint32_t FAudio_timems() +{ + return SDL_GetTicks(); +} + +/* FAudio I/O */ + +FAudioIOStream* FAudio_fopen(const char *path) +{ +#if 0 /* FIXME: IOStream */ + FAudioIOStream *io = (FAudioIOStream*) FAudio_malloc( + sizeof(FAudioIOStream) + ); + SDL_RWops *rwops = SDL_RWFromFile(path, "rb"); + io->data = rwops; + io->read = (FAudio_readfunc) rwops->read; + io->seek = (FAudio_seekfunc) rwops->seek; + io->close = (FAudio_closefunc) rwops->close; + io->lock = FAudio_PlatformCreateMutex(); + return io; +#else + return NULL; +#endif +} + +FAudioIOStream* FAudio_memopen(void *mem, int len) +{ +#if 0 /* FIXME: IOStream */ + FAudioIOStream *io = (FAudioIOStream*) FAudio_malloc( + sizeof(FAudioIOStream) + ); + SDL_RWops *rwops = SDL_RWFromMem(mem, len); + io->data = rwops; + io->read = (FAudio_readfunc) rwops->read; + io->seek = (FAudio_seekfunc) rwops->seek; + io->close = (FAudio_closefunc) rwops->close; + io->lock = FAudio_PlatformCreateMutex(); + return io; +#else + return NULL; +#endif +} + +uint8_t* FAudio_memptr(FAudioIOStream *io, size_t offset) +{ +#if 0 /* FIXME: IOStream */ + SDL_RWops *rwops = (SDL_RWops*) io->data; + FAudio_assert(rwops->type == SDL_RWOPS_MEMORY); + return rwops->hidden.mem.base + offset; +#else + return NULL; +#endif +} + +void FAudio_close(FAudioIOStream *io) +{ + io->close(io->data); + FAudio_PlatformDestroyMutex((FAudioMutex) io->lock); + FAudio_free(io); +} + +#ifdef FAUDIO_DUMP_VOICES +FAudioIOStreamOut* FAudio_fopen_out(const char *path, const char *mode) +{ +#if 0 /* FIXME: IOStream */ + FAudioIOStreamOut *io = (FAudioIOStreamOut*) FAudio_malloc( + sizeof(FAudioIOStreamOut) + ); + SDL_RWops *rwops = SDL_RWFromFile(path, mode); + io->data = rwops; + io->read = (FAudio_readfunc) rwops->read; + io->write = (FAudio_writefunc) rwops->write; + io->seek = (FAudio_seekfunc) rwops->seek; + io->size = (FAudio_sizefunc) rwops->size; + io->close = (FAudio_closefunc) rwops->close; + io->lock = FAudio_PlatformCreateMutex(); + return io; +#else + return NULL; +#endif +} + +void FAudio_close_out(FAudioIOStreamOut *io) +{ + io->close(io->data); + FAudio_PlatformDestroyMutex((FAudioMutex) io->lock); + FAudio_free(io); +} +#endif /* FAUDIO_DUMP_VOICES */ + +/* UTF8->UTF16 Conversion, taken from PhysicsFS */ + +#define UNICODE_BOGUS_CHAR_VALUE 0xFFFFFFFF +#define UNICODE_BOGUS_CHAR_CODEPOINT '?' + +static uint32_t FAudio_UTF8_CodePoint(const char **_str) +{ + const char *str = *_str; + uint32_t retval = 0; + uint32_t octet = (uint32_t) ((uint8_t) *str); + uint32_t octet2, octet3, octet4; + + if (octet == 0) /* null terminator, end of string. */ + return 0; + + else if (octet < 128) /* one octet char: 0 to 127 */ + { + (*_str)++; /* skip to next possible start of codepoint. */ + return octet; + } /* else if */ + + else if ((octet > 127) && (octet < 192)) /* bad (starts with 10xxxxxx). */ + { + /* + * Apparently each of these is supposed to be flagged as a bogus + * char, instead of just resyncing to the next valid codepoint. + */ + (*_str)++; /* skip to next possible start of codepoint. */ + return UNICODE_BOGUS_CHAR_VALUE; + } /* else if */ + + else if (octet < 224) /* two octets */ + { + (*_str)++; /* advance at least one byte in case of an error */ + octet -= (128+64); + octet2 = (uint32_t) ((uint8_t) *(++str)); + if ((octet2 & (128+64)) != 128) /* Format isn't 10xxxxxx? */ + return UNICODE_BOGUS_CHAR_VALUE; + + *_str += 1; /* skip to next possible start of codepoint. */ + retval = ((octet << 6) | (octet2 - 128)); + if ((retval >= 0x80) && (retval <= 0x7FF)) + return retval; + } /* else if */ + + else if (octet < 240) /* three octets */ + { + (*_str)++; /* advance at least one byte in case of an error */ + octet -= (128+64+32); + octet2 = (uint32_t) ((uint8_t) *(++str)); + if ((octet2 & (128+64)) != 128) /* Format isn't 10xxxxxx? */ + return UNICODE_BOGUS_CHAR_VALUE; + + octet3 = (uint32_t) ((uint8_t) *(++str)); + if ((octet3 & (128+64)) != 128) /* Format isn't 10xxxxxx? */ + return UNICODE_BOGUS_CHAR_VALUE; + + *_str += 2; /* skip to next possible start of codepoint. */ + retval = ( ((octet << 12)) | ((octet2-128) << 6) | ((octet3-128)) ); + + /* There are seven "UTF-16 surrogates" that are illegal in UTF-8. */ + switch (retval) + { + case 0xD800: + case 0xDB7F: + case 0xDB80: + case 0xDBFF: + case 0xDC00: + case 0xDF80: + case 0xDFFF: + return UNICODE_BOGUS_CHAR_VALUE; + } /* switch */ + + /* 0xFFFE and 0xFFFF are illegal, too, so we check them at the edge. */ + if ((retval >= 0x800) && (retval <= 0xFFFD)) + return retval; + } /* else if */ + + else if (octet < 248) /* four octets */ + { + (*_str)++; /* advance at least one byte in case of an error */ + octet -= (128+64+32+16); + octet2 = (uint32_t) ((uint8_t) *(++str)); + if ((octet2 & (128+64)) != 128) /* Format isn't 10xxxxxx? */ + return UNICODE_BOGUS_CHAR_VALUE; + + octet3 = (uint32_t) ((uint8_t) *(++str)); + if ((octet3 & (128+64)) != 128) /* Format isn't 10xxxxxx? */ + return UNICODE_BOGUS_CHAR_VALUE; + + octet4 = (uint32_t) ((uint8_t) *(++str)); + if ((octet4 & (128+64)) != 128) /* Format isn't 10xxxxxx? */ + return UNICODE_BOGUS_CHAR_VALUE; + + *_str += 3; /* skip to next possible start of codepoint. */ + retval = ( ((octet << 18)) | ((octet2 - 128) << 12) | + ((octet3 - 128) << 6) | ((octet4 - 128)) ); + if ((retval >= 0x10000) && (retval <= 0x10FFFF)) + return retval; + } /* else if */ + + /* + * Five and six octet sequences became illegal in rfc3629. + * We throw the codepoint away, but parse them to make sure we move + * ahead the right number of bytes and don't overflow the buffer. + */ + + else if (octet < 252) /* five octets */ + { + (*_str)++; /* advance at least one byte in case of an error */ + octet = (uint32_t) ((uint8_t) *(++str)); + if ((octet & (128+64)) != 128) /* Format isn't 10xxxxxx? */ + return UNICODE_BOGUS_CHAR_VALUE; + + octet = (uint32_t) ((uint8_t) *(++str)); + if ((octet & (128+64)) != 128) /* Format isn't 10xxxxxx? */ + return UNICODE_BOGUS_CHAR_VALUE; + + octet = (uint32_t) ((uint8_t) *(++str)); + if ((octet & (128+64)) != 128) /* Format isn't 10xxxxxx? */ + return UNICODE_BOGUS_CHAR_VALUE; + + octet = (uint32_t) ((uint8_t) *(++str)); + if ((octet & (128+64)) != 128) /* Format isn't 10xxxxxx? */ + return UNICODE_BOGUS_CHAR_VALUE; + + *_str += 4; /* skip to next possible start of codepoint. */ + return UNICODE_BOGUS_CHAR_VALUE; + } /* else if */ + + else /* six octets */ + { + (*_str)++; /* advance at least one byte in case of an error */ + octet = (uint32_t) ((uint8_t) *(++str)); + if ((octet & (128+64)) != 128) /* Format isn't 10xxxxxx? */ + return UNICODE_BOGUS_CHAR_VALUE; + + octet = (uint32_t) ((uint8_t) *(++str)); + if ((octet & (128+64)) != 128) /* Format isn't 10xxxxxx? */ + return UNICODE_BOGUS_CHAR_VALUE; + + octet = (uint32_t) ((uint8_t) *(++str)); + if ((octet & (128+64)) != 128) /* Format isn't 10xxxxxx? */ + return UNICODE_BOGUS_CHAR_VALUE; + + octet = (uint32_t) ((uint8_t) *(++str)); + if ((octet & (128+64)) != 128) /* Format isn't 10xxxxxx? */ + return UNICODE_BOGUS_CHAR_VALUE; + + octet = (uint32_t) ((uint8_t) *(++str)); + if ((octet & (128+64)) != 128) /* Format isn't 10xxxxxx? */ + return UNICODE_BOGUS_CHAR_VALUE; + + *_str += 6; /* skip to next possible start of codepoint. */ + return UNICODE_BOGUS_CHAR_VALUE; + } /* else if */ + + return UNICODE_BOGUS_CHAR_VALUE; +} + +void FAudio_UTF8_To_UTF16(const char *src, uint16_t *dst, size_t len) +{ + len -= sizeof (uint16_t); /* save room for null char. */ + while (len >= sizeof (uint16_t)) + { + uint32_t cp = FAudio_UTF8_CodePoint(&src); + if (cp == 0) + break; + else if (cp == UNICODE_BOGUS_CHAR_VALUE) + cp = UNICODE_BOGUS_CHAR_CODEPOINT; + + if (cp > 0xFFFF) /* encode as surrogate pair */ + { + if (len < (sizeof (uint16_t) * 2)) + break; /* not enough room for the pair, stop now. */ + + cp -= 0x10000; /* Make this a 20-bit value */ + + *(dst++) = 0xD800 + ((cp >> 10) & 0x3FF); + len -= sizeof (uint16_t); + + cp = 0xDC00 + (cp & 0x3FF); + } /* if */ + + *(dst++) = cp; + len -= sizeof (uint16_t); + } /* while */ + + *dst = 0; +} + +/* vim: set noexpandtab shiftwidth=8 tabstop=8: */ + +#else + +extern int this_tu_is_empty; + +#endif /* FAUDIO_SDL3_PLATFORM */