feat: Initial Implementation of DMA Audio
This commit is contained in:
		
							
								
								
									
										26
									
								
								src/Bus.zig
									
									
									
									
									
								
							
							
						
						
									
										26
									
								
								src/Bus.zig
									
									
									
									
									
								
							| @@ -1,5 +1,6 @@ | ||||
| const std = @import("std"); | ||||
|  | ||||
| const AudioDeviceId = @import("sdl2").SDL_AudioDeviceID; | ||||
| const Bios = @import("bus/Bios.zig"); | ||||
| const Ewram = @import("bus/Ewram.zig"); | ||||
| const GamePak = @import("bus/GamePak.zig"); | ||||
| @@ -10,6 +11,7 @@ const Apu = @import("apu.zig").Apu; | ||||
| const DmaControllers = @import("bus/dma.zig").DmaControllers; | ||||
| const Timers = @import("bus/timer.zig").Timers; | ||||
| const Scheduler = @import("scheduler.zig").Scheduler; | ||||
| const FilePaths = @import("util.zig").FilePaths; | ||||
|  | ||||
| const io = @import("bus/io.zig"); | ||||
| const Allocator = std.mem.Allocator; | ||||
| @@ -33,12 +35,12 @@ io: Io, | ||||
|  | ||||
| sched: *Scheduler, | ||||
|  | ||||
| pub fn init(alloc: Allocator, sched: *Scheduler, rom_path: []const u8, bios_path: ?[]const u8, save_path: ?[]const u8) !Self { | ||||
| pub fn init(alloc: Allocator, sched: *Scheduler, dev: AudioDeviceId, paths: FilePaths) !Self { | ||||
|     return Self{ | ||||
|         .pak = try GamePak.init(alloc, rom_path, save_path), | ||||
|         .bios = try Bios.init(alloc, bios_path), | ||||
|         .pak = try GamePak.init(alloc, paths.rom, paths.save), | ||||
|         .bios = try Bios.init(alloc, paths.bios), | ||||
|         .ppu = try Ppu.init(alloc, sched), | ||||
|         .apu = Apu.init(), | ||||
|         .apu = Apu.init(dev), | ||||
|         .iwram = try Iwram.init(alloc), | ||||
|         .ewram = try Ewram.init(alloc), | ||||
|         .dma = DmaControllers.init(), | ||||
| @@ -56,6 +58,22 @@ pub fn deinit(self: Self) void { | ||||
|     self.ppu.deinit(); | ||||
| } | ||||
|  | ||||
| pub fn handleDMATransfers(self: *Self) void { | ||||
|     while (self.isDmaRunning()) { | ||||
|         if (self.dma._1.step(self)) continue; | ||||
|         if (self.dma._0.step(self)) continue; | ||||
|         if (self.dma._2.step(self)) continue; | ||||
|         if (self.dma._3.step(self)) continue; | ||||
|     } | ||||
| } | ||||
|  | ||||
| fn isDmaRunning(self: *const Self) bool { | ||||
|     return self.dma._0.active or | ||||
|         self.dma._1.active or | ||||
|         self.dma._2.active or | ||||
|         self.dma._3.active; | ||||
| } | ||||
|  | ||||
| pub fn read(self: *const Self, comptime T: type, address: u32) T { | ||||
|     const page = @truncate(u8, address >> 24); | ||||
|     const align_addr = alignAddress(T, address); | ||||
|   | ||||
							
								
								
									
										117
									
								
								src/apu.zig
									
									
									
									
									
								
							
							
						
						
									
										117
									
								
								src/apu.zig
									
									
									
									
									
								
							| @@ -1,8 +1,13 @@ | ||||
| const std = @import("std"); | ||||
| const SDL = @import("sdl2"); | ||||
| const io = @import("bus/io.zig"); | ||||
| const Arm7tdmi = @import("cpu.zig").Arm7tdmi; | ||||
|  | ||||
| const SoundFifo = std.fifo.LinearFifo(u8, .{ .Static = 0x20 }); | ||||
| const AudioDeviceId = SDL.SDL_AudioDeviceID; | ||||
|  | ||||
| const io = @import("bus/io.zig"); | ||||
| const intToBytes = @import("util.zig").intToBytes; | ||||
| const log = std.log.scoped(.APU); | ||||
|  | ||||
| pub const Apu = struct { | ||||
|     const Self = @This(); | ||||
| @@ -11,30 +16,45 @@ pub const Apu = struct { | ||||
|     ch2: Tone, | ||||
|     ch3: Wave, | ||||
|     ch4: Noise, | ||||
|     chA: DmaSound, | ||||
|     chB: DmaSound, | ||||
|     chA: DmaSound(.A), | ||||
|     chB: DmaSound(.B), | ||||
|  | ||||
|     bias: io.SoundBias, | ||||
|     ch_vol_cnt: io.ChannelVolumeControl, | ||||
|     dma_cnt: io.DmaSoundControl, | ||||
|     cnt: io.SoundControl, | ||||
|  | ||||
|     pub fn init() Self { | ||||
|     dev: AudioDeviceId, | ||||
|  | ||||
|     pub fn init(dev: AudioDeviceId) Self { | ||||
|         return .{ | ||||
|             .ch1 = ToneSweep.init(), | ||||
|             .ch2 = Tone.init(), | ||||
|             .ch3 = Wave.init(), | ||||
|             .ch4 = Noise.init(), | ||||
|             .chA = DmaSound.init(), | ||||
|             .chB = DmaSound.init(), | ||||
|             .chA = DmaSound(.A).init(), | ||||
|             .chB = DmaSound(.B).init(), | ||||
|  | ||||
|             .ch_vol_cnt = .{ .raw = 0 }, | ||||
|             .dma_cnt = .{ .raw = 0 }, | ||||
|             .cnt = .{ .raw = 0 }, | ||||
|             .bias = .{ .raw = 0x0200 }, | ||||
|  | ||||
|             .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.sa_reset.read()) self.chA.fifo = SoundFifo.init(); | ||||
|         if (new.sb_reset.read()) self.chB.fifo = SoundFifo.init(); | ||||
|  | ||||
|         self.dma_cnt = new; | ||||
|     } | ||||
|  | ||||
|     pub fn setSoundCntX(self: *Self, value: bool) void { | ||||
|         self.cnt.apu_enable.write(value); | ||||
|     } | ||||
| @@ -50,6 +70,21 @@ pub const Apu = struct { | ||||
|     pub fn setBiasHigh(self: *Self, byte: u8) void { | ||||
|         self.bias.raw = (@as(u16, byte) << 8) | (self.bias.raw & 0xFF); | ||||
|     } | ||||
|  | ||||
|     pub fn handleTimerOverflow(self: *Self, kind: DmaSoundKind, cpu: *Arm7tdmi) void { | ||||
|         if (!self.cnt.apu_enable.read()) return; | ||||
|  | ||||
|         const samples = switch (kind) { | ||||
|             .A => blk: { | ||||
|                 break :blk self.chA.handleTimerOverflow(cpu, self.dma_cnt); | ||||
|             }, | ||||
|             .B => blk: { | ||||
|                 break :blk self.chB.handleTimerOverflow(cpu, self.dma_cnt); | ||||
|             }, | ||||
|         }; | ||||
|  | ||||
|         _ = SDL.SDL_QueueAudio(self.dev, &samples, 2); | ||||
|     } | ||||
| }; | ||||
|  | ||||
| const ToneSweep = struct { | ||||
| @@ -162,16 +197,66 @@ const Noise = struct { | ||||
|         }; | ||||
|     } | ||||
| }; | ||||
| const DmaSound = struct { | ||||
|     const Self = @This(); | ||||
|  | ||||
|     a: SoundFifo, | ||||
|     b: SoundFifo, | ||||
| pub fn DmaSound(comptime kind: DmaSoundKind) type { | ||||
|     return struct { | ||||
|         const Self = @This(); | ||||
|  | ||||
|     fn init() Self { | ||||
|         return .{ | ||||
|             .a = SoundFifo.init(), | ||||
|             .b = SoundFifo.init(), | ||||
|         }; | ||||
|     } | ||||
|         fifo: SoundFifo, | ||||
|  | ||||
|         kind: DmaSoundKind, | ||||
|  | ||||
|         fn init() Self { | ||||
|             return .{ .fifo = SoundFifo.init(), .kind = kind }; | ||||
|         } | ||||
|  | ||||
|         pub fn push(self: *Self, value: u32) void { | ||||
|             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 { | ||||
|             return self.fifo.readableLength(); | ||||
|         } | ||||
|  | ||||
|         pub fn handleTimerOverflow(self: *Self, cpu: *Arm7tdmi, cnt: io.DmaSoundControl) [2]u8 { | ||||
|             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; | ||||
|                 }, | ||||
|             } | ||||
|  | ||||
|             if (self.len() <= 15) { | ||||
|                 cpu.bus.dma._1.enableSoundDma(fifo_addr); | ||||
|                 cpu.bus.dma._2.enableSoundDma(fifo_addr); | ||||
|             } | ||||
|  | ||||
|             return .{ left, right }; | ||||
|         } | ||||
|     }; | ||||
| } | ||||
|  | ||||
| const DmaSoundKind = enum { | ||||
|     A, | ||||
|     B, | ||||
| }; | ||||
|   | ||||
| @@ -53,10 +53,13 @@ fn DmaController(comptime id: u2) type { | ||||
|         /// Internal. Word Count | ||||
|         _word_count: if (id == 3) u16 else u14, | ||||
|  | ||||
|         // Internal. FIFO Word Count | ||||
|         _fifo_word_count: u8, | ||||
|  | ||||
|         /// Some DMA Transfers are enabled during Hblank / VBlank and / or | ||||
|         /// have delays. Thefore bit 15 of DMACNT isn't actually something | ||||
|         /// we can use to control when we do or do not execute a step in a DMA Transfer | ||||
|         enabled: bool, | ||||
|         active: bool, | ||||
|  | ||||
|         pub fn init() Self { | ||||
|             return .{ | ||||
| @@ -70,7 +73,8 @@ fn DmaController(comptime id: u2) type { | ||||
|                 ._sad = 0, | ||||
|                 ._dad = 0, | ||||
|                 ._word_count = 0, | ||||
|                 .enabled = false, | ||||
|                 ._fifo_word_count = 4, | ||||
|                 .active = false, | ||||
|             }; | ||||
|         } | ||||
|  | ||||
| @@ -96,7 +100,7 @@ fn DmaController(comptime id: u2) type { | ||||
|                 self._word_count = if (self.word_count == 0) std.math.maxInt(@TypeOf(self._word_count)) else self.word_count; | ||||
|  | ||||
|                 // Only a Start Timing of 00 has a DMA Transfer immediately begin | ||||
|                 self.enabled = new.start_timing.read() == 0b00; | ||||
|                 self.active = new.start_timing.read() == 0b00; | ||||
|             } | ||||
|  | ||||
|             self.cnt.raw = halfword; | ||||
| @@ -108,27 +112,50 @@ fn DmaController(comptime id: u2) type { | ||||
|         } | ||||
|  | ||||
|         pub inline fn check(self: *Self, bus: *Bus) bool { | ||||
|             if (!self.enabled) return false; // FIXME: Check CNT register? | ||||
|             if (!self.active) return false; // FIXME: Check CNT register? | ||||
|  | ||||
|             self.step(bus); | ||||
|             return true; | ||||
|         } | ||||
|  | ||||
|         pub fn step(self: *Self, bus: *Bus) void { | ||||
|             @setCold(true); | ||||
|         pub fn step(self: *Self, bus: *Bus) bool { | ||||
|             if (!self.active) return false; | ||||
|  | ||||
|             const sad_adj = std.meta.intToEnum(Adjustment, self.cnt.sad_adj.read()) catch unreachable; | ||||
|             const dad_adj = std.meta.intToEnum(Adjustment, self.cnt.dad_adj.read()) catch unreachable; | ||||
|             const is_fifo = (self.id == 1 or self.id == 2) and self.cnt.start_timing.read() == 0b11; | ||||
|  | ||||
|             var offset: u32 = 0; | ||||
|             if (self.cnt.transfer_type.read()) { | ||||
|                 offset = @sizeOf(u32); // 32-bit Transfer | ||||
|                 const word = bus.read(u32, self._sad); | ||||
|                 bus.write(u32, self._dad, word); | ||||
|             // // if (is_fifo) { | ||||
|             // //     const offset = @sizeOf(u32); | ||||
|             // //     bus.write(u32, self._dad, bus.read(u32, self._sad)); | ||||
|  | ||||
|             // //     // TODO: Deduplicate | ||||
|             // //     switch (sad_adj) { | ||||
|             // //         .Increment => self._sad +%= offset, | ||||
|             // //         .Decrement => self._sad -%= offset, | ||||
|             // //         .Fixed => {}, | ||||
|  | ||||
|             // //         // TODO: Figure out correct behaviour on Illegal Source Addr Control Type | ||||
|             // //         .IncrementReload => std.debug.panic("panic(DmaTransfer): {} is an illegal src addr adjustment type", .{sad_adj}), | ||||
|             // //     } | ||||
|  | ||||
|             // //     self._fifo_word_count -= 1; | ||||
|  | ||||
|             // //     if (self._fifo_word_count == 0) { | ||||
|             // //         self._fifo_word_count = 4; | ||||
|             // //         self.active = false; | ||||
|             // //     } | ||||
|  | ||||
|             // //     return true; | ||||
|             // // } | ||||
|  | ||||
|             const transfer_type = self.cnt.transfer_type.read() or is_fifo; | ||||
|             const offset: u32 = if (transfer_type) @sizeOf(u32) else @sizeOf(u16); | ||||
|  | ||||
|             if (transfer_type) { | ||||
|                 bus.write(u32, self._dad, bus.read(u32, self._sad)); | ||||
|             } else { | ||||
|                 offset = @sizeOf(u16); // 16-bit Transfer | ||||
|                 const halfword = bus.read(u16, self._sad); | ||||
|                 bus.write(u16, self._dad, halfword); | ||||
|                 bus.write(u16, self._dad, bus.read(u16, self._sad)); | ||||
|             } | ||||
|  | ||||
|             switch (sad_adj) { | ||||
| @@ -140,10 +167,12 @@ fn DmaController(comptime id: u2) type { | ||||
|                 .IncrementReload => std.debug.panic("panic(DmaTransfer): {} is an illegal src addr adjustment type", .{sad_adj}), | ||||
|             } | ||||
|  | ||||
|             switch (dad_adj) { | ||||
|                 .Increment, .IncrementReload => self._dad +%= offset, | ||||
|                 .Decrement => self._dad -%= offset, | ||||
|                 .Fixed => {}, | ||||
|             if (!is_fifo) { | ||||
|                 switch (dad_adj) { | ||||
|                     .Increment, .IncrementReload => self._dad +%= offset, | ||||
|                     .Decrement => self._dad -%= offset, | ||||
|                     .Fixed => {}, | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             self._word_count -= 1; | ||||
| @@ -165,8 +194,10 @@ fn DmaController(comptime id: u2) type { | ||||
|                 // We want to disable our internal enabled flag regardless of repeat | ||||
|                 // because we only want to step A DMA that repeats during it's specific | ||||
|                 // timing window | ||||
|                 self.enabled = false; | ||||
|                 self.active = false; | ||||
|             } | ||||
|  | ||||
|             return true; | ||||
|         } | ||||
|  | ||||
|         pub fn isBlocking(self: *const Self) bool { | ||||
| @@ -175,21 +206,31 @@ fn DmaController(comptime id: u2) type { | ||||
|         } | ||||
|  | ||||
|         pub fn pollBlankingDma(self: *Self, comptime kind: DmaKind) void { | ||||
|             if (self.enabled) return; | ||||
|             if (self.active) return; | ||||
|  | ||||
|             switch (kind) { | ||||
|                 .HBlank => self.enabled = self.cnt.enabled.read() and self.cnt.start_timing.read() == 0b10, | ||||
|                 .VBlank => self.enabled = self.cnt.enabled.read() and self.cnt.start_timing.read() == 0b01, | ||||
|                 .HBlank => self.active = self.cnt.enabled.read() and self.cnt.start_timing.read() == 0b10, | ||||
|                 .VBlank => self.active = self.cnt.enabled.read() and self.cnt.start_timing.read() == 0b01, | ||||
|                 .Immediate, .Special => {}, | ||||
|             } | ||||
|  | ||||
|             if (self.cnt.repeat.read() and self.enabled) { | ||||
|             if (self.cnt.repeat.read() and self.active) { | ||||
|                 self._word_count = if (self.word_count == 0) std.math.maxInt(@TypeOf(self._word_count)) else self.word_count; | ||||
|  | ||||
|                 const dad_adj = std.meta.intToEnum(Adjustment, self.cnt.dad_adj.read()) catch unreachable; | ||||
|                 if (dad_adj == .IncrementReload) self._dad = self.dad; | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         pub fn enableSoundDma(self: *Self, fifo_addr: u32) void { | ||||
|             comptime std.debug.assert(id == 1 or id == 2); | ||||
|  | ||||
|             if (self.cnt.enabled.read() and self.cnt.start_timing.read() == 0b11 and self.dad == fifo_addr) { | ||||
|                 self.active = true; | ||||
|                 self._word_count = 4; | ||||
|                 self.cnt.repeat.set(); | ||||
|             } | ||||
|         } | ||||
|     }; | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -143,8 +143,8 @@ pub fn write(bus: *Bus, comptime T: type, address: u32, value: T) void { | ||||
|             0x0400_001C => bus.ppu.setBgOffsets(3, value), | ||||
|  | ||||
|             // Sound | ||||
|             0x0400_00A0 => log.warn("Wrote 0x{X:0>8} to FIFO_A", .{value}), | ||||
|             0x0400_00A4 => log.warn("Wrote 0x{X:0>8} to FIFO_B", .{value}), | ||||
|             0x0400_00A0 => bus.apu.chA.push(value), | ||||
|             0x0400_00A4 => bus.apu.chB.push(value), | ||||
|  | ||||
|             // DMA Transfers | ||||
|             0x0400_00B0 => bus.dma._0.writeSad(value), | ||||
| @@ -208,7 +208,7 @@ pub fn write(bus: *Bus, comptime T: type, address: u32, value: T) void { | ||||
|  | ||||
|             // Sound | ||||
|             0x0400_0080 => bus.apu.ch_vol_cnt.raw = value, | ||||
|             0x0400_0082 => bus.apu.dma_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, | ||||
|  | ||||
|   | ||||
| @@ -103,6 +103,17 @@ fn Timer(comptime id: u2) type { | ||||
|                 cpu.handleInterrupt(); | ||||
|             } | ||||
|  | ||||
|             // DMA Sound Things | ||||
|             if (id == 0 or id == 1) { | ||||
|                 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 | ||||
|             switch (id) { | ||||
|                 0 => if (tim._1.cnt.cascade.read()) { | ||||
|   | ||||
							
								
								
									
										18
									
								
								src/cpu.zig
									
									
									
									
									
								
							
							
						
						
									
										18
									
								
								src/cpu.zig
									
									
									
									
									
								
							| @@ -246,15 +246,6 @@ pub const Arm7tdmi = struct { | ||||
|     } | ||||
|  | ||||
|     pub fn step(self: *Self) void { | ||||
|         // If we're processing a DMA (not Sound or Blanking) the CPU is disabled | ||||
|         if (self.handleDMATransfers()) return; | ||||
|  | ||||
|         // If we're halted, the cpu is disabled | ||||
|         if (self.bus.io.haltcnt == .Halt) { | ||||
|             self.sched.tick += 1; | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         if (self.cpsr.t.read()) { | ||||
|             const opcode = self.thumbFetch(); | ||||
|             if (enable_logging) if (self.log_file) |file| self.debug_log(file, opcode); | ||||
| @@ -296,15 +287,6 @@ pub const Arm7tdmi = struct { | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     fn handleDMATransfers(self: *Self) bool { | ||||
|         if (self.bus.dma._0.check(self.bus)) return self.bus.dma._0.isBlocking(); | ||||
|         if (self.bus.dma._1.check(self.bus)) return self.bus.dma._1.isBlocking(); | ||||
|         if (self.bus.dma._2.check(self.bus)) return self.bus.dma._2.isBlocking(); | ||||
|         if (self.bus.dma._3.check(self.bus)) return self.bus.dma._3.isBlocking(); | ||||
|  | ||||
|         return false; | ||||
|     } | ||||
|  | ||||
|     fn thumbFetch(self: *Self) u16 { | ||||
|         defer self.r[15] += 2; | ||||
|         return self.bus.read(u16, self.r[15]); | ||||
|   | ||||
| @@ -46,7 +46,9 @@ pub fn runFrame(sched: *Scheduler, cpu: *Arm7tdmi, bus: *Bus) void { | ||||
|     const frame_end = sched.tick + cycles_per_frame; | ||||
|  | ||||
|     while (sched.tick < frame_end) { | ||||
|         cpu.step(); | ||||
|         if (bus.io.haltcnt == .Halt) sched.tick += 1; | ||||
|         if (bus.io.haltcnt == .Execute) cpu.step(); | ||||
|         bus.handleDMATransfers(); | ||||
|  | ||||
|         while (sched.tick >= sched.nextTimestamp()) { | ||||
|             sched.handleEvent(cpu, bus); | ||||
|   | ||||
							
								
								
									
										29
									
								
								src/main.zig
									
									
									
									
									
								
							
							
						
						
									
										29
									
								
								src/main.zig
									
									
									
									
									
								
							| @@ -66,11 +66,33 @@ pub fn main() anyerror!void { | ||||
|     defer if (save_path) |path| alloc.free(path); | ||||
|     log.info("Save Path: {s}", .{save_path}); | ||||
|  | ||||
|     // Initialize SDL | ||||
|     const status = SDL.SDL_Init(SDL.SDL_INIT_VIDEO | SDL.SDL_INIT_EVENTS | SDL.SDL_INIT_AUDIO | SDL.SDL_INIT_GAMECONTROLLER); | ||||
|     defer SDL.SDL_Quit(); | ||||
|     if (status < 0) sdlPanic(); | ||||
|  | ||||
|     // Initialize SDL Audio | ||||
|     var have: SDL.SDL_AudioSpec = undefined; | ||||
|     var want = std.mem.zeroes(SDL.SDL_AudioSpec); | ||||
|     want.freq = 32768; | ||||
|     want.format = SDL.AUDIO_S8; | ||||
|     want.channels = 2; | ||||
|     want.samples = 0x200; | ||||
|     want.callback = null; | ||||
|  | ||||
|     const audio_dev = SDL.SDL_OpenAudioDevice(null, 0, &want, &have, 0); | ||||
|     defer SDL.SDL_CloseAudioDevice(audio_dev); | ||||
|     if (audio_dev == 0) sdlPanic(); | ||||
|  | ||||
|     // Start Playback on the Audio evice | ||||
|     SDL.SDL_PauseAudioDevice(audio_dev, 0); | ||||
|  | ||||
|     // Initialize Emulator | ||||
|     var scheduler = Scheduler.init(alloc); | ||||
|     defer scheduler.deinit(); | ||||
|  | ||||
|     var bus = try Bus.init(alloc, &scheduler, rom_path, bios_path, save_path); | ||||
|     const paths = .{ .bios = bios_path, .rom = rom_path, .save = save_path }; | ||||
|     var bus = try Bus.init(alloc, &scheduler, audio_dev, paths); | ||||
|     defer bus.deinit(); | ||||
|  | ||||
|     var cpu = Arm7tdmi.init(&scheduler, &bus); | ||||
| @@ -91,11 +113,6 @@ pub fn main() anyerror!void { | ||||
|     const emu_thread = try Thread.spawn(.{}, emu.run, .{ .LimitedFPS, &quit, &emu_rate, &scheduler, &cpu, &bus }); | ||||
|     defer emu_thread.join(); | ||||
|  | ||||
|     // Initialize SDL | ||||
|     const status = SDL.SDL_Init(SDL.SDL_INIT_VIDEO | SDL.SDL_INIT_EVENTS | SDL.SDL_INIT_AUDIO | SDL.SDL_INIT_GAMECONTROLLER); | ||||
|     if (status < 0) sdlPanic(); | ||||
|     defer SDL.SDL_Quit(); | ||||
|  | ||||
|     const title = correctTitle(bus.pak.title); | ||||
|  | ||||
|     var title_buf: [0x20]u8 = std.mem.zeroes([0x20]u8); | ||||
|   | ||||
| @@ -101,3 +101,9 @@ pub fn fixTitle(alloc: std.mem.Allocator, title: [12]u8) ![]u8 { | ||||
|  | ||||
|     return buf; | ||||
| } | ||||
|  | ||||
| pub const FilePaths = struct { | ||||
|     rom: []const u8, | ||||
|     bios: ?[]const u8, | ||||
|     save: ?[]const u8, | ||||
| }; | ||||
|   | ||||
		Reference in New Issue
	
	Block a user