Implementing pattern effects, detune on FM preview, default instrument

This commit is contained in:
Kagamiin~ 2023-10-21 14:35:20 -03:00
parent 821b567b8c
commit a1b7e52467
7 changed files with 258 additions and 73 deletions

View file

@ -240,6 +240,11 @@ enum DivDispatchCmds {
DIV_ALWAYS_SET_VOLUME, // () -> alwaysSetVol
DIV_CMD_ESFM_OP_PANNING, // (op, value)
DIV_CMD_ESFM_OUTLVL, // (op, value)
DIV_CMD_ESFM_MODIN, // (op, value)
DIV_CMD_ESFM_ENV_DELAY, // (op, value)
DIV_CMD_MAX
};

View file

@ -85,7 +85,6 @@ enum DivInstrumentType: unsigned short {
DIV_INS_TED=52,
DIV_INS_C140=53,
DIV_INS_C219=54,
// TODO: Ask tilde to standardize this!!!
DIV_INS_ESFM=55,
DIV_INS_MAX,
DIV_INS_NULL

View file

@ -230,6 +230,7 @@ void DivPlatformESFM::tick(bool sysTick) {
}
}
// detune/fixed pitch
if (opE.fixed) {
if (m.ssg.had) {
opE.ct=(opE.ct&(~(7<<2)))|((m.ssg.val&7)<<2);
@ -351,44 +352,21 @@ void DivPlatformESFM::tick(bool sysTick) {
}
int DivPlatformESFM::octave(int freq) {
if (freq>=0x3ff<<6) {
return 1<<7;
} else if (freq>=0x3ff<<5) {
return 1<<6;
} else if (freq>=0x3ff<<4) {
return 1<<5;
} else if (freq>=0x3ff<<3) {
return 1<<4;
} else if (freq>=0x3ff<<2) {
return 1<<3;
} else if (freq>=0x3ff<<1) {
return 1<<2;
} else if (freq>=0x3ff) {
return 1<<1;
} else {
return 1<<0;
int result=1;
while (freq>0x3ff) {
freq>>=1;
result<<=1;
}
return 1<<0;
return result;
}
int DivPlatformESFM::toFreq(int freq) {
if (freq>=0x3ff<<6) {
return 0x1c00|((freq>>7)&0x3ff);
} else if (freq>=0x3ff<<5) {
return 0x1800|((freq>>6)&0x3ff);
} else if (freq>=0x3ff<<4) {
return 0x1400|((freq>>5)&0x3ff);
} else if (freq>=0x3ff<<3) {
return 0x1000|((freq>>4)&0x3ff);
} else if (freq>=0x3ff<<2) {
return 0xc00|((freq>>3)&0x3ff);
} else if (freq>=0x3ff<<1) {
return 0x800|((freq>>2)&0x3ff);
} else if (freq>=0x3ff<<0) {
return 0x400|((freq>>1)&0x3ff);
} else {
return freq&0x3ff;
int block=0;
while (freq>0x3ff) {
freq>>=1;
block++;
}
return ((block&7)<<10)|(freq&0x3ff);
}
void DivPlatformESFM::muteChannel(int ch, bool mute) {
@ -510,7 +488,6 @@ int DivPlatformESFM::dispatch(DivCommand c) {
unsigned short baseAddr=c.chan*32 + o*8;
DivInstrumentFM::Operator& op=chan[c.chan].state.fm.op[o];
DivInstrumentESFM::Operator& opE=chan[c.chan].state.esfm.op[o];
rWrite(baseAddr+OFFSET_DAM_DVB_LEFT_RIGHT_MODIN,((opE.modIn&7)<<1)|(((opE.left&chan[c.chan].globalPan)&1)<<4)|(((opE.right&(chan[c.chan].globalPan>>1))&1)<<5)|((op.dvb&1)<<6)|(op.dam<<7));
}
break;
@ -585,7 +562,7 @@ int DivPlatformESFM::dispatch(DivCommand c) {
break;
}
case DIV_CMD_FM_AR: {
if (c.value<0) {
if (c.value<0) {
for (int o=0; o<4; o++) {
unsigned short baseAddr=c.chan*32 + o*8;
DivInstrumentFM::Operator& op=chan[c.chan].state.fm.op[o];
@ -603,7 +580,7 @@ int DivPlatformESFM::dispatch(DivCommand c) {
break;
}
case DIV_CMD_FM_DR: {
if (c.value<0) {
if (c.value<0) {
for (int o=0; o<4; o++) {
unsigned short baseAddr=c.chan*32 + o*8;
DivInstrumentFM::Operator& op=chan[c.chan].state.fm.op[o];
@ -621,7 +598,7 @@ int DivPlatformESFM::dispatch(DivCommand c) {
break;
}
case DIV_CMD_FM_SL: {
if (c.value<0) {
if (c.value<0) {
for (int o=0; o<4; o++) {
unsigned short baseAddr=c.chan*32 + o*8;
DivInstrumentFM::Operator& op=chan[c.chan].state.fm.op[o];
@ -639,7 +616,7 @@ int DivPlatformESFM::dispatch(DivCommand c) {
break;
}
case DIV_CMD_FM_RR: {
if (c.value<0) {
if (c.value<0) {
for (int o=0; o<4; o++) {
unsigned short baseAddr=c.chan*32 + o*8;
DivInstrumentFM::Operator& op=chan[c.chan].state.fm.op[o];
@ -657,7 +634,7 @@ int DivPlatformESFM::dispatch(DivCommand c) {
break;
}
case DIV_CMD_FM_AM: {
if (c.value<0) {
if (c.value<0) {
for (int o=0; o<4; o++) {
unsigned short baseAddr=c.chan*32 + o*8;
DivInstrumentFM::Operator& op=chan[c.chan].state.fm.op[o];
@ -675,7 +652,7 @@ int DivPlatformESFM::dispatch(DivCommand c) {
break;
}
case DIV_CMD_FM_VIB: {
if (c.value<0) {
if (c.value<0) {
for (int o=0; o<4; o++) {
unsigned short baseAddr=c.chan*32 + o*8;
DivInstrumentFM::Operator& op=chan[c.chan].state.fm.op[o];
@ -693,7 +670,7 @@ int DivPlatformESFM::dispatch(DivCommand c) {
break;
}
case DIV_CMD_FM_SUS: {
if (c.value<0) {
if (c.value<0) {
for (int o=0; o<4; o++) {
unsigned short baseAddr=c.chan*32 + o*8;
DivInstrumentFM::Operator& op=chan[c.chan].state.fm.op[o];
@ -711,7 +688,7 @@ int DivPlatformESFM::dispatch(DivCommand c) {
break;
}
case DIV_CMD_FM_KSR: {
if (c.value<0) {
if (c.value<0) {
for (int o=0; o<4; o++) {
unsigned short baseAddr=c.chan*32 + o*8;
DivInstrumentFM::Operator& op=chan[c.chan].state.fm.op[o];
@ -729,7 +706,7 @@ int DivPlatformESFM::dispatch(DivCommand c) {
break;
}
case DIV_CMD_FM_WS: {
if (c.value<0) {
if (c.value<0) {
for (int o=0; o<4; o++) {
unsigned short baseAddr=c.chan*32 + o*8;
DivInstrumentFM::Operator& op=chan[c.chan].state.fm.op[o];
@ -760,7 +737,7 @@ int DivPlatformESFM::dispatch(DivCommand c) {
}
// KSL
case DIV_CMD_FM_RS: {
if (c.value<0) {
if (c.value<0) {
for (int o=0; o<4; o++) {
unsigned short baseAddr=c.chan*32 + o*8;
DivInstrumentFM::Operator& op=chan[c.chan].state.fm.op[o];
@ -785,6 +762,134 @@ int DivPlatformESFM::dispatch(DivCommand c) {
}
break;
}
case DIV_CMD_FM_AM_DEPTH: {
unsigned int o = c.value;
if (o >= 4) break;
unsigned short baseAddr=c.chan*32 + o*8;
DivInstrumentFM::Operator& op=chan[c.chan].state.fm.op[o];
DivInstrumentESFM::Operator& opE=chan[c.chan].state.esfm.op[o];
op.dam=c.value2&1;
rWrite(baseAddr+OFFSET_DAM_DVB_LEFT_RIGHT_MODIN,((opE.modIn&7)<<1)|(((opE.left&chan[c.chan].globalPan)&1)<<4)|(((opE.right&(chan[c.chan].globalPan>>1))&1)<<5)|((op.dvb&1)<<6)|(op.dam<<7));
break;
}
case DIV_CMD_FM_PM_DEPTH: {
unsigned int o = c.value;
if (o >= 4) break;
unsigned short baseAddr=c.chan*32 + o*8;
DivInstrumentFM::Operator& op=chan[c.chan].state.fm.op[o];
DivInstrumentESFM::Operator& opE=chan[c.chan].state.esfm.op[o];
op.dvb=c.value2&1;
rWrite(baseAddr+OFFSET_DAM_DVB_LEFT_RIGHT_MODIN,((opE.modIn&7)<<1)|(((opE.left&chan[c.chan].globalPan)&1)<<4)|(((opE.right&(chan[c.chan].globalPan>>1))&1)<<5)|((op.dvb&1)<<6)|(op.dam<<7));
break;
}
case DIV_CMD_FM_FIXFREQ: {
unsigned int o=c.value&3;
bool isFNum=c.value&4;
DivInstrumentESFM::Operator& opE=chan[c.chan].state.esfm.op[o];
if (!opE.fixed) break;
if (isFNum) {
opE.dt=(c.value2)&0xff;
opE.ct=(opE.ct&(~3))|((c.value2>>8)&3);
chan[c.chan].freqChanged=true;
} else {
opE.ct=(opE.ct&(~(7<<2)))|((c.value2&7)<<2);
chan[c.chan].freqChanged=true;
}
break;
}
case DIV_CMD_ESFM_OP_PANNING: {
unsigned int o=c.value;
if (o >= 4) break;
unsigned short baseAddr=c.chan*32 + o*8;
DivInstrumentFM::Operator& op=chan[c.chan].state.fm.op[o];
DivInstrumentESFM::Operator& opE=chan[c.chan].state.esfm.op[o];
opE.left=c.value2&1;
opE.right=(c.value2&2)>>1;
rWrite(baseAddr+OFFSET_DAM_DVB_LEFT_RIGHT_MODIN,((opE.modIn&7)<<1)|(((opE.left&chan[c.chan].globalPan)&1)<<4)|(((opE.right&(chan[c.chan].globalPan>>1))&1)<<5)|((op.dvb&1)<<6)|(op.dam<<7));
break;
}
case DIV_CMD_ESFM_OUTLVL: {
if (c.value<0) {
for (int o=0; o<4; o++) {
unsigned short baseAddr=c.chan*32 + o*8;
DivInstrumentFM::Operator& op=chan[c.chan].state.fm.op[o];
DivInstrumentESFM::Operator& opE=chan[c.chan].state.esfm.op[o];
unsigned char noise=chan[c.chan].state.esfm.noise&3;
opE.outLvl=c.value2&7;
if (isMuted[c.chan]) {
rWrite(baseAddr+OFFSET_OUTLVL_NOISE_WS,(op.ws&7)|((o==3?noise:0)<<3)|0);
} else {
rWrite(baseAddr+OFFSET_OUTLVL_NOISE_WS,(op.ws&7)|((o==3?noise:0)<<3)|((opE.outLvl&7)<<5));
}
}
} else {
unsigned int o = c.value;
if (o >= 4) break;
unsigned short baseAddr=c.chan*32 + o*8;
DivInstrumentFM::Operator& op=chan[c.chan].state.fm.op[o];
DivInstrumentESFM::Operator& opE=chan[c.chan].state.esfm.op[o];
unsigned char noise=chan[c.chan].state.esfm.noise&3;
opE.outLvl=c.value2&7;
if (isMuted[c.chan]) {
rWrite(baseAddr+OFFSET_OUTLVL_NOISE_WS,(op.ws&7)|((o==3?noise:0)<<3)|0);
} else {
rWrite(baseAddr+OFFSET_OUTLVL_NOISE_WS,(op.ws&7)|((o==3?noise:0)<<3)|((opE.outLvl&7)<<5));
}
}
break;
}
case DIV_CMD_ESFM_MODIN: {
if (c.value<0) {
for (int o=0; o<4; o++) {
unsigned short baseAddr=c.chan*32 + o*8;
DivInstrumentFM::Operator& op=chan[c.chan].state.fm.op[o];
DivInstrumentESFM::Operator& opE=chan[c.chan].state.esfm.op[o];
opE.modIn=c.value2&7;
rWrite(baseAddr+OFFSET_DAM_DVB_LEFT_RIGHT_MODIN,((opE.modIn&7)<<1)|(((opE.left&chan[c.chan].globalPan)&1)<<4)|(((opE.right&(chan[c.chan].globalPan>>1))&1)<<5)|((op.dvb&1)<<6)|(op.dam<<7));
}
} else {
unsigned int o = c.value;
if (o >= 4) break;
unsigned short baseAddr=c.chan*32 + o*8;
DivInstrumentFM::Operator& op=chan[c.chan].state.fm.op[o];
DivInstrumentESFM::Operator& opE=chan[c.chan].state.esfm.op[o];
opE.modIn=c.value2&7;
rWrite(baseAddr+OFFSET_DAM_DVB_LEFT_RIGHT_MODIN,((opE.modIn&7)<<1)|(((opE.left&chan[c.chan].globalPan)&1)<<4)|(((opE.right&(chan[c.chan].globalPan>>1))&1)<<5)|((op.dvb&1)<<6)|(op.dam<<7));
}
break;
}
case DIV_CMD_ESFM_ENV_DELAY: {
if (c.value<0) {
for (int o=0; o<4; o++) {
unsigned short baseAddr=c.chan*32 + o*8;
DivInstrumentESFM::Operator& opE=chan[c.chan].state.esfm.op[o];
opE.delay=c.value2&7;
rWrite(baseAddr+OFFSET_FREQH_BLOCK_DELAY,chan[c.chan].freqH[o]|(opE.delay<<5));
}
} else {
unsigned int o = c.value;
if (o >= 4) break;
unsigned short baseAddr=c.chan*32 + o*8;
DivInstrumentESFM::Operator& opE=chan[c.chan].state.esfm.op[o];
opE.delay=c.value2&7;
rWrite(baseAddr+OFFSET_FREQH_BLOCK_DELAY,chan[c.chan].freqH[o]|(opE.delay<<5));
}
break;
}
case DIV_CMD_STD_NOISE_MODE: {
unsigned int o=3;
unsigned short baseAddr=c.chan*32 + o*8;
DivInstrumentFM::Operator& op=chan[c.chan].state.fm.op[o];
DivInstrumentESFM::Operator& opE=chan[c.chan].state.esfm.op[o];
DivInstrumentESFM insE=chan[c.chan].state.esfm;
insE.noise=c.value&3;
if (isMuted[c.chan]) {
rWrite(baseAddr+OFFSET_OUTLVL_NOISE_WS,(op.ws&7)|(insE.noise<<3)|0);
} else {
rWrite(baseAddr+OFFSET_OUTLVL_NOISE_WS,(op.ws&7)|(insE.noise<<3)|((opE.outLvl&7)<<5));
}
break;
}
case DIV_CMD_FM_HARD_RESET:
chan[c.chan].hardReset=c.value;
break;
@ -843,10 +948,10 @@ DivDispatchOscBuffer* DivPlatformESFM::getOscBuffer(int ch) {
}
unsigned char* DivPlatformESFM::getRegisterPool() {
// TODO: DEBUG, remove this, it impacts performance
for (int i=0; i<ESFM_REG_POOL_SIZE; i++) {
regPool[i] = ESFM_readback_reg(&chip, i);
}
// // Uncomment this for debugging weird behavior
// for (int i=0; i<ESFM_REG_POOL_SIZE; i++) {
// regPool[i]=ESFM_readback_reg(&chip, i);
// }
return regPool;
}

View file

@ -239,7 +239,12 @@ const char* cmdName[]={
"EXTERNAL",
"ALWAYS_SET_VOLUME"
"ALWAYS_SET_VOLUME",
"DIV_CMD_ESFM_OP_PANNING",
"DIV_CMD_ESFM_OUTLVL",
"DIV_CMD_ESFM_MODIN",
"DIV_CMD_ESFM_ENV_DELAY"
};
static_assert((sizeof(cmdName)/sizeof(void*))==DIV_CMD_MAX,"update cmdName!");

View file

@ -132,8 +132,7 @@ enum DivSystem {
DIV_SYSTEM_TED,
DIV_SYSTEM_C140,
DIV_SYSTEM_C219,
// TODO: Ask Tilde to standardize!
DIV_SYSTEM_ESFM,
DIV_SYSTEM_ESFM
};
enum DivEffectType: unsigned short {
@ -606,38 +605,42 @@ struct DivSong {
nullInsQSound.std.panLMacro.mode=true;
// ESFM default instrument - port of OPM default instrument?
// TODO: adjust these values so they actually match
// ESFM default instrument - port of OPN default instrument
nullInsESFM.esfm.noise=0;
nullInsESFM.esfm.op[0].outLvl=0;
nullInsESFM.esfm.op[0].modIn=4;
nullInsESFM.fm.op[0].tl=21;
nullInsESFM.esfm.op[0].dt=2;
nullInsESFM.fm.op[0].tl=42;
nullInsESFM.fm.op[0].ar=15;
nullInsESFM.fm.op[0].dr=9;
nullInsESFM.fm.op[0].sl=7;
nullInsESFM.fm.op[0].dr=3;
nullInsESFM.fm.op[0].sl=15;
nullInsESFM.fm.op[0].rr=3;
nullInsESFM.fm.op[0].mult=7;
nullInsESFM.fm.op[0].mult=5;
nullInsESFM.esfm.op[1].outLvl=0;
nullInsESFM.esfm.op[1].modIn=7;
nullInsESFM.fm.op[1].tl=24;
nullInsESFM.esfm.op[1].dt=-3;
nullInsESFM.fm.op[1].tl=18;
nullInsESFM.fm.op[1].ar=15;
nullInsESFM.fm.op[1].dr=2;
nullInsESFM.fm.op[1].sl=11;
nullInsESFM.fm.op[1].rr=2;
nullInsESFM.fm.op[1].dr=3;
nullInsESFM.fm.op[1].sl=15;
nullInsESFM.fm.op[1].rr=4;
nullInsESFM.fm.op[1].mult=1;
nullInsESFM.esfm.op[2].outLvl=0;
nullInsESFM.esfm.op[2].modIn=7;
nullInsESFM.fm.op[2].tl=17;
nullInsESFM.esfm.op[2].dt=2;
nullInsESFM.fm.op[2].tl=48;
nullInsESFM.fm.op[2].ar=15;
nullInsESFM.fm.op[2].dr=4;
nullInsESFM.fm.op[2].sl=15;
nullInsESFM.fm.op[2].rr=3;
nullInsESFM.fm.op[2].mult=2;
nullInsESFM.fm.op[2].dr=2;
nullInsESFM.fm.op[2].sl=11;
nullInsESFM.fm.op[2].rr=1;
nullInsESFM.fm.op[2].mult=1;
nullInsESFM.fm.op[2].sus=1;
nullInsESFM.esfm.op[3].outLvl=7;
nullInsESFM.esfm.op[3].modIn=7;
nullInsESFM.esfm.op[3].dt=-3;
nullInsESFM.fm.op[3].tl=0;
nullInsESFM.fm.op[3].ar=15;
nullInsESFM.fm.op[3].dr=3;

View file

@ -392,6 +392,10 @@ int negEffectVal(unsigned char, unsigned char val) {
return -(int)val;
};
int effectValNibbleFlagPackReversed(unsigned char, unsigned char val) {
return (((val&0x0f)!=0)<<1)|((val&0xf0)!=0);
}
template<const int mask> int effectValAnd(unsigned char, unsigned char val) {
return val&mask;
};
@ -612,6 +616,52 @@ void DivEngine::registerSystems() {
{0x20, {DIV_CMD_SAMPLE_FREQ, "20xx: Set PCM frequency"}}
};
EffectHandlerMap fmESFMPostEffectHandlerMap={
{0x12, {DIV_CMD_FM_TL, "12xx: Set level of operator 1 (0 highest, 3F lowest)", constVal<0>, effectVal}},
{0x13, {DIV_CMD_FM_TL, "13xx: Set level of operator 2 (0 highest, 3F lowest)", constVal<1>, effectVal}},
{0x14, {DIV_CMD_FM_TL, "14xx: Set level of operator 3 (0 highest, 3F lowest)", constVal<2>, effectVal}},
{0x15, {DIV_CMD_FM_TL, "15xx: Set level of operator 4 (0 highest, 3F lowest)", constVal<3>, effectVal}},
{0x16, {DIV_CMD_FM_MULT, "16xy: Set operator multiplier (x: operator from 1 to 4; y: multiplier)", effectOpValNoZero<4>, effectValAnd<15>}},
{0x19, {DIV_CMD_FM_AR, "19xx: Set attack of all operators (0 to F)", constVal<-1>, effectValAnd<15>}},
{0x1a, {DIV_CMD_FM_AR, "1Axx: Set attack of operator 1 (0 to F)", constVal<0>, effectValAnd<15>}},
{0x1b, {DIV_CMD_FM_AR, "1Bxx: Set attack of operator 2 (0 to F)", constVal<1>, effectValAnd<15>}},
{0x1c, {DIV_CMD_FM_AR, "1Cxx: Set attack of operator 3 (0 to F)", constVal<2>, effectValAnd<15>}},
{0x1d, {DIV_CMD_FM_AR, "1Dxx: Set attack of operator 4 (0 to F)", constVal<3>, effectValAnd<15>}},
{0x1e, {DIV_CMD_FM_AM_DEPTH, "1Exy: Set AM depth (x: operator from 1 to 4 (0 for all ops); y: depth (0: 1dB, 1: 4.8dB))", effectOpVal<4>, effectValAnd<1>}},
{0x1f, {DIV_CMD_FM_PM_DEPTH, "1Fxy: Set vibrato depth (x: operator from 1 to 4 (0 for all ops); y: depth (0: normal, 1: double))", effectOpVal<4>, effectValAnd<1>}},
{0x20, {DIV_CMD_ESFM_OP_PANNING, "20xy: Set panning of operator 1 (x: left; y: right)", constVal<0>, effectValNibbleFlagPackReversed}},
{0x21, {DIV_CMD_ESFM_OP_PANNING, "21xy: Set panning of operator 2 (x: left; y: right)", constVal<1>, effectValNibbleFlagPackReversed}},
{0x22, {DIV_CMD_ESFM_OP_PANNING, "22xy: Set panning of operator 3 (x: left; y: right)", constVal<2>, effectValNibbleFlagPackReversed}},
{0x23, {DIV_CMD_ESFM_OP_PANNING, "23xy: Set panning of operator 4 (x: left; y: right)", constVal<3>, effectValNibbleFlagPackReversed}},
{0x24, {DIV_CMD_ESFM_OUTLVL, "24xy: Set output level register (x: operator from 1 to 4 (0 for all ops); y: level from 0 to 7)", effectOpVal<4>, effectValAnd<7>}},
{0x25, {DIV_CMD_ESFM_MODIN, "25xy: Set modulation input level (x: operator from 1 to 4 (0 for all ops); y: level from 0 to 7)", effectOpVal<4>, effectValAnd<7>}},
{0x26, {DIV_CMD_ESFM_ENV_DELAY, "26xy: Set envelope delay (x: operator from 1 to 4 (0 for all ops); y: delay from 0 to 7)", effectOpVal<4>, effectValAnd<7>}},
{0x27, {DIV_CMD_STD_NOISE_MODE, "27xx: Set noise mode for operator 4 (x: mode from 0 to 3)", effectValAnd<3>}},
{0x2a, {DIV_CMD_FM_WS, "2Axy: Set waveform (x: operator from 1 to 4 (0 for all ops); y: waveform from 0 to 7)", effectOpVal<4>, effectValAnd<7>}},
{0x2f, {DIV_CMD_FM_FIXFREQ, "2Fxy: Set fixed frequency block (x: operator from 1 to 4; y: octave from 0 to 7)", effectOpValNoZero<4>, effectValAnd<7>}},
{0x50, {DIV_CMD_FM_AM, "50xy: Set AM (x: operator from 1 to 4 (0 for all ops); y: AM)", effectOpVal<4>, effectValAnd<1>}},
{0x51, {DIV_CMD_FM_SL, "51xy: Set sustain level (x: operator from 1 to 4 (0 for all ops); y: sustain)", effectOpVal<4>, effectValAnd<15>}},
{0x52, {DIV_CMD_FM_RR, "52xy: Set release (x: operator from 1 to 4 (0 for all ops); y: release)", effectOpVal<4>, effectValAnd<15>}},
{0x53, {DIV_CMD_FM_VIB, "53xy: Set vibrato (x: operator from 1 to 4 (0 for all ops); y: enabled)", effectOpVal<4>, effectValAnd<1>}},
{0x54, {DIV_CMD_FM_RS, "54xy: Set envelope scale (x: operator from 1 to 4 (0 for all ops); y: scale from 0 to 3)", effectOpVal<4>, effectValAnd<3>}},
{0x55, {DIV_CMD_FM_SUS, "55xy: Set envelope sustain (x: operator from 1 to 4 (0 for all ops); y: enabled)", effectOpVal<4>, effectValAnd<1>}},
{0x56, {DIV_CMD_FM_DR, "56xx: Set decay of all operators (0 to F)", constVal<-1>, effectValAnd<15>}},
{0x57, {DIV_CMD_FM_DR, "57xx: Set decay of operator 1 (0 to F)", constVal<0>, effectValAnd<15>}},
{0x58, {DIV_CMD_FM_DR, "58xx: Set decay of operator 2 (0 to F)", constVal<1>, effectValAnd<15>}},
{0x59, {DIV_CMD_FM_DR, "59xx: Set decay of operator 3 (0 to F)", constVal<2>, effectValAnd<15>}},
{0x5a, {DIV_CMD_FM_DR, "5Axx: Set decay of operator 4 (0 to F)", constVal<3>, effectValAnd<15>}},
{0x5b, {DIV_CMD_FM_KSR, "5Bxy: Set whether key will scale envelope (x: operator from 1 to 4 (0 for all ops); y: enabled)", effectOpVal<4>, effectValAnd<1>}}
};
const EffectHandler fmESFMFixFreqFNumHandler[4]={
{DIV_CMD_FM_FIXFREQ, "3xyy: Set fixed frequency F-num of operator 1 (x: high 2 bits from 0 to 3; y: low 8 bits of F-num)", constVal<4>, effectValLong<10>},
{DIV_CMD_FM_FIXFREQ, "3xyy: Set fixed frequency F-num of operator 2 (x: high 2 bits from 4 to 7; y: low 8 bits of F-num)", constVal<5>, effectValLong<10>},
{DIV_CMD_FM_FIXFREQ, "3xyy: Set fixed frequency F-num of operator 3 (x: high 2 bits from 8 to B; y: low 8 bits of F-num)", constVal<6>, effectValLong<10>},
{DIV_CMD_FM_FIXFREQ, "3xyy: Set fixed frequency F-num of operator 4 (x: high 2 bits from C to F; y: low 8 bits of F-num)", constVal<7>, effectValLong<10>},
};
for (int i=0; i<16; i++) {
fmESFMPostEffectHandlerMap.emplace(0x30+i,fmESFMFixFreqFNumHandler[i/4]);
}
// SysDefs
// this chip uses YMZ ADPCM, but the emulator uses ADPCM-B because I got it wrong back then.
@ -1917,8 +1967,7 @@ void DivEngine::registerSystems() {
{0x12, {DIV_CMD_SNES_INVERT, "12xy: Set invert mode (x: surround; y: invert)"}},
}
);
// TODO: ask Tilde to standardize!
sysDefs[DIV_SYSTEM_ESFM]=new DivSysDef(
"ESS ES1xxx-series (ESFM)", NULL, 0xd0, 0, 18, true, false, 0, false, 0,
"A unique FM synth featured in PC sound cards.\nBased on the OPL3 design, but with lots of its features extended.",
@ -1927,7 +1976,10 @@ void DivEngine::registerSystems() {
{DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM},
{DIV_INS_ESFM, DIV_INS_ESFM, DIV_INS_ESFM, DIV_INS_ESFM, DIV_INS_ESFM, DIV_INS_ESFM, DIV_INS_ESFM, DIV_INS_ESFM, DIV_INS_ESFM, DIV_INS_ESFM, DIV_INS_ESFM, DIV_INS_ESFM, DIV_INS_ESFM, DIV_INS_ESFM, DIV_INS_ESFM, DIV_INS_ESFM, DIV_INS_ESFM, DIV_INS_ESFM},
{},
{}
{
{0x2e, {DIV_CMD_FM_HARD_RESET, "2Exx: Toggle hard envelope reset on new notes"}},
},
fmESFMPostEffectHandlerMap
);
sysDefs[DIV_SYSTEM_DUMMY]=new DivSysDef(

View file

@ -366,15 +366,31 @@ void FurnaceGUI::renderFMPreviewESFM(const DivInstrumentFM& params, const DivIns
const DivInstrumentFM::Operator& op=params.op[i];
const DivInstrumentESFM::Operator& opE=esfmParams.op[i];
unsigned short baseAddr=i*8;
unsigned char freqL, freqH;
if (opE.fixed) {
freqL=opE.dt;
freqH=opE.ct&0x1f;
} else {
// perform detune calculation
int offset=(opE.ct<<7)+opE.dt;
double fbase=(mult0?2048.0:1024.0)*pow(2.0,(float)offset/(128.0*12.0));
int bf=round(fbase);
int block=0;
while (bf>0x3ff) {
bf>>=1;
block++;
}
freqL=bf&0xff;
freqH=((block&7)<<2)|((bf>>8)&3);
}
ESFM_WRITE(baseAddr+0,(op.am<<7)|((op.vib&1)<<6)|((op.sus&1)<<5)|((op.ksr&1)<<4)|(op.mult&0x0f));
ESFM_WRITE(baseAddr+1,(op.ksl<<6)|(op.tl&0x3f));
ESFM_WRITE(baseAddr+2,(op.ar<<4)|(op.dr&0x0f));
ESFM_WRITE(baseAddr+3,(op.sl<<4)|(op.rr&0x0f));
// TODO: implement ct/dt detune... how will we do that?
ESFM_WRITE(baseAddr+4,0);
ESFM_WRITE(baseAddr+5,(opE.delay<<5)|(mult0?0x0a:0x06));
ESFM_WRITE(baseAddr+4,freqL);
ESFM_WRITE(baseAddr+5,(opE.delay<<5)|freqH);
ESFM_WRITE(baseAddr+6,(op.dam<<7)|((op.dvb&1)<<6)|((opE.right&1)<<5)|((opE.left&1)<<4)|((opE.modIn&7)<<1));
ESFM_WRITE(baseAddr+7,(opE.outLvl<<5)|((i==3?esfmParams.noise:0)<<3)|(op.ws&7));