// The audio processing unit.
//
// This build does not synthesize sound. What it does implement is the part a
// game program can observe, and that part is not optional: the APU also owns the
// frame counter, which is a CPU-visible timer. A game that waits for the frame
// IRQ to synchronise its music and its scrolling will wait forever without it,
// because `$4015` reads as zero and the IRQ never arrives. So the timer is here
// and is accurate; the four waveform channels have their registers and their
// length counters enough to make status reads honest, and there is no mixing
// stage behind them.
//
// The frame counter is a divider from the CPU clock, not from the PPU's. That
// matters: 29830 CPU cycles per NTSC frame, and the sequence steps four times
// per frame in 4-step mode or five times in 5-step mode.
///|
/// Which of the frame counter's two sequences is running.
pub(all) enum FrameMode {
// Compared by name in tests rather than by value, so no derived Eq.
/// Four steps, and it raises an IRQ at the end.
FourStep
/// Five steps, and it raises no IRQ. Games that need a clean audio stream pick
/// this one precisely because the interrupt would disturb their timing.
FiveStep
}
///|
/// The frame counter's step within its sequence.
///
/// The sequence's position is exposed because the quarter- and half-frame clocks
/// drive the length counters, which `$4015` reports.
pub(all) enum FrameStep {
/// Nothing clocked yet.
Idle
/// Quarter-frame clock only.
Quarter
/// Half-frame clock, which is where the length counters and the sweeps run.
Half
/// Both clocks, applied together.
Both
}
///|
/// The APU.
pub(all) struct Apu {
/// Which of the two sequences the counter is running.
mut mode : FrameMode
/// True when the 4-step sequence should raise an IRQ at its end.
mut irq_inhibit : Bool
/// True once the frame counter has raised an IRQ, cleared by reading `$4015`.
mut frame_irq : Bool
/// CPU cycles counted since the last step.
mut cycles : Int64
/// Position within the sequence, 0 to 3 or 0 to 4.
mut step : Int32
/// Length counter for each of the five channels, in the order pulse 1, pulse
/// 2, triangle, noise, DMC. Zero means the channel has silenced itself.
length : FixedArray[UInt32]
/// True when a channel's length counter is being held rather than clocked.
halt : FixedArray[Bool]
/// The raw register values, kept so a status read can report whether a channel
/// is active without the caller having to track the writes itself.
control : FixedArray[UInt32]
/// True when the DMC channel has an active sample, which is the only way
/// `$4015` bit 4 ever reads as set.
mut dmc_active : Bool
/// The low five bits of the last `$4015` write: which channels a program has
/// turned on. A length-counter write to a channel that is off is ignored,
/// which is what keeps a silenced channel from starting up again.
mut enabled_channels : UInt32
}
///|
/// Cycles per frame-counter tick on NTSC.
///
/// The four-step sequence steps at CPU cycles 7457, 14913, 22371 and 29829, so
/// keeping a running counter and comparing against those four points is closer to
/// the hardware than dividing by a single period.
fn frame_step_cycles() -> FixedArray[Int64] {
[7457L, 14913L, 22371L, 29829L, 37281L]
}
///|
/// True when the counter is running the four-step sequence.
fn Apu::four_step(self : Apu) -> Bool {
match self.mode {
FourStep => true
FiveStep => false
}
}
///|
/// The name of the sequence, used in reports and tests.
pub fn FrameMode::name(self : FrameMode) -> String {
match self {
FourStep => "4-step"
FiveStep => "5-step"
}
}
///|
/// A freshly powered APU.
pub fn Apu::new() -> Apu {
{
mode: FourStep,
irq_inhibit: false,
frame_irq: false,
cycles: 0L,
step: 0,
length: FixedArray::make(5, 0U),
halt: FixedArray::make(5, false),
control: FixedArray::make(5, 0U),
dmc_active: false,
enabled_channels: 0U,
}
}
///|
/// Advances the frame counter by a number of CPU cycles.
///
/// Returns true on the cycle the four-step sequence raises its IRQ, so the
/// console can take the interrupt at the right moment rather than polling.
pub fn Apu::tick(self : Apu, cycles : Int64) -> Bool {
self.cycles = self.cycles + cycles
let mut raised = false
// The sequence is periodic, so this has to wrap rather than stop at its end:
// a counter that stopped would raise the frame IRQ once and never again, and
// a game waiting for the second one would hang on the same frame it started.
let limit = if self.four_step() { 4 } else { 5 }
let period = if self.four_step() { 29829L } else { 37281L }
while self.step < limit && self.cycles >= frame_step_cycles()[self.step] {
// The step's position decides which clocks fire: quarter then half,
// alternating. The five-step sequence ends on a lone quarter-frame clock.
let which = if self.step % 2 == 0 { Quarter } else { Half }
self.clock_step(which)
self.step = self.step + 1
// The four-step sequence raises the frame IRQ as it wraps. The five-step one
// never does, which is why a game that needs clean audio selects it.
if self.four_step() && self.step == 4 && !self.irq_inhibit {
self.frame_irq = true
raised = true
}
}
if self.step >= limit {
// Wrap: fold the elapsed period out of the counter and start the sequence
// again. Subtracting the period rather than resetting to zero keeps the
// fractional remainder, so a frame that is not an exact multiple of the
// period does not drift.
self.cycles = self.cycles - period
self.step = 0
}
raised
}
///|
/// Applies one of the frame counter's clocks to the channels.
fn Apu::clock_step(self : Apu, which : FrameStep) -> Unit {
// The frame counter runs whether or not any channel is on: it is a divider of
// the CPU clock, not a property of the audio channels, and its interrupt is
// raised regardless. Gating it on `$4015` would silently disable the one part
// of the APU a program can depend on.
match which {
Idle => ()
// A quarter-frame clock advances the envelope and the triangle's linear
// counter. Neither affects anything observable here yet, so the only work is
// deciding whether the half-frame clock also fires.
Quarter => ()
// The half-frame clock decrements every length counter that is not halted,
// which is what eventually makes `$4015` report the channel as finished.
Half => self.clock_length()
Both => self.clock_length()
}
}
///|
/// Decrements each channel's length counter.
fn Apu::clock_length(self : Apu) -> Unit {
for channel in 0..<5 {
if self.halt[channel] {
continue
}
if self.length[channel] > 0U {
self.length[channel] = self.length[channel] - 1U
}
}
}
///|
/// Reads the status register `$4015`.
///
/// Bit *n* is set while channel *n* still has length left, which is how a game
/// knows a note has finished without tracking the length counter itself. Reading
/// the register also clears the frame IRQ flag, which is the only way to
/// acknowledge it.
pub fn Apu::read_status(self : Apu) -> UInt32 {
let mut value = 0U
for channel in 0..<4 {
if self.length[channel] > 0U {
value = value | (1U << channel)
}
}
if self.dmc_active {
value = value | 0x10U
}
if self.frame_irq {
value = value | 0x40U
}
self.frame_irq = false
value
}
///|
/// Writes the status register `$4015`, which only uses its low five bits: each
/// one enables or disables a channel, and writing a zero clears that channel's
/// length counter so it falls silent at once.
pub fn Apu::write_status(self : Apu, value : UInt32) -> Unit {
for channel in 0..<4 {
let on = (value & (1U << channel)) != 0
if !on {
self.length[channel] = 0U
}
}
self.dmc_active = (value & 0x10U) != 0
self.enabled_channels = value & 0x1FU
// Any write here restarts the counter, which a game depends on because it is
// the documented way to get a clean four-step sequence from the top.
self.cycles = 0L
self.step = 0
self.frame_irq = false
}
///|
/// Writes a channel register.
///
/// Only the bits that change the length counter and the frame counter are acted
/// on. The rest — the timer periods, the envelopes — would matter to a mixer and
/// are recorded so a later version can pick them up without changing the
/// register decode.
pub fn Apu::write_register(
self : Apu,
address : UInt32,
value : UInt32,
) -> Unit {
let value = value & 0xFFU
match address & 0x1FU {
0x00U | 0x04U => {
// Pulse 1 and 2 duty and halt.
let channel = if (address & 0x1FU) == 0U { 0 } else { 1 }
self.control[channel] = value
self.halt[channel] = (value & 0x20U) != 0
}
0x01U | 0x05U => {
let channel = if (address & 0x1FU) == 0x01U { 0 } else { 1 }
self.control[channel] = value
}
0x02U | 0x06U => ()
0x03U | 0x07U => {
// The length counter is loaded from the top five bits whenever a channel's
// fourth register is written, but only if the channel is enabled. Loading
// it while the channel is off is what keeps a silenced channel silenced.
let channel = if (address & 0x1FU) == 0x03U { 0 } else { 1 }
self.load_length(channel, value >> 3)
}
0x08U => {
self.control[2] = value
self.halt[2] = (value & 0x80U) != 0
}
0x0AU => ()
0x0BU => self.load_length(2, value >> 3)
0x0CU => {
self.control[3] = value
self.halt[3] = (value & 0x20U) != 0
}
0x0EU => ()
0x0FU => self.load_length(3, value >> 3)
0x10U =>
// A DMC register write restarts the sample, which is what makes the
// channel's status bit go high.
self.control[4] = value
0x11U => ()
0x12U => ()
0x13U => {
self.control[4] = value
self.dmc_active = (value & 0x10U) != 0
}
0x15U => self.write_status(value)
0x17U => {
// The frame counter register. Writing it resets the sequence and, in
// five-step mode, clocks everything once immediately, which is why the
// order of a game's init writes is audible.
self.mode = if (value & 0x80U) != 0 { FiveStep } else { FourStep }
self.irq_inhibit = (value & 0x40U) != 0
self.cycles = 0L
self.step = 0
if self.four_step() && !self.irq_inhibit {
self.frame_irq = false
}
if !self.four_step() {
self.clock_step(Both)
}
}
_ => ()
}
}
///|
/// Loads a channel's length counter from the top five bits of a register write.
fn Apu::load_length(self : Apu, channel : Int32, value : UInt32) -> Unit {
if channel < 0 || channel >= 4 {
return
}
// The counter only loads while the channel is on. A write to a channel the
// program has not enabled leaves the counter at zero, so the channel stays
// silent rather than starting on the next half-frame clock.
if (self.enabled_channels & (1U << channel)) == 0U {
return
}
self.length[channel] = value & 0x1FU
}