Skip to content

Expansion Audio

References: the authoritative spec is apu-2a03.md § Expansion-chip audio; levels are tracked in accuracy-ledger.md. This page is a curated handbook entry point.

Several NES cartridge boards carry their own sound hardware that mixes into the console's external-audio input alongside the 2A03. RustyNES synthesizes six such expansion chips and sums each into the mix through the Mapper::mix_audio(&mut self) -> i16 hook (default 0 for boards with no audio hardware). Each synth core lives in the owning mapper crate, not the 2A03 APU crate, because it is cartridge hardware.

Supported chips

Chip Channels Core Clocking
VRC6 2 pulse + 1 saw Vrc6Pulse ×2 + Vrc6Saw (rustynes-mappers) every CPU cycle
VRC7 6 FM (OPLL) rustynes_apu::Opll (emu2413-derived, MIT) OPLL calc() every 36 CPU cycles (~49,716 Hz)
FDS 1 wavetable + FM FdsAudio (fds.rs) wave/mod every 16 CPU cycles
MMC5 2 pulse + 7-bit PCM Mmc5Audio (m005_mmc5.rs) pulse every other CPU cycle
Namco 163 1–8 time-multiplexed wavetable Namco163Audio round-robin every 15 CPU cycles
Sunsoft 5B 3 tone + noise + envelope Sunsoft5BAudio (FME-7) every CPU cycle

All cores are behind the default-on mapper-audio Cargo feature; with it off (e.g. the no_std build) the register decoders still latch so save-state round-trip is preserved, but clock/mix become silent no-op shims. The VRC7 OPLL core is deliberately the MIT emu2413 lineage, not the license- incompatible Nuked-OPLL.

Relative levels (v2.1.6 "Expansion Audio", VRC6 recalibrated v2.2.7)

Each chip's mix_audio() is scaled so its full-volume output sits at the loudness the hardware produces relative to the 2A03 pulse — calibrated against the reference-emulator field (Mesen2 was RustyNES's historical accuracy bar, but VRC6 was recalibrated away from it in v2.2.7 because Mesen2 is the loud outlier for VRC6; see below), measured by the bbbradsmith db_* decibel-comparison ROMs and asserted by the audio_expansion.rs level_db_* oracle. VRC6 (≈1.0, v2.2.7), MMC5 (≈1.0, "equivalent to the APU") and Namco 163 1-channel (≈6.02) are the pinned corrections. VRC6 was recalibrated in v2.2.7 "Timbre II": the v2.1.6 target (≈1.506) mirrored Mesen2's specifically louder ×5 mixer weight, but a NESdev-forum report plus a cross-reference across eleven reference emulators and the NESdev wiki found Mesen2 to be the loud outlier — the wiki states the VRC6 pulse channels are roughly loudness-equivalent to the 2A03's, and rustico / tetanes / BizHawk each encode that equivalence exactly — so the target moved to ≈1.0 and VRC6_MIX_SCALE moved 979 → 650 (m024_vrc6.rs). One level remains an honest documented gap: the VRC7 FM absolute level (the FM synth is implemented and its instrument ROM is verified canonical, but its patch/feedback-dependent pseudo-sine output is not cleanly oracle-pinned, so db_vrc7 stays a byte-exact snapshot regression guard). (The Sunsoft 5B absolute level was itself an i16-mix_audio-contract gap until v2.2.3 widened the trait return to i32 and pinned it via level_db_5b; its log-DAC shape is hardware-exact and, since v2.2.7, its envelope uses the exact 5-bit 1.5 dB/step DAC — see below.) Separately, v2.2.7 also completed the Sunsoft 5B envelope DAC: the envelope-mode volume now indexes a 32-level, 1.5 dB/step table (SUNSOFT5B_LOG_VOL32) instead of truncating to the 4-bit, 3 dB/step approximation, matching nestopia / rustico; the fixed 4-bit tone volume and the absolute 5B level are unchanged. See accuracy-ledger.md § Expansion-audio levels.

Because a non-expansion mapper's mix_audio() returns 0, the expansion mix is a separate additive term — the level corrections leave the base 2A03 mix byte-identical, so AccuracyCoin / blargg / nestest are unaffected.

NSF playback

A classic .nsf may declare expansion audio in its $07B bitfield (bit 0 VRC6, 1 VRC7, 2 FDS, 3 MMC5, 4 N163, 5 5B). The NSF player does not reimplement any synthesis: NsfExpansion (nsf_expansion.rs) owns instances of the exact same cores and routes the NSF register windows into them, so an NSF VRC6 tune sounds identical to a VRC6 cartridge. NsfExpansion is unreachable from any oracle cartridge ROM, so it cannot perturb the existing audio suites.

Frontend

The Audio Mixer tool panel (v2.1.6, debugger::audio_mixer) exposes per-source balance sliders and per-channel scopes / VU meters, including the on-cart expansion channel. See frontend.md.