Add cutscene controls for the audio mixer, fix PSP pause latency

- Fixed PSP pause taking up to ~1.1s to actually go silent: its output
  thread ran independently of stream->state, draining its whole software
  ring regardless. audiostream_t.state is now volatile and the output
  thread checks AUDIO_STREAM_STATE_PLAYING before each hardware chunk,
  skipping output (without consuming the ring) while paused - pause now
  goes silent within about one chunk (~23ms) and resume has no gap.
- Added audioMixerPause/Resume/SetPan/SetLoop/FadeTo/IsFading to the
  mixer - immediate, channel-indexed primitives for cutscenes to drive.
  Fade transitions (fadeFrom/To/Duration/Time/Easing) live on
  audiomixerchannelstate_t and advance every frame in
  audioMixerChannelApplyVolume(), reusing the same easingApply()
  interpolation uifullbox_t already uses for screen fades.
- New src/dusk/rpg/cutscene/item/audio/ with 9 cutscene item types:
  AUDIO_PLAY (+ AUDIO_PLAY_SIMPLE/AUDIO_PLAY_LOOPED shorthands),
  AUDIO_STOP, AUDIO_PAUSE, AUDIO_RESUME, AUDIO_FADE (+ FADE_OUT/FADE_IN
  shorthands), AUDIO_FADE_WAIT, AUDIO_SET_PAN, AUDIO_SET_LOOP, and the
  combined AUDIO_SET - registered through the same enum/union/callback
  table/macro mechanism every other item type uses.
- Wired the same 9 types into the JSON-based (offline JSONC -> binary
  .cts) cutscene asset pipeline: tools/asset/cutscene/__main__.py's
  encoder and assetcutsceneloader.c's decoder. Verified round-trip by
  hand-encoding/decoding a test file covering all 9 types, and confirmed
  the two existing real cutscene files re-encode byte-identical.

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

Co-Authored-By: Claude Sonnet 5 <[email protected]>
This commit is contained in:
2026-09-02 17:44:06 -05:00
co-authored by Claude Sonnet 5
parent 9c28d5a3b7
commit 9364768397
31 changed files with 1183 additions and 3 deletions
+15
View File
@@ -330,6 +330,21 @@ void audioStreamPSPThreadFeed(thread_t *thread) {
}
if(stopRequested) break;
// Paused: skip consuming from the ring and skip
// sceAudioOutputPannedBlocking() entirely - that call is the only
// thing keeping the PSP's own tiny hardware buffer fed, so simply
// not calling it silences output within about one hardware chunk
// (~23ms) instead of waiting for this thread's own much larger
// software ring (AUDIO_PSP_RING_FRAMES, ~1.1s) to drain naturally.
// The ring is left completely untouched, so resuming
// (audioStreamPlay()) continues exactly where playback left off,
// with no skip or gap. See audiostream.h's own comment on why
// reading stream->state from this thread is safe.
if(!(stream->state & AUDIO_STREAM_STATE_PLAYING)) {
sceKernelDelayThread(AUDIO_PSP_IDLE_POLL_MICROS);
continue;
}
// Production has stopped and what's left fits in one chunk (0 up to
// AUDIO_PSP_CHUNK_FRAMES - it can never be more, since production
// never adds more once readReachedEnd/readFailed is set) - this is