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:
2026-08-31 17:18:49 -05:00
parent 8049e90853
commit 15bd9fc43c
6 changed files with 252 additions and 58 deletions
+52 -10
View File
@@ -19,6 +19,8 @@ errorret_t audioStreamInit(audiostream_t *stream) {
stream->loopStart = -1; stream->loopStart = -1;
stream->loopTo = 0; stream->loopTo = 0;
stream->duration = 0; stream->duration = 0;
stream->startFrame = 0;
stream->seeking = false;
stream->user = NULL; stream->user = NULL;
stream->onLoop = NULL; stream->onLoop = NULL;
stream->onEnd = NULL; stream->onEnd = NULL;
@@ -43,16 +45,53 @@ void audioStreamPause(audiostream_t *stream) {
void audioStreamSetPosition(audiostream_t *stream, const float_t position) { void audioStreamSetPosition(audiostream_t *stream, const float_t position) {
assertNotNull(stream, "Stream cannot be NULL."); assertNotNull(stream, "Stream cannot be NULL.");
// Clamp time. // Wrap into [0, duration).
float_t t = position; float_t t = position;
while(t < 0) t += stream->duration; while(t < 0) t += stream->duration;
while(t >= stream->duration) t -= stream->duration; while(t >= stream->duration) t -= stream->duration;
// Here I will set the stream time, this will require new data to be fetched stream->startFrame = (size_t) (t * stream->pcm.sampleRate);
// next update() call. stream->seeking = true;
// To confirm: If this is called late in the frame, will the PCM data be sent
// to the output despite time being adjusted? is this going to cause static? // Force the next audioStreamUpdate() to re-buffer from startFrame instead
// of continuing whatever was already buffered - see the platform Buffer()
// implementations for how each one applies startFrame; PSP is the one
// exception (see audioStreamSetPosition's own doc comment).
stream->state &= ~AUDIO_STREAM_STATE_BUFFERED;
}
void audioStreamSetLoopPoints(
audiostream_t *stream,
const float_t loopStart,
const float_t loopTo
) {
assertNotNull(stream, "Stream cannot be NULL.");
assertTrue(
loopStart < 0 || loopTo < loopStart,
"loopTo must be before loopStart."
);
stream->loopStart = loopStart;
stream->loopTo = loopTo;
}
void audioStreamGetPanFactors(
const audiostream_t *stream,
float_t *outLeft,
float_t *outRight
) {
assertNotNull(stream, "Stream cannot be NULL.");
assertNotNull(outLeft, "outLeft cannot be NULL.");
assertNotNull(outRight, "outRight cannot be NULL.");
// directionality is an int8_t clamped to AUDIO_STREAM_LEFT..RIGHT
// (-128..127) by its own type, so pan is always within [-1.0, 0.992] -
// no further clamping needed.
const float_t pan = (float_t) stream->directionality / 128.0f;
*outLeft = pan > 0 ? (1.0f - pan) : 1.0f;
*outRight = pan < 0 ? (1.0f + pan) : 1.0f;
} }
void audioStreamSetVolume(audiostream_t *stream, const uint8_t volume) { void audioStreamSetVolume(audiostream_t *stream, const uint8_t volume) {
@@ -103,10 +142,7 @@ 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. Looping currently only supports restarting from the very // consumes it.
// 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.
// Checked in this order (finished-check before needs-buffering) so that a // Checked in this order (finished-check before needs-buffering) so that a
// loop restart falls straight through into re-buffering within this same // loop restart falls straight through into re-buffering within this same
// call, instead of leaving BUFFERED cleared for the caller to notice and // call, instead of leaving BUFFERED cleared for the caller to notice and
@@ -120,6 +156,12 @@ errorret_t audioStreamUpdate(audiostream_t *stream) {
if(stream->state & AUDIO_STREAM_STATE_LOOPING) { if(stream->state & AUDIO_STREAM_STATE_LOOPING) {
stream->state &= ~AUDIO_STREAM_STATE_BUFFERED; stream->state &= ~AUDIO_STREAM_STATE_BUFFERED;
// Resume from loopTo rather than the very start of the buffer - only
// matters for platforms that reach this generic restart path at all;
// PSP/Dolphin loop entirely on their own (thread/hardware) and never
// report "finished" while looping, so in practice this is Linux-only.
stream->startFrame = (size_t) (stream->loopTo * stream->pcm.sampleRate);
if(stream->onLoop != NULL) { if(stream->onLoop != NULL) {
stream->onLoop(stream); stream->onLoop(stream);
} }
+67 -4
View File
@@ -63,15 +63,40 @@ typedef struct audiostream_s {
// TODO: Can we use a 3D vector + Pro Logic II? // TODO: Can we use a 3D vector + Pro Logic II?
int8_t directionality; int8_t directionality;
// At what point does the stream loop back, -1 means "at end of stream" // In seconds, where the loop segment ends and playback jumps back to
// loopTo - -1 (the default) means "at the end of the stream", i.e. loop
// the whole buffer. Set via audioStreamSetLoopPoints().
float_t loopStart; float_t loopStart;
// When a loop occurs, where do we loop to? Defaults to 0 or start of stream. // In seconds, where a loop jumps back to once it reaches loopStart.
// Defaults to 0 (start of stream). Set via audioStreamSetLoopPoints().
float_t loopTo; float_t loopTo;
// Cached duration of the stream in seconds. // Cached duration of the stream in seconds.
float_t duration; float_t duration;
// Frame offset the next platform Buffer() call should start playback
// from - set by audioStreamSetPosition() (an explicit seek) and by
// audioStreamUpdate() itself (to loopTo's frame offset, for platforms
// that re-enter Buffer() on every loop restart rather than looping
// natively). Each platform's Buffer()-invoking entry point must read and
// reset this to 0 synchronously, in the same call that decided to
// (re)buffer - not later/asynchronously (e.g. from a feeder thread),
// since audioStreamUpdate() may already have moved on to something else
// that touches this field by the time an async reader gets to it.
size_t startFrame;
// True when startFrame came from an explicit audioStreamSetPosition()
// seek rather than a natural loop restart. Platforms whose Buffer() call
// can leave previously-queued audio still playing underneath the new
// pass (currently just Linux's SDL queue, kept deliberately overlapping
// across a loop restart to avoid a gap) need this to tell "jump now,
// discarding whatever's still playing" (seek) apart from "let the old
// tail keep playing while the new pass queues underneath it" (loop
// restart) - both look identical as just "a pending startFrame"
// otherwise. Consumed (reset to false) the same way as startFrame.
bool_t seeking;
// Callbacks // Callbacks
void *user; void *user;
void (*onLoop)(audiostream_t *stream); void (*onLoop)(audiostream_t *stream);
@@ -128,12 +153,50 @@ void audioStreamPlay(audiostream_t *stream);
void audioStreamPause(audiostream_t *stream); void audioStreamPause(audiostream_t *stream);
/** /**
* Sets the playback position of the given audio stream, in seconds. * Seeks the given audio stream to the given position, in seconds, wrapping
* into range if the position is outside [0, duration). Takes effect on the
* next audioStreamUpdate() call for platforms that re-buffer per frame
* (e.g. Linux); on PSP, which manages an entire playback pass on its own
* feeder thread once started, a seek only takes effect the next time the
* stream begins playing from a stopped state, not instantaneously mid-pass.
* *
* @param stream The audio stream to rewind. * @param stream The audio stream to seek.
* @param position The new playback position, in seconds.
*/ */
void audioStreamSetPosition(audiostream_t *stream, const float_t position); void audioStreamSetPosition(audiostream_t *stream, const float_t position);
/**
* Sets the loop region for the given audio stream: once playback reaches
* loopStart, it jumps back to loopTo instead of continuing (or stopping,
* if not looping). Has no effect unless looping is also enabled via
* audioStreamSetLooping().
*
* @param stream The audio stream to update.
* @param loopStart Where the loop segment ends, in seconds, or -1 to loop
* the whole stream (the default).
* @param loopTo Where the loop segment starts, in seconds.
*/
void audioStreamSetLoopPoints(
audiostream_t *stream,
const float_t loopStart,
const float_t loopTo
);
/**
* Computes normalized left/right pan factors (0..1) for the given audio
* stream's directionality. Callers multiply these by their own
* platform-specific base volume representation.
*
* @param stream The audio stream to read directionality from.
* @param outLeft Set to the left channel's pan factor.
* @param outRight Set to the right channel's pan factor.
*/
void audioStreamGetPanFactors(
const audiostream_t *stream,
float_t *outLeft,
float_t *outRight
);
/** /**
* Sets the playback volume of the given audio stream. * Sets the playback volume of the given audio stream.
* *
+17 -16
View File
@@ -42,10 +42,8 @@ errorret_t audioStreamDolphinBuffer(audiostream_t *stream) {
const size_t frameSize = stream->pcm.channels * sizeof(int16_t); const size_t frameSize = stream->pcm.channels * sizeof(int16_t);
// Simple linear pan; AUDIO_STREAM_LEFT/CENTER/RIGHT map to -128..0..127. float_t leftFactor, rightFactor;
float_t pan = (float_t) stream->directionality / 128.0f; audioStreamGetPanFactors(stream, &leftFactor, &rightFactor);
if(pan < -1.0f) pan = -1.0f;
if(pan > 1.0f) pan = 1.0f;
const float_t baseVolume = (float_t) stream->volume / 255.0f; const float_t baseVolume = (float_t) stream->volume / 255.0f;
@@ -57,8 +55,8 @@ errorret_t audioStreamDolphinBuffer(audiostream_t *stream) {
config.format = ANSND_VOICE_PCM_FORMAT_SIGNED_16_PCM; config.format = ANSND_VOICE_PCM_FORMAT_SIGNED_16_PCM;
config.channels = stream->pcm.channels; config.channels = stream->pcm.channels;
config.pitch = 1.0f; config.pitch = 1.0f;
config.left_volume = baseVolume * (pan > 0 ? (1.0f - pan) : 1.0f); config.left_volume = baseVolume * leftFactor;
config.right_volume = baseVolume * (pan < 0 ? (1.0f + pan) : 1.0f); config.right_volume = baseVolume * rightFactor;
// The DSP DMAs frame_data_ptr directly out of main memory, bypassing the // The DSP DMAs frame_data_ptr directly out of main memory, bypassing the
// CPU cache, and rejects any pointer in cached virtual address space // CPU cache, and rejects any pointer in cached virtual address space
// (0x8xxxxxxx.. - checked as "negative" internally, returned as // (0x8xxxxxxx.. - checked as "negative" internally, returned as
@@ -68,28 +66,31 @@ errorret_t audioStreamDolphinBuffer(audiostream_t *stream) {
DCFlushRange(stream->data, stream->dataSize); DCFlushRange(stream->data, stream->dataSize);
config.frame_data_ptr = MEM_VIRTUAL_TO_PHYSICAL(stream->data); config.frame_data_ptr = MEM_VIRTUAL_TO_PHYSICAL(stream->data);
config.frame_count = (u32) (stream->dataSize / frameSize); config.frame_count = (u32) (stream->dataSize / frameSize);
config.start_offset = (u32) stream->startFrame;
stream->startFrame = 0;
stream->seeking = false;
config.voice_callback = audioStreamDolphinVoiceCallback; config.voice_callback = audioStreamDolphinVoiceCallback;
config.stream_callback = NULL; // single-buffer playback config.stream_callback = NULL; // single-buffer playback
config.user_pointer = stream; config.user_pointer = stream;
// Loop entirely in hardware rather than mirroring PSP/Linux's // Loop entirely in hardware rather than mirroring PSP/Linux's
// detect-finished-then-restart-from-software approach: ansnd's DSP mixer // detect-finished-then-restart-from-software approach: ansnd's DSP mixer
// wraps frame_count-1 back to 0 on its own, so there's no restart latency // wraps loop_end_offset back to loop_start_offset on its own, so there's
// to create a gap in the first place (unlike a software restart, which // no restart latency to create a gap in the first place (unlike a
// always costs at least a little). Only restart-from-the-very-start is // software restart, which always costs at least a little).
// supported elsewhere in this codebase (loopTo isn't honored yet), which
// matches exactly what loop_start_offset=0 gives here.
// //
// Trade-off: onLoop never fires for a Dolphin voice looping this way - // Trade-off: onLoop never fires for a Dolphin voice looping this way -
// there's no ANSND_VOICE_STATE for "wrapped", only state transitions like // there's no ANSND_VOICE_STATE for "wrapped", only state transitions like
// FINISHED/STOPPED, which a looping voice never reaches. And unlike // FINISHED/STOPPED, which a looping voice never reaches. And unlike
// PSP's per-restart tail fade, the DSP does no smoothing at the wrap // PSP's per-restart tail fade, the DSP does no smoothing at the wrap
// point - it requires the source data's last frame to already flow // point - it requires loopStart's frame to already flow cleanly into
// cleanly into its first (true today only because the shared 441Hz test // loopTo's (true today only because the shared 441Hz test tone was
// tone was deliberately chosen to divide evenly into the sample rate). // deliberately chosen to divide evenly into the sample rate).
if(stream->state & AUDIO_STREAM_STATE_LOOPING) { if(stream->state & AUDIO_STREAM_STATE_LOOPING) {
config.loop_start_offset = 0; config.loop_start_offset = (u32) (stream->loopTo * stream->pcm.sampleRate);
config.loop_end_offset = config.frame_count - 1; config.loop_end_offset = stream->loopStart >= 0
? (u32) (stream->loopStart * stream->pcm.sampleRate) - 1
: config.frame_count - 1;
} }
s32 result = ansnd_configure_pcm_voice((u32) stream->platform.voiceId, &config); s32 result = ansnd_configure_pcm_voice((u32) stream->platform.voiceId, &config);
+50 -10
View File
@@ -9,6 +9,7 @@
#include "audio/audiostream.h" #include "audio/audiostream.h"
#include "assert/assert.h" #include "assert/assert.h"
#include "util/memory.h" #include "util/memory.h"
#include "util/math.h"
// How many frames of lead time to keep queued ahead of playback. Matches // 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 // 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) { errorret_t audioStreamLinuxBuffer(audiostream_t *stream) {
assertNotNull(stream, "Stream cannot be NULL."); assertNotNull(stream, "Stream cannot be NULL.");
uint8_t *mixed = memoryAllocate(stream->dataSize); const size_t frameSize = stream->pcm.channels * sizeof(int16_t);
memoryZero(mixed, stream->dataSize); const size_t totalFrames = stream->dataSize / frameSize;
SDL_MixAudioFormat(
mixed, stream->data, AUDIO_S16SYS, (Uint32) stream->dataSize,
(stream->volume * SDL_MIX_MAXVOLUME) / 0xFF
);
int queued = SDL_QueueAudio( // Consumed synchronously (right here, in the same call that decided to
stream->platform.device, mixed, (Uint32) stream->dataSize // (re)buffer) rather than left for later - see startFrame's own comment.
); const size_t startFrame = mathMin(stream->startFrame, totalFrames);
memoryFree(mixed); 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) { if(queued != 0) {
errorThrow("Failed to queue SDL2 audio data: %s", SDL_GetError()); errorThrow("Failed to queue SDL2 audio data: %s", SDL_GetError());
+59 -18
View File
@@ -9,6 +9,7 @@
#include "audio/audiostream.h" #include "audio/audiostream.h"
#include "assert/assert.h" #include "assert/assert.h"
#include "util/memory.h" #include "util/memory.h"
#include "util/math.h"
#include <pspaudio.h> #include <pspaudio.h>
#include <pspthreadman.h> #include <pspthreadman.h>
@@ -92,6 +93,9 @@ errorret_t audioStreamPSPBuffer(audiostream_t *stream) {
assertNotNull(stream, "Stream cannot be NULL."); assertNotNull(stream, "Stream cannot be NULL.");
stream->platform.finished = false; stream->platform.finished = false;
stream->platform.startFrame = stream->startFrame;
stream->startFrame = 0;
stream->seeking = false;
stream->platform.playRequested = true; stream->platform.playRequested = true;
errorOk(); errorOk();
@@ -125,7 +129,22 @@ void audioStreamPSPThreadFeed(thread_t *thread) {
stream->platform.playRequested = false; stream->platform.playRequested = false;
const size_t totalFrames = stream->dataSize / frameSize; 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; bool_t reachedEnd = false;
// Every call always sends a full, constant-size AUDIO_PSP_CHUNK_FRAMES // 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 // better option (see audiostream.c), but here the thread can just
// keep going and call onLoop itself instead. // keep going and call onLoop itself instead.
while(!threadShouldStop(thread) && !reachedEnd) { 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 const size_t framesThisChunk = framesRemaining < AUDIO_PSP_CHUNK_FRAMES
? framesRemaining ? framesRemaining
: AUDIO_PSP_CHUNK_FRAMES; : AUDIO_PSP_CHUNK_FRAMES;
@@ -156,17 +182,33 @@ void audioStreamPSPThreadFeed(thread_t *thread) {
if(reachesEndThisChunk && willLoop) { if(reachesEndThisChunk && willLoop) {
// Instead of padding the rest of this constant-size chunk with // Instead of padding the rest of this constant-size chunk with
// silence, fill it with the start of the next pass - a looping // silence, fill it with the start of the loop segment (loopToFrame)
// stream should never have dead air baked into its output at all. // - a looping stream should never have dead air baked into its
// Not faded (unlike the true-end case below): a wrap only sounds // output at all. Not faded (unlike the true-end case below): a wrap
// seamless if the source data already loops cleanly (true for the // only sounds seamless if the source data already loops cleanly
// shared test tone, deliberately authored at 441Hz for exactly // (true for the shared test tone, deliberately authored at 441Hz
// this), matching the same assumption Dolphin's native hardware // for exactly this), matching the same assumption Dolphin's native
// looping makes - imperfectly-authored loop content will still // hardware looping makes - imperfectly-authored loop content will
// click here. // still click here.
wrapFrames = remainderFrames < totalFrames ? remainderFrames : totalFrames; //
// 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( memoryCopy(
chunk + (framesThisChunk * channels), stream->data, wrapFrames * frameSize chunk + (framesThisChunk * channels),
stream->data + (loopToFrame * frameSize),
wrapFrames * frameSize
); );
if(wrapFrames < remainderFrames) { if(wrapFrames < remainderFrames) {
memoryZero( memoryZero(
@@ -196,13 +238,12 @@ void audioStreamPSPThreadFeed(thread_t *thread) {
// 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 leftFactor, rightFactor;
if(pan < -1.0f) pan = -1.0f; audioStreamGetPanFactors(stream, &leftFactor, &rightFactor);
if(pan > 1.0f) pan = 1.0f;
const int baseVolume = (stream->volume * PSP_AUDIO_VOLUME_MAX) / 0xFF; const int baseVolume = (stream->volume * PSP_AUDIO_VOLUME_MAX) / 0xFF;
const int leftVolume = (int) (baseVolume * (pan > 0 ? (1.0f - pan) : 1.0f)); const int leftVolume = (int) (baseVolume * leftFactor);
const int rightVolume = (int) (baseVolume * (pan < 0 ? (1.0f + pan) : 1.0f)); const int rightVolume = (int) (baseVolume * rightFactor);
sceAudioOutputPannedBlocking( sceAudioOutputPannedBlocking(
stream->platform.channel, leftVolume, rightVolume, chunk stream->platform.channel, leftVolume, rightVolume, chunk
@@ -216,7 +257,7 @@ void audioStreamPSPThreadFeed(thread_t *thread) {
// starve the next chunk). audioStreamUpdate() picks this up and // starve the next chunk). audioStreamUpdate() picks this up and
// fires onLoop safely from the main thread instead. // fires onLoop safely from the main thread instead.
stream->loopCount++; stream->loopCount++;
position = wrapFrames; position = loopToFrame + wrapFrames;
} else { } else {
reachedEnd = true; reachedEnd = true;
} }
+7
View File
@@ -30,6 +30,13 @@ typedef struct {
// a pass; cleared by the thread once it picks it up. // a pass; cleared by the thread once it picks it up.
volatile bool_t playRequested; volatile bool_t playRequested;
// Frame offset the next pass should start from - captured synchronously
// from audiostream_t.startFrame by audioStreamPSPBuffer() (which also
// resets that field to 0) rather than read directly by the feeder thread,
// since the thread only wakes up asynchronously and audiostream_t's
// shared field may already have moved on to a different value by then.
size_t startFrame;
// Set by the thread once it has fed the last chunk of a pass. // Set by the thread once it has fed the last chunk of a pass.
volatile bool_t finished; volatile bool_t finished;
} audiostreampsp_t; } audiostreampsp_t;