Add audio stream looping (restart-from-start only)

audioStreamSetLooping() sets AUDIO_STREAM_STATE_LOOPING; when a
looping stream finishes, audioStreamUpdate() clears BUFFERED (not
PLAYING) and fires onLoop instead of onEnd, causing the next Update()
to naturally re-trigger the platform Buffer() call on the same
unmodified stream->data/dataSize - full restart from position 0,
with no platform-specific code needed. loopTo (looping to a point
other than the start) isn't honored yet - would need slicing the
buffer, flagged as a follow-up.

Also fixed the shared test tone's frequency (440Hz -> 441Hz): 44100Hz
doesn't divide evenly by 440Hz (100.23 samples/cycle), so the buffer's
last sample didn't exactly match its first - a small discontinuity at
every loop boundary independent of PSP's own click fixes. 441Hz
divides evenly into exactly 100 samples/cycle, closing that gap for
every platform, not just the ones with their own tail-handling.

Verified end-to-end on Linux (5 onLoop firings over ~9s for a 1s
tone, no errors) and confirmed compiling for PSP.
This commit is contained in:
2026-08-31 12:20:50 -05:00
parent 7ad735552a
commit 9b0214ee55
3 changed files with 46 additions and 5 deletions
+22 -1
View File
@@ -68,6 +68,16 @@ void audioStreamSetDirectionality(
// TODO: Need to update internal decoder?
}
void audioStreamSetLooping(audiostream_t *stream, const bool_t looping) {
assertNotNull(stream, "Stream cannot be NULL.");
if(looping) {
stream->state |= AUDIO_STREAM_STATE_LOOPING;
} else {
stream->state &= ~AUDIO_STREAM_STATE_LOOPING;
}
}
errorret_t audioStreamUpdate(audiostream_t *stream) {
assertNotNull(stream, "Stream cannot be NULL.");
@@ -77,7 +87,10 @@ errorret_t audioStreamUpdate(audiostream_t *stream) {
// TODO: Once streamed (rather than fully in-memory) sources exist, this is
// where new data would be decoded/read in and re-buffered as playback
// consumes it, and looping (onLoop) would be handled.
// consumes it. Looping currently only supports restarting from the very
// start of the buffer (loopTo is not yet honored) since replaying the
// exact same stream->data/dataSize needs no platform-specific changes;
// an arbitrary loopTo offset would need slicing that buffer instead.
if(
(stream->state & AUDIO_STREAM_STATE_PLAYING) &&
!(stream->state & AUDIO_STREAM_STATE_BUFFERED)
@@ -89,12 +102,20 @@ errorret_t audioStreamUpdate(audiostream_t *stream) {
(stream->state & AUDIO_STREAM_STATE_BUFFERED) &&
audioStreamPlatformIsFinished(stream)
) {
if(stream->state & AUDIO_STREAM_STATE_LOOPING) {
stream->state &= ~AUDIO_STREAM_STATE_BUFFERED;
if(stream->onLoop != NULL) {
stream->onLoop(stream);
}
} else {
stream->state &= ~(AUDIO_STREAM_STATE_PLAYING | AUDIO_STREAM_STATE_BUFFERED);
if(stream->onEnd != NULL) {
stream->onEnd(stream);
}
}
}
errorOk();
}
+11
View File
@@ -141,6 +141,17 @@ void audioStreamSetDirectionality(
const int8_t directionality
);
/**
* Sets whether the given audio stream loops back to the start when it
* reaches the end, rather than stopping and firing onEnd. Only looping
* back to the very start of the buffer is currently supported - loopTo is
* not yet honored.
*
* @param stream The audio stream to update.
* @param looping Whether the stream should loop.
*/
void audioStreamSetLooping(audiostream_t *stream, const bool_t looping);
/**
* Updates the given audio stream, decoding new data and advancing playback
* as needed. Should be called every frame for every active stream.
+10 -1
View File
@@ -30,7 +30,10 @@ engine_t ENGINE;
// 44100Hz: PSP's sceAudioChReserve hardware channels are fixed at this rate.
#define AUDIO_TEST_TONE_SAMPLE_RATE 44100
#define AUDIO_TEST_TONE_FREQUENCY 440
// 441Hz (not 440): divides 44100Hz evenly into exactly 100 samples/cycle,
// so a whole-second buffer's last sample exactly matches its first -
// a seamless loop point with no source-data discontinuity to click on.
#define AUDIO_TEST_TONE_FREQUENCY 441
#define AUDIO_TEST_TONE_SAMPLES AUDIO_TEST_TONE_SAMPLE_RATE
#define AUDIO_TEST_TONE_SIZE (AUDIO_TEST_TONE_SAMPLES * sizeof(int16_t))
@@ -41,6 +44,10 @@ void engineTestToneOnEnd(audiostream_t *stream) {
consolePrint("Test tone finished playing");
}
void engineTestToneOnLoop(audiostream_t *stream) {
consolePrint("Test tone looped");
}
errorret_t engineInit(const int32_t argc, const char_t **argv) {
assertInit();
memoryZero(&ENGINE, sizeof(engine_t));
@@ -81,6 +88,8 @@ errorret_t engineInit(const int32_t argc, const char_t **argv) {
AUDIO_TEST_TONE_SAMPLE_RATE, 1
));
stream->onEnd = engineTestToneOnEnd;
stream->onLoop = engineTestToneOnLoop;
audioStreamSetLooping(stream, true);
audioStreamPlay(stream);
consolePrint("Engine initialized");