diff --git a/CMakeLists.txt b/CMakeLists.txt index 9f72bdfc..12bc768c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -66,6 +66,7 @@ src/engine/platform/sms.cpp src/engine/platform/gb.cpp src/engine/platform/pce.cpp src/engine/platform/nes.cpp +src/engine/platform/c64.cpp src/engine/platform/dummy.cpp) #imgui/imgui.cpp diff --git a/README.md b/README.md index 3985c619..a86e2348 100644 --- a/README.md +++ b/README.md @@ -1,15 +1,13 @@ # Furnace -did i say prepare? - this is a work-in-progress chip music player (currently) for the .dmf format. ## features -- supports Sega Genesis, Master System, Game Boy, PC Engine and NES (for now, with more systems coming soon) +- supports Sega Genesis, Master System, Game Boy, PC Engine, NES and C64 (for now, with more systems coming soon) - clean-room design (zero reverse-engineered code and zero decompilation; using official DMF specs, guesswork and ABX tests only) - bug/quirk implementation for increased playback accuracy -- accurate emulation cores (Nuked, MAME, SameBoy, Mednafen PCE and puNES) +- accurate emulation cores (Nuked, MAME, SameBoy, Mednafen PCE, puNES and reSID (hahaha!)) - open-source. GPLv2. ## dependencies diff --git a/src/engine/engine.cpp b/src/engine/engine.cpp index 26052436..360d5d5b 100644 --- a/src/engine/engine.cpp +++ b/src/engine/engine.cpp @@ -11,6 +11,7 @@ #include "platform/gb.h" #include "platform/pce.h" #include "platform/nes.h" +#include "platform/c64.h" #include "platform/dummy.h" #include #include @@ -760,6 +761,14 @@ bool DivEngine::init() { case DIV_SYSTEM_NES: dispatch=new DivPlatformNES; break; + case DIV_SYSTEM_C64_6581: + dispatch=new DivPlatformC64; + ((DivPlatformC64*)dispatch)->setChipModel(true); + break; + case DIV_SYSTEM_C64_8580: + dispatch=new DivPlatformC64; + ((DivPlatformC64*)dispatch)->setChipModel(false); + break; default: logW("this system is not supported yet! using dummy platform.\n"); dispatch=new DivPlatformDummy; diff --git a/src/engine/platform/c64.cpp b/src/engine/platform/c64.cpp new file mode 100644 index 00000000..5ab17b43 --- /dev/null +++ b/src/engine/platform/c64.cpp @@ -0,0 +1,239 @@ +#include "c64.h" +#include "../engine.h" +#include + +#define FREQ_BASE 277.0f + +void DivPlatformC64::acquire(int& l, int& r) { + sid.clock(); + l=sid.output(); + r=l; +} + +static unsigned char noiseTable[256]={ + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 5, 4, + 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, + 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, + 3, 2, 1, 0, 11, 10, 9, 8, 7, 6, 5, 4, + 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, + 3, 2, 1, 0, 11, 10, 9, 8, 7, 6, 5, 4, + 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, + 3, 2, 1, 0, 11, 10, 9, 8, 7, 6, 5, 4, + 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, + 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, + 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, + 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, + 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, + 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, + 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, + 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, + 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, + 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, + 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, + 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, + 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, + 15 +}; + +void DivPlatformC64::tick() { + for (int i=0; i<3; i++) { + chan[i].std.next(); + if (chan[i].std.hadVol) { + // ok, why are the volumes like that? + chan[i].outVol=chan[i].std.vol-(15-chan[i].vol); + if (chan[i].outVol<0) chan[i].outVol=0; + } + if (chan[i].std.hadArp) { + if (i==3) { // noise + if (chan[i].std.arpMode) { + chan[i].baseFreq=chan[i].std.arp; + } else { + chan[i].baseFreq=chan[i].note+chan[i].std.arp-12; + } + if (chan[i].baseFreq>255) chan[i].baseFreq=255; + if (chan[i].baseFreq<0) chan[i].baseFreq=0; + } else { + if (!chan[i].inPorta) { + if (chan[i].std.arpMode) { + chan[i].baseFreq=round(FREQ_BASE*pow(2.0f,((float)(chan[i].std.arp)/12.0f))); + } else { + chan[i].baseFreq=round(FREQ_BASE*pow(2.0f,((float)(chan[i].note+chan[i].std.arp-12)/12.0f))); + } + } + } + chan[i].freqChanged=true; + } else { + if (chan[i].std.arpMode && chan[i].std.finishedArp) { + chan[i].baseFreq=round(FREQ_BASE*pow(2.0f,((float)(chan[i].note)/12.0f))); + chan[i].freqChanged=true; + } + } + if (chan[i].std.hadDuty) { + chan[i].duty=chan[i].std.duty; + if (i==3 && chan[i].duty>1) chan[i].duty=1; + DivInstrument* ins=parent->getIns(chan[i].ins); + if (i!=2) { + } + if (i==3) { // noise + chan[i].freqChanged=true; + } + } + if (chan[i].sweepChanged) { + chan[i].sweepChanged=false; + if (i==0) { + //rWrite(16+i*5,chan[i].sweep); + } + } + if (chan[i].onTheKey) { + DivInstrument* ins=parent->getIns(chan[i].ins); + sid.write(i*7+4, + (ins->c64.noiseOn<<7)| + (ins->c64.pulseOn<<6)| + (ins->c64.sawOn<<5)| + (ins->c64.triOn<<4)| + 1 + ); + chan[i].onTheKey=false; + } + if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) { + DivInstrument* ins=parent->getIns(chan[i].ins); + chan[i].freq=(chan[i].baseFreq*(ONE_SEMITONE-chan[i].pitch))/ONE_SEMITONE; + if (chan[i].keyOn) { + sid.write(i*7+5,(ins->c64.a<<4)|(ins->c64.d)); + sid.write(i*7+6,(ins->c64.s<<4)|(ins->c64.r)); + sid.write(i*7+4, + (ins->c64.noiseOn<<7)| + (ins->c64.pulseOn<<6)| + (ins->c64.sawOn<<5)| + (ins->c64.triOn<<4)| + 8 + ); + chan[i].onTheKey=true; + } + if (chan[i].keyOff) { + sid.write(i*7+5,(ins->c64.a<<4)|(ins->c64.d)); + sid.write(i*7+6,(ins->c64.s<<4)|(ins->c64.r)); + sid.write(i*7+4, + (ins->c64.noiseOn<<7)| + (ins->c64.pulseOn<<6)| + (ins->c64.sawOn<<5)| + (ins->c64.triOn<<4)| + 0 + ); + } + sid.write(i*7,chan[i].freq&0xff); + sid.write(i*7+1,chan[i].freq>>8); + if (chan[i].keyOn) chan[i].keyOn=false; + if (chan[i].keyOff) chan[i].keyOff=false; + chan[i].freqChanged=false; + } + } +} + +int DivPlatformC64::dispatch(DivCommand c) { + switch (c.cmd) { + case DIV_CMD_NOTE_ON: + chan[c.chan].baseFreq=round(FREQ_BASE*pow(2.0f,((float)c.value/12.0f))); + chan[c.chan].freqChanged=true; + chan[c.chan].note=c.value; + chan[c.chan].active=true; + chan[c.chan].keyOn=true; + chan[c.chan].std.init(parent->getIns(chan[c.chan].ins)); + break; + case DIV_CMD_NOTE_OFF: + chan[c.chan].active=false; + chan[c.chan].keyOff=true; + chan[c.chan].std.init(NULL); + break; + case DIV_CMD_INSTRUMENT: + if (chan[c.chan].ins!=c.value) { + chan[c.chan].ins=c.value; + } + break; + case DIV_CMD_VOLUME: + if (chan[c.chan].vol!=c.value) { + chan[c.chan].vol=c.value; + if (!chan[c.chan].std.hasVol) { + chan[c.chan].outVol=c.value; + } + if (c.chan==2) { + } else { + } + } + break; + case DIV_CMD_GET_VOLUME: + return chan[c.chan].vol; + break; + case DIV_CMD_PITCH: + chan[c.chan].pitch=c.value; + chan[c.chan].freqChanged=true; + break; + case DIV_CMD_NOTE_PORTA: { + int destFreq=round(FREQ_BASE*pow(2.0f,((float)c.value2/12.0f))); + bool return2=false; + if (destFreq>chan[c.chan].baseFreq) { + chan[c.chan].baseFreq+=c.value; + if (chan[c.chan].baseFreq>=destFreq) { + chan[c.chan].baseFreq=destFreq; + return2=true; + } + } else { + chan[c.chan].baseFreq-=c.value; + if (chan[c.chan].baseFreq<=destFreq) { + chan[c.chan].baseFreq=destFreq; + return2=true; + } + } + chan[c.chan].freqChanged=true; + if (return2) { + chan[c.chan].inPorta=false; + return 2; + } + break; + } + case DIV_CMD_STD_NOISE_MODE: + chan[c.chan].duty=c.value; + if (c.chan==3) { // noise + chan[c.chan].freqChanged=true; + } + break; + case DIV_CMD_LEGATO: + if (c.chan==3) break; + chan[c.chan].baseFreq=round(FREQ_BASE/pow(2.0f,((float)c.value/12.0f))); + chan[c.chan].freqChanged=true; + chan[c.chan].note=c.value; + break; + case DIV_CMD_PRE_PORTA: + chan[c.chan].std.init(parent->getIns(chan[c.chan].ins)); + chan[c.chan].inPorta=c.value; + break; + case DIV_CMD_GET_VOLMAX: + return 15; + break; + case DIV_ALWAYS_SET_VOLUME: + return 1; + break; + default: + break; + } + return 1; +} + +void DivPlatformC64::setChipModel(bool is6581) { + if (is6581) { + sid.set_chip_model(MOS6581); + } else { + sid.set_chip_model(MOS8580); + } +} + +int DivPlatformC64::init(DivEngine* p, int channels, int sugRate) { + parent=p; + rate=985248; + + sid.reset(); + + sid.write(0x18,0x0f); + + return 3; +} diff --git a/src/engine/platform/c64.h b/src/engine/platform/c64.h new file mode 100644 index 00000000..efd6a405 --- /dev/null +++ b/src/engine/platform/c64.h @@ -0,0 +1,48 @@ +#ifndef _C64_H +#define _C64_H + +#include "../dispatch.h" +#include "../macroInt.h" +#include "sound/c64/sid.h" + +class DivPlatformC64: public DivDispatch { + struct Channel { + int freq, baseFreq, pitch, prevFreq; + unsigned char ins, note, duty, sweep; + bool active, insChanged, freqChanged, sweepChanged, keyOn, keyOff, inPorta, onTheKey; + signed char vol, outVol, wave; + DivMacroInt std; + Channel(): + freq(0), + baseFreq(0), + pitch(0), + prevFreq(65535), + ins(-1), + note(0), + duty(0), + sweep(0), + active(false), + insChanged(true), + freqChanged(false), + sweepChanged(false), + keyOn(false), + keyOff(false), + inPorta(false), + onTheKey(false), + vol(15), + wave(-1) {} + }; + Channel chan[3]; + + SID sid; + + void updateWave(); + public: + void acquire(int& l, int& r); + int dispatch(DivCommand c); + void tick(); + int init(DivEngine* parent, int channels, int sugRate); + void setChipModel(bool is6581); +}; + +#endif diff --git a/src/engine/platform/nes.h b/src/engine/platform/nes.h index 47f0b1e3..eacfc21e 100644 --- a/src/engine/platform/nes.h +++ b/src/engine/platform/nes.h @@ -1,5 +1,5 @@ -#ifndef _APU_H -#define _APU_H +#ifndef _NES_H +#define _NES_H #include "../dispatch.h" #include "../macroInt.h"