1
0

create raylib chip interface

This commit is contained in:
Rokas Puzonas 2023-09-02 13:18:54 +03:00
parent ffbb823d6e
commit 90ae28d4d0
4 changed files with 141 additions and 95 deletions

View File

@ -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/

View File

@ -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];
}

View File

@ -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
View 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);
}
}
}
}