feat: implement ch1
TODO: It's really loud
This commit is contained in:
		
							
								
								
									
										181
									
								
								src/apu.zig
									
									
									
									
									
								
							
							
						
						
									
										181
									
								
								src/apu.zig
									
									
									
									
									
								
							| @@ -24,7 +24,8 @@ pub const Apu = struct { | ||||
|     chB: DmaSound(.B), | ||||
|  | ||||
|     bias: io.SoundBias, | ||||
|     ch_vol_cnt: io.ChannelVolumeControl, | ||||
|     /// NR51 | ||||
|     psg_cnt: io.ChannelVolumeControl, | ||||
|     dma_cnt: io.DmaSoundControl, | ||||
|     cnt: io.SoundControl, | ||||
|  | ||||
| @@ -33,6 +34,7 @@ pub const Apu = struct { | ||||
|     sched: *Scheduler, | ||||
|  | ||||
|     fs: FrameSequencer, | ||||
|     capacitor: f32, | ||||
|  | ||||
|     pub fn init(sched: *Scheduler) Self { | ||||
|         const apu: Self = .{ | ||||
| @@ -43,7 +45,7 @@ pub const Apu = struct { | ||||
|             .chA = DmaSound(.A).init(), | ||||
|             .chB = DmaSound(.B).init(), | ||||
|  | ||||
|             .ch_vol_cnt = .{ .raw = 0 }, | ||||
|             .psg_cnt = .{ .raw = 0 }, | ||||
|             .dma_cnt = .{ .raw = 0 }, | ||||
|             .cnt = .{ .raw = 0 }, | ||||
|             .bias = .{ .raw = 0x0200 }, | ||||
| @@ -52,16 +54,22 @@ pub const Apu = struct { | ||||
|             .stream = SDL.SDL_NewAudioStream(SDL.AUDIO_F32, 2, 1 << 15, SDL.AUDIO_F32, 2, host_sample_rate) orelse unreachable, | ||||
|             .sched = sched, | ||||
|  | ||||
|             .capacitor = 0, | ||||
|             .fs = FrameSequencer.init(), | ||||
|         }; | ||||
|  | ||||
|         sched.push(.SampleAudio, sched.now() + apu.sampleTicks()); | ||||
|         sched.push(.{ .ApuChannel = 0 }, sched.now() + SquareWave.ticks); | ||||
|         sched.push(.FrameSequencer, sched.now() + ((1 << 24) / 1 << 15)); | ||||
|         sched.push(.FrameSequencer, sched.now() + ((1 << 24) / 512)); | ||||
|  | ||||
|         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 { | ||||
|         const new: io.DmaSoundControl = .{ .raw = value }; | ||||
|  | ||||
| @@ -80,11 +88,12 @@ pub const Apu = struct { | ||||
|         if (value) { | ||||
|             self.fs.step = 0; // Reset Frame Sequencer | ||||
|  | ||||
|             // TODO: Reset Duty position for Square channels | ||||
|             // TODO: Reset Duty Position for Ch2 Square | ||||
|             self.ch1.square.pos = 0; | ||||
|  | ||||
|             // TODO: Reset Channel 3 offset ptr | ||||
|         } else { | ||||
|             // TODO: Reset APU | ||||
|             self.reset(); | ||||
|         } | ||||
|     } | ||||
|  | ||||
| @@ -102,12 +111,12 @@ pub const Apu = struct { | ||||
|  | ||||
|     /// NR50 | ||||
|     pub fn setSoundCntLLow(self: *Self, byte: u8) void { | ||||
|         self.ch_vol_cnt.raw = (self.ch_vol_cnt.raw & 0xFF00) | byte; | ||||
|         self.psg_cnt.raw = (self.psg_cnt.raw & 0xFF00) | byte; | ||||
|     } | ||||
|  | ||||
|     /// NR51 | ||||
|     pub fn setSoundCntLHigh(self: *Self, byte: u8) void { | ||||
|         self.ch_vol_cnt.raw = @as(u16, byte) << 8 | (self.ch_vol_cnt.raw & 0xFF); | ||||
|         self.psg_cnt.raw = @as(u16, byte) << 8 | (self.psg_cnt.raw & 0xFF); | ||||
|     } | ||||
|  | ||||
|     pub fn setBiasHigh(self: *Self, byte: u8) void { | ||||
| @@ -117,9 +126,31 @@ pub const Apu = struct { | ||||
|     pub fn sampleAudio(self: *Self, late: u64) void { | ||||
|  | ||||
|         // Sample Channel 1 | ||||
|         const ch1_sample = self.ch1.amplitude(); | ||||
|         const ch1_left = if (self.ch_vol_cnt.ch1_left.read()) ch1_sample else 0; | ||||
|         const ch1_right = if (self.ch_vol_cnt.ch1_right.read()) ch1_sample else 0; | ||||
|         const ch1_sample = self.highPassFilter(self.ch1.amplitude(), self.ch1.enabled); | ||||
|         const ch1_left = if (self.psg_cnt.ch1_left.read()) ch1_sample else 0; | ||||
|         const ch1_right = if (self.psg_cnt.ch1_right.read()) ch1_sample else 0; | ||||
|  | ||||
|         const mixed_left = ch1_left; | ||||
|         const mixed_right = ch1_right; | ||||
|  | ||||
|         // 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; | ||||
| @@ -131,10 +162,10 @@ pub const Apu = struct { | ||||
|         // const chB_right = if (self.dma_cnt.chB_right.read()) chB else 0; | ||||
|  | ||||
|         // Mix all Channels | ||||
|         // const left = (chA_left + chB_left + ch1_left) / 3; | ||||
|         // const right = (chA_right + chB_right + ch1_right) / 3 | ||||
|         const left = ch1_left; | ||||
|         const right = ch1_right; | ||||
|         // 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()) { | ||||
|             log.warn("Sampling Cycle changed from {} to {}", .{ self.sampling_cycle, self.bias.sampling_cycle.read() }); | ||||
| @@ -176,7 +207,7 @@ pub const Apu = struct { | ||||
|             1, 3, 5 => {}, | ||||
|         } | ||||
|  | ||||
|         self.sched.push(.FrameSequencer, self.sched.now() + ((1 << 24) / 1 << 15) - late); | ||||
|         self.sched.push(.FrameSequencer, self.sched.now() + ((1 << 24) / 512) - late); | ||||
|     } | ||||
|  | ||||
|     fn tickLengths(self: *Self) void { | ||||
| @@ -205,6 +236,18 @@ pub const Apu = struct { | ||||
|             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 { | ||||
| @@ -221,15 +264,12 @@ const ToneSweep = struct { | ||||
|  | ||||
|     /// Length Functionality | ||||
|     len_dev: Length, | ||||
|  | ||||
|     /// Sweep Functionality | ||||
|     sweep_dev: Sweep, | ||||
|  | ||||
|     /// Envelope Functionality | ||||
|     env_dev: Envelope, | ||||
|  | ||||
|     /// Frequency Timer Functionality | ||||
|     square: SquareWave, | ||||
|  | ||||
|     enabled: bool, | ||||
|  | ||||
|     sample: u8, | ||||
| @@ -257,11 +297,11 @@ const ToneSweep = struct { | ||||
|                 this.timer = if (period == 0) 8 else period; | ||||
|  | ||||
|                 if (this.enabled and period != 0) { | ||||
|                     const new_freq: u11 = this.calcFrequency(ch1); | ||||
|                     const new_freq = this.calcFrequency(ch1); | ||||
|  | ||||
|                     if (new_freq <= 0x7FF and ch1.sweep.shift.read() != 0) { | ||||
|                         ch1.freq.frequency.write(new_freq); | ||||
|                         this.shadow = new_freq; | ||||
|                         ch1.freq.frequency.write(@truncate(u11, new_freq)); | ||||
|                         this.shadow = @truncate(u11, new_freq); | ||||
|  | ||||
|                         _ = this.calcFrequency(ch1); | ||||
|                     } | ||||
| @@ -269,10 +309,12 @@ const ToneSweep = struct { | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         fn calcFrequency(this: *This, ch1: *Self) u11 { | ||||
|             const shadow_shifted = this.shadow >> ch1.sweep.shift.read(); | ||||
|         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) this.shadow - shadow_shifted else this.shadow + shadow_shifted; | ||||
|  | ||||
|             const freq = if (decrease) shadow - shadow_shifted else shadow + shadow_shifted; | ||||
|  | ||||
|             if (freq > 0x7FF) ch1.enabled = false; | ||||
|  | ||||
| @@ -296,6 +338,16 @@ const ToneSweep = struct { | ||||
|         }; | ||||
|     } | ||||
|  | ||||
|     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); | ||||
|     } | ||||
| @@ -313,17 +365,23 @@ const ToneSweep = struct { | ||||
|  | ||||
|         self.sample = 0; | ||||
|         if (!self.isDacEnabled()) return; | ||||
|         self.sample = if (self.enabled) self.square.getSample(self.duty) * self.env_dev.vol else 0; | ||||
|         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 = 64 - self.duty.length.read(); | ||||
|         self.len_dev.timer = @as(u7, self.duty.length.read() ^ 0x3F); | ||||
|     } | ||||
|  | ||||
|     /// NR12 | ||||
| @@ -332,25 +390,35 @@ const ToneSweep = struct { | ||||
|         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 { | ||||
|         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 { | ||||
|         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; | ||||
|  | ||||
|                 // FIXME: This conflicts with my GB emulator | ||||
|                 new.length_enable.write(false); | ||||
|                 // 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 (last two bits unmodified) | ||||
|             // 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(); | ||||
| @@ -367,6 +435,7 @@ const ToneSweep = struct { | ||||
|             self.enabled = self.isDacEnabled(); | ||||
|         } | ||||
|  | ||||
|         self.square.updateLength(fs, self, new); | ||||
|         self.freq = new; | ||||
|     } | ||||
|  | ||||
| @@ -559,12 +628,16 @@ const FrameSequencer = struct { | ||||
|     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: u16, | ||||
|     timer: u7, | ||||
|  | ||||
|     pub fn init() Self { | ||||
|         return .{ .timer = 0 }; | ||||
| @@ -613,9 +686,9 @@ const Envelope = struct { | ||||
|                 self.timer = cnt.period.read(); | ||||
|  | ||||
|                 if (cnt.direction.read()) { | ||||
|                     if (self.vol > 0x0) self.vol -= 1; | ||||
|                 } else { | ||||
|                     if (self.vol < 0xF) self.vol += 1; | ||||
|                 } else { | ||||
|                     if (self.vol > 0x0) self.vol -= 1; | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
| @@ -624,43 +697,57 @@ const Envelope = struct { | ||||
|  | ||||
| const SquareWave = struct { | ||||
|     const Self = @This(); | ||||
|     const ticks: u64 = (1 << 24) / (1 << 18); | ||||
|     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, | ||||
|         }; | ||||
|     } | ||||
|  | ||||
|     fn handleTimerOverflow(self: *Self, cnt: io.Frequency, late: u64) void { | ||||
|         const when = (2048 - @as(u64, cnt.frequency.read())) * 4; | ||||
|     /// 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 }, when * ticks - late); | ||||
|  | ||||
|         self.sched.push(.{ .ApuChannel = 0 }, self.sched.now() + timer * ticks - late); | ||||
|     } | ||||
|  | ||||
|     fn reloadTimer(self: *Self, value: u11) void { | ||||
|         self.sched.removeScheduledEvent(.{ .ApuChannel = 0 }); | ||||
|  | ||||
|         // TODO: Implement Obscure Behaviour | ||||
|         const when = (2048 - @as(u64, value)) * 4; | ||||
|         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 }, when * ticks); | ||||
|         self.sched.push(.{ .ApuChannel = 0 }, self.sched.now() + timer * ticks); | ||||
|     } | ||||
|  | ||||
|     fn getSample(self: *const Self, cnt: io.Duty) u1 { | ||||
|         const pattern = cnt.pattern.read(); // 2^18 | ||||
|     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, // 1/8th | ||||
|             0b01 => @as(u8, 0b10000001) >> i, // 1/4th | ||||
|             0b10 => @as(u8, 0b10000111) >> i, // 1/2nd | ||||
|             0b11 => @as(u8, 0b01111110) >> i, // 3/4th | ||||
|             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_0079 => bus.apu.ch4.envelope.raw, | ||||
|             0x0400_007C => bus.apu.ch4.poly.raw, | ||||
|             0x0400_0081 => @truncate(T, bus.apu.ch_vol_cnt.raw >> 8), | ||||
|             0x0400_0081 => @truncate(T, bus.apu.psg_cnt.raw >> 8), | ||||
|             0x0400_0089 => @truncate(T, bus.apu.bias.raw >> 8), | ||||
|  | ||||
|             // Serial Communication 1 | ||||
| @@ -151,7 +151,7 @@ pub fn write(bus: *Bus, comptime T: type, address: u32, value: T) void { | ||||
|  | ||||
|             // Sound | ||||
|             0x0400_0080 => { | ||||
|                 bus.apu.ch_vol_cnt.raw = @truncate(u16, value); | ||||
|                 bus.apu.psg_cnt.raw = @truncate(u16, value); | ||||
|                 bus.apu.dma_cnt.raw = @truncate(u16, value >> 16); | ||||
|             }, | ||||
|             0x0400_00A0 => bus.apu.chA.push(value), | ||||
| @@ -232,7 +232,10 @@ pub fn write(bus: *Bus, comptime T: type, address: u32, value: T) void { | ||||
|             0x0400_004E, 0x0400_0056 => {}, // Not used | ||||
|  | ||||
|             // Sound | ||||
|             0x0400_0080 => bus.apu.ch_vol_cnt.raw = value, | ||||
|             0x0400_0060 => bus.apu.ch1.sweep.raw = @truncate(u8, 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_0084 => bus.apu.setSoundCntX(value >> 7 & 1 == 1), | ||||
|             0x0400_0088 => bus.apu.bias.raw = value, | ||||
| @@ -314,7 +317,7 @@ pub fn write(bus: *Bus, comptime T: type, address: u32, value: T) void { | ||||
|             0x0400_0062 => bus.apu.ch1.duty.raw = value, | ||||
|             0x0400_0063 => bus.apu.ch1.envelope.raw = value, | ||||
|             0x0400_0064 => bus.apu.ch1.setFreqLow(value), | ||||
|             0x0400_0065 => bus.apu.ch1.setFreqHigh(value), | ||||
|             0x0400_0065 => bus.apu.ch1.setFreqHigh(&bus.apu.fs, value), | ||||
|             0x0400_0068 => bus.apu.ch2.duty.raw = value, | ||||
|             0x0400_0069 => bus.apu.ch2.envelope.raw = value, | ||||
|             0x0400_006C => bus.apu.ch2.setFreqLow(value), | ||||
| @@ -578,8 +581,8 @@ pub const NoiseControl = extern union { | ||||
|  | ||||
| /// Read / Write | ||||
| pub const ChannelVolumeControl = extern union { | ||||
|     left_vol: Bitfield(u16, 0, 3), | ||||
|     right_vol: Bitfield(u16, 4, 3), | ||||
|     right_vol: Bitfield(u16, 0, 3), | ||||
|     left_vol: Bitfield(u16, 4, 3), | ||||
|  | ||||
|     ch1_right: Bit(u16, 8), | ||||
|     ch2_right: Bit(u16, 9), | ||||
|   | ||||
		Reference in New Issue
	
	Block a user