fixed timer_period bug, cleaned up. square channels kind of working. need to figure out how to sample properly.

This commit is contained in:
Theron 2019-12-30 20:10:27 -06:00
parent bf2317dc28
commit 686d45f8d3
4 changed files with 22 additions and 50 deletions

View File

@ -30,7 +30,8 @@ use dmc::DMC;
// TODO: organize APU structs
const FRAME_COUNTER_STEPS: [usize; 5] = [3728, 7456, 11185, 14914, 18640];
const CYCLES_PER_SAMPLE: f32 = 894_886.5/44_100.0; // APU frequency over sample frequency. May need to turn this down slightly as it's outputting less than 44_100Hz.
// const CYCLES_PER_SAMPLE: f32 = 894_886.5/44_100.0; // APU frequency over sample frequency. May need to turn this down slightly as it's outputting less than 44_100Hz.
const CYCLES_PER_SAMPLE: f32 = 19.65;
const LENGTH_COUNTER_TABLE: [u8; 32] = [
10, 254, 20, 2, 40, 4, 80, 6, 160, 8, 60, 10, 14, 12, 26, 14,
12, 16, 24, 18, 48, 20, 96, 22, 192, 24, 72, 26, 16, 28, 32, 30,
@ -111,6 +112,7 @@ impl Apu {
}
pub fn write_reg(&mut self, address: usize, value: u8) {
// println!("writing 0b{:08b} to 0x{:X}", value, address);
match address {
0x4000 => self.square1.write_duty(value),
0x4001 => self.square1.write_sweep(value),

View File

@ -75,7 +75,7 @@ impl Square {
}
// Update volume for this channel
// The mixer receives the current envelope volume except when
self.sample = if self.duty_cycle[self.duty_counter] == 0 // The sequencer output is zero, or
self.sample = if self.duty_cycle[self.duty_counter] == 0 // the sequencer output is zero, or
|| self.sweep_divider > 0x7FF // overflow from the sweep unit's adder is silencing the channel,
|| self.length_counter == 0 // the length counter is zero, or
|| self.timer < 8 { // the timer has a value less than eight.
@ -83,7 +83,6 @@ impl Square {
} else {
self.decay_counter
};
println!("{}", self.sample);
}
pub fn clock_envelope(&mut self) {
@ -101,7 +100,7 @@ impl Square {
pub fn clock_length_counter(&mut self) {
if !(self.length_counter == 0 || self.length_counter_halt) {
self.length_counter -= 1;
}
}
}
fn clock_envelope_divider(&mut self) {
@ -136,7 +135,7 @@ impl Square {
}
// If the divider's counter is zero, the sweep is enabled, and the sweep unit is not muting the channel: The pulse's period is adjusted.
if self.sweep_counter == 0 && self.sweep_enabled && !(self.timer < 8 || self.sweep_divider > 0x7FF) {
self.adjust_sweep();
self.timer_period = self.sweep_divider;
}
// If the divider's counter is zero or the reload flag is true: The counter is set to P and the reload flag is cleared. Otherwise, the counter is decremented.
if self.sweep_counter == 0 || self.sweep_reload {
@ -147,13 +146,9 @@ impl Square {
}
}
fn adjust_sweep(&mut self) {
self.timer_period = self.sweep_divider;
}
// $4000/$4004
pub fn write_duty(&mut self, value: u8) {
// TODO: The duty cycle is changed (see table below), but the sequencer's current position isn't affected.
// The duty cycle is changed (see table below), but the sequencer's current position isn't affected.
self.duty_cycle = DUTY_CYCLE_SEQUENCES[(value >> 6) as usize];
self.length_counter_halt = value & (1<<5) != 0;
self.constant_volume_flag = value & (1<<4) != 0;
@ -175,8 +170,8 @@ impl Square {
// $4002/$4006
pub fn write_timer_low(&mut self, value: u8) {
self.timer &= 0b00000111_00000000; // mask off everything but high 3 bits of 11-bit timer
self.timer |= value as u16; // apply low 8 bits
self.timer_period &= 0b00000111_00000000; // mask off everything but high 3 bits of 11-bit timer
self.timer_period |= value as u16; // apply low 8 bits
}
// $4003/$4007
@ -186,8 +181,8 @@ impl Square {
self.length_counter = super::LENGTH_COUNTER_TABLE[value as usize >> 3];
}
let timer_high = value as u16 & 0b0000_0111;
self.timer &= 0b11111000_11111111; // mask off high 3 bits of 11-bit timer
self.timer |= timer_high << 8; // apply high timer bits in their place
self.timer_period &= 0b11111000_11111111; // mask off high 3 bits of 11-bit timer
self.timer_period |= timer_high << 8; // apply high timer bits in their place
// The sequencer is immediately restarted at the first value of the current sequence. The envelope is also restarted. The period divider is not reset.
self.duty_counter = 0;
self.start = true;

View File

@ -2,28 +2,6 @@ extern crate sdl2;
use sdl2::audio::{AudioCallback, AudioSpecDesired};
// pub struct Speaker {
// buffer: [f32; 4096*4],
// head: usize,
// }
// impl AudioCallback for Speaker {
// type Channel = f32;
// fn callback(&mut self, out: &mut [f32]) {
// for (i, x) in out.iter_mut().enumerate() {
// *x = self.buffer[i+self.head]; // get data from apu
// }
// self.head = (self.head + 4096) % (4096*4)
// }
// }
// impl Speaker {
// pub fn append(&mut self, sample: f32) {
// self.buffer[self.head] = sample;
// self.head = (self.head + 1) % (4096*4);
// }
// }
pub fn initialize(context: &sdl2::Sdl) -> Result<sdl2::audio::AudioQueue<f32>, String> {
let audio_subsystem = context.audio()?;
@ -35,5 +13,3 @@ pub fn initialize(context: &sdl2::Sdl) -> Result<sdl2::audio::AudioQueue<f32>, S
audio_subsystem.open_queue(None, &desired_spec)
}
// problem is: how to get data into callback from outside? can't change its signature. can't sneak it in through struct as struct is consumed by the audio device

View File

@ -37,7 +37,6 @@ fn main() -> Result<(), String> {
// Set up audio
let mut audio_device = audio::initialize(&sdl_context).expect("Could not create audio device");
let mut half_cycle = false;
let mut audio_buffer = Vec::<f32>::new();
audio_device.resume();
// Initialize hardware components
@ -70,19 +69,11 @@ fn main() -> Result<(), String> {
match cpu.apu.clock() {
Some(sample) => {
sps += 1;
// if sample != 0.0 {
// println!("sample {}", sample);
// }
audio_buffer.push(sample)
audio_device.queue(&vec![sample]);
},
None => (),
};
}
if audio_buffer.len() == 44_100 {
// println!("queueing: {:?}", &audio_buffer[..32]);
audio_device.queue(&audio_buffer);
audio_buffer = vec![];
}
// clock PPU three times for every CPU cycle
for _ in 0..cpu_cycles * 3 {
let (pixel, end_of_frame) = cpu.ppu.clock();
@ -130,5 +121,13 @@ fn main() -> Result<(), String> {
Ok(())
}
// TODO: reset function?
// TODO: save/load functionality
/*
TODO:
- remaining APU channels
- common mappers
- battery-backed RAM solution
- GUI? drag and drop ROMs?
- reset function
- save/load/pause functionality
*/