Skip to content

Commit

Permalink
Add triangle waves
Browse files Browse the repository at this point in the history
  • Loading branch information
aelred committed Sep 29, 2024
1 parent 6f91ebe commit 0865883
Show file tree
Hide file tree
Showing 9 changed files with 262 additions and 41 deletions.
49 changes: 44 additions & 5 deletions src/apu/mod.rs
Original file line number Diff line number Diff line change
@@ -1,45 +1,53 @@
//! Emulates the APU (audio processing unit)
use bitflags::bitflags;
use pulse::PulseGenerator;
use triangle::TriangleGenerator;

mod envelope;
mod pulse;
mod triangle;

#[derive(Default)]
pub struct APU {
pulse_1: PulseGenerator,
pulse_2: PulseGenerator,
triangle: TriangleGenerator,
// APU can run in two "modes", which affect timing and interrupts
mode_toggle: bool,
cycles: u16,
output: u8,
}

impl APU {
pub fn tick(&mut self) -> u8 {
pub fn tick(&mut self) -> f32 {
let pulse_1 = self.pulse_1.tick();
let pulse_2 = self.pulse_2.tick();
let triangle = self.triangle.tick();

let cycles = self.cycles;
self.cycles += 1;

match (self.mode_toggle, cycles) {
(_, 3728) | (_, 11185) => {
(_, 7457) | (_, 22371) => {
self.pulse_1.clock_envelope();
self.pulse_2.clock_envelope();
self.triangle.clock_linear_counter();
}
(_, 7456) | (false, 14914) | (true, 18640) => {
(_, 14913) | (false, 29829) | (true, 37281) => {
self.pulse_1.clock_envelope();
self.pulse_2.clock_envelope();
self.triangle.clock_linear_counter();
self.pulse_1.clock_length_counter();
self.pulse_2.clock_length_counter();
self.triangle.clock_length_counter();
}
(false, 14915) | (true, 18641) => {
(false, 14915) | (true, 37282) => {
self.cycles = 0;
}
_ => {}
}

pulse_1 + pulse_2
mix(pulse_1, pulse_2, triangle)
}

pub fn write_pulse_1_flags(&mut self, value: u8) {
Expand All @@ -66,6 +74,18 @@ impl APU {
self.pulse_2.write_length(value);
}

pub fn write_triangle_flags(&mut self, value: u8) {
self.triangle.write_flags(value);
}

pub fn write_triangle_timer(&mut self, value: u8) {
self.triangle.write_timer(value);
}

pub fn write_triangle_length(&mut self, value: u8) {
self.triangle.write_length(value);
}

pub fn write_frame_counter(&mut self, value: u8) {
let value = FrameCounter::from_bits_truncate(value);
self.mode_toggle = value.contains(FrameCounter::MODE);
Expand All @@ -82,9 +102,28 @@ impl APU {
let status = Status::from_bits_truncate(value);
self.pulse_1.set_enabled(status.contains(Status::PULSE_1));
self.pulse_2.set_enabled(status.contains(Status::PULSE_2));
self.triangle.set_enabled(status.contains(Status::TRIANGLE));
}
}

// Mix output channels, produce a value between 0.0 and 1.0
fn mix(pulse_1: u8, pulse_2: u8, triangle: u8) -> f32 {
let pulse_in = (pulse_1 + pulse_2) as f32;
let pulse_out = if pulse_in == 0.0 {
0.0
} else {
95.88 / ((8128.0 / pulse_in) + 100.0)
};

let tnd_in = (triangle as f32) / 8227.0;
let tnd_out = if tnd_in == 0.0 {
0.0
} else {
159.79 / (1.0 / tnd_in + 100.0)
};
pulse_out + tnd_out
}

bitflags! {
struct Status: u8 {
const PULSE_1 = 0b0000_0001;
Expand Down
30 changes: 18 additions & 12 deletions src/apu/pulse.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ use super::envelope::Envelope;
// A 'pulse wave' is a rectangular wave (alternating from high to low).
pub struct PulseGenerator {
enabled: bool,
// Pulse generator tick at half the CPU speed
odd_cycle: bool,
// `timer` starts at `timer_initial` and counts down to 0.
// When it reaches 0, it is reloaded with `timer_initial` and `sequencer` is incremented.
// A lower `timer_initial` value results in a higher frequency.
Expand Down Expand Up @@ -83,11 +85,14 @@ impl PulseGenerator {
let waveform = PULSE_DUTY_WAVEFORM[self.duty_cycle as usize];
let value = (waveform.rotate_right(self.sequencer as u32) & 0b1) * volume * playing as u8;

if self.timer == 0 {
self.timer = self.timer_initial;
self.sequencer = self.sequencer.wrapping_add(1);
} else {
self.timer -= 1;
self.odd_cycle = !self.odd_cycle;
if !self.odd_cycle {
if self.timer == 0 {
self.timer = self.timer_initial;
self.sequencer = self.sequencer.wrapping_add(1);
} else {
self.timer -= 1;
}
}

value
Expand Down Expand Up @@ -119,6 +124,7 @@ mod tests {
fn test_pulse_generator_produces_rectangle_wave() {
let mut pulse = PulseGenerator {
enabled: true,
odd_cycle: false,
timer_initial: 8,
timer: 8,
sequencer: 0,
Expand All @@ -135,18 +141,18 @@ mod tests {

// Get two periods of the waveform
let wave: Vec<u8> = std::iter::repeat_with(|| pulse.tick())
.take(9 * 16)
.take(18 * 16)
.collect();

// Each part of wave is repeated `timer + 1 = 9` times
// Each part of wave is repeated `(timer + 1) * 2 = 18` CPU cycles
assert_eq!(
wave,
[
vec![0; 9],
vec![11; 2 * 9],
vec![0; 6 * 9],
vec![11; 2 * 9],
vec![0; 5 * 9]
vec![0; 18],
vec![11; 2 * 18],
vec![0; 6 * 18],
vec![11; 2 * 18],
vec![0; 5 * 18]
]
.concat()
);
Expand Down
179 changes: 179 additions & 0 deletions src/apu/triangle.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
use super::LENGTH_COUNTER_TABLE;
use bitflags::bitflags;

use super::Length;

#[derive(Default)]
// A 'triangle wave' is a waveform that goes up and down in a triangle shape.
pub struct TriangleGenerator {
enabled: bool,
// `timer` starts at `timer_initial` and counts down to 0.
// When it reaches 0, it is reloaded with `timer_initial` and `sequencer` is incremented.
// A lower `timer_initial` value results in a higher frequency.
timer_initial: u16,
timer: u16,
// The index into the waveform.
sequencer: u8,
length_counter: u8,
length_counter_halt: bool,
linear_counter: u8,
linear_counter_reload: u8,
linear_counter_reload_flag: bool,
linear_counter_control: bool,
}

impl TriangleGenerator {
pub fn set_enabled(&mut self, enabled: bool) {
if !enabled {
self.length_counter = 0;
}
self.enabled = enabled;
}

pub fn write_flags(&mut self, value: u8) {
let flags = TriangleFlags::from_bits_truncate(value);
self.length_counter_halt = flags.contains(TriangleFlags::LENGTH_COUNTER_HALT);
self.linear_counter_control = flags.contains(TriangleFlags::LINEAR_COUNTER_CONTROL);
self.linear_counter_reload = (flags & TriangleFlags::LINEAR_COUNTER_RELOAD).bits();
}

pub fn write_timer(&mut self, value: u8) {
// Set the low bits of the timer
self.timer_initial = (self.timer_initial & 0xFF00) | value as u16;
}

pub fn write_length(&mut self, value: u8) {
// Set the high bits of the timer
let value = Length::from_bits_truncate(value);
let timer_high = (value & Length::TIMER_HIGH).bits();
self.timer_initial = (self.timer_initial & 0x00FF) | ((timer_high as u16) << 8);
let length_index = (value & Length::LENGTH_COUNTER).bits() >> 3;

if self.enabled {
self.length_counter = LENGTH_COUNTER_TABLE[length_index as usize];
}

self.linear_counter_reload_flag = true;
}

pub fn halted(&self) -> bool {
self.length_counter == 0
}

// Low-frequency clock to stop sound after a certain time
pub fn clock_length_counter(&mut self) {
if self.length_counter > 0 && !self.length_counter_halt {
self.length_counter -= 1;
}
}

// Low-frequency clock to stop sound after a certain time, with more fine-grained control
pub fn clock_linear_counter(&mut self) {
self.linear_counter = self.linear_counter.saturating_sub(1);
if self.linear_counter_reload_flag {
self.linear_counter = self.linear_counter_reload;
}
if !self.linear_counter_control {
self.linear_counter_reload_flag = false;
}
}

// High-frequency tick to control waveform generation
pub fn tick(&mut self) -> u8 {
let playing = !self.halted();
let value = TRIANGLE_WAVEFORM[(self.sequencer % 32) as usize] * playing as u8;

if self.timer == 0 {
self.timer = self.timer_initial;
if self.length_counter != 0 && self.linear_counter != 0 {
self.sequencer = self.sequencer.wrapping_add(1);
}
} else {
self.timer -= 1;
}

value
}
}

bitflags! {
#[derive(Copy, Clone)]
struct TriangleFlags: u8 {
// Linear counter control and length counter halt share a flag
const LENGTH_COUNTER_HALT = 0b1000_0000;
const LINEAR_COUNTER_CONTROL = 0b1000_0000;
const LINEAR_COUNTER_RELOAD = 0b0111_1111;
}
}

const TRIANGLE_WAVEFORM: [u8; 32] = [
15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0, // descending
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, // ascending
];

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn test_triangle_generator_produces_triangle_wave() {
let mut triangle = TriangleGenerator {
enabled: true,
timer_initial: 8,
timer: 8,
sequencer: 0,
length_counter: 5,
length_counter_halt: false,
linear_counter: 255,
linear_counter_reload: 255,
linear_counter_reload_flag: false,
linear_counter_control: false,
};

// Get two periods of the waveform
let wave: Vec<u8> = std::iter::repeat_with(|| triangle.tick())
.take(9 * 64)
.collect();

// Each part of wave is repeated `timer + 1 = 9` times
assert_eq!(
wave,
[
vec![15; 9],
vec![14; 9],
vec![13; 9],
vec![12; 9],
vec![11; 9],
vec![10; 9],
vec![9; 9],
vec![8; 9],
vec![7; 9],
vec![6; 9],
vec![5; 9],
vec![4; 9],
vec![3; 9],
vec![2; 9],
vec![1; 9],
vec![0; 9],
vec![0; 9],
vec![1; 9],
vec![2; 9],
vec![3; 9],
vec![4; 9],
vec![5; 9],
vec![6; 9],
vec![7; 9],
vec![8; 9],
vec![9; 9],
vec![10; 9],
vec![11; 9],
vec![12; 9],
vec![13; 9],
vec![14; 9],
vec![15; 9]
]
.concat()
.repeat(2)
);
}
}
3 changes: 3 additions & 0 deletions src/cpu/memory.rs
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,9 @@ impl<PRG: Memory, PPU: PPURegisters, IN: Input> Memory for NESCPUMemory<PRG, PPU
APU_PULSE_2_FLAGS => self.apu.write_pulse_2_flags(byte),
APU_PULSE_2_TIMER => self.apu.write_pulse_2_timer(byte),
APU_PULSE_2_LENGTH => self.apu.write_pulse_2_length(byte),
APU_TRIANGLE_FLAGS => self.apu.write_triangle_flags(byte),
APU_TRIANGLE_TIMER => self.apu.write_triangle_timer(byte),
APU_TRIANGLE_LENGTH => self.apu.write_triangle_length(byte),
APU_FRAME_COUNTER => self.apu.write_frame_counter(byte),
APU_STATUS => self.apu.write_status(byte),
_ => self.the_rest.write(address, byte), // TODO
Expand Down
Loading

0 comments on commit 0865883

Please sign in to comment.