From f716ac262d87a96676c07c20845d2e603b13be0e Mon Sep 17 00:00:00 2001 From: James Alan Nguyen Date: Sat, 23 Apr 2022 19:52:09 +1000 Subject: [PATCH 01/15] #79: OPM import progress and start everything else --- src/engine/engine.h | 7 ++ src/engine/fileOpsIns.cpp | 150 ++++++++++++++++++++++++++++++++++++-- src/engine/safeReader.cpp | 48 +++++++++++- src/engine/safeReader.h | 3 + 4 files changed, 198 insertions(+), 10 deletions(-) diff --git a/src/engine/engine.h b/src/engine/engine.h index 515b9c590..c8afb86a9 100644 --- a/src/engine/engine.h +++ b/src/engine/engine.h @@ -31,6 +31,7 @@ #include #include #include +#include #define addWarning(x) \ if (warnings.empty()) { \ @@ -288,9 +289,15 @@ class DivEngine { void loadVGI(SafeReader& reader, std::vector& ret, String& stripPath); void loadS3I(SafeReader& reader, std::vector& ret, String& stripPath); void loadSBI(SafeReader& reader, std::vector& ret, String& stripPath); + void loadOPLI(SafeReader& reader, std::vector& ret, String& stripPath); + void loadOPNI(SafeReader& reader, std::vector& ret, String& stripPath); + void loadPAT(SafeReader& reader, std::vector& ret, String& stripPath); + void loadY12(SafeReader& reader, std::vector& ret, String& stripPath); void loadBNK(SafeReader& reader, std::vector& ret, String& stripPath); void loadOPM(SafeReader& reader, std::vector& ret, String& stripPath); void loadFF(SafeReader& reader, std::vector& ret, String& stripPath); + void loadWOPL(SafeReader& reader, std::vector& ret, String& stripPath); + void loadWOPN(SafeReader& reader, std::vector& ret, String& stripPath); bool initAudioBackend(); bool deinitAudioBackend(); diff --git a/src/engine/fileOpsIns.cpp b/src/engine/fileOpsIns.cpp index f9eaab38b..148c9d2e9 100644 --- a/src/engine/fileOpsIns.cpp +++ b/src/engine/fileOpsIns.cpp @@ -30,9 +30,15 @@ enum DivInsFormats { DIV_INSFORMAT_BTI, DIV_INSFORMAT_S3I, DIV_INSFORMAT_SBI, + DIV_INSFORMAT_PAT, + DIV_INSFORMAT_Y12, + DIV_INSFORMAT_OPLI, + DIV_INSFORMAT_OPNI, DIV_INSFORMAT_BNK, DIV_INSFORMAT_OPM, DIV_INSFORMAT_FF, + DIV_INSFORMAT_WOPL, + DIV_INSFORMAT_WOPN, }; // Patch data structures @@ -670,6 +676,7 @@ void DivEngine::loadBNK(SafeReader& reader, std::vector& ret, St auto& ins = insList[i]; ins->type = DIV_INS_OPL; + ins->fm.ops = 2; timbre.mode = reader.readC(); timbre.percVoice = reader.readC(); @@ -810,16 +817,132 @@ void DivEngine::loadFF(SafeReader& reader, std::vector& ret, Str } void DivEngine::loadOPM(SafeReader& reader, std::vector& ret, String& stripPath) { - DivInstrument* ins[128]; - memset(ins,0,128*sizeof(void*)); + std::vector insList; + std::stringstream ss; + + int readCount = 0; + + bool patchNameRead = false, + lfoRead = false, + characteristicRead = false, + m1Read = false, + c1Read = false, + m2Read = false, + c2Read = false; + + auto completePatchRead = [&]() { + return patchNameRead && lfoRead && characteristicRead && m1Read && c1Read && m2Read && c2Read; + }; + auto resetPatchRead = [&]() { + patchNameRead = lfoRead = characteristicRead = m1Read = c1Read = m2Read = c2Read = false; + }; + auto readOpmOperator = [](SafeReader& reader, DivInstrumentFM::Operator& op) { + op.ar = atoi(reader.readString_Token().c_str()); + op.dr = atoi(reader.readString_Token().c_str()); + op.d2r = atoi(reader.readString_Token().c_str()); + op.rr = atoi(reader.readString_Token().c_str()); + op.sl = atoi(reader.readString_Token().c_str()); + op.tl = atoi(reader.readString_Token().c_str()); + op.ksl = atoi(reader.readString_Token().c_str()); + op.mult = atoi(reader.readString_Token().c_str()); + op.dt = atoi(reader.readString_Token().c_str()); + op.dt2 = atoi(reader.readString_Token().c_str()); + op.ssgEnv = atoi(reader.readString_Token().c_str()); + }; + + DivInstrument* newPatch = nullptr; try { - String line; - + reader.seek(0, SEEK_SET); + while (!reader.isEOF()) { + String token = reader.readString_Token(); + if (token.length() == 0) { + continue; + } + + if (token.compare(0,2,"//") == 0) { + if (!reader.isEOF()) { + reader.readString_Line(); + } + continue; + } + + // At this point we know any other line would be associated with patch params + if (newPatch == nullptr) { + 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 order but they + // must absolutely be properly grouped per patch! Line prefixes must be separated by a space! + + // Patch number + name + // "@:123 Name of patch" + if (token.length() >= 2) { + if (token[0] == '@') { + newPatch->name = reader.readString_Line(); + patchNameRead = true; + } else if (token.compare(0,3,"CH:") == 0) { + // CH: PAN FL CON AMS PMS SLOT NE + reader.readString_Token(); // skip PAN + newPatch->fm.fb = atoi(reader.readString_Token().c_str()); + newPatch->fm.alg = atoi(reader.readString_Token().c_str()); + newPatch->fm.ams = atoi(reader.readString_Token().c_str()); + newPatch->fm.ams2 = atoi(reader.readString_Token().c_str()); + reader.readString_Token(); // skip SLOT + reader.readString_Token(); // 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 this as these are chip-global. + reader.readString_Line(); + lfoRead = true; + } else { + // other unsupported lines ignored. + reader.readString_Line(); + } + } + + if (completePatchRead()) { + insList.push_back(newPatch); + newPatch = nullptr; + ++readCount; + } + } + + if (newPatch != nullptr) { + 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) { - lastError="premature end of file"; + lastError = "premature end of file"; logE("premature end of file"); - return; + for (int i = readCount; i >= 0; --i) { + delete insList[i]; + } + delete newPatch; } } @@ -947,12 +1070,24 @@ std::vector DivEngine::instrumentFromFile(const char* path) { format=DIV_INSFORMAT_S3I; } else if (extS==String(".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(".pat")) { + format=DIV_INSFORMAT_PAT; + } else if (extS==String(".y12")) { + format=DIV_INSFORMAT_Y12; } else if (extS==String(".bnk")) { format=DIV_INSFORMAT_BNK; } else if (extS==String(".opm")) { format=DIV_INSFORMAT_OPM; } else if (extS==String(".ff")) { format=DIV_INSFORMAT_FF; + } else if (extS==String(".wopl")) { + format=DIV_INSFORMAT_WOPL; + } else if (extS==String(".wopn")) { + format=DIV_INSFORMAT_WOPN; } } @@ -971,7 +1106,8 @@ std::vector DivEngine::instrumentFromFile(const char* path) { break; case DIV_INSFORMAT_BTI: // TODO break; - case DIV_INSFORMAT_OPM: // TODO + case DIV_INSFORMAT_OPM: + loadOPM(reader,ret,stripPath); break; case DIV_INSFORMAT_S3I: loadS3I(reader,ret,stripPath); diff --git a/src/engine/safeReader.cpp b/src/engine/safeReader.cpp index 7fa66e8b3..e46827c68 100644 --- a/src/engine/safeReader.cpp +++ b/src/engine/safeReader.cpp @@ -139,12 +139,14 @@ double SafeReader::readD() { } String SafeReader::readString(size_t stlen) { - String ret; + String ret(stlen, ' '); #ifdef READ_DEBUG logD("SR: reading string len %d at %x",stlen,curSeek); #endif size_t curPos=0; - while (curPos= len; +} \ No newline at end of file diff --git a/src/engine/safeReader.h b/src/engine/safeReader.h index f674f5922..9fd0282b3 100644 --- a/src/engine/safeReader.h +++ b/src/engine/safeReader.h @@ -66,6 +66,9 @@ class SafeReader { double readD_BE(); String readString(); String readString(size_t len); + String readString_Line(); + String readString_Token(unsigned char delim=' '); + bool isEOF(); SafeReader(void* b, size_t l): buf((unsigned char*)b), From 3550ad512a83bdd1c420056838905098fdd3a2fa Mon Sep 17 00:00:00 2001 From: James Alan Nguyen Date: Sat, 23 Apr 2022 20:20:29 +1000 Subject: [PATCH 02/15] OPM import: Fix reset read procedural flow --- src/engine/fileOpsIns.cpp | 7 ++++--- src/gui/gui.cpp | 4 ++-- src/gui/settings.cpp | 1 + 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/engine/fileOpsIns.cpp b/src/engine/fileOpsIns.cpp index 148c9d2e9..b74c7cba3 100644 --- a/src/engine/fileOpsIns.cpp +++ b/src/engine/fileOpsIns.cpp @@ -829,12 +829,15 @@ void DivEngine::loadOPM(SafeReader& reader, std::vector& ret, St c1Read = false, m2Read = false, c2Read = false; + + DivInstrument* newPatch = nullptr; auto completePatchRead = [&]() { return patchNameRead && lfoRead && characteristicRead && m1Read && c1Read && m2Read && c2Read; }; auto resetPatchRead = [&]() { patchNameRead = lfoRead = characteristicRead = m1Read = c1Read = m2Read = c2Read = false; + newPatch = nullptr; }; auto readOpmOperator = [](SafeReader& reader, DivInstrumentFM::Operator& op) { op.ar = atoi(reader.readString_Token().c_str()); @@ -850,8 +853,6 @@ void DivEngine::loadOPM(SafeReader& reader, std::vector& ret, St op.ssgEnv = atoi(reader.readString_Token().c_str()); }; - DivInstrument* newPatch = nullptr; - try { reader.seek(0, SEEK_SET); while (!reader.isEOF()) { @@ -922,7 +923,7 @@ void DivEngine::loadOPM(SafeReader& reader, std::vector& ret, St if (completePatchRead()) { insList.push_back(newPatch); - newPatch = nullptr; + resetPatchRead(); ++readCount; } } diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index 009952a63..d3218b30c 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -1290,9 +1290,9 @@ void FurnaceGUI::openFileDialog(FurnaceGUIFileDialogs type) { if (!dirExists(workingDirIns)) workingDirIns=getHomeDir(); hasOpened=fileDialog->openLoad( "Load Instrument", - {"compatible files", "*.fui *.dmp *.tfi *.vgi *.s3i *.sbi *.bnk *.ff", + {"compatible files", "*.fui *.dmp *.tfi *.vgi *.s3i *.sbi *.bnk *.ff *.opm", "all files", ".*"}, - "compatible files{.fui,.dmp,.tfi,.vgi,.s3i,.sbi,.bnk,.ff},.*", + "compatible files{.fui,.dmp,.tfi,.vgi,.s3i,.sbi,.bnk,.ff,.opm},.*", workingDirIns, dpiScale ); diff --git a/src/gui/settings.cpp b/src/gui/settings.cpp index f1cd27a36..f1565b8ee 100644 --- a/src/gui/settings.cpp +++ b/src/gui/settings.cpp @@ -2489,6 +2489,7 @@ void FurnaceGUI::applyUISettings(bool updateFonts) { 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,".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 (fileDialog!=NULL) delete fileDialog; From 8ad827478c20cd7858cb77f797231b802babf665 Mon Sep 17 00:00:00 2001 From: James Alan Nguyen Date: Sat, 23 Apr 2022 21:48:18 +1000 Subject: [PATCH 03/15] #79: OPM complete for now --- src/engine/fileOpsIns.cpp | 47 ++++++++++++++++++++++----------------- 1 file changed, 27 insertions(+), 20 deletions(-) diff --git a/src/engine/fileOpsIns.cpp b/src/engine/fileOpsIns.cpp index b74c7cba3..a8b2b652c 100644 --- a/src/engine/fileOpsIns.cpp +++ b/src/engine/fileOpsIns.cpp @@ -839,18 +839,23 @@ void DivEngine::loadOPM(SafeReader& reader, std::vector& ret, St patchNameRead = lfoRead = characteristicRead = m1Read = c1Read = m2Read = c2Read = false; newPatch = nullptr; }; - auto readOpmOperator = [](SafeReader& reader, DivInstrumentFM::Operator& op) { - op.ar = atoi(reader.readString_Token().c_str()); - op.dr = atoi(reader.readString_Token().c_str()); - op.d2r = atoi(reader.readString_Token().c_str()); - op.rr = atoi(reader.readString_Token().c_str()); - op.sl = atoi(reader.readString_Token().c_str()); - op.tl = atoi(reader.readString_Token().c_str()); - op.ksl = atoi(reader.readString_Token().c_str()); - op.mult = atoi(reader.readString_Token().c_str()); - op.dt = atoi(reader.readString_Token().c_str()); - op.dt2 = atoi(reader.readString_Token().c_str()); - op.ssgEnv = atoi(reader.readString_Token().c_str()); + auto readIntStrWithinRange = [](String& input, int limitLow, int limitHigh) { + int x = atoi(input.c_str()); + return (x>limitHigh) ? limitHigh : + (x& ret, St newPatch->fm.ops = 4; } - // Read each line for their respective params. They may not be written in the same order but they + // 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! - // Patch number + name - // "@:123 Name of patch" if (token.length() >= 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.readString_Line(); + newPatch->name = newPatch->name.length() > 0 ? newPatch->name : fmt::format("{0}[{1}]", stripPath, readCount); + patchNameRead = true; } else if (token.compare(0,3,"CH:") == 0) { // CH: PAN FL CON AMS PMS SLOT NE reader.readString_Token(); // skip PAN - newPatch->fm.fb = atoi(reader.readString_Token().c_str()); - newPatch->fm.alg = atoi(reader.readString_Token().c_str()); - newPatch->fm.ams = atoi(reader.readString_Token().c_str()); - newPatch->fm.ams2 = atoi(reader.readString_Token().c_str()); + newPatch->fm.fb = readIntStrWithinRange(reader.readString_Token(), 0, 7); + newPatch->fm.alg = readIntStrWithinRange(reader.readString_Token(), 0, 7); + newPatch->fm.ams = readIntStrWithinRange(reader.readString_Token(), 0, 4); + newPatch->fm.fms = readIntStrWithinRange(reader.readString_Token(), 0, 7); reader.readString_Token(); // skip SLOT reader.readString_Token(); // skip NE characteristicRead = true; @@ -912,7 +919,7 @@ void DivEngine::loadOPM(SafeReader& reader, std::vector& ret, St m2Read = true; } else if (token.compare(0,4,"LFO:") == 0) { // LFO: LFRQ AMD PMD WF NFRQ - // Furnace patches do not store this as these are chip-global. + // Furnace patches do not store these as these are chip-global. reader.readString_Line(); lfoRead = true; } else { From 0f47a3ed7b049208183a6f84067b4a53898e3f47 Mon Sep 17 00:00:00 2001 From: James Alan Nguyen Date: Sat, 23 Apr 2022 23:40:58 +1000 Subject: [PATCH 04/15] Fix DT range --- src/engine/fileOpsIns.cpp | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/src/engine/fileOpsIns.cpp b/src/engine/fileOpsIns.cpp index a8b2b652c..8b43f65ef 100644 --- a/src/engine/fileOpsIns.cpp +++ b/src/engine/fileOpsIns.cpp @@ -640,6 +640,7 @@ void DivEngine::loadSBI(SafeReader& reader, std::vector& ret, St delete ins; } } + void DivEngine::loadBNK(SafeReader& reader, std::vector& ret, String& stripPath) { std::vector insList; std::vector instNames; @@ -853,7 +854,8 @@ void DivEngine::loadOPM(SafeReader& reader, std::vector& ret, St op.tl = readIntStrWithinRange(reader.readString_Token(), 0, 127); op.rs = readIntStrWithinRange(reader.readString_Token(), 0, 3);; op.mult = readIntStrWithinRange(reader.readString_Token(), 0, 15); - op.dt = readIntStrWithinRange(reader.readString_Token(), -3, 4); + op.dt = readIntStrWithinRange(reader.readString_Token(), 0, 7); + op.dt = (op.dt >= 4) ? (7 - op.dt) : (op.dt + 3); op.dt2 = readIntStrWithinRange(reader.readString_Token(), 0, 3); op.am = readIntStrWithinRange(reader.readString_Token(), 0, 1); }; @@ -881,7 +883,7 @@ void DivEngine::loadOPM(SafeReader& reader, std::vector& ret, St } // 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! + // must absolutely be properly grouped per patch! Line prefixes must be separated by a space! (see inline comments) if (token.length() >= 2) { if (token[0] == '@') { @@ -889,8 +891,8 @@ void DivEngine::loadOPM(SafeReader& reader, std::vector& ret, St // Note: Fallback to bank filename and current patch number in _file_ order (not @n order) newPatch->name = reader.readString_Line(); newPatch->name = newPatch->name.length() > 0 ? newPatch->name : fmt::format("{0}[{1}]", stripPath, readCount); - patchNameRead = true; + } else if (token.compare(0,3,"CH:") == 0) { // CH: PAN FL CON AMS PMS SLOT NE reader.readString_Token(); // skip PAN @@ -901,27 +903,33 @@ void DivEngine::loadOPM(SafeReader& reader, std::vector& ret, St reader.readString_Token(); // skip SLOT reader.readString_Token(); // 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 these are chip-global. + // Furnace patches do not store these as they are chip-global. reader.readString_Line(); lfoRead = true; + } else { // other unsupported lines ignored. reader.readString_Line(); @@ -950,7 +958,9 @@ void DivEngine::loadOPM(SafeReader& reader, std::vector& ret, St for (int i = readCount; i >= 0; --i) { delete insList[i]; } - delete newPatch; + if (newPatch != nullptr) { + delete newPatch; + } } } From b8d9fab7455963f675f10ce223ad665a07841066 Mon Sep 17 00:00:00 2001 From: James Alan Nguyen Date: Sun, 24 Apr 2022 00:29:33 +1000 Subject: [PATCH 05/15] Start .y12 (Gens KMod/Kaneda) patch dump import --- src/engine/fileOpsIns.cpp | 48 ++++++++++++++++++++++++++++++++++++--- 1 file changed, 45 insertions(+), 3 deletions(-) diff --git a/src/engine/fileOpsIns.cpp b/src/engine/fileOpsIns.cpp index 8b43f65ef..ddf9a1df7 100644 --- a/src/engine/fileOpsIns.cpp +++ b/src/engine/fileOpsIns.cpp @@ -641,6 +641,45 @@ void DivEngine::loadSBI(SafeReader& reader, std::vector& ret, St } } +void DivEngine::loadY12(SafeReader& reader, std::vector& 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() & 0xF); + 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(); + reader.seek(9, SEEK_CUR); + } + ins->fm.alg = reader.readC(); + ins->fm.fb = reader.readC(); + reader.seek(14, SEEK_CUR); + 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& ret, String& stripPath) { std::vector insList; std::vector instNames; @@ -1124,21 +1163,24 @@ std::vector DivEngine::instrumentFromFile(const char* path) { break; case DIV_INSFORMAT_BTI: // TODO break; - case DIV_INSFORMAT_OPM: - loadOPM(reader,ret,stripPath); - break; case DIV_INSFORMAT_S3I: loadS3I(reader,ret,stripPath); break; case DIV_INSFORMAT_SBI: loadSBI(reader,ret,stripPath); break; + case DIV_INSFORMAT_Y12: + loadY12(reader,ret,stripPath); + break; case DIV_INSFORMAT_BNK: loadBNK(reader, ret, stripPath); break; case DIV_INSFORMAT_FF: loadFF(reader,ret,stripPath); break; + case DIV_INSFORMAT_OPM: + loadOPM(reader, ret, stripPath); + break; } if (reader.tell() Date: Sun, 24 Apr 2022 00:45:19 +1000 Subject: [PATCH 06/15] #79: .y12 import done --- src/engine/fileOpsIns.cpp | 6 +++--- src/gui/gui.cpp | 4 ++-- src/gui/settings.cpp | 1 + 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/engine/fileOpsIns.cpp b/src/engine/fileOpsIns.cpp index ddf9a1df7..602fbba55 100644 --- a/src/engine/fileOpsIns.cpp +++ b/src/engine/fileOpsIns.cpp @@ -650,12 +650,12 @@ void DivEngine::loadY12(SafeReader& reader, std::vector& ret, St ins->fm.ops = 4; ins->name = stripPath; - for (int i = 0; i < 4; ++i) { + for (int i : {0,1,2,3}) { 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() & 0xF); + insOp.tl = (reader.readC() & 0x3F); tmp = reader.readC(); insOp.rs = ((tmp >> 6) & 0x3); insOp.ar = (tmp & 0x1F); @@ -671,7 +671,7 @@ void DivEngine::loadY12(SafeReader& reader, std::vector& ret, St } ins->fm.alg = reader.readC(); ins->fm.fb = reader.readC(); - reader.seek(14, SEEK_CUR); + reader.seek(62, SEEK_CUR); ret.push_back(ins); } catch (EndOfFileException& e) { lastError = "premature end of file"; diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index d3218b30c..47b81f6eb 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -1290,9 +1290,9 @@ void FurnaceGUI::openFileDialog(FurnaceGUIFileDialogs type) { if (!dirExists(workingDirIns)) workingDirIns=getHomeDir(); hasOpened=fileDialog->openLoad( "Load Instrument", - {"compatible files", "*.fui *.dmp *.tfi *.vgi *.s3i *.sbi *.bnk *.ff *.opm", + {"compatible files", "*.fui *.dmp *.tfi *.vgi *.s3i *.sbi *.y12 *.bnk *.ff *.opm", "all files", ".*"}, - "compatible files{.fui,.dmp,.tfi,.vgi,.s3i,.sbi,.bnk,.ff,.opm},.*", + "compatible files{.fui,.dmp,.tfi,.vgi,.s3i,.sbi,.y12,.bnk,.ff,.opm},.*", workingDirIns, dpiScale ); diff --git a/src/gui/settings.cpp b/src/gui/settings.cpp index f1565b8ee..5ed9cf0ad 100644 --- a/src/gui/settings.cpp +++ b/src/gui/settings.cpp @@ -2485,6 +2485,7 @@ void FurnaceGUI::applyUISettings(bool updateFonts) { 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,".sbi",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,".fti",uiColors[GUI_COLOR_FILE_INSTR],ICON_FA_FILE); ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByExtension,".bti",uiColors[GUI_COLOR_FILE_INSTR],ICON_FA_FILE); From 6bd199923f042bb1d9882fec6869eab7bf2112ae Mon Sep 17 00:00:00 2001 From: James Alan Nguyen Date: Sun, 24 Apr 2022 02:37:25 +1000 Subject: [PATCH 07/15] OPLI progress... still figuring out correct readings --- src/engine/engine.h | 3 - src/engine/fileOpsIns.cpp | 126 +++++++++++++++++++++++++++++++++----- src/engine/safeReader.cpp | 2 +- src/gui/settings.cpp | 2 + 4 files changed, 114 insertions(+), 19 deletions(-) diff --git a/src/engine/engine.h b/src/engine/engine.h index c8afb86a9..5e840362e 100644 --- a/src/engine/engine.h +++ b/src/engine/engine.h @@ -291,13 +291,10 @@ class DivEngine { void loadSBI(SafeReader& reader, std::vector& ret, String& stripPath); void loadOPLI(SafeReader& reader, std::vector& ret, String& stripPath); void loadOPNI(SafeReader& reader, std::vector& ret, String& stripPath); - void loadPAT(SafeReader& reader, std::vector& ret, String& stripPath); void loadY12(SafeReader& reader, std::vector& ret, String& stripPath); void loadBNK(SafeReader& reader, std::vector& ret, String& stripPath); void loadOPM(SafeReader& reader, std::vector& ret, String& stripPath); void loadFF(SafeReader& reader, std::vector& ret, String& stripPath); - void loadWOPL(SafeReader& reader, std::vector& ret, String& stripPath); - void loadWOPN(SafeReader& reader, std::vector& ret, String& stripPath); bool initAudioBackend(); bool deinitAudioBackend(); diff --git a/src/engine/fileOpsIns.cpp b/src/engine/fileOpsIns.cpp index 602fbba55..87233ef63 100644 --- a/src/engine/fileOpsIns.cpp +++ b/src/engine/fileOpsIns.cpp @@ -30,15 +30,12 @@ enum DivInsFormats { DIV_INSFORMAT_BTI, DIV_INSFORMAT_S3I, DIV_INSFORMAT_SBI, - DIV_INSFORMAT_PAT, DIV_INSFORMAT_Y12, DIV_INSFORMAT_OPLI, DIV_INSFORMAT_OPNI, DIV_INSFORMAT_BNK, DIV_INSFORMAT_OPM, DIV_INSFORMAT_FF, - DIV_INSFORMAT_WOPL, - DIV_INSFORMAT_WOPN, }; // Patch data structures @@ -641,6 +638,105 @@ void DivEngine::loadSBI(SafeReader& reader, std::vector& ret, St } } +void DivEngine::loadOPLI(SafeReader& reader, std::vector& ret, String& stripPath) { + DivInstrument* ins = new DivInstrument; + + try { + reader.seek(0, SEEK_SET); + String header = reader.readString(11); + if (header.compare("WOPL3-INST") == 0) { + uint16_t version = reader.readS(); + bool isPerc = (reader.readC() == 1); + + ins->type = DIV_INS_OPL; + String insName = reader.readString(32); + insName = (insName.length() > 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_2op = ((instTypeFlags & 0x1) > 0); + bool is_4op = (((instTypeFlags>>1) & 0x1) > 0); + bool is_2x2op = (((instTypeFlags>>2) & 0x1) > 0); + 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) { + ins->fm.ops = 4; + ins->fm.alg = (feedConnect & 0x1) | ((feedConnect2nd & 0x1) << 1); + for (int i : {0,2,1,3}) { + readOpliOp(reader, ins->fm.op[i]); + } + } else { + ins->fm.ops = 2; + for (int i = 0; i < 2; ++i) { + readOpliOp(reader, ins->fm.op[i]); + } + if (is_rhythm) { + ins->fm.opllPreset = (uint8_t)(1<<4); + + } else if (is_2x2op) { + ins->name = fmt::format("{0} (1)", insName); + ret.push_back(ins); + + ins = new DivInstrument; + ins->type = DIV_INS_OPL; + ins->name = fmt::format("{0} (2)", insName); + for (int i = 0; i < 2; ++i) { + 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& ret, String& stripPath) { + DivInstrument* ins = new DivInstrument; + + try { + reader.seek(0, SEEK_SET); + } catch (EndOfFileException& e) { + lastError = "premature end of file"; + logE("premature end of file"); + delete ins; + } +} + void DivEngine::loadY12(SafeReader& reader, std::vector& ret, String& stripPath) { DivInstrument *ins = new DivInstrument; @@ -653,18 +749,18 @@ void DivEngine::loadY12(SafeReader& reader, std::vector& ret, St for (int i : {0,1,2,3}) { DivInstrumentFM::Operator& insOp = ins->fm.op[i]; uint8_t tmp = reader.readC(); - insOp.mult = (tmp & 0xF); + 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); + insOp.ar = tmp & 0x1F; tmp = reader.readC(); - insOp.dr = (tmp & 0x1F); + insOp.dr = tmp & 0x1F; insOp.am = ((tmp >> 7) & 0x1); insOp.d2r = (reader.readC() & 0x1F); tmp = reader.readC(); - insOp.rr = (tmp & 0xF); + insOp.rr = tmp & 0xF; insOp.sl = ((tmp >> 4) & 0xF); insOp.ssgEnv = reader.readC(); reader.seek(9, SEEK_CUR); @@ -884,7 +980,7 @@ void DivEngine::loadOPM(SafeReader& reader, std::vector& ret, St return (x>limitHigh) ? limitHigh : (x DivEngine::instrumentFromFile(const char* path) { } else if (extS==String(".opli")) { format=DIV_INSFORMAT_OPLI; } else if (extS==String(".opni")) { - format=DIV_INSFORMAT_OPNI; - } else if (extS==String(".pat")) { - format=DIV_INSFORMAT_PAT; + format=DIV_INSFORMAT_OPNI;; } else if (extS==String(".y12")) { format=DIV_INSFORMAT_Y12; } else if (extS==String(".bnk")) { @@ -1141,10 +1235,6 @@ std::vector DivEngine::instrumentFromFile(const char* path) { format=DIV_INSFORMAT_OPM; } else if (extS==String(".ff")) { format=DIV_INSFORMAT_FF; - } else if (extS==String(".wopl")) { - format=DIV_INSFORMAT_WOPL; - } else if (extS==String(".wopn")) { - format=DIV_INSFORMAT_WOPN; } } @@ -1169,6 +1259,12 @@ std::vector DivEngine::instrumentFromFile(const char* path) { case DIV_INSFORMAT_SBI: loadSBI(reader,ret,stripPath); 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; diff --git a/src/engine/safeReader.cpp b/src/engine/safeReader.cpp index e46827c68..97031b2be 100644 --- a/src/engine/safeReader.cpp +++ b/src/engine/safeReader.cpp @@ -139,7 +139,7 @@ double SafeReader::readD() { } String SafeReader::readString(size_t stlen) { - String ret(stlen, ' '); + String ret; #ifdef READ_DEBUG logD("SR: reading string len %d at %x",stlen,curSeek); #endif diff --git a/src/gui/settings.cpp b/src/gui/settings.cpp index 5ed9cf0ad..c7a235113 100644 --- a/src/gui/settings.cpp +++ b/src/gui/settings.cpp @@ -2485,6 +2485,8 @@ void FurnaceGUI::applyUISettings(bool updateFonts) { 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,".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,".fti",uiColors[GUI_COLOR_FILE_INSTR],ICON_FA_FILE); From bb0253d82fc3a8f90568adead3cd93625e747d95 Mon Sep 17 00:00:00 2001 From: James Alan Nguyen Date: Sun, 24 Apr 2022 03:15:20 +1000 Subject: [PATCH 08/15] Urgh why are your operators BACKWARDS FFS --- src/engine/fileOpsIns.cpp | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/engine/fileOpsIns.cpp b/src/engine/fileOpsIns.cpp index 87233ef63..d864bda5a 100644 --- a/src/engine/fileOpsIns.cpp +++ b/src/engine/fileOpsIns.cpp @@ -655,12 +655,12 @@ void DivEngine::loadOPLI(SafeReader& reader, std::vector& ret, S reader.seek(7, SEEK_CUR); // skip MIDI params uint8_t instTypeFlags = reader.readC(); // [0EEEDCBA] - see WOPL/OPLI spec - bool is_2op = ((instTypeFlags & 0x1) > 0); - bool is_4op = (((instTypeFlags>>1) & 0x1) > 0); - bool is_2x2op = (((instTypeFlags>>2) & 0x1) > 0); + bool is_2op = ((instTypeFlags & 0x3) == 0); + 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) { + auto readOpliOp = [](SafeReader& reader, DivInstrumentFM::Operator& op) { uint8_t characteristics = reader.readC(); uint8_t keyScaleLevel = reader.readC(); uint8_t attackDecay = reader.readC(); @@ -687,15 +687,15 @@ void DivEngine::loadOPLI(SafeReader& reader, std::vector& ret, S ins->fm.alg = (feedConnect & 0x1); ins->fm.fb = ((feedConnect >> 1) & 0xF); - if (is_4op) { + if (is_4op && !is_2x2op) { ins->fm.ops = 4; ins->fm.alg = (feedConnect & 0x1) | ((feedConnect2nd & 0x1) << 1); - for (int i : {0,2,1,3}) { + for (int i : {2,0,3,1}) { readOpliOp(reader, ins->fm.op[i]); } } else { ins->fm.ops = 2; - for (int i = 0; i < 2; ++i) { + for (int i : {1,0}) { readOpliOp(reader, ins->fm.op[i]); } if (is_rhythm) { @@ -708,7 +708,7 @@ void DivEngine::loadOPLI(SafeReader& reader, std::vector& ret, S ins = new DivInstrument; ins->type = DIV_INS_OPL; ins->name = fmt::format("{0} (2)", insName); - for (int i = 0; i < 2; ++i) { + for (int i : {1,0}) { readOpliOp(reader, ins->fm.op[i]); } } From 3865e3eac60fe6d5acc03afda5af6a544ca24b52 Mon Sep 17 00:00:00 2001 From: James Alan Nguyen Date: Sun, 24 Apr 2022 03:49:01 +1000 Subject: [PATCH 09/15] #79: OPNI support added...... --- src/engine/fileOpsIns.cpp | 52 +++++++++++++++++++++++++++++++++++++++ src/gui/gui.cpp | 4 +-- 2 files changed, 54 insertions(+), 2 deletions(-) diff --git a/src/engine/fileOpsIns.cpp b/src/engine/fileOpsIns.cpp index d864bda5a..d541a9df2 100644 --- a/src/engine/fileOpsIns.cpp +++ b/src/engine/fileOpsIns.cpp @@ -702,6 +702,7 @@ void DivEngine::loadOPLI(SafeReader& reader, std::vector& ret, S ins->fm.opllPreset = (uint8_t)(1<<4); } else if (is_2x2op) { + // Note: Pair detuning offset not mappable. Use E5xx effect :P ins->name = fmt::format("{0} (1)", insName); ret.push_back(ins); @@ -730,6 +731,57 @@ void DivEngine::loadOPNI(SafeReader& reader, std::vector& ret, S try { reader.seek(0, SEEK_SET); + + String header = reader.readString(11); + if (header.compare("WOPN2-INST") == 0 || header.compare("WOPN2-IN2T") == 0) { // omfg + uint16_t version = reader.readS(); + if (!(version >= 2) || version > 0xF) { + // version 1 doesn't have a version field........ + reader.seek(-2, SEEK_CUR); + } + + bool is_perc = (reader.readC() == 0x1); + ins->type = DIV_INS_FM; + ins->fm.ops = 4; + + String insName = reader.readString(32); + ins->name = (insName.length() > 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,1,2,3}) { + 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"); diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index 47b81f6eb..4d6f10896 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -1290,9 +1290,9 @@ void FurnaceGUI::openFileDialog(FurnaceGUIFileDialogs type) { if (!dirExists(workingDirIns)) workingDirIns=getHomeDir(); hasOpened=fileDialog->openLoad( "Load Instrument", - {"compatible files", "*.fui *.dmp *.tfi *.vgi *.s3i *.sbi *.y12 *.bnk *.ff *.opm", + {"compatible files", "*.fui *.dmp *.tfi *.vgi *.s3i *.sbi *.opli *.opni *.y12 *.bnk *.ff *.opm", "all files", ".*"}, - "compatible files{.fui,.dmp,.tfi,.vgi,.s3i,.sbi,.y12,.bnk,.ff,.opm},.*", + "compatible files{.fui,.dmp,.tfi,.vgi,.s3i,.sbi,.opli,.opni,.y12,.bnk,.ff,.opm},.*", workingDirIns, dpiScale ); From 6638941c9dfd0c3c98c15b971dfa2377cbfc8ce8 Mon Sep 17 00:00:00 2001 From: James Alan Nguyen Date: Sun, 24 Apr 2022 04:34:32 +1000 Subject: [PATCH 10/15] Address unused stuff and CI fix --- src/engine/engine.h | 1 - src/engine/fileOpsIns.cpp | 12 +++++++----- src/engine/safeReader.cpp | 5 +++++ src/engine/safeReader.h | 3 ++- 4 files changed, 14 insertions(+), 7 deletions(-) diff --git a/src/engine/engine.h b/src/engine/engine.h index 5e840362e..b76e271e2 100644 --- a/src/engine/engine.h +++ b/src/engine/engine.h @@ -31,7 +31,6 @@ #include #include #include -#include #define addWarning(x) \ if (warnings.empty()) { \ diff --git a/src/engine/fileOpsIns.cpp b/src/engine/fileOpsIns.cpp index d541a9df2..d913d96d4 100644 --- a/src/engine/fileOpsIns.cpp +++ b/src/engine/fileOpsIns.cpp @@ -646,7 +646,11 @@ void DivEngine::loadOPLI(SafeReader& reader, std::vector& ret, S String header = reader.readString(11); if (header.compare("WOPL3-INST") == 0) { uint16_t version = reader.readS(); - bool isPerc = (reader.readC() == 1); + if (version > 3) { + logW("Unknown OPLI version."); + } + + reader.readC(); // skip isPerc field ins->type = DIV_INS_OPL; String insName = reader.readString(32); @@ -655,7 +659,6 @@ void DivEngine::loadOPLI(SafeReader& reader, std::vector& ret, S reader.seek(7, SEEK_CUR); // skip MIDI params uint8_t instTypeFlags = reader.readC(); // [0EEEDCBA] - see WOPL/OPLI spec - bool is_2op = ((instTypeFlags & 0x3) == 0); bool is_4op = ((instTypeFlags & 0x1) == 1); bool is_2x2op = (((instTypeFlags>>1) & 0x1) == 1); bool is_rhythm = (((instTypeFlags>>4) & 0x7) > 0); @@ -740,7 +743,7 @@ void DivEngine::loadOPNI(SafeReader& reader, std::vector& ret, S reader.seek(-2, SEEK_CUR); } - bool is_perc = (reader.readC() == 0x1); + reader.readC(); // skip isPerc ins->type = DIV_INS_FM; ins->fm.ops = 4; @@ -1006,7 +1009,6 @@ void DivEngine::loadFF(SafeReader& reader, std::vector& ret, Str void DivEngine::loadOPM(SafeReader& reader, std::vector& ret, String& stripPath) { std::vector insList; - std::stringstream ss; int readCount = 0; @@ -1027,7 +1029,7 @@ void DivEngine::loadOPM(SafeReader& reader, std::vector& ret, St patchNameRead = lfoRead = characteristicRead = m1Read = c1Read = m2Read = c2Read = false; newPatch = nullptr; }; - auto readIntStrWithinRange = [](String& input, int limitLow, int limitHigh) { + auto readIntStrWithinRange = [](String&& input, int limitLow, int limitHigh) { int x = atoi(input.c_str()); return (x>limitHigh) ? limitHigh : (x= len; diff --git a/src/engine/safeReader.h b/src/engine/safeReader.h index 9fd0282b3..4216a5969 100644 --- a/src/engine/safeReader.h +++ b/src/engine/safeReader.h @@ -67,7 +67,8 @@ class SafeReader { String readString(); String readString(size_t len); String readString_Line(); - String readString_Token(unsigned char delim=' '); + String readString_Token(unsigned char delim); + String readString_Token(); bool isEOF(); SafeReader(void* b, size_t l): From 18f7dcc0b0fff37545c51f98080147aa4466a752 Mon Sep 17 00:00:00 2001 From: James Alan Nguyen Date: Sun, 24 Apr 2022 12:31:37 +1000 Subject: [PATCH 11/15] Address review comments --- src/engine/fileOpsIns.cpp | 131 +++++++++++++++++++++----------------- src/engine/safeReader.cpp | 13 ++-- src/engine/safeReader.h | 8 +-- 3 files changed, 82 insertions(+), 70 deletions(-) diff --git a/src/engine/fileOpsIns.cpp b/src/engine/fileOpsIns.cpp index d913d96d4..796fd3ea6 100644 --- a/src/engine/fileOpsIns.cpp +++ b/src/engine/fileOpsIns.cpp @@ -498,11 +498,11 @@ void DivEngine::loadS3I(SafeReader& reader, std::vector& ret, St // Skip more stuff we don't need reader.seek(21, SEEK_CUR); } else { - lastError = "S3I PCM samples currently not supported."; + lastError="S3I PCM samples currently not supported."; logE("S3I PCM samples currently not supported."); } 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(); @@ -511,7 +511,7 @@ void DivEngine::loadS3I(SafeReader& reader, std::vector& ret, St logW("S3I signature invalid."); }; } catch (EndOfFileException& e) { - lastError = "premature end of file"; + lastError="premature end of file"; logE("premature end of file"); delete ins; return; @@ -534,7 +534,7 @@ void DivEngine::loadSBI(SafeReader& reader, std::vector& ret, St // 32-byte null terminated instrument name 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) { opM.mult = sbi.Mcharacteristics & 0xF; @@ -608,7 +608,7 @@ void DivEngine::loadSBI(SafeReader& reader, std::vector& ret, St if (is_6op) { // Freq Monster 801 6op SBIs use a 4+2op layout // 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); readSbiOpData(sbi_op12, reader); @@ -618,7 +618,7 @@ void DivEngine::loadSBI(SafeReader& reader, std::vector& ret, St DivInstrumentFM::Operator& opC6 = ins->fm.op[1]; ins->type = DIV_INS_OPL; ins->fm.ops = 2; - ins->name = fmt::format("{0} (2op)", patchName); + ins->name = fmt::sprintf("%s (2op)", patchName); writeOp(sbi_op12, opM6, opC6); ins->fm.alg = (sbi_op12.FeedConnect & 0x1); ins->fm.fb = ((sbi_op12.FeedConnect >> 1) & 0x7); @@ -632,7 +632,7 @@ void DivEngine::loadSBI(SafeReader& reader, std::vector& ret, St } } catch (EndOfFileException& e) { - lastError = "premature end of file"; + lastError="premature end of file"; logE("premature end of file"); delete ins; } @@ -644,7 +644,7 @@ void DivEngine::loadOPLI(SafeReader& reader, std::vector& ret, S try { reader.seek(0, SEEK_SET); String header = reader.readString(11); - if (header.compare("WOPL3-INST") == 0) { + if (header == "WOPL3-INST") { uint16_t version = reader.readS(); if (version > 3) { logW("Unknown OPLI version."); @@ -654,7 +654,7 @@ void DivEngine::loadOPLI(SafeReader& reader, std::vector& ret, S ins->type = DIV_INS_OPL; String insName = reader.readString(32); - insName = (insName.length() > 0) ? insName : stripPath; + 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 @@ -693,7 +693,7 @@ void DivEngine::loadOPLI(SafeReader& reader, std::vector& ret, S if (is_4op && !is_2x2op) { ins->fm.ops = 4; ins->fm.alg = (feedConnect & 0x1) | ((feedConnect2nd & 0x1) << 1); - for (int i : {2,0,3,1}) { + for (int i : {2,0,3,1}) { // omfg >_< readOpliOp(reader, ins->fm.op[i]); } } else { @@ -706,12 +706,12 @@ void DivEngine::loadOPLI(SafeReader& reader, std::vector& ret, S } else if (is_2x2op) { // Note: Pair detuning offset not mappable. Use E5xx effect :P - ins->name = fmt::format("{0} (1)", insName); + ins->name = fmt::sprintf("%s (1)", insName); ret.push_back(ins); ins = new DivInstrument; ins->type = DIV_INS_OPL; - ins->name = fmt::format("{0} (2)", insName); + ins->name = fmt::sprintf("%s (2)", insName); for (int i : {1,0}) { readOpliOp(reader, ins->fm.op[i]); } @@ -723,7 +723,7 @@ void DivEngine::loadOPLI(SafeReader& reader, std::vector& ret, S ret.push_back(ins); } } catch (EndOfFileException& e) { - lastError = "premature end of file"; + lastError="premature end of file"; logE("premature end of file"); delete ins; } @@ -736,7 +736,7 @@ void DivEngine::loadOPNI(SafeReader& reader, std::vector& ret, S reader.seek(0, SEEK_SET); String header = reader.readString(11); - if (header.compare("WOPN2-INST") == 0 || header.compare("WOPN2-IN2T") == 0) { // omfg + 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........ @@ -748,7 +748,7 @@ void DivEngine::loadOPNI(SafeReader& reader, std::vector& ret, S ins->fm.ops = 4; String insName = reader.readString(32); - ins->name = (insName.length() > 0) ? insName : stripPath; + 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); @@ -777,7 +777,7 @@ void DivEngine::loadOPNI(SafeReader& reader, std::vector& ret, S op.ssgEnv = ssgEg; }; - for (int i : {0,1,2,3}) { + for (int i = 0; i < 4; ++i) { readOpniOp(reader, ins->fm.op[i]); } @@ -786,7 +786,7 @@ void DivEngine::loadOPNI(SafeReader& reader, std::vector& ret, S ret.push_back(ins); } } catch (EndOfFileException& e) { - lastError = "premature end of file"; + lastError="premature end of file"; logE("premature end of file"); delete ins; } @@ -801,7 +801,7 @@ void DivEngine::loadY12(SafeReader& reader, std::vector& ret, St ins->fm.ops = 4; ins->name = stripPath; - for (int i : {0,1,2,3}) { + for (int i; i < 4; ++i) { DivInstrumentFM::Operator& insOp = ins->fm.op[i]; uint8_t tmp = reader.readC(); insOp.mult = tmp & 0xF; @@ -825,7 +825,7 @@ void DivEngine::loadY12(SafeReader& reader, std::vector& ret, St reader.seek(62, SEEK_CUR); ret.push_back(ins); } catch (EndOfFileException& e) { - lastError = "premature end of file"; + lastError="premature end of file"; logE("premature end of file"); delete ins; } @@ -904,14 +904,14 @@ void DivEngine::loadBNK(SafeReader& reader, std::vector& ret, St ins->fm.op[0].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); } catch (EndOfFileException& e) { - lastError = "premature end of file"; + lastError="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]; } is_failed = true; @@ -919,7 +919,7 @@ void DivEngine::loadBNK(SafeReader& reader, std::vector& ret, St } else { // assume GEMS BNK for now. - lastError = "GEMS BNK currently not supported."; + lastError="GEMS BNK currently not supported."; logE("GEMS BNK currently not supported."); } @@ -994,9 +994,9 @@ void DivEngine::loadFF(SafeReader& reader, std::vector& ret, Str ++readCount; } } catch (EndOfFileException& e) { - lastError = "premature end of file"; + lastError="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]; } return; @@ -1011,6 +1011,7 @@ void DivEngine::loadOPM(SafeReader& reader, std::vector& ret, St std::vector insList; int readCount = 0; + bool is_failed = false; bool patchNameRead = false, lfoRead = false, @@ -1020,52 +1021,55 @@ void DivEngine::loadOPM(SafeReader& reader, std::vector& ret, St m2Read = false, c2Read = false; - DivInstrument* newPatch = nullptr; + DivInstrument* newPatch = NULL; auto completePatchRead = [&]() { return patchNameRead && lfoRead && characteristicRead && m1Read && c1Read && m2Read && c2Read; }; auto resetPatchRead = [&]() { patchNameRead = lfoRead = characteristicRead = m1Read = c1Read = m2Read = c2Read = false; - newPatch = nullptr; + newPatch = NULL; }; auto readIntStrWithinRange = [](String&& input, int limitLow, int limitHigh) { - int x = atoi(input.c_str()); + 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= 4) ? (7 - op.dt) : (op.dt + 3); - op.dt2 = readIntStrWithinRange(reader.readString_Token(), 0, 3); - op.am = readIntStrWithinRange(reader.readString_Token(), 0, 1); + op.dt2 = readIntStrWithinRange(reader.readStringToken(), 0, 3); + op.am = readIntStrWithinRange(reader.readStringToken(), 0, 1); }; try { reader.seek(0, SEEK_SET); while (!reader.isEOF()) { - String token = reader.readString_Token(); - if (token.length() == 0) { + String token = reader.readStringToken(); + if (token.size() == 0) { continue; } if (token.compare(0,2,"//") == 0) { if (!reader.isEOF()) { - reader.readString_Line(); + reader.readStringLine(); } continue; } // At this point we know any other line would be associated with patch params - if (newPatch == nullptr) { + if (newPatch == NULL) { newPatch = new DivInstrument; newPatch->type = DIV_INS_FM; newPatch->fm.ops = 4; @@ -1074,23 +1078,23 @@ void DivEngine::loadOPM(SafeReader& reader, std::vector& ret, St // 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.length() >= 2) { + 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.readString_Line(); - newPatch->name = newPatch->name.length() > 0 ? newPatch->name : fmt::format("{0}[{1}]", stripPath, readCount); + 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.readString_Token(); // skip PAN - newPatch->fm.fb = readIntStrWithinRange(reader.readString_Token(), 0, 7); - newPatch->fm.alg = readIntStrWithinRange(reader.readString_Token(), 0, 7); - newPatch->fm.ams = readIntStrWithinRange(reader.readString_Token(), 0, 4); - newPatch->fm.fms = readIntStrWithinRange(reader.readString_Token(), 0, 7); - reader.readString_Token(); // skip SLOT - reader.readString_Token(); // skip 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) { @@ -1116,12 +1120,12 @@ void DivEngine::loadOPM(SafeReader& reader, std::vector& ret, St } 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.readString_Line(); + reader.readStringLine(); lfoRead = true; } else { // other unsupported lines ignored. - reader.readString_Line(); + reader.readStringLine(); } } @@ -1132,7 +1136,7 @@ void DivEngine::loadOPM(SafeReader& reader, std::vector& ret, St } } - if (newPatch != nullptr) { + 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; @@ -1142,12 +1146,21 @@ void DivEngine::loadOPM(SafeReader& reader, std::vector& ret, St ret.push_back(insList[i]); } } catch (EndOfFileException& e) { - lastError = "premature end of file"; + lastError="premature end of file"; logE("premature end of file"); - for (int i = readCount; i >= 0; --i) { + 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 != nullptr) { + if (newPatch != NULL) { delete newPatch; } } diff --git a/src/engine/safeReader.cpp b/src/engine/safeReader.cpp index 9d0da1a96..eb9f63ae1 100644 --- a/src/engine/safeReader.cpp +++ b/src/engine/safeReader.cpp @@ -165,7 +165,7 @@ String SafeReader::readString() { return ret; } -String SafeReader::readString_Line() { +String SafeReader::readStringLine() { String ret; unsigned char c; if (isEOF()) throw EndOfFileException(this, len); @@ -179,7 +179,7 @@ String SafeReader::readString_Line() { return ret; } -String SafeReader::readString_Token(unsigned char delim) { +String SafeReader::readStringToken(unsigned char delim) { String ret; unsigned char c; if (isEOF()) throw EndOfFileException(this, len); @@ -198,12 +198,11 @@ String SafeReader::readString_Token(unsigned char delim) { } return ret; } -String SafeReader::readString_Token() { - return readString_Token(' '); + +String SafeReader::readStringToken() { + return readStringToken(' '); } - - -bool SafeReader::isEOF() { +inline bool SafeReader::isEOF() { return curSeek >= len; } \ No newline at end of file diff --git a/src/engine/safeReader.h b/src/engine/safeReader.h index 4216a5969..5f27383c3 100644 --- a/src/engine/safeReader.h +++ b/src/engine/safeReader.h @@ -66,10 +66,10 @@ class SafeReader { double readD_BE(); String readString(); String readString(size_t len); - String readString_Line(); - String readString_Token(unsigned char delim); - String readString_Token(); - bool isEOF(); + String readStringLine(); + String readStringToken(unsigned char delim); + String readStringToken(); + inline bool isEOF(); SafeReader(void* b, size_t l): buf((unsigned char*)b), From 580cff9d32417684823a0f4537d6958ecdc6a37f Mon Sep 17 00:00:00 2001 From: James Alan Nguyen Date: Sun, 24 Apr 2022 12:37:49 +1000 Subject: [PATCH 12/15] Decided to EOF fail seeks beyond bounds --- src/engine/fileOpsIns.cpp | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/engine/fileOpsIns.cpp b/src/engine/fileOpsIns.cpp index 796fd3ea6..ce03915b0 100644 --- a/src/engine/fileOpsIns.cpp +++ b/src/engine/fileOpsIns.cpp @@ -818,11 +818,15 @@ void DivEngine::loadY12(SafeReader& reader, std::vector& ret, St insOp.rr = tmp & 0xF; insOp.sl = ((tmp >> 4) & 0xF); insOp.ssgEnv = reader.readC(); - reader.seek(9, SEEK_CUR); + if (!reader.seek(9, SEEK_CUR)) { + throw EndOfFileException(&reader, reader.tell() + 9); + } } ins->fm.alg = reader.readC(); ins->fm.fb = reader.readC(); - reader.seek(62, SEEK_CUR); + if (!reader.seek(62, SEEK_CUR)) { + throw EndOfFileException(&reader, reader.tell() + 62); + } ret.push_back(ins); } catch (EndOfFileException& e) { lastError="premature end of file"; @@ -858,7 +862,9 @@ void DivEngine::loadBNK(SafeReader& reader, std::vector& ret, St } // 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 for (int i = 0; i < readCount; ++i) { From 2a48adfde8a9d42cdd8ce23aa08b683ae7ade504 Mon Sep 17 00:00:00 2001 From: James Alan Nguyen Date: Sun, 24 Apr 2022 13:43:56 +1000 Subject: [PATCH 13/15] Inline error --- src/engine/safeReader.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/engine/safeReader.h b/src/engine/safeReader.h index 5f27383c3..e95c90a01 100644 --- a/src/engine/safeReader.h +++ b/src/engine/safeReader.h @@ -69,7 +69,7 @@ class SafeReader { String readStringLine(); String readStringToken(unsigned char delim); String readStringToken(); - inline bool isEOF(); + bool isEOF(); SafeReader(void* b, size_t l): buf((unsigned char*)b), From 2b90bd6c66b82d4589b0bab2b01c1b3bd249b520 Mon Sep 17 00:00:00 2001 From: James Alan Nguyen Date: Sun, 24 Apr 2022 13:55:21 +1000 Subject: [PATCH 14/15] = 0 --- src/engine/fileOpsIns.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/engine/fileOpsIns.cpp b/src/engine/fileOpsIns.cpp index ce03915b0..e080b7797 100644 --- a/src/engine/fileOpsIns.cpp +++ b/src/engine/fileOpsIns.cpp @@ -801,7 +801,7 @@ void DivEngine::loadY12(SafeReader& reader, std::vector& ret, St ins->fm.ops = 4; ins->name = stripPath; - for (int i; i < 4; ++i) { + for (int i = 0; i < 4; ++i) { DivInstrumentFM::Operator& insOp = ins->fm.op[i]; uint8_t tmp = reader.readC(); insOp.mult = tmp & 0xF; From 798bc08431419fe811262be24b5d1d9016d9434a Mon Sep 17 00:00:00 2001 From: James Alan Nguyen Date: Sun, 24 Apr 2022 14:04:56 +1000 Subject: [PATCH 15/15] =?UTF-8?q?verdammte=20compilerschie=C3=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/engine/safeReader.cpp | 4 ---- src/engine/safeReader.h | 2 +- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/src/engine/safeReader.cpp b/src/engine/safeReader.cpp index eb9f63ae1..36d6097d6 100644 --- a/src/engine/safeReader.cpp +++ b/src/engine/safeReader.cpp @@ -202,7 +202,3 @@ String SafeReader::readStringToken(unsigned char delim) { String SafeReader::readStringToken() { return readStringToken(' '); } - -inline bool SafeReader::isEOF() { - return curSeek >= len; -} \ No newline at end of file diff --git a/src/engine/safeReader.h b/src/engine/safeReader.h index e95c90a01..e21311f98 100644 --- a/src/engine/safeReader.h +++ b/src/engine/safeReader.h @@ -69,7 +69,7 @@ class SafeReader { String readStringLine(); String readStringToken(unsigned char delim); String readStringToken(); - bool isEOF(); + inline bool isEOF() { return curSeek >= len; }; SafeReader(void* b, size_t l): buf((unsigned char*)b),