diff --git a/CMakeLists.txt b/CMakeLists.txt index f994fe8bb..c8d34898d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -530,6 +530,7 @@ src/engine/platform/pokemini.cpp src/engine/platform/pong.cpp src/engine/platform/vic20.cpp src/engine/platform/vrc6.cpp +src/engine/platform/es5506.cpp src/engine/platform/scc.cpp src/engine/platform/ymz280b.cpp src/engine/platform/namcowsg.cpp diff --git a/papers/format.md b/papers/format.md index 3f93f451d..570fe7fe5 100644 --- a/papers/format.md +++ b/papers/format.md @@ -1657,6 +1657,10 @@ chips which aren't on this list don't have any flags. - 1: 16.67MHz - bit 4: stereo (bool) +## 0xb1: Ensoniq ES5506 + +- bit 0-4: channels (int) + ## 0xb5: tildearrow Sound Unit - bit 0: clockSel (int) diff --git a/src/engine/dispatch.h b/src/engine/dispatch.h index 32540db2a..6cb1b4e99 100644 --- a/src/engine/dispatch.h +++ b/src/engine/dispatch.h @@ -217,6 +217,18 @@ enum DivDispatchCmds { DIV_CMD_FM_AM2_DEPTH, // (depth) DIV_CMD_FM_PM2_DEPTH, // (depth) + DIV_CMD_ES5506_FILTER_MODE, // (value) + DIV_CMD_ES5506_FILTER_K1, // (value, mask) + DIV_CMD_ES5506_FILTER_K2, // (value, mask) + DIV_CMD_ES5506_FILTER_K1_SLIDE, // (value, negative) + DIV_CMD_ES5506_FILTER_K2_SLIDE, // (value, negative) + DIV_CMD_ES5506_ENVELOPE_COUNT, // (count) + DIV_CMD_ES5506_ENVELOPE_LVRAMP, // (ramp) + DIV_CMD_ES5506_ENVELOPE_RVRAMP, // (ramp) + DIV_CMD_ES5506_ENVELOPE_K1RAMP, // (ramp, slowdown) + DIV_CMD_ES5506_ENVELOPE_K2RAMP, // (ramp, slowdown) + DIV_CMD_ES5506_PAUSE, // (value) + DIV_ALWAYS_SET_VOLUME, // () -> alwaysSetVol DIV_CMD_MAX @@ -634,8 +646,8 @@ class DivDispatch { #define COLOR_PAL (283.75*15625.0+25.0) #define CLAMP_VAR(x,xMin,xMax) \ - if (xxMax) x=xMax; + if ((x)<(xMin)) (x)=(xMin); \ + if ((x)>(xMax)) (x)=(xMax); #define NEW_ARP_STRAT (parent->song.linearPitch==2 && !parent->song.oldArpStrategy) #define HACKY_LEGATO_MESS chan[c.chan].std.arp.will && !chan[c.chan].std.arp.mode && !NEW_ARP_STRAT diff --git a/src/engine/dispatchContainer.cpp b/src/engine/dispatchContainer.cpp index b77d6adeb..030bab559 100644 --- a/src/engine/dispatchContainer.cpp +++ b/src/engine/dispatchContainer.cpp @@ -67,6 +67,7 @@ #include "platform/vrc6.h" #include "platform/fds.h" #include "platform/mmc5.h" +#include "platform/es5506.h" #include "platform/scc.h" #include "platform/ymz280b.h" #include "platform/rf5c68.h" @@ -436,6 +437,9 @@ void DivDispatchContainer::init(DivSystem sys, DivEngine* eng, int chanCount, do case DIV_SYSTEM_MMC5: dispatch=new DivPlatformMMC5; break; + case DIV_SYSTEM_ES5506: + dispatch=new DivPlatformES5506; + break; case DIV_SYSTEM_SCC: dispatch=new DivPlatformSCC; ((DivPlatformSCC*)dispatch)->setChipModel(false); diff --git a/src/engine/engine.cpp b/src/engine/engine.cpp index 361dd255a..6bf61677f 100644 --- a/src/engine/engine.cpp +++ b/src/engine/engine.cpp @@ -2515,6 +2515,8 @@ int DivEngine::getEffectiveSampleRate(int rate) { return (48828*MIN(128,(rate*128/48828)))/128; case DIV_SYSTEM_X1_010: return (31250*MIN(255,(rate*16/31250)))/16; // TODO: support variable clock case + case DIV_SYSTEM_ES5506: + return (31250*MIN(131071,(rate*2048/31250)))/2048; // TODO: support variable clock, channel limit case default: break; } diff --git a/src/engine/platform/es5506.cpp b/src/engine/platform/es5506.cpp new file mode 100644 index 000000000..3b775eb0b --- /dev/null +++ b/src/engine/platform/es5506.cpp @@ -0,0 +1,1334 @@ +/** + * Furnace Tracker - multi-system chiptune tracker + * Copyright (C) 2021-2023 tildearrow and contributors + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "es5506.h" +#include "../engine.h" +#include "../../ta-log.h" +#include +#include + +#define PITCH_OFFSET ((double)(16*2048*(chanMax+1))) +#define NOTE_ES5506(c,note) (parent->calcBaseFreq(chipClock,chan[c].pcm.freqOffs,note,false)) + +#define rWrite(a,...) {if(!skipRegisterWrites) {hostIntf32.emplace(4,(a),__VA_ARGS__); }} +#define rRead(a,st,...) {hostIntf32.emplace(st,4,(a),__VA_ARGS__);} +#define immWrite(a,...) {hostIntf32.emplace(4,(a),__VA_ARGS__);} +#define pageWrite(p,a,...) \ + if (!skipRegisterWrites) { \ + if (curPage!=(p)) { \ + curPage=(p); \ + rWrite(0xf,curPage); \ + } \ + rWrite((a),__VA_ARGS__); \ + } + +#define pageWriteMask(p,pm,a,...) \ + if (!skipRegisterWrites) { \ + if ((curPage&(pm))!=((p)&(pm))) { \ + curPage=(curPage&~(pm))|((p)&(pm)); \ + rWrite(0xf,curPage,(pm)); \ + } \ + rWrite((a),__VA_ARGS__); \ + } + +#define pageReadMask(p,pm,a,st,...) \ + if (!skipRegisterWrites) { \ + if ((curPage&(pm))!=((p)&(pm))) { \ + curPage=(curPage&~(pm))|((p)&(pm)); \ + rWrite(0xf,curPage,(pm)); \ + } \ + rRead(st,(a),__VA_ARGS__); \ + } + + +const char* regCheatSheetES5506[]={ + "CR", "00|00", + "FC", "00|01", + "LVOL", "00|02", + "LVRAMP", "00|03", + "RVOL", "00|04", + "RVRAMP", "00|05", + "ECOUNT", "00|06", + "K2", "00|07", + "K2RAMP", "00|08", + "K1", "00|09", + "K1RAMP", "00|0A", + "ACTV", "00|0B", + "MODE", "00|0C", + "POT", "00|0D", + "IRQV", "00|0E", + "PAGE", "00|0F", + "CR", "20|00", + "START", "20|01", + "END", "20|02", + "ACCUM", "20|03", + "O4(n-1)", "20|04", + "O3(n-2)", "20|05", + "O3(n-1)", "20|06", + "O2(n-2)", "20|07", + "O2(n-1)", "20|08", + "O1(n-1)", "20|09", + "W_ST", "20|0A", + "W_END", "20|0B", + "LR_END", "20|0C", + "POT", "20|0D", + "IRQV", "20|0E", + "PAGE", "20|0F", + "CH0L", "40|00", + "CH0R", "40|01", + "CH1L", "40|02", + "CH1R", "40|03", + "CH2L", "40|04", + "CH2R", "40|05", + "CH3L", "40|06", + "CH3R", "40|07", + "CH4L", "40|08", + "CH4R", "40|09", + "CH5L", "40|0A", + "CH5R", "40|0B", + "POT", "40|0D", + "IRQV", "40|0E", + "PAGE", "40|0F", + NULL +}; + +const char** DivPlatformES5506::getRegisterSheet() { + return regCheatSheetES5506; +} + +void DivPlatformES5506::acquire(short** buf, size_t len) { + for (size_t h=0; h(int)chanMax; i--) { + oscBuf[i]->data[oscBuf[i]->needle++]=0; + } + // convert 32 bit access to 8 bit host interface + while (!hostIntf32.empty()) { + QueuedHostIntf w=hostIntf32.front(); + if (w.isRead && (w.read!=NULL)) { + hostIntf8.emplace(w.state,0,w.addr,w.read,w.mask); + hostIntf8.emplace(w.state,1,w.addr,w.read,w.mask); + hostIntf8.emplace(w.state,2,w.addr,w.read,w.mask); + hostIntf8.emplace(w.state,3,w.addr,w.read,w.mask,w.delay); + } else { + hostIntf8.emplace(0,w.addr,w.val,w.mask); + hostIntf8.emplace(1,w.addr,w.val,w.mask); + hostIntf8.emplace(2,w.addr,w.val,w.mask); + hostIntf8.emplace(3,w.addr,w.val,w.mask,w.delay); + } + hostIntf32.pop(); + } + prevChanCycle=es5506.voice_cycle(); + es5506.tick_perf(); + for (int o=0; o<6; o++) { + buf[(o<<1)|0][h]=es5506.lout(o); + buf[(o<<1)|1][h]=es5506.rout(o); + } + for (int i=chanMax; i>=0; i--) { + oscBuf[i]->data[oscBuf[i]->needle++]=(short)(chan[i].oscOut&0xffff); + } + } +} + +void DivPlatformES5506::e_pin(bool state) { + if (es5506.e_falling_edge()) { // get channel outputs + if (es5506.voice_update()) { + const signed int lOut=es5506.voice_lout(prevChanCycle); + const signed int rOut=es5506.voice_rout(prevChanCycle); + chan[prevChanCycle].oscOut=CLAMP((lOut+rOut)>>5,-32768,32767); + } + } + if (es5506.e_rising_edge()) { // host interface + if (cycle) { // wait until delay + cycle--; + } else if (!hostIntf8.empty()) { + QueuedHostIntf w=hostIntf8.front(); + unsigned char shift=24-(w.step<<3); + if (w.isRead) { + *w.read=((*w.read)&(~((0xff<0) { + cycle+=w.delay; + } + queuedReadState.emplace(w.read,w.state); + isReaded=true; + } else { + isReaded=false; + } + hostIntf8.pop(); + } else { + isReaded=false; + unsigned int mask=(w.mask>>shift)&0xff; + if ((mask==0xff) || isMasked) { + if (mask==0xff) { + maskedVal=(w.val>>shift)&0xff; + } + es5506.host_w((w.addr<<2)+w.step,maskedVal); + if(dumpWrites) { + addWrite((w.addr<<2)+w.step,maskedVal); + } + isMasked=false; + if ((w.step==3) && (w.delay>0)) { + cycle+=w.delay; + } + hostIntf8.pop(); + } else if (!isMasked) { + maskedVal=((w.val>>shift)&mask)|(es5506.host_r((w.addr<<2)+w.step)&~mask); + isMasked=true; + } + } + } + } + if (!queuedReadState.empty()) { + QueuedReadState w=queuedReadState.front(); + const unsigned char state=w.state; + if (state&0x80) { + if (irqTrigger) { + if ((irqv&0x80)==0) { + queuedRead.emplace(irqv&0x1f); + } + irqTrigger=false; + } + } + queuedReadState.pop(); + } + if (!queuedRead.empty()) { + unsigned char ch=queuedRead.front()&0x1f; + if (chan[ch].isReverseLoop) { // Reversed loop + pageWriteMask(0x00|ch,0x5f,0x00,(chan[ch].pcm.direction?0x0000:0x0040)|0x08,0x78); + chan[ch].isReverseLoop=false; + } + queuedRead.pop(); + } + isReaded=false; +} + +void DivPlatformES5506::irqb(bool state) { + rRead(0x0e,0x80,&irqv,0x9f); + irqTrigger=true; +} + +void DivPlatformES5506::tick(bool sysTick) { + for (int i=0; i<=chanMax; i++) { + chan[i].std.next(); + DivInstrument* ins=parent->getIns(chan[i].ins,DIV_INS_ES5506); + signed int k1=chan[i].k1Prev,k2=chan[i].k2Prev; + // volume/panning macros + if (chan[i].std.vol.had) { + const unsigned int nextVol=VOL_SCALE_LOG((0xffff*chan[i].vol)/0xff,(0xffff*(unsigned int)chan[i].std.vol.val)/chan[i].volMacroMax,0xffff); + if (chan[i].outVol!=nextVol) { + chan[i].outVol=nextVol; + if (!isMuted[i]) { + chan[i].volChanged.lVol=1; + chan[i].volChanged.rVol=1; + } + } + } + if (chan[i].std.panL.had) { + const unsigned int nextLVol=VOL_SCALE_LOG((0xffff*chan[i].lVol)/0xff,(0xffff*(unsigned int)chan[i].std.panL.val)/chan[i].panMacroMax,0xffff); + if (chan[i].outLVol!=nextLVol) { + chan[i].outLVol=nextLVol; + if (!isMuted[i]) { + chan[i].volChanged.lVol=1; + } + } + } + if (chan[i].std.panR.had) { + const unsigned int nextRVol=VOL_SCALE_LOG((0xffff*chan[i].rVol)/0xff,(0xffff*(unsigned int)chan[i].std.panR.val)/chan[i].panMacroMax,0xffff); + if (chan[i].outRVol!=nextRVol) { + chan[i].outRVol=nextRVol; + if (!isMuted[i]) { + chan[i].volChanged.rVol=1; + } + } + } + // arpeggio/pitch macros, frequency related + if (NEW_ARP_STRAT) { + chan[i].handleArp(); + } else if (chan[i].std.arp.had) { + if (!chan[i].inPorta) { + chan[i].nextNote=parent->calcArp(chan[i].note,chan[i].std.arp.val); + } + chan[i].noteChanged.note=1; + } + if (chan[i].std.pitch.had) { + if (chan[i].std.pitch.mode) { + chan[i].pitch2+=chan[i].std.pitch.val; + CLAMP_VAR(chan[i].pitch2,-2048,2048); + } else { + chan[i].pitch2=chan[i].std.pitch.val; + } + chan[i].freqChanged=true; + } + // phase reset macro + if (chan[i].std.phaseReset.had) { + if (chan[i].std.phaseReset.val==1) { + chan[i].keyOn=true; + } + } + // filter macros + if (chan[i].std.duty.had) { + if (chan[i].filter.mode!=DivInstrumentES5506::Filter::FilterMode(chan[i].std.duty.val&3)) { + chan[i].filter.mode=DivInstrumentES5506::Filter::FilterMode(chan[i].std.duty.val&3); + chan[i].filterChanged.mode=1; + } + } + if (chan[i].std.ex1.had) { + switch (chan[i].std.ex1.mode) { + case 0: // absolute + if (chan[i].filter.k1!=(chan[i].std.ex1.val&0xffff)) { + chan[i].filter.k1=chan[i].std.ex1.val&0xffff; + chan[i].filterChanged.k1=1; + } + break; + case 1: // relative + if (chan[i].k1Offs!=chan[i].std.ex1.val) { + chan[i].k1Offs=chan[i].std.ex1.val; + chan[i].filterChanged.k1=1; + } + break; + /*case 2: { // delta + const signed int next_k1=CLAMP(chan[i].k1Offs+chan[i].std.ex1.val,-65535,65535); + if (chan[i].k1Offs!=next_k1) { + chan[i].k1Offs=next_k1; + chan[i].filterChanged.k1=1; + } + break; + } + */ + default: + break; + } + } + if (chan[i].std.ex2.had) { + switch (chan[i].std.ex2.mode) { + case 0: // absolute + if (chan[i].filter.k2!=(chan[i].std.ex2.val&0xffff)) { + chan[i].filter.k2=chan[i].std.ex2.val&0xffff; + chan[i].filterChanged.k2=1; + } + break; + case 1: // relative + if (chan[i].k2Offs!=chan[i].std.ex1.val) { + chan[i].k2Offs=chan[i].std.ex1.val; + chan[i].filterChanged.k2=1; + } + break; + /*case 2: { // delta + const signed int next_k2=CLAMP(chan[i].k2Offs+chan[i].std.ex2.val,-65535,65535); + if (chan[i].k2Offs!=next_k2) { + chan[i].k2Offs=next_k2; + chan[i].filterChanged.k2=1; + } + break; + } + */ + default: + break; + } + } + // envelope macros + if (chan[i].std.ex3.had) { + if (chan[i].envelope.ecount!=(chan[i].std.ex3.val&0x1ff)) { + chan[i].envelope.ecount=chan[i].std.ex3.val&0x1ff; + chan[i].envChanged.ecount=1; + } + } + if (chan[i].std.ex4.had) { + if (chan[i].envelope.lVRamp!=chan[i].std.ex4.val) { + chan[i].envelope.lVRamp=chan[i].std.ex4.val; + chan[i].envChanged.lVRamp=1; + } + } + if (chan[i].std.ex5.had) { + if (chan[i].envelope.rVRamp!=chan[i].std.ex5.val) { + chan[i].envelope.rVRamp=chan[i].std.ex5.val; + chan[i].envChanged.rVRamp=1; + } + } + if (chan[i].std.ex6.had) { + if (chan[i].envelope.k1Ramp!=chan[i].std.ex6.val) { + chan[i].envelope.k1Ramp=chan[i].std.ex6.val; + chan[i].envChanged.k1Ramp=1; + } + } + if (chan[i].std.ex7.had) { + if (chan[i].envelope.k2Ramp!=chan[i].std.ex7.val) { + chan[i].envelope.k2Ramp=chan[i].std.ex7.val; + chan[i].envChanged.k2Ramp=1; + } + } + if (chan[i].std.ex8.had) { + if (chan[i].envelope.k1Slow!=(bool)(chan[i].std.ex8.val&1)) { + chan[i].envelope.k1Slow=chan[i].std.ex8.val&1; + chan[i].envChanged.k1Ramp=1; + } + if (chan[i].envelope.k2Slow!=(bool)(chan[i].std.ex8.val&2)) { + chan[i].envelope.k2Slow=chan[i].std.ex8.val&2; + chan[i].envChanged.k2Ramp=1; + } + } + // filter slide + if (!chan[i].keyOn) { + if (chan[i].k1Slide!=0 && chan[i].filter.k1>0 && chan[i].filter.k1<65535) { + signed int next=CLAMP(chan[i].filter.k1+chan[i].k1Slide,0,65535); + if (chan[i].filter.k1!=next) { + chan[i].filter.k1=next; + chan[i].filterChanged.k1=1; + } + } + if (chan[i].k2Slide!=0 && chan[i].filter.k2>0 && chan[i].filter.k2<65535) { + signed int next=CLAMP(chan[i].filter.k2+chan[i].k2Slide,0,65535); + if (chan[i].filter.k2!=next) { + chan[i].filter.k2=next; + chan[i].filterChanged.k2=1; + } + } + } + // channel assignment + if (chan[i].active && chan[i].std.fb.had) { + const unsigned char ca=CLAMP(chan[i].std.fb.val,0,5); + if (chan[i].ca!=ca) { + chan[i].ca=ca; + if (!chan[i].keyOn) { + chan[i].volChanged.ca=1; + } + } + } + // control macros + if (chan[i].active && chan[i].std.alg.had) { + if (chan[i].pcm.pause!=(bool)(chan[i].std.alg.val&1)) { + chan[i].pcm.pause=chan[i].std.alg.val&1; + if (!chan[i].keyOn) { + pageWriteMask(0x00|i,0x5f,0x00,chan[i].pcm.pause?0x0002:0x0000,0x0002); + } + } + if (chan[i].pcm.direction!=(bool)(chan[i].std.alg.val&2)) { + chan[i].pcm.direction=chan[i].std.alg.val&2; + if (!chan[i].keyOn) { + pageWriteMask(0x00|i,0x5f,0x00,chan[i].pcm.direction?0x0040:0x0000,0x0040); + } + } + } + if (chan[i].pcm.isNoteMap) { + // note map macros + if (chan[i].std.wave.had) { + if (chan[i].std.wave.val>=0 && chan[i].std.wave.val<120) { + if (chan[i].pcm.next!=chan[i].std.wave.val) { + chan[i].pcm.next=chan[i].std.wave.val; + chan[i].pcmChanged.index=1; + } + } + } + } else if (!chan[i].pcm.isNoteMap) { + if (chan[i].std.wave.had) { + if (chan[i].std.wave.val>=0 && chan[i].std.wave.valsong.sampleLen) { + if (chan[i].pcm.next!=chan[i].std.wave.val) { + chan[i].pcm.next=chan[i].std.wave.val; + chan[i].pcmChanged.index=1; + } + } + } + } + // update registers + if (chan[i].volChanged.changed) { + if (!isMuted[i]) { // calculate volume (16 bit) + if (chan[i].volChanged.lVol) { + chan[i].resLVol=VOL_SCALE_LOG(chan[i].outVol,chan[i].outLVol,0xffff); + if (!chan[i].keyOn && chan[i].active) { + pageWrite(0x00|i,0x02,chan[i].resLVol); + } + } + if (chan[i].volChanged.rVol) { + chan[i].resRVol=VOL_SCALE_LOG(chan[i].outVol,chan[i].outRVol,0xffff); + if (!chan[i].keyOn && chan[i].active) { + pageWrite(0x00|i,0x04,chan[i].resRVol); + } + } + if (chan[i].volChanged.ca) { + pageWriteMask(0x00|i,0x5f,0x00,(chan[i].ca<<10),0x1c00); + } + } else { // mute + pageWrite(0x00|i,0x02,0); + pageWrite(0x00|i,0x04,0); + } + chan[i].volChanged.changed=0; + } + if (chan[i].pcmChanged.changed) { + if (chan[i].pcmChanged.index) { + const int next=chan[i].pcm.next; + bool sampleVaild=false; + if (((ins->amiga.useNoteMap) && (next>=0 && next<120)) || + ((!ins->amiga.useNoteMap) && (next>=0 && nextsong.sampleLen))) { + DivInstrumentAmiga::SampleMap& noteMapind=ins->amiga.noteMap[next]; + int sample=next; + if (ins->amiga.useNoteMap) { + sample=noteMapind.map; + } + if (sample>=0 && samplesong.sampleLen) { + const unsigned int offES5506=sampleOffES5506[sample]; + sampleVaild=true; + chan[i].pcm.index=sample; + chan[i].pcm.isNoteMap=ins->amiga.useNoteMap; + DivSample* s=parent->getSample(sample); + // get frequency offset + double off=1.0; + double center=(double)s->centerRate; + if (center<1) { + off=1.0; + } else { + off=(double)center/8363.0; + } + if (ins->amiga.useNoteMap) { + off*=(double)noteMapind.freq/((double)MAX(1,center)*pow(2.0,((double)next-48.0)/12.0)); + chan[i].pcm.note=next; + } + // get loop mode + DivSampleLoopMode loopMode=s->isLoopable()?s->loopMode:DIV_SAMPLE_LOOP_MAX; + const unsigned int start=offES5506<<10; + const unsigned int length=s->samples-1; + const unsigned int end=start+(length<<11); + const unsigned int nextBank=(offES5506>>22)&3; + const double nextFreqOffs=PITCH_OFFSET*off; + chan[i].pcm.loopMode=loopMode; + chan[i].pcm.bank=nextBank; + chan[i].pcm.start=start; + chan[i].pcm.end=end; + chan[i].pcm.length=length; + if ((chan[i].pcm.loopMode!=loopMode) || (chan[i].pcm.bank!=nextBank)) { + chan[i].pcm.loopMode=loopMode; + chan[i].pcm.bank=nextBank; + chan[i].pcmChanged.loopBank=1; + } + if (chan[i].pcm.nextFreqOffs!=nextFreqOffs) { + chan[i].pcm.nextFreqOffs=nextFreqOffs; + chan[i].noteChanged.offs=1; + } + } + } + if (sampleVaild) { + if (!chan[i].keyOn) { + pageWrite(0x20|i,0x03,(chan[i].pcm.direction)?chan[i].pcm.end:chan[i].pcm.start); + } + chan[i].pcmChanged.slice=1; + } + chan[i].pcmChanged.index=0; + } + if (chan[i].pcmChanged.slice) { + if (!chan[i].keyOn) { + if (chan[i].pcm.index>=0 && chan[i].pcm.indexsong.sampleLen) { + // get loop mode + DivSample* s=parent->getSample(chan[i].pcm.index); + double loopStart=(double)s->loopStart; + double loopEnd=(double)s->loopEnd; + const unsigned int start=sampleOffES5506[chan[i].pcm.index]<<10; + const unsigned int nextLoopStart=(start+(unsigned int)(loopStart*2048.0))&0xfffff800; + const unsigned int nextLoopEnd=(start+(unsigned int)((loopEnd-1.0)*2048.0))&0xffffff80; + if ((chan[i].pcm.loopStart!=nextLoopStart) || (chan[i].pcm.loopEnd!=nextLoopEnd)) { + chan[i].pcm.loopStart=nextLoopStart; + chan[i].pcm.loopEnd=nextLoopEnd; + chan[i].pcmChanged.position=1; + } + } + } + chan[i].pcmChanged.slice=0; + } + if (chan[i].pcmChanged.position) { + if (!chan[i].keyOn) { + pageWrite(0x20|i,0x01,(chan[i].pcm.loopMode==DIV_SAMPLE_LOOP_MAX)?chan[i].pcm.start:chan[i].pcm.loopStart); + pageWrite(0x20|i,0x02,(chan[i].pcm.loopMode==DIV_SAMPLE_LOOP_MAX)?chan[i].pcm.end:chan[i].pcm.loopEnd); + } + chan[i].pcmChanged.position=0; + } + if (chan[i].pcmChanged.loopBank) { + if (!chan[i].keyOn) { + unsigned int loopFlag=(chan[i].pcm.bank<<14)|(chan[i].pcm.direction?0x0040:0x0000); + chan[i].isReverseLoop=false; + switch (chan[i].pcm.loopMode) { + case DIV_SAMPLE_LOOP_FORWARD: // Forward loop + loopFlag|=0x0008; + break; + case DIV_SAMPLE_LOOP_BACKWARD: // Backward loop: IRQ enable + loopFlag|=0x0038; + chan[i].isReverseLoop=true; + break; + case DIV_SAMPLE_LOOP_PINGPONG: // Pingpong loop: Hardware support + loopFlag|=0x0018; + break; + case DIV_SAMPLE_LOOP_MAX: // no loop + default: + break; + } + // Set loop mode & Bank + pageWriteMask(0x00|i,0x5f,0x00,loopFlag,0xe0fd); + } + chan[i].pcmChanged.loopBank=0; + } + chan[i].pcmChanged.dummy=0; + } + if (chan[i].filterChanged.changed) { + if (!chan[i].keyOn) { + if (chan[i].filterChanged.mode) { + pageWriteMask(0x00|i,0x5f,0x00,(chan[i].filter.mode<<8),0x0300); + } + if (chan[i].filterChanged.k2) { + if (chan[i].std.ex2.mode!=0) { // Relative + k2=CLAMP(chan[i].filter.k2+chan[i].k2Offs,0,65535); + } else { + k2=chan[i].filter.k2; + } + } + if (chan[i].filterChanged.k1) { + if (chan[i].std.ex1.mode!=0) { // Relative + k1=CLAMP(chan[i].filter.k1+chan[i].k1Offs,0,65535); + } else { + k1=chan[i].filter.k1; + } + } + } + chan[i].filterChanged.changed=0; + } + if (chan[i].envChanged.changed) { + if (!chan[i].keyOn) { + if (chan[i].envChanged.lVRamp) { + pageWrite(0x00|i,0x03,((unsigned char)chan[i].envelope.lVRamp)<<8); + } + if (chan[i].envChanged.rVRamp) { + pageWrite(0x00|i,0x05,((unsigned char)chan[i].envelope.rVRamp)<<8); + } + if (chan[i].envChanged.ecount) { + pageWrite(0x00|i,0x06,chan[i].envelope.ecount); + } + if (chan[i].envChanged.k2Ramp) { + pageWrite(0x00|i,0x08,(((unsigned char)chan[i].envelope.k2Ramp)<<8)|(chan[i].envelope.k2Slow?1:0)); + } + if (chan[i].envChanged.k1Ramp) { + pageWrite(0x00|i,0x0a,(((unsigned char)chan[i].envelope.k1Ramp)<<8)|(chan[i].envelope.k1Slow?1:0)); + } + } + chan[i].envChanged.changed=0; + } + if (chan[i].noteChanged.changed) { // note value changed or frequency offset is changed + if (chan[i].noteChanged.offs) { + if (chan[i].pcm.freqOffs!=chan[i].pcm.nextFreqOffs) { + chan[i].pcm.freqOffs=chan[i].pcm.nextFreqOffs; + chan[i].nextFreq=NOTE_ES5506(i,chan[i].currNote); + chan[i].noteChanged.freq=1; + chan[i].freqChanged=true; + } + } + if (chan[i].noteChanged.note) { + if (chan[i].currNote!=chan[i].nextNote) { + chan[i].currNote=chan[i].nextNote; + const int nextFreq=NOTE_ES5506(i,chan[i].nextNote); + if (chan[i].nextFreq!=nextFreq) { + chan[i].nextFreq=nextFreq; + chan[i].noteChanged.freq=1; + } + } + } + if (chan[i].noteChanged.freq) { + if (chan[i].baseFreq!=chan[i].nextFreq) { + chan[i].baseFreq=chan[i].nextFreq; + chan[i].freqChanged=true; + } + } + chan[i].noteChanged.changed=0; + } + if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) { + chan[i].freq=CLAMP(parent->calcFreq(chan[i].baseFreq,chan[i].pitch,chan[i].fixedArp?chan[i].baseNoteOverride:chan[i].arpOff,chan[i].fixedArp,false,2,chan[i].pitch2,chipClock,chan[i].pcm.freqOffs),0,0x1ffff); + if (chan[i].keyOn) { + if (chan[i].pcm.index>=0 && chan[i].pcm.indexsong.sampleLen) { + unsigned int startPos=chan[i].pcm.direction?chan[i].pcm.end:chan[i].pcm.start; + if (chan[i].pcm.nextPos) { + const unsigned int start=chan[i].pcm.start; + const unsigned int end=chan[i].pcm.length; + startPos=start+((chan[i].pcm.direction?(end-chan[i].pcm.nextPos):(chan[i].pcm.nextPos))<<11); + chan[i].pcm.nextPos=0; + } + chan[i].k1Prev=0xffff; + chan[i].k2Prev=0xffff; + pageWriteMask(0x00|i,0x5f,0x00,0x0303); // Wipeout CR + pageWrite(0x00|i,0x06,0); // Clear ECOUNT + pageWrite(0x20|i,0x03,startPos); // Set ACCUM to start address + pageWrite(0x00|i,0x07,0xffff); // Set K1 and K2 to 0xffff + pageWrite(0x00|i,0x09,0xffff,~0,(chanMax+1)*4*2); // needs to 4 sample period delay + pageWrite(0x00|i,0x01,chan[i].freq); + pageWrite(0x20|i,0x01,(chan[i].pcm.loopMode==DIV_SAMPLE_LOOP_MAX)?chan[i].pcm.start:chan[i].pcm.loopStart); + pageWrite(0x20|i,0x02,(chan[i].pcm.loopMode==DIV_SAMPLE_LOOP_MAX)?chan[i].pcm.end:chan[i].pcm.loopEnd); + // initialize overwrite + if (chan[i].overwrite.state.overwrited) { + // Filter + if (chan[i].overwrite.state.mode) { + chan[i].filter.mode=chan[i].overwrite.filter.mode; + } + if (chan[i].overwrite.state.k1) { + chan[i].filter.k1=chan[i].overwrite.filter.k1; + } + if (chan[i].overwrite.state.k2) { + chan[i].filter.k2=chan[i].overwrite.filter.k2; + } + // Envelope + if (chan[i].overwrite.state.ecount) { + chan[i].envelope.ecount=chan[i].overwrite.envelope.ecount; + } + if (chan[i].overwrite.state.lVRamp) { + chan[i].envelope.lVRamp=chan[i].overwrite.envelope.lVRamp; + } + if (chan[i].overwrite.state.rVRamp) { + chan[i].envelope.rVRamp=chan[i].overwrite.envelope.rVRamp; + } + if (chan[i].overwrite.state.k1Ramp) { + chan[i].envelope.k1Ramp=chan[i].overwrite.envelope.k1Ramp; + chan[i].envelope.k1Slow=chan[i].overwrite.envelope.k1Slow; + } + if (chan[i].overwrite.state.k2Ramp) { + chan[i].envelope.k2Ramp=chan[i].overwrite.envelope.k2Ramp; + chan[i].envelope.k2Slow=chan[i].overwrite.envelope.k2Slow; + } + chan[i].overwrite.state.overwrited=0; + } + // initialize envelope + pageWrite(0x00|i,0x03,(unsigned char)(chan[i].envelope.lVRamp)<<8); + pageWrite(0x00|i,0x05,(unsigned char)(chan[i].envelope.rVRamp)<<8); + pageWrite(0x00|i,0x0a,((unsigned char)(chan[i].envelope.k1Ramp)<<8)|(chan[i].envelope.k1Slow?1:0)); + pageWrite(0x00|i,0x08,((unsigned char)(chan[i].envelope.k2Ramp)<<8)|(chan[i].envelope.k2Slow?1:0)); + // initialize filter + pageWriteMask(0x00|i,0x5f,0x00,(chan[i].pcm.bank<<14)|(chan[i].filter.mode<<8),0xc300); + if ((chan[i].std.ex2.mode!=0) && (chan[i].std.ex2.had)) { + k2=CLAMP(chan[i].filter.k2+chan[i].k2Offs,0,65535); + } else { + k2=chan[i].filter.k2; + } + pageWrite(0x00|i,0x07,k2); + chan[i].k2Prev=k2; + if ((chan[i].std.ex1.mode!=0) && (chan[i].std.ex1.had)) { + k1=CLAMP(chan[i].filter.k1+chan[i].k1Offs,0,65535); + } else { + k1=chan[i].filter.k1; + } + pageWrite(0x00|i,0x09,k1); + chan[i].k1Prev=k1; + pageWrite(0x00|i,0x02,chan[i].resLVol); + pageWrite(0x00|i,0x04,chan[i].resRVol); + unsigned int loopFlag=(chan[i].ca<<10)|(chan[i].pcm.direction?0x0040:0x0000); + chan[i].isReverseLoop=false; + switch (chan[i].pcm.loopMode) { + case DIV_SAMPLE_LOOP_FORWARD: // Forward loop + loopFlag|=0x0008; + break; + case DIV_SAMPLE_LOOP_BACKWARD: // Backward loop: IRQ enable + loopFlag|=0x0038; + chan[i].isReverseLoop=true; + break; + case DIV_SAMPLE_LOOP_PINGPONG: // Pingpong loop: Hardware support + loopFlag|=0x0018; + break; + case DIV_SAMPLE_LOOP_MAX: // no loop + default: + break; + } + if (chan[i].pcm.pause) { + loopFlag|=0x0002; + } + // Run sample + pageWrite(0x00|i,0x06,chan[i].envelope.ecount); // Clear ECOUNT + pageWriteMask(0x00|i,0x5f,0x00,loopFlag,0x3cff); + } + } + if (chan[i].keyOff) { + pageWriteMask(0x00|i,0x5f,0x00,0x0303); // Wipeout CR + } else if (!chan[i].keyOn && chan[i].active) { + pageWrite(0x00|i,0x01,chan[i].freq); + } + if (chan[i].keyOn) chan[i].keyOn=false; + if (chan[i].keyOff) chan[i].keyOff=false; + chan[i].freqChanged=false; + } + if (!chan[i].keyOn && chan[i].active) { + if (chan[i].k2Prev!=k2) { + pageWrite(0x00|i,0x07,k2); + chan[i].k2Prev=k2; + } + if (chan[i].k1Prev!=k1) { + pageWrite(0x00|i,0x09,k1); + chan[i].k1Prev=k1; + } + } + } +} + +int DivPlatformES5506::dispatch(DivCommand c) { + switch (c.cmd) { + case DIV_CMD_NOTE_ON: { + DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_ES5506); + bool sampleVaild=false; + if (((ins->amiga.useNoteMap) && (c.value>=0 && c.value<120)) || + ((!ins->amiga.useNoteMap) && (ins->amiga.initSample>=0 && ins->amiga.initSamplesong.sampleLen))) { + DivInstrumentAmiga::SampleMap& noteMapind=ins->amiga.noteMap[c.value]; + int sample=ins->amiga.initSample; + if (ins->amiga.useNoteMap) { + sample=noteMapind.map; + } + if (sample>=0 && samplesong.sampleLen) { + sampleVaild=true; + chan[c.chan].volMacroMax=ins->type==DIV_INS_AMIGA?64:0xffff; + chan[c.chan].panMacroMax=ins->type==DIV_INS_AMIGA?127:0xffff; + chan[c.chan].pcm.next=sample; + chan[c.chan].filter=ins->es5506.filter; + chan[c.chan].envelope=ins->es5506.envelope; + } + } + if (!sampleVaild) { + chan[c.chan].pcm.index=chan[c.chan].pcm.next=-1; + chan[c.chan].filter=DivInstrumentES5506::Filter(); + chan[c.chan].envelope=DivInstrumentES5506::Envelope(); + } + if (c.value!=DIV_NOTE_NULL) { + chan[c.chan].note=c.value; + chan[c.chan].nextNote=chan[c.chan].note; + chan[c.chan].pcm.nextFreqOffs=chan[c.chan].pcm.freqOffs; + chan[c.chan].freqChanged=true; + chan[c.chan].pcmChanged.changed=0xff; + chan[c.chan].noteChanged.changed=0xff; + chan[c.chan].volChanged.changed=0xff; + } + if (!chan[c.chan].std.vol.will) { + chan[c.chan].outVol=(0xffff*chan[c.chan].vol)/0xff; + } + if (!chan[c.chan].std.panL.will) { + chan[c.chan].outLVol=(0xffff*chan[c.chan].lVol)/0xff; + } + if (!chan[c.chan].std.panR.will) { + chan[c.chan].outRVol=(0xffff*chan[c.chan].rVol)/0xff; + } + chan[c.chan].active=true; + chan[c.chan].keyOn=true; + chan[c.chan].macroInit(ins); + break; + } + case DIV_CMD_NOTE_OFF: + chan[c.chan].filter=DivInstrumentES5506::Filter(); + chan[c.chan].envelope=DivInstrumentES5506::Envelope(); + chan[c.chan].active=false; + chan[c.chan].keyOff=true; + chan[c.chan].macroInit(NULL); + break; + case DIV_CMD_NOTE_OFF_ENV: + case DIV_CMD_ENV_RELEASE: + chan[c.chan].std.release(); + break; + case DIV_CMD_INSTRUMENT: + if (chan[c.chan].ins!=c.value || c.value2==1) { + chan[c.chan].ins=c.value; + } + break; + case DIV_CMD_VOLUME: + if (chan[c.chan].vol!=(unsigned int)(c.value)) { + chan[c.chan].vol=c.value; + if (!chan[c.chan].std.vol.has) { + chan[c.chan].outVol=(0xffff*c.value)/0xff; + if (!isMuted[c.chan]) { + chan[c.chan].volChanged.changed=0xff; + } + } + } + break; + case DIV_CMD_GET_VOLUME: + if (chan[c.chan].std.vol.has) { + return chan[c.chan].vol; + } + return chan[c.chan].outVol; + break; + case DIV_CMD_PANNING: { + if (chan[c.chan].ca!=0) { + chan[c.chan].ca=0; + chan[c.chan].volChanged.ca=1; + } + // Left volume + if (chan[c.chan].lVol!=(unsigned int)(c.value)) { + chan[c.chan].lVol=c.value; + if (!chan[c.chan].std.panL.has) { + chan[c.chan].outLVol=(0xffff*c.value)/0xff; + if (!isMuted[c.chan]) { + chan[c.chan].volChanged.lVol=1; + } + } + } + // Right volume + if (chan[c.chan].rVol!=(unsigned int)(c.value2)) { + chan[c.chan].rVol=c.value2; + if (!chan[c.chan].std.panR.has) { + chan[c.chan].outRVol=(0xffff*c.value2)/0xff; + if (!isMuted[c.chan]) { + chan[c.chan].volChanged.rVol=1; + } + } + } + break; + } + case DIV_CMD_SURROUND_PANNING: { + unsigned char ca=CLAMP(c.value>>1,0,5); + if (chan[c.chan].ca!=ca) { + chan[c.chan].ca=ca; + chan[c.chan].volChanged.ca=1; + } + if ((c.value&1)==0) { + // Left volume + if (chan[c.chan].lVol!=(unsigned int)(c.value2)) { + chan[c.chan].lVol=c.value2; + if (!chan[c.chan].std.panL.has) { + chan[c.chan].outLVol=(0xffff*c.value2)/0xff; + if (!isMuted[c.chan]) { + chan[c.chan].volChanged.lVol=1; + } + } + } + } + else if ((c.value&1)==1) { + // Right volume + if (chan[c.chan].rVol!=(unsigned int)(c.value2)) { + chan[c.chan].rVol=c.value2; + if (!chan[c.chan].std.panR.has) { + chan[c.chan].outRVol=(0xffff*c.value2)/0xff; + if (!isMuted[c.chan]) { + chan[c.chan].volChanged.rVol=1; + } + } + } + } + break; + } + case DIV_CMD_PITCH: + chan[c.chan].pitch=c.value; + chan[c.chan].freqChanged=true; + break; + // sample commands + case DIV_CMD_WAVE: + if (!chan[c.chan].useWave) { + if (chan[c.chan].active) { + DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_ES5506); + if (((ins->amiga.useNoteMap) && (c.value>=0 && c.value<120)) || + ((!ins->amiga.useNoteMap) && (c.value>=0 && c.valuesong.sampleLen))) { + chan[c.chan].pcm.next=c.value; + chan[c.chan].pcmChanged.index=1; + } + } + } + // reserved for useWave + break; + // Filter commands + case DIV_CMD_ES5506_FILTER_MODE: + if (!chan[c.chan].active) { + if (!chan[c.chan].overwrite.state.mode) { + chan[c.chan].overwrite.filter.mode=chan[c.chan].filter.mode; + chan[c.chan].overwrite.state.mode=1; + } + chan[c.chan].overwrite.filter.mode=DivInstrumentES5506::Filter::FilterMode(c.value&3); + } + chan[c.chan].filter.mode=DivInstrumentES5506::Filter::FilterMode(c.value&3); + chan[c.chan].filterChanged.mode=1; + break; + case DIV_CMD_ES5506_FILTER_K1: + if (!chan[c.chan].active) { + if (!chan[c.chan].overwrite.state.k1) { + chan[c.chan].overwrite.filter.k1=chan[c.chan].filter.k1; + chan[c.chan].overwrite.state.k1=1; + } + chan[c.chan].overwrite.filter.k1=(chan[c.chan].overwrite.filter.k1&~c.value2)|(c.value&c.value2); + } + chan[c.chan].filter.k1=(chan[c.chan].filter.k1&~c.value2)|(c.value&c.value2); + chan[c.chan].filterChanged.k1=1; + break; + case DIV_CMD_ES5506_FILTER_K2: + if (!chan[c.chan].active) { + if (!chan[c.chan].overwrite.state.k2) { + chan[c.chan].overwrite.filter.k2=chan[c.chan].filter.k2; + chan[c.chan].overwrite.state.k2=1; + } + chan[c.chan].overwrite.filter.k2=(chan[c.chan].overwrite.filter.k2&~c.value2)|(c.value&c.value2); + } + chan[c.chan].filter.k2=(chan[c.chan].filter.k2&~c.value2)|(c.value&c.value2); + chan[c.chan].filterChanged.k2=1; + break; + case DIV_CMD_ES5506_FILTER_K1_SLIDE: + chan[c.chan].k1Slide=c.value2?(-c.value):c.value; + break; + case DIV_CMD_ES5506_FILTER_K2_SLIDE: + chan[c.chan].k2Slide=c.value2?(-c.value):c.value; + break; + // Envelope commands + case DIV_CMD_ES5506_ENVELOPE_COUNT: + if (!chan[c.chan].active) { + if (!chan[c.chan].overwrite.state.ecount) { + chan[c.chan].overwrite.envelope.ecount=chan[c.chan].envelope.ecount; + chan[c.chan].overwrite.state.ecount=1; + } + chan[c.chan].overwrite.envelope.ecount=c.value&0x1ff; + } + chan[c.chan].envelope.ecount=c.value&0x1ff; + chan[c.chan].envChanged.ecount=1; + break; + case DIV_CMD_ES5506_ENVELOPE_LVRAMP: + if (!chan[c.chan].active) { + if (!chan[c.chan].overwrite.state.lVRamp) { + chan[c.chan].overwrite.envelope.lVRamp=chan[c.chan].envelope.lVRamp; + chan[c.chan].overwrite.state.lVRamp=1; + } + chan[c.chan].overwrite.envelope.lVRamp=(signed char)(c.value&0xff); + } + chan[c.chan].envelope.lVRamp=(signed char)(c.value&0xff); + chan[c.chan].envChanged.lVRamp=1; + break; + case DIV_CMD_ES5506_ENVELOPE_RVRAMP: + if (!chan[c.chan].active) { + if (!chan[c.chan].overwrite.state.rVRamp) { + chan[c.chan].overwrite.envelope.rVRamp=chan[c.chan].envelope.rVRamp; + chan[c.chan].overwrite.state.rVRamp=1; + } + chan[c.chan].overwrite.envelope.rVRamp=(signed char)(c.value&0xff); + } + chan[c.chan].envelope.rVRamp=(signed char)(c.value&0xff); + chan[c.chan].envChanged.rVRamp=1; + break; + case DIV_CMD_ES5506_ENVELOPE_K1RAMP: + if (!chan[c.chan].active) { + if (!chan[c.chan].overwrite.state.k1Ramp) { + chan[c.chan].overwrite.envelope.k1Ramp=chan[c.chan].envelope.k1Ramp; + chan[c.chan].overwrite.envelope.k1Slow=chan[c.chan].envelope.k1Slow; + chan[c.chan].overwrite.state.k1Ramp=1; + } + chan[c.chan].overwrite.envelope.k1Ramp=(signed char)(c.value&0xff); + chan[c.chan].overwrite.envelope.k1Slow=c.value2&1; + } + chan[c.chan].envelope.k1Ramp=(signed char)(c.value&0xff); + chan[c.chan].envelope.k1Slow=c.value2&1; + chan[c.chan].envChanged.k1Ramp=1; + break; + case DIV_CMD_ES5506_ENVELOPE_K2RAMP: + if (!chan[c.chan].active) { + if (!chan[c.chan].overwrite.state.k2Ramp) { + chan[c.chan].overwrite.envelope.k2Ramp=chan[c.chan].envelope.k2Ramp; + chan[c.chan].overwrite.envelope.k2Slow=chan[c.chan].envelope.k2Slow; + chan[c.chan].overwrite.state.k2Ramp=1; + } + chan[c.chan].overwrite.envelope.k2Ramp=(signed char)(c.value&0xff); + chan[c.chan].overwrite.envelope.k2Slow=c.value2&1; + } + chan[c.chan].envelope.k2Ramp=(signed char)(c.value&0xff); + chan[c.chan].envelope.k2Slow=c.value2&1; + chan[c.chan].envChanged.k2Ramp=1; + break; + // controls + case DIV_CMD_ES5506_PAUSE: + if (chan[c.chan].active) { + if (chan[c.chan].pcm.pause!=(bool)(c.value&1)) { + chan[c.chan].pcm.pause=c.value&1; + pageWriteMask(0x00|c.chan,0x5f,0x00,chan[c.chan].pcm.pause?0x0002:0x0000,0x0002); + } + } + break; + case DIV_CMD_NOTE_PORTA: { + int nextFreq=chan[c.chan].baseFreq; + const int destFreq=NOTE_ES5506(c.chan,c.value2); + bool return2=false; + if (destFreq>nextFreq) { + nextFreq+=c.value; + if (nextFreq>=destFreq) { + nextFreq=destFreq; + return2=true; + } + } else { + nextFreq-=c.value; + if (nextFreq<=destFreq) { + nextFreq=destFreq; + return2=true; + } + } + chan[c.chan].nextFreq=nextFreq; + chan[c.chan].noteChanged.freq=1; + if (return2) { + chan[c.chan].inPorta=false; + return 2; + } + break; + } + case DIV_CMD_LEGATO: { + chan[c.chan].note=c.value; + chan[c.chan].nextNote=chan[c.chan].note+((HACKY_LEGATO_MESS)?(chan[c.chan].std.arp.val-12):(0)); + chan[c.chan].noteChanged.note=1; + break; + } + case DIV_CMD_PRE_PORTA: + if (chan[c.chan].active && c.value2) { + if (parent->song.resetMacroOnPorta) chan[c.chan].macroInit(parent->getIns(chan[c.chan].ins,DIV_INS_ES5506)); + } + if (!chan[c.chan].inPorta && c.value && !parent->song.brokenPortaArp && chan[c.chan].std.arp.will && !NEW_ARP_STRAT) { + chan[c.chan].nextNote=chan[c.chan].note; + chan[c.chan].noteChanged.note=1; + } + chan[c.chan].inPorta=c.value; + break; + case DIV_CMD_SAMPLE_POS: { + if (chan[c.chan].useWave) break; + if (chan[c.chan].active) { + const unsigned int start=chan[c.chan].pcm.start; + const unsigned int end=chan[c.chan].pcm.length; + const unsigned int pos=chan[c.chan].pcm.direction?(end-c.value):c.value; + if ((chan[c.chan].pcm.direction && pos>0) || ((!chan[c.chan].pcm.direction) && posrate=rate; + } + + initChanMax=MAX(4,flags.getInt("channels",0x1f)&0x1f); + chanMax=initChanMax; + pageWriteMask(0x00,0x60,0x0b,chanMax); +} + +void DivPlatformES5506::poke(unsigned int addr, unsigned short val) { + immWrite(addr, val); +} + +void DivPlatformES5506::poke(std::vector& wlist) { + for (DivRegWrite& i: wlist) immWrite(i.addr,i.val); +} + +DivDispatchOscBuffer* DivPlatformES5506::getOscBuffer(int ch) { + return oscBuf[ch]; +} + +unsigned char* DivPlatformES5506::getRegisterPool() { + unsigned char* regPoolPtr = regPool; + for (unsigned char p=0; p<128; p++) { + for (unsigned char r=0; r<16; r++) { + unsigned int reg=es5506.regs_r(p,r,false); + for (int b=0; b<4; b++) { + *regPoolPtr++ = reg>>(24-(b<<3)); + } + } + } + return regPool; +} + +int DivPlatformES5506::getRegisterPoolSize() { + return 4*16*128; // 7 bit page x 16 registers per page x 32 bit per registers +} +const void* DivPlatformES5506::getSampleMem(int index) { + return index == 0 ? sampleMem : NULL; +} + +size_t DivPlatformES5506::getSampleMemCapacity(int index) { + return index == 0 ? 16777216 : 0; // 2Mword x 16bit * 4 banks +} + +size_t DivPlatformES5506::getSampleMemUsage(int index) { + return index == 0 ? sampleMemLen : 0; +} + +bool DivPlatformES5506::isSampleLoaded(int index, int sample) { + if (index!=0) return false; + if (sample<0 || sample>255) return false; + return sampleLoaded[sample]; +} + +void DivPlatformES5506::renderSamples(int sysID) { + memset(sampleMem,0,getSampleMemCapacity()); + memset(sampleOffES5506,0,256*sizeof(unsigned int)); + memset(sampleLoaded,0,256*sizeof(bool)); + + size_t memPos=128; // add silent at begin and end of each bank for reverse playback + for (int i=0; isong.sampleLen; i++) { + DivSample* s=parent->song.sample[i]; + if (!s->renderOn[0][sysID]) { + sampleOffES5506[i]=0; + continue; + } + + unsigned int length=s->length16; + // fit sample size to single bank size + if (length>(4194304-128)) { + length=4194304-128; + } + if ((memPos&0xc00000)!=((memPos+length+128)&0xc00000)) { + memPos=((memPos+0x3fffff)&0xc00000)+128; + } + if (memPos>=(getSampleMemCapacity()-128)) { + logW("out of ES5506 memory for sample %d!",i); + break; + } + if (memPos+length>=(getSampleMemCapacity()-128)) { + memcpy(sampleMem+(memPos/sizeof(short)),s->data16,(getSampleMemCapacity()-128)-memPos); + logW("out of ES5506 memory for sample %d!",i); + } else { + memcpy(sampleMem+(memPos/sizeof(short)),s->data16,length); + } + sampleOffES5506[i]=memPos; + sampleLoaded[i]=true; + memPos+=length; + } + sampleMemLen=memPos+256; +} + +int DivPlatformES5506::init(DivEngine* p, int channels, int sugRate, const DivConfig& flags) { + sampleMem=new signed short[getSampleMemCapacity()/sizeof(short)]; + sampleMemLen=0; + parent=p; + dumpWrites=false; + skipRegisterWrites=false; + + for (int i=0; i<32; i++) { + isMuted[i]=false; + oscBuf[i]=new DivDispatchOscBuffer; + } + + setFlags(flags); + + reset(); + return 32; +} + +void DivPlatformES5506::quit() { + delete[] sampleMem; + for (int i=0; i<32; i++) { + delete oscBuf[i]; + } +} diff --git a/src/engine/platform/es5506.h b/src/engine/platform/es5506.h new file mode 100644 index 000000000..d5ac2b9ba --- /dev/null +++ b/src/engine/platform/es5506.h @@ -0,0 +1,329 @@ +/** + * Furnace Tracker - multi-system chiptune tracker + * Copyright (C) 2021-2023 tildearrow and contributors + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef _ES5506_H +#define _ES5506_H + +#pragma once + +#include "../dispatch.h" +#include "../engine.h" +#include +#include "../macroInt.h" +#include "../sample.h" +#include "vgsound_emu/src/es550x/es5506.hpp" + +class DivPlatformES5506: public DivDispatch, public es550x_intf { + struct Channel : public SharedChannel { + struct PCM { + bool isNoteMap; + int index, next; + int note; + double freqOffs; + double nextFreqOffs; + bool pause, direction; + unsigned int bank; + unsigned int start; + unsigned int end; + unsigned int length; + unsigned int loopStart; + unsigned int loopEnd; + unsigned int nextPos; + DivSampleLoopMode loopMode; + PCM(): + isNoteMap(false), + index(-1), + next(-1), + note(0), + freqOffs(1.0), + nextFreqOffs(1.0), + pause(false), + direction(false), + bank(0), + start(0), + end(0), + length(0), + loopStart(0), + loopEnd(0), + nextPos(0), + loopMode(DIV_SAMPLE_LOOP_MAX) {} + } pcm; + int nextFreq, nextNote, currNote, wave; + unsigned int volMacroMax, panMacroMax; + bool useWave, isReverseLoop; + unsigned int cr; + + struct NoteChanged { // Note changed flags + union { // pack flag bits in single byte + struct { // flag bits + unsigned char offs: 1; // frequency offset + unsigned char note: 1; // note + unsigned char freq: 1; // base frequency + unsigned char dummy: 5; // dummy for bit padding + }; + unsigned char changed; // Packed flags are stored here + }; + + NoteChanged() : + changed(0) {} + } noteChanged; + + struct VolChanged { // Volume changed flags + union { // pack flag bits in single byte + struct { // flag bits + unsigned char lVol: 1; // left volume + unsigned char rVol: 1; // right volume + unsigned char ca: 1; // Channel assignment + unsigned char dummy: 5; // dummy for bit padding + }; + unsigned char changed; // Packed flags are stored here + }; + + VolChanged() : + changed(0) {} + } volChanged; + + struct FilterChanged { // Filter changed flags + union { // pack flag bits in single byte + struct { // flag bits + unsigned char mode: 1; // Filter mode + unsigned char k1: 1; // K1 + unsigned char k2: 1; // K2 + unsigned char dummy: 5; // dummy for bit padding + }; + unsigned char changed; // Packed flags are stored here + }; + + FilterChanged(): + changed(0) {} + } filterChanged; + + struct EnvChanged { // Envelope changed flags + union { // pack flag bits in single byte + struct { // flag bits + unsigned char ecount: 1; // Envelope count + unsigned char lVRamp: 1; // Left volume Ramp + unsigned char rVRamp: 1; // Right volume Ramp + unsigned char k1Ramp: 1; // K1 Ramp w/Slow flag + unsigned char k2Ramp: 1; // K2 Ramp w/Slow flag + unsigned char dummy: 3; // dummy for bit padding + }; + unsigned char changed; // Packed flags are stored here + }; + + EnvChanged(): + changed(0) {} + } envChanged; + + struct PCMChanged { + union { + struct { + unsigned char index: 1; // sample index + unsigned char slice: 1; // transwave slice + unsigned char position: 1; // sample position in memory + unsigned char loopBank: 1; // Loop mode and Bank + unsigned char dummy: 4; // dummy for bit padding + }; + unsigned char changed; + }; + PCMChanged(): + changed(0) {} + } pcmChanged; + + struct Overwrite { + DivInstrumentES5506::Filter filter; + DivInstrumentES5506::Envelope envelope; + + struct State { + // overwrited flag + union { + struct { + unsigned char mode: 1; // filter mode + unsigned char k1: 1; // k1 + unsigned char k2: 1; // k2 + unsigned char ecount: 1; // envelope count + unsigned char lVRamp: 1; // left volume ramp + unsigned char rVRamp: 1; // right volume ramp + unsigned char k1Ramp: 1; // k1 ramp + unsigned char k2Ramp: 1; // k2 ramp + }; + unsigned char overwrited; + }; + State(): + overwrited(0) {} + } state; + + Overwrite(): + filter(DivInstrumentES5506::Filter()), + envelope(DivInstrumentES5506::Envelope()), + state(State()) {} + } overwrite; + + unsigned char ca; + signed int k1Offs, k2Offs; + signed int k1Slide, k2Slide; + signed int k1Prev, k2Prev; + unsigned int lVol, rVol; + unsigned int outLVol, outRVol; + unsigned int resLVol, resRVol; + signed int oscOut; + DivInstrumentES5506::Filter filter; + DivInstrumentES5506::Envelope envelope; + Channel(): + SharedChannel(0xff), + pcm(PCM()), + nextFreq(0), + nextNote(0), + currNote(0), + wave(-1), + volMacroMax(0xffff), + panMacroMax(0xffff), + useWave(false), + isReverseLoop(false), + cr(0), + noteChanged(NoteChanged()), + volChanged(VolChanged()), + filterChanged(FilterChanged()), + envChanged(EnvChanged()), + pcmChanged(PCMChanged()), + overwrite(Overwrite()), + ca(0), + k1Offs(0), + k2Offs(0), + k1Slide(0), + k2Slide(0), + k1Prev(0xffff), + k2Prev(0xffff), + lVol(0xff), + rVol(0xff), + outLVol(0xffff), + outRVol(0xffff), + resLVol(0xffff), + resRVol(0xffff), + oscOut(0), + filter(DivInstrumentES5506::Filter()), + envelope(DivInstrumentES5506::Envelope()) { + outVol=0xffff; + } + }; + Channel chan[32]; + DivDispatchOscBuffer* oscBuf[32]; + bool isMuted[32]; + signed short* sampleMem; // ES5506 uses 16 bit data bus for samples + size_t sampleMemLen; + unsigned int sampleOffES5506[256]; + bool sampleLoaded[256]; + struct QueuedHostIntf { + unsigned char state; + unsigned char step; + unsigned char addr; + unsigned int val; + unsigned int mask; + unsigned int* read; + unsigned short delay; + bool isRead; + QueuedHostIntf(unsigned char s, unsigned char a, unsigned int v, unsigned int m=(unsigned int)(~0), unsigned short d=0): + state(0), + step(s), + addr(a), + val(v), + mask(m), + read(NULL), + delay(0), + isRead(false) {} + QueuedHostIntf(unsigned char st, unsigned char s, unsigned char a, unsigned int* r, unsigned int m=(unsigned int)(~0), unsigned short d=0): + state(st), + step(s), + addr(a), + val(0), + mask(m), + read(r), + delay(d), + isRead(true) {} + }; + struct QueuedReadState { + unsigned int* read; + unsigned char state; + QueuedReadState(unsigned int* r, unsigned char s): + read(r), + state(s) {} + }; + std::queue hostIntf32; + std::queue hostIntf8; + std::queue queuedRead; + std::queue queuedReadState; + int cycle, curPage; + unsigned char maskedVal; + unsigned int irqv; + bool isMasked, isReaded; + bool irqTrigger; + unsigned int curCR; + unsigned char prevChanCycle; + + unsigned char initChanMax, chanMax; + + es5506_core es5506; + unsigned char regPool[4*16*128]; // 7 bit page x 16 registers per page x 32 bit per registers + + friend void putDispatchChip(void*,int); + friend void putDispatchChan(void*,int,int); + + public: + virtual void e_pin(bool state) override; // E output + + virtual void irqb(bool state) override; // IRQB output + virtual s16 read_sample(u8 voice, u8 bank, u32 address) override { + if (sampleMem==NULL) return 0; + return sampleMem[((bank&3)<<21)|(address&0x1fffff)]; + } + + virtual void acquire(short** buf, size_t len) override; + virtual int dispatch(DivCommand c) override; + virtual void* getChanState(int chan) override; + virtual DivMacroInt* getChanMacroInt(int ch) override; + virtual DivDispatchOscBuffer* getOscBuffer(int chan) override; + virtual unsigned char* getRegisterPool() override; + virtual int getRegisterPoolSize() override; + virtual void reset() override; + virtual void forceIns() override; + virtual void tick(bool sysTick=true) override; + virtual void muteChannel(int ch, bool mute) override; + virtual int getOutputCount() override; + virtual bool keyOffAffectsArp(int ch) override; + virtual void setFlags(const DivConfig& flags) override; + virtual void notifyInsChange(int ins) override; + virtual void notifyWaveChange(int wave) override; + virtual void notifyInsDeletion(void* ins) override; + virtual void poke(unsigned int addr, unsigned short val) override; + virtual void poke(std::vector& wlist) override; + virtual const void* getSampleMem(int index = 0) override; + virtual size_t getSampleMemCapacity(int index = 0) override; + virtual size_t getSampleMemUsage(int index = 0) override; + virtual bool isSampleLoaded(int index, int sample) override; + virtual void renderSamples(int sysID) override; + virtual const char** getRegisterSheet() override; + virtual int init(DivEngine* parent, int channels, int sugRate, const DivConfig& flags) override; + virtual void quit() override; + DivPlatformES5506(): + DivDispatch(), + es550x_intf(), + es5506(*this) {} +}; + +#endif diff --git a/src/engine/platform/genesis.cpp b/src/engine/platform/genesis.cpp index 05d3c08be..188b258bd 100644 --- a/src/engine/platform/genesis.cpp +++ b/src/engine/platform/genesis.cpp @@ -681,7 +681,10 @@ int DivPlatformGenesis::dispatch(DivCommand c) { break; } else { rWrite(0x2b,1<<7); - if (dumpWrites) addWrite(0xffff0000,chan[c.chan].dacSample); + if (dumpWrites) { + addWrite(0xffff0000,chan[c.chan].dacSample); + addWrite(0xffff0003,chan[c.chan].dacDirection); + } } chan[c.chan].dacPos=0; chan[c.chan].dacPeriod=0; diff --git a/src/engine/playback.cpp b/src/engine/playback.cpp index 823b2f8a9..6837decfc 100644 --- a/src/engine/playback.cpp +++ b/src/engine/playback.cpp @@ -217,6 +217,18 @@ const char* cmdName[]={ "FM_AM2_DEPTH", "FM_PM2_DEPTH", + "ES5506_FILTER_MODE", + "ES5506_FILTER_K1", + "ES5506_FILTER_K2", + "ES5506_FILTER_K1_SLIDE", + "ES5506_FILTER_K2_SLIDE", + "ES5506_ENVELOPE_COUNT", + "ES5506_ENVELOPE_LVRAMP", + "ES5506_ENVELOPE_RVRAMP", + "ES5506_ENVELOPE_K1RAMP", + "ES5506_ENVELOPE_K2RAMP", + "ES5506_PAUSE", + "ALWAYS_SET_VOLUME" }; @@ -1508,8 +1520,7 @@ void DivEngine::nextBuf(float** in, float** out, int inChans, int outChans, unsi samp_temp=s->data16[sPreview.pos]; if (sPreview.dir) { sPreview.pos--; - } - else { + } else { sPreview.pos++; } } diff --git a/src/engine/sysDef.cpp b/src/engine/sysDef.cpp index ba62b923d..9cce4e095 100644 --- a/src/engine/sysDef.cpp +++ b/src/engine/sysDef.cpp @@ -407,6 +407,10 @@ template int effectValAnd(unsigned char, unsigned char val) { return val&mask; }; +template int effectValShift(unsigned char, unsigned char val) { + return val< int effectOpVal(unsigned char, unsigned char val) { if ((val>>4)>maxOp) throw DivDoNotHandleEffect(); return (val>>4)-1; @@ -421,6 +425,10 @@ template int effectValLong(unsigned char cmd, unsigned char val) return ((((unsigned int)cmd)&((1<<(bits-8))-1))<<8)|((unsigned int)val); }; +template int effectValLongShift(unsigned char cmd, unsigned char val) { + return (((((unsigned int)cmd)&((1<<(bits-8))-1))<<8)|((unsigned int)val))<}}, + {0x14, {DIV_CMD_ES5506_FILTER_K1, "14xx: Set filter coefficient K1 low byte (00 to FF)",effectValShift<0>,constVal<0x00ff>}}, + {0x15, {DIV_CMD_ES5506_FILTER_K1, "15xx: Set filter coefficient K1 high byte (00 to FF)",effectValShift<8>,constVal<0xff00>}}, + {0x16, {DIV_CMD_ES5506_FILTER_K2, "16xx: Set filter coefficient K2 low byte (00 to FF)",effectValShift<0>,constVal<0x00ff>}}, + {0x17, {DIV_CMD_ES5506_FILTER_K2, "17xx: Set filter coefficient K2 high byte (00 to FF)",effectValShift<8>,constVal<0xff00>}}, + {0x18, {DIV_CMD_ES5506_FILTER_K1_SLIDE, "18xx: Set filter coefficient K1 slide up (00 to FF)",effectVal,constVal<0>}}, + {0x19, {DIV_CMD_ES5506_FILTER_K1_SLIDE, "19xx: Set filter coefficient K1 slide down (00 to FF)",effectVal,constVal<1>}}, + {0x1a, {DIV_CMD_ES5506_FILTER_K2_SLIDE, "1Axx: Set filter coefficient K2 slide up (00 to FF)",effectVal,constVal<0>}}, + {0x1b, {DIV_CMD_ES5506_FILTER_K2_SLIDE, "1Bxx: Set filter coefficient K2 slide down (00 to FF)",effectVal,constVal<1>}}, + {0x22, {DIV_CMD_ES5506_ENVELOPE_LVRAMP, "22xx: Set envelope left volume ramp (signed) (00 to FF)",effectVal}}, + {0x23, {DIV_CMD_ES5506_ENVELOPE_RVRAMP, "23xx: Set envelope right volume ramp (signed) (00 to FF)",effectVal}}, + {0x24, {DIV_CMD_ES5506_ENVELOPE_K1RAMP, "24xx: Set envelope filter coefficient k1 ramp (signed) (00 to FF)",effectVal,constVal<0>}}, + {0x25, {DIV_CMD_ES5506_ENVELOPE_K1RAMP, "25xx: Set envelope filter coefficient k1 ramp (signed, slower) (00 to FF)",effectVal,constVal<1>}}, + {0x26, {DIV_CMD_ES5506_ENVELOPE_K2RAMP, "26xx: Set envelope filter coefficient k2 ramp (signed) (00 to FF)",effectVal,constVal<0>}}, + {0x27, {DIV_CMD_ES5506_ENVELOPE_K2RAMP, "27xx: Set envelope filter coefficient k2 ramp (signed, slower) (00 to FF)",effectVal,constVal<1>}}, + {0xdf, {DIV_CMD_SAMPLE_DIR, "DFxx: Set sample playback direction (0: normal; 1: reverse)"}} + }; + EffectHandlerMap es5506PostEffectHandlerMap={ + {0x12, {DIV_CMD_ES5506_PAUSE, "120x: Set pause (bit 0)",effectValAnd<1>}} + }; + const EffectHandler es5506ECountHandler(DIV_CMD_ES5506_ENVELOPE_COUNT, "2xxx: Set envelope count (000 to 1FF)", effectValLong<9>); + const EffectHandler es5506K1Handler(DIV_CMD_ES5506_FILTER_K1, "3xxx: Set filter coefficient K1 (000 to FFF)", effectValLongShift<12,4>,constVal<0xfff0>); + const EffectHandler es5506K2Handler(DIV_CMD_ES5506_FILTER_K2, "4xxx: Set filter coefficient K2 (000 to FFF)", effectValLongShift<12,4>,constVal<0xfff0>); + for (int i=0; i<2; i++) es5506PreEffectHandlerMap.emplace(0x20+i,es5506ECountHandler); + for (int i=0; i<16; i++) es5506PreEffectHandlerMap.emplace(0x30+i, es5506K1Handler); + for (int i=0; i<16; i++) es5506PreEffectHandlerMap.emplace(0x40+i, es5506K2Handler); + + // TODO: custom sample format sysDefs[DIV_SYSTEM_ES5506]=new DivSysDef( - "Ensoniq ES5506", NULL, 0xb1, 0, 32, false, true, 0, false, (1U<writeC(0xd6+i); } break; + case DIV_SYSTEM_ES5506: + for (int i=0; i<32; i++) { + for (int b=0; b<4; b++) { + w->writeC(0xbe); + w->writeC((0xf<<2)+b); + w->writeC(i); + } + unsigned int init_cr=0x0303; + for (int b=0; b<4; b++) { + w->writeC(0xbe); + w->writeC(b); + w->writeC(init_cr>>(24-(b<<3))); + } + for (int r=1; r<11; r++) { + for (int b=0; b<4; b++) { + w->writeC(0xbe); + w->writeC((r<<2)+b); + w->writeC(((r==7 || r==9) && b&2)?0xff:0); + } + } + for (int b=0; b<4; b++) { + w->writeC(0xbe); + w->writeC((0xf<<2)+b); + w->writeC(0x20|i); + } + for (int r=1; r<10; r++) { + for (int b=0; b<4; b++) { + w->writeC(0xbe); + w->writeC((r<<2)+b); + w->writeC(0); + } + } + } + break; case DIV_SYSTEM_OPL: case DIV_SYSTEM_OPL_DRUMS: // disable envelope @@ -758,6 +792,11 @@ void DivEngine::performVGMWrite(SafeWriter* w, DivSystem sys, DivRegWrite& write w->writeC(write.val&0xff); } break; + case DIV_SYSTEM_ES5506: + w->writeC(0xbe); + w->writeC(write.addr&0xff); + w->writeC(write.val&0xff); + break; case DIV_SYSTEM_VBOY: w->writeC(0xc7); w->writeS_BE(baseAddr2S|(write.addr>>2)); @@ -1034,6 +1073,7 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version, bool p int writeSegaPCM=0; DivDispatch* writeX1010[2]={NULL,NULL}; DivDispatch* writeQSound[2]={NULL,NULL}; + DivDispatch* writeES5506[2]={NULL,NULL}; DivDispatch* writeZ280[2]={NULL,NULL}; DivDispatch* writeRF5C68[2]={NULL,NULL}; DivDispatch* writeMSM6295[2]={NULL,NULL}; @@ -1383,6 +1423,20 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version, bool p howManyChips++; } break; + case DIV_SYSTEM_ES5506: + if (!hasES5505) { + // VGM identifies ES5506 if highest bit sets, otherwise ES5505 + hasES5505=0x80000000|disCont[i].dispatch->chipClock; + willExport[i]=true; + writeES5506[0]=disCont[i].dispatch; + } else if (!(hasES5505&0x40000000)) { + isSecond[i]=true; + willExport[i]=false; + hasES5505|=0xc0000000; + writeES5506[1]=disCont[i].dispatch; + howManyChips++; + } + break; case DIV_SYSTEM_VBOY: if (!hasVSU) { hasVSU=disCont[i].dispatch->chipClock; @@ -1684,7 +1738,7 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version, bool p w->writeI(hasES5503); w->writeI(hasES5505); w->writeC(0); // 5503 chans - w->writeC(0); // 5505 chans + w->writeC(hasES5505?1:0); // 5505 chans w->writeC(0); // C352 clock divider w->writeC(0); // reserved w->writeI(hasX1); @@ -1807,7 +1861,7 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version, bool p if (memPos>=16777216) break; } - for (int i=0; iwriteC(0x67); w->writeC(0x66); w->writeC(0x80); @@ -1933,6 +1987,19 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version, bool p } } + // TODO + for (int i=0; i<2; i++) { + if (writeES5506[i]!=NULL && writeES5506[i]->getSampleMemUsage()>0) { + w->writeC(0x67); + w->writeC(0x66); + w->writeC(0x8F); + w->writeI((writeES5506[i]->getSampleMemUsage()+8)|(i*0x80000000)); + w->writeI(writeES5506[i]->getSampleMemCapacity()); + w->writeI(0); + w->write(writeES5506[i]->getSampleMem(),writeES5506[i]->getSampleMemUsage()); + } + } + // initialize streams int streamID=0; if (!directStream) { diff --git a/src/gui/debug.cpp b/src/gui/debug.cpp index c9093188c..ddb1bb01c 100644 --- a/src/gui/debug.cpp +++ b/src/gui/debug.cpp @@ -46,6 +46,7 @@ #include "../engine/platform/x1_010.h" #include "../engine/platform/n163.h" #include "../engine/platform/vrc6.h" +#include "../engine/platform/es5506.h" #include "../engine/platform/lynx.h" #include "../engine/platform/pcmdac.h" #include "../engine/platform/k007232.h" @@ -491,6 +492,24 @@ void putDispatchChip(void* data, int type) { ImGui::TextColored(ch->outStereo?colorOn:colorOff,">> OutStereo"); break; } + case DIV_SYSTEM_ES5506: { + DivPlatformES5506* ch=(DivPlatformES5506*)data; + ImGui::Text("> ES5506"); + COMMON_CHIP_DEBUG; + ImGui::Text("- cycle: %d",ch->cycle); + ImGui::Text("- curPage: %d",ch->curPage); + ImGui::Text("- maskedVal: %.2x",ch->maskedVal); + ImGui::Text("- irqv: %.2x",ch->irqv); + ImGui::Text("- curCR: %.8x",ch->curCR); + ImGui::Text("- prevChanCycle: %d",ch->prevChanCycle); + ImGui::Text("- initChanMax: %d",ch->initChanMax); + ImGui::Text("- chanMax: %d",ch->chanMax); + COMMON_CHIP_DEBUG_BOOL; + ImGui::TextColored(ch->isMasked?colorOn:colorOff,">> IsMasked"); + ImGui::TextColored(ch->isReaded?colorOn:colorOff,">> isReaded"); + ImGui::TextColored(ch->irqTrigger?colorOn:colorOff,">> IrqTrigger"); + break; + } case DIV_SYSTEM_K007232: { DivPlatformK007232* ch=(DivPlatformK007232*)data; ImGui::Text("> K007232"); @@ -861,6 +880,97 @@ void putDispatchChan(void* data, int chanNum, int type) { ImGui::TextColored(ch->furnaceDac?colorOn:colorOff,">> FurnaceDAC"); break; } + case DIV_SYSTEM_ES5506: { + DivPlatformES5506::Channel* ch=(DivPlatformES5506::Channel*)data; + ImGui::Text("> ES5506"); + COMMON_CHAN_DEBUG; + ImGui::Text("- nextFreq: %d",ch->nextFreq); + ImGui::Text("- nextNote: %d",ch->nextNote); + ImGui::Text("- currNote: %d",ch->currNote); + ImGui::Text("- wave: %d",ch->wave); + ImGui::Text("- VolMacroMax: %d",ch->volMacroMax); + ImGui::Text("- PanMacroMax: %d",ch->panMacroMax); + ImGui::Text("* PCM:"); + ImGui::Text(" * index: %d",ch->pcm.index); + ImGui::Text(" - next: %d",ch->pcm.next); + ImGui::Text(" - note: %d",ch->pcm.note); + ImGui::Text(" * freqOffs: %.6f",ch->pcm.freqOffs); + ImGui::Text(" - next: %.6f",ch->pcm.nextFreqOffs); + ImGui::Text(" - bank: %.2x",ch->pcm.bank); + ImGui::Text(" - start: %.8x",ch->pcm.start); + ImGui::Text(" - end: %.8x",ch->pcm.end); + ImGui::Text(" - length: %.8x",ch->pcm.length); + ImGui::Text(" - loopStart: %.8x",ch->pcm.loopStart); + ImGui::Text(" - loopEnd: %.8x",ch->pcm.loopEnd); + ImGui::Text(" - loopMode: %d",ch->pcm.loopMode); + ImGui::Text(" - nextPos: %d",ch->pcm.nextPos); + ImGui::Text("* Filter:"); + ImGui::Text(" - Mode: %d",ch->filter.mode); + ImGui::Text(" - K1: %.4x",ch->filter.k1); + ImGui::Text(" - K2: %.4x",ch->filter.k2); + ImGui::Text("* Envelope:"); + ImGui::Text(" - EnvCount: %.3x",ch->envelope.ecount); + ImGui::Text(" - LVRamp: %d",ch->envelope.lVRamp); + ImGui::Text(" - RVRamp: %d",ch->envelope.rVRamp); + ImGui::Text(" - K1Ramp: %d",ch->envelope.k1Ramp); + ImGui::Text(" - K2Ramp: %d",ch->envelope.k2Ramp); + ImGui::Text(" - K1Offs: %d",ch->k1Offs); + ImGui::Text(" - K2Offs: %d",ch->k2Offs); + ImGui::Text(" - K1Slide: %d",ch->k1Slide); + ImGui::Text(" - K2Slide: %d",ch->k2Slide); + ImGui::Text(" - K1Prev: %.4x",ch->k1Prev); + ImGui::Text(" - K2Prev: %.4x",ch->k2Prev); + ImGui::Text("* Overwrite:"); + ImGui::Text(" * Filter:"); + ImGui::Text(" - Mode: %d",ch->overwrite.filter.mode); + ImGui::Text(" - K1: %.4x",ch->overwrite.filter.k1); + ImGui::Text(" - K2: %.4x",ch->overwrite.filter.k2); + ImGui::Text(" * Envelope:"); + ImGui::Text(" - EnvCount: %.3x",ch->overwrite.envelope.ecount); + ImGui::Text(" - LVRamp: %d",ch->overwrite.envelope.lVRamp); + ImGui::Text(" - RVRamp: %d",ch->overwrite.envelope.rVRamp); + ImGui::Text(" - K1Ramp: %d",ch->overwrite.envelope.k1Ramp); + ImGui::Text(" - K2Ramp: %d",ch->overwrite.envelope.k2Ramp); + ImGui::Text("- CA: %.2x",ch->ca); + ImGui::Text("- LVol: %.2x",ch->lVol); + ImGui::Text("- RVol: %.2x",ch->rVol); + ImGui::Text("- outLVol: %.2x",ch->outLVol); + ImGui::Text("- outRVol: %.2x",ch->outRVol); + ImGui::Text("- ResLVol: %.2x",ch->resLVol); + ImGui::Text("- ResRVol: %.2x",ch->resRVol); + ImGui::Text("- oscOut: %d",ch->oscOut); + COMMON_CHAN_DEBUG_BOOL; + ImGui::TextColored(ch->volChanged.lVol?colorOn:colorOff,">> LVolChanged"); + ImGui::TextColored(ch->volChanged.rVol?colorOn:colorOff,">> RVolChanged"); + ImGui::TextColored(ch->filterChanged.mode?colorOn:colorOff,">> FilterModeChanged"); + ImGui::TextColored(ch->filterChanged.k1?colorOn:colorOff,">> FilterK1Changed"); + ImGui::TextColored(ch->filterChanged.k2?colorOn:colorOff,">> FilterK2Changed"); + ImGui::TextColored(ch->envChanged.ecount?colorOn:colorOff,">> EnvECountChanged"); + ImGui::TextColored(ch->envChanged.lVRamp?colorOn:colorOff,">> EnvLVRampChanged"); + ImGui::TextColored(ch->envChanged.rVRamp?colorOn:colorOff,">> EnvRVRampChanged"); + ImGui::TextColored(ch->envChanged.k1Ramp?colorOn:colorOff,">> EnvK1RampChanged"); + ImGui::TextColored(ch->envChanged.k2Ramp?colorOn:colorOff,">> EnvK2RampChanged"); + ImGui::TextColored(ch->pcmChanged.index?colorOn:colorOff,">> PCMIndexChanged"); + ImGui::TextColored(ch->pcmChanged.slice?colorOn:colorOff,">> PCMSliceChanged"); + ImGui::TextColored(ch->pcmChanged.position?colorOn:colorOff,">> PCMPositionChanged"); + ImGui::TextColored(ch->pcmChanged.loopBank?colorOn:colorOff,">> PCMLoopBankChanged"); + ImGui::TextColored(ch->isReverseLoop?colorOn:colorOff,">> IsReverseLoop"); + ImGui::TextColored(ch->pcm.pause?colorOn:colorOff,">> PCMPause"); + ImGui::TextColored(ch->pcm.direction?colorOn:colorOff,">> PCMDirection"); + ImGui::TextColored(ch->envelope.k1Slow?colorOn:colorOff,">> EnvK1Slow"); + ImGui::TextColored(ch->envelope.k2Slow?colorOn:colorOff,">> EnvK2Slow"); + ImGui::TextColored(ch->overwrite.envelope.k1Slow?colorOn:colorOff,">> EnvK1SlowOverwrite"); + ImGui::TextColored(ch->overwrite.envelope.k2Slow?colorOn:colorOff,">> EnvK2SlowOverwrite"); + ImGui::TextColored(ch->overwrite.state.mode?colorOn:colorOff,">> FilterModeOverwrited"); + ImGui::TextColored(ch->overwrite.state.k1?colorOn:colorOff,">> FilterK1Overwrited"); + ImGui::TextColored(ch->overwrite.state.k2?colorOn:colorOff,">> FilterK2Overwrited"); + ImGui::TextColored(ch->overwrite.state.ecount?colorOn:colorOff,">> EnvECountOverwrited"); + ImGui::TextColored(ch->overwrite.state.lVRamp?colorOn:colorOff,">> EnvLVRampOverwrited"); + ImGui::TextColored(ch->overwrite.state.rVRamp?colorOn:colorOff,">> EnvRVRampOverwrited"); + ImGui::TextColored(ch->overwrite.state.k1Ramp?colorOn:colorOff,">> EnvK1RampOverwrited"); + ImGui::TextColored(ch->overwrite.state.k2Ramp?colorOn:colorOff,">> EnvK2RampOverwrited"); + break; + } case DIV_SYSTEM_LYNX: { DivPlatformLynx::Channel* ch=(DivPlatformLynx::Channel*)data; ImGui::Text("> Lynx"); diff --git a/src/gui/debugWindow.cpp b/src/gui/debugWindow.cpp index c07c000bb..af2757359 100644 --- a/src/gui/debugWindow.cpp +++ b/src/gui/debugWindow.cpp @@ -191,6 +191,7 @@ void FurnaceGUI::drawDebug() { ImGui::Text("loopMode: %d ()",(unsigned char)sample->loopMode); } + ImGui::Text("depth: %d",(unsigned char)sample->depth); ImGui::Text("length8: %d",sample->length8); ImGui::Text("length16: %d",sample->length16); ImGui::Text("length1: %d",sample->length1); diff --git a/src/gui/guiConst.cpp b/src/gui/guiConst.cpp index 80a76c43d..0735df06c 100644 --- a/src/gui/guiConst.cpp +++ b/src/gui/guiConst.cpp @@ -984,6 +984,7 @@ const int availableSystems[]={ DIV_SYSTEM_VRC6, DIV_SYSTEM_FDS, DIV_SYSTEM_MMC5, + DIV_SYSTEM_ES5506, DIV_SYSTEM_SCC, DIV_SYSTEM_SCC_PLUS, DIV_SYSTEM_YMZ280B, @@ -1101,6 +1102,7 @@ const int chipsSample[]={ DIV_SYSTEM_K007232, DIV_SYSTEM_GA20, DIV_SYSTEM_PCM_DAC, + DIV_SYSTEM_ES5506, 0 // don't remove this last one! }; diff --git a/src/gui/insEdit.cpp b/src/gui/insEdit.cpp index 1d93cb671..fb19b4ba9 100644 --- a/src/gui/insEdit.cpp +++ b/src/gui/insEdit.cpp @@ -295,8 +295,8 @@ const char* es5506EnvelopeModes[3]={ "k1 slowdown", "k2 slowdown", NULL }; -const char* es5506ControlModes[2]={ - "pause", NULL +const char* es5506ControlModes[3]={ + "pause", "reverse", NULL }; const int orderedOps[4]={ @@ -4326,7 +4326,9 @@ void FurnaceGUI::drawInsEdit() { } ImGui::EndCombo(); } + // Wavetable if (ins->type==DIV_INS_AMIGA || ins->type==DIV_INS_SNES) { + ImGui::BeginDisabled(ins->amiga.useNoteMap); P(ImGui::Checkbox("Use wavetable (Amiga/SNES/Generic DAC only)",&ins->amiga.useWave)); if (ins->amiga.useWave) { int len=ins->amiga.waveLen+1; @@ -4348,12 +4350,14 @@ void FurnaceGUI::drawInsEdit() { PARAMETER } } + ImGui::EndDisabled(); } + // Note map ImGui::BeginDisabled(ins->amiga.useWave); P(ImGui::Checkbox("Use sample map",&ins->amiga.useNoteMap)); if (ins->amiga.useNoteMap) { // TODO: frequency map? - if (ImGui::BeginTable("NoteMap",2,ImGuiTableFlags_ScrollY|ImGuiTableFlags_Borders|ImGuiTableFlags_SizingStretchSame)) { + if (ImGui::BeginTable("NoteMap",2/*3*/,ImGuiTableFlags_ScrollY|ImGuiTableFlags_Borders|ImGuiTableFlags_SizingStretchSame)) { ImGui::TableSetupColumn("c0",ImGuiTableColumnFlags_WidthFixed); ImGui::TableSetupColumn("c1",ImGuiTableColumnFlags_WidthStretch); //ImGui::TableSetupColumn("c2",ImGuiTableColumnFlags_WidthStretch); @@ -5098,6 +5102,10 @@ void FurnaceGUI::drawInsEdit() { dutyLabel="Duty"; dutyMax=ins->amiga.useSample?0:7; } + if (ins->type==DIV_INS_ES5506) { + dutyLabel="Filter Mode"; + dutyMax=3; + } if (ins->type==DIV_INS_SU) { dutyMax=127; } @@ -5441,7 +5449,8 @@ void FurnaceGUI::drawInsEdit() { macroList.push_back(FurnaceGUIMacroDesc("Envelope K1 ramp",&ins->std.ex6Macro,-128,127,160,uiColors[GUI_COLOR_MACRO_OTHER])); macroList.push_back(FurnaceGUIMacroDesc("Envelope K2 ramp",&ins->std.ex7Macro,-128,127,160,uiColors[GUI_COLOR_MACRO_OTHER])); macroList.push_back(FurnaceGUIMacroDesc("Envelope mode",&ins->std.ex8Macro,0,2,64,uiColors[GUI_COLOR_MACRO_OTHER],false,NULL,NULL,true,es5506EnvelopeModes)); - macroList.push_back(FurnaceGUIMacroDesc("Control",&ins->std.algMacro,0,1,32,uiColors[GUI_COLOR_MACRO_OTHER],false,NULL,NULL,true,es5506ControlModes)); + macroList.push_back(FurnaceGUIMacroDesc("Channel assignment",&ins->std.fbMacro,0,5,64,uiColors[GUI_COLOR_MACRO_OTHER])); + macroList.push_back(FurnaceGUIMacroDesc("Control",&ins->std.algMacro,0,2,32,uiColors[GUI_COLOR_MACRO_OTHER],false,NULL,NULL,true,es5506ControlModes)); } if (ins->type==DIV_INS_MSM5232) { macroList.push_back(FurnaceGUIMacroDesc("Noise",&ins->std.ex3Macro,0,1,32,uiColors[GUI_COLOR_MACRO_OTHER],false,NULL,NULL,true)); diff --git a/src/gui/presets.cpp b/src/gui/presets.cpp index 1335666bf..09bc6715d 100644 --- a/src/gui/presets.cpp +++ b/src/gui/presets.cpp @@ -2012,6 +2012,11 @@ void FurnaceGUI::initSystemPresets() { CH(DIV_SYSTEM_X1_010, 1.0f, 0, "clockSel=1") } ); + ENTRY( + "Sammy/Seta/Visco SSV", { + CH(DIV_SYSTEM_ES5506, 1.0f, 0, "channels=31") + } + ); ENTRY( "Cave 68000", { CH(DIV_SYSTEM_YMZ280B, 1.0f, 0, "") @@ -2424,6 +2429,11 @@ void FurnaceGUI::initSystemPresets() { CH(DIV_SYSTEM_PCM_DAC, 1.0f, 0, "") } ); + ENTRY( + "Ensoniq ES5506 (OTTO)", { + CH(DIV_SYSTEM_ES5506, 1.0f, 0, "channels=31") + } + ); CATEGORY_END; CATEGORY_BEGIN("Wavetable","chips which use user-specified waveforms to generate sound."); diff --git a/src/gui/sampleEdit.cpp b/src/gui/sampleEdit.cpp index 115223cc8..950420e20 100644 --- a/src/gui/sampleEdit.cpp +++ b/src/gui/sampleEdit.cpp @@ -248,6 +248,7 @@ void FurnaceGUI::drawSampleEdit() { } ImGui::EndCombo(); } + ImGui::Text("Length: %d",sample->samples); bool isThereSNES=false; for (int i=0; isong.systemLen; i++) { diff --git a/src/gui/sysConf.cpp b/src/gui/sysConf.cpp index 75f535c09..a11ccdd48 100644 --- a/src/gui/sysConf.cpp +++ b/src/gui/sysConf.cpp @@ -845,7 +845,7 @@ bool FurnaceGUI::drawSysConf(int chan, DivSystem type, DivConfig& flags, bool mo altered=true; } ImGui::Text("Initial channel limit:"); - if (CWSliderInt("##InitialChannelLimit",&channels,1,8)) { + if (CWSliderInt("##N163_InitialChannelLimit",&channels,1,8)) { if (channels<1) channels=1; if (channels>8) channels=8; altered=true; @@ -863,6 +863,22 @@ bool FurnaceGUI::drawSysConf(int chan, DivSystem type, DivConfig& flags, bool mo } break; } + case DIV_SYSTEM_ES5506: { + int channels=flags.getInt("channels",0x1f)+1; + ImGui::Text("Initial channel limit:"); + if (CWSliderInt("##OTTO_InitialChannelLimit",&channels,5,32)) { + if (channels<5) channels=5; + if (channels>32) channels=32; + altered=true; + } rightClickable + + if (altered) { + e->lockSave([&]() { + flags.set("channels",channels-1); + }); + } + break; + } case DIV_SYSTEM_YM2203: case DIV_SYSTEM_YM2203_EXT: case DIV_SYSTEM_YM2203_CSM: {