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}