Compare commits
No commits in common. "0184ec3e5e8a2eb32b30dc1b148947c4de486af9" and "c3611a0f00eee8a34fbd1cb5dfe7220439555a1e" have entirely different histories.
0184ec3e5e
...
c3611a0f00
500
src/apu.zig
500
src/apu.zig
|
@ -24,8 +24,7 @@ pub const Apu = struct {
|
||||||
chB: DmaSound(.B),
|
chB: DmaSound(.B),
|
||||||
|
|
||||||
bias: io.SoundBias,
|
bias: io.SoundBias,
|
||||||
/// NR51
|
ch_vol_cnt: io.ChannelVolumeControl,
|
||||||
psg_cnt: io.ChannelVolumeControl,
|
|
||||||
dma_cnt: io.DmaSoundControl,
|
dma_cnt: io.DmaSoundControl,
|
||||||
cnt: io.SoundControl,
|
cnt: io.SoundControl,
|
||||||
|
|
||||||
|
@ -33,19 +32,16 @@ pub const Apu = struct {
|
||||||
stream: *SDL.SDL_AudioStream,
|
stream: *SDL.SDL_AudioStream,
|
||||||
sched: *Scheduler,
|
sched: *Scheduler,
|
||||||
|
|
||||||
fs: FrameSequencer,
|
|
||||||
capacitor: f32,
|
|
||||||
|
|
||||||
pub fn init(sched: *Scheduler) Self {
|
pub fn init(sched: *Scheduler) Self {
|
||||||
const apu: Self = .{
|
const apu: Self = .{
|
||||||
.ch1 = ToneSweep.init(sched),
|
.ch1 = ToneSweep.init(),
|
||||||
.ch2 = Tone.init(),
|
.ch2 = Tone.init(),
|
||||||
.ch3 = Wave.init(),
|
.ch3 = Wave.init(),
|
||||||
.ch4 = Noise.init(),
|
.ch4 = Noise.init(),
|
||||||
.chA = DmaSound(.A).init(),
|
.chA = DmaSound(.A).init(),
|
||||||
.chB = DmaSound(.B).init(),
|
.chB = DmaSound(.B).init(),
|
||||||
|
|
||||||
.psg_cnt = .{ .raw = 0 },
|
.ch_vol_cnt = .{ .raw = 0 },
|
||||||
.dma_cnt = .{ .raw = 0 },
|
.dma_cnt = .{ .raw = 0 },
|
||||||
.cnt = .{ .raw = 0 },
|
.cnt = .{ .raw = 0 },
|
||||||
.bias = .{ .raw = 0x0200 },
|
.bias = .{ .raw = 0x0200 },
|
||||||
|
@ -53,23 +49,13 @@ pub const Apu = struct {
|
||||||
.sampling_cycle = 0b00,
|
.sampling_cycle = 0b00,
|
||||||
.stream = SDL.SDL_NewAudioStream(SDL.AUDIO_F32, 2, 1 << 15, SDL.AUDIO_F32, 2, host_sample_rate) orelse unreachable,
|
.stream = SDL.SDL_NewAudioStream(SDL.AUDIO_F32, 2, 1 << 15, SDL.AUDIO_F32, 2, host_sample_rate) orelse unreachable,
|
||||||
.sched = sched,
|
.sched = sched,
|
||||||
|
|
||||||
.capacitor = 0,
|
|
||||||
.fs = FrameSequencer.init(),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
sched.push(.SampleAudio, sched.now() + apu.sampleTicks());
|
sched.push(.SampleAudio, sched.now() + apu.sampleTicks());
|
||||||
sched.push(.{ .ApuChannel = 0 }, sched.now() + SquareWave.ticks);
|
|
||||||
sched.push(.FrameSequencer, sched.now() + ((1 << 24) / 512));
|
|
||||||
|
|
||||||
return apu;
|
return apu;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn reset(self: *Self) void {
|
|
||||||
self.ch1.reset();
|
|
||||||
// TODO: Reset the rest of the channels
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn setDmaCnt(self: *Self, value: u16) void {
|
pub fn setDmaCnt(self: *Self, value: u16) void {
|
||||||
const new: io.DmaSoundControl = .{ .raw = value };
|
const new: io.DmaSoundControl = .{ .raw = value };
|
||||||
|
|
||||||
|
@ -81,42 +67,16 @@ pub const Apu = struct {
|
||||||
self.dma_cnt = new;
|
self.dma_cnt = new;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// NR52
|
|
||||||
pub fn setSoundCntX(self: *Self, value: bool) void {
|
pub fn setSoundCntX(self: *Self, value: bool) void {
|
||||||
self.cnt.apu_enable.write(value);
|
self.cnt.apu_enable.write(value);
|
||||||
|
|
||||||
if (value) {
|
|
||||||
self.fs.step = 0; // Reset Frame Sequencer
|
|
||||||
|
|
||||||
// TODO: Reset Duty Position for Ch2 Square
|
|
||||||
self.ch1.square.pos = 0;
|
|
||||||
|
|
||||||
// TODO: Reset Channel 3 offset ptr
|
|
||||||
} else {
|
|
||||||
self.reset();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// NR52
|
|
||||||
pub fn soundCntX(self: *const Self) u32 {
|
|
||||||
const apu_enable = @boolToInt(self.cnt.apu_enable.read());
|
|
||||||
|
|
||||||
const ch1_enable = @boolToInt(self.ch1.enabled);
|
|
||||||
const ch2_enable = @boolToInt(self.ch2.enabled);
|
|
||||||
const ch3_enable = @boolToInt(self.ch3.enabled);
|
|
||||||
const ch4_enable = @boolToInt(self.ch4.enabled);
|
|
||||||
|
|
||||||
return apu_enable << 7 | ch4_enable << 3 | ch3_enable << 2 | ch2_enable << 1 | ch1_enable;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// NR50
|
|
||||||
pub fn setSoundCntLLow(self: *Self, byte: u8) void {
|
pub fn setSoundCntLLow(self: *Self, byte: u8) void {
|
||||||
self.psg_cnt.raw = (self.psg_cnt.raw & 0xFF00) | byte;
|
self.ch_vol_cnt.raw = (self.ch_vol_cnt.raw & 0xFF00) | byte;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// NR51
|
|
||||||
pub fn setSoundCntLHigh(self: *Self, byte: u8) void {
|
pub fn setSoundCntLHigh(self: *Self, byte: u8) void {
|
||||||
self.psg_cnt.raw = @as(u16, byte) << 8 | (self.psg_cnt.raw & 0xFF);
|
self.ch_vol_cnt.raw = @as(u16, byte) << 8 | (self.ch_vol_cnt.raw & 0xFF);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn setBiasHigh(self: *Self, byte: u8) void {
|
pub fn setBiasHigh(self: *Self, byte: u8) void {
|
||||||
|
@ -124,48 +84,16 @@ pub const Apu = struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn sampleAudio(self: *Self, late: u64) void {
|
pub fn sampleAudio(self: *Self, late: u64) void {
|
||||||
|
const chA = if (self.dma_cnt.chA_vol.read()) self.chA.amplitude() else self.chA.amplitude() / 2;
|
||||||
|
const chA_left = if (self.dma_cnt.chA_left.read()) chA else 0;
|
||||||
|
const chA_right = if (self.dma_cnt.chA_right.read()) chA else 0;
|
||||||
|
|
||||||
// Sample Channel 1
|
const chB = if (self.dma_cnt.chB_vol.read()) self.chB.amplitude() else self.chB.amplitude() / 2;
|
||||||
const ch1_sample = self.highPassFilter(self.ch1.amplitude(), self.ch1.enabled);
|
const chB_left = if (self.dma_cnt.chB_left.read()) chB else 0;
|
||||||
const ch1_left = if (self.psg_cnt.ch1_left.read()) ch1_sample else 0;
|
const chB_right = if (self.dma_cnt.chB_right.read()) chB else 0;
|
||||||
const ch1_right = if (self.psg_cnt.ch1_right.read()) ch1_sample else 0;
|
|
||||||
|
|
||||||
const mixed_left = ch1_left;
|
const left = (chA_left + chB_left) / 2;
|
||||||
const mixed_right = ch1_right;
|
const right = (chA_right + chB_right) / 2;
|
||||||
|
|
||||||
// Apply NR50 Volume Modifications
|
|
||||||
const nr50_left = (@intToFloat(f32, self.psg_cnt.left_vol.read()) + 1.0) * mixed_left;
|
|
||||||
const nr50_right = (@intToFloat(f32, self.psg_cnt.right_vol.read()) + 1.0) * mixed_right;
|
|
||||||
|
|
||||||
// Apply SOUNDCNT_H Volume Modifications
|
|
||||||
const psg_left = switch (self.dma_cnt.ch_vol.read()) {
|
|
||||||
0b00 => nr50_left * 0.25,
|
|
||||||
0b01 => nr50_left * 0.5,
|
|
||||||
0b10 => nr50_left * 0.75,
|
|
||||||
0b11 => nr50_left, // Prohibited
|
|
||||||
};
|
|
||||||
|
|
||||||
const psg_right = switch (self.dma_cnt.ch_vol.read()) {
|
|
||||||
0b00 => nr50_right * 0.25,
|
|
||||||
0b01 => nr50_right * 0.5,
|
|
||||||
0b10 => nr50_right * 0.75,
|
|
||||||
0b11 => nr50_right, // Prohibited
|
|
||||||
};
|
|
||||||
|
|
||||||
// Sample Dma Channels
|
|
||||||
// const chA = if (self.dma_cnt.chA_vol.read()) self.chA.amplitude() else self.chA.amplitude() / 2;
|
|
||||||
// const chA_left = if (self.dma_cnt.chA_left.read()) chA else 0;
|
|
||||||
// const chA_right = if (self.dma_cnt.chA_right.read()) chA else 0;
|
|
||||||
|
|
||||||
// const chB = if (self.dma_cnt.chB_vol.read()) self.chB.amplitude() else self.chB.amplitude() / 2;
|
|
||||||
// const chB_left = if (self.dma_cnt.chB_left.read()) chB else 0;
|
|
||||||
// const chB_right = if (self.dma_cnt.chB_right.read()) chB else 0;
|
|
||||||
|
|
||||||
// Mix all Channels
|
|
||||||
// const left = (chA_left + chB_left + psg_left) / 3;
|
|
||||||
// const right = (chA_right + chB_right + psg_right) / 3
|
|
||||||
const left = psg_left;
|
|
||||||
const right = psg_right;
|
|
||||||
|
|
||||||
if (self.sampling_cycle != self.bias.sampling_cycle.read()) {
|
if (self.sampling_cycle != self.bias.sampling_cycle.read()) {
|
||||||
log.warn("Sampling Cycle changed from {} to {}", .{ self.sampling_cycle, self.bias.sampling_cycle.read() });
|
log.warn("Sampling Cycle changed from {} to {}", .{ self.sampling_cycle, self.bias.sampling_cycle.read() });
|
||||||
|
@ -193,36 +121,6 @@ pub const Apu = struct {
|
||||||
return @as(u64, 1) << (15 + @as(u6, self.bias.sampling_cycle.read()));
|
return @as(u64, 1) << (15 + @as(u6, self.bias.sampling_cycle.read()));
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn tickFrameSequencer(self: *Self, late: u64) void {
|
|
||||||
self.fs.tick();
|
|
||||||
|
|
||||||
switch (self.fs.step) {
|
|
||||||
7 => self.tickEnvelopes(), // Clock Envelope
|
|
||||||
0, 4 => self.tickLengths(), // Clock Length
|
|
||||||
2, 6 => {
|
|
||||||
// Clock Length and Sweep
|
|
||||||
self.tickLengths();
|
|
||||||
self.ch1.tickSweep();
|
|
||||||
},
|
|
||||||
1, 3, 5 => {},
|
|
||||||
}
|
|
||||||
|
|
||||||
self.sched.push(.FrameSequencer, self.sched.now() + ((1 << 24) / 512) - late);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn tickLengths(self: *Self) void {
|
|
||||||
self.ch1.tickLength();
|
|
||||||
self.ch2.tickLength();
|
|
||||||
self.ch3.tickLength();
|
|
||||||
self.ch4.tickLength();
|
|
||||||
}
|
|
||||||
|
|
||||||
fn tickEnvelopes(self: *Self) void {
|
|
||||||
self.ch1.tickEnvelope();
|
|
||||||
self.ch2.tickEnvelope();
|
|
||||||
self.ch4.tickEnvelope();
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn handleTimerOverflow(self: *Self, cpu: *Arm7tdmi, tim_id: u3) void {
|
pub fn handleTimerOverflow(self: *Self, cpu: *Arm7tdmi, tim_id: u3) void {
|
||||||
if (!self.cnt.apu_enable.read()) return;
|
if (!self.cnt.apu_enable.read()) return;
|
||||||
|
|
||||||
|
@ -236,18 +134,6 @@ pub const Apu = struct {
|
||||||
if (self.chB.len() <= 15) cpu.bus.dma._2.enableSoundDma(0x0400_00A4);
|
if (self.chB.len() <= 15) cpu.bus.dma._2.enableSoundDma(0x0400_00A4);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn highPassFilter(self: *Self, sample: f32, ch_enabled: bool) f32 {
|
|
||||||
const charge_factor = 0.999958;
|
|
||||||
|
|
||||||
var output: f32 = 0;
|
|
||||||
if (ch_enabled) {
|
|
||||||
output = sample - self.capacitor;
|
|
||||||
self.capacitor = sample - output * std.math.pow(f32, charge_factor, @intToFloat(f32, (1 << 22) / self.sampleRate()));
|
|
||||||
}
|
|
||||||
|
|
||||||
return output;
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const ToneSweep = struct {
|
const ToneSweep = struct {
|
||||||
|
@ -262,185 +148,21 @@ const ToneSweep = struct {
|
||||||
/// NR13, NR14
|
/// NR13, NR14
|
||||||
freq: io.Frequency,
|
freq: io.Frequency,
|
||||||
|
|
||||||
/// Length Functionality
|
fn init() Self {
|
||||||
len_dev: Length,
|
|
||||||
/// Sweep Functionality
|
|
||||||
sweep_dev: Sweep,
|
|
||||||
/// Envelope Functionality
|
|
||||||
env_dev: Envelope,
|
|
||||||
/// Frequency Timer Functionality
|
|
||||||
square: SquareWave,
|
|
||||||
enabled: bool,
|
|
||||||
|
|
||||||
sample: u8,
|
|
||||||
|
|
||||||
const Sweep = struct {
|
|
||||||
const This = @This();
|
|
||||||
|
|
||||||
timer: u8,
|
|
||||||
enabled: bool,
|
|
||||||
shadow: u11,
|
|
||||||
|
|
||||||
pub fn init() This {
|
|
||||||
return .{
|
|
||||||
.timer = 0,
|
|
||||||
.enabled = false,
|
|
||||||
.shadow = 0,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn tick(this: *This, ch1: *Self) void {
|
|
||||||
if (this.timer != 0) this.timer -= 1;
|
|
||||||
|
|
||||||
if (this.timer == 0) {
|
|
||||||
const period = ch1.sweep.period.read();
|
|
||||||
this.timer = if (period == 0) 8 else period;
|
|
||||||
|
|
||||||
if (this.enabled and period != 0) {
|
|
||||||
const new_freq = this.calcFrequency(ch1);
|
|
||||||
|
|
||||||
if (new_freq <= 0x7FF and ch1.sweep.shift.read() != 0) {
|
|
||||||
ch1.freq.frequency.write(@truncate(u11, new_freq));
|
|
||||||
this.shadow = @truncate(u11, new_freq);
|
|
||||||
|
|
||||||
_ = this.calcFrequency(ch1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn calcFrequency(this: *This, ch1: *Self) u12 {
|
|
||||||
const shadow = @as(u12, this.shadow);
|
|
||||||
const shadow_shifted = shadow >> ch1.sweep.shift.read();
|
|
||||||
const decrease = ch1.sweep.direction.read();
|
|
||||||
|
|
||||||
const freq = if (decrease) shadow - shadow_shifted else shadow + shadow_shifted;
|
|
||||||
|
|
||||||
if (freq > 0x7FF) ch1.enabled = false;
|
|
||||||
|
|
||||||
return freq;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
fn init(sched: *Scheduler) Self {
|
|
||||||
return .{
|
return .{
|
||||||
.sweep = .{ .raw = 0 },
|
.sweep = .{ .raw = 0 },
|
||||||
.duty = .{ .raw = 0 },
|
.duty = .{ .raw = 0 },
|
||||||
.envelope = .{ .raw = 0 },
|
.envelope = .{ .raw = 0 },
|
||||||
.freq = .{ .raw = 0 },
|
.freq = .{ .raw = 0 },
|
||||||
.sample = 0,
|
|
||||||
.enabled = false,
|
|
||||||
|
|
||||||
.square = SquareWave.init(sched),
|
|
||||||
.len_dev = Length.init(),
|
|
||||||
.sweep_dev = Sweep.init(),
|
|
||||||
.env_dev = Envelope.init(),
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
fn reset(self: *Self) void {
|
|
||||||
self.sweep.raw = 0;
|
|
||||||
self.duty.raw = 0;
|
|
||||||
self.envelope.raw = 0;
|
|
||||||
self.freq.raw = 0;
|
|
||||||
|
|
||||||
self.sample = 0;
|
|
||||||
self.enabled = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn tickSweep(self: *Self) void {
|
|
||||||
self.sweep_dev.tick(self);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn tickLength(self: *Self) void {
|
|
||||||
self.len_dev.tick(self.freq, &self.enabled);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn tickEnvelope(self: *Self) void {
|
|
||||||
self.env_dev.tick(self.envelope);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn channelTimerOverflow(self: *Self, late: u64) void {
|
|
||||||
self.square.handleTimerOverflow(self.freq, late);
|
|
||||||
|
|
||||||
self.sample = 0;
|
|
||||||
if (!self.isDacEnabled()) return;
|
|
||||||
self.sample = if (self.enabled) self.square.sample(self.duty) * self.env_dev.vol else 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn amplitude(self: *const Self) f32 {
|
|
||||||
return (@intToFloat(f32, self.sample) / 7.5) - 1.0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// NR11, NR12
|
|
||||||
pub fn setSoundCntH(self: *Self, value: u16) void {
|
|
||||||
self.setDuty(@truncate(u8, value));
|
|
||||||
self.setEnvelope(@truncate(u8, value >> 8));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// NR11
|
|
||||||
pub fn setDuty(self: *Self, value: u8) void {
|
|
||||||
self.duty.raw = value;
|
|
||||||
self.len_dev.timer = @as(u7, self.duty.length.read() ^ 0x3F);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// NR12
|
|
||||||
pub fn setEnvelope(self: *Self, value: u8) void {
|
|
||||||
self.envelope.raw = value;
|
|
||||||
if (!self.isDacEnabled()) self.enabled = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// NR13, NR14
|
|
||||||
pub fn setFreq(self: *Self, fs: *const FrameSequencer, value: u16) void {
|
|
||||||
self.setFreqLow(@truncate(u8, value));
|
|
||||||
self.setFreqHigh(fs, @truncate(u8, value >> 8));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// NR13
|
|
||||||
pub fn setFreqLow(self: *Self, byte: u8) void {
|
pub fn setFreqLow(self: *Self, byte: u8) void {
|
||||||
self.freq.raw = (self.freq.raw & 0xFF00) | byte;
|
self.freq.raw = (self.freq.raw & 0xFF00) | byte;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// NR14
|
pub fn setFreqHigh(self: *Self, byte: u8) void {
|
||||||
pub fn setFreqHigh(self: *Self, fs: *const FrameSequencer, byte: u8) void {
|
self.freq.raw = (@as(u16, byte) << 8) | (self.freq.raw & 0xFF);
|
||||||
var new: io.Frequency = .{ .raw = (@as(u16, byte) << 8) | (self.freq.raw & 0xFF) };
|
|
||||||
|
|
||||||
if (new.trigger.read()) {
|
|
||||||
self.enabled = true; // FIXME: is this necessary?
|
|
||||||
|
|
||||||
if (self.len_dev.timer == 0) {
|
|
||||||
self.len_dev.timer = 64;
|
|
||||||
|
|
||||||
// We unset this on the old frequency because of the obscure
|
|
||||||
// behaviour outside of this if statement's scope
|
|
||||||
self.freq.length_enable.write(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Reload Frequency Timer
|
|
||||||
self.square.reloadTimer(self.freq.frequency.read());
|
|
||||||
|
|
||||||
// Reload Envelope period and timer
|
|
||||||
self.env_dev.timer = self.envelope.period.read();
|
|
||||||
self.env_dev.vol = self.envelope.init_vol.read();
|
|
||||||
|
|
||||||
// Sweep Trigger Behaviour
|
|
||||||
const sw_period = self.sweep.period.read();
|
|
||||||
const sw_shift = self.sweep.shift.read();
|
|
||||||
|
|
||||||
self.sweep_dev.shadow = self.freq.frequency.read();
|
|
||||||
self.sweep_dev.timer = if (sw_period == 0) 8 else sw_period;
|
|
||||||
self.sweep_dev.enabled = sw_period != 0 or sw_shift != 0;
|
|
||||||
if (sw_shift != 0) _ = self.sweep_dev.calcFrequency(self);
|
|
||||||
|
|
||||||
self.enabled = self.isDacEnabled();
|
|
||||||
}
|
|
||||||
|
|
||||||
self.square.updateLength(fs, self, new);
|
|
||||||
self.freq = new;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn isDacEnabled(self: *const Self) bool {
|
|
||||||
return self.envelope.raw & 0xF8 != 0;
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -454,34 +176,14 @@ const Tone = struct {
|
||||||
/// NR23, NR24
|
/// NR23, NR24
|
||||||
freq: io.Frequency,
|
freq: io.Frequency,
|
||||||
|
|
||||||
/// Length Functionarlity
|
|
||||||
len_dev: Length,
|
|
||||||
|
|
||||||
/// Envelope Functionality
|
|
||||||
env_dev: Envelope,
|
|
||||||
|
|
||||||
enabled: bool,
|
|
||||||
|
|
||||||
fn init() Self {
|
fn init() Self {
|
||||||
return .{
|
return .{
|
||||||
.duty = .{ .raw = 0 },
|
.duty = .{ .raw = 0 },
|
||||||
.envelope = .{ .raw = 0 },
|
.envelope = .{ .raw = 0 },
|
||||||
.freq = .{ .raw = 0 },
|
.freq = .{ .raw = 0 },
|
||||||
.enabled = false,
|
|
||||||
|
|
||||||
.len_dev = Length.init(),
|
|
||||||
.env_dev = Envelope.init(),
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn tickLength(self: *Self) void {
|
|
||||||
self.len_dev.tick(self.freq, &self.enabled);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn tickEnvelope(self: *Self) void {
|
|
||||||
self.env_dev.tick(self.envelope);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn setFreqLow(self: *Self, byte: u8) void {
|
pub fn setFreqLow(self: *Self, byte: u8) void {
|
||||||
self.freq.raw = (self.freq.raw & 0xFF00) | byte;
|
self.freq.raw = (self.freq.raw & 0xFF00) | byte;
|
||||||
}
|
}
|
||||||
|
@ -504,27 +206,15 @@ const Wave = struct {
|
||||||
/// NR33, NR34
|
/// NR33, NR34
|
||||||
freq: io.Frequency,
|
freq: io.Frequency,
|
||||||
|
|
||||||
/// Length Functionarlity
|
|
||||||
len_dev: Length,
|
|
||||||
|
|
||||||
enabled: bool,
|
|
||||||
|
|
||||||
fn init() Self {
|
fn init() Self {
|
||||||
return .{
|
return .{
|
||||||
.select = .{ .raw = 0 },
|
.select = .{ .raw = 0 },
|
||||||
.vol = .{ .raw = 0 },
|
.vol = .{ .raw = 0 },
|
||||||
.freq = .{ .raw = 0 },
|
.freq = .{ .raw = 0 },
|
||||||
.length = 0,
|
.length = 0,
|
||||||
|
|
||||||
.len_dev = Length.init(),
|
|
||||||
.enabled = false,
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn tickLength(self: *Self) void {
|
|
||||||
self.len_dev.tick(self.freq, &self.enabled);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn setFreqLow(self: *Self, byte: u8) void {
|
pub fn setFreqLow(self: *Self, byte: u8) void {
|
||||||
self.freq.raw = (self.freq.raw & 0xFF00) | byte;
|
self.freq.raw = (self.freq.raw & 0xFF00) | byte;
|
||||||
}
|
}
|
||||||
|
@ -547,34 +237,14 @@ const Noise = struct {
|
||||||
/// NR44
|
/// NR44
|
||||||
cnt: io.NoiseControl,
|
cnt: io.NoiseControl,
|
||||||
|
|
||||||
/// Length Functionarlity
|
|
||||||
len_dev: Length,
|
|
||||||
|
|
||||||
/// Envelope Functionality
|
|
||||||
env_dev: Envelope,
|
|
||||||
|
|
||||||
enabled: bool,
|
|
||||||
|
|
||||||
fn init() Self {
|
fn init() Self {
|
||||||
return .{
|
return .{
|
||||||
.len = 0,
|
.len = 0,
|
||||||
.envelope = .{ .raw = 0 },
|
.envelope = .{ .raw = 0 },
|
||||||
.poly = .{ .raw = 0 },
|
.poly = .{ .raw = 0 },
|
||||||
.cnt = .{ .raw = 0 },
|
.cnt = .{ .raw = 0 },
|
||||||
.enabled = false,
|
|
||||||
|
|
||||||
.len_dev = Length.init(),
|
|
||||||
.env_dev = Envelope.init(),
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn tickLength(self: *Self) void {
|
|
||||||
self.len_dev.ch4Tick(self.cnt, &self.enabled);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn tickEnvelope(self: *Self) void {
|
|
||||||
self.env_dev.tick(self.envelope);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn DmaSound(comptime kind: DmaSoundKind) type {
|
pub fn DmaSound(comptime kind: DmaSoundKind) type {
|
||||||
|
@ -615,141 +285,3 @@ const DmaSoundKind = enum {
|
||||||
A,
|
A,
|
||||||
B,
|
B,
|
||||||
};
|
};
|
||||||
|
|
||||||
const FrameSequencer = struct {
|
|
||||||
const Self = @This();
|
|
||||||
|
|
||||||
step: u3,
|
|
||||||
|
|
||||||
pub fn init() Self {
|
|
||||||
return .{ .step = 0 };
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn tick(self: *Self) void {
|
|
||||||
self.step +%= 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn lengthIsNext(self: *const Self) bool {
|
|
||||||
return (self.step +% 1) & 1 == 0; // Steps, 0, 2, 4, and 6 clock length
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const Length = struct {
|
|
||||||
const Self = @This();
|
|
||||||
|
|
||||||
timer: u7,
|
|
||||||
|
|
||||||
pub fn init() Self {
|
|
||||||
return .{ .timer = 0 };
|
|
||||||
}
|
|
||||||
|
|
||||||
fn tick(self: *Self, freq: io.Frequency, ch_enabled: *bool) void {
|
|
||||||
const len_enable = freq.length_enable.read();
|
|
||||||
|
|
||||||
if (len_enable and self.timer > 0) {
|
|
||||||
self.timer -= 1;
|
|
||||||
|
|
||||||
// if length timer is now 0
|
|
||||||
if (self.timer == 0) ch_enabled.* = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn ch4Tick(self: *Self, cnt: io.NoiseControl, ch_enabled: *bool) void {
|
|
||||||
const len_enable = cnt.length_enable.read();
|
|
||||||
|
|
||||||
if (len_enable and self.timer > 0) {
|
|
||||||
self.timer -= 1;
|
|
||||||
|
|
||||||
// if length timer is now 0
|
|
||||||
if (self.timer == 0) ch_enabled.* = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const Envelope = struct {
|
|
||||||
const Self = @This();
|
|
||||||
|
|
||||||
/// Period Timer
|
|
||||||
timer: u3,
|
|
||||||
/// Current Volume
|
|
||||||
vol: u4,
|
|
||||||
|
|
||||||
pub fn init() Self {
|
|
||||||
return .{ .timer = 0, .vol = 0 };
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn tick(self: *Self, cnt: io.Envelope) void {
|
|
||||||
if (cnt.period.read() != 0) {
|
|
||||||
if (self.timer != 0) self.timer -= 1;
|
|
||||||
|
|
||||||
if (self.timer == 0) {
|
|
||||||
self.timer = cnt.period.read();
|
|
||||||
|
|
||||||
if (cnt.direction.read()) {
|
|
||||||
if (self.vol < 0xF) self.vol += 1;
|
|
||||||
} else {
|
|
||||||
if (self.vol > 0x0) self.vol -= 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const SquareWave = struct {
|
|
||||||
const Self = @This();
|
|
||||||
const ticks: u64 = (1 << 24) / (1 << 22);
|
|
||||||
|
|
||||||
pos: u3,
|
|
||||||
sched: *Scheduler,
|
|
||||||
timer: u12,
|
|
||||||
|
|
||||||
pub fn init(sched: *Scheduler) Self {
|
|
||||||
return .{
|
|
||||||
.timer = 0,
|
|
||||||
.pos = 0,
|
|
||||||
.sched = sched,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Obscure NRx4 Behaviour
|
|
||||||
fn updateLength(_: *Self, fs: *const FrameSequencer, ch1: *ToneSweep, new_cnt: io.Frequency) void {
|
|
||||||
if (!fs.lengthIsNext() and !ch1.freq.length_enable.read() and new_cnt.length_enable.read() and ch1.len_dev.timer != 0) {
|
|
||||||
ch1.len_dev.timer -= 1;
|
|
||||||
|
|
||||||
if (ch1.len_dev.timer == 0 and !new_cnt.trigger.read()) ch1.enabled = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn handleTimerOverflow(self: *Self, cnt: io.Frequency, late: u64) void {
|
|
||||||
const timer = (2048 - @as(u64, cnt.frequency.read())) * 4;
|
|
||||||
|
|
||||||
self.timer = @truncate(u12, timer);
|
|
||||||
self.pos = (self.pos +% 1) & 7;
|
|
||||||
|
|
||||||
self.sched.push(.{ .ApuChannel = 0 }, self.sched.now() + timer * ticks - late);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn reloadTimer(self: *Self, value: u11) void {
|
|
||||||
self.sched.removeScheduledEvent(.{ .ApuChannel = 0 });
|
|
||||||
|
|
||||||
const tmp: u64 = (2048 - @as(u64, value)) * 4; // What Freq Timer should be assuming no weird behaviour
|
|
||||||
const timer = (tmp & ~@as(u64, 0x3)) | self.timer & 0x3; // Keep the last two bits from the old timer
|
|
||||||
self.timer = @truncate(u12, timer);
|
|
||||||
|
|
||||||
self.sched.push(.{ .ApuChannel = 0 }, self.sched.now() + timer * ticks);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn sample(self: *const Self, cnt: io.Duty) u1 {
|
|
||||||
const pattern = cnt.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 @truncate(u1, result);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
|
@ -118,7 +118,7 @@ pub fn read(bus: *const Bus, comptime T: type, address: u32) T {
|
||||||
0x0400_0073 => bus.apu.ch3.vol.raw,
|
0x0400_0073 => bus.apu.ch3.vol.raw,
|
||||||
0x0400_0079 => bus.apu.ch4.envelope.raw,
|
0x0400_0079 => bus.apu.ch4.envelope.raw,
|
||||||
0x0400_007C => bus.apu.ch4.poly.raw,
|
0x0400_007C => bus.apu.ch4.poly.raw,
|
||||||
0x0400_0081 => @truncate(T, bus.apu.psg_cnt.raw >> 8),
|
0x0400_0081 => @truncate(T, bus.apu.ch_vol_cnt.raw >> 8),
|
||||||
0x0400_0089 => @truncate(T, bus.apu.bias.raw >> 8),
|
0x0400_0089 => @truncate(T, bus.apu.bias.raw >> 8),
|
||||||
|
|
||||||
// Serial Communication 1
|
// Serial Communication 1
|
||||||
|
@ -151,7 +151,7 @@ pub fn write(bus: *Bus, comptime T: type, address: u32, value: T) void {
|
||||||
|
|
||||||
// Sound
|
// Sound
|
||||||
0x0400_0080 => {
|
0x0400_0080 => {
|
||||||
bus.apu.psg_cnt.raw = @truncate(u16, value);
|
bus.apu.ch_vol_cnt.raw = @truncate(u16, value);
|
||||||
bus.apu.dma_cnt.raw = @truncate(u16, value >> 16);
|
bus.apu.dma_cnt.raw = @truncate(u16, value >> 16);
|
||||||
},
|
},
|
||||||
0x0400_00A0 => bus.apu.chA.push(value),
|
0x0400_00A0 => bus.apu.chA.push(value),
|
||||||
|
@ -232,10 +232,7 @@ pub fn write(bus: *Bus, comptime T: type, address: u32, value: T) void {
|
||||||
0x0400_004E, 0x0400_0056 => {}, // Not used
|
0x0400_004E, 0x0400_0056 => {}, // Not used
|
||||||
|
|
||||||
// Sound
|
// Sound
|
||||||
0x0400_0060 => bus.apu.ch1.sweep.raw = @truncate(u8, value),
|
0x0400_0080 => bus.apu.ch_vol_cnt.raw = value,
|
||||||
0x0400_0062 => bus.apu.ch1.setSoundCntH(value),
|
|
||||||
0x0400_0064 => bus.apu.ch1.setFreq(&bus.apu.fs, value),
|
|
||||||
0x0400_0080 => bus.apu.psg_cnt.raw = value,
|
|
||||||
0x0400_0082 => bus.apu.setDmaCnt(value),
|
0x0400_0082 => bus.apu.setDmaCnt(value),
|
||||||
0x0400_0084 => bus.apu.setSoundCntX(value >> 7 & 1 == 1),
|
0x0400_0084 => bus.apu.setSoundCntX(value >> 7 & 1 == 1),
|
||||||
0x0400_0088 => bus.apu.bias.raw = value,
|
0x0400_0088 => bus.apu.bias.raw = value,
|
||||||
|
@ -317,7 +314,7 @@ pub fn write(bus: *Bus, comptime T: type, address: u32, value: T) void {
|
||||||
0x0400_0062 => bus.apu.ch1.duty.raw = value,
|
0x0400_0062 => bus.apu.ch1.duty.raw = value,
|
||||||
0x0400_0063 => bus.apu.ch1.envelope.raw = value,
|
0x0400_0063 => bus.apu.ch1.envelope.raw = value,
|
||||||
0x0400_0064 => bus.apu.ch1.setFreqLow(value),
|
0x0400_0064 => bus.apu.ch1.setFreqLow(value),
|
||||||
0x0400_0065 => bus.apu.ch1.setFreqHigh(&bus.apu.fs, value),
|
0x0400_0065 => bus.apu.ch1.setFreqHigh(value),
|
||||||
0x0400_0068 => bus.apu.ch2.duty.raw = value,
|
0x0400_0068 => bus.apu.ch2.duty.raw = value,
|
||||||
0x0400_0069 => bus.apu.ch2.envelope.raw = value,
|
0x0400_0069 => bus.apu.ch2.envelope.raw = value,
|
||||||
0x0400_006C => bus.apu.ch2.setFreqLow(value),
|
0x0400_006C => bus.apu.ch2.setFreqLow(value),
|
||||||
|
@ -581,8 +578,8 @@ pub const NoiseControl = extern union {
|
||||||
|
|
||||||
/// Read / Write
|
/// Read / Write
|
||||||
pub const ChannelVolumeControl = extern union {
|
pub const ChannelVolumeControl = extern union {
|
||||||
right_vol: Bitfield(u16, 0, 3),
|
left_vol: Bitfield(u16, 0, 3),
|
||||||
left_vol: Bitfield(u16, 4, 3),
|
right_vol: Bitfield(u16, 4, 3),
|
||||||
|
|
||||||
ch1_right: Bit(u16, 8),
|
ch1_right: Bit(u16, 8),
|
||||||
ch2_right: Bit(u16, 9),
|
ch2_right: Bit(u16, 9),
|
||||||
|
|
|
@ -51,13 +51,6 @@ pub const Scheduler = struct {
|
||||||
3 => cpu.bus.tim._3.handleOverflow(cpu, late),
|
3 => cpu.bus.tim._3.handleOverflow(cpu, late),
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
.ApuChannel => |id| {
|
|
||||||
switch (id) {
|
|
||||||
0 => cpu.bus.apu.ch1.channelTimerOverflow(late),
|
|
||||||
else => {},
|
|
||||||
}
|
|
||||||
},
|
|
||||||
.FrameSequencer => cpu.bus.apu.tickFrameSequencer(late),
|
|
||||||
.SampleAudio => cpu.bus.apu.sampleAudio(late),
|
.SampleAudio => cpu.bus.apu.sampleAudio(late),
|
||||||
.HBlank => cpu.bus.ppu.handleHBlankEnd(cpu, late), // The end of a HBlank
|
.HBlank => cpu.bus.ppu.handleHBlankEnd(cpu, late), // The end of a HBlank
|
||||||
.VBlank => cpu.bus.ppu.handleHDrawEnd(cpu, late), // The end of a VBlank
|
.VBlank => cpu.bus.ppu.handleHDrawEnd(cpu, late), // The end of a VBlank
|
||||||
|
@ -111,6 +104,4 @@ pub const EventKind = union(enum) {
|
||||||
Draw,
|
Draw,
|
||||||
TimerOverflow: u2,
|
TimerOverflow: u2,
|
||||||
SampleAudio,
|
SampleAudio,
|
||||||
FrameSequencer,
|
|
||||||
ApuChannel: u2,
|
|
||||||
};
|
};
|
||||||
|
|
Loading…
Reference in New Issue