Skip to content

Commit

Permalink
refactor: some audio cleanup
Browse files Browse the repository at this point in the history
  • Loading branch information
lukexor committed Apr 5, 2024
1 parent a1a1b9b commit 6eeff9e
Show file tree
Hide file tree
Showing 8 changed files with 100 additions and 70 deletions.
15 changes: 8 additions & 7 deletions tetanes-core/src/apu.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use crate::{
apu::{
dmc::Dmc,
filter::{Consume, FilterChain},
frame_counter::{FcMode, FrameCounter},
frame_counter::{FrameCounter, Mode},
noise::Noise,
pulse::{OutputFreq, Pulse, PulseChannel},
triangle::Triangle,
Expand Down Expand Up @@ -231,7 +231,8 @@ impl Apu {
fn clock_frame_counter(&mut self) {
let clock = self.frame_counter.clock();

if self.frame_counter.mode == FcMode::Step4
// Set IRQ on last 3 cycles for Step4 mode
if self.frame_counter.mode == Mode::Step4
&& !self.irq_disabled
&& self.frame_counter.step >= 4
{
Expand Down Expand Up @@ -261,7 +262,7 @@ impl Apu {
}

// Clock Step5 immediately
if self.frame_counter.update() && self.frame_counter.mode == FcMode::Step5 {
if self.frame_counter.update() {
self.clock_quarter_frame();
self.clock_half_frame();
}
Expand All @@ -287,6 +288,10 @@ impl Apu {
impl Clock for Apu {
/// Clock the APU.
fn clock(&mut self) -> usize {
// Technically only clocks every 2 CPU cycles, but due
// to half-cycle timings, we clock every cycle
self.clock_frame_counter();

self.pulse1.length.reload();
self.pulse2.length.reload();
self.noise.length.reload();
Expand All @@ -301,10 +306,6 @@ impl Clock for Apu {
}
self.triangle.clock();

// Technically only clocks every 2 CPU cycles, but due
// to half-cycle timings, we clock every cycle
self.clock_frame_counter();

self.cycle = self.cycle.wrapping_add(1);

1
Expand Down
84 changes: 54 additions & 30 deletions tetanes-core/src/apu/frame_counter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,19 +10,28 @@ pub struct FrameCounter {
pub step_cycles: [[u16; 6]; 2],
pub cycles: u16,
pub step: usize,
pub mode: FcMode,
pub mode: Mode,
pub write_buffer: Option<u8>,
pub write_delay: u8,
pub block_counter: u8,
}

/// The Frame Counter step sequence mode.
#[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum FcMode {
pub enum Mode {
Step4,
Step5,
}

impl Default for FcMode {
/// The Frame Counter clock type.
#[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum Type {
None,
Quarter,
Half,
}

impl Default for Mode {
fn default() -> Self {
Self::Step4
}
Expand All @@ -38,6 +47,15 @@ impl FrameCounter {
[8313, 8314, 8312, 8314, 8312, 1],
];

const FRAME_TYPE: [Type; 6] = [
Type::Quarter,
Type::Half,
Type::Quarter,
Type::None,
Type::Half,
Type::None,
];

pub fn new() -> Self {
let region = NesRegion::default();
let step_cycles = Self::step_cycles(region);
Expand All @@ -46,9 +64,10 @@ impl FrameCounter {
step_cycles,
cycles: step_cycles[0][0],
step: 0,
mode: FcMode::Step4,
mode: Mode::Step4,
write_buffer: None,
write_delay: 0,
block_counter: 0,
}
}

Expand All @@ -65,15 +84,29 @@ impl FrameCounter {
}

pub fn update(&mut self) -> bool {
let mut update = false;
if let Some(val) = self.write_buffer {
self.write_delay -= 1;
if self.write_delay == 0 {
self.reload(val);
self.mode = if val & 0x80 == 0x80 {
Mode::Step5
} else {
Mode::Step4
};
self.step = 0;
self.cycles = self.step_cycles[self.mode as usize][self.step];
self.write_buffer = None;
return true;
// Writing to $4017 with bit 7 set will immediately generate a quarter/half frame
if self.mode == Mode::Step5 && self.block_counter == 0 {
update = true;
self.block_counter = 2;
}
}
}
false
if self.block_counter > 0 {
self.block_counter -= 1;
}
update
}

/// On write to $4017
Expand All @@ -82,55 +115,46 @@ impl FrameCounter {
// Writes occurring on odd clocks are delayed
self.write_delay = if cycle & 0x01 == 0x01 { 4 } else { 3 };
}

pub fn reload(&mut self, val: u8) {
self.mode = if val & 0x80 == 0x80 {
FcMode::Step5
} else {
FcMode::Step4
};
self.step = 0;
self.cycles = self.step_cycles[self.mode as usize][self.step];

// Clock Step5 immediately
if self.mode == FcMode::Step5 {
self.clock();
}
}
}

impl Clock for FrameCounter {
fn clock(&mut self) -> usize {
let mut clock = 0;
if self.cycles > 0 {
self.cycles -= 1;
}
if self.cycles == 0 {
let clock = self.step;
clock = self.step;
if Self::FRAME_TYPE[self.step] != Type::None && self.block_counter == 0 {
// Do not allow writes to $4017 to clock for the next cycle (odd + following even
// cycle)
self.block_counter = 2;
}

self.step += 1;
if self.step > 5 {
if self.step == 6 {
self.step = 0;
}
self.cycles = self.step_cycles[self.mode as usize][self.step];
clock
} else {
0
}
clock
}
}

impl Reset for FrameCounter {
fn reset(&mut self, kind: ResetKind) {
if kind == ResetKind::Hard {
self.mode = FcMode::Step4;
self.mode = Mode::Step4;
}
self.step = 0;
self.cycles = self.step_cycles[self.mode as usize][self.step];
// After reset, APU acts as if $4017 was written 9-12 clocks before first instruction,
// since reset takes 7 cycles, add 3 here
self.write_buffer = Some(match self.mode {
FcMode::Step4 => 0x00,
FcMode::Step5 => 0x80,
Mode::Step4 => 0x00,
Mode::Step5 => 0x80,
});
self.write_delay = 3;
self.block_counter = 0;
}
}
6 changes: 4 additions & 2 deletions tetanes-core/src/apu/length_counter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ pub struct LengthCounter {
pub counter: u8, // Entry into LENGTH_TABLE
pub previous_counter: u8,
pub reload: u8,
pub needs_to_clock: bool,
}

impl LengthCounter {
Expand All @@ -34,6 +35,7 @@ impl LengthCounter {
counter: 0,
previous_counter: 0,
reload: 0,
needs_to_clock: false,
}
}

Expand All @@ -42,7 +44,7 @@ impl LengthCounter {
if self.enabled {
self.reload = Self::LENGTH_TABLE[val as usize]; // D7..D3
self.previous_counter = self.counter;
// TODO: set apu needs to run
self.needs_to_clock = true;
}
}

Expand All @@ -67,8 +69,8 @@ impl LengthCounter {

#[inline]
pub fn write_ctrl(&mut self, halt: bool) {
// TODO: set apu needs to run
self.new_halt = halt;
self.needs_to_clock = true;
}
}

Expand Down
6 changes: 3 additions & 3 deletions tetanes-core/src/bus.rs
Original file line number Diff line number Diff line change
Expand Up @@ -182,8 +182,8 @@ impl Clock for Bus {
1
}

fn clock_to(&mut self, clock: u64) {
self.ppu.clock_to(clock);
fn clock_to(&mut self, clock: usize) -> usize {
self.ppu.clock_to(clock)
}
}

Expand Down Expand Up @@ -461,7 +461,7 @@ mod test {
let mut bus = Bus::default();

bus.clock_to(12);
assert_eq!(bus.ppu.master_clock(), 12, "ppu clock");
assert_eq!(bus.ppu.master_clock, 12, "ppu clock");
bus.clock();
assert_eq!(bus.apu.cycle, 1, "apu clock");
}
Expand Down
4 changes: 3 additions & 1 deletion tetanes-core/src/common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,9 @@ pub trait Clock {
fn clock(&mut self) -> usize {
0
}
fn clock_to(&mut self, _clocks: u64) {}
fn clock_to(&mut self, _clock: usize) -> usize {
0
}
}

/// Trait for types that can output `f32` audio samples.
Expand Down
Loading

0 comments on commit 6eeff9e

Please sign in to comment.