SDL_mixer: sinewave: Allow creating a non-infinite sinewave MIX_Audio.

From 4b6680b72faa229cdbf6dfa0a6afca02be4f552f Mon Sep 17 00:00:00 2001
From: "Ryan C. Gordon" <[EMAIL REDACTED]>
Date: Mon, 12 Jan 2026 16:29:14 -0500
Subject: [PATCH] sinewave: Allow creating a non-infinite sinewave MIX_Audio.

Fixes #798.
---
 include/SDL3_mixer/SDL_mixer.h | 11 +++++++----
 src/SDL_mixer.c                |  3 ++-
 src/SDL_mixer_internal.h       |  1 +
 src/decoder_sinewave.c         | 26 +++++++++++++++++++++++---
 test/testmixer.c               |  3 ++-
 5 files changed, 35 insertions(+), 9 deletions(-)

diff --git a/include/SDL3_mixer/SDL_mixer.h b/include/SDL3_mixer/SDL_mixer.h
index 2901aded..c75ac92d 100644
--- a/include/SDL3_mixer/SDL_mixer.h
+++ b/include/SDL3_mixer/SDL_mixer.h
@@ -781,13 +781,14 @@ extern SDL_DECLSPEC MIX_Audio * SDLCALL MIX_LoadRawAudioNoCopy(MIX_Mixer *mixer,
  * This is useful just to have _something_ to play, perhaps for testing or
  * debugging purposes.
  *
- * The resulting MIX_Audio will generate infinite audio when assigned to a
- * track.
- *
  * You specify its frequency in Hz (determines the pitch of the sinewave's
  * audio) and amplitude (determines the volume of the sinewave: 1.0f is very
  * loud, 0.0f is silent).
  *
+ * A number of milliseconds of audio to generate can be specified.
+ * Specifying a value less than zero will generate infinite audio (when
+ * assigned to a MIX_Track, the sinewave will play forever).
+ *
  * MIX_Audio objects can be shared between multiple mixers. The `mixer`
  * parameter just suggests the most likely mixer to use this audio, in case
  * some optimization might be applied, but this is not required, and a NULL
@@ -796,6 +797,8 @@ extern SDL_DECLSPEC MIX_Audio * SDLCALL MIX_LoadRawAudioNoCopy(MIX_Mixer *mixer,
  * \param mixer a mixer this audio is intended to be used with. May be NULL.
  * \param hz the sinewave's frequency in Hz.
  * \param amplitude the sinewave's amplitude from 0.0f to 1.0f.
+ * \param ms the maximum number of milliseconds of audio to generate, or less
+ *        than zero to generate infinite audio.
  * \returns an audio object that can be used to make sound on a mixer, or NULL
  *          on failure; call SDL_GetError() for more information.
  *
@@ -807,7 +810,7 @@ extern SDL_DECLSPEC MIX_Audio * SDLCALL MIX_LoadRawAudioNoCopy(MIX_Mixer *mixer,
  * \sa MIX_SetTrackAudio
  * \sa MIX_LoadAudio_IO
  */
-extern SDL_DECLSPEC MIX_Audio * SDLCALL MIX_CreateSineWaveAudio(MIX_Mixer *mixer, int hz, float amplitude);
+extern SDL_DECLSPEC MIX_Audio * SDLCALL MIX_CreateSineWaveAudio(MIX_Mixer *mixer, int hz, float amplitude, Sint64 ms);
 
 
 /**
diff --git a/src/SDL_mixer.c b/src/SDL_mixer.c
index 11bbe833..1d0412ec 100644
--- a/src/SDL_mixer.c
+++ b/src/SDL_mixer.c
@@ -1246,7 +1246,7 @@ MIX_Audio *MIX_LoadRawAudioNoCopy(MIX_Mixer *mixer, const void *data, size_t dat
     return audio;
 }
 
-MIX_Audio *MIX_CreateSineWaveAudio(MIX_Mixer *mixer, int hz, float amplitude)
+MIX_Audio *MIX_CreateSineWaveAudio(MIX_Mixer *mixer, int hz, float amplitude, Sint64 ms)
 {
     if (!CheckInitialized()) {
         return NULL;
@@ -1267,6 +1267,7 @@ MIX_Audio *MIX_CreateSineWaveAudio(MIX_Mixer *mixer, int hz, float amplitude)
     SDL_SetStringProperty(props, MIX_PROP_AUDIO_DECODER_STRING, "SINEWAVE");
     SDL_SetNumberProperty(props, MIX_PROP_DECODER_SINEWAVE_HZ_NUMBER, hz);
     SDL_SetFloatProperty(props, MIX_PROP_DECODER_SINEWAVE_AMPLITUDE_FLOAT, amplitude);
+    SDL_SetNumberProperty(props, MIX_PROP_DECODER_SINEWAVE_MS_NUMBER, ms);
     SDL_SetBooleanProperty(props, MIX_PROP_AUDIO_LOAD_ONDEMAND_BOOLEAN, true);
     MIX_Audio *audio = MIX_LoadAudioWithProperties(props);
     SDL_DestroyProperties(props);
diff --git a/src/SDL_mixer_internal.h b/src/SDL_mixer_internal.h
index 248ecacd..be9aecda 100644
--- a/src/SDL_mixer_internal.h
+++ b/src/SDL_mixer_internal.h
@@ -217,6 +217,7 @@ struct MIX_Mixer
 #define MIX_PROP_DECODER_FREQ_NUMBER "SDL_mixer.decoder.freq"
 #define MIX_PROP_DECODER_SINEWAVE_HZ_NUMBER "SDL_mixer.decoder.sinewave.hz"
 #define MIX_PROP_DECODER_SINEWAVE_AMPLITUDE_FLOAT "SDL_mixer.decoder.sinewave.amplitude"
+#define MIX_PROP_DECODER_SINEWAVE_MS_NUMBER "SDL_mixer.decoder.sinewave.ms"
 #define MIX_PROP_DECODER_WAVPACK_WVC_IOSTREAM_POINTER "SDL_mixer.decoder.wavpack.wvc_iostream"
 #define MIX_PROP_DECODER_WAVPACK_WVC_PATH_STRING "SDL_mixer.decoder.wavpack.wvc_path"
 #define MIX_PROP_DECODER_FLUIDSYNTH_SOUNDFONT_IOSTREAM_POINTER "SDL_mixer.decoder.fluidsynth.soundfont_iostream"
diff --git a/src/decoder_sinewave.c b/src/decoder_sinewave.c
index 48856200..a8799002 100644
--- a/src/decoder_sinewave.c
+++ b/src/decoder_sinewave.c
@@ -35,12 +35,14 @@ typedef struct SINEWAVE_AudioData
     int hz;
     float amplitude;
     int sample_rate;
+    Sint64 total_frames;
 } SINEWAVE_AudioData;
 
 typedef struct SINEWAVE_TrackData
 {
     const SINEWAVE_AudioData *adata;
     int current_sine_sample;
+    Sint64 position;
 } SINEWAVE_TrackData;
 
 static bool SDLCALL SINEWAVE_init_audio(SDL_IOStream *io, SDL_AudioSpec *spec, SDL_PropertiesID props, Sint64 *duration_frames, void **audio_userdata)
@@ -52,6 +54,7 @@ static bool SDLCALL SINEWAVE_init_audio(SDL_IOStream *io, SDL_AudioSpec *spec, S
 
     const Sint64 si64hz = SDL_GetNumberProperty(props, MIX_PROP_DECODER_SINEWAVE_HZ_NUMBER, -1);
     const float famp = SDL_GetFloatProperty(props, MIX_PROP_DECODER_SINEWAVE_AMPLITUDE_FLOAT, -1.0f);
+    const Sint64 ms = SDL_GetNumberProperty(props, MIX_PROP_DECODER_SINEWAVE_MS_NUMBER, -1);
 
     if ((si64hz <= 0) || (famp <= 0.0f)) {
         return false;
@@ -69,8 +72,9 @@ static bool SDLCALL SINEWAVE_init_audio(SDL_IOStream *io, SDL_AudioSpec *spec, S
     adata->hz = (int) si64hz;
     adata->amplitude = famp;
     adata->sample_rate = spec->freq;
+    adata->total_frames = ms < 0 ? MIX_DURATION_INFINITE : MIX_MSToFrames(spec->freq, ms);
 
-    *duration_frames = MIX_DURATION_INFINITE;
+    *duration_frames = adata->total_frames;
     *audio_userdata = adata;
 
     return true;
@@ -99,8 +103,14 @@ static bool SDLCALL SINEWAVE_decode(void *track_userdata, SDL_AudioStream *strea
     const float amplitude = adata->amplitude;
     int current_sine_sample = tdata->current_sine_sample;
     float samples[256];
+    const bool infinite_sine = (adata->total_frames < 0);
+    const size_t total_frames = infinite_sine ? SDL_arraysize(samples) : SDL_min(adata->total_frames - tdata->position, SDL_arraysize(samples));
 
-    for (size_t i = 0; i < SDL_arraysize(samples); i++) {
+    if (!total_frames) {
+        return false;
+    }
+
+    for (size_t i = 0; i < total_frames; i++) {
         const float phase = current_sine_sample * hz / fsample_rate;
         samples[i] = SDL_sinf(phase * 2.0f * SDL_PI_F) * amplitude;
         current_sine_sample++;
@@ -109,15 +119,25 @@ static bool SDLCALL SINEWAVE_decode(void *track_userdata, SDL_AudioStream *strea
     // wrapping around to avoid floating-point errors
     tdata->current_sine_sample = current_sine_sample % sample_rate;
 
+    if (!infinite_sine) {
+        tdata->position += total_frames;
+    }
+
     SDL_PutAudioStreamData(stream, samples, sizeof (samples));
 
-    return true;   // infinite data
+    return true;
 }
 
 static bool SDLCALL SINEWAVE_seek(void *track_userdata, Uint64 frame)
 {
     SINEWAVE_TrackData *tdata = (SINEWAVE_TrackData *) track_userdata;
     const SINEWAVE_AudioData *adata = tdata->adata;
+    if (adata->total_frames >= 0) {
+        if (frame > (Uint64) adata->total_frames) {
+            return SDL_SetError("Past end of sinewave");
+        }
+        tdata->position = frame;
+    }
     tdata->current_sine_sample = frame % adata->sample_rate;
     return true;
 }
diff --git a/test/testmixer.c b/test/testmixer.c
index e742ffb2..0449ae79 100644
--- a/test/testmixer.c
+++ b/test/testmixer.c
@@ -159,6 +159,7 @@ SDL_AppResult SDL_AppInit(void **appstate, int argc, char *argv[])
 
     const char *audiofname = argv[1];
     MIX_Audio *audio = MIX_LoadAudio(mixer, audiofname, false);
+    //MIX_Audio *audio = MIX_CreateSineWaveAudio(mixer, 300, 0.25f, 5000);
     if (!audio) {
         SDL_Log("Failed to load '%s': %s", audiofname, SDL_GetError());
         return SDL_APP_FAILURE;
@@ -245,7 +246,7 @@ SDL_AppResult SDL_AppInit(void **appstate, int argc, char *argv[])
     }
 
     // we cheat here with PlayAudio, since the sinewave decoder produces infinite audio.
-    //MIX_PlayAudio(mixer, MIX_CreateSineWaveAudio(mixer, 300, 0.25f));
+    //MIX_PlayAudio(mixer, MIX_CreateSineWaveAudio(mixer, 300, 0.25f, -1));
 
     return SDL_APP_CONTINUE;
 }