apu.rsannotatedapu.rssource759 lines · 25.5 KB · raw

GBA APU (audio processing unit)

Contains the 4 Game Boy Color APU channels (slightly modified) plus two 8-bit PCM channels (Direct Sound)

5mod audio;
6mod psg;
8use crate::api::GbaAudioConfig;
9use crate::apu::audio::{BasicResampler, InterpolatingResampler};
10use crate::apu::psg::Psg;
11use crate::dma::DmaState;
12use bincode::{Decode, Encode};
13use gba_config::GbaAudioInterpolation;
14use jgenesis_common::define_bit_enum;
15use jgenesis_common::frontend::AudioOutput;
16use jgenesis_common::num::GetBit;
17use std::array;
18
19pub const FIFO_A_ADDRESS: u32 = 0x40000A0;
20pub const FIFO_B_ADDRESS: u32 = 0x40000A4;
21
22const FIFO_CAPACITY: u8 = 7;
23
24define_bit_enum!(DirectSoundTimer, [Zero, One]);

Implementation based on https://github.com/mgba-emu/mgba/issues/1847

27#[derive(Debug, Clone, Encode, Decode)]
28struct DirectSoundFifo {
29    buffer: [u32; FIFO_CAPACITY as usize],
30    read_idx: u8,
31    write_idx: u8,
32    len: u8,
33    playing: [i8; 4],
34    playing_idx: u8,
35}
37impl DirectSoundFifo {
38    fn new() -> Self {
39        Self {
40            buffer: array::from_fn(|_| 0),
41            read_idx: 0,
42            write_idx: 0,
43            len: 0,
44            playing: array::from_fn(|_| 0),
45            playing_idx: 0,
46        }
47    }
48
49    fn push(&mut self, word: u32) {
50        self.buffer[self.write_idx as usize] = word;
51
52        self.write_idx += 1;
53        if self.write_idx == FIFO_CAPACITY {
54            self.write_idx = 0;
55        }
56
57        self.len += 1;
58        if self.len > FIFO_CAPACITY {
59            self.reset();
60        }
61    }
62
63    fn push_halfword(&mut self, address: u32, halfword: u16) {
64        let existing = self.buffer[self.write_idx as usize];
65
66        let word = if !address.bit(1) {
67            u32::from(halfword) | (existing & !0xFFFF)
68        } else {
69            (u32::from(halfword) << 16) | (existing & 0xFFFF)
70        };
71
72        self.push(word);
73    }
74
75    fn push_byte(&mut self, address: u32, byte: u8) {
76        let mut bytes = self.buffer[self.write_idx as usize].to_le_bytes();
77        bytes[(address & 3) as usize] = byte;
78
79        self.push(u32::from_le_bytes(bytes));
80    }
81
82    fn pop(&mut self) -> Option<i8> {
83        if self.playing_idx == 0 {
84            if self.len == 0 {
85                return None;
86            }
87
88            let word = self.buffer[self.read_idx as usize];
89
90            self.read_idx += 1;
91            if self.read_idx == FIFO_CAPACITY {
92                self.read_idx = 0;
93            }
94
95            self.len -= 1;
96
97            self.playing = word.to_le_bytes().map(|b| b as i8);
98        }
99
100        let sample = self.playing[self.playing_idx as usize];
101        self.playing_idx = (self.playing_idx + 1) % 4;
102
103        Some(sample)
104    }
105
106    fn reset(&mut self) {
107        *self = Self::new();
108    }
109}
110
111#[derive(Debug, Clone, Encode, Decode)]
112struct DirectSoundChannel {
113    name: String,
114    fifo: DirectSoundFifo,
115    current_sample: i8,
116    volume_shift: u8,
117    l_enabled: bool,
118    r_enabled: bool,
119    timer: DirectSoundTimer,
120}
121
122impl DirectSoundChannel {
123    fn new(name: String) -> Self {
124        Self {
125            name,
126            fifo: DirectSoundFifo::new(),
127            current_sample: 0,
128            volume_shift: 0,
129            l_enabled: false,
130            r_enabled: false,
131            timer: DirectSoundTimer::default(),
132        }
133    }
134
135    fn pop_fifo(&mut self) {
136        self.current_sample = self.fifo.pop().unwrap_or(self.current_sample);
137    }
138
139    fn reset_fifo(&mut self) {
140        self.fifo.reset();
141    }
142
143    fn dma_request(&self) -> bool {
144        self.fifo.len <= FIFO_CAPACITY - 4
145    }
146}
147
148#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Encode, Decode)]
149enum PwmClockShift {
150    #[default]
151    Nine = 0, // 32768 Hz, 9-bit samples
152    Eight = 1, // 65536 Hz, 8-bit samples
153    Seven = 2, // 131072 Hz, 7-bit samples
154    Six = 3,   // 262144 Hz, 6-bit samples
155}
156
157impl PwmClockShift {
158    fn from_bits(bits: u8) -> Self {
159        [Self::Nine, Self::Eight, Self::Seven, Self::Six][(bits & 3) as usize]
160    }
161
162    fn gba_clock_downshift(self) -> u8 {
163        // Downshifting from ~16.77 MHz
164        match self {
165            Self::Nine => 9,
166            Self::Eight => 8,
167            Self::Seven => 7,
168            Self::Six => 6,
169        }
170    }
171
172    fn mixed_sample_downshift(self) -> u8 {
173        // Downshifting from unsigned 10-bit
174        match self {
175            Self::Nine => 1,
176            Self::Eight => 2,
177            Self::Seven => 3,
178            Self::Six => 4,
179        }
180    }
181
182    fn source_frequency(self) -> u64 {
183        crate::GBA_CLOCK_SPEED >> self.gba_clock_downshift()
184    }
185}
186
187#[derive(Debug, Clone, Encode, Decode)]
188struct PwmControl {
189    clock_shift: PwmClockShift,
190    sound_bias: i16,
191}
192
193impl PwmControl {
194    fn new() -> Self {
195        Self { clock_shift: PwmClockShift::default(), sound_bias: 0x200 }
196    }
197}
198
199#[derive(Debug, Clone, Encode, Decode)]
200enum AudioResampler {
201    Basic(Box<BasicResampler>),
202    Interpolating(Box<InterpolatingResampler>),
203}
204
205impl AudioResampler {
206    fn new(
207        config: GbaAudioConfig,
208        clock_shift: PwmClockShift,
209        output_frequency: u64,
210        pcm_frequencies: [Option<f64>; 2],
211    ) -> Self {
212        match config.audio_interpolation {
213            GbaAudioInterpolation::NearestNeighbor => {
214                Self::Basic(Box::new(BasicResampler::new(clock_shift, output_frequency)))
215            }
216            GbaAudioInterpolation::CubicHermite | GbaAudioInterpolation::WindowedSinc => {
217                Self::Interpolating(Box::new(InterpolatingResampler::new(
218                    config.audio_interpolation,
219                    config.psg_low_pass,
220                    output_frequency,
221                    pcm_frequencies,
222                )))
223            }
224        }
225    }
226
227    fn update_source_frequency(&mut self, clock_shift: PwmClockShift) {
228        if let Self::Basic(resampler) = self {
229            resampler.update_source_frequency(clock_shift);
230        }
231    }
232
233    fn update_output_frequency(&mut self, output_frequency: u64) {
234        match self {
235            Self::Basic(resampler) => resampler.update_output_frequency(output_frequency),
236            Self::Interpolating(resampler) => resampler.update_output_frequency(output_frequency),
237        }
238    }
239
240    fn push_mixed_sample(&mut self, sample: [f64; 2]) {
241        if let Self::Basic(resampler) = self {
242            resampler.push_mixed_sample(sample);
243        }
244    }
245
246    fn push_psg(&mut self, sample: (i16, i16)) {
247        if let Self::Interpolating(resampler) = self {
248            resampler.push_psg(sample);
249        }
250    }
251
252    fn push_pcm_a(&mut self, sample: i8) {
253        if let Self::Interpolating(resampler) = self {
254            resampler.push_pcm_a(sample);
255        }
256    }
257
258    fn push_pcm_b(&mut self, sample: i8) {
259        if let Self::Interpolating(resampler) = self {
260            resampler.push_pcm_b(sample);
261        }
262    }
263
264    fn update_pcm_a_frequency(&mut self, frequency: Option<f64>) {
265        if let Self::Interpolating(resampler) = self {
266            resampler.update_pcm_a_frequency(frequency);
267        }
268    }
269
270    fn update_pcm_b_frequency(&mut self, frequency: Option<f64>) {
271        if let Self::Interpolating(resampler) = self {
272            resampler.update_pcm_b_frequency(frequency);
273        }
274    }
275
276    fn drain_audio_output<A: AudioOutput>(
277        &mut self,
278        audio_output: &mut A,
279        pcm_volume_shifts: [bool; 2],
280        psg_volume_shift: u8,
281        pcm_a_enabled: [bool; 2],
282        pcm_b_enabled: [bool; 2],
283    ) -> Result<(), A::Err> {
284        match self {
285            Self::Basic(resampler) => resampler.drain_audio_output(audio_output),
286            Self::Interpolating(resampler) => resampler.drain_audio_output(
287                audio_output,
288                pcm_volume_shifts,
289                psg_volume_shift,
290                pcm_a_enabled,
291                pcm_b_enabled,
292            ),
293        }
294    }
295}
296
297#[derive(Debug, Clone, Encode, Decode)]
298pub struct Apu {
299    enabled: bool,
300    pcm_a: DirectSoundChannel,
301    pcm_b: DirectSoundChannel,
302    psg: Psg,
303    psg_volume: u8,
304    psg_volume_shift: u8,
305    pwm: PwmControl,
306    resampler: AudioResampler,
307    output_frequency: u64,
308    timer_frequencies: [Option<f64>; 2],
309    cycles: u64,
310    config: GbaAudioConfig,
311}
312
313impl Apu {
314    pub fn new(config: GbaAudioConfig) -> Self {
315        const DEFAULT_OUTPUT_FREQUENCY: u64 = 48000;
316
317        Self {
318            enabled: false,
319            pcm_a: DirectSoundChannel::new("A".into()),
320            pcm_b: DirectSoundChannel::new("B".into()),
321            psg: Psg::new(),
322            psg_volume: 2,
323            psg_volume_shift: 0,
324            pwm: PwmControl::new(),
325            resampler: AudioResampler::new(
326                config,
327                PwmClockShift::default(),
328                DEFAULT_OUTPUT_FREQUENCY,
329                [None; 2],
330            ),
331            output_frequency: DEFAULT_OUTPUT_FREQUENCY,
332            timer_frequencies: [None; 2],
333            cycles: 0,
334            config,
335        }
336    }
337
338    pub fn step_to(&mut self, cycles: u64) {
339        if cycles <= self.cycles {
340            return;
341        }
342
343        let clock_shift = self.pwm.clock_shift.gba_clock_downshift();
344        let pwm_samples_elapsed = (cycles >> clock_shift) - (self.cycles >> clock_shift);
345        let psg_ticks = 1 << (21 - (24 - clock_shift));
346
347        match self.config.audio_interpolation {
348            GbaAudioInterpolation::NearestNeighbor => {
349                for _ in 0..pwm_samples_elapsed {
350                    for _ in 0..psg_ticks {
351                        self.psg.tick_2mhz(self.enabled);
352                    }
353
354                    let (sample_l, sample_r) = self.generate_mixed_pwm_sample();
355                    self.resampler.push_mixed_sample([sample_l, sample_r]);
356                }
357            }
358            GbaAudioInterpolation::CubicHermite | GbaAudioInterpolation::WindowedSinc => {
359                for _ in 0..pwm_samples_elapsed * psg_ticks {
360                    self.psg.tick_2mhz(self.enabled);
361                    self.resampler.push_psg(self.psg.sample(self.config.psg_channels_enabled()));
362                }
363            }
364        }
365
366        self.cycles = cycles;
367    }
368
369    fn generate_mixed_pwm_sample(&self) -> (f64, f64) {
370        if !self.enabled {
371            return (0.0, 0.0);
372        }
373
374        let (mut pwm_l, mut pwm_r) = self.sample();
375
376        let sample_downshift = self.pwm.clock_shift.mixed_sample_downshift();
377        pwm_l >>= sample_downshift;
378        pwm_r >>= sample_downshift;
379
380        // Remap from [0x000, 0x3FF >> shift] to [0, 1]
381        let max_sample = f64::from(0x3FF >> sample_downshift);
382        let mut sample_l = f64::from(pwm_l) / max_sample;
383        let mut sample_r = f64::from(pwm_r) / max_sample;
384
385        // Remap from [0, 1] to [-1, +1], if only for volume purposes
386        sample_l = 2.0 * (sample_l - 0.5);
387        sample_r = 2.0 * (sample_r - 0.5);
388
389        (sample_l, sample_r)
390    }
391
392    fn sample_pcm(&self, channels_enabled: [bool; 2]) -> (i16, i16) {
393        let channels_enabled = channels_enabled.map(i16::from);
394
395        // Left shift from signed 8-bit to signed 10-bit, then apply optional 50% volume reduction
396        let raw_samples = [self.pcm_a.current_sample, self.pcm_b.current_sample].map(i16::from);
397        let samples = [
398            ((channels_enabled[0] * raw_samples[0]) << 2) >> self.pcm_a.volume_shift,
399            ((channels_enabled[1] * raw_samples[1]) << 2) >> self.pcm_b.volume_shift,
400        ];
401
402        let l_enabled = [self.pcm_a.l_enabled, self.pcm_b.l_enabled].map(i16::from);
403        let r_enabled = [self.pcm_a.r_enabled, self.pcm_b.r_enabled].map(i16::from);
404
405        // PCM sum is clamped before mixing with PSG (tested on hardware)
406        let pcm_l = (l_enabled[0] * samples[0] + l_enabled[1] * samples[1]).clamp(-0x200, 0x1FF);
407        let pcm_r = (r_enabled[0] * samples[0] + r_enabled[1] * samples[1]).clamp(-0x200, 0x1FF);
408
409        (pcm_l, pcm_r)
410    }
411
412    fn sample(&self) -> (u16, u16) {
413        let (pcm_l, pcm_r) = self.sample_pcm(self.config.pcm_channels_enabled());
414
415        // Mixed PSG sample is already signed 10-bit
416        let (mut psg_l, mut psg_r) = self.psg.sample(self.config.psg_channels_enabled());
417        psg_l >>= self.psg_volume_shift;
418        psg_r >>= self.psg_volume_shift;
419
420        // PCM + PSG sum is clamped to signed 10-bit (verified on hardware)
421        // Then added with sound bias and clamped to unsigned 10-bit
422        let final_mix = |pcm: i16, psg: i16| {
423            let signed_sum = (pcm + psg).clamp(-0x200, 0x1FF);
424            (signed_sum + self.pwm.sound_bias).clamp(0x000, 0x3FF) as u16
425        };
426
427        let sample_l = final_mix(pcm_l, psg_l);
428        let sample_r = final_mix(pcm_r, psg_r);
429        (sample_l, sample_r)
430    }
431
432    pub fn handle_timer_overflow(&mut self, timer_idx: usize, cycles: u64, dma: &mut DmaState) {
433        self.step_to(cycles);
434
435        let timer = match timer_idx {
436            0 => DirectSoundTimer::Zero,
437            1 => DirectSoundTimer::One,
438            _ => return,
439        };
440
441        if self.pcm_a.timer == timer {
442            if self.pcm_a.dma_request() {
443                dma.notify_apu_fifo_a(cycles);
444            }
445
446            self.pcm_a.pop_fifo();
447            self.resampler
448                .push_pcm_a(self.pcm_a.current_sample * i8::from(self.config.pcm_a_enabled));
449        }
450
451        if self.pcm_b.timer == timer {
452            if self.pcm_b.dma_request() {
453                dma.notify_apu_fifo_b(cycles);
454            }
455
456            self.pcm_b.pop_fifo();
457            self.resampler
458                .push_pcm_b(self.pcm_b.current_sample * i8::from(self.config.pcm_b_enabled));
459        }
460    }
461
462    #[allow(clippy::match_same_arms)]
463    pub fn read_register(&self, address: u32) -> Option<u8> {
464        let value = match address {
465            0x4000060 => self.psg.read_sound1cnt_l(),
466            0x4000061 => 0, // SOUND1CNT_L high
467            0x4000062 => self.psg.read_sound1cnt_h_low(),
468            0x4000063 => self.psg.read_sound1cnt_h_high(),
469            0x4000064 => 0, // SOUND1CNT_X low
470            0x4000065 => self.psg.read_sound1cnt_x_high(),
471            0x4000066 => 0,
472            0x4000067 => 0,
473            0x4000068 => self.psg.read_sound2cnt_l_low(),
474            0x4000069 => self.psg.read_sound2cnt_l_high(),
475            0x400006A => 0,
476            0x400006B => 0,
477            0x400006C => 0, // SOUND2CNT_H low
478            0x400006D => self.psg.read_sound2cnt_h_high(),
479            0x400006E => 0,
480            0x400006F => 0,
481            0x4000070 => self.psg.read_sound3cnt_l(),
482            0x4000071 => 0, // SOUND3CNT_L high
483            0x4000072 => 0, // SOUND3CNT_H low
484            0x4000073 => self.psg.read_sound3cnt_h_high(),
485            0x4000074 => 0, // SOUND3CNT_X low
486            0x4000075 => self.psg.read_sound3cnt_x_high(),
487            0x4000076 => 0,
488            0x4000077 => 0,
489            0x4000078 => 0, // SOUND4CNT_L low
490            0x4000079 => self.psg.read_sound4cnt_l_high(),
491            0x400007A => 0,
492            0x400007B => 0,
493            0x400007C => self.psg.read_sound4cnt_h_low(),
494            0x400007D => self.psg.read_sound4cnt_h_high(),
495            0x400007E => 0,
496            0x400007F => 0,
497            0x4000080 => self.psg.read_soundcnt_l_low(),
498            0x4000081 => self.psg.read_soundcnt_l_high(),
499            0x4000082 => self.read_soundcnt_h_low(),
500            0x4000083 => self.read_soundcnt_h_high(),
501            0x4000084 => self.read_soundcnt_x(),
502            0x4000085 => 0, // SOUNDCNT_X high
503            0x4000086 => 0,
504            0x4000087 => 0,
505            0x4000088 => self.read_soundbias_low(),
506            0x4000089 => self.read_soundbias_high(),
507            0x400008A => 0,
508            0x400008B => 0,
509            0x4000090..=0x400009F => self.psg.read_wave_ram(address),
510            _ => {
511                log::debug!("Unimplemented APU register read: {address:08X}");
512                return None;
513            }
514        };
515
516        Some(value)
517    }
518
519    pub fn read_register_halfword(&mut self, address: u32) -> Option<u16> {
520        let lsb = self.read_register(address)?;
521        let msb = self.read_register(address | 1)?;
522        Some(u16::from_le_bytes([lsb, msb]))
523    }
524
525    #[allow(clippy::match_same_arms)]
526    pub fn write_register(&mut self, address: u32, value: u8) {
527        // Registers outside of $4000082-$4000089 are not writable when APU is disabled
528        if !self.enabled && !(0x4000082..0x400008A).contains(&address) {
529            return;
530        }
531
532        match address {
533            0x4000060 => self.psg.write_sound1cnt_l(value),
534            0x4000061 => {} // SOUND1CNT_L high
535            0x4000062 => self.psg.write_sound1cnt_h_low(value),
536            0x4000063 => self.psg.write_sound1cnt_h_high(value),
537            0x4000064 => self.psg.write_sound1cnt_x_low(value),
538            0x4000065 => self.psg.write_sound1cnt_x_high(value),
539            0x4000068 => self.psg.write_sound2cnt_l_low(value),
540            0x4000069 => self.psg.write_sound2cnt_l_high(value),
541            0x400006C => self.psg.write_sound2cnt_h_low(value),
542            0x400006D => self.psg.write_sound2cnt_h_high(value),
543            0x4000070 => self.psg.write_sound3cnt_l(value),
544            0x4000071 => {} // SOUND3CNT_L high
545            0x4000072 => self.psg.write_sound3cnt_h_low(value),
546            0x4000073 => self.psg.write_sound3cnt_h_high(value),
547            0x4000074 => self.psg.write_sound3cnt_x_low(value),
548            0x4000075 => self.psg.write_sound3cnt_x_high(value),
549            0x4000078 => self.psg.write_sound4cnt_l_low(value),
550            0x4000079 => self.psg.write_sound4cnt_l_high(value),
551            0x400007C => self.psg.write_sound4cnt_h_low(value),
552            0x400007D => self.psg.write_sound4cnt_h_high(value),
553            0x4000080 => self.psg.write_soundcnt_l_low(value),
554            0x4000081 => self.psg.write_soundcnt_l_high(value),
555            0x4000082 => self.write_soundcnt_h_low(value),
556            0x4000083 => self.write_soundcnt_h_high(value),
557            0x4000084 => self.write_soundcnt_x(value),
558            0x4000085 => {} // SOUNDCNT_X high
559            0x4000088 => self.write_soundbias_low(value),
560            0x4000089 => self.write_soundbias_high(value),
561            0x4000090..=0x400009F => self.psg.write_wave_ram(address, value),
562            0x40000A0..=0x40000A3 => self.pcm_a.fifo.push_byte(address, value),
563            0x40000A4..=0x40000A7 => self.pcm_b.fifo.push_byte(address, value),
564            0x40000A8..=0x40000AF => {} // Unused
565            _ => {
566                log::debug!("Unimplemented APU register write: {address:08X} {value:02X}");
567            }
568        }
569    }
570
571    pub fn write_register_halfword(&mut self, address: u32, value: u16) {
572        match address & !1 {
573            0x40000A0 | 0x40000A2 => self.pcm_a.fifo.push_halfword(address, value),
574            0x40000A4 | 0x40000A6 => self.pcm_b.fifo.push_halfword(address, value),
575            address => {
576                let [lsb, msb] = value.to_le_bytes();
577                self.write_register(address, lsb);
578                self.write_register(address | 1, msb);
579            }
580        }
581    }
582
583    pub fn write_register_word(&mut self, address: u32, value: u32) {
584        match address & !3 {
585            0x40000A0 => self.pcm_a.fifo.push(value),
586            0x40000A4 => self.pcm_b.fifo.push(value),
587            address => {
588                let bytes = value.to_le_bytes();
589                for (i, byte) in bytes.into_iter().enumerate() {
590                    let i = i as u32;
591                    self.write_register(address | i, byte);
592                }
593            }
594        }
595    }
596
597    // $4000082: SOUNDCNT_H low byte (GBA-specific volume control)
598    fn write_soundcnt_h_low(&mut self, value: u8) {
599        self.psg_volume = value & 3;
600
601        // PSG volume 3 "prohibited" functions same as 0 (tested on hardware)
602        self.psg_volume_shift = 2 - (self.psg_volume % 3);
603
604        self.pcm_a.volume_shift = 1 - ((value >> 2) & 1);
605        self.pcm_b.volume_shift = 1 - ((value >> 3) & 1);
606
607        log::trace!("SOUNDCNT_H low write: {value:02X}");
608        log::trace!("  PSG volume: {}%", 100 >> self.psg_volume_shift);
609        log::trace!("  PCM A volume: {}%", 100 >> self.pcm_a.volume_shift);
610        log::trace!("  PCM B volume: {}%", 100 >> self.pcm_b.volume_shift);
611    }
612
613    // $4000082: SOUNDCNT_H low byte (GBA-specific volume control)
614    fn read_soundcnt_h_low(&self) -> u8 {
615        (self.psg_volume)
616            | ((1 - self.pcm_a.volume_shift) << 2)
617            | ((1 - self.pcm_b.volume_shift) << 3)
618    }
619
620    // $4000083: SOUNDCNT_H high byte (Direct Sound mixing and timer control)
621    fn write_soundcnt_h_high(&mut self, value: u8) {
622        self.pcm_a.r_enabled = value.bit(0);
623        self.pcm_a.l_enabled = value.bit(1);
624        self.pcm_a.timer = DirectSoundTimer::from_bit(value.bit(2));
625
626        if value.bit(3) {
627            self.pcm_a.reset_fifo();
628        }
629
630        self.pcm_b.r_enabled = value.bit(4);
631        self.pcm_b.l_enabled = value.bit(5);
632        self.pcm_b.timer = DirectSoundTimer::from_bit(value.bit(6));
633
634        if value.bit(7) {
635            self.pcm_b.reset_fifo();
636        }
637
638        self.resampler.update_pcm_a_frequency(self.timer_frequencies[self.pcm_a.timer as usize]);
639        self.resampler.update_pcm_b_frequency(self.timer_frequencies[self.pcm_b.timer as usize]);
640
641        log::trace!("SOUNDCNT_H high write: {value:02X}");
642        log::trace!("  PCM A stereo enabled: [{}, {}]", self.pcm_a.l_enabled, self.pcm_a.r_enabled);
643        log::trace!("  PCM A timer: {:?}", self.pcm_a.timer);
644        log::trace!("  PCM A reset: {}", value.bit(3));
645        log::trace!("  PCM B stereo enabled: [{}, {}]", self.pcm_b.l_enabled, self.pcm_b.r_enabled);
646        log::trace!("  PCM B timer: {:?}", self.pcm_b.timer);
647        log::trace!("  PCM B reset: {}", value.bit(7));
648    }
649
650    // $4000083: SOUNDCNT_H high byte (Direct Sound mixing and timer control)
651    fn read_soundcnt_h_high(&self) -> u8 {
652        u8::from(self.pcm_a.r_enabled)
653            | (u8::from(self.pcm_a.l_enabled) << 1)
654            | ((self.pcm_a.timer as u8) << 2)
655            | (u8::from(self.pcm_b.r_enabled) << 4)
656            | (u8::from(self.pcm_b.l_enabled) << 5)
657            | ((self.pcm_b.timer as u8) << 6)
658    }
659
660    // $4000084: SOUNDCNT_X / NR52 (channel enabled status, enable/disable APU)
661    fn write_soundcnt_x(&mut self, value: u8) {
662        self.enabled = value.bit(7);
663
664        if !self.enabled {
665            // Disabling the APU resets all PSG registers to 0
666            self.psg.disable();
667            self.pcm_a.reset_fifo();
668            self.pcm_b.reset_fifo();
669            self.pcm_a.current_sample = 0;
670            self.pcm_b.current_sample = 0;
671        }
672
673        log::trace!("SOUNDCNT_X write: {value:02X}");
674        log::trace!("  APU enabled: {}", self.enabled);
675    }
676
677    // $4000084: SOUNDCNT_X / NR52 (channel enabled status, enable/disable APU)
678    fn read_soundcnt_x(&self) -> u8 {
679        self.psg.read_soundcnt_x(self.enabled)
680    }
681
682    // $4000088: SOUNDBIAS low byte (sound bias lowest 7 bits)
683    fn write_soundbias_low(&mut self, value: u8) {
684        self.pwm.sound_bias = (self.pwm.sound_bias & !0xFF) | i16::from(value & !1);
685
686        log::trace!("SOUNDBIAS low write: {value:02X}");
687        log::trace!("  PWM sound bias: {:03X}", self.pwm.sound_bias);
688    }
689
690    // $4000088: SOUNDBIAS low byte (sound bias lowest 7 bits)
691    fn read_soundbias_low(&self) -> u8 {
692        self.pwm.sound_bias as u8
693    }
694
695    // $4000089: SOUNDBIAS high byte (PWM sampling cycle, sound bias highest 2 bits)
696    fn write_soundbias_high(&mut self, value: u8) {
697        self.pwm.sound_bias = (self.pwm.sound_bias & 0xFF) | (i16::from(value & 3) << 8);
698        self.pwm.clock_shift = PwmClockShift::from_bits(value >> 6);
699
700        self.resampler.update_source_frequency(self.pwm.clock_shift);
701
702        log::trace!("SOUNDBIAS high write: {value:02X}");
703        log::trace!("  PWM sound bias: {:03X}", self.pwm.sound_bias);
704        log::trace!("  PWM sample rate: {} Hz", self.pwm.clock_shift.source_frequency());
705    }
706
707    // $4000089: SOUNDBIAS high byte (PWM sampling cycle, sound bias highest 2 bits)
708    fn read_soundbias_high(&self) -> u8 {
709        ((self.pwm.sound_bias >> 8) as u8) | ((self.pwm.clock_shift as u8) << 6)
710    }
711
712    pub fn update_output_frequency(&mut self, output_frequency: u64) {
713        self.resampler.update_output_frequency(output_frequency);
714    }
715
716    pub fn drain_audio_output<A: AudioOutput>(
717        &mut self,
718        audio_output: &mut A,
719    ) -> Result<(), A::Err> {
720        self.resampler.drain_audio_output(
721            audio_output,
722            [self.pcm_a.volume_shift != 0, self.pcm_b.volume_shift != 0],
723            self.psg_volume_shift,
724            [self.pcm_a.l_enabled, self.pcm_a.r_enabled],
725            [self.pcm_b.l_enabled, self.pcm_b.r_enabled],
726        )
727    }
728
729    pub fn reload_config(&mut self, config: GbaAudioConfig) {
730        let prev_interpolation = self.config.audio_interpolation;
731        self.config = config;
732
733        if prev_interpolation != self.config.audio_interpolation {
734            self.resampler = AudioResampler::new(
735                self.config,
736                self.pwm.clock_shift,
737                self.output_frequency,
738                [
739                    self.timer_frequencies[self.pcm_a.timer as usize],
740                    self.timer_frequencies[self.pcm_b.timer as usize],
741                ],
742            );
743        } else if let AudioResampler::Interpolating(resampler) = &mut self.resampler {
744            resampler.update_psg_low_pass(config.psg_low_pass);
745        }
746    }
747
748    pub fn notify_timer_frequency_update(&mut self, timer_idx: u8, frequency: Option<f64>) {
749        self.timer_frequencies[timer_idx as usize] = frequency;
750
751        if self.pcm_a.timer as u8 == timer_idx {
752            self.resampler.update_pcm_a_frequency(frequency);
753        }
754
755        if self.pcm_b.timer as u8 == timer_idx {
756            self.resampler.update_pcm_b_frequency(frequency);
757        }
758    }
759}