Merge branch 'master' of https://github.com/tildearrow/furnace into sample_macro

This commit is contained in:
cam900 2022-09-18 00:11:53 +09:00
commit 0f5a400b29
140 changed files with 2646 additions and 1419 deletions

View file

@ -239,6 +239,10 @@ void DivEngine::setConf(String key, double value) {
conf[key]=fmt::sprintf("%f",value);
}
void DivEngine::setConf(String key, const char* value) {
conf[key]=String(value);
}
void DivEngine::setConf(String key, String value) {
conf[key]=value;
}

View file

@ -147,37 +147,68 @@ void DivEngine::walkSong(int& loopOrder, int& loopRow, int& loopEnd) {
int nextOrder=-1;
int nextRow=0;
int effectVal=0;
int lastSuspectedLoopEnd=-1;
DivPattern* pat[DIV_MAX_CHANS];
unsigned char wsWalked[8192];
memset(wsWalked,0,8192);
for (int i=0; i<curSubSong->ordersLen; i++) {
for (int j=0; j<chans; j++) {
pat[j]=curPat[j].getPattern(curOrders->ord[j][i],false);
}
if (i>lastSuspectedLoopEnd) {
lastSuspectedLoopEnd=i;
}
for (int j=nextRow; j<curSubSong->patLen; j++) {
nextRow=0;
bool changingOrder=false;
bool jumpingOrder=false;
if (wsWalked[((i<<5)+(j>>3))&8191]&(1<<(j&7))) {
loopOrder=i;
loopRow=j;
loopEnd=lastSuspectedLoopEnd;
return;
}
for (int k=0; k<chans; k++) {
for (int l=0; l<curPat[k].effectCols; l++) {
effectVal=pat[k]->data[j][5+(l<<1)];
if (effectVal<0) effectVal=0;
if (pat[k]->data[j][4+(l<<1)]==0x0d) {
if (nextOrder==-1 && (i<curSubSong->ordersLen-1 || !song.ignoreJumpAtEnd)) {
nextOrder=i+1;
nextRow=effectVal;
if (song.jumpTreatment==2) {
if ((i<curSubSong->ordersLen-1 || !song.ignoreJumpAtEnd)) {
nextOrder=i+1;
nextRow=effectVal;
jumpingOrder=true;
}
} else if (song.jumpTreatment==1) {
if (nextOrder==-1 && (i<curSubSong->ordersLen-1 || !song.ignoreJumpAtEnd)) {
nextOrder=i+1;
nextRow=effectVal;
jumpingOrder=true;
}
} else {
if ((i<curSubSong->ordersLen-1 || !song.ignoreJumpAtEnd)) {
if (!changingOrder) {
nextOrder=i+1;
}
jumpingOrder=true;
nextRow=effectVal;
}
}
} else if (pat[k]->data[j][4+(l<<1)]==0x0b) {
if (nextOrder==-1) {
if (nextOrder==-1 || song.jumpTreatment==0) {
nextOrder=effectVal;
nextRow=0;
if (song.jumpTreatment==1 || song.jumpTreatment==2 || !jumpingOrder) {
nextRow=0;
}
changingOrder=true;
}
}
}
}
wsWalked[((i<<5)+(j>>3))&8191]|=1<<(j&7);
if (nextOrder!=-1) {
if (nextOrder<=i) {
loopOrder=nextOrder;
loopRow=nextRow;
loopEnd=i;
return;
}
i=nextOrder-1;
nextOrder=-1;
break;
@ -1659,6 +1690,7 @@ void DivEngine::playSub(bool preserveDrift, int goalRow) {
speedAB=false;
playing=true;
skipping=true;
memset(walked,0,8192);
for (int i=0; i<song.systemLen; i++) disCont[i].dispatch->setSkipRegisterWrites(true);
while (playing && curOrder<goal) {
if (nextTick(preserveDrift)) {
@ -3440,9 +3472,8 @@ void DivEngine::setConsoleMode(bool enable) {
}
bool DivEngine::switchMaster() {
deinitAudioBackend();
quitDispatch();
initDispatch();
logI("switching output...");
deinitAudioBackend(true);
if (initAudioBackend()) {
for (int i=0; i<song.systemLen; i++) {
disCont[i].setRates(got.rate);
@ -3586,6 +3617,7 @@ void DivEngine::quitDispatch() {
bool DivEngine::initAudioBackend() {
// load values
logI("initializing audio.");
if (audioEngine==DIV_AUDIO_NULL) {
if (getConfString("audioEngine","SDL")=="JACK") {
audioEngine=DIV_AUDIO_JACK;
@ -3691,8 +3723,9 @@ bool DivEngine::initAudioBackend() {
return true;
}
bool DivEngine::deinitAudioBackend() {
bool DivEngine::deinitAudioBackend(bool dueToSwitchMaster) {
if (output!=NULL) {
logI("closing audio output.");
output->quit();
if (output->midiIn) {
if (output->midiIn->isDeviceOpen()) {
@ -3709,7 +3742,9 @@ bool DivEngine::deinitAudioBackend() {
output->quitMidi();
delete output;
output=NULL;
//audioEngine=DIV_AUDIO_NULL;
if (dueToSwitchMaster) {
audioEngine=DIV_AUDIO_NULL;
}
}
return true;
}

View file

@ -46,9 +46,8 @@
#define BUSY_BEGIN_SOFT softLocked=true; isBusy.lock();
#define BUSY_END isBusy.unlock(); softLocked=false;
#define DIV_VERSION "dev112"
#define DIV_ENGINE_VERSION 112
#define DIV_VERSION "dev114"
#define DIV_ENGINE_VERSION 114
// for imports
#define DIV_VERSION_MOD 0xff01
#define DIV_VERSION_FC 0xff02
@ -358,6 +357,8 @@ class DivEngine {
double exportFadeOut;
std::map<String,String> conf;
std::deque<DivNoteEvent> pendingNotes;
// bitfield
unsigned char walked[8192];
bool isMuted[DIV_MAX_CHANS];
std::mutex isBusy, saveLock;
String configPath;
@ -453,7 +454,7 @@ class DivEngine {
int loadSampleROM(String path, ssize_t expectedSize, unsigned char*& ret);
bool initAudioBackend();
bool deinitAudioBackend();
bool deinitAudioBackend(bool dueToSwitchMaster=false);
void registerSystems();
void initSongWithDesc(const int* description);
@ -545,6 +546,7 @@ class DivEngine {
void setConf(String key, int value);
void setConf(String key, float value);
void setConf(String key, double value);
void setConf(String key, const char* value);
void setConf(String key, String value);
// calculate base frequency/period
@ -1074,6 +1076,7 @@ class DivEngine {
memset(reversePitchTable,0,4096*sizeof(int));
memset(pitchTable,0,4096*sizeof(int));
memset(sysDefs,0,256*sizeof(void*));
memset(walked,0,8192);
for (int i=0; i<256; i++) {
sysFileMapFur[i]=DIV_SYSTEM_NULL;

View file

@ -179,6 +179,7 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) {
ds.brokenPortaArp=false;
ds.snNoLowPeriods=true;
ds.delayBehavior=0;
ds.jumpTreatment=2;
// 1.1 compat flags
if (ds.version>24) {
@ -1081,6 +1082,9 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) {
if (ds.version<110) {
ds.delayBehavior=1;
}
if (ds.version<113) {
ds.jumpTreatment=1;
}
ds.isDMF=false;
reader.readS(); // reserved
@ -1503,7 +1507,12 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) {
} else {
reader.readC();
}
for (int i=0; i<5; i++) {
if (ds.version>=113) {
ds.jumpTreatment=reader.readC();
} else {
reader.readC();
}
for (int i=0; i<4; i++) {
reader.readC();
}
}
@ -3751,7 +3760,8 @@ SafeWriter* DivEngine::saveFur(bool notPrimary) {
w->writeC(song.brokenPortaArp);
w->writeC(song.snNoLowPeriods);
w->writeC(song.delayBehavior);
for (int i=0; i<5; i++) {
w->writeC(song.jumpTreatment);
for (int i=0; i<4; i++) {
w->writeC(0);
}

View file

@ -150,9 +150,13 @@ void DivEngine::loadDMP(SafeReader& reader, std::vector<DivInstrument*>& ret, St
ins->type=DIV_INS_FM;
logD("instrument type is Arcade");
break;
case 9: // Neo Geo
ins->type=DIV_INS_FM;
logD("instrument type is Neo Geo");
break;
default:
logD("instrument type is unknown");
lastError="unknown instrument type!";
lastError=fmt::sprintf("unknown instrument type %d!",sys);
delete ins;
return;
break;
@ -171,11 +175,21 @@ void DivEngine::loadDMP(SafeReader& reader, std::vector<DivInstrument*>& ret, St
mode=reader.readC();
logD("instrument mode is %d",mode);
if (mode==0) {
if (version<11) {
ins->type=DIV_INS_STD;
if (ins->type==DIV_INS_FM) {
if (sys==9) {
ins->type=DIV_INS_AY;
} else {
ins->type=DIV_INS_STD;
}
}
} else {
ins->type=DIV_INS_FM;
if (sys==3 || sys==6) {
ins->type=DIV_INS_OPLL;
} else if (sys==1) {
ins->type=DIV_INS_OPL;
} else {
ins->type=DIV_INS_FM;
}
}
} else {
ins->type=DIV_INS_FM;
@ -232,12 +246,23 @@ void DivEngine::loadDMP(SafeReader& reader, std::vector<DivInstrument*>& ret, St
ins->fm.op[j].dvb=reader.readC();
ins->fm.op[j].dam=reader.readC();
} else {
ins->fm.op[j].rs=reader.readC();
ins->fm.op[j].dt=reader.readC();
ins->fm.op[j].dt2=ins->fm.op[j].dt>>4;
ins->fm.op[j].dt&=15;
ins->fm.op[j].d2r=reader.readC();
ins->fm.op[j].ssgEnv=reader.readC();
if (sys==3 || sys==6) { // OPLL/VRC7
ins->fm.op[j].ksr=reader.readC()?1:0;
ins->fm.op[j].vib=reader.readC();
if (j==0) {
ins->fm.opllPreset=ins->fm.op[j].vib>>4;
}
ins->fm.op[j].vib=ins->fm.op[j].vib?1:0;
ins->fm.op[j].ksl=reader.readC()?1:0;
ins->fm.op[j].ssgEnv=reader.readC();
} else {
ins->fm.op[j].rs=reader.readC();
ins->fm.op[j].dt=reader.readC();
ins->fm.op[j].dt2=ins->fm.op[j].dt>>4;
ins->fm.op[j].dt&=15;
ins->fm.op[j].d2r=reader.readC();
ins->fm.op[j].ssgEnv=reader.readC();
}
}
}
} else { // STD
@ -247,6 +272,9 @@ void DivEngine::loadDMP(SafeReader& reader, std::vector<DivInstrument*>& ret, St
if (version>5) {
for (int i=0; i<ins->std.volMacro.len; i++) {
ins->std.volMacro.val[i]=reader.readI();
if (ins->std.volMacro.val[i]>15 && sys==6) { // FDS
ins->type=DIV_INS_FDS;
}
}
} else {
for (int i=0; i<ins->std.volMacro.len; i++) {
@ -805,7 +833,7 @@ void DivEngine::loadOPNI(SafeReader& reader, std::vector<DivInstrument*>& ret, S
op.mult = dtMul & 0xF;
op.dt = ((dtMul >> 4) & 0x7);
op.tl = totalLevel & 0x3F;
op.tl = totalLevel & 0x7F;
op.rs = ((arRateScale >> 6) & 0x3);
op.ar = arRateScale & 0x1F;
op.dr = drAmpEnable & 0x1F;
@ -1643,7 +1671,7 @@ void DivEngine::loadWOPN(SafeReader& reader, std::vector<DivInstrument*>& ret, S
total += (op.mult = dtMul & 0xF);
total += (op.dt = ((dtMul >> 4) & 0x7));
total += (op.tl = totalLevel & 0x3F);
total += (op.tl = totalLevel & 0x7F);
total += (op.rs = ((arRateScale >> 6) & 0x3));
total += (op.ar = arRateScale & 0x1F);
total += (op.dr = drAmpEnable & 0x1F);

View file

@ -71,8 +71,10 @@ void DivInstrument::putInsData(SafeWriter* w) {
w->writeC(op.ws);
w->writeC(op.ksr);
w->writeC(op.enable);
// reserved
for (int k=0; k<12; k++) {
for (int k=0; k<11; k++) {
w->writeC(0);
}
}
@ -716,8 +718,14 @@ DivDataErrors DivInstrument::readInsData(SafeReader& reader, short version) {
op.ws=reader.readC();
op.ksr=reader.readC();
if (version>=114) {
op.enable=reader.readC();
} else {
reader.readC();
}
// reserved
for (int k=0; k<12; k++) reader.readC();
for (int k=0; k<11; k++) reader.readC();
}
// GB

View file

@ -67,7 +67,7 @@ void DivPlatformArcade::acquire_nuked(short* bufL, short* bufR, size_t start, si
w.addrOrVal=true;
}
}
OPM_Clock(&fm,NULL,NULL,NULL,NULL);
OPM_Clock(&fm,NULL,NULL,NULL,NULL);
OPM_Clock(&fm,NULL,NULL,NULL,NULL);
@ -77,13 +77,13 @@ void DivPlatformArcade::acquire_nuked(short* bufL, short* bufR, size_t start, si
for (int i=0; i<8; i++) {
oscBuf[i]->data[oscBuf[i]->needle++]=fm.ch_out[i];
}
if (o[0]<-32768) o[0]=-32768;
if (o[0]>32767) o[0]=32767;
if (o[1]<-32768) o[1]=-32768;
if (o[1]>32767) o[1]=32767;
bufL[h]=o[0];
bufR[h]=o[1];
}
@ -106,7 +106,7 @@ void DivPlatformArcade::acquire_ymfm(short* bufL, short* bufR, size_t start, siz
delay=1;
}
}
fm_ymfm->generate(&out_ymfm);
for (int i=0; i<8; i++) {
@ -120,7 +120,7 @@ void DivPlatformArcade::acquire_ymfm(short* bufL, short* bufR, size_t start, siz
os[1]=out_ymfm.data[1];
if (os[1]<-32768) os[1]=-32768;
if (os[1]>32767) os[1]=32767;
bufL[h]=os[0];
bufR[h]=os[1];
}
@ -255,6 +255,10 @@ void DivPlatformArcade::tick(bool sysTick) {
chan[i].state.ams=chan[i].std.ams.val;
rWrite(chanOffs[i]+ADDR_FMS_AMS,((chan[i].state.fms&7)<<4)|(chan[i].state.ams&3));
}
if (chan[i].std.ex4.had && chan[i].active) {
chan[i].opMask=chan[i].std.ex4.val&15;
chan[i].opMaskChanged=true;
}
for (int j=0; j<4; j++) {
unsigned short baseAddr=chanOffs[i]|opOffs[j];
DivInstrumentFM::Operator& op=chan[i].state.op[j];
@ -347,8 +351,9 @@ void DivPlatformArcade::tick(bool sysTick) {
immWrite(i+0x30,chan[i].freq<<2);
chan[i].freqChanged=false;
}
if (chan[i].keyOn) {
immWrite(0x08,0x78|i);
if (chan[i].keyOn || chan[i].opMaskChanged) {
immWrite(0x08,(chan[i].opMask<<3)|i);
chan[i].opMaskChanged=false;
chan[i].keyOn=false;
}
}
@ -370,6 +375,11 @@ int DivPlatformArcade::dispatch(DivCommand c) {
if (chan[c.chan].insChanged) {
chan[c.chan].state=ins->fm;
chan[c.chan].opMask=
(chan[c.chan].state.op[0].enable?1:0)|
(chan[c.chan].state.op[2].enable?2:0)|
(chan[c.chan].state.op[1].enable?4:0)|
(chan[c.chan].state.op[3].enable?8:0);
}
chan[c.chan].macroInit(ins);
@ -502,6 +512,12 @@ int DivPlatformArcade::dispatch(DivCommand c) {
break;
}
case DIV_CMD_FM_LFO: {
if(c.value==0) {
rWrite(0x01,0x02);
}
else {
rWrite(0x01,0x00);
}
rWrite(0x18,c.value);
break;
}
@ -825,6 +841,8 @@ void DivPlatformArcade::reset() {
pmDepth=0x7f;
//rWrite(0x18,0x10);
immWrite(0x01,0x02); // LFO Off
immWrite(0x18,0x00); // LFO Freq Off
immWrite(0x19,amDepth);
immWrite(0x19,0x80|pmDepth);
//rWrite(0x1b,0x00);

View file

@ -43,9 +43,9 @@ class DivPlatformArcade: public DivPlatformOPM {
int freq, baseFreq, pitch, pitch2, note;
int ins;
signed char konCycles;
bool active, insChanged, freqChanged, keyOn, keyOff, inPorta, portaPause, furnacePCM, hardReset;
bool active, insChanged, freqChanged, keyOn, keyOff, inPorta, portaPause, furnacePCM, hardReset, opMaskChanged;
int vol, outVol;
unsigned char chVolL, chVolR;
unsigned char chVolL, chVolR, opMask;
void macroInit(DivInstrument* which) {
std.init(which);
pitch2=0;
@ -68,10 +68,12 @@ class DivPlatformArcade: public DivPlatformOPM {
portaPause(false),
furnacePCM(false),
hardReset(false),
opMaskChanged(false),
vol(0),
outVol(0),
chVolL(127),
chVolR(127) {}
chVolR(127),
opMask(15) {}
};
Channel chan[8];
DivDispatchOscBuffer* oscBuf[8];

View file

@ -347,6 +347,10 @@ void DivPlatformGenesis::tick(bool sysTick) {
chan[i].state.ams=chan[i].std.ams.val;
rWrite(chanOffs[i]+ADDR_LRAF,(IS_REALLY_MUTED(i)?0:(chan[i].pan<<6))|(chan[i].state.fms&7)|((chan[i].state.ams&3)<<4));
}
if (chan[i].std.ex4.had && chan[i].active) {
chan[i].opMask=chan[i].std.ex4.val&15;
chan[i].opMaskChanged=true;
}
for (int j=0; j<4; j++) {
unsigned short baseAddr=chanOffs[i]|opOffs[j];
DivInstrumentFM::Operator& op=chan[i].state.op[j];
@ -479,8 +483,9 @@ void DivPlatformGenesis::tick(bool sysTick) {
}
chan[i].freqChanged=false;
}
if (chan[i].keyOn) {
if (i<6) immWrite(0x28,0xf0|konOffs[i]);
if (chan[i].keyOn || chan[i].opMaskChanged) {
if (i<6) immWrite(0x28,(chan[i].opMask<<4)|konOffs[i]);
chan[i].opMaskChanged=false;
chan[i].keyOn=false;
}
}
@ -591,6 +596,11 @@ int DivPlatformGenesis::dispatch(DivCommand c) {
if (chan[c.chan].insChanged) {
chan[c.chan].state=ins->fm;
chan[c.chan].opMask=
(chan[c.chan].state.op[0].enable?1:0)|
(chan[c.chan].state.op[2].enable?2:0)|
(chan[c.chan].state.op[1].enable?4:0)|
(chan[c.chan].state.op[3].enable?8:0);
}
chan[c.chan].macroInit(ins);

View file

@ -45,9 +45,9 @@ class DivPlatformGenesis: public DivPlatformOPN {
unsigned char freqH, freqL;
int freq, baseFreq, pitch, pitch2, portaPauseFreq, note;
int ins;
bool active, insChanged, freqChanged, keyOn, keyOff, portaPause, furnaceDac, inPorta, hardReset;
bool active, insChanged, freqChanged, keyOn, keyOff, portaPause, furnaceDac, inPorta, hardReset, opMaskChanged;
int vol, outVol;
unsigned char pan;
unsigned char pan, opMask;
bool dacMode;
int dacPeriod;
@ -82,9 +82,11 @@ class DivPlatformGenesis: public DivPlatformOPN {
furnaceDac(false),
inPorta(false),
hardReset(false),
opMaskChanged(false),
vol(0),
outVol(0),
pan(3),
opMask(15),
dacMode(false),
dacPeriod(0),
dacRate(0),

View file

@ -25,6 +25,7 @@
#define CHIP_DIVIDER fmDivBase
#define IS_REALLY_MUTED(x) (isMuted[x] && (x<5 || !softPCM || (isMuted[5] && isMuted[6])))
#define IS_EXTCH_MUTED (isOpMuted[0] && isOpMuted[1] && isOpMuted[2] && isOpMuted[3])
int DivPlatformGenesisExt::dispatch(DivCommand c) {
if (c.chan<2) {
@ -69,10 +70,11 @@ int DivPlatformGenesisExt::dispatch(DivCommand c) {
rWrite(baseAddr+0x70,op.d2r&31);
rWrite(baseAddr+0x80,(op.rr&15)|(op.sl<<4));
rWrite(baseAddr+0x90,op.ssgEnv&15);
opChan[ch].mask=op.enable;
}
if (opChan[ch].insChanged) { // TODO how does this work?
rWrite(chanOffs[2]+0xb0,(chan[2].state.alg&7)|(chan[2].state.fb<<3));
rWrite(chanOffs[2]+0xb4,(opChan[ch].pan<<6)|(chan[2].state.fms&7)|((chan[2].state.ams&3)<<4));
rWrite(chanOffs[2]+0xb4,(IS_EXTCH_MUTED?0:(opChan[ch].pan<<6))|(chan[2].state.fms&7)|((chan[2].state.ams&3)<<4));
}
opChan[ch].insChanged=false;
@ -123,7 +125,7 @@ int DivPlatformGenesisExt::dispatch(DivCommand c) {
opChan[i].pan=opChan[ch].pan;
}
}
rWrite(chanOffs[2]+0xb4,(opChan[ch].pan<<6)|(chan[2].state.fms&7)|((chan[2].state.ams&3)<<4));
rWrite(chanOffs[2]+0xb4,(IS_EXTCH_MUTED?0:(opChan[ch].pan<<6))|(chan[2].state.fms&7)|((chan[2].state.ams&3)<<4));
break;
}
case DIV_CMD_PITCH: {
@ -397,6 +399,8 @@ void DivPlatformGenesisExt::muteChannel(int ch, bool mute) {
rWrite(baseAddr+0x40,op.tl);
immWrite(baseAddr+0x40,op.tl);
}
rWrite(chanOffs[2]+0xb4,(IS_EXTCH_MUTED?0:(opChan[ch-2].pan<<6))|(chan[2].state.fms&7)|((chan[2].state.ams&3)<<4));
}
static int opChanOffsL[4]={
@ -412,7 +416,7 @@ void DivPlatformGenesisExt::tick(bool sysTick) {
bool writeSomething=false;
unsigned char writeMask=2;
for (int i=0; i<4; i++) {
writeMask|=opChan[i].active<<(4+i);
writeMask|=(unsigned char)(opChan[i].mask && opChan[i].active)<<(4+i);
if (opChan[i].keyOn || opChan[i].keyOff) {
writeSomething=true;
writeMask&=~(1<<(4+i));
@ -459,10 +463,12 @@ void DivPlatformGenesisExt::tick(bool sysTick) {
immWrite(opChanOffsH[i],opChan[i].freq>>8);
immWrite(opChanOffsL[i],opChan[i].freq&0xff);
}
writeMask|=opChan[i].active<<(4+i);
writeMask|=(unsigned char)(opChan[i].mask && opChan[i].active)<<(4+i);
if (opChan[i].keyOn) {
writeNoteOn=true;
writeMask|=1<<(4+i);
if (opChan[i].mask) {
writeMask|=1<<(4+i);
}
opChan[i].keyOn=false;
}
}
@ -544,7 +550,11 @@ void DivPlatformGenesisExt::forceIns() {
rWrite(baseAddr+ADDR_SSG,op.ssgEnv&15);
}
rWrite(chanOffs[i]+ADDR_FB_ALG,(chan[i].state.alg&7)|(chan[i].state.fb<<3));
rWrite(chanOffs[i]+ADDR_LRAF,(IS_REALLY_MUTED(i)?0:(chan[i].pan<<6))|(chan[i].state.fms&7)|((chan[i].state.ams&3)<<4));
if (i==2) {
rWrite(chanOffs[i]+ADDR_LRAF,(IS_EXTCH_MUTED?0:(opChan[0].pan<<6))|(chan[i].state.fms&7)|((chan[i].state.ams&3)<<4));
} else {
rWrite(chanOffs[i]+ADDR_LRAF,(IS_REALLY_MUTED(i)?0:(chan[i].pan<<6))|(chan[i].state.fms&7)|((chan[i].state.ams&3)<<4));
}
if (chan[i].active) {
chan[i].keyOn=true;
chan[i].freqChanged=true;

View file

@ -27,7 +27,7 @@ class DivPlatformGenesisExt: public DivPlatformGenesis {
unsigned char freqH, freqL;
int freq, baseFreq, pitch, pitch2, portaPauseFreq, ins;
signed char konCycles;
bool active, insChanged, freqChanged, keyOn, keyOff, portaPause, inPorta;
bool active, insChanged, freqChanged, keyOn, keyOff, portaPause, inPorta, mask;
int vol;
unsigned char pan;
OpChannel():
@ -46,6 +46,7 @@ class DivPlatformGenesisExt: public DivPlatformGenesis {
keyOff(false),
portaPause(false),
inPorta(false),
mask(true),
vol(0),
pan(3) {}
};

View file

@ -642,6 +642,9 @@ void DivPlatformN163::setFlags(unsigned int flags) {
for (int i=0; i<8; i++) {
oscBuf[i]->rate=rate/(initChanMax+1);
}
// needed to make sure changing channel count won't trigger glitches
reset();
}
int DivPlatformN163::init(DivEngine* p, int channels, int sugRate, unsigned int flags) {

View file

@ -249,7 +249,9 @@ void DivPlatformOPL::acquire_nuked(short* bufL, short* bufR, size_t start, size_
if (os[1]>32767) os[1]=32767;
bufL[h]=os[0];
bufR[h]=os[1];
if (oplType==3 || oplType==759) {
bufR[h]=os[1];
}
}
}
@ -1531,7 +1533,7 @@ void DivPlatformOPL::reset() {
}
bool DivPlatformOPL::isStereo() {
return true;
return (oplType==3 || oplType==759);
}
bool DivPlatformOPL::keyOffAffectsArp(int ch) {

View file

@ -0,0 +1,143 @@
//============================================================================
//
// SSSS tt lll lll
// SS SS tt ll ll
// SS tttttt eeee ll ll aaaa
// SSSS tt ee ee ll ll aa
// SS tt eeeeee ll ll aaaaa -- "An Atari 2600 VCS Emulator"
// SS SS tt ee ll ll aa aa
// SSSS ttt eeeee llll llll aaaaa
//
// Copyright (c) 1995-2021 by Bradford W. Mott, Stephen Anthony
// and the Stella Team
//
// See the file "License.txt" for information on usage and redistribution of
// this file, and for a DISCLAIMER OF ALL WARRANTIES.
//============================================================================
#define _USE_MATH_DEFINES
#include "Audio.h"
#include <math.h>
namespace {
constexpr double R_MAX = 30.;
constexpr double R = 1.;
short mixingTableEntry(unsigned char v, unsigned char vMax)
{
return static_cast<short>(
floor(0x7fff * double(v) / double(vMax) * (R_MAX + R * double(vMax)) / (R_MAX + R * double(v)))
);
}
}
namespace TIA {
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Audio::Audio()
{
for (unsigned char i = 0; i <= 0x1e; ++i) myMixingTableSum[i] = mixingTableEntry(i, 0x1e);
for (unsigned char i = 0; i <= 0x0f; ++i) myMixingTableIndividual[i] = mixingTableEntry(i, 0x0f);
reset(false);
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void Audio::reset(bool st)
{
myCounter = 0;
mySampleIndex = 0;
stereo = st;
myCurrentSample[0]=0;
myCurrentSample[1]=0;
myChannelOut[0]=0;
myChannelOut[1]=0;
myChannel0.reset();
myChannel1.reset();
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void Audio::tick()
{
switch (myCounter) {
case 9:
case 81:
myChannel0.phase0();
myChannel1.phase0();
break;
case 37:
case 149:
phase1();
break;
}
if (++myCounter == 228) myCounter = 0;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void Audio::write(unsigned char addr, unsigned char val) {
switch (addr&0x3f) {
case 0x15:
myChannel0.audc(val);
break;
case 0x16:
myChannel1.audc(val);
break;
case 0x17:
myChannel0.audf(val);
break;
case 0x18:
myChannel1.audf(val);
break;
case 0x19:
myChannel0.audv(val);
break;
case 0x1a:
myChannel1.audv(val);
break;
}
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void Audio::phase1()
{
unsigned char sample0 = myChannel0.phase1();
unsigned char sample1 = myChannel1.phase1();
addSample(sample0, sample1);
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void Audio::addSample(unsigned char sample0, unsigned char sample1)
{
if(stereo) {
myCurrentSample[0] = myMixingTableIndividual[sample0];
myCurrentSample[1] = myMixingTableIndividual[sample1];
}
else {
myCurrentSample[0] = myMixingTableSum[sample0 + sample1];
}
myChannelOut[0] = myMixingTableIndividual[sample0];
myChannelOut[1] = myMixingTableIndividual[sample1];
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
AudioChannel& Audio::channel0()
{
return myChannel0;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
AudioChannel& Audio::channel1()
{
return myChannel1;
}
}

View file

@ -0,0 +1,72 @@
//============================================================================
//
// SSSS tt lll lll
// SS SS tt ll ll
// SS tttttt eeee ll ll aaaa
// SSSS tt ee ee ll ll aa
// SS tt eeeeee ll ll aaaaa -- "An Atari 2600 VCS Emulator"
// SS SS tt ee ll ll aa aa
// SSSS ttt eeeee llll llll aaaaa
//
// Copyright (c) 1995-2021 by Bradford W. Mott, Stephen Anthony
// and the Stella Team
//
// See the file "License.txt" for information on usage and redistribution of
// this file, and for a DISCLAIMER OF ALL WARRANTIES.
//============================================================================
#ifndef TIA_AUDIO_HXX
#define TIA_AUDIO_HXX
#include "AudioChannel.h"
#include <array>
namespace TIA {
class Audio
{
public:
Audio();
void reset(bool stereo);
void tick();
void write(unsigned char addr, unsigned char val);
AudioChannel& channel0();
AudioChannel& channel1();
short myCurrentSample[2];
short myChannelOut[2];
private:
void phase1();
void addSample(unsigned char sample0, unsigned char sample1);
private:
unsigned char myCounter{0};
AudioChannel myChannel0;
AudioChannel myChannel1;
bool stereo;
std::array<short, 0x1e + 1> myMixingTableSum;
std::array<short, 0x0f + 1> myMixingTableIndividual;
unsigned int mySampleIndex{0};
#ifdef GUI_SUPPORT
bool myRewindMode{false};
mutable ByteArray mySamples;
#endif
private:
Audio(const Audio&) = delete;
Audio(Audio&&) = delete;
Audio& operator=(const Audio&) = delete;
Audio& operator=(Audio&&) = delete;
};
}
#endif // TIA_AUDIO_HXX

View file

@ -0,0 +1,140 @@
//============================================================================
//
// SSSS tt lll lll
// SS SS tt ll ll
// SS tttttt eeee ll ll aaaa
// SSSS tt ee ee ll ll aa
// SS tt eeeeee ll ll aaaaa -- "An Atari 2600 VCS Emulator"
// SS SS tt ee ll ll aa aa
// SSSS ttt eeeee llll llll aaaaa
//
// Copyright (c) 1995-2021 by Bradford W. Mott, Stephen Anthony
// and the Stella Team
//
// See the file "License.txt" for information on usage and redistribution of
// this file, and for a DISCLAIMER OF ALL WARRANTIES.
//============================================================================
#include "AudioChannel.h"
namespace TIA {
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void AudioChannel::reset()
{
myAudc = myAudv = myAudf = 0;
myClockEnable = myNoiseFeedback = myNoiseCounterBit4 = myPulseCounterHold = false;
myDivCounter = myPulseCounter = myNoiseCounter = 0;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void AudioChannel::phase0()
{
if (myClockEnable) {
myNoiseCounterBit4 = myNoiseCounter & 0x01;
switch (myAudc & 0x03) {
case 0x00:
case 0x01:
myPulseCounterHold = false;
break;
case 0x02:
myPulseCounterHold = (myNoiseCounter & 0x1e) != 0x02;
break;
case 0x03:
myPulseCounterHold = !myNoiseCounterBit4;
break;
}
switch (myAudc & 0x03) {
case 0x00:
myNoiseFeedback =
((myPulseCounter ^ myNoiseCounter) & 0x01) ||
!(myNoiseCounter || (myPulseCounter != 0x0a)) ||
!(myAudc & 0x0c);
break;
default:
myNoiseFeedback =
(((myNoiseCounter & 0x04) ? 1 : 0) ^ (myNoiseCounter & 0x01)) ||
myNoiseCounter == 0;
break;
}
}
myClockEnable = myDivCounter == myAudf;
if (myDivCounter == myAudf || myDivCounter == 0x1f) {
myDivCounter = 0;
} else {
++myDivCounter;
}
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
unsigned char AudioChannel::phase1()
{
if (myClockEnable) {
bool pulseFeedback = false;
switch (myAudc >> 2) {
case 0x00:
pulseFeedback =
(((myPulseCounter & 0x02) ? 1 : 0) ^ (myPulseCounter & 0x01)) &&
(myPulseCounter != 0x0a) &&
(myAudc & 0x03);
break;
case 0x01:
pulseFeedback = !(myPulseCounter & 0x08);
break;
case 0x02:
pulseFeedback = !myNoiseCounterBit4;
break;
case 0x03:
pulseFeedback = !((myPulseCounter & 0x02) || !(myPulseCounter & 0x0e));
break;
}
myNoiseCounter >>= 1;
if (myNoiseFeedback) {
myNoiseCounter |= 0x10;
}
if (!myPulseCounterHold) {
myPulseCounter = ~(myPulseCounter >> 1) & 0x07;
if (pulseFeedback) {
myPulseCounter |= 0x08;
}
}
}
return (myPulseCounter & 0x01) * myAudv;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void AudioChannel::audc(unsigned char value)
{
myAudc = value & 0x0f;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void AudioChannel::audv(unsigned char value)
{
myAudv = value & 0x0f;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void AudioChannel::audf(unsigned char value)
{
myAudf = value & 0x1f;
}
}

View file

@ -0,0 +1,61 @@
//============================================================================
//
// SSSS tt lll lll
// SS SS tt ll ll
// SS tttttt eeee ll ll aaaa
// SSSS tt ee ee ll ll aa
// SS tt eeeeee ll ll aaaaa -- "An Atari 2600 VCS Emulator"
// SS SS tt ee ll ll aa aa
// SSSS ttt eeeee llll llll aaaaa
//
// Copyright (c) 1995-2021 by Bradford W. Mott, Stephen Anthony
// and the Stella Team
//
// See the file "License.txt" for information on usage and redistribution of
// this file, and for a DISCLAIMER OF ALL WARRANTIES.
//============================================================================
#ifndef TIA_AUDIO_CHANNEL_HXX
#define TIA_AUDIO_CHANNEL_HXX
namespace TIA {
class AudioChannel
{
public:
AudioChannel() = default;
void reset();
void phase0();
unsigned char phase1();
void audc(unsigned char value);
void audf(unsigned char value);
void audv(unsigned char value);
private:
unsigned char myAudc{0};
unsigned char myAudv{0};
unsigned char myAudf{0};
bool myClockEnable{false};
bool myNoiseFeedback{false};
bool myNoiseCounterBit4{false};
bool myPulseCounterHold{false};
unsigned char myDivCounter{0};
unsigned char myPulseCounter{0};
unsigned char myNoiseCounter{0};
private:
AudioChannel(const AudioChannel&) = delete;
AudioChannel(AudioChannel&&) = delete;
AudioChannel& operator=(const AudioChannel&) = delete;
AudioChannel& operator=(AudioChannel&&) = delete;
};
}
#endif // TIA_AUDIO_CHANNEL_HXX

View file

@ -1,377 +0,0 @@
//============================================================================
//
// SSSS tt lll lll
// SS SS tt ll ll
// SS tttttt eeee ll ll aaaa
// SSSS tt ee ee ll ll aa
// SS tt eeeeee ll ll aaaaa -- "An Atari 2600 VCS Emulator"
// SS SS tt ee ll ll aa aa
// SSSS ttt eeeee llll llll aaaaa
//
// Copyright (c) 1995-2016 by Bradford W. Mott, Stephen Anthony
// and the Stella Team
//
// See the file "License.txt" for information on usage and redistribution of
// this file, and for a DISCLAIMER OF ALL WARRANTIES.
//
// $Id: TIASnd.cxx 3239 2015-12-29 19:22:46Z stephena $
//============================================================================
#include "TIATables.h"
#include "TIASnd.h"
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
TIASound::TIASound(int outputFrequency)
: myChannelMode(Hardware2Stereo),
myOutputFrequency(outputFrequency),
myVolumePercentage(100)
{
reset();
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void TIASound::reset()
{
// Fill the polynomials
polyInit(Bit4, 4, 4, 3);
polyInit(Bit5, 5, 5, 3);
polyInit(Bit9, 9, 9, 5);
// Initialize instance variables
for(int chan = 0; chan <= 1; ++chan)
{
myVolume[chan] = 0;
myDivNCnt[chan] = 0;
myDivNMax[chan] = 0;
myDiv3Cnt[chan] = 3;
myAUDC[chan] = 0;
myAUDF[chan] = 0;
myAUDV[chan] = 0;
myP4[chan] = 0;
myP5[chan] = 0;
myP9[chan] = 0;
}
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void TIASound::outputFrequency(int freq)
{
myOutputFrequency = freq;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
std::string TIASound::channels(unsigned int hardware, bool stereo)
{
if(hardware == 1)
myChannelMode = Hardware1;
else
myChannelMode = stereo ? Hardware2Stereo : Hardware2Mono;
switch(myChannelMode)
{
case Hardware1: return "Hardware1";
case Hardware2Mono: return "Hardware2Mono";
case Hardware2Stereo: return "Hardware2Stereo";
default: return "";
}
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void TIASound::set(unsigned short address, unsigned char value)
{
int chan = ~address & 0x1;
switch(address)
{
case TIARegister::AUDC0:
case TIARegister::AUDC1:
myAUDC[chan] = value & 0x0f;
break;
case TIARegister::AUDF0:
case TIARegister::AUDF1:
myAUDF[chan] = value & 0x1f;
break;
case TIARegister::AUDV0:
case TIARegister::AUDV1:
myAUDV[chan] = (value & 0x0f) << AUDV_SHIFT;
break;
default:
return;
}
unsigned short newVal = 0;
// An AUDC value of 0 is a special case
if (myAUDC[chan] == SET_TO_1 || myAUDC[chan] == POLY5_POLY5)
{
// Indicate the clock is zero so no processing will occur,
// and set the output to the selected volume
newVal = 0;
myVolume[chan] = (myAUDV[chan] * myVolumePercentage) / 100;
}
else
{
// Otherwise calculate the 'divide by N' value
newVal = myAUDF[chan] + 1;
// If bits 2 & 3 are set, then multiply the 'div by n' count by 3
if((myAUDC[chan] & DIV3_MASK) == DIV3_MASK && myAUDC[chan] != POLY5_DIV3)
newVal *= 3;
}
// Only reset those channels that have changed
if(newVal != myDivNMax[chan])
{
// Reset the divide by n counters
myDivNMax[chan] = newVal;
// If the channel is now volume only or was volume only,
// reset the counter (otherwise let it complete the previous)
if ((myDivNCnt[chan] == 0) || (newVal == 0))
myDivNCnt[chan] = newVal;
}
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
unsigned char TIASound::get(unsigned short address) const
{
switch(address)
{
case TIARegister::AUDC0: return myAUDC[0];
case TIARegister::AUDC1: return myAUDC[1];
case TIARegister::AUDF0: return myAUDF[0];
case TIARegister::AUDF1: return myAUDF[1];
case TIARegister::AUDV0: return myAUDV[0] >> AUDV_SHIFT;
case TIARegister::AUDV1: return myAUDV[1] >> AUDV_SHIFT;
default: return 0;
}
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void TIASound::volume(unsigned int percent)
{
if(percent <= 100)
myVolumePercentage = percent;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void TIASound::process(short* buffer, unsigned int samples, DivDispatchOscBuffer** oscBuf)
{
// Make temporary local copy
unsigned char audc0 = myAUDC[0], audc1 = myAUDC[1];
unsigned char p5_0 = myP5[0], p5_1 = myP5[1];
unsigned char div_n_cnt0 = myDivNCnt[0], div_n_cnt1 = myDivNCnt[1];
short v0 = myVolume[0], v1 = myVolume[1];
// Take external volume into account
short audv0 = (myAUDV[0] * myVolumePercentage) / 100,
audv1 = (myAUDV[1] * myVolumePercentage) / 100;
// Loop until the sample buffer is full
while(samples > 0)
{
// Process channel 0
if (div_n_cnt0 > 1)
{
div_n_cnt0--;
}
else if (div_n_cnt0 == 1)
{
int prev_bit5 = Bit5[p5_0];
div_n_cnt0 = myDivNMax[0];
// The P5 counter has multiple uses, so we increment it here
p5_0++;
if (p5_0 == POLY5_SIZE)
p5_0 = 0;
// Check clock modifier for clock tick
if ((audc0 & 0x02) == 0 ||
((audc0 & 0x01) == 0 && Div31[p5_0]) ||
((audc0 & 0x01) == 1 && Bit5[p5_0]) ||
((audc0 & 0x0f) == POLY5_DIV3 && Bit5[p5_0] != prev_bit5))
{
if (audc0 & 0x04) // Pure modified clock selected
{
if ((audc0 & 0x0f) == POLY5_DIV3) // POLY5 -> DIV3 mode
{
if ( Bit5[p5_0] != prev_bit5 )
{
myDiv3Cnt[0]--;
if ( !myDiv3Cnt[0] )
{
myDiv3Cnt[0] = 3;
v0 = v0 ? 0 : audv0;
}
}
}
else
{
// If the output was set turn it off, else turn it on
v0 = v0 ? 0 : audv0;
}
}
else if (audc0 & 0x08) // Check for p5/p9
{
if (audc0 == POLY9) // Check for poly9
{
// Increase the poly9 counter
myP9[0]++;
if (myP9[0] == POLY9_SIZE)
myP9[0] = 0;
v0 = Bit9[myP9[0]] ? audv0 : 0;
}
else if ( audc0 & 0x02 )
{
v0 = (v0 || audc0 & 0x01) ? 0 : audv0;
}
else // Must be poly5
{
v0 = Bit5[p5_0] ? audv0 : 0;
}
}
else // Poly4 is the only remaining option
{
// Increase the poly4 counter
myP4[0]++;
if (myP4[0] == POLY4_SIZE)
myP4[0] = 0;
v0 = Bit4[myP4[0]] ? audv0 : 0;
}
}
}
// Process channel 1
if (div_n_cnt1 > 1)
{
div_n_cnt1--;
}
else if (div_n_cnt1 == 1)
{
int prev_bit5 = Bit5[p5_1];
div_n_cnt1 = myDivNMax[1];
// The P5 counter has multiple uses, so we increment it here
p5_1++;
if (p5_1 == POLY5_SIZE)
p5_1 = 0;
// Check clock modifier for clock tick
if ((audc1 & 0x02) == 0 ||
((audc1 & 0x01) == 0 && Div31[p5_1]) ||
((audc1 & 0x01) == 1 && Bit5[p5_1]) ||
((audc1 & 0x0f) == POLY5_DIV3 && Bit5[p5_1] != prev_bit5))
{
if (audc1 & 0x04) // Pure modified clock selected
{
if ((audc1 & 0x0f) == POLY5_DIV3) // POLY5 -> DIV3 mode
{
if ( Bit5[p5_1] != prev_bit5 )
{
myDiv3Cnt[1]--;
if ( ! myDiv3Cnt[1] )
{
myDiv3Cnt[1] = 3;
v1 = v1 ? 0 : audv1;
}
}
}
else
{
// If the output was set turn it off, else turn it on
v1 = v1 ? 0 : audv1;
}
}
else if (audc1 & 0x08) // Check for p5/p9
{
if (audc1 == POLY9) // Check for poly9
{
// Increase the poly9 counter
myP9[1]++;
if (myP9[1] == POLY9_SIZE)
myP9[1] = 0;
v1 = Bit9[myP9[1]] ? audv1 : 0;
}
else if ( audc1 & 0x02 )
{
v1 = (v1 || audc1 & 0x01) ? 0 : audv1;
}
else // Must be poly5
{
v1 = Bit5[p5_1] ? audv1 : 0;
}
}
else // Poly4 is the only remaining option
{
// Increase the poly4 counter
myP4[1]++;
if (myP4[1] == POLY4_SIZE)
myP4[1] = 0;
v1 = Bit4[myP4[1]] ? audv1 : 0;
}
}
}
short byte = v0 + v1;
switch(myChannelMode)
{
case Hardware2Mono: // mono sampling with 2 hardware channels
*(buffer++) = byte;
*(buffer++) = byte;
samples--;
break;
case Hardware2Stereo: // stereo sampling with 2 hardware channels
*(buffer++) = v0;
*(buffer++) = v1;
samples--;
break;
case Hardware1: // mono/stereo sampling with only 1 hardware channel
*(buffer++) = (v0 + v1) >> 1;
samples--;
break;
}
if (oscBuf!=NULL) {
oscBuf[0]->data[oscBuf[0]->needle++]=v0;
oscBuf[1]->data[oscBuf[1]->needle++]=v1;
}
}
// Save for next round
myP5[0] = p5_0;
myP5[1] = p5_1;
myVolume[0] = v0;
myVolume[1] = v1;
myDivNCnt[0] = div_n_cnt0;
myDivNCnt[1] = div_n_cnt1;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void TIASound::polyInit(unsigned char* poly, int size, int f0, int f1)
{
int mask = (1 << size) - 1, x = mask;
for(int i = 0; i < mask; i++)
{
int bit0 = ( ( size - f0 ) ? ( x >> ( size - f0 ) ) : x ) & 0x01;
int bit1 = ( ( size - f1 ) ? ( x >> ( size - f1 ) ) : x ) & 0x01;
poly[i] = x & 1;
// calculate next bit
x = ( x >> 1 ) | ( ( bit0 ^ bit1 ) << ( size - 1) );
}
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
const unsigned char TIASound::Div31[POLY5_SIZE] = {
0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
};

View file

@ -1,186 +0,0 @@
//============================================================================
//
// SSSS tt lll lll
// SS SS tt ll ll
// SS tttttt eeee ll ll aaaa
// SSSS tt ee ee ll ll aa
// SS tt eeeeee ll ll aaaaa -- "An Atari 2600 VCS Emulator"
// SS SS tt ee ll ll aa aa
// SSSS ttt eeeee llll llll aaaaa
//
// Copyright (c) 1995-2016 by Bradford W. Mott, Stephen Anthony
// and the Stella Team
//
// See the file "License.txt" for information on usage and redistribution of
// this file, and for a DISCLAIMER OF ALL WARRANTIES.
//
// $Id: TIASnd.hxx 3239 2015-12-29 19:22:46Z stephena $
//============================================================================
#ifndef TIASOUND_HXX
#define TIASOUND_HXX
#include <string>
#include "../../../dispatch.h"
/**
This class implements a fairly accurate emulation of the TIA sound
hardware. This class uses code/ideas from z26 and MESS.
Currently, the sound generation routines work at 31400Hz only.
Resampling can be done by passing in a different output frequency.
@author Bradford W. Mott, Stephen Anthony, z26 and MESS teams
@version $Id: TIASnd.hxx 3239 2015-12-29 19:22:46Z stephena $
*/
class TIASound
{
public:
/**
Create a new TIA Sound object using the specified output frequency
*/
TIASound(int outputFrequency = 31400);
public:
/**
Reset the sound emulation to its power-on state
*/
void reset();
/**
Set the frequency output samples should be generated at
*/
void outputFrequency(int freq);
/**
Selects the number of audio channels per sample. There are two factors
to consider: hardware capability and desired mixing.
@param hardware The number of channels supported by the sound system
@param stereo Whether to output the internal sound signals into 1
or 2 channels
@return Status of the channel configuration used
*/
std::string channels(unsigned int hardware, bool stereo);
public:
/**
Sets the specified sound register to the given value
@param address Register address
@param value Value to store in the register
*/
void set(unsigned short address, unsigned char value);
/**
Gets the specified sound register's value
@param address Register address
*/
unsigned char get(unsigned short address) const;
/**
Create sound samples based on the current sound register settings
in the specified buffer. NOTE: If channels is set to stereo then
the buffer will need to be twice as long as the number of samples.
@param buffer The location to store generated samples
@param samples The number of samples to generate
*/
void process(short* buffer, unsigned int samples, DivDispatchOscBuffer** oscBuf=NULL);
/**
Set the volume of the samples created (0-100)
*/
void volume(unsigned int percent);
private:
void polyInit(unsigned char* poly, int size, int f0, int f1);
private:
// Definitions for AUDCx (15, 16)
enum AUDCxRegister
{
SET_TO_1 = 0x00, // 0000
POLY4 = 0x01, // 0001
DIV31_POLY4 = 0x02, // 0010
POLY5_POLY4 = 0x03, // 0011
PURE1 = 0x04, // 0100
PURE2 = 0x05, // 0101
DIV31_PURE = 0x06, // 0110
POLY5_2 = 0x07, // 0111
POLY9 = 0x08, // 1000
POLY5 = 0x09, // 1001
DIV31_POLY5 = 0x0a, // 1010
POLY5_POLY5 = 0x0b, // 1011
DIV3_PURE = 0x0c, // 1100
DIV3_PURE2 = 0x0d, // 1101
DIV93_PURE = 0x0e, // 1110
POLY5_DIV3 = 0x0f // 1111
};
enum {
POLY4_SIZE = 0x000f,
POLY5_SIZE = 0x001f,
POLY9_SIZE = 0x01ff,
DIV3_MASK = 0x0c,
AUDV_SHIFT = 10 // shift 2 positions for AUDV,
// then another 8 for 16-bit sound
};
enum ChannelMode {
Hardware2Mono, // mono sampling with 2 hardware channels
Hardware2Stereo, // stereo sampling with 2 hardware channels
Hardware1 // mono/stereo sampling with only 1 hardware channel
};
private:
// Structures to hold the 6 tia sound control bytes
unsigned char myAUDC[2]; // AUDCx (15, 16)
unsigned char myAUDF[2]; // AUDFx (17, 18)
short myAUDV[2]; // AUDVx (19, 1A)
short myVolume[2]; // Last output volume for each channel
unsigned char myP4[2]; // Position pointer for the 4-bit POLY array
unsigned char myP5[2]; // Position pointer for the 5-bit POLY array
unsigned short myP9[2]; // Position pointer for the 9-bit POLY array
unsigned char myDivNCnt[2]; // Divide by n counter. one for each channel
unsigned char myDivNMax[2]; // Divide by n maximum, one for each channel
unsigned char myDiv3Cnt[2]; // Div 3 counter, used for POLY5_DIV3 mode
ChannelMode myChannelMode;
int myOutputFrequency;
unsigned int myVolumePercentage;
/*
Initialize the bit patterns for the polynomials (at runtime).
The 4bit and 5bit patterns are the identical ones used in the tia chip.
Though the patterns could be packed with 8 bits per byte, using only a
single bit per byte keeps the math simple, which is important for
efficient processing.
*/
unsigned char Bit4[POLY4_SIZE];
unsigned char Bit5[POLY5_SIZE];
unsigned char Bit9[POLY9_SIZE];
/*
The 'Div by 31' counter is treated as another polynomial because of
the way it operates. It does not have a 50% duty cycle, but instead
has a 13:18 ratio (of course, 13+18 = 31). This could also be
implemented by using counters.
*/
static const unsigned char Div31[POLY5_SIZE];
private:
// Following constructors and assignment operators not supported
TIASound(const TIASound&) = delete;
TIASound(TIASound&&) = delete;
TIASound& operator=(const TIASound&) = delete;
TIASound& operator=(TIASound&&) = delete;
};
#endif

View file

@ -1,219 +0,0 @@
//============================================================================
//
// SSSS tt lll lll
// SS SS tt ll ll
// SS tttttt eeee ll ll aaaa
// SSSS tt ee ee ll ll aa
// SS tt eeeeee ll ll aaaaa -- "An Atari 2600 VCS Emulator"
// SS SS tt ee ll ll aa aa
// SSSS ttt eeeee llll llll aaaaa
//
// Copyright (c) 1995-2016 by Bradford W. Mott, Stephen Anthony
// and the Stella Team
//
// See the file "License.txt" for information on usage and redistribution of
// this file, and for a DISCLAIMER OF ALL WARRANTIES.
//
// $Id: TIATables.hxx 3239 2015-12-29 19:22:46Z stephena $
//============================================================================
#ifndef TIA_TABLES_HXX
#define TIA_TABLES_HXX
enum TIABit {
P0Bit = 0x01, // Bit for Player 0
M0Bit = 0x02, // Bit for Missle 0
P1Bit = 0x04, // Bit for Player 1
M1Bit = 0x08, // Bit for Missle 1
BLBit = 0x10, // Bit for Ball
PFBit = 0x20, // Bit for Playfield
ScoreBit = 0x40, // Bit for Playfield score mode
PriorityBit = 0x80 // Bit for Playfield priority
};
enum TIAColor {
BKColor = 0, // Color index for Background
PFColor = 1, // Color index for Playfield
P0Color = 2, // Color index for Player 0
P1Color = 3, // Color index for Player 1
M0Color = 4, // Color index for Missle 0
M1Color = 5, // Color index for Missle 1
BLColor = 6, // Color index for Ball
HBLANKColor = 7 // Color index for HMove blank area
};
enum CollisionBit
{
Cx_M0P1 = 1 << 0, // Missle0 - Player1 collision
Cx_M0P0 = 1 << 1, // Missle0 - Player0 collision
Cx_M1P0 = 1 << 2, // Missle1 - Player0 collision
Cx_M1P1 = 1 << 3, // Missle1 - Player1 collision
Cx_P0PF = 1 << 4, // Player0 - Playfield collision
Cx_P0BL = 1 << 5, // Player0 - Ball collision
Cx_P1PF = 1 << 6, // Player1 - Playfield collision
Cx_P1BL = 1 << 7, // Player1 - Ball collision
Cx_M0PF = 1 << 8, // Missle0 - Playfield collision
Cx_M0BL = 1 << 9, // Missle0 - Ball collision
Cx_M1PF = 1 << 10, // Missle1 - Playfield collision
Cx_M1BL = 1 << 11, // Missle1 - Ball collision
Cx_BLPF = 1 << 12, // Ball - Playfield collision
Cx_P0P1 = 1 << 13, // Player0 - Player1 collision
Cx_M0M1 = 1 << 14 // Missle0 - Missle1 collision
};
// TIA Write/Read register names
enum TIARegister {
VSYNC = 0x00, // Write: vertical sync set-clear (D1)
VBLANK = 0x01, // Write: vertical blank set-clear (D7-6,D1)
WSYNC = 0x02, // Write: wait for leading edge of hrz. blank (strobe)
RSYNC = 0x03, // Write: reset hrz. sync counter (strobe)
NUSIZ0 = 0x04, // Write: number-size player-missle 0 (D5-0)
NUSIZ1 = 0x05, // Write: number-size player-missle 1 (D5-0)
COLUP0 = 0x06, // Write: color-lum player 0 (D7-1)
COLUP1 = 0x07, // Write: color-lum player 1 (D7-1)
COLUPF = 0x08, // Write: color-lum playfield (D7-1)
COLUBK = 0x09, // Write: color-lum background (D7-1)
CTRLPF = 0x0a, // Write: cntrl playfield ballsize & coll. (D5-4,D2-0)
REFP0 = 0x0b, // Write: reflect player 0 (D3)
REFP1 = 0x0c, // Write: reflect player 1 (D3)
PF0 = 0x0d, // Write: playfield register byte 0 (D7-4)
PF1 = 0x0e, // Write: playfield register byte 1 (D7-0)
PF2 = 0x0f, // Write: playfield register byte 2 (D7-0)
RESP0 = 0x10, // Write: reset player 0 (strobe)
RESP1 = 0x11, // Write: reset player 1 (strobe)
RESM0 = 0x12, // Write: reset missle 0 (strobe)
RESM1 = 0x13, // Write: reset missle 1 (strobe)
RESBL = 0x14, // Write: reset ball (strobe)
AUDC0 = 0x15, // Write: audio control 0 (D3-0)
AUDC1 = 0x16, // Write: audio control 1 (D4-0)
AUDF0 = 0x17, // Write: audio frequency 0 (D4-0)
AUDF1 = 0x18, // Write: audio frequency 1 (D3-0)
AUDV0 = 0x19, // Write: audio volume 0 (D3-0)
AUDV1 = 0x1a, // Write: audio volume 1 (D3-0)
GRP0 = 0x1b, // Write: graphics player 0 (D7-0)
GRP1 = 0x1c, // Write: graphics player 1 (D7-0)
ENAM0 = 0x1d, // Write: graphics (enable) missle 0 (D1)
ENAM1 = 0x1e, // Write: graphics (enable) missle 1 (D1)
ENABL = 0x1f, // Write: graphics (enable) ball (D1)
HMP0 = 0x20, // Write: horizontal motion player 0 (D7-4)
HMP1 = 0x21, // Write: horizontal motion player 1 (D7-4)
HMM0 = 0x22, // Write: horizontal motion missle 0 (D7-4)
HMM1 = 0x23, // Write: horizontal motion missle 1 (D7-4)
HMBL = 0x24, // Write: horizontal motion ball (D7-4)
VDELP0 = 0x25, // Write: vertical delay player 0 (D0)
VDELP1 = 0x26, // Write: vertical delay player 1 (D0)
VDELBL = 0x27, // Write: vertical delay ball (D0)
RESMP0 = 0x28, // Write: reset missle 0 to player 0 (D1)
RESMP1 = 0x29, // Write: reset missle 1 to player 1 (D1)
HMOVE = 0x2a, // Write: apply horizontal motion (strobe)
HMCLR = 0x2b, // Write: clear horizontal motion registers (strobe)
CXCLR = 0x2c, // Write: clear collision latches (strobe)
CXM0P = 0x00, // Read collision: D7=(M0,P1); D6=(M0,P0)
CXM1P = 0x01, // Read collision: D7=(M1,P0); D6=(M1,P1)
CXP0FB = 0x02, // Read collision: D7=(P0,PF); D6=(P0,BL)
CXP1FB = 0x03, // Read collision: D7=(P1,PF); D6=(P1,BL)
CXM0FB = 0x04, // Read collision: D7=(M0,PF); D6=(M0,BL)
CXM1FB = 0x05, // Read collision: D7=(M1,PF); D6=(M1,BL)
CXBLPF = 0x06, // Read collision: D7=(BL,PF); D6=(unused)
CXPPMM = 0x07, // Read collision: D7=(P0,P1); D6=(M0,M1)
INPT0 = 0x08, // Read pot port: D7
INPT1 = 0x09, // Read pot port: D7
INPT2 = 0x0a, // Read pot port: D7
INPT3 = 0x0b, // Read pot port: D7
INPT4 = 0x0c, // Read P1 joystick trigger: D7
INPT5 = 0x0d // Read P2 joystick trigger: D7
};
/**
The TIA class uses some static tables that aren't dependent on the actual
TIA state. For code organization, it's better to place that functionality
here.
@author Stephen Anthony
@version $Id: TIATables.hxx 3239 2015-12-29 19:22:46Z stephena $
*/
class TIATables
{
public:
/**
Compute all static tables used by the TIA
*/
static void computeAllTables();
// Player mask table
// [suppress mode][nusiz][pixel]
static unsigned char PxMask[2][8][320];
// Missle mask table (entries are true or false)
// [number][size][pixel]
// There are actually only 4 possible size combinations on a real system
// The fifth size is used for simulating the starfield effect in
// Cosmic Ark and Stay Frosty
static unsigned char MxMask[8][5][320];
// Ball mask table (entries are true or false)
// [size][pixel]
static unsigned char BLMask[4][320];
// Playfield mask table for reflected and non-reflected playfields
// [reflect, pixel]
static unsigned int PFMask[2][160];
// A mask table which can be used when an object is disabled
static unsigned char DisabledMask[640];
// Used to set the collision register to the correct value
static unsigned short CollisionMask[64];
// Indicates the update delay associated with poking at a TIA address
static const short PokeDelay[64];
#if 0
// Used to convert value written in a motion register into
// its internal representation
static const int CompleteMotion[76][16];
#endif
// Indicates if HMOVE blanks should occur for the corresponding cycle
static const bool HMOVEBlankEnableCycles[76];
// Used to reflect a players graphics
static unsigned char GRPReflect[256];
// Indicates if player is being reset during delay, display or other times
// [nusiz][old pixel][new pixel]
static signed char PxPosResetWhen[8][160][160];
private:
// Compute the collision decode table
static void buildCollisionMaskTable();
// Compute the player mask table
static void buildPxMaskTable();
// Compute the missle mask table
static void buildMxMaskTable();
// Compute the ball mask table
static void buildBLMaskTable();
// Compute playfield mask table
static void buildPFMaskTable();
// Compute the player reflect table
static void buildGRPReflectTable();
// Compute the player position reset when table
static void buildPxPosResetWhenTable();
private:
// Following constructors and assignment operators not supported
TIATables() = delete;
TIATables(const TIATables&) = delete;
TIATables(TIATables&&) = delete;
TIATables& operator=(const TIATables&) = delete;
TIATables& operator=(TIATables&&) = delete;
};
#endif

View file

@ -292,6 +292,10 @@ bool opz_registers::write(uint16_t index, uint8_t data, uint32_t &channel, uint3
// note from tildearrow:
// - are you kidding? I have to write to this "load preset" register before keying on?
// another note from tildearrow:
// - see https://github.com/110-kenichi/ymfm/blob/main/src/ymfm_opz.cpp
// - is 0x08 the actual key on register just like OPM?
// - if so then what's bit 5?
if ((index & 0xf8) == 0x20 /*&& bitfield(index, 0, 3) == bitfield(m_regdata[0x08], 0, 3)*/)
{
channel = bitfield(index, 0, 3);

View file

@ -22,7 +22,7 @@
#include <string.h>
#include <math.h>
#define rWrite(a,v) if (!skipRegisterWrites) {tia.set(a,v); regPool[((a)-0x15)&0x0f]=v; if (dumpWrites) {addWrite(a,v);} }
#define rWrite(a,v) if (!skipRegisterWrites) {tia.write(a,v); regPool[((a)-0x15)&0x0f]=v; if (dumpWrites) {addWrite(a,v);} }
const char* regCheatSheetTIA[]={
"AUDC0", "15",
@ -39,7 +39,22 @@ const char** DivPlatformTIA::getRegisterSheet() {
}
void DivPlatformTIA::acquire(short* bufL, short* bufR, size_t start, size_t len) {
tia.process(bufL+start,len,oscBuf);
for (size_t h=start; h<start+len; h++) {
tia.tick();
if (mixingType==2) {
bufL[h]=tia.myCurrentSample[0];
bufR[h]=tia.myCurrentSample[1];
} else if (mixingType==1) {
bufL[h]=(tia.myCurrentSample[0]+tia.myCurrentSample[1])>>1;
} else {
bufL[h]=tia.myCurrentSample[0];
}
if (++chanOscCounter>=114) {
chanOscCounter=0;
oscBuf[0]->data[oscBuf[0]->needle++]=tia.myChannelOut[0];
oscBuf[1]->data[oscBuf[1]->needle++]=tia.myChannelOut[1];
}
}
}
unsigned char DivPlatformTIA::dealWithFreq(unsigned char shape, int base, int pitch) {
@ -300,7 +315,7 @@ int DivPlatformTIA::getRegisterPoolSize() {
}
void DivPlatformTIA::reset() {
tia.reset();
tia.reset(mixingType);
memset(regPool,0,16);
for (int i=0; i<2; i++) {
chan[i]=DivPlatformTIA::Channel();
@ -309,8 +324,12 @@ void DivPlatformTIA::reset() {
}
}
float DivPlatformTIA::getPostAmp() {
return 0.5f;
}
bool DivPlatformTIA::isStereo() {
return false;
return (mixingType==2);
}
bool DivPlatformTIA::keyOffAffectsArp(int ch) {
@ -333,25 +352,28 @@ void DivPlatformTIA::poke(std::vector<DivRegWrite>& wlist) {
void DivPlatformTIA::setFlags(unsigned int flags) {
if (flags&1) {
rate=31250;
rate=COLOR_PAL*4.0/5.0;
} else {
rate=31468;
rate=COLOR_NTSC;
}
chipClock=rate;
mixingType=(flags>>1)&3;
for (int i=0; i<2; i++) {
oscBuf[i]->rate=rate;
oscBuf[i]->rate=rate/114;
}
tia.reset(mixingType);
}
int DivPlatformTIA::init(DivEngine* p, int channels, int sugRate, unsigned int flags) {
parent=p;
dumpWrites=false;
skipRegisterWrites=false;
mixingType=0;
chanOscCounter=0;
for (int i=0; i<2; i++) {
isMuted[i]=false;
oscBuf[i]=new DivDispatchOscBuffer;
}
tia.channels(1,false);
setFlags(flags);
reset();
return 2;

View file

@ -22,7 +22,7 @@
#include "../dispatch.h"
#include "../macroInt.h"
#include <queue>
#include "sound/tia/TIASnd.h"
#include "sound/tia/Audio.h"
class DivPlatformTIA: public DivDispatch {
protected:
@ -42,7 +42,9 @@ class DivPlatformTIA: public DivDispatch {
Channel chan[2];
DivDispatchOscBuffer* oscBuf[2];
bool isMuted[2];
TIASound tia;
unsigned char mixingType;
unsigned char chanOscCounter;
TIA::Audio tia;
unsigned char regPool[16];
friend void putDispatchChan(void*,int,int);
@ -61,6 +63,7 @@ class DivPlatformTIA: public DivDispatch {
void tick(bool sysTick=true);
void muteChannel(int ch, bool mute);
void setFlags(unsigned int flags);
float getPostAmp();
bool isStereo();
bool keyOffAffectsArp(int ch);
void notifyInsDeletion(void* ins);

View file

@ -906,6 +906,7 @@ void DivPlatformTX81Z::reset() {
pmDepth=0x7f;
//rWrite(0x18,0x10);
immWrite(0x18,0x00); // LFO Freq Off
immWrite(0x19,amDepth);
immWrite(0x19,0x80|pmDepth);
//rWrite(0x1b,0x00);

View file

@ -214,7 +214,7 @@ void DivPlatformVRC6::tick(bool sysTick) {
if (chan[i].freq<0) chan[i].freq=0;
if (chan[i].keyOff) {
chWrite(i,2,0);
} else {
} else if (chan[i].active) {
chWrite(i,1,chan[i].freq&0xff);
chWrite(i,2,0x80|((chan[i].freq>>8)&0xf));
}

View file

@ -273,6 +273,10 @@ void DivPlatformYM2203::tick(bool sysTick) {
chan[i].state.fb=chan[i].std.fb.val;
rWrite(chanOffs[i]+ADDR_FB_ALG,(chan[i].state.alg&7)|(chan[i].state.fb<<3));
}
if (chan[i].std.ex4.had && chan[i].active) {
chan[i].opMask=chan[i].std.ex4.val&15;
chan[i].opMaskChanged=true;
}
for (int j=0; j<4; j++) {
unsigned short baseAddr=chanOffs[i]|opOffs[j];
DivInstrumentFM::Operator& op=chan[i].state.op[j];
@ -385,8 +389,9 @@ void DivPlatformYM2203::tick(bool sysTick) {
immWrite(chanOffs[i]+ADDR_FREQ,chan[i].freq&0xff);
chan[i].freqChanged=false;
}
if (chan[i].keyOn) {
immWrite(0x28,0xf0|konOffs[i]);
if (chan[i].keyOn || chan[i].opMaskChanged) {
immWrite(0x28,(chan[i].opMask<<4)|konOffs[i]);
chan[i].opMaskChanged=false;
chan[i].keyOn=false;
}
}
@ -409,6 +414,11 @@ int DivPlatformYM2203::dispatch(DivCommand c) {
if (chan[c.chan].insChanged) {
chan[c.chan].state=ins->fm;
chan[c.chan].opMask=
(chan[c.chan].state.op[0].enable?1:0)|
(chan[c.chan].state.op[2].enable?2:0)|
(chan[c.chan].state.op[1].enable?4:0)|
(chan[c.chan].state.op[3].enable?8:0);
}
for (int i=0; i<4; i++) {

View file

@ -43,9 +43,9 @@ class DivPlatformYM2203: public DivPlatformOPN {
DivInstrumentFM state;
unsigned char freqH, freqL;
int freq, baseFreq, pitch, pitch2, portaPauseFreq, note, ins;
unsigned char psgMode, autoEnvNum, autoEnvDen;
unsigned char psgMode, autoEnvNum, autoEnvDen, opMask;
signed char konCycles;
bool active, insChanged, freqChanged, keyOn, keyOff, portaPause, inPorta, furnacePCM, hardReset;
bool active, insChanged, freqChanged, keyOn, keyOff, portaPause, inPorta, furnacePCM, hardReset, opMaskChanged;
int vol, outVol;
int sample;
DivMacroInt std;
@ -66,6 +66,7 @@ class DivPlatformYM2203: public DivPlatformOPN {
psgMode(1),
autoEnvNum(0),
autoEnvDen(0),
opMask(15),
active(false),
insChanged(true),
freqChanged(false),
@ -75,6 +76,7 @@ class DivPlatformYM2203: public DivPlatformOPN {
inPorta(false),
furnacePCM(false),
hardReset(false),
opMaskChanged(false),
vol(0),
outVol(15),
sample(-1) {}

View file

@ -59,6 +59,7 @@ int DivPlatformYM2203Ext::dispatch(DivCommand c) {
rWrite(baseAddr+0x70,op.d2r&31);
rWrite(baseAddr+0x80,(op.rr&15)|(op.sl<<4));
rWrite(baseAddr+0x90,op.ssgEnv&15);
opChan[ch].mask=op.enable;
}
if (opChan[ch].insChanged) { // TODO how does this work?
rWrite(chanOffs[2]+0xb0,(ins->fm.alg&7)|(ins->fm.fb<<3));
@ -358,7 +359,7 @@ void DivPlatformYM2203Ext::tick(bool sysTick) {
bool writeSomething=false;
unsigned char writeMask=2;
for (int i=0; i<4; i++) {
writeMask|=opChan[i].active<<(4+i);
writeMask|=(unsigned char)(opChan[i].mask && opChan[i].active)<<(4+i);
if (opChan[i].keyOn || opChan[i].keyOff) {
writeSomething=true;
writeMask&=~(1<<(4+i));
@ -395,10 +396,12 @@ void DivPlatformYM2203Ext::tick(bool sysTick) {
immWrite(opChanOffsH[i],opChan[i].freq>>8);
immWrite(opChanOffsL[i],opChan[i].freq&0xff);
}
writeMask|=opChan[i].active<<(4+i);
writeMask|=(unsigned char)(opChan[i].mask && opChan[i].active)<<(4+i);
if (opChan[i].keyOn) {
writeNoteOn=true;
writeMask|=1<<(4+i);
if (opChan[i].mask) {
writeMask|=1<<(4+i);
}
opChan[i].keyOn=false;
}
}

View file

@ -27,12 +27,12 @@ class DivPlatformYM2203Ext: public DivPlatformYM2203 {
unsigned char freqH, freqL;
int freq, baseFreq, pitch, pitch2, portaPauseFreq, ins;
signed char konCycles;
bool active, insChanged, freqChanged, keyOn, keyOff, portaPause, inPorta;
bool active, insChanged, freqChanged, keyOn, keyOff, portaPause, inPorta, mask;
int vol;
unsigned char pan;
// UGLY
OpChannel(): freqH(0), freqL(0), freq(0), baseFreq(0), pitch(0), pitch2(0), portaPauseFreq(0), ins(-1), active(false), insChanged(true), freqChanged(false), keyOn(false), keyOff(false), portaPause(false),
inPorta(false), vol(0), pan(3) {}
inPorta(false), mask(true), vol(0), pan(3) {}
};
OpChannel opChan[4];
bool isOpMuted[4];

View file

@ -441,6 +441,10 @@ void DivPlatformYM2608::tick(bool sysTick) {
chan[i].state.ams=chan[i].std.ams.val;
rWrite(chanOffs[i]+ADDR_LRAF,(isMuted[i]?0:(chan[i].pan<<6))|(chan[i].state.fms&7)|((chan[i].state.ams&3)<<4));
}
if (chan[i].std.ex4.had && chan[i].active) {
chan[i].opMask=chan[i].std.ex4.val&15;
chan[i].opMaskChanged=true;
}
for (int j=0; j<4; j++) {
unsigned short baseAddr=chanOffs[i]|opOffs[j];
DivInstrumentFM::Operator& op=chan[i].state.op[j];
@ -641,8 +645,9 @@ void DivPlatformYM2608::tick(bool sysTick) {
immWrite(chanOffs[i]+ADDR_FREQ,chan[i].freq&0xff);
chan[i].freqChanged=false;
}
if (chan[i].keyOn) {
immWrite(0x28,0xf0|konOffs[i]);
if (chan[i].keyOn || chan[i].opMaskChanged) {
immWrite(0x28,(chan[i].opMask<<4)|konOffs[i]);
chan[i].opMaskChanged=false;
chan[i].keyOn=false;
}
}
@ -766,6 +771,11 @@ int DivPlatformYM2608::dispatch(DivCommand c) {
if (chan[c.chan].insChanged) {
chan[c.chan].state=ins->fm;
chan[c.chan].opMask=
(chan[c.chan].state.op[0].enable?1:0)|
(chan[c.chan].state.op[2].enable?2:0)|
(chan[c.chan].state.op[1].enable?4:0)|
(chan[c.chan].state.op[3].enable?8:0);
}
for (int i=0; i<4; i++) {

View file

@ -48,9 +48,9 @@ class DivPlatformYM2608: public DivPlatformOPN {
DivInstrumentFM state;
unsigned char freqH, freqL;
int freq, baseFreq, pitch, pitch2, portaPauseFreq, note, ins;
unsigned char psgMode, autoEnvNum, autoEnvDen;
unsigned char psgMode, autoEnvNum, autoEnvDen, opMask;
signed char konCycles;
bool active, insChanged, freqChanged, keyOn, keyOff, portaPause, inPorta, furnacePCM, hardReset;
bool active, insChanged, freqChanged, keyOn, keyOff, portaPause, inPorta, furnacePCM, hardReset, opMaskChanged;
int vol, outVol;
int sample;
unsigned char pan;
@ -73,6 +73,7 @@ class DivPlatformYM2608: public DivPlatformOPN {
psgMode(1),
autoEnvNum(0),
autoEnvDen(0),
opMask(15),
active(false),
insChanged(true),
freqChanged(false),
@ -82,6 +83,7 @@ class DivPlatformYM2608: public DivPlatformOPN {
inPorta(false),
furnacePCM(false),
hardReset(false),
opMaskChanged(false),
vol(0),
outVol(15),
sample(-1),

View file

@ -59,6 +59,7 @@ int DivPlatformYM2608Ext::dispatch(DivCommand c) {
rWrite(baseAddr+0x70,op.d2r&31);
rWrite(baseAddr+0x80,(op.rr&15)|(op.sl<<4));
rWrite(baseAddr+0x90,op.ssgEnv&15);
opChan[ch].mask=op.enable;
}
if (opChan[ch].insChanged) { // TODO how does this work?
rWrite(chanOffs[2]+0xb0,(ins->fm.alg&7)|(ins->fm.fb<<3));
@ -358,7 +359,7 @@ void DivPlatformYM2608Ext::tick(bool sysTick) {
bool writeSomething=false;
unsigned char writeMask=2;
for (int i=0; i<4; i++) {
writeMask|=opChan[i].active<<(4+i);
writeMask|=(unsigned char)(opChan[i].mask && opChan[i].active)<<(4+i);
if (opChan[i].keyOn || opChan[i].keyOff) {
writeSomething=true;
writeMask&=~(1<<(4+i));
@ -395,10 +396,12 @@ void DivPlatformYM2608Ext::tick(bool sysTick) {
immWrite(opChanOffsH[i],opChan[i].freq>>8);
immWrite(opChanOffsL[i],opChan[i].freq&0xff);
}
writeMask|=opChan[i].active<<(4+i);
writeMask|=(unsigned char)(opChan[i].mask && opChan[i].active)<<(4+i);
if (opChan[i].keyOn) {
writeNoteOn=true;
writeMask|=1<<(4+i);
if (opChan[i].mask) {
writeMask|=1<<(4+i);
}
opChan[i].keyOn=false;
}
}

View file

@ -27,12 +27,12 @@ class DivPlatformYM2608Ext: public DivPlatformYM2608 {
unsigned char freqH, freqL;
int freq, baseFreq, pitch, pitch2, portaPauseFreq, ins;
signed char konCycles;
bool active, insChanged, freqChanged, keyOn, keyOff, portaPause, inPorta;
bool active, insChanged, freqChanged, keyOn, keyOff, portaPause, inPorta, mask;
int vol;
unsigned char pan;
// UGLY
OpChannel(): freqH(0), freqL(0), freq(0), baseFreq(0), pitch(0), pitch2(0), portaPauseFreq(0), ins(-1), active(false), insChanged(true), freqChanged(false), keyOn(false), keyOff(false), portaPause(false),
inPorta(false), vol(0), pan(3) {}
inPorta(false), mask(true), vol(0), pan(3) {}
};
OpChannel opChan[4];
bool isOpMuted[4];

View file

@ -378,6 +378,10 @@ void DivPlatformYM2610::tick(bool sysTick) {
chan[i].state.ams=chan[i].std.ams.val;
rWrite(chanOffs[i]+ADDR_LRAF,(isMuted[i]?0:(chan[i].pan<<6))|(chan[i].state.fms&7)|((chan[i].state.ams&3)<<4));
}
if (chan[i].std.ex4.had && chan[i].active) {
chan[i].opMask=chan[i].std.ex4.val&15;
chan[i].opMaskChanged=true;
}
for (int j=0; j<4; j++) {
unsigned short baseAddr=chanOffs[i]|opOffs[j];
DivInstrumentFM::Operator& op=chan[i].state.op[j];
@ -581,8 +585,9 @@ void DivPlatformYM2610::tick(bool sysTick) {
immWrite(chanOffs[i]+ADDR_FREQ,chan[i].freq&0xff);
chan[i].freqChanged=false;
}
if (chan[i].keyOn) {
immWrite(0x28,0xf0|konOffs[i]);
if (chan[i].keyOn || chan[i].opMaskChanged) {
immWrite(0x28,(chan[i].opMask<<4)|konOffs[i]);
chan[i].opMaskChanged=false;
chan[i].keyOn=false;
}
}
@ -749,6 +754,11 @@ int DivPlatformYM2610::dispatch(DivCommand c) {
if (chan[c.chan].insChanged) {
chan[c.chan].state=ins->fm;
chan[c.chan].opMask=
(chan[c.chan].state.op[0].enable?1:0)|
(chan[c.chan].state.op[2].enable?2:0)|
(chan[c.chan].state.op[1].enable?4:0)|
(chan[c.chan].state.op[3].enable?8:0);
}
for (int i=0; i<4; i++) {

View file

@ -441,6 +441,10 @@ void DivPlatformYM2610B::tick(bool sysTick) {
chan[i].state.ams=chan[i].std.ams.val;
rWrite(chanOffs[i]+ADDR_LRAF,(isMuted[i]?0:(chan[i].pan<<6))|(chan[i].state.fms&7)|((chan[i].state.ams&3)<<4));
}
if (chan[i].std.ex4.had && chan[i].active) {
chan[i].opMask=chan[i].std.ex4.val&15;
chan[i].opMaskChanged=true;
}
for (int j=0; j<4; j++) {
unsigned short baseAddr=chanOffs[i]|opOffs[j];
DivInstrumentFM::Operator& op=chan[i].state.op[j];
@ -643,8 +647,9 @@ void DivPlatformYM2610B::tick(bool sysTick) {
immWrite(chanOffs[i]+ADDR_FREQ,chan[i].freq&0xff);
chan[i].freqChanged=false;
}
if (chan[i].keyOn) {
immWrite(0x28,0xf0|konOffs[i]);
if (chan[i].keyOn || chan[i].opMaskChanged) {
immWrite(0x28,(chan[i].opMask<<4)|konOffs[i]);
chan[i].opMaskChanged=false;
chan[i].keyOn=false;
}
}
@ -811,6 +816,11 @@ int DivPlatformYM2610B::dispatch(DivCommand c) {
if (chan[c.chan].insChanged) {
chan[c.chan].state=ins->fm;
chan[c.chan].opMask=
(chan[c.chan].state.op[0].enable?1:0)|
(chan[c.chan].state.op[2].enable?2:0)|
(chan[c.chan].state.op[1].enable?4:0)|
(chan[c.chan].state.op[3].enable?8:0);
}
for (int i=0; i<4; i++) {

View file

@ -55,6 +55,7 @@ int DivPlatformYM2610BExt::dispatch(DivCommand c) {
rWrite(baseAddr+0x70,op.d2r&31);
rWrite(baseAddr+0x80,(op.rr&15)|(op.sl<<4));
rWrite(baseAddr+0x90,op.ssgEnv&15);
opChan[ch].mask=op.enable;
}
if (opChan[ch].insChanged) { // TODO how does this work?
rWrite(chanOffs[extChanOffs]+0xb0,(ins->fm.alg&7)|(ins->fm.fb<<3));
@ -354,7 +355,7 @@ void DivPlatformYM2610BExt::tick(bool sysTick) {
bool writeSomething=false;
unsigned char writeMask=2;
for (int i=0; i<4; i++) {
writeMask|=opChan[i].active<<(4+i);
writeMask|=(unsigned char)(opChan[i].mask && opChan[i].active)<<(4+i);
if (opChan[i].keyOn || opChan[i].keyOff) {
writeSomething=true;
writeMask&=~(1<<(4+i));
@ -391,10 +392,12 @@ void DivPlatformYM2610BExt::tick(bool sysTick) {
immWrite(opChanOffsH[i],opChan[i].freq>>8);
immWrite(opChanOffsL[i],opChan[i].freq&0xff);
}
writeMask|=opChan[i].active<<(4+i);
writeMask|=(unsigned char)(opChan[i].mask && opChan[i].active)<<(4+i);
if (opChan[i].keyOn) {
writeNoteOn=true;
writeMask|=1<<(4+i);
if (opChan[i].mask) {
writeMask|=1<<(4+i);
}
opChan[i].keyOn=false;
}
}

View file

@ -55,6 +55,7 @@ int DivPlatformYM2610Ext::dispatch(DivCommand c) {
rWrite(baseAddr+0x70,op.d2r&31);
rWrite(baseAddr+0x80,(op.rr&15)|(op.sl<<4));
rWrite(baseAddr+0x90,op.ssgEnv&15);
opChan[ch].mask=op.enable;
}
if (opChan[ch].insChanged) { // TODO how does this work?
rWrite(chanOffs[extChanOffs]+0xb0,(ins->fm.alg&7)|(ins->fm.fb<<3));
@ -354,7 +355,7 @@ void DivPlatformYM2610Ext::tick(bool sysTick) {
bool writeSomething=false;
unsigned char writeMask=2;
for (int i=0; i<4; i++) {
writeMask|=opChan[i].active<<(4+i);
writeMask|=(unsigned char)(opChan[i].mask && opChan[i].active)<<(4+i);
if (opChan[i].keyOn || opChan[i].keyOff) {
writeSomething=true;
writeMask&=~(1<<(4+i));
@ -391,10 +392,12 @@ void DivPlatformYM2610Ext::tick(bool sysTick) {
immWrite(opChanOffsH[i],opChan[i].freq>>8);
immWrite(opChanOffsL[i],opChan[i].freq&0xff);
}
writeMask|=opChan[i].active<<(4+i);
writeMask|=(unsigned char)(opChan[i].mask && opChan[i].active)<<(4+i);
if (opChan[i].keyOn) {
writeNoteOn=true;
writeMask|=1<<(4+i);
if (opChan[i].mask) {
writeMask|=1<<(4+i);
}
opChan[i].keyOn=false;
}
}

View file

@ -52,10 +52,10 @@ template<int ChanNum> class DivPlatformYM2610Base: public DivPlatformOPN {
int freq, baseFreq, pitch, pitch2, portaPauseFreq, note, ins;
unsigned char psgMode, autoEnvNum, autoEnvDen;
signed char konCycles;
bool active, insChanged, freqChanged, keyOn, keyOff, portaPause, inPorta, furnacePCM, hardReset;
bool active, insChanged, freqChanged, keyOn, keyOff, portaPause, inPorta, furnacePCM, hardReset, opMaskChanged;
int vol, outVol;
int sample;
unsigned char pan;
unsigned char pan, opMask;
int macroVolMul;
DivMacroInt std;
void macroInit(DivInstrument* which) {
@ -84,10 +84,12 @@ template<int ChanNum> class DivPlatformYM2610Base: public DivPlatformOPN {
inPorta(false),
furnacePCM(false),
hardReset(false),
opMaskChanged(false),
vol(0),
outVol(15),
sample(-1),
pan(3),
opMask(15),
macroVolMul(255) {}
};
@ -96,7 +98,7 @@ template<int ChanNum> class DivPlatformYM2610Base: public DivPlatformOPN {
unsigned char freqH, freqL;
int freq, baseFreq, pitch, pitch2, portaPauseFreq, ins;
signed char konCycles;
bool active, insChanged, freqChanged, keyOn, keyOff, portaPause, inPorta;
bool active, insChanged, freqChanged, keyOn, keyOff, portaPause, inPorta, mask;
int vol;
unsigned char pan;
// UGLY
@ -116,6 +118,7 @@ template<int ChanNum> class DivPlatformYM2610Base: public DivPlatformOPN {
keyOff(false),
portaPause(false),
inPorta(false),
mask(true),
vol(0),
pan(3) {}
};

View file

@ -31,7 +31,9 @@ void DivEngine::nextOrder() {
curRow=0;
if (repeatPattern) return;
if (++curOrder>=curSubSong->ordersLen) {
logV("end of orders reached");
endOfSong=true;
memset(walked,0,8192);
curOrder=0;
}
}
@ -351,15 +353,31 @@ void DivEngine::processRow(int i, bool afterDelay) {
if (effectVal>0) speed2=effectVal;
break;
case 0x0b: // change order
if (changeOrd==-1) {
if (changeOrd==-1 || song.jumpTreatment==0) {
changeOrd=effectVal;
changePos=0;
if (song.jumpTreatment==1 || song.jumpTreatment==2) {
changePos=0;
}
}
break;
case 0x0d: // next order
if (changeOrd<0 && (curOrder<(curSubSong->ordersLen-1) || !song.ignoreJumpAtEnd)) {
changeOrd=-2;
changePos=effectVal;
if (song.jumpTreatment==2) {
if ((curOrder<(curSubSong->ordersLen-1) || !song.ignoreJumpAtEnd)) {
changeOrd=-2;
changePos=effectVal;
}
} else if (song.jumpTreatment==1) {
if (changeOrd<0 && (curOrder<(curSubSong->ordersLen-1) || !song.ignoreJumpAtEnd)) {
changeOrd=-2;
changePos=effectVal;
}
} else {
if (curOrder<(curSubSong->ordersLen-1) || !song.ignoreJumpAtEnd) {
if (changeOrd<0) {
changeOrd=-2;
}
changePos=effectVal;
}
}
break;
case 0xed: // delay
@ -915,18 +933,23 @@ void DivEngine::nextRow() {
processRow(i,false);
}
walked[((curOrder<<5)+(curRow>>3))&8191]|=1<<(curRow&7);
if (changeOrd!=-1) {
if (repeatPattern) {
curRow=0;
changeOrd=-1;
} else {
curRow=changePos;
changePos=0;
if (changeOrd==-2) changeOrd=curOrder+1;
if (changeOrd<=curOrder) endOfSong=true;
// old loop detection routine
//if (changeOrd<=curOrder) endOfSong=true;
curOrder=changeOrd;
if (curOrder>=curSubSong->ordersLen) {
curOrder=0;
endOfSong=true;
memset(walked,0,8192);
}
changeOrd=-1;
}
@ -936,6 +959,13 @@ void DivEngine::nextRow() {
if (haltOn==DIV_HALT_PATTERN) halted=true;
}
// new loop detection routine
if (!endOfSong && walked[((curOrder<<5)+(curRow>>3))&8191]&(1<<(curRow&7))) {
logV("loop reached");
endOfSong=true;
memset(walked,0,8192);
}
if (song.brokenSpeedSel) {
if ((curSubSong->patLen&1) && curOrder&1) {
ticks=((curRow&1)?speed2:speed1)*(curSubSong->timeBase+1);

View file

@ -468,6 +468,11 @@ struct DivSong {
// 1: broken (don't allow value higher than speed)
// 2: lax (allow value higher than speed)
unsigned char delayBehavior;
// 0B/0D treatment
// 0: normal (0B/0D accepted)
// 1: old Furnace (first one accepted)
// 2: DefleMask (0D takes priority over 0B)
unsigned char jumpTreatment;
bool properNoiseLayout;
bool waveDutyIsVol;
bool resetMacroOnPorta;
@ -571,6 +576,7 @@ struct DivSong {
pitchSlideSpeed(4),
loopModality(2),
delayBehavior(2),
jumpTreatment(0),
properNoiseLayout(true),
waveDutyIsVol(false),
resetMacroOnPorta(false),