apu
This commit is contained in:
parent
69dda36534
commit
9c68257177
|
@ -18,7 +18,7 @@ impl DMC {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
pub fn control(&mut self, value: u8) {
|
pub fn write_control(&mut self, value: u8) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -26,11 +26,11 @@ impl DMC {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn sample_address(&mut self, value: u8) {
|
pub fn write_sample_address(&mut self, value: u8) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn sample_length(&mut self, value: u8) {
|
pub fn write_sample_length(&mut self, value: u8) {
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -27,6 +27,8 @@ use dmc::DMC;
|
||||||
// We need to take a sample 44100 times per second. The CPU clocks (not steps) at 1.789773 MHz. Meaning the APU, going half as fast,
|
// We need to take a sample 44100 times per second. The CPU clocks (not steps) at 1.789773 MHz. Meaning the APU, going half as fast,
|
||||||
// clocks 894,886.5 times per second. 894,886.5/44,100=20.29 APU clocks per audio sample.
|
// clocks 894,886.5 times per second. 894,886.5/44,100=20.29 APU clocks per audio sample.
|
||||||
|
|
||||||
|
// TODO: organize APU structs
|
||||||
|
|
||||||
pub struct Apu {
|
pub struct Apu {
|
||||||
square1: Square,
|
square1: Square,
|
||||||
square2: Square,
|
square2: Square,
|
||||||
|
@ -54,7 +56,7 @@ struct Envelope {
|
||||||
}
|
}
|
||||||
|
|
||||||
const FRAME_COUNTER_STEPS: [usize; 5] = [3728, 7456, 11185, 14914, 18640];
|
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
|
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.
|
||||||
|
|
||||||
impl Apu {
|
impl Apu {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
|
@ -106,28 +108,28 @@ impl Apu {
|
||||||
|
|
||||||
pub fn write_reg(&mut self, address: usize, value: u8) {
|
pub fn write_reg(&mut self, address: usize, value: u8) {
|
||||||
match address {
|
match address {
|
||||||
0x4000 => self.square1.duty(value),
|
0x4000 => self.square1.write_duty(value),
|
||||||
0x4001 => self.square1.sweep(value),
|
0x4001 => self.square1.write_sweep(value),
|
||||||
0x4002 => self.square1.timer_low(value),
|
0x4002 => self.square1.write_timer_low(value),
|
||||||
0x4003 => self.square1.timer_high(value),
|
0x4003 => self.square1.write_timer_high(value),
|
||||||
0x4004 => self.square2.duty(value),
|
0x4004 => self.square2.write_duty(value),
|
||||||
0x4005 => self.square2.sweep(value),
|
0x4005 => self.square2.write_sweep(value),
|
||||||
0x4006 => self.square2.timer_low(value),
|
0x4006 => self.square2.write_timer_low(value),
|
||||||
0x4007 => self.square2.timer_high(value),
|
0x4007 => self.square2.write_timer_high(value),
|
||||||
0x4008 => self.triangle.counter(value),
|
0x4008 => self.triangle.write_counter(value),
|
||||||
0x4009 => (),
|
0x4009 => (),
|
||||||
0x400A => self.triangle.timer_low(value),
|
0x400A => self.triangle.write_timer_low(value),
|
||||||
0x400B => self.triangle.timer_high(value),
|
0x400B => self.triangle.write_timer_high(value),
|
||||||
0x400C => self.noise.envelope(value),
|
0x400C => self.noise.write_envelope(value),
|
||||||
0x400D => (),
|
0x400D => (),
|
||||||
0x400E => self.noise.loop_noise(value),
|
0x400E => self.noise.write_loop_noise(value),
|
||||||
0x400F => self.noise.load_length_counter(value),
|
0x400F => self.noise.write_length_counter(value),
|
||||||
0x4010 => self.dmc.control(value),
|
0x4010 => self.dmc.write_control(value),
|
||||||
0x4011 => self.dmc.direct_load(value),
|
0x4011 => self.dmc.direct_load(value),
|
||||||
0x4012 => self.dmc.sample_address(value),
|
0x4012 => self.dmc.write_sample_address(value),
|
||||||
0x4013 => self.dmc.sample_length(value),
|
0x4013 => self.dmc.write_sample_length(value),
|
||||||
0x4014 => (),
|
0x4014 => (),
|
||||||
0x4015 => self.control(value),
|
0x4015 => self.write_control(value),
|
||||||
0x4016 => (),
|
0x4016 => (),
|
||||||
0x4017 => self.set_frame_counter(value),
|
0x4017 => self.set_frame_counter(value),
|
||||||
_ => panic!("bad address written: 0x{:X}", address),
|
_ => panic!("bad address written: 0x{:X}", address),
|
||||||
|
@ -188,7 +190,7 @@ impl Apu {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn control(&mut self, value: u8) {
|
fn write_control(&mut self, value: u8) {
|
||||||
// Writing to this register clears the DMC interrupt flag.
|
// Writing to this register clears the DMC interrupt flag.
|
||||||
self.dmc.interrupt = false;
|
self.dmc.interrupt = false;
|
||||||
// Writing a zero to any of the channel enable bits will silence that channel and immediately set its length counter to 0.
|
// Writing a zero to any of the channel enable bits will silence that channel and immediately set its length counter to 0.
|
||||||
|
|
|
@ -35,7 +35,7 @@ impl Noise {
|
||||||
self.linear_feedback_sr |= feedback << 14;
|
self.linear_feedback_sr |= feedback << 14;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn envelope(&mut self, value: u8) {
|
pub fn write_envelope(&mut self, value: u8) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -47,10 +47,10 @@ impl Noise {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn loop_noise(&mut self, value: u8) {
|
pub fn write_loop_noise(&mut self, value: u8) {
|
||||||
|
|
||||||
}
|
}
|
||||||
pub fn load_length_counter(&mut self, value: u8) {
|
pub fn write_length_counter(&mut self, value: u8) {
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,45 +7,80 @@ const DUTY_CYCLE_SEQUENCES: [[u8; 8]; 4] = [
|
||||||
|
|
||||||
pub struct Square {
|
pub struct Square {
|
||||||
pub sample: u16,
|
pub sample: u16,
|
||||||
duty_cycle: [u8; 8],
|
divider: u16,
|
||||||
duty_counter: u8,
|
|
||||||
length_counter_halt: bool, // (this bit is also the envelope's loop flag)
|
|
||||||
constant_volume_flag: bool, // (0: use volume from envelope; 1: use constant volume)
|
|
||||||
timer: u16,
|
|
||||||
pub length_counter: u8,
|
|
||||||
envelope: u8,
|
|
||||||
sweep: u8,
|
|
||||||
pub enabled: bool,
|
pub enabled: bool,
|
||||||
decay_counter: u8,
|
|
||||||
|
duty_cycle: [u8; 8],
|
||||||
|
duty_counter: usize,
|
||||||
|
|
||||||
|
envelope: u16,
|
||||||
start: bool,
|
start: bool,
|
||||||
divider: u8,
|
decay_counter: u16,
|
||||||
|
constant_volume_flag: bool, // (0: use volume from envelope; 1: use constant volume)
|
||||||
|
|
||||||
|
length_counter_halt: bool, // (this bit is also the envelope's loop flag)
|
||||||
|
pub length_counter: u8,
|
||||||
|
|
||||||
|
timer: u16,
|
||||||
|
timer_period: u16,
|
||||||
|
sweep: u8,
|
||||||
|
sweep_divider: u8,
|
||||||
|
shift_count: u8,
|
||||||
|
sweep_adder_overflow: bool,
|
||||||
|
sweep_enabled: bool,
|
||||||
|
sweep_negate: bool,
|
||||||
|
sweep_reload: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Square {
|
impl Square {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Square {
|
Square {
|
||||||
|
sample: 0,
|
||||||
|
divider: 0,
|
||||||
|
enabled: false,
|
||||||
|
|
||||||
duty_cycle: DUTY_CYCLE_SEQUENCES[0],
|
duty_cycle: DUTY_CYCLE_SEQUENCES[0],
|
||||||
duty_counter: 0,
|
duty_counter: 0,
|
||||||
length_counter_halt: false,
|
|
||||||
constant_volume_flag: false,
|
|
||||||
timer: 0,
|
|
||||||
length_counter: 0,
|
|
||||||
envelope: 0,
|
envelope: 0,
|
||||||
sweep: 0,
|
|
||||||
sample: 0,
|
|
||||||
enabled: false,
|
|
||||||
decay_counter: 0,
|
|
||||||
start: false,
|
start: false,
|
||||||
divider: 0,
|
decay_counter: 0,
|
||||||
|
constant_volume_flag: false,
|
||||||
|
|
||||||
|
timer: 0,
|
||||||
|
timer_period: 0,
|
||||||
|
sweep: 0,
|
||||||
|
sweep_divider: 0,
|
||||||
|
shift_count: 0,
|
||||||
|
sweep_adder_overflow: false,
|
||||||
|
sweep_enabled: false,
|
||||||
|
sweep_negate: false,
|
||||||
|
sweep_reload: false,
|
||||||
|
|
||||||
|
length_counter: 0,
|
||||||
|
length_counter_halt: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn clock(&mut self) {
|
pub fn clock(&mut self) {
|
||||||
|
// The sequencer is clocked by an 11-bit timer. Given the timer value t = HHHLLLLLLLL formed by timer high and timer low, this timer is updated every APU cycle
|
||||||
}
|
// (i.e., every second CPU cycle), and counts t, t-1, ..., 0, t, t-1, ..., clocking the waveform generator when it goes from 0 to t.
|
||||||
|
if self.timer == 0 {
|
||||||
pub fn clock_frame_counter(&mut self) {
|
self.timer = self.timer_period;
|
||||||
|
self.duty_counter = (self.duty_counter + 1) % 8;
|
||||||
|
} else {
|
||||||
|
self.timer -= 1;
|
||||||
|
}
|
||||||
|
// 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.sweep_adder_overflow // 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.
|
||||||
|
0
|
||||||
|
} else {
|
||||||
|
self.decay_counter
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn clock_envelope(&mut self) {
|
pub fn clock_envelope(&mut self) {
|
||||||
|
@ -80,50 +115,46 @@ impl Square {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn clock_sweep(&mut self) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
// $4000/$4004
|
// $4000/$4004
|
||||||
pub fn duty(&mut self, value: u8) {
|
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.
|
||||||
self.duty_cycle = DUTY_CYCLE_SEQUENCES[(value >> 6) as usize];
|
self.duty_cycle = DUTY_CYCLE_SEQUENCES[(value >> 6) as usize];
|
||||||
self.length_counter_halt = value & (1<<5) != 0;
|
self.length_counter_halt = value & (1<<5) != 0;
|
||||||
self.constant_volume_flag = value & (1<<4) != 0;
|
self.constant_volume_flag = value & (1<<4) != 0;
|
||||||
if self.constant_volume_flag {
|
if self.constant_volume_flag {
|
||||||
self.envelope = value & 0b1111;
|
self.envelope = value as u16 & 0b1111;
|
||||||
} else {
|
} else {
|
||||||
self.envelope = self.decay_counter;
|
self.envelope = self.decay_counter;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// $4001/$4005
|
// $4001/$4005
|
||||||
pub fn sweep(&mut self, value: u8) {
|
pub fn write_sweep(&mut self, value: u8) {
|
||||||
|
self.sweep_enabled = value >> 7 == 1;
|
||||||
|
self.sweep_divider = value >> 4 & 0b111;
|
||||||
|
self.sweep_negate = value & 0b1000 != 0;
|
||||||
|
self.shift_count = value & 0b111;
|
||||||
}
|
}
|
||||||
|
|
||||||
// $4002/$4006
|
// $4002/$4006
|
||||||
pub fn timer_low(&mut self, value: u8) {
|
pub fn write_timer_low(&mut self, value: u8) {
|
||||||
|
self.timer &= 0b11111111_00000000;
|
||||||
|
self.timer |= value as u16;
|
||||||
}
|
}
|
||||||
|
|
||||||
// $4003/$4007
|
// $4003/$4007
|
||||||
pub fn timer_high(&mut self, value: u8) {
|
pub fn write_timer_high(&mut self, value: u8) {
|
||||||
|
// LLLL.Lttt Pulse channel 1 length counter load and timer (write)
|
||||||
|
self.length_counter = value >> 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
|
||||||
|
// 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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct EnvelopeGenerator {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
struct SweepUnit {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
struct Timer {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
struct Sequencer {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
struct LengthCounter {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
|
@ -20,15 +20,15 @@ impl Triangle {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn timer_low(&mut self, value: u8) {
|
pub fn write_timer_low(&mut self, value: u8) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn timer_high(&mut self, value: u8) {
|
pub fn write_timer_high(&mut self, value: u8) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn counter(&mut self, value: u8) {
|
pub fn write_counter(&mut self, value: u8) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -29,8 +29,8 @@ pub fn initialize(context: &sdl2::Sdl) -> Result<sdl2::audio::AudioQueue<f32>, S
|
||||||
|
|
||||||
let desired_spec = AudioSpecDesired {
|
let desired_spec = AudioSpecDesired {
|
||||||
freq: Some(44_100),
|
freq: Some(44_100),
|
||||||
channels: Some(1), // mono
|
channels: Some(1), // mono
|
||||||
samples: Some(4096), // default sample size
|
samples: None, // default sample size
|
||||||
};
|
};
|
||||||
|
|
||||||
audio_subsystem.open_queue(None, &desired_spec)
|
audio_subsystem.open_queue(None, &desired_spec)
|
||||||
|
|
14
src/main.rs
14
src/main.rs
|
@ -37,6 +37,7 @@ fn main() -> Result<(), String> {
|
||||||
// Set up audio
|
// Set up audio
|
||||||
let mut audio_device = audio::initialize(&sdl_context).expect("Could not create audio device");
|
let mut audio_device = audio::initialize(&sdl_context).expect("Could not create audio device");
|
||||||
let mut half_cycle = false;
|
let mut half_cycle = false;
|
||||||
|
let mut audio_buffer = Vec::<f32>::new();
|
||||||
audio_device.resume();
|
audio_device.resume();
|
||||||
|
|
||||||
// Initialize hardware components
|
// Initialize hardware components
|
||||||
|
@ -49,6 +50,7 @@ fn main() -> Result<(), String> {
|
||||||
let mut timer = Instant::now();
|
let mut timer = Instant::now();
|
||||||
let mut fps_timer = Instant::now();
|
let mut fps_timer = Instant::now();
|
||||||
let mut fps = 0;
|
let mut fps = 0;
|
||||||
|
let mut sps = 0;
|
||||||
|
|
||||||
// PROFILER.lock().unwrap().start("./main.profile").unwrap();
|
// PROFILER.lock().unwrap().start("./main.profile").unwrap();
|
||||||
'running: loop {
|
'running: loop {
|
||||||
|
@ -66,10 +68,14 @@ fn main() -> Result<(), String> {
|
||||||
}
|
}
|
||||||
for _ in 0..apu_cycles {
|
for _ in 0..apu_cycles {
|
||||||
match cpu.apu.clock() {
|
match cpu.apu.clock() {
|
||||||
Some(sample) => audio_device.queue(&wav),
|
Some(sample) => {sps += 1; audio_buffer.push(sample)},
|
||||||
None => false,
|
None => (),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
if audio_buffer.len() == 44_100 {
|
||||||
|
audio_device.queue(&audio_buffer);
|
||||||
|
audio_buffer = vec![];
|
||||||
|
}
|
||||||
// clock PPU three times for every CPU cycle
|
// clock PPU three times for every CPU cycle
|
||||||
for _ in 0..cpu_cycles * 3 {
|
for _ in 0..cpu_cycles * 3 {
|
||||||
let (pixel, end_of_frame) = cpu.ppu.clock();
|
let (pixel, end_of_frame) = cpu.ppu.clock();
|
||||||
|
@ -107,6 +113,10 @@ fn main() -> Result<(), String> {
|
||||||
println!("fps: {}", fps);
|
println!("fps: {}", fps);
|
||||||
fps = 0;
|
fps = 0;
|
||||||
fps_timer = now;
|
fps_timer = now;
|
||||||
|
|
||||||
|
println!("samples per second: {}", sps);
|
||||||
|
sps = 0;
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// PROFILER.lock().unwrap().stop().unwrap();
|
// PROFILER.lock().unwrap().stop().unwrap();
|
||||||
|
|
Loading…
Reference in New Issue