SDL: Added SDL_(Get|Set)AudioStreamSpeed

From e55844274de9381e80e568b0162b07292b28f2ab Mon Sep 17 00:00:00 2001
From: Brick <[EMAIL REDACTED]>
Date: Thu, 31 Aug 2023 13:31:42 +0100
Subject: [PATCH] Added SDL_(Get|Set)AudioStreamSpeed

---
 include/SDL3/SDL_audio.h          | 35 +++++++++++++++++++++++++++++
 src/audio/SDL_audiocvt.c          | 37 +++++++++++++++++++++++++++++++
 src/dynapi/SDL_dynapi.sym         |  2 ++
 src/dynapi/SDL_dynapi_overrides.h |  2 ++
 src/dynapi/SDL_dynapi_procs.h     |  2 ++
 5 files changed, 78 insertions(+)

diff --git a/include/SDL3/SDL_audio.h b/include/SDL3/SDL_audio.h
index f7f96289652a..4886f86f97c9 100644
--- a/include/SDL3/SDL_audio.h
+++ b/include/SDL3/SDL_audio.h
@@ -703,11 +703,46 @@ extern DECLSPEC int SDLCALL SDL_GetAudioStreamFormat(SDL_AudioStream *stream,
  * \sa SDL_PutAudioStreamData
  * \sa SDL_GetAudioStreamData
  * \sa SDL_GetAudioStreamAvailable
+ * \sa SDL_SetAudioStreamSpeed
  */
 extern DECLSPEC int SDLCALL SDL_SetAudioStreamFormat(SDL_AudioStream *stream,
                                                      const SDL_AudioSpec *src_spec,
                                                      const SDL_AudioSpec *dst_spec);
 
+/**
+ * Get the playback speed of an audio stream.
+ *
+ * \param stream the SDL_AudioStream to query.
+ * \returns the playback speed of the stream, or 0.0 on error
+ *
+ * \threadsafety It is safe to call this function from any thread, as it holds
+ *               a stream-specific mutex while running.
+ *
+ * \since This function is available since SDL 3.0.0.
+ *
+ * \sa SDL_SetAudioStreamSpeed
+ */
+extern DECLSPEC float SDLCALL SDL_GetAudioStreamSpeed(SDL_AudioStream *stream);
+
+/**
+ * Change the playback speed of an audio stream.
+ *
+ * \param stream The stream the speed is being changed
+ * \param speed The new speed. 1.0 is normal speed, 1.2 is 20% faster, etc.
+ *              Must be between 0.01 and 100.
+ * \returns 0 on success, or -1 on error.
+ *
+ * \threadsafety It is safe to call this function from any thread, as it holds
+ *               a stream-specific mutex while running.
+ *
+ * \since This function is available since SDL 3.0.0.
+ *
+ * \sa SDL_GetAudioStreamSpeed
+ * \sa SDL_SetAudioStreamFormat
+ */
+extern DECLSPEC int SDLCALL SDL_SetAudioStreamSpeed(SDL_AudioStream *stream,
+                                                    float speed);
+
 /**
  * Add data to be converted/resampled to the stream.
  *
diff --git a/src/audio/SDL_audiocvt.c b/src/audio/SDL_audiocvt.c
index 93ae439e307b..b04b5c15f068 100644
--- a/src/audio/SDL_audiocvt.c
+++ b/src/audio/SDL_audiocvt.c
@@ -1237,6 +1237,43 @@ int SDL_SetAudioStreamFormat(SDL_AudioStream *stream, const SDL_AudioSpec *src_s
     return 0;
 }
 
+float SDL_GetAudioStreamSpeed(SDL_AudioStream *stream)
+{
+    if (!stream) {
+        SDL_InvalidParamError("stream");
+        return 0.0f;
+    }
+
+    SDL_LockMutex(stream->lock);
+    float speed = stream->speed;
+    SDL_UnlockMutex(stream->lock);
+
+    return speed;
+}
+
+int SDL_SetAudioStreamSpeed(SDL_AudioStream *stream, float speed)
+{
+    if (!stream) {
+        return SDL_InvalidParamError("stream");
+    }
+
+    // Picked mostly arbitrarily.
+    static const float min_speed = 0.01f;
+    static const float max_speed = 100.0f;
+
+    if (speed < min_speed) {
+        return SDL_SetError("Speed is too low");
+    } else if (speed > max_speed) {
+        return SDL_SetError("Speed is too high");
+    }
+
+    SDL_LockMutex(stream->lock);
+    stream->speed = speed;
+    SDL_UnlockMutex(stream->lock);
+
+    return 0;
+}
+
 static int CheckAudioStreamIsFullySetup(SDL_AudioStream *stream)
 {
     if (stream->src_spec.format == 0) {
diff --git a/src/dynapi/SDL_dynapi.sym b/src/dynapi/SDL_dynapi.sym
index 0887e4ba36e1..dec025772085 100644
--- a/src/dynapi/SDL_dynapi.sym
+++ b/src/dynapi/SDL_dynapi.sym
@@ -902,6 +902,8 @@ SDL3_0.0.0 {
     SDL_WriteS64BE;
     SDL_GDKGetDefaultUser;
     SDL_SetWindowFocusable;
+    SDL_GetAudioStreamSpeed;
+    SDL_SetAudioStreamSpeed;
     # extra symbols go here (don't modify this line)
   local: *;
 };
diff --git a/src/dynapi/SDL_dynapi_overrides.h b/src/dynapi/SDL_dynapi_overrides.h
index 93d5db6b85df..faa73153a782 100644
--- a/src/dynapi/SDL_dynapi_overrides.h
+++ b/src/dynapi/SDL_dynapi_overrides.h
@@ -927,3 +927,5 @@
 #define SDL_WriteS64BE SDL_WriteS64BE_REAL
 #define SDL_GDKGetDefaultUser SDL_GDKGetDefaultUser_REAL
 #define SDL_SetWindowFocusable SDL_SetWindowFocusable_REAL
+#define SDL_GetAudioStreamSpeed SDL_GetAudioStreamSpeed_REAL
+#define SDL_SetAudioStreamSpeed SDL_SetAudioStreamSpeed_REAL
diff --git a/src/dynapi/SDL_dynapi_procs.h b/src/dynapi/SDL_dynapi_procs.h
index 61bf6a26ce62..d0778b87ee39 100644
--- a/src/dynapi/SDL_dynapi_procs.h
+++ b/src/dynapi/SDL_dynapi_procs.h
@@ -973,3 +973,5 @@ SDL_DYNAPI_PROC(SDL_bool,SDL_WriteS64BE,(SDL_RWops *a, Sint64 b),(a,b),return)
 SDL_DYNAPI_PROC(int,SDL_GDKGetDefaultUser,(XUserHandle *a),(a),return)
 #endif
 SDL_DYNAPI_PROC(int,SDL_SetWindowFocusable,(SDL_Window *a, SDL_bool b),(a,b),return)
+SDL_DYNAPI_PROC(float,SDL_GetAudioStreamSpeed,(SDL_AudioStream *a),(a),return)
+SDL_DYNAPI_PROC(int,SDL_SetAudioStreamSpeed,(SDL_AudioStream *a, float b),(a,b),return)