pub struct Audio { /* private fields */ }Expand description
Audio Interface state: the two-deep DMA FIFO, the DAC rate divider, and the derived-timing sample emission.
Implementations§
Source§impl Audio
impl Audio
Sourcepub const fn set_region(&mut self, region: Region)
pub const fn set_region(&mut self, region: Region)
Select the console region (sets the video clock and re-derives the rate). Wired from the cart header at ROM load; defaults to NTSC.
Sourcepub const fn underruns(&self) -> u64
pub const fn underruns(&self) -> u64
Observed underrun count (buffer starvations) — for the harness.
Sourcepub const fn sample_rate(&self) -> u32
pub const fn sample_rate(&self) -> u32
The derived output sample rate in Hz (0 until AI_DACRATE is set).
Sourcepub fn drain(&mut self) -> Vec<StereoSample>
pub fn drain(&mut self) -> Vec<StereoSample>
Drain the emitted stereo stream produced since the last drain.
Sourcepub fn read_reg(&self, index: u32) -> u32
pub fn read_reg(&self, index: u32) -> u32
Read an AI register (index = (addr >> 2) & 7).
Every register except AI_STATUS (index 3) is write-only and reads back
a mirror of AI_LENGTH (the front transfer’s remaining bytes), per the
wiki and ares. AI_STATUS reports the FULL/BUSY/ENABLED flags plus the
best-effort COUNT/WC readback (ledgered — no oracle pins its phase).
Sourcepub fn write_reg(&mut self, index: u32, val: u32) -> AiIrq
pub fn write_reg(&mut self, index: u32, val: u32) -> AiIrq
Write an AI register (index = (addr >> 2) & 7), returning the MI
interrupt effect for the Bus to apply.
The interrupt fires when a transfer starts, not when it ends: writing
AI_LENGTH into an idle queue (dma_count == 0) starts that buffer
immediately and raises the interrupt now; a second buffer queued behind a
playing one raises nothing until it is promoted in Audio::tick. This
is what lets software refill during playback (wiki §DMA).
Sourcepub fn tick<B: AudioBus>(&mut self, now: u64, bus: &mut B)
pub fn tick<B: AudioBus>(&mut self, now: u64, bus: &mut B)
Advance the AI to now master ticks, emitting every output sample whose
scheduled tick has arrived.
Derived timing (ADR 0006): the number of samples emitted is a function of
now and the DAC period, never of an independently incremented counter.
Hot path — allocation into the sink is the only cost while playing.
Sourcepub fn tick_without_bus(&mut self, now: u64) -> Option<NeedsBus>
pub fn tick_without_bus(&mut self, now: u64) -> Option<NeedsBus>
The part of a step that needs no bus access, returning None when it
finished the step on its own and Some(NeedsBus) when samples must be read
out of RDRAM.
This advances state, despite the Option return: it stamps last_tick
on every call and anchors next_sample_tick on the first one. It is named
tick_* rather than is_* for that reason.
Split out so a caller can decide whether to pay for bus access before
arranging it. A caller that owns this struct cannot lend it out without
first moving it, and that move is pure overhead on the ~99.95% of steps
that emit nothing (docs/audio.md §Derived timing). Audio::tick calls
this too, so there is one implementation of the early-outs and no way for
the two to disagree.
Sourcepub fn tick_with_bus<B: AudioBus>(&mut self, proof: NeedsBus, bus: &mut B)
pub fn tick_with_bus<B: AudioBus>(&mut self, proof: NeedsBus, bus: &mut B)
The rest of the step, which reads sample words out of RDRAM.
Reachable only with a NeedsBus from Audio::tick_without_bus, so the
two halves cannot run out of order: the preconditions — a non-zero period
and at least one sample already due — are carried by the type rather than by
a comment or an assertion. The token also carries the period, so the 64-bit
divide that produced it is not repeated here.