rustyn64_audio/lib.rs
1//! `rustyn64-audio` — the Audio Interface (AI) DAC + sample-DMA path.
2//!
3//! The RSP's audio microcode mixes samples into an RDRAM buffer; the AI then
4//! DMAs that buffer out to the DAC at a programmable sample rate (set via the
5//! `AI_DACRATE` divider off the video clock) and raises an AI interrupt when a
6//! buffer **starts** playing (not when it drains — see [`Audio::write_reg`]).
7//! This crate models the AI side; the actual mixing is RSP microcode (in
8//! `rustyn64-rsp`), so under LLE the audio "falls out free" (ADR 0002).
9//!
10//! The model follows the hardware description in
11//! `n64brew_wiki/markdown/Audio Interface.md` and the reference behavior in
12//! ares (ISC) `ref-proj/ares/ares/n64/ai/`: a two-deep DMA FIFO, the
13//! delayed-carry address bug reproduced as a 13-bit/11-bit split with a
14//! one-sample-deferred carry, the `AI_LENGTH` mirror on every write-only
15//! register, and a DAC that decays toward silence on underrun.
16//!
17//! Part of the one-directional chip-crate graph (see `docs/architecture.md`):
18//! this crate does NOT depend on any other chip crate; it reaches RDRAM and the
19//! interrupt line through the [`AudioBus`] trait. `#![no_std]` + `alloc`.
20
21#![no_std]
22#![forbid(unsafe_code)]
23#![warn(missing_docs)]
24// The DAC deliberately reinterprets the 32-bit RDRAM word's halves as signed
25// 16-bit samples, so a wrapping u->i cast is the intended operation.
26#![allow(
27 clippy::cast_possible_truncation,
28 clippy::cast_lossless,
29 clippy::cast_possible_wrap
30)]
31
32extern crate alloc;
33
34use alloc::vec::Vec;
35use serde::{Deserialize, Serialize};
36
37/// The canonical master clock (`MASTER_HZ`, ADR 0006), duplicated here because
38/// the chip-crate graph forbids `rustyn64-audio` depending on `rustyn64-core`.
39///
40/// The DAC period (master ticks per output sample) is `MASTER_HZ / sample_rate`.
41/// A cross-crate test in `rustyn64-core` asserts this equals the scheduler's
42/// `MASTER_HZ`, so the two cannot drift.
43pub const MASTER_HZ: u64 = 187_500_000;
44
45/// Video clock feeding the AI DAC divider on **NTSC** consoles (Hz).
46///
47/// The sample rate is this divided by `AI_DACRATE + 1`, so `AI_DACRATE = 1103`
48/// yields ~44.1 kHz. Provenance: project64/N64-Tests `DoubleShot` (which
49/// computes `(VI_NTSC_CLOCK / FREQ) - 1`) and the N64brew wiki. Documented, not
50/// tuned — accuracy-ledger entry for the region video clock.
51pub const VIDEO_CLOCK_NTSC: u32 = 48_681_812;
52
53/// Video clock feeding the AI DAC divider on **PAL** consoles (Hz).
54///
55/// Same derivation as [`VIDEO_CLOCK_NTSC`]; the differing clock is why the same
56/// `AI_DACRATE` detunes between regions. PAL VI *cadence* is still residual R-6;
57/// this constant is only the AI divisor.
58pub const VIDEO_CLOCK_PAL: u32 = 49_656_530;
59
60/// The emulated console region, selecting the AI video clock.
61#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
62pub enum Region {
63 /// NTSC (~60 Hz), the common region.
64 #[default]
65 Ntsc,
66 /// PAL (~50 Hz).
67 Pal,
68}
69
70impl Region {
71 /// The video clock (Hz) this region feeds into the AI DAC divider.
72 #[must_use]
73 pub const fn video_clock(self) -> u32 {
74 match self {
75 Self::Ntsc => VIDEO_CLOCK_NTSC,
76 Self::Pal => VIDEO_CLOCK_PAL,
77 }
78 }
79
80 /// The region a cartridge's **destination code** (ROM header byte `0x3E`, the
81 /// last character of the 4-byte game code) implies.
82 ///
83 /// The destination characters are the N64brew Wiki *ROM Header* §Standard
84 /// header table (read from `n64brew_wiki/html/ROM Header.xhtml` — the markdown
85 /// mirror renders that table as a bare `[TABLE]` placeholder):
86 ///
87 /// `A` All · `B` Brazil · `C` China · `D` Germany · `E` North America ·
88 /// `F` France · `G` Gateway 64 (NTSC) · `H` Netherlands · `I` Italy ·
89 /// `J` Japan · `K` Korea · `L` Gateway 64 (PAL) · `N` Canada · `P` Europe ·
90 /// `S` Spain · `U` Australia · `W` Scandinavia · `X`/`Y`/`Z` Europe
91 ///
92 /// **Provenance, and its limit.** That table gives *destinations*, **not TV
93 /// standards** — only `G`/`L` are labeled NTSC/PAL by the wiki itself. The
94 /// 50/60 Hz classification of the remaining countries is the **broadcast
95 /// standard for each territory**, not a hardware-documented N64 fact, and
96 /// there is **no oracle** for it (no test ROM checks region detection). It is
97 /// therefore modeled and ledgered as documented-but-ungated rather than
98 /// presented as measured — see `docs/accuracy-ledger.md` (T-71-005).
99 ///
100 /// Three cases are decided explicitly rather than guessed:
101 /// - **`B` Brazil → NTSC.** Brazil broadcast PAL-**M**, which is a *60 Hz*
102 /// standard; for the AI divisor it behaves as NTSC, not as 50 Hz PAL.
103 /// - **`C` China → NTSC.** A PAL territory, but with no known retail N64; the
104 /// default is kept rather than inventing a 50 Hz cartridge that never shipped.
105 /// - **`A` "All" → NTSC.** Names no single region, so it takes the default.
106 ///
107 /// Any unrecognized byte also returns [`Region::Ntsc`], so an unknown or
108 /// homebrew code is behavior-preserving rather than silently retuning audio.
109 #[must_use]
110 pub const fn from_destination_code(code: u8) -> Self {
111 match code {
112 // Europe and the 50 Hz territories.
113 b'D' | b'F' | b'H' | b'I' | b'L' | b'P' | b'S' | b'U' | b'W' | b'X' | b'Y' | b'Z' => {
114 Self::Pal
115 }
116 // North America, Japan, Korea, Canada, Gateway-NTSC, Brazil (PAL-M is
117 // 60 Hz), China (no retail N64), and "All" — plus every unknown byte.
118 _ => Self::Ntsc,
119 }
120 }
121}
122
123/// The narrow bus the AI sees (`RustyNES`'s `ApuBus` analog): fetch a DMA sample
124/// word from RDRAM and raise the AI interrupt when a buffer starts.
125pub trait AudioBus {
126 /// Fetch a big-endian 32-bit sample word (two 16-bit L/R samples) from the
127 /// AI DMA buffer in RDRAM at `addr`.
128 fn ai_dma_read_u32(&self, addr: u32) -> u32;
129 /// Raise the AI interrupt on the MI (a queued buffer became active). Default
130 /// no-op so a non-interrupt bus can still drive the DAC.
131 fn raise_ai_interrupt(&mut self) {}
132}
133
134/// One stereo output frame (interleaved signed 16-bit L, R).
135#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
136pub struct StereoSample {
137 /// Left channel.
138 pub left: i16,
139 /// Right channel.
140 pub right: i16,
141}
142
143/// The result an AI register write hands back to the Bus, which owns the MI
144/// interrupt lines the AI itself cannot name.
145#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
146pub enum AiIrq {
147 /// The write had no interrupt effect.
148 #[default]
149 None,
150 /// Raise `MI_INTR.ai` — the first buffer of an idle queue was enqueued and
151 /// starts immediately.
152 Raise,
153 /// Lower `MI_INTR.ai` — a write to `AI_STATUS` acknowledges the interrupt.
154 Lower,
155}
156
157/// Proof that a step needs the bus, produced only by [`Audio::tick_without_bus`]
158/// and consumed only by [`Audio::tick_with_bus`].
159///
160/// The two halves exist for a caller that cannot lend this struct out without
161/// first moving it, and so needs to know whether the step will touch RDRAM
162/// *before* paying for the move. At a typical ~32 kHz that is about one step in
163/// 1,950. (In this workspace that caller is the Bus, which owns every chip.)
164///
165/// The fields are private and the type has no constructor, so it cannot be forged.
166/// It carries the DAC period as well as the proof, which keeps the 64-bit divide
167/// that produced it from being repeated in the second half.
168///
169/// **`Copy` and `Clone` are deliberately not derived**, and it is taken by value:
170/// either would let a caller keep a token past the step it authorized and present
171/// it again once `next_sample_tick` had moved on. `Debug` is derived because it
172/// cannot duplicate the value.
173///
174/// **Dropping a token loses the samples that were due**, unlike the RDP's
175/// equivalent: `tick_without_bus` has already stamped `last_tick`, and the `while`
176/// in the second half is what advances `next_sample_tick`, so a dropped token
177/// leaves those samples unemitted and the schedule behind `now` — the next call
178/// then emits them late in a burst.
179///
180/// What makes ignoring one loud is the `#[must_use]` on
181/// [`Audio::tick_without_bus`] **itself**, not the one on this type: an attribute
182/// on `T` does not propagate through `Option<T>`, and `Option` — unlike `Result`
183/// — is not `#[must_use]` either.
184///
185/// The attribute here is **dormant, and kept only for the day the return type is
186/// not wrapped** — it does not catch a caller who binds the token and drops it.
187/// That was proposed in review and probed rather than assumed: with
188/// `if let Some(_proof) = ai.tick_without_bus(1) {}`, `cargo clippy --all-targets`
189/// reports **zero** warnings both with and without it. A type-level `#[must_use]`
190/// fires on an unused *expression*, not on a binding; a bound-then-dropped value
191/// is `unused_variables`, which the leading underscore silences either way.
192#[derive(Debug)]
193#[must_use]
194pub struct NeedsBus {
195 /// Master ticks between output samples, already divided.
196 period: u64,
197 /// The tick the step is advancing to, as passed to `tick_without_bus`.
198 now: u64,
199}
200
201/// Audio Interface state: the two-deep DMA FIFO, the DAC rate divider, and the
202/// derived-timing sample emission.
203#[derive(Debug, Clone, Serialize, Deserialize)]
204pub struct Audio {
205 // --- The two-deep DMA FIFO (front = index 0). ---
206 /// RDRAM base of each queued transfer (24-bit, 8-byte aligned). Slot 0 is
207 /// the transfer currently draining.
208 dma_addr: [u32; 2],
209 /// Remaining bytes of each queued transfer (18-bit, 8-byte aligned).
210 dma_len: [u32; 2],
211 /// Number of queued transfers, 0..=2. `BUSY` = `> 0`, `FULL` = `> 1`.
212 dma_count: u8,
213 /// Deferred carry into `dma_addr[0]` bits 13..=23 — the delayed-carry bug.
214 /// Set when the low 13 bits wrap past an `0x2000` page, applied one sample
215 /// later (which is the *next transfer* when it wraps on the final sample).
216 addr_carry: u32,
217 /// `AI_CONTROL` bit 0.
218 dma_enable: bool,
219
220 // --- The DAC rate. ---
221 /// `AI_DACRATE` (14-bit sample-period divider).
222 dac_rate: u16,
223 /// `AI_BITRATE` (4-bit half-bit-clock divider).
224 bit_rate: u8,
225 /// The region video clock (Hz) the DAC rate divides.
226 video_clock: u32,
227 /// Derived output sample rate in Hz, `video_clock / (dac_rate + 1)`, or
228 /// [`Audio::DEFAULT_DAC_HZ`] before `AI_DACRATE` is programmed.
229 ///
230 /// Never 0 on a constructed machine: a zero rate stops the DAC, and a
231 /// stopped DAC cannot retire a queued transfer (ledger R-16).
232 sample_rate: u32,
233
234 // --- Derived-timing emission (ADR 0006: everything off `master_ticks`). ---
235 /// Master tick at which the next output sample is due; 0 = unanchored.
236 next_sample_tick: u64,
237 /// The most recent `master_ticks` the DAC was advanced to (drives the
238 /// `AI_STATUS` `COUNT`/`WC` readback without threading the clock into reads).
239 last_tick: u64,
240 /// Last emitted sample, held and decayed on underrun so the DAC does not
241 /// hard-stop (matches ares' decay-to-silence behavior).
242 dac_hold: StereoSample,
243 /// Count of buffer starvations (a transfer drained with none queued behind
244 /// it) — observable so a resampler cannot silently paper over underrun.
245 underruns: u64,
246
247 /// The emitted stereo stream on the emulated timeline, drained per frame by
248 /// the frontend (which resamples to the host rate — ADR 0004).
249 sink: Vec<StereoSample>,
250}
251
252impl Default for Audio {
253 fn default() -> Self {
254 Self::new()
255 }
256}
257
258impl Audio {
259 /// The 8 KiB (`0x2000`) page whose crossing arms the delayed-carry bug.
260 const PAGE: u32 = 0x2000;
261
262 /// DAC rate used while `AI_DACRATE` is unprogrammed.
263 ///
264 /// **A modeling default, not a measured hardware value — see ledger R-16.**
265 /// `AI_DACRATE`'s reset value is not documented anywhere this project
266 /// mirrors, so no rate can be *derived* for the pre-programmed window. What
267 /// matters, and what IS established, is structural: the DAC has no stopped
268 /// state, so the transfer queue must be able to retire before software
269 /// programs the rate. This value is taken from ares (ISC, vendorable), whose
270 /// `AI::power()` sets `dac.frequency = 44100`.
271 ///
272 /// Nothing observable should depend on the exact number: every title
273 /// programs `AI_DACRATE` before it plays anything, so this rate only governs
274 /// how fast an unprogrammed DAC drains silence. If a future change makes an
275 /// output depend on it, that dependency is the bug, not this constant.
276 const DEFAULT_DAC_HZ: u32 = 44_100;
277
278 /// Construct at power-on (NTSC, idle).
279 #[must_use]
280 pub const fn new() -> Self {
281 let mut ai = Self {
282 dma_addr: [0; 2],
283 dma_len: [0; 2],
284 dma_count: 0,
285 addr_carry: 0,
286 dma_enable: false,
287 dac_rate: 0,
288 bit_rate: 0,
289 video_clock: VIDEO_CLOCK_NTSC,
290 // Placeholder only — `recompute_rate` below derives the real value.
291 sample_rate: 0,
292 next_sample_tick: 0,
293 last_tick: 0,
294 dac_hold: StereoSample { left: 0, right: 0 },
295 underruns: 0,
296 sink: Vec::new(),
297 };
298 // Derive the power-on rate through the SAME function every later rate
299 // change uses, rather than repeating its `dac_rate == 0` decision here.
300 // The duplicate literal this replaces was only kept correct by a comment
301 // saying "must match `recompute_rate`" — precisely the comment-enforced
302 // invariant this project distrusts, and the drift would be silent: a
303 // constructor left at 0 puts the machine back in the stopped-DAC state
304 // ledger R-16's livelock needs, and no test of a *programmed* DAC would
305 // notice. Raised in review on #205.
306 ai.recompute_rate();
307 ai
308 }
309
310 /// Select the console region (sets the video clock and re-derives the rate).
311 /// Wired from the cart header at ROM load; defaults to NTSC.
312 pub const fn set_region(&mut self, region: Region) {
313 self.video_clock = region.video_clock();
314 self.recompute_rate();
315 }
316
317 /// Observed underrun count (buffer starvations) — for the harness.
318 #[must_use]
319 pub const fn underruns(&self) -> u64 {
320 self.underruns
321 }
322
323 /// The derived output sample rate in Hz (0 until `AI_DACRATE` is set).
324 #[must_use]
325 pub const fn sample_rate(&self) -> u32 {
326 self.sample_rate
327 }
328
329 /// Drain the emitted stereo stream produced since the last drain.
330 pub fn drain(&mut self) -> Vec<StereoSample> {
331 core::mem::take(&mut self.sink)
332 }
333
334 /// Read an AI register (`index` = `(addr >> 2) & 7`).
335 ///
336 /// Every register except `AI_STATUS` (index 3) is write-only and reads back
337 /// a mirror of `AI_LENGTH` (the front transfer's remaining bytes), per the
338 /// wiki and ares. `AI_STATUS` reports the FULL/BUSY/ENABLED flags plus the
339 /// best-effort `COUNT`/`WC` readback (ledgered — no oracle pins its phase).
340 #[must_use]
341 pub fn read_reg(&self, index: u32) -> u32 {
342 if index == 3 {
343 self.status()
344 } else {
345 // AI_LENGTH mirror: remaining bytes of the active transfer.
346 self.dma_len[0] & 0x0003_FFFF
347 }
348 }
349
350 /// Assemble `AI_STATUS`.
351 ///
352 /// FULL (bit 31 and bit 0), BUSY (bit 30), and ENABLED (bit 25) are the
353 /// flags software polls and are exact. Bits 20 and 24 read as 1 on hardware
354 /// (ares). `COUNT` (bits 14..=1) and `WC` (bit 19) are a best-effort model
355 /// of the DAC's internal down-counter — striven, not gated (ledgered),
356 /// because no public capture pins their exact phase.
357 fn status(&self) -> u32 {
358 let mut s = 0u32;
359 if self.dma_count > 1 {
360 s |= 1 << 31; // FULL
361 s |= 1 << 0; // FULL (mirror copy)
362 }
363 if self.dma_count > 0 {
364 s |= 1 << 30; // BUSY
365 }
366 s |= 1 << 24; // always 1 (ares)
367 if self.dma_enable {
368 s |= 1 << 25; // ENABLED
369 }
370 s |= 1 << 20; // always 1 (ares)
371 // COUNT ticks down at the VI clock, from DACRATE/2 to 0, reloading; WC
372 // (LRCK) toggles at DACRATE/2, out of phase by half a period. Both are
373 // gated by BITRATE != 0. Derived from `last_tick`; see ledger.
374 // Gated on `AI_BITRATE` ALONE: the wiki says the counter "always ticks
375 // unless `AI_BITRATE` is 0" and that a `BITRATE` of 0 "stops the clock" —
376 // neither is said to depend on `AI_DACRATE`. `COUNT`/`WC` additionally need
377 // a DAC rate to have a range at all, which their own guard below supplies.
378 if self.bit_rate != 0 {
379 // All three readbacks are phases of the same video-clock tick count.
380 let vi_ticks = self.last_tick.saturating_mul(u64::from(self.video_clock)) / MASTER_HZ;
381 let half = u64::from(self.dac_rate) / 2;
382 if half != 0 {
383 let phase = vi_ticks % (half * 2);
384 let count = if phase < half {
385 half - phase
386 } else {
387 half * 2 - phase
388 };
389 s |= ((count as u32) & 0x3FFF) << 1;
390 if phase >= half {
391 s |= 1 << 19; // WC high on the second half-period
392 }
393 }
394 // BC (bit 16) — the BCLK line to the BU9480 DAC. `AI_BITRATE` is
395 // "Half of bit clock period" and "the bit clock rate is the Video
396 // clock, divided by two, divided by one more than this number"
397 // (wiki §AI_BITRATE), so one half-period is `BITRATE + 1` video
398 // clocks and the line toggles once per half-period. A `BITRATE` of 0
399 // stops the clock, which the enclosing gate excludes — and note that
400 // gate is BITRATE-only, so BCLK keeps running with no DAC rate set.
401 //
402 // The wiki itself hedges this ("believed", "probably") because the
403 // CPU "cannot reliably sample it rapidly enough even when BITRATE is
404 // set to 15" — so it is un-observable by software in practice and
405 // stays ungated (ledger R-16), like `COUNT`/`WC`.
406 let half_periods = vi_ticks / (u64::from(self.bit_rate) + 1);
407 if half_periods & 1 != 0 {
408 s |= 1 << 16; // BC
409 }
410 }
411 s
412 }
413
414 /// Write an AI register (`index` = `(addr >> 2) & 7`), returning the MI
415 /// interrupt effect for the Bus to apply.
416 ///
417 /// The **interrupt fires when a transfer starts, not when it ends**: writing
418 /// `AI_LENGTH` into an idle queue (`dma_count == 0`) starts that buffer
419 /// immediately and raises the interrupt now; a second buffer queued behind a
420 /// playing one raises nothing until it is promoted in [`Audio::tick`]. This
421 /// is what lets software refill during playback (wiki §DMA).
422 pub fn write_reg(&mut self, index: u32, val: u32) -> AiIrq {
423 match index {
424 0 => {
425 // AI_DRAM_ADDR: stage the next free slot's base (24-bit, & ~7).
426 if self.dma_count < 2 {
427 self.dma_addr[self.dma_count as usize] = val & 0x00FF_FFF8;
428 }
429 AiIrq::None
430 }
431 1 => {
432 // AI_LENGTH: stage the next free slot's length (18-bit, & ~7)
433 // and enqueue it. Enqueueing into an idle queue starts playback.
434 let length = val & 0x0003_FFF8;
435 if self.dma_count < 2 {
436 let starting = self.dma_count == 0;
437 self.dma_len[self.dma_count as usize] = length;
438 self.dma_count += 1;
439 if starting {
440 // The buffer starts now; anchor the first sample one
441 // period out from the DAC's current position and fire
442 // the IRQ (it fires on *start*, not drain).
443 self.addr_carry = 0;
444 self.next_sample_tick = self.last_tick.saturating_add(self.period_ticks());
445 return AiIrq::Raise;
446 }
447 }
448 AiIrq::None
449 }
450 2 => {
451 // AI_CONTROL: DMA enable (bit 0).
452 self.dma_enable = val & 1 != 0;
453 AiIrq::None
454 }
455 3 => AiIrq::Lower, // AI_STATUS write acknowledges the interrupt.
456 4 => {
457 // AI_DACRATE (14-bit): re-derive the sample rate.
458 self.dac_rate = (val & 0x3FFF) as u16;
459 self.recompute_rate();
460 AiIrq::None
461 }
462 5 => {
463 // AI_BITRATE (4-bit).
464 self.bit_rate = (val & 0xF) as u8;
465 AiIrq::None
466 }
467 _ => AiIrq::None, // indices 6/7 are unmapped.
468 }
469 }
470
471 /// Re-derive [`Audio::sample_rate`] from the current video clock and
472 /// `AI_DACRATE`.
473 const fn recompute_rate(&mut self) {
474 // An unprogrammed `AI_DACRATE` falls back to [`Self::DEFAULT_DAC_HZ`]
475 // rather than to zero. Zero used to mean "emit nothing", which is wrong
476 // in a way that reaches far past audio: with the DAC stopped, `tick()`
477 // returns before `emit_sample`, and `emit_sample` is the ONLY place a
478 // drained transfer is retired — so `AI_STATUS.FULL` could latch and never
479 // clear, and a game polling it for a free DMA slot spun forever. World
480 // Driver Championship did exactly that (ledger R-16).
481 //
482 // What is ESTABLISHED and what is INFERRED, kept apart deliberately —
483 // the reset semantics are admitted undocumented below, and an inference
484 // dressed as a hardware fact is what this ledger exists to prevent.
485 //
486 // ESTABLISHED (readable reference, ISC): ares runs its DAC from power-on.
487 // `AI::power()` sets `dac.frequency = 44100` and `AI::main()` calls
488 // `sample()` unconditionally, so its equivalent retirement block runs
489 // before any `AI_DACRATE` write.
490 // INFERRED (from that, plus a divider having no "off" encoding): the
491 // hardware DAC counter likewise has no stopped state.
492 // NOT ESTABLISHED: what `AI_DACRATE` holds at reset. No source this
493 // project mirrors says, which is exactly why the rate is labeled a
494 // modeling default and not a measured value.
495 //
496 // The naive alternative — letting `dac_rate == 0` compute
497 // `video_clock / 1` ≈ 48 MHz — is what the old zero-gate was avoiding,
498 // and it would flood the sink. A default rate avoids both failures.
499 //
500 // TODO(T-AUDIO-01): separate an explicit `AI_DACRATE = 0` write from the
501 // unprogrammed reset state. Needs a `dac_rate_programmed` field, hence a
502 // save-state layout bump (ADR 0005) — deferred, see below and ledger R-16.
503 //
504 // KNOWN SIMPLIFICATION, ledgered (R-16): this conflates "never
505 // programmed" with "software explicitly wrote 0". ares keeps them apart —
506 // `power()` sets 44100, while its `AI_DACRATE` write honors a literal
507 // zero (`dac.frequency = max(1, videoFrequency / (dacRate + 1))`,
508 // `ai/io.cpp`) — so full fidelity needs a `dac_rate_programmed` flag to
509 // tell the two apart. That adds a field to a serialized struct and so
510 // changes the save-state layout (ADR 0005), which is not a change to make
511 // in passing. Unobservable in practice: a DACRATE of 0 asks for a ~48 MHz
512 // DAC and no title does it. Recorded rather than silently accepted.
513 self.sample_rate = if self.video_clock == 0 {
514 0
515 } else if self.dac_rate == 0 {
516 Self::DEFAULT_DAC_HZ
517 } else {
518 self.video_clock / (self.dac_rate as u32 + 1)
519 };
520 }
521
522 /// Master ticks between output samples, `MASTER_HZ / sample_rate`.
523 ///
524 /// Zero only when the video clock is unset, which cannot happen on a
525 /// constructed machine. It is **no longer** zero for an unprogrammed
526 /// `AI_DACRATE` — that falls back to [`Self::DEFAULT_DAC_HZ`], because a
527 /// stopped DAC cannot retire a transfer and latched `AI_STATUS.FULL`
528 /// (ledger R-16).
529 fn period_ticks(&self) -> u64 {
530 if self.sample_rate == 0 {
531 0
532 } else {
533 (MASTER_HZ / u64::from(self.sample_rate)).max(1)
534 }
535 }
536
537 /// Advance the AI to `now` master ticks, emitting every output sample whose
538 /// scheduled tick has arrived.
539 ///
540 /// Derived timing (ADR 0006): the number of samples emitted is a function of
541 /// `now` and the DAC period, never of an independently incremented counter.
542 /// Hot path — allocation into the sink is the only cost while playing.
543 pub fn tick<B: AudioBus>(&mut self, now: u64, bus: &mut B) {
544 if let Some(proof) = self.tick_without_bus(now) {
545 self.tick_with_bus(proof, bus);
546 }
547 }
548
549 /// The part of a step that needs **no bus access**, returning `None` when it
550 /// finished the step on its own and `Some(NeedsBus)` when samples must be read
551 /// out of RDRAM.
552 ///
553 /// **This advances state**, despite the `Option` return: it stamps `last_tick`
554 /// on every call and anchors `next_sample_tick` on the first one. It is named
555 /// `tick_*` rather than `is_*` for that reason.
556 ///
557 /// Split out so a caller can decide whether to pay for bus access *before*
558 /// arranging it. A caller that owns this struct cannot lend it out without
559 /// first moving it, and that move is pure overhead on the ~99.95% of steps
560 /// that emit nothing (`docs/audio.md` §Derived timing). [`Audio::tick`] calls
561 /// this too, so there is one implementation of the early-outs and no way for
562 /// the two to disagree.
563 #[must_use = "a `Some` means samples are due and the step needs `tick_with_bus`"]
564 pub fn tick_without_bus(&mut self, now: u64) -> Option<NeedsBus> {
565 self.last_tick = now;
566 // A schedule exists and its next sample is still ahead: nothing to do, and
567 // in particular no need for `period_ticks`, whose 64-bit divide dominated
568 // this function (`docs/audio.md` §Derived timing for the cost and the
569 // reason it is an ordering change rather than a cached field).
570 //
571 // The old code returned from the same states without touching a field, so
572 // this only moves the decision earlier.
573 if self.next_sample_tick != 0 && now < self.next_sample_tick {
574 // The R-16 guard still has to be checkable on the hot path, or it
575 // would only ever be evaluated on the ~0.05% of calls that emit.
576 // `debug_assert` compiles out of release, so this costs nothing.
577 debug_assert!(
578 self.sample_rate != 0,
579 "a constructed AI must never have a stopped DAC"
580 );
581 return None;
582 }
583 let period = self.period_ticks();
584 if period == 0 {
585 // Unreachable on a constructed machine: `video_clock` is always
586 // set and an unprogrammed `AI_DACRATE` now yields
587 // `DEFAULT_DAC_HZ`, not zero (ledger R-16). Kept as a guard against
588 // a divide-by-zero rather than as a modeled DAC state.
589 //
590 // The assert makes that claim CHECKABLE instead of merely stated: a
591 // future change that reintroduces a zero rate is exactly the R-16
592 // defect, and a silent `return` is how it hid the first time.
593 debug_assert!(period > 0, "a constructed AI must never have a stopped DAC");
594 return None;
595 }
596 if self.next_sample_tick == 0 {
597 // Anchor the first sample one period out so a large `now` does not
598 // dump a backlog of silence at power-on / rate change.
599 self.next_sample_tick = now.saturating_add(period);
600 return None;
601 }
602 Some(NeedsBus { period, now })
603 }
604
605 /// The rest of the step, which reads sample words out of RDRAM.
606 ///
607 /// Reachable only with a [`NeedsBus`] from [`Audio::tick_without_bus`], so the
608 /// two halves cannot run out of order: the preconditions — a non-zero period
609 /// and at least one sample already due — are carried by the type rather than by
610 /// a comment or an assertion. The token also carries the period, so the 64-bit
611 /// divide that produced it is not repeated here.
612 // `needless_pass_by_value` is correct that a `&NeedsBus` would compile — both
613 // fields are `Copy`, so nothing here needs ownership. Taking it by value is the
614 // entire safety property: a token passed by reference stays usable, and the
615 // caller could present the same one again after `next_sample_tick` had moved
616 // past it. Ownership is what makes a token good for exactly one step, so this
617 // signature is load-bearing rather than careless.
618 #[allow(clippy::needless_pass_by_value)]
619 pub fn tick_with_bus<B: AudioBus>(&mut self, proof: NeedsBus, bus: &mut B) {
620 let NeedsBus { period, now } = proof;
621 while self.next_sample_tick <= now {
622 self.emit_sample(bus);
623 self.next_sample_tick = self.next_sample_tick.saturating_add(period);
624 }
625 }
626
627 /// Emit exactly one output sample: play from RDRAM if a transfer is active,
628 /// otherwise decay the held DAC value toward silence.
629 fn emit_sample<B: AudioBus>(&mut self, bus: &mut B) {
630 let active = self.dma_count > 0 && self.dma_len[0] > 0 && self.dma_enable;
631 if active {
632 // Apply the deferred carry into the high address bits first — this
633 // is the one-cycle-late carry that produces the +0x2000 bug when the
634 // previous sample crossed a page on the final word of a transfer.
635 let high =
636 (self.dma_addr[0] & !(Self::PAGE - 1)).wrapping_add(self.addr_carry * Self::PAGE);
637 self.dma_addr[0] = high | (self.dma_addr[0] & (Self::PAGE - 1));
638
639 let word = bus.ai_dma_read_u32(self.dma_addr[0] & 0x00FF_FFFF);
640 let sample = StereoSample {
641 left: (word >> 16) as i16,
642 right: word as i16,
643 };
644 self.dac_hold = sample;
645 self.sink.push(sample);
646
647 // Advance the low 13 bits by one word; the carry out is remembered,
648 // not applied, until the next sample.
649 let low = (self.dma_addr[0] & (Self::PAGE - 1)).wrapping_add(4);
650 self.addr_carry = u32::from(low >= Self::PAGE);
651 self.dma_addr[0] = (self.dma_addr[0] & !(Self::PAGE - 1)) | (low & (Self::PAGE - 1));
652 self.dma_len[0] -= 4;
653 } else {
654 // Underrun / idle: hold-and-decay toward zero (deterministic, no
655 // float). A DAC that keeps its rate but has nothing to play settles
656 // to silence rather than clicking off.
657 self.dac_hold.left = (i32::from(self.dac_hold.left) * 63 / 64) as i16;
658 self.dac_hold.right = (i32::from(self.dac_hold.right) * 63 / 64) as i16;
659 self.sink.push(self.dac_hold);
660 }
661
662 // A drained front transfer promotes the queued one (if any) and raises
663 // the interrupt as that buffer *starts*.
664 if self.dma_count > 0 && self.dma_len[0] == 0 {
665 self.dma_count -= 1;
666 if self.dma_count > 0 {
667 self.dma_addr[0] = self.dma_addr[1];
668 self.dma_len[0] = self.dma_len[1];
669 bus.raise_ai_interrupt();
670 } else if self.dma_enable {
671 // Ran dry with nothing queued — an observable starvation.
672 self.underruns += 1;
673 }
674 }
675 }
676}
677
678/// Returns the crate version string.
679#[must_use]
680pub const fn version() -> &'static str {
681 env!("CARGO_PKG_VERSION")
682}
683
684#[cfg(test)]
685mod tests {
686 use super::*;
687
688 /// A bus backed by a flat RDRAM image, recording interrupt raises.
689 struct TestBus {
690 ram: Vec<u8>,
691 irqs: u32,
692 }
693 impl TestBus {
694 fn new(size: usize) -> Self {
695 Self {
696 ram: alloc::vec![0u8; size],
697 irqs: 0,
698 }
699 }
700 fn write_word(&mut self, addr: u32, val: u32) {
701 let a = addr as usize;
702 self.ram[a..a + 4].copy_from_slice(&val.to_be_bytes());
703 }
704 }
705 impl AudioBus for TestBus {
706 fn ai_dma_read_u32(&self, addr: u32) -> u32 {
707 let a = addr as usize;
708 u32::from_be_bytes([
709 self.ram[a],
710 self.ram[a + 1],
711 self.ram[a + 2],
712 self.ram[a + 3],
713 ])
714 }
715 fn raise_ai_interrupt(&mut self) {
716 self.irqs += 1;
717 }
718 }
719
720 /// Program a standard NTSC ~44 kHz DAC and enable DMA.
721 fn programmed() -> Audio {
722 let mut ai = Audio::new();
723 ai.write_reg(4, 1103); // AI_DACRATE → ~44_136 Hz
724 ai.write_reg(2, 1); // AI_CONTROL: DMA enable
725 ai
726 }
727
728 /// **The FIRST `tick` only anchors the sample clock; it never emits.**
729 ///
730 /// This test used to be called `idle_tick_emits_nothing_before_dacrate` and
731 /// asserted "no rate programmed → no samples". It was **vacuous, and vacuous
732 /// before the DAC default landed**: a single `tick` returns at the
733 /// `next_sample_tick == 0` anchor branch regardless of the rate, so it passed
734 /// identically before and after a change to the very behavior it claimed to
735 /// pin — the "success and failure paths converge" trap. Renamed and re-aimed
736 /// at the rule it actually exercises, which is worth pinning on its own: the
737 /// anchor exists so a large first `now` cannot dump a backlog of silence.
738 #[test]
739 fn the_first_tick_only_anchors_the_sample_clock() {
740 let mut ai = Audio::new();
741 let mut bus = TestBus::new(0x1000);
742 ai.tick(1_000_000, &mut bus);
743 assert!(
744 ai.drain().is_empty(),
745 "the anchoring tick must not emit a backlog"
746 );
747 // And the second tick DOES emit — without this half the test would pass
748 // just as well against a DAC that never emits anything at all.
749 ai.tick(1_000_000 + MASTER_HZ / 60, &mut bus);
750 assert!(
751 !ai.drain().is_empty(),
752 "the tick after the anchor must emit at the default rate"
753 );
754 }
755
756 #[test]
757 fn dacrate_derives_rate_per_region() {
758 let mut ai = Audio::new();
759 ai.write_reg(4, 1103);
760 assert_eq!(ai.sample_rate(), VIDEO_CLOCK_NTSC / 1104);
761 ai.set_region(Region::Pal);
762 assert_eq!(ai.sample_rate(), VIDEO_CLOCK_PAL / 1104);
763 }
764
765 /// **The destination code selects the region (T-71-005).** The characters are
766 /// the N64brew ROM-header table; the three judgment calls are pinned here so
767 /// a later reader sees them as decisions rather than accidents.
768 #[test]
769 fn destination_code_selects_the_region() {
770 use Region::{Ntsc, Pal};
771 // The 50 Hz territories.
772 for c in [
773 b'D', b'F', b'H', b'I', b'L', b'P', b'S', b'U', b'W', b'X', b'Y', b'Z',
774 ] {
775 assert_eq!(
776 Region::from_destination_code(c),
777 Pal,
778 "{} should be PAL",
779 c as char
780 );
781 }
782 // The 60 Hz territories.
783 for c in [b'E', b'J', b'K', b'N', b'G'] {
784 assert_eq!(
785 Region::from_destination_code(c),
786 Ntsc,
787 "{} should be NTSC",
788 c as char
789 );
790 }
791 // The three explicit decisions (see `from_destination_code`): Brazil is
792 // PAL-M, a 60 Hz standard; China has no known retail N64; "All" names no
793 // single region.
794 assert_eq!(
795 Region::from_destination_code(b'B'),
796 Ntsc,
797 "Brazil is PAL-M/60Hz"
798 );
799 assert_eq!(
800 Region::from_destination_code(b'C'),
801 Ntsc,
802 "China: no retail N64"
803 );
804 assert_eq!(
805 Region::from_destination_code(b'A'),
806 Ntsc,
807 "\"All\" takes the default"
808 );
809 // An unknown/homebrew byte must be behavior-preserving, not retune audio.
810 assert_eq!(
811 Region::from_destination_code(0),
812 Ntsc,
813 "unknown code keeps NTSC"
814 );
815 assert_eq!(
816 Region::from_destination_code(b'?'),
817 Ntsc,
818 "unknown code keeps NTSC"
819 );
820 }
821
822 /// **The selected region actually changes the sample rate.** Guards against a
823 /// mapping that is correct but never reaches the DAC divisor.
824 #[test]
825 fn a_pal_destination_code_retunes_the_dac() {
826 let mut ai = Audio::new();
827 ai.write_reg(4, 1103); // AI_DACRATE: rate = clock / (dacrate + 1)
828 let ntsc = ai.sample_rate();
829 ai.set_region(Region::from_destination_code(b'P')); // Europe
830 assert_ne!(
831 ai.sample_rate(),
832 ntsc,
833 "a PAL cartridge must retune the DAC"
834 );
835 assert_eq!(ai.sample_rate(), VIDEO_CLOCK_PAL / 1104);
836 }
837
838 #[test]
839 fn set_region_before_dacrate_does_not_fabricate_the_video_clock_rate() {
840 // What this has always been protecting: selecting a region before
841 // AI_DACRATE is programmed must NOT compute `video_clock / 1` (~48 MHz)
842 // and flood the sink.
843 //
844 // It previously asserted the rate was exactly **0** and that the DAC
845 // "emits nothing". That over-specified the protection into a bug: a
846 // zero rate makes `tick` return before `emit_sample`, and `emit_sample`
847 // is the only place a drained transfer is retired — so `AI_STATUS.FULL`
848 // could latch forever (see
849 // `full_clears_even_when_dacrate_was_never_programmed`, and ledger R-16).
850 // The unprogrammed DAC now runs at `DEFAULT_DAC_HZ`. The assertion is
851 // therefore an ORDER-OF-MAGNITUDE bound, which is what the comment
852 // always described, rather than an exact value the hardware does not
853 // document.
854 let mut ai = Audio::new();
855 ai.set_region(Region::Pal);
856 // Two assertions, each catching something the other cannot. The equality
857 // pins the WIRING — that `recompute_rate` reads the named constant rather
858 // than an inlined literal that could drift from it. The bound pins the
859 // PROPERTY, and it is the one that catches the failure class this test was
860 // written for: any future rate derived from the video clock rather than an
861 // audio rate.
862 assert_eq!(
863 ai.sample_rate(),
864 Audio::DEFAULT_DAC_HZ,
865 "an unprogrammed DAC must run at the documented default"
866 );
867 assert!(
868 ai.sample_rate() > 0 && ai.sample_rate() < 100_000,
869 "an unprogrammed DAC runs at an audio rate, not the video clock: {}",
870 ai.sample_rate()
871 );
872 let mut bus = TestBus::new(0x1000);
873 // Prime the sample clock first. Without this the single `tick` below
874 // returns at the `next_sample_tick == 0` anchor branch, `emitted` is
875 // always 0, and the upper bound passes even with the emission path
876 // completely broken — the vacuity `the_first_tick_only_anchors_the_sample_clock`
877 // documents. Raised in review on #205.
878 ai.tick(0, &mut bus);
879 ai.tick(MASTER_HZ / 60, &mut bus); // one frame
880 let emitted = ai.drain().len();
881 // A TWO-SIDED bound, because only the pair is evidence: the upper bound
882 // rejects the ~800k samples/frame a video-clock rate would produce, and
883 // the lower bound rejects a DAC that emits nothing — which is the state
884 // that caused the R-16 livelock and which an upper bound alone accepts.
885 // 44100/60 = 735.
886 assert!(
887 (500..5_000).contains(&emitted),
888 "one frame of an unprogrammed DAC must emit ~735 samples, not a flood \
889 and not silence: {emitted}"
890 );
891 }
892
893 /// **`AI_STATUS.FULL` must be able to clear even if `AI_DACRATE` was never
894 /// programmed** — the World Driver Championship livelock (ledger R-16).
895 ///
896 /// A game may queue two buffers and poll `FULL` for a free slot before it
897 /// programs the DAC. Retirement lives in `emit_sample`, which only runs when
898 /// the DAC has a period, so a stopped DAC latched `FULL` permanently and the
899 /// poll never exited. Mutation guard: restore the `dac_rate == 0 → 0` rate
900 /// and this test hangs on `FULL` forever (it fails on the assertion below).
901 #[test]
902 fn full_clears_even_when_dacrate_was_never_programmed() {
903 let mut ai = Audio::new();
904 let mut bus = TestBus::new(0x4000);
905 // Two queued transfers, no AI_DACRATE write, DMA enabled.
906 ai.write_reg(2, 1); // AI_CONTROL: DMA enable
907 ai.write_reg(0, 0x0000_1000);
908 ai.write_reg(1, 0x40);
909 ai.write_reg(0, 0x0000_2000);
910 ai.write_reg(1, 0x40);
911 assert_ne!(ai.status() & (1 << 31), 0, "two queued → FULL");
912
913 // Advance a second of emulated time. With a running DAC the two 64-byte
914 // transfers drain almost immediately; with a stopped one, never.
915 ai.tick(MASTER_HZ, &mut bus);
916 assert_eq!(
917 ai.status() & (1 << 31),
918 0,
919 "FULL must clear once a transfer retires, even with AI_DACRATE unset"
920 );
921 }
922
923 #[test]
924 fn write_only_registers_mirror_ai_length() {
925 let mut ai = programmed();
926 ai.write_reg(0, 0x0000_1000); // AI_DRAM_ADDR
927 ai.write_reg(1, 0x40); // AI_LENGTH = 64 bytes
928 // Indices 0,1,2,4,5 all read back the AI_LENGTH mirror (remaining bytes).
929 assert_eq!(ai.read_reg(0), 0x40);
930 assert_eq!(ai.read_reg(1), 0x40);
931 assert_eq!(ai.read_reg(2), 0x40);
932 assert_eq!(ai.read_reg(4), 0x40);
933 assert_eq!(ai.read_reg(5), 0x40);
934 }
935
936 #[test]
937 fn ai_length_masks_to_eight_byte_granularity() {
938 let mut ai = programmed();
939 ai.write_reg(1, 0x47); // low 3 bits dropped → 0x40
940 assert_eq!(ai.read_reg(1), 0x40);
941 }
942
943 #[test]
944 fn first_buffer_raises_irq_on_enqueue() {
945 let mut ai = programmed();
946 assert_eq!(ai.write_reg(0, 0x100), AiIrq::None);
947 assert_eq!(
948 ai.write_reg(1, 0x40),
949 AiIrq::Raise,
950 "the first buffer starts immediately and raises the AI interrupt"
951 );
952 }
953
954 #[test]
955 fn ai_status_write_acknowledges_the_interrupt() {
956 let mut ai = programmed();
957 assert_eq!(ai.write_reg(3, 0), AiIrq::Lower);
958 }
959
960 /// **`BC` toggles at the documented bit-clock half-period (R-16).**
961 ///
962 /// The wiki gives `AI_BITRATE` as "half of bit clock period" and the bit clock
963 /// as "the Video clock, divided by two, divided by one more than this number",
964 /// so one half-period is `BITRATE + 1` video clocks and BCLK toggles once per
965 /// half-period. This counts the transitions over a fixed span and compares
966 /// against that relation derived independently from the two documented
967 /// quantities — so a wrong divisor (`BITRATE` instead of `BITRATE + 1`, or the
968 /// DAC rate) changes the count and fails.
969 #[test]
970 fn bc_toggles_at_the_documented_bit_clock_half_period() {
971 const BITRATE: u32 = 15;
972 let span_master_ticks = 20_000u64;
973
974 let mut ai = programmed();
975 ai.write_reg(5, BITRATE); // AI_BITRATE
976 let mut bus = TestBus::new(0x1000);
977
978 let mut transitions = 0usize;
979 let mut prev = ai.status() & (1 << 16) != 0;
980 for now in 1..=span_master_ticks {
981 ai.tick(now, &mut bus);
982 let bc = ai.status() & (1 << 16) != 0;
983 if bc != prev {
984 transitions += 1;
985 prev = bc;
986 }
987 }
988
989 // Expected from the documented relation alone: video clocks elapsed over
990 // the span, divided by the half-period of `BITRATE + 1` video clocks.
991 let vi_ticks = span_master_ticks * u64::from(VIDEO_CLOCK_NTSC) / MASTER_HZ;
992 let expected = vi_ticks / u64::from(BITRATE + 1);
993 assert_eq!(
994 transitions as u64,
995 expected,
996 "BC must toggle once per {} video clocks",
997 BITRATE + 1
998 );
999 assert!(
1000 transitions > 100,
1001 "the span must actually exercise the clock"
1002 );
1003 }
1004
1005 /// **The bit clock does not depend on the DAC rate.** The wiki gates the
1006 /// counter on `AI_BITRATE` alone ("It always ticks unless `AI_BITRATE` is 0")
1007 /// and never on `AI_DACRATE`, so BCLK must keep toggling with no DAC rate
1008 /// programmed — even though `COUNT`/`WC`, which need a range, report nothing.
1009 #[test]
1010 fn the_bit_clock_runs_without_a_dac_rate() {
1011 let mut ai = Audio::new();
1012 ai.write_reg(5, 15); // AI_BITRATE only — no AI_DACRATE
1013 let mut bus = TestBus::new(0x1000);
1014 let mut seen_high = false;
1015 let mut seen_low = false;
1016 for now in 1..=5_000u64 {
1017 ai.tick(now, &mut bus);
1018 let st = ai.status();
1019 if st & (1 << 16) != 0 {
1020 seen_high = true;
1021 } else {
1022 seen_low = true;
1023 }
1024 // COUNT has no range without a DAC rate, so it must stay clear.
1025 assert_eq!(st & (0x3FFF << 1), 0, "COUNT needs a DAC rate");
1026 }
1027 assert!(
1028 seen_high && seen_low,
1029 "BCLK must toggle with no DAC rate programmed"
1030 );
1031 }
1032
1033 /// **A `BITRATE` of 0 stops the bit clock.** The wiki: "A written value of 0
1034 /// instead stops the clock", so `BC` must not toggle.
1035 #[test]
1036 fn a_zero_bitrate_stops_the_bit_clock() {
1037 let mut ai = programmed();
1038 ai.write_reg(5, 0); // AI_BITRATE = 0 → clock stopped
1039 let mut bus = TestBus::new(0x1000);
1040 for now in 1..=5_000u64 {
1041 ai.tick(now, &mut bus);
1042 assert_eq!(ai.status() & (1 << 16), 0, "BC must stay low at BITRATE 0");
1043 }
1044 }
1045
1046 /// **Every bus-free early-out returns `None` on its own condition, and the one
1047 /// case that needs RDRAM returns `Some`.**
1048 ///
1049 /// A predicate that *always* skipped would pass any test that only checks the
1050 /// skip fires — and a silently-always-skipping AI is a dead DAC. So the
1051 /// positive case is asserted here beside the negatives, and the token it
1052 /// returns is consumed, which is the only way to reach the second half.
1053 ///
1054 /// `last_tick` is checked on the skip paths too: the bus-free half must still
1055 /// *advance* on the steps it declines to finish, or the DAC stops tracking the
1056 /// clock while appearing to work.
1057 #[test]
1058 fn every_bus_free_early_out_fires_on_its_own_condition() {
1059 let mut bus = TestBus::new(0x1_0000);
1060
1061 // 1. A schedule exists and its next sample is still ahead.
1062 let mut ai = programmed();
1063 ai.write_reg(0, 0x100);
1064 ai.write_reg(1, 8);
1065 let due = ai.next_sample_tick;
1066 assert!(
1067 ai.tick_without_bus(due - 1).is_none(),
1068 "no bus needed before the boundary"
1069 );
1070 assert_eq!(ai.last_tick, due - 1, "the skipped step still advances");
1071
1072 // 2. No schedule yet: the first sample is anchored without touching RDRAM.
1073 let mut fresh = programmed();
1074 assert!(
1075 fresh.tick_without_bus(7).is_none(),
1076 "anchoring needs no bus"
1077 );
1078 assert_ne!(fresh.next_sample_tick, 0, "and it did anchor");
1079
1080 // 3. A sample is due — this one does need the bus, and the token proves it.
1081 bus.write_word(0x100, 0x0001_0002);
1082 bus.write_word(0x104, 0x0003_0004);
1083 let proof = ai
1084 .tick_without_bus(due)
1085 .expect("a due sample must ask for the bus");
1086 ai.tick_with_bus(proof, &mut bus);
1087 assert_eq!(
1088 ai.drain(),
1089 [StereoSample { left: 1, right: 2 }],
1090 "the second half emits the sample the token was issued for"
1091 );
1092 }
1093
1094 #[test]
1095 fn status_reports_busy_and_full() {
1096 let mut ai = programmed();
1097 ai.write_reg(0, 0x100);
1098 ai.write_reg(1, 0x40); // one queued → BUSY
1099 assert_ne!(ai.status() & (1 << 30), 0, "BUSY");
1100 assert_eq!(ai.status() & (1 << 31), 0, "not FULL with one buffer");
1101 ai.write_reg(0, 0x200);
1102 ai.write_reg(1, 0x40); // two queued → FULL
1103 assert_ne!(ai.status() & (1 << 31), 0, "FULL");
1104 assert_ne!(ai.status() & 1, 0, "FULL mirror bit 0");
1105 assert_ne!(ai.status() & (1 << 25), 0, "ENABLED");
1106 }
1107
1108 /// **A sample due exactly at `now` is emitted on that call, not the next one.**
1109 ///
1110 /// [`Audio::tick`] returns early — before computing the DAC period — when no
1111 /// sample is due, which is what keeps a 64-bit divide off the hot path. The
1112 /// boundary of that early-out is `now < next_sample_tick`, and an off-by-one
1113 /// there defers every sample by one RCP step: audio that is *correct but late*,
1114 /// with no wrong sample anywhere.
1115 ///
1116 /// Nothing else in this suite can see that. Mutating the test to `<=` leaves the
1117 /// whole workspace green, because the other AI tests advance `now` in strides of
1118 /// a full period or longer and so never land on the boundary. The gross failure
1119 /// (never emitting at all) is caught seven times over; this one-tick case was
1120 /// caught zero times, which is why it is pinned here rather than assumed.
1121 ///
1122 /// `next_sample_tick` is read directly instead of recomputing the anchor from
1123 /// `period_ticks`, so the test asserts against where the DAC actually is rather
1124 /// than against a second copy of the formula it is meant to check. The schedule
1125 /// comes from `write_reg` — enqueuing a transfer sets `next_sample_tick` — so no
1126 /// priming tick is needed and none is issued; an earlier version called `tick`
1127 /// first and claimed it anchored the schedule, which it did not.
1128 #[test]
1129 fn a_sample_due_exactly_now_is_emitted_on_this_call() {
1130 let mut ai = programmed();
1131 let mut bus = TestBus::new(0x1_0000);
1132 bus.write_word(0x100, 0x0001_0002);
1133 bus.write_word(0x104, 0x0003_0004);
1134 ai.write_reg(0, 0x100);
1135 ai.write_reg(1, 8); // 8 bytes = 2 sample-pairs
1136
1137 let due = ai.next_sample_tick;
1138 assert!(
1139 due > 1,
1140 "enqueuing a transfer must schedule the next sample"
1141 );
1142
1143 ai.tick(due - 1, &mut bus);
1144 assert!(
1145 ai.drain().is_empty(),
1146 "no sample is due one tick before the boundary"
1147 );
1148
1149 ai.tick(due, &mut bus);
1150 assert_eq!(
1151 ai.drain(),
1152 [StereoSample { left: 1, right: 2 }],
1153 "the sample due exactly at `now` must be emitted on this call"
1154 );
1155 }
1156
1157 #[test]
1158 fn plays_a_buffer_and_drains_it() {
1159 let mut ai = programmed();
1160 let mut bus = TestBus::new(0x1_0000);
1161 // 4 stereo words at 0x100.
1162 for i in 0..4u32 {
1163 bus.write_word(0x100 + i * 4, 0x0001_0002u32.wrapping_add(i));
1164 }
1165 ai.write_reg(0, 0x100);
1166 ai.write_reg(1, 16); // 16 bytes = 4 sample-pairs
1167 // Advance exactly 4 sample periods (the anchor puts sample 1 at +period,
1168 // so ticks period..=4*period emit 4 samples before the DAC would decay).
1169 let period = ai.period_ticks();
1170 ai.tick(period * 4, &mut bus);
1171 let out = ai.drain();
1172 assert_eq!(out.len(), 4, "exactly the 4 buffered samples played");
1173 assert_eq!(out[0], StereoSample { left: 1, right: 2 });
1174 assert_eq!(out[3], StereoSample { left: 1, right: 5 });
1175 }
1176
1177 #[test]
1178 fn second_buffer_promotes_and_raises_irq_on_start() {
1179 let mut ai = programmed();
1180 let mut bus = TestBus::new(0x1_0000);
1181 // 8-byte (two-pair) buffers — AI_LENGTH granularity is 8 bytes (& ~7).
1182 bus.write_word(0x100, 0x1111_2222);
1183 bus.write_word(0x104, 0x1111_3333);
1184 bus.write_word(0x200, 0x3333_4444);
1185 bus.write_word(0x204, 0x3333_5555);
1186 ai.write_reg(0, 0x100);
1187 ai.write_reg(1, 8); // buffer 1
1188 ai.write_reg(0, 0x200);
1189 ai.write_reg(1, 8); // buffer 2 (queued, no IRQ yet)
1190 let period = ai.period_ticks();
1191 ai.tick(period * 6, &mut bus);
1192 let out = ai.drain();
1193 assert_eq!(
1194 out[0],
1195 StereoSample {
1196 left: 0x1111,
1197 right: 0x2222
1198 }
1199 );
1200 assert_eq!(
1201 out[2],
1202 StereoSample {
1203 left: 0x3333,
1204 right: 0x4444
1205 }
1206 );
1207 assert_eq!(bus.irqs, 1, "promotion of buffer 2 raised exactly one IRQ");
1208 }
1209
1210 /// **The delayed-carry hardware bug.** A transfer whose final sample ends
1211 /// exactly on an `0x2000` page boundary makes the AI add `0x2000` to the
1212 /// *next* buffer's address. This test fails if the bug is "corrected".
1213 #[test]
1214 fn delayed_carry_bug_bumps_the_next_buffer() {
1215 let mut ai = programmed();
1216 let mut bus = TestBus::new(0x1_0000);
1217 // Buffer 1: two pairs (8 bytes) ending exactly on the 0x2000 boundary —
1218 // the final read at 0x1FFC advances the low 13 bits 0x1FFC → 0x2000,
1219 // wrapping to 0 and arming the deferred carry.
1220 bus.write_word(0x1FF8, 0xAAAA_BBBB);
1221 bus.write_word(0x1FFC, 0xAAAA_CCCC);
1222 ai.write_reg(0, 0x1FF8);
1223 ai.write_reg(1, 8);
1224 // Buffer 2 is programmed at 0x0100, but the delayed carry adds 0x2000,
1225 // so playback reads from 0x2100 instead.
1226 bus.write_word(0x0100, 0x0000_0000); // what a correct AI would read
1227 bus.write_word(0x2100, 0xCCCC_DDDD); // what the buggy AI actually reads
1228 ai.write_reg(0, 0x0100);
1229 ai.write_reg(1, 8);
1230 let period = ai.period_ticks();
1231 ai.tick(period * 8, &mut bus);
1232 let out = ai.drain();
1233 assert_eq!(
1234 out[1],
1235 StereoSample {
1236 left: 0xAAAAu16 as i16,
1237 right: 0xCCCCu16 as i16
1238 }
1239 );
1240 assert_eq!(
1241 out[2],
1242 StereoSample {
1243 left: 0xCCCCu16 as i16,
1244 right: 0xDDDDu16 as i16
1245 },
1246 "the delayed carry bumped buffer 2's address by 0x2000"
1247 );
1248 }
1249
1250 #[test]
1251 fn underrun_is_observable_and_decays() {
1252 let mut ai = programmed();
1253 let mut bus = TestBus::new(0x1_0000);
1254 bus.write_word(0x100, 0x4000_4000);
1255 bus.write_word(0x104, 0x4000_4000);
1256 ai.write_reg(0, 0x100);
1257 ai.write_reg(1, 8); // two pairs, then starvation
1258 let period = ai.period_ticks();
1259 ai.tick(period * 6, &mut bus);
1260 assert_eq!(ai.underruns(), 1, "the starvation is counted");
1261 let out = ai.drain();
1262 assert_eq!(
1263 out[0],
1264 StereoSample {
1265 left: 0x4000,
1266 right: 0x4000
1267 }
1268 );
1269 // Once starved, samples decay toward zero from the last held value.
1270 assert!(out.len() > 2 && out[2].left < 0x4000 && out[2].left > 0);
1271 }
1272
1273 #[test]
1274 fn emission_is_deterministic() {
1275 let run = || {
1276 let mut ai = programmed();
1277 let mut bus = TestBus::new(0x1_0000);
1278 for i in 0..8u32 {
1279 bus.write_word(0x100 + i * 4, 0xDEAD_0000u32.wrapping_add(i));
1280 }
1281 ai.write_reg(0, 0x100);
1282 ai.write_reg(1, 32);
1283 ai.tick(ai.period_ticks() * 16, &mut bus);
1284 ai.drain()
1285 };
1286 assert_eq!(run(), run(), "same input → byte-identical sample stream");
1287 }
1288
1289 #[test]
1290 fn version_is_non_empty() {
1291 assert!(!version().is_empty());
1292 }
1293}