Skip to content

Hot Path Audio Mono Resampling

OpenAI Codex edited this page Nov 1, 2026 · 5 revisions

Mono Voice Resampling

Reviewed: November 1, 2026 · Source: b7140848
State: Implemented in #182; no separate user toggle.
Release availability: Included in v0.8.136-0-g458730bf5373 (September 30, 2026).

Mono voices use a one-channel libsamplerate converter, then expand the resampled output into the stereo representation used downstream. Stereo voices use two channels. Both routes retain SRC_SINC_FASTEST; the held linear-resampler proposal #189 is a separate audio-quality experiment.

Before #182

The older source duplicated mono input into left and right channels before feeding a persistent two-channel converter. The old “Don't do 2ch resampling” FIXME described that historical path and has been removed.

flowchart LR
    A[Fetch mono source] --> B[Duplicate into L/R input]
    B --> C[Two-channel sinc converter]
    C --> D[Filter / HRTF / mix]
Loading

Current execution

flowchart TD
    A[Resolve voice channel mode] --> B{Existing converter has different channel count?}
    B -->|Yes| C[Destroy incompatible converter]
    B -->|No| D[Use or create converter]
    C --> D
    D --> E{Mono?}
    E -->|Yes| F[Pack callback input into mono buffer]
    F --> G[One-channel SRC_SINC_FASTEST]
    G --> H[Expand resampled values into L/R]
    E -->|No| I[Two-channel SRC_SINC_FASTEST]
    H --> J[Existing filter / HRTF / mix]
    I --> J
Loading

voice_get_samples() still supplies the voice fetch interface. voice_resample_callback() packs the left values from its two-slot source buffer into mono_resample_buf when the selected converter has one channel. voice_resample() receives mono output into a temporary buffer and calls mcpx_apu_expand_mono_samples() after interpolation. This removes the duplicate interpolation work; it does not remove all two-slot intermediate storage or downstream channel work.

The following pseudocode describes the implemented boundary, rather than an additional fallback policy:

channels = stereo ? 2 : 1
if converter exists and converter.channels != channels:
    destroy converter
if converter does not exist:
    create SRC_SINC_FASTEST converter for channels
read requested frames through callback
if mono:
    expand each resampled value into both output channels
continue existing filtering, spatialization and mixing

State and ownership

The persistent converter and its recorded channel count belong to one MCPXAPUVoiceFilter. Channel-count changes destroy and recreate it. The implementation does not migrate the old converter's phase or buffered history into the new channel layout; transition behavior therefore remains a specific correctness test, rather than a promise of continuous history conversion.

VOICE_ON resets filters and destroys the previous converter so a reused voice chooses its channel layout afresh. VP reset and teardown destroy converters as well. Snapshot pre-load quiesces the APU and resets the VP; host converter pointers are not guest snapshot state. Pause/resume and active-history save/load remain useful regression cases.

Required behavior includes sample order, pitch/source progression, loops, completion/notifiers, per-channel filter histories, HRTF, multipass and monitor paths. Expansion occurs before downstream processing that can intentionally make channels differ.

Evidence and limits

#182 is the implementation and qualification history. Its older-head workload results apply to those builds and settings; they are not fresh measurements of November main. The code mechanism and stable availability are established independently of a universal game-FPS claim. Benefit depends on the share of active mono voices and resampling cost.

For future changes, compare one-channel output with each channel of the maintained two-channel reference. Exercise non-integer/changing pitch, loop boundaries, short callback reads, mono/stereo transitions, voice reuse, pause/reset, active snapshots, filters, HRTF and multipass. Record route reachability, mono/stereo frame counts, channel samples processed, worker CPU, callback p95/p99 and whole-game cadence at equal guest progress. A local interpolation reduction alone cannot establish an overall speedup.

Source and related work

Clone this wiki locally