sdl2-compat: Re-add SDL_AudioCVT interface, based on SDL_AudioStream

From 17b5c20b39797ea4f470b068a93f758cc175cd09 Mon Sep 17 00:00:00 2001
From: Sylvain <[EMAIL REDACTED]>
Date: Mon, 16 Jan 2023 09:52:02 +0100
Subject: [PATCH] Re-add SDL_AudioCVT interface, based on SDL_AudioStream

---
 src/sdl2_compat.c          | 223 ++++++++++++++++++++++++++++++++++++-
 src/sdl2_compat.h          |  56 ++++++++++
 src/sdl3_include_wrapper.h |  10 --
 src/sdl3_syms.h            |   2 -
 4 files changed, 274 insertions(+), 17 deletions(-)

diff --git a/src/sdl2_compat.c b/src/sdl2_compat.c
index 5148917..46dfe37 100644
--- a/src/sdl2_compat.c
+++ b/src/sdl2_compat.c
@@ -1111,7 +1111,7 @@ SDL_GetRevisionNumber(void)
 }
 
 
-DECLSPEC void SDLCALL
+DECLSPEC int SDLCALL
 SDL_SetError(const char *fmt, ...)
 {
     char ch;
@@ -1133,6 +1133,7 @@ SDL_SetError(const char *fmt, ...)
         SDL3_SetError("%s", str);
         SDL3_free(str);
     }
+    return -1;
 }
 
 DECLSPEC int SDLCALL
@@ -2986,8 +2987,7 @@ SDL_OpenAudio(SDL_AudioSpec *desired, SDL_AudioSpec *obtained)
     }
 
     if (g_audio_id > 0) {
-        SDL_SetError("Audio device is already opened");
-        return -1;
+        return SDL3_SetError("Audio device is already opened");
     }
 
     if (obtained) {
@@ -3081,14 +3081,14 @@ SDL_LockAudioDevice(SDL_AudioDeviceID dev)
     SDL3_LockAudioDevice(id);
 }
 
-DECLSPEC void SDLCALL 
+DECLSPEC void SDLCALL
 SDL_UnlockAudioDevice(SDL_AudioDeviceID dev)
 {
     SDL_AudioDeviceID id = dev == 1 ? g_audio_id : dev;
     SDL3_UnlockAudioDevice(id);
 }
 
-DECLSPEC void SDLCALL 
+DECLSPEC void SDLCALL
 SDL_CloseAudioDevice(SDL_AudioDeviceID dev)
 {
     SDL_AudioDeviceID id = dev == 1 ? g_audio_id : dev;
@@ -3914,6 +3914,219 @@ DECLSPEC void SDLCALL SDL_SIMDFree(void *ptr)
 }
 
 
+
+static SDL_bool SDL_IsSupportedAudioFormat(const SDL_AudioFormat fmt)
+{
+    switch (fmt) {
+    case AUDIO_U8:
+    case AUDIO_S8:
+    case AUDIO_U16LSB:
+    case AUDIO_S16LSB:
+    case AUDIO_U16MSB:
+    case AUDIO_S16MSB:
+    case AUDIO_S32LSB:
+    case AUDIO_S32MSB:
+    case AUDIO_F32LSB:
+    case AUDIO_F32MSB:
+        return SDL_TRUE; /* supported. */
+
+    default:
+        break;
+    }
+
+    return SDL_FALSE; /* unsupported. */
+}
+
+static SDL_bool SDL_IsSupportedChannelCount(const int channels)
+{
+    return ((channels >= 1) && (channels <= 8)) ? SDL_TRUE : SDL_FALSE;
+}
+
+
+typedef struct {
+    SDL_AudioFormat src_format;
+    Uint8 src_channels;
+    int src_rate;
+    SDL_AudioFormat dst_format;
+    Uint8 dst_channels;
+    int dst_rate;
+} AudioParam;
+
+#define RESAMPLER_BITS_PER_SAMPLE           16
+#define RESAMPLER_SAMPLES_PER_ZERO_CROSSING (1 << ((RESAMPLER_BITS_PER_SAMPLE / 2) + 1))
+
+
+DECLSPEC int SDLCALL SDL_BuildAudioCVT(SDL_AudioCVT *cvt,
+                                       SDL_AudioFormat src_format,
+                                       Uint8 src_channels,
+                                       int src_rate,
+                                       SDL_AudioFormat dst_format,
+                                       Uint8 dst_channels,
+                                       int dst_rate)
+{
+    /* Sanity check target pointer */
+    if (cvt == NULL) {
+        return SDL_InvalidParamError("cvt");
+    }
+
+    /* Make sure we zero out the audio conversion before error checking */
+    SDL_zerop(cvt);
+
+    if (!SDL_IsSupportedAudioFormat(src_format)) {
+        return SDL_SetError("Invalid source format");
+    }
+    if (!SDL_IsSupportedAudioFormat(dst_format)) {
+        return SDL_SetError("Invalid destination format");
+    }
+    if (!SDL_IsSupportedChannelCount(src_channels)) {
+        return SDL_SetError("Invalid source channels");
+    }
+    if (!SDL_IsSupportedChannelCount(dst_channels)) {
+        return SDL_SetError("Invalid destination channels");
+    }
+    if (src_rate <= 0) {
+        return SDL_SetError("Source rate is equal to or less than zero");
+    }
+    if (dst_rate <= 0) {
+        return SDL_SetError("Destination rate is equal to or less than zero");
+    }
+    if (src_rate >= SDL_MAX_SINT32 / RESAMPLER_SAMPLES_PER_ZERO_CROSSING) {
+        return SDL_SetError("Source rate is too high");
+    }
+    if (dst_rate >= SDL_MAX_SINT32 / RESAMPLER_SAMPLES_PER_ZERO_CROSSING) {
+        return SDL_SetError("Destination rate is too high");
+    }
+
+#if DEBUG_CONVERT
+    SDL_Log("SDL_AUDIO_CONVERT: Build format %04x->%04x, channels %u->%u, rate %d->%d\n",
+            src_format, dst_format, src_channels, dst_channels, src_rate, dst_rate);
+#endif
+
+    /* Start off with no conversion necessary */
+    cvt->src_format = src_format;
+    cvt->dst_format = dst_format;
+    cvt->needed = 0;
+    cvt->filter_index = 0;
+    SDL_zeroa(cvt->filters);
+    cvt->len_mult = 1;
+    cvt->len_ratio = 1.0;
+    cvt->rate_incr = ((double)dst_rate) / ((double)src_rate);
+
+    /* Use the filters[] to store some data ... */
+    {
+        AudioParam ap;
+        ap.src_format = src_format;
+        ap.src_channels = src_channels;
+        ap.src_rate = src_rate;
+        ap.dst_format = dst_format;
+        ap.dst_channels = dst_channels;
+        ap.dst_rate = dst_rate;
+
+        /* Store at the end of filters[], aligned */
+        SDL_memcpy(
+            (Uint8 *)&cvt->filters[SDL_AUDIOCVT_MAX_FILTERS + 1] - (sizeof(AudioParam) & ~3),
+            &ap,
+            sizeof(ap));
+
+        cvt->filters[0] = NULL;
+        cvt->needed = 1;
+        if (src_format == dst_format && src_rate == dst_rate && src_channels == dst_channels) {
+            cvt->needed = 0;
+        }
+
+        if (src_rate < dst_rate) {
+            const int mult = (dst_rate / src_rate);
+            cvt->len_mult *= mult;
+            cvt->len_ratio *= mult;
+        } else {
+            const int div = (src_rate / dst_rate);
+            cvt->len_ratio /= div;
+        }
+
+        if (src_channels < dst_channels) {
+            cvt->len_mult = ((cvt->len_mult * dst_channels) + (src_channels - 1)) / src_channels;
+        }
+    }
+
+    return cvt->needed;
+}
+
+
+DECLSPEC int SDLCALL SDL_ConvertAudio(SDL_AudioCVT *cvt)
+{
+
+    SDL_AudioStream *stream;
+    SDL_AudioFormat src_format, dst_format;
+    int src_channels, src_rate;
+    int dst_channels, dst_rate;
+
+    int src_len, dst_len, real_dst_len;
+    int src_samplesize, dst_samplesize;
+
+    /* Sanity check target pointer */
+    if (cvt == NULL) {
+        return SDL_InvalidParamError("cvt");
+    }
+
+    {
+        AudioParam ap;
+
+        /* Fetch from the end of filters[], aligned */
+        SDL_memcpy(
+            &ap,
+            (Uint8 *)&cvt->filters[SDL_AUDIOCVT_MAX_FILTERS + 1] - (sizeof(AudioParam) & ~3),
+            sizeof(ap));
+
+        src_format = ap.dst_format;
+        src_channels = ap.src_channels;
+        src_rate = ap.src_rate;
+        dst_format = ap.dst_format;
+        dst_channels = ap.dst_channels;
+        dst_rate = ap.dst_rate;
+    }
+
+    stream = SDL3_CreateAudioStream(src_format, src_channels, src_rate,
+                                    dst_format, dst_channels, dst_rate);
+    if (stream == NULL) {
+        goto failure;
+    }
+
+    src_samplesize = (SDL_AUDIO_BITSIZE(src_format) / 8) * src_channels;
+    dst_samplesize = (SDL_AUDIO_BITSIZE(dst_format) / 8) * dst_channels;
+
+    src_len = cvt->len & ~(src_samplesize - 1);
+    dst_len = dst_samplesize * (src_len / src_samplesize);
+    if (src_rate < dst_rate) {
+        const double mult = ((double)dst_rate) / ((double)src_rate);
+        dst_len *= (int) SDL_ceil(mult);
+    }
+
+    /* Run the audio converter */
+    if (SDL3_PutAudioStreamData(stream, cvt->buf, src_len) < 0 ||
+        SDL3_FlushAudioStream(stream) < 0) {
+        goto failure;
+    }
+
+    dst_len = SDL_min(dst_len, cvt->len * cvt->len_mult);
+    dst_len = dst_len & ~(dst_samplesize - 1);
+
+    /* Get back in the same buffer */
+    real_dst_len = SDL3_GetAudioStreamData(stream, cvt->buf, dst_len);
+    if (real_dst_len < 0) {
+        goto failure;
+    }
+
+    cvt->len_cvt = real_dst_len;
+
+    SDL3_DestroyAudioStream(stream);
+    return 0;
+
+failure:
+    SDL3_DestroyAudioStream(stream);
+    return -1;
+}
+
+
 #ifdef __cplusplus
 }
 #endif
diff --git a/src/sdl2_compat.h b/src/sdl2_compat.h
index 6097e7c..a276bd0 100644
--- a/src/sdl2_compat.h
+++ b/src/sdl2_compat.h
@@ -35,4 +35,60 @@ typedef Sint32 SDL2_SensorID;  /* this became unsigned in SDL3, but we'll just h
 
 typedef Sint64 SDL2_GestureID;
 
+
+struct SDL_AudioCVT;
+typedef void (SDLCALL * SDL_AudioFilter) (struct SDL_AudioCVT * cvt,
+                                          SDL_AudioFormat format);
+
+/**
+ *  \brief Upper limit of filters in SDL_AudioCVT
+ *
+ *  The maximum number of SDL_AudioFilter functions in SDL_AudioCVT is
+ *  currently limited to 9. The SDL_AudioCVT.filters array has 10 pointers,
+ *  one of which is the terminating NULL pointer.
+ */
+#define SDL_AUDIOCVT_MAX_FILTERS 9
+
+/**
+ *  \struct SDL_AudioCVT
+ *  \brief A structure to hold a set of audio conversion filters and buffers.
+ *
+ *  Note that various parts of the conversion pipeline can take advantage
+ *  of SIMD operations (like SSE2, for example). SDL_AudioCVT doesn't require
+ *  you to pass it aligned data, but can possibly run much faster if you
+ *  set both its (buf) field to a pointer that is aligned to 16 bytes, and its
+ *  (len) field to something that's a multiple of 16, if possible.
+ */
+#if defined(__GNUC__) && !defined(__CHERI_PURE_CAPABILITY__)
+/* This structure is 84 bytes on 32-bit architectures, make sure GCC doesn't
+   pad it out to 88 bytes to guarantee ABI compatibility between compilers.
+   This is not a concern on CHERI architectures, where pointers must be stored
+   at aligned locations otherwise they will become invalid, and thus structs
+   containing pointers cannot be packed without giving a warning or error.
+   vvv
+   The next time we rev the ABI, make sure to size the ints and add padding.
+*/
+#define SDL_AUDIOCVT_PACKED __attribute__((packed))
+#else
+#define SDL_AUDIOCVT_PACKED
+#endif
+/* */
+typedef struct SDL_AudioCVT
+{
+    int needed;                 /**< Set to 1 if conversion possible */
+    SDL_AudioFormat src_format; /**< Source audio format */
+    SDL_AudioFormat dst_format; /**< Target audio format */
+    double rate_incr;           /**< Rate conversion increment */
+    Uint8 *buf;                 /**< Buffer to hold entire audio data */
+    int len;                    /**< Length of original audio buffer */
+    int len_cvt;                /**< Length of converted audio buffer */
+    int len_mult;               /**< buffer must be len*len_mult big */
+    double len_ratio;           /**< Given len, final size is len*len_ratio */
+    SDL_AudioFilter filters[SDL_AUDIOCVT_MAX_FILTERS + 1]; /**< NULL-terminated list of filter functions */
+    int filter_index;           /**< Current audio conversion function */
+} SDL_AUDIOCVT_PACKED SDL_AudioCVT;
+
+
+
+
 #endif /* sdl2_compat_h */
diff --git a/src/sdl3_include_wrapper.h b/src/sdl3_include_wrapper.h
index 9260720..f697e49 100644
--- a/src/sdl3_include_wrapper.h
+++ b/src/sdl3_include_wrapper.h
@@ -44,8 +44,6 @@
 #define SDL_GetAudioDeviceStatus IGNORE_THIS_VERSION_OF_SDL_GetAudioDeviceStatus
 #define SDL_PauseAudioDevice IGNORE_THIS_VERSION_OF_SDL_PauseAudioDevice
 #define SDL_LoadWAV_RW IGNORE_THIS_VERSION_OF_SDL_LoadWAV_RW
-#define SDL_BuildAudioCVT IGNORE_THIS_VERSION_OF_SDL_BuildAudioCVT
-#define SDL_ConvertAudio IGNORE_THIS_VERSION_OF_SDL_ConvertAudio
 #define SDL_CreateAudioStream IGNORE_THIS_VERSION_OF_SDL_CreateAudioStream
 #define SDL_PutAudioStreamData IGNORE_THIS_VERSION_OF_SDL_PutAudioStreamData
 #define SDL_GetAudioStreamAvailable IGNORE_THIS_VERSION_OF_SDL_GetAudioStreamAvailable
@@ -986,14 +984,6 @@ typedef void (__cdecl *pfnSDL_CurrentEndThread) (unsigned);
 #undef SDL_LoadWAV_RW
 #endif
 
-#ifdef SDL_BuildAudioCVT
-#undef SDL_BuildAudioCVT
-#endif
-
-#ifdef SDL_ConvertAudio
-#undef SDL_ConvertAudio
-#endif
-
 #ifdef SDL_CreateAudioStream
 #undef SDL_CreateAudioStream
 #endif
diff --git a/src/sdl3_syms.h b/src/sdl3_syms.h
index f578a99..708f8fb 100644
--- a/src/sdl3_syms.h
+++ b/src/sdl3_syms.h
@@ -107,8 +107,6 @@ SDL3_SYM(SDL_AudioStatus,GetAudioDeviceStatus,(SDL_AudioDeviceID a),(a),return)
 SDL3_SYM(void,PlayAudioDevice,(SDL_AudioDeviceID a),(a),)
 SDL3_SYM(void,PauseAudioDevice,(SDL_AudioDeviceID a),(a),)
 SDL3_SYM(SDL_AudioSpec*,LoadWAV_RW,(SDL_RWops *a, int b, SDL_AudioSpec *c, Uint8 **d, Uint32 *e),(a,b,c,d,e),return)
-SDL3_SYM_PASSTHROUGH(int,BuildAudioCVT,(SDL_AudioCVT *a, SDL_AudioFormat b, Uint8 c, int d, SDL_AudioFormat e, Uint8 f, int g),(a,b,c,d,e,f,g),return)
-SDL3_SYM_PASSTHROUGH(int,ConvertAudio,(SDL_AudioCVT *a),(a),return)
 SDL3_SYM_PASSTHROUGH(void,MixAudioFormat,(Uint8 *a, const Uint8 *b, SDL_AudioFormat c, Uint32 d, int e),(a,b,c,d,e),)
 SDL3_SYM(void,LockAudioDevice,(SDL_AudioDeviceID a),(a),)
 SDL3_SYM(void,UnlockAudioDevice,(SDL_AudioDeviceID a),(a),)