From 0f28ae0feeb113d4a3ea274ee8526452c134b45e Mon Sep 17 00:00:00 2001 From: tildearrow Date: Thu, 20 Jan 2022 16:09:05 -0500 Subject: [PATCH] implement Furnace-style PCM on Arcade/Gen/NES/PCE --- src/engine/platform/arcade.cpp | 37 ++++++++++++++++++++++------ src/engine/platform/arcade.h | 4 +-- src/engine/platform/genesis.cpp | 38 ++++++++++++++++++++--------- src/engine/platform/genesis.h | 4 +-- src/engine/platform/nes.cpp | 43 +++++++++++++++++++++++++++------ src/engine/platform/nes.h | 3 ++- src/engine/platform/pce.cpp | 37 ++++++++++++++++++++++------ src/engine/platform/pce.h | 5 ++-- 8 files changed, 132 insertions(+), 39 deletions(-) diff --git a/src/engine/platform/arcade.cpp b/src/engine/platform/arcade.cpp index e9d9b6505..f1483a7c6 100644 --- a/src/engine/platform/arcade.cpp +++ b/src/engine/platform/arcade.cpp @@ -199,6 +199,16 @@ void DivPlatformArcade::tick() { chan[i].keyOn=false; } } + + for (int i=8; i<13; i++) { + if (chan[i].freqChanged) { + chan[i].freq=chan[i].baseFreq+(chan[i].pitch>>1)-64; + if (chan[i].furnacePCM) { + chan[i].pcm.freq=MIN(255,((440.0*pow(2.0,double(chan[i].freq+256)/(64.0*12.0)))*255)/31250); + } + chan[i].freqChanged=false; + } + } } void DivPlatformArcade::muteChannel(int ch, bool mute) { @@ -216,18 +226,31 @@ void DivPlatformArcade::muteChannel(int ch, bool mute) { int DivPlatformArcade::dispatch(DivCommand c) { switch (c.cmd) { case DIV_CMD_NOTE_ON: { + DivInstrument* ins=parent->getIns(chan[c.chan].ins); if (c.chan>7) { if (skipRegisterWrites) break; - chan[c.chan].pcm.sample=12*sampleBank+c.value%12; - if (chan[c.chan].pcm.sample>=parent->song.sampleLen) { - chan[c.chan].pcm.sample=-1; - break; + if (ins->type==DIV_INS_AMIGA) { + chan[c.chan].pcm.sample=ins->amiga.initSample; + if (chan[c.chan].pcm.sample<0 || chan[c.chan].pcm.sample>=parent->song.sampleLen) { + chan[c.chan].pcm.sample=-1; + break; + } + chan[c.chan].pcm.pos=0; + chan[c.chan].baseFreq=c.value<<6; + chan[c.chan].freqChanged=true; + chan[c.chan].furnacePCM=true; + } else { + chan[c.chan].pcm.sample=12*sampleBank+c.value%12; + if (chan[c.chan].pcm.sample>=parent->song.sampleLen) { + chan[c.chan].pcm.sample=-1; + break; + } + chan[c.chan].pcm.pos=0; + chan[c.chan].pcm.freq=MIN(255,(parent->song.sample[chan[c.chan].pcm.sample]->rate*255)/31250); + chan[c.chan].furnacePCM=false; } - chan[c.chan].pcm.pos=0; - chan[c.chan].pcm.freq=MIN(255,(parent->song.sample[chan[c.chan].pcm.sample]->rate*255)/31250); break; } - DivInstrument* ins=parent->getIns(chan[c.chan].ins); for (int i=0; i<4; i++) { unsigned short baseAddr=chanOffs[c.chan]|opOffs[i]; diff --git a/src/engine/platform/arcade.h b/src/engine/platform/arcade.h index 2e4e2de6d..0094a4646 100644 --- a/src/engine/platform/arcade.h +++ b/src/engine/platform/arcade.h @@ -16,7 +16,7 @@ class DivPlatformArcade: public DivDispatch { int freq, baseFreq, pitch; unsigned char ins; signed char konCycles; - bool active, insChanged, freqChanged, keyOn, keyOff, portaPause; + bool active, insChanged, freqChanged, keyOn, keyOff, portaPause, furnacePCM; int vol; unsigned char chVolL, chVolR; @@ -27,7 +27,7 @@ class DivPlatformArcade: public DivDispatch { unsigned char freq; PCMChannel(): sample(-1), pos(0), len(0), freq(0) {} } pcm; - Channel(): freqH(0), freqL(0), freq(0), baseFreq(0), pitch(0), ins(-1), active(false), insChanged(true), freqChanged(false), keyOn(false), keyOff(false), portaPause(false), vol(0), chVolL(127), chVolR(127) {} + Channel(): freqH(0), freqL(0), freq(0), baseFreq(0), pitch(0), ins(-1), active(false), insChanged(true), freqChanged(false), keyOn(false), keyOff(false), portaPause(false), furnacePCM(false), vol(0), chVolL(127), chVolR(127) {} }; Channel chan[13]; struct QueuedWrite { diff --git a/src/engine/platform/genesis.cpp b/src/engine/platform/genesis.cpp index d59260f1d..604c052bd 100644 --- a/src/engine/platform/genesis.cpp +++ b/src/engine/platform/genesis.cpp @@ -32,7 +32,7 @@ void DivPlatformGenesis::acquire(short* bufL, short* bufR, size_t start, size_t dacSample=-1; } } - dacPeriod+=dacRate; + dacPeriod+=MAX(40,dacRate); } } @@ -102,6 +102,9 @@ void DivPlatformGenesis::tick() { int freqt=toFreq(chan[i].freq); immWrite(chanOffs[i]+0xa4,freqt>>8); immWrite(chanOffs[i]+0xa0,freqt&0xff); + if (chan[i].furnaceDac) { + dacRate=(1280000*1.25)/chan[i].baseFreq; + } chan[i].freqChanged=false; } if (chan[i].keyOn) { @@ -171,20 +174,33 @@ int DivPlatformGenesis::dispatch(DivCommand c) { } switch (c.cmd) { case DIV_CMD_NOTE_ON: { + DivInstrument* ins=parent->getIns(chan[c.chan].ins); if (c.chan==5 && dacMode) { if (skipRegisterWrites) break; - dacSample=12*sampleBank+c.value%12; - if (dacSample>=parent->song.sampleLen) { - dacSample=-1; - break; + if (ins->type==DIV_INS_AMIGA) { // Furnace mode + dacSample=ins->amiga.initSample; + if (dacSample<0 || dacSample>=parent->song.sampleLen) { + dacSample=-1; + break; + } + dacPos=0; + dacPeriod=0; + chan[c.chan].baseFreq=644.0f*pow(2.0f,((float)c.value/12.0f)); + chan[c.chan].freqChanged=true; + chan[c.chan].furnaceDac=true; + } else { // compatible mode + dacSample=12*sampleBank+c.value%12; + if (dacSample>=parent->song.sampleLen) { + dacSample=-1; + break; + } + dacPos=0; + dacPeriod=0; + dacRate=1280000/parent->song.sample[dacSample]->rate; + chan[c.chan].furnaceDac=false; } - dacPos=0; - dacPeriod=0; - dacRate=1280000/parent->song.sample[dacSample]->rate; break; - } - DivInstrument* ins=parent->getIns(chan[c.chan].ins); - + } for (int i=0; i<4; i++) { unsigned short baseAddr=chanOffs[c.chan]|opOffs[i]; diff --git a/src/engine/platform/genesis.h b/src/engine/platform/genesis.h index b132bfb50..8f8caf4e3 100644 --- a/src/engine/platform/genesis.h +++ b/src/engine/platform/genesis.h @@ -13,10 +13,10 @@ class DivPlatformGenesis: public DivDispatch { int freq, baseFreq, pitch; unsigned char ins; signed char konCycles; - bool active, insChanged, freqChanged, keyOn, keyOff, portaPause; + bool active, insChanged, freqChanged, keyOn, keyOff, portaPause, furnaceDac; int vol; unsigned char pan; - Channel(): freqH(0), freqL(0), freq(0), baseFreq(0), pitch(0), ins(-1), active(false), insChanged(true), freqChanged(false), keyOn(false), keyOff(false), portaPause(false), vol(0), pan(3) {} + Channel(): freqH(0), freqL(0), freq(0), baseFreq(0), pitch(0), ins(-1), active(false), insChanged(true), freqChanged(false), keyOn(false), keyOff(false), portaPause(false), furnaceDac(false), vol(0), pan(3) {} }; Channel chan[10]; bool isMuted[10]; diff --git a/src/engine/platform/nes.cpp b/src/engine/platform/nes.cpp index 50c81fa0a..cc60d99e8 100644 --- a/src/engine/platform/nes.cpp +++ b/src/engine/platform/nes.cpp @@ -158,20 +158,49 @@ void DivPlatformNES::tick() { chan[i].freqChanged=false; } } + + // PCM + if (chan[4].freqChanged) { + chan[4].freq=parent->calcFreq(chan[4].baseFreq,chan[4].pitch,false); + if (chan[4].furnaceDac) { + dacRate=MIN(chan[4].freq,32000); + } + chan[4].freqChanged=false; + } } int DivPlatformNES::dispatch(DivCommand c) { switch (c.cmd) { case DIV_CMD_NOTE_ON: if (c.chan==4) { // PCM - dacSample=12*sampleBank+c.value%12; - if (dacSample>=parent->song.sampleLen) { - dacSample=-1; - break; + DivInstrument* ins=parent->getIns(chan[c.chan].ins); + if (ins->type==DIV_INS_AMIGA) { + dacSample=ins->amiga.initSample; + if (dacSample<0 || dacSample>=parent->song.sampleLen) { + dacSample=-1; + break; + } + dacPos=0; + dacPeriod=0; + chan[c.chan].baseFreq=440.0f*pow(2.0f,((float)(c.value+3)/12.0f)); + if (c.value!=DIV_NOTE_NULL) { + chan[c.chan].freqChanged=true; + chan[c.chan].note=c.value; + } + chan[c.chan].active=true; + chan[c.chan].keyOn=true; + chan[c.chan].furnaceDac=true; + } else { + dacSample=12*sampleBank+c.value%12; + if (dacSample>=parent->song.sampleLen) { + dacSample=-1; + break; + } + dacPos=0; + dacPeriod=0; + dacRate=parent->song.sample[dacSample]->rate; + chan[c.chan].furnaceDac=false; } - dacPos=0; - dacPeriod=0; - dacRate=parent->song.sample[dacSample]->rate; break; } else if (c.chan==3) { // noise if (c.value!=DIV_NOTE_NULL) { diff --git a/src/engine/platform/nes.h b/src/engine/platform/nes.h index b3d3ce467..912424de1 100644 --- a/src/engine/platform/nes.h +++ b/src/engine/platform/nes.h @@ -8,7 +8,7 @@ class DivPlatformNES: public DivDispatch { struct Channel { int freq, baseFreq, pitch, prevFreq; unsigned char ins, note, duty, sweep; - bool active, insChanged, freqChanged, sweepChanged, keyOn, keyOff, inPorta; + bool active, insChanged, freqChanged, sweepChanged, keyOn, keyOff, inPorta, furnaceDac; signed char vol, outVol, wave; DivMacroInt std; Channel(): @@ -27,6 +27,7 @@ class DivPlatformNES: public DivDispatch { keyOn(false), keyOff(false), inPorta(false), + furnaceDac(false), vol(15), outVol(15), wave(-1) {} diff --git a/src/engine/platform/pce.cpp b/src/engine/platform/pce.cpp index e2719905f..11a934d59 100644 --- a/src/engine/platform/pce.cpp +++ b/src/engine/platform/pce.cpp @@ -127,6 +127,9 @@ void DivPlatformPCE::tick() { if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) { //DivInstrument* ins=parent->getIns(chan[i].ins); chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true); + if (chan[i].furnaceDac) { + chan[i].dacRate=chan[i].freq; + } if (chan[i].freq>4095) chan[i].freq=4095; if (chan[i].note>0x5d) chan[i].freq=0x01; chWrite(i,0x02,chan[i].freq&0xff); @@ -153,14 +156,34 @@ int DivPlatformPCE::dispatch(DivCommand c) { switch (c.cmd) { case DIV_CMD_NOTE_ON: if (chan[c.chan].pcm) { - chan[c.chan].dacSample=12*sampleBank+c.value%12; - if (chan[c.chan].dacSample>=parent->song.sampleLen) { - chan[c.chan].dacSample=-1; - break; + DivInstrument* ins=parent->getIns(chan[c.chan].ins); + if (ins->type==DIV_INS_AMIGA) { + chan[c.chan].dacSample=ins->amiga.initSample; + if (chan[c.chan].dacSample<0 || chan[c.chan].dacSample>=parent->song.sampleLen) { + chan[c.chan].dacSample=-1; + break; + } + chan[c.chan].dacPos=0; + chan[c.chan].dacPeriod=0; + if (c.value!=DIV_NOTE_NULL) { + 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].furnaceDac=true; + } else { + chan[c.chan].dacSample=12*sampleBank+c.value%12; + if (chan[c.chan].dacSample>=parent->song.sampleLen) { + chan[c.chan].dacSample=-1; + break; + } + chan[c.chan].dacPos=0; + chan[c.chan].dacPeriod=0; + chan[c.chan].dacRate=1789773/parent->song.sample[chan[c.chan].dacSample]->rate; + chan[c.chan].furnaceDac=false; } - chan[c.chan].dacPos=0; - chan[c.chan].dacPeriod=0; - chan[c.chan].dacRate=1789773/parent->song.sample[chan[c.chan].dacSample]->rate; break; } if (c.value!=DIV_NOTE_NULL) { diff --git a/src/engine/platform/pce.h b/src/engine/platform/pce.h index 3eac1d347..bdf0bf324 100644 --- a/src/engine/platform/pce.h +++ b/src/engine/platform/pce.h @@ -13,7 +13,7 @@ class DivPlatformPCE: public DivDispatch { unsigned int dacPos; int dacSample; unsigned char ins, pan; - bool active, insChanged, freqChanged, keyOn, keyOff, inPorta, noise, pcm; + bool active, insChanged, freqChanged, keyOn, keyOff, inPorta, noise, pcm, furnaceDac; signed char vol, outVol, wave; DivMacroInt std; Channel(): @@ -24,7 +24,7 @@ class DivPlatformPCE: public DivDispatch { dacPeriod(0), dacRate(0), dacPos(0), - dacSample(0), + dacSample(-1), ins(-1), pan(255), active(false), @@ -35,6 +35,7 @@ class DivPlatformPCE: public DivDispatch { inPorta(false), noise(false), pcm(false), + furnaceDac(false), vol(31), outVol(31), wave(-1) {}