Add WIP sound controller and backend

Former-commit-id: 50e5bb71a620ac138a880872ae1f13f1c0c0604c
This commit is contained in:
Yonatan Goldschmidt 2019-11-28 17:56:53 +02:00 committed by Michel Heily
parent 7cfa4bb07d
commit 763ee217d8
5 changed files with 293 additions and 14 deletions

View file

@ -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");
},
}
});
});
}
}

View file

@ -20,6 +20,8 @@ impl GameBoyAdvance {
cpu: Core, cpu: Core,
bios_rom: Vec<u8>, bios_rom: Vec<u8>,
gamepak: Cartridge, gamepak: Cartridge,
// TODO rename this "graphics backend"
// TODO add sound backend
backend: Box<dyn EmulatorBackend>, backend: Box<dyn EmulatorBackend>,
) -> GameBoyAdvance { ) -> GameBoyAdvance {
let io = IoDevices::new(); let io = IoDevices::new();
@ -107,5 +109,7 @@ impl GameBoyAdvance {
} }
io.intc.request_irqs(irqs); io.intc.request_irqs(irqs);
io.sound.update(self.cpu.cycles);
} }
} }

View file

@ -4,6 +4,7 @@ use super::gpu::regs::WindowFlags;
use super::gpu::*; use super::gpu::*;
use super::interrupt::InterruptController; use super::interrupt::InterruptController;
use super::keypad; use super::keypad;
use super::sound::SoundController;
use super::sysbus::BoxedMemory; use super::sysbus::BoxedMemory;
use super::timer::Timers; use super::timer::Timers;
@ -20,6 +21,7 @@ pub enum HaltState {
pub struct IoDevices { pub struct IoDevices {
pub intc: InterruptController, pub intc: InterruptController,
pub gpu: Gpu, pub gpu: Gpu,
pub sound: SoundController,
pub timers: Timers, pub timers: Timers,
pub dmac: DmaController, pub dmac: DmaController,
pub keyinput: u16, pub keyinput: u16,
@ -35,6 +37,7 @@ impl IoDevices {
pub fn new() -> IoDevices { pub fn new() -> IoDevices {
IoDevices { IoDevices {
gpu: Gpu::new(), gpu: Gpu::new(),
sound: SoundController::new(),
timers: Timers::new(), timers: Timers::new(),
dmac: DmaController::new(), dmac: DmaController::new(),
intc: InterruptController::new(), intc: InterruptController::new(),
@ -105,15 +108,15 @@ impl Bus for IoDevices {
REG_POSTFLG => io.post_boot_flag as u16, REG_POSTFLG => io.post_boot_flag as u16,
REG_HALTCNT => 0, REG_HALTCNT => 0,
REG_KEYINPUT => io.keyinput as u16, REG_KEYINPUT => io.keyinput as u16,
REG_SOUND1CNT_L..=DMA_BASE => {
println!( REG_SOUND1CNT_L..=DMA_BASE => io.sound.handle_read(io_addr),
"Unimplemented read from {:x} {}",
io_addr,
io_reg_string(io_addr)
);
0
}
_ => { _ => {
// println!(
// "Unimplemented read from {:x} {}",
// io_addr,
// io_reg_string(io_addr)
// );
0 0
} }
} }
@ -256,14 +259,16 @@ impl Bus for IoDevices {
io.haltcnt = HaltState::Halt; io.haltcnt = HaltState::Halt;
} }
} }
REG_SOUND1CNT_L..=DMA_BASE => { REG_SOUND1CNT_L..=DMA_BASE => {
println!( io.sound.handle_write(io_addr, value);
"Unimplemented write to {:x} {}",
io_addr,
io_reg_string(io_addr)
);
} }
_ => { _ => {
// 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 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 { match addr {
REG_DISPCNT => "REG_DISPCNT", REG_DISPCNT => "REG_DISPCNT",
REG_DISPSTAT => "REG_DISPSTAT", REG_DISPSTAT => "REG_DISPSTAT",

View file

@ -1,6 +1,7 @@
pub mod arm7tdmi; pub mod arm7tdmi;
pub mod cartridge; pub mod cartridge;
pub mod gpu; pub mod gpu;
pub mod sound;
pub mod sysbus; pub mod sysbus;
pub use sysbus::SysBus; pub use sysbus::SysBus;
pub mod interrupt; pub mod interrupt;

212
src/core/sound/mod.rs Normal file
View file

@ -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
}