From 90ae28d4d09cfde2beda71712ea1bce1d773dd65 Mon Sep 17 00:00:00 2001 From: Rokas Puzonas Date: Sat, 2 Sep 2023 13:18:54 +0300 Subject: [PATCH] create raylib chip interface --- README.md | 2 + src/chip.zig | 21 ++++++++ src/main.zig | 117 +++++++++----------------------------------- src/raylib-chip.zig | 96 ++++++++++++++++++++++++++++++++++++ 4 files changed, 141 insertions(+), 95 deletions(-) create mode 100644 src/raylib-chip.zig diff --git a/README.md b/README.md index d835e55..5cd0a92 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,5 @@ # CHIP-8 Emulator Techinal reference: http://devernay.free.fr/hacks/chip8/C8TECH10.HTM +ROM archive: https://johnearnest.github.io/chip8Archive/ +Awesome chip8: https://chip-8.github.io/ diff --git a/src/chip.zig b/src/chip.zig index 1ca6b4d..7a86035 100644 --- a/src/chip.zig +++ b/src/chip.zig @@ -63,6 +63,27 @@ pub const ChipContext = struct { }; } + pub fn init_default_sprites(self: *ChipContext) void { + self.set_memory(0x000, &[_]u8{ + 0xF0, 0x90, 0x90, 0x90, 0xF0, // "0" + 0x20, 0x60, 0x20, 0x20, 0x70, // "1" + 0xF0, 0x10, 0xF0, 0x80, 0xF0, // "2" + 0xF0, 0x10, 0xF0, 0x10, 0xF0, // "3" + 0x90, 0x90, 0xF0, 0x10, 0x10, // "4" + 0xF0, 0x80, 0xF0, 0x10, 0xF0, // "5" + 0xF0, 0x80, 0xF0, 0x90, 0xF0, // "6" + 0xF0, 0x10, 0x20, 0x40, 0x40, // "7" + 0xF0, 0x90, 0xF0, 0x90, 0xF0, // "8" + 0xF0, 0x90, 0xF0, 0x10, 0xF0, // "9" + 0xF0, 0x90, 0xF0, 0x90, 0x90, // "A" + 0xE0, 0x90, 0xE0, 0x90, 0xE0, // "B" + 0xF0, 0x80, 0x80, 0x80, 0xF0, // "C" + 0xE0, 0x90, 0x90, 0x90, 0xE0, // "D" + 0xF0, 0x80, 0xF0, 0x80, 0xF0, // "E" + 0xF0, 0x80, 0xF0, 0x80, 0x80 // "F" + }); + } + pub fn display_get(self: *ChipContext, x: u8, y: u8) bool { return self.display[y * ChipContext.DISPLAY_WIDTH + x]; } diff --git a/src/main.zig b/src/main.zig index 06ffd5f..0b295ca 100755 --- a/src/main.zig +++ b/src/main.zig @@ -1,87 +1,34 @@ const rl = @import("raylib"); const std = @import("std"); +const assert = @import("std").debug.assert; const ChipContext = @import("chip.zig").ChipContext; +const RaylibChip = @import("raylib-chip.zig"); -pub fn render_display(chip: *ChipContext, x: i32, y: i32, pixel_size: i32, on_color: rl.Color, off_color: rl.Color) void { - for (0..ChipContext.DISPLAY_HEIGHT) |oy| { - for (0..ChipContext.DISPLAY_WIDTH) |ox| { - const ix = x + @as(i32, @intCast(ox)) * pixel_size; - const iy = y + @as(i32, @intCast(oy)) * pixel_size; - const pixel = chip.display[oy * ChipContext.DISPLAY_WIDTH + ox]; - const color = if (pixel) on_color else off_color; - rl.drawRectangle(ix, iy, pixel_size, pixel_size, color); - } - } -} +pub fn gen_sin_wave(wave: *rl.Wave, frequency: f32) void { + assert(wave.sampleSize == 16); // Only 16 bits are supported -pub fn read_keyboard_input(chip: *ChipContext) void { - const keys = [16]rl.KeyboardKey{ - .key_x, - .key_one, - .key_two, - .key_three, - .key_q, - .key_w, - .key_e, - .key_a, - .key_s, - .key_d, - .key_z, - .key_c, - .key_four, - .key_r, - .key_f, - .key_v, - }; - for (0.., keys) |i, key| { - chip.input[i] = rl.isKeyDown(key); - } -} + const sample_rate: f32 = @floatFromInt(wave.sampleRate); + const sample_size: u5 = @truncate(wave.sampleSize); + const max_sample_value: f32 = @floatFromInt(@shlExact(@as(u32, 1), sample_size - 1)); -pub fn create_sin_wave(samples: []i16, sample_rate: u32, duration: u32, frequency: u32, volume: f32) void { - const sample_rate_float: f32 = @floatFromInt(sample_rate); - const frequency_float: f32 = @floatFromInt(frequency); - - const sample_size = 16; - const max_sample_value: f32 = @floatFromInt((1 << (sample_size-1)) - 1); - - const frame_count = sample_rate * duration; - for (1..frame_count) |i| { - const i_float: f32 = @floatFromInt(i); - const wave: f32 = @sin(std.math.pi*2*frequency_float/sample_rate_float*i_float); - samples[i] = @intFromFloat(wave*max_sample_value * volume); + const data: [*]i16 = @ptrCast(@alignCast(wave.data)); + for (0..wave.frameCount) |i| { + const i_f32: f32 = @floatFromInt(i); + const sin_value: f32 = @sin(std.math.pi*2*frequency/sample_rate*i_f32); + data[i] = @intFromFloat(sin_value*max_sample_value); } } pub fn main() anyerror!void { var chip = ChipContext.init(); - - chip.set_memory(0x000, &[_]u8{ - 0xF0, 0x90, 0x90, 0x90, 0xF0, // "0" - 0x20, 0x60, 0x20, 0x20, 0x70, // "1" - 0xF0, 0x10, 0xF0, 0x80, 0xF0, // "2" - 0xF0, 0x10, 0xF0, 0x10, 0xF0, // "3" - 0x90, 0x90, 0xF0, 0x10, 0x10, // "4" - 0xF0, 0x80, 0xF0, 0x10, 0xF0, // "5" - 0xF0, 0x80, 0xF0, 0x90, 0xF0, // "6" - 0xF0, 0x10, 0x20, 0x40, 0x40, // "7" - 0xF0, 0x90, 0xF0, 0x90, 0xF0, // "8" - 0xF0, 0x90, 0xF0, 0x10, 0xF0, // "9" - 0xF0, 0x90, 0xF0, 0x90, 0x90, // "A" - 0xE0, 0x90, 0xE0, 0x90, 0xE0, // "B" - 0xF0, 0x80, 0x80, 0x80, 0xF0, // "C" - 0xE0, 0x90, 0x90, 0x90, 0xE0, // "D" - 0xF0, 0x80, 0xF0, 0x80, 0xF0, // "E" - 0xF0, 0x80, 0xF0, 0x80, 0x80 // "F" - }); + chip.init_default_sprites(); { - const file = try std.fs.cwd().openFile("morse_demo.ch8", .{ .mode = .read_only }); + const file = try std.fs.cwd().openFile("ROMs/morse_demo.ch8", .{ .mode = .read_only }); defer file.close(); try chip.set_memory_from_file(0x200, file); } - const pixel_size = 20; const screen_width = ChipContext.DISPLAY_WIDTH * pixel_size; const screen_height = ChipContext.DISPLAY_HEIGHT * pixel_size; @@ -94,12 +41,8 @@ pub fn main() anyerror!void { rl.setTargetFPS(60); - var chip_speed: f32 = 500; - var timer_speed: f32 = 60; - const sample_rate = 44100; var data = [1]i16{0} ** sample_rate; - create_sin_wave(&data, sample_rate, 1.0, 440, 0.2); var chip_wave = rl.Wave{ .frameCount = sample_rate, .sampleRate = sample_rate, @@ -107,41 +50,25 @@ pub fn main() anyerror!void { .channels = 1, .data = @ptrCast(&data), }; + gen_sin_wave(&chip_wave, 440); var chip_sound = rl.loadSoundFromWave(chip_wave); defer rl.unloadSound(chip_sound); + rl.setSoundVolume(chip_sound, 0.2); + + var raylib_chip = RaylibChip.init(&chip, &chip_sound); + raylib_chip.tick_speed = 500; + raylib_chip.timer_speed = 60; - var chip_clock_time: f32 = 0; - var chip_timer_timer: f32 = 0; while (!rl.windowShouldClose()) { var dt = rl.getFrameTime(); - chip_clock_time += dt; - chip_timer_timer += dt; - read_keyboard_input(&chip); - - while (chip_clock_time > 1/chip_speed) { - chip.step() catch {}; - chip_clock_time -= 1/chip_speed; - } - - while (chip_timer_timer > 1/timer_speed) { - chip.update_timer(); - chip_timer_timer -= 1/timer_speed; - } - - if (chip.ST > 0) { - if (!rl.isSoundPlaying(chip_sound)) { - rl.playSound(chip_sound); - } - } else { - rl.stopSound(chip_sound); - } + raylib_chip.update(dt); { rl.beginDrawing(); defer rl.endDrawing(); rl.clearBackground(rl.Color.white); - render_display(&chip, 0, 0, pixel_size, rl.Color.black, rl.Color.ray_white); + raylib_chip.render(0, 0, screen_width, screen_height); } } } diff --git a/src/raylib-chip.zig b/src/raylib-chip.zig new file mode 100644 index 0000000..1867179 --- /dev/null +++ b/src/raylib-chip.zig @@ -0,0 +1,96 @@ +const Self = @This(); +const rl = @import("raylib"); +const ChipContext = @import("chip.zig").ChipContext; + +chip: *ChipContext, +on_color: rl.Color, +off_color: rl.Color, +timer_speed: f32, +tick_speed: f32, +beep_sound: *rl.Sound, + +tick_time: f32, +timer_time: f32, + +pub fn init(chip: *ChipContext, beep_sound: *rl.Sound) Self { + return Self{ + .chip = chip, + .off_color = rl.Color.black, + .on_color = rl.Color.ray_white, + .timer_speed = 60, + .tick_speed = 500, + .tick_time = 0, + .timer_time = 0, + .beep_sound = beep_sound + }; +} + +pub fn update_input(self: *Self) void { + const keys = [16]rl.KeyboardKey{ + .key_x, + .key_one, + .key_two, + .key_three, + .key_q, + .key_w, + .key_e, + .key_a, + .key_s, + .key_d, + .key_z, + .key_c, + .key_four, + .key_r, + .key_f, + .key_v, + }; + + for (0.., keys) |i, key| { + self.chip.input[i] = rl.isKeyDown(key); + } +} + +pub fn update(self: *Self, dt: f32) void { + self.update_input(); + + self.tick_time += dt; + while (self.tick_time > 1/self.tick_speed) { + self.chip.step() catch {}; + self.tick_time -= 1/self.tick_speed; + } + + self.timer_time += dt; + while (self.timer_time > 1/self.timer_speed) { + self.chip.update_timer(); + self.timer_time -= 1/self.timer_speed; + } + + const beep_sound = self.beep_sound.*; + if (self.chip.ST > 0) { + if (!rl.isSoundPlaying(beep_sound)) { + rl.playSound(beep_sound); + } + } else { + if (rl.isSoundPlaying(beep_sound)) { + rl.stopSound(beep_sound); + } + } +} + +pub fn render(self: *Self, x: i32, y: i32, width: i32, height: i32) void { + const pixel_width = @divFloor(width, ChipContext.DISPLAY_WIDTH); + const pixel_height = @divFloor(height, ChipContext.DISPLAY_HEIGHT); + + rl.drawRectangle(0, 0, width, height, self.off_color); + + for (0..ChipContext.DISPLAY_HEIGHT) |oy| { + for (0..ChipContext.DISPLAY_WIDTH) |ox| { + const pixel = self.chip.display[oy * ChipContext.DISPLAY_WIDTH + ox]; + if (pixel) { + const ix = x + @as(i32, @intCast(ox)) * pixel_width; + const iy = y + @as(i32, @intCast(oy)) * pixel_height; + rl.drawRectangle(ix, iy, pixel_width, pixel_height, self.on_color); + } + } + } +}