zba/src/apu.zig

269 lines
6.8 KiB
Zig
Raw Normal View History

2022-03-15 11:08:07 +00:00
const std = @import("std");
const SDL = @import("sdl2");
const io = @import("bus/io.zig");
const Arm7tdmi = @import("cpu.zig").Arm7tdmi;
const Scheduler = @import("scheduler.zig").Scheduler;
2022-03-15 11:08:07 +00:00
const SoundFifo = std.fifo.LinearFifo(u8, .{ .Static = 0x20 });
const AudioDeviceId = SDL.SDL_AudioDeviceID;
2022-03-15 11:08:07 +00:00
const intToBytes = @import("util.zig").intToBytes;
const log = std.log.scoped(.APU);
2022-03-15 11:08:07 +00:00
const sample_rate = 32768;
const sample_ticks = (1 << 24) / sample_rate;
2022-03-15 11:08:07 +00:00
pub const Apu = struct {
const Self = @This();
ch1: ToneSweep,
ch2: Tone,
ch3: Wave,
ch4: Noise,
chA: DmaSound(.A),
chB: DmaSound(.B),
2022-03-15 11:08:07 +00:00
bias: io.SoundBias,
ch_vol_cnt: io.ChannelVolumeControl,
dma_cnt: io.DmaSoundControl,
cnt: io.SoundControl,
2022-04-14 02:21:25 +00:00
dev: ?AudioDeviceId,
sched: *Scheduler,
pub fn init(sched: *Scheduler) Self {
const apu: Self = .{
2022-03-15 11:08:07 +00:00
.ch1 = ToneSweep.init(),
.ch2 = Tone.init(),
.ch3 = Wave.init(),
.ch4 = Noise.init(),
.chA = DmaSound(.A).init(),
.chB = DmaSound(.B).init(),
2022-03-15 11:08:07 +00:00
.ch_vol_cnt = .{ .raw = 0 },
.dma_cnt = .{ .raw = 0 },
.cnt = .{ .raw = 0 },
.bias = .{ .raw = 0x0200 },
2022-04-14 02:21:25 +00:00
.dev = null,
.sched = sched,
2022-03-15 11:08:07 +00:00
};
sched.push(.SampleAudio, sched.now() + sample_ticks);
return apu;
2022-03-15 11:08:07 +00:00
}
2022-04-14 02:21:25 +00:00
pub fn attachAudioDevice(self: *Self, dev: AudioDeviceId) void {
self.dev = dev;
}
pub fn setDmaCnt(self: *Self, value: u16) void {
const new: io.DmaSoundControl = .{ .raw = value };
// Reinitializing instead of resetting is fine because
// the FIFOs I'm using are stack allocated and 0x20 bytes big
if (new.chA_reset.read()) self.chA.fifo = SoundFifo.init();
if (new.chB_reset.read()) self.chB.fifo = SoundFifo.init();
self.dma_cnt = new;
}
2022-03-15 11:08:07 +00:00
pub fn setSoundCntX(self: *Self, value: bool) void {
self.cnt.apu_enable.write(value);
}
pub fn setSoundCntLLow(self: *Self, byte: u8) void {
self.ch_vol_cnt.raw = (self.ch_vol_cnt.raw & 0xFF00) | byte;
}
2022-03-28 22:40:47 +00:00
pub fn setSoundCntLHigh(self: *Self, byte: u8) void {
self.ch_vol_cnt.raw = @as(u16, byte) << 8 | (self.ch_vol_cnt.raw & 0xFF);
}
2022-03-15 11:08:07 +00:00
pub fn setBiasHigh(self: *Self, byte: u8) void {
self.bias.raw = (@as(u16, byte) << 8) | (self.bias.raw & 0xFF);
}
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;
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;
const left = (chA_left + chB_left) / 2;
const right = (chA_right + chB_right) / 2;
if (self.dev) |dev| _ = SDL.SDL_QueueAudio(dev, &[2]f32{ left, right }, 2 * @sizeOf(f32));
self.sched.push(.SampleAudio, self.sched.now() + sample_ticks - late);
}
pub fn handleTimerOverflow(self: *Self, cpu: *Arm7tdmi, tim_id: u3) void {
if (!self.cnt.apu_enable.read()) return;
if (@boolToInt(self.dma_cnt.chA_timer.read()) == tim_id) {
self.chA.updateSample();
if (self.chA.len() <= 15) cpu.bus.dma._1.enableSoundDma(0x0400_00A0);
}
if (@boolToInt(self.dma_cnt.chB_timer.read()) == tim_id) {
self.chB.updateSample();
if (self.chB.len() <= 15) cpu.bus.dma._2.enableSoundDma(0x0400_00A4);
}
}
2022-03-15 11:08:07 +00:00
};
const ToneSweep = struct {
const Self = @This();
2022-03-28 22:40:47 +00:00
/// NR10
2022-03-15 11:08:07 +00:00
sweep: io.Sweep,
2022-03-28 22:40:47 +00:00
/// NR11
2022-03-15 11:08:07 +00:00
duty: io.Duty,
2022-03-28 22:40:47 +00:00
/// NR12
2022-03-15 11:08:07 +00:00
envelope: io.Envelope,
2022-03-28 22:40:47 +00:00
/// NR13, NR14
2022-03-15 11:08:07 +00:00
freq: io.Frequency,
fn init() Self {
return .{
.sweep = .{ .raw = 0 },
.duty = .{ .raw = 0 },
.envelope = .{ .raw = 0 },
.freq = .{ .raw = 0 },
};
}
2022-03-28 22:40:47 +00:00
pub fn setFreqLow(self: *Self, byte: u8) void {
self.freq.raw = (self.freq.raw & 0xFF00) | byte;
}
2022-03-15 11:08:07 +00:00
pub fn setFreqHigh(self: *Self, byte: u8) void {
self.freq.raw = (@as(u16, byte) << 8) | (self.freq.raw & 0xFF);
}
};
const Tone = struct {
const Self = @This();
2022-03-28 22:40:47 +00:00
/// NR21
2022-03-15 11:08:07 +00:00
duty: io.Duty,
2022-03-28 22:40:47 +00:00
/// NR22
2022-03-15 11:08:07 +00:00
envelope: io.Envelope,
2022-03-28 22:40:47 +00:00
/// NR23, NR24
2022-03-15 11:08:07 +00:00
freq: io.Frequency,
fn init() Self {
return .{
.duty = .{ .raw = 0 },
.envelope = .{ .raw = 0 },
.freq = .{ .raw = 0 },
};
}
2022-03-28 22:40:47 +00:00
pub fn setFreqLow(self: *Self, byte: u8) void {
self.freq.raw = (self.freq.raw & 0xFF00) | byte;
}
2022-03-15 11:08:07 +00:00
pub fn setFreqHigh(self: *Self, byte: u8) void {
2022-03-28 22:40:47 +00:00
self.freq.raw = @as(u16, byte) << 8 | (self.freq.raw & 0xFF);
2022-03-15 11:08:07 +00:00
}
};
const Wave = struct {
const Self = @This();
/// Write-only
2022-03-28 22:40:47 +00:00
/// NR30
2022-03-15 11:08:07 +00:00
select: io.WaveSelect,
2022-04-08 05:13:58 +00:00
/// NR31
2022-03-15 11:08:07 +00:00
length: u8,
2022-03-28 22:40:47 +00:00
/// NR32
2022-03-15 11:08:07 +00:00
vol: io.WaveVolume,
2022-03-28 22:40:47 +00:00
/// NR33, NR34
2022-03-15 11:08:07 +00:00
freq: io.Frequency,
fn init() Self {
return .{
.select = .{ .raw = 0 },
.vol = .{ .raw = 0 },
.freq = .{ .raw = 0 },
.length = 0,
};
}
2022-03-28 22:40:47 +00:00
pub fn setFreqLow(self: *Self, byte: u8) void {
self.freq.raw = (self.freq.raw & 0xFF00) | byte;
}
pub fn setFreqHigh(self: *Self, byte: u8) void {
self.freq.raw = @as(u16, byte) << 8 | (self.freq.raw & 0xFF);
}
2022-03-15 11:08:07 +00:00
};
const Noise = struct {
const Self = @This();
/// Write-only
/// NR41
len: u6,
2022-03-28 22:40:47 +00:00
/// NR42
2022-03-15 11:08:07 +00:00
envelope: io.Envelope,
2022-03-28 22:40:47 +00:00
/// NR43
2022-03-15 11:08:07 +00:00
poly: io.PolyCounter,
2022-03-28 22:40:47 +00:00
/// NR44
2022-03-15 11:08:07 +00:00
cnt: io.NoiseControl,
fn init() Self {
return .{
.len = 0,
.envelope = .{ .raw = 0 },
.poly = .{ .raw = 0 },
.cnt = .{ .raw = 0 },
};
}
};
pub fn DmaSound(comptime kind: DmaSoundKind) type {
return struct {
const Self = @This();
2022-03-15 11:08:07 +00:00
fifo: SoundFifo,
kind: DmaSoundKind,
sample: i8,
fn init() Self {
return .{
.fifo = SoundFifo.init(),
.kind = kind,
.sample = 0,
};
}
pub fn push(self: *Self, value: u32) void {
self.fifo.write(&intToBytes(u32, value)) catch {};
}
pub fn len(self: *const Self) usize {
return self.fifo.readableLength();
}
pub fn updateSample(self: *Self) void {
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);
}
};
}
const DmaSoundKind = enum {
A,
B,
2022-03-15 11:08:07 +00:00
};