create raylib chip interface
This commit is contained in:
parent
ffbb823d6e
commit
90ae28d4d0
@ -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/
|
||||
|
21
src/chip.zig
21
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];
|
||||
}
|
||||
|
117
src/main.zig
117
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
96
src/raylib-chip.zig
Normal file
96
src/raylib-chip.zig
Normal file
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user