style(apu): split apu.zig into multiple files + refactor

This commit is contained in:
2022-10-10 13:20:15 -03:00
parent b5d8a65e69
commit dc7cad9691
11 changed files with 959 additions and 925 deletions

View File

@@ -0,0 +1,75 @@
const io = @import("../../bus/io.zig");
/// Linear Feedback Shift Register
const Scheduler = @import("../../scheduler.zig").Scheduler;
const FrameSequencer = @import("../../apu.zig").FrameSequencer;
const Noise = @import("../Noise.zig");
const Self = @This();
pub const interval: u64 = (1 << 24) / (1 << 22);
shift: u15,
timer: u16,
sched: *Scheduler,
pub fn create(sched: *Scheduler) Self {
return .{
.shift = 0,
.timer = 0,
.sched = sched,
};
}
pub fn sample(self: *const Self) i8 {
return if ((~self.shift & 1) == 1) 1 else -1;
}
/// Update the sate of the Channel Length TImer
pub fn updateLength(_: *Self, fs: *const FrameSequencer, ch4: *Noise, nr44: io.NoiseControl) void {
// Write to NRx4 when FS's next step is not one that clocks the length counter
if (!fs.isLengthNext()) {
// If length_enable was disabled but is now enabled and length timer is not 0 already,
// decrement the length timer
if (!ch4.cnt.length_enable.read() and nr44.length_enable.read() and ch4.len_dev.timer != 0) {
ch4.len_dev.timer -= 1;
// If Length Timer is now 0 and trigger is clear, disable the channel
if (ch4.len_dev.timer == 0 and !nr44.trigger.read()) ch4.enabled = false;
}
}
}
/// Reload LFSR Timer
pub fn reload(self: *Self, poly: io.PolyCounter) void {
self.sched.removeScheduledEvent(.{ .ApuChannel = 3 });
const div = Self.divisor(poly.div_ratio.read());
const timer = div << poly.shift.read();
self.sched.push(.{ .ApuChannel = 3 }, @as(u64, timer) * interval);
}
/// Scheduler Event Handler for LFSR Timer Expire
/// FIXME: This gets called a lot, clogging up the Scheduler
pub fn onLfsrTimerExpire(self: *Self, poly: io.PolyCounter, late: u64) void {
// Obscure: "Using a noise channel clock shift of 14 or 15
// results in the LFSR receiving no clocks."
if (poly.shift.read() >= 14) return;
const div = Self.divisor(poly.div_ratio.read());
const timer = div << poly.shift.read();
const tmp = (self.shift & 1) ^ ((self.shift & 2) >> 1);
self.shift = (self.shift >> 1) | (tmp << 14);
if (poly.width.read())
self.shift = (self.shift & ~@as(u15, 0x40)) | tmp << 6;
self.sched.push(.{ .ApuChannel = 3 }, @as(u64, timer) * interval -| late);
}
fn divisor(code: u3) u16 {
if (code == 0) return 8;
return @as(u16, code) << 4;
}

View File

@@ -0,0 +1,75 @@
const std = @import("std");
const io = @import("../../bus/io.zig");
const Scheduler = @import("../../scheduler.zig").Scheduler;
const FrameSequencer = @import("../../apu.zig").FrameSequencer;
const ToneSweep = @import("../ToneSweep.zig");
const Tone = @import("../Tone.zig");
const Self = @This();
pub const interval: u64 = (1 << 24) / (1 << 22);
pos: u3,
sched: *Scheduler,
timer: u16,
pub fn init(sched: *Scheduler) Self {
return .{
.timer = 0,
.pos = 0,
.sched = sched,
};
}
/// Updates the State of either Ch1 or Ch2's Length Timer
pub fn updateLength(_: *const Self, comptime T: type, fs: *const FrameSequencer, ch: *T, nrx34: io.Frequency) void {
comptime std.debug.assert(T == ToneSweep or T == Tone);
// Write to NRx4 when FS's next step is not one that clocks the length counter
if (!fs.isLengthNext()) {
// If length_enable was disabled but is now enabled and length timer is not 0 already,
// decrement the length timer
if (!ch.freq.length_enable.read() and nrx34.length_enable.read() and ch.len_dev.timer != 0) {
ch.len_dev.timer -= 1;
// If Length Timer is now 0 and trigger is clear, disable the channel
if (ch.len_dev.timer == 0 and !nrx34.trigger.read()) ch.enabled = false;
}
}
}
/// Scheduler Event Handler for Square Synth Timer Expire
pub fn onSquareTimerExpire(self: *Self, comptime T: type, nrx34: io.Frequency, late: u64) void {
comptime std.debug.assert(T == ToneSweep or T == Tone);
self.pos +%= 1;
self.timer = (@as(u16, 2048) - nrx34.frequency.read()) * 4;
self.sched.push(.{ .ApuChannel = if (T == ToneSweep) 0 else 1 }, @as(u64, self.timer) * interval -| late);
}
/// Reload Square Wave Timer
pub fn reload(self: *Self, comptime T: type, value: u11) void {
comptime std.debug.assert(T == ToneSweep or T == Tone);
const channel = if (T == ToneSweep) 0 else 1;
self.sched.removeScheduledEvent(.{ .ApuChannel = channel });
const tmp = (@as(u16, 2048) - value) * 4; // What Freq Timer should be assuming no weird behaviour
self.timer = (tmp & ~@as(u16, 0x3)) | self.timer & 0x3; // Keep the last two bits from the old timer;
self.sched.push(.{ .ApuChannel = channel }, @as(u64, self.timer) * interval);
}
pub fn sample(self: *const Self, nrx1: io.Duty) i8 {
const pattern = nrx1.pattern.read();
const i = self.pos ^ 7; // index of 0 should get highest bit
const result = switch (pattern) {
0b00 => @as(u8, 0b00000001) >> i, // 12.5%
0b01 => @as(u8, 0b00000011) >> i, // 25%
0b10 => @as(u8, 0b00001111) >> i, // 50%
0b11 => @as(u8, 0b11111100) >> i, // 75%
};
return if (result & 1 == 1) 1 else -1;
}

View File

@@ -0,0 +1,95 @@
const std = @import("std");
const io = @import("../../bus/io.zig");
const Scheduler = @import("../../scheduler.zig").Scheduler;
const FrameSequencer = @import("../../apu.zig").FrameSequencer;
const Wave = @import("../Wave.zig");
const buf_len = 0x20;
pub const interval: u64 = (1 << 24) / (1 << 22);
const Self = @This();
buf: [buf_len]u8,
timer: u16,
offset: u12,
sched: *Scheduler,
pub fn read(self: *const Self, comptime T: type, nr30: io.WaveSelect, addr: u32) T {
// TODO: Handle reads when Channel 3 is disabled
const base = if (!nr30.bank.read()) @as(u32, 0x10) else 0; // Read from the Opposite Bank in Use
const i = base + addr - 0x0400_0090;
return std.mem.readIntSliceLittle(T, self.buf[i..][0..@sizeOf(T)]);
}
pub fn write(self: *Self, comptime T: type, nr30: io.WaveSelect, addr: u32, value: T) void {
// TODO: Handle writes when Channel 3 is disabled
const base = if (!nr30.bank.read()) @as(u32, 0x10) else 0; // Write to the Opposite Bank in Use
const i = base + addr - 0x0400_0090;
std.mem.writeIntSliceLittle(T, self.buf[i..][0..@sizeOf(T)], value);
}
pub fn init(sched: *Scheduler) Self {
return .{
.buf = [_]u8{0x00} ** buf_len,
.timer = 0,
.offset = 0,
.sched = sched,
};
}
/// Reload internal Wave Timer
pub fn reload(self: *Self, value: u11) void {
self.sched.removeScheduledEvent(.{ .ApuChannel = 2 });
self.timer = (@as(u16, 2048) - value) * 2;
self.sched.push(.{ .ApuChannel = 2 }, @as(u64, self.timer) * interval);
}
/// Scheduler Event Handler
pub fn onWaveTimerExpire(self: *Self, nrx34: io.Frequency, nr30: io.WaveSelect, late: u64) void {
if (nr30.dimension.read()) {
self.offset = (self.offset + 1) % 0x40; // 0x20 bytes (both banks), which contain 2 samples each
} else {
self.offset = (self.offset + 1) % 0x20; // 0x10 bytes, which contain 2 samples each
}
self.timer = (@as(u16, 2048) - nrx34.frequency.read()) * 2;
self.sched.push(.{ .ApuChannel = 2 }, @as(u64, self.timer) * interval -| late);
}
/// Generate Sample from Wave Synth
pub fn sample(self: *const Self, nr30: io.WaveSelect) u4 {
const base = if (nr30.bank.read()) @as(u32, 0x10) else 0;
const value = self.buf[base + self.offset / 2];
return if (self.offset & 1 == 0) @truncate(u4, value >> 4) else @truncate(u4, value);
}
/// TODO: Write comment
pub fn shift(_: *const Self, nr32: io.WaveVolume) u2 {
return switch (nr32.kind.read()) {
0b00 => 3, // Mute / Zero
0b01 => 0, // 100% Volume
0b10 => 1, // 50% Volume
0b11 => 2, // 25% Volume
};
}
/// Update state of Channel 3 Length Device
pub fn updateLength(_: *Self, fs: *const FrameSequencer, ch3: *Wave, nrx34: io.Frequency) void {
// Write to NRx4 when FS's next step is not one that clocks the length counter
if (!fs.isLengthNext()) {
// If length_enable was disabled but is now enabled and length timer is not 0 already,
// decrement the length timer
if (!ch3.freq.length_enable.read() and nrx34.length_enable.read() and ch3.len_dev.timer != 0) {
ch3.len_dev.timer -= 1;
// If Length Timer is now 0 and trigger is clear, disable the channel
if (ch3.len_dev.timer == 0 and !nrx34.trigger.read()) ch3.enabled = false;
}
}
}