chore: redo apu sampling

This commit is contained in:
Rekai Nyangadzayi Musuka 2022-05-22 08:18:29 -03:00
parent a2d2a84850
commit e70fe73899
2 changed files with 146 additions and 72 deletions

View File

@ -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 {

View File

@ -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,