Browse Source

CHIP-8 emulation.

* Passes all of the tests I've run against it and plays a bunch of games!
    * Kind of a bad sound implementation, but it's good enough for CHIP-8. ;)
master
Ian Burgmyer 4 years ago
parent
commit
212e234460
  1. 5
      CMakeLists.txt
  2. 1
      libplip/Audio/PlipAudio.h
  3. 100
      libplip/Core/Chip8/Chip8Instance.cpp
  4. 26
      libplip/Core/Chip8/Chip8Instance.h
  5. 4
      libplip/Core/PlipCore.h
  6. 264
      libplip/Cpu/Chip8/Chip8.Ops.cpp
  7. 113
      libplip/Cpu/Chip8/Chip8.cpp
  8. 79
      libplip/Cpu/Chip8/Chip8.h
  9. 26
      libplip/Cpu/PlipCpu.cpp
  10. 31
      libplip/Cpu/PlipCpu.h
  11. 17
      libplip/PlipEmulationException.h
  12. 25
      libplip/PlipUtility.cpp
  13. 14
      libplip/PlipUtility.h
  14. 10
      plip-sdl/SDL/SdlAudio.cpp
  15. 1
      plip-sdl/SDL/SdlAudio.h
  16. 26
      plip-sdl/main.cpp

5
CMakeLists.txt

@ -34,10 +34,15 @@ add_custom_target(
add_library(${lib_name}
libplip/PlipInstance.cpp
libplip/PlipIo.cpp
libplip/PlipUtility.cpp
libplip/Core/PlipCore.cpp
libplip/Core/Chip8/Chip8Instance.cpp
libplip/Cpu/PlipCpu.cpp
libplip/Cpu/Chip8/Chip8.cpp
libplip/Cpu/Chip8/Chip8.Ops.cpp
libplip/Input/PlipInput.cpp
libplip/Input/PlipInputDefinition.cpp

1
libplip/Audio/PlipAudio.h

@ -12,6 +12,7 @@ namespace Plip {
public:
virtual void DequeueAll() = 0;
virtual void Enqueue(std::vector<float> buffer) = 0;
virtual int GetBufferSize() = 0;
virtual uintmax_t GetQueueSize() = 0;
virtual bool IsActive() final { return m_active; }

100
libplip/Core/Chip8/Chip8Instance.cpp

@ -5,14 +5,18 @@
* (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_memoryMap->AddBlock(m_ram);
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 });
@ -30,9 +34,97 @@ namespace Plip::Core::Chip8 {
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) {
@ -44,13 +136,13 @@ namespace Plip::Core::Chip8 {
// Zero RAM.
for(auto i = 0; i < RamSize; i++)
m_memoryMap->SetByte(i, 0x00);
m_memory->SetByte(i, 0x00);
// Load program.
auto ramByte = ProgramOffset;
auto dataByte = 0;
while(dataByte < size && ramByte < RamSize)
m_memoryMap->SetByte(ramByte++, data[dataByte++]);
m_memory->SetByte(ramByte++, data[dataByte++]);
// Write character set.
WriteCharacterSet(CharacterSet);
@ -60,6 +152,6 @@ namespace Plip::Core::Chip8 {
void Chip8Instance::WriteCharacterSet(uint32_t address) {
for(auto i = 0; i < m_charsetLength; i++)
m_memoryMap->SetByte(address + i, m_charset[i]);
m_memory->SetByte(address + i, m_charset[i]);
}
}

26
libplip/Core/Chip8/Chip8Instance.h

@ -12,6 +12,7 @@
#include "../PlipCore.h"
#include "../../PlipError.h"
#include "../../PlipInstance.h"
#include "../../Cpu/Chip8/Chip8.h"
#include "../../Input/PlipInput.h"
#include "../../Memory/PlipMemoryRam.h"
@ -19,22 +20,43 @@ namespace Plip::Core::Chip8 {
class Chip8Instance : public PlipCore {
public:
Chip8Instance(PlipAudio *audio, PlipInput *input, PlipVideo *video);
~Chip8Instance();
void Delta(long ns) override;
PlipError Load(const std::string &path) override;
static const uint32_t ClockRate = 500;
static constexpr long ClockTime = (1000 / ClockRate) * 1000000;
static const uint32_t CharacterSet = 0x000;
static const uint32_t CharacterSet = 0x100;
static const long DelayTimerTick = 16666666;
static const uint32_t ProgramOffset = 0x200;
static const uint32_t RamSize = 0x1000;
static const int SineHz = 440;
static constexpr double SineVolume = 0.25;
private:
double m_angle = 0.0;
double m_delta;
int m_channels;
int m_sampleCount;
int m_sampleRate;
Cpu::Chip8 *m_cpu;
long m_cycleRemaining = 0;
long m_delayRemaining = DelayTimerTick;
long m_cycleTime = 0;
PlipMemoryRam *m_ram;
std::unordered_map<int, PlipInputDefinition> m_inputList;
Plip::PlipVideoFormatInfo m_videoFormat {};
void *m_videoOutput;
void Draw();
std::vector<float> GenerateSilence();
std::vector<float> GenerateSine();
void WriteCharacterSet(uint32_t address);
static const int ScreenWidth = 64;
static const int ScreenHeight = 32;
static constexpr uint8_t m_charset[] {
0xF0, 0x90, 0x90, 0x90, 0xF0, // 0
0x20, 0x60, 0x20, 0x20, 0x70, // 1

4
libplip/Core/PlipCore.h

@ -31,7 +31,7 @@ namespace Plip {
static std::vector<PlipCoreDescription> GetSupportedCores();
virtual void Delta(long ns) = 0;
virtual PlipMemoryMap* GetMemoryMap() final { return m_memoryMap; }
virtual PlipMemoryMap* GetMemoryMap() final { return m_memory; }
virtual PlipError Load(const std::string &path) = 0;
protected:
@ -41,7 +41,7 @@ namespace Plip {
PlipInput *m_input;
PlipVideo *m_video;
PlipMemoryMap *m_memoryMap = new PlipMemoryMap();
PlipMemoryMap *m_memory = new PlipMemoryMap();
private:
static constexpr PlipCoreDescription m_supportedCores[] = {

264
libplip/Cpu/Chip8/Chip8.Ops.cpp

@ -0,0 +1,264 @@
/* Chip8.Ops.cpp
*
* CHIP-8 opcodes.
*/
#include <iomanip>
#include <sstream>
#include "../../PlipEmulationException.h"
#include "Chip8.h"
namespace Plip::Cpu {
void Chip8::Op0NNN(uint16_t address) {
// RCA 1802 routine. Not implemented.
}
void Chip8::Op00E0() {
// Clears the screen.
for(auto i = 0; i < VideoSize; i++)
m_videoBuffer[i] = 0;
}
void Chip8::Op00EE() {
// Returns from a subroutine.
if(m_sp == 0) {
std::stringstream ex;
ex << "stack underflow\n\n" << DumpRegisters();
throw PlipEmulationException(ex.str().c_str());
}
m_pc = m_stack[--m_sp];
}
void Chip8::Op1NNN(uint16_t address) {
// Jumps to an address.
m_pc = address;
}
void Chip8::Op2NNN(uint16_t address) {
// Calls a subroutine.
if(m_sp >= StackSize) {
std::stringstream ex;
ex << "stack overflow\n\n" << DumpRegisters();
throw PlipEmulationException(ex.str().c_str());
}
m_stack[m_sp++] = m_pc;
m_pc = address;
}
void Chip8::Op3XNN(uint8_t reg, uint8_t value) {
// Skips the next instruction if reg == value.
if(m_reg[reg] == value) m_pc += 2;
}
void Chip8::Op4XNN(uint8_t reg, uint8_t value) {
// Skips the next instruction if reg != value.
if(m_reg[reg] != value) m_pc += 2;
}
void Chip8::Op5XY0(uint8_t left, uint8_t right) {
// Skips the next instruction if left == right.
if(m_reg[left] == m_reg[right]) m_pc += 2;
}
void Chip8::Op6XNN(uint8_t reg, uint8_t value) {
// Sets reg to value.
m_reg[reg] = value;
}
void Chip8::Op7XNN(uint8_t reg, uint8_t value) {
// Adds value to reg.
m_reg[reg] += value;
}
void Chip8::Op8XYO(uint8_t left, uint8_t right, uint8_t op) {
uint8_t old;
// Various bitwise and math operations.
switch(op) {
case 0x0:
// left = right
m_reg[left] = m_reg[right];
break;
case 0x1:
// left |= right
m_reg[left] |= m_reg[right];
break;
case 0x2:
// left &= right
m_reg[left] &= m_reg[right];
break;
case 0x3:
// left ^= right
m_reg[left] ^= m_reg[right];
break;
case 0x4:
// left += right, VF = 1 on carry, otherwise 0
old = m_reg[left];
m_reg[left] += m_reg[right];
m_reg[0xF] = old > m_reg[left] ? 1 : 0;
break;
case 0x5:
// left -= right, VF = 1 on borrow, otherwise 0
old = m_reg[left];
m_reg[left] -= m_reg[right];
m_reg[0xF] = old < m_reg[left] ? 0 : 1;
break;
case 0x6:
// left >>= 1, VF = LSB of right before shift
m_reg[0xF] = m_reg[left] & 0x1;
m_reg[left] >>= 1;
break;
case 0x7:
// left = right - left, VF = 1 on borrow, otherwise 0
m_reg[left] = m_reg[right] - m_reg[left];
m_reg[0xF] = m_reg[right] < m_reg[left] ? 0 : 1;
break;
case 0xE:
// left <<= 1, VF = MSB of right before shift
m_reg[0xF] = (m_reg[left] & 0x80) > 0 ? 1 : 0;
m_reg[left] <<= 1;
break;
default: break;
}
}
void Chip8::Op9XY0(uint8_t left, uint8_t right) {
// Skips the next instruction if left != right.
if(m_reg[left] != m_reg[right]) m_pc += 2;
}
void Chip8::OpANNN(uint16_t address) {
// Sets i to address.
m_i = address;
}
void Chip8::OpBNNN(uint16_t address) {
// Jumps to address + V0.
m_pc = address + m_reg[0];
}
void Chip8::OpCXNN(uint8_t reg, uint8_t value) {
// Sets reg to random(0-255) & value.
std::uniform_int_distribution<int> num(0, 255);
m_reg[reg] = num(m_rng) & value;
}
void Chip8::OpDXYN(uint8_t xReg, uint8_t yReg, uint8_t size) {
// Draws an (8 x size) sprite at address I to (xReg, yReg).
auto sprite = new uint64_t[size];
auto x = m_reg[xReg];
auto y = m_reg[yReg];
for(auto i = 0; i < size; i++) {
// Push sprites all the way to the highest bits, then
// bump them to the right as much as necessary.
sprite[i] = ((uint64_t)m_memory->GetByte(m_i + i) << 56) >> x;
}
m_reg[0xF] = 0;
for(auto i = 0; i < size; i++) {
if(y + i >= VideoSize) return; // Don't attempt to write past maxY.
uint64_t oldRow = m_videoBuffer[y + i];
m_videoBuffer[y + i] ^= sprite[i];
// Invert the old row, OR it with the new row, and invert the result.
// If it's greater than 0, a bit has been turned off.
oldRow = ~oldRow;
if(~(oldRow | m_videoBuffer[y + i]) > 0)
m_reg[0xF] = 1;
}
}
void Chip8::OpEXOO(uint8_t reg, uint8_t op) {
// Input functions.
switch(op) {
case 0x9E:
// Skips the next instruction if the key in reg is pressed.
if(m_input->GetInput(m_reg[reg]).digital) m_pc += 2;
break;
case 0xA1:
// Skips the next instruction if the key in reg is released.
if(!m_input->GetInput(m_reg[reg]).digital) m_pc += 2;
break;
default: break;
}
}
void Chip8::OpFXOO(uint8_t reg, uint8_t op) {
std::stringstream ss;
std::string str;
// Various other instructions.
switch(op) {
case 0x07:
// Store the value of the delay timer in reg.
m_reg[reg] = m_timerDelay;
break;
case 0x0A:
// Wait for a keypress and store the result in reg.
m_waitForKey = true;
m_keyRegister = reg;
break;
case 0x15:
// Set the delay timer to reg.
m_timerDelay = m_reg[reg];
break;
case 0x18:
// Set the audio timer to reg.
m_timerAudio = m_reg[reg];
break;
case 0x1E:
// Add reg to I.
m_i += m_reg[reg];
break;
case 0x29:
// Set I to the address of the charset value in reg.
m_i = m_charsetAddress + (m_reg[reg] * 5);
break;
case 0x33:
// Stores reg as a binary-coded decimal, starting at I.
ss << std::setfill('0') << std::setw(3) << std::to_string(m_reg[reg]);
str = ss.str();
for(auto i = 0; i < 3; i++)
m_memory->SetByte(m_i + i, str[i] - '0');
break;
case 0x55:
// Stores the values of V0 to reg, inclusive, in I and up.
for(auto i = 0; i < reg + 1; i++)
m_memory->SetByte(m_i + i, m_reg[i]);
break;
case 0x65:
// Fills registers V0 to reg, inclusive, with the values in
// memory starting with I.
for(auto i = 0; i < reg + 1; i++)
m_reg[i] = m_memory->GetByte(m_i + i);
break;
default: break;
}
}
}

113
libplip/Cpu/Chip8/Chip8.cpp

@ -0,0 +1,113 @@
/* Chip8.cpp
*
* An implementation of a CHIP-8 CPU.
*/
#include <iomanip>
#include <sstream>
#include "Chip8.h"
#include "../../PlipUtility.h"
namespace Plip::Cpu {
Chip8::Chip8(long hz, PlipMemoryMap *memoryMap, uint16_t charset, Plip::PlipInput *input)
: PlipCpu(hz, memoryMap) {
m_charsetAddress = charset;
m_input = input;
m_videoBuffer = new uint64_t[VideoSize] {};
}
void Chip8::Cycle() {
auto inst = Fetch();
auto left = GetReg1(inst);
auto right = GetReg2(inst);
auto val = GetValue(inst);
auto addr = GetAddress(inst);
if(m_waitForKey) {
for(uint8_t i = 0; i < 0x10; i++) {
if(!m_input->GetInput(i).digital) continue;
m_waitForKey = false;
m_reg[m_keyRegister] = i;
break;
}
}
if(m_waitForKey) return;
switch(inst & 0xF000) {
case 0x0000:
if(val == 0xE0)
Op00E0();
else if(val == 0xEE)
Op00EE();
else
Op0NNN(addr);
break;
case 0x1000: Op1NNN(addr); break;
case 0x2000: Op2NNN(addr); break;
case 0x3000: Op3XNN(left, val); break;
case 0x4000: Op4XNN(left, val); break;
case 0x5000: Op5XY0(left, right); break;
case 0x6000: Op6XNN(left, val); break;
case 0x7000: Op7XNN(left, val); break;
case 0x8000: Op8XYO(left, right, inst & 0xF); break;
case 0x9000: Op9XY0(left, right); break;
case 0xA000: OpANNN(addr); break;
case 0xB000: OpBNNN(addr); break;
case 0xC000: OpCXNN(left, val); break;
case 0xD000: OpDXYN(left, right, inst & 0xF); break;
case 0xE000: OpEXOO(left, val); break;
case 0xF000: OpFXOO(left, val); break;
}
}
void Chip8::DelayTimer() {
if(m_timerAudio > 0) m_timerAudio--;
if(m_timerDelay > 0) m_timerDelay--;
}
std::string Chip8::DumpRegisters() {
using util = Plip::PlipUtility;
const char *regLabel = "0123456789ABCDEF";
std::stringstream dump;
dump << util::DumpValue(" PC", m_pc, 3) << '\n'
<< util::DumpValue(" SP", m_sp, StackSize > 16 ? 2 : 1) << "\n\n"
<< util::DumpValue("Audio", m_timerAudio, 2) << '\n'
<< util::DumpValue("Delay", m_timerDelay, 2) << "\n\n"
<< util::DumpValue(" I", m_i, 4) << '\n';
for(auto i = 0; i < 16; i++) {
std::stringstream label;
label << " V" << regLabel[i];
dump << util::DumpValue(label.str(), m_reg[i], 2) << '\n';
}
dump << "\n\tStack: ";
for(auto i = 0; i < StackSize; i++) {
if(i == m_sp) dump << "[";
dump << util::FormatHex(m_stack[i], 3);
if(i == m_sp) dump << "]";
dump << ' ';
}
return dump.str();
}
void Chip8::Reset(uint32_t pc) {
m_timerAudio = 0;
m_timerDelay = 0;
m_sp = 0;
m_pc = pc;
m_i = 0;
for(auto &reg : m_reg)
reg = 0;
for(auto &stack : m_stack)
stack = 0;
}
}

79
libplip/Cpu/Chip8/Chip8.h

@ -0,0 +1,79 @@
/* Chip8.h
*
* An implementation of a CHIP-8 CPU.
*/
#pragma once
#include <random>
#include "../PlipCpu.h"
#include "../../Input/PlipInput.h"
#include "../../Video/PlipVideo.h"
namespace Plip::Cpu {
class Chip8 : public PlipCpu {
public:
Chip8(long hz, PlipMemoryMap* memoryMap, uint16_t charset, Plip::PlipInput *input);
void Cycle() override;
void DelayTimer();
uint64_t* GetVideo() { return m_videoBuffer; }
[[nodiscard]] bool IsAudioPlaying() const { return m_timerAudio >= 2; }
void Reset(uint32_t pc) override;
static const int VideoSize = 32; // 64 x 32
private:
std::string DumpRegisters();
inline uint16_t Fetch() {
uint8_t high = m_memory->GetByte(m_pc++);
uint8_t low = m_memory->GetByte(m_pc++);
return (high << 8) + low;
}
static inline uint16_t GetAddress(uint16_t b) { return b & 0xFFF; }
static inline uint8_t GetReg1(uint16_t b) { return (b >> 8) & 0xF; }
static inline uint8_t GetReg2(uint16_t b) { return (b >> 4) & 0xF; }
static inline uint8_t GetValue(uint16_t b) { return b & 0xFF; }
void Op0NNN(uint16_t address);
void Op00E0();
void Op00EE();
void Op1NNN(uint16_t address);
void Op2NNN(uint16_t address);
void Op3XNN(uint8_t reg, uint8_t value);
void Op4XNN(uint8_t reg, uint8_t value);
void Op5XY0(uint8_t left, uint8_t right);
void Op6XNN(uint8_t reg, uint8_t value);
void Op7XNN(uint8_t reg, uint8_t value);
void Op8XYO(uint8_t left, uint8_t right, uint8_t op);
void Op9XY0(uint8_t left, uint8_t right);
void OpANNN(uint16_t address);
void OpBNNN(uint16_t address);
void OpCXNN(uint8_t reg, uint8_t value);
void OpDXYN(uint8_t xReg, uint8_t yReg, uint8_t size);
void OpEXOO(uint8_t reg, uint8_t op);
void OpFXOO(uint8_t reg, uint8_t op);
static const int StackSize = 12;
std::default_random_engine m_rng;
Plip::PlipInput *m_input;
uint64_t *m_videoBuffer;
uint16_t m_charsetAddress;
uint8_t m_timerAudio = 0;
uint8_t m_timerDelay = 0;
uint16_t m_sp = 0;
uint16_t m_pc = 0;
uint16_t m_i = 0;
uint8_t m_reg[16] {};
uint16_t m_stack[StackSize] {};
bool m_waitForKey = false;
uint8_t m_keyRegister = 0;
};
}

26
libplip/Cpu/PlipCpu.cpp

@ -0,0 +1,26 @@
/* PlipCpu.cpp
*
* Defines a CPU implementation.
*/
#include "PlipCpu.h"
namespace Plip::Cpu {
PlipCpu::PlipCpu(long hz, PlipMemoryMap* memoryMap) {
SetHz(hz);
m_memory = memoryMap;
}
long PlipCpu::GetCycleTime() const {
return m_cycle;
}
long PlipCpu::GetHz(long hz) const {
return m_hz;
}
void PlipCpu::SetHz(long hz) {
m_hz = hz;
m_cycle = 1000000000 / hz;
}
}

31
libplip/Cpu/PlipCpu.h

@ -0,0 +1,31 @@
/* PlipCpu.h
*
* Defines a CPU implementation.
*/
#pragma once
#include <string>
#include "../Memory/PlipMemoryMap.h"
namespace Plip::Cpu {
class PlipCpu {
public:
[[nodiscard]] long GetCycleTime() const;
[[nodiscard]] long GetHz(long hz) const;
void SetHz(long hz);
virtual void Cycle() = 0;
virtual void Reset(uint32_t pc) = 0;
protected:
PlipCpu(long hz, PlipMemoryMap* memoryMap);
long m_hz {};
Plip::PlipMemoryMap *m_memory;
private:
long m_cycle {};
};
}

17
libplip/PlipEmulationException.h

@ -0,0 +1,17 @@
/* PlipEmulationException.h
*
* An exception that may occur during emulation.
*/
#pragma once
#include <stdexcept>
#include <string>
#include <utility>
namespace Plip {
struct PlipEmulationException : std::runtime_error {
explicit PlipEmulationException(const char *message)
: std::runtime_error(message) {}
};
}

25
libplip/PlipUtility.cpp

@ -0,0 +1,25 @@
/* PlipUtility.cpp
*
* Miscellaneous helper functions.
*/
#include <iomanip>
#include <sstream>
#include "PlipUtility.h"
namespace Plip {
std::string PlipUtility::DumpValue(const std::string &label, uintmax_t value, int precision) {
std::stringstream dump;
dump << '\t' << label << ": " << FormatHex(value, precision);
return dump.str();
}
std::string PlipUtility::FormatHex(uintmax_t value, int precision) {
std::stringstream fmt;
fmt << "0x" << std::uppercase << std::setfill('0') << std::setw(precision)
<< std::hex << value;
return fmt.str();
}
}

14
libplip/PlipUtility.h

@ -0,0 +1,14 @@
/* PlipUtility.h
*
* Miscellaneous helper functions.
*/
#include <string>
namespace Plip {
class PlipUtility {
public:
static std::string DumpValue(const std::string &label, uintmax_t value, int precision);
static std::string FormatHex(uintmax_t value, int precision);
};
}

10
plip-sdl/SDL/SdlAudio.cpp

@ -14,9 +14,9 @@ namespace PlipSdl {
// Open audio device.
SDL_AudioSpec want {};
want.freq = 8000; // Plip::PlipAudio::SampleRate;
want.format = AUDIO_S8;
want.channels = 1; // Plip::PlipAudio::Channels;
want.freq = Plip::PlipAudio::SampleRate;
want.format = AUDIO_F32;
want.channels = Plip::PlipAudio::Channels;
want.samples = SampleLength;
want.callback = nullptr;
@ -81,6 +81,10 @@ namespace PlipSdl {
(this->*m_playFunc)(buffer.data(), buffer.size());
}
int SdlAudio::GetBufferSize() {
return m_spec.samples;
}
uintmax_t SdlAudio::GetQueueSize() {
return SDL_GetQueuedAudioSize(m_device);
}

1
plip-sdl/SDL/SdlAudio.h

@ -17,6 +17,7 @@ namespace PlipSdl {
void DequeueAll() override;
void Enqueue(std::vector<float> buffer) override;
int GetBufferSize() override;
uintmax_t GetQueueSize() override;
static const int SampleLength = 4096;

26
plip-sdl/main.cpp

@ -31,29 +31,6 @@ std::vector<std::vector<std::string>> intParamMapping = {
{ "fps" , "video", "targetFps" }
};
#include <cmath>
double angle = 0.0;
std::vector<float> waveGen() {
using pa = Plip::PlipAudio;
auto vol = 0.5;
auto len = PlipSdl::SdlAudio::SampleLength * 8;
auto hz = 440;
auto smp = pa::SampleRate;
auto chan = pa::Channels;
std::vector<float> res;
auto cycles = (double)hz / (double)smp;
auto delta = cycles * 2.0 * M_PI;
for(auto i = 0; i < len; i++) {
for(auto c = 0; c < chan; c++)
res.push_back(vol * std::sin(angle));
angle += delta;
}
return res;
}
void gameLoop(Plip::PlipInstance *plip, PlipSdl::Config *config, PlipSdl::SdlEvent *event, PlipSdl::Timer *timer) {
auto audio = plip->GetAudio();
auto video = plip->GetVideo();
@ -73,9 +50,6 @@ void gameLoop(Plip::PlipInstance *plip, PlipSdl::Config *config, PlipSdl::SdlEve
// TODO: Fix this so that it will skip frames where appropriate.
plip->Run(frameTime);
if(audio->GetQueueSize() < PlipSdl::SdlAudio::SampleLength / 2)
audio->Enqueue(waveGen());
auto time = timer->StopwatchStop();
auto delay = frameTime - time;
while(delay < 0)

Loading…
Cancel
Save