From 763ee217d8fa42cbf482b553c34302ea4df1403a Mon Sep 17 00:00:00 2001 From: Yonatan Goldschmidt Date: Thu, 28 Nov 2019 17:56:53 +0200 Subject: [PATCH] Add WIP sound controller and backend Former-commit-id: 50e5bb71a620ac138a880872ae1f13f1c0c0604c --- src/backend/cpal_backend.rs | 57 ++++++++++ src/core/gba.rs | 4 + src/core/iodev.rs | 33 +++--- src/core/mod.rs | 1 + src/core/sound/mod.rs | 212 ++++++++++++++++++++++++++++++++++++ 5 files changed, 293 insertions(+), 14 deletions(-) create mode 100644 src/backend/cpal_backend.rs create mode 100644 src/core/sound/mod.rs diff --git a/src/backend/cpal_backend.rs b/src/backend/cpal_backend.rs new file mode 100644 index 0000000..64c9cdc --- /dev/null +++ b/src/backend/cpal_backend.rs @@ -0,0 +1,57 @@ +use std::thread; + +extern crate cpal; + +use cpal::traits::{DeviceTrait, EventLoopTrait, HostTrait}; +use cpal::EventLoop; + +pub struct CpalSoundBackend { + event_loop: EventLoop, + sample_rate: usize, + channels: usize, +} + +impl CpalSoundBackend { + pub fn new() -> CpalSoundBackend { + let host = cpal::default_host(); + let device = host.default_output_device().expect("failed to find a default output device"); + let format = device.default_output_format()?; + let event_loop = host.event_loop(); + let stream_id = event_loop.build_output_stream(&device, &format)?; + + event_loop.play_stream(stream_id.clone())?; + + CpalSoundBackend { + event_loop: event_loop, + sample_rate: format.sample_rate.0, + channels: format.channels, + } + } + + pub fn start(&self) { + thread::spawn(move || { + self.event_loop.run(move |id, result| { + let data = match result { + Ok(data) => data, + Err(err) => { + println!("an error occurred on stream {:?}: {}", id, err); + return; + } + }; + + match data { + cpal::StreamData::Output { buffer: cpal::UnknownTypeOutputBuffer::F32(mut buffer) } => { + for sample in buffer.chunks_mut(format.channels as usize) { + // TODO get samples from SoundController + sample[0] = 0.0; + sample[1] = 0.0; + } + }, + _ => { + panic!("expected F32"); + }, + } + }); + }); + } +} diff --git a/src/core/gba.rs b/src/core/gba.rs index cd89c43..7b42cc0 100644 --- a/src/core/gba.rs +++ b/src/core/gba.rs @@ -20,6 +20,8 @@ impl GameBoyAdvance { cpu: Core, bios_rom: Vec, gamepak: Cartridge, + // TODO rename this "graphics backend" + // TODO add sound backend backend: Box, ) -> GameBoyAdvance { let io = IoDevices::new(); @@ -107,5 +109,7 @@ impl GameBoyAdvance { } io.intc.request_irqs(irqs); + + io.sound.update(self.cpu.cycles); } } diff --git a/src/core/iodev.rs b/src/core/iodev.rs index af21a7a..ee5fb7a 100644 --- a/src/core/iodev.rs +++ b/src/core/iodev.rs @@ -4,6 +4,7 @@ use super::gpu::regs::WindowFlags; use super::gpu::*; use super::interrupt::InterruptController; use super::keypad; +use super::sound::SoundController; use super::sysbus::BoxedMemory; use super::timer::Timers; @@ -20,6 +21,7 @@ pub enum HaltState { pub struct IoDevices { pub intc: InterruptController, pub gpu: Gpu, + pub sound: SoundController, pub timers: Timers, pub dmac: DmaController, pub keyinput: u16, @@ -35,6 +37,7 @@ impl IoDevices { pub fn new() -> IoDevices { IoDevices { gpu: Gpu::new(), + sound: SoundController::new(), timers: Timers::new(), dmac: DmaController::new(), intc: InterruptController::new(), @@ -105,15 +108,15 @@ impl Bus for IoDevices { REG_POSTFLG => io.post_boot_flag as u16, REG_HALTCNT => 0, REG_KEYINPUT => io.keyinput as u16, - REG_SOUND1CNT_L..=DMA_BASE => { - println!( - "Unimplemented read from {:x} {}", - io_addr, - io_reg_string(io_addr) - ); - 0 - } + + REG_SOUND1CNT_L..=DMA_BASE => io.sound.handle_read(io_addr), + _ => { + // println!( + // "Unimplemented read from {:x} {}", + // io_addr, + // io_reg_string(io_addr) + // ); 0 } } @@ -256,14 +259,16 @@ impl Bus for IoDevices { io.haltcnt = HaltState::Halt; } } + REG_SOUND1CNT_L..=DMA_BASE => { - println!( - "Unimplemented write to {:x} {}", - io_addr, - io_reg_string(io_addr) - ); + io.sound.handle_write(io_addr, value); } _ => { + // println!( + // "Unimplemented write to {:x} {}", + // io_addr, + // io_reg_string(io_addr) + // ); } } } @@ -410,7 +415,7 @@ pub mod consts { pub const REG_HALTCNT: Addr = 0x0400_0301; // 1 W Undocumented - Power Down Control } -fn io_reg_string(addr: u32) -> &'static str { +pub fn io_reg_string(addr: u32) -> &'static str { match addr { REG_DISPCNT => "REG_DISPCNT", REG_DISPSTAT => "REG_DISPSTAT", diff --git a/src/core/mod.rs b/src/core/mod.rs index 124debd..31160a7 100644 --- a/src/core/mod.rs +++ b/src/core/mod.rs @@ -1,6 +1,7 @@ pub mod arm7tdmi; pub mod cartridge; pub mod gpu; +pub mod sound; pub mod sysbus; pub use sysbus::SysBus; pub mod interrupt; diff --git a/src/core/sound/mod.rs b/src/core/sound/mod.rs new file mode 100644 index 0000000..c3da82a --- /dev/null +++ b/src/core/sound/mod.rs @@ -0,0 +1,212 @@ +use bit::BitIndex; + +use super::iodev::consts::*; +use super::iodev::io_reg_string; + +const DMG_RATIOS: [f32; 4] = [0.25, 0.5, 1.0, 0.0]; +const DUTY_RATIOS: [f32; 4] = [0.125, 0.25, 0.5, 0.75]; + +#[derive(Debug)] +pub struct SoundController { + sample_rate_to_cpu_freq: usize, // how many "cycles" are a sample? + last_sample_cycles: usize, // cycles count when we last provided a new sample. + + mse: bool, + + left_volume: usize, + left_sqr1: bool, + left_sqr2: bool, + left_wave: bool, + left_noise: bool, + + right_volume: usize, + right_sqr1: bool, + right_sqr2: bool, + right_wave: bool, + right_noise: bool, + + dmg_volume_ratio: f32, + + sqr1_rate: usize, + sqr1_timed: bool, + sqr1_length: f32, + sqr1_duty: f32, + sqr1_step_time: usize, + sqr1_step_increase: bool, + sqr1_initial_vol: usize, + sqr1_cur_vol: usize, +} + +impl SoundController { + pub fn new() -> SoundController { + SoundController { + sample_rate_to_cpu_freq: 12345, + last_sample_cycles: 0, + mse: false, + left_volume: 0, + left_sqr1: false, + left_sqr2: false, + left_wave: false, + left_noise: false, + right_volume: 0, + right_sqr1: false, + right_sqr2: false, + right_wave: false, + right_noise: false, + dmg_volume_ratio: 0.0, + sqr1_rate: 0, + sqr1_timed: false, + sqr1_length: 0.0, + sqr1_duty: DUTY_RATIOS[0], + sqr1_step_time: 0, + sqr1_step_increase: false, + sqr1_initial_vol: 0, + sqr1_cur_vol: 0, + } + } + + pub fn handle_read(&self, io_addr: u32) -> u16 { + let value = match io_addr { + REG_SOUNDCNT_X => cbit(7, self.mse), + REG_SOUNDCNT_L => { + self.left_volume as u16 + | (self.right_volume as u16) << 4 + | cbit(8, self.left_sqr1) + | cbit(9, self.left_sqr2) + | cbit(10, self.left_wave) + | cbit(11, self.left_noise) + | cbit(12, self.right_sqr1) + | cbit(13, self.right_sqr2) + | cbit(14, self.right_wave) + | cbit(15, self.right_noise) + } + + REG_SOUNDCNT_H => DMG_RATIOS + .iter() + .position(|&f| f == self.dmg_volume_ratio) + .expect("bad dmg_volume_ratio!") as u16, + + _ => { + println!( + "Unimplemented read from {:x} {}", + io_addr, + io_reg_string(io_addr) + ); + 0 + } + }; + println!( + "Read {} ({:08x}) = {:04x}", + io_reg_string(io_addr), + io_addr, + value + ); + value + } + + pub fn handle_write(&mut self, io_addr: u32, value: u16) { + println!( + "Write {} ({:08x}) = {:04x}", + io_reg_string(io_addr), + io_addr, + value + ); + if io_addr == REG_SOUNDCNT_X { + if value & bit(7) != 0 { + if !self.mse { + println!("MSE enabled!"); + self.mse = true; + } + } else { + if self.mse { + println!("MSE disabled!"); + self.mse = false; + } + } + + // other fields of this register are read-only anyway, ignore them. + return; + } + + if !self.mse { + println!("MSE disabled, refusing to write"); + return; + } + + match io_addr { + REG_SOUNDCNT_L => { + self.left_volume = value.bit_range(0..2) as usize; + self.right_volume = value.bit_range(4..6) as usize; + self.left_sqr1 = value.bit(8); + self.left_sqr2 = value.bit(9); + self.left_wave = value.bit(10); + self.left_noise = value.bit(11); + self.right_sqr1 = value.bit(12); + self.right_sqr2 = value.bit(13); + self.right_wave = value.bit(14); + self.right_noise = value.bit(15); + } + + REG_SOUNDCNT_H => { + self.dmg_volume_ratio = DMG_RATIOS[value.bit_range(0..1) as usize]; + if value.bit_range(2..15) != 0 { + println!( + "unsupported bits in REG_SOUNDCNT_H, {:04x}", + value.bit_range(2..15) + ); + } + } + + REG_SOUND1CNT_H => { + self.sqr1_length = (64 - value.bit_range(0..5) as usize) as f32 / 256.0; + self.sqr1_duty = DUTY_RATIOS[value.bit_range(6..7) as usize]; + self.sqr1_step_time = value.bit_range(8..10) as usize; + self.sqr1_step_increase = value.bit(11); + self.sqr1_initial_vol = value.bit_range(12..15) as usize; + } + + REG_SOUND1CNT_X => { + self.sqr1_rate = value.bit_range(0..10) as usize; + self.sqr1_timed = value.bit(14); + if value.bit(15) { + self.sqr1_cur_vol = self.sqr1_initial_vol; + } + } + + _ => { + println!( + "Unimplemented write to {:x} {}", + io_addr, + io_reg_string(io_addr) + ); + } + } + } + + pub fn update(&mut self, cycles: usize) { + if cycles - self.last_sample_cycles >= self.sample_rate_to_cpu_freq { + self.last_sample_cycles += self.sample_rate_to_cpu_freq; + println!("{:?}", cycles); + } + } +} + +// TODO move +fn cbit(idx: u8, value: bool) -> u16 { + if value { + 1 << idx + } else { + 0 + } +} + +// TODO mvoe +fn bit(idx: u8) -> u16 { + 1 << idx +} + +fn rate_to_freq(rate: u16) -> usize { + assert!(rate < 2048); + + (2 << 17) as usize / (2048 - rate) as usize +}