Merge pull request #1559 from Kagamiin/feature/esfm

Add ESFM system
This commit is contained in:
tildearrow 2024-01-16 16:51:33 -05:00 committed by GitHub
commit 0acd62f4d5
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
27 changed files with 5594 additions and 90 deletions

View file

@ -243,6 +243,11 @@ enum DivDispatchCmds {
DIV_CMD_C64_AD, // (value)
DIV_CMD_C64_SR, // (value)
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_ALWAYS_SET_VOLUME, // () -> alwaysSetVol
DIV_CMD_MAX

View file

@ -82,6 +82,7 @@
#include "platform/ted.h"
#include "platform/c140.h"
#include "platform/pcmdac.h"
#include "platform/esfm.h"
#include "platform/dummy.h"
#include "../ta-log.h"
#include "song.h"
@ -644,6 +645,9 @@ void DivDispatchContainer::init(DivSystem sys, DivEngine* eng, int chanCount, do
case DIV_SYSTEM_PCM_DAC:
dispatch=new DivPlatformPCMDAC;
break;
case DIV_SYSTEM_ESFM:
dispatch=new DivPlatformESFM;
break;
case DIV_SYSTEM_DUMMY:
dispatch=new DivPlatformDummy;
break;

View file

@ -1371,6 +1371,9 @@ DivInstrument* DivEngine::getIns(int index, DivInstrumentType fallbackType) {
case DIV_INS_OPL_DRUMS:
return &song.nullInsOPLDrums;
break;
case DIV_INS_ESFM:
return &song.nullInsESFM;
break;
default:
break;
}
@ -2409,6 +2412,9 @@ int DivEngine::addInstrument(int refChan, DivInstrumentType fallbackType) {
case DIV_INS_OPL_DRUMS:
*ins=song.nullInsOPLDrums;
break;
case DIV_INS_ESFM:
*ins=song.nullInsESFM;
break;
default:
break;
}

View file

@ -6445,7 +6445,7 @@ SafeWriter* DivEngine::saveText(bool separatePatterns) {
w->writeText(fmt::sprintf("- type: %d\n",(int)ins->type));
if (ins->type==DIV_INS_FM || ins->type==DIV_INS_OPL || ins->type==DIV_INS_OPLL || ins->type==DIV_INS_OPZ || ins->type==DIV_INS_OPL_DRUMS || ins->type==DIV_INS_OPM) {
if (ins->type==DIV_INS_FM || ins->type==DIV_INS_OPL || ins->type==DIV_INS_OPLL || ins->type==DIV_INS_OPZ || ins->type==DIV_INS_OPL_DRUMS || ins->type==DIV_INS_OPM || ins->type==DIV_INS_ESFM) {
w->writeText("- FM parameters:\n");
w->writeText(fmt::sprintf(" - ALG: %d\n",ins->fm.alg));
w->writeText(fmt::sprintf(" - FB: %d\n",ins->fm.fb));
@ -6489,6 +6489,25 @@ SafeWriter* DivEngine::saveText(bool separatePatterns) {
}
}
if (ins->type==DIV_INS_ESFM) {
w->writeText("- ESFM parameters:\n");
w->writeText(fmt::sprintf(" - noise mode: %d\n",ins->esfm.noise));
for (int j=0; j<ins->fm.ops; j++) {
DivInstrumentESFM::Operator& opE=ins->esfm.op[j];
w->writeText(fmt::sprintf(" - operator %d:\n",j));
w->writeText(fmt::sprintf(" - DL: %d\n",opE.delay));
w->writeText(fmt::sprintf(" - OL: %d\n",opE.outLvl));
w->writeText(fmt::sprintf(" - MI: %d\n",opE.modIn));
w->writeText(fmt::sprintf(" - output left: %s\n",trueFalse[opE.left?1:0]));
w->writeText(fmt::sprintf(" - output right: %s\n",trueFalse[opE.right?1:0]));
w->writeText(fmt::sprintf(" - CT: %d\n",opE.ct));
w->writeText(fmt::sprintf(" - DT: %d\n",opE.dt));
w->writeText(fmt::sprintf(" - fixed frequency: %s\n",trueFalse[opE.fixed?1:0]));
}
}
if (ins->type==DIV_INS_GB) {
w->writeText("- Game Boy parameters:\n");
w->writeText(fmt::sprintf(" - volume: %d\n",ins->gb.envVol));

View file

@ -230,6 +230,29 @@ bool DivInstrumentSNES::operator==(const DivInstrumentSNES& other) {
);
}
bool DivInstrumentESFM::operator==(const DivInstrumentESFM& other) {
return (
_C(noise) &&
_C(op[0]) &&
_C(op[1]) &&
_C(op[2]) &&
_C(op[3])
);
}
bool DivInstrumentESFM::Operator::operator==(const DivInstrumentESFM::Operator& other) {
return (
_C(delay) &&
_C(outLvl) &&
_C(modIn) &&
_C(left) &&
_C(right) &&
_C(fixed) &&
_C(ct) &&
_C(dt)
);
}
#undef _C
#define FEATURE_BEGIN(x) \
@ -743,6 +766,22 @@ void DivInstrument::writeFeatureNE(SafeWriter* w) {
FEATURE_END;
}
void DivInstrument::writeFeatureEF(SafeWriter* w) {
FEATURE_BEGIN("EF");
w->writeC(esfm.noise&3);
for (int i=0; i<4; i++) {
DivInstrumentESFM::Operator& op=esfm.op[i];
w->writeC(((op.delay&7)<<5)|((op.outLvl&7)<<2)|((op.right&1)<<1)|(op.left&1));
w->writeC((op.fixed&1)<<3|(op.modIn&7));
w->writeC(op.ct);
w->writeC(op.dt);
}
FEATURE_END;
}
void DivInstrument::putInsData2(SafeWriter* w, bool fui, const DivSong* song, bool insName) {
size_t blockStartSeek=0;
size_t blockEndSeek=0;
@ -786,6 +825,7 @@ void DivInstrument::putInsData2(SafeWriter* w, bool fui, const DivSong* song, bo
bool featureES=false;
bool featureX1=false;
bool featureNE=false;
bool featureEF=false;
bool checkForWL=false;
@ -999,7 +1039,10 @@ void DivInstrument::putInsData2(SafeWriter* w, bool fui, const DivSong* song, bo
featureSM=true;
featureSL=true;
break;
case DIV_INS_ESFM:
featureFM=true;
featureEF=true;
break;
case DIV_INS_MAX:
break;
case DIV_INS_NULL:
@ -1047,6 +1090,9 @@ void DivInstrument::putInsData2(SafeWriter* w, bool fui, const DivSong* song, bo
if (x1_010!=defaultIns.x1_010) {
featureX1=true;
}
if (esfm!=defaultIns.esfm) {
featureEF=true;
}
}
// check ins name
@ -1189,6 +1235,9 @@ void DivInstrument::putInsData2(SafeWriter* w, bool fui, const DivSong* song, bo
if (featureNE) {
writeFeatureNE(w);
}
if (featureEF) {
writeFeatureEF(w);
}
if (fui && (featureSL || featureWL)) {
w->write("EN",2);
@ -2598,6 +2647,32 @@ void DivInstrument::readFeatureNE(SafeReader& reader, short version) {
READ_FEAT_END;
}
void DivInstrument::readFeatureEF(SafeReader& reader, short version) {
READ_FEAT_BEGIN;
unsigned char next=reader.readC();
esfm.noise=next&3;
for (int i=0; i<4; i++) {
DivInstrumentESFM::Operator& op=esfm.op[i];
next=reader.readC();
op.delay=(next>>5)&7;
op.outLvl=(next>>2)&7;
op.right=(next>>1)&1;
op.left=next&1;
next=reader.readC();
op.modIn=next&7;
op.fixed=(next>>3)&1;
op.ct=reader.readC();
op.dt=reader.readC();
}
READ_FEAT_END;
}
DivDataErrors DivInstrument::readInsDataNew(SafeReader& reader, short version, bool fui, DivSong* song) {
unsigned char featCode[2];
bool volIsCutoff=false;
@ -2666,6 +2741,8 @@ DivDataErrors DivInstrument::readInsDataNew(SafeReader& reader, short version, b
readFeatureX1(reader,version);
} else if (memcmp(featCode,"NE",2)==0) { // NES (DPCM)
readFeatureNE(reader,version);
} else if (memcmp(featCode,"EF",2)==0) { // ESFM
readFeatureEF(reader,version);
} else {
if (song==NULL && (memcmp(featCode,"SL",2)==0 || (memcmp(featCode,"WL",2)==0))) {
// nothing

View file

@ -85,6 +85,7 @@ enum DivInstrumentType: unsigned short {
DIV_INS_TED=52,
DIV_INS_C140=53,
DIV_INS_C219=54,
DIV_INS_ESFM=55,
DIV_INS_MAX,
DIV_INS_NULL
};
@ -771,6 +772,56 @@ struct DivInstrumentSNES {
d2(0) {}
};
// ESFM operator structure:
// - DELAY, OUT, MOD, L, R, NOISE
// - Virtual: CT, DT, FIXED
// - In FM struct: AM, DAM, AR, DR, MULT, RR, SL, TL
// - In FM struct: KSL, VIB, DVB, WS, SUS, KSR
// - Not in struct: FNUML, FNUMH, BLOCK
struct DivInstrumentESFM {
bool operator==(const DivInstrumentESFM& other);
bool operator!=(const DivInstrumentESFM& other) {
return !(*this==other);
}
// Only works on OP4, so putting it outside the Operator struct instead
unsigned char noise;
struct Operator {
unsigned char delay, outLvl, modIn, left, right, fixed;
signed char ct, dt;
bool operator==(const Operator& other);
bool operator!=(const Operator& other) {
return !(*this==other);
}
Operator():
delay(0),
outLvl(0),
modIn(0),
left(1),
right(1),
fixed(0),
ct(0),
dt(0) {}
} op[4];
DivInstrumentESFM():
noise(0)
{
op[0].modIn=4;
op[0].outLvl=0;
op[1].modIn=7;
op[1].outLvl=0;
op[2].modIn=7;
op[2].outLvl=0;
op[3].modIn=7;
op[3].outLvl=7;
}
};
struct DivInstrument {
String name;
DivInstrumentType type;
@ -787,6 +838,7 @@ struct DivInstrument {
DivInstrumentSoundUnit su;
DivInstrumentES5506 es5506;
DivInstrumentSNES snes;
DivInstrumentESFM esfm;
/**
* these are internal functions.
@ -811,6 +863,7 @@ struct DivInstrument {
void writeFeatureES(SafeWriter* w);
void writeFeatureX1(SafeWriter* w);
void writeFeatureNE(SafeWriter* w);
void writeFeatureEF(SafeWriter* w);
void readFeatureNA(SafeReader& reader, short version);
void readFeatureFM(SafeReader& reader, short version);
@ -831,6 +884,7 @@ struct DivInstrument {
void readFeatureES(SafeReader& reader, short version);
void readFeatureX1(SafeReader& reader, short version);
void readFeatureNE(SafeReader& reader, short version);
void readFeatureEF(SafeReader& reader, short version);
DivDataErrors readInsDataOld(SafeReader& reader, short version);
DivDataErrors readInsDataNew(SafeReader& reader, short version, bool fui, DivSong* song);

1073
src/engine/platform/esfm.cpp Normal file

File diff suppressed because it is too large Load diff

210
src/engine/platform/esfm.h Normal file
View file

@ -0,0 +1,210 @@
/**
* Furnace Tracker - multi-system chiptune tracker
* Copyright (C) 2021-2023 tildearrow and contributors
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "../dispatch.h"
#include "../../fixedQueue.h"
#include "../../../extern/ESFMu/esfm.h"
// ESFM register address space technically spans 0x800 (2048) bytes,
// but we only need the first 0x254 (596) during normal use.
// Rounding it up to 0x400 bytes, the nearest power of 2.
#define ESFM_REG_POOL_SIZE 0x400
class DivPlatformESFM: public DivDispatch {
struct Channel: public SharedChannel<int> {
struct {
DivInstrumentFM fm;
DivInstrumentESFM esfm;
} state;
unsigned char freqL[4], freqH[4];
bool hardReset;
unsigned char globalPan;
int macroVolMul;
struct {
int baseNoteOverride;
bool fixedArp;
int arpOff;
int pitch2;
bool hasOpArp;
bool hasOpPitch;
} opsState[4];
void handleArpFmOp(int offset=0, int o=0) {
DivMacroInt::IntOp& m=this->std.op[o];
if (m.ssg.had) {
opsState[o].hasOpArp=true;
if (m.ssg.val<0) {
if (!(m.ssg.val&0x40000000)) {
opsState[o].baseNoteOverride=(m.ssg.val|0x40000000)+offset;
opsState[o].fixedArp=true;
} else {
opsState[o].arpOff=m.ssg.val;
opsState[o].fixedArp=false;
}
} else {
if (m.ssg.val&0x40000000) {
opsState[o].baseNoteOverride=(m.ssg.val&(~0x40000000))+offset;
opsState[o].fixedArp=true;
} else {
opsState[o].arpOff=m.ssg.val;
opsState[o].fixedArp=false;
}
}
freqChanged=true;
}
else
{
opsState[o].hasOpArp=false;
}
}
void handlePitchFmOp(int o)
{
DivMacroInt::IntOp& m=this->std.op[o];
if (m.dt.had) {
opsState[o].hasOpPitch=true;
if (m.dt.mode) {
opsState[o].pitch2+=m.dt.val;
CLAMP_VAR(opsState[o].pitch2,-131071,131071);
} else {
opsState[o].pitch2=m.dt.val;
}
this->freqChanged=true;
}
else
{
opsState[o].hasOpPitch=false;
}
}
Channel():
SharedChannel<int>(0),
freqL{0, 0, 0, 0},
freqH{0, 0, 0, 0},
hardReset(false),
globalPan(3),
macroVolMul(64) {
memset(opsState, 0, sizeof(opsState));
}
};
Channel chan[18];
DivDispatchOscBuffer* oscBuf[18];
bool isMuted[18];
struct QueuedWrite {
unsigned short addr;
unsigned char val;
bool addrOrVal;
QueuedWrite(): addr(0), val(0), addrOrVal(false) {}
QueuedWrite(unsigned short a, unsigned char v): addr(a), val(v), addrOrVal(false) {}
};
FixedQueue<QueuedWrite,2048> writes;
esfm_chip chip;
unsigned char regPool[ESFM_REG_POOL_SIZE];
short oldWrites[ESFM_REG_POOL_SIZE];
short pendingWrites[ESFM_REG_POOL_SIZE];
int octave(int freq);
int toFreq(int freq);
void commitState(int ch, DivInstrument* ins);
friend void putDispatchChip(void*,int);
friend void putDispatchChan(void*,int,int);
inline void rWrite(unsigned short a, short v) {
if (!skipRegisterWrites && a<ESFM_REG_POOL_SIZE) {
pendingWrites[a]=v;
}
}
inline void immWrite(unsigned short a, unsigned char v) {
if (!skipRegisterWrites) {
writes.push_back(QueuedWrite(a,v));
if (dumpWrites) {
addWrite(a,v);
}
}
}
#ifdef KVS
#undef KVS
#endif
/**
* ESFM doesn't have predef algorithms, so a custom KVS heuristic for auto mode is needed.
* This is a bit too complex for a macro.
* The heuristic for auto mode is expressed as:
* true for an operator o
* where op[o].outLvl = 7,
* or op[o].outLvl > 0 and o == 3 (last operator),
* or op[o].outLvl > 0 and (op[o].outLvl - op[o + 1].modIn) >= 2,
* or op[o].outLvl > 0 and op[o + 1].modIn == 0.
*/
inline bool KVS(int c, int o) {
if (c<0 || c>=18 || o<0 || o>=4) return false;
if (chan[c].state.fm.op[o].kvs==1) return true;
if (chan[c].state.fm.op[o].kvs==2) {
if (chan[c].state.esfm.op[o].outLvl==7) return true;
else if (chan[c].state.esfm.op[o].outLvl>0) {
if (o==3) return true;
else if ((chan[c].state.esfm.op[o].outLvl-chan[c].state.esfm.op[o+1].modIn)>=2) {
return true;
}
else if (chan[c].state.esfm.op[o+1].modIn==0) {
return true;
}
}
}
return false;
}
public:
void acquire(short** buf, size_t len);
int dispatch(DivCommand c);
void* getChanState(int chan);
DivMacroInt* getChanMacroInt(int ch);
unsigned short getPan(int ch);
DivDispatchOscBuffer* getOscBuffer(int chan);
unsigned char* getRegisterPool();
int getRegisterPoolSize();
int getOutputCount();
void reset();
void forceIns();
void tick(bool sysTick=true);
void muteChannel(int ch, bool mute);
bool keyOffAffectsArp(int ch);
bool keyOffAffectsPorta(int ch);
void toggleRegisterDump(bool enable);
void notifyInsChange(int ins);
void notifyInsDeletion(void* ins);
int mapVelocity(int ch, float vel);
void poke(unsigned int addr, unsigned short val);
void poke(std::vector<DivRegWrite>& wlist);
void setFlags(const DivConfig& flags);
int init(DivEngine* parent, int channels, int sugRate, const DivConfig& flags);
void quit();
~DivPlatformESFM();
};

View file

@ -242,7 +242,13 @@ const char* cmdName[]={
"C64_AD",
"C64_SR",
"ALWAYS_SET_VOLUME"
"ESFM_OP_PANNING",
"ESFM_OUTLVL",
"ESFM_MODIN",
"ESFM_ENV_DELAY",
"ALWAYS_SET_VOLUME",
};
static_assert((sizeof(cmdName)/sizeof(void*))==DIV_CMD_MAX,"update cmdName!");

View file

@ -131,7 +131,8 @@ enum DivSystem {
DIV_SYSTEM_K053260,
DIV_SYSTEM_TED,
DIV_SYSTEM_C140,
DIV_SYSTEM_C219
DIV_SYSTEM_C219,
DIV_SYSTEM_ESFM
};
enum DivEffectType: unsigned short {
@ -397,7 +398,7 @@ struct DivSong {
std::vector<DivEffectStorage> effects;
DivInstrument nullIns, nullInsOPLL, nullInsOPL, nullInsOPLDrums, nullInsQSound;
DivInstrument nullIns, nullInsOPLL, nullInsOPL, nullInsOPLDrums, nullInsQSound, nullInsESFM;
DivWavetable nullWave;
DivSample nullSample;
@ -607,6 +608,49 @@ struct DivSong {
nullInsOPLDrums.fm.op[3].mult=2;
nullInsQSound.std.panLMacro.mode=true;
// 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.esfm.op[0].dt=2;
nullInsESFM.fm.op[0].tl=42;
nullInsESFM.fm.op[0].ar=15;
nullInsESFM.fm.op[0].dr=3;
nullInsESFM.fm.op[0].sl=15;
nullInsESFM.fm.op[0].rr=3;
nullInsESFM.fm.op[0].mult=5;
nullInsESFM.esfm.op[1].outLvl=0;
nullInsESFM.esfm.op[1].modIn=7;
nullInsESFM.esfm.op[1].dt=-3;
nullInsESFM.fm.op[1].tl=18;
nullInsESFM.fm.op[1].ar=15;
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.esfm.op[2].dt=2;
nullInsESFM.fm.op[2].tl=48;
nullInsESFM.fm.op[2].ar=15;
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;
nullInsESFM.fm.op[3].sl=15;
nullInsESFM.fm.op[3].rr=9;
nullInsESFM.fm.op[3].mult=1;
}
};

View file

@ -615,6 +615,56 @@ void DivEngine::registerSystems() {
{0x20, {DIV_CMD_SAMPLE_FREQ, "20xx: Set PCM frequency"}}
};
EffectHandlerMap fmESFMPostEffectHandlerMap={
{0x10, {DIV_CMD_FM_AM_DEPTH, "10xy: Set AM depth (x: operator from 1 to 4 (0 for all ops); y: depth (0: 1dB, 1: 4.8dB))", effectOpVal<4>, effectValAnd<1>}},
{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>}},
{0x17, {DIV_CMD_FM_PM_DEPTH, "17xy: Set vibrato depth (x: operator from 1 to 4 (0 for all ops); y: depth (0: normal, 1: double))", effectOpVal<4>, effectValAnd<1>}},
{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>}},
{0x20, {DIV_CMD_ESFM_OP_PANNING, "20xy: Set panning of operator 1 (x: left; y: right)", constVal<0>, effectVal}},
{0x21, {DIV_CMD_ESFM_OP_PANNING, "21xy: Set panning of operator 2 (x: left; y: right)", constVal<1>, effectVal}},
{0x22, {DIV_CMD_ESFM_OP_PANNING, "22xy: Set panning of operator 3 (x: left; y: right)", constVal<2>, effectVal}},
{0x23, {DIV_CMD_ESFM_OP_PANNING, "23xy: Set panning of operator 4 (x: left; y: right)", constVal<3>, effectVal}},
{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>}},
{0x40, {DIV_CMD_FM_DT, "40xx: Set detune of operator 1 (80: center)", constVal<0>, effectVal}},
{0x41, {DIV_CMD_FM_DT, "41xx: Set detune of operator 2 (80: center)", constVal<1>, effectVal}},
{0x42, {DIV_CMD_FM_DT, "42xx: Set detune of operator 3 (80: center)", constVal<2>, effectVal}},
{0x43, {DIV_CMD_FM_DT, "43xx: Set detune of operator 4 (80: center)", constVal<3>, effectVal}},
{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.
@ -1921,6 +1971,20 @@ void DivEngine::registerSystems() {
}
);
sysDefs[DIV_SYSTEM_ESFM]=new DivSysDef(
"ESS ES1xxx series (ESFM)", NULL, 0xd1, 0, 18, true, false, 0, false, 0, 0, 0,
"a unique FM synth featured in PC sound cards.\nbased on the OPL3 design, but with lots of its features extended.",
{"FM 1", "FM 2", "FM 3", "FM 4", "FM 5", "FM 6", "FM 7", "FM 8", "FM 9", "FM 10", "FM 11", "FM 12", "FM 13", "FM 14", "FM 15", "FM 16", "FM 17", "FM 18"},
{"1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15", "16", "17", "18"},
{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(
"Dummy System", NULL, 0xfd, 0, 8, false, true, 0, false, 0, 0, 0,
"this is a system designed for testing purposes.",