Repository navigation
Hot Path Audio ADPCM Block Reuse
Reviewed: November 1, 2026 · Source:
b7140848
State: Implemented in #187; repeatable game speedup not established.
Release availability: Main only at this review; absent from v0.8.136-0-g458730bf5373 (September 30, 2026).
The voice processor retains one owned encoded/decoded ADPCM block per voice across sample-fetch callbacks. Every new block fetch still obtains current encoded guest bytes. A hit requires matching channel count, encoded byte count and every encoded byte; a matching address or logical block number alone is never sufficient.
Before #187, voice_get_samples() could reuse decoded samples only within the current call. Its local decoded storage disappeared on return.
flowchart LR
A[Callback fetches encoded block] --> B[Decode into local storage]
B --> C[Copy requested samples]
C --> D[Return and discard decoded block]
D --> E[Later callback fetches and decodes again]
The current path separates callback-local source mappings from per-voice decoded ownership:
flowchart TD
A[Need next ADPCM block] --> B[Read current encoded bytes]
B --> C{Size and channel layout fit cache?}
C -->|No| D[Invalidate and reject invalid block]
C -->|Yes| E{Valid cache / channels / size / all bytes match?}
E -->|Yes| F[Use retained decoded prefix]
E -->|No| G[Decode into owned per-voice storage]
G --> H{Valid decoded frame count?}
H -->|No| D
H -->|Yes| I[Save encoded bytes / metadata / count]
I --> F
F --> J[Bounds-check requested frame and emit samples]
J --> K[Advance existing loop / position / completion state]
MCPXADPCMBlockCache stores a validity flag, channel count, encoded size, decoded frame count, up to 72 encoded bytes and 130 interleaved int16_t values. The decoded count is frames per channel; only that prefix is valid. mcpx_apu_adpcm_fits_cache() validates layout and capacity before decoding or comparing. Invalid headers or out-of-range decoded positions do not expose an old cached block.
Conceptually, mcpx_apu_adpcm_decode_cached() does this:
validate encoded size and channel count
if cache.valid and channels match and size matches and all bytes match:
return owned decoded prefix
otherwise:
decode current bytes into owned storage
validate decoded count
save current encoded bytes, metadata and count
return owned decoded prefix
Address is not part of the current content identity: independently decodable blocks with the same bytes and layout produce the same result even at different addresses. The cache contains copied bytes and samples, not a borrowed pointer into callback-local buffers or guest memory.
voice_reset_filters() invalidates the cache when VOICE_ON starts a new stream. VP reset invalidates every voice cache; snapshot pre-load quiesces the APU and invokes that reset. Unsupported/non-ADPCM input and invalid decode paths also clear validity. A different byte sequence, channel count or size causes a miss. Loop, position, notification and completion logic stays in voice_get_samples().
This cache must not be confused with the sample-mapping caches: mappings live for one callback and reread current guest values, while decoded samples can survive callbacks only after fresh-byte validation.
#187 merged October 3. Its final native observations did not establish a repeatable game-performance gain, and some PGR2 tail metrics worsened. The merge retained the mechanism without claiming a speedup. Do not convert fewer decoder calls or correctness passes into a game-FPS percentage; those results also do not qualify November main as a fresh candidate.
Useful regression cases are repeated callbacks within one block; in-place guest rewrites at the same address; changed channels/size; invalid/truncated headers; every emitted-frame bound; loops and stream/SGE boundaries; voice reset/reuse; and snapshot restoration. Compare exact decoded samples and downstream output. For timing, include hit/miss ratios, decoded blocks per output frame, byte-comparison cost, worker CPU and game cadence/tails under matched settings.
The 89-step-index × 16-nibble transition table is a different experiment: it accelerates a decode that still happens, whereas a block-cache hit skips that decode. #195 is closed/unmerged research and is not part of this delivered cache. Retain predictor update, clipping and sample order if revisiting it.
DSP tooling guide and navigation updated November 2, 2026 for experimental PR #384, source 8f51d237. Capture/replay usage and limits.
The earlier wiki-wide review was published November 1, 2026. Emulator source: b7140848. Reviewed wiki content: 1d2b671. Current status · Release availability · Publication process.
- Home
- Current status
- Stable versus main
- Implemented mechanisms
- Controls and defaults
- Measured results and limits
- Complete change register
- System
- CPU and vCPU
- Memory and DMA
- Graphics / NV2A
- APU and audio
- Timing and timers
- Platform and I/O
- Hardware observations
- Fidelity boundaries
- Methodology
- PGR2 routes
- Debugging lag / hot paths
- Tools and commands
- DSP capture, replay and fuzzing
- HLE readiness and public XISO proof
- Concurrent DSP shadow and OpenAL proof
- DSP HLE operation inventory
- DSP HLE fix examples and testing
- Development
- Upstream shortlist
- Wiki maintenance
- Research / retired work
- Optimization catalog
- Hot-path shortcuts
- Researched experiments
- Unproven proposal directory
- PR lessons
- September26 snapshot
Repository · PRs · Issues