Sync to master
This commit is contained in:
parent
0a49d4bfd0
commit
8e1ce1abb1
|
@ -384,7 +384,6 @@ class DivEngine {
|
|||
sample(-1),
|
||||
wave(-1),
|
||||
pos(0),
|
||||
dir(false),
|
||||
pBegin(-1),
|
||||
pEnd(-1),
|
||||
dir(false) {}
|
||||
|
|
|
@ -405,7 +405,7 @@ struct DivInstrumentAmiga {
|
|||
reversed(0),
|
||||
loopStart(-1),
|
||||
loopEnd(-1),
|
||||
loopMode(DIV_SAMPLE_LOOPMODE_ONESHOT) {}
|
||||
loopMode(DIV_SAMPLE_LOOP_MAX) {}
|
||||
};
|
||||
|
||||
short initSample;
|
||||
|
|
|
@ -27,7 +27,7 @@
|
|||
#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,...) {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) { \
|
||||
|
@ -47,13 +47,13 @@
|
|||
rWrite((a),__VA_ARGS__); \
|
||||
}
|
||||
|
||||
#define pageReadMask(p,pm,a,...) \
|
||||
#define pageReadMask(p,pm,a,st,...) \
|
||||
if (!skipRegisterWrites) { \
|
||||
if ((curPage&(pm))!=((p)&(pm))) { \
|
||||
curPage=(curPage&~(pm))|((p)&(pm)); \
|
||||
rWrite(0xf,curPage,(pm)); \
|
||||
} \
|
||||
rRead((a),__VA_ARGS__); \
|
||||
rRead(st,(a),__VA_ARGS__); \
|
||||
}
|
||||
|
||||
|
||||
|
@ -112,90 +112,16 @@ const char** DivPlatformES5506::getRegisterSheet() {
|
|||
return regCheatSheetES5506;
|
||||
}
|
||||
|
||||
const char* DivPlatformES5506::getEffectName(unsigned char effect) {
|
||||
switch (effect) {
|
||||
case 0x10:
|
||||
return "10xx: Change waveform or sample, transwave index";
|
||||
break;
|
||||
case 0x11:
|
||||
return "11xx: Set filter mode (00 to 03)";
|
||||
break;
|
||||
case 0x12:
|
||||
return "120x: Set pause (bit 0)";
|
||||
break;
|
||||
case 0x13:
|
||||
return "130x: Set transwave slice mode (bit 0)";
|
||||
break;
|
||||
case 0x14:
|
||||
return "14xx: Set filter coefficient K1 low byte";
|
||||
break;
|
||||
case 0x15:
|
||||
return "15xx: Set filter coefficient K1 high byte";
|
||||
break;
|
||||
case 0x16:
|
||||
return "16xx: Set filter coefficient K2 low byte";
|
||||
break;
|
||||
case 0x17:
|
||||
return "17xx: Set filter coefficient K2 high byte";
|
||||
break;
|
||||
case 0x18:
|
||||
return "18xx: Set filter coefficient K1 slide up";
|
||||
break;
|
||||
case 0x19:
|
||||
return "19xx: Set filter coefficient K1 slide down";
|
||||
break;
|
||||
case 0x1a:
|
||||
return "1axx: Set filter coefficient K2 slide up";
|
||||
break;
|
||||
case 0x1b:
|
||||
return "1bxx: Set filter coefficient K2 slide down";
|
||||
break;
|
||||
case 0x20:
|
||||
return "20xx: Set envelope count (000 to 0FF)";
|
||||
break;
|
||||
case 0x21:
|
||||
return "21xx: Set envelope count (100 to 1FF)";
|
||||
break;
|
||||
case 0x22:
|
||||
return "22xx: Set envelope left volume ramp (signed)";
|
||||
break;
|
||||
case 0x23:
|
||||
return "23xx: Set envelope right volume ramp (signed)";
|
||||
break;
|
||||
case 0x24:
|
||||
return "24xx: Set envelope filter coefficient k1 ramp (signed)";
|
||||
break;
|
||||
case 0x25:
|
||||
return "25xx: Set envelope filter coefficient k1 ramp (signed, slower)";
|
||||
break;
|
||||
case 0x26:
|
||||
return "26xx: Set envelope filter coefficient k2 ramp (signed)";
|
||||
break;
|
||||
case 0x27:
|
||||
return "27xx: Set envelope filter coefficient k2 ramp (signed, slower)";
|
||||
break;
|
||||
default:
|
||||
if ((effect&0xf0)==0x30) {
|
||||
return "3xxx: Set filter coefficient K1";
|
||||
} else if ((effect&0xf0)==0x40) {
|
||||
return "4xxx: Set filter coefficient K2";
|
||||
} else if ((effect&0xf0)==0x50) {
|
||||
return "5xxx: Set transwave slice point";
|
||||
}
|
||||
break;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
void DivPlatformES5506::acquire(short* bufL, short* bufR, size_t start, size_t len) {
|
||||
for (size_t h=start; h<start+len; h++) {
|
||||
// convert 32 bit access to 8 bit host interface
|
||||
while (!hostIntf32.empty()) {
|
||||
QueuedHostIntf w=hostIntf32.front();
|
||||
if (w.isRead && (w.read!=NULL)) {
|
||||
hostIntf8.emplace(0,w.addr,w.read,w.mask);
|
||||
hostIntf8.emplace(1,w.addr,w.read,w.mask);
|
||||
hostIntf8.emplace(2,w.addr,w.read,w.mask);
|
||||
hostIntf8.emplace(3,w.addr,w.read,w.mask,w.delay);
|
||||
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);
|
||||
|
@ -244,6 +170,7 @@ void DivPlatformES5506::e_pin(bool state)
|
|||
if (w.delay>0) {
|
||||
cycle+=w.delay;
|
||||
}
|
||||
queuedReadState.emplace(w.read,w.state);
|
||||
isReaded=true;
|
||||
} else {
|
||||
isReaded=false;
|
||||
|
@ -272,116 +199,127 @@ void DivPlatformES5506::e_pin(bool state)
|
|||
}
|
||||
}
|
||||
}
|
||||
if (isReaded) {
|
||||
isReaded=false;
|
||||
if (irqTrigger) {
|
||||
irqTrigger=false;
|
||||
if ((irqv&0x80)==0) {
|
||||
unsigned char ch=irqv&0x1f;
|
||||
if (chan[ch].isReverseLoop) { // Reversed loop
|
||||
pageWriteMask(0x00|ch,0x5f,0x00,(chan[ch].pcm.reversed?0x0000:0x0040)|0x08,0x78);
|
||||
chan[ch].isReverseLoop=false;
|
||||
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);
|
||||
}
|
||||
if (chan[ch].transwaveIRQ) {
|
||||
if ((chan[ch].cr&0x37)==0x34) { // IRQE = 1, BLE = 1, LPE = 0, LEI = 1
|
||||
DivInstrument* ins=parent->getIns(chan[ch].ins);
|
||||
if (!ins->amiga.useNoteMap && ins->amiga.transWave.enable) {
|
||||
const int next=chan[ch].pcm.next;
|
||||
if (next>=0 && next<(int)ins->amiga.transWaveMap.size()) {
|
||||
DivInstrumentAmiga::TransWaveMap& transWaveInd=ins->amiga.transWaveMap[next];
|
||||
int sample=transWaveInd.ind;
|
||||
if (sample>=0 && sample<parent->song.sampleLen) {
|
||||
chan[ch].pcm.index=sample;
|
||||
chan[ch].transWave.ind=next;
|
||||
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;
|
||||
}
|
||||
// get loop mode, transwave loop
|
||||
double loopStart=(double)s->loopStart;
|
||||
double loopEnd=(double)s->loopEnd;
|
||||
DivSampleLoopMode loopMode=s->isLoopable()?s->loopMode:DIV_SAMPLE_LOOPMODE_ONESHOT;
|
||||
if (transWaveInd.loopMode!=DIV_SAMPLE_LOOPMODE_ONESHOT) {
|
||||
loopMode=transWaveInd.loopMode;
|
||||
} else if ((chan[ch].pcm.loopMode==DIV_SAMPLE_LOOPMODE_ONESHOT) || (!s->isLoopable())) { // default
|
||||
loopMode=DIV_SAMPLE_LOOPMODE_PINGPONG;
|
||||
}
|
||||
// get loop position
|
||||
loopStart=(double)transWaveInd.loopStart;
|
||||
loopEnd=(double)transWaveInd.loopEnd;
|
||||
if (ins->amiga.transWave.sliceEnable) { // sliced loop position?
|
||||
chan[ch].transWave.updateSize(s->samples,loopStart,loopEnd);
|
||||
chan[ch].transWave.slicePos(double(chan[ch].transWave.slice)/4095.0);
|
||||
loopStart=transWaveInd.sliceStart;
|
||||
loopEnd=transWaveInd.sliceEnd;
|
||||
}
|
||||
// get reversed
|
||||
bool reversed=ins->amiga.reversed;
|
||||
if (transWaveInd.reversed!=2) {
|
||||
reversed=transWaveInd.reversed;
|
||||
}
|
||||
const unsigned int start=s->offES5506<<10;
|
||||
const unsigned int length=s->samples-1;
|
||||
const unsigned int end=start+(length<<11);
|
||||
const double nextFreqOffs=PITCH_OFFSET*off;
|
||||
chan[ch].pcm.loopMode=loopMode;
|
||||
chan[ch].pcm.reversed=reversed;
|
||||
chan[ch].pcm.bank=(s->offES5506>>22)&3;
|
||||
chan[ch].pcm.start=start;
|
||||
chan[ch].pcm.loopStart=(start+(unsigned int)(loopStart*2048.0))&0xfffff800;
|
||||
chan[ch].pcm.loopEnd=(start+(unsigned int)((loopEnd-1.0)*2048.0))&0xffffff80;
|
||||
chan[ch].pcm.end=end;
|
||||
chan[ch].pcm.length=length;
|
||||
pageWrite(0x20|ch,0x01,chan[ch].pcm.loopStart);
|
||||
pageWrite(0x20|ch,0x02,chan[ch].pcm.loopEnd);
|
||||
pageWrite(0x20|ch,0x03,(chan[ch].pcm.reversed)?chan[ch].pcm.loopEnd:chan[ch].pcm.loopStart);
|
||||
unsigned int loopFlag=(chan[ch].pcm.bank<<14)|(chan[ch].pcm.reversed?0x0040:0x0000);
|
||||
chan[ch].isReverseLoop=false;
|
||||
switch (chan[ch].pcm.loopMode) {
|
||||
case DIV_SAMPLE_LOOPMODE_ONESHOT: // One shot (no loop)
|
||||
default:
|
||||
break;
|
||||
case DIV_SAMPLE_LOOPMODE_FORWARD: // Foward loop
|
||||
loopFlag|=0x0008;
|
||||
break;
|
||||
case DIV_SAMPLE_LOOPMODE_BACKWARD: // Backward loop: IRQ enable
|
||||
loopFlag|=0x0038;
|
||||
chan[ch].isReverseLoop=true;
|
||||
break;
|
||||
case DIV_SAMPLE_LOOPMODE_PINGPONG: // Pingpong loop: Hardware support
|
||||
loopFlag|=0x0018;
|
||||
break;
|
||||
}
|
||||
// Set loop mode & Bank
|
||||
pageWriteMask(0x00|ch,0x5f,0x00,loopFlag,0xfcfc);
|
||||
if (chan[ch].pcm.nextFreqOffs!=nextFreqOffs) {
|
||||
chan[ch].pcm.nextFreqOffs=nextFreqOffs;
|
||||
chan[ch].noteChanged.offs=1;
|
||||
}
|
||||
irqTrigger=false;
|
||||
}
|
||||
} else {
|
||||
unsigned char ch=w.state&0x1f;
|
||||
if (chan[ch].transwaveIRQ) {
|
||||
if ((chan[ch].cr&0x37)==0x34) { // IRQE = 1, BLE = 1, LPE = 0, LEI = 1
|
||||
DivInstrument* ins=parent->getIns(chan[ch].ins);
|
||||
if (!ins->amiga.useNoteMap && ins->amiga.transWave.enable) {
|
||||
const int next=chan[ch].pcm.next;
|
||||
if (next>=0 && next<(int)ins->amiga.transWaveMap.size()) {
|
||||
DivInstrumentAmiga::TransWaveMap& transWaveInd=ins->amiga.transWaveMap[next];
|
||||
int sample=transWaveInd.ind;
|
||||
if (sample>=0 && sample<parent->song.sampleLen) {
|
||||
chan[ch].pcm.index=sample;
|
||||
chan[ch].transWave.ind=next;
|
||||
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;
|
||||
}
|
||||
// get loop mode, transwave loop
|
||||
double loopStart=(double)s->loopStart;
|
||||
double loopEnd=(double)s->loopEnd;
|
||||
DivSampleLoopMode loopMode=s->isLoopable()?s->loopMode:DIV_SAMPLE_LOOP_MAX;
|
||||
if (transWaveInd.loopMode!=DIV_SAMPLE_LOOP_MAX) {
|
||||
loopMode=transWaveInd.loopMode;
|
||||
} else if ((chan[ch].pcm.loopMode==DIV_SAMPLE_LOOP_MAX) || (!s->isLoopable())) { // default
|
||||
loopMode=DIV_SAMPLE_LOOP_PINGPONG;
|
||||
}
|
||||
// get loop position
|
||||
loopStart=(double)transWaveInd.loopStart;
|
||||
loopEnd=(double)transWaveInd.loopEnd;
|
||||
if (ins->amiga.transWave.sliceEnable) { // sliced loop position?
|
||||
chan[ch].transWave.updateSize(s->samples,loopStart,loopEnd);
|
||||
chan[ch].transWave.slicePos(double(chan[ch].transWave.slice)/4095.0);
|
||||
loopStart=transWaveInd.sliceStart;
|
||||
loopEnd=transWaveInd.sliceEnd;
|
||||
}
|
||||
// get reversed
|
||||
bool reversed=ins->amiga.reversed;
|
||||
if (transWaveInd.reversed!=2) {
|
||||
reversed=transWaveInd.reversed;
|
||||
}
|
||||
const unsigned int start=s->offES5506<<10;
|
||||
const unsigned int length=s->samples-1;
|
||||
const unsigned int end=start+(length<<11);
|
||||
const double nextFreqOffs=PITCH_OFFSET*off;
|
||||
chan[ch].pcm.loopMode=loopMode;
|
||||
chan[ch].pcm.reversed=reversed;
|
||||
chan[ch].pcm.bank=(s->offES5506>>22)&3;
|
||||
chan[ch].pcm.start=start;
|
||||
chan[ch].pcm.loopStart=(start+(unsigned int)(loopStart*2048.0))&0xfffff800;
|
||||
chan[ch].pcm.loopEnd=(start+(unsigned int)((loopEnd-1.0)*2048.0))&0xffffff80;
|
||||
chan[ch].pcm.end=end;
|
||||
chan[ch].pcm.length=length;
|
||||
pageWrite(0x20|ch,0x01,chan[ch].pcm.loopStart);
|
||||
pageWrite(0x20|ch,0x02,chan[ch].pcm.loopEnd);
|
||||
pageWrite(0x20|ch,0x03,(chan[ch].pcm.reversed)?chan[ch].pcm.loopEnd:chan[ch].pcm.loopStart);
|
||||
unsigned int loopFlag=(chan[ch].pcm.bank<<14)|(chan[ch].pcm.reversed?0x0040:0x0000);
|
||||
chan[ch].isReverseLoop=false;
|
||||
switch (chan[ch].pcm.loopMode) {
|
||||
case DIV_SAMPLE_LOOP_MAX: // One shot (no loop)
|
||||
default:
|
||||
break;
|
||||
case DIV_SAMPLE_LOOP_FORWARD: // Foward loop
|
||||
loopFlag|=0x0008;
|
||||
break;
|
||||
case DIV_SAMPLE_LOOP_BACKWARD: // Backward loop: IRQ enable
|
||||
loopFlag|=0x0038;
|
||||
chan[ch].isReverseLoop=true;
|
||||
break;
|
||||
case DIV_SAMPLE_LOOP_PINGPONG: // Pingpong loop: Hardware support
|
||||
loopFlag|=0x0018;
|
||||
break;
|
||||
}
|
||||
// Set loop mode & Bank
|
||||
pageWriteMask(0x00|ch,0x5f,0x00,loopFlag,0xfcfc);
|
||||
if (chan[ch].pcm.nextFreqOffs!=nextFreqOffs) {
|
||||
chan[ch].pcm.nextFreqOffs=nextFreqOffs;
|
||||
chan[ch].noteChanged.offs=1;
|
||||
}
|
||||
}
|
||||
chan[ch].pcmChanged.changed=0;
|
||||
}
|
||||
chan[ch].pcmChanged.changed=0;
|
||||
}
|
||||
chan[ch].transwaveIRQ=false;
|
||||
}
|
||||
if (chan[ch].isTranswave) {
|
||||
pageReadMask(0x00|ch,0x5f,0x00,&chan[ch].cr);
|
||||
chan[ch].transwaveIRQ=true;
|
||||
chan[ch].isTranswave=false;
|
||||
}
|
||||
chan[ch].transwaveIRQ=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.reversed?0x0000:0x0040)|0x08,0x78);
|
||||
chan[ch].isReverseLoop=false;
|
||||
}
|
||||
if (chan[ch].isTranswave) {
|
||||
pageReadMask(0x00|ch,0x5f,0x00,ch,&chan[ch].cr);
|
||||
chan[ch].transwaveIRQ=true;
|
||||
chan[ch].isTranswave=false;
|
||||
}
|
||||
queuedRead.pop();
|
||||
}
|
||||
isReaded=false;
|
||||
}
|
||||
|
||||
void DivPlatformES5506::irqb(bool state) {
|
||||
rRead(0x0e,&irqv,0x9f);
|
||||
rRead(0x0e,0x80,&irqv,0x9f);
|
||||
irqTrigger=true;
|
||||
}
|
||||
|
||||
|
@ -392,7 +330,7 @@ void DivPlatformES5506::tick(bool sysTick) {
|
|||
signed int k1=chan[i].k1Prev,k2=chan[i].k2Prev;
|
||||
// volume/panning macros
|
||||
if (chan[i].std.vol.had) {
|
||||
const unsigned int nextVol=((chan[i].vol&0xff)*MIN(0xffff,(0xffff*chan[i].std.vol.val)/chan[i].volMacroMax))/0xff;
|
||||
const unsigned int nextVol=VOL_SCALE_LOG((0xffff*chan[i].vol)/0xff,(0xffff*chan[i].std.vol.val)/chan[i].volMacroMax,0xffff);
|
||||
if (chan[i].outVol!=nextVol) {
|
||||
chan[i].outVol=nextVol;
|
||||
if (!isMuted[i]) {
|
||||
|
@ -401,7 +339,7 @@ void DivPlatformES5506::tick(bool sysTick) {
|
|||
}
|
||||
}
|
||||
if (chan[i].std.panL.had) {
|
||||
const unsigned int nextLVol=(((ins->es5506.lVol*(chan[i].lVol&0xff))/0xff)*MIN(0xffff,(0xffff*chan[i].std.panL.val)/chan[i].panMacroMax))/0xffff;
|
||||
const unsigned int nextLVol=VOL_SCALE_LOG((0xffff*chan[i].lVol)/0xff,(0xffff*chan[i].std.panL.val)/chan[i].panMacroMax,0xffff);
|
||||
if (chan[i].outLVol!=nextLVol) {
|
||||
chan[i].outLVol=nextLVol;
|
||||
if (!isMuted[i]) {
|
||||
|
@ -410,7 +348,7 @@ void DivPlatformES5506::tick(bool sysTick) {
|
|||
}
|
||||
}
|
||||
if (chan[i].std.panR.had) {
|
||||
const unsigned int nextRVol=(((ins->es5506.rVol*(chan[i].rVol&0xff))/0xff)*MIN(0xffff,(0xffff*chan[i].std.panR.val)/chan[i].panMacroMax))/0xffff;
|
||||
const unsigned int nextRVol=VOL_SCALE_LOG((0xffff*chan[i].rVol)/0xff,(0xffff*chan[i].std.panR.val)/chan[i].panMacroMax,0xffff);
|
||||
if (chan[i].outRVol!=nextRVol) {
|
||||
chan[i].outRVol=nextRVol;
|
||||
if (!isMuted[i]) {
|
||||
|
@ -458,18 +396,19 @@ void DivPlatformES5506::tick(bool sysTick) {
|
|||
}
|
||||
if (chan[i].std.ex1.had) {
|
||||
switch (chan[i].std.ex1.mode) {
|
||||
case 0: // 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 1: // absolute
|
||||
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) {
|
||||
|
@ -478,24 +417,26 @@ void DivPlatformES5506::tick(bool sysTick) {
|
|||
}
|
||||
break;
|
||||
}
|
||||
*/
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (chan[i].std.ex2.had) {
|
||||
switch (chan[i].std.ex2.mode) {
|
||||
case 0: // 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 1: // absolute
|
||||
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) {
|
||||
|
@ -504,6 +445,7 @@ void DivPlatformES5506::tick(bool sysTick) {
|
|||
}
|
||||
break;
|
||||
}
|
||||
*/
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
@ -621,13 +563,13 @@ void DivPlatformES5506::tick(bool sysTick) {
|
|||
if (chan[i].volChanged.changed) {
|
||||
if (!isMuted[i]) { // calculate volume (16 bit)
|
||||
if (chan[i].volChanged.lVol) {
|
||||
chan[i].resLVol=(chan[i].outVol*chan[i].outLVol)/0xffff;
|
||||
chan[i].resLVol=VOL_SCALE_LOG(chan[i].outVol,chan[i].outLVol,0xffff);
|
||||
if (!chan[i].keyOn) {
|
||||
pageWrite(0x00|i,0x02,chan[i].resLVol);
|
||||
}
|
||||
}
|
||||
if (chan[i].volChanged.rVol) {
|
||||
chan[i].resRVol=(chan[i].outVol*chan[i].outRVol)/0xffff;
|
||||
chan[i].resRVol=VOL_SCALE_LOG(chan[i].outVol,chan[i].outRVol,0xffff);
|
||||
if (!chan[i].keyOn) {
|
||||
pageWrite(0x00|i,0x04,chan[i].resRVol);
|
||||
}
|
||||
|
@ -655,11 +597,11 @@ void DivPlatformES5506::tick(bool sysTick) {
|
|||
// get loop mode, transwave loop
|
||||
double loopStart=(double)s->loopStart;
|
||||
double loopEnd=(double)s->loopEnd;
|
||||
DivSampleLoopMode loopMode=s->isLoopable()?s->loopMode:DIV_SAMPLE_LOOPMODE_ONESHOT;
|
||||
if (transWaveInd.loopMode!=DIV_SAMPLE_LOOPMODE_ONESHOT) {
|
||||
DivSampleLoopMode loopMode=s->isLoopable()?s->loopMode:DIV_SAMPLE_LOOP_MAX;
|
||||
if (transWaveInd.loopMode!=DIV_SAMPLE_LOOP_MAX) {
|
||||
loopMode=transWaveInd.loopMode;
|
||||
} else if ((chan[i].pcm.loopMode==DIV_SAMPLE_LOOPMODE_ONESHOT) || (!s->isLoopable())) { // default
|
||||
loopMode=DIV_SAMPLE_LOOPMODE_PINGPONG;
|
||||
} else if ((chan[i].pcm.loopMode==DIV_SAMPLE_LOOP_MAX) || (!s->isLoopable())) { // default
|
||||
loopMode=DIV_SAMPLE_LOOP_PINGPONG;
|
||||
}
|
||||
// get loop position
|
||||
loopStart=(double)transWaveInd.loopStart;
|
||||
|
@ -693,13 +635,13 @@ void DivPlatformES5506::tick(bool sysTick) {
|
|||
if (((ins->amiga.useNoteMap && !ins->amiga.transWave.enable) && (next>=0 && next<120)) ||
|
||||
((!ins->amiga.useNoteMap && ins->amiga.transWave.enable) && (next>=0 && next<(int)ins->amiga.transWaveMap.size())) ||
|
||||
((!ins->amiga.useNoteMap && !ins->amiga.transWave.enable) && (next>=0 && next<parent->song.sampleLen))) {
|
||||
DivInstrumentAmiga::NoteMap& noteMapind=ins->amiga.noteMap[next];
|
||||
DivInstrumentAmiga::SampleMap& noteMapind=ins->amiga.noteMap[next];
|
||||
DivInstrumentAmiga::TransWaveMap& transWaveInd=ins->amiga.transWaveMap[next];
|
||||
int sample=next;
|
||||
if (ins->amiga.transWave.enable) {
|
||||
sample=transWaveInd.ind;
|
||||
} else if (ins->amiga.useNoteMap) {
|
||||
sample=noteMapind.ind;
|
||||
sample=noteMapind.map;
|
||||
}
|
||||
if (sample>=0 && sample<parent->song.sampleLen) {
|
||||
sampleVaild=true;
|
||||
|
@ -723,12 +665,12 @@ void DivPlatformES5506::tick(bool sysTick) {
|
|||
// get loop mode, transwave loop
|
||||
double loopStart=(double)s->loopStart;
|
||||
double loopEnd=(double)s->loopEnd;
|
||||
DivSampleLoopMode loopMode=s->isLoopable()?s->loopMode:DIV_SAMPLE_LOOPMODE_ONESHOT;
|
||||
DivSampleLoopMode loopMode=s->isLoopable()?s->loopMode:DIV_SAMPLE_LOOP_MAX;
|
||||
if (ins->amiga.transWave.enable) {
|
||||
if (transWaveInd.loopMode!=DIV_SAMPLE_LOOPMODE_ONESHOT) {
|
||||
if (transWaveInd.loopMode!=DIV_SAMPLE_LOOP_MAX) {
|
||||
loopMode=transWaveInd.loopMode;
|
||||
} else if ((chan[i].pcm.loopMode==DIV_SAMPLE_LOOPMODE_ONESHOT) || (!s->isLoopable())) { // default
|
||||
loopMode=DIV_SAMPLE_LOOPMODE_PINGPONG;
|
||||
} else if ((chan[i].pcm.loopMode==DIV_SAMPLE_LOOP_MAX) || (!s->isLoopable())) { // default
|
||||
loopMode=DIV_SAMPLE_LOOP_PINGPONG;
|
||||
}
|
||||
// get loop position
|
||||
loopStart=(double)transWaveInd.loopStart;
|
||||
|
@ -805,8 +747,8 @@ void DivPlatformES5506::tick(bool sysTick) {
|
|||
}
|
||||
if (chan[i].pcmChanged.position) {
|
||||
if (!chan[i].keyOn) {
|
||||
pageWrite(0x20|i,0x01,(chan[i].pcm.loopMode==DIV_SAMPLE_LOOPMODE_ONESHOT)?chan[i].pcm.start:chan[i].pcm.loopStart);
|
||||
pageWrite(0x20|i,0x02,(chan[i].pcm.loopMode==DIV_SAMPLE_LOOPMODE_ONESHOT)?chan[i].pcm.end:chan[i].pcm.loopEnd);
|
||||
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;
|
||||
}
|
||||
|
@ -815,17 +757,17 @@ void DivPlatformES5506::tick(bool sysTick) {
|
|||
unsigned int loopFlag=(chan[i].pcm.bank<<14)|(chan[i].pcm.reversed?0x0040:0x0000);
|
||||
chan[i].isReverseLoop=false;
|
||||
switch (chan[i].pcm.loopMode) {
|
||||
case DIV_SAMPLE_LOOPMODE_ONESHOT: // One shot (no loop)
|
||||
case DIV_SAMPLE_LOOP_MAX: // One shot (no loop)
|
||||
default:
|
||||
break;
|
||||
case DIV_SAMPLE_LOOPMODE_FORWARD: // Foward loop
|
||||
case DIV_SAMPLE_LOOP_FORWARD: // Foward loop
|
||||
loopFlag|=0x0008;
|
||||
break;
|
||||
case DIV_SAMPLE_LOOPMODE_BACKWARD: // Backward loop: IRQ enable
|
||||
case DIV_SAMPLE_LOOP_BACKWARD: // Backward loop: IRQ enable
|
||||
loopFlag|=0x0038;
|
||||
chan[i].isReverseLoop=true;
|
||||
break;
|
||||
case DIV_SAMPLE_LOOPMODE_PINGPONG: // Pingpong loop: Hardware support
|
||||
case DIV_SAMPLE_LOOP_PINGPONG: // Pingpong loop: Hardware support
|
||||
loopFlag|=0x0018;
|
||||
break;
|
||||
}
|
||||
|
@ -844,14 +786,14 @@ void DivPlatformES5506::tick(bool sysTick) {
|
|||
pageWriteMask(0x00|i,0x5f,0x00,(chan[i].filter.mode<<8),0x0300);
|
||||
}
|
||||
if (chan[i].filterChanged.k2) {
|
||||
if (chan[i].std.ex2.mode!=1) { // Relative
|
||||
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!=1) { // Relative
|
||||
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;
|
||||
|
@ -921,8 +863,8 @@ void DivPlatformES5506::tick(bool sysTick) {
|
|||
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_LOOPMODE_ONESHOT)?chan[i].pcm.start:chan[i].pcm.loopStart);
|
||||
pageWrite(0x20|i,0x02,(chan[i].pcm.loopMode==DIV_SAMPLE_LOOPMODE_ONESHOT)?chan[i].pcm.end:chan[i].pcm.loopEnd);
|
||||
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 envelope
|
||||
pageWrite(0x00|i,0x03,((unsigned char)chan[i].envelope.lVRamp)<<8);
|
||||
pageWrite(0x00|i,0x05,((unsigned char)chan[i].envelope.rVRamp)<<8);
|
||||
|
@ -930,14 +872,14 @@ void DivPlatformES5506::tick(bool sysTick) {
|
|||
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!=1) && (chan[i].std.ex2.had)) {
|
||||
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!=1) && (chan[i].std.ex1.had)) {
|
||||
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;
|
||||
|
@ -949,17 +891,17 @@ void DivPlatformES5506::tick(bool sysTick) {
|
|||
unsigned int loopFlag=chan[i].pcm.reversed?0x0040:0x0000;
|
||||
chan[i].isReverseLoop=false;
|
||||
switch (chan[i].pcm.loopMode) {
|
||||
case DIV_SAMPLE_LOOPMODE_ONESHOT: // One shot (no loop)
|
||||
case DIV_SAMPLE_LOOP_MAX: // One shot (no loop)
|
||||
default:
|
||||
break;
|
||||
case DIV_SAMPLE_LOOPMODE_FORWARD: // Foward loop
|
||||
case DIV_SAMPLE_LOOP_FORWARD: // Foward loop
|
||||
loopFlag|=0x0008;
|
||||
break;
|
||||
case DIV_SAMPLE_LOOPMODE_BACKWARD: // Backward loop: IRQ enable
|
||||
case DIV_SAMPLE_LOOP_BACKWARD: // Backward loop: IRQ enable
|
||||
loopFlag|=0x0038;
|
||||
chan[i].isReverseLoop=true;
|
||||
break;
|
||||
case DIV_SAMPLE_LOOPMODE_PINGPONG: // Pingpong loop: Hardware support
|
||||
case DIV_SAMPLE_LOOP_PINGPONG: // Pingpong loop: Hardware support
|
||||
loopFlag|=0x0018;
|
||||
break;
|
||||
}
|
||||
|
@ -1001,13 +943,13 @@ int DivPlatformES5506::dispatch(DivCommand c) {
|
|||
if (((ins->amiga.useNoteMap && !ins->amiga.transWave.enable) && (c.value>=0 && c.value<120)) ||
|
||||
((!ins->amiga.useNoteMap && ins->amiga.transWave.enable) && (ins->amiga.transWave.ind>=0 && ins->amiga.transWave.ind<(int)ins->amiga.transWaveMap.size())) ||
|
||||
((!ins->amiga.useNoteMap && !ins->amiga.transWave.enable) && (ins->amiga.initSample>=0 && ins->amiga.initSample<parent->song.sampleLen))) {
|
||||
DivInstrumentAmiga::NoteMap& noteMapind=ins->amiga.noteMap[c.value];
|
||||
DivInstrumentAmiga::SampleMap& noteMapind=ins->amiga.noteMap[c.value];
|
||||
DivInstrumentAmiga::TransWaveMap& transWaveInd=ins->amiga.transWaveMap[ins->amiga.transWave.ind];
|
||||
int sample=ins->amiga.initSample;
|
||||
if (ins->amiga.transWave.enable) {
|
||||
sample=transWaveInd.ind;
|
||||
} else if (ins->amiga.useNoteMap) {
|
||||
sample=noteMapind.ind;
|
||||
sample=noteMapind.map;
|
||||
}
|
||||
if (sample>=0 && sample<parent->song.sampleLen) {
|
||||
sampleVaild=true;
|
||||
|
@ -1033,12 +975,12 @@ int DivPlatformES5506::dispatch(DivCommand c) {
|
|||
// get loop mode, transwave loop
|
||||
double loopStart=(double)s->loopStart;
|
||||
double loopEnd=(double)s->loopEnd;
|
||||
DivSampleLoopMode loopMode=s->isLoopable()?s->loopMode:DIV_SAMPLE_LOOPMODE_ONESHOT;
|
||||
DivSampleLoopMode loopMode=s->isLoopable()?s->loopMode:DIV_SAMPLE_LOOP_MAX;
|
||||
if (ins->amiga.transWave.enable) {
|
||||
if (transWaveInd.loopMode!=DIV_SAMPLE_LOOPMODE_ONESHOT) {
|
||||
if (transWaveInd.loopMode!=DIV_SAMPLE_LOOP_MAX) {
|
||||
loopMode=transWaveInd.loopMode;
|
||||
} else if ((chan[c.chan].pcm.loopMode==DIV_SAMPLE_LOOPMODE_ONESHOT) || (!s->isLoopable())) { // default
|
||||
loopMode=DIV_SAMPLE_LOOPMODE_PINGPONG;
|
||||
} else if ((chan[c.chan].pcm.loopMode==DIV_SAMPLE_LOOP_MAX) || (!s->isLoopable())) { // default
|
||||
loopMode=DIV_SAMPLE_LOOP_PINGPONG;
|
||||
}
|
||||
// get loop position
|
||||
loopStart=(double)transWaveInd.loopStart;
|
||||
|
@ -1093,10 +1035,10 @@ int DivPlatformES5506::dispatch(DivCommand c) {
|
|||
chan[c.chan].outVol=(0xffff*chan[c.chan].vol)/0xff;
|
||||
}
|
||||
if (!chan[c.chan].std.panL.will) {
|
||||
chan[c.chan].outLVol=(ins->es5506.lVol*chan[c.chan].lVol)/0xff;
|
||||
chan[c.chan].outLVol=(0xffff*chan[c.chan].lVol)/0xff;
|
||||
}
|
||||
if (!chan[c.chan].std.panR.will) {
|
||||
chan[c.chan].outRVol=(ins->es5506.rVol*chan[c.chan].rVol)/0xff;
|
||||
chan[c.chan].outRVol=(0xffff*chan[c.chan].rVol)/0xff;
|
||||
}
|
||||
chan[c.chan].active=true;
|
||||
chan[c.chan].keyOn=true;
|
||||
|
@ -1142,7 +1084,7 @@ int DivPlatformES5506::dispatch(DivCommand c) {
|
|||
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=(ins->es5506.lVol*c.value)/0xff;
|
||||
chan[c.chan].outLVol=(0xffff*c.value)/0xff;
|
||||
if (!isMuted[c.chan]) {
|
||||
chan[c.chan].volChanged.lVol=1;
|
||||
}
|
||||
|
@ -1152,7 +1094,7 @@ int DivPlatformES5506::dispatch(DivCommand c) {
|
|||
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=(ins->es5506.rVol*c.value2)/0xff;
|
||||
chan[c.chan].outRVol=(0xffff*c.value2)/0xff;
|
||||
if (!isMuted[c.chan]) {
|
||||
chan[c.chan].volChanged.rVol=1;
|
||||
}
|
||||
|
@ -1336,6 +1278,8 @@ DivMacroInt* DivPlatformES5506::getChanMacroInt(int ch) {
|
|||
void DivPlatformES5506::reset() {
|
||||
while (!hostIntf32.empty()) hostIntf32.pop();
|
||||
while (!hostIntf8.empty()) hostIntf8.pop();
|
||||
while (!queuedRead.empty()) queuedRead.pop();
|
||||
while (!queuedReadState.empty()) queuedReadState.pop();
|
||||
for (int i=0; i<32; i++) {
|
||||
chan[i]=DivPlatformES5506::Channel();
|
||||
chan[i].std.setEngine(parent);
|
||||
|
|
|
@ -27,7 +27,7 @@
|
|||
#include <queue>
|
||||
#include "../macroInt.h"
|
||||
#include "../sample.h"
|
||||
#include "sound/es550x/es5506.hpp"
|
||||
#include "vgsound_emu/src/es550x/es5506.hpp"
|
||||
|
||||
class DivPlatformES5506: public DivDispatch, public es550x_intf {
|
||||
struct Channel {
|
||||
|
@ -60,7 +60,7 @@ class DivPlatformES5506: public DivDispatch, public es550x_intf {
|
|||
length(0),
|
||||
loopStart(0),
|
||||
loopEnd(0),
|
||||
loopMode(DIV_SAMPLE_LOOPMODE_ONESHOT) {}
|
||||
loopMode(DIV_SAMPLE_LOOP_MAX) {}
|
||||
} pcm;
|
||||
int freq, baseFreq, nextFreq, pitch, pitch2, note, nextNote, currNote, ins, wave;
|
||||
unsigned int volMacroMax, panMacroMax;
|
||||
|
@ -213,6 +213,7 @@ class DivPlatformES5506: public DivDispatch, public es550x_intf {
|
|||
signed short* sampleMem; // ES5506 uses 16 bit data bus for samples
|
||||
size_t sampleMemLen;
|
||||
struct QueuedHostIntf {
|
||||
unsigned char state;
|
||||
unsigned char step;
|
||||
unsigned char addr;
|
||||
unsigned int val;
|
||||
|
@ -221,6 +222,7 @@ class DivPlatformES5506: public DivDispatch, public es550x_intf {
|
|||
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),
|
||||
|
@ -228,7 +230,8 @@ class DivPlatformES5506: public DivDispatch, public es550x_intf {
|
|||
read(NULL),
|
||||
delay(0),
|
||||
isRead(false) {}
|
||||
QueuedHostIntf(unsigned char s, unsigned char a, unsigned int* r, unsigned int m=(unsigned int)(~0), unsigned short d=0):
|
||||
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),
|
||||
|
@ -237,8 +240,17 @@ class DivPlatformES5506: public DivDispatch, public es550x_intf {
|
|||
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<QueuedHostIntf> hostIntf32;
|
||||
std::queue<QueuedHostIntf> hostIntf8;
|
||||
std::queue<unsigned char> queuedRead;
|
||||
std::queue<QueuedReadState> queuedReadState;
|
||||
int cycle, curPage;
|
||||
unsigned char maskedVal;
|
||||
unsigned int irqv;
|
||||
|
@ -288,7 +300,6 @@ class DivPlatformES5506: public DivDispatch, public es550x_intf {
|
|||
virtual size_t getSampleMemUsage(int index = 0) override;
|
||||
virtual void renderSamples() override;
|
||||
virtual const char** getRegisterSheet() override;
|
||||
virtual const char* getEffectName(unsigned char effect) override;
|
||||
virtual int init(DivEngine* parent, int channels, int sugRate, unsigned int flags) override;
|
||||
virtual void quit() override;
|
||||
DivPlatformES5506():
|
||||
|
|
|
@ -1,453 +0,0 @@
|
|||
/*
|
||||
License: BSD-3-Clause
|
||||
see https://github.com/cam900/vgsound_emu/blob/vgsound_emu_v1/LICENSE for more details
|
||||
|
||||
Copyright holder(s): cam900
|
||||
Modifiers and Contributors for Furnace: cam900
|
||||
Ensoniq ES5504 emulation core
|
||||
|
||||
see es550x.cpp for more info
|
||||
*/
|
||||
|
||||
#include "es5504.hpp"
|
||||
|
||||
// Internal functions
|
||||
void es5504_core::tick()
|
||||
{
|
||||
m_voice_update = false;
|
||||
m_voice_end = false;
|
||||
// /CAS, E
|
||||
if (m_clkin.falling_edge()) // falling edge triggers /CAS, E clock
|
||||
{
|
||||
// /CAS
|
||||
if (m_cas.tick())
|
||||
{
|
||||
// /CAS high, E low: get sample address
|
||||
if (m_cas.falling_edge())
|
||||
{
|
||||
// /CAS low, E low: fetch sample
|
||||
if (!m_e.current_edge())
|
||||
m_voice[m_voice_cycle].fetch(m_voice_cycle, m_voice_fetch);
|
||||
}
|
||||
}
|
||||
// E
|
||||
if (m_clkin.falling_edge()) // falling edge triggers E clock
|
||||
{
|
||||
if (m_e.tick())
|
||||
{
|
||||
m_intf.e_pin(m_e.current_edge());
|
||||
if (m_e.rising_edge()) // Host access
|
||||
{
|
||||
m_host_intf.m_rw = m_host_intf.m_rw_strobe;
|
||||
m_host_intf.m_host_access = m_host_intf.m_host_access_strobe;
|
||||
voice_tick();
|
||||
}
|
||||
if (m_e.falling_edge()) // Voice memory
|
||||
{
|
||||
m_host_intf.m_host_access = false;
|
||||
m_voice[m_voice_cycle].fetch(m_voice_cycle, m_voice_fetch);
|
||||
}
|
||||
}
|
||||
if (m_e.current_edge()) // Host interface
|
||||
{
|
||||
if (m_host_intf.m_host_access)
|
||||
{
|
||||
if (m_host_intf.m_rw && (m_e.cycle() == 2)) // Read
|
||||
{
|
||||
m_hd = read(m_ha);
|
||||
m_host_intf.m_host_access = false;
|
||||
}
|
||||
else if ((!m_host_intf.m_rw) && (m_e.cycle() == 2)) // Write
|
||||
write(m_ha, m_hd);
|
||||
}
|
||||
}
|
||||
else if (!m_e.current_edge())
|
||||
{
|
||||
if (m_e.cycle() == 2)
|
||||
{
|
||||
// reset host access state
|
||||
m_hd = 0;
|
||||
m_host_intf.m_host_access_strobe = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// less cycle accurate, but less CPU heavy routine
|
||||
void es5504_core::tick_perf()
|
||||
{
|
||||
m_voice_update = false;
|
||||
m_voice_end = false;
|
||||
// update
|
||||
// falling edge
|
||||
m_e.m_edge.set(false);
|
||||
m_intf.e_pin(false);
|
||||
m_host_intf.m_host_access = m_host_intf.m_host_access_strobe = false;
|
||||
m_voice[m_voice_cycle].fetch(m_voice_cycle, m_voice_fetch);
|
||||
voice_tick();
|
||||
// rising edge
|
||||
m_e.m_edge.set(true);
|
||||
m_intf.e_pin(true);
|
||||
m_host_intf.m_rw = m_host_intf.m_rw_strobe;
|
||||
m_host_intf.m_host_access = m_host_intf.m_host_access_strobe;
|
||||
// falling edge
|
||||
m_e.m_edge.set(false);
|
||||
m_intf.e_pin(false);
|
||||
m_host_intf.m_host_access = m_host_intf.m_host_access_strobe = false;
|
||||
m_voice[m_voice_cycle].fetch(m_voice_cycle, m_voice_fetch);
|
||||
voice_tick();
|
||||
// rising edge
|
||||
m_e.m_edge.set(true);
|
||||
m_intf.e_pin(true);
|
||||
m_host_intf.m_rw = m_host_intf.m_rw_strobe;
|
||||
m_host_intf.m_host_access = m_host_intf.m_host_access_strobe;
|
||||
}
|
||||
|
||||
void es5504_core::voice_tick()
|
||||
{
|
||||
// Voice updates every 2 E clock cycle (= 1 CHSTRB cycle or 4 BCLK clock cycle)
|
||||
m_voice_update = bitfield(m_voice_fetch++, 0);
|
||||
if (m_voice_update)
|
||||
{
|
||||
// Update voice
|
||||
m_voice[m_voice_cycle].tick(m_voice_cycle);
|
||||
|
||||
// Refresh output (Multiplexed analog output)
|
||||
m_ch[m_voice[m_voice_cycle].m_cr.ca] = m_voice[m_voice_cycle].m_ch;
|
||||
|
||||
if ((++m_voice_cycle) > std::min<u8>(24, m_active)) // ~ 25 voices
|
||||
{
|
||||
m_voice_end = true;
|
||||
m_voice_cycle = 0;
|
||||
}
|
||||
|
||||
m_voice_fetch = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void es5504_core::voice_t::fetch(u8 voice, u8 cycle)
|
||||
{
|
||||
m_alu.m_sample[cycle] = m_host.m_intf.read_sample(voice, bitfield(m_cr.ca, 0, 3), bitfield(m_alu.get_accum_integer() + cycle, 0, m_alu.m_integer));
|
||||
}
|
||||
|
||||
void es5504_core::voice_t::tick(u8 voice)
|
||||
{
|
||||
m_ch = 0;
|
||||
|
||||
// Filter execute
|
||||
m_filter.tick(m_alu.interpolation());
|
||||
|
||||
if (m_alu.busy())
|
||||
{
|
||||
// Send to output
|
||||
m_ch = ((sign_ext<s32>(m_filter.m_o4_1, 16) >> 3) * m_volume) >> 12; // Analog multiplied in real chip, 13/12 bit ladder DAC
|
||||
|
||||
// ALU execute
|
||||
if (m_alu.tick())
|
||||
{
|
||||
m_alu.loop_exec();
|
||||
}
|
||||
|
||||
// ADC check
|
||||
adc_exec();
|
||||
}
|
||||
|
||||
// Update IRQ
|
||||
m_alu.irq_exec(m_host.m_intf, m_host.m_irqv, voice);
|
||||
}
|
||||
|
||||
// ADC; Correct?
|
||||
void es5504_core::voice_t::adc_exec()
|
||||
{
|
||||
if (m_cr.adc)
|
||||
m_host.m_adc = m_host.m_intf.adc_r() & ~0x7;
|
||||
}
|
||||
|
||||
void es5504_core::reset()
|
||||
{
|
||||
es550x_shared_core::reset();
|
||||
for (auto & elem : m_voice)
|
||||
elem.reset();
|
||||
|
||||
m_adc = 0;
|
||||
std::fill(std::begin(m_ch), std::end(m_ch), 0);
|
||||
}
|
||||
|
||||
void es5504_core::voice_t::reset()
|
||||
{
|
||||
es550x_shared_core::es550x_voice_t::reset();
|
||||
m_volume = 0;
|
||||
m_ch = 0;
|
||||
}
|
||||
|
||||
// Accessors
|
||||
u16 es5504_core::host_r(u8 address)
|
||||
{
|
||||
if (!m_host_intf.m_host_access)
|
||||
{
|
||||
m_ha = address;
|
||||
if (m_e.rising_edge()) // update directly
|
||||
m_hd = read(m_ha, true);
|
||||
else
|
||||
{
|
||||
m_host_intf.m_rw_strobe = true;
|
||||
m_host_intf.m_host_access_strobe = true;
|
||||
}
|
||||
}
|
||||
return m_hd;
|
||||
}
|
||||
|
||||
void es5504_core::host_w(u8 address, u16 data)
|
||||
{
|
||||
if (!m_host_intf.m_host_access)
|
||||
{
|
||||
m_ha = address;
|
||||
m_hd = data;
|
||||
if (m_e.rising_edge()) // update directly
|
||||
write(m_ha, m_hd, true);
|
||||
else
|
||||
{
|
||||
m_host_intf.m_rw_strobe = false;
|
||||
m_host_intf.m_host_access_strobe = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
u16 es5504_core::read(u8 address, bool cpu_access)
|
||||
{
|
||||
return regs_r(m_page, address, cpu_access);
|
||||
}
|
||||
|
||||
void es5504_core::write(u8 address, u16 data, bool cpu_access)
|
||||
{
|
||||
regs_w(m_page, address, data, cpu_access);
|
||||
}
|
||||
|
||||
u16 es5504_core::regs_r(u8 page, u8 address, bool cpu_access)
|
||||
{
|
||||
u16 ret = 0xffff;
|
||||
address = bitfield(address, 0, 4); // 4 bit address for CPU access
|
||||
|
||||
if (address >= 12) // Global registers
|
||||
{
|
||||
switch (address)
|
||||
{
|
||||
case 12: // A/D (A to D Convert/Test)
|
||||
ret = (ret & ~0xfffb) | (m_adc & 0xfffb);
|
||||
break;
|
||||
case 13: // ACT (Number of voices)
|
||||
ret = (ret & ~0x1f) | bitfield(m_active, 0, 5);
|
||||
break;
|
||||
case 14: // IRQV (Interrupting voice vector)
|
||||
ret = (ret & ~0x9f) | (m_irqv.irqb ? 0x80 : 0) | bitfield(m_irqv.voice, 0, 5);
|
||||
if (cpu_access)
|
||||
{
|
||||
m_irqv.clear();
|
||||
if (bitfield(ret, 7) != m_irqv.irqb)
|
||||
m_voice[m_irqv.voice].m_alu.irq_update(m_intf, m_irqv);
|
||||
}
|
||||
break;
|
||||
case 15: // PAGE (Page select register)
|
||||
ret = (ret & ~0x3f) | bitfield(m_page, 0, 6);
|
||||
break;
|
||||
}
|
||||
}
|
||||
else // Voice specific registers
|
||||
{
|
||||
const u8 voice = bitfield(page, 0, 5); // Voice select
|
||||
if (voice < 25)
|
||||
{
|
||||
voice_t &v = m_voice[voice];
|
||||
if (bitfield(page, 5)) // Page 32 - 56
|
||||
{
|
||||
switch (address)
|
||||
{
|
||||
case 1: // O4(n-1) (Filter 4 Temp Register)
|
||||
ret = v.m_filter.m_o4_1;
|
||||
break;
|
||||
case 2: // O3(n-2) (Filter 3 Temp Register #2)
|
||||
ret = v.m_filter.m_o3_2;
|
||||
break;
|
||||
case 3: // O3(n-1) (Filter 3 Temp Register #1)
|
||||
ret = v.m_filter.m_o3_1;
|
||||
break;
|
||||
case 4: // O2(n-2) (Filter 2 Temp Register #2)
|
||||
ret = v.m_filter.m_o2_2;
|
||||
break;
|
||||
case 5: // O2(n-1) (Filter 2 Temp Register #1)
|
||||
ret = v.m_filter.m_o2_1;
|
||||
break;
|
||||
case 6: // O1(n-1) (Filter 1 Temp Register)
|
||||
ret = v.m_filter.m_o1_1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
else // Page 0 - 24
|
||||
{
|
||||
switch (address)
|
||||
{
|
||||
case 0: // CR (Control Register)
|
||||
ret = (ret & ~0xff) |
|
||||
(v.m_alu.m_cr.stop0 ? 0x01 : 0x00)
|
||||
| (v.m_alu.m_cr.stop1 ? 0x02 : 0x00)
|
||||
| (v.m_cr.adc ? 0x04 : 0x00)
|
||||
| (v.m_alu.m_cr.lpe ? 0x08 : 0x00)
|
||||
| (v.m_alu.m_cr.ble ? 0x10 : 0x00)
|
||||
| (v.m_alu.m_cr.irqe ? 0x20 : 0x00)
|
||||
| (v.m_alu.m_cr.dir ? 0x40 : 0x00)
|
||||
| (v.m_alu.m_cr.irq ? 0x80 : 0x00);
|
||||
break;
|
||||
case 1: // FC (Frequency Control)
|
||||
ret = (ret & ~0xfffe) | (v.m_alu.m_fc << 1);
|
||||
break;
|
||||
case 2: // STRT-H (Loop Start Register High)
|
||||
ret = (ret & ~0x1fff) | bitfield(v.m_alu.m_start, 16, 13);
|
||||
break;
|
||||
case 3: // STRT-L (Loop Start Register Low)
|
||||
ret = (ret & ~0xffe0) | (v.m_alu.m_start & 0xffe0);
|
||||
break;
|
||||
case 4: // END-H (Loop End Register High)
|
||||
ret = (ret & ~0x1fff) | bitfield(v.m_alu.m_end, 16, 13);
|
||||
break;
|
||||
case 5: // END-L (Loop End Register Low)
|
||||
ret = (ret & ~0xffe0) | (v.m_alu.m_end & 0xffe0);
|
||||
break;
|
||||
case 6: // K2 (Filter Cutoff Coefficient #2)
|
||||
ret = (ret & ~0xfff0) | (v.m_filter.m_k2 & 0xfff0);
|
||||
break;
|
||||
case 7: // K1 (Filter Cutoff Coefficient #1)
|
||||
ret = (ret & ~0xfff0) | (v.m_filter.m_k1 & 0xfff0);
|
||||
break;
|
||||
case 8: // Volume
|
||||
ret = (ret & ~0xfff0) | ((v.m_volume << 4) & 0xfff0);
|
||||
break;
|
||||
case 9: // CA (Filter Config, Channel Assign)
|
||||
ret = (ret & ~0x3f) |
|
||||
bitfield(v.m_cr.ca, 0, 4)
|
||||
| (bitfield(v.m_filter.m_lp, 0, 2) << 4);
|
||||
break;
|
||||
case 10: // ACCH (Accumulator High)
|
||||
ret = (ret & ~0x1fff) | bitfield(v.m_alu.m_accum, 16, 13);
|
||||
break;
|
||||
case 11: // ACCL (Accumulator Low)
|
||||
ret = bitfield(v.m_alu.m_accum, 0, 16);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void es5504_core::regs_w(u8 page, u8 address, u16 data, bool cpu_access)
|
||||
{
|
||||
address = bitfield(address, 0, 4); // 4 bit address for CPU access
|
||||
|
||||
if (address >= 12) // Global registers
|
||||
{
|
||||
switch (address)
|
||||
{
|
||||
case 12: // A/D (A to D Convert/Test)
|
||||
if (bitfield(m_adc, 0)) // Writable ADC
|
||||
{
|
||||
m_adc = (m_adc & 7) | (data & ~7);
|
||||
m_intf.adc_w(m_adc & ~7);
|
||||
}
|
||||
m_adc = (m_adc & ~3) | (data & 3);
|
||||
break;
|
||||
case 13: // ACT (Number of voices)
|
||||
m_active = std::min<u8>(24, bitfield(data, 0, 5));
|
||||
break;
|
||||
case 14: // IRQV (Interrupting voice vector)
|
||||
// Read only
|
||||
break;
|
||||
case 15: // PAGE (Page select register)
|
||||
m_page = bitfield(data, 0, 6);
|
||||
break;
|
||||
}
|
||||
}
|
||||
else // Voice specific registers
|
||||
{
|
||||
const u8 voice = bitfield(page, 0, 5); // Voice select
|
||||
if (voice < 25)
|
||||
{
|
||||
voice_t &v = m_voice[voice];
|
||||
if (bitfield(page, 5)) // Page 32 - 56
|
||||
{
|
||||
switch (address)
|
||||
{
|
||||
case 1: // O4(n-1) (Filter 4 Temp Register)
|
||||
v.m_filter.m_o4_1 = sign_ext<s32>(data, 16);
|
||||
break;
|
||||
case 2: // O3(n-2) (Filter 3 Temp Register #2)
|
||||
v.m_filter.m_o3_2 = sign_ext<s32>(data, 16);
|
||||
break;
|
||||
case 3: // O3(n-1) (Filter 3 Temp Register #1)
|
||||
v.m_filter.m_o3_1 = sign_ext<s32>(data, 16);
|
||||
break;
|
||||
case 4: // O2(n-2) (Filter 2 Temp Register #2)
|
||||
v.m_filter.m_o2_2 = sign_ext<s32>(data, 16);
|
||||
break;
|
||||
case 5: // O2(n-1) (Filter 2 Temp Register #1)
|
||||
v.m_filter.m_o2_1 = sign_ext<s32>(data, 16);
|
||||
break;
|
||||
case 6: // O1(n-1) (Filter 1 Temp Register)
|
||||
v.m_filter.m_o1_1 = sign_ext<s32>(data, 16);
|
||||
break;
|
||||
}
|
||||
}
|
||||
else // Page 0 - 24
|
||||
{
|
||||
switch (address)
|
||||
{
|
||||
case 0: // CR (Control Register)
|
||||
v.m_alu.m_cr.stop0 = bitfield(data, 0);
|
||||
v.m_alu.m_cr.stop1 = bitfield(data, 1);
|
||||
v.m_cr.adc = bitfield(data, 2);
|
||||
v.m_alu.m_cr.lpe = bitfield(data, 3);
|
||||
v.m_alu.m_cr.ble = bitfield(data, 4);
|
||||
v.m_alu.m_cr.irqe = bitfield(data, 5);
|
||||
v.m_alu.m_cr.dir = bitfield(data, 6);
|
||||
v.m_alu.m_cr.irq = bitfield(data, 7);
|
||||
break;
|
||||
case 1: // FC (Frequency Control)
|
||||
v.m_alu.m_fc = bitfield(data, 1, 15);
|
||||
break;
|
||||
case 2: // STRT-H (Loop Start Register High)
|
||||
v.m_alu.m_start = (v.m_alu.m_start & ~0x1fff0000) | (bitfield<u32>(data, 0, 13) << 16);
|
||||
break;
|
||||
case 3: // STRT-L (Loop Start Register Low)
|
||||
v.m_alu.m_start = (v.m_alu.m_start & ~0xffe0) | (data & 0xffe0);
|
||||
break;
|
||||
case 4: // END-H (Loop End Register High)
|
||||
v.m_alu.m_end = (v.m_alu.m_end & ~0x1fff0000) | (bitfield<u32>(data, 0, 13) << 16);
|
||||
break;
|
||||
case 5: // END-L (Loop End Register Low)
|
||||
v.m_alu.m_end = (v.m_alu.m_end & ~0xffe0) | (data & 0xffe0);
|
||||
break;
|
||||
case 6: // K2 (Filter Cutoff Coefficient #2)
|
||||
v.m_filter.m_k2 = data & 0xfff0;
|
||||
break;
|
||||
case 7: // K1 (Filter Cutoff Coefficient #1)
|
||||
v.m_filter.m_k1 = data & 0xfff0;
|
||||
break;
|
||||
case 8: // Volume
|
||||
v.m_volume = bitfield(data, 4, 12);
|
||||
break;
|
||||
case 9: // CA (Filter Config, Channel Assign)
|
||||
v.m_cr.ca = bitfield(data, 0, 4);
|
||||
v.m_filter.m_lp = bitfield(data, 4, 2);
|
||||
break;
|
||||
case 10: // ACCH (Accumulator High)
|
||||
v.m_alu.m_accum = (v.m_alu.m_accum & ~0x1fff0000) | (bitfield<u32>(data, 0, 13) << 16);
|
||||
break;
|
||||
case 11: // ACCL (Accumulator Low)
|
||||
v.m_alu.m_accum = (v.m_alu.m_accum & ~0xffff) | data;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,97 +0,0 @@
|
|||
/*
|
||||
License: BSD-3-Clause
|
||||
see https://github.com/cam900/vgsound_emu/blob/vgsound_emu_v1/LICENSE for more details
|
||||
|
||||
Copyright holder(s): cam900
|
||||
Modifiers and Contributors for Furnace: cam900
|
||||
Ensoniq ES5504 emulation core
|
||||
|
||||
See es550x.cpp for more info
|
||||
*/
|
||||
|
||||
#include "es550x.hpp"
|
||||
|
||||
#ifndef _VGSOUND_EMU_ES5504_HPP
|
||||
#define _VGSOUND_EMU_ES5504_HPP
|
||||
|
||||
#pragma once
|
||||
|
||||
// ES5504 specific
|
||||
class es5504_core : public es550x_shared_core
|
||||
{
|
||||
public:
|
||||
// constructor
|
||||
es5504_core(es550x_intf &intf)
|
||||
: es550x_shared_core(intf)
|
||||
, m_voice{*this,*this,*this,*this,*this,
|
||||
*this,*this,*this,*this,*this,
|
||||
*this,*this,*this,*this,*this,
|
||||
*this,*this,*this,*this,*this,
|
||||
*this,*this,*this,*this,*this}
|
||||
{
|
||||
}
|
||||
// host interface
|
||||
u16 host_r(u8 address);
|
||||
void host_w(u8 address, u16 data);
|
||||
|
||||
// internal state
|
||||
virtual void reset() override;
|
||||
virtual void tick() override;
|
||||
|
||||
// less cycle accurate, but also less cpu heavy update routine
|
||||
void tick_perf();
|
||||
|
||||
// 16 analog output channels
|
||||
s32 out(u8 ch) { return m_ch[ch & 0xf]; }
|
||||
|
||||
//-----------------------------------------------------------------
|
||||
//
|
||||
// for preview/debug purpose only, not for serious emulators
|
||||
//
|
||||
//-----------------------------------------------------------------
|
||||
|
||||
// bypass chips host interface for debug purpose only
|
||||
u16 read(u8 address, bool cpu_access = false);
|
||||
void write(u8 address, u16 data, bool cpu_access = false);
|
||||
|
||||
u16 regs_r(u8 page, u8 address, bool cpu_access = false);
|
||||
void regs_w(u8 page, u8 address, u16 data, bool cpu_access = false);
|
||||
|
||||
u16 regs_r(u8 page, u8 address) { u8 prev = m_page; m_page = page; u16 ret = read(address, false); m_page = prev; return ret; }
|
||||
|
||||
// per-voice outputs
|
||||
s32 voice_out(u8 voice) { return (voice < 25) ? m_voice[voice].m_ch : 0; }
|
||||
|
||||
protected:
|
||||
virtual inline u8 max_voices() override { return 25; }
|
||||
virtual void voice_tick() override;
|
||||
|
||||
private:
|
||||
// es5504 voice structs
|
||||
struct voice_t : es550x_voice_t
|
||||
{
|
||||
// constructor
|
||||
voice_t(es5504_core &host)
|
||||
: es550x_voice_t(20, 9, false)
|
||||
, m_host(host)
|
||||
{}
|
||||
|
||||
// internal state
|
||||
virtual void reset() override;
|
||||
virtual void fetch(u8 voice, u8 cycle) override;
|
||||
virtual void tick(u8 voice) override;
|
||||
|
||||
void adc_exec();
|
||||
|
||||
// registers
|
||||
es5504_core &m_host;
|
||||
u16 m_volume = 0; // 12 bit Volume
|
||||
s32 m_ch = 0; // channel outputs
|
||||
};
|
||||
|
||||
voice_t m_voice[25]; // 25 voices
|
||||
u16 m_adc = 0; // ADC register
|
||||
s32 m_ch[16] = {0}; // 16 channel outputs
|
||||
};
|
||||
|
||||
#endif
|
|
@ -1,606 +0,0 @@
|
|||
/*
|
||||
License: BSD-3-Clause
|
||||
see https://github.com/cam900/vgsound_emu/blob/vgsound_emu_v1/LICENSE for more details
|
||||
|
||||
Copyright holder(s): cam900
|
||||
Modifiers and Contributors for Furnace: cam900
|
||||
Ensoniq ES5505 emulation core
|
||||
|
||||
see es550x.cpp for more info
|
||||
*/
|
||||
|
||||
#include "es5505.hpp"
|
||||
|
||||
// Internal functions
|
||||
void es5505_core::tick()
|
||||
{
|
||||
m_voice_update = false;
|
||||
m_voice_end = false;
|
||||
// CLKIN
|
||||
if (m_clkin.tick())
|
||||
{
|
||||
// SERBCLK
|
||||
if (m_clkin.m_edge.m_changed) // BCLK is freely running clock
|
||||
{
|
||||
if (m_bclk.tick())
|
||||
{
|
||||
m_intf.bclk(m_bclk.current_edge());
|
||||
// Serial output
|
||||
if (m_bclk.falling_edge())
|
||||
{
|
||||
// SERLRCLK
|
||||
if (m_lrclk.tick())
|
||||
m_intf.lrclk(m_lrclk.current_edge());
|
||||
}
|
||||
// SERWCLK
|
||||
if (m_lrclk.m_edge.m_changed)
|
||||
m_wclk = 0;
|
||||
if (m_bclk.falling_edge())
|
||||
{
|
||||
if (m_wclk == ((m_sermode.sony_bb) ? 1 : 0))
|
||||
{
|
||||
if (m_lrclk.current_edge())
|
||||
{
|
||||
for (int i = 0; i < 4; i++)
|
||||
{
|
||||
// copy output
|
||||
m_output[i] = m_output_temp[i];
|
||||
m_output_latch[i] = m_ch[i];
|
||||
m_output_temp[i].reset();
|
||||
// clamp to 16 bit (upper 5 bits are overflow guard bits)
|
||||
m_output_latch[i].m_left = clamp<s32>(m_output_latch[i].m_left, -0x8000, 0x7fff);
|
||||
m_output_latch[i].m_right = clamp<s32>(m_output_latch[i].m_right, -0x8000, 0x7fff);
|
||||
// set signed
|
||||
if (m_output_latch[i].m_left < 0)
|
||||
m_output_temp[i].m_left = -1;
|
||||
if (m_output_latch[i].m_right < 0)
|
||||
m_output_temp[i].m_right = -1;
|
||||
}
|
||||
}
|
||||
m_wclk_lr = m_lrclk.current_edge();
|
||||
m_output_bit = 16;
|
||||
}
|
||||
s8 output_bit = --m_output_bit;
|
||||
if (m_output_bit >= 0)
|
||||
{
|
||||
for (int i = 0; i < 4; i++)
|
||||
{
|
||||
if (m_wclk_lr) // Right output
|
||||
m_output_temp[i].m_right = (m_output_temp[i].m_right << 1) | bitfield(m_output_latch[i].m_right, output_bit);
|
||||
else // Left output
|
||||
m_output_temp[i].m_left = (m_output_temp[i].m_left << 1) | bitfield(m_output_latch[i].m_left, output_bit);
|
||||
}
|
||||
}
|
||||
m_wclk++;
|
||||
}
|
||||
}
|
||||
}
|
||||
// /CAS, E
|
||||
if (m_clkin.falling_edge()) // falling edge triggers /CAS, E clock
|
||||
{
|
||||
// /CAS
|
||||
if (m_cas.tick())
|
||||
{
|
||||
// /CAS high, E low: get sample address
|
||||
if (m_cas.falling_edge())
|
||||
{
|
||||
// /CAS low, E low: fetch sample
|
||||
if (!m_e.current_edge())
|
||||
m_voice[m_voice_cycle].fetch(m_voice_cycle, m_voice_fetch);
|
||||
}
|
||||
}
|
||||
// E
|
||||
if (m_e.tick())
|
||||
{
|
||||
m_intf.e_pin(m_e.current_edge());
|
||||
if (m_e.rising_edge()) // Host access
|
||||
{
|
||||
m_host_intf.m_rw = m_host_intf.m_rw_strobe;
|
||||
m_host_intf.m_host_access = m_host_intf.m_host_access_strobe;
|
||||
voice_tick();
|
||||
}
|
||||
else if (m_e.falling_edge()) // Voice memory
|
||||
{
|
||||
m_host_intf.m_host_access = false;
|
||||
m_voice[m_voice_cycle].fetch(m_voice_cycle, m_voice_fetch);
|
||||
}
|
||||
if (m_e.current_edge()) // Host interface
|
||||
{
|
||||
if (m_host_intf.m_host_access)
|
||||
{
|
||||
if (m_host_intf.m_rw && (m_e.cycle() == 2)) // Read
|
||||
{
|
||||
m_hd = read(m_ha);
|
||||
m_host_intf.m_host_access = false;
|
||||
}
|
||||
else if ((!m_host_intf.m_rw) && (m_e.cycle() == 2)) // Write
|
||||
write(m_ha, m_hd);
|
||||
}
|
||||
}
|
||||
else if (!m_e.current_edge())
|
||||
{
|
||||
if (m_e.cycle() == 2)
|
||||
{
|
||||
// reset host access state
|
||||
m_hd = 0;
|
||||
m_host_intf.m_host_access_strobe = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// less cycle accurate, but less CPU heavy routine
|
||||
void es5505_core::tick_perf()
|
||||
{
|
||||
m_voice_update = false;
|
||||
m_voice_end = false;
|
||||
// output
|
||||
for (int c = 0; c < 4; c++)
|
||||
{
|
||||
m_output[c].m_left = clamp<s32>(m_ch[c].m_left, -0x8000, 0x7fff);
|
||||
m_output[c].m_right = clamp<s32>(m_ch[c].m_right, -0x8000, 0x7fff);
|
||||
}
|
||||
|
||||
// update
|
||||
// falling edge
|
||||
m_e.m_edge.set(false);
|
||||
m_intf.e_pin(false);
|
||||
m_host_intf.m_host_access = m_host_intf.m_host_access_strobe = false;
|
||||
m_voice[m_voice_cycle].fetch(m_voice_cycle, m_voice_fetch);
|
||||
voice_tick();
|
||||
// rising edge
|
||||
m_e.m_edge.set(true);
|
||||
m_intf.e_pin(true);
|
||||
m_host_intf.m_rw = m_host_intf.m_rw_strobe;
|
||||
m_host_intf.m_host_access = m_host_intf.m_host_access_strobe;
|
||||
// falling edge
|
||||
m_e.m_edge.set(false);
|
||||
m_intf.e_pin(false);
|
||||
m_host_intf.m_host_access = m_host_intf.m_host_access_strobe = false;
|
||||
m_voice[m_voice_cycle].fetch(m_voice_cycle, m_voice_fetch);
|
||||
voice_tick();
|
||||
// rising edge
|
||||
m_e.m_edge.set(true);
|
||||
m_intf.e_pin(true);
|
||||
m_host_intf.m_rw = m_host_intf.m_rw_strobe;
|
||||
m_host_intf.m_host_access = m_host_intf.m_host_access_strobe;
|
||||
}
|
||||
|
||||
void es5505_core::voice_tick()
|
||||
{
|
||||
// Voice updates every 2 E clock cycle (or 4 BCLK clock cycle)
|
||||
m_voice_update = bitfield(m_voice_fetch++, 0);
|
||||
if (m_voice_update)
|
||||
{
|
||||
// Update voice
|
||||
m_voice[m_voice_cycle].tick(m_voice_cycle);
|
||||
|
||||
// Refresh output
|
||||
if ((++m_voice_cycle) > clamp<u8>(m_active, 7, 31)) // 8 ~ 32 voices
|
||||
{
|
||||
m_voice_end = true;
|
||||
m_voice_cycle = 0;
|
||||
for (auto & elem : m_ch)
|
||||
elem.reset();
|
||||
|
||||
for (auto & elem : m_voice)
|
||||
{
|
||||
m_ch[bitfield(elem.m_cr.ca, 0, 2)].m_left += elem.m_ch.m_left;
|
||||
m_ch[bitfield(elem.m_cr.ca, 0, 2)].m_right += elem.m_ch.m_right;
|
||||
elem.m_ch.reset();
|
||||
}
|
||||
}
|
||||
m_voice_fetch = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void es5505_core::voice_t::fetch(u8 voice, u8 cycle)
|
||||
{
|
||||
m_alu.m_sample[cycle] = m_host.m_intf.read_sample(voice, bitfield(m_cr.bs, 0), bitfield(m_alu.get_accum_integer() + cycle, 0, m_alu.m_integer));
|
||||
}
|
||||
|
||||
void es5505_core::voice_t::tick(u8 voice)
|
||||
{
|
||||
m_ch.reset();
|
||||
|
||||
// Filter execute
|
||||
m_filter.tick(m_alu.interpolation());
|
||||
|
||||
if (m_alu.busy())
|
||||
{
|
||||
// Send to output
|
||||
m_ch.m_left = volume_calc(m_lvol, sign_ext<s32>(m_filter.m_o4_1, 16));
|
||||
m_ch.m_right = volume_calc(m_rvol, sign_ext<s32>(m_filter.m_o4_1, 16));
|
||||
|
||||
// ALU execute
|
||||
if (m_alu.tick())
|
||||
m_alu.loop_exec();
|
||||
}
|
||||
|
||||
// Update IRQ
|
||||
m_alu.irq_exec(m_host.m_intf, m_host.m_irqv, voice);
|
||||
}
|
||||
|
||||
// volume calculation
|
||||
s32 es5505_core::voice_t::volume_calc(u8 volume, s32 in)
|
||||
{
|
||||
u8 exponent = bitfield(volume, 4, 4);
|
||||
u8 mantissa = bitfield(volume, 0, 4);
|
||||
return exponent ? (in * s32(0x10 | mantissa)) >> (20 - exponent) : 0;
|
||||
}
|
||||
|
||||
void es5505_core::reset()
|
||||
{
|
||||
es550x_shared_core::reset();
|
||||
for (auto & elem : m_voice)
|
||||
elem.reset();
|
||||
|
||||
m_sermode.reset();
|
||||
m_bclk.reset();
|
||||
m_lrclk.reset();
|
||||
m_wclk = 0;
|
||||
m_wclk_lr = false;
|
||||
m_output_bit = 0;
|
||||
for (auto & elem : m_ch)
|
||||
elem.reset();
|
||||
for (auto & elem : m_output)
|
||||
elem.reset();
|
||||
for (auto & elem : m_output_temp)
|
||||
elem.reset();
|
||||
for (auto & elem : m_output_latch)
|
||||
elem.reset();
|
||||
}
|
||||
|
||||
void es5505_core::voice_t::reset()
|
||||
{
|
||||
es550x_shared_core::es550x_voice_t::reset();
|
||||
m_lvol = 0;
|
||||
m_rvol = 0;
|
||||
m_ch.reset();
|
||||
}
|
||||
|
||||
// Accessors
|
||||
u16 es5505_core::host_r(u8 address)
|
||||
{
|
||||
if (!m_host_intf.m_host_access)
|
||||
{
|
||||
m_ha = address;
|
||||
if (m_e.rising_edge()) // update directly
|
||||
m_hd = read(m_ha, true);
|
||||
else
|
||||
{
|
||||
m_host_intf.m_rw_strobe = true;
|
||||
m_host_intf.m_host_access_strobe = true;
|
||||
}
|
||||
}
|
||||
return m_hd;
|
||||
}
|
||||
|
||||
void es5505_core::host_w(u8 address, u16 data)
|
||||
{
|
||||
if (!m_host_intf.m_host_access)
|
||||
{
|
||||
m_ha = address;
|
||||
m_hd = data;
|
||||
if (m_e.rising_edge()) // update directly
|
||||
write(m_ha, m_hd, true);
|
||||
else
|
||||
{
|
||||
m_host_intf.m_rw_strobe = false;
|
||||
m_host_intf.m_host_access_strobe = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
u16 es5505_core::read(u8 address, bool cpu_access)
|
||||
{
|
||||
return regs_r(m_page, address, cpu_access);
|
||||
}
|
||||
|
||||
void es5505_core::write(u8 address, u16 data, bool cpu_access)
|
||||
{
|
||||
regs_w(m_page, address, data, cpu_access);
|
||||
}
|
||||
|
||||
u16 es5505_core::regs_r(u8 page, u8 address, bool cpu_access)
|
||||
{
|
||||
u16 ret = 0xffff;
|
||||
address = bitfield(address, 0, 4); // 4 bit address for CPU access
|
||||
|
||||
if (address >= 13) // Global registers
|
||||
{
|
||||
switch (address)
|
||||
{
|
||||
case 13: // ACT (Number of voices)
|
||||
ret = (ret & ~0x1f) | bitfield(m_active, 0, 5);
|
||||
break;
|
||||
case 14: // IRQV (Interrupting voice vector)
|
||||
ret = (ret & ~0x9f) | (m_irqv.irqb ? 0x80 : 0) | bitfield(m_irqv.voice, 0, 5);
|
||||
if (cpu_access)
|
||||
{
|
||||
m_irqv.clear();
|
||||
if (bitfield(ret, 7) != m_irqv.irqb)
|
||||
m_voice[m_irqv.voice].m_alu.irq_update(m_intf, m_irqv);
|
||||
}
|
||||
break;
|
||||
case 15: // PAGE (Page select register)
|
||||
ret = (ret & ~0x7f) | bitfield(m_page, 0, 7);
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (bitfield(page, 6)) // Channel registers
|
||||
{
|
||||
switch (address)
|
||||
{
|
||||
case 0: // CH0L (Channel 0 Left)
|
||||
case 2: // CH1L (Channel 1 Left)
|
||||
case 4: // CH2L (Channel 2 Left)
|
||||
if (!cpu_access) // CPU can't read here
|
||||
ret = m_ch[bitfield(address, 0, 2)].m_left;
|
||||
break;
|
||||
case 1: // CH0R (Channel 0 Right)
|
||||
case 3: // CH1R (Channel 1 Right)
|
||||
case 5: // CH2R (Channel 2 Right)
|
||||
if (!cpu_access) // CPU can't read here
|
||||
ret = m_ch[bitfield(address, 0, 2)].m_right;
|
||||
break;
|
||||
case 6: // CH3L (Channel 3 Left)
|
||||
if ((!cpu_access) || m_sermode.adc)
|
||||
ret = m_ch[3].m_left;
|
||||
break;
|
||||
case 7: // CH3R (Channel 3 Right)
|
||||
if ((!cpu_access) || m_sermode.adc)
|
||||
ret = m_ch[3].m_right;
|
||||
break;
|
||||
case 8: // SERMODE (Serial Mode)
|
||||
ret = (ret & ~0xf807) |
|
||||
(m_sermode.adc ? 0x01 : 0x00)
|
||||
| (m_sermode.test ? 0x02 : 0x00)
|
||||
| (m_sermode.sony_bb ? 0x04 : 0x00)
|
||||
| (bitfield(m_sermode.msb, 0, 5) << 11);
|
||||
break;
|
||||
case 9: // PAR (Port A/D Register)
|
||||
ret = (ret & ~0x3f) | (m_intf.adc_r() & ~0x3f);
|
||||
break;
|
||||
}
|
||||
}
|
||||
else // Voice specific registers
|
||||
{
|
||||
const u8 voice = bitfield(page, 0, 5); // Voice select
|
||||
voice_t &v = m_voice[voice];
|
||||
if (bitfield(page, 5)) // Page 32 - 63
|
||||
{
|
||||
switch (address)
|
||||
{
|
||||
case 1: // O4(n-1) (Filter 4 Temp Register)
|
||||
ret = v.m_filter.m_o4_1;
|
||||
break;
|
||||
case 2: // O3(n-2) (Filter 3 Temp Register #2)
|
||||
ret = v.m_filter.m_o3_2;
|
||||
break;
|
||||
case 3: // O3(n-1) (Filter 3 Temp Register #1)
|
||||
ret = v.m_filter.m_o3_1;
|
||||
break;
|
||||
case 4: // O2(n-2) (Filter 2 Temp Register #2)
|
||||
ret = v.m_filter.m_o2_2;
|
||||
break;
|
||||
case 5: // O2(n-1) (Filter 2 Temp Register #1)
|
||||
ret = v.m_filter.m_o2_1;
|
||||
break;
|
||||
case 6: // O1(n-1) (Filter 1 Temp Register)
|
||||
ret = v.m_filter.m_o1_1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
else // Page 0 - 31
|
||||
{
|
||||
switch (address)
|
||||
{
|
||||
case 0: // CR (Control Register)
|
||||
ret = (ret & ~0xfff) |
|
||||
(v.m_alu.m_cr.stop0 ? 0x01 : 0x00)
|
||||
| (v.m_alu.m_cr.stop1 ? 0x02 : 0x00)
|
||||
| (bitfield(v.m_cr.bs, 0) ? 0x04 : 0x00)
|
||||
| (v.m_alu.m_cr.lpe ? 0x08 : 0x00)
|
||||
| (v.m_alu.m_cr.ble ? 0x10 : 0x00)
|
||||
| (v.m_alu.m_cr.irqe ? 0x20 : 0x00)
|
||||
| (v.m_alu.m_cr.dir ? 0x40 : 0x00)
|
||||
| (v.m_alu.m_cr.irq ? 0x80 : 0x00)
|
||||
| (bitfield(v.m_cr.ca, 0, 2) << 8)
|
||||
| (bitfield(v.m_filter.m_lp, 0, 2) << 10);
|
||||
break;
|
||||
case 1: // FC (Frequency Control)
|
||||
ret = (ret & ~0xfffe) | (bitfield(v.m_alu.m_fc, 0, 15) << 1);
|
||||
break;
|
||||
case 2: // STRT-H (Loop Start Register High)
|
||||
ret = (ret & ~0x1fff) | bitfield(v.m_alu.m_start, 16, 13);
|
||||
break;
|
||||
case 3: // STRT-L (Loop Start Register Low)
|
||||
ret = (ret & ~0xffe0) | (v.m_alu.m_start & 0xffe0);
|
||||
break;
|
||||
case 4: // END-H (Loop End Register High)
|
||||
ret = (ret & ~0x1fff) | bitfield(v.m_alu.m_end, 16, 13);
|
||||
break;
|
||||
case 5: // END-L (Loop End Register Low)
|
||||
ret = (ret & ~0xffe0) | (v.m_alu.m_end & 0xffe0);
|
||||
break;
|
||||
case 6: // K2 (Filter Cutoff Coefficient #2)
|
||||
ret = (ret & ~0xfff0) | (v.m_filter.m_k2 & 0xfff0);
|
||||
break;
|
||||
case 7: // K1 (Filter Cutoff Coefficient #1)
|
||||
ret = (ret & ~0xfff0) | (v.m_filter.m_k1 & 0xfff0);
|
||||
break;
|
||||
case 8: // LVOL (Left Volume)
|
||||
ret = (ret & ~0xff00) | ((v.m_lvol << 8) & 0xff00);
|
||||
break;
|
||||
case 9: // RVOL (Right Volume)
|
||||
ret = (ret & ~0xff00) | ((v.m_rvol << 8) & 0xff00);
|
||||
break;
|
||||
case 10: // ACCH (Accumulator High)
|
||||
ret = (ret & ~0x1fff) | bitfield(v.m_alu.m_accum, 16, 13);
|
||||
break;
|
||||
case 11: // ACCL (Accumulator Low)
|
||||
ret = bitfield(v.m_alu.m_accum, 0, 16);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void es5505_core::regs_w(u8 page, u8 address, u16 data, bool cpu_access)
|
||||
{
|
||||
address = bitfield(address, 0, 4); // 4 bit address for CPU access
|
||||
|
||||
if (address >= 12) // Global registers
|
||||
{
|
||||
switch (address)
|
||||
{
|
||||
case 13: // ACT (Number of voices)
|
||||
m_active = std::max<u8>(7, bitfield(data, 0, 5));
|
||||
break;
|
||||
case 14: // IRQV (Interrupting voice vector)
|
||||
// Read only
|
||||
break;
|
||||
case 15: // PAGE (Page select register)
|
||||
m_page = bitfield(data, 0, 7);
|
||||
break;
|
||||
}
|
||||
}
|
||||
else // Voice specific registers
|
||||
{
|
||||
if (bitfield(page, 6)) // Channel registers
|
||||
{
|
||||
switch (address)
|
||||
{
|
||||
case 0: // CH0L (Channel 0 Left)
|
||||
if (m_sermode.test)
|
||||
m_ch[0].m_left = data;
|
||||
break;
|
||||
case 1: // CH0R (Channel 0 Right)
|
||||
if (m_sermode.test)
|
||||
m_ch[0].m_right = data;
|
||||
break;
|
||||
case 2: // CH1L (Channel 1 Left)
|
||||
if (m_sermode.test)
|
||||
m_ch[1].m_left = data;
|
||||
break;
|
||||
case 3: // CH1R (Channel 1 Right)
|
||||
if (m_sermode.test)
|
||||
m_ch[1].m_right = data;
|
||||
break;
|
||||
case 4: // CH2L (Channel 2 Left)
|
||||
if (m_sermode.test)
|
||||
m_ch[2].m_left = data;
|
||||
break;
|
||||
case 5: // CH2R (Channel 2 Right)
|
||||
if (m_sermode.test)
|
||||
m_ch[2].m_right = data;
|
||||
break;
|
||||
case 6: // CH3L (Channel 3 Left)
|
||||
if (m_sermode.test)
|
||||
m_ch[3].m_left = data;
|
||||
break;
|
||||
case 7: // CH3R (Channel 3 Right)
|
||||
if (m_sermode.test)
|
||||
m_ch[3].m_right = data;
|
||||
break;
|
||||
case 8: // SERMODE (Serial Mode)
|
||||
m_sermode.adc = bitfield(data, 0);
|
||||
m_sermode.test = bitfield(data, 1);
|
||||
m_sermode.sony_bb = bitfield(data, 2);
|
||||
m_sermode.msb = bitfield(data, 11, 5);
|
||||
break;
|
||||
case 9: // PAR (Port A/D Register)
|
||||
// Read only
|
||||
break;
|
||||
}
|
||||
}
|
||||
else // Voice specific registers
|
||||
{
|
||||
const u8 voice = bitfield(page, 0, 5); // Voice select
|
||||
voice_t &v = m_voice[voice];
|
||||
if (bitfield(page, 5)) // Page 32 - 56
|
||||
{
|
||||
switch (address)
|
||||
{
|
||||
case 1: // O4(n-1) (Filter 4 Temp Register)
|
||||
v.m_filter.m_o4_1 = sign_ext<s32>(data, 16);
|
||||
break;
|
||||
case 2: // O3(n-2) (Filter 3 Temp Register #2)
|
||||
v.m_filter.m_o3_2 = sign_ext<s32>(data, 16);
|
||||
break;
|
||||
case 3: // O3(n-1) (Filter 3 Temp Register #1)
|
||||
v.m_filter.m_o3_1 = sign_ext<s32>(data, 16);
|
||||
break;
|
||||
case 4: // O2(n-2) (Filter 2 Temp Register #2)
|
||||
v.m_filter.m_o2_2 = sign_ext<s32>(data, 16);
|
||||
break;
|
||||
case 5: // O2(n-1) (Filter 2 Temp Register #1)
|
||||
v.m_filter.m_o2_1 = sign_ext<s32>(data, 16);
|
||||
break;
|
||||
case 6: // O1(n-1) (Filter 1 Temp Register)
|
||||
v.m_filter.m_o1_1 = sign_ext<s32>(data, 16);
|
||||
break;
|
||||
}
|
||||
}
|
||||
else // Page 0 - 24
|
||||
{
|
||||
switch (address)
|
||||
{
|
||||
case 0: // CR (Control Register)
|
||||
v.m_alu.m_cr.stop0 = bitfield(data, 0);
|
||||
v.m_alu.m_cr.stop1 = bitfield(data, 1);
|
||||
v.m_cr.bs = bitfield(data, 2);
|
||||
v.m_alu.m_cr.lpe = bitfield(data, 3);
|
||||
v.m_alu.m_cr.ble = bitfield(data, 4);
|
||||
v.m_alu.m_cr.irqe = bitfield(data, 5);
|
||||
v.m_alu.m_cr.dir = bitfield(data, 6);
|
||||
v.m_alu.m_cr.irq = bitfield(data, 7);
|
||||
v.m_cr.ca = bitfield(data, 8, 2);
|
||||
v.m_filter.m_lp = bitfield(data, 10, 2);
|
||||
break;
|
||||
case 1: // FC (Frequency Control)
|
||||
v.m_alu.m_fc = bitfield(data, 1, 15);
|
||||
break;
|
||||
case 2: // STRT-H (Loop Start Register High)
|
||||
v.m_alu.m_start = (v.m_alu.m_start & ~0x1fff0000) | (bitfield<u32>(data, 0, 13) << 16);
|
||||
break;
|
||||
case 3: // STRT-L (Loop Start Register Low)
|
||||
v.m_alu.m_start = (v.m_alu.m_start & ~0xffe0) | (data & 0xffe0);
|
||||
break;
|
||||
case 4: // END-H (Loop End Register High)
|
||||
v.m_alu.m_end = (v.m_alu.m_end & ~0x1fff0000) | (bitfield<u32>(data, 0, 13) << 16);
|
||||
break;
|
||||
case 5: // END-L (Loop End Register Low)
|
||||
v.m_alu.m_end = (v.m_alu.m_end & ~0xffe0) | (data & 0xffe0);
|
||||
break;
|
||||
case 6: // K2 (Filter Cutoff Coefficient #2)
|
||||
v.m_filter.m_k2 = data & 0xfff0;
|
||||
break;
|
||||
case 7: // K1 (Filter Cutoff Coefficient #1)
|
||||
v.m_filter.m_k1 = data & 0xfff0;
|
||||
break;
|
||||
case 8: // LVOL (Left Volume)
|
||||
v.m_lvol = bitfield(data, 8, 8);
|
||||
break;
|
||||
case 9: // RVOL (Right Volume)
|
||||
v.m_rvol = bitfield(data, 8, 8);
|
||||
break;
|
||||
case 10: // ACCH (Accumulator High)
|
||||
v.m_alu.m_accum = (v.m_alu.m_accum & ~0x1fff0000) | (bitfield<u32>(data, 0, 13) << 16);
|
||||
break;
|
||||
case 11: // ACCL (Accumulator Low)
|
||||
v.m_alu.m_accum = (v.m_alu.m_accum & ~0xffff) | data;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,152 +0,0 @@
|
|||
/*
|
||||
License: BSD-3-Clause
|
||||
see https://github.com/cam900/vgsound_emu/blob/vgsound_emu_v1/LICENSE for more details
|
||||
|
||||
Copyright holder(s): cam900
|
||||
Modifiers and Contributors for Furnace: cam900
|
||||
Ensoniq ES5504 emulation core
|
||||
|
||||
See es550x.cpp for more info
|
||||
*/
|
||||
|
||||
#include "es550x.hpp"
|
||||
|
||||
#ifndef _VGSOUND_EMU_ES5505_HPP
|
||||
#define _VGSOUND_EMU_ES5505_HPP
|
||||
|
||||
#pragma once
|
||||
|
||||
// ES5505 specific
|
||||
class es5505_core : public es550x_shared_core
|
||||
{
|
||||
public:
|
||||
// constructor
|
||||
es5505_core(es550x_intf &intf)
|
||||
: es550x_shared_core(intf)
|
||||
, m_voice{*this,*this,*this,*this,*this,*this,*this,*this,
|
||||
*this,*this,*this,*this,*this,*this,*this,*this,
|
||||
*this,*this,*this,*this,*this,*this,*this,*this,
|
||||
*this,*this,*this,*this,*this,*this,*this,*this}
|
||||
{
|
||||
}
|
||||
// host interface
|
||||
u16 host_r(u8 address);
|
||||
void host_w(u8 address, u16 data);
|
||||
|
||||
// internal state
|
||||
virtual void reset() override;
|
||||
virtual void tick() override;
|
||||
|
||||
// less cycle accurate, but also less cpu heavy update routine
|
||||
void tick_perf();
|
||||
|
||||
// clock outputs
|
||||
bool bclk() { return m_bclk.current_edge(); }
|
||||
bool bclk_rising_edge() { return m_bclk.rising_edge(); }
|
||||
bool bclk_falling_edge() { return m_bclk.falling_edge(); }
|
||||
|
||||
// Input mode for Channel 3
|
||||
void lin(s32 in) { if (m_sermode.adc) { m_ch[3].m_left = in; } }
|
||||
void rin(s32 in) { if (m_sermode.adc) { m_ch[3].m_right = in; } }
|
||||
|
||||
// 4 stereo output channels
|
||||
s32 lout(u8 ch) { return m_ch[ch & 0x3].m_left; }
|
||||
s32 rout(u8 ch) { return m_ch[ch & 0x3].m_right; }
|
||||
|
||||
//-----------------------------------------------------------------
|
||||
//
|
||||
// for preview/debug purpose only, not for serious emulators
|
||||
//
|
||||
//-----------------------------------------------------------------
|
||||
|
||||
// bypass chips host interface for debug purpose only
|
||||
u16 read(u8 address, bool cpu_access = false);
|
||||
void write(u8 address, u16 data, bool cpu_access = false);
|
||||
|
||||
u16 regs_r(u8 page, u8 address, bool cpu_access = false);
|
||||
void regs_w(u8 page, u8 address, u16 data, bool cpu_access = false);
|
||||
|
||||
u16 regs_r(u8 page, u8 address) { u8 prev = m_page; m_page = page; u16 ret = read(address, false); m_page = prev; return ret; }
|
||||
|
||||
// per-voice outputs
|
||||
s32 voice_lout(u8 voice) { return (voice < 32) ? m_voice[voice].m_ch.m_left : 0; }
|
||||
s32 voice_rout(u8 voice) { return (voice < 32) ? m_voice[voice].m_ch.m_right : 0; }
|
||||
|
||||
protected:
|
||||
virtual inline u8 max_voices() override { return 32; }
|
||||
virtual void voice_tick() override;
|
||||
|
||||
private:
|
||||
struct output_t
|
||||
{
|
||||
void reset()
|
||||
{
|
||||
m_left = 0;
|
||||
m_right = 0;
|
||||
};
|
||||
|
||||
s32 m_left = 0;
|
||||
s32 m_right = 0;
|
||||
};
|
||||
|
||||
// es5505 voice structs
|
||||
struct voice_t : es550x_voice_t
|
||||
{
|
||||
// constructor
|
||||
voice_t(es5505_core &host)
|
||||
: es550x_voice_t(20, 9, false)
|
||||
, m_host(host)
|
||||
{}
|
||||
|
||||
// internal state
|
||||
virtual void reset() override;
|
||||
virtual void fetch(u8 voice, u8 cycle) override;
|
||||
virtual void tick(u8 voice) override;
|
||||
|
||||
s32 volume_calc(u8 volume, s32 in);
|
||||
|
||||
// registers
|
||||
es5505_core &m_host;
|
||||
u8 m_lvol = 0; // Left volume
|
||||
u8 m_rvol = 0; // Right volume
|
||||
output_t m_ch; // channel output
|
||||
};
|
||||
|
||||
struct sermode_t
|
||||
{
|
||||
sermode_t()
|
||||
: adc(0)
|
||||
, test(0)
|
||||
, sony_bb(0)
|
||||
, msb(0)
|
||||
{};
|
||||
|
||||
void reset()
|
||||
{
|
||||
adc = 0;
|
||||
test = 0;
|
||||
sony_bb = 0;
|
||||
msb = 0;
|
||||
}
|
||||
|
||||
u8 adc : 1; // A/D
|
||||
u8 test : 1; // Test
|
||||
u8 sony_bb : 1; // Sony/BB format serial output
|
||||
u8 msb : 5; // Serial output MSB
|
||||
};
|
||||
|
||||
voice_t m_voice[32]; // 32 voices
|
||||
// Serial related stuffs
|
||||
sermode_t m_sermode; // Serial mode register
|
||||
clock_pulse_t<s8, 4, 0> m_bclk; // BCLK clock (CLKIN / 4), freely running clock
|
||||
clock_pulse_t<s8, 16, 1> m_lrclk; // LRCLK
|
||||
s16 m_wclk = 0; // WCLK
|
||||
bool m_wclk_lr = false; // WCLK, L/R output select
|
||||
s8 m_output_bit = 0; // Bit position in output
|
||||
output_t m_ch[4]; // 4 stereo output channels
|
||||
output_t m_output[4]; // Serial outputs
|
||||
output_t m_output_temp[4]; // temporary signal for serial output
|
||||
output_t m_output_latch[4]; // output latch
|
||||
};
|
||||
|
||||
#endif
|
|
@ -1,794 +0,0 @@
|
|||
/*
|
||||
License: BSD-3-Clause
|
||||
see https://github.com/cam900/vgsound_emu/blob/vgsound_emu_v1/LICENSE for more details
|
||||
|
||||
Copyright holder(s): cam900
|
||||
Modifiers and Contributors for Furnace: cam900
|
||||
Ensoniq ES5506 emulation core
|
||||
|
||||
see es550x.cpp for more info
|
||||
*/
|
||||
|
||||
#include "es5506.hpp"
|
||||
|
||||
// Internal functions
|
||||
void es5506_core::tick()
|
||||
{
|
||||
m_voice_update = false;
|
||||
m_voice_end = false;
|
||||
// CLKIN
|
||||
if (m_clkin.tick())
|
||||
{
|
||||
// BCLK
|
||||
if (m_clkin.m_edge.m_changed && (!m_mode.bclk_en)) // BCLK is freely running clock
|
||||
{
|
||||
if (m_bclk.tick())
|
||||
{
|
||||
m_intf.bclk(m_bclk.current_edge());
|
||||
// Serial output
|
||||
if (!m_mode.lrclk_en)
|
||||
{
|
||||
if (m_bclk.falling_edge())
|
||||
{
|
||||
// LRCLK
|
||||
if (m_lrclk.tick())
|
||||
{
|
||||
m_intf.lrclk(m_lrclk.current_edge());
|
||||
if (m_lrclk.rising_edge())
|
||||
{
|
||||
m_w_st_curr = m_w_st;
|
||||
m_w_end_curr = m_w_end;
|
||||
}
|
||||
if (m_lrclk.falling_edge()) // update width
|
||||
m_lrclk.set_width_latch(m_lr_end);
|
||||
}
|
||||
}
|
||||
}
|
||||
// WCLK
|
||||
if (!m_mode.wclk_en)
|
||||
{
|
||||
if (!m_mode.lrclk_en)
|
||||
{
|
||||
if (m_lrclk.m_edge.m_changed)
|
||||
m_wclk = 0;
|
||||
}
|
||||
if (m_bclk.falling_edge())
|
||||
{
|
||||
if (m_wclk == m_w_st_curr)
|
||||
{
|
||||
m_intf.wclk(true);
|
||||
if (m_lrclk.current_edge())
|
||||
{
|
||||
for (int i = 0; i < 6; i++)
|
||||
{
|
||||
// copy output
|
||||
m_output[i] = m_output_temp[i];
|
||||
m_output_latch[i] = m_ch[i];
|
||||
m_output_temp[i].reset();
|
||||
// clamp to 20 bit (upper 3 bits are overflow guard bits)
|
||||
m_output_latch[i].m_left = clamp<s32>(m_output_latch[i].m_left, -0x80000, 0x7ffff);
|
||||
m_output_latch[i].m_right = clamp<s32>(m_output_latch[i].m_right, -0x80000, 0x7ffff);
|
||||
// set signed
|
||||
if (m_output_latch[i].m_left < 0)
|
||||
m_output_temp[i].m_left = -1;
|
||||
if (m_output_latch[i].m_right < 0)
|
||||
m_output_temp[i].m_right = -1;
|
||||
}
|
||||
}
|
||||
m_wclk_lr = m_lrclk.current_edge();
|
||||
m_output_bit = 20;
|
||||
}
|
||||
if (m_wclk < m_w_end_curr)
|
||||
{
|
||||
s8 output_bit = --m_output_bit;
|
||||
if (m_output_bit >= 0)
|
||||
{
|
||||
for (int i = 0; i < 6; i++)
|
||||
{
|
||||
if (m_wclk_lr) // Right output
|
||||
m_output_temp[i].m_right = (m_output_temp[i].m_right << 1) | bitfield(m_output_latch[i].m_right, output_bit);
|
||||
else // Left output
|
||||
m_output_temp[i].m_left = (m_output_temp[i].m_left << 1) | bitfield(m_output_latch[i].m_left, output_bit);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (m_wclk == m_w_end_curr)
|
||||
m_intf.wclk(false);
|
||||
|
||||
m_wclk++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// /CAS, E
|
||||
if (m_clkin.falling_edge()) // falling edge triggers /CAS, E clock
|
||||
{
|
||||
// /CAS
|
||||
if (m_cas.tick())
|
||||
{
|
||||
// single OTTO master mode, /CAS high, E low: get sample address
|
||||
// single OTTO early mode, /CAS falling, E high: get sample address
|
||||
if (m_cas.falling_edge())
|
||||
{
|
||||
if (!m_e.current_edge())
|
||||
{
|
||||
// single OTTO master mode, /CAS low, E low: fetch sample
|
||||
if (m_mode.master)
|
||||
m_voice[m_voice_cycle].fetch(m_voice_cycle, m_voice_fetch);
|
||||
}
|
||||
else if (m_e.current_edge())
|
||||
{
|
||||
// dual OTTO slave mode, /CAS low, E high: fetch sample
|
||||
if (m_mode.dual && (!m_mode.master)) // Dual OTTO, slave mode
|
||||
m_voice[m_voice_cycle].fetch(m_voice_cycle, m_voice_fetch);
|
||||
}
|
||||
}
|
||||
}
|
||||
// E
|
||||
if (m_e.tick())
|
||||
{
|
||||
m_intf.e_pin(m_e.current_edge());
|
||||
if (m_e.rising_edge())
|
||||
{
|
||||
m_host_intf.m_rw = m_host_intf.m_rw_strobe;
|
||||
m_host_intf.m_host_access = m_host_intf.m_host_access_strobe;
|
||||
}
|
||||
else if (m_e.falling_edge())
|
||||
{
|
||||
m_host_intf.m_host_access = false;
|
||||
voice_tick();
|
||||
}
|
||||
if (m_e.current_edge()) // Host interface
|
||||
{
|
||||
if (m_host_intf.m_host_access)
|
||||
{
|
||||
if (m_host_intf.m_rw && (m_e.cycle() == 0)) // Read
|
||||
{
|
||||
m_hd = read(m_ha);
|
||||
m_host_intf.m_host_access = false;
|
||||
}
|
||||
else if ((!m_host_intf.m_rw) && (m_e.cycle() == 2)) // Write
|
||||
write(m_ha, m_hd);
|
||||
}
|
||||
}
|
||||
else if (!m_e.current_edge())
|
||||
{
|
||||
if (m_e.cycle() == 2)
|
||||
{
|
||||
// reset host access state
|
||||
m_hd = 0;
|
||||
m_host_intf.m_host_access_strobe = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// less cycle accurate, but less CPU heavy routine
|
||||
void es5506_core::tick_perf()
|
||||
{
|
||||
m_voice_update = false;
|
||||
m_voice_end = false;
|
||||
// output
|
||||
if (((!m_mode.lrclk_en) && (!m_mode.bclk_en) && (!m_mode.wclk_en)) && (m_w_st < m_w_end))
|
||||
{
|
||||
const int output_bits = 20 - (m_w_end - m_w_st);
|
||||
if (output_bits < 20)
|
||||
{
|
||||
for (int c = 0; c < 6; c++)
|
||||
{
|
||||
m_output[c].m_left = clamp<s32>(m_ch[c].m_left, -0x80000, 0x7ffff) >> output_bits;
|
||||
m_output[c].m_right = clamp<s32>(m_ch[c].m_right, -0x80000, 0x7ffff) >> output_bits;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int c = 0; c < 6; c++)
|
||||
{
|
||||
m_output[c].m_left = 0;
|
||||
m_output[c].m_right = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// update
|
||||
// falling edge
|
||||
m_e.m_edge.set(false);
|
||||
m_intf.e_pin(false);
|
||||
m_host_intf.m_host_access = m_host_intf.m_host_access_strobe = false;
|
||||
m_voice[m_voice_cycle].fetch(m_voice_cycle, m_voice_fetch);
|
||||
voice_tick();
|
||||
// rising edge
|
||||
m_e.m_edge.set(true);
|
||||
m_intf.e_pin(true);
|
||||
m_host_intf.m_rw = m_host_intf.m_rw_strobe;
|
||||
m_host_intf.m_host_access = m_host_intf.m_host_access_strobe;
|
||||
// falling edge
|
||||
m_e.m_edge.set(false);
|
||||
m_intf.e_pin(false);
|
||||
m_host_intf.m_host_access = m_host_intf.m_host_access_strobe = false;
|
||||
m_voice[m_voice_cycle].fetch(m_voice_cycle, m_voice_fetch);
|
||||
voice_tick();
|
||||
// rising edge
|
||||
m_e.m_edge.set(true);
|
||||
m_intf.e_pin(true);
|
||||
m_host_intf.m_rw = m_host_intf.m_rw_strobe;
|
||||
m_host_intf.m_host_access = m_host_intf.m_host_access_strobe;
|
||||
}
|
||||
|
||||
void es5506_core::voice_tick()
|
||||
{
|
||||
// Voice updates every 2 E clock cycle (or 4 BCLK clock cycle)
|
||||
m_voice_update = bitfield(m_voice_fetch++, 0);
|
||||
if (m_voice_update)
|
||||
{
|
||||
// Update voice
|
||||
m_voice[m_voice_cycle].tick(m_voice_cycle);
|
||||
|
||||
// Refresh output
|
||||
if ((++m_voice_cycle) > clamp<u8>(m_active, 4, 31)) // 5 ~ 32 voices
|
||||
{
|
||||
m_voice_end = true;
|
||||
m_voice_cycle = 0;
|
||||
for (auto & elem : m_ch)
|
||||
elem.reset();
|
||||
|
||||
for (auto & elem : m_voice)
|
||||
{
|
||||
const u8 ca = bitfield(elem.m_cr.ca, 0, 3);
|
||||
if (ca < 6)
|
||||
{
|
||||
m_ch[ca].m_left += elem.m_ch.m_left;
|
||||
m_ch[ca].m_right += elem.m_ch.m_right;
|
||||
}
|
||||
elem.m_ch.reset();
|
||||
}
|
||||
}
|
||||
m_voice_fetch = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void es5506_core::voice_t::fetch(u8 voice, u8 cycle)
|
||||
{
|
||||
m_alu.m_sample[cycle] = m_host.m_intf.read_sample(voice, bitfield(m_cr.bs, 0, 1), bitfield(m_alu.get_accum_integer() + cycle, 0, m_alu.m_integer));
|
||||
if (m_cr.cmpd) // Decompress (Upper 8 bit is used for compressed format)
|
||||
m_alu.m_sample[cycle] = decompress(bitfield(m_alu.m_sample[cycle], 8, 8));
|
||||
}
|
||||
|
||||
void es5506_core::voice_t::tick(u8 voice)
|
||||
{
|
||||
m_ch.reset();
|
||||
|
||||
// Filter execute
|
||||
m_filter.tick(m_alu.interpolation());
|
||||
|
||||
if (m_alu.busy())
|
||||
{
|
||||
if (!m_mute)
|
||||
{
|
||||
// Send to output
|
||||
m_ch.m_left = volume_calc(m_lvol, sign_ext<s32>(m_filter.m_o4_1, 16));
|
||||
m_ch.m_right = volume_calc(m_rvol, sign_ext<s32>(m_filter.m_o4_1, 16));
|
||||
}
|
||||
|
||||
// ALU execute
|
||||
if (m_alu.tick())
|
||||
m_alu.loop_exec();
|
||||
}
|
||||
// Envelope
|
||||
if (m_ecount != 0)
|
||||
{
|
||||
// Left and Right volume
|
||||
if (bitfield(m_lvramp, 0, 8) != 0)
|
||||
m_lvol = clamp<s32>(m_lvol + sign_ext<s32>(bitfield(m_lvramp, 0, 8), 8), 0, 0xffff);
|
||||
if (bitfield(m_rvramp, 0, 8) != 0)
|
||||
m_rvol = clamp<s32>(m_rvol + sign_ext<s32>(bitfield(m_rvramp, 0, 8), 8), 0, 0xffff);
|
||||
|
||||
// Filter coeffcient
|
||||
if ((m_k1ramp.ramp != 0) && ((m_k1ramp.slow == 0) || (bitfield(m_filtcount, 0, 3) == 0)))
|
||||
m_filter.m_k1 = clamp<s32>(m_filter.m_k1 + sign_ext<s32>(m_k1ramp.ramp, 8), 0, 0xffff);
|
||||
if ((m_k2ramp.ramp != 0) && ((m_k2ramp.slow == 0) || (bitfield(m_filtcount, 0, 3) == 0)))
|
||||
m_filter.m_k2 = clamp<s32>(m_filter.m_k2 + sign_ext<s32>(m_k2ramp.ramp, 8), 0, 0xffff);
|
||||
|
||||
m_ecount--;
|
||||
}
|
||||
m_filtcount = bitfield(m_filtcount + 1, 0, 3);
|
||||
|
||||
// Update IRQ
|
||||
m_alu.irq_exec(m_host.m_intf, m_host.m_irqv, voice);
|
||||
}
|
||||
|
||||
// Compressed format
|
||||
s16 es5506_core::voice_t::decompress(u8 sample)
|
||||
{
|
||||
u8 exponent = bitfield(sample, 5, 3);
|
||||
u8 mantissa = bitfield(sample, 0, 5);
|
||||
return (exponent > 0) ?
|
||||
s16(((bitfield(mantissa, 4) ? 0x10 : ~0x1f) | bitfield(mantissa, 0, 4)) << (4 + (exponent - 1))) :
|
||||
s16(((bitfield(mantissa, 4) ? ~0xf : 0) | bitfield(mantissa, 0, 4)) << 4);
|
||||
}
|
||||
|
||||
// volume calculation
|
||||
s32 es5506_core::voice_t::volume_calc(u16 volume, s32 in)
|
||||
{
|
||||
u8 exponent = bitfield(volume, 12, 4);
|
||||
u8 mantissa = bitfield(volume, 4, 8);
|
||||
return (in * s32(0x100 | mantissa)) >> (20 - exponent);
|
||||
}
|
||||
|
||||
void es5506_core::reset()
|
||||
{
|
||||
es550x_shared_core::reset();
|
||||
for (auto & elem : m_voice)
|
||||
elem.reset();
|
||||
|
||||
m_read_latch = 0xffffffff;
|
||||
m_write_latch = 0xffffffff;
|
||||
m_w_st = 0;
|
||||
m_w_end = 0;
|
||||
m_lr_end = 0;
|
||||
m_w_st_curr = 0;
|
||||
m_w_end_curr = 0;
|
||||
m_mode.reset();
|
||||
m_bclk.reset();
|
||||
m_lrclk.reset(32);
|
||||
m_wclk = 0;
|
||||
m_wclk_lr = false;
|
||||
m_output_bit = 0;
|
||||
for (auto & elem : m_ch)
|
||||
elem.reset();
|
||||
for (auto & elem : m_output)
|
||||
elem.reset();
|
||||
for (auto & elem : m_output_temp)
|
||||
elem.reset();
|
||||
for (auto & elem : m_output_latch)
|
||||
elem.reset();
|
||||
}
|
||||
|
||||
void es5506_core::voice_t::reset()
|
||||
{
|
||||
es550x_shared_core::es550x_voice_t::reset();
|
||||
m_lvol = 0;
|
||||
m_lvramp = 0;
|
||||
m_rvol = 0;
|
||||
m_rvramp = 0;
|
||||
m_ecount = 0;
|
||||
m_k2ramp.reset();
|
||||
m_k1ramp.reset();
|
||||
m_filtcount = 0;
|
||||
m_ch.reset();
|
||||
m_mute = false;
|
||||
}
|
||||
|
||||
// Accessors
|
||||
u8 es5506_core::host_r(u8 address)
|
||||
{
|
||||
if (!m_host_intf.m_host_access)
|
||||
{
|
||||
m_ha = address;
|
||||
if (m_e.rising_edge()) // update directly
|
||||
m_hd = read(m_ha, true);
|
||||
else
|
||||
{
|
||||
m_host_intf.m_rw_strobe = true;
|
||||
m_host_intf.m_host_access_strobe = true;
|
||||
}
|
||||
}
|
||||
return m_hd;
|
||||
}
|
||||
|
||||
void es5506_core::host_w(u8 address, u8 data)
|
||||
{
|
||||
if (!m_host_intf.m_host_access)
|
||||
{
|
||||
m_ha = address;
|
||||
m_hd = data;
|
||||
if (m_e.rising_edge()) // update directly
|
||||
write(m_ha, m_hd, true);
|
||||
else
|
||||
{
|
||||
m_host_intf.m_rw_strobe = false;
|
||||
m_host_intf.m_host_access_strobe = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
u8 es5506_core::read(u8 address, bool cpu_access)
|
||||
{
|
||||
const u8 byte = bitfield(address, 0, 2); // byte select
|
||||
const u8 shift = 24 - (byte << 3);
|
||||
if (byte != 0) // Return already latched register if not highest byte is accessing
|
||||
return bitfield(m_read_latch, shift, 8);
|
||||
|
||||
address = bitfield(address, 2, 4); // 4 bit address for CPU access
|
||||
|
||||
// get read register
|
||||
m_read_latch = regs_r(m_page, address, cpu_access);
|
||||
|
||||
return bitfield(m_read_latch, 24, 8);
|
||||
}
|
||||
|
||||
void es5506_core::write(u8 address, u8 data, bool cpu_access)
|
||||
{
|
||||
const u8 byte = bitfield(address, 0, 2); // byte select
|
||||
const u8 shift = 24 - (byte << 3);
|
||||
address = bitfield(address, 2, 4); // 4 bit address for CPU access
|
||||
|
||||
// Update register latch
|
||||
m_write_latch = (m_write_latch & ~(0xff << shift)) | (u32(data) << shift);
|
||||
|
||||
if (byte != 3) // Wait until lowest byte is writed
|
||||
return;
|
||||
|
||||
regs_w(m_page, address, m_write_latch, cpu_access);
|
||||
|
||||
// Reset latch
|
||||
m_write_latch = 0;
|
||||
}
|
||||
|
||||
u32 es5506_core::regs_r(u8 page, u8 address, bool cpu_access)
|
||||
{
|
||||
u32 read_latch = 0xffffffff;
|
||||
if (address >= 13) // Global registers
|
||||
{
|
||||
switch (address)
|
||||
{
|
||||
case 13: // POT (Pot A/D Register)
|
||||
read_latch = (read_latch & ~0x3ff) | bitfield(m_intf.adc_r(), 0, 10);
|
||||
break;
|
||||
case 14: // IRQV (Interrupting voice vector)
|
||||
read_latch = (read_latch & ~0x9f) | (m_irqv.irqb ? 0x80 : 0) | bitfield(m_irqv.voice, 0, 5);
|
||||
if (cpu_access)
|
||||
{
|
||||
m_irqv.clear();
|
||||
if (bitfield(read_latch, 7) != m_irqv.irqb)
|
||||
m_voice[m_irqv.voice].m_alu.irq_update(m_intf, m_irqv);
|
||||
}
|
||||
break;
|
||||
case 15: // PAGE (Page select register)
|
||||
read_latch = (read_latch & ~0x7f) | bitfield(m_page, 0, 7);
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (bitfield(page, 6)) // Channel registers are Write only
|
||||
{
|
||||
if (!cpu_access) // CPU can't read here
|
||||
{
|
||||
switch (address)
|
||||
{
|
||||
case 0: // CH0L (Channel 0 Left)
|
||||
case 2: // CH1L (Channel 1 Left)
|
||||
case 4: // CH2L (Channel 2 Left)
|
||||
case 6: // CH3L (Channel 3 Left)
|
||||
case 8: // CH4L (Channel 4 Left)
|
||||
case 10: // CH5L (Channel 5 Left)
|
||||
read_latch = m_ch[bitfield(address, 1, 3)].m_left;
|
||||
break;
|
||||
case 1: // CH0R (Channel 0 Right)
|
||||
case 3: // CH1R (Channel 1 Right)
|
||||
case 5: // CH2R (Channel 2 Right)
|
||||
case 7: // CH3R (Channel 3 Right)
|
||||
case 9: // CH4R (Channel 4 Right)
|
||||
case 11: // CH5R (Channel 5 Right)
|
||||
read_latch = m_ch[bitfield(address, 1, 3)].m_right;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
const u8 voice = bitfield(page, 0, 5); // Voice select
|
||||
voice_t &v = m_voice[voice];
|
||||
if (bitfield(page, 5)) // Page 32 - 63
|
||||
{
|
||||
switch (address)
|
||||
{
|
||||
case 0: // CR (Control Register)
|
||||
read_latch = (read_latch & ~0xffff) |
|
||||
(v.m_alu.m_cr.stop0 ? 0x0001 : 0x0000)
|
||||
| (v.m_alu.m_cr.stop1 ? 0x0002 : 0x0000)
|
||||
| (v.m_alu.m_cr.lei ? 0x0004 : 0x0000)
|
||||
| (v.m_alu.m_cr.lpe ? 0x0008 : 0x0000)
|
||||
| (v.m_alu.m_cr.ble ? 0x0010 : 0x0000)
|
||||
| (v.m_alu.m_cr.irqe ? 0x0020 : 0x0000)
|
||||
| (v.m_alu.m_cr.dir ? 0x0040 : 0x0000)
|
||||
| (v.m_alu.m_cr.irq ? 0x0080 : 0x0000)
|
||||
| (bitfield(v.m_filter.m_lp, 0, 2) << 8)
|
||||
| (bitfield(v.m_cr.ca, 0, 3) << 10)
|
||||
| (v.m_cr.cmpd ? 0x2000 : 0x0000)
|
||||
| (bitfield(v.m_cr.bs, 0, 2) << 14);
|
||||
break;
|
||||
case 1: // START (Loop Start Register)
|
||||
read_latch = (read_latch & ~0xfffff800) | (v.m_alu.m_start & 0xfffff800);
|
||||
break;
|
||||
case 2: // END (Loop End Register)
|
||||
read_latch = (read_latch & ~0xffffff80) | (v.m_alu.m_end & 0xffffff80);
|
||||
break;
|
||||
case 3: // ACCUM (Accumulator Register)
|
||||
read_latch = v.m_alu.m_accum;
|
||||
break;
|
||||
case 4: // O4(n-1) (Filter 4 Temp Register)
|
||||
if (cpu_access)
|
||||
read_latch = (read_latch & ~0x3ffff) | bitfield(v.m_filter.m_o4_1, 0, 18);
|
||||
else
|
||||
read_latch = v.m_filter.m_o4_1;
|
||||
break;
|
||||
case 5: // O3(n-2) (Filter 3 Temp Register #2)
|
||||
if (cpu_access)
|
||||
read_latch = (read_latch & ~0x3ffff) | bitfield(v.m_filter.m_o3_2, 0, 18);
|
||||
else
|
||||
read_latch = v.m_filter.m_o3_2;
|
||||
break;
|
||||
case 6: // O3(n-1) (Filter 3 Temp Register #1)
|
||||
if (cpu_access)
|
||||
read_latch = (read_latch & ~0x3ffff) | bitfield(v.m_filter.m_o3_1, 0, 18);
|
||||
else
|
||||
read_latch = v.m_filter.m_o3_1;
|
||||
break;
|
||||
case 7: // O2(n-2) (Filter 2 Temp Register #2)
|
||||
if (cpu_access)
|
||||
read_latch = (read_latch & ~0x3ffff) | bitfield(v.m_filter.m_o2_2, 0, 18);
|
||||
else
|
||||
read_latch = v.m_filter.m_o2_2;
|
||||
break;
|
||||
case 8: // O2(n-1) (Filter 2 Temp Register #1)
|
||||
if (cpu_access)
|
||||
read_latch = (read_latch & ~0x3ffff) | bitfield(v.m_filter.m_o2_1, 0, 18);
|
||||
else
|
||||
read_latch = v.m_filter.m_o2_1;
|
||||
break;
|
||||
case 9: // O1(n-1) (Filter 1 Temp Register)
|
||||
if (cpu_access)
|
||||
read_latch = (read_latch & ~0x3ffff) | bitfield(v.m_filter.m_o1_1, 0, 18);
|
||||
else
|
||||
read_latch = v.m_filter.m_o1_1;
|
||||
break;
|
||||
case 10: // W_ST (Word Clock Start Register)
|
||||
read_latch = (read_latch & ~0x7f) | bitfield(m_w_st, 0, 7);
|
||||
break;
|
||||
case 11: // W_END (Word Clock End Register)
|
||||
read_latch = (read_latch & ~0x7f) | bitfield(m_w_end, 0, 7);
|
||||
break;
|
||||
case 12: // LR_END (Left/Right Clock End Register)
|
||||
read_latch = (read_latch & ~0x7f) | bitfield(m_lr_end, 0, 7);
|
||||
break;
|
||||
}
|
||||
}
|
||||
else // Page 0 - 31
|
||||
{
|
||||
switch (address)
|
||||
{
|
||||
case 0: // CR (Control Register)
|
||||
read_latch = (read_latch & ~0xffff) |
|
||||
(v.m_alu.m_cr.stop0 ? 0x0001 : 0x0000)
|
||||
| (v.m_alu.m_cr.stop1 ? 0x0002 : 0x0000)
|
||||
| (v.m_alu.m_cr.lei ? 0x0004 : 0x0000)
|
||||
| (v.m_alu.m_cr.lpe ? 0x0008 : 0x0000)
|
||||
| (v.m_alu.m_cr.ble ? 0x0010 : 0x0000)
|
||||
| (v.m_alu.m_cr.irqe ? 0x0020 : 0x0000)
|
||||
| (v.m_alu.m_cr.dir ? 0x0040 : 0x0000)
|
||||
| (v.m_alu.m_cr.irq ? 0x0080 : 0x0000)
|
||||
| (bitfield(v.m_filter.m_lp, 0, 2) << 8)
|
||||
| (bitfield(v.m_cr.ca, 0, 3) << 10)
|
||||
| (v.m_cr.cmpd ? 0x2000 : 0x0000)
|
||||
| (bitfield(v.m_cr.bs, 0, 2) << 14);
|
||||
break;
|
||||
case 1: // FC (Frequency Control)
|
||||
read_latch = (read_latch & ~0x1ffff) | bitfield(v.m_alu.m_fc, 0, 17);
|
||||
break;
|
||||
case 2: // LVOL (Left Volume)
|
||||
read_latch = (read_latch & ~0xffff) | bitfield(v.m_lvol, 0, 16);
|
||||
break;
|
||||
case 3: // LVRAMP (Left Volume Ramp)
|
||||
read_latch = (read_latch & ~0xff00) | (bitfield(v.m_lvramp, 0, 8) << 8);
|
||||
break;
|
||||
case 4: // RVOL (Right Volume)
|
||||
read_latch = (read_latch & ~0xffff) | bitfield(v.m_rvol, 0, 16);
|
||||
break;
|
||||
case 5: // RVRAMP (Right Volume Ramp)
|
||||
read_latch = (read_latch & ~0xff00) | (bitfield(v.m_rvramp, 0, 8) << 8);
|
||||
break;
|
||||
case 6: // ECOUNT (Envelope Counter)
|
||||
read_latch = (read_latch & ~0x01ff) | bitfield(v.m_ecount, 0, 9);
|
||||
break;
|
||||
case 7: // K2 (Filter Cutoff Coefficient #2)
|
||||
read_latch = (read_latch & ~0xffff) | bitfield(v.m_filter.m_k2, 0, 16);
|
||||
break;
|
||||
case 8: // K2RAMP (Filter Cutoff Coefficient #2 Ramp)
|
||||
read_latch = (read_latch & ~0xff01) | (bitfield(v.m_k2ramp.ramp, 0, 8) << 8) | (v.m_k2ramp.slow ? 0x0001 : 0x0000);
|
||||
break;
|
||||
case 9: // K1 (Filter Cutoff Coefficient #1)
|
||||
read_latch = (read_latch & ~0xffff) | bitfield(v.m_filter.m_k1, 0, 16);
|
||||
break;
|
||||
case 10: // K1RAMP (Filter Cutoff Coefficient #1 Ramp)
|
||||
read_latch = (read_latch & ~0xff01) | (bitfield(v.m_k1ramp.ramp, 0, 8) << 8) | (v.m_k1ramp.slow ? 0x0001 : 0x0000);
|
||||
break;
|
||||
case 11: // ACT (Number of voices)
|
||||
read_latch = (read_latch & ~0x1f) | bitfield(m_active, 0, 5);
|
||||
break;
|
||||
case 12: // MODE (Global Mode)
|
||||
read_latch = (read_latch & ~0x1f) |
|
||||
(m_mode.lrclk_en ? 0x01 : 0x00)
|
||||
| (m_mode.wclk_en ? 0x02 : 0x00)
|
||||
| (m_mode.bclk_en ? 0x04 : 0x00)
|
||||
| (m_mode.master ? 0x08 : 0x00)
|
||||
| (m_mode.dual ? 0x10 : 0x00);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return read_latch;
|
||||
}
|
||||
|
||||
void es5506_core::regs_w(u8 page, u8 address, u32 data, bool cpu_access)
|
||||
{
|
||||
if (address >= 13) // Global registers
|
||||
{
|
||||
switch (address)
|
||||
{
|
||||
case 13: // POT (Pot A/D Register)
|
||||
// Read only
|
||||
break;
|
||||
case 14: // IRQV (Interrupting voice vector)
|
||||
// Read only
|
||||
break;
|
||||
case 15: // PAGE (Page select register)
|
||||
m_page = bitfield(data, 0, 7);
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (bitfield(page, 6)) // Channel registers are Write only, and for test purposes
|
||||
{
|
||||
switch (address)
|
||||
{
|
||||
case 0: // CH0L (Channel 0 Left)
|
||||
case 2: // CH1L (Channel 1 Left)
|
||||
case 4: // CH2L (Channel 2 Left)
|
||||
case 6: // CH3L (Channel 3 Left)
|
||||
case 8: // CH4L (Channel 4 Left)
|
||||
case 10: // CH5L (Channel 5 Left)
|
||||
m_ch[bitfield(address, 1, 3)].m_left = sign_ext<s32>(bitfield(data, 0, 23), 23);
|
||||
break;
|
||||
case 1: // CH0R (Channel 0 Right)
|
||||
case 3: // CH1R (Channel 1 Right)
|
||||
case 5: // CH2R (Channel 2 Right)
|
||||
case 7: // CH3R (Channel 3 Right)
|
||||
case 9: // CH4R (Channel 4 Right)
|
||||
case 11: // CH5R (Channel 5 Right)
|
||||
m_ch[bitfield(address, 1, 3)].m_right = sign_ext<s32>(bitfield(data, 0, 23), 23);
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
const u8 voice = bitfield(page, 0, 5); // Voice select
|
||||
voice_t &v = m_voice[voice];
|
||||
if (bitfield(page, 5)) // Page 32 - 63
|
||||
{
|
||||
switch (address)
|
||||
{
|
||||
case 0: // CR (Control Register)
|
||||
v.m_alu.m_cr.stop0 = bitfield(data, 0);
|
||||
v.m_alu.m_cr.stop1 = bitfield(data, 1);
|
||||
v.m_alu.m_cr.lei = bitfield(data, 2);
|
||||
v.m_alu.m_cr.lpe = bitfield(data, 3);
|
||||
v.m_alu.m_cr.ble = bitfield(data, 4);
|
||||
v.m_alu.m_cr.irqe = bitfield(data, 5);
|
||||
v.m_alu.m_cr.dir = bitfield(data, 6);
|
||||
v.m_alu.m_cr.irq = bitfield(data, 7);
|
||||
v.m_filter.m_lp = bitfield(data, 8, 2);
|
||||
v.m_cr.ca = std::min<u8>(5, bitfield(data, 10, 3));
|
||||
v.m_cr.cmpd = bitfield(data, 13);
|
||||
v.m_cr.bs = bitfield(data, 14, 2);
|
||||
break;
|
||||
case 1: // START (Loop Start Register)
|
||||
v.m_alu.m_start = data & 0xfffff800;
|
||||
break;
|
||||
case 2: // END (Loop End Register)
|
||||
v.m_alu.m_end = data & 0xffffff80;
|
||||
break;
|
||||
case 3: // ACCUM (Accumulator Register)
|
||||
v.m_alu.m_accum = data;
|
||||
break;
|
||||
case 4: // O4(n-1) (Filter 4 Temp Register)
|
||||
v.m_filter.m_o4_1 = sign_ext<s32>(bitfield(data, 0, 18), 18);
|
||||
break;
|
||||
case 5: // O3(n-2) (Filter 3 Temp Register #2)
|
||||
v.m_filter.m_o3_2 = sign_ext<s32>(bitfield(data, 0, 18), 18);
|
||||
break;
|
||||
case 6: // O3(n-1) (Filter 3 Temp Register #1)
|
||||
v.m_filter.m_o3_1 = sign_ext<s32>(bitfield(data, 0, 18), 18);
|
||||
break;
|
||||
case 7: // O2(n-2) (Filter 2 Temp Register #2)
|
||||
v.m_filter.m_o2_2 = sign_ext<s32>(bitfield(data, 0, 18), 18);
|
||||
break;
|
||||
case 8: // O2(n-1) (Filter 2 Temp Register #1)
|
||||
v.m_filter.m_o2_1 = sign_ext<s32>(bitfield(data, 0, 18), 18);
|
||||
break;
|
||||
case 9: // O1(n-1) (Filter 1 Temp Register)
|
||||
v.m_filter.m_o1_1 = sign_ext<s32>(bitfield(data, 0, 18), 18);
|
||||
break;
|
||||
case 10: // W_ST (Word Clock Start Register)
|
||||
m_w_st = bitfield(data, 0, 7);
|
||||
break;
|
||||
case 11: // W_END (Word Clock End Register)
|
||||
m_w_end = bitfield(data, 0, 7);
|
||||
break;
|
||||
case 12: // LR_END (Left/Right Clock End Register)
|
||||
m_lr_end = bitfield(data, 0, 7);
|
||||
m_lrclk.set_width(m_lr_end);
|
||||
break;
|
||||
}
|
||||
}
|
||||
else // Page 0 - 31
|
||||
{
|
||||
switch (address)
|
||||
{
|
||||
case 0: // CR (Control Register)
|
||||
v.m_alu.m_cr.stop0 = bitfield(data, 0);
|
||||
v.m_alu.m_cr.stop1 = bitfield(data, 1);
|
||||
v.m_alu.m_cr.lei = bitfield(data, 2);
|
||||
v.m_alu.m_cr.lpe = bitfield(data, 3);
|
||||
v.m_alu.m_cr.ble = bitfield(data, 4);
|
||||
v.m_alu.m_cr.irqe = bitfield(data, 5);
|
||||
v.m_alu.m_cr.dir = bitfield(data, 6);
|
||||
v.m_alu.m_cr.irq = bitfield(data, 7);
|
||||
v.m_filter.m_lp = bitfield(data, 8, 2);
|
||||
v.m_cr.ca = std::min<u8>(5, bitfield(data, 10, 3));
|
||||
v.m_cr.cmpd = bitfield(data, 13);
|
||||
v.m_cr.bs = bitfield(data, 14, 2);
|
||||
break;
|
||||
case 1: // FC (Frequency Control)
|
||||
v.m_alu.m_fc = bitfield(data, 0, 17);
|
||||
break;
|
||||
case 2: // LVOL (Left Volume)
|
||||
v.m_lvol = bitfield(data, 0, 16);
|
||||
break;
|
||||
case 3: // LVRAMP (Left Volume Ramp)
|
||||
v.m_lvramp = bitfield(data, 8, 8);
|
||||
break;
|
||||
case 4: // RVOL (Right Volume)
|
||||
v.m_rvol = bitfield(data, 0, 16);
|
||||
break;
|
||||
case 5: // RVRAMP (Right Volume Ramp)
|
||||
v.m_rvramp = bitfield(data, 8, 8);
|
||||
break;
|
||||
case 6: // ECOUNT (Envelope Counter)
|
||||
v.m_ecount = bitfield(data, 0, 9);
|
||||
break;
|
||||
case 7: // K2 (Filter Cutoff Coefficient #2)
|
||||
v.m_filter.m_k2 = bitfield(data, 0, 16);
|
||||
break;
|
||||
case 8: // K2RAMP (Filter Cutoff Coefficient #2 Ramp)
|
||||
v.m_k2ramp.slow = bitfield(data, 0);
|
||||
v.m_k2ramp.ramp = bitfield(data, 8, 8);
|
||||
break;
|
||||
case 9: // K1 (Filter Cutoff Coefficient #1)
|
||||
v.m_filter.m_k1 = bitfield(data, 0, 16);
|
||||
break;
|
||||
case 10: // K1RAMP (Filter Cutoff Coefficient #1 Ramp)
|
||||
v.m_k1ramp.slow = bitfield(data, 0);
|
||||
v.m_k1ramp.ramp = bitfield(data, 8, 8);
|
||||
break;
|
||||
case 11: // ACT (Number of voices)
|
||||
m_active = std::max<u8>(4, bitfield(data, 0, 5));
|
||||
break;
|
||||
case 12: // MODE (Global Mode)
|
||||
m_mode.lrclk_en = bitfield(data, 0);
|
||||
m_mode.wclk_en = bitfield(data, 1);
|
||||
m_mode.bclk_en = bitfield(data, 2);
|
||||
m_mode.master = bitfield(data, 3);
|
||||
m_mode.dual = bitfield(data, 4);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,190 +0,0 @@
|
|||
/*
|
||||
License: BSD-3-Clause
|
||||
see https://github.com/cam900/vgsound_emu/blob/vgsound_emu_v1/LICENSE for more details
|
||||
|
||||
Copyright holder(s): cam900
|
||||
Modifiers and Contributors for Furnace: cam900
|
||||
Ensoniq ES5504 emulation core
|
||||
|
||||
See es550x.cpp for more info
|
||||
*/
|
||||
|
||||
#include "es550x.hpp"
|
||||
|
||||
#ifndef _VGSOUND_EMU_ES5506_HPP
|
||||
#define _VGSOUND_EMU_ES5506_HPP
|
||||
|
||||
#pragma once
|
||||
|
||||
// ES5506 specific
|
||||
class es5506_core : public es550x_shared_core
|
||||
{
|
||||
public:
|
||||
// constructor
|
||||
es5506_core(es550x_intf &intf)
|
||||
: es550x_shared_core(intf)
|
||||
, m_voice{*this,*this,*this,*this,*this,*this,*this,*this,
|
||||
*this,*this,*this,*this,*this,*this,*this,*this,
|
||||
*this,*this,*this,*this,*this,*this,*this,*this,
|
||||
*this,*this,*this,*this,*this,*this,*this,*this}
|
||||
{
|
||||
}
|
||||
// host interface
|
||||
u8 host_r(u8 address);
|
||||
void host_w(u8 address, u8 data);
|
||||
|
||||
// internal state
|
||||
virtual void reset() override;
|
||||
virtual void tick() override;
|
||||
|
||||
// less cycle accurate, but also less cpu heavy update routine
|
||||
void tick_perf();
|
||||
|
||||
// clock outputs
|
||||
bool bclk() { return m_bclk.current_edge(); }
|
||||
bool bclk_rising_edge() { return m_bclk.rising_edge(); }
|
||||
bool bclk_falling_edge() { return m_bclk.falling_edge(); }
|
||||
|
||||
// 6 stereo output channels
|
||||
s32 lout(u8 ch) { return m_output[std::min<u8>(5, ch & 0x7)].m_left; }
|
||||
s32 rout(u8 ch) { return m_output[std::min<u8>(5, ch & 0x7)].m_right; }
|
||||
|
||||
//-----------------------------------------------------------------
|
||||
//
|
||||
// for preview/debug purpose only, not for serious emulators
|
||||
//
|
||||
//-----------------------------------------------------------------
|
||||
|
||||
// bypass chips host interface for debug purpose only
|
||||
u8 read(u8 address, bool cpu_access = false);
|
||||
void write(u8 address, u8 data, bool cpu_access = false);
|
||||
|
||||
u32 regs_r(u8 page, u8 address, bool cpu_access = false);
|
||||
void regs_w(u8 page, u8 address, u32 data, bool cpu_access = false);
|
||||
|
||||
u8 regs8_r(u8 page, u8 address) { u8 prev = m_page; m_page = page; u8 ret = read(address, false); m_page = prev; return ret; }
|
||||
void set_mute(u8 ch, bool mute) { m_voice[ch & 0x1f].m_mute = mute; }
|
||||
|
||||
// per-voice outputs
|
||||
s32 voice_lout(u8 voice) { return (voice < 32) ? m_voice[voice].m_ch.m_left : 0; }
|
||||
s32 voice_rout(u8 voice) { return (voice < 32) ? m_voice[voice].m_ch.m_right : 0; }
|
||||
|
||||
protected:
|
||||
virtual inline u8 max_voices() override { return 32; }
|
||||
virtual void voice_tick() override;
|
||||
|
||||
private:
|
||||
struct output_t
|
||||
{
|
||||
void reset()
|
||||
{
|
||||
m_left = 0;
|
||||
m_right = 0;
|
||||
};
|
||||
|
||||
s32 m_left = 0;
|
||||
s32 m_right = 0;
|
||||
};
|
||||
|
||||
// es5506 voice structs
|
||||
struct voice_t : es550x_voice_t
|
||||
{
|
||||
// constructor
|
||||
voice_t(es5506_core &host)
|
||||
: es550x_voice_t(21, 11, true)
|
||||
, m_host(host) {}
|
||||
|
||||
// internal state
|
||||
virtual void reset() override;
|
||||
virtual void fetch(u8 voice, u8 cycle) override;
|
||||
virtual void tick(u8 voice) override;
|
||||
|
||||
// accessors, getters, setters
|
||||
s16 decompress(u8 sample);
|
||||
s32 volume_calc(u16 volume, s32 in);
|
||||
|
||||
struct filter_ramp_t
|
||||
{
|
||||
filter_ramp_t()
|
||||
: slow(0)
|
||||
, ramp(0)
|
||||
{ };
|
||||
|
||||
void reset()
|
||||
{
|
||||
slow = 0;
|
||||
ramp = 0;
|
||||
};
|
||||
|
||||
u16 slow : 1; // Slow mode flag
|
||||
u16 ramp = 8; // Ramp value
|
||||
};
|
||||
|
||||
// registers
|
||||
es5506_core &m_host;
|
||||
s32 m_lvol = 0; // Left volume - 4 bit exponent, 8 bit mantissa, 4 LSBs are used for fine control of ramp increment for hardware envelope
|
||||
s32 m_lvramp = 0; // Left volume ramp
|
||||
s32 m_rvol = 0; // Right volume
|
||||
s32 m_rvramp = 0; // Righr volume ramp
|
||||
s16 m_ecount = 0; // Envelope counter
|
||||
filter_ramp_t m_k2ramp; // Filter coefficient 2 Ramp
|
||||
filter_ramp_t m_k1ramp; // Filter coefficient 1 Ramp
|
||||
u8 m_filtcount = 0; // Internal counter for slow mode
|
||||
output_t m_ch; // channel output
|
||||
bool m_mute = false; // mute flag (for debug purpose)
|
||||
};
|
||||
|
||||
// 5 bit mode
|
||||
struct mode_t
|
||||
{
|
||||
mode_t()
|
||||
: bclk_en(0)
|
||||
, wclk_en(0)
|
||||
, lrclk_en(0)
|
||||
, master(0)
|
||||
, dual(0)
|
||||
{ };
|
||||
|
||||
void reset()
|
||||
{
|
||||
bclk_en = 1;
|
||||
wclk_en = 1;
|
||||
lrclk_en = 1;
|
||||
master = 0;
|
||||
dual = 0;
|
||||
}
|
||||
|
||||
u8 bclk_en : 1; // Set BCLK to output
|
||||
u8 wclk_en : 1; // Set WCLK to output
|
||||
u8 lrclk_en : 1; // Set LRCLK to output
|
||||
u8 master : 1; // Set memory mode to master
|
||||
u8 dual : 1; // Set dual chip config
|
||||
};
|
||||
|
||||
voice_t m_voice[32]; // 32 voices
|
||||
|
||||
// Host interfaces
|
||||
u32 m_read_latch = 0; // 32 bit register latch for host read
|
||||
u32 m_write_latch = 0; // 32 bit register latch for host write
|
||||
|
||||
// Serial register
|
||||
u8 m_w_st = 0; // Word clock start register
|
||||
u8 m_w_end = 0; // Word clock end register
|
||||
u8 m_lr_end = 0; // Left/Right clock end register
|
||||
mode_t m_mode; // Global mode
|
||||
|
||||
// Serial related stuffs
|
||||
u8 m_w_st_curr = 0; // Word clock start, current status
|
||||
u8 m_w_end_curr = 0; // Word clock end register
|
||||
clock_pulse_t<s8, 4, 0> m_bclk; // BCLK clock (CLKIN / 4), freely running clock
|
||||
clock_pulse_t<s8, 32, 1> m_lrclk; // LRCLK
|
||||
s16 m_wclk = 0; // WCLK
|
||||
bool m_wclk_lr = false; // WCLK, L/R output select
|
||||
s8 m_output_bit = 0; // Bit position in output
|
||||
output_t m_ch[6]; // 6 stereo output channels
|
||||
output_t m_output[6]; // Serial outputs
|
||||
output_t m_output_temp[6]; // temporary signal for serial output
|
||||
output_t m_output_latch[6]; // output latch
|
||||
};
|
||||
|
||||
#endif
|
|
@ -1,78 +0,0 @@
|
|||
/*
|
||||
License: BSD-3-Clause
|
||||
see https://github.com/cam900/vgsound_emu/blob/vgsound_emu_v1/LICENSE for more details
|
||||
|
||||
Copyright holder(s): cam900
|
||||
Modifiers and Contributors for Furnace: cam900
|
||||
Ensoniq ES5504/ES5505/ES5506 emulation core
|
||||
|
||||
After ES5503 DOC's appeared, Ensoniq announces ES5504 DOC II, ES5505 OTIS, ES5506 OTTO.
|
||||
|
||||
These are not just PCM chip; but with built-in 4 pole filters and variable voice limits.
|
||||
|
||||
It can be trades higher sample rate and finer frequency and Tons of voices, or vice-versa.
|
||||
|
||||
These are mainly used with their synthesizers, musical stuffs. It's also mainly paired with ES5510 ESP/ES5511 ESP2 for post processing.
|
||||
ES5506 can be paired with itself, It's called Dual chip configuration and Both chips are can be shares same memory spaces.
|
||||
|
||||
ES5505 was also mainly used on Taito's early- to late-90s arcade hardware for their PCM sample based sound system,
|
||||
paired with ES5510 ESP for post processing. It's configuration is borrowed from Ensoniq's 32 Voice synths powered by these chips.
|
||||
It's difference is external logic to adds per-voice bankswitching looks like what Konami doing on K007232.
|
||||
|
||||
Atari Panther was will be use ES5505, but finally canceled.
|
||||
|
||||
Ensoniq's ISA Sound Card for PC, Soundscape used ES5506, "Elite" model has optional daughterboard with ES5510 for digital effects.
|
||||
|
||||
Related chips:
|
||||
ES5530 "OPUS" variant is 2-in-one chip with built-in ES5506 and Sequoia.
|
||||
ES5540 "OTTOFX" variant is ES5506 and ES5510 merged in single package.
|
||||
ES5548 "OTTO48" variant is used at late-90s ensoniq synths and musical instruments, 2 ES5506s are merged in single package, or with 48 voices in chip?
|
||||
|
||||
Chip difference:
|
||||
ES5504 to ES5505:
|
||||
Total voice amount is expanded to 32, rather than 25.
|
||||
ADC and DAC is completely redesigned. it's has now voice-independent 10 bit and Sony/Burr-Brown format DAC.
|
||||
Output channel and Volume is changed to 16 mono to 4 stereo, 12 bit Analog to 8 bit Stereo digital, also Floating point-ish format and independent per left and right output.
|
||||
Channel 3 is can be Input/Output.
|
||||
Channel output is can be accessible at host for test purpose.
|
||||
Max sample memory is expanded to 2MWords (1MWords * 2 Banks)
|
||||
|
||||
ES5505 to ES5506:
|
||||
Frequency is more finer now: 11 bit fraction rather than 9 bit.
|
||||
Output channel and Volume is changed to 4 stereo to 6 stereo, 8 bit to 16 bit, but only 12 bit is used for calculation; 4 LSB is used for envelope ramping.
|
||||
Transwave flag is added - its helpful for transwave process, with interrupt per voices.
|
||||
Hardware envelope is added - K1, K2, Volume value is can be modified in run-time. also K1, K2 is expanded to 16 bit for finer envelope ramping.
|
||||
Filter calculation resolution is expanded to 18 bit.
|
||||
All channels are output, Serial output is now partially programmable.
|
||||
Max sample memory is expanded to 8MWords (2MWords * 4 Banks)
|
||||
|
||||
Register format between these chips are incompatible.
|
||||
|
||||
*/
|
||||
|
||||
#include "es550x.hpp"
|
||||
|
||||
// Shared functions
|
||||
void es550x_shared_core::reset()
|
||||
{
|
||||
m_host_intf.reset();
|
||||
m_ha = 0;
|
||||
m_hd = 0;
|
||||
m_page = 0;
|
||||
m_irqv.reset();
|
||||
m_active = max_voices() - 1;
|
||||
m_voice_cycle = 0;
|
||||
m_voice_fetch = 0;
|
||||
m_voice_update = false;
|
||||
m_voice_end = false;
|
||||
m_clkin.reset();
|
||||
m_cas.reset();
|
||||
m_e.reset();
|
||||
}
|
||||
|
||||
void es550x_shared_core::es550x_voice_t::reset()
|
||||
{
|
||||
m_cr.reset();
|
||||
m_alu.reset();
|
||||
m_filter.reset();
|
||||
}
|
|
@ -1,284 +0,0 @@
|
|||
/*
|
||||
License: BSD-3-Clause
|
||||
see https://github.com/cam900/vgsound_emu/blob/vgsound_emu_v1/LICENSE for more details
|
||||
|
||||
Copyright holder(s): cam900
|
||||
Modifiers and Contributors for Furnace: cam900
|
||||
Ensoniq ES5504/ES5505/ES5506 emulation core
|
||||
|
||||
See es550x.cpp for more info
|
||||
*/
|
||||
|
||||
#ifndef _VGSOUND_EMU_ES550X_HPP
|
||||
#define _VGSOUND_EMU_ES550X_HPP
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <algorithm>
|
||||
#include <memory>
|
||||
#include "util.hpp"
|
||||
|
||||
// ES5504/ES5505/ES5506 interface
|
||||
class es550x_intf
|
||||
{
|
||||
public:
|
||||
virtual void e_pin(bool state) {} // E output
|
||||
virtual void bclk(bool state) {} // BCLK output (serial specific)
|
||||
virtual void lrclk(bool state) {} // LRCLK output (serial specific)
|
||||
virtual void wclk(bool state) {} // WCLK output (serial specific)
|
||||
|
||||
virtual void irqb(bool state) {} // IRQB output
|
||||
virtual u16 adc_r() { return 0; } // ADC input
|
||||
virtual void adc_w(u16 data) {} // ADC output
|
||||
virtual s16 read_sample(u8 voice, u8 bank, u32 address) { return 0; }
|
||||
};
|
||||
|
||||
// Shared functions for ES5504/ES5505/ES5506
|
||||
class es550x_shared_core
|
||||
{
|
||||
friend class es550x_intf; // es550x specific memory interface
|
||||
public:
|
||||
// constructor
|
||||
es550x_shared_core(es550x_intf &intf)
|
||||
: m_intf(intf)
|
||||
{ };
|
||||
|
||||
// internal state
|
||||
virtual void reset();
|
||||
virtual void tick() {}
|
||||
|
||||
// clock outputs
|
||||
bool _cas() { return m_cas.current_edge(); }
|
||||
bool _cas_rising_edge() { return m_cas.rising_edge(); }
|
||||
bool _cas_falling_edge() { return m_cas.falling_edge(); }
|
||||
|
||||
bool e() { return m_e.current_edge(); }
|
||||
bool e_rising_edge() { return m_e.rising_edge(); }
|
||||
bool e_falling_edge() { return m_e.falling_edge(); }
|
||||
|
||||
//-----------------------------------------------------------------
|
||||
//
|
||||
// for preview/debug purpose only, not for serious emulators
|
||||
//
|
||||
//-----------------------------------------------------------------
|
||||
|
||||
// voice cycle
|
||||
u8 voice_cycle() { return m_voice_cycle; }
|
||||
|
||||
// voice update flag
|
||||
bool voice_update() { return m_voice_update; }
|
||||
bool voice_end() { return m_voice_end; }
|
||||
|
||||
protected:
|
||||
// Constants
|
||||
virtual inline u8 max_voices() { return 32; }
|
||||
|
||||
// Shared registers, functions
|
||||
virtual void voice_tick() {} // voice tick
|
||||
|
||||
// Interrupt bits
|
||||
struct es550x_irq_t
|
||||
{
|
||||
es550x_irq_t()
|
||||
: voice(0)
|
||||
, irqb(1)
|
||||
{ };
|
||||
|
||||
void reset()
|
||||
{
|
||||
voice = 0;
|
||||
irqb = 1;
|
||||
}
|
||||
|
||||
void set(u8 index)
|
||||
{
|
||||
irqb = 0;
|
||||
voice = index;
|
||||
}
|
||||
|
||||
void clear()
|
||||
{
|
||||
irqb = 1;
|
||||
voice = 0;
|
||||
}
|
||||
|
||||
u8 voice : 5;
|
||||
u8 irqb : 1;
|
||||
};
|
||||
|
||||
// Common control bits
|
||||
struct es550x_control_t
|
||||
{
|
||||
es550x_control_t()
|
||||
: ca(0)
|
||||
, adc(0)
|
||||
, bs(0)
|
||||
, cmpd(0)
|
||||
{ };
|
||||
|
||||
void reset()
|
||||
{
|
||||
ca = 0;
|
||||
adc = 0;
|
||||
bs = 0;
|
||||
cmpd = 0;
|
||||
}
|
||||
|
||||
u8 ca : 4; // Channel assign (4 bit (16 channel or Bank) for ES5504, 2 bit (4 stereo channels) for ES5505, 3 bit (6 stereo channels) for ES5506)
|
||||
// ES5504 Specific
|
||||
u8 adc : 1; // Start ADC
|
||||
// ES5505/ES5506 Specific
|
||||
u8 bs : 2; // Bank bit (1 bit for ES5505, 2 bit for ES5506)
|
||||
u8 cmpd : 1; // Use compressed sample format
|
||||
};
|
||||
|
||||
// Accumulator
|
||||
struct es550x_alu_t
|
||||
{
|
||||
es550x_alu_t(u8 integer, u8 fraction, bool transwave)
|
||||
: m_integer(integer)
|
||||
, m_fraction(fraction)
|
||||
, m_total_bits(integer + fraction)
|
||||
, m_accum_mask(u32(std::min<u64>(~0, u64(u64(1) << u64(integer + fraction)) - 1)))
|
||||
, m_transwave(transwave)
|
||||
{}
|
||||
|
||||
const u8 m_integer;
|
||||
const u8 m_fraction;
|
||||
const u8 m_total_bits;
|
||||
const u32 m_accum_mask;
|
||||
const bool m_transwave;
|
||||
|
||||
void reset();
|
||||
bool busy();
|
||||
bool tick();
|
||||
void loop_exec();
|
||||
s32 interpolation();
|
||||
u32 get_accum_integer();
|
||||
void irq_exec(es550x_intf &intf, es550x_irq_t &irqv, u8 index);
|
||||
void irq_update(es550x_intf &intf, es550x_irq_t &irqv) { intf.irqb(irqv.irqb ? false : true); }
|
||||
|
||||
struct es550x_alu_cr_t
|
||||
{
|
||||
es550x_alu_cr_t()
|
||||
: stop0(0)
|
||||
, stop1(0)
|
||||
, lpe(0)
|
||||
, ble(0)
|
||||
, irqe(0)
|
||||
, dir(0)
|
||||
, irq(0)
|
||||
, lei(0)
|
||||
{ };
|
||||
|
||||
void reset()
|
||||
{
|
||||
stop0 = 0;
|
||||
stop1 = 0;
|
||||
lpe = 0;
|
||||
ble = 0;
|
||||
irqe = 0;
|
||||
dir = 0;
|
||||
irq = 0;
|
||||
lei = 0;
|
||||
}
|
||||
|
||||
u8 stop0 : 1; // Stop with ALU
|
||||
u8 stop1 : 1; // Stop with processor
|
||||
u8 lpe : 1; // Loop enable
|
||||
u8 ble : 1; // Bidirectional loop enable
|
||||
u8 irqe : 1; // IRQ enable
|
||||
u8 dir : 1; // Playback direction
|
||||
u8 irq : 1; // IRQ bit
|
||||
u8 lei : 1; // Loop end ignore (ES5506 specific)
|
||||
};
|
||||
|
||||
es550x_alu_cr_t m_cr;
|
||||
u32 m_fc = 0; // Frequency - 6 integer, 9 fraction for ES5506/ES5505, 6 integer, 11 fraction for ES5506
|
||||
u32 m_start = 0; // Start register
|
||||
u32 m_end = 0; // End register
|
||||
u32 m_accum = 0; // Accumulator - 20 integer, 9 fraction for ES5506/ES5505, 21 integer, 11 fraction for ES5506
|
||||
s32 m_sample[2] = {0}; // Samples
|
||||
};
|
||||
|
||||
// Filter
|
||||
struct es550x_filter_t
|
||||
{
|
||||
void reset();
|
||||
void tick(s32 in);
|
||||
s32 lp_exec(s32 coeff, s32 in, s32 prev_out);
|
||||
s32 hp_exec(s32 coeff, s32 in, s32 prev_out, s32 prev_in);
|
||||
|
||||
// Registers
|
||||
u8 m_lp = 0; // Filter mode
|
||||
// Filter coefficient registers
|
||||
s32 m_k2 = 0; // Filter coefficient 2 - 12 bit for filter calculation, 4 LSBs are used for fine control of ramp increment for hardware envelope (ES5506)
|
||||
s32 m_k1 = 0; // Filter coefficient 1
|
||||
// Filter storage registers
|
||||
s32 m_o1_1 = 0; // First stage
|
||||
s32 m_o2_1 = 0; // Second stage
|
||||
s32 m_o2_2 = 0; // Second stage HP
|
||||
s32 m_o3_1 = 0; // Third stage
|
||||
s32 m_o3_2 = 0; // Third stage HP
|
||||
s32 m_o4_1 = 0; // Final stage
|
||||
};
|
||||
|
||||
// Common voice struct
|
||||
struct es550x_voice_t
|
||||
{
|
||||
es550x_voice_t(u8 integer, u8 fraction, bool transwave)
|
||||
: m_alu(integer, fraction, transwave)
|
||||
{}
|
||||
|
||||
// internal state
|
||||
virtual void reset();
|
||||
virtual void fetch(u8 voice, u8 cycle) = 0;
|
||||
virtual void tick(u8 voice) = 0;
|
||||
|
||||
es550x_control_t m_cr;
|
||||
es550x_alu_t m_alu;
|
||||
es550x_filter_t m_filter;
|
||||
};
|
||||
|
||||
|
||||
// Host interfaces
|
||||
struct host_interface_flag_t
|
||||
{
|
||||
host_interface_flag_t()
|
||||
: m_host_access(0)
|
||||
, m_host_access_strobe(0)
|
||||
, m_rw(0)
|
||||
, m_rw_strobe(0)
|
||||
{}
|
||||
|
||||
void reset()
|
||||
{
|
||||
m_host_access = 0;
|
||||
m_host_access_strobe = 0;
|
||||
m_rw = 0;
|
||||
m_rw_strobe = 0;
|
||||
}
|
||||
|
||||
u8 m_host_access : 1; // Host access trigger
|
||||
u8 m_host_access_strobe : 1; // Host access strobe
|
||||
u8 m_rw : 1; // R/W state
|
||||
u8 m_rw_strobe : 1; // R/W strobe
|
||||
};
|
||||
host_interface_flag_t m_host_intf; // Host interface flag
|
||||
u8 m_ha = 0; // Host address (4 bit)
|
||||
u16 m_hd = 0; // Host data (16 bit for ES5504/ES5505, 8 bit for ES5506)
|
||||
u8 m_page = 0; // Page
|
||||
es550x_irq_t m_irqv; // Voice interrupt vector registers
|
||||
// Internal states
|
||||
u8 m_active = max_voices() - 1; // Activated voices (-1, ~25 for ES5504, ~32 for ES5505/ES5506)
|
||||
u8 m_voice_cycle = 0; // Voice cycle
|
||||
u8 m_voice_fetch = 0; // Voice fetch cycle
|
||||
bool m_voice_update = false; // Voice update flag
|
||||
bool m_voice_end = false; // End of one voice cycle flag
|
||||
es550x_intf &m_intf; // es550x specific memory interface
|
||||
clock_pulse_t<s8, 1, 0> m_clkin; // CLKIN clock
|
||||
clock_pulse_t<s8, 2, 1> m_cas; // /CAS clock (CLKIN / 4), falling edge of CLKIN trigger this clock
|
||||
clock_pulse_t<s8, 4, 0> m_e; // E clock (CLKIN / 8), falling edge of CLKIN trigger this clock
|
||||
};
|
||||
|
||||
#endif
|
|
@ -1,116 +0,0 @@
|
|||
/*
|
||||
License: BSD-3-Clause
|
||||
see https://github.com/cam900/vgsound_emu/blob/vgsound_emu_v1/LICENSE for more details
|
||||
|
||||
Copyright holder(s): cam900
|
||||
Modifiers and Contributors for Furnace: cam900
|
||||
Ensoniq ES5504/ES5505/ES5506 Shared Accumulator emulation core
|
||||
|
||||
see es550x.cpp for more info
|
||||
*/
|
||||
|
||||
#include "es550x.hpp"
|
||||
|
||||
// Accumulator functions
|
||||
void es550x_shared_core::es550x_alu_t::reset()
|
||||
{
|
||||
m_cr.reset();
|
||||
m_fc = 0;
|
||||
m_start = 0;
|
||||
m_end = 0;
|
||||
m_accum = 0;
|
||||
m_sample[0] = m_sample[1] = 0;
|
||||
}
|
||||
|
||||
bool es550x_shared_core::es550x_alu_t::busy()
|
||||
{
|
||||
return ((!m_cr.stop0) && (!m_cr.stop1));
|
||||
}
|
||||
|
||||
bool es550x_shared_core::es550x_alu_t::tick()
|
||||
{
|
||||
if (m_cr.dir)
|
||||
m_accum -= m_fc;
|
||||
else
|
||||
m_accum += m_fc;
|
||||
|
||||
m_accum &= m_accum_mask;
|
||||
return ((!m_cr.lei)
|
||||
&& ((( m_cr.dir) && (m_accum < m_start))
|
||||
|| ((!m_cr.dir) && (m_accum > m_end)))) ? true : false;
|
||||
}
|
||||
|
||||
void es550x_shared_core::es550x_alu_t::loop_exec()
|
||||
{
|
||||
if (m_cr.irqe) // Set IRQ
|
||||
m_cr.irq = 1;
|
||||
|
||||
if (m_cr.dir) // Reverse playback
|
||||
{
|
||||
if (m_cr.lpe) // Loop enable
|
||||
{
|
||||
if (m_cr.ble) // Bidirectional
|
||||
{
|
||||
m_cr.dir = 0;
|
||||
m_accum = m_start + (m_start - m_accum);
|
||||
}
|
||||
else// Normal
|
||||
m_accum = m_end - (m_start - m_accum);
|
||||
}
|
||||
else if (m_cr.ble && m_transwave) // m_transwave
|
||||
{
|
||||
m_cr.lpe = m_cr.ble = 0;
|
||||
m_cr.lei = 1; // Loop end ignore
|
||||
m_accum = m_end - (m_start - m_accum);
|
||||
}
|
||||
else // Stop
|
||||
m_cr.stop0 = 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (m_cr.lpe) // Loop enable
|
||||
{
|
||||
if (m_cr.ble) // Bidirectional
|
||||
{
|
||||
m_cr.dir = 1;
|
||||
m_accum = m_end - (m_end - m_accum);
|
||||
}
|
||||
else // Normal
|
||||
m_accum = (m_accum - m_end) + m_start;
|
||||
}
|
||||
else if (m_cr.ble && m_transwave) // m_transwave
|
||||
{
|
||||
m_cr.lpe = m_cr.ble = 0;
|
||||
m_cr.lei = 1; // Loop end ignore
|
||||
m_accum = (m_accum - m_end) + m_start;
|
||||
}
|
||||
else // Stop
|
||||
m_cr.stop0 = 1;
|
||||
}
|
||||
}
|
||||
|
||||
s32 es550x_shared_core::es550x_alu_t::interpolation()
|
||||
{
|
||||
// SF = S1 + ACCfr * (S2 - S1)
|
||||
return m_sample[0] + ((bitfield<s32>(m_accum, std::max<s8>(0, m_fraction - 9), 9) * (m_sample[1] - m_sample[0])) >> 9);
|
||||
}
|
||||
|
||||
u32 es550x_shared_core::es550x_alu_t::get_accum_integer()
|
||||
{
|
||||
return bitfield(m_accum, m_fraction, m_integer);
|
||||
}
|
||||
|
||||
void es550x_shared_core::es550x_alu_t::irq_exec(es550x_intf &intf, es550x_irq_t &irqv, u8 index)
|
||||
{
|
||||
const u8 prev = irqv.irqb;
|
||||
if (m_cr.irq)
|
||||
{
|
||||
if (irqv.irqb)
|
||||
{
|
||||
irqv.set(index);
|
||||
m_cr.irq = 0;
|
||||
}
|
||||
}
|
||||
if (prev != irqv.irqb)
|
||||
irq_update(intf, irqv);
|
||||
}
|
|
@ -1,71 +0,0 @@
|
|||
/*
|
||||
License: BSD-3-Clause
|
||||
see https://github.com/cam900/vgsound_emu/blob/vgsound_emu_v1/LICENSE for more details
|
||||
|
||||
Copyright holder(s): cam900
|
||||
Modifiers and Contributors for Furnace: cam900
|
||||
Ensoniq ES5504/ES5505/ES5506 Shared Filter emulation core
|
||||
|
||||
see es550x.cpp for more info
|
||||
*/
|
||||
|
||||
#include "es550x.hpp"
|
||||
|
||||
// Filter functions
|
||||
void es550x_shared_core::es550x_filter_t::reset()
|
||||
{
|
||||
m_lp = 0;
|
||||
m_k2 = 0;
|
||||
m_k1 = 0;
|
||||
m_o1_1 = 0;
|
||||
m_o2_1 = 0;
|
||||
m_o2_2 = 0;
|
||||
m_o3_1 = 0;
|
||||
m_o3_2 = 0;
|
||||
m_o4_1 = 0;
|
||||
}
|
||||
|
||||
void es550x_shared_core::es550x_filter_t::tick(s32 in)
|
||||
{
|
||||
s32 coeff_k1 = s32(bitfield(m_k1, 4, 12)); // 12 MSB used
|
||||
s32 coeff_k2 = s32(bitfield(m_k2, 4, 12)); // 12 MSB used
|
||||
// Store previous filter data
|
||||
m_o2_2 = m_o2_1;
|
||||
m_o3_2 = m_o3_1;
|
||||
|
||||
// First and second stage: LP/K1, LP/K1 Fixed
|
||||
m_o1_1 = lp_exec(coeff_k1, in, m_o1_1);
|
||||
m_o2_1 = lp_exec(coeff_k1, m_o1_1, m_o2_1);
|
||||
switch (m_lp)
|
||||
{
|
||||
case 0: // LP3 = 0, LP4 = 0: HP/K2, HP/K2
|
||||
default:
|
||||
m_o3_1 = hp_exec(coeff_k2, m_o2_1, m_o3_1, m_o2_2);
|
||||
m_o4_1 = hp_exec(coeff_k2, m_o3_1, m_o4_1, m_o3_2);
|
||||
break;
|
||||
case 1: // LP3 = 0, LP4 = 1: HP/K2, LP/K1
|
||||
m_o3_1 = lp_exec(coeff_k1, m_o2_1, m_o3_1);
|
||||
m_o4_1 = hp_exec(coeff_k2, m_o3_1, m_o4_1, m_o3_2);
|
||||
break;
|
||||
case 2: // LP3 = 1, LP4 = 0: LP/K2, LP/K2
|
||||
m_o3_1 = lp_exec(coeff_k2, m_o2_1, m_o3_1);
|
||||
m_o4_1 = lp_exec(coeff_k2, m_o3_1, m_o4_1);
|
||||
break;
|
||||
case 3: // LP3 = 1, LP4 = 1: LP/K2, LP/K1
|
||||
m_o3_1 = lp_exec(coeff_k1, m_o2_1, m_o3_1);
|
||||
m_o4_1 = lp_exec(coeff_k2, m_o3_1, m_o4_1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
s32 es550x_shared_core::es550x_filter_t::lp_exec(s32 coeff, s32 in, s32 prev_out)
|
||||
{
|
||||
// Yn = K*(Xn - Yn-1) + Yn-1
|
||||
return ((coeff * (in - prev_out)) / 4096) + prev_out;
|
||||
}
|
||||
|
||||
s32 es550x_shared_core::es550x_filter_t::hp_exec(s32 coeff, s32 in, s32 prev_out, s32 prev_in)
|
||||
{
|
||||
// Yn = Xn - Xn-1 + K*Yn-1
|
||||
return in - prev_in + ((coeff * prev_out) / 8192) + (prev_out / 2);
|
||||
}
|
|
@ -1,141 +0,0 @@
|
|||
/*
|
||||
License: BSD-3-Clause
|
||||
see https://github.com/cam900/vgsound_emu/blob/vgsound_emu_v1/LICENSE for more details
|
||||
|
||||
Copyright holder(s): cam900
|
||||
Modifiers and Contributors for Furnace: cam900
|
||||
Various core utilities for vgsound_emu
|
||||
*/
|
||||
|
||||
#ifndef _VGSOUND_EMU_CORE_UTIL_HPP
|
||||
#define _VGSOUND_EMU_CORE_UTIL_HPP
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <iterator>
|
||||
#include <algorithm>
|
||||
#include <memory>
|
||||
|
||||
typedef unsigned char u8;
|
||||
typedef unsigned short u16;
|
||||
typedef unsigned int u32;
|
||||
typedef unsigned long long u64;
|
||||
typedef signed char s8;
|
||||
typedef signed short s16;
|
||||
typedef signed int s32;
|
||||
|
||||
// get bitfield, bitfield(input, position, len)
|
||||
template<typename T> T bitfield(T in, u8 pos, u8 len = 1)
|
||||
{
|
||||
return (in >> pos) & (len ? (T(1 << len) - 1) : 1);
|
||||
}
|
||||
|
||||
// get sign extended value, sign_ext<type>(input, len)
|
||||
template<typename T> T sign_ext(T in, u8 len)
|
||||
{
|
||||
len = std::max<u8>(0, (8 * sizeof(T)) - len);
|
||||
return T(T(in) << len) >> len;
|
||||
}
|
||||
|
||||
// std::clamp is only for C++17 or later; I use my own code
|
||||
template<typename T> T clamp(T in, T min, T max)
|
||||
{
|
||||
return std::min(std::max(in, min), max);
|
||||
}
|
||||
|
||||
template<typename T, T InitWidth, u8 InitEdge = 0>
|
||||
struct clock_pulse_t
|
||||
{
|
||||
void reset(T init = InitWidth)
|
||||
{
|
||||
m_edge.reset();
|
||||
m_width = m_width_latch = m_counter = init;
|
||||
m_cycle = 0;
|
||||
}
|
||||
|
||||
bool tick(T width = 0)
|
||||
{
|
||||
bool carry = ((--m_counter) <= 0);
|
||||
if (carry)
|
||||
{
|
||||
if (!width)
|
||||
m_width = m_width_latch;
|
||||
else
|
||||
m_width = width; // reset width
|
||||
m_counter = m_width;
|
||||
m_cycle = 0;
|
||||
}
|
||||
else
|
||||
m_cycle++;
|
||||
|
||||
m_edge.tick(carry);
|
||||
return carry;
|
||||
}
|
||||
|
||||
void set_width(T width) { m_width = width; }
|
||||
void set_width_latch(T width) { m_width_latch = width; }
|
||||
|
||||
// Accessors
|
||||
bool current_edge() { return m_edge.m_current; }
|
||||
bool rising_edge() { return m_edge.m_rising; }
|
||||
bool falling_edge() { return m_edge.m_rising; }
|
||||
T cycle() { return m_cycle; }
|
||||
|
||||
struct edge_t
|
||||
{
|
||||
edge_t()
|
||||
: m_current(InitEdge ^ 1)
|
||||
, m_previous(InitEdge)
|
||||
, m_rising(0)
|
||||
, m_falling(0)
|
||||
, m_changed(0)
|
||||
{
|
||||
set(InitEdge);
|
||||
}
|
||||
|
||||
void tick(bool toggle)
|
||||
{
|
||||
u8 current = m_current;
|
||||
if (toggle)
|
||||
current ^= 1;
|
||||
set(current);
|
||||
}
|
||||
|
||||
void set(u8 edge)
|
||||
{
|
||||
edge &= 1;
|
||||
m_rising = m_falling = m_changed = 0;
|
||||
if (m_current != edge)
|
||||
{
|
||||
m_changed = 1;
|
||||
if (m_current && (!edge))
|
||||
m_falling = 1;
|
||||
else if ((!m_current) && edge)
|
||||
m_rising = 1;
|
||||
m_current = edge;
|
||||
}
|
||||
m_previous = m_current;
|
||||
}
|
||||
|
||||
void reset()
|
||||
{
|
||||
m_previous = InitEdge;
|
||||
m_current = InitEdge ^ 1;
|
||||
set(InitEdge);
|
||||
}
|
||||
|
||||
u8 m_current : 1; // current edge
|
||||
u8 m_previous : 1; // previous edge
|
||||
u8 m_rising : 1; // rising edge
|
||||
u8 m_falling : 1; // falling edge
|
||||
u8 m_changed : 1; // changed flag
|
||||
};
|
||||
|
||||
edge_t m_edge;
|
||||
T m_width = InitWidth; // clock pulse width
|
||||
T m_width_latch = InitWidth; // clock pulse width latch
|
||||
T m_counter = InitWidth; // clock counter
|
||||
T m_cycle = 0; // clock cycle
|
||||
};
|
||||
|
||||
#endif
|
|
@ -178,12 +178,6 @@ struct DivSample {
|
|||
bool resampleBlep(double rate);
|
||||
bool resampleSinc(double rate);
|
||||
|
||||
/**
|
||||
* check if sample is loopable.
|
||||
* @return whether it was loopable.
|
||||
*/
|
||||
bool isLoopable();
|
||||
|
||||
/**
|
||||
* save this sample to a file.
|
||||
* @param path a path.
|
||||
|
|
|
@ -180,6 +180,7 @@ void FurnaceGUI::drawDebug() {
|
|||
ImGui::Text("loopMode: %d (<NULL!>)",(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);
|
||||
|
|
|
@ -907,7 +907,7 @@ struct FurnaceGUIMacroDesc {
|
|||
float height;
|
||||
const char* displayName;
|
||||
const char** bitfieldBits;
|
||||
const char** modeName;
|
||||
const char* modeName;
|
||||
ImVec4 color;
|
||||
unsigned int bitOffset;
|
||||
bool isBitfield, blockMode, bit30;
|
||||
|
|
|
@ -161,9 +161,8 @@ const char* resampleStrats[DIV_RESAMPLE_MAX]={
|
|||
"best possible"
|
||||
};
|
||||
|
||||
const char* loopMode[DIV_SAMPLE_LOOPMODE_MAX]={
|
||||
"Disable",
|
||||
"Foward",
|
||||
const char* loopMode[DIV_SAMPLE_LOOP_MAX]={
|
||||
"Forward",
|
||||
"Backward",
|
||||
"Pingpong"
|
||||
};
|
||||
|
|
|
@ -247,10 +247,6 @@ const char* n163UpdateBits[8]={
|
|||
"now", "every waveform changed", NULL
|
||||
};
|
||||
|
||||
const char* es5506FilterModes[4]={
|
||||
"HP/K2, HP/K2", "HP/K2, LP/K1", "LP/K2, LP/K2", "LP/K2, LP/K1",
|
||||
};
|
||||
|
||||
const char* suControlBits[5]={
|
||||
"ring mod", "low pass", "high pass", "band pass", NULL
|
||||
};
|
||||
|
@ -263,18 +259,6 @@ const char* panBits[3]={
|
|||
"right", "left", NULL
|
||||
};
|
||||
|
||||
const char* es5506EnvelopeModes[3]={
|
||||
"k1 slowdown", "k2 slowdown", NULL
|
||||
};
|
||||
|
||||
const char* es5506ControlModes[2]={
|
||||
"pause", NULL
|
||||
};
|
||||
|
||||
const char* transwaveControlModes[2]={
|
||||
"slice", NULL
|
||||
};
|
||||
|
||||
const char* oneBit[2]={
|
||||
"on", NULL
|
||||
};
|
||||
|
@ -287,6 +271,10 @@ const char* es5506ControlModes[2]={
|
|||
"pause", NULL
|
||||
};
|
||||
|
||||
const char* transwaveControlModes[2]={
|
||||
"slice", NULL
|
||||
};
|
||||
|
||||
const int orderedOps[4]={
|
||||
0, 2, 1, 3
|
||||
};
|
||||
|
@ -388,21 +376,17 @@ String macroHoverES5506FilterMode(int id, float val, void* u) {
|
|||
String macroLFOWaves(int id, float val, void* u) {
|
||||
switch (((int)val)&3) {
|
||||
case 0:
|
||||
mode="Saw";
|
||||
break;
|
||||
return "Saw";
|
||||
case 1:
|
||||
mode="Square";
|
||||
break;
|
||||
return "Square";
|
||||
case 2:
|
||||
mode="Sine";
|
||||
break;
|
||||
return "Sine";
|
||||
case 3:
|
||||
mode="Random";
|
||||
break;
|
||||
return "Random";
|
||||
default:
|
||||
break;
|
||||
return "???";
|
||||
}
|
||||
return fmt::sprintf("%d: %s",id,mode);
|
||||
return "???";
|
||||
}
|
||||
|
||||
void addAALine(ImDrawList* dl, const ImVec2& p1, const ImVec2& p2, const ImU32 color, float thickness=1.0f) {
|
||||
|
@ -3242,10 +3226,10 @@ void FurnaceGUI::drawInsEdit() {
|
|||
}
|
||||
if (ImGui::BeginTabItem("FM Macros")) {
|
||||
if (ins->type==DIV_INS_OPLL) {
|
||||
macroList.push_back(FurnaceGUIMacroDesc(FM_NAME(FM_SUS),&ins->std.algMacro,0,1,32,uiColors[GUI_COLOR_MACRO_OTHER],false,false,NULL,NULL,true));
|
||||
macroList.push_back(FurnaceGUIMacroDesc(FM_NAME(FM_SUS),&ins->std.algMacro,0,1,32,uiColors[GUI_COLOR_MACRO_OTHER],false,NULL,NULL,true));
|
||||
macroList.push_back(FurnaceGUIMacroDesc(FM_NAME(FM_FB),&ins->std.fbMacro,0,7,96,uiColors[GUI_COLOR_MACRO_OTHER]));
|
||||
macroList.push_back(FurnaceGUIMacroDesc(FM_NAME(FM_DC),&ins->std.fmsMacro,0,1,32,uiColors[GUI_COLOR_MACRO_OTHER],false,false,NULL,NULL,true));
|
||||
macroList.push_back(FurnaceGUIMacroDesc(FM_NAME(FM_DM),&ins->std.amsMacro,0,1,32,uiColors[GUI_COLOR_MACRO_OTHER],false,false,NULL,NULL,true));
|
||||
macroList.push_back(FurnaceGUIMacroDesc(FM_NAME(FM_DC),&ins->std.fmsMacro,0,1,32,uiColors[GUI_COLOR_MACRO_OTHER],false,NULL,NULL,true));
|
||||
macroList.push_back(FurnaceGUIMacroDesc(FM_NAME(FM_DM),&ins->std.amsMacro,0,1,32,uiColors[GUI_COLOR_MACRO_OTHER],false,NULL,NULL,true));
|
||||
} else {
|
||||
macroList.push_back(FurnaceGUIMacroDesc(FM_NAME(FM_ALG),&ins->std.algMacro,0,7,96,uiColors[GUI_COLOR_MACRO_OTHER]));
|
||||
macroList.push_back(FurnaceGUIMacroDesc(FM_NAME(FM_FB),&ins->std.fbMacro,0,7,96,uiColors[GUI_COLOR_MACRO_OTHER]));
|
||||
|
@ -3311,10 +3295,10 @@ void FurnaceGUI::drawInsEdit() {
|
|||
macroList.push_back(FurnaceGUIMacroDesc(FM_NAME(FM_MULT),&ins->std.opMacros[ordi].multMacro,0,15,64,uiColors[GUI_COLOR_MACRO_OTHER]));
|
||||
macroList.push_back(FurnaceGUIMacroDesc(FM_NAME(FM_WS),&ins->std.opMacros[ordi].wsMacro,0,7,64,uiColors[GUI_COLOR_MACRO_OTHER]));
|
||||
|
||||
macroList.push_back(FurnaceGUIMacroDesc(FM_NAME(FM_AM),&ins->std.opMacros[ordi].amMacro,0,1,32,uiColors[GUI_COLOR_MACRO_OTHER],false,false,NULL,NULL,true));
|
||||
macroList.push_back(FurnaceGUIMacroDesc(FM_NAME(FM_VIB),&ins->std.opMacros[ordi].vibMacro,0,1,32,uiColors[GUI_COLOR_MACRO_OTHER],false,false,NULL,NULL,true));
|
||||
macroList.push_back(FurnaceGUIMacroDesc(FM_NAME(FM_KSR),&ins->std.opMacros[ordi].ksrMacro,0,1,32,uiColors[GUI_COLOR_MACRO_OTHER],false,false,NULL,NULL,true));
|
||||
macroList.push_back(FurnaceGUIMacroDesc(FM_NAME(FM_SUS),&ins->std.opMacros[ordi].susMacro,0,1,32,uiColors[GUI_COLOR_MACRO_OTHER],false,false,NULL,NULL,true));
|
||||
macroList.push_back(FurnaceGUIMacroDesc(FM_NAME(FM_AM),&ins->std.opMacros[ordi].amMacro,0,1,32,uiColors[GUI_COLOR_MACRO_OTHER],false,NULL,NULL,true));
|
||||
macroList.push_back(FurnaceGUIMacroDesc(FM_NAME(FM_VIB),&ins->std.opMacros[ordi].vibMacro,0,1,32,uiColors[GUI_COLOR_MACRO_OTHER],false,NULL,NULL,true));
|
||||
macroList.push_back(FurnaceGUIMacroDesc(FM_NAME(FM_KSR),&ins->std.opMacros[ordi].ksrMacro,0,1,32,uiColors[GUI_COLOR_MACRO_OTHER],false,NULL,NULL,true));
|
||||
macroList.push_back(FurnaceGUIMacroDesc(FM_NAME(FM_SUS),&ins->std.opMacros[ordi].susMacro,0,1,32,uiColors[GUI_COLOR_MACRO_OTHER],false,NULL,NULL,true));
|
||||
} else if (ins->type==DIV_INS_OPLL) {
|
||||
macroList.push_back(FurnaceGUIMacroDesc(FM_NAME(FM_TL),&ins->std.opMacros[ordi].tlMacro,0,maxTl,128,uiColors[GUI_COLOR_MACRO_OTHER]));
|
||||
macroList.push_back(FurnaceGUIMacroDesc(FM_NAME(FM_AR),&ins->std.opMacros[ordi].arMacro,0,maxArDr,64,uiColors[GUI_COLOR_MACRO_OTHER]));
|
||||
|
@ -3324,10 +3308,10 @@ void FurnaceGUI::drawInsEdit() {
|
|||
macroList.push_back(FurnaceGUIMacroDesc(FM_NAME(FM_KSL),&ins->std.opMacros[ordi].kslMacro,0,3,32,uiColors[GUI_COLOR_MACRO_OTHER]));
|
||||
macroList.push_back(FurnaceGUIMacroDesc(FM_NAME(FM_MULT),&ins->std.opMacros[ordi].multMacro,0,15,64,uiColors[GUI_COLOR_MACRO_OTHER]));
|
||||
|
||||
macroList.push_back(FurnaceGUIMacroDesc(FM_NAME(FM_AM),&ins->std.opMacros[ordi].amMacro,0,1,32,uiColors[GUI_COLOR_MACRO_OTHER],false,false,NULL,NULL,true));
|
||||
macroList.push_back(FurnaceGUIMacroDesc(FM_NAME(FM_VIB),&ins->std.opMacros[ordi].vibMacro,0,1,32,uiColors[GUI_COLOR_MACRO_OTHER],false,false,NULL,NULL,true));
|
||||
macroList.push_back(FurnaceGUIMacroDesc(FM_NAME(FM_KSR),&ins->std.opMacros[ordi].ksrMacro,0,1,32,uiColors[GUI_COLOR_MACRO_OTHER],false,false,NULL,NULL,true));
|
||||
macroList.push_back(FurnaceGUIMacroDesc(FM_NAME(FM_EGS),&ins->std.opMacros[ordi].egtMacro,0,1,32,uiColors[GUI_COLOR_MACRO_OTHER],false,false,NULL,NULL,true));
|
||||
macroList.push_back(FurnaceGUIMacroDesc(FM_NAME(FM_AM),&ins->std.opMacros[ordi].amMacro,0,1,32,uiColors[GUI_COLOR_MACRO_OTHER],false,NULL,NULL,true));
|
||||
macroList.push_back(FurnaceGUIMacroDesc(FM_NAME(FM_VIB),&ins->std.opMacros[ordi].vibMacro,0,1,32,uiColors[GUI_COLOR_MACRO_OTHER],false,NULL,NULL,true));
|
||||
macroList.push_back(FurnaceGUIMacroDesc(FM_NAME(FM_KSR),&ins->std.opMacros[ordi].ksrMacro,0,1,32,uiColors[GUI_COLOR_MACRO_OTHER],false,NULL,NULL,true));
|
||||
macroList.push_back(FurnaceGUIMacroDesc(FM_NAME(FM_EGS),&ins->std.opMacros[ordi].egtMacro,0,1,32,uiColors[GUI_COLOR_MACRO_OTHER],false,NULL,NULL,true));
|
||||
} else {
|
||||
macroList.push_back(FurnaceGUIMacroDesc(FM_NAME(FM_TL),&ins->std.opMacros[ordi].tlMacro,0,maxTl,128,uiColors[GUI_COLOR_MACRO_OTHER]));
|
||||
macroList.push_back(FurnaceGUIMacroDesc(FM_NAME(FM_AR),&ins->std.opMacros[ordi].arMacro,0,maxArDr,64,uiColors[GUI_COLOR_MACRO_OTHER]));
|
||||
|
@ -3344,7 +3328,7 @@ void FurnaceGUI::drawInsEdit() {
|
|||
macroList.push_back(FurnaceGUIMacroDesc(FM_NAME(FM_AM),&ins->std.opMacros[ordi].amMacro,0,1,32,uiColors[GUI_COLOR_MACRO_OTHER],false,NULL,NULL,true));
|
||||
|
||||
if (ins->type==DIV_INS_FM) {
|
||||
macroList.push_back(FurnaceGUIMacroDesc(FM_NAME(FM_SSG),&ins->std.opMacros[ordi].ssgMacro,0,4,64,uiColors[GUI_COLOR_MACRO_OTHER],false,false,NULL,NULL,true,ssgEnvBits));
|
||||
macroList.push_back(FurnaceGUIMacroDesc(FM_NAME(FM_SSG),&ins->std.opMacros[ordi].ssgMacro,0,4,64,uiColors[GUI_COLOR_MACRO_OTHER],false,NULL,NULL,true,ssgEnvBits));
|
||||
}
|
||||
}
|
||||
drawMacros(macroList);
|
||||
|
@ -3826,22 +3810,22 @@ void FurnaceGUI::drawInsEdit() {
|
|||
ImGui::TableNextColumn();
|
||||
ImGui::Text("%s",noteNames[60+i]);
|
||||
ImGui::TableNextColumn();
|
||||
if (ins->amiga.noteMap[i].ind<0 || ins->amiga.noteMap[i].ind>=e->song.sampleLen) {
|
||||
if (ins->amiga.noteMap[i].map<0 || ins->amiga.noteMap[i].map>=e->song.sampleLen) {
|
||||
sName="-- empty --";
|
||||
ins->amiga.noteMap[i].ind=-1;
|
||||
ins->amiga.noteMap[i].map=-1;
|
||||
} else {
|
||||
sName=e->song.sample[ins->amiga.noteMap[i].ind]->name;
|
||||
sName=e->song.sample[ins->amiga.noteMap[i].map]->name;
|
||||
}
|
||||
ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x);
|
||||
if (ImGui::BeginCombo(fmt::sprintf("##SampleMap_Index_%d",i).c_str(),sName.c_str())) {
|
||||
String id;
|
||||
if (ImGui::Selectable("-- empty --",ins->amiga.noteMap[i].ind==-1)) { PARAMETER
|
||||
ins->amiga.noteMap[i].ind=-1;
|
||||
if (ImGui::Selectable("-- empty --",ins->amiga.noteMap[i].map==-1)) { PARAMETER
|
||||
ins->amiga.noteMap[i].map=-1;
|
||||
}
|
||||
for (int j=0; j<e->song.sampleLen; j++) {
|
||||
id=fmt::sprintf("%d: %s",j,e->song.sample[j]->name);
|
||||
if (ImGui::Selectable(id.c_str(),ins->amiga.noteMap[i].ind==j)) { PARAMETER
|
||||
ins->amiga.noteMap[i].ind=j;
|
||||
if (ImGui::Selectable(id.c_str(),ins->amiga.noteMap[i].map==j)) { PARAMETER
|
||||
ins->amiga.noteMap[i].map=j;
|
||||
if (ins->amiga.noteMap[i].freq<=0) ins->amiga.noteMap[i].freq=(int)((double)e->song.sample[j]->centerRate*pow(2.0,((double)i-48.0)/12.0));
|
||||
}
|
||||
}
|
||||
|
@ -4045,7 +4029,7 @@ void FurnaceGUI::drawInsEdit() {
|
|||
}
|
||||
if (ins->amiga.transWave.enable) {
|
||||
if (ImGui::BeginTabItem("Transwave Macros")) {
|
||||
macroList.push_back(FurnaceGUIMacroDesc("Transwave control",&ins->std.fbMacro,0,1,32,uiColors[GUI_COLOR_MACRO_OTHER],false,false,NULL,NULL,true,transwaveControlModes));
|
||||
macroList.push_back(FurnaceGUIMacroDesc("Transwave control",&ins->std.fbMacro,0,1,32,uiColors[GUI_COLOR_MACRO_OTHER],false,NULL,NULL,true,transwaveControlModes));
|
||||
macroList.push_back(FurnaceGUIMacroDesc("Transwave slice",&ins->std.fmsMacro,0,4095,160,uiColors[GUI_COLOR_MACRO_OTHER]));
|
||||
drawMacros(macroList);
|
||||
ImGui::EndTabItem();
|
||||
|
@ -4833,14 +4817,14 @@ void FurnaceGUI::drawInsEdit() {
|
|||
}
|
||||
if (!panSingleNoBit) {
|
||||
if (ins->type==DIV_INS_AMIGA && ins->std.panLMacro.mode) {
|
||||
macroList.push_back(FurnaceGUIMacroDesc("Surround",&ins->std.panRMacro,0,1,32,uiColors[GUI_COLOR_MACRO_OTHER],false,false,NULL,NULL,true));
|
||||
macroList.push_back(FurnaceGUIMacroDesc("Surround",&ins->std.panRMacro,0,1,32,uiColors[GUI_COLOR_MACRO_OTHER],false,NULL,NULL,true));
|
||||
} else {
|
||||
macroList.push_back(FurnaceGUIMacroDesc("Panning (right)",&ins->std.panRMacro,panMin,panMax,CLAMP(31+panMax-panMin,32,160),uiColors[GUI_COLOR_MACRO_OTHER]));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
macroList.push_back(FurnaceGUIMacroDesc("Pitch",&ins->std.pitchMacro,-2048,2047,160,uiColors[GUI_COLOR_MACRO_PITCH],true,true,macroRelativeMode));
|
||||
macroList.push_back(FurnaceGUIMacroDesc("Pitch",&ins->std.pitchMacro,-2048,2047,160,uiColors[GUI_COLOR_MACRO_PITCH],true,macroRelativeMode));
|
||||
if (ins->type==DIV_INS_FM ||
|
||||
ins->type==DIV_INS_OPM ||
|
||||
ins->type==DIV_INS_STD ||
|
||||
|
@ -4873,7 +4857,7 @@ void FurnaceGUI::drawInsEdit() {
|
|||
}
|
||||
if (ex1Max>0) {
|
||||
if (ins->type==DIV_INS_C64) {
|
||||
macroList.push_back(FurnaceGUIMacroDesc("Filter Mode",&ins->std.ex1Macro,0,ex1Max,64,uiColors[GUI_COLOR_MACRO_OTHER],false,false,NULL,NULL,true,filtModeBits));
|
||||
macroList.push_back(FurnaceGUIMacroDesc("Filter Mode",&ins->std.ex1Macro,0,ex1Max,64,uiColors[GUI_COLOR_MACRO_OTHER],false,NULL,NULL,true,filtModeBits));
|
||||
} else if (ins->type==DIV_INS_SAA1099) {
|
||||
macroList.push_back(FurnaceGUIMacroDesc("Envelope",&ins->std.ex1Macro,0,ex1Max,160,uiColors[GUI_COLOR_MACRO_OTHER],false,NULL,NULL,true,saaEnvBits));
|
||||
} else if (ins->type==DIV_INS_X1_010 && !ins->amiga.useSample) {
|
||||
|
@ -4900,7 +4884,7 @@ void FurnaceGUI::drawInsEdit() {
|
|||
if (ins->type==DIV_INS_C64) {
|
||||
macroList.push_back(FurnaceGUIMacroDesc("Resonance",&ins->std.ex2Macro,0,ex2Max,64,uiColors[GUI_COLOR_MACRO_OTHER]));
|
||||
} else if (ins->type==DIV_INS_N163) {
|
||||
macroList.push_back(FurnaceGUIMacroDesc("Wave Update",&ins->std.ex2Macro,0,ex2Max,64,uiColors[GUI_COLOR_MACRO_OTHER],false,false,NULL,NULL,true,n163UpdateBits));
|
||||
macroList.push_back(FurnaceGUIMacroDesc("Wave Update",&ins->std.ex2Macro,0,ex2Max,64,uiColors[GUI_COLOR_MACRO_OTHER],false,NULL,NULL,true,n163UpdateBits));
|
||||
} else if (ins->type==DIV_INS_FDS) {
|
||||
macroList.push_back(FurnaceGUIMacroDesc("Mod Speed",&ins->std.ex2Macro,0,ex2Max,160,uiColors[GUI_COLOR_MACRO_OTHER]));
|
||||
} else if (ins->type==DIV_INS_SU) {
|
||||
|
@ -4912,12 +4896,12 @@ void FurnaceGUI::drawInsEdit() {
|
|||
} else if (ins->type==DIV_INS_SNES) {
|
||||
macroList.push_back(FurnaceGUIMacroDesc("Gain Rate",&ins->std.ex2Macro,0,ex2Max,160,uiColors[GUI_COLOR_MACRO_VOLUME]));
|
||||
} else {
|
||||
macroList.push_back(FurnaceGUIMacroDesc("Envelope",&ins->std.ex2Macro,0,ex2Max,ex2Bit?64:160,uiColors[GUI_COLOR_MACRO_OTHER],false,false,NULL,NULL,ex2Bit,ayEnvBits));
|
||||
macroList.push_back(FurnaceGUIMacroDesc("Envelope",&ins->std.ex2Macro,0,ex2Max,ex2Bit?64:160,uiColors[GUI_COLOR_MACRO_OTHER],false,NULL,NULL,ex2Bit,ayEnvBits));
|
||||
}
|
||||
}
|
||||
if (ins->type==DIV_INS_C64) {
|
||||
macroList.push_back(FurnaceGUIMacroDesc("Special",&ins->std.ex3Macro,0,2,32,uiColors[GUI_COLOR_MACRO_OTHER],false,false,NULL,NULL,true,c64SpecialBits));
|
||||
macroList.push_back(FurnaceGUIMacroDesc("Test/Gate",&ins->std.ex4Macro,0,1,32,uiColors[GUI_COLOR_MACRO_OTHER],false,false,NULL,NULL,true));
|
||||
macroList.push_back(FurnaceGUIMacroDesc("Special",&ins->std.ex3Macro,0,2,32,uiColors[GUI_COLOR_MACRO_OTHER],false,NULL,NULL,true,c64SpecialBits));
|
||||
macroList.push_back(FurnaceGUIMacroDesc("Test/Gate",&ins->std.ex4Macro,0,1,32,uiColors[GUI_COLOR_MACRO_OTHER],false,NULL,NULL,true));
|
||||
}
|
||||
if (ins->type==DIV_INS_AY || ins->type==DIV_INS_AY8930 || (ins->type==DIV_INS_X1_010 && !ins->amiga.useSample)) {
|
||||
macroList.push_back(FurnaceGUIMacroDesc("AutoEnv Num",&ins->std.ex3Macro,0,15,160,uiColors[GUI_COLOR_MACRO_OTHER]));
|
||||
|
@ -4925,14 +4909,14 @@ void FurnaceGUI::drawInsEdit() {
|
|||
}
|
||||
if (ins->type==DIV_INS_AY8930) {
|
||||
// oh my i am running out of macros
|
||||
macroList.push_back(FurnaceGUIMacroDesc("Noise AND Mask",&ins->std.fbMacro,0,8,96,uiColors[GUI_COLOR_MACRO_OTHER],false,false,NULL,NULL,true));
|
||||
macroList.push_back(FurnaceGUIMacroDesc("Noise OR Mask",&ins->std.fmsMacro,0,8,96,uiColors[GUI_COLOR_MACRO_OTHER],false,false,NULL,NULL,true));
|
||||
macroList.push_back(FurnaceGUIMacroDesc("Noise AND Mask",&ins->std.fbMacro,0,8,96,uiColors[GUI_COLOR_MACRO_OTHER],false,NULL,NULL,true));
|
||||
macroList.push_back(FurnaceGUIMacroDesc("Noise OR Mask",&ins->std.fmsMacro,0,8,96,uiColors[GUI_COLOR_MACRO_OTHER],false,NULL,NULL,true));
|
||||
}
|
||||
if (ins->type==DIV_INS_N163) {
|
||||
macroList.push_back(FurnaceGUIMacroDesc("WaveLoad Wave",&ins->std.ex3Macro,0,255,160,uiColors[GUI_COLOR_MACRO_OTHER]));
|
||||
macroList.push_back(FurnaceGUIMacroDesc("WaveLoad Pos",&ins->std.algMacro,0,255,160,uiColors[GUI_COLOR_MACRO_OTHER]));
|
||||
macroList.push_back(FurnaceGUIMacroDesc("WaveLoad Len",&ins->std.fbMacro,0,252,160,uiColors[GUI_COLOR_MACRO_OTHER]));
|
||||
macroList.push_back(FurnaceGUIMacroDesc("WaveLoad Trigger",&ins->std.fmsMacro,0,2,160,uiColors[GUI_COLOR_MACRO_OTHER],false,false,NULL,NULL,true,n163UpdateBits));
|
||||
macroList.push_back(FurnaceGUIMacroDesc("WaveLoad Trigger",&ins->std.fmsMacro,0,2,160,uiColors[GUI_COLOR_MACRO_OTHER],false,NULL,NULL,true,n163UpdateBits));
|
||||
}
|
||||
if (ins->type==DIV_INS_FDS) {
|
||||
macroList.push_back(FurnaceGUIMacroDesc("Mod Position",&ins->std.ex3Macro,0,127,160,uiColors[GUI_COLOR_MACRO_OTHER]));
|
||||
|
|
|
@ -116,16 +116,11 @@ void FurnaceGUI::drawSampleEdit() {
|
|||
sample->loopStart=-1;
|
||||
sample->loopEnd=sample->samples;
|
||||
}
|
||||
if (sample->loopEnd>sample->samples) {
|
||||
sample->loopEnd=sample->samples;
|
||||
}
|
||||
sample->loopMode=DivSampleLoopMode(mode);
|
||||
updateSampleTex=true;
|
||||
}
|
||||
ImGui::TableNextRow();
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::Text("Length: %d",sample->samples);
|
||||
bool doLoop=(sample->loopMode!=DIV_SAMPLE_LOOPMODE_ONESHOT);
|
||||
if (doLoop) {
|
||||
ImGui::TableNextRow();
|
||||
ImGui::TableNextColumn();
|
||||
|
@ -678,16 +673,11 @@ void FurnaceGUI::drawSampleEdit() {
|
|||
sample->loopStart=-1;
|
||||
sample->loopEnd=sample->samples;
|
||||
}
|
||||
if (sample->loopEnd>sample->samples) {
|
||||
sample->loopEnd=sample->samples;
|
||||
}
|
||||
sample->loopMode=DivSampleLoopMode(mode);
|
||||
updateSampleTex=true;
|
||||
}
|
||||
ImGui::TableNextRow();
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::Text("Length: %d",sample->samples);
|
||||
bool doLoop=(sample->loopMode!=DIV_SAMPLE_LOOPMODE_ONESHOT);
|
||||
if (doLoop) {
|
||||
ImGui::TableNextRow();
|
||||
ImGui::TableNextColumn();
|
||||
|
|
Loading…
Reference in New Issue