Clean up audio subsystem duplication, implement seeking and loop regions
- Extract audioStreamGetPanFactors() into the shared layer, replacing
identical pan-to-LR math duplicated in the PSP and Dolphin backends
(and dropping a dead clamp - directionality's int8_t range already
guarantees pan stays in [-1, 1]).
- Implement audioStreamSetPosition() for real (was previously a no-op
that computed a value and threw it away) and add
audioStreamSetLoopPoints() to actually drive loopStart/loopTo, which
were previously dead fields with no setter at all. Both are threaded
through all three platform backends:
- PSP: the feeder thread now bounds each pass by the loop segment
and wraps to loopTo instead of always frame 0, while still filling
hardware chunks gaplessly.
- Dolphin: loopTo/loopStart map directly onto ansnd's existing
loop_start_offset/loop_end_offset, and startFrame onto start_offset.
- Linux: Buffer() now queues only the current segment, clearing the
SDL queue on an explicit seek but preserving the existing
overlap-based gapless loop restart otherwise.
- Linux: skip the mix scratch-buffer entirely at full volume, queuing
stream->data directly instead of allocating/zeroing/copying into one
every buffer call for no reason.
Verified no regression in the default (no seek, no loop points) case on
Linux/PPSSPP/Dolphin (-a LLE), and verified seek + loop-region behavior
manually via a temporary engine.c smoke-test tweak (reverted) showing
the expected faster loop cadence and seek-then-loop sequencing.
This commit is contained in:
@@ -9,6 +9,7 @@
|
||||
#include "audio/audiostream.h"
|
||||
#include "assert/assert.h"
|
||||
#include "util/memory.h"
|
||||
#include "util/math.h"
|
||||
|
||||
// How many frames of lead time to keep queued ahead of playback. Matches
|
||||
// SDL_AudioSpec.samples below - the device's own internal buffer size - so
|
||||
@@ -44,17 +45,56 @@ errorret_t audioStreamLinuxDispose(audiostream_t *stream) {
|
||||
errorret_t audioStreamLinuxBuffer(audiostream_t *stream) {
|
||||
assertNotNull(stream, "Stream cannot be NULL.");
|
||||
|
||||
uint8_t *mixed = memoryAllocate(stream->dataSize);
|
||||
memoryZero(mixed, stream->dataSize);
|
||||
SDL_MixAudioFormat(
|
||||
mixed, stream->data, AUDIO_S16SYS, (Uint32) stream->dataSize,
|
||||
(stream->volume * SDL_MIX_MAXVOLUME) / 0xFF
|
||||
);
|
||||
const size_t frameSize = stream->pcm.channels * sizeof(int16_t);
|
||||
const size_t totalFrames = stream->dataSize / frameSize;
|
||||
|
||||
int queued = SDL_QueueAudio(
|
||||
stream->platform.device, mixed, (Uint32) stream->dataSize
|
||||
);
|
||||
memoryFree(mixed);
|
||||
// Consumed synchronously (right here, in the same call that decided to
|
||||
// (re)buffer) rather than left for later - see startFrame's own comment.
|
||||
const size_t startFrame = mathMin(stream->startFrame, totalFrames);
|
||||
const bool_t seeking = stream->seeking;
|
||||
stream->startFrame = 0;
|
||||
stream->seeking = false;
|
||||
|
||||
size_t endFrame = totalFrames;
|
||||
if((stream->state & AUDIO_STREAM_STATE_LOOPING) && stream->loopStart >= 0) {
|
||||
endFrame = mathMin(
|
||||
(size_t) (stream->loopStart * stream->pcm.sampleRate), totalFrames
|
||||
);
|
||||
}
|
||||
// A seek (or a loop restart landing exactly on the loop end) can put
|
||||
// startFrame at or past endFrame - e.g. seeking into an outro after the
|
||||
// loop point. Play out to the true end of the buffer once instead, same
|
||||
// as PSP.
|
||||
if(startFrame >= endFrame) endFrame = totalFrames;
|
||||
|
||||
const size_t bytes = (endFrame - startFrame) * frameSize;
|
||||
const uint8_t *segment = stream->data + (startFrame * frameSize);
|
||||
|
||||
// Only an explicit seek discards whatever's still queued and jumps -
|
||||
// a natural loop restart deliberately leaves the previous pass's tail
|
||||
// (AUDIO_LINUX_LEAD_FRAMES worth) queued and appends the new pass after
|
||||
// it, which is what makes looping gapless (see IsFinished()'s comment).
|
||||
// Clearing on every Buffer() call would destroy that overlap and
|
||||
// reintroduce the exact gap this was built to avoid.
|
||||
if(seeking) {
|
||||
SDL_ClearQueuedAudio(stream->platform.device);
|
||||
}
|
||||
|
||||
int queued;
|
||||
if(stream->volume == 0xFF) {
|
||||
// Nothing to mix at full volume - queue the segment directly instead of
|
||||
// allocating/zeroing a same-size scratch buffer just to copy it in.
|
||||
queued = SDL_QueueAudio(stream->platform.device, segment, (Uint32) bytes);
|
||||
} else {
|
||||
uint8_t *mixed = memoryAllocate(bytes);
|
||||
memoryZero(mixed, bytes);
|
||||
SDL_MixAudioFormat(
|
||||
mixed, segment, AUDIO_S16SYS, (Uint32) bytes,
|
||||
(stream->volume * SDL_MIX_MAXVOLUME) / 0xFF
|
||||
);
|
||||
queued = SDL_QueueAudio(stream->platform.device, mixed, (Uint32) bytes);
|
||||
memoryFree(mixed);
|
||||
}
|
||||
|
||||
if(queued != 0) {
|
||||
errorThrow("Failed to queue SDL2 audio data: %s", SDL_GetError());
|
||||
|
||||
Reference in New Issue
Block a user