From 45a6d70479df80a92317d00577b81ebd6b7cd751 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Thu, 13 May 2021 02:39:26 -0500 Subject: [PATCH] massive improvements to genesis playback --- src/engine/dispatch.h | 5 +- src/engine/engine.cpp | 23 +++++ src/engine/engine.h | 2 + src/engine/platform/genesis.cpp | 149 +++++++++++++++++++++++++++----- src/engine/platform/genesis.h | 18 +++- src/engine/playback.cpp | 63 +++++++++----- src/engine/sample.h | 13 +++ 7 files changed, 228 insertions(+), 45 deletions(-) diff --git a/src/engine/dispatch.h b/src/engine/dispatch.h index 90edb330c..0cb0154b1 100644 --- a/src/engine/dispatch.h +++ b/src/engine/dispatch.h @@ -8,7 +8,10 @@ enum DivDispatchCmds { DIV_CMD_VOLUME, DIV_CMD_PITCH_UP, DIV_CMD_PITCH_DOWN, - DIV_CMD_PITCH_TO + DIV_CMD_PITCH_TO, + DIV_CMD_PANNING, + + DIV_CMD_SAMPLE_MODE }; struct DivCommand { diff --git a/src/engine/engine.cpp b/src/engine/engine.cpp index 8c98026fd..dfc28dd5a 100644 --- a/src/engine/engine.cpp +++ b/src/engine/engine.cpp @@ -593,6 +593,7 @@ bool DivEngine::load(void* f, size_t slen) { song=ds; chans=getChannelCount(song.system); + renderSamples(); } catch (EndOfFileException e) { logE("premature end of file!\n"); return false; @@ -600,6 +601,28 @@ bool DivEngine::load(void* f, size_t slen) { return true; } +static double samplePitches[11]={ + 0.1666666666, 0.2, 0.25, 0.333333333, 0.5, + 1, + 2, 3, 4, 5, 6 +}; + +void DivEngine::renderSamples() { + for (int i=0; irendLength!=0) delete[] s->rendData; + s->rendLength=(double)s->length/samplePitches[s->pitch]; + s->rendData=new short[s->rendLength]; + int k=0; + for (double j=0; jlength; j+=samplePitches[s->pitch]) { + if (k>=s->rendLength) { + break; + } + s->rendData[k++]=s->data[(unsigned int)j]; + } + } +} + void DivEngine::play() { } diff --git a/src/engine/engine.h b/src/engine/engine.h index 9fa834daf..a2c6630c6 100644 --- a/src/engine/engine.h +++ b/src/engine/engine.h @@ -31,6 +31,8 @@ class DivEngine { void nextOrder(); void nextRow(); void nextTick(); + bool perSystemEffect(int ch, unsigned char effect, unsigned char effectVal); + void renderSamples(); public: DivSong song; diff --git a/src/engine/platform/genesis.cpp b/src/engine/platform/genesis.cpp index 130ce81aa..0cee7db76 100644 --- a/src/engine/platform/genesis.cpp +++ b/src/engine/platform/genesis.cpp @@ -6,14 +6,37 @@ // i think there is no wait for data writes, just for ON/OFF writes void DivPlatformGenesis::acquire(short& l, short& r) { short o[2]; + for (int i=0; i<6; i++) { + if (--chan[i].konCycles<0) chan[i].konCycles=0; + } + + if (dacMode && dacSample!=-1) { + if (--dacPeriod<1) { + DivSample* s=parent->song.sample[dacSample]; + writes.emplace(0x2a,((unsigned short)s->rendData[dacPos++]+0x8000)>>8); + if (dacPos>s->rendLength) { + dacSample=-1; + } + dacPeriod=dacRate; + } + } + if (!writes.empty() && --delay<0) { - QueuedWrite w=writes.front(); - //printf("write: %x = %.2x\n",w.addr,w.val); - OPN2_Write(&fm,0x0+((w.addr>>8)<<1),w.addr); - OPN2_Clock(&fm,o); - OPN2_Write(&fm,0x1+((w.addr>>8)<<1),w.val); - writes.pop(); - delay=24; + delay=0; + QueuedWrite& w=writes.front(); + if (w.addrOrVal) { + OPN2_Write(&fm,0x1+((w.addr>>8)<<1),w.val); + //printf("write: %x = %.2x\n",w.addr,w.val); + lastBusy=0; + writes.pop(); + } else { + lastBusy++; + if (fm.write_busy==0) { + //printf("busycounter: %d\n",lastBusy); + OPN2_Write(&fm,0x0+((w.addr>>8)<<1),w.addr); + w.addrOrVal=true; + } + } } OPN2_Clock(&fm,o); //OPN2_Write(&fm,0,0); @@ -36,28 +59,64 @@ static unsigned char konOffs[6]={ static unsigned short notes[12]={ 644,681,722,765,810,858,910,964,1021,1081,1146,1214 }; +static bool isOutput[8][4]={ + // 1 3 2 4 + {false,false,false,true}, + {false,false,false,true}, + {false,false,false,true}, + {false,false,false,true}, + {false,false,true ,true}, + {false,true ,true ,true}, + {false,true ,true ,true}, + {true ,true ,true ,true}, +}; +static unsigned char dtTable[8]={ + 7,6,5,0,1,2,3,0 +}; +static int dacRates[6]={ + 160,160,116,80,58,40 +}; int DivPlatformGenesis::dispatch(DivCommand c) { switch (c.cmd) { case DIV_CMD_NOTE_ON: { + if (c.chan==5 && dacMode) { + dacSample=c.value%12; + if (dacSample>=parent->song.sampleLen) { + dacSample=-1; + break; + } + dacPos=0; + dacPeriod=0; + dacRate=dacRates[parent->song.sample[dacSample]->rate]; + break; + } if (c.chan>5) break; //chan[c.chan].freq=16.4f*pow(2.0f,((float)c.value/12.0f)); DivInstrument* ins=parent->song.ins[chan[c.chan].ins]; writes.emplace(0x28,0x00|konOffs[c.chan]); - for (int i=0; i<4; i++) { - unsigned short baseAddr=chanOffs[c.chan]|opOffs[i]; - DivInstrumentFM::Operator op=ins->fm.op[i]; - writes.emplace(baseAddr+0x30,(op.mult&15)|(op.dt<<4)); - writes.emplace(baseAddr+0x40,op.tl); - writes.emplace(baseAddr+0x50,(op.ar&31)|(op.rs<<6)); - writes.emplace(baseAddr+0x60,(op.dr&31)|(op.am<<7)); - writes.emplace(baseAddr+0x70,op.d2r&31); - writes.emplace(baseAddr+0x80,(op.rr&15)|(op.sl<<4)); + if (chan[c.chan].insChanged) { + chan[c.chan].insChanged=false; + for (int i=0; i<4; i++) { + unsigned short baseAddr=chanOffs[c.chan]|opOffs[i]; + DivInstrumentFM::Operator op=ins->fm.op[i]; + writes.emplace(baseAddr+0x30,(op.mult&15)|(dtTable[op.dt&7]<<4)); + if (isOutput[ins->fm.alg][i]) { + writes.emplace(baseAddr+0x40,127-(((127-op.tl)*chan[c.chan].vol)/127)); + } else { + writes.emplace(baseAddr+0x40,op.tl); + } + writes.emplace(baseAddr+0x50,(op.ar&31)|(op.rs<<6)); + writes.emplace(baseAddr+0x60,(op.dr&31)|(op.am<<7)); + writes.emplace(baseAddr+0x70,op.d2r&31); + writes.emplace(baseAddr+0x80,(op.rr&15)|(op.sl<<4)); + writes.emplace(baseAddr+0x90,op.ssgEnv&15); + } + writes.emplace(chanOffs[c.chan]+0xb0,(ins->fm.alg&7)|(ins->fm.fb<<3)); + writes.emplace(chanOffs[c.chan]+0xb4,(chan[c.chan].pan<<6)|(ins->fm.fms&7)|((ins->fm.ams&3)<<4)); } writes.emplace(chanOffs[c.chan]+0xa4,((c.value/12)<<3)|(notes[c.value%12]>>8)); writes.emplace(chanOffs[c.chan]+0xa0,notes[c.value%12]); - writes.emplace(chanOffs[c.chan]+0xb0,(ins->fm.alg&7)|(ins->fm.fb<<3)); - writes.emplace(chanOffs[c.chan]+0xb4,0xc0|(ins->fm.fms&7)|((ins->fm.ams&3)<<4)); writes.emplace(0x28,0xf0|konOffs[c.chan]); chan[c.chan].active=true; break; @@ -66,12 +125,49 @@ int DivPlatformGenesis::dispatch(DivCommand c) { writes.emplace(0x28,0x00|konOffs[c.chan]); chan[c.chan].active=false; break; - case DIV_CMD_VOLUME: + case DIV_CMD_VOLUME: { chan[c.chan].vol=c.value; + DivInstrument* ins=parent->song.ins[chan[c.chan].ins]; + for (int i=0; i<4; i++) { + unsigned short baseAddr=chanOffs[c.chan]|opOffs[i]; + DivInstrumentFM::Operator op=ins->fm.op[i]; + if (isOutput[ins->fm.alg][i]) { + writes.emplace(baseAddr+0x40,127-(((127-op.tl)*chan[c.chan].vol)/127)); + } else { + writes.emplace(baseAddr+0x40,op.tl); + } + } break; + } case DIV_CMD_INSTRUMENT: + if (chan[c.chan].ins!=c.value) { + chan[c.chan].insChanged=true; + } chan[c.chan].ins=c.value; break; + case DIV_CMD_PANNING: { + switch (c.value) { + case 0x01: + chan[c.chan].pan=1; + break; + case 0x10: + chan[c.chan].pan=2; + break; + default: + chan[c.chan].pan=3; + break; + } + DivInstrument* ins=parent->song.ins[chan[c.chan].ins]; + writes.emplace(chanOffs[c.chan]+0xb4,(chan[c.chan].pan<<6)|(ins->fm.fms&7)|((ins->fm.ams&3)<<4)); + break; + } + case DIV_CMD_SAMPLE_MODE: { + if (c.chan==5) { + dacMode=c.value; + writes.emplace(0x2b,c.value<<7); + } + break; + } default: break; } @@ -82,7 +178,20 @@ int DivPlatformGenesis::init(DivEngine* p, int channels, int sugRate) { parent=p; rate=1278409; OPN2_Reset(&fm); + for (int i=0; i<10; i++) { + chan[i].vol=0x7f; + } + + lastBusy=60; + dacMode=0; + dacPeriod=0; + dacPos=0; + dacRate=0; + dacSample=-1; + + // LFO + writes.emplace(0x22,0x08); - delay=100; + delay=0; return 10; } diff --git a/src/engine/platform/genesis.h b/src/engine/platform/genesis.h index 0f64d9348..d8ef4c7bd 100644 --- a/src/engine/platform/genesis.h +++ b/src/engine/platform/genesis.h @@ -6,20 +6,30 @@ class DivPlatformGenesis: public DivDispatch { struct Channel { unsigned short freq; unsigned char ins; - bool active; + signed char konCycles; + bool active, insChanged; signed char vol; - Channel(): freq(0), ins(0), active(false), vol(0) {} + unsigned char pan; + Channel(): freq(0), ins(0), active(false), insChanged(true), vol(0), pan(3) {} }; Channel chan[10]; struct QueuedWrite { unsigned short addr; unsigned char val; - QueuedWrite(unsigned short a, unsigned char v): addr(a), val(v) {} + bool addrOrVal; + QueuedWrite(unsigned short a, unsigned char v): addr(a), val(v), addrOrVal(false) {} }; std::queue writes; ym3438_t fm; - int psg; int delay; + unsigned char lastBusy; + + bool dacMode; + int dacPeriod; + int dacRate; + int dacPos; + int dacSample; + public: void acquire(short& l, short& r); int dispatch(DivCommand c); diff --git a/src/engine/playback.cpp b/src/engine/playback.cpp index 6b50a5afb..e7ba441b2 100644 --- a/src/engine/playback.cpp +++ b/src/engine/playback.cpp @@ -15,13 +15,30 @@ const char* formatNote(unsigned char note, unsigned char octave) { static char ret[4]; if (note==100) { return "OFF"; - } else if (octave==0) { + } else if (octave==0 && note==0) { return "---"; } snprintf(ret,4,"%s%d",notes[note%12],octave+note/12); return ret; } +bool DivEngine::perSystemEffect(int ch, unsigned char effect, unsigned char effectVal) { + switch (song.system) { + case DIV_SYSTEM_GENESIS: + switch (effect) { + case 0x17: // DAC enable + dispatch->dispatch(DivCommand(DIV_CMD_SAMPLE_MODE,ch,(effectVal>0))); + break; + default: + return false; + } + break; + default: + return false; + } + return true; +} + void DivEngine::nextRow() { static char pb[4096]; static char pb1[4096]; @@ -79,28 +96,13 @@ void DivEngine::nextRow() { for (int i=0; idata[curOrder]; - // instrument - if (pat->data[curRow][2]!=255) { - dispatch->dispatch(DivCommand(DIV_CMD_INSTRUMENT,i,pat->data[curRow][2])); - } - // note - if (pat->data[curRow][0]==100) { - dispatch->dispatch(DivCommand(DIV_CMD_NOTE_OFF,i)); - } else if (pat->data[curRow][1]!=0) { - dispatch->dispatch(DivCommand(DIV_CMD_NOTE_ON,i,pat->data[curRow][0]+pat->data[curRow][1]*12)); - } - - // volume - if (pat->data[curRow][3]!=255) { - dispatch->dispatch(DivCommand(DIV_CMD_VOLUME,i,pat->data[curRow][3])); - } - // effects for (int j=0; jeffectRows; j++) { unsigned char effect=pat->data[curRow][4+(j<<1)]; unsigned char effectVal=pat->data[curRow][5+(j<<1)]; - switch (effect) { + // per-system effect + if (!perSystemEffect(i,effect,effectVal)) switch (effect) { case 0x09: // speed 1 song.speed1=effectVal; break; @@ -115,8 +117,29 @@ void DivEngine::nextRow() { changeOrd=curOrder+1; changePos=effectVal; break; + case 0x08: // panning + dispatch->dispatch(DivCommand(DIV_CMD_PANNING,i,effectVal)); + break; } } + + // instrument + if (pat->data[curRow][2]!=255) { + dispatch->dispatch(DivCommand(DIV_CMD_INSTRUMENT,i,pat->data[curRow][2])); + } + // note + if (pat->data[curRow][0]==100) { + dispatch->dispatch(DivCommand(DIV_CMD_NOTE_OFF,i)); + } else if (!(pat->data[curRow][0]==0 && pat->data[curRow][1]==0)) { + dispatch->dispatch(DivCommand(DIV_CMD_NOTE_ON,i,pat->data[curRow][0]+pat->data[curRow][1]*12)); + } + + // volume + if (pat->data[curRow][3]!=255) { + dispatch->dispatch(DivCommand(DIV_CMD_VOLUME,i,pat->data[curRow][3])); + } + + } } @@ -163,7 +186,7 @@ void DivEngine::nextBuf(float** in, float** out, int inChans, int outChans, unsi blip_read_samples(bb[1],bbOut[1],size,0); for (size_t i=0; i