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;
}