diff --git a/src/apu.zig b/src/apu.zig index ea9148a..ea12bd3 100644 --- a/src/apu.zig +++ b/src/apu.zig @@ -53,7 +53,7 @@ pub const Apu = struct { .bias = .{ .raw = 0x0200 }, .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_U16, 2, 1 << 15, SDL.AUDIO_U16, 2, host_sample_rate) orelse unreachable, .sched = sched, .capacitor = 0, @@ -133,64 +133,60 @@ pub const Apu = struct { } pub fn sampleAudio(self: *Self, late: u64) void { - // zig fmt: off - const any_ch_enabled = self.ch1.enabled - or self.ch2.enabled - or self.ch3.enabled - or self.ch4.enabled; - // zig fmt: on + var left: i16 = 0; + var right: i16 = 0; + // SOUNDCNT_L Channel Enable flags const ch_left: u4 = self.psg_cnt.ch_left.read(); const ch_right: u4 = self.psg_cnt.ch_right.read(); - // FIXME: Obscure behaviour? - // Apply NR50 Volume Modifications - const left_master_vol = (@intToFloat(f32, self.psg_cnt.left_vol.read()) + 1.0) / 7.0; - const right_master_vol = (@intToFloat(f32, self.psg_cnt.right_vol.read()) + 1.0) / 7.0; - - // Apply SOUNDCNT_H Volume Modifications - const gba_vol: f32 = switch (self.dma_cnt.ch_vol.read()) { - 0b00 => 0.25, - 0b01 => 0.5, - 0b10 => 0.75, - 0b11 => 0.0, + // Determine SOUNDCNT_H volume modifications + const gba_vol: u4 = switch (self.dma_cnt.ch_vol.read()) { + 0b00 => 2, + 0b01 => 1, + else => 0, }; - // Sample Channel 1 - const ch1_sample = self.highPass(self.ch1.amplitude(), any_ch_enabled); - const ch1_left = if (ch_left & 1 == 1) ch1_sample else 0; - const ch1_right = if (ch_right & 1 == 1) ch1_sample else 0; + // Add all PSG channels together + left += if (ch_left & 1 == 1) self.ch1.amplitude() else 0; + left += if (ch_left >> 1 & 1 == 1) self.ch2.amplitude() else 0; + left += if (ch_left >> 2 & 1 == 1) self.ch3.amplitude() else 0; + left += if (ch_left >> 3 == 1) self.ch4.amplitude() else 0; - // Sample Channel 2 - const ch2_sample = self.highPass(self.ch2.amplitude(), any_ch_enabled); - const ch2_left = if (ch_left >> 1 & 1 == 1) ch2_sample else 0; - const ch2_right = if (ch_right >> 1 & 1 == 1) ch2_sample else 0; + right += if (ch_right & 1 == 1) self.ch1.amplitude() else 0; + right += if (ch_right >> 1 & 1 == 1) self.ch2.amplitude() else 0; + right += if (ch_right >> 2 & 1 == 1) self.ch3.amplitude() else 0; + right += if (ch_right >> 3 == 1) self.ch4.amplitude() else 0; - // Sample Channel 3 - const ch3_sample = self.highPass(self.ch3.amplitude(), any_ch_enabled); - const ch3_left = if (ch_left >> 2 & 1 == 1) ch3_sample else 0; - const ch3_right = if (ch_right >> 2 & 1 == 1) ch3_sample else 0; + // Multiply by master channel volume + left *= 1 + @as(i16, self.psg_cnt.left_vol.read()); + right *= 1 + @as(i16, self.psg_cnt.right_vol.read()); - // Sample Channel 4 - const ch4_sample = self.highPass(self.ch4.amplitude(), any_ch_enabled); - const ch4_left = if (ch_left >> 3 == 1) ch4_sample else 0; - const ch4_right = if (ch_right >> 3 == 1) ch4_sample else 0; + // Apply GBA volume modifications to PSG Channels + left >>= gba_vol; + right >>= gba_vol; - const psg_left = (ch1_left + ch2_left + ch3_left + ch4_left) * left_master_vol * gba_vol; - const psg_right = (ch1_right + ch2_right + ch3_right + ch4_right) * right_master_vol * gba_vol; + const chA_sample = self.chA.amplitude() << if (self.dma_cnt.chA_vol.read()) @as(u4, 2) else 1; + const chB_sample = self.chB.amplitude() << if (self.dma_cnt.chB_vol.read()) @as(u4, 2) else 1; - // Sample Dma Channels - const chA_sample = if (self.dma_cnt.chA_vol.read()) self.chA.amplitude() * 4 else self.chA.amplitude() * 2; - const chA_left = if (self.dma_cnt.chA_left.read()) chA_sample else 0; - const chA_right = if (self.dma_cnt.chA_right.read()) chA_sample else 0; + left += if (self.dma_cnt.chA_left.read()) chA_sample else 0; + left += if (self.dma_cnt.chB_left.read()) chB_sample else 0; - const chB_sample = if (self.dma_cnt.chB_vol.read()) self.chB.amplitude() * 4 else self.chB.amplitude() * 2; - const chB_left = if (self.dma_cnt.chB_left.read()) chB_sample else 0; - const chB_right = if (self.dma_cnt.chB_right.read()) chB_sample else 0; + right += if (self.dma_cnt.chA_right.read()) chA_sample else 0; + right += if (self.dma_cnt.chB_right.read()) chB_sample else 0; - // Mix all Channels - const left = (chA_left + chB_left + psg_left) / 6.0; - const right = (chA_right + chB_right + psg_right) / 6.0; + // Add SOUNDBIAS + // FIXME: Is SOUNDBIAS 9-bit or 10-bit? + const bias = @as(i16, self.bias.level.read()) << 1; + left += bias; + right += bias; + + const tmp_left = std.math.clamp(@intCast(u16, left), std.math.minInt(u11), std.math.maxInt(u11)); + const tmp_right = std.math.clamp(@intCast(u16, left), std.math.minInt(u11), std.math.maxInt(u11)); + + // Extend to 16-bit signed audio samples + const final_left = (tmp_left << 5) | (tmp_left >> 6); + const final_right = (tmp_right << 5) | (tmp_right >> 6); if (self.sampling_cycle != self.bias.sampling_cycle.read()) { log.info("Sampling Cycle changed from {} to {}", .{ self.sampling_cycle, self.bias.sampling_cycle.read() }); @@ -201,15 +197,92 @@ pub const Apu = struct { defer SDL.SDL_FreeAudioStream(old); self.sampling_cycle = self.bias.sampling_cycle.read(); - self.stream = SDL.SDL_NewAudioStream(SDL.AUDIO_F32, 2, @intCast(c_int, self.sampleRate()), SDL.AUDIO_F32, 2, host_sample_rate) orelse unreachable; + self.stream = SDL.SDL_NewAudioStream(SDL.AUDIO_U16, 2, @intCast(c_int, self.sampleRate()), SDL.AUDIO_U16, 2, host_sample_rate) orelse unreachable; } - while (SDL.SDL_AudioStreamAvailable(self.stream) > (@sizeOf(f32) * 2 * 0x800)) {} + while (SDL.SDL_AudioStreamAvailable(self.stream) > (@sizeOf(i16) * 2 * 0x800)) {} - _ = SDL.SDL_AudioStreamPut(self.stream, &[2]f32{ left, right }, 2 * @sizeOf(f32)); + _ = SDL.SDL_AudioStreamPut(self.stream, &[2]u16{ final_left, final_right }, 2 * @sizeOf(u16)); self.sched.push(.SampleAudio, self.sampleTicks() -| late); } + // pub fn sampleAudio(self: *Self, late: u64) void { + // // zig fmt: off + // const any_ch_enabled = self.ch1.enabled + // or self.ch2.enabled + // or self.ch3.enabled + // or self.ch4.enabled; + // // zig fmt: on + + // const ch_left: u4 = self.psg_cnt.ch_left.read(); + // const ch_right: u4 = self.psg_cnt.ch_right.read(); + + // // FIXME: Obscure behaviour? + // // Apply NR50 Volume Modifications + // const left_master_vol = (@intToFloat(f32, self.psg_cnt.left_vol.read()) + 1.0) / 7.0; + // const right_master_vol = (@intToFloat(f32, self.psg_cnt.right_vol.read()) + 1.0) / 7.0; + + // // Apply SOUNDCNT_H Volume Modifications + // const gba_vol: f32 = switch (self.dma_cnt.ch_vol.read()) { + // 0b00 => 0.25, + // 0b01 => 0.5, + // else => 1.0, + // }; + + // // Sample Channel 1 + // const ch1_sample = self.highPass(self.ch1.amplitude(), any_ch_enabled); + // const ch1_left = if (ch_left & 1 == 1) ch1_sample else 0; + // const ch1_right = if (ch_right & 1 == 1) ch1_sample else 0; + + // // Sample Channel 2 + // const ch2_sample = self.highPass(self.ch2.amplitude(), any_ch_enabled); + // const ch2_left = if (ch_left >> 1 & 1 == 1) ch2_sample else 0; + // const ch2_right = if (ch_right >> 1 & 1 == 1) ch2_sample else 0; + + // // Sample Channel 3 + // const ch3_sample = self.highPass(self.ch3.amplitude(), any_ch_enabled); + // const ch3_left = if (ch_left >> 2 & 1 == 1) ch3_sample else 0; + // const ch3_right = if (ch_right >> 2 & 1 == 1) ch3_sample else 0; + + // // Sample Channel 4 + // const ch4_sample = self.highPass(self.ch4.amplitude(), any_ch_enabled); + // const ch4_left = if (ch_left >> 3 == 1) ch4_sample else 0; + // const ch4_right = if (ch_right >> 3 == 1) ch4_sample else 0; + + // const psg_left = (ch1_left + ch2_left + ch3_left + ch4_left) * left_master_vol * gba_vol; + // const psg_right = (ch1_right + ch2_right + ch3_right + ch4_right) * right_master_vol * gba_vol; + + // // Sample Dma Channels + // const chA_sample = if (self.dma_cnt.chA_vol.read()) self.chA.amplitude() * 4 else self.chA.amplitude() * 2; + // const chA_left = if (self.dma_cnt.chA_left.read()) chA_sample else 0; + // const chA_right = if (self.dma_cnt.chA_right.read()) chA_sample else 0; + + // const chB_sample = if (self.dma_cnt.chB_vol.read()) self.chB.amplitude() * 4 else self.chB.amplitude() * 2; + // const chB_left = if (self.dma_cnt.chB_left.read()) chB_sample else 0; + // const chB_right = if (self.dma_cnt.chB_right.read()) chB_sample else 0; + + // // Mix all Channels + // const left = (chA_left + chB_left + psg_left) / 6.0; + // const right = (chA_right + chB_right + psg_right) / 6.0; + + // if (self.sampling_cycle != self.bias.sampling_cycle.read()) { + // log.info("Sampling Cycle changed from {} to {}", .{ self.sampling_cycle, self.bias.sampling_cycle.read() }); + + // // Sample Rate Changed, Create a new Resampler since i can't figure out how to change + // // the parameters of the old one + // const old = self.stream; + // defer SDL.SDL_FreeAudioStream(old); + + // self.sampling_cycle = self.bias.sampling_cycle.read(); + // self.stream = SDL.SDL_NewAudioStream(SDL.AUDIO_F32, 2, @intCast(c_int, self.sampleRate()), SDL.AUDIO_F32, 2, host_sample_rate) orelse unreachable; + // } + + // while (SDL.SDL_AudioStreamAvailable(self.stream) > (@sizeOf(f32) * 2 * 0x800)) {} + + // _ = SDL.SDL_AudioStreamPut(self.stream, &[2]f32{ left, right }, 2 * @sizeOf(f32)); + // self.sched.push(.SampleAudio, self.sampleTicks() -| late); + // } + fn sampleTicks(self: *const Self) u64 { return (1 << 24) / self.sampleRate(); } @@ -296,7 +369,7 @@ const ToneSweep = struct { square: SquareWave, enabled: bool, - sample: u8, + sample: i8, const SweepDevice = struct { const This = @This(); @@ -389,11 +462,11 @@ const ToneSweep = struct { self.sample = 0; if (!self.isDacEnabled()) return; - self.sample = if (self.enabled) self.square.sample(self.duty) * self.env_dev.vol else 0; + self.sample = if (self.enabled) self.square.sample(self.duty) * @as(i8, self.env_dev.vol) else 0; } - fn amplitude(self: *const Self) f32 { - return (@intToFloat(f32, self.sample) / 7.5) - 1.0; + fn amplitude(self: *const Self) i16 { + return @as(i16, self.sample); } /// NR11, NR12 @@ -484,7 +557,7 @@ const Tone = struct { square: SquareWave, enabled: bool, - sample: u8, + sample: i8, fn init(sched: *Scheduler) Self { return .{ @@ -523,11 +596,11 @@ const Tone = struct { self.sample = 0; if (!self.isDacEnabled()) return; - self.sample = if (self.enabled) self.square.sample(self.duty) * self.env_dev.vol else 0; + self.sample = if (self.enabled) self.square.sample(self.duty) * @as(i8, self.env_dev.vol) else 0; } - fn amplitude(self: *const Self) f32 { - return (@intToFloat(f32, self.sample) / 7.5) - 1.0; + fn amplitude(self: *const Self) i16 { + return @as(i16, self.sample); } /// NR21, NR22 @@ -609,7 +682,7 @@ const Wave = struct { wave_dev: WaveDevice, enabled: bool, - sample: u8, + sample: i8, fn init(sched: *Scheduler) Self { return .{ @@ -696,11 +769,12 @@ const Wave = struct { self.sample = 0; if (!self.select.enabled.read()) return; - self.sample = if (self.enabled) self.wave_dev.sample(self.select) >> self.wave_dev.shift(self.vol) else 0; + // Convert unsigned 4-bit wave sample to signed 8-bit sample + self.sample = (2 * @as(i8, self.wave_dev.sample(self.select)) - 15) >> self.wave_dev.shift(self.vol); } - fn amplitude(self: *const Self) f32 { - return (@intToFloat(f32, self.sample) / 7.5) - 1.0; + fn amplitude(self: *const Self) i16 { + return @as(i16, self.sample); } }; @@ -727,7 +801,7 @@ const Noise = struct { lfsr: Lfsr, enabled: bool, - sample: u8, + sample: i8, fn init(sched: *Scheduler) Self { return .{ @@ -821,11 +895,11 @@ const Noise = struct { self.sample = 0; if (!self.isDacEnabled()) return; - self.sample = if (self.enabled) self.lfsr.sample() * self.env_dev.vol else 0; + self.sample = if (self.enabled) self.lfsr.sample() * @as(i8, self.env_dev.vol) else 0; } - fn amplitude(self: *const Self) f32 { - return (@intToFloat(f32, self.sample) / 7.5) - 1.0; + fn amplitude(self: *const Self) i16 { + return @as(i16, self.sample); } fn isDacEnabled(self: *const Self) bool { @@ -861,8 +935,8 @@ pub fn DmaSound(comptime kind: DmaSoundKind) type { if (self.fifo.readItem()) |sample| self.sample = @bitCast(i8, sample); } - pub fn amplitude(self: *const Self) f32 { - return @intToFloat(f32, self.sample) / 127.5 - (1 / 255); + pub fn amplitude(self: *const Self) i16 { + return @as(i16, self.sample); } }; } @@ -1100,7 +1174,7 @@ const SquareWave = struct { self.sched.push(.{ .ApuChannel = if (kind == .Ch1) 0 else 1 }, @as(u64, self.timer) * tickInterval); } - fn sample(self: *const Self, cnt: io.Duty) u1 { + fn sample(self: *const Self, cnt: io.Duty) i8 { const pattern = cnt.pattern.read(); const i = self.pos ^ 7; // index of 0 should get highest bit @@ -1111,7 +1185,7 @@ const SquareWave = struct { 0b11 => @as(u8, 0b11111100) >> i, // 75% }; - return @truncate(u1, result); + return if (result & 1 == 1) 1 else -1; } }; @@ -1133,8 +1207,8 @@ const Lfsr = struct { }; } - fn sample(self: *const Self) u1 { - return @truncate(u1, ~self.shift); + fn sample(self: *const Self) i8 { + return if ((~self.shift & 1) == 1) 1 else -1; } fn updateLength(_: *Self, fs: *const FrameSequencer, ch4: *Noise, new: io.NoiseControl) void { diff --git a/src/main.zig b/src/main.zig index bc5aaac..4cee0ba 100644 --- a/src/main.zig +++ b/src/main.zig @@ -245,7 +245,7 @@ fn initAudio(apu: *Apu) SDL.SDL_AudioDeviceID { var have: SDL.SDL_AudioSpec = undefined; var want: SDL.SDL_AudioSpec = .{ .freq = sample_rate, - .format = SDL.AUDIO_F32, + .format = SDL.AUDIO_U16, .channels = 2, .samples = 0x100, .callback = audioCallback,