implement Furnace-style PCM on Arcade/Gen/NES/PCE

This commit is contained in:
tildearrow 2022-01-20 16:09:05 -05:00
parent 812d0397a8
commit 0f28ae0fee
8 changed files with 132 additions and 39 deletions

View file

@ -199,6 +199,16 @@ void DivPlatformArcade::tick() {
chan[i].keyOn=false; 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) { void DivPlatformArcade::muteChannel(int ch, bool mute) {
@ -216,8 +226,20 @@ void DivPlatformArcade::muteChannel(int ch, bool mute) {
int DivPlatformArcade::dispatch(DivCommand c) { int DivPlatformArcade::dispatch(DivCommand c) {
switch (c.cmd) { switch (c.cmd) {
case DIV_CMD_NOTE_ON: { case DIV_CMD_NOTE_ON: {
DivInstrument* ins=parent->getIns(chan[c.chan].ins);
if (c.chan>7) { if (c.chan>7) {
if (skipRegisterWrites) break; if (skipRegisterWrites) 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; chan[c.chan].pcm.sample=12*sampleBank+c.value%12;
if (chan[c.chan].pcm.sample>=parent->song.sampleLen) { if (chan[c.chan].pcm.sample>=parent->song.sampleLen) {
chan[c.chan].pcm.sample=-1; chan[c.chan].pcm.sample=-1;
@ -225,9 +247,10 @@ int DivPlatformArcade::dispatch(DivCommand c) {
} }
chan[c.chan].pcm.pos=0; 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].pcm.freq=MIN(255,(parent->song.sample[chan[c.chan].pcm.sample]->rate*255)/31250);
chan[c.chan].furnacePCM=false;
}
break; break;
} }
DivInstrument* ins=parent->getIns(chan[c.chan].ins);
for (int i=0; i<4; i++) { for (int i=0; i<4; i++) {
unsigned short baseAddr=chanOffs[c.chan]|opOffs[i]; unsigned short baseAddr=chanOffs[c.chan]|opOffs[i];

View file

@ -16,7 +16,7 @@ class DivPlatformArcade: public DivDispatch {
int freq, baseFreq, pitch; int freq, baseFreq, pitch;
unsigned char ins; unsigned char ins;
signed char konCycles; signed char konCycles;
bool active, insChanged, freqChanged, keyOn, keyOff, portaPause; bool active, insChanged, freqChanged, keyOn, keyOff, portaPause, furnacePCM;
int vol; int vol;
unsigned char chVolL, chVolR; unsigned char chVolL, chVolR;
@ -27,7 +27,7 @@ class DivPlatformArcade: public DivDispatch {
unsigned char freq; unsigned char freq;
PCMChannel(): sample(-1), pos(0), len(0), freq(0) {} PCMChannel(): sample(-1), pos(0), len(0), freq(0) {}
} pcm; } 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]; Channel chan[13];
struct QueuedWrite { struct QueuedWrite {

View file

@ -32,7 +32,7 @@ void DivPlatformGenesis::acquire(short* bufL, short* bufR, size_t start, size_t
dacSample=-1; dacSample=-1;
} }
} }
dacPeriod+=dacRate; dacPeriod+=MAX(40,dacRate);
} }
} }
@ -102,6 +102,9 @@ void DivPlatformGenesis::tick() {
int freqt=toFreq(chan[i].freq); int freqt=toFreq(chan[i].freq);
immWrite(chanOffs[i]+0xa4,freqt>>8); immWrite(chanOffs[i]+0xa4,freqt>>8);
immWrite(chanOffs[i]+0xa0,freqt&0xff); immWrite(chanOffs[i]+0xa0,freqt&0xff);
if (chan[i].furnaceDac) {
dacRate=(1280000*1.25)/chan[i].baseFreq;
}
chan[i].freqChanged=false; chan[i].freqChanged=false;
} }
if (chan[i].keyOn) { if (chan[i].keyOn) {
@ -171,8 +174,21 @@ int DivPlatformGenesis::dispatch(DivCommand c) {
} }
switch (c.cmd) { switch (c.cmd) {
case DIV_CMD_NOTE_ON: { case DIV_CMD_NOTE_ON: {
DivInstrument* ins=parent->getIns(chan[c.chan].ins);
if (c.chan==5 && dacMode) { if (c.chan==5 && dacMode) {
if (skipRegisterWrites) break; if (skipRegisterWrites) 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; dacSample=12*sampleBank+c.value%12;
if (dacSample>=parent->song.sampleLen) { if (dacSample>=parent->song.sampleLen) {
dacSample=-1; dacSample=-1;
@ -181,10 +197,10 @@ int DivPlatformGenesis::dispatch(DivCommand c) {
dacPos=0; dacPos=0;
dacPeriod=0; dacPeriod=0;
dacRate=1280000/parent->song.sample[dacSample]->rate; dacRate=1280000/parent->song.sample[dacSample]->rate;
chan[c.chan].furnaceDac=false;
}
break; break;
} }
DivInstrument* ins=parent->getIns(chan[c.chan].ins);
for (int i=0; i<4; i++) { for (int i=0; i<4; i++) {
unsigned short baseAddr=chanOffs[c.chan]|opOffs[i]; unsigned short baseAddr=chanOffs[c.chan]|opOffs[i];

View file

@ -13,10 +13,10 @@ class DivPlatformGenesis: public DivDispatch {
int freq, baseFreq, pitch; int freq, baseFreq, pitch;
unsigned char ins; unsigned char ins;
signed char konCycles; signed char konCycles;
bool active, insChanged, freqChanged, keyOn, keyOff, portaPause; bool active, insChanged, freqChanged, keyOn, keyOff, portaPause, furnaceDac;
int vol; int vol;
unsigned char pan; 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]; Channel chan[10];
bool isMuted[10]; bool isMuted[10];

View file

@ -158,12 +158,39 @@ void DivPlatformNES::tick() {
chan[i].freqChanged=false; 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) { int DivPlatformNES::dispatch(DivCommand c) {
switch (c.cmd) { switch (c.cmd) {
case DIV_CMD_NOTE_ON: case DIV_CMD_NOTE_ON:
if (c.chan==4) { // PCM if (c.chan==4) { // PCM
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; dacSample=12*sampleBank+c.value%12;
if (dacSample>=parent->song.sampleLen) { if (dacSample>=parent->song.sampleLen) {
dacSample=-1; dacSample=-1;
@ -172,6 +199,8 @@ int DivPlatformNES::dispatch(DivCommand c) {
dacPos=0; dacPos=0;
dacPeriod=0; dacPeriod=0;
dacRate=parent->song.sample[dacSample]->rate; dacRate=parent->song.sample[dacSample]->rate;
chan[c.chan].furnaceDac=false;
}
break; break;
} else if (c.chan==3) { // noise } else if (c.chan==3) { // noise
if (c.value!=DIV_NOTE_NULL) { if (c.value!=DIV_NOTE_NULL) {

View file

@ -8,7 +8,7 @@ class DivPlatformNES: public DivDispatch {
struct Channel { struct Channel {
int freq, baseFreq, pitch, prevFreq; int freq, baseFreq, pitch, prevFreq;
unsigned char ins, note, duty, sweep; 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; signed char vol, outVol, wave;
DivMacroInt std; DivMacroInt std;
Channel(): Channel():
@ -27,6 +27,7 @@ class DivPlatformNES: public DivDispatch {
keyOn(false), keyOn(false),
keyOff(false), keyOff(false),
inPorta(false), inPorta(false),
furnaceDac(false),
vol(15), vol(15),
outVol(15), outVol(15),
wave(-1) {} wave(-1) {}

View file

@ -127,6 +127,9 @@ void DivPlatformPCE::tick() {
if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) { if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) {
//DivInstrument* ins=parent->getIns(chan[i].ins); //DivInstrument* ins=parent->getIns(chan[i].ins);
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true); 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].freq>4095) chan[i].freq=4095;
if (chan[i].note>0x5d) chan[i].freq=0x01; if (chan[i].note>0x5d) chan[i].freq=0x01;
chWrite(i,0x02,chan[i].freq&0xff); chWrite(i,0x02,chan[i].freq&0xff);
@ -153,6 +156,24 @@ int DivPlatformPCE::dispatch(DivCommand c) {
switch (c.cmd) { switch (c.cmd) {
case DIV_CMD_NOTE_ON: case DIV_CMD_NOTE_ON:
if (chan[c.chan].pcm) { if (chan[c.chan].pcm) {
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; chan[c.chan].dacSample=12*sampleBank+c.value%12;
if (chan[c.chan].dacSample>=parent->song.sampleLen) { if (chan[c.chan].dacSample>=parent->song.sampleLen) {
chan[c.chan].dacSample=-1; chan[c.chan].dacSample=-1;
@ -161,6 +182,8 @@ int DivPlatformPCE::dispatch(DivCommand c) {
chan[c.chan].dacPos=0; chan[c.chan].dacPos=0;
chan[c.chan].dacPeriod=0; chan[c.chan].dacPeriod=0;
chan[c.chan].dacRate=1789773/parent->song.sample[chan[c.chan].dacSample]->rate; chan[c.chan].dacRate=1789773/parent->song.sample[chan[c.chan].dacSample]->rate;
chan[c.chan].furnaceDac=false;
}
break; break;
} }
if (c.value!=DIV_NOTE_NULL) { if (c.value!=DIV_NOTE_NULL) {

View file

@ -13,7 +13,7 @@ class DivPlatformPCE: public DivDispatch {
unsigned int dacPos; unsigned int dacPos;
int dacSample; int dacSample;
unsigned char ins, pan; 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; signed char vol, outVol, wave;
DivMacroInt std; DivMacroInt std;
Channel(): Channel():
@ -24,7 +24,7 @@ class DivPlatformPCE: public DivDispatch {
dacPeriod(0), dacPeriod(0),
dacRate(0), dacRate(0),
dacPos(0), dacPos(0),
dacSample(0), dacSample(-1),
ins(-1), ins(-1),
pan(255), pan(255),
active(false), active(false),
@ -35,6 +35,7 @@ class DivPlatformPCE: public DivDispatch {
inPorta(false), inPorta(false),
noise(false), noise(false),
pcm(false), pcm(false),
furnaceDac(false),
vol(31), vol(31),
outVol(31), outVol(31),
wave(-1) {} wave(-1) {}