76 lines
2.7 KiB
Zig
76 lines
2.7 KiB
Zig
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;
|
|
}
|