Skip to main content
← PSoXide SDK guides and examples · SDK f2fec1c301f1 · All Cargo features enabled; check feature gates before use.

psx_spu/
lib.rs

1// SPDX-License-Identifier: GPL-2.0-or-later
2//! High-level PS1 SPU (Sound Processing Unit) API.
3//!
4//! Turns SPU-programming-by-magic-numbers into typed primitives:
5//!
6//! - [`Voice`] -- typed voice index in `0..24`.
7//! - [`Pitch`] -- Q5.12 sample-rate multiplier; `Pitch::UNITY` =
8//!   44100 Hz (one sample per SPU tick).
9//! - [`Volume`] -- per-voice / main-output signed linear gain.
10//! - [`CdVolume`] -- CD-DA / XA input signed Q15 gain.
11//! - [`Adsr`] -- typed envelope descriptor; builds the two-word SPU
12//!   envelope register pair.
13//! - [`SpuAddr`] -- 8-byte-aligned SPU RAM pointer (the only shape
14//!   voice-start / loop / transfer registers accept).
15//!
16//! Typical boot sequence:
17//!
18//! ```ignore
19//! spu::init();                              // turn the SPU on, sensible defaults
20//! spu::set_main_volume(Volume::MAX, Volume::MAX);
21//! spu::upload_adpcm(SpuAddr::new(0x1010), &TONE_SAMPLE);
22//! let v = Voice::V0;
23//! v.set_volume(Volume::MAX, Volume::MAX);
24//! v.set_pitch(Pitch::UNITY);
25//! v.set_start_addr(SpuAddr::new(0x1010));
26//! v.set_adsr(Adsr::default_tone());
27//! Voice::start(v.mask());                   // start the tone
28//! ```
29//!
30//! ## What this crate owns vs doesn't
31//!
32//! - **Owns**: typed register access, key-on/off masks, the ADPCM
33//!   byte-stream upload path, ADSR encoding helpers, sample-start
34//!   address alignment.
35//! - **Doesn't own (yet)**: ADPCM encoder (ship pre-baked samples
36//!   instead -- see `vendor/tone_*.adpcm`, or cook `.psau` with
37//!   `psxed audio-pack`), reverb preset tables, DMA-based sample
38//!   upload. Those land as the ladder pulls them in.
39//!
40//! ## Q-format conventions
41//!
42//! - **Pitch**: Q5.12 in a u16. `0x1000` = 1.0 = the sample plays
43//!   at its recorded rate (44100 Hz). Halving to `0x0800` drops an
44//!   octave; `0x2000` raises one. Pitch field is 14 bits on
45//!   hardware; values above `0x3FFF` clamp.
46//! - **Volumes**: i16. Sign carries, the magnitude is linear. Max
47//!   output is `0x3FFF` (= 1.0); `Volume::MAX` already uses this.
48//!   Negative means "phase-inverted," used for stereo tricks we
49//!   don't wire up here yet.
50//! - **CD volumes**: signed Q15. Max input gain is `0x7FFF`, exposed
51//!   as `CdVolume::MAX`.
52//! - **SPU RAM addresses**: stored in registers as `addr / 8`,
53//!   [`SpuAddr`] does the divide so the caller passes byte offsets.
54
55#![no_std]
56#![deny(unsafe_op_in_unsafe_fn)]
57#![warn(missing_docs)]
58
59use psx_hw::spu::{
60    control as spucnt_bits, mask as voice_mask, status as spustat_bits, voice, CD_VOL_LEFT,
61    CD_VOL_RIGHT, IRQ_ADDR, MAIN_VOL_LEFT, MAIN_VOL_RIGHT, REVERB_VOL_LEFT, REVERB_VOL_RIGHT,
62    REVERB_WORK_BASE, SPUCNT, SPUSTAT, TRANSFER_ADDR, TRANSFER_CTRL, TRANSFER_CTRL_NORMAL,
63    TRANSFER_DATA,
64};
65use psx_io::periph::SpuDma;
66
67// ======================================================================
68// MMIO helpers -- hand-rolled volatile access at fixed SPU offsets
69// ======================================================================
70
71/// Raw MMIO write of a 16-bit SPU register.
72///
73/// Every SPU register is 16-bit (the controller doesn't care about
74/// higher-width accesses -- the lower half is what matters). We go
75/// through a volatile pointer so the compiler can't reorder writes
76/// across register boundaries.
77#[inline]
78fn write_reg16(addr: u32, value: u16) {
79    // SAFETY: SPU registers live in the hardware-MMIO window
80    // (`psx_hw::spu`). The SDK only exposes functions
81    // that compute their `addr` from typed handles whose
82    // constructors validate ranges, so no caller can point this at
83    // non-SPU memory.
84    unsafe {
85        core::ptr::write_volatile(addr as *mut u16, value);
86    }
87}
88
89/// Raw MMIO read of a 16-bit SPU register.
90#[inline]
91fn read_reg16(addr: u32) -> u16 {
92    // SAFETY: same MMIO contract as `write_reg16`.
93    unsafe { core::ptr::read_volatile(addr as *const u16) }
94}
95
96// ======================================================================
97// The driver
98// ======================================================================
99
100/// The SPU's memory-moving driver: it owns the SPU's DMA channel, so the one
101/// operation that copies RAM into sound RAM, the ADPCM upload, is a method
102/// here and the borrow checker sees a second uploader.
103///
104/// The voice, volume and reverb registers are free functions in this crate:
105/// writing one cannot touch RAM, so they need no owner yet.
106///
107/// ```text
108/// let mut spu = psx_spu::Spu::new(peripherals.spu_dma);
109/// spu.upload_adpcm(SpuAddr::new(0x1010), &TONE_SAMPLE);
110/// ```
111#[derive(Debug)]
112#[repr(transparent)]
113pub struct Spu(SpuDma);
114
115impl Spu {
116    /// Take over the SPU: reset it to a sane playable state and write the
117    /// silence block every one-shot's repeat address points at.
118    ///
119    /// Sets:
120    /// - SPUCNT: enable, unmute, default reverb off, CD audio off
121    /// - Main volume: max
122    /// - Every voice: silenced (volume 0, ADSR release fire, key-off)
123    /// - Reverb input and wet-output depth, pitch-mod, and noise: all disabled
124    /// - Transfer mode: "normal" (the upload path we expose)
125    ///
126    /// Call once at boot before any voice operations.
127    #[doc(alias = "SpuInit")]
128    pub fn new(mut dma: SpuDma) -> Self {
129        init_with(&mut dma);
130        Self(dma)
131    }
132
133    /// Upload ADPCM sample bytes to SPU RAM.
134    ///
135    /// Goes over DMA channel 4 when `bytes` is word-aligned and a whole number
136    /// of words, which is fast and the only reliable path past the 32-halfword
137    /// transfer FIFO, and otherwise falls back to the manual-transfer (PIO)
138    /// path, one halfword per write.
139    ///
140    /// `bytes` must be a multiple of 2 (one halfword per two bytes).
141    /// `dest` must be 8-byte-aligned (the SPU voice-start register's
142    /// resolution) and above the silence block.
143    ///
144    /// # Panics
145    /// If `bytes` is odd in length, or `dest` overlaps the silence block at
146    /// 0x1000.
147    #[doc(alias = "SpuWrite")]
148    pub fn upload_adpcm(&mut self, dest: SpuAddr, bytes: &[u8]) {
149        upload_adpcm_with(&mut self.0, dest, bytes);
150    }
151
152    /// Give the DMA token back.
153    #[inline(always)]
154    pub fn release(self) -> SpuDma {
155        self.0
156    }
157}
158
159/// A token for a deprecated forwarder that never took one.
160fn steal_dma() -> SpuDma {
161    // SAFETY: a token is a logic guard, not a memory-safety one (see
162    // `psx_io::periph`), and the old free functions never took one.
163    unsafe { SpuDma::steal() }
164}
165
166/// Reset the SPU to a sane playable state.
167#[deprecated(note = "use `Spu::new` with the `SpuDma` token")]
168pub fn init() {
169    init_with(&mut steal_dma());
170}
171
172/// Upload ADPCM sample bytes to SPU RAM.
173#[deprecated(note = "use `Spu::upload_adpcm` with the `SpuDma` token")]
174pub fn upload_adpcm(dest: SpuAddr, bytes: &[u8]) {
175    upload_adpcm_with(&mut steal_dma(), dest, bytes);
176}
177
178// ======================================================================
179// Initialisation
180// ======================================================================
181
182// The register sequence behind `Spu::new`.
183fn init_with(dma: &mut SpuDma) {
184    // Silence everything immediately -- key-off on all 24 voices
185    // before we touch any other state, so nothing glitches audibly
186    // on cold boot.
187    write_reg16(voice_mask::KEY_OFF_LO, 0xFFFF);
188    write_reg16(voice_mask::KEY_OFF_HI, 0x00FF);
189
190    // Zero every voice register block.
191    for v in 0..24 {
192        let base = voice::base(v as u32);
193        write_reg16(base + voice::VOL_LEFT, 0);
194        write_reg16(base + voice::VOL_RIGHT, 0);
195        write_reg16(base + voice::PITCH, 0);
196        write_reg16(base + voice::START_ADDR, 0);
197        write_reg16(base + voice::ADSR_LO, 0);
198        write_reg16(base + voice::ADSR_HI, 0);
199    }
200
201    // Disable reverb, noise, pitch modulation on all voices.
202    write_reg16(voice_mask::REVERB_ENABLE_LO, 0);
203    write_reg16(voice_mask::REVERB_ENABLE_HI, 0);
204    // Clearing SPUCNT's reverb-master bit stops feedback writes but does not
205    // stop the hardware read/APF/output path. The retail BIOS leaves a live
206    // preset (including non-zero vLOUT and mBASE) behind; a later sample-bank
207    // DMA can overwrite that work area and become audible even with EON=0.
208    // PA5 on silicon measured 56.8 dB of suppression from these two writes.
209    write_reg16(REVERB_VOL_LEFT, 0);
210    write_reg16(REVERB_VOL_RIGHT, 0);
211    // Park the reverb work area at the very top of SPU RAM.
212    //
213    // The work area runs from mBASE upward to the end of RAM, so a BIOS-left
214    // base low in memory reserves most of SPU RAM for reverb and anything a
215    // game uploads above it shares memory with the reverb engine. Zeroing the
216    // volumes above silences reverb's OUTPUT but leaves that claim on memory,
217    // and nothing else here moves it.
218    write_reg16(REVERB_WORK_BASE, 0xFFFE);
219    write_reg16(voice_mask::PITCH_MOD_LO, 0);
220    write_reg16(voice_mask::PITCH_MOD_HI, 0);
221    write_reg16(voice_mask::NOISE_LO, 0);
222    write_reg16(voice_mask::NOISE_HI, 0);
223
224    // Main volume to max -- per-voice volume still controls the mix.
225    write_reg16(MAIN_VOL_LEFT, Volume::MAX.register_bits());
226    write_reg16(MAIN_VOL_RIGHT, Volume::MAX.register_bits());
227    write_reg16(CD_VOL_LEFT, CdVolume::SILENCE.0 as u16);
228    write_reg16(CD_VOL_RIGHT, CdVolume::SILENCE.0 as u16);
229
230    // SPUCNT: bit 15 = SPU enable, bit 14 = mute OFF (i.e. audible),
231    // everything else zero. Writing in that order matches PSX-SPX's
232    // recommendation -- enable-then-unmute avoids a click.
233    write_reg16(SPUCNT, spucnt_bits::ENABLE); // enabled, muted
234    wait_spu_status(0x0000); // wait for SPUSTAT to stabilise
235    write_reg16(SPUCNT, spucnt_bits::ENABLE | spucnt_bits::UNMUTE); // enabled + unmuted
236    wait_spu_status(0x0000);
237
238    // Transfer mode: "Stop" (bit 0..=2 = 0). Games toggle this to
239    // Manual/DMA as needed when uploading.
240    write_reg16(TRANSFER_CTRL, TRANSFER_CTRL_NORMAL); // normal mode
241
242    // The one upload allowed onto the silence block: it writes the block.
243    upload_adpcm_unguarded(dma, SILENCE_BLOCK, &SILENCE_BLOCK_BYTES);
244}
245
246/// One ADPCM block of silence that loops on itself, for every one-shot's
247/// repeat address to point at. See [`SILENCE_BLOCK_BYTES`].
248///
249/// SPU RAM below 0x1000 belongs to the hardware's own decode buffers, so
250/// 0x1000 is the first address a program may use, and everything in this
251/// workspace already starts its sample bank at 0x1010. That leaves exactly one
252/// block spare, which is all this needs.
253pub const SILENCE_BLOCK: SpuAddr = SpuAddr::new(0x1000);
254
255/// Shift/filter 0, flags END|REPEAT, and fourteen bytes of zero nibbles.
256///
257/// A voice that lands here decodes silence and jumps straight back to the top
258/// of the same block, so it stays here until something keys it off.
259static SILENCE_BLOCK_BYTES: [u8; 16] = [0x00, 0x03, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
260
261/// Spin budget for one SPU handshake. Generous next to the delay the
262/// hardware actually takes, short enough that a wedged SPU hands control
263/// back rather than freezing the frame.
264const SPU_HANDSHAKE_SPINS: u32 = 100_000;
265
266/// Poll SPUSTAT's "SPU mode" field (low 6 bits) until it matches
267/// `want & 0x3F`.
268///
269/// This is not optional politeness. Per PSX-SPX, SPUCNT bits 0-5 are NOT
270/// applied immediately: after writing SPUCNT you must wait until the low
271/// bits of SPUSTAT reflect the new mode. Code that writes the transfer
272/// mode and starts moving data on the next instruction is talking to an
273/// SPU that has not entered that mode yet.
274///
275/// Bounded, unlike the first version of this: an unbounded spin on a
276/// register the hardware may never update is a hung console.
277fn wait_spu_status(want: u16) {
278    let mask = spustat_bits::MODE_MASK;
279    for _ in 0..SPU_HANDSHAKE_SPINS {
280        if (read_reg16(SPUSTAT) & mask) == (want & mask) {
281            return;
282        }
283    }
284}
285
286/// Wait for SPUSTAT bit 10, the data-transfer busy flag, to clear.
287///
288/// The DMA channel reporting "done" only means the CPU side finished
289/// handing bytes over; the SPU is still draining its own FIFO into sound
290/// RAM. Changing the transfer mode before this clears cuts the tail of
291/// the upload off.
292fn wait_transfer_idle() {
293    // Bit 10 clearing once is not enough on silicon. The 2026-08-07 console
294    // capture reconstructs a 64-byte DMA upload as having landed only its
295    // first 6 bytes, with the boundary MOVING between builds while a
296    // manual-FIFO upload of the same shape lands identically every time. A
297    // head that arrives, a tail that does not, and a boundary that wanders is
298    // a race on the drain rather than a wrong address or a dropped mode: the
299    // DMA controller has handed its words over, this flag reads clear, and
300    // the SPU is still writing them into sound RAM when the caller puts the
301    // transfer mode back to Stop underneath it.
302    //
303    // So require the flag to stay clear rather than to be clear once, and
304    // then leave a settle behind it. Timing record 0x65 puts 512 halfwords at
305    // roughly 32 cycles each, so a whole 32-halfword FIFO drains in about a
306    // thousand; the margin below is several times that and costs nothing
307    // against an upload measured in kilobytes.
308    const CONSECUTIVE_CLEAR: u32 = 16;
309    let mut clear = 0u32;
310    for _ in 0..SPU_HANDSHAKE_SPINS {
311        if read_reg16(SPUSTAT) & spustat_bits::TRANSFER_BUSY == 0 {
312            clear += 1;
313            if clear >= CONSECUTIVE_CLEAR {
314                break;
315            }
316        } else {
317            clear = 0;
318        }
319    }
320    // Settle. `read_reg16` is a volatile MMIO read, so this cannot be
321    // optimised away into nothing.
322    for _ in 0..SPU_DRAIN_SETTLE_READS {
323        let _ = read_reg16(SPUSTAT);
324    }
325}
326
327/// Status reads spent letting the SPU finish writing sound RAM after its
328/// transfer-busy flag goes clear. See [`wait_transfer_idle`].
329const SPU_DRAIN_SETTLE_READS: u32 = 256;
330
331// ======================================================================
332// Typed primitives
333// ======================================================================
334
335/// A 16-bit signed SPU voice/main volume.
336///
337/// Static voice/main volume uses `-0x4000..=0x3FFF` as its linear range.
338/// Positive magnitudes are the normal "louder" direction and a negative one
339/// inverts the phase. A value outside the range is limited to it when it is
340/// written to the register, so it never reaches the sweep-mode bit.
341#[derive(Copy, Clone, Debug, PartialEq, Eq)]
342#[repr(transparent)]
343pub struct Volume(pub i16);
344
345impl Volume {
346    /// Silence.
347    pub const SILENCE: Self = Self(0);
348    /// Maximum linear-positive volume (0x3FFF = +1.0).
349    pub const MAX: Self = Self(0x3FFF);
350    /// Half-scale convenience (0x2000 ≈ 0.5).
351    pub const HALF: Self = Self(0x2000);
352
353    /// Build from a normalized float-ish 0.0..=1.0 value without
354    /// actually using floats (since we're `no_std`, no FPU). `num`
355    /// and `den` are integer -- `Volume::linear(3, 4)` is 0.75.
356    pub const fn linear(num: u16, den: u16) -> Self {
357        if num >= den {
358            return Self::MAX; // 1.0 and above saturate (a zero `den` too)
359        }
360        let v = ((Self::MAX.0 as u32) * (num as u32)) / (den as u32);
361        Self(v as i16)
362    }
363
364    /// The 16-bit value the SPU volume registers take.
365    ///
366    /// Bit 15 selects sweep mode and bits 0 to 14 hold a signed 15-bit level
367    /// (psx-spx, "SPU Voice Volume"), so a fixed volume clears bit 15 and a
368    /// negative level is its 15-bit two's complement. The level is limited to
369    /// `-0x4000..=0x3FFF` first; a wider `i16` would otherwise set bit 15
370    /// or wrap in the 15-bit field.
371    const fn register_bits(self) -> u16 {
372        let level = if self.0 < -0x4000 {
373            -0x4000
374        } else if self.0 > 0x3FFF {
375            0x3FFF
376        } else {
377            self.0
378        };
379        (level as u16) & 0x7FFF
380    }
381}
382
383impl Default for Volume {
384    fn default() -> Self {
385        Self::SILENCE
386    }
387}
388
389/// CD-DA / XA input volume.
390///
391/// CD input volumes are plain signed Q15 values on hardware, so full
392/// positive gain is `0x7FFF`. Use this instead of [`Volume`], whose
393/// `0x3FFF` maximum is for voice/main registers.
394#[derive(Copy, Clone, Debug, PartialEq, Eq)]
395#[repr(transparent)]
396pub struct CdVolume(pub i16);
397
398impl CdVolume {
399    /// Silence.
400    pub const SILENCE: Self = Self(0);
401    /// Maximum positive CD input gain.
402    pub const MAX: Self = Self(0x7FFF);
403    /// Half-scale CD input gain.
404    pub const HALF: Self = Self(0x4000);
405
406    /// Build from a normalized `0.0..=1.0` ratio without floats.
407    pub const fn linear(num: u16, den: u16) -> Self {
408        let v = ((Self::MAX.0 as u32) * (num as u32)) / (den as u32);
409        Self(v as i16)
410    }
411}
412
413impl Default for CdVolume {
414    fn default() -> Self {
415        Self::SILENCE
416    }
417}
418
419/// Q5.12 pitch / sample-rate multiplier. `0x1000` = play at the
420/// sample's recorded rate. Halving = one octave down, doubling =
421/// one octave up.
422#[derive(Copy, Clone, Debug, PartialEq, Eq)]
423#[repr(transparent)]
424pub struct Pitch(u16);
425
426impl Pitch {
427    /// `0x1000` -- play at the sample's native 44100 Hz rate.
428    pub const UNITY: Self = Self(0x1000);
429    /// `0x0800` -- one octave below recorded rate.
430    pub const OCTAVE_DOWN: Self = Self(0x0800);
431    /// `0x2000` -- one octave above recorded rate.
432    pub const OCTAVE_UP: Self = Self(0x2000);
433    /// Hardware cap (14-bit field). Higher values clamp.
434    pub const MAX: Self = Self(0x3FFF);
435
436    /// Raw constructor. Values above `0x3FFF` clamp on hardware.
437    pub const fn raw(v: u16) -> Self {
438        Self(v)
439    }
440
441    /// The raw register value, as [`Pitch::raw`] took it. Callers scaling a
442    /// duration by pitch need the number back: at half unity a sample lasts
443    /// twice as long, and a cutoff that ignores that clips the sound.
444    pub const fn raw_value(self) -> u16 {
445        self.0
446    }
447
448    /// Pitch that makes a native-rate sine loop play at `hz_num /
449    /// hz_den` where the loop's natural frequency is `base_hz`.
450    /// Integer-only so `no_std` users can compute at compile time.
451    pub const fn for_frequency(target_hz: u32, base_hz: u32) -> Self {
452        let raw = ((0x1000u32) * target_hz) / base_hz;
453        if raw > 0x3FFF {
454            Self(0x3FFF)
455        } else {
456            Self(raw as u16)
457        }
458    }
459
460    /// Pitch that plays a decoded sample stream at its declared
461    /// source rate. `44100` maps to [`Pitch::UNITY`].
462    pub const fn for_sample_rate(sample_rate_hz: u32) -> Self {
463        if sample_rate_hz == 0 {
464            return Self::UNITY;
465        }
466        let raw = ((0x1000u32) * sample_rate_hz + 22_050) / 44_100;
467        if raw > 0x3FFF {
468            Self(0x3FFF)
469        } else if raw == 0 {
470            Self(1)
471        } else {
472            Self(raw as u16)
473        }
474    }
475
476    /// Underlying 14-bit value.
477    pub const fn as_u16(self) -> u16 {
478        self.0
479    }
480}
481
482/// An 8-byte-aligned SPU RAM address.
483///
484/// SPU voice-start / loop / transfer registers all hold `addr / 8`
485/// in their 16-bit field. Construction asserts alignment so the
486/// division is lossless and caller bugs don't silently round down.
487#[derive(Copy, Clone, Debug, PartialEq, Eq)]
488#[repr(transparent)]
489pub struct SpuAddr(u32);
490
491impl SpuAddr {
492    /// Build from a byte offset into SPU RAM. Panics if `addr`
493    /// isn't a multiple of 8.
494    #[allow(clippy::manual_is_multiple_of)]
495    pub const fn new(addr: u32) -> Self {
496        assert!(addr % 8 == 0, "SpuAddr: addr must be multiple of 8");
497        assert!(addr < 512 * 1024, "SpuAddr: past end of 512 KiB SPU RAM",);
498        Self(addr)
499    }
500
501    /// The byte offset this address represents.
502    pub const fn byte_offset(self) -> u32 {
503        self.0
504    }
505
506    /// The 16-bit field value the SPU registers actually want
507    /// (`addr / 8`).
508    pub const fn register_value(self) -> u16 {
509        (self.0 / 8) as u16
510    }
511}
512
513/// ADSR envelope descriptor. Builds the pair of 16-bit envelope
514/// registers the SPU needs.
515///
516/// Hardware bit-fields (PSX-SPX § "Voice 0..23 ADSR Register"):
517///
518/// **ADSR Lower (u16):**
519/// ```text
520///   bits 0..=3  : sustain level (0..15, sets target after decay)
521///   bits 4..=7  : decay shift (0..15)
522///   bits 8..=14 : attack shift (0..127)
523///   bit  15     : attack mode (0 = linear, 1 = exponential)
524/// ```
525///
526/// **ADSR Upper (u16):**
527/// ```text
528///   bits 0..=4  : release shift (0..31)
529///   bit  5      : release mode (0 = linear, 1 = exponential)
530///   bits 6..=12 : sustain shift (0..127)
531///   bit  13     : reserved
532///   bit  14     : sustain direction (0 = increase, 1 = decrease)
533///   bit  15     : sustain mode (0 = linear, 1 = exponential)
534/// ```
535///
536/// You rarely want to set all of these by hand; use
537/// [`Adsr::default_tone`] for a generic instrument-like preset or
538/// [`Adsr::percussive`] for a snappy one-shot.
539#[derive(Copy, Clone, Debug)]
540pub struct Adsr {
541    /// Pre-packed 16-bit ADSR lower word.
542    pub lower: u16,
543    /// Pre-packed 16-bit ADSR upper word.
544    pub upper: u16,
545}
546
547impl Adsr {
548    /// Generic sustained-tone envelope: instant attack to full, hold at
549    /// max sustain until [`Voice::release`], then an exponential release
550    /// (~100ms). The right default for melodies and held notes.
551    ///
552    /// The original version packed "attack shift 0x7F" into ADSR1's
553    /// 5-bit attack-shift field (bits 14-10); the overflow encoded
554    /// attack shift 31 + step 3 -- the slowest envelope the hardware
555    /// can express, so the voice never rose above the noise floor.
556    /// Layout per PSX-SPX: ADSR1 = [15 atk mode][14-10 atk shift]
557    /// [9-8 atk step][7-4 decay shift][3-0 sustain level]; ADSR2 =
558    /// [15 sus mode][14 sus dir][12-8 sus shift][7-6 sus step]
559    /// [5 rel mode][4-0 rel shift].
560    pub const fn default_tone() -> Self {
561        Self {
562            // Linear attack, shift 0, step 0 (instant); no decay;
563            // sustain level 0xF (max).
564            lower: 0x000F,
565            // Sustain holds (linear increase, capped at max);
566            // exponential release, shift 7.
567            upper: 0x0027,
568        }
569    }
570
571    /// Percussive one-shot -- blips, UI clicks, hit SFX. Instant attack
572    /// to full, then a snappy exponential fade in the sustain phase
573    /// (~150ms), so the voice silences itself without a key-off.
574    pub const fn percussive() -> Self {
575        Self {
576            // Instant attack, no decay, sustain level max.
577            lower: 0x000F,
578            // Sustain: exponential decrease, shift 6 (the self-fade);
579            // exponential release, shift 5.
580            upper: 0xC625,
581        }
582    }
583
584    /// Preserve a one-shot sample's own attack and decay until ADPCM END.
585    /// Unlike `percussive`, this does not fade during a sample's lead-in.
586    /// Release is fast and exponential; configure the repeat address to the
587    /// silence block (as `Voice::configure_sample` does) for END handling.
588    pub const fn sample_one_shot() -> Self {
589        Self {
590            lower: 0x000F,
591            upper: 0x0025,
592        }
593    }
594
595    /// Full-level envelope for sampled playback that a caller will end
596    /// itself (looped holds, streams). NOT for fire-and-forget one-shots:
597    /// on real hardware the ADPCM END+mute terminator does not silence
598    /// the voice, it drops it into RELEASE at this envelope's release
599    /// rate -- which is the slowest the hardware encodes -- while the
600    /// voice loops from the repeat address. A one-shot keyed under this
601    /// envelope therefore repeats at full volume indefinitely, and
602    /// key-off does not audibly help. Measured by hardware-tests SB1 on
603    /// console (2026-08-02): envelope 7FFF at 4.7 s with ENDX long set,
604    /// 3.7 s after key-off. The emulator zeroes the envelope at the
605    /// terminator instead, so only silicon shows it. Use
606    /// [`Adsr::percussive`] for one-shots; SB1 measured it silent within
607    /// two frames on the same console.
608    pub const fn sample() -> Self {
609        Self {
610            lower: 0x000F,
611            upper: 0x001F,
612        }
613    }
614
615    /// All-zero ADSR.
616    ///
617    /// This does NOT hold the voice at key-on volume, despite being the
618    /// obvious "no envelope" choice: zero means sustain level 0, so on real
619    /// hardware the envelope decays to silence shortly after key-on. Verified
620    /// on console, where a held tone faded out after ~3 seconds. Use
621    /// [`Adsr::sample`] for a level that holds.
622    pub const fn passthrough() -> Self {
623        Self { lower: 0, upper: 0 }
624    }
625}
626
627// ======================================================================
628// Voice handle
629// ======================================================================
630
631/// A typed voice index in `0..24`. Construct via the `V0`..`V23`
632/// constants or [`Voice::new`]. All per-voice operations are
633/// methods on this type.
634#[derive(Copy, Clone, Debug, PartialEq, Eq)]
635#[repr(transparent)]
636pub struct Voice(u8);
637
638impl Voice {
639    /// Voice index 0.
640    pub const V0: Self = Self(0);
641    /// Voice index 1.
642    pub const V1: Self = Self(1);
643    /// Voice index 2.
644    pub const V2: Self = Self(2);
645    /// Voice index 3.
646    pub const V3: Self = Self(3);
647    /// Voice index 4.
648    pub const V4: Self = Self(4);
649    /// Voice index 5.
650    pub const V5: Self = Self(5);
651    /// Voice index 6.
652    pub const V6: Self = Self(6);
653    /// Voice index 7.
654    pub const V7: Self = Self(7);
655    // V8..V23 follow the same pattern; keep them collapsed until a
656    // game actually needs 9+ voices in flight. `Voice::new(N)` is
657    // always available as the escape hatch.
658
659    /// Build a voice by index. Panics (const-asserts) if `n >= 24`.
660    pub const fn new(n: u8) -> Self {
661        assert!(n < 24, "Voice: index must be < 24");
662        Self(n)
663    }
664
665    /// The 0-based voice index this handle points at.
666    pub const fn index(self) -> u8 {
667        self.0
668    }
669
670    /// Bitmask this voice occupies in the 24-bit key-on / key-off
671    /// register pair. `Voice::V0.mask() == 0b1`.
672    pub const fn mask(self) -> u32 {
673        1u32 << (self.0 as u32)
674    }
675
676    /// Base MMIO address of this voice's 16-byte register block.
677    #[inline]
678    const fn reg_base(self) -> u32 {
679        voice::base(self.0 as u32)
680    }
681
682    /// Set per-voice stereo volume. The main-output mix is still
683    /// modulated by [`set_main_volume`], but each voice can have
684    /// its own level.
685    pub fn set_volume(self, left: Volume, right: Volume) {
686        write_reg16(self.reg_base() + voice::VOL_LEFT, left.register_bits());
687        write_reg16(self.reg_base() + voice::VOL_RIGHT, right.register_bits());
688    }
689
690    /// Set the voice's sample-rate pitch (Q5.12). [`Pitch::UNITY`]
691    /// = native 44100 Hz.
692    pub fn set_pitch(self, pitch: Pitch) {
693        write_reg16(self.reg_base() + voice::PITCH, pitch.as_u16());
694    }
695
696    /// Point the voice at the ADPCM sample starting at `addr`.
697    /// Voice will begin playing from this address at the next key-on.
698    pub fn set_start_addr(self, addr: SpuAddr) {
699        write_reg16(self.reg_base() + voice::START_ADDR, addr.register_value());
700    }
701
702    /// Set the voice's loop (repeat) address: where playback jumps when a
703    /// sample block with the loop-end flag finishes. The hardware itself
704    /// rewrites this register when a block with the loop-start flag passes,
705    /// so set it AFTER key-on to override; chaining two buffers' loop
706    /// addresses is how streamed audio ping-pongs without a key-off.
707    pub fn set_loop_addr(self, addr: SpuAddr) {
708        write_reg16(self.reg_base() + voice::REPEAT_ADDR, addr.register_value());
709    }
710
711    /// Install ADSR envelope parameters on this voice.
712    pub fn set_adsr(self, adsr: Adsr) {
713        write_reg16(self.reg_base() + voice::ADSR_LO, adsr.lower);
714        write_reg16(self.reg_base() + voice::ADSR_HI, adsr.upper);
715    }
716
717    /// Configure this voice to play a sample already uploaded at `addr`.
718    pub fn configure_sample(self, addr: SpuAddr, sample_rate_hz: u32, volume: Volume, adsr: Adsr) {
719        self.set_volume(volume, volume);
720        self.set_pitch(Pitch::for_sample_rate(sample_rate_hz));
721        self.set_start_addr(addr);
722        // Where silicon jumps when the sample's last block raises END. The
723        // hardware latches this register itself, but only from a block
724        // carrying the loop-start flag, and a one-shot has none: every psau in
725        // this workspace is 0x00 on the first block and 0x01 on the last. So
726        // without this write the register simply keeps whatever a previous
727        // sound left in it, and the voice runs off the end of its own data
728        // into whichever sample happens to sit at that stale address.
729        //
730        // The 2026-08-03 SB2 capture read it back on silicon: 0x034C for
731        // thirteen of fifteen segments regardless of what was keyed, against a
732        // start of 0x0202. The one segment that wrote the register explicitly
733        // read back correct, which is what says this write takes.
734        //
735        // Audibly, it was a blip after the launcher's browse sound (jump at
736        // 0x1010 running into pickup_coin at 0x28B0) and after VoXide's
737        // footstep. A caller that wants a genuine loop sets its own address
738        // after this returns.
739        self.set_loop_addr(SILENCE_BLOCK);
740        self.set_adsr(adsr);
741    }
742
743    /// Trigger the voices whose bits are set in `mask`. The SPU
744    /// begins playing each voice from its configured start address,
745    /// applying its ADSR attack phase.
746    ///
747    /// Example: `Voice::start(Voice::V0.mask() | Voice::V3.mask())`.
748    #[doc(alias = "KON")]
749    #[doc(alias = "SpuSetKey")]
750    pub fn start(mask: u32) {
751        write_reg16(voice_mask::KEY_ON_LO, mask as u16);
752        write_reg16(voice_mask::KEY_ON_HI, (mask >> 16) as u16);
753    }
754
755    /// Renamed to [`start`](Self::start).
756    #[deprecated(note = "renamed to `start`")]
757    #[inline(always)]
758    pub fn key_on(mask: u32) {
759        Self::start(mask)
760    }
761
762    /// Which voices have decoded a block carrying the END flag since ENDX was
763    /// last cleared, one sticky bit each.
764    ///
765    /// This is the only direct evidence that a one-shot reached its own
766    /// terminator. Everything else about termination has to be inferred: the
767    /// repeat address says where the hardware *would* jump, not whether it
768    /// did, and the 2026-08-03 SB2 capture read that register back as correct
769    /// while voices were audibly running into the next sample anyway.
770    #[doc(alias = "ENDX")]
771    pub fn ended_voices() -> u32 {
772        read_reg16(voice_mask::ENDX_LO) as u32 | ((read_reg16(voice_mask::ENDX_HI) as u32) << 16)
773    }
774
775    /// Clear the sticky END flags for the voices in `mask`, so the next
776    /// [`Voice::ended_voices`] reports only what happened after this call.
777    pub fn clear_ended(mask: u32) {
778        write_reg16(voice_mask::ENDX_LO, mask as u16);
779        write_reg16(voice_mask::ENDX_HI, (mask >> 16) as u16);
780    }
781
782    /// Stop the voices whose bits are set in `mask` -- fires the
783    /// release phase of the ADSR.
784    #[doc(alias = "KOFF")]
785    #[doc(alias = "SpuSetKey")]
786    pub fn release(mask: u32) {
787        write_reg16(voice_mask::KEY_OFF_LO, mask as u16);
788        write_reg16(voice_mask::KEY_OFF_HI, (mask >> 16) as u16);
789    }
790
791    /// Renamed to [`release`](Self::release).
792    #[deprecated(note = "renamed to `release`")]
793    #[inline(always)]
794    pub fn key_off(mask: u32) {
795        Self::release(mask)
796    }
797
798    /// Route the voices whose bits are set in `mask` to the noise generator
799    /// instead of their ADPCM data (the classic PSX percussion/wind source);
800    /// voices outside `mask` play normally. [`init`] clears the mask. Pitch
801    /// comes from the shared noise clock, [`set_noise_clock`], not the
802    /// voice's pitch register.
803    pub fn set_noise_mask(mask: u32) {
804        write_reg16(voice_mask::NOISE_LO, mask as u16);
805        write_reg16(voice_mask::NOISE_HI, (mask >> 16) as u16);
806    }
807}
808
809// ======================================================================
810// Global controls
811// ======================================================================
812
813/// Set the sound RAM address that raises the SPU IRQ when accessed.
814/// Address units are encoded by [`SpuAddr`]; this does not enable the IRQ.
815pub fn set_irq_address(address: SpuAddr) {
816    write_reg16(IRQ_ADDR, address.register_value());
817}
818
819/// Enable or disable the SPU's address IRQ without changing other controls.
820/// Disabling clears the SPU latch. Acknowledging the CPU interrupt controller
821/// remains the caller's responsibility, so ISR and polled users can differ.
822pub fn enable_irq(enabled: bool) {
823    let control = read_reg16(SPUCNT);
824    write_reg16(
825        SPUCNT,
826        if enabled {
827            control | spucnt_bits::IRQ_ENABLE
828        } else {
829            control & !spucnt_bits::IRQ_ENABLE
830        },
831    );
832}
833
834/// Whether the SPU's address IRQ latch is set.
835pub fn is_irq_pending() -> bool {
836    read_reg16(SPUSTAT) & spustat_bits::IRQ_FLAG != 0
837}
838
839/// Renamed to [`is_irq_pending`].
840#[deprecated(note = "renamed to `is_irq_pending`")]
841#[inline(always)]
842pub fn irq_pending() -> bool {
843    is_irq_pending()
844}
845
846// ======================================================================
847
848/// Set the main L/R output volume that every voice mixes through.
849pub fn set_main_volume(left: Volume, right: Volume) {
850    write_reg16(MAIN_VOL_LEFT, left.register_bits());
851    write_reg16(MAIN_VOL_RIGHT, right.register_bits());
852}
853
854/// Set the SPU CD input volume.
855///
856/// CD-DA and XA samples first leave the CD-ROM controller, then enter
857/// the SPU's CD input where these gains are applied before main mix.
858pub fn set_cd_volume(left: CdVolume, right: CdVolume) {
859    write_reg16(CD_VOL_LEFT, left.0 as u16);
860    write_reg16(CD_VOL_RIGHT, right.0 as u16);
861}
862
863/// Set the shared noise-generator clock, SPUCNT bits 8..=13: `shift`
864/// (0..=15) halves the frequency per step, 0 = highest pitch; `step`
865/// (0..=3) selects the +4..+7 fine increment. Applies to every voice
866/// routed to noise via [`Voice::set_noise_mask`].
867pub fn set_noise_clock(shift: u8, step: u8) {
868    let field = (((shift as u16) & 0xF) << 10) | (((step as u16) & 0x3) << 8);
869    let control = read_reg16(SPUCNT) & !spucnt_bits::NOISE_CLOCK_MASK;
870    write_reg16(SPUCNT, control | field);
871}
872
873/// Enable or disable routing CD-DA/XA input into the SPU mixer.
874pub fn enable_cd_audio(enabled: bool) {
875    let mut control = read_reg16(SPUCNT);
876    if enabled {
877        control |= spucnt_bits::CD_AUDIO_ENABLE;
878    } else {
879        control &= !spucnt_bits::CD_AUDIO_ENABLE;
880    }
881    write_reg16(SPUCNT, control);
882}
883
884// ======================================================================
885// ADPCM upload (PIO path)
886// ======================================================================
887
888// The upload behind `Spu::upload_adpcm`, guarded against the silence block.
889//
890// The PIO path follows PSX-SPX "SPU Data Transfer"; the process (PSX-SPX § "SPU Data Transfer"):
891/// 1. Write transfer-control = 0 (reset).
892/// 2. Write target address register.
893/// 3. Write transfer-control = 4 (manual).
894/// 4. Push halfword data through [`TRANSFER_DATA`].
895/// 5. Wait for the transfer to drain (SPUSTAT bit 7 = transfer busy).
896fn upload_adpcm_with(dma: &mut SpuDma, dest: SpuAddr, bytes: &[u8]) {
897    assert!(
898        bytes.len().is_multiple_of(2),
899        "upload_adpcm: byte slice must be a multiple of 2",
900    );
901    // The silent block every one-shot's repeat address points at lives at
902    // 0x1000, so a bank laid over it takes away the only thing stopping a
903    // finished voice wandering into other samples -- and does it silently,
904    // since the symptom is a stray blip rather than a crash. Celeste's PICO-8
905    // waveform bank starts at exactly 0x1000, which is how this was found.
906    // Banks start at 0x1010.
907    assert!(
908        dest.byte_offset() >= SILENCE_BLOCK.byte_offset() + SILENCE_BLOCK_BYTES.len() as u32,
909        "upload_adpcm: destination overlaps the SPU silence block at 0x1000",
910    );
911    upload_adpcm_unguarded(dma, dest, bytes);
912}
913
914// The upload without the silence-block guard, for `Spu::new` writing
915// that block. `bytes` is a whole number of halfwords.
916fn upload_adpcm_unguarded(dma: &mut SpuDma, dest: SpuAddr, bytes: &[u8]) {
917    // Prefer DMA (channel 4): fast, and the only reliable path for uploads
918    // larger than the 32-halfword transfer FIFO. Falls back to PIO when the
919    // source is not word-aligned / a whole number of 32-bit words.
920    if !upload_adpcm_dma(dma, dest, bytes) {
921        upload_adpcm_pio(dest, bytes);
922    }
923}
924
925/// DMA upload (channel 4, RAM -> SPU RAM), block-sync. Returns `false` (so the
926/// caller falls back to PIO) when `bytes` is not 4-byte aligned or not a whole
927/// number of 32-bit words -- the DMA controller is word-addressed.
928fn upload_adpcm_dma(dma: &mut SpuDma, dest: SpuAddr, bytes: &[u8]) -> bool {
929    let src = bytes.as_ptr() as u32;
930    // An empty slice would program a zero block count, which silicon reads
931    // as 65,536 blocks: megabytes past the slice. PIO moves nothing instead.
932    if bytes.is_empty() || !src.is_multiple_of(4) || !bytes.len().is_multiple_of(4) {
933        return false;
934    }
935    let words = (bytes.len() / 4) as u32;
936    // Largest block size (in words) that divides the count; the SPU transfer
937    // FIFO is 16 words, so cap there. ADPCM is whole 16-byte (4-word) blocks,
938    // so 4 always divides.
939    let block_size = if words.is_multiple_of(16) {
940        16
941    } else if words.is_multiple_of(8) {
942        8
943    } else if words.is_multiple_of(4) {
944        4
945    } else if words.is_multiple_of(2) {
946        2
947    } else {
948        1
949    };
950    let block_count = words / block_size;
951    if block_count > 0xFFFF {
952        return false; // larger than one block-sync transfer can describe
953    }
954
955    // Stop any in-flight transfer, and WAIT for the SPU to really be
956    // stopped before touching anything else. Going through Stop is what
957    // makes the wait below meaningful: SPUSTAT only tells us the mode
958    // changed if it actually changed.
959    let spucnt = read_reg16(SPUCNT) & !spucnt_bits::TRANSFER_MODE_MASK;
960    write_reg16(SPUCNT, spucnt);
961    wait_spu_status(spucnt);
962    write_reg16(TRANSFER_CTRL, TRANSFER_CTRL_NORMAL);
963    write_reg16(TRANSFER_ADDR, dest.register_value());
964    // SPUCNT transfer mode = DMA Write (bits 5..4 = 10), then wait for
965    // the SPU to enter it. Without this the DMA can deliver the whole
966    // payload to an SPU still in Stop mode -- and the smaller the
967    // payload, the more of it is lost, which is exactly the size
968    // dependence the console showed: kilobyte banks mostly arrived while
969    // a 32-byte table arrived not at all.
970    write_reg16(SPUCNT, spucnt | spucnt_bits::TRANSFER_DMA_WRITE);
971    wait_spu_status(spucnt | spucnt_bits::TRANSFER_DMA_WRITE);
972
973    // Channel 4: main RAM -> SPU, block-sync, forward, start; block until done.
974    // SAFETY: `bytes` is word-aligned and a whole number of words (checked
975    // above), so it is exactly `words` initialised `u32`s.
976    let source =
977        unsafe { core::slice::from_raw_parts(bytes.as_ptr().cast::<u32>(), words as usize) };
978    let _ = dma.write_blocks(source, block_size as u16);
979    // The channel is done handing bytes over; the SPU is still writing
980    // them into sound RAM.
981    wait_transfer_idle();
982
983    // Return to Stop transfer-mode.
984    write_reg16(SPUCNT, spucnt);
985    true
986}
987
988/// PIO upload (manual FIFO writes) -- small or non-word-aligned uploads. Sets
989/// SPUCNT Manual-Write mode (bits 5..4 = 01) around the writes, the step the
990/// original path was missing (it only poked TRANSFER_CTRL), which silently
991/// no-op'd the upload on hardware and on FIFO-accurate emulators.
992fn upload_adpcm_pio(dest: SpuAddr, bytes: &[u8]) {
993    let spucnt = read_reg16(SPUCNT) & !spucnt_bits::TRANSFER_MODE_MASK;
994    write_reg16(SPUCNT, spucnt);
995    wait_spu_status(spucnt);
996    write_reg16(TRANSFER_CTRL, TRANSFER_CTRL_NORMAL);
997    write_reg16(TRANSFER_ADDR, dest.register_value());
998    // Manual Write (bits 5..4 = 01), and wait for the SPU to be in it
999    // before pushing a single halfword. Same reason as the DMA path.
1000    write_reg16(SPUCNT, spucnt | spucnt_bits::TRANSFER_MANUAL_WRITE);
1001    wait_spu_status(spucnt | spucnt_bits::TRANSFER_MANUAL_WRITE);
1002
1003    let mut i = 0;
1004    while i + 1 < bytes.len() {
1005        let lo = bytes[i] as u16;
1006        let hi = bytes[i + 1] as u16;
1007        write_reg16(TRANSFER_DATA, lo | (hi << 8));
1008        i += 2;
1009    }
1010
1011    wait_transfer_idle();
1012    write_reg16(SPUCNT, spucnt);
1013    // Leave the transfer type NORMAL, not 0. PSX-SPX is explicit that
1014    // the transfer control register "should be 0004h"; parking it at 0 selects another
1015    // transfer type, and on silicon that poisons everything that touches
1016    // sample RAM afterwards -- voices key on and their envelope collapses
1017    // straight back to zero, so every sampled sound goes silent while
1018    // noise-mode voices, which never read SPU RAM, keep playing. Measured
1019    // by hardware-tests SB2 on console (2026-08-02): after a probe that
1020    // left this at 0, its own tone ladder was silent and a following SB1
1021    // run was silent too, both fine on the emulator, which ignores this
1022    // register entirely.
1023    write_reg16(TRANSFER_CTRL, TRANSFER_CTRL_NORMAL);
1024    for _ in 0..200 {
1025        core::hint::spin_loop();
1026    }
1027}
1028
1029// ======================================================================
1030// Tests
1031// ======================================================================
1032
1033#[cfg(test)]
1034mod tests {
1035    use super::*;
1036
1037    #[test]
1038    fn the_driver_is_its_token_and_gives_it_back() {
1039        assert_eq!(core::mem::size_of::<Spu>(), 0);
1040        // SAFETY: a test-local token on the host. `Spu::new` would reset the
1041        // registers, so the driver is built around the token directly.
1042        let spu = Spu(unsafe { SpuDma::steal() });
1043        let _token: SpuDma = spu.release();
1044    }
1045
1046    #[test]
1047    fn the_silence_block_ends_and_repeats_onto_itself() {
1048        // Without both flags the voice either runs on past this block or
1049        // stops here by luck rather than by contract.
1050        assert_eq!(SILENCE_BLOCK_BYTES[1] & 0x01, 0x01, "END");
1051        assert_eq!(SILENCE_BLOCK_BYTES[1] & 0x02, 0x02, "REPEAT");
1052        assert!(
1053            SILENCE_BLOCK_BYTES[2..].iter().all(|&b| b == 0),
1054            "the block has to decode to silence",
1055        );
1056    }
1057
1058    #[test]
1059    fn the_silence_block_owns_the_first_usable_block_of_spu_ram() {
1060        // SPU RAM below 0x1000 is the hardware's own decode area, so 0x1000 is
1061        // the first address a program may use and this claims it. A bank laid
1062        // over it takes away the only thing stopping a finished voice
1063        // wandering, which is why upload_adpcm refuses the overlap rather than
1064        // trusting every game to start at 0x1010.
1065        assert_eq!(SILENCE_BLOCK.byte_offset(), 0x1000);
1066        assert_eq!(SILENCE_BLOCK_BYTES.len(), 16);
1067    }
1068
1069    #[test]
1070    fn volume_linear_scales() {
1071        assert_eq!(Volume::linear(0, 1), Volume::SILENCE);
1072        assert_eq!(Volume::linear(1, 1), Volume::MAX);
1073        // 0.5 → 0x1FFF (rounded down from 0x3FFF / 2).
1074        let half = Volume::linear(1, 2);
1075        assert_eq!(half.0, 0x1FFF);
1076    }
1077
1078    /// psx-spx, SPU volume registers: bit 15 selects sweep mode, bits 0-14
1079    /// hold a signed 15-bit level (-0x4000..=0x3FFF in use). A fixed volume
1080    /// therefore never sets bit 15, and a negative one is two's complement
1081    /// in 15 bits.
1082    #[test]
1083    fn volume_register_value_follows_the_psx_spx_layout() {
1084        for raw in i16::MIN..=i16::MAX {
1085            let bits = Volume(raw).register_bits();
1086            assert_eq!(bits & 0x8000, 0, "{raw}: sweep bit set");
1087            // Sign-extend the 15-bit field the way the SPU reads it.
1088            let level = ((bits << 1) as i16) >> 1;
1089            assert_eq!(level, raw.clamp(-0x4000, 0x3FFF), "{raw}");
1090        }
1091        assert_eq!(Volume(0x3FFF).register_bits(), 0x3FFF);
1092        assert_eq!(Volume(-0x2000).register_bits(), 0x6000);
1093        assert_eq!(Volume(-0x4000).register_bits(), 0x4000);
1094    }
1095
1096    #[test]
1097    fn volume_linear_saturates_instead_of_wrapping() {
1098        // 2.0 used to compute 0x7FFE, which the register reads as -2.
1099        assert_eq!(Volume::linear(2, 1), Volume::MAX);
1100        assert_eq!(Volume::linear(5, 4), Volume::MAX);
1101        assert_eq!(Volume::linear(1, 0), Volume::MAX);
1102        assert_eq!(Volume::linear(3, 4).0, 0x2FFF);
1103    }
1104
1105    #[test]
1106    fn cd_volume_uses_q15_full_scale() {
1107        assert_eq!(CdVolume::linear(0, 1), CdVolume::SILENCE);
1108        assert_eq!(CdVolume::linear(1, 1), CdVolume::MAX);
1109        assert_eq!(CdVolume::HALF.0, 0x4000);
1110    }
1111
1112    #[test]
1113    fn pitch_for_frequency() {
1114        // Play a 440 Hz native sample at 880 Hz → pitch 0x2000.
1115        let p = Pitch::for_frequency(880, 440);
1116        assert_eq!(p.as_u16(), 0x2000);
1117        // Play at native rate.
1118        let p = Pitch::for_frequency(440, 440);
1119        assert_eq!(p, Pitch::UNITY);
1120        // Very high multiplier clamps to MAX.
1121        let p = Pitch::for_frequency(100_000, 440);
1122        assert_eq!(p, Pitch::MAX);
1123    }
1124
1125    #[test]
1126    fn pitch_for_sample_rate_maps_to_spu_step() {
1127        assert_eq!(Pitch::for_sample_rate(44_100), Pitch::UNITY);
1128        assert_eq!(Pitch::for_sample_rate(22_050).as_u16(), 0x0800);
1129        assert_eq!(Pitch::for_sample_rate(88_200).as_u16(), 0x2000);
1130    }
1131
1132    #[test]
1133    fn spu_addr_register_value_divides_by_8() {
1134        assert_eq!(SpuAddr::new(0x1000).register_value(), 0x0200);
1135        assert_eq!(SpuAddr::new(0x1008).register_value(), 0x0201);
1136    }
1137
1138    #[test]
1139    #[should_panic = "multiple of 8"]
1140    fn spu_addr_rejects_misalignment() {
1141        let _ = SpuAddr::new(0x1004);
1142    }
1143
1144    #[test]
1145    fn voice_mask_correct() {
1146        assert_eq!(Voice::V0.mask(), 0x01);
1147        assert_eq!(Voice::V3.mask(), 0x08);
1148        assert_eq!(Voice::new(23).mask(), 0x0080_0000);
1149    }
1150
1151    #[test]
1152    #[should_panic = "index must be < 24"]
1153    fn voice_new_rejects_out_of_range() {
1154        let _ = Voice::new(24);
1155    }
1156}