commit
0acd62f4d5
27 changed files with 5594 additions and 90 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
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
210
src/engine/platform/esfm.h
Normal 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();
|
||||
};
|
||||
|
|
@ -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!");
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -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.",
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue