From af8c0f5ccda08b7727f1ee62f2663fe70eb62fb9 Mon Sep 17 00:00:00 2001 From: Dominic Masters Date: Mon, 31 Aug 2026 10:36:51 -0500 Subject: [PATCH] Implement PSP audio playback via native sceAudio Reserves a hardware channel per stream (64-aligned sample count, mono/stereo format) and outputs through sceAudioOutputPannedBlocking, giving PSP real stereo panning from directionality. Enforces the 44100Hz hardware-channel constraint with a clear error instead of silently mispitching, and drops the shared test tone to 44100Hz to match. Verified via the dusk-psp toolchain and a headless PPSSPP run (correct channel reservation, onEnd firing once, no errors). --- src/dusk/audio/audiostreampcm.c | 6 +- src/dusk/engine/engine.c | 3 +- src/duskpsp/CMakeLists.txt | 1 + src/duskpsp/audio/CMakeLists.txt | 11 ++++ src/duskpsp/audio/audiostreampsp.c | 98 ++++++++++++++++++++++++++++++ src/duskpsp/audio/audiostreampsp.h | 5 ++ 6 files changed, 121 insertions(+), 3 deletions(-) create mode 100644 src/duskpsp/audio/CMakeLists.txt diff --git a/src/dusk/audio/audiostreampcm.c b/src/dusk/audio/audiostreampcm.c index 5c91463e..26a915ee 100644 --- a/src/dusk/audio/audiostreampcm.c +++ b/src/dusk/audio/audiostreampcm.c @@ -24,9 +24,11 @@ errorret_t audioStreamPcmInit( stream->dataSize = dataSize; stream->pcm.sampleRate = sampleRate; stream->pcm.channels = channels; - stream->duration = (float_t) dataSize / + stream->duration = ( + (float_t) dataSize / (float_t) (channels * sizeof(int16_t)) / - (float_t) sampleRate; + (float_t) sampleRate + ); errorChain(audioStreamPlatformInit(stream)); diff --git a/src/dusk/engine/engine.c b/src/dusk/engine/engine.c index 8ec68a16..ac43d89c 100644 --- a/src/dusk/engine/engine.c +++ b/src/dusk/engine/engine.c @@ -28,7 +28,8 @@ engine_t ENGINE; -#define AUDIO_TEST_TONE_SAMPLE_RATE 48000 +// 44100Hz: PSP's sceAudioChReserve hardware channels are fixed at this rate. +#define AUDIO_TEST_TONE_SAMPLE_RATE 44100 #define AUDIO_TEST_TONE_FREQUENCY 440 #define AUDIO_TEST_TONE_SAMPLES AUDIO_TEST_TONE_SAMPLE_RATE #define AUDIO_TEST_TONE_SIZE (AUDIO_TEST_TONE_SAMPLES * sizeof(int16_t)) diff --git a/src/duskpsp/CMakeLists.txt b/src/duskpsp/CMakeLists.txt index 5ca3534c..97448068 100644 --- a/src/duskpsp/CMakeLists.txt +++ b/src/duskpsp/CMakeLists.txt @@ -16,6 +16,7 @@ target_sources(${DUSK_BINARY_TARGET_NAME} # Subdirs add_subdirectory(asset) +add_subdirectory(audio) add_subdirectory(input) add_subdirectory(log) if(DUSK_NETWORK) diff --git a/src/duskpsp/audio/CMakeLists.txt b/src/duskpsp/audio/CMakeLists.txt new file mode 100644 index 00000000..6afc7ea2 --- /dev/null +++ b/src/duskpsp/audio/CMakeLists.txt @@ -0,0 +1,11 @@ +# Copyright (c) 2026 Dominic Masters +# +# This software is released under the MIT License. +# https://opensource.org/licenses/MIT + +# Sources +target_sources(${DUSK_LIBRARY_TARGET_NAME} + PUBLIC + audiopsp.c + audiostreampsp.c +) diff --git a/src/duskpsp/audio/audiostreampsp.c b/src/duskpsp/audio/audiostreampsp.c index d3279c45..e138cd5e 100644 --- a/src/duskpsp/audio/audiostreampsp.c +++ b/src/duskpsp/audio/audiostreampsp.c @@ -6,3 +6,101 @@ */ #include "audiostreampsp.h" +#include "audio/audiostream.h" +#include "assert/assert.h" +#include "util/memory.h" +#include + +errorret_t audioStreamPSPInit(audiostream_t *stream) { + assertNotNull(stream, "Stream cannot be NULL."); + + if(stream->pcm.channels != 1 && stream->pcm.channels != 2) { + errorThrow( + "PSP audio only supports mono or stereo PCM, got %d channels.", + stream->pcm.channels + ); + } + + // sceAudioChReserve's hardware channels always run at the PSP's native + // 44100Hz; there is no per-channel sample rate. Arbitrary rates would need + // sceAudioSRCChReserve's single exclusive channel instead. + if(stream->pcm.sampleRate != 44100) { + errorThrow( + "PSP audio channels are fixed at 44100Hz, got %uHz.", + stream->pcm.sampleRate + ); + } + + const size_t frameSize = stream->pcm.channels * sizeof(int16_t); + const size_t frameCount = stream->dataSize / frameSize; + + int samples = (int) PSP_AUDIO_SAMPLE_ALIGN(frameCount); + if(samples < PSP_AUDIO_SAMPLE_MIN) samples = PSP_AUDIO_SAMPLE_MIN; + if(samples > PSP_AUDIO_SAMPLE_MAX) { + // TODO: sources longer than ~1.5s need chunked streaming across + // multiple buffer calls instead of a single one. + samples = PSP_AUDIO_SAMPLE_MAX; + } + + const int format = stream->pcm.channels == 1 + ? PSP_AUDIO_FORMAT_MONO + : PSP_AUDIO_FORMAT_STEREO; + + const int channel = sceAudioChReserve(PSP_AUDIO_NEXT_CHANNEL, samples, format); + if(channel < 0) { + errorThrow("Failed to reserve PSP audio channel: 0x%08X", channel); + } + + stream->platform.channel = channel; + stream->platform.samples = samples; + + errorOk(); +} + +errorret_t audioStreamPSPDispose(audiostream_t *stream) { + assertNotNull(stream, "Stream cannot be NULL."); + + sceAudioChRelease(stream->platform.channel); + + errorOk(); +} + +errorret_t audioStreamPSPBuffer(audiostream_t *stream) { + assertNotNull(stream, "Stream cannot be NULL."); + + const size_t frameSize = stream->pcm.channels * sizeof(int16_t); + const size_t bufferSize = (size_t) stream->platform.samples * frameSize; + + int16_t *buffer = memoryAllocate(bufferSize); + memoryZero(buffer, bufferSize); + memoryCopy( + buffer, stream->data, + stream->dataSize < bufferSize ? stream->dataSize : bufferSize + ); + + // Simple linear pan; AUDIO_STREAM_LEFT/CENTER/RIGHT map to -128..0..127. + float_t pan = (float_t) stream->directionality / 128.0f; + if(pan < -1.0f) pan = -1.0f; + if(pan > 1.0f) pan = 1.0f; + + 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 result = sceAudioOutputPannedBlocking( + stream->platform.channel, leftVolume, rightVolume, buffer + ); + memoryFree(buffer); + + if(result < 0) { + errorThrow("Failed to output PSP audio data: 0x%08X", result); + } + + errorOk(); +} + +bool_t audioStreamPSPIsFinished(audiostream_t *stream) { + assertNotNull(stream, "Stream cannot be NULL."); + + return sceAudioGetChannelRestLength(stream->platform.channel) <= 0; +} diff --git a/src/duskpsp/audio/audiostreampsp.h b/src/duskpsp/audio/audiostreampsp.h index cf22b5bf..3d48d6b8 100644 --- a/src/duskpsp/audio/audiostreampsp.h +++ b/src/duskpsp/audio/audiostreampsp.h @@ -11,7 +11,12 @@ typedef struct audiostream_s audiostream_t; typedef struct { + // Reserved hardware output channel, from sceAudioChReserve. int channel; + + // Number of samples (frames) per output call the channel was reserved + // with. 64-aligned, between PSP_AUDIO_SAMPLE_MIN and PSP_AUDIO_SAMPLE_MAX. + int samples; } audiostreampsp_t; /**