Cache PSP volume/pan computation, mix channel + master volume into mixer playback, wire boa.mp3 as looping BGM

- PSP output thread now caches the last computed leftVolume/rightVolume and
  only recomputes them when stream->volume/directionality actually changed
  since the previous chunk, instead of recomputing every ~23ms chunk
  unconditionally.
- Mixer channels and the mixer itself now carry their own volume
  (audioMixerSetChannelVolume()/audioMixerSetMasterVolume()), multiplied
  with each sound's own volume every audioMixerUpdateEarly() and applied via
  audioStreamSetVolume() - so changing a channel's or the master volume
  affects whatever's already playing, not just future sounds. Also fixed
  audioMixerUpdateEarly() to tolerate a PLAY command queued outside the
  normal per-frame cycle (no loadingAsset yet from a preceding
  audioMixerUpdateLate(), e.g. during engine startup) by leaving it queued
  for the following frame instead of asserting.
- engine.c now plays boa.mp3 on AUDIO_MIXER_CHANNEL_BGM_0 on loop through
  the mixer instead of driving a raw audiostream_t directly, exercising the
  new mixer pipeline end to end.

Built and verified on Linux, PSP (Docker) and GameCube (Docker).

Co-Authored-By: Claude Sonnet 5 <[email protected]>
This commit is contained in:
2026-09-02 15:10:09 -05:00
co-authored by Claude Sonnet 5
parent d49594d3fb
commit d5078b978c
5 changed files with 164 additions and 44 deletions
+25 -8
View File
@@ -73,6 +73,7 @@ errorret_t audioStreamPSPInit(audiostream_t *stream) {
stream->platform.channel = channel;
stream->platform.finished = false;
stream->platform.playRequested = false;
stream->platform.hasLastVolume = false;
const size_t ringSize =
AUDIO_PSP_RING_FRAMES * stream->channels * sizeof(int16_t);
@@ -381,17 +382,33 @@ 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 leftFactor, rightFactor;
audioStreamGetPanFactors(stream, &leftFactor, &rightFactor);
// Checked every chunk (~23ms at 44100Hz) so SetVolume/SetDirectionality
// take effect mid-playback, unlike the platform's other one-shot
// calls - but only actually recomputed when one of them has changed
// since the last chunk (see lastVolume's own comment); otherwise the
// previous chunk's already-computed leftVolume/rightVolume are reused
// as-is.
if(
!stream->platform.hasLastVolume ||
stream->platform.lastVolume != stream->volume ||
stream->platform.lastDirectionality != stream->directionality
) {
float_t leftFactor, rightFactor;
audioStreamGetPanFactors(stream, &leftFactor, &rightFactor);
const int baseVolume = (int) (stream->volume * PSP_AUDIO_VOLUME_MAX);
const int leftVolume = (int) (baseVolume * leftFactor);
const int rightVolume = (int) (baseVolume * rightFactor);
const int baseVolume = (int) (stream->volume * PSP_AUDIO_VOLUME_MAX);
stream->platform.lastLeftVolume = (int) (baseVolume * leftFactor);
stream->platform.lastRightVolume = (int) (baseVolume * rightFactor);
stream->platform.lastVolume = stream->volume;
stream->platform.lastDirectionality = stream->directionality;
stream->platform.hasLastVolume = true;
}
sceAudioOutputPannedBlocking(
stream->platform.channel, leftVolume, rightVolume, chunk
stream->platform.channel,
stream->platform.lastLeftVolume,
stream->platform.lastRightVolume,
chunk
);
stream->platform.framesOutput += framesThisChunk;