Fixed PSP trying to load entirely into memory

This commit is contained in:
2026-08-31 19:07:51 -05:00
parent 15bd9fc43c
commit d37109f5f7
26 changed files with 995 additions and 232 deletions
+66 -10
View File
@@ -9,6 +9,7 @@
#include "audio/audiostream.h"
#include "assert/assert.h"
#include "util/memory.h"
#include "util/math.h"
#include <ansndlib.h>
#include <ogc/cache.h>
#include <ogc/system.h>
@@ -25,6 +26,7 @@ errorret_t audioStreamDolphinInit(audiostream_t *stream) {
stream->platform.voiceId = voiceId;
stream->platform.finished = false;
stream->platform.buffer = NULL;
errorOk();
}
@@ -34,6 +36,11 @@ errorret_t audioStreamDolphinDispose(audiostream_t *stream) {
ansnd_deallocate_voice((u32) stream->platform.voiceId);
if(stream->platform.buffer != NULL) {
memoryFree(stream->platform.buffer);
stream->platform.buffer = NULL;
}
errorOk();
}
@@ -41,6 +48,28 @@ errorret_t audioStreamDolphinBuffer(audiostream_t *stream) {
assertNotNull(stream, "Stream cannot be NULL.");
const size_t frameSize = stream->pcm.channels * sizeof(int16_t);
const size_t totalFrames = audioStreamPcmGetTotalFrames(stream);
// loopEndFrame/loopToFrame define the loop segment [loopToFrame,
// loopEndFrame) the DSP wraps within, once looping is enabled -
// defaulting to the whole clip (loopStart == -1, loopTo == 0) so
// behaviour is unchanged when no explicit loop points are configured.
// Same math as PSP/Linux - see their own comments.
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
);
const size_t startFrame = mathMin(stream->startFrame, totalFrames);
stream->startFrame = 0;
stream->seeking = false;
// A seek can legitimately land past the loop segment's end (e.g. into an
// outro) - buffer out to the true end of the clip in that case, same as
// PSP/Linux, instead of only ever buffering the loop segment.
const size_t bufferFrames = startFrame < loopEndFrame ? loopEndFrame : totalFrames;
float_t leftFactor, rightFactor;
audioStreamGetPanFactors(stream, &leftFactor, &rightFactor);
@@ -49,6 +78,37 @@ errorret_t audioStreamDolphinBuffer(audiostream_t *stream) {
stream->platform.finished = false;
// ansnd's hardware loop needs one contiguous buffer spanning everything
// the voice might play - both start_offset and the loop segment address
// the same frame_data_ptr - so (unlike PSP/Linux, which stream bounded
// windows on demand) this reads the whole segment up front. Still
// bounded to the current loop segment's length rather than the whole
// clip if the clip is longer than one loop - see this struct field's
// own comment. Re-read fresh every Buffer() call.
if(stream->platform.buffer != NULL) {
memoryFree(stream->platform.buffer);
stream->platform.buffer = NULL;
}
errorChain(audioStreamPcmSeek(stream, 0));
uint8_t *buffer = (uint8_t *) memoryAllocate(bufferFrames * frameSize);
size_t framesRead = 0;
errorret_t readRet = audioStreamPcmRead(
stream, (int16_t *) buffer, bufferFrames, &framesRead
);
if(errorIsNotOk(readRet)) {
memoryFree(buffer);
errorChain(readRet);
}
if(framesRead < bufferFrames) {
// The asset is shorter than its declared header size (corrupt or
// truncated) - pad what's missing with silence.
memoryZero(
buffer + (framesRead * frameSize), (bufferFrames - framesRead) * frameSize
);
}
stream->platform.buffer = buffer;
ansnd_pcm_voice_config_t config;
memoryZero(&config, sizeof(ansnd_pcm_voice_config_t));
config.samplerate = stream->pcm.sampleRate;
@@ -63,12 +123,10 @@ errorret_t audioStreamDolphinBuffer(audiostream_t *stream) {
// ANSND_ERROR_INVALID_MEMORY) - it needs the physical address instead.
// Flush first so the DMA sees what the CPU actually wrote, not stale
// memory contents.
DCFlushRange(stream->data, stream->dataSize);
config.frame_data_ptr = MEM_VIRTUAL_TO_PHYSICAL(stream->data);
config.frame_count = (u32) (stream->dataSize / frameSize);
config.start_offset = (u32) stream->startFrame;
stream->startFrame = 0;
stream->seeking = false;
DCFlushRange(buffer, bufferFrames * frameSize);
config.frame_data_ptr = MEM_VIRTUAL_TO_PHYSICAL(buffer);
config.frame_count = (u32) bufferFrames;
config.start_offset = (u32) startFrame;
config.voice_callback = audioStreamDolphinVoiceCallback;
config.stream_callback = NULL; // single-buffer playback
config.user_pointer = stream;
@@ -87,10 +145,8 @@ errorret_t audioStreamDolphinBuffer(audiostream_t *stream) {
// loopTo's (true today only because the shared 441Hz test tone was
// deliberately chosen to divide evenly into the sample rate).
if(stream->state & AUDIO_STREAM_STATE_LOOPING) {
config.loop_start_offset = (u32) (stream->loopTo * stream->pcm.sampleRate);
config.loop_end_offset = stream->loopStart >= 0
? (u32) (stream->loopStart * stream->pcm.sampleRate) - 1
: config.frame_count - 1;
config.loop_start_offset = (u32) loopToFrame;
config.loop_end_offset = (u32) loopEndFrame - 1;
}
s32 result = ansnd_configure_pcm_voice((u32) stream->platform.voiceId, &config);
+15 -1
View File
@@ -19,6 +19,19 @@ typedef struct {
// itself (unlike PSP/Linux), so this flag is how audioStreamDolphinIsFinished()
// answers without a callback of its own.
volatile bool_t finished;
// ansnd's DSP hardware loop (loop_start_offset/loop_end_offset) needs one
// contiguous physical buffer spanning the whole segment it may play -
// both the pre-loop lead-in and the loop segment itself, since the DSP
// addresses both as offsets into the same frame_data_ptr. Unlike
// PSP/Linux (which stream bounded windows on demand), a Dolphin voice
// can't be handed data incrementally mid-playback, so this buffer is
// read from the asset and owned here for the lifetime of one pass -
// bounded to the current loop segment's length (not the whole clip, if
// the clip is longer than one loop), freed/reallocated on each
// audioStreamDolphinBuffer() call and released for good in
// audioStreamDolphinDispose().
uint8_t *buffer;
} audiostreamdolphin_t;
/**
@@ -40,7 +53,8 @@ errorret_t audioStreamDolphinInit(audiostream_t *stream);
errorret_t audioStreamDolphinDispose(audiostream_t *stream);
/**
* Sends the stream's currently staged PCM data (stream->data) to its
* Reads the current loop segment's PCM data from the stream's asset (see
* audiostreamdolphin_t.buffer's own comment) and sends it to its
* allocated voice.
*
* @param stream The audio stream to output.