sdl2-compat: audio: U16 audio support was removed from SDL3, so manage it here now.

From 43205a9d8feffea3d30bfd5ac86d7138d329b672 Mon Sep 17 00:00:00 2001
From: "Ryan C. Gordon" <[EMAIL REDACTED]>
Date: Wed, 1 Mar 2023 10:27:47 -0500
Subject: [PATCH] audio: U16 audio support was removed from SDL3, so manage it
 here now.

---
 src/dynapi/SDL_dynapi_procs.h |  14 +-
 src/sdl2_compat.c             | 248 ++++++++++++++++++++++++++++++----
 src/sdl2_compat.h             |   7 +
 src/sdl3_syms.h               |  14 +-
 4 files changed, 244 insertions(+), 39 deletions(-)

diff --git a/src/dynapi/SDL_dynapi_procs.h b/src/dynapi/SDL_dynapi_procs.h
index be85563..2863898 100644
--- a/src/dynapi/SDL_dynapi_procs.h
+++ b/src/dynapi/SDL_dynapi_procs.h
@@ -677,13 +677,13 @@ SDL_DYNAPI_PROC(void,SDL_UnlockJoysticks,(void),(),)
 SDL_DYNAPI_PROC(void,SDL_GetMemoryFunctions,(SDL_malloc_func *a, SDL_calloc_func *b, SDL_realloc_func *c, SDL_free_func *d),(a,b,c,d),)
 SDL_DYNAPI_PROC(int,SDL_SetMemoryFunctions,(SDL_malloc_func a, SDL_calloc_func b, SDL_realloc_func c, SDL_free_func d),(a,b,c,d),return)
 SDL_DYNAPI_PROC(int,SDL_GetNumAllocations,(void),(),return)
-SDL_DYNAPI_PROC(SDL_AudioStream*,SDL_NewAudioStream,(const SDL_AudioFormat a, const Uint8 b, const int c, const SDL_AudioFormat d, const Uint8 e, const int f),(a,b,c,d,e,f),return)
-SDL_DYNAPI_PROC(int,SDL_AudioStreamPut,(SDL_AudioStream *a, const void *b, int c),(a,b,c),return)
-SDL_DYNAPI_PROC(int,SDL_AudioStreamGet,(SDL_AudioStream *a, void *b, int c),(a,b,c),return)
-SDL_DYNAPI_PROC(void,SDL_AudioStreamClear,(SDL_AudioStream *a),(a),)
-SDL_DYNAPI_PROC(int,SDL_AudioStreamAvailable,(SDL_AudioStream *a),(a),return)
-SDL_DYNAPI_PROC(void,SDL_FreeAudioStream,(SDL_AudioStream *a),(a),)
-SDL_DYNAPI_PROC(int,SDL_AudioStreamFlush,(SDL_AudioStream *a),(a),return)
+SDL_DYNAPI_PROC(SDL2_AudioStream*,SDL_NewAudioStream,(const SDL_AudioFormat a, const Uint8 b, const int c, const SDL_AudioFormat d, const Uint8 e, const int f),(a,b,c,d,e,f),return)
+SDL_DYNAPI_PROC(int,SDL_AudioStreamPut,(SDL2_AudioStream *a, const void *b, int c),(a,b,c),return)
+SDL_DYNAPI_PROC(int,SDL_AudioStreamGet,(SDL2_AudioStream *a, void *b, int c),(a,b,c),return)
+SDL_DYNAPI_PROC(void,SDL_AudioStreamClear,(SDL2_AudioStream *a),(a),)
+SDL_DYNAPI_PROC(int,SDL_AudioStreamAvailable,(SDL2_AudioStream *a),(a),return)
+SDL_DYNAPI_PROC(void,SDL_FreeAudioStream,(SDL2_AudioStream *a),(a),)
+SDL_DYNAPI_PROC(int,SDL_AudioStreamFlush,(SDL2_AudioStream *a),(a),return)
 SDL_DYNAPI_PROC(float,SDL_acosf,(float a),(a),return)
 SDL_DYNAPI_PROC(float,SDL_asinf,(float a),(a),return)
 SDL_DYNAPI_PROC(float,SDL_atanf,(float a),(a),return)
diff --git a/src/sdl2_compat.c b/src/sdl2_compat.c
index 02baab4..a6abb05 100644
--- a/src/sdl2_compat.c
+++ b/src/sdl2_compat.c
@@ -635,6 +635,9 @@ BOOL WINAPI _DllMainCRTStartup(HANDLE dllhandle, DWORD reason, LPVOID reserved)
     #error Please define an init procedure for your platform.
 #endif
 
+/* removed in SDL3 (no U16 audio formats supported) */
+#define SDL2_AUDIO_U16LSB 0x0010  /* Unsigned 16-bit samples */
+#define SDL2_AUDIO_U16MSB 0x1010  /* As above, but big-endian byte order */
 
 /* removed in SDL3 (which only uses SDL_WINDOW_HIDDEN now). */
 #define SDL2_WINDOW_SHOWN 0x000000004
@@ -1068,6 +1071,7 @@ typedef struct EventFilterWrapperData
 
 /* Some SDL2 state we need to keep... */
 
+/* !!! FIXME: unify coding convention on the globals: some are MyVariableName and some are my_variable_name */
 static SDL2_EventFilter EventFilter2 = NULL;
 static void *EventFilterUserData2 = NULL;
 static SDL_mutex *EventWatchListMutex = NULL;
@@ -3210,9 +3214,31 @@ SDL_GL_GetSwapInterval(void)
     return val;
 }
 
+/* !!! FIXME: move these up with the other globals. */
 static SDL_AudioDeviceID g_audio_id = 0;
 static SDL_AudioSpec g_audio_spec;
 
+DECLSPEC SDL_AudioDeviceID SDLCALL
+SDL_OpenAudioDevice(const char *device, int iscapture, const SDL_AudioSpec *desired, SDL_AudioSpec *obtained, int allowed_changes)
+{
+    SDL_AudioSpec desired3;
+    SDL_memcpy(&desired3, desired, sizeof (SDL_AudioSpec));
+
+    if ((desired3.format == SDL2_AUDIO_U16LSB) || (desired3.format == SDL2_AUDIO_U16MSB)) {
+        if (allowed_changes & SDL_AUDIO_ALLOW_FORMAT_CHANGE) {
+            desired3.format = AUDIO_S16SYS;  /* just pick a supported format instead. */
+        } else {
+            /* technically we should convert this behind the scenes, but all of this is getting
+               replaced with a different interface in SDL3 soon, so it's not worth
+               hooking the audio callback to manage it before that work is done. */
+            SDL3_SetError("U16 audio unsupported");
+            return 0;
+        }
+    }
+
+    return SDL3_OpenAudioDevice(device, iscapture, &desired3, obtained, allowed_changes);
+}
+
 DECLSPEC int SDLCALL
 SDL_OpenAudio(SDL_AudioSpec *desired, SDL_AudioSpec *obtained)
 {
@@ -3253,6 +3279,189 @@ SDL_OpenAudio(SDL_AudioSpec *desired, SDL_AudioSpec *obtained)
     return -1;
 }
 
+/* this converts the buffer in-place. The buffer size does not change. */
+static Sint16 *AudioUi16LSBToSi16Sys(Uint16 *buffer, const size_t num_samples)
+{
+    size_t i;
+    const Uint16 *src = buffer;
+    Sint16 *dst = (Sint16 *) buffer;
+
+    for (i = 0; i < num_samples; i++) {
+        dst[i] = (Sint16) (SDL_SwapLE16(src[i]) ^ 0x8000);
+    }
+
+    return dst;
+}
+
+/* this converts the buffer in-place. The buffer size does not change. */
+static Sint16 *AudioUi16MSBToSi16Sys(Uint16 *buffer, const size_t num_samples)
+{
+    size_t i;
+    const Uint16 *src = buffer;
+    Sint16 *dst = (Sint16 *) buffer;
+
+    for (i = 0; i < num_samples; i++) {
+        dst[i] = (Sint16) (SDL_SwapBE16(src[i]) ^ 0x8000);
+    }
+
+    return dst;
+}
+
+/* this converts the buffer in-place. The buffer size does not change. */
+static Uint16 *AudioSi16SysToUi16LSB(Sint16 *buffer, const size_t num_samples)
+{
+    size_t i;
+    const Sint16 *src = buffer;
+    Uint16 *dst = (Uint16 *) buffer;
+
+    for (i = 0; i < num_samples; i++) {
+        dst[i] = SDL_SwapLE16(((Uint16) src[i]) ^ 0x8000);
+    }
+
+    return dst;
+}
+/* this converts the buffer in-place. The buffer size does not change. */
+static Uint16 *AudioSi16SysToUi16MSB(Sint16 *buffer, const size_t num_samples)
+{
+    size_t i;
+    const Sint16 *src = buffer;
+    Uint16 *dst = (Uint16 *) buffer;
+
+    for (i = 0; i < num_samples; i++) {
+        dst[i] = SDL_SwapBE16(((Uint16) src[i]) ^ 0x8000);
+    }
+
+    return dst;
+}
+
+DECLSPEC SDL2_AudioStream * SDLCALL
+SDL_NewAudioStream(const SDL_AudioFormat real_src_format, const Uint8 src_channels, const int src_rate, const SDL_AudioFormat real_dst_format, const Uint8 dst_channels, const int dst_rate)
+{
+    SDL_AudioFormat src_format = real_src_format;
+    SDL_AudioFormat dst_format = real_dst_format;
+    SDL2_AudioStream *retval = SDL_malloc(sizeof (SDL2_AudioStream));
+    if (!retval) {
+        SDL3_OutOfMemory();
+        return NULL;
+    }
+
+    /* SDL3 removed U16 audio formats. Convert to S16SYS. */
+    if ((src_format == SDL2_AUDIO_U16LSB) || (src_format == SDL2_AUDIO_U16MSB)) {
+        src_format = AUDIO_S16SYS;
+    }
+    if ((dst_format == SDL2_AUDIO_U16LSB) || (dst_format == SDL2_AUDIO_U16MSB)) {
+        dst_format = AUDIO_S16SYS;
+    }
+
+    retval->stream3 = SDL3_CreateAudioStream(src_format, src_channels, src_rate, dst_format, dst_channels, dst_rate);
+    if (retval->stream3 == NULL) {
+        SDL_free(retval);
+        return NULL;
+    }
+
+    retval->src_format = real_src_format;
+    retval->dst_format = real_dst_format;
+    return retval;
+}
+
+DECLSPEC int SDLCALL
+SDL_AudioStreamPut(SDL2_AudioStream *stream2, const void *buf, int len)
+{
+    int retval;
+
+    /* SDL3 removed U16 audio formats. Convert to S16SYS. */
+    if (stream2 && buf && len && ((stream2->src_format == SDL2_AUDIO_U16LSB) || (stream2->src_format == SDL2_AUDIO_U16MSB))) {
+        const Uint32 tmpsamples = len / sizeof (Uint16);
+        Sint16 *tmpbuf = (Sint16 *) SDL3_malloc(len);
+        if (!tmpbuf) {
+            return SDL3_OutOfMemory();
+        }
+        SDL_memcpy(tmpbuf, buf, len);
+        if (stream2->src_format == SDL2_AUDIO_U16LSB) {
+            AudioUi16LSBToSi16Sys((Uint16 *) tmpbuf, tmpsamples);
+        } else if (stream2->src_format == SDL2_AUDIO_U16MSB) {
+            AudioUi16MSBToSi16Sys((Uint16 *) tmpbuf, tmpsamples);
+        }
+        retval = SDL3_PutAudioStreamData(stream2->stream3, tmpbuf, len);
+        SDL_free(tmpbuf);
+    } else {
+        retval = SDL3_PutAudioStreamData(stream2->stream3, buf, len);
+    }
+
+    return retval;
+}
+
+DECLSPEC int SDLCALL
+SDL_AudioStreamGet(SDL2_AudioStream *stream2, void *buf, int len)
+{
+    const int retval = stream2 ? SDL3_GetAudioStreamData(stream2->stream3, buf, len) : SDL3_InvalidParamError("stream");
+
+    if (retval > 0) {
+        /* SDL3 removed U16 audio formats. Convert to S16SYS. */
+        SDL_assert(stream2 != NULL);
+        SDL_assert(buf != NULL);
+        SDL_assert(len > 0);
+        if ((stream2->dst_format == SDL2_AUDIO_U16LSB) || (stream2->dst_format == SDL2_AUDIO_U16MSB)) {
+            if (stream2->dst_format == SDL2_AUDIO_U16LSB) {
+                AudioSi16SysToUi16LSB((Sint16 *) buf, retval / sizeof (Sint16));
+            } else if (stream2->dst_format == SDL2_AUDIO_U16MSB) {
+                AudioSi16SysToUi16MSB((Sint16 *) buf, retval / sizeof (Sint16));
+            }
+        }
+    }
+
+    return retval;
+}
+
+DECLSPEC int SDLCALL
+SDL_AudioStreamClear(SDL2_AudioStream *stream2)
+{
+    return SDL3_ClearAudioStream(stream2 ? stream2->stream3 : NULL);
+}
+
+DECLSPEC int SDLCALL
+SDL_AudioStreamAvailable(SDL2_AudioStream *stream2)
+{
+    return SDL3_GetAudioStreamAvailable(stream2 ? stream2->stream3 : NULL);
+}
+
+DECLSPEC void SDLCALL
+SDL_FreeAudioStream(SDL2_AudioStream *stream2)
+{
+    if (stream2) {
+        SDL3_DestroyAudioStream(stream2->stream3);
+        SDL3_free(stream2);
+    }
+}
+
+DECLSPEC int SDLCALL
+SDL_AudioStreamFlush(SDL2_AudioStream *stream2)
+{
+    return SDL3_FlushAudioStream(stream2 ? stream2->stream3 : NULL);
+}
+
+DECLSPEC void SDLCALL
+SDL_MixAudioFormat(Uint8 *dst, const Uint8 *src, SDL_AudioFormat format, Uint32 len, int volume)
+{
+    /* SDL3 removed U16 audio formats. Convert to S16SYS. */
+    if ((format == SDL2_AUDIO_U16LSB) || (format == SDL2_AUDIO_U16MSB)) {
+        const Uint32 tmpsamples = len / sizeof (Uint16);
+        Sint16 *tmpbuf = (Sint16 *) SDL3_malloc(len);
+        if (tmpbuf) {  /* if malloc fails, oh well, no mixed audio for you. */
+            SDL_memcpy(tmpbuf, src, len);
+            if (format == SDL2_AUDIO_U16LSB) {
+                AudioUi16LSBToSi16Sys((Uint16 *) tmpbuf, tmpsamples);
+            } else if (format == SDL2_AUDIO_U16MSB) {
+                AudioUi16MSBToSi16Sys((Uint16 *) tmpbuf, tmpsamples);
+            }
+            SDL3_MixAudioFormat(dst, src, AUDIO_S16SYS, tmpsamples * sizeof (Sint16), volume);
+            SDL3_free(tmpbuf);
+        }
+    } else {
+        SDL3_MixAudioFormat(dst, src, format, len, volume);
+    }
+}
+
 /*
  * Moved here from SDL_mixer.c, since it relies on internals of an opened
  *  audio device (and is deprecated, by the way!).
@@ -3262,7 +3471,7 @@ SDL_MixAudio(Uint8 *dst, const Uint8 *src, Uint32 len, int volume)
 {
     /* Mix the user-level audio format */
     if (g_audio_id > 0) {
-        SDL3_MixAudioFormat(dst, src, g_audio_spec.format, len, volume); /* FIXME: is this correct ?? */
+        SDL_MixAudioFormat(dst, src, g_audio_spec.format, len, volume);  /* call the sdl2-compat version, since it needs to handle U16 audio data. */
     }
 }
 
@@ -4221,12 +4430,6 @@ SDL_SetWindowMaximumSize(SDL_Window *window, int max_w, int max_h)
     SDL3_SetWindowMaximumSize(window, max_w, max_h);
 }
 
-DECLSPEC void SDLCALL
-SDL_MixAudioFormat(Uint8 *dst, const Uint8 *src, SDL_AudioFormat format, Uint32 len, int volume)
-{
-    SDL3_MixAudioFormat(dst, src, format, len, volume);
-}
-
 DECLSPEC void SDLCALL
 SDL_hid_close(SDL_hid_device * dev)
 {
@@ -4444,12 +4647,6 @@ SDL_GameControllerSetPlayerIndex(SDL_GameController *gamecontroller, int player_
     SDL3_SetGamepadPlayerIndex(gamecontroller, player_index);
 }
 
-DECLSPEC void SDLCALL 
-SDL_AudioStreamClear(SDL_AudioStream *stream)
-{
-    SDL3_ClearAudioStream(stream);
-}
-
 DECLSPEC void SDLCALL
 SDL_JoystickGetGUIDString(SDL_JoystickGUID guid, char *pszGUID, int cbGUID)
 {
@@ -4873,15 +5070,16 @@ SDL_SIMDFree(void *ptr)
 }
 
 
+/* !!! FIXME: move this all up with the other audio functions */
 static SDL_bool
 SDL_IsSupportedAudioFormat(const SDL_AudioFormat fmt)
 {
     switch (fmt) {
     case AUDIO_U8:
     case AUDIO_S8:
-    case AUDIO_U16LSB:
+    case SDL2_AUDIO_U16LSB:
     case AUDIO_S16LSB:
-    case AUDIO_U16MSB:
+    case SDL2_AUDIO_U16MSB:
     case AUDIO_S16MSB:
     case AUDIO_S32LSB:
     case AUDIO_S32MSB:
@@ -5010,12 +5208,10 @@ SDL_BuildAudioCVT(SDL_AudioCVT *cvt,
     return cvt->needed;
 }
 
-
 DECLSPEC int SDLCALL
 SDL_ConvertAudio(SDL_AudioCVT *cvt)
 {
-
-    SDL_AudioStream *stream;
+    SDL2_AudioStream *stream2;
     SDL_AudioFormat src_format, dst_format;
     int src_channels, src_rate;
     int dst_channels, dst_rate;
@@ -5044,9 +5240,10 @@ SDL_ConvertAudio(SDL_AudioCVT *cvt)
         dst_rate = ap.dst_rate;
     }
 
-    stream = SDL3_CreateAudioStream(src_format, src_channels, src_rate,
+    /* don't use the SDL3 stream directly; we want the U16 support in the sdl2-compat layer */
+    stream2 = SDL_NewAudioStream(src_format, src_channels, src_rate,
                                     dst_format, dst_channels, dst_rate);
-    if (stream == NULL) {
+    if (stream2 == NULL) {
         goto failure;
     }
 
@@ -5061,8 +5258,8 @@ SDL_ConvertAudio(SDL_AudioCVT *cvt)
     }
 
     /* Run the audio converter */
-    if (SDL3_PutAudioStreamData(stream, cvt->buf, src_len) < 0 ||
-        SDL3_FlushAudioStream(stream) < 0) {
+    if (SDL_AudioStreamPut(stream2, cvt->buf, src_len) < 0 ||
+        SDL_AudioStreamFlush(stream2) < 0) {
         goto failure;
     }
 
@@ -5070,18 +5267,19 @@ SDL_ConvertAudio(SDL_AudioCVT *cvt)
     dst_len = dst_len & ~(dst_samplesize - 1);
 
     /* Get back in the same buffer */
-    real_dst_len = SDL3_GetAudioStreamData(stream, cvt->buf, dst_len);
+    real_dst_len = SDL_AudioStreamGet(stream2, cvt->buf, dst_len);
     if (real_dst_len < 0) {
         goto failure;
     }
 
     cvt->len_cvt = real_dst_len;
 
-    SDL3_DestroyAudioStream(stream);
+    SDL_FreeAudioStream(stream2);
+
     return 0;
 
 failure:
-    SDL3_DestroyAudioStream(stream);
+    SDL_FreeAudioStream(stream2);
     return -1;
 }
 
diff --git a/src/sdl2_compat.h b/src/sdl2_compat.h
index b771794..2156577 100644
--- a/src/sdl2_compat.h
+++ b/src/sdl2_compat.h
@@ -89,4 +89,11 @@ typedef struct SDL_AudioCVT
     int filter_index;           /**< Current audio conversion function */
 } SDL_AUDIOCVT_PACKED SDL_AudioCVT;
 
+typedef struct SDL2_AudioStream
+{
+    SDL_AudioStream *stream3;
+    SDL_AudioFormat src_format;
+    SDL_AudioFormat dst_format;
+} SDL2_AudioStream;
+
 #endif /* sdl2_compat_h */
diff --git a/src/sdl3_syms.h b/src/sdl3_syms.h
index 49fe544..5c1fb10 100644
--- a/src/sdl3_syms.h
+++ b/src/sdl3_syms.h
@@ -102,7 +102,7 @@ SDL3_SYM_PASSTHROUGH(const char*,GetAudioDriver,(int a),(a),return)
 SDL3_SYM_PASSTHROUGH(const char*,GetCurrentAudioDriver,(void),(),return)
 SDL3_SYM_PASSTHROUGH(int,GetNumAudioDevices,(int a),(a),return)
 SDL3_SYM_PASSTHROUGH(const char*,GetAudioDeviceName,(int a, int b),(a,b),return)
-SDL3_SYM_PASSTHROUGH(SDL_AudioDeviceID,OpenAudioDevice,(const char *a, int b, const SDL_AudioSpec *c, SDL_AudioSpec *d, int e),(a,b,c,d,e),return)
+SDL3_SYM(SDL_AudioDeviceID,OpenAudioDevice,(const char *a, int b, const SDL_AudioSpec *c, SDL_AudioSpec *d, int e),(a,b,c,d,e),return)
 SDL3_SYM(SDL_AudioStatus,GetAudioDeviceStatus,(SDL_AudioDeviceID a),(a),return)
 SDL3_SYM(int,PlayAudioDevice,(SDL_AudioDeviceID a),(a),return)
 SDL3_SYM(int,PauseAudioDevice,(SDL_AudioDeviceID a),(a),return)
@@ -599,13 +599,13 @@ SDL3_SYM_PASSTHROUGH(SDL_bool,Vulkan_CreateSurface,(SDL_Window *a, VkInstance b,
 SDL3_SYM_PASSTHROUGH(void,GetMemoryFunctions,(SDL_malloc_func *a, SDL_calloc_func *b, SDL_realloc_func *c, SDL_free_func *d),(a,b,c,d),)
 SDL3_SYM_PASSTHROUGH(int,SetMemoryFunctions,(SDL_malloc_func a, SDL_calloc_func b, SDL_realloc_func c, SDL_free_func d),(a,b,c,d),return)
 SDL3_SYM_PASSTHROUGH(int,GetNumAllocations,(void),(),return)
-SDL3_SYM_RENAMED(SDL_AudioStream*,NewAudioStream,CreateAudioStream,(const SDL_AudioFormat a, const Uint8 b, const int c, const SDL_AudioFormat d, const Uint8 e, const int f),(a,b,c,d,e,f),return)
-SDL3_SYM_RENAMED(int,AudioStreamPut,PutAudioStreamData,(SDL_AudioStream *a, const void *b, int c),(a,b,c),return)
-SDL3_SYM_RENAMED(int,AudioStreamGet,GetAudioStreamData,(SDL_AudioStream *a, void *b, int c),(a,b,c),return)
+SDL3_SYM(SDL_AudioStream*,CreateAudioStream,(const SDL_AudioFormat a, const Uint8 b, const int c, const SDL_AudioFormat d, const Uint8 e, const int f),(a,b,c,d,e,f),return)
+SDL3_SYM(int,PutAudioStreamData,(SDL_AudioStream *a, const void *b, int c),(a,b,c),return)
+SDL3_SYM(int,GetAudioStreamData,(SDL_AudioStream *a, void *b, int c),(a,b,c),return)
 SDL3_SYM(int,ClearAudioStream,(SDL_AudioStream *a),(a),return)
-SDL3_SYM_RENAMED(int,AudioStreamAvailable,GetAudioStreamAvailable,(SDL_AudioStream *a),(a),return)
-SDL3_SYM_RENAMED(void,FreeAudioStream,DestroyAudioStream,(SDL_AudioStream *a),(a),)
-SDL3_SYM_RENAMED(int,AudioStreamFlush,FlushAudioStream,(SDL_AudioStream *a),(a),return)
+SDL3_SYM(int,GetAudioStreamAvailable,(SDL_AudioStream *a),(a),return)
+SDL3_SYM(void,DestroyAudioStream,(SDL_AudioStream *a),(a),)
+SDL3_SYM(int,FlushAudioStream,(SDL_AudioStream *a),(a),return)
 SDL3_SYM_PASSTHROUGH(float,acosf,(float a),(a),return)
 SDL3_SYM_PASSTHROUGH(float,asinf,(float a),(a),return)
 SDL3_SYM_PASSTHROUGH(float,atanf,(float a),(a),return)