Commit Graph

5 Commits

Author SHA1 Message Date
YourWishes f8f8a80a21 Add MP3 audio stream support with hardware/software decoder backends
New ASSET_LOADER_TYPE_MP3 (hand-rolled MPEG-1/2/2.5 Layer III header
parser - no third-party dependency needed just for metadata, since PSP's
hardware path doesn't need one at all) plus a shared audiostreammp3.c
stream layer mirroring audiostreampcm.c's shape. Generalized the stream
dispatch (hoisted sampleRate/channels onto audiostream_t, added
audioStreamGetTotalFrames()/Seek()/Read()) so all three platform audio
backends keep working unchanged, just calling the generic names instead
of PCM-specific ones.

Two decoder backends behind one interface: PSP uses the real sceMp3
hardware decoder (firmware-offloaded, lazily initialized on first use);
Linux and Dolphin share one minimp3-based software decoder (public
domain, vendored via CMake FetchContent) - libogc's own MP3Player wraps
libmad (GPL) and drives its own output pipeline, not a fit for the
ansnd-based architecture already in place, so skipped in favor of the
shared minimp3 path.

Fixed three real bugs found via hardware/runtime testing along the way:
- LAME's Xing header counts its own placeholder frame in the declared
  total, which made playback stall permanently one frame short of the
  declared end (looked like "never loops") - fixed by subtracting it.
- sceMp3Decode() can return more PCM than one MPEG frame's worth in a
  single call (PSP's pcmBuf is provisioned for 2x), overflowing the
  shared per-frame decode buffer with no bound check - very intermittent
  corruption/clicking on real hardware. Widened the buffer to the real
  worst case and added an assertion.
- sceMp3ResetPlayPosition()'s exact internal reset semantics aren't
  documented precisely enough to trust for looping - occasionally
  disagreed with the fresh stream position fed right after, clicking at
  the loop boundary about 1 in 3-4 loops. Rewind now fully tears down and
  recreates the decoder instead, the same path already proven correct at
  first Init. Also widened the PSP ring buffer to absorb that now-heavier
  operation, capping each top-up call's own work so the bigger buffer
  doesn't turn into one long blocking decode burst instead.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-01 08:21:30 -05:00
YourWishes d37109f5f7 Fixed PSP trying to load entirely into memory 2026-08-31 19:07:51 -05:00
YourWishes 15bd9fc43c 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.
2026-08-31 17:18:49 -05:00
YourWishes 8a77001016 Fix audio loop gaps: same-tick re-buffer + Linux lead-margin refill
Two independent gap sources, both confirmed fixed on Linux:

1. audiostream.c's Update() used if/else-if, so a loop restart
   (clearing BUFFERED) couldn't re-trigger Buffer() until the *next*
   frame's Update() noticed - up to one frame of dead air on every
   loop, on every platform. Restructured to two sequential ifs so a
   loop falls straight through into re-buffering in the same call.

2. audioStreamLinuxIsFinished only reported true once SDL's queue was
   completely empty - meaning silence had already started by the time
   "empty" could be observed, guaranteeing a gap by construction.
   Changed it to report ready-to-refill once the queue drops to a
   4096-frame lead margin (matching the SDL device's own internal
   buffer size) instead of waiting for zero. SDL_QueueAudio only ever
   appends to a FIFO, so refilling that early never causes overlap -
   it just means the device's callback never runs dry.

Confirmed looping gaplessly on Linux.
2026-08-31 12:33:33 -05:00
YourWishes 8ea370a1d1 Add audio subsystem skeleton with a working Linux PCM playback path
Cross-platform audiostream/audio API with per-platform hooks for
PSP/Dolphin/Linux. Linux is fully wired end-to-end through SDL2
(device open, volume-mixed queueing, finish detection via
SDL_GetQueuedAudioSize) and verified playing a generated test tone
in engine.c. PSP and Dolphin hooks are stubbed for now.
2026-08-31 10:24:02 -05:00