Add WIP sound controller and backend
Former-commit-id: 50e5bb71a620ac138a880872ae1f13f1c0c0604c
This commit is contained in:
parent
7cfa4bb07d
commit
763ee217d8
57
src/backend/cpal_backend.rs
Normal file
57
src/backend/cpal_backend.rs
Normal 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");
|
||||||
|
},
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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",
|
||||||
|
|
|
@ -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
212
src/core/sound/mod.rs
Normal 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
|
||||||
|
}
|
Reference in a new issue