You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
157 lines
5.6 KiB
157 lines
5.6 KiB
/* Chip8Instance.cpp |
|
* |
|
* A CHIP-8 implementation for Plip. |
|
* |
|
* (hahaha, more like PLIP-8 amirite) |
|
*/ |
|
|
|
#include <cmath> |
|
|
|
#include "Chip8Instance.h" |
|
#include "../../PlipIo.h" |
|
|
|
namespace Plip::Core::Chip8 { |
|
Chip8Instance::Chip8Instance(PlipAudio *audio, PlipInput *input, PlipVideo *video) |
|
: Plip::PlipCore(audio, input, video) { |
|
using pa = Plip::PlipAudio; |
|
|
|
m_ram = new PlipMemoryRam(RamSize); |
|
m_memory->AddBlock(m_ram); |
|
|
|
m_input->AddInput(0x0, PlipInputDefinition(PlipInputType::Digital, "0"), { .digital = false }); |
|
m_input->AddInput(0x1, PlipInputDefinition(PlipInputType::Digital, "1"), { .digital = false }); |
|
m_input->AddInput(0x2, PlipInputDefinition(PlipInputType::Digital, "2"), { .digital = false }); |
|
m_input->AddInput(0x3, PlipInputDefinition(PlipInputType::Digital, "3"), { .digital = false }); |
|
m_input->AddInput(0x4, PlipInputDefinition(PlipInputType::Digital, "4"), { .digital = false }); |
|
m_input->AddInput(0x5, PlipInputDefinition(PlipInputType::Digital, "5"), { .digital = false }); |
|
m_input->AddInput(0x6, PlipInputDefinition(PlipInputType::Digital, "6"), { .digital = false }); |
|
m_input->AddInput(0x7, PlipInputDefinition(PlipInputType::Digital, "7"), { .digital = false }); |
|
m_input->AddInput(0x8, PlipInputDefinition(PlipInputType::Digital, "8"), { .digital = false }); |
|
m_input->AddInput(0x9, PlipInputDefinition(PlipInputType::Digital, "9"), { .digital = false }); |
|
m_input->AddInput(0xA, PlipInputDefinition(PlipInputType::Digital, "A"), { .digital = false }); |
|
m_input->AddInput(0xB, PlipInputDefinition(PlipInputType::Digital, "B"), { .digital = false }); |
|
m_input->AddInput(0xC, PlipInputDefinition(PlipInputType::Digital, "C"), { .digital = false }); |
|
m_input->AddInput(0xD, PlipInputDefinition(PlipInputType::Digital, "D"), { .digital = false }); |
|
m_input->AddInput(0xE, PlipInputDefinition(PlipInputType::Digital, "E"), { .digital = false }); |
|
m_input->AddInput(0xF, PlipInputDefinition(PlipInputType::Digital, "F"), { .digital = false }); |
|
|
|
m_video->Resize(ScreenWidth, ScreenHeight); |
|
m_videoFormat = Plip::PlipVideo::GetFormatInfo(m_video->GetFormat()); |
|
m_videoOutput = malloc(ScreenWidth * ScreenHeight * m_videoFormat.pixelWidth); |
|
|
|
m_cpu = new Cpu::Chip8(ClockRate, m_memory, CharacterSet, m_input); |
|
m_cpu->Reset(0x200); |
|
|
|
m_cycleTime = m_cpu->GetCycleTime(); |
|
|
|
m_channels = pa::Channels; |
|
m_sampleRate = pa::SampleRate; |
|
m_sampleCount = m_audio->GetBufferSize(); |
|
|
|
auto cycles = (double)SineHz / (double)m_sampleRate; |
|
m_delta = cycles * 2.0 * M_PI; |
|
} |
|
|
|
Chip8Instance::~Chip8Instance() { |
|
free(m_videoOutput); |
|
} |
|
|
|
void Chip8Instance::Delta(long ns) { |
|
m_cycleRemaining += ns; |
|
|
|
do { |
|
m_cpu->Cycle(); |
|
m_cycleRemaining -= m_cycleTime; |
|
|
|
if(m_audio->GetQueueSize() < 1024) { |
|
// This is a pretty lame implementation, but it should be good |
|
// enough for this. |
|
if(m_cpu->IsAudioPlaying()) { |
|
m_audio->Enqueue(GenerateSine()); |
|
} else { |
|
m_audio->Enqueue(GenerateSilence()); |
|
} |
|
} |
|
|
|
m_delayRemaining -= m_cycleTime; |
|
if(m_delayRemaining <= 0) { |
|
m_cpu->DelayTimer(); |
|
m_delayRemaining += DelayTimerTick; |
|
} |
|
} while(m_cycleTime < m_cycleRemaining); |
|
|
|
Draw(); |
|
} |
|
|
|
void Chip8Instance::Draw() { |
|
auto buffer = m_cpu->GetVideo(); |
|
|
|
for(auto y = 0; y < ScreenHeight; y++) { |
|
auto row = buffer[y]; |
|
for(auto x = 0; x < ScreenWidth; x++) { |
|
auto bit = row >> (63 - x) & 0x1; |
|
m_videoFormat.plot(m_videoOutput, y * ScreenWidth + x, |
|
bit * 255, bit * 255, bit * 255); |
|
} |
|
} |
|
|
|
m_video->BeginDraw(); |
|
m_video->EndDraw(); |
|
m_video->Draw(m_videoOutput); |
|
m_video->Render(); |
|
} |
|
|
|
std::vector<float> Chip8Instance::GenerateSilence() { |
|
using pa = Plip::PlipAudio; |
|
|
|
std::vector<float> res; |
|
for(auto i = 0; i < m_sampleCount; i++) { |
|
for(auto c = 0; c < m_channels; c++) |
|
res.push_back(0); |
|
m_angle += m_delta; // Keep the wave generator going to prevent clicks. |
|
} |
|
|
|
return res; |
|
} |
|
|
|
std::vector<float> Chip8Instance::GenerateSine() { |
|
using pa = Plip::PlipAudio; |
|
|
|
std::vector<float> res; |
|
for(auto i = 0; i < m_sampleCount; i++) { |
|
for(auto c = 0; c < m_channels; c++) |
|
res.push_back(SineVolume * std::sin(m_angle)); |
|
m_angle += m_delta; |
|
} |
|
|
|
return res; |
|
} |
|
|
|
PlipError Chip8Instance::Load(const std::string &path) { |
|
using io = Plip::PlipIo; |
|
if(!io::FileExists(path)) return PlipError::FileNotFound; |
|
|
|
auto size = io::GetSize(path); |
|
auto data = io::ReadFile(path, size); |
|
|
|
// Zero RAM. |
|
for(auto i = 0; i < RamSize; i++) |
|
m_memory->SetByte(i, 0x00); |
|
|
|
// Load program. |
|
auto ramByte = ProgramOffset; |
|
auto dataByte = 0; |
|
while(dataByte < size && ramByte < RamSize) |
|
m_memory->SetByte(ramByte++, data[dataByte++]); |
|
|
|
// Write character set. |
|
WriteCharacterSet(CharacterSet); |
|
|
|
return PlipError::Success; |
|
} |
|
|
|
void Chip8Instance::WriteCharacterSet(uint32_t address) { |
|
for(auto i = 0; i < m_charsetLength; i++) |
|
m_memory->SetByte(address + i, m_charset[i]); |
|
} |
|
}
|
|
|