Partially ES5506 support (not working yet!)
Add sample related enums Add support for backward/pingpong loop, loop end position Structize Notemap in sample instrument
This commit is contained in:
parent
96715ed88c
commit
29ea6dc360
50 changed files with 4501 additions and 248 deletions
|
|
@ -109,8 +109,8 @@ void DivPlatformAmiga::acquire(short* bufL, short* bufR, size_t start, size_t le
|
|||
DivSample* s=parent->getSample(chan[i].sample);
|
||||
if (s->samples>0) {
|
||||
writeAudDat(s->data8[chan[i].audPos++]);
|
||||
if (chan[i].audPos>=s->samples || chan[i].audPos>=131071) {
|
||||
if (s->loopStart>=0 && s->loopStart<(int)s->samples) {
|
||||
if (((s->loopMode!=DIV_SAMPLE_LOOPMODE_ONESHOT) && chan[i].audPos>=s->loopEnd) || (chan[i].audPos>=s->samples) || (chan[i].audPos>=131071)) {
|
||||
if (s->isLoopable()) {
|
||||
chan[i].audPos=s->loopStart;
|
||||
} else {
|
||||
chan[i].sample=-1;
|
||||
|
|
|
|||
638
src/engine/platform/es5506.cpp
Normal file
638
src/engine/platform/es5506.cpp
Normal file
|
|
@ -0,0 +1,638 @@
|
|||
/**
|
||||
* Furnace Tracker - multi-system chiptune tracker
|
||||
* Copyright (C) 2021-2022 tildearrow and contributors
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
#include "es5506.h"
|
||||
#include "../engine.h"
|
||||
#include "../../ta-log.h"
|
||||
#include <math.h>
|
||||
#include <map>
|
||||
|
||||
#define CHIP_FREQBASE (16*2048)
|
||||
#define NOTE_ES5506(c,note) (chan[c].pcm.freqOffs*NOTE_FREQUENCY(note))
|
||||
|
||||
#define rWrite(a,...) {if(!skipRegisterWrites) {hostIntf32.emplace(4,(a),__VA_ARGS__); }}
|
||||
#define rRead(a,...) {hostIntf32.emplace(4,(a),__VA_ARGS__);}
|
||||
#define immWrite(a,...) {hostIntf32.emplace(4,(a),__VA_ARGS__);}
|
||||
#define pageWrite(p,a,...) \
|
||||
if (!skipRegisterWrites) { \
|
||||
if (curPage!=(p)) { \
|
||||
curPage=(p); \
|
||||
rWrite(0xf,curPage); \
|
||||
} \
|
||||
rWrite((a),__VA_ARGS__); \
|
||||
}
|
||||
|
||||
#define pageWriteMask(p,pm,a,...) \
|
||||
if (!skipRegisterWrites) { \
|
||||
if ((curPage&(pm))!=((p)&(pm))) { \
|
||||
curPage=(curPage&~(pm))|((p)&(pm)); \
|
||||
rWrite(0xf,curPage,(pm)); \
|
||||
} \
|
||||
rWrite((a),__VA_ARGS__); \
|
||||
}
|
||||
|
||||
|
||||
const char* regCheatSheetES5506[]={
|
||||
"CR", "00|00",
|
||||
"FC", "00|01",
|
||||
"LVOL", "00|02",
|
||||
"LVRAMP", "00|03",
|
||||
"RVOL", "00|04",
|
||||
"RVRAMP", "00|05",
|
||||
"ECOUNT", "00|06",
|
||||
"K2", "00|07",
|
||||
"K2RAMP", "00|08",
|
||||
"K1", "00|09",
|
||||
"K1RAMP", "00|0A",
|
||||
"ACTV", "00|0B",
|
||||
"MODE", "00|0C",
|
||||
"POT", "00|0D",
|
||||
"IRQV", "00|0E",
|
||||
"PAGE", "00|0F",
|
||||
"CR", "20|00",
|
||||
"START", "20|01",
|
||||
"END", "20|02",
|
||||
"ACCUM", "20|03",
|
||||
"O4(n-1)", "20|04",
|
||||
"O3(n-2)", "20|05",
|
||||
"O3(n-1)", "20|06",
|
||||
"O2(n-2)", "20|07",
|
||||
"O2(n-1)", "20|08",
|
||||
"O1(n-1)", "20|09",
|
||||
"W_ST", "20|0A",
|
||||
"W_END", "20|0B",
|
||||
"LR_END", "20|0C",
|
||||
"POT", "20|0D",
|
||||
"IRQV", "20|0E",
|
||||
"PAGE", "20|0F",
|
||||
"CH0L", "40|00",
|
||||
"CH0R", "40|01",
|
||||
"CH1L", "40|02",
|
||||
"CH1R", "40|03",
|
||||
"CH2L", "40|04",
|
||||
"CH2R", "40|05",
|
||||
"CH3L", "40|06",
|
||||
"CH3R", "40|07",
|
||||
"CH4L", "40|08",
|
||||
"CH4R", "40|09",
|
||||
"CH5L", "40|0A",
|
||||
"CH5R", "40|0B",
|
||||
"POT", "40|0D",
|
||||
"IRQV", "40|0E",
|
||||
"PAGE", "40|0F",
|
||||
NULL
|
||||
};
|
||||
|
||||
const char** DivPlatformES5506::getRegisterSheet() {
|
||||
return regCheatSheetES5506;
|
||||
}
|
||||
|
||||
const char* DivPlatformES5506::getEffectName(unsigned char effect) {
|
||||
switch (effect) {
|
||||
case 0x10:
|
||||
return "10xx: Set echo feedback level (00 to FF)";
|
||||
break;
|
||||
case 0x11:
|
||||
return "11xx: Set channel echo level (00 to FF)";
|
||||
break;
|
||||
default:
|
||||
if ((effect & 0xf0) == 0x30) {
|
||||
return "3xxx: Set echo delay buffer length (000 to AA5)";
|
||||
}
|
||||
}
|
||||
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);
|
||||
} else {
|
||||
hostIntf8.emplace(0,w.addr,w.val,w.mask);
|
||||
hostIntf8.emplace(1,w.addr,w.val,w.mask);
|
||||
hostIntf8.emplace(2,w.addr,w.val,w.mask);
|
||||
hostIntf8.emplace(3,w.addr,w.val,w.mask,w.delay);
|
||||
}
|
||||
hostIntf32.pop();
|
||||
}
|
||||
es5506.tick_perf();
|
||||
bufL[h]=es5506.lout(0);
|
||||
bufR[h]=es5506.rout(0);
|
||||
}
|
||||
}
|
||||
|
||||
void DivPlatformES5506::e(bool state)
|
||||
{
|
||||
if (es5506.e_rising_edge()) {
|
||||
if (cycle) { // wait until delay
|
||||
cycle--;
|
||||
} else if (!hostIntf8.empty()) {
|
||||
QueuedHostIntf w=hostIntf8.front();
|
||||
unsigned char shift=24-(w.step<<3);
|
||||
if (w.isRead) {
|
||||
*w.read=((*w.read)&(~((0xff<<shift)&w.mask)))|((es5506.host_r((w.addr<<2)+w.step)<<shift)&w.mask);
|
||||
if (w.step==3) {
|
||||
if (w.delay>0) {
|
||||
cycle+=w.delay;
|
||||
}
|
||||
isReaded=true;
|
||||
} else {
|
||||
isReaded=false;
|
||||
}
|
||||
hostIntf8.pop();
|
||||
} else {
|
||||
isReaded=false;
|
||||
unsigned int mask=(w.mask>>shift)&0xff;
|
||||
if ((mask==0xff) || isMasked) {
|
||||
if (mask==0xff) {
|
||||
maskedVal=(w.val>>shift)&0xff;
|
||||
}
|
||||
es5506.host_w((w.addr<<2)+w.step,maskedVal);
|
||||
if(dumpWrites) {
|
||||
addWrite((w.addr<<2)+w.step,maskedVal);
|
||||
}
|
||||
isMasked=false;
|
||||
if ((w.step==3) && (w.delay>0)) {
|
||||
cycle+=w.delay;
|
||||
}
|
||||
hostIntf8.pop();
|
||||
} else if (!isMasked) {
|
||||
maskedVal=((w.val>>shift)&mask)|(es5506.host_r((w.addr<<2)+w.step)&~mask);
|
||||
isMasked=true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (isReaded) {
|
||||
isReaded=false;
|
||||
if (irqTrigger) {
|
||||
irqTrigger=false;
|
||||
if ((irqv&0x80)==0) {
|
||||
unsigned char ch=irqv&0x1f;
|
||||
if (chan[ch].isReversed) { // Reversed loop
|
||||
pageWriteMask(0x00|ch,0x5f,0x00,0x48,0x78);
|
||||
chan[ch].isReversed=false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DivPlatformES5506::irqb(bool state) {
|
||||
rRead(0x0e,&irqv,0x9f);
|
||||
irqTrigger=true;
|
||||
}
|
||||
|
||||
void DivPlatformES5506::tick() {
|
||||
for (int i=0; i<=chanMax; i++) {
|
||||
chan[i].std.next();
|
||||
DivInstrument* ins=parent->getIns(chan[i].ins);
|
||||
// volume/panning macros
|
||||
if (chan[i].std.vol.had) {
|
||||
chan[i].outVol=((chan[i].vol&0xff)*MIN(0xffff,chan[i].std.vol.val))/0xff;
|
||||
if (!isMuted[i]) {
|
||||
chan[i].volChanged=true;
|
||||
}
|
||||
}
|
||||
if (chan[i].std.panL.had) {
|
||||
chan[i].outLVol=(((ins->es5506.lVol*(chan[i].lVol&0xf))/0xf)*MIN(0xffff,chan[i].std.panL.val))/0xffff;
|
||||
if (!isMuted[i]) {
|
||||
chan[i].volChanged=true;
|
||||
}
|
||||
}
|
||||
if (chan[i].std.panR.had) {
|
||||
chan[i].outRVol=(((ins->es5506.rVol*(chan[i].rVol&0xf))/0xf)*MIN(0xffff,chan[i].std.panR.val))/0xffff;
|
||||
if (!isMuted[i]) {
|
||||
chan[i].volChanged=true;
|
||||
}
|
||||
}
|
||||
// arpeggio/pitch macros, frequency related
|
||||
if (chan[i].std.arp.had) {
|
||||
if (!chan[i].inPorta) {
|
||||
if (chan[i].std.arp.mode) {
|
||||
chan[i].baseFreq=NOTE_ES5506(i,chan[i].std.arp.val);
|
||||
} else {
|
||||
chan[i].baseFreq=NOTE_ES5506(i,chan[i].note+chan[i].std.arp.val);
|
||||
}
|
||||
}
|
||||
chan[i].freqChanged=true;
|
||||
} else {
|
||||
if (chan[i].std.arp.mode && chan[i].std.arp.finished) {
|
||||
chan[i].baseFreq=NOTE_ES5506(i,chan[i].note);
|
||||
chan[i].freqChanged=true;
|
||||
}
|
||||
}
|
||||
if (chan[i].std.pitch.had) {
|
||||
chan[i].freqChanged=true;
|
||||
}
|
||||
// phase reset macro
|
||||
if (chan[i].std.phaseReset.had) {
|
||||
if (chan[i].std.phaseReset.val==1) {
|
||||
chan[i].keyOn=true;
|
||||
}
|
||||
}
|
||||
// update registers
|
||||
if (chan[i].volChanged) {
|
||||
if (!isMuted[i]) { // calculate volume (16 bit)
|
||||
chan[i].resLVol=(chan[i].outVol*chan[i].outLVol)/0xffff;
|
||||
chan[i].resRVol=(chan[i].outVol*chan[i].outRVol)/0xffff;
|
||||
if (!chan[i].keyOn) {
|
||||
pageWrite(0x00|i,0x02,chan[i].resLVol);
|
||||
pageWrite(0x00|i,0x04,chan[i].resRVol);
|
||||
}
|
||||
} else { // mute
|
||||
pageWrite(0x00|i,0x02,0);
|
||||
pageWrite(0x00|i,0x04,0);
|
||||
}
|
||||
chan[i].volChanged=false;
|
||||
}
|
||||
if (chan[i].filterChanged) {
|
||||
pageWriteMask(0x00|i,0x5f,0x00,(chan[i].filter.mode<<8),0x0300);
|
||||
if (!chan[i].keyOn) {
|
||||
pageWrite(0x00|i,0x07,chan[i].filter.k2);
|
||||
pageWrite(0x00|i,0x09,chan[i].filter.k1);
|
||||
}
|
||||
chan[i].filterChanged=false;
|
||||
}
|
||||
if (chan[i].envChanged) {
|
||||
if (!chan[i].keyOn) {
|
||||
pageWrite(0x00|i,0x06,chan[i].envelope.ecount);
|
||||
}
|
||||
chan[i].envChanged=false;
|
||||
}
|
||||
if (chan[i].rampChanged) {
|
||||
if (!chan[i].keyOn) {
|
||||
pageWrite(0x00|i,0x03,((unsigned char)chan[i].envelope.lVRamp)<<8);
|
||||
pageWrite(0x00|i,0x05,((unsigned char)chan[i].envelope.rVRamp)<<8);
|
||||
pageWrite(0x00|i,0x0a,(((unsigned char)chan[i].envelope.k1Ramp)<<8)|(chan[i].envelope.k1Slow?1:0));
|
||||
pageWrite(0x00|i,0x08,(((unsigned char)chan[i].envelope.k2Ramp)<<8)|(chan[i].envelope.k2Slow?1:0));
|
||||
}
|
||||
chan[i].rampChanged=false;
|
||||
}
|
||||
if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) {
|
||||
chan[i].freq=parent->calcFreq(chan[i].baseFreq*(chanMax+1),chan[i].pitch,false)+chan[i].std.pitch.val;
|
||||
if (chan[i].freq<0) chan[i].freq=0;
|
||||
if (chan[i].freq>0x1ffff) chan[i].freq=0x1ffff;
|
||||
if (chan[i].keyOn) {
|
||||
if (chan[i].pcm.index>=0) {
|
||||
pageWriteMask(0x00|i,0x5f,0x00,0x0303); // Wipeout CR
|
||||
pageWrite(0x00|i,0x06,0); // Clear ECOUNT
|
||||
pageWrite(0x20|i,0x03,chan[i].pcm.base); // Set ACCUM to start address
|
||||
pageWrite(0x00|i,0x09,0xffff); // Set K1 and K2 to 0xffff
|
||||
pageWrite(0x00|i,0x07,0xffff,~0,(chanMax+1)*4*2); // needs to 4 sample period delay
|
||||
pageWrite(0x00|i,0x01,chan[i].freq);
|
||||
if (chan[i].pcm.loopMode!=DIV_SAMPLE_LOOPMODE_ONESHOT) {
|
||||
pageWrite(0x20|i,0x01,chan[i].pcm.loopStart);
|
||||
}
|
||||
pageWrite(0x20|i,0x02,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);
|
||||
pageWrite(0x00|i,0x0a,(((unsigned char)chan[i].envelope.k1Ramp)<<8)|(chan[i].envelope.k1Slow?1:0));
|
||||
pageWrite(0x00|i,0x08,(((unsigned char)chan[i].envelope.k2Ramp)<<8)|(chan[i].envelope.k2Slow?1:0));
|
||||
// initialize filter
|
||||
pageWriteMask(0x00|i,0x5f,0x00,(chan[i].pcm.bank<<14)|(chan[i].filter.mode<<8),0xc300);
|
||||
pageWrite(0x00|i,0x09,chan[i].filter.k1);
|
||||
pageWrite(0x00|i,0x07,chan[i].filter.k2);
|
||||
pageWrite(0x00|i,0x02,chan[i].resLVol);
|
||||
pageWrite(0x00|i,0x04,chan[i].resRVol);
|
||||
unsigned int loopFlag=0x0000;
|
||||
chan[i].isReversed=false;
|
||||
switch (chan[i].pcm.loopMode) {
|
||||
case DIV_SAMPLE_LOOPMODE_ONESHOT: // One shot (no loop)
|
||||
default:
|
||||
loopFlag=0x0000;
|
||||
break;
|
||||
case DIV_SAMPLE_LOOPMODE_FOWARD: // Foward loop
|
||||
loopFlag=0x0008;
|
||||
break;
|
||||
case DIV_SAMPLE_LOOPMODE_BACKWARD: // Backward loop: IRQ enable
|
||||
loopFlag=0x0038;
|
||||
chan[i].isReversed=true;
|
||||
break;
|
||||
case DIV_SAMPLE_LOOPMODE_PINGPONG: // Pingpong loop: Hardware support
|
||||
loopFlag=0x0018;
|
||||
break;
|
||||
}
|
||||
// Run sample
|
||||
pageWrite(0x00|i,0x06,chan[i].envelope.ecount); // Clear ECOUNT
|
||||
pageWriteMask(0x00|i,0x5f,0x00,loopFlag,0x3cff);
|
||||
}
|
||||
}
|
||||
if (chan[i].keyOff) {
|
||||
pageWriteMask(0x00|i,0x5f,0x00,0x0003); // Wipeout CR
|
||||
} else if (chan[i].active) {
|
||||
pageWrite(0x00|i,0x01,chan[i].freq);
|
||||
}
|
||||
if (chan[i].keyOn) chan[i].keyOn=false;
|
||||
if (chan[i].keyOff) chan[i].keyOff=false;
|
||||
chan[i].freqChanged=false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int DivPlatformES5506::dispatch(DivCommand c) {
|
||||
switch (c.cmd) {
|
||||
case DIV_CMD_NOTE_ON: {
|
||||
DivInstrument* ins=parent->getIns(chan[c.chan].ins);
|
||||
chan[c.chan].sample=ins->amiga.useNoteMap?ins->amiga.noteMap[c.value].ind:ins->amiga.initSample;
|
||||
double off=1.0;
|
||||
if (chan[c.chan].sample>=0 && chan[c.chan].sample<parent->song.sampleLen) {
|
||||
chan[c.chan].pcm.index=chan[c.chan].sample;
|
||||
DivSample* s=parent->getSample(chan[c.chan].sample);
|
||||
if (s->centerRate<1) {
|
||||
off=1.0;
|
||||
} else {
|
||||
off=ins->amiga.useNoteMap?((double)ins->amiga.noteMap[c.value].freq/((double)s->centerRate*pow(2.0,((double)c.value-48.0)/12.0))):((double)s->centerRate/8363.0);
|
||||
}
|
||||
unsigned int base=s->offES5506<<10;
|
||||
chan[c.chan].pcm.loopMode=s->isLoopable()?s->loopMode:DIV_SAMPLE_LOOPMODE_ONESHOT;
|
||||
chan[c.chan].pcm.freqOffs=off;
|
||||
chan[c.chan].pcm.bank=(s->offES5506>>22)&3;
|
||||
chan[c.chan].pcm.base=base;
|
||||
chan[c.chan].pcm.loopStart=(base+(s->loopStart<<11))&0xfffff800;
|
||||
chan[c.chan].pcm.loopEnd=((base+(s->loopEnd<<11))-0x800)&0xffffff80;
|
||||
chan[c.chan].filter=ins->es5506.filter;
|
||||
chan[c.chan].envelope=ins->es5506.envelope;
|
||||
} else {
|
||||
chan[c.chan].sample=-1;
|
||||
chan[c.chan].pcm.index=-1;
|
||||
chan[c.chan].filter=DivInstrumentES5506::Filter();
|
||||
chan[c.chan].envelope=DivInstrumentES5506::Envelope();
|
||||
}
|
||||
if (c.value!=DIV_NOTE_NULL) {
|
||||
chan[c.chan].baseFreq=NOTE_ES5506(c.chan,c.value);
|
||||
chan[c.chan].freqChanged=true;
|
||||
chan[c.chan].volChanged=true;
|
||||
chan[c.chan].note=c.value;
|
||||
}
|
||||
if (!chan[c.chan].std.vol.will) {
|
||||
chan[c.chan].outVol=(0xffff*chan[c.chan].vol)/0xff;
|
||||
}
|
||||
if (!chan[c.chan].std.panL.will) {
|
||||
chan[c.chan].outLVol=(ins->es5506.lVol*chan[c.chan].lVol)/0xf;
|
||||
}
|
||||
if (!chan[c.chan].std.panR.will) {
|
||||
chan[c.chan].outRVol=(ins->es5506.rVol*chan[c.chan].rVol)/0xf;
|
||||
}
|
||||
chan[c.chan].active=true;
|
||||
chan[c.chan].keyOn=true;
|
||||
chan[c.chan].std.init(ins);
|
||||
break;
|
||||
}
|
||||
case DIV_CMD_NOTE_OFF:
|
||||
chan[c.chan].filter=DivInstrumentES5506::Filter();
|
||||
chan[c.chan].envelope=DivInstrumentES5506::Envelope();
|
||||
chan[c.chan].sample=-1;
|
||||
chan[c.chan].active=false;
|
||||
chan[c.chan].keyOff=true;
|
||||
chan[c.chan].std.init(NULL);
|
||||
break;
|
||||
case DIV_CMD_NOTE_OFF_ENV:
|
||||
case DIV_CMD_ENV_RELEASE:
|
||||
chan[c.chan].std.release();
|
||||
break;
|
||||
case DIV_CMD_INSTRUMENT:
|
||||
if (chan[c.chan].ins!=c.value || c.value2==1) {
|
||||
chan[c.chan].ins=c.value;
|
||||
}
|
||||
break;
|
||||
case DIV_CMD_VOLUME:
|
||||
if (chan[c.chan].vol!=c.value) {
|
||||
chan[c.chan].vol=c.value;
|
||||
if (!chan[c.chan].std.vol.has) {
|
||||
chan[c.chan].outVol=(0xffff*c.value)/0xff;
|
||||
if (!isMuted[c.chan]) {
|
||||
chan[c.chan].volChanged=true;
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
case DIV_CMD_GET_VOLUME:
|
||||
if (chan[c.chan].std.vol.has) {
|
||||
return chan[c.chan].vol;
|
||||
}
|
||||
return chan[c.chan].outVol;
|
||||
break;
|
||||
case DIV_CMD_PANNING: {
|
||||
DivInstrument* ins=parent->getIns(chan[c.chan].ins);
|
||||
// 08LR, each nibble means volume multipler for each channels
|
||||
// Left volume
|
||||
unsigned char lVol=(c.value>>4)&0xf;
|
||||
if (chan[c.chan].lVol!=lVol) {
|
||||
chan[c.chan].lVol=lVol;
|
||||
if (!chan[c.chan].std.panL.has) {
|
||||
chan[c.chan].outLVol=(ins->es5506.lVol*lVol)/0xf;
|
||||
if (!isMuted[c.chan]) {
|
||||
chan[c.chan].volChanged=true;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Right volume
|
||||
unsigned char rVol=(c.value>>0)&0xf;
|
||||
if (chan[c.chan].rVol!=rVol) {
|
||||
chan[c.chan].rVol=rVol;
|
||||
if (!chan[c.chan].std.panR.has) {
|
||||
chan[c.chan].outRVol=(ins->es5506.rVol*rVol)/0xf;
|
||||
if (!isMuted[c.chan]) {
|
||||
chan[c.chan].volChanged=true;
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case DIV_CMD_PITCH:
|
||||
chan[c.chan].pitch=c.value;
|
||||
chan[c.chan].freqChanged=true;
|
||||
break;
|
||||
case DIV_CMD_NOTE_PORTA: {
|
||||
int destFreq=NOTE_ES5506(c.chan,c.value2);
|
||||
bool return2=false;
|
||||
if (destFreq>chan[c.chan].baseFreq) {
|
||||
chan[c.chan].baseFreq+=c.value;
|
||||
if (chan[c.chan].baseFreq>=destFreq) {
|
||||
chan[c.chan].baseFreq=destFreq;
|
||||
return2=true;
|
||||
}
|
||||
} else {
|
||||
chan[c.chan].baseFreq-=c.value;
|
||||
if (chan[c.chan].baseFreq<=destFreq) {
|
||||
chan[c.chan].baseFreq=destFreq;
|
||||
return2=true;
|
||||
}
|
||||
}
|
||||
chan[c.chan].freqChanged=true;
|
||||
if (return2) {
|
||||
chan[c.chan].inPorta=false;
|
||||
return 2;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case DIV_CMD_LEGATO: {
|
||||
chan[c.chan].baseFreq=NOTE_ES5506(c.chan,c.value+((chan[c.chan].std.arp.will && !chan[c.chan].std.arp.mode)?(chan[c.chan].std.arp.val-12):(0)));
|
||||
chan[c.chan].freqChanged=true;
|
||||
chan[c.chan].note=c.value;
|
||||
break;
|
||||
}
|
||||
case DIV_CMD_PRE_PORTA:
|
||||
if (chan[c.chan].active && c.value2) {
|
||||
if (parent->song.resetMacroOnPorta) chan[c.chan].std.init(parent->getIns(chan[c.chan].ins));
|
||||
}
|
||||
chan[c.chan].inPorta=c.value;
|
||||
break;
|
||||
case DIV_CMD_GET_VOLMAX:
|
||||
return 255;
|
||||
break;
|
||||
case DIV_ALWAYS_SET_VOLUME:
|
||||
return 1;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
void DivPlatformES5506::muteChannel(int ch, bool mute) {
|
||||
isMuted[ch]=mute;
|
||||
es5506.set_mute(ch,mute);
|
||||
}
|
||||
|
||||
void DivPlatformES5506::forceIns() {
|
||||
for (int i=0; i<=chanMax; i++) {
|
||||
chan[i].insChanged=true;
|
||||
chan[i].freqChanged=true;
|
||||
chan[i].volChanged=true;
|
||||
chan[i].sample=-1;
|
||||
}
|
||||
}
|
||||
|
||||
void* DivPlatformES5506::getChanState(int ch) {
|
||||
return &chan[ch];
|
||||
}
|
||||
|
||||
void DivPlatformES5506::reset() {
|
||||
while (!hostIntf32.empty()) hostIntf32.pop();
|
||||
while (!hostIntf8.empty()) hostIntf8.pop();
|
||||
for (int i=0; i<32; i++) {
|
||||
chan[i]=DivPlatformES5506::Channel();
|
||||
chan[i].std.setEngine(parent);
|
||||
}
|
||||
es5506.reset();
|
||||
for (int i=0; i<32; i++) {
|
||||
es5506.set_mute(i,isMuted[i]);
|
||||
}
|
||||
|
||||
cycle=0;
|
||||
curPage=0;
|
||||
maskedVal=0;
|
||||
irqv=0x80;
|
||||
isMasked=false;
|
||||
isReaded=false;
|
||||
irqTrigger=false;
|
||||
chanMax=initChanMax;
|
||||
|
||||
pageWriteMask(0x00,0x60,0x0b,chanMax);
|
||||
pageWriteMask(0x00,0x60,0x0b,0x1f);
|
||||
pageWriteMask(0x20,0x60,0x0a,0x01);
|
||||
pageWriteMask(0x20,0x60,0x0b,0x11);
|
||||
pageWriteMask(0x20,0x60,0x0c,0x20);
|
||||
pageWriteMask(0x00,0x60,0x0c,0x08); // Reset serial output
|
||||
}
|
||||
|
||||
bool DivPlatformES5506::isStereo() {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DivPlatformES5506::keyOffAffectsArp(int ch) {
|
||||
return true;
|
||||
}
|
||||
|
||||
void DivPlatformES5506::notifyInsChange(int ins) {
|
||||
for (int i=0; i<32; i++) {
|
||||
if (chan[i].ins==ins) {
|
||||
chan[i].insChanged=true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DivPlatformES5506::notifyWaveChange(int wave) {
|
||||
// TODO when wavetables are added
|
||||
// TODO they probably won't be added unless the samples reside in RAM
|
||||
}
|
||||
|
||||
void DivPlatformES5506::notifyInsDeletion(void* ins) {
|
||||
for (int i=0; i<32; i++) {
|
||||
chan[i].std.notifyInsDeletion((DivInstrument*)ins);
|
||||
}
|
||||
}
|
||||
|
||||
void DivPlatformES5506::setFlags(unsigned int flags) {
|
||||
initChanMax=MAX(4,flags&0x1f);
|
||||
chanMax=initChanMax;
|
||||
pageWriteMask(0x00,0x60,0x0b,chanMax);
|
||||
}
|
||||
|
||||
void DivPlatformES5506::poke(unsigned int addr, unsigned short val) {
|
||||
immWrite(addr, val);
|
||||
}
|
||||
|
||||
void DivPlatformES5506::poke(std::vector<DivRegWrite>& wlist) {
|
||||
for (DivRegWrite& i: wlist) immWrite(i.addr,i.val);
|
||||
}
|
||||
|
||||
unsigned char* DivPlatformES5506::getRegisterPool() {
|
||||
unsigned char* regPoolPtr = regPool;
|
||||
for (unsigned char p=0; p<128; p++) {
|
||||
for (unsigned char r=0; r<16; r++) {
|
||||
unsigned int reg=es5506.regs_r(p,r,false);
|
||||
for (int b=0; b<4; b++) {
|
||||
*regPoolPtr++ = reg>>(24-(b<<3));
|
||||
}
|
||||
}
|
||||
}
|
||||
return regPool;
|
||||
}
|
||||
|
||||
int DivPlatformES5506::getRegisterPoolSize() {
|
||||
return 4*16*128; // 7 bit page x 16 registers per page x 32 bit per registers
|
||||
}
|
||||
|
||||
int DivPlatformES5506::init(DivEngine* p, int channels, int sugRate, unsigned int flags) {
|
||||
parent=p;
|
||||
dumpWrites=false;
|
||||
skipRegisterWrites=false;
|
||||
|
||||
for (int i=0; i<32; i++) {
|
||||
isMuted[i]=false;
|
||||
}
|
||||
setFlags(flags);
|
||||
|
||||
chipClock=16000000;
|
||||
rate=chipClock/16;
|
||||
reset();
|
||||
return 32;
|
||||
}
|
||||
|
||||
void DivPlatformES5506::quit() {
|
||||
}
|
||||
173
src/engine/platform/es5506.h
Normal file
173
src/engine/platform/es5506.h
Normal file
|
|
@ -0,0 +1,173 @@
|
|||
/**
|
||||
* Furnace Tracker - multi-system chiptune tracker
|
||||
* Copyright (C) 2021-2022 tildearrow and contributors
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
#ifndef _ES5506_H
|
||||
#define _ES5506_H
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "../dispatch.h"
|
||||
#include "../engine.h"
|
||||
#include <queue>
|
||||
#include "../macroInt.h"
|
||||
#include "../sample.h"
|
||||
#include "sound/es550x/es5506.hpp"
|
||||
|
||||
class DivPlatformES5506: public DivDispatch, public es550x_intf {
|
||||
struct Channel {
|
||||
struct PCM {
|
||||
double freqOffs;
|
||||
int index;
|
||||
unsigned int bank;
|
||||
unsigned int base;
|
||||
unsigned int loopStart;
|
||||
unsigned int loopEnd;
|
||||
DivSampleLoopMode loopMode;
|
||||
PCM():
|
||||
freqOffs(1.0),
|
||||
index(-1),
|
||||
bank(0),
|
||||
base(0),
|
||||
loopStart(0),
|
||||
loopEnd(0),
|
||||
loopMode(DIV_SAMPLE_LOOPMODE_ONESHOT) {}
|
||||
} pcm;
|
||||
int freq, baseFreq, pitch;
|
||||
unsigned short audLen;
|
||||
unsigned int audPos;
|
||||
int sample, wave;
|
||||
unsigned char ins;
|
||||
int note;
|
||||
int panning;
|
||||
bool active, insChanged, freqChanged, volChanged, filterChanged, envChanged, rampChanged, keyOn, keyOff, inPorta, useWave, isReversed;
|
||||
int vol, outVol;
|
||||
int lVol, outLVol;
|
||||
int rVol, outRVol;
|
||||
int resLVol, resRVol;
|
||||
DivInstrumentES5506::Filter filter;
|
||||
DivInstrumentES5506::Envelope envelope;
|
||||
DivMacroInt std;
|
||||
Channel():
|
||||
freq(0),
|
||||
baseFreq(0),
|
||||
pitch(0),
|
||||
audLen(0),
|
||||
audPos(0),
|
||||
sample(-1),
|
||||
ins(-1),
|
||||
note(0),
|
||||
panning(0x10),
|
||||
active(false),
|
||||
insChanged(true),
|
||||
freqChanged(false),
|
||||
volChanged(false),
|
||||
filterChanged(false),
|
||||
envChanged(false),
|
||||
rampChanged(false),
|
||||
keyOn(false),
|
||||
keyOff(false),
|
||||
inPorta(false),
|
||||
vol(0xffff),
|
||||
outVol(0xffff),
|
||||
lVol(0xffff),
|
||||
outLVol(0xffff),
|
||||
rVol(0xffff),
|
||||
outRVol(0xffff),
|
||||
resLVol(0xffff),
|
||||
resRVol(0xffff) {}
|
||||
};
|
||||
Channel chan[32];
|
||||
bool isMuted[32];
|
||||
struct QueuedHostIntf {
|
||||
unsigned char step;
|
||||
unsigned char addr;
|
||||
unsigned int val;
|
||||
unsigned int mask;
|
||||
unsigned int* read;
|
||||
unsigned short delay;
|
||||
bool isRead;
|
||||
QueuedHostIntf(unsigned char s, unsigned char a, unsigned int v, unsigned int m=(unsigned int)(~0), unsigned short d=0):
|
||||
step(s),
|
||||
addr(a),
|
||||
val(v),
|
||||
mask(m),
|
||||
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):
|
||||
step(s),
|
||||
addr(a),
|
||||
val(0),
|
||||
mask(m),
|
||||
read(r),
|
||||
delay(d),
|
||||
isRead(true) {}
|
||||
};
|
||||
std::queue<QueuedHostIntf> hostIntf32;
|
||||
std::queue<QueuedHostIntf> hostIntf8;
|
||||
int cycle, curPage;
|
||||
unsigned char maskedVal;
|
||||
unsigned int irqv;
|
||||
bool isMasked, isReaded;
|
||||
bool irqTrigger;
|
||||
|
||||
unsigned char initChanMax, chanMax;
|
||||
|
||||
es5506_core es5506;
|
||||
unsigned char regPool[4*16*128]; // 7 bit page x 16 registers per page x 32 bit per registers
|
||||
|
||||
friend void putDispatchChan(void*,int,int);
|
||||
|
||||
public:
|
||||
virtual void e(bool state) override; // E output
|
||||
|
||||
virtual void irqb(bool state) override; // IRQB output
|
||||
virtual s16 read_sample(u8 voice, u8 bank, u32 address) override {
|
||||
if (parent->es5506Mem==NULL) return 0;
|
||||
return parent->es5506Mem[((bank&3)<<21)|(address&0x1fffff)];
|
||||
}
|
||||
|
||||
virtual void acquire(short* bufL, short* bufR, size_t start, size_t len) override;
|
||||
virtual int dispatch(DivCommand c) override;
|
||||
virtual void* getChanState(int chan) override;
|
||||
virtual unsigned char* getRegisterPool() override;
|
||||
virtual int getRegisterPoolSize() override;
|
||||
virtual void reset() override;
|
||||
virtual void forceIns() override;
|
||||
virtual void tick() override;
|
||||
virtual void muteChannel(int ch, bool mute) override;
|
||||
virtual bool isStereo() override;
|
||||
virtual bool keyOffAffectsArp(int ch) override;
|
||||
virtual void setFlags(unsigned int flags) override;
|
||||
virtual void notifyInsChange(int ins) override;
|
||||
virtual void notifyWaveChange(int wave) override;
|
||||
virtual void notifyInsDeletion(void* ins) override;
|
||||
virtual void poke(unsigned int addr, unsigned short val) override;
|
||||
virtual void poke(std::vector<DivRegWrite>& wlist) 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():
|
||||
DivDispatch(),
|
||||
es550x_intf(),
|
||||
es5506(*this) {}
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
@ -96,8 +96,9 @@ void DivPlatformGenesis::acquire_nuked(short* bufL, short* bufR, size_t start, s
|
|||
urgentWrite(0x2a,(unsigned char)s->data8[dacPos]+0x80);
|
||||
}
|
||||
}
|
||||
if (++dacPos>=s->samples) {
|
||||
if (s->loopStart>=0 && s->loopStart<(int)s->samples) {
|
||||
dacPos++;
|
||||
if (((s->loopMode!=DIV_SAMPLE_LOOPMODE_ONESHOT) && dacPos>=s->loopEnd) || (dacPos>=s->samples)) {
|
||||
if (s->isLoopable()) {
|
||||
dacPos=s->loopStart;
|
||||
} else {
|
||||
dacSample=-1;
|
||||
|
|
@ -165,8 +166,9 @@ void DivPlatformGenesis::acquire_ymfm(short* bufL, short* bufR, size_t start, si
|
|||
urgentWrite(0x2a,(unsigned char)s->data8[dacPos]+0x80);
|
||||
}
|
||||
}
|
||||
if (++dacPos>=s->samples) {
|
||||
if (s->loopStart>=0 && s->loopStart<(int)s->samples) {
|
||||
dacPos++;
|
||||
if (((s->loopMode!=DIV_SAMPLE_LOOPMODE_ONESHOT) && dacPos>=s->loopEnd) || (dacPos>=s->samples)) {
|
||||
if (s->isLoopable()) {
|
||||
dacPos=s->loopStart;
|
||||
} else {
|
||||
dacSample=-1;
|
||||
|
|
|
|||
|
|
@ -62,8 +62,9 @@ void DivPlatformMMC5::acquire(short* bufL, short* bufR, size_t start, size_t len
|
|||
if (!isMuted[4]) {
|
||||
rWrite(0x5011,((unsigned char)s->data8[dacPos]+0x80));
|
||||
}
|
||||
if (++dacPos>=s->samples) {
|
||||
if (s->loopStart>=0 && s->loopStart<(int)s->samples) {
|
||||
dacPos++;
|
||||
if (((s->loopMode!=DIV_SAMPLE_LOOPMODE_ONESHOT) && dacPos>=s->loopEnd) || (dacPos>=s->samples)) {
|
||||
if (s->isLoopable()) {
|
||||
dacPos=s->loopStart;
|
||||
} else {
|
||||
dacSample=-1;
|
||||
|
|
|
|||
|
|
@ -89,8 +89,9 @@ void DivPlatformNES::acquire(short* bufL, short* bufR, size_t start, size_t len)
|
|||
rWrite(0x4011,next);
|
||||
}
|
||||
}
|
||||
if (++dacPos>=s->samples) {
|
||||
if (s->loopStart>=0 && s->loopStart<(int)s->samples) {
|
||||
dacPos++;
|
||||
if (((s->loopMode!=DIV_SAMPLE_LOOPMODE_ONESHOT) && dacPos>=s->loopEnd) || (dacPos>=s->samples)) {
|
||||
if (s->isLoopable()) {
|
||||
dacPos=s->loopStart;
|
||||
} else {
|
||||
dacSample=-1;
|
||||
|
|
|
|||
|
|
@ -90,8 +90,8 @@ void DivPlatformPCE::acquire(short* bufL, short* bufR, size_t start, size_t len)
|
|||
chWrite(i,0x04,0xdf);
|
||||
chWrite(i,0x06,(((unsigned char)s->data8[chan[i].dacPos]+0x80)>>3));
|
||||
chan[i].dacPos++;
|
||||
if (chan[i].dacPos>=s->samples) {
|
||||
if (s->loopStart>=0 && s->loopStart<(int)s->samples) {
|
||||
if (((s->loopMode!=DIV_SAMPLE_LOOPMODE_ONESHOT) && chan[i].dacPos>=s->loopEnd) || (chan[i].dacPos>=s->samples)) {
|
||||
if (s->isLoopable()) {
|
||||
chan[i].dacPos=s->loopStart;
|
||||
} else {
|
||||
chan[i].dacSample=-1;
|
||||
|
|
|
|||
|
|
@ -299,11 +299,11 @@ void DivPlatformQSound::tick() {
|
|||
qsound_bank = 0x8000 | (s->offQSound >> 16);
|
||||
qsound_addr = s->offQSound & 0xffff;
|
||||
|
||||
int length = s->samples;
|
||||
int length = s->isLoopable()?s->loopEnd:s->samples;
|
||||
if (length > 65536 - 16) {
|
||||
length = 65536 - 16;
|
||||
}
|
||||
if (s->loopStart == -1 || s->loopStart >= length) {
|
||||
if ((!s->isLoopable()) || s->loopStart>=length) {
|
||||
qsound_end = s->offQSound + length + 15;
|
||||
qsound_loop = 15;
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -53,8 +53,8 @@ void DivPlatformSegaPCM::acquire(short* bufL, short* bufR, size_t start, size_t
|
|||
pcmR+=(s->data8[chan[i].pcm.pos>>8]*chan[i].chVolR);
|
||||
}
|
||||
chan[i].pcm.pos+=chan[i].pcm.freq;
|
||||
if (chan[i].pcm.pos>=(s->samples<<8)) {
|
||||
if (s->loopStart>=0 && s->loopStart<(int)s->samples) {
|
||||
if (((s->loopMode!=DIV_SAMPLE_LOOPMODE_ONESHOT) && chan[i].pcm.pos>=(s->loopEnd<<8)) || (chan[i].pcm.pos>=(s->samples<<8))) {
|
||||
if (s->isLoopable()) {
|
||||
chan[i].pcm.pos=s->loopStart<<8;
|
||||
} else {
|
||||
chan[i].pcm.sample=-1;
|
||||
|
|
@ -153,7 +153,7 @@ int DivPlatformSegaPCM::dispatch(DivCommand c) {
|
|||
addWrite(0x10084+(c.chan<<3),(s->offSegaPCM)&0xff);
|
||||
addWrite(0x10085+(c.chan<<3),(s->offSegaPCM>>8)&0xff);
|
||||
addWrite(0x10006+(c.chan<<3),MIN(255,((s->offSegaPCM&0xffff)+s->length8-1)>>8));
|
||||
if (s->loopStart<0 || s->loopStart>=(int)s->length8) {
|
||||
if (!s->isLoopable()) {
|
||||
addWrite(0x10086+(c.chan<<3),2+((s->offSegaPCM>>16)<<3));
|
||||
} else {
|
||||
int loopPos=(s->offSegaPCM&0xffff)+s->loopStart+s->loopOffP;
|
||||
|
|
@ -183,7 +183,7 @@ int DivPlatformSegaPCM::dispatch(DivCommand c) {
|
|||
addWrite(0x10084+(c.chan<<3),(s->offSegaPCM)&0xff);
|
||||
addWrite(0x10085+(c.chan<<3),(s->offSegaPCM>>8)&0xff);
|
||||
addWrite(0x10006+(c.chan<<3),MIN(255,((s->offSegaPCM&0xffff)+s->length8-1)>>8));
|
||||
if (s->loopStart<0 || s->loopStart>=(int)s->length8) {
|
||||
if (!s->isLoopable()) {
|
||||
addWrite(0x10086+(c.chan<<3),2+((s->offSegaPCM>>16)<<3));
|
||||
} else {
|
||||
int loopPos=(s->offSegaPCM&0xffff)+s->loopStart+s->loopOffP;
|
||||
|
|
|
|||
444
src/engine/platform/sound/es550x/es5504.cpp
Normal file
444
src/engine/platform/sound/es550x/es5504.cpp
Normal file
|
|
@ -0,0 +1,444 @@
|
|||
/*
|
||||
License: BSD-3-Clause
|
||||
see https://github.com/cam900/vgsound_emu/LICENSE for more details
|
||||
|
||||
Copyright holder(s): cam900
|
||||
Ensoniq ES5504 emulation core
|
||||
|
||||
see es550x.cpp for more info
|
||||
*/
|
||||
|
||||
#include "es5504.hpp"
|
||||
|
||||
// Internal functions
|
||||
void es5504_core::tick()
|
||||
{
|
||||
// /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(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()
|
||||
{
|
||||
// update
|
||||
// falling edge
|
||||
m_e.m_edge.set(false);
|
||||
m_intf.e(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(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(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(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)
|
||||
if (bitfield(m_voice_fetch++, 0))
|
||||
{
|
||||
// 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_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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
87
src/engine/platform/sound/es550x/es5504.hpp
Normal file
87
src/engine/platform/sound/es550x/es5504.hpp
Normal file
|
|
@ -0,0 +1,87 @@
|
|||
/*
|
||||
License: BSD-3-Clause
|
||||
see https://github.com/cam900/vgsound_emu/LICENSE for more details
|
||||
|
||||
Copyright holder(s): 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]; }
|
||||
|
||||
// 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; }
|
||||
|
||||
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
|
||||
599
src/engine/platform/sound/es550x/es5505.cpp
Normal file
599
src/engine/platform/sound/es550x/es5505.cpp
Normal file
|
|
@ -0,0 +1,599 @@
|
|||
/*
|
||||
License: BSD-3-Clause
|
||||
see https://github.com/cam900/vgsound_emu/LICENSE for more details
|
||||
|
||||
Copyright holder(s): cam900
|
||||
Ensoniq ES5505 emulation core
|
||||
|
||||
see es550x.cpp for more info
|
||||
*/
|
||||
|
||||
#include "es5505.hpp"
|
||||
|
||||
// Internal functions
|
||||
void es5505_core::tick()
|
||||
{
|
||||
// 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_left << 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(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()
|
||||
{
|
||||
// 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(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(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(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(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)
|
||||
if (bitfield(m_voice_fetch++, 0))
|
||||
{
|
||||
// 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_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 = clamp<u8>(bitfield(data, 0, 5), 7, 31);
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
141
src/engine/platform/sound/es550x/es5505.hpp
Normal file
141
src/engine/platform/sound/es550x/es5505.hpp
Normal file
|
|
@ -0,0 +1,141 @@
|
|||
/*
|
||||
License: BSD-3-Clause
|
||||
see https://github.com/cam900/vgsound_emu/LICENSE for more details
|
||||
|
||||
Copyright holder(s): 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; }
|
||||
|
||||
// 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; }
|
||||
|
||||
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
|
||||
u8 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
|
||||
787
src/engine/platform/sound/es550x/es5506.cpp
Normal file
787
src/engine/platform/sound/es550x/es5506.cpp
Normal file
|
|
@ -0,0 +1,787 @@
|
|||
/*
|
||||
License: BSD-3-Clause
|
||||
see https://github.com/cam900/vgsound_emu/LICENSE for more details
|
||||
|
||||
Copyright holder(s): cam900
|
||||
Ensoniq ES5506 emulation core
|
||||
|
||||
see es550x.cpp for more info
|
||||
*/
|
||||
|
||||
#include "es5506.hpp"
|
||||
|
||||
// Internal functions
|
||||
void es5506_core::tick()
|
||||
{
|
||||
// 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_left << 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(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()
|
||||
{
|
||||
// output
|
||||
if (((!m_mode.lrclk_en) && (!m_mode.bclk_en) && (!m_mode.bclk_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(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(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(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(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)
|
||||
if (bitfield(m_voice_fetch++, 0))
|
||||
{
|
||||
// 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_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(sign_ext<s32>(m_filter.m_o4_1, 16), m_lvol);
|
||||
m_ch.m_right = volume_calc(sign_ext<s32>(m_filter.m_o4_1, 16), m_rvol);
|
||||
}
|
||||
|
||||
// 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::min<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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
179
src/engine/platform/sound/es550x/es5506.hpp
Normal file
179
src/engine/platform/sound/es550x/es5506.hpp
Normal file
|
|
@ -0,0 +1,179 @@
|
|||
/*
|
||||
License: BSD-3-Clause
|
||||
see https://github.com/cam900/vgsound_emu/LICENSE for more details
|
||||
|
||||
Copyright holder(s): 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; }
|
||||
|
||||
// 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; }
|
||||
|
||||
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
|
||||
u8 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
|
||||
75
src/engine/platform/sound/es550x/es550x.cpp
Normal file
75
src/engine/platform/sound/es550x/es550x.cpp
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
/*
|
||||
License: BSD-3-Clause
|
||||
see https://github.com/cam900/vgsound_emu/LICENSE for more details
|
||||
|
||||
Copyright holder(s): 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_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();
|
||||
}
|
||||
391
src/engine/platform/sound/es550x/es550x.hpp
Normal file
391
src/engine/platform/sound/es550x/es550x.hpp
Normal file
|
|
@ -0,0 +1,391 @@
|
|||
/*
|
||||
License: BSD-3-Clause
|
||||
see https://github.com/cam900/vgsound_emu/LICENSE for more details
|
||||
|
||||
Copyright holder(s): cam900
|
||||
Ensoniq ES5504/ES5505/ES5506 emulation core
|
||||
|
||||
See es550x.cpp for more info
|
||||
*/
|
||||
|
||||
#include <algorithm>
|
||||
#include <memory>
|
||||
|
||||
#ifndef _VGSOUND_EMU_ES550X_HPP
|
||||
#define _VGSOUND_EMU_ES550X_HPP
|
||||
|
||||
#pragma once
|
||||
|
||||
namespace es550x
|
||||
{
|
||||
typedef unsigned char u8;
|
||||
typedef unsigned short u16;
|
||||
typedef unsigned int u32;
|
||||
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 (in > max) ? max : ((in < min) ? min : in);
|
||||
}
|
||||
|
||||
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
|
||||
};
|
||||
};
|
||||
|
||||
// ES5504/ES5505/ES5506 interface
|
||||
using namespace es550x;
|
||||
class es550x_intf
|
||||
{
|
||||
public:
|
||||
virtual void e(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
|
||||
using namespace es550x;
|
||||
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(); }
|
||||
|
||||
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_transwave(transwave)
|
||||
{}
|
||||
|
||||
const u8 m_integer;
|
||||
const u8 m_fraction;
|
||||
const u8 m_total_bits;
|
||||
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
|
||||
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
|
||||
116
src/engine/platform/sound/es550x/es550x_alu.cpp
Normal file
116
src/engine/platform/sound/es550x/es550x_alu.cpp
Normal file
|
|
@ -0,0 +1,116 @@
|
|||
/*
|
||||
License: BSD-3-Clause
|
||||
see https://github.com/cam900/vgsound_emu/LICENSE for more details
|
||||
|
||||
Copyright holder(s): 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 = bitfield(m_accum - m_fc, 0, m_total_bits);
|
||||
return ((!m_cr.lei) && (m_accum < m_start)) ? true : false;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_accum = bitfield(m_accum + m_fc, 0, m_total_bits);
|
||||
return ((!m_cr.lei) && (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_accum + m_start) - m_end;
|
||||
}
|
||||
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_start) - m_end;
|
||||
}
|
||||
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(m_accum, std::min<u8>(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);
|
||||
}
|
||||
70
src/engine/platform/sound/es550x/es550x_filter.cpp
Normal file
70
src/engine/platform/sound/es550x/es550x_filter.cpp
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
/*
|
||||
License: BSD-3-Clause
|
||||
see https://github.com/cam900/vgsound_emu/LICENSE for more details
|
||||
|
||||
Copyright holder(s): 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_o4_1 = lp_exec(coeff_k1, m_o2_1, m_o3_1);
|
||||
m_o3_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_o4_1 = lp_exec(coeff_k1, m_o2_1, m_o3_1);
|
||||
m_o3_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);
|
||||
}
|
||||
|
|
@ -83,8 +83,8 @@ void DivPlatformSwan::acquire(short* bufL, short* bufR, size_t start, size_t len
|
|||
continue;
|
||||
}
|
||||
rWrite(0x09,(unsigned char)s->data8[dacPos++]+0x80);
|
||||
if (dacPos>=s->samples) {
|
||||
if (s->loopStart>=0 && s->loopStart<(int)s->samples) {
|
||||
if (((s->loopMode!=DIV_SAMPLE_LOOPMODE_ONESHOT) && dacPos>=s->loopEnd) || dacPos>=s->samples) {
|
||||
if (s->isLoopable()) {
|
||||
dacPos=s->loopStart;
|
||||
} else {
|
||||
dacSample=-1;
|
||||
|
|
|
|||
|
|
@ -96,8 +96,8 @@ void DivPlatformVERA::acquire(short* bufL, short* bufR, size_t start, size_t len
|
|||
rWritePCMData(tmp_r&0xff);
|
||||
}
|
||||
chan[16].pcm.pos++;
|
||||
if (chan[16].pcm.pos>=s->samples) {
|
||||
if (s->loopStart>=0 && s->loopStart<(int)s->samples) {
|
||||
if (((s->loopMode!=DIV_SAMPLE_LOOPMODE_ONESHOT) && chan[16].pcm.pos>=s->loopEnd) || (chan[16].pcm.pos>=s->samples)) {
|
||||
if (s->isLoopable()) {
|
||||
chan[16].pcm.pos=s->loopStart;
|
||||
} else {
|
||||
chan[16].pcm.sample=-1;
|
||||
|
|
|
|||
|
|
@ -77,8 +77,8 @@ void DivPlatformVRC6::acquire(short* bufL, short* bufR, size_t start, size_t len
|
|||
chWrite(i,0,0x80|chan[i].dacOut);
|
||||
}
|
||||
chan[i].dacPos++;
|
||||
if (chan[i].dacPos>=s->samples) {
|
||||
if (s->loopStart>=0 && s->loopStart<(int)s->samples) {
|
||||
if (((s->loopMode!=DIV_SAMPLE_LOOPMODE_ONESHOT) && chan[i].dacPos>=s->loopEnd) || (chan[i].dacPos>=s->samples)) {
|
||||
if (s->isLoopable()) {
|
||||
chan[i].dacPos=s->loopStart;
|
||||
} else {
|
||||
chan[i].dacSample=-1;
|
||||
|
|
|
|||
|
|
@ -644,11 +644,11 @@ int DivPlatformYM2610::dispatch(DivCommand c) {
|
|||
DivSample* s=parent->getSample(chan[c.chan].sample);
|
||||
immWrite(0x12,(s->offB>>8)&0xff);
|
||||
immWrite(0x13,s->offB>>16);
|
||||
int end=s->offB+s->lengthB-1;
|
||||
int end=((s->offB+s->lengthB+0xff)&~0xff)-1;
|
||||
immWrite(0x14,(end>>8)&0xff);
|
||||
immWrite(0x15,end>>16);
|
||||
immWrite(0x11,isMuted[c.chan]?0:(chan[c.chan].pan<<6));
|
||||
immWrite(0x10,(s->loopStart>=0)?0x90:0x80); // start/repeat
|
||||
immWrite(0x10,((s->loopMode!=DIV_SAMPLE_LOOPMODE_ONESHOT) && (s->loopStart>=0))?0x90:0x80); // start/repeat
|
||||
if (c.value!=DIV_NOTE_NULL) {
|
||||
chan[c.chan].note=c.value;
|
||||
chan[c.chan].baseFreq=NOTE_ADPCMB(chan[c.chan].note);
|
||||
|
|
@ -679,11 +679,11 @@ int DivPlatformYM2610::dispatch(DivCommand c) {
|
|||
DivSample* s=parent->getSample(12*sampleBank+c.value%12);
|
||||
immWrite(0x12,(s->offB>>8)&0xff);
|
||||
immWrite(0x13,s->offB>>16);
|
||||
int end=s->offB+s->lengthB-1;
|
||||
int end=((s->offB+s->lengthB+0xff)&~0xff)-1;
|
||||
immWrite(0x14,(end>>8)&0xff);
|
||||
immWrite(0x15,end>>16);
|
||||
immWrite(0x11,isMuted[c.chan]?0:(chan[c.chan].pan<<6));
|
||||
immWrite(0x10,(s->loopStart>=0)?0x90:0x80); // start/repeat
|
||||
immWrite(0x10,((s->loopMode!=DIV_SAMPLE_LOOPMODE_ONESHOT) && (s->loopStart>=0))?0x90:0x80); // start/repeat
|
||||
chan[c.chan].baseFreq=(((unsigned int)s->rate)<<16)/(chipClock/144);
|
||||
chan[c.chan].freqChanged=true;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -707,11 +707,11 @@ int DivPlatformYM2610B::dispatch(DivCommand c) {
|
|||
DivSample* s=parent->getSample(chan[c.chan].sample);
|
||||
immWrite(0x12,(s->offB>>8)&0xff);
|
||||
immWrite(0x13,s->offB>>16);
|
||||
int end=s->offB+s->lengthB-1;
|
||||
int end=((s->offB+s->lengthB+0xff)&~0xff)-1;
|
||||
immWrite(0x14,(end>>8)&0xff);
|
||||
immWrite(0x15,end>>16);
|
||||
immWrite(0x11,isMuted[c.chan]?0:(chan[c.chan].pan<<6));
|
||||
immWrite(0x10,(s->loopStart>=0)?0x90:0x80); // start/repeat
|
||||
immWrite(0x10,((s->loopMode!=DIV_SAMPLE_LOOPMODE_ONESHOT) && (s->loopStart>=0))?0x90:0x80); // start/repeat
|
||||
if (c.value!=DIV_NOTE_NULL) {
|
||||
chan[c.chan].note=c.value;
|
||||
chan[c.chan].baseFreq=NOTE_ADPCMB(chan[c.chan].note);
|
||||
|
|
@ -742,11 +742,11 @@ int DivPlatformYM2610B::dispatch(DivCommand c) {
|
|||
DivSample* s=parent->getSample(12*sampleBank+c.value%12);
|
||||
immWrite(0x12,(s->offB>>8)&0xff);
|
||||
immWrite(0x13,s->offB>>16);
|
||||
int end=s->offB+s->lengthB-1;
|
||||
int end=((s->offB+s->lengthB+0xff)&~0xff)-1;
|
||||
immWrite(0x14,(end>>8)&0xff);
|
||||
immWrite(0x15,end>>16);
|
||||
immWrite(0x11,isMuted[c.chan]?0:(chan[c.chan].pan<<6));
|
||||
immWrite(0x10,(s->loopStart>=0)?0x90:0x80); // start/repeat
|
||||
immWrite(0x10,((s->loopMode!=DIV_SAMPLE_LOOPMODE_ONESHOT) && (s->loopStart>=0))?0x90:0x80); // start/repeat
|
||||
chan[c.chan].baseFreq=(((unsigned int)s->rate)<<16)/(chipClock/144);
|
||||
chan[c.chan].freqChanged=true;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue