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"
|
||||
#include <pspaudio.h>
|
||||
#include <pspthreadman.h>
|
||||
|
||||
@@ -92,6 +93,9 @@ errorret_t audioStreamPSPBuffer(audiostream_t *stream) {
|
||||
assertNotNull(stream, "Stream cannot be NULL.");
|
||||
|
||||
stream->platform.finished = false;
|
||||
stream->platform.startFrame = stream->startFrame;
|
||||
stream->startFrame = 0;
|
||||
stream->seeking = false;
|
||||
stream->platform.playRequested = true;
|
||||
|
||||
errorOk();
|
||||
@@ -125,7 +129,22 @@ void audioStreamPSPThreadFeed(thread_t *thread) {
|
||||
stream->platform.playRequested = false;
|
||||
|
||||
const size_t totalFrames = stream->dataSize / frameSize;
|
||||
size_t position = 0;
|
||||
|
||||
// loopEndFrame/loopToFrame define the loop segment [loopToFrame,
|
||||
// loopEndFrame) that a looping pass wraps within, once it's reached -
|
||||
// defaulting to the whole buffer (loopStart == -1, loopTo == 0) so
|
||||
// behaviour is unchanged when no explicit loop points are configured.
|
||||
const size_t loopEndFrame = stream->loopStart >= 0
|
||||
? mathMin((size_t) (stream->loopStart * stream->pcm.sampleRate), totalFrames)
|
||||
: totalFrames;
|
||||
const size_t loopToFrame = mathMin(
|
||||
(size_t) (stream->loopTo * stream->pcm.sampleRate), loopEndFrame
|
||||
);
|
||||
|
||||
// Only the very first pass honors an explicit seek (audioStreamSetPosition,
|
||||
// captured into platform.startFrame by audioStreamPSPBuffer()) - every
|
||||
// subsequent loop wraps to loopToFrame instead.
|
||||
size_t position = stream->platform.startFrame;
|
||||
bool_t reachedEnd = false;
|
||||
|
||||
// Every call always sends a full, constant-size AUDIO_PSP_CHUNK_FRAMES
|
||||
@@ -139,7 +158,14 @@ void audioStreamPSPThreadFeed(thread_t *thread) {
|
||||
// better option (see audiostream.c), but here the thread can just
|
||||
// keep going and call onLoop itself instead.
|
||||
while(!threadShouldStop(thread) && !reachedEnd) {
|
||||
const size_t framesRemaining = totalFrames - position;
|
||||
// Normally bounded by loopEndFrame (the loop segment's end), but a
|
||||
// seek can legitimately land past it (e.g. into an outro after the
|
||||
// loop point) - in that case play out to the true end of the buffer
|
||||
// once instead of underflowing framesRemaining.
|
||||
const size_t currentEndFrame = position < loopEndFrame
|
||||
? loopEndFrame
|
||||
: totalFrames;
|
||||
const size_t framesRemaining = currentEndFrame - position;
|
||||
const size_t framesThisChunk = framesRemaining < AUDIO_PSP_CHUNK_FRAMES
|
||||
? framesRemaining
|
||||
: AUDIO_PSP_CHUNK_FRAMES;
|
||||
@@ -156,17 +182,33 @@ void audioStreamPSPThreadFeed(thread_t *thread) {
|
||||
|
||||
if(reachesEndThisChunk && willLoop) {
|
||||
// Instead of padding the rest of this constant-size chunk with
|
||||
// silence, fill it with the start of the next pass - a looping
|
||||
// stream should never have dead air baked into its output at all.
|
||||
// Not faded (unlike the true-end case below): a wrap only sounds
|
||||
// seamless if the source data already loops cleanly (true for the
|
||||
// shared test tone, deliberately authored at 441Hz for exactly
|
||||
// this), matching the same assumption Dolphin's native hardware
|
||||
// looping makes - imperfectly-authored loop content will still
|
||||
// click here.
|
||||
wrapFrames = remainderFrames < totalFrames ? remainderFrames : totalFrames;
|
||||
// silence, fill it with the start of the loop segment (loopToFrame)
|
||||
// - a looping stream should never have dead air baked into its
|
||||
// output at all. Not faded (unlike the true-end case below): a wrap
|
||||
// only sounds seamless if the source data already loops cleanly
|
||||
// (true for the shared test tone, deliberately authored at 441Hz
|
||||
// for exactly this), matching the same assumption Dolphin's native
|
||||
// hardware looping makes - imperfectly-authored loop content will
|
||||
// still click here.
|
||||
//
|
||||
// NOTE: if the loop segment (loopEndFrame - loopToFrame) is shorter
|
||||
// than one hardware chunk (AUDIO_PSP_CHUNK_FRAMES, ~23ms @ 44100Hz),
|
||||
// only a single copy of it fills the remainder here rather than
|
||||
// repeating it to fill the whole chunk - any leftover space is
|
||||
// zero-padded (a brief, audible gap) and loopCount still only
|
||||
// increments once per hardware chunk, not once per actual loop
|
||||
// repeat. Not hit by anything in this codebase today (the shared
|
||||
// test tone's default whole-buffer loop is 1 second), but a future
|
||||
// short music-loop tail would need this generalized to a
|
||||
// repeat-fill instead.
|
||||
const size_t loopSegmentFrames = loopEndFrame - loopToFrame;
|
||||
wrapFrames = remainderFrames < loopSegmentFrames
|
||||
? remainderFrames
|
||||
: loopSegmentFrames;
|
||||
memoryCopy(
|
||||
chunk + (framesThisChunk * channels), stream->data, wrapFrames * frameSize
|
||||
chunk + (framesThisChunk * channels),
|
||||
stream->data + (loopToFrame * frameSize),
|
||||
wrapFrames * frameSize
|
||||
);
|
||||
if(wrapFrames < remainderFrames) {
|
||||
memoryZero(
|
||||
@@ -196,13 +238,12 @@ void audioStreamPSPThreadFeed(thread_t *thread) {
|
||||
|
||||
// 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;
|
||||
if(pan < -1.0f) pan = -1.0f;
|
||||
if(pan > 1.0f) pan = 1.0f;
|
||||
float_t leftFactor, rightFactor;
|
||||
audioStreamGetPanFactors(stream, &leftFactor, &rightFactor);
|
||||
|
||||
const int baseVolume = (stream->volume * PSP_AUDIO_VOLUME_MAX) / 0xFF;
|
||||
const int leftVolume = (int) (baseVolume * (pan > 0 ? (1.0f - pan) : 1.0f));
|
||||
const int rightVolume = (int) (baseVolume * (pan < 0 ? (1.0f + pan) : 1.0f));
|
||||
const int leftVolume = (int) (baseVolume * leftFactor);
|
||||
const int rightVolume = (int) (baseVolume * rightFactor);
|
||||
|
||||
sceAudioOutputPannedBlocking(
|
||||
stream->platform.channel, leftVolume, rightVolume, chunk
|
||||
@@ -216,7 +257,7 @@ void audioStreamPSPThreadFeed(thread_t *thread) {
|
||||
// starve the next chunk). audioStreamUpdate() picks this up and
|
||||
// fires onLoop safely from the main thread instead.
|
||||
stream->loopCount++;
|
||||
position = wrapFrames;
|
||||
position = loopToFrame + wrapFrames;
|
||||
} else {
|
||||
reachedEnd = true;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user