1//! GBA APU (audio processing unit) 2//! 3//! Contains the 4 Game Boy Color APU channels (slightly modified) plus two 8-bit PCM channels (Direct Sound) 4 5mod audio; 6mod psg; 7 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]); 25 26// 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} 36 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}