SDL_mixer: api: MIX_Generate now returns number of mixed bytes, without appended silence.

From 40ee0bc84ab43b2e7c14ac9e7480e3e56128c165 Mon Sep 17 00:00:00 2001
From: "Ryan C. Gordon" <[EMAIL REDACTED]>
Date: Thu, 26 Feb 2026 10:20:18 -0500
Subject: [PATCH] api: MIX_Generate now returns number of mixed bytes, without
 appended silence.

(The buffer is still fully initialized with the requested number of bytes,
though, which is probably how most things will want to use this function.)

Fixes #815.
---
 include/SDL3_mixer/SDL_mixer.h | 14 ++++++++++++--
 src/SDL_mixer.c                | 24 ++++++++++++++++++------
 src/SDL_mixer_internal.h       |  1 +
 test/testmixer.c               |  6 ++++--
 4 files changed, 35 insertions(+), 10 deletions(-)

diff --git a/include/SDL3_mixer/SDL_mixer.h b/include/SDL3_mixer/SDL_mixer.h
index c6c0ba35..04f47f89 100644
--- a/include/SDL3_mixer/SDL_mixer.h
+++ b/include/SDL3_mixer/SDL_mixer.h
@@ -2991,10 +2991,20 @@ extern SDL_DECLSPEC bool SDLCALL MIX_SetPostMixCallback(MIX_Mixer *mixer, MIX_Po
  * This function can not be used with mixers from MIX_CreateMixerDevice();
  * those generate audio as needed internally.
  *
+ * This function returns the number of _bytes_ of real audio mixed, which
+ * might be less than `buflen`. While all `buflen` bytes of `buffer` will be
+ * initialized, if available tracks to mix run out, the end of the buffer will
+ * be initialized with silence; this silence will not be counted in the return
+ * value, so the caller has the option to identify how much of the buffer has
+ * legimitate contents vs appended silence. As such, any value >= 0 signifies
+ * success. A return value of -1 means failure (out of memory, invalid
+ * parameters, etc).
+ *
  * \param mixer the mixer for which to generate more audio.
  * \param buffer a pointer to a buffer to store audio in.
  * \param buflen the number of bytes to store in buffer.
- * \returns true on success or false on failure; call SDL_GetError() for more
+ * \returns The number of bytes of mixed audio, discounting appended silence,
+ *          on success, or -1 on failure; call SDL_GetError() for more
  *          information.
  *
  * \threadsafety It is safe to call this function from any thread.
@@ -3003,7 +3013,7 @@ extern SDL_DECLSPEC bool SDLCALL MIX_SetPostMixCallback(MIX_Mixer *mixer, MIX_Po
  *
  * \sa MIX_CreateMixer
  */
-extern SDL_DECLSPEC bool SDLCALL MIX_Generate(MIX_Mixer *mixer, void *buffer, int buflen);
+extern SDL_DECLSPEC int SDLCALL MIX_Generate(MIX_Mixer *mixer, void *buffer, int buflen);
 
 
 /* Decode audio files directly without a mixer ... */
diff --git a/src/SDL_mixer.c b/src/SDL_mixer.c
index 53cfdedb..898e5605 100644
--- a/src/SDL_mixer.c
+++ b/src/SDL_mixer.c
@@ -528,12 +528,13 @@ static void MixFloat32Audio(float *dst, const float *src, const int buffer_size,
 // SDL calls this function from the audio device thread as more data is needed the mixer.
 static void SDLCALL MixerCallback(void *userdata, SDL_AudioStream *stream, int additional_amount, int total_amount)
 {
+    MIX_Mixer *mixer = (MIX_Mixer *) userdata;
+    mixer->actual_mixed_bytes = 0;
+
     if (additional_amount == 0) {
         return;  // nothing to actually do yet. This was a courtesy call; the stream still has enough buffered.
     }
 
-    MIX_Mixer *mixer = (MIX_Mixer *) userdata;
-
     // it should be asking for float data...
     SDL_assert((additional_amount % sizeof (float)) == 0);
 
@@ -603,6 +604,10 @@ static void SDLCALL MixerCallback(void *userdata, SDL_AudioStream *stream, int a
             }
         }
 
+        if (group_bytes > mixer->actual_mixed_bytes) {
+            mixer->actual_mixed_bytes = group_bytes;
+        }
+
         if (group->postmix_callback) {
             group->postmix_callback(group->postmix_callback_userdata, group, &mixer->spec, group_mixbuf, additional_amount / sizeof (float));
         }
@@ -619,14 +624,21 @@ static void SDLCALL MixerCallback(void *userdata, SDL_AudioStream *stream, int a
     SDL_PutAudioStreamData(stream, final_mixbuf, additional_amount);
 }
 
-bool MIX_Generate(MIX_Mixer *mixer, void *buffer, int buflen)
+int MIX_Generate(MIX_Mixer *mixer, void *buffer, int buflen)
 {
+    SDL_AudioSpec output_spec;
     if (!CheckMixerParam(mixer)) {
-        return false;
+        return -1;
     } else if (mixer->device_id) {
-        return SDL_SetError("Can't use MIX_Generate with a MIX_Mixer from MIX_CreateMixerDevice");
+        SDL_SetError("Can't use MIX_Generate with a MIX_Mixer from MIX_CreateMixerDevice");
+        return -1;
+    } else if (!SDL_GetAudioStreamFormat(mixer->output_stream, NULL, &output_spec)) {
+        return -1;
+    } else if (!SDL_GetAudioStreamData(mixer->output_stream, buffer, buflen)) {  // will fire MixerCallback() to generate audio.
+        return -1;
     }
-    return SDL_GetAudioStreamData(mixer->output_stream, buffer, buflen);  // will fire MixerCallback() to generate audio.
+
+    return (mixer->actual_mixed_bytes / sizeof (float)) * SDL_AUDIO_BYTESIZE(output_spec.format);
 }
 
 static void InitDecoders(void)
diff --git a/src/SDL_mixer_internal.h b/src/SDL_mixer_internal.h
index 3e0e514a..cdc0c45b 100644
--- a/src/SDL_mixer_internal.h
+++ b/src/SDL_mixer_internal.h
@@ -205,6 +205,7 @@ struct MIX_Mixer
     void *postmix_callback_userdata;
     float *mix_buffer;
     size_t mix_buffer_allocation;
+    int actual_mixed_bytes;   // on each iteration of the mixer, number of bytes of real mixed audio, ignoring silence at end if no audio was available to mix there.
     float gain;
     MIX_VBAP2D vbap2d;
     MIX_Mixer *prev;  // double-linked list for all_mixers.
diff --git a/test/testmixer.c b/test/testmixer.c
index b2bc4360..1ca084cb 100644
--- a/test/testmixer.c
+++ b/test/testmixer.c
@@ -264,11 +264,13 @@ SDL_AppResult SDL_AppIterate(void *appstate)
 {
     #if USE_MIX_GENERATE
     float buf[1024];
-    if (!MIX_Generate(mixer, buf, sizeof (buf))) {
+    const int br = MIX_Generate(mixer, buf, sizeof (buf));
+    if (br == -1) {
         SDL_Log("MIX_Generate failed: %s", SDL_GetError());
         return SDL_APP_FAILURE;
     }
-    SDL_WriteIO(io, buf, sizeof (buf));
+    //SDL_Log("MIX_Generate() came through with %d bytes vs %d requested", br, (int) sizeof (buf));
+    SDL_WriteIO(io, buf, br);
     #endif
 
 //    SDL_RenderClear(renderer);