Compare commits
No commits in common. "172a59aefb1988963c4becbe8e4f6e51fa150c32" and "6b09250a5695f1c1cea1227143ca14842b8cc9db" have entirely different histories.
172a59aefb
...
6b09250a56
|
@ -41,7 +41,7 @@ pub fn init(alloc: Allocator, sched: *Scheduler, paths: FilePaths) !Self {
|
||||||
.pak = try GamePak.init(alloc, paths.rom, paths.save),
|
.pak = try GamePak.init(alloc, paths.rom, paths.save),
|
||||||
.bios = try Bios.init(alloc, paths.bios),
|
.bios = try Bios.init(alloc, paths.bios),
|
||||||
.ppu = try Ppu.init(alloc, sched),
|
.ppu = try Ppu.init(alloc, sched),
|
||||||
.apu = Apu.init(sched),
|
.apu = Apu.init(),
|
||||||
.iwram = try Iwram.init(alloc),
|
.iwram = try Iwram.init(alloc),
|
||||||
.ewram = try Ewram.init(alloc),
|
.ewram = try Ewram.init(alloc),
|
||||||
.dma = DmaControllers.init(),
|
.dma = DmaControllers.init(),
|
||||||
|
|
98
src/apu.zig
98
src/apu.zig
|
@ -1,9 +1,7 @@
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const SDL = @import("sdl2");
|
const SDL = @import("sdl2");
|
||||||
const io = @import("bus/io.zig");
|
const io = @import("bus/io.zig");
|
||||||
|
|
||||||
const Arm7tdmi = @import("cpu.zig").Arm7tdmi;
|
const Arm7tdmi = @import("cpu.zig").Arm7tdmi;
|
||||||
const Scheduler = @import("scheduler.zig").Scheduler;
|
|
||||||
|
|
||||||
const SoundFifo = std.fifo.LinearFifo(u8, .{ .Static = 0x20 });
|
const SoundFifo = std.fifo.LinearFifo(u8, .{ .Static = 0x20 });
|
||||||
const AudioDeviceId = SDL.SDL_AudioDeviceID;
|
const AudioDeviceId = SDL.SDL_AudioDeviceID;
|
||||||
|
@ -11,9 +9,6 @@ const AudioDeviceId = SDL.SDL_AudioDeviceID;
|
||||||
const intToBytes = @import("util.zig").intToBytes;
|
const intToBytes = @import("util.zig").intToBytes;
|
||||||
const log = std.log.scoped(.APU);
|
const log = std.log.scoped(.APU);
|
||||||
|
|
||||||
const sample_rate = 32768;
|
|
||||||
const sample_ticks = 280896 * 60 / sample_rate;
|
|
||||||
|
|
||||||
pub const Apu = struct {
|
pub const Apu = struct {
|
||||||
const Self = @This();
|
const Self = @This();
|
||||||
|
|
||||||
|
@ -30,10 +25,9 @@ pub const Apu = struct {
|
||||||
cnt: io.SoundControl,
|
cnt: io.SoundControl,
|
||||||
|
|
||||||
dev: ?AudioDeviceId,
|
dev: ?AudioDeviceId,
|
||||||
sched: *Scheduler,
|
|
||||||
|
|
||||||
pub fn init(sched: *Scheduler) Self {
|
pub fn init() Self {
|
||||||
const apu: Self = .{
|
return .{
|
||||||
.ch1 = ToneSweep.init(),
|
.ch1 = ToneSweep.init(),
|
||||||
.ch2 = Tone.init(),
|
.ch2 = Tone.init(),
|
||||||
.ch3 = Wave.init(),
|
.ch3 = Wave.init(),
|
||||||
|
@ -47,11 +41,7 @@ pub const Apu = struct {
|
||||||
.bias = .{ .raw = 0x0200 },
|
.bias = .{ .raw = 0x0200 },
|
||||||
|
|
||||||
.dev = null,
|
.dev = null,
|
||||||
.sched = sched,
|
|
||||||
};
|
};
|
||||||
sched.push(.SampleAudio, sched.now() + sample_ticks);
|
|
||||||
|
|
||||||
return apu;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn attachAudioDevice(self: *Self, dev: AudioDeviceId) void {
|
pub fn attachAudioDevice(self: *Self, dev: AudioDeviceId) void {
|
||||||
|
@ -63,8 +53,8 @@ pub const Apu = struct {
|
||||||
|
|
||||||
// Reinitializing instead of resetting is fine because
|
// Reinitializing instead of resetting is fine because
|
||||||
// the FIFOs I'm using are stack allocated and 0x20 bytes big
|
// the FIFOs I'm using are stack allocated and 0x20 bytes big
|
||||||
if (new.chA_reset.read()) self.chA.fifo = SoundFifo.init();
|
if (new.sa_reset.read()) self.chA.fifo = SoundFifo.init();
|
||||||
if (new.chB_reset.read()) self.chB.fifo = SoundFifo.init();
|
if (new.sb_reset.read()) self.chB.fifo = SoundFifo.init();
|
||||||
|
|
||||||
self.dma_cnt = new;
|
self.dma_cnt = new;
|
||||||
}
|
}
|
||||||
|
@ -85,35 +75,19 @@ pub const Apu = struct {
|
||||||
self.bias.raw = (@as(u16, byte) << 8) | (self.bias.raw & 0xFF);
|
self.bias.raw = (@as(u16, byte) << 8) | (self.bias.raw & 0xFF);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn sampleAudio(self: *Self, late: u64) void {
|
pub fn handleTimerOverflow(self: *Self, kind: DmaSoundKind, cpu: *Arm7tdmi) 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 (!self.cnt.apu_enable.read()) return;
|
||||||
|
|
||||||
if (@boolToInt(self.dma_cnt.chA_timer.read()) == tim_id) {
|
const samples = switch (kind) {
|
||||||
self.chA.updateSample();
|
.A => blk: {
|
||||||
if (self.chA.len() <= 15) cpu.bus.dma._1.enableSoundDma(0x0400_00A0);
|
break :blk self.chA.handleTimerOverflow(cpu, self.dma_cnt);
|
||||||
}
|
},
|
||||||
|
.B => blk: {
|
||||||
|
break :blk self.chB.handleTimerOverflow(cpu, self.dma_cnt);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
if (@boolToInt(self.dma_cnt.chB_timer.read()) == tim_id) {
|
if (self.dev) |dev| _ = SDL.SDL_QueueAudio(dev, &samples, 2);
|
||||||
self.chB.updateSample();
|
|
||||||
if (self.chB.len() <= 15) cpu.bus.dma._2.enableSoundDma(0x0400_00A4);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -233,31 +207,55 @@ pub fn DmaSound(comptime kind: DmaSoundKind) type {
|
||||||
const Self = @This();
|
const Self = @This();
|
||||||
|
|
||||||
fifo: SoundFifo,
|
fifo: SoundFifo,
|
||||||
|
|
||||||
kind: DmaSoundKind,
|
kind: DmaSoundKind,
|
||||||
sample: i8,
|
|
||||||
|
|
||||||
fn init() Self {
|
fn init() Self {
|
||||||
return .{
|
return .{ .fifo = SoundFifo.init(), .kind = kind };
|
||||||
.fifo = SoundFifo.init(),
|
|
||||||
.kind = kind,
|
|
||||||
.sample = 0,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn push(self: *Self, value: u32) void {
|
pub fn push(self: *Self, value: u32) void {
|
||||||
self.fifo.write(&intToBytes(u32, value)) catch {};
|
self.fifo.write(&intToBytes(u32, value)) catch {};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn pop(self: *Self) u8 {
|
||||||
|
return self.fifo.readItem() orelse 0;
|
||||||
|
}
|
||||||
|
|
||||||
pub fn len(self: *const Self) usize {
|
pub fn len(self: *const Self) usize {
|
||||||
return self.fifo.readableLength();
|
return self.fifo.readableLength();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn updateSample(self: *Self) void {
|
pub fn handleTimerOverflow(self: *Self, cpu: *Arm7tdmi, cnt: io.DmaSoundControl) [2]u8 {
|
||||||
if (self.fifo.readItem()) |sample| self.sample = @bitCast(i8, sample);
|
const sample = self.pop();
|
||||||
|
|
||||||
|
var left: u8 = 0;
|
||||||
|
var right: u8 = 0;
|
||||||
|
var fifo_addr: u32 = undefined;
|
||||||
|
|
||||||
|
switch (kind) {
|
||||||
|
.A => {
|
||||||
|
const vol = @boolToInt(!cnt.sa_vol.read()); // if unset, vol is 50%
|
||||||
|
if (cnt.sa_left_enable.read()) left = sample >> vol;
|
||||||
|
if (cnt.sa_right_enable.read()) right = sample >> vol;
|
||||||
|
|
||||||
|
fifo_addr = 0x0400_00A0;
|
||||||
|
},
|
||||||
|
.B => {
|
||||||
|
const vol = @boolToInt(!cnt.sb_vol.read()); // if unset, vol is 50%
|
||||||
|
if (cnt.sb_left_enable.read()) left = sample >> vol;
|
||||||
|
if (cnt.sb_right_enable.read()) right = sample >> vol;
|
||||||
|
|
||||||
|
fifo_addr = 0x0400_00A4;
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn amplitude(self: *const Self) f32 {
|
if (self.len() <= 15) {
|
||||||
return @intToFloat(f32, self.sample) / 127.5 - (1 / 255);
|
cpu.bus.dma._1.enableSoundDma(fifo_addr);
|
||||||
|
cpu.bus.dma._2.enableSoundDma(fifo_addr);
|
||||||
|
}
|
||||||
|
|
||||||
|
return .{ left, right };
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -268,13 +268,13 @@ pub fn write(bus: *Bus, comptime T: type, address: u32, value: T) void {
|
||||||
0x0400_00DE => bus.dma._3.writeCntHigh(value),
|
0x0400_00DE => bus.dma._3.writeCntHigh(value),
|
||||||
|
|
||||||
// Timers
|
// Timers
|
||||||
0x0400_0100 => bus.tim._0.setReload(value),
|
0x0400_0100 => bus.tim._0.writeCntLow(value),
|
||||||
0x0400_0102 => bus.tim._0.writeCntHigh(value),
|
0x0400_0102 => bus.tim._0.writeCntHigh(value),
|
||||||
0x0400_0104 => bus.tim._1.setReload(value),
|
0x0400_0104 => bus.tim._1.writeCntLow(value),
|
||||||
0x0400_0106 => bus.tim._1.writeCntHigh(value),
|
0x0400_0106 => bus.tim._1.writeCntHigh(value),
|
||||||
0x0400_0108 => bus.tim._2.setReload(value),
|
0x0400_0108 => bus.tim._2.writeCntLow(value),
|
||||||
0x0400_010A => bus.tim._2.writeCntHigh(value),
|
0x0400_010A => bus.tim._2.writeCntHigh(value),
|
||||||
0x0400_010C => bus.tim._3.setReload(value),
|
0x0400_010C => bus.tim._3.writeCntLow(value),
|
||||||
0x0400_010E => bus.tim._3.writeCntHigh(value),
|
0x0400_010E => bus.tim._3.writeCntHigh(value),
|
||||||
0x0400_0110 => {}, // Not Used
|
0x0400_0110 => {}, // Not Used
|
||||||
|
|
||||||
|
@ -595,20 +595,20 @@ pub const ChannelVolumeControl = extern union {
|
||||||
/// Read / Write
|
/// Read / Write
|
||||||
pub const DmaSoundControl = extern union {
|
pub const DmaSoundControl = extern union {
|
||||||
ch_vol: Bitfield(u16, 0, 2),
|
ch_vol: Bitfield(u16, 0, 2),
|
||||||
chA_vol: Bit(u16, 2),
|
sa_vol: Bit(u16, 2),
|
||||||
chB_vol: Bit(u16, 3),
|
sb_vol: Bit(u16, 3),
|
||||||
|
|
||||||
chA_right: Bit(u16, 8),
|
sa_right_enable: Bit(u16, 8),
|
||||||
chA_left: Bit(u16, 9),
|
sa_left_enable: Bit(u16, 9),
|
||||||
chA_timer: Bit(u16, 10),
|
sa_timer: Bit(u16, 10),
|
||||||
/// Write only?
|
/// Write only?
|
||||||
chA_reset: Bit(u16, 11),
|
sa_reset: Bit(u16, 11),
|
||||||
|
|
||||||
chB_right: Bit(u16, 12),
|
sb_right_enable: Bit(u16, 12),
|
||||||
chB_left: Bit(u16, 13),
|
sb_left_enable: Bit(u16, 13),
|
||||||
chB_timer: Bit(u16, 14),
|
sb_timer: Bit(u16, 14),
|
||||||
/// Write only?
|
/// Write only?
|
||||||
chB_reset: Bit(u16, 15),
|
sb_reset: Bit(u16, 15),
|
||||||
raw: u16,
|
raw: u16,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -33,7 +33,7 @@ fn Timer(comptime id: u2) type {
|
||||||
/// Read Only, Internal. Please use self.counter()
|
/// Read Only, Internal. Please use self.counter()
|
||||||
_counter: u16,
|
_counter: u16,
|
||||||
|
|
||||||
/// Write Only, Internal. Please use self.setReload()
|
/// Write Only, Internal. Please use self.writeCntLow()
|
||||||
_reload: u16,
|
_reload: u16,
|
||||||
|
|
||||||
/// Write Only, Internal. Please use self.WriteCntHigh()
|
/// Write Only, Internal. Please use self.WriteCntHigh()
|
||||||
|
@ -56,17 +56,18 @@ fn Timer(comptime id: u2) type {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn counter(self: *const Self) u16 {
|
pub fn counter(self: *const Self) u16 {
|
||||||
if (self.cnt.cascade.read() or !self.cnt.enabled.read()) return self._counter;
|
if (self.cnt.cascade.read())
|
||||||
|
return self._counter
|
||||||
|
else
|
||||||
return self._counter +% @truncate(u16, (self.sched.now() - self._start_timestamp) / self.frequency());
|
return self._counter +% @truncate(u16, (self.sched.now() - self._start_timestamp) / self.frequency());
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn writeCnt(self: *Self, word: u32) void {
|
pub fn writeCnt(self: *Self, word: u32) void {
|
||||||
self.setReload(@truncate(u16, word));
|
self.writeCntLow(@truncate(u16, word));
|
||||||
self.writeCntHigh(@truncate(u16, word >> 16));
|
self.writeCntHigh(@truncate(u16, word >> 16));
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn setReload(self: *Self, halfword: u16) void {
|
pub fn writeCntLow(self: *Self, halfword: u16) void {
|
||||||
self._reload = halfword;
|
self._reload = halfword;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -76,18 +77,13 @@ fn Timer(comptime id: u2) type {
|
||||||
// If Timer happens to be enabled, It will either be resheduled or disabled
|
// If Timer happens to be enabled, It will either be resheduled or disabled
|
||||||
self.sched.removeScheduledEvent(.{ .TimerOverflow = id });
|
self.sched.removeScheduledEvent(.{ .TimerOverflow = id });
|
||||||
|
|
||||||
if (self.cnt.enabled.read() and (new.cascade.read() or !new.enabled.read())) {
|
if (!self.cnt.enabled.read() and new.enabled.read()) {
|
||||||
// Either through the cascade bit or the enable bit, the timer has effectively been disabled
|
// Reload on Rising edge
|
||||||
// The Counter should hold whatever value it should have been at when it was disabled
|
self._counter = self._reload;
|
||||||
self._counter +%= @truncate(u16, (self.sched.now() - self._start_timestamp) / self.frequency());
|
|
||||||
|
if (!new.cascade.read()) self.scheduleOverflow(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
// The counter is only reloaded on the rising edge of the enable bit
|
|
||||||
if (!self.cnt.enabled.read() and new.enabled.read()) self._counter = self._reload;
|
|
||||||
|
|
||||||
// If Timer is enabled and we're not cascading, we need to schedule an overflow event
|
|
||||||
if (new.enabled.read() and !new.cascade.read()) self.scheduleOverflow(0);
|
|
||||||
|
|
||||||
self.cnt.raw = halfword;
|
self.cnt.raw = halfword;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -109,7 +105,13 @@ fn Timer(comptime id: u2) type {
|
||||||
|
|
||||||
// DMA Sound Things
|
// DMA Sound Things
|
||||||
if (id == 0 or id == 1) {
|
if (id == 0 or id == 1) {
|
||||||
cpu.bus.apu.handleTimerOverflow(cpu, id);
|
const apu = &cpu.bus.apu;
|
||||||
|
|
||||||
|
const a_tim = @boolToInt(apu.dma_cnt.sa_timer.read());
|
||||||
|
const b_tim = @boolToInt(apu.dma_cnt.sb_timer.read());
|
||||||
|
|
||||||
|
if (a_tim == id) apu.handleTimerOverflow(.A, cpu);
|
||||||
|
if (b_tim == id) apu.handleTimerOverflow(.B, cpu);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Perform Cascade Behaviour
|
// Perform Cascade Behaviour
|
||||||
|
|
|
@ -242,9 +242,9 @@ fn initAudio() SDL.SDL_AudioDeviceID {
|
||||||
var have: SDL.SDL_AudioSpec = undefined;
|
var have: SDL.SDL_AudioSpec = undefined;
|
||||||
var want = std.mem.zeroes(SDL.SDL_AudioSpec);
|
var want = std.mem.zeroes(SDL.SDL_AudioSpec);
|
||||||
want.freq = 32768;
|
want.freq = 32768;
|
||||||
want.format = SDL.AUDIO_F32;
|
want.format = SDL.AUDIO_S8;
|
||||||
want.channels = 2;
|
want.channels = 2;
|
||||||
want.samples = 0x100;
|
want.samples = 0x200;
|
||||||
want.callback = null;
|
want.callback = null;
|
||||||
|
|
||||||
const dev = SDL.SDL_OpenAudioDevice(null, 0, &want, &have, 0);
|
const dev = SDL.SDL_OpenAudioDevice(null, 0, &want, &have, 0);
|
||||||
|
|
|
@ -51,7 +51,6 @@ pub const Scheduler = struct {
|
||||||
3 => cpu.bus.tim._3.handleOverflow(cpu, late),
|
3 => cpu.bus.tim._3.handleOverflow(cpu, 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
|
||||||
}
|
}
|
||||||
|
@ -103,5 +102,4 @@ pub const EventKind = union(enum) {
|
||||||
VBlank,
|
VBlank,
|
||||||
Draw,
|
Draw,
|
||||||
TimerOverflow: u2,
|
TimerOverflow: u2,
|
||||||
SampleAudio,
|
|
||||||
};
|
};
|
||||||
|
|
Loading…
Reference in New Issue