Skip to main content

Apu

Struct Apu 

Source
pub struct Apu {
    pub region: Region,
    pub pulse1: Pulse,
    pub pulse2: Pulse,
    pub triangle: Triangle,
    pub noise: Noise,
    pub dmc: Dmc,
    pub frame_counter: FrameCounter,
    pub sample_rate: u32,
    /* private fields */
}
Expand description

Top-level APU.

Fields§

§region: Region

Region (NTSC / PAL / Dendy).

§pulse1: Pulse

Pulse 1.

§pulse2: Pulse

Pulse 2.

§triangle: Triangle

Triangle.

§noise: Noise

Noise.

§dmc: Dmc

DMC.

§frame_counter: FrameCounter

Frame counter.

§sample_rate: u32

Sample rate (Hz) for diagnostics.

Implementations§

Source§

impl Apu

Source

pub fn new(region: Region, sample_rate: u32) -> Self

New APU.

Source

pub fn set_audio_provenance(&mut self, enabled: bool)

Arm or disarm audio provenance.

Arming allocates both stores; disarming frees them. Mirrors Ppu::set_pixel_provenance, including that re-arming an already-armed APU is a no-op rather than a silent wipe — the frontend re-asserts the arm every frame (a lesson from the pixel panel, whose edge-triggered mirror desynced permanently the moment a ROM load installed a fresh core).

Source

pub const fn audio_provenance_armed(&self) -> bool

Whether audio provenance is armed.

Source

pub fn register_attribution(&self) -> Option<&RegisterAttribution>

The per-register write attribution, or None when disarmed.

Source

pub fn mix_trace(&self) -> Option<&MixTrace>

The per-CPU-cycle mix trace, or None when disarmed.

Source

pub fn begin_audio_provenance_frame(&mut self, first_cycle: u64)

Begin a new frame’s mix trace, anchored at first_cycle.

The register attribution is deliberately NOT cleared here: “which instruction last wrote $4003” is a question whose answer legitimately predates the current frame, and clearing it every frame would report a register nobody has touched this frame as never written.

Source

pub fn clear_audio_provenance_history(&mut self)

Forget the register attribution history. Called on a cold boot, where the history it describes genuinely ended.

Source

pub const fn record_bus_handled_register_write(&mut self, addr: u16, value: u8)

Attribute a write in $4000-$4017 that the bus does NOT route through Self::write_register.

Two addresses in the range are not APU registers and are handled entirely on the bus: $4014 (OAM DMA, which arms a burst) and $4016 (controller strobe, which is buffered to the next M2-low boundary). Bus::write dispatches only $4000-$4013 | $4015 | $4017 to write_register, so the attribution recorded there can never see those two — yet the table reserves slots for them, because the range is what the bus already classifies as an APU write and punching a hole in it would invite off-by-one arithmetic at every call site.

Without this entry point those two slots would stay permanently empty while the docs claimed they were tracked. This records the cause exactly as write_register would, and dispatches nothing — the emulation of both addresses stays wherever the bus already implements it.

Source

pub const fn set_attrib_context(&mut self, pc: u16, cycle: u64)

Push the writing instruction’s PC + cycle down, mirroring the PPU’s write-attribution context. Called once per instruction by the core.

Source

pub fn take_audio_provenance(&mut self) -> AudioProvenanceStash

Lift both stores out for a same-timeline restore (run-ahead), leaving the APU disarmed. See crate::provenance::AudioProvenanceStash for why this exists at all.

Source

pub fn put_audio_provenance(&mut self, stash: AudioProvenanceStash)

Put back stores taken by Self::take_audio_provenance.

Source

pub fn reset(&mut self)

Reset (warm). Per nesdev: most APU state is preserved across reset except $4015 is cleared (channels disabled, DMC silenced).

v2.0.0 beta.3 (A4 cycle-accurate reset, promoted to the only path in beta.4): the 2A03 reset sequence behaves as if the LAST value written to $4017 were written again (blargg apu_reset spec) — the retained value is re-issued through the normal $4017 write path (pending mode + the 3/4-cycle aligned delay + the mode-1 immediate quarter/half clock), and the CPU’s 8 clocked reset cycles then age the re-armed counter so execution resumes ~9-12 cycles after the effective write (the 4017_timing window).

Source

pub const fn set_channel_mask(&mut self, mask: u8)

Set the per-channel enable mask (a UI playback overlay; see Apu::channel_mask). Bit 0 = pulse 1, 1 = pulse 2, 2 = triangle, 3 = noise, 4 = DMC, 5 = external/mapper audio. CHANNEL_MASK_ALL is the determinism-safe default (byte-identical mixer output).

Source

pub const fn channel_mask(&self) -> u8

Current per-channel enable mask.

Source

pub fn set_channel_gain(&mut self, gain: [f32; 6])

v1.4.0 Workstream C — set the per-channel output gain (a UI mixing overlay; see Apu::channel_gain). Index 0 = pulse 1, 1 = pulse 2, 2 = triangle, 3 = noise, 4 = DMC, 5 = external/mapper audio. Each gain is clamped to 0.0..=2.0. CHANNEL_GAIN_UNITY (all 1.0) is the determinism-safe default (byte-identical mixer output).

Source

pub fn set_filter_model(&mut self, model: FilterModel)

v2.1.3 — select the analog output-filter model (see crate::mixer::FilterModel). Default crate::mixer::FilterModel::NesRf is byte-identical to the pre-v2.1.3 output; the softer models drop the aggressive 440 Hz high-pass for a fuller low end. Display/tonal only — channel content is unchanged.

Source

pub const fn channel_gain(&self) -> [f32; 6]

Current per-channel output gain. See Apu::set_channel_gain.

Source

pub fn pulse1_out(&self) -> u8

Pulse 1 raw output volume (0..=15) — for tests.

Source

pub fn pulse2_out(&self) -> u8

Pulse 2 raw output volume.

Source

pub fn triangle_out(&self) -> u8

Triangle raw output (0..=15).

Source

pub fn noise_out(&self) -> u8

Noise raw output (0..=15).

Source

pub const fn dmc_out(&self) -> u8

DMC raw output (0..=127).

Source

pub const fn external_out(&self) -> f32

v2.1.6 “Expansion Audio” — the most recent RAW on-cart expansion-audio sample (pre-Self::channel_gain, the last_external field). 0.0 when the loaded board has no expansion audio. Read-only display tap for the frontend Audio Mixer expansion-channel scope / VU meter — it reads a copy and never feeds back into synthesis, so it is determinism-neutral.

Source

pub const fn frame_irq_pending(&self) -> bool

Frame IRQ pending?

Source

pub const fn dmc_irq_pending(&self) -> bool

DMC IRQ pending?

Source

pub const fn irq_line(&self) -> bool

Combined IRQ line — true if either source is asserting.

Session-26 iter 5 (2026-05-23): the frame-counter contribution is irq_line_active (the CPU’s IRQSource::FrameCounter registration), NOT irq_flag (the $4015 bit 6 visibility). The two are SEPARATE fields since iter 5 — see FrameCounter::irq_flag and FrameCounter::irq_line_active. AccuracyCoin Tests I/J/K specifically test that $4015 bit 6 is visible during inhibit (transient 2-cycle window at FC steps 29828-29829) while NO CPU IRQ fires (Test M).

Source

pub const fn last_frame_events(&self) -> FrameEvents

Returns the frame-counter events fired by the most recent tick call.

The bus reads this immediately after Self::tick_with_external to fan-out the events to on-cart audio extensions (MMC5) whose envelope and length-counter sub-units share the 2A03 frame-counter cadence. The value is overwritten at the start of every tick, so observers must consume it before the next tick.

Source

pub fn drain_audio(&mut self) -> Vec<f32>

Drain all finalized audio samples (host sample rate, normalized to approximately [-0.5, 0.5]).

Source

pub fn drain_audio_into(&mut self, out: &mut [f32]) -> usize

Drain into a slice; returns count copied.

Source

pub const fn dmc_dma_pending(&self) -> bool

Has a DMC DMA request been raised? The bus polls this each CPU cycle (BEFORE issuing reads) so it can halt the CPU on the next read cycle.

Source

pub const fn dmc_dma_is_load(&self) -> bool

Whether the pending DMC DMA is a load DMA.

Source

pub const fn dmc_dma_serviceable(&self) -> bool

W3-Stage-3 (mc-r1-dmc-delayed-4015): the bus-side per-cycle DMC DMA service-gate term — TriCNES _6502 line 4218: DoDMCDMA && (APU_Status_DMC || APU_ImplicitAbortDMC4015). While false, a pending (or halted in-flight) DMC DMA is NOT serviced and the CPU resumes — the emergent explicit abort. pending_dmc_abort is the implicit-abort override (the 1-cycle abort DMA runs regardless).

Source

pub const fn dmc_dma_short(&self) -> bool

Whether the pending DMC DMA should use the short 3-cycle service path.

Source

pub const fn dmc_dma_deliver_before_tick(&self) -> bool

Whether the current DMC DMA get should make the fetched byte visible before the get-cycle APU tick.

Source

pub const fn defer_next_dmc_reload_once(&mut self)

Defer the next immediate DMC reload request by one CPU tick.

Source

pub const fn dmc_abort_pending(&self) -> bool

Has a one-cycle DMC abort halt been raised?

Source

pub const fn dmc_abort_delay(&self) -> u8

Read-only accessor for the DMC abort-delay countdown (CPU cycles until pending_dmc_abort flips to true). Exposed for the Session-21 per-cycle DMC trace tooling (crates/rustynes-core/src/ irq_trace.rs) which records the scheduler’s calibration state for cross-diffing against Mesen2’s NesDmc.cpp.

Source

pub const fn dmc_dma_cooldown(&self) -> u8

Read-only accessor for the DMC DMA cooldown countdown (CPU cycles during which a newly-empty sample buffer must NOT raise a new DMA request). See Self::dmc_abort_delay.

Source

pub const fn dmc_dma_delay(&self) -> u8

Read-only accessor for the DMC DMA delay countdown (CPU cycles until an initial-load DMA after $4015 enable transitions from “armed” to pending_dmc_dma = true). See Self::dmc_abort_delay.

Source

pub const fn dmc_timer(&self) -> u16

Diagnostic: the DMC channel’s internal byte-timer countdown. Exposed for the per-cycle DMC-DMA cross-diff tracing that pins the abort-context reload-arm phase (the +4-cycle A->B interval divergence).

Source

pub const fn dmc_bits_remaining(&self) -> u8

Diagnostic: bits remaining in the DMC output shift register.

Source

pub const fn dmc_silence(&self) -> bool

Diagnostic: DMC output-unit silence flag.

Source

pub const fn dmc_buffer_full(&self) -> bool

Diagnostic: DMC sample buffer occupied.

Source

pub const fn apu_phase(&self) -> bool

Read-only accessor for the APU’s two-cycle phase counter (false = put, true = get; toggled every CPU tick by tick_with_external). See Self::dmc_abort_delay.

Source

pub const fn set_canonical_cycle(&mut self, cycle: u64)

v2.0.0 beta.1 (A1 one-clock collapse): assign the APU’s cycle counter from the CANONICAL bus cycle counter. Called by the bus’s per-cycle hook (cpu_clockapu_advance_one) immediately before Self::tick_with_external, replacing the legacy independent cpu_cycle += 1 mirror. The bus increments its canonical counter earlier in the same per-cycle hook, so the value assigned here equals the post-increment value the legacy mirror produced — the one_clock_invariants harness test pins the residue. Promoted to the only path in v2.0.0 beta.4.

Source

pub const fn cpu_cycle(&self) -> u64

Read-only accessor for the APU-side cumulative CPU-cycle counter (v2.0.0-beta.1 one-clock instrumentation).

This is one of the five counters of the timebase substrate the v2.0.0 “Timebase” rewrite collapses (ADR 0002 + the v2.0.0 master-clock plan): Cpu::master_clock, Cpu::cycles, LockstepBus::cycle, LockstepBus::ppu_clock, and this field are each advanced exactly once (or by one region divider) per CPU cycle at different points within the cycle, and must never drift. The RW-1 parity collapse already derives apu_phase / put_cycle from (cpu_cycle + parity_seed) & 1; exposing the raw counter lets the test harness assert the cross-chip affine invariants (one_clock_invariants.rs) that gate the beta.1 counter collapse.

Source

pub const fn seed_apu_phase(&mut self, phase: bool)

CM-1: seed the absolute apu_phase alignment (the parity of the CPU cycles on which the APU — incl. the DMC byte-timer — clocks). RustyNES starts apu_phase = false, so the DMC always arms on one CPU-cycle parity; Mesen’s DMC arms on its _currentCycle alignment (one cycle off, giving span 3 vs RustyNES’s 4). Seeding true flips the whole APU phase by one CPU cycle to test the Mesen-matching arm parity. Broad impact: also shifts pulse/noise/frame-counter. Default-off.

Source

pub const fn dmc_dma_addr(&self) -> u16

Address the DMC wants to read. Valid only when dmc_dma_pending() returns true.

Source

pub const fn dmc_need_halt(&self) -> bool

v1.2 Sprint 3 (get/put scheduler, ADR-0007).

Returns true while the DMC still needs an initial halt cycle on the bus. Set by any code path that raises pending_dmc_dma; the new bus::service_dmc_dma implementation under the dmc-get-put-scheduler feature flag clears it after processing the halt get-cycle.

Source

pub const fn dmc_need_dummy_read(&self) -> bool

v1.2 Sprint 3 (get/put scheduler, ADR-0007).

Returns true while the DMC still needs a dummy-read / alignment cycle after the halt cycle. Cleared by the new scheduler once the alignment cycle has been processed.

Source

pub const fn clear_dmc_need_halt(&mut self)

v1.2 Sprint 3 — bus clears this after consuming the halt get-cycle (_needHalt = false in Mesen2’s NesCpu.cpp).

Source

pub const fn clear_dmc_need_dummy_read(&mut self)

v1.2 Sprint 3 — bus clears this after consuming the alignment cycle (_needDummyRead = false in Mesen2’s NesCpu.cpp).

Source

pub fn complete_dmc_dma(&mut self, byte: u8)

Bus calls this when it has executed a DMC DMA fetch (post-halt) and is delivering the sample byte.

Source

pub fn complete_dmc_dma_before_get_tick(&mut self, byte: u8)

Complete a DMC DMA get whose fetched byte is visible before the get-cycle APU tick.

Source

pub const fn complete_dmc_abort(&mut self)

Bus calls this after either consuming or suppressing a one-cycle DMC abort halt.

Source

pub const fn cancel_dmc_dma(&mut self)

v1.2 Sprint 3 iter 3 (get/put scheduler, ADR 0007) — DMC DMA abort with cancel semantics.

Under the OLD scheduler, Self::complete_dmc_abort clears only the abort flag; the DMC DMA still fires afterward (the abort just inserts a 1-cycle halt). Under the get/put model the abort CANCELS the DMA entirely — no byte fetch, all flag state cleared — matching Mesen2’s processCycle::if(_abortDmcDma) branch (NesCpu.cpp:386-390):

if(_abortDmcDma) {
    _dmcDmaRunning = false;
    _abortDmcDma = false;
    _needDummyRead = false;
    _needHalt = false;
}

This is the “Option C” semantic shift from the iter 3 research audit: abort cancels the fetch rather than letting it complete after a wasted cycle. The new bus-side service_dmc_dma (under dmc-get-put-scheduler feature) calls this when it detects dmc_abort_pending mid-loop.

Source

pub fn tick(&mut self)

One CPU clock. Bus must NOT have halted the CPU for DMC DMA when calling this (the bus is responsible for performing the DMA fetch before resuming tick() calls).

Standalone/test convenience: production (LockstepBus) drives the canonical cycle counter via Self::set_canonical_cycle before each Self::tick_with_external (the v2.0.0 one-clock contract — the APU never self-increments). This helper self-advances the counter so standalone APU stepping (unit tests, the snapshot fixtures) keeps the one-cycle-per-tick behavior.

Source

pub fn tick_with_external(&mut self, external: f32)

Same as tick, but accepts an additional pre-mixed audio sample from the cartridge (VRC6 / VRC7 / MMC5 / Sunsoft 5B / Namco 163 / FDS). The external value is added to the APU’s own mix BEFORE the band-limited buffer push.

The expected scale is ~ [-0.5, 0.5] (matching the APU mixer’s own output range). The bus is responsible for converting whatever the mapper returns (currently i16 from Mapper::mix_audio) into that range.

Source

pub fn promote_dmc_pending_next(&mut self)

Visibility-delay promotion (called at END of cycle, after the CPU’s bus access): a reload latched this cycle becomes visible to the NEXT cycle’s DMA servicing (first-service on the put cycle => span 4), matching TriCNES _EmulateAPU-after-_6502 invisible-arm ordering.

Source

pub fn dmc_tick_end(&mut self)

v2.0 Program M (M-1 within-cycle order, mc-r1-dmc-bytetimer-end): clock the DMC byte-timer + arm the reload at END of cycle (after the CPU’s bus access), the mirror of the cycle-START block in tick_with_external that dmc_clock_at_start now suppresses. Order matches tick_with_external: byte-timer clock (on this cycle’s already-set apu_phase) -> reenable consume-edge clear -> reload-arm -> cannot_run decrement. The bus calls this from cpu_clock_apu_dmc (end-of-cycle), AFTER promote_dmc_pending_next so a reload latched here is invisible to its own cycle (promoted -> serviced the NEXT cycle = span-4, like the references). The LOAD delay-arm is NOT here — it stays at cycle-start.

Source

pub fn tick_dmc(&mut self)

v2.0 F-2: advance ONLY the DMC byte-timer + DMA arm by one CPU cycle. The R1 bus calls this at END of cycle (after the access) when Self::set_dmc_driven_externally is set, so the DMC fire-phase matches main’s tick_one_cpu_cycle (the cycle DMASync’s $4000 conflict expects) while the rest of the APU — incl. the IRQ line — stays on the cycle-start tick_with_external. Order mirrors tick_with_external: delay-arm → APU-rate timer clock (via the dmc_ext_phase flip-flop) → reload-arm.

Source

pub fn tick_dmc_timer_only(&mut self)

v2.0 interleaved-DMA Phase B: advance ONLY the DMC byte-timer clock (no delay/reload ARM), for a cycle of an interleaved DMC DMA span. The timer advances (so the variable-3/4-span feeds back into the next fire-cycle — divergence-A self-consistency) WITHOUT re-arming a new DMA mid-span (no cascade). Toggles the same dmc_ext_phase flip-flop as Self::tick_dmc so the every-other-cycle cadence stays consistent across normal + DMA cycles. (In the burst model this re-wedged; in the per-cycle interleaved model each DMA cycle is discrete and arm-gated, so it should hold.)

Source

pub const fn set_dmc_driven_externally(&mut self, on: bool)

v2.0 F-2: route the DMC byte-timer + arm to Self::tick_dmc instead of tick_with_external. Default false = byte-identical.

Source

pub const fn put_cycle(&self) -> bool

v2.0 interleaved-DMA Phase A: the global get/put flip-flop (TriCNES APU_PutCycle). true = put cycle, false = get cycle.

Source

pub const fn seed_apu_alignment(&mut self, alignment: u8)

v2.0 interleaved-DMA Phase A: seed the global get/put flip-flop from an APUAlignment value (TriCNES Emulator.cs:685/776), the single seed the interleaved DMA (Phase B) will share with the DMC fire-phase (divergence A). The low bit selects the parity (TriCNES case 0/2 -> put, 1/3 -> get).

Phase A seeds ONLY put_cycle and deliberately leaves dmc_ext_phase untouched, so the un-wedged feature-on behavior is preserved (the f2e experiment proved flipping dmc_ext_phase alone regresses). The exact put_cycle <-> dmc_ext_phase pairing is determined empirically in Phase B, when the bus first consumes put_cycle for the get/put decision.

Source

pub const fn snapshot_restored_parity(&self) -> bool

W3-Stage-4 (2026-06-10): whether the most recent Apu::restore blob carried the Stage-4 parity/DMA-state tail. The bus consults this after a snapshot restore: when true the exact put_cycle / parity_seed phase came from the blob and must NOT be overwritten by the boot Self::seed_apu_alignment call (pre-Stage-4 blobs lack the tail, so the bus falls back to the boot seed exactly as before).

Source

pub fn write_register(&mut self, addr: u16, value: u8)

CPU register write ($4000-$4017 excluding $4014).

Source

pub fn read_status(&mut self) -> u8

CPU register read (only $4015 is meaningful). Reading clears the frame IRQ flag.

Source

pub fn clear_frame_irq_immediate_for_dma(&mut self)

Clear the frame IRQ flag immediately for DMA no-op reads of $4015.

The normal CPU-visible $4015 read path keeps the put-cycle deferred clear needed by frame-counter timing tests. DMC DMA no-op repeats use this after sampling the status value so the halted-read side effect is visible before the CPU resumes the original $4015 read.

Session-26 iter 5: also deassert the CPU IRQ line driver (irq_line_active) since the DMA no-op read mirrors a CPU $4015 read on the silicon — the IRQ source is removed from the CPU’s _irqSource list synchronously.

Source§

impl Apu

Source

pub fn snapshot(&self) -> Vec<u8>

Encode the APU’s mutable state into a versioned binary blob.

Source

pub fn restore(&mut self, data: &[u8]) -> Result<(), ApuSnapshotError>

Decode a previously Apu::snapshoted blob.

§Errors

Returns ApuSnapshotError on a malformed blob.

Trait Implementations§

Source§

impl Clone for Apu

Source§

fn clone(&self) -> Apu

Returns a duplicate of the value. Read more
1.0.0 · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for Apu

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more

Auto Trait Implementations§

§

impl Freeze for Apu

§

impl RefUnwindSafe for Apu

§

impl Send for Apu

§

impl Sync for Apu

§

impl Unpin for Apu

§

impl UnsafeUnpin for Apu

§

impl UnwindSafe for Apu

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> ToOwned for T
where T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.