kbd and sound
This commit is contained in:
264
src/main.cpp
264
src/main.cpp
@@ -1,6 +1,11 @@
|
|||||||
#include <SDL2/SDL.h>
|
#include <SDL2/SDL.h>
|
||||||
|
#include <SDL2/SDL_audio.h>
|
||||||
|
#include <SDL2/SDL_render.h>
|
||||||
|
#include <atomic>
|
||||||
|
#include <condition_variable>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
#include <sys/types.h>
|
#include <sys/types.h>
|
||||||
|
#include <thread>
|
||||||
|
|
||||||
#define BYTECODE_READER_IMPLEMENTATION
|
#define BYTECODE_READER_IMPLEMENTATION
|
||||||
#include "reader.hpp"
|
#include "reader.hpp"
|
||||||
@@ -22,28 +27,40 @@ const int TARGET_CYCLES_PER_SECOND = 500;
|
|||||||
const int TARGET_MS_PER_CYCLE = 1000 / TARGET_CYCLES_PER_SECOND;
|
const int TARGET_MS_PER_CYCLE = 1000 / TARGET_CYCLES_PER_SECOND;
|
||||||
const int TARGET_FRAMERATE = 60;
|
const int TARGET_FRAMERATE = 60;
|
||||||
const int TARGET_MS_PER_FRAME = 1000 / TARGET_FRAMERATE;
|
const int TARGET_MS_PER_FRAME = 1000 / TARGET_FRAMERATE;
|
||||||
|
const int TARGET_MS_PER_TICK = 1000 / 60;
|
||||||
static int SCALE = 10;
|
static int SCALE = 10;
|
||||||
static int BG_COLOR = 0x081820;
|
static int BG_COLOR = 0x081820;
|
||||||
static int FG_COLOR = 0x88c070;
|
static int FG_COLOR = 0x88c070;
|
||||||
|
|
||||||
void draw(SDL_Renderer *renderer, SDL_Texture *texture,
|
const int SAMPLE_RATE = 44100; // 44.1kHz sample rate
|
||||||
bool framebuffer[SCREEN_HEIGHT][SCREEN_WIDTH]) {
|
const int FREQUENCY = 440; // A4 tone (you can change this)
|
||||||
|
const int AMPLITUDE = 28000; // Volume level
|
||||||
|
const int SAMPLES_PER_CYCLE = SAMPLE_RATE / FREQUENCY;
|
||||||
|
|
||||||
printf("Drawing...\n");
|
void audioCallback(void *userdata, Uint8 *stream, int len) {
|
||||||
|
(void)userdata;
|
||||||
|
static int phase = 0;
|
||||||
|
Sint16 *buffer = (Sint16 *)stream;
|
||||||
|
int length = len / 2;
|
||||||
|
|
||||||
uint32_t pixels[SCREEN_WIDTH * SCREEN_HEIGHT];
|
for (int i = 0; i < length; i++) {
|
||||||
for (int i = 0; i < SCREEN_HEIGHT; i++) {
|
buffer[i] = (phase < SAMPLES_PER_CYCLE / 4) ? AMPLITUDE : -AMPLITUDE;
|
||||||
for (int j = 0; j < SCREEN_WIDTH; j++) {
|
phase = (phase + 1) % SAMPLES_PER_CYCLE;
|
||||||
pixels[i * SCREEN_WIDTH + j] =
|
|
||||||
framebuffer[i][j] ? FG_COLOR : BG_COLOR;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
SDL_UpdateTexture(texture, nullptr, pixels,
|
void initAudio() {
|
||||||
SCREEN_WIDTH * sizeof(uint32_t));
|
SDL_AudioSpec want, have;
|
||||||
SDL_RenderClear(renderer);
|
SDL_zero(want);
|
||||||
SDL_RenderCopy(renderer, texture, nullptr, nullptr);
|
want.freq = SAMPLE_RATE;
|
||||||
SDL_RenderPresent(renderer);
|
want.format = AUDIO_S16SYS;
|
||||||
|
want.channels = 1;
|
||||||
|
want.samples = 2048; // Buffer size
|
||||||
|
want.callback = audioCallback;
|
||||||
|
|
||||||
|
if (SDL_OpenAudio(&want, &have) < 0) {
|
||||||
|
SDL_Log("Failed to open audio: %s", SDL_GetError());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static uint8_t FONT[0x10][0x05] = {
|
static uint8_t FONT[0x10][0x05] = {
|
||||||
@@ -117,13 +134,12 @@ class Chip8 {
|
|||||||
void view_ram();
|
void view_ram();
|
||||||
void dump_ram();
|
void dump_ram();
|
||||||
void view_stack();
|
void view_stack();
|
||||||
void dump_fb(int);
|
|
||||||
|
|
||||||
// allow the font to be read
|
// allow the font to be read
|
||||||
bool is_protected(size_t addr) {
|
bool is_protected(size_t addr) {
|
||||||
if (addr <= sizeof(FONT))
|
if (addr <= sizeof(FONT))
|
||||||
return false;
|
return false;
|
||||||
return addr < 0x200;
|
return addr < 0x1FF;
|
||||||
}
|
}
|
||||||
|
|
||||||
// only allow addresses in the program space to be executed, addresses not
|
// only allow addresses in the program space to be executed, addresses not
|
||||||
@@ -153,6 +169,7 @@ class Chip8 {
|
|||||||
|
|
||||||
void set_sound_timer(uint8_t val) {
|
void set_sound_timer(uint8_t val) {
|
||||||
// enable buzzer
|
// enable buzzer
|
||||||
|
printf("The sound timer is set to %d\n", val);
|
||||||
this->sound_timer = val;
|
this->sound_timer = val;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -168,32 +185,64 @@ class Chip8 {
|
|||||||
return this->fb[y][x];
|
return this->fb[y][x];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool fb[SCREEN_HEIGHT][SCREEN_WIDTH];
|
||||||
|
std::atomic_uint8_t delay;
|
||||||
|
std::atomic_uint8_t sound_timer;
|
||||||
|
std::mutex key_mutex;
|
||||||
|
std::condition_variable key_cv;
|
||||||
|
std::atomic<bool> key_pressed = false;
|
||||||
|
std::atomic<uint8_t> last_key = 0;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
uint8_t *ram;
|
uint8_t *ram;
|
||||||
bool fb[SCREEN_HEIGHT][SCREEN_WIDTH];
|
|
||||||
uint16_t pc;
|
uint16_t pc;
|
||||||
uint16_t *stack;
|
uint16_t *stack;
|
||||||
uint8_t sp;
|
uint8_t sp;
|
||||||
uint8_t v[16];
|
uint8_t v[16];
|
||||||
uint16_t i;
|
uint16_t i;
|
||||||
uint8_t delay;
|
|
||||||
uint8_t sound_timer;
|
|
||||||
// bool compat;
|
// bool compat;
|
||||||
};
|
};
|
||||||
|
|
||||||
int Chip8::run() {
|
void draw(SDL_Renderer *renderer, SDL_Texture *texture, Chip8 *chip8) {
|
||||||
using namespace std::chrono;
|
printf("Drawing...\n");
|
||||||
|
|
||||||
int exit = 0;
|
uint32_t pixels[SCREEN_WIDTH * SCREEN_HEIGHT];
|
||||||
constexpr auto cycle_time = milliseconds(TARGET_MS_PER_CYCLE);
|
for (int i = 0; i < SCREEN_HEIGHT; i++) {
|
||||||
constexpr auto timer_interval = milliseconds(TARGET_MS_PER_FRAME);
|
for (int j = 0; j < SCREEN_WIDTH; j++) {
|
||||||
auto last_timer_update = high_resolution_clock::now();
|
pixels[i * SCREEN_WIDTH + j] =
|
||||||
|
chip8->fb[i][j] ? FG_COLOR : BG_COLOR;
|
||||||
if (SDL_Init(SDL_INIT_VIDEO) < 0) {
|
}
|
||||||
printf("Failed to initialize SDL: %s\n", SDL_GetError());
|
|
||||||
return 1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SDL_UpdateTexture(texture, nullptr, pixels,
|
||||||
|
SCREEN_WIDTH * sizeof(uint32_t));
|
||||||
|
SDL_RenderClear(renderer);
|
||||||
|
SDL_RenderCopy(renderer, texture, nullptr, nullptr);
|
||||||
|
SDL_RenderPresent(renderer);
|
||||||
|
}
|
||||||
|
|
||||||
|
void timer_thread(Chip8 *chip8) {
|
||||||
|
using namespace std::chrono;
|
||||||
|
|
||||||
|
initAudio();
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
std::this_thread::sleep_for(milliseconds(TARGET_MS_PER_TICK)); // ~60Hz
|
||||||
|
if (chip8->delay > 0) {
|
||||||
|
chip8->delay--;
|
||||||
|
}
|
||||||
|
if (chip8->sound_timer > 0) {
|
||||||
|
SDL_PauseAudio(0);
|
||||||
|
chip8->sound_timer--;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (chip8->sound_timer == 0) {
|
||||||
|
SDL_PauseAudio(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void render_thread(Chip8 *chip8) {
|
||||||
SDL_Window *window = SDL_CreateWindow(
|
SDL_Window *window = SDL_CreateWindow(
|
||||||
"CHIP-8 Emulator", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED,
|
"CHIP-8 Emulator", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED,
|
||||||
SCREEN_WIDTH * SCALE, SCREEN_HEIGHT * SCALE, SDL_WINDOW_SHOWN);
|
SCREEN_WIDTH * SCALE, SCREEN_HEIGHT * SCALE, SDL_WINDOW_SHOWN);
|
||||||
@@ -203,16 +252,68 @@ int Chip8::run() {
|
|||||||
SDL_TEXTUREACCESS_STREAMING,
|
SDL_TEXTUREACCESS_STREAMING,
|
||||||
SCREEN_WIDTH, SCREEN_HEIGHT);
|
SCREEN_WIDTH, SCREEN_HEIGHT);
|
||||||
|
|
||||||
bool running = true;
|
memset(chip8->fb, 0, SCREEN_WIDTH * SCREEN_HEIGHT);
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
printf("Rendering...\n");
|
||||||
|
draw(renderer, texture, chip8);
|
||||||
|
std::this_thread::sleep_for(
|
||||||
|
std::chrono::milliseconds(TARGET_MS_PER_FRAME)); // 60Hz
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void input_thread(Chip8 *chip8, std::atomic_bool &running) {
|
||||||
SDL_Event event;
|
SDL_Event event;
|
||||||
|
while (true) {
|
||||||
|
while (SDL_PollEvent(&event)) {
|
||||||
|
if (event.type == SDL_QUIT)
|
||||||
|
running = false;
|
||||||
|
if (event.type == SDL_KEYDOWN || event.type == SDL_KEYUP) {
|
||||||
|
printf("Key pressed! %c\n", event.key.keysym.sym);
|
||||||
|
std::unique_lock<std::mutex> lock(chip8->key_mutex);
|
||||||
|
chip8->key_pressed = (event.type == SDL_KEYDOWN);
|
||||||
|
// check if the key is a valid CHIP-8, either a number or a, b,
|
||||||
|
// c, d, e, or f
|
||||||
|
if ((event.key.keysym.sym < 0x30 ||
|
||||||
|
event.key.keysym.sym > 0x39) && // 0-9
|
||||||
|
(event.key.keysym.sym < 0x61 ||
|
||||||
|
event.key.keysym.sym > 0x66))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
uint8_t key = event.key.keysym.sym - 0x30;
|
||||||
|
if (key > 0x09) {
|
||||||
|
key = key - 0x27;
|
||||||
|
}
|
||||||
|
printf("Key: %x\n", key);
|
||||||
|
chip8->last_key = key; // Map this to CHIP-8 keys
|
||||||
|
chip8->key_cv.notify_one();
|
||||||
|
lock.unlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int Chip8::run() {
|
||||||
|
using namespace std::chrono;
|
||||||
|
|
||||||
|
constexpr auto cycle_time = milliseconds(TARGET_MS_PER_CYCLE);
|
||||||
|
|
||||||
|
if (SDL_Init(SDL_INIT_VIDEO) < 0) {
|
||||||
|
printf("Failed to initialize SDL: %s\n", SDL_GetError());
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::atomic_bool running = true;
|
||||||
|
|
||||||
|
std::thread render(render_thread, this);
|
||||||
|
std::this_thread::sleep_for(milliseconds(TARGET_MS_PER_FRAME));
|
||||||
|
std::thread input(input_thread, this, std::ref(running));
|
||||||
|
std::thread timer(timer_thread, this);
|
||||||
|
|
||||||
|
int ret = 0;
|
||||||
|
|
||||||
while (running) {
|
while (running) {
|
||||||
while (SDL_PollEvent(&event)) {
|
|
||||||
if (event.type == SDL_QUIT) {
|
|
||||||
running = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
auto start_time = high_resolution_clock::now();
|
auto start_time = high_resolution_clock::now();
|
||||||
|
|
||||||
uint16_t op = (read_mem(pc) << 8) | read_mem(pc + 1);
|
uint16_t op = (read_mem(pc) << 8) | read_mem(pc + 1);
|
||||||
@@ -232,7 +333,7 @@ int Chip8::run() {
|
|||||||
case EXIT: {
|
case EXIT: {
|
||||||
// From Peter Miller's chip8run. Exit emulator with a return value
|
// From Peter Miller's chip8run. Exit emulator with a return value
|
||||||
// of N.
|
// of N.
|
||||||
exit = bytecode.operand.byte;
|
ret = bytecode.operand.byte;
|
||||||
running = false;
|
running = false;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -557,7 +658,14 @@ int Chip8::run() {
|
|||||||
// pressed.
|
// pressed.
|
||||||
// Checks the keyboard, and if the key corresponding to the value of
|
// Checks the keyboard, and if the key corresponding to the value of
|
||||||
// Vx is currently in the down position, PC is increased by 2.
|
// Vx is currently in the down position, PC is increased by 2.
|
||||||
fprintf(stderr, "SKIP_PRESSED_REG not implemented\n");
|
// fprintf(stderr, "SKIP_PRESSED_REG not implemented\n");
|
||||||
|
printf("Last key: %x\n", this->last_key.load());
|
||||||
|
printf("Key: %x\n", bytecode.operand.byte);
|
||||||
|
uint8_t key = this->v[bytecode.operand.byte];
|
||||||
|
|
||||||
|
if (this->last_key.load() == key && this->key_pressed.load()) {
|
||||||
|
pc += 2;
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case SKIP_NOT_PRESSED_REG: {
|
case SKIP_NOT_PRESSED_REG: {
|
||||||
@@ -565,7 +673,17 @@ int Chip8::run() {
|
|||||||
// not pressed.
|
// not pressed.
|
||||||
// Checks the keyboard, and if the key corresponding to the value of
|
// Checks the keyboard, and if the key corresponding to the value of
|
||||||
// Vx is currently in the up position, PC is increased by 2.
|
// Vx is currently in the up position, PC is increased by 2.
|
||||||
fprintf(stderr, "SKIP_NOT_PRESSED_REG not implemented\n");
|
// fprintf(stderr, "SKIP_NOT_PRESSED_REG not implemented\n");
|
||||||
|
printf("Last key: %x\n", this->last_key.load());
|
||||||
|
printf("Key: %x\n", bytecode.operand.byte);
|
||||||
|
uint8_t key = this->v[bytecode.operand.byte];
|
||||||
|
|
||||||
|
if (this->last_key.load() != key) {
|
||||||
|
pc += 2;
|
||||||
|
}
|
||||||
|
if (this->last_key.load() == key && !this->key_pressed.load()) {
|
||||||
|
pc += 2;
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case LD_REG_DT: {
|
case LD_REG_DT: {
|
||||||
@@ -579,19 +697,31 @@ int Chip8::run() {
|
|||||||
// Vx.
|
// Vx.
|
||||||
// All execution stops until a key is pressed, then the value of
|
// All execution stops until a key is pressed, then the value of
|
||||||
// that key is stored in Vx.
|
// that key is stored in Vx.
|
||||||
fprintf(stderr, "LD_REG_K not implemented\n");
|
std::unique_lock<std::mutex> lock(this->key_mutex);
|
||||||
|
|
||||||
|
this->key_cv.wait(lock, [&]() {
|
||||||
|
this->v[bytecode.operand.byte] = this->last_key.load();
|
||||||
|
return this->key_pressed.load();
|
||||||
|
});
|
||||||
|
lock.unlock();
|
||||||
|
while (this->key_pressed.load()) {
|
||||||
|
printf("Waiting for key to be released %x %x\n",
|
||||||
|
this->last_key.load(), this->key_pressed.load());
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case LD_DT_REG: {
|
case LD_DT_REG: {
|
||||||
// Set delay timer = Vx.
|
// Set delay timer = Vx.
|
||||||
// DT is set equal to the value of Vx.
|
// DT is set equal to the value of Vx.
|
||||||
this->delay = this->v[bytecode.operand.reg_reg.x];
|
this->delay = this->v[bytecode.operand.byte];
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case LD_ST_REG: {
|
case LD_ST_REG: {
|
||||||
// Set sound timer = Vx.
|
// Set sound timer = Vx.
|
||||||
// ST is set equal to the value of Vx.
|
// ST is set equal to the value of Vx.
|
||||||
set_sound_timer(bytecode.operand.byte);
|
printf("Sound timer set to %d\n", this->v[bytecode.operand.byte]);
|
||||||
|
set_sound_timer(this->v[bytecode.operand.byte]);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case ADD_I_REG: {
|
case ADD_I_REG: {
|
||||||
@@ -655,31 +785,10 @@ int Chip8::run() {
|
|||||||
case UNKNOWN_INSTRUCTION: {
|
case UNKNOWN_INSTRUCTION: {
|
||||||
fprintf(stderr, "Unknown instruction type: %d\n",
|
fprintf(stderr, "Unknown instruction type: %d\n",
|
||||||
bytecode.instruction_type);
|
bytecode.instruction_type);
|
||||||
exit = 1;
|
exit(1);
|
||||||
running = false;
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
auto now = high_resolution_clock::now();
|
|
||||||
// 60hz clock
|
|
||||||
if (duration_cast<milliseconds>(now - last_timer_update) >=
|
|
||||||
timer_interval) {
|
|
||||||
printf("Updating...\n");
|
|
||||||
if (delay > 0) {
|
|
||||||
--delay;
|
|
||||||
}
|
|
||||||
if (sound_timer > 0) {
|
|
||||||
--sound_timer;
|
|
||||||
}
|
|
||||||
|
|
||||||
// SDL technically has an SDL_Delay function thing, but doing it
|
|
||||||
// this way allows us to be more flexible and more away from SDL in
|
|
||||||
// the future if we wanted to.
|
|
||||||
draw(renderer, texture, this->fb);
|
|
||||||
last_timer_update = now;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto elapsed_time = duration_cast<milliseconds>(
|
auto elapsed_time = duration_cast<milliseconds>(
|
||||||
high_resolution_clock::now() - start_time);
|
high_resolution_clock::now() - start_time);
|
||||||
if (elapsed_time < cycle_time) {
|
if (elapsed_time < cycle_time) {
|
||||||
@@ -687,12 +796,16 @@ int Chip8::run() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
SDL_DestroyTexture(texture);
|
timer.detach();
|
||||||
SDL_DestroyRenderer(renderer);
|
render.detach();
|
||||||
SDL_DestroyWindow(window);
|
input.detach();
|
||||||
SDL_Quit();
|
|
||||||
|
|
||||||
return exit;
|
// SDL_DestroyTexture(texture);
|
||||||
|
// SDL_DestroyRenderer(renderer);
|
||||||
|
// SDL_DestroyWindow(window);
|
||||||
|
// SDL_Quit();
|
||||||
|
|
||||||
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Chip8::view_ram() {
|
void Chip8::view_ram() {
|
||||||
@@ -738,19 +851,6 @@ void Chip8::view_stack() {
|
|||||||
printf("\n");
|
printf("\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
void Chip8::dump_fb(int _) {
|
|
||||||
(void)_;
|
|
||||||
FILE *fp = fopen("fb.dump", "wb");
|
|
||||||
|
|
||||||
if (fp == NULL) {
|
|
||||||
printf("Failed to open file\n");
|
|
||||||
exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
fwrite(fb, SCREEN_HEIGHT * SCREEN_WIDTH, 1, fp);
|
|
||||||
fclose(fp);
|
|
||||||
}
|
|
||||||
|
|
||||||
void destroy(int _) {
|
void destroy(int _) {
|
||||||
(void)_;
|
(void)_;
|
||||||
|
|
||||||
|
|||||||
2
test.sh
2
test.sh
@@ -11,6 +11,8 @@ extern_roms=(
|
|||||||
"https://github.com/Timendus/chip8-test-suite/raw/main/bin/2-ibm-logo.ch8"
|
"https://github.com/Timendus/chip8-test-suite/raw/main/bin/2-ibm-logo.ch8"
|
||||||
"https://github.com/Timendus/chip8-test-suite/raw/main/bin/3-corax+.ch8"
|
"https://github.com/Timendus/chip8-test-suite/raw/main/bin/3-corax+.ch8"
|
||||||
"https://github.com/Timendus/chip8-test-suite/raw/main/bin/4-flags.ch8"
|
"https://github.com/Timendus/chip8-test-suite/raw/main/bin/4-flags.ch8"
|
||||||
|
"https://github.com/Timendus/chip8-test-suite/raw/main/bin/6-keypad.ch8"
|
||||||
|
"https://github.com/Timendus/chip8-test-suite/raw/main/bin/7-beep.ch8"
|
||||||
)
|
)
|
||||||
|
|
||||||
for rom in "${extern_roms[@]}"; do
|
for rom in "${extern_roms[@]}"; do
|
||||||
|
|||||||
Reference in New Issue
Block a user