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:
@@ -68,6 +68,16 @@ void audioStreamSetDirectionality(
|
|||||||
// TODO: Need to update internal decoder?
|
// 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) {
|
errorret_t audioStreamUpdate(audiostream_t *stream) {
|
||||||
assertNotNull(stream, "Stream cannot be NULL.");
|
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
|
// 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
|
// 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(
|
if(
|
||||||
(stream->state & AUDIO_STREAM_STATE_PLAYING) &&
|
(stream->state & AUDIO_STREAM_STATE_PLAYING) &&
|
||||||
!(stream->state & AUDIO_STREAM_STATE_BUFFERED)
|
!(stream->state & AUDIO_STREAM_STATE_BUFFERED)
|
||||||
@@ -89,10 +102,18 @@ errorret_t audioStreamUpdate(audiostream_t *stream) {
|
|||||||
(stream->state & AUDIO_STREAM_STATE_BUFFERED) &&
|
(stream->state & AUDIO_STREAM_STATE_BUFFERED) &&
|
||||||
audioStreamPlatformIsFinished(stream)
|
audioStreamPlatformIsFinished(stream)
|
||||||
) {
|
) {
|
||||||
stream->state &= ~(AUDIO_STREAM_STATE_PLAYING | AUDIO_STREAM_STATE_BUFFERED);
|
if(stream->state & AUDIO_STREAM_STATE_LOOPING) {
|
||||||
|
stream->state &= ~AUDIO_STREAM_STATE_BUFFERED;
|
||||||
|
|
||||||
if(stream->onEnd != NULL) {
|
if(stream->onLoop != NULL) {
|
||||||
stream->onEnd(stream);
|
stream->onLoop(stream);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
stream->state &= ~(AUDIO_STREAM_STATE_PLAYING | AUDIO_STREAM_STATE_BUFFERED);
|
||||||
|
|
||||||
|
if(stream->onEnd != NULL) {
|
||||||
|
stream->onEnd(stream);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -141,6 +141,17 @@ void audioStreamSetDirectionality(
|
|||||||
const int8_t directionality
|
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
|
* Updates the given audio stream, decoding new data and advancing playback
|
||||||
* as needed. Should be called every frame for every active stream.
|
* as needed. Should be called every frame for every active stream.
|
||||||
|
|||||||
@@ -30,7 +30,10 @@ engine_t ENGINE;
|
|||||||
|
|
||||||
// 44100Hz: PSP's sceAudioChReserve hardware channels are fixed at this rate.
|
// 44100Hz: PSP's sceAudioChReserve hardware channels are fixed at this rate.
|
||||||
#define AUDIO_TEST_TONE_SAMPLE_RATE 44100
|
#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_SAMPLES AUDIO_TEST_TONE_SAMPLE_RATE
|
||||||
#define AUDIO_TEST_TONE_SIZE (AUDIO_TEST_TONE_SAMPLES * sizeof(int16_t))
|
#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");
|
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) {
|
errorret_t engineInit(const int32_t argc, const char_t **argv) {
|
||||||
assertInit();
|
assertInit();
|
||||||
memoryZero(&ENGINE, sizeof(engine_t));
|
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
|
AUDIO_TEST_TONE_SAMPLE_RATE, 1
|
||||||
));
|
));
|
||||||
stream->onEnd = engineTestToneOnEnd;
|
stream->onEnd = engineTestToneOnEnd;
|
||||||
|
stream->onLoop = engineTestToneOnLoop;
|
||||||
|
audioStreamSetLooping(stream, true);
|
||||||
audioStreamPlay(stream);
|
audioStreamPlay(stream);
|
||||||
|
|
||||||
consolePrint("Engine initialized");
|
consolePrint("Engine initialized");
|
||||||
|
|||||||
Reference in New Issue
Block a user