diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 17018dd..02da65f 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -7,6 +7,8 @@ target_sources( main.cpp Machine.cpp Machine.h + Sound.cpp + Sound.h Graphics/Graphics.cpp Graphics/Graphics.h Graphics/Color.h diff --git a/src/Graphics/Graphics.cpp b/src/Graphics/Graphics.cpp index 73ddecf..1b48f20 100644 --- a/src/Graphics/Graphics.cpp +++ b/src/Graphics/Graphics.cpp @@ -18,7 +18,7 @@ void Graphics::create_sdl() { SDL_SetAppMetadata("CHIP-8 Emulator", "0.0.1", "fun.skrd.chip8"); - if (!SDL_Init(SDL_INIT_VIDEO)) + if (!SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO)) { throw std::runtime_error(std::format("Couldn't initialize SDL: {}", SDL_GetError())); } diff --git a/src/Interpreter/Interpreter.cpp b/src/Interpreter/Interpreter.cpp index c7fe6af..97c9a0d 100644 --- a/src/Interpreter/Interpreter.cpp +++ b/src/Interpreter/Interpreter.cpp @@ -8,6 +8,7 @@ Interpreter::Interpreter(std::shared_ptr machine_state) : random_generator{std::random_device{}()} { srand(time(nullptr)); + // quirks = static_cast(InterpreterQuirks::COSMAC_STORE_AND_LOAD); for (bool& display : this->machine_state->display) { display = rand() % 100 > 50; @@ -345,16 +346,19 @@ void Interpreter::ld_vx_vy(const Instruction& instruction) const void Interpreter::or_vx_vy(const Instruction& instruction) const { this->machine_state->v[instruction.x] |= this->machine_state->v[instruction.y]; + this->machine_state->v[0xF] = 0; } void Interpreter::and_vx_vy(const Instruction& instruction) const { this->machine_state->v[instruction.x] &= this->machine_state->v[instruction.y]; + this->machine_state->v[0xF] = 0; } void Interpreter::xor_vx_vy(const Instruction& instruction) const { this->machine_state->v[instruction.x] ^= this->machine_state->v[instruction.y]; + this->machine_state->v[0xF] = 0; } void Interpreter::add_vx_vy(const Instruction& instruction) const @@ -531,22 +535,39 @@ void Interpreter::ld_vx_dt(const Instruction& instruction) const this->machine_state->v[instruction.x] = this->machine_state->dt; } -void Interpreter::ld_vx_k(const Instruction& instruction) const +void Interpreter::ld_vx_k(const Instruction& instruction) { - if (this->machine_state->keyboard == 0) + if (this->machine_state->keyboard == 0 && this->wait_for_key_released == 0) { this->machine_state->pc -= 2; return; } - for (auto key = 0; key < 16; key++) + if (this->machine_state->keyboard != 0 && this->wait_for_key_released == 0) { - if (this->machine_state->keyboard & 1 << key) + this->wait_for_key_released = this->machine_state->keyboard; + this->machine_state->pc -= 2; + return; + } + + if (this->wait_for_key_released != 0) + { + const auto released_keys = this->wait_for_key_released & (~this->machine_state->keyboard); + if (released_keys != 0) { - this->machine_state->v[instruction.x] = key; - break; + for (auto key = 0; key < 16; key++) + { + if (released_keys & 1 << key) + { + this->machine_state->v[instruction.x] = key; + this->wait_for_key_released = 0; + return; + } + } } } + + this->machine_state->pc -= 2; } void Interpreter::ld_dt_vx(const Instruction& instruction) const diff --git a/src/Interpreter/Interpreter.h b/src/Interpreter/Interpreter.h index 9bacec1..03cf193 100644 --- a/src/Interpreter/Interpreter.h +++ b/src/Interpreter/Interpreter.h @@ -20,6 +20,7 @@ class Interpreter std::mt19937 random_generator; uint8_t quirks = 0; + uint16_t wait_for_key_released = false; void execute_instruction(const Instruction& instruction); @@ -51,7 +52,7 @@ class Interpreter void skp_vx(const Instruction& instruction) const; void sknp_vx(const Instruction& instruction) const; void ld_vx_dt(const Instruction& instruction) const; - void ld_vx_k(const Instruction& instruction) const; + void ld_vx_k(const Instruction& instruction); void ld_dt_vx(const Instruction& instruction) const; void ld_st_vx(const Instruction& instruction) const; void add_i_vx(const Instruction& instruction) const; diff --git a/src/Machine.cpp b/src/Machine.cpp index 28e865b..575c3a0 100644 --- a/src/Machine.cpp +++ b/src/Machine.cpp @@ -16,6 +16,7 @@ Machine::Machine() : machine_state{std::make_shared()}, graphics{std::make_shared()}, + sound{std::make_unique()}, callback_manager(std::make_shared()), interpreter{std::make_shared(this->machine_state)}, @@ -26,6 +27,9 @@ Machine::Machine() : last_update_time{0}, accumulator{0}, target_cycle_time{1.0 / this->ips}, + last_timer_time{0}, + timer_accumulator{0}, + timer_cycle_time{1.0 / 60}, running{false} { this->register_callbacks(); @@ -36,9 +40,14 @@ void Machine::iterate() { if (running) { + this->update_timers(); this->execute_interpreter(); } + if (machine_state->st > 0) + { + this->sound->square(); + } this->graphics->start(); this->ui_manager->render(); this->graphics->end(); @@ -158,6 +167,21 @@ void Machine::execute_interpreter() } } +void Machine::update_timers() +{ + const auto current_time = SDL_GetTicks(); + const auto delta_time = static_cast(current_time - this->last_update_time) / 1000.0; + this->last_timer_time = current_time; + this->timer_accumulator += delta_time; + + while (this->timer_accumulator >= this->timer_cycle_time) + { + machine_state->dt = machine_state->dt > 0 ? machine_state->dt - 1 : 0; + machine_state->st = machine_state->st > 0 ? machine_state->st - 1 : 0; + this->timer_accumulator -= this->timer_cycle_time; + } +} + void Machine::register_callbacks() { callback_manager->rom_load_callback.push_back(std::bind( diff --git a/src/Machine.h b/src/Machine.h index f26969a..d255f47 100644 --- a/src/Machine.h +++ b/src/Machine.h @@ -1,6 +1,7 @@ #ifndef MACHINE_H #define MACHINE_H +#include "Sound.h" #include "Graphics/Graphics.h" #include "Interpreter/Interpreter.h" #include "SDL3/SDL_events.h" @@ -11,6 +12,7 @@ class Machine { std::shared_ptr machine_state; std::shared_ptr graphics; + std::unique_ptr sound; std::shared_ptr callback_manager; std::shared_ptr interpreter; @@ -21,10 +23,16 @@ class Machine double accumulator; double target_cycle_time; + uint64_t last_timer_time; + double timer_accumulator; + double timer_cycle_time; + std::string rom; bool running; void execute_interpreter(); + void update_timers(); + void on_rom_load(const std::string& path); void on_reset() const; void on_stop(); diff --git a/src/Sound.cpp b/src/Sound.cpp new file mode 100644 index 0000000..736858b --- /dev/null +++ b/src/Sound.cpp @@ -0,0 +1,88 @@ +#include "Sound.h" + +#include + +#include "SDL3/SDL.h" + +void SDLAudioStreamDestroyer::operator()(SDL_AudioStream* stream) const +{ + SDL_DestroyAudioStream(stream); +} + +Sound::Sound() +{ + spec.channels = 1; + spec.format = SDL_AUDIO_F32; + spec.freq = 8000; + + SDL_AudioStream* raw_stream = SDL_OpenAudioDeviceStream(SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK, &spec, nullptr, nullptr); + if (!raw_stream) + { + throw std::runtime_error(std::format("Couldn't initialize audio stream: {}", SDL_GetError())); + } + + stream = std::unique_ptr(raw_stream); + + + SDL_ResumeAudioStreamDevice(stream.get()); +} + +void Sound::sine() +{ + constexpr int minimum_audio = (8000 * sizeof(float)) / 6; + constexpr int freq = 440; + + if (SDL_GetAudioStreamQueued(stream.get()) < minimum_audio) + { + static float samples[512]; + for (std::size_t i = 0; i < std::size(samples); i++) + { + const float phase = current_sine_sample * freq / 8000.0f; + samples[i] = SDL_sinf(phase * 2 * SDL_PI_F); + current_sine_sample++; + } + + current_sine_sample %= 8000; + + SDL_PutAudioStreamData(stream.get(), samples, sizeof (samples)); + } +} + +void Sound::noise() +{ + constexpr int minimum_audio = (8000 * sizeof(float)) / 6; + constexpr int freq = 440; + if (SDL_GetAudioStreamQueued(stream.get()) < minimum_audio) + { + static float samples[512]; + + for (std::size_t i = 0; i < std::size(samples); i++) + { + samples[i] = SDL_rand(2) * freq; + } + + SDL_PutAudioStreamData(stream.get(), samples, sizeof (samples)); + } +} + +void Sound::square() +{ + constexpr int minimum_audio = (8000 * sizeof(float)) / 6; + constexpr int freq = 440; + constexpr float amplitude = 0.5f; + + if (SDL_GetAudioStreamQueued(stream.get()) < minimum_audio) + { + static float samples[512]; + for (std::size_t i = 0; i < std::size(samples); i++) + { + const float phase = current_sine_sample * freq / 8000.0f; + samples[i] = (SDL_sinf(phase * 2 * SDL_PI_F) >= 0.0f) ? amplitude : -amplitude; + current_sine_sample++; + } + + current_sine_sample %= 8000; + + SDL_PutAudioStreamData(stream.get(), samples, sizeof(samples)); + } +} diff --git a/src/Sound.h b/src/Sound.h new file mode 100644 index 0000000..b04277e --- /dev/null +++ b/src/Sound.h @@ -0,0 +1,28 @@ +#ifndef SOUND_H +#define SOUND_H +#include +#include + + +struct SDLAudioStreamDestroyer +{ + void operator()(SDL_AudioStream* stream) const; +}; + +class Sound +{ + SDL_AudioSpec spec; + std::unique_ptr stream; + + int current_sine_sample = 0; + +public: + Sound(); + + void sine(); + void noise(); + void square(); +}; + + +#endif //SOUND_H