diff --git a/src/duskpsp/audio/audiostreampsp.c b/src/duskpsp/audio/audiostreampsp.c index a89c872a..940dad6a 100644 --- a/src/duskpsp/audio/audiostreampsp.c +++ b/src/duskpsp/audio/audiostreampsp.c @@ -26,6 +26,12 @@ // it above main so audio feeding always wins scheduling contention. #define AUDIO_PSP_THREAD_PRIORITY 18 +// How many frames at the very end of a stream get linearly faded to zero, +// avoiding the audible click of the waveform stopping at a non-zero +// amplitude (either into padding on a partial final chunk, or the DAC +// just stopping outright on an exact-multiple-length one). +#define AUDIO_PSP_FADE_FRAMES 32 + errorret_t audioStreamPSPInit(audiostream_t *stream) { assertNotNull(stream, "Stream cannot be NULL."); @@ -95,10 +101,15 @@ void audioStreamPSPThreadFeed(thread_t *thread) { sceKernelChangeThreadPriority(sceKernelGetThreadId(), AUDIO_PSP_THREAD_PRIORITY); audiostream_t *stream = (audiostream_t *) thread->data; - const size_t frameSize = stream->pcm.channels * sizeof(int16_t); + const size_t channels = stream->pcm.channels; + const size_t frameSize = channels * sizeof(int16_t); const size_t totalFrames = stream->dataSize / frameSize; const size_t chunkSize = AUDIO_PSP_CHUNK_FRAMES * frameSize; + // A previous playback pass may have left this shortened for its own + // final partial chunk (see below) - restore the full chunk length. + sceAudioSetChannelDataLen(stream->platform.channel, AUDIO_PSP_CHUNK_FRAMES); + int16_t *chunk = memoryAllocate(chunkSize); size_t position = 0; @@ -107,12 +118,38 @@ void audioStreamPSPThreadFeed(thread_t *thread) { const size_t framesThisChunk = framesRemaining < AUDIO_PSP_CHUNK_FRAMES ? framesRemaining : AUDIO_PSP_CHUNK_FRAMES; + const bool_t isLastChunk = framesThisChunk == framesRemaining; memoryZero(chunk, chunkSize); memoryCopy( chunk, stream->data + (position * frameSize), framesThisChunk * frameSize ); + if(isLastChunk) { + // Fade the tail down to zero so the waveform never stops (or meets + // the zero-padding below) at a non-zero amplitude - that abrupt + // jump is what was heard as a click at the end of playback. + const size_t fadeFrames = framesThisChunk < AUDIO_PSP_FADE_FRAMES + ? framesThisChunk + : AUDIO_PSP_FADE_FRAMES; + for(size_t i = 0; i < fadeFrames; i++) { + const size_t frame = framesThisChunk - fadeFrames + i; + const float_t factor = 1.0f - ((float_t) (i + 1) / (float_t) fadeFrames); + for(size_t c = 0; c < channels; c++) { + int16_t *sample = &chunk[frame * channels + c]; + *sample = (int16_t) ((float_t) *sample * factor); + } + } + + // Only declare the real sample count for output instead of the full + // reserved chunk size, 64-aligned (sceAudioChReserve's own + // constraint - not documented for this call, but not worth risking). + const int alignedFrames = (int) PSP_AUDIO_SAMPLE_ALIGN(framesThisChunk); + if((size_t) alignedFrames != AUDIO_PSP_CHUNK_FRAMES) { + sceAudioSetChannelDataLen(stream->platform.channel, alignedFrames); + } + } + // Re-read every chunk (~23ms at 44100Hz) so SetVolume/SetDirectionality // take effect mid-playback, unlike the platform's other one-shot calls. float_t pan = (float_t) stream->directionality / 128.0f;