Fix PSP end-of-playback click via tail fade + exact final chunk length
The feeder's final chunk zero-padded up to the full 1024-frame reserved size (e.g. 956 padding samples for a 68-sample remainder), jumping straight from whatever amplitude the waveform ended at down to silence - an audible click. Fixed two ways: fade the last 32 real frames linearly to zero before any padding begins (also covers the case where total length is an exact multiple of the chunk size, where the waveform would otherwise just stop abruptly with no padding at all), and declare only the real sample count via sceAudioSetChannelDataLen (64-aligned) for the final chunk instead of padding out to the full reserved length, shrinking the leftover padding to under 64 samples. Channel length gets reset to the full chunk size at the start of each feed pass in case a previous playback left it shortened. Verified via PPSSPP: sceAudioSetChannelDataLen(7, 1024) then (7, 128) for the tone's 68-sample remainder, onEnd still fires once.
This commit is contained in:
@@ -26,6 +26,12 @@
|
|||||||
// it above main so audio feeding always wins scheduling contention.
|
// it above main so audio feeding always wins scheduling contention.
|
||||||
#define AUDIO_PSP_THREAD_PRIORITY 18
|
#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) {
|
errorret_t audioStreamPSPInit(audiostream_t *stream) {
|
||||||
assertNotNull(stream, "Stream cannot be NULL.");
|
assertNotNull(stream, "Stream cannot be NULL.");
|
||||||
|
|
||||||
@@ -95,10 +101,15 @@ void audioStreamPSPThreadFeed(thread_t *thread) {
|
|||||||
sceKernelChangeThreadPriority(sceKernelGetThreadId(), AUDIO_PSP_THREAD_PRIORITY);
|
sceKernelChangeThreadPriority(sceKernelGetThreadId(), AUDIO_PSP_THREAD_PRIORITY);
|
||||||
|
|
||||||
audiostream_t *stream = (audiostream_t *) thread->data;
|
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 totalFrames = stream->dataSize / frameSize;
|
||||||
const size_t chunkSize = AUDIO_PSP_CHUNK_FRAMES * 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);
|
int16_t *chunk = memoryAllocate(chunkSize);
|
||||||
size_t position = 0;
|
size_t position = 0;
|
||||||
|
|
||||||
@@ -107,12 +118,38 @@ void audioStreamPSPThreadFeed(thread_t *thread) {
|
|||||||
const size_t framesThisChunk = framesRemaining < AUDIO_PSP_CHUNK_FRAMES
|
const size_t framesThisChunk = framesRemaining < AUDIO_PSP_CHUNK_FRAMES
|
||||||
? framesRemaining
|
? framesRemaining
|
||||||
: AUDIO_PSP_CHUNK_FRAMES;
|
: AUDIO_PSP_CHUNK_FRAMES;
|
||||||
|
const bool_t isLastChunk = framesThisChunk == framesRemaining;
|
||||||
|
|
||||||
memoryZero(chunk, chunkSize);
|
memoryZero(chunk, chunkSize);
|
||||||
memoryCopy(
|
memoryCopy(
|
||||||
chunk, stream->data + (position * frameSize), framesThisChunk * frameSize
|
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
|
// Re-read every chunk (~23ms at 44100Hz) so SetVolume/SetDirectionality
|
||||||
// take effect mid-playback, unlike the platform's other one-shot calls.
|
// take effect mid-playback, unlike the platform's other one-shot calls.
|
||||||
float_t pan = (float_t) stream->directionality / 128.0f;
|
float_t pan = (float_t) stream->directionality / 128.0f;
|
||||||
|
|||||||
Reference in New Issue
Block a user