Merge pull request #390 from djtuBIG-MaliceX/feature/Moar-patch-bank-support
More #79 - OPM, Y12, OPLI, OPNI import
This commit is contained in:
commit
e027c7e4cb
|
@ -288,6 +288,9 @@ class DivEngine {
|
||||||
void loadVGI(SafeReader& reader, std::vector<DivInstrument*>& ret, String& stripPath);
|
void loadVGI(SafeReader& reader, std::vector<DivInstrument*>& ret, String& stripPath);
|
||||||
void loadS3I(SafeReader& reader, std::vector<DivInstrument*>& ret, String& stripPath);
|
void loadS3I(SafeReader& reader, std::vector<DivInstrument*>& ret, String& stripPath);
|
||||||
void loadSBI(SafeReader& reader, std::vector<DivInstrument*>& ret, String& stripPath);
|
void loadSBI(SafeReader& reader, std::vector<DivInstrument*>& ret, String& stripPath);
|
||||||
|
void loadOPLI(SafeReader& reader, std::vector<DivInstrument*>& ret, String& stripPath);
|
||||||
|
void loadOPNI(SafeReader& reader, std::vector<DivInstrument*>& ret, String& stripPath);
|
||||||
|
void loadY12(SafeReader& reader, std::vector<DivInstrument*>& ret, String& stripPath);
|
||||||
void loadBNK(SafeReader& reader, std::vector<DivInstrument*>& ret, String& stripPath);
|
void loadBNK(SafeReader& reader, std::vector<DivInstrument*>& ret, String& stripPath);
|
||||||
void loadOPM(SafeReader& reader, std::vector<DivInstrument*>& ret, String& stripPath);
|
void loadOPM(SafeReader& reader, std::vector<DivInstrument*>& ret, String& stripPath);
|
||||||
void loadFF(SafeReader& reader, std::vector<DivInstrument*>& ret, String& stripPath);
|
void loadFF(SafeReader& reader, std::vector<DivInstrument*>& ret, String& stripPath);
|
||||||
|
|
|
@ -30,6 +30,9 @@ enum DivInsFormats {
|
||||||
DIV_INSFORMAT_BTI,
|
DIV_INSFORMAT_BTI,
|
||||||
DIV_INSFORMAT_S3I,
|
DIV_INSFORMAT_S3I,
|
||||||
DIV_INSFORMAT_SBI,
|
DIV_INSFORMAT_SBI,
|
||||||
|
DIV_INSFORMAT_Y12,
|
||||||
|
DIV_INSFORMAT_OPLI,
|
||||||
|
DIV_INSFORMAT_OPNI,
|
||||||
DIV_INSFORMAT_BNK,
|
DIV_INSFORMAT_BNK,
|
||||||
DIV_INSFORMAT_OPM,
|
DIV_INSFORMAT_OPM,
|
||||||
DIV_INSFORMAT_FF,
|
DIV_INSFORMAT_FF,
|
||||||
|
@ -499,7 +502,7 @@ void DivEngine::loadS3I(SafeReader& reader, std::vector<DivInstrument*>& ret, St
|
||||||
logE("S3I PCM samples currently not supported.");
|
logE("S3I PCM samples currently not supported.");
|
||||||
}
|
}
|
||||||
ins->name = reader.readString(28);
|
ins->name = reader.readString(28);
|
||||||
ins->name = (ins->name.length() == 0) ? stripPath : ins->name;
|
ins->name = (ins->name.size() == 0) ? stripPath : ins->name;
|
||||||
|
|
||||||
int s3i_signature = reader.readI();
|
int s3i_signature = reader.readI();
|
||||||
|
|
||||||
|
@ -531,7 +534,7 @@ void DivEngine::loadSBI(SafeReader& reader, std::vector<DivInstrument*>& ret, St
|
||||||
|
|
||||||
// 32-byte null terminated instrument name
|
// 32-byte null terminated instrument name
|
||||||
String patchName = reader.readString(32);
|
String patchName = reader.readString(32);
|
||||||
patchName = (patchName.length() == 0) ? stripPath : patchName;
|
patchName = (patchName.size() == 0) ? stripPath : patchName;
|
||||||
|
|
||||||
auto writeOp = [](sbi_t& sbi, DivInstrumentFM::Operator& opM, DivInstrumentFM::Operator& opC) {
|
auto writeOp = [](sbi_t& sbi, DivInstrumentFM::Operator& opM, DivInstrumentFM::Operator& opC) {
|
||||||
opM.mult = sbi.Mcharacteristics & 0xF;
|
opM.mult = sbi.Mcharacteristics & 0xF;
|
||||||
|
@ -605,7 +608,7 @@ void DivEngine::loadSBI(SafeReader& reader, std::vector<DivInstrument*>& ret, St
|
||||||
if (is_6op) {
|
if (is_6op) {
|
||||||
// Freq Monster 801 6op SBIs use a 4+2op layout
|
// Freq Monster 801 6op SBIs use a 4+2op layout
|
||||||
// Save the 4op portion before reading the 2op part
|
// Save the 4op portion before reading the 2op part
|
||||||
ins->name = fmt::format("{0} (4op)", ins->name);
|
ins->name = fmt::sprintf("%s (4op)", ins->name);
|
||||||
ret.push_back(ins);
|
ret.push_back(ins);
|
||||||
|
|
||||||
readSbiOpData(sbi_op12, reader);
|
readSbiOpData(sbi_op12, reader);
|
||||||
|
@ -615,7 +618,7 @@ void DivEngine::loadSBI(SafeReader& reader, std::vector<DivInstrument*>& ret, St
|
||||||
DivInstrumentFM::Operator& opC6 = ins->fm.op[1];
|
DivInstrumentFM::Operator& opC6 = ins->fm.op[1];
|
||||||
ins->type = DIV_INS_OPL;
|
ins->type = DIV_INS_OPL;
|
||||||
ins->fm.ops = 2;
|
ins->fm.ops = 2;
|
||||||
ins->name = fmt::format("{0} (2op)", patchName);
|
ins->name = fmt::sprintf("%s (2op)", patchName);
|
||||||
writeOp(sbi_op12, opM6, opC6);
|
writeOp(sbi_op12, opM6, opC6);
|
||||||
ins->fm.alg = (sbi_op12.FeedConnect & 0x1);
|
ins->fm.alg = (sbi_op12.FeedConnect & 0x1);
|
||||||
ins->fm.fb = ((sbi_op12.FeedConnect >> 1) & 0x7);
|
ins->fm.fb = ((sbi_op12.FeedConnect >> 1) & 0x7);
|
||||||
|
@ -634,6 +637,204 @@ void DivEngine::loadSBI(SafeReader& reader, std::vector<DivInstrument*>& ret, St
|
||||||
delete ins;
|
delete ins;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void DivEngine::loadOPLI(SafeReader& reader, std::vector<DivInstrument*>& ret, String& stripPath) {
|
||||||
|
DivInstrument* ins = new DivInstrument;
|
||||||
|
|
||||||
|
try {
|
||||||
|
reader.seek(0, SEEK_SET);
|
||||||
|
String header = reader.readString(11);
|
||||||
|
if (header == "WOPL3-INST") {
|
||||||
|
uint16_t version = reader.readS();
|
||||||
|
if (version > 3) {
|
||||||
|
logW("Unknown OPLI version.");
|
||||||
|
}
|
||||||
|
|
||||||
|
reader.readC(); // skip isPerc field
|
||||||
|
|
||||||
|
ins->type = DIV_INS_OPL;
|
||||||
|
String insName = reader.readString(32);
|
||||||
|
insName = (insName.size() > 0) ? insName : stripPath;
|
||||||
|
ins->name = insName;
|
||||||
|
reader.seek(7, SEEK_CUR); // skip MIDI params
|
||||||
|
uint8_t instTypeFlags = reader.readC(); // [0EEEDCBA] - see WOPL/OPLI spec
|
||||||
|
|
||||||
|
bool is_4op = ((instTypeFlags & 0x1) == 1);
|
||||||
|
bool is_2x2op = (((instTypeFlags>>1) & 0x1) == 1);
|
||||||
|
bool is_rhythm = (((instTypeFlags>>4) & 0x7) > 0);
|
||||||
|
|
||||||
|
auto readOpliOp = [](SafeReader& reader, DivInstrumentFM::Operator& op) {
|
||||||
|
uint8_t characteristics = reader.readC();
|
||||||
|
uint8_t keyScaleLevel = reader.readC();
|
||||||
|
uint8_t attackDecay = reader.readC();
|
||||||
|
uint8_t sustainRelease = reader.readC();
|
||||||
|
uint8_t waveSelect = reader.readC();
|
||||||
|
|
||||||
|
op.mult = characteristics & 0xF;
|
||||||
|
op.ksr = ((characteristics >> 4) & 0x1);
|
||||||
|
op.sus = ((characteristics >> 5) & 0x1);
|
||||||
|
op.vib = ((characteristics >> 6) & 0x1);
|
||||||
|
op.am = ((characteristics >> 7) & 0x1);
|
||||||
|
op.tl = keyScaleLevel & 0x3F;
|
||||||
|
op.ksl = ((keyScaleLevel >> 6) & 0x3);
|
||||||
|
op.ar = ((attackDecay >> 4) & 0xF);
|
||||||
|
op.dr = attackDecay & 0xF;
|
||||||
|
op.rr = sustainRelease & 0xF;
|
||||||
|
op.sl = ((sustainRelease >> 4) & 0xF);
|
||||||
|
op.ws = waveSelect;
|
||||||
|
};
|
||||||
|
|
||||||
|
uint8_t feedConnect = reader.readC();
|
||||||
|
uint8_t feedConnect2nd = reader.readC();
|
||||||
|
|
||||||
|
ins->fm.alg = (feedConnect & 0x1);
|
||||||
|
ins->fm.fb = ((feedConnect >> 1) & 0xF);
|
||||||
|
|
||||||
|
if (is_4op && !is_2x2op) {
|
||||||
|
ins->fm.ops = 4;
|
||||||
|
ins->fm.alg = (feedConnect & 0x1) | ((feedConnect2nd & 0x1) << 1);
|
||||||
|
for (int i : {2,0,3,1}) { // omfg >_<
|
||||||
|
readOpliOp(reader, ins->fm.op[i]);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ins->fm.ops = 2;
|
||||||
|
for (int i : {1,0}) {
|
||||||
|
readOpliOp(reader, ins->fm.op[i]);
|
||||||
|
}
|
||||||
|
if (is_rhythm) {
|
||||||
|
ins->fm.opllPreset = (uint8_t)(1<<4);
|
||||||
|
|
||||||
|
} else if (is_2x2op) {
|
||||||
|
// Note: Pair detuning offset not mappable. Use E5xx effect :P
|
||||||
|
ins->name = fmt::sprintf("%s (1)", insName);
|
||||||
|
ret.push_back(ins);
|
||||||
|
|
||||||
|
ins = new DivInstrument;
|
||||||
|
ins->type = DIV_INS_OPL;
|
||||||
|
ins->name = fmt::sprintf("%s (2)", insName);
|
||||||
|
for (int i : {1,0}) {
|
||||||
|
readOpliOp(reader, ins->fm.op[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Skip rest of file
|
||||||
|
reader.seek(0, SEEK_END);
|
||||||
|
ret.push_back(ins);
|
||||||
|
}
|
||||||
|
} catch (EndOfFileException& e) {
|
||||||
|
lastError="premature end of file";
|
||||||
|
logE("premature end of file");
|
||||||
|
delete ins;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void DivEngine::loadOPNI(SafeReader& reader, std::vector<DivInstrument*>& ret, String& stripPath) {
|
||||||
|
DivInstrument* ins = new DivInstrument;
|
||||||
|
|
||||||
|
try {
|
||||||
|
reader.seek(0, SEEK_SET);
|
||||||
|
|
||||||
|
String header = reader.readString(11);
|
||||||
|
if (header == "WOPN2-INST" || header == "WOPN2-IN2T") { // omfg >_<
|
||||||
|
uint16_t version = reader.readS();
|
||||||
|
if (!(version >= 2) || version > 0xF) {
|
||||||
|
// version 1 doesn't have a version field........
|
||||||
|
reader.seek(-2, SEEK_CUR);
|
||||||
|
}
|
||||||
|
|
||||||
|
reader.readC(); // skip isPerc
|
||||||
|
ins->type = DIV_INS_FM;
|
||||||
|
ins->fm.ops = 4;
|
||||||
|
|
||||||
|
String insName = reader.readString(32);
|
||||||
|
ins->name = (insName.size() > 0) ? insName : stripPath;
|
||||||
|
reader.seek(3, SEEK_CUR); // skip MIDI params
|
||||||
|
uint8_t feedAlgo = reader.readC();
|
||||||
|
ins->fm.alg = (feedAlgo & 0x7);
|
||||||
|
ins->fm.fb = ((feedAlgo>>3) & 0x7);
|
||||||
|
reader.readC(); // Skip global bank flags - see WOPN/OPNI spec
|
||||||
|
|
||||||
|
auto readOpniOp = [](SafeReader& reader, DivInstrumentFM::Operator& op) {
|
||||||
|
uint8_t dtMul = reader.readC();
|
||||||
|
uint8_t totalLevel = reader.readC();
|
||||||
|
uint8_t arRateScale = reader.readC();
|
||||||
|
uint8_t drAmpEnable = reader.readC();
|
||||||
|
uint8_t d2r = reader.readC();
|
||||||
|
uint8_t susRelease = reader.readC();
|
||||||
|
uint8_t ssgEg = reader.readC();
|
||||||
|
|
||||||
|
op.mult = dtMul & 0xF;
|
||||||
|
op.dt = ((dtMul >> 4) & 0x7);
|
||||||
|
op.tl = totalLevel & 0x3F;
|
||||||
|
op.rs = ((arRateScale >> 6) & 0x3);
|
||||||
|
op.ar = arRateScale & 0x1F;
|
||||||
|
op.dr = drAmpEnable & 0x1F;
|
||||||
|
op.am = ((drAmpEnable >> 7) & 0x1);
|
||||||
|
op.d2r = d2r & 0x1F;
|
||||||
|
op.rr = susRelease & 0xF;
|
||||||
|
op.sl = ((susRelease >> 4) & 0xF);
|
||||||
|
op.ssgEnv = ssgEg;
|
||||||
|
};
|
||||||
|
|
||||||
|
for (int i = 0; i < 4; ++i) {
|
||||||
|
readOpniOp(reader, ins->fm.op[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Skip rest of file
|
||||||
|
reader.seek(0, SEEK_END);
|
||||||
|
ret.push_back(ins);
|
||||||
|
}
|
||||||
|
} catch (EndOfFileException& e) {
|
||||||
|
lastError="premature end of file";
|
||||||
|
logE("premature end of file");
|
||||||
|
delete ins;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void DivEngine::loadY12(SafeReader& reader, std::vector<DivInstrument*>& ret, String& stripPath) {
|
||||||
|
DivInstrument *ins = new DivInstrument;
|
||||||
|
|
||||||
|
try {
|
||||||
|
reader.seek(0, SEEK_SET);
|
||||||
|
ins->type = DIV_INS_FM;
|
||||||
|
ins->fm.ops = 4;
|
||||||
|
ins->name = stripPath;
|
||||||
|
|
||||||
|
for (int i = 0; i < 4; ++i) {
|
||||||
|
DivInstrumentFM::Operator& insOp = ins->fm.op[i];
|
||||||
|
uint8_t tmp = reader.readC();
|
||||||
|
insOp.mult = tmp & 0xF;
|
||||||
|
insOp.dt = ((tmp >> 4) & 0x7);
|
||||||
|
insOp.tl = (reader.readC() & 0x3F);
|
||||||
|
tmp = reader.readC();
|
||||||
|
insOp.rs = ((tmp >> 6) & 0x3);
|
||||||
|
insOp.ar = tmp & 0x1F;
|
||||||
|
tmp = reader.readC();
|
||||||
|
insOp.dr = tmp & 0x1F;
|
||||||
|
insOp.am = ((tmp >> 7) & 0x1);
|
||||||
|
insOp.d2r = (reader.readC() & 0x1F);
|
||||||
|
tmp = reader.readC();
|
||||||
|
insOp.rr = tmp & 0xF;
|
||||||
|
insOp.sl = ((tmp >> 4) & 0xF);
|
||||||
|
insOp.ssgEnv = reader.readC();
|
||||||
|
if (!reader.seek(9, SEEK_CUR)) {
|
||||||
|
throw EndOfFileException(&reader, reader.tell() + 9);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ins->fm.alg = reader.readC();
|
||||||
|
ins->fm.fb = reader.readC();
|
||||||
|
if (!reader.seek(62, SEEK_CUR)) {
|
||||||
|
throw EndOfFileException(&reader, reader.tell() + 62);
|
||||||
|
}
|
||||||
|
ret.push_back(ins);
|
||||||
|
} catch (EndOfFileException& e) {
|
||||||
|
lastError="premature end of file";
|
||||||
|
logE("premature end of file");
|
||||||
|
delete ins;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void DivEngine::loadBNK(SafeReader& reader, std::vector<DivInstrument*>& ret, String& stripPath) {
|
void DivEngine::loadBNK(SafeReader& reader, std::vector<DivInstrument*>& ret, String& stripPath) {
|
||||||
std::vector<DivInstrument*> insList;
|
std::vector<DivInstrument*> insList;
|
||||||
std::vector<String*> instNames;
|
std::vector<String*> instNames;
|
||||||
|
@ -661,7 +862,9 @@ void DivEngine::loadBNK(SafeReader& reader, std::vector<DivInstrument*>& ret, St
|
||||||
}
|
}
|
||||||
|
|
||||||
// Seek to BNK data
|
// Seek to BNK data
|
||||||
reader.seek(data_offset, SEEK_SET);
|
if (!reader.seek(data_offset, SEEK_SET)) {
|
||||||
|
throw EndOfFileException(&reader, data_offset);
|
||||||
|
};
|
||||||
|
|
||||||
// Read until EOF
|
// Read until EOF
|
||||||
for (int i = 0; i < readCount; ++i) {
|
for (int i = 0; i < readCount; ++i) {
|
||||||
|
@ -670,6 +873,7 @@ void DivEngine::loadBNK(SafeReader& reader, std::vector<DivInstrument*>& ret, St
|
||||||
auto& ins = insList[i];
|
auto& ins = insList[i];
|
||||||
|
|
||||||
ins->type = DIV_INS_OPL;
|
ins->type = DIV_INS_OPL;
|
||||||
|
ins->fm.ops = 2;
|
||||||
|
|
||||||
timbre.mode = reader.readC();
|
timbre.mode = reader.readC();
|
||||||
timbre.percVoice = reader.readC();
|
timbre.percVoice = reader.readC();
|
||||||
|
@ -706,14 +910,14 @@ void DivEngine::loadBNK(SafeReader& reader, std::vector<DivInstrument*>& ret, St
|
||||||
|
|
||||||
ins->fm.op[0].ws = reader.readC();
|
ins->fm.op[0].ws = reader.readC();
|
||||||
ins->fm.op[1].ws = reader.readC();
|
ins->fm.op[1].ws = reader.readC();
|
||||||
ins->name = instNames[i]->length() > 0 ? (*instNames[i]) : fmt::format("{0}[{1}]", stripPath, i);
|
ins->name = instNames[i]->length() > 0 ? (*instNames[i]) : fmt::sprintf("%s[%d]", stripPath, i);
|
||||||
}
|
}
|
||||||
reader.seek(0, SEEK_END);
|
reader.seek(0, SEEK_END);
|
||||||
|
|
||||||
} catch (EndOfFileException& e) {
|
} catch (EndOfFileException& e) {
|
||||||
lastError="premature end of file";
|
lastError="premature end of file";
|
||||||
logE("premature end of file");
|
logE("premature end of file");
|
||||||
for (int i = readCount; i >= 0; --i) {
|
for (int i = readCount - 1; i >= 0; --i) {
|
||||||
delete insList[i];
|
delete insList[i];
|
||||||
}
|
}
|
||||||
is_failed = true;
|
is_failed = true;
|
||||||
|
@ -798,7 +1002,7 @@ void DivEngine::loadFF(SafeReader& reader, std::vector<DivInstrument*>& ret, Str
|
||||||
} catch (EndOfFileException& e) {
|
} catch (EndOfFileException& e) {
|
||||||
lastError="premature end of file";
|
lastError="premature end of file";
|
||||||
logE("premature end of file");
|
logE("premature end of file");
|
||||||
for (int i = readCount; i >= 0; --i) {
|
for (int i = readCount - 1; i >= 0; --i) {
|
||||||
delete insList[i];
|
delete insList[i];
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
|
@ -810,16 +1014,161 @@ void DivEngine::loadFF(SafeReader& reader, std::vector<DivInstrument*>& ret, Str
|
||||||
}
|
}
|
||||||
|
|
||||||
void DivEngine::loadOPM(SafeReader& reader, std::vector<DivInstrument*>& ret, String& stripPath) {
|
void DivEngine::loadOPM(SafeReader& reader, std::vector<DivInstrument*>& ret, String& stripPath) {
|
||||||
DivInstrument* ins[128];
|
std::vector<DivInstrument*> insList;
|
||||||
memset(ins,0,128*sizeof(void*));
|
|
||||||
|
int readCount = 0;
|
||||||
|
bool is_failed = false;
|
||||||
|
|
||||||
|
bool patchNameRead = false,
|
||||||
|
lfoRead = false,
|
||||||
|
characteristicRead = false,
|
||||||
|
m1Read = false,
|
||||||
|
c1Read = false,
|
||||||
|
m2Read = false,
|
||||||
|
c2Read = false;
|
||||||
|
|
||||||
|
DivInstrument* newPatch = NULL;
|
||||||
|
|
||||||
|
auto completePatchRead = [&]() {
|
||||||
|
return patchNameRead && lfoRead && characteristicRead && m1Read && c1Read && m2Read && c2Read;
|
||||||
|
};
|
||||||
|
auto resetPatchRead = [&]() {
|
||||||
|
patchNameRead = lfoRead = characteristicRead = m1Read = c1Read = m2Read = c2Read = false;
|
||||||
|
newPatch = NULL;
|
||||||
|
};
|
||||||
|
auto readIntStrWithinRange = [](String&& input, int limitLow, int limitHigh) {
|
||||||
|
int x = std::stoi(input.c_str());
|
||||||
|
if (x > limitHigh || x < limitLow) {
|
||||||
|
throw std::invalid_argument(fmt::sprintf("%s is out of bounds of range [%d..%d]", input, limitLow, limitHigh));
|
||||||
|
}
|
||||||
|
return (x>limitHigh) ? limitHigh :
|
||||||
|
(x<limitLow) ? limitLow : x;
|
||||||
|
};
|
||||||
|
auto readOpmOperator = [&](SafeReader& reader, DivInstrumentFM::Operator& op) {
|
||||||
|
op.ar = readIntStrWithinRange(reader.readStringToken(), 0, 31);
|
||||||
|
op.dr = readIntStrWithinRange(reader.readStringToken(), 0, 31);
|
||||||
|
op.d2r = readIntStrWithinRange(reader.readStringToken(), 0, 31);
|
||||||
|
op.rr = readIntStrWithinRange(reader.readStringToken(), 0, 31);
|
||||||
|
op.sl = readIntStrWithinRange(reader.readStringToken(), 0, 15);
|
||||||
|
op.tl = readIntStrWithinRange(reader.readStringToken(), 0, 127);
|
||||||
|
op.rs = readIntStrWithinRange(reader.readStringToken(), 0, 3);;
|
||||||
|
op.mult = readIntStrWithinRange(reader.readStringToken(), 0, 15);
|
||||||
|
op.dt = readIntStrWithinRange(reader.readStringToken(), 0, 7);
|
||||||
|
op.dt = (op.dt >= 4) ? (7 - op.dt) : (op.dt + 3);
|
||||||
|
op.dt2 = readIntStrWithinRange(reader.readStringToken(), 0, 3);
|
||||||
|
op.am = readIntStrWithinRange(reader.readStringToken(), 0, 1);
|
||||||
|
};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
String line;
|
reader.seek(0, SEEK_SET);
|
||||||
|
while (!reader.isEOF()) {
|
||||||
|
String token = reader.readStringToken();
|
||||||
|
if (token.size() == 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (token.compare(0,2,"//") == 0) {
|
||||||
|
if (!reader.isEOF()) {
|
||||||
|
reader.readStringLine();
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// At this point we know any other line would be associated with patch params
|
||||||
|
if (newPatch == NULL) {
|
||||||
|
newPatch = new DivInstrument;
|
||||||
|
newPatch->type = DIV_INS_FM;
|
||||||
|
newPatch->fm.ops = 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read each line for their respective params. They may not be written in the same LINE order but they
|
||||||
|
// must absolutely be properly grouped per patch! Line prefixes must be separated by a space! (see inline comments)
|
||||||
|
|
||||||
|
if (token.size() >= 2) {
|
||||||
|
if (token[0] == '@') {
|
||||||
|
// @:123 Name of patch
|
||||||
|
// Note: Fallback to bank filename and current patch number in _file_ order (not @n order)
|
||||||
|
newPatch->name = reader.readStringLine();
|
||||||
|
newPatch->name = newPatch->name.size() > 0 ? newPatch->name : fmt::sprintf("%s[%d]", stripPath, readCount);
|
||||||
|
patchNameRead = true;
|
||||||
|
|
||||||
|
} else if (token.compare(0,3,"CH:") == 0) {
|
||||||
|
// CH: PAN FL CON AMS PMS SLOT NE
|
||||||
|
reader.readStringToken(); // skip PAN
|
||||||
|
newPatch->fm.fb = readIntStrWithinRange(reader.readStringToken(), 0, 7);
|
||||||
|
newPatch->fm.alg = readIntStrWithinRange(reader.readStringToken(), 0, 7);
|
||||||
|
newPatch->fm.ams = readIntStrWithinRange(reader.readStringToken(), 0, 4);
|
||||||
|
newPatch->fm.fms = readIntStrWithinRange(reader.readStringToken(), 0, 7);
|
||||||
|
reader.readStringToken(); // skip SLOT
|
||||||
|
reader.readStringToken(); // skip NE
|
||||||
|
characteristicRead = true;
|
||||||
|
|
||||||
|
} else if (token.compare(0,3,"C1:") == 0) {
|
||||||
|
// C1: AR D1R D2R RR D1L TL KS MUL DT1 DT2 AMS-EN
|
||||||
|
readOpmOperator(reader, newPatch->fm.op[2]);
|
||||||
|
c1Read = true;
|
||||||
|
|
||||||
|
} else if (token.compare(0,3,"C2:") == 0) {
|
||||||
|
// C2: AR D1R D2R RR D1L TL KS MUL DT1 DT2 AMS-EN
|
||||||
|
readOpmOperator(reader, newPatch->fm.op[3]);
|
||||||
|
c2Read = true;
|
||||||
|
|
||||||
|
} else if (token.compare(0,3,"M1:") == 0) {
|
||||||
|
// M1: AR D1R D2R RR D1L TL KS MUL DT1 DT2 AMS-EN
|
||||||
|
readOpmOperator(reader, newPatch->fm.op[0]);
|
||||||
|
m1Read = true;
|
||||||
|
|
||||||
|
} else if (token.compare(0,3,"M2:") == 0) {
|
||||||
|
// M2: AR D1R D2R RR D1L TL KS MUL DT1 DT2 AMS-EN
|
||||||
|
readOpmOperator(reader, newPatch->fm.op[1]);
|
||||||
|
m2Read = true;
|
||||||
|
|
||||||
|
} else if (token.compare(0,4,"LFO:") == 0) {
|
||||||
|
// LFO: LFRQ AMD PMD WF NFRQ
|
||||||
|
// Furnace patches do not store these as they are chip-global.
|
||||||
|
reader.readStringLine();
|
||||||
|
lfoRead = true;
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// other unsupported lines ignored.
|
||||||
|
reader.readStringLine();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (completePatchRead()) {
|
||||||
|
insList.push_back(newPatch);
|
||||||
|
resetPatchRead();
|
||||||
|
++readCount;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (newPatch != NULL) {
|
||||||
|
addWarning("Last OPM patch read was incomplete and therefore not imported.");
|
||||||
|
logW("Last OPM patch read was incomplete and therefore not imported.");
|
||||||
|
delete newPatch;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < readCount; ++i) {
|
||||||
|
ret.push_back(insList[i]);
|
||||||
|
}
|
||||||
} catch (EndOfFileException& e) {
|
} catch (EndOfFileException& e) {
|
||||||
lastError="premature end of file";
|
lastError="premature end of file";
|
||||||
logE("premature end of file");
|
logE("premature end of file");
|
||||||
return;
|
is_failed = true;
|
||||||
|
} catch (std::invalid_argument& e) {
|
||||||
|
lastError=fmt::sprintf("Invalid value found in patch file. %s", e.what());
|
||||||
|
logE("Invalid value found in patch file.");
|
||||||
|
logE(e.what());
|
||||||
|
is_failed = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is_failed) {
|
||||||
|
for (int i = readCount - 1; i >= 0; --i) {
|
||||||
|
delete insList[i];
|
||||||
|
}
|
||||||
|
if (newPatch != NULL) {
|
||||||
|
delete newPatch;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -947,6 +1296,12 @@ std::vector<DivInstrument*> DivEngine::instrumentFromFile(const char* path) {
|
||||||
format=DIV_INSFORMAT_S3I;
|
format=DIV_INSFORMAT_S3I;
|
||||||
} else if (extS==String(".sbi")) {
|
} else if (extS==String(".sbi")) {
|
||||||
format=DIV_INSFORMAT_SBI;
|
format=DIV_INSFORMAT_SBI;
|
||||||
|
} else if (extS==String(".opli")) {
|
||||||
|
format=DIV_INSFORMAT_OPLI;
|
||||||
|
} else if (extS==String(".opni")) {
|
||||||
|
format=DIV_INSFORMAT_OPNI;;
|
||||||
|
} else if (extS==String(".y12")) {
|
||||||
|
format=DIV_INSFORMAT_Y12;
|
||||||
} else if (extS==String(".bnk")) {
|
} else if (extS==String(".bnk")) {
|
||||||
format=DIV_INSFORMAT_BNK;
|
format=DIV_INSFORMAT_BNK;
|
||||||
} else if (extS==String(".opm")) {
|
} else if (extS==String(".opm")) {
|
||||||
|
@ -971,20 +1326,30 @@ std::vector<DivInstrument*> DivEngine::instrumentFromFile(const char* path) {
|
||||||
break;
|
break;
|
||||||
case DIV_INSFORMAT_BTI: // TODO
|
case DIV_INSFORMAT_BTI: // TODO
|
||||||
break;
|
break;
|
||||||
case DIV_INSFORMAT_OPM: // TODO
|
|
||||||
break;
|
|
||||||
case DIV_INSFORMAT_S3I:
|
case DIV_INSFORMAT_S3I:
|
||||||
loadS3I(reader,ret,stripPath);
|
loadS3I(reader,ret,stripPath);
|
||||||
break;
|
break;
|
||||||
case DIV_INSFORMAT_SBI:
|
case DIV_INSFORMAT_SBI:
|
||||||
loadSBI(reader,ret,stripPath);
|
loadSBI(reader,ret,stripPath);
|
||||||
break;
|
break;
|
||||||
|
case DIV_INSFORMAT_OPLI:
|
||||||
|
loadOPLI(reader,ret,stripPath);
|
||||||
|
break;
|
||||||
|
case DIV_INSFORMAT_OPNI:
|
||||||
|
loadOPNI(reader, ret, stripPath);
|
||||||
|
break;
|
||||||
|
case DIV_INSFORMAT_Y12:
|
||||||
|
loadY12(reader,ret,stripPath);
|
||||||
|
break;
|
||||||
case DIV_INSFORMAT_BNK:
|
case DIV_INSFORMAT_BNK:
|
||||||
loadBNK(reader, ret, stripPath);
|
loadBNK(reader, ret, stripPath);
|
||||||
break;
|
break;
|
||||||
case DIV_INSFORMAT_FF:
|
case DIV_INSFORMAT_FF:
|
||||||
loadFF(reader,ret,stripPath);
|
loadFF(reader,ret,stripPath);
|
||||||
break;
|
break;
|
||||||
|
case DIV_INSFORMAT_OPM:
|
||||||
|
loadOPM(reader, ret, stripPath);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (reader.tell()<reader.size()) {
|
if (reader.tell()<reader.size()) {
|
||||||
|
|
|
@ -144,7 +144,9 @@ String SafeReader::readString(size_t stlen) {
|
||||||
logD("SR: reading string len %d at %x",stlen,curSeek);
|
logD("SR: reading string len %d at %x",stlen,curSeek);
|
||||||
#endif
|
#endif
|
||||||
size_t curPos=0;
|
size_t curPos=0;
|
||||||
while (curPos<stlen) {
|
if (isEOF()) throw EndOfFileException(this, len);
|
||||||
|
|
||||||
|
while (!isEOF() && curPos<stlen) {
|
||||||
unsigned char c=readC();
|
unsigned char c=readC();
|
||||||
if (c!=0) ret.push_back(c);
|
if (c!=0) ret.push_back(c);
|
||||||
curPos++;
|
curPos++;
|
||||||
|
@ -155,8 +157,48 @@ String SafeReader::readString(size_t stlen) {
|
||||||
String SafeReader::readString() {
|
String SafeReader::readString() {
|
||||||
String ret;
|
String ret;
|
||||||
unsigned char c;
|
unsigned char c;
|
||||||
while ((c=readC())!=0) {
|
if (isEOF()) throw EndOfFileException(this, len);
|
||||||
|
|
||||||
|
while (!isEOF() && (c=readC())!=0) {
|
||||||
ret.push_back(c);
|
ret.push_back(c);
|
||||||
}
|
}
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
String SafeReader::readStringLine() {
|
||||||
|
String ret;
|
||||||
|
unsigned char c;
|
||||||
|
if (isEOF()) throw EndOfFileException(this, len);
|
||||||
|
|
||||||
|
while (!isEOF() && (c = readC()) != 0) {
|
||||||
|
if (c=='\r'||c=='\n') {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
ret.push_back(c);
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
String SafeReader::readStringToken(unsigned char delim) {
|
||||||
|
String ret;
|
||||||
|
unsigned char c;
|
||||||
|
if (isEOF()) throw EndOfFileException(this, len);
|
||||||
|
|
||||||
|
while (!isEOF() && (c=readC())!=0) {
|
||||||
|
if (c == '\r' || c == '\n') {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (c == delim) {
|
||||||
|
if (ret.length() == 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
ret.push_back(c);
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
String SafeReader::readStringToken() {
|
||||||
|
return readStringToken(' ');
|
||||||
|
}
|
||||||
|
|
|
@ -66,6 +66,10 @@ class SafeReader {
|
||||||
double readD_BE();
|
double readD_BE();
|
||||||
String readString();
|
String readString();
|
||||||
String readString(size_t len);
|
String readString(size_t len);
|
||||||
|
String readStringLine();
|
||||||
|
String readStringToken(unsigned char delim);
|
||||||
|
String readStringToken();
|
||||||
|
inline bool isEOF() { return curSeek >= len; };
|
||||||
|
|
||||||
SafeReader(void* b, size_t l):
|
SafeReader(void* b, size_t l):
|
||||||
buf((unsigned char*)b),
|
buf((unsigned char*)b),
|
||||||
|
|
|
@ -1290,9 +1290,9 @@ void FurnaceGUI::openFileDialog(FurnaceGUIFileDialogs type) {
|
||||||
if (!dirExists(workingDirIns)) workingDirIns=getHomeDir();
|
if (!dirExists(workingDirIns)) workingDirIns=getHomeDir();
|
||||||
hasOpened=fileDialog->openLoad(
|
hasOpened=fileDialog->openLoad(
|
||||||
"Load Instrument",
|
"Load Instrument",
|
||||||
{"compatible files", "*.fui *.dmp *.tfi *.vgi *.s3i *.sbi *.bnk *.ff",
|
{"compatible files", "*.fui *.dmp *.tfi *.vgi *.s3i *.sbi *.opli *.opni *.y12 *.bnk *.ff *.opm",
|
||||||
"all files", ".*"},
|
"all files", ".*"},
|
||||||
"compatible files{.fui,.dmp,.tfi,.vgi,.s3i,.sbi,.bnk,.ff},.*",
|
"compatible files{.fui,.dmp,.tfi,.vgi,.s3i,.sbi,.opli,.opni,.y12,.bnk,.ff,.opm},.*",
|
||||||
workingDirIns,
|
workingDirIns,
|
||||||
dpiScale
|
dpiScale
|
||||||
);
|
);
|
||||||
|
|
|
@ -2485,10 +2485,14 @@ void FurnaceGUI::applyUISettings(bool updateFonts) {
|
||||||
ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByExtension,".vgi",uiColors[GUI_COLOR_FILE_INSTR],ICON_FA_FILE);
|
ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByExtension,".vgi",uiColors[GUI_COLOR_FILE_INSTR],ICON_FA_FILE);
|
||||||
ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByExtension,".s3i",uiColors[GUI_COLOR_FILE_INSTR],ICON_FA_FILE);
|
ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByExtension,".s3i",uiColors[GUI_COLOR_FILE_INSTR],ICON_FA_FILE);
|
||||||
ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByExtension,".sbi",uiColors[GUI_COLOR_FILE_INSTR],ICON_FA_FILE);
|
ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByExtension,".sbi",uiColors[GUI_COLOR_FILE_INSTR],ICON_FA_FILE);
|
||||||
|
ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByExtension,".opli",uiColors[GUI_COLOR_FILE_INSTR],ICON_FA_FILE);
|
||||||
|
ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByExtension,".opni",uiColors[GUI_COLOR_FILE_INSTR],ICON_FA_FILE);
|
||||||
|
ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByExtension,".y12",uiColors[GUI_COLOR_FILE_INSTR],ICON_FA_FILE);
|
||||||
ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByExtension,".bnk",uiColors[GUI_COLOR_FILE_INSTR],ICON_FA_FILE);
|
ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByExtension,".bnk",uiColors[GUI_COLOR_FILE_INSTR],ICON_FA_FILE);
|
||||||
ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByExtension,".fti",uiColors[GUI_COLOR_FILE_INSTR],ICON_FA_FILE);
|
ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByExtension,".fti",uiColors[GUI_COLOR_FILE_INSTR],ICON_FA_FILE);
|
||||||
ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByExtension,".bti",uiColors[GUI_COLOR_FILE_INSTR],ICON_FA_FILE);
|
ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByExtension,".bti",uiColors[GUI_COLOR_FILE_INSTR],ICON_FA_FILE);
|
||||||
ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByExtension,".ff",uiColors[GUI_COLOR_FILE_INSTR],ICON_FA_FILE);
|
ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByExtension,".ff",uiColors[GUI_COLOR_FILE_INSTR],ICON_FA_FILE);
|
||||||
|
ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByExtension,".opm",uiColors[GUI_COLOR_FILE_INSTR],ICON_FA_FILE);
|
||||||
|
|
||||||
if (updateFonts) {
|
if (updateFonts) {
|
||||||
if (fileDialog!=NULL) delete fileDialog;
|
if (fileDialog!=NULL) delete fileDialog;
|
||||||
|
|
Loading…
Reference in a new issue