Start working on DMA sound
Former-commit-id: 066210a86a7836b6ae1dfd5ce229d050cbe00ca4
This commit is contained in:
parent
38e504515b
commit
70cb99161d
|
@ -2,7 +2,7 @@ use std::collections::VecDeque;
|
||||||
|
|
||||||
use super::arm7tdmi::{Addr, Bus};
|
use super::arm7tdmi::{Addr, Bus};
|
||||||
use super::sysbus::SysBus;
|
use super::sysbus::SysBus;
|
||||||
use super::{Interrupt, IrqBitmask, SyncedIoDevice};
|
use super::{Interrupt, IrqBitmask};
|
||||||
|
|
||||||
use num::FromPrimitive;
|
use num::FromPrimitive;
|
||||||
|
|
||||||
|
@ -27,6 +27,7 @@ pub struct DmaChannel {
|
||||||
running: bool,
|
running: bool,
|
||||||
cycles: usize,
|
cycles: usize,
|
||||||
start_cycles: usize,
|
start_cycles: usize,
|
||||||
|
fifo_mode: bool,
|
||||||
irq: Interrupt,
|
irq: Interrupt,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -52,6 +53,7 @@ impl DmaChannel {
|
||||||
ctrl: DmaChannelCtrl(0),
|
ctrl: DmaChannelCtrl(0),
|
||||||
cycles: 0,
|
cycles: 0,
|
||||||
start_cycles: 0,
|
start_cycles: 0,
|
||||||
|
fifo_mode: false,
|
||||||
internal: Default::default(),
|
internal: Default::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -88,36 +90,23 @@ impl DmaChannel {
|
||||||
|
|
||||||
pub fn write_dma_ctrl(&mut self, value: u16) -> bool {
|
pub fn write_dma_ctrl(&mut self, value: u16) -> bool {
|
||||||
let ctrl = DmaChannelCtrl(value);
|
let ctrl = DmaChannelCtrl(value);
|
||||||
|
let timing = ctrl.timing();
|
||||||
let mut start_immediately = false;
|
let mut start_immediately = false;
|
||||||
if ctrl.is_enabled() && !self.ctrl.is_enabled() {
|
if ctrl.is_enabled() && !self.ctrl.is_enabled() {
|
||||||
self.start_cycles = self.cycles;
|
self.start_cycles = self.cycles;
|
||||||
self.running = true;
|
self.running = true;
|
||||||
start_immediately = ctrl.timing() == 0;
|
start_immediately = timing == 0;
|
||||||
self.internal.src_addr = self.src;
|
self.internal.src_addr = self.src;
|
||||||
self.internal.dst_addr = self.dst;
|
self.internal.dst_addr = self.dst;
|
||||||
self.internal.count = self.wc;
|
self.internal.count = self.wc;
|
||||||
|
self.fifo_mode = timing == 3 && (self.id == 0 || self.id == 1);
|
||||||
}
|
}
|
||||||
self.ctrl = ctrl;
|
self.ctrl = ctrl;
|
||||||
return start_immediately;
|
return start_immediately;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn xfer(&mut self, sb: &mut SysBus, irqs: &mut IrqBitmask) {
|
#[inline]
|
||||||
let word_size = if self.ctrl.is_32bit() { 4 } else { 2 };
|
fn xfer_adj_addrs(&mut self, word_size: u32) {
|
||||||
let count = match self.internal.count {
|
|
||||||
0 => match self.id {
|
|
||||||
3 => 0x1_0000,
|
|
||||||
_ => 0x0_4000,
|
|
||||||
},
|
|
||||||
_ => self.internal.count,
|
|
||||||
};
|
|
||||||
for _ in 0..count {
|
|
||||||
if word_size == 4 {
|
|
||||||
let w = sb.read_32(self.internal.src_addr);
|
|
||||||
sb.write_32(self.internal.dst_addr, w)
|
|
||||||
} else {
|
|
||||||
let hw = sb.read_16(self.internal.src_addr);
|
|
||||||
sb.write_16(self.internal.dst_addr, hw)
|
|
||||||
}
|
|
||||||
match self.ctrl.src_adj() {
|
match self.ctrl.src_adj() {
|
||||||
/* Increment */ 0 => self.internal.src_addr += word_size,
|
/* Increment */ 0 => self.internal.src_addr += word_size,
|
||||||
/* Decrement */ 1 => self.internal.src_addr -= word_size,
|
/* Decrement */ 1 => self.internal.src_addr -= word_size,
|
||||||
|
@ -131,6 +120,38 @@ impl DmaChannel {
|
||||||
_ => panic!("forbidden DMA dest address adjustment"),
|
_ => panic!("forbidden DMA dest address adjustment"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn xfer(&mut self, sb: &mut SysBus, irqs: &mut IrqBitmask) {
|
||||||
|
let word_size = if self.ctrl.is_32bit() { 4 } else { 2 };
|
||||||
|
let count = match self.internal.count {
|
||||||
|
0 => match self.id {
|
||||||
|
3 => 0x1_0000,
|
||||||
|
_ => 0x0_4000,
|
||||||
|
},
|
||||||
|
_ => self.internal.count,
|
||||||
|
};
|
||||||
|
let fifo_mode = self.fifo_mode;
|
||||||
|
|
||||||
|
if fifo_mode {
|
||||||
|
println!("FIFO Tranfer");
|
||||||
|
for _ in 0..count {
|
||||||
|
let v = sb.read_16(self.internal.src_addr);
|
||||||
|
sb.write_16(self.internal.dst_addr, v);
|
||||||
|
self.internal.src_addr += 2;
|
||||||
|
}
|
||||||
|
} else if word_size == 4 {
|
||||||
|
for _ in 0..count {
|
||||||
|
let w = sb.read_32(self.internal.src_addr);
|
||||||
|
sb.write_32(self.internal.dst_addr, w);
|
||||||
|
self.xfer_adj_addrs(word_size);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for _ in 0..count {
|
||||||
|
let hw = sb.read_16(self.internal.src_addr);
|
||||||
|
sb.write_16(self.internal.dst_addr, hw);
|
||||||
|
self.xfer_adj_addrs(word_size)
|
||||||
|
}
|
||||||
|
}
|
||||||
if self.ctrl.is_triggering_irq() {
|
if self.ctrl.is_triggering_irq() {
|
||||||
irqs.add_irq(self.irq);
|
irqs.add_irq(self.irq);
|
||||||
}
|
}
|
||||||
|
@ -210,6 +231,17 @@ impl DmaController {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn notify_sound_fifo(&mut self, fifo_addr: u32) {
|
||||||
|
for i in 1..=2 {
|
||||||
|
if self.channels[i].ctrl.is_enabled()
|
||||||
|
&& self.channels[i].ctrl.timing() == 3
|
||||||
|
&& self.channels[i].dst == fifo_addr
|
||||||
|
{
|
||||||
|
self.xfers_queue.push_back(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bitfield! {
|
bitfield! {
|
||||||
|
|
|
@ -8,6 +8,7 @@ use super::gpu::*;
|
||||||
use super::interrupt::*;
|
use super::interrupt::*;
|
||||||
use super::iodev::*;
|
use super::iodev::*;
|
||||||
use super::sysbus::SysBus;
|
use super::sysbus::SysBus;
|
||||||
|
use super::timer::TimerEvent;
|
||||||
|
|
||||||
use super::SyncedIoDevice;
|
use super::SyncedIoDevice;
|
||||||
|
|
||||||
|
@ -113,7 +114,8 @@ where
|
||||||
0
|
0
|
||||||
};
|
};
|
||||||
|
|
||||||
io.timers.step(cycles, &mut self.sysbus, &mut irqs);
|
io.timers.tick(cycles, &mut self.sysbus, &mut irqs);
|
||||||
|
|
||||||
if let Some(new_gpu_state) = io.gpu.step(cycles, &mut self.sysbus, &mut irqs) {
|
if let Some(new_gpu_state) = io.gpu.step(cycles, &mut self.sysbus, &mut irqs) {
|
||||||
match new_gpu_state {
|
match new_gpu_state {
|
||||||
GpuState::VBlank => {
|
GpuState::VBlank => {
|
||||||
|
|
51
src/core/sound/fifo.rs
Normal file
51
src/core/sound/fifo.rs
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
// TODO write tests or replace with a crate
|
||||||
|
|
||||||
|
const SOUND_FIFO_CAPACITY: usize = 32;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct SoundFifo {
|
||||||
|
wr_pos: usize,
|
||||||
|
rd_pos: usize,
|
||||||
|
count: usize,
|
||||||
|
data: [i8; SOUND_FIFO_CAPACITY],
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SoundFifo {
|
||||||
|
pub fn new() -> SoundFifo {
|
||||||
|
SoundFifo {
|
||||||
|
wr_pos: 0,
|
||||||
|
rd_pos: 0,
|
||||||
|
count: 0,
|
||||||
|
data: [0; SOUND_FIFO_CAPACITY],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn write(&mut self, value: i8) {
|
||||||
|
if self.count >= SOUND_FIFO_CAPACITY {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
self.data[self.wr_pos] = value;
|
||||||
|
self.wr_pos = (self.wr_pos + 1) % SOUND_FIFO_CAPACITY;
|
||||||
|
self.count += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn read(&mut self) -> i8 {
|
||||||
|
if self.count == 0 {
|
||||||
|
return 0;
|
||||||
|
};
|
||||||
|
let value = self.data[self.rd_pos];
|
||||||
|
self.rd_pos = (self.rd_pos + 1) % SOUND_FIFO_CAPACITY;
|
||||||
|
self.count -= 1;
|
||||||
|
value
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn count(&self) -> usize {
|
||||||
|
self.count
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn reset(&mut self) {
|
||||||
|
self.wr_pos = 0;
|
||||||
|
self.rd_pos = 0;
|
||||||
|
self.count = 0;
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,11 +1,41 @@
|
||||||
use bit::BitIndex;
|
use bit::BitIndex;
|
||||||
|
|
||||||
|
use super::dma::DmaController;
|
||||||
use super::iodev::consts::*;
|
use super::iodev::consts::*;
|
||||||
use super::iodev::io_reg_string;
|
use super::iodev::io_reg_string;
|
||||||
|
|
||||||
|
mod fifo;
|
||||||
|
use fifo::SoundFifo;
|
||||||
|
|
||||||
const DMG_RATIOS: [f32; 4] = [0.25, 0.5, 1.0, 0.0];
|
const DMG_RATIOS: [f32; 4] = [0.25, 0.5, 1.0, 0.0];
|
||||||
|
const DMA_RATIOS: [f32; 2] = [0.5, 1.0];
|
||||||
|
const DMA_TIMERS: [usize; 2] = [0, 1];
|
||||||
const DUTY_RATIOS: [f32; 4] = [0.125, 0.25, 0.5, 0.75];
|
const DUTY_RATIOS: [f32; 4] = [0.125, 0.25, 0.5, 0.75];
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct NoiseChannel {}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct DmaSound {
|
||||||
|
volume: f32,
|
||||||
|
enable_right: bool,
|
||||||
|
enable_left: bool,
|
||||||
|
timer_select: usize,
|
||||||
|
fifo: SoundFifo<i8>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for DmaSound {
|
||||||
|
fn default() -> DmaSound {
|
||||||
|
DmaSound {
|
||||||
|
volume: DMA_RATIOS[0],
|
||||||
|
enable_right: false,
|
||||||
|
enable_left: false,
|
||||||
|
timer_select: 0,
|
||||||
|
fifo: SoundFifo::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct SoundController {
|
pub struct SoundController {
|
||||||
sample_rate_to_cpu_freq: usize, // how many "cycles" are a sample?
|
sample_rate_to_cpu_freq: usize, // how many "cycles" are a sample?
|
||||||
|
@ -35,6 +65,9 @@ pub struct SoundController {
|
||||||
sqr1_step_increase: bool,
|
sqr1_step_increase: bool,
|
||||||
sqr1_initial_vol: usize,
|
sqr1_initial_vol: usize,
|
||||||
sqr1_cur_vol: usize,
|
sqr1_cur_vol: usize,
|
||||||
|
|
||||||
|
sound_a: DmaSound,
|
||||||
|
sound_b: DmaSound,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SoundController {
|
impl SoundController {
|
||||||
|
@ -62,6 +95,8 @@ impl SoundController {
|
||||||
sqr1_step_increase: false,
|
sqr1_step_increase: false,
|
||||||
sqr1_initial_vol: 0,
|
sqr1_initial_vol: 0,
|
||||||
sqr1_cur_vol: 0,
|
sqr1_cur_vol: 0,
|
||||||
|
sound_a: Default::default(),
|
||||||
|
sound_b: Default::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -81,10 +116,26 @@ impl SoundController {
|
||||||
| cbit(15, self.right_noise)
|
| cbit(15, self.right_noise)
|
||||||
}
|
}
|
||||||
|
|
||||||
REG_SOUNDCNT_H => DMG_RATIOS
|
REG_SOUNDCNT_H => {
|
||||||
|
DMG_RATIOS
|
||||||
.iter()
|
.iter()
|
||||||
.position(|&f| f == self.dmg_volume_ratio)
|
.position(|&f| f == self.dmg_volume_ratio)
|
||||||
.expect("bad dmg_volume_ratio!") as u16,
|
.expect("bad dmg_volume_ratio!") as u16
|
||||||
|
| DMA_RATIOS
|
||||||
|
.iter()
|
||||||
|
.position(|&f| f == self.sound_a.volume)
|
||||||
|
.unwrap() as u16
|
||||||
|
| DMA_RATIOS
|
||||||
|
.iter()
|
||||||
|
.position(|&f| f == self.sound_b.volume)
|
||||||
|
.unwrap() as u16
|
||||||
|
| cbit(8, self.sound_a.enable_right)
|
||||||
|
| cbit(9, self.sound_a.enable_left)
|
||||||
|
| cbit(10, self.sound_a.timer_select != 0)
|
||||||
|
| cbit(12, self.sound_b.enable_right)
|
||||||
|
| cbit(13, self.sound_b.enable_left)
|
||||||
|
| cbit(14, self.sound_b.timer_select != 0)
|
||||||
|
}
|
||||||
|
|
||||||
_ => {
|
_ => {
|
||||||
println!(
|
println!(
|
||||||
|
@ -105,12 +156,6 @@ impl SoundController {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn handle_write(&mut self, io_addr: u32, value: u16) {
|
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 io_addr == REG_SOUNDCNT_X {
|
||||||
if value & bit(7) != 0 {
|
if value & bit(7) != 0 {
|
||||||
if !self.mse {
|
if !self.mse {
|
||||||
|
@ -149,11 +194,20 @@ impl SoundController {
|
||||||
|
|
||||||
REG_SOUNDCNT_H => {
|
REG_SOUNDCNT_H => {
|
||||||
self.dmg_volume_ratio = DMG_RATIOS[value.bit_range(0..1) as usize];
|
self.dmg_volume_ratio = DMG_RATIOS[value.bit_range(0..1) as usize];
|
||||||
if value.bit_range(2..15) != 0 {
|
self.sound_a.volume = DMA_RATIOS[value.bit(2) as usize];
|
||||||
println!(
|
self.sound_b.volume = DMA_RATIOS[value.bit(3) as usize];
|
||||||
"unsupported bits in REG_SOUNDCNT_H, {:04x}",
|
self.sound_a.enable_right = value.bit(8);
|
||||||
value.bit_range(2..15)
|
self.sound_a.enable_left = value.bit(9);
|
||||||
);
|
self.sound_a.timer_select = DMA_TIMERS[value.bit(10) as usize];
|
||||||
|
self.sound_b.enable_right = value.bit(12);
|
||||||
|
self.sound_b.enable_left = value.bit(13);
|
||||||
|
self.sound_b.timer_select = DMA_TIMERS[value.bit(14) as usize];
|
||||||
|
|
||||||
|
if value.bit(11) {
|
||||||
|
self.sound_a.fifo.reset();
|
||||||
|
}
|
||||||
|
if value.bit(15) {
|
||||||
|
self.sound_b.fifo.reset();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -173,6 +227,16 @@ impl SoundController {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
REG_FIFO_A => {
|
||||||
|
self.sound_a.fifo.write((value & 0xff00 >> 8) as i8);
|
||||||
|
self.sound_a.fifo.write((value & 0xff) as i8);
|
||||||
|
}
|
||||||
|
|
||||||
|
REG_FIFO_B => {
|
||||||
|
self.sound_b.fifo.write((value & 0xff00 >> 8) as i8);
|
||||||
|
self.sound_b.fifo.write((value & 0xff) as i8);
|
||||||
|
}
|
||||||
|
|
||||||
_ => {
|
_ => {
|
||||||
println!(
|
println!(
|
||||||
"Unimplemented write to {:x} {}",
|
"Unimplemented write to {:x} {}",
|
||||||
|
@ -183,10 +247,23 @@ impl SoundController {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn handle_timer_overflow(&mut self, dmac: &mut DmaController, timer_id: usize) {
|
||||||
|
if !self.mse {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// TODO - play sound ?
|
||||||
|
|
||||||
|
if timer_id == self.sound_a.timer_select {
|
||||||
|
dmac.notify_sound_fifo(REG_FIFO_A);
|
||||||
|
}
|
||||||
|
if timer_id == self.sound_b.timer_select {
|
||||||
|
dmac.notify_sound_fifo(REG_FIFO_B);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn update(&mut self, cycles: usize) {
|
pub fn update(&mut self, cycles: usize) {
|
||||||
if cycles - self.last_sample_cycles >= self.sample_rate_to_cpu_freq {
|
while cycles - self.last_sample_cycles >= self.sample_rate_to_cpu_freq {
|
||||||
self.last_sample_cycles += self.sample_rate_to_cpu_freq;
|
self.last_sample_cycles += self.sample_rate_to_cpu_freq;
|
||||||
println!("{:?}", cycles);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,9 +19,9 @@ pub struct Timer {
|
||||||
pub cycles: usize,
|
pub cycles: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub enum TimerAction {
|
pub enum TimerEvent {
|
||||||
Overflow(usize),
|
Overflow(usize, usize),
|
||||||
Increment,
|
Increment(usize),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Timer {
|
impl Timer {
|
||||||
|
@ -49,7 +49,7 @@ impl Timer {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn add_cycles(&mut self, cycles: usize, irqs: &mut IrqBitmask) -> TimerAction {
|
pub fn add_cycles(&mut self, cycles: usize, irqs: &mut IrqBitmask) -> TimerEvent {
|
||||||
let mut num_overflows = 0;
|
let mut num_overflows = 0;
|
||||||
self.cycles += cycles;
|
self.cycles += cycles;
|
||||||
|
|
||||||
|
@ -66,9 +66,9 @@ impl Timer {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if num_overflows > 0 {
|
if num_overflows > 0 {
|
||||||
return TimerAction::Overflow(num_overflows);
|
return TimerEvent::Overflow(self.timer_id, num_overflows);
|
||||||
} else {
|
} else {
|
||||||
return TimerAction::Increment;
|
return TimerEvent::Increment(self.timer_id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -112,31 +112,37 @@ impl Timers {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
impl SyncedIoDevice for Timers {
|
pub fn tick(
|
||||||
fn step(&mut self, cycles: usize, _sb: &mut SysBus, irqs: &mut IrqBitmask) {
|
&mut self,
|
||||||
|
cycles: usize,
|
||||||
|
sb: &mut SysBus,
|
||||||
|
irqs: &mut IrqBitmask,
|
||||||
|
) -> Option<TimerEvent> {
|
||||||
for i in 0..4 {
|
for i in 0..4 {
|
||||||
if self[i].timer_ctl.enabled() && !self[i].timer_ctl.cascade() {
|
if self[i].timer_ctl.enabled() && !self[i].timer_ctl.cascade() {
|
||||||
match self[i].add_cycles(cycles, irqs) {
|
let event = self[i].add_cycles(cycles, irqs);
|
||||||
TimerAction::Overflow(num_overflows) => {
|
match event {
|
||||||
|
TimerEvent::Overflow(_, num_overflows) => {
|
||||||
if self.trace {
|
if self.trace {
|
||||||
println!("TMR{} overflown!", i);
|
println!("TMR{} overflown!", i);
|
||||||
}
|
}
|
||||||
match i {
|
if i != 3 {
|
||||||
3 => {}
|
|
||||||
_ => {
|
|
||||||
let next_i = i + 1;
|
let next_i = i + 1;
|
||||||
if self[next_i].timer_ctl.cascade() {
|
if self[next_i].timer_ctl.cascade() {
|
||||||
self[next_i].add_cycles(num_overflows, irqs);
|
self[next_i].add_cycles(num_overflows, irqs);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if i == 0 || i == 1 {
|
||||||
|
sb.io.sound.handle_timer_overflow(&mut sb.io.dmac, i);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
TimerAction::Increment => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
|
return Some(event);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Reference in a new issue