gearmulator

Emulation of classic VA synths of the late 90s/2000s that are based on Motorola 56300 family DSPs
Log | Files | Refs | Submodules | README | LICENSE

commit a03ae96337bbe2332924b3856613089e176644e7
parent b8094452200625521ac9526e5d89361d8615a024
Author: dsp56300 <dsp56300@users.noreply.github.com>
Date:   Wed,  4 Dec 2024 00:06:19 +0100

do not use midi packets to parse & build singles before export, corrupts Q patches that should be exported as-is

Diffstat:
Msource/mqJucePlugin/mqPatchManager.cpp | 35++++++++++++++++++++---------------
Msource/mqLib/mqmiditypes.h | 22+++++++++++++++++++++-
Msource/mqLib/mqstate.cpp | 40++++++++++++++++++++++++++++++++++++++++
Msource/mqLib/mqstate.h | 4++++
Msource/xtJucePlugin/xtPatchManager.cpp | 57+++++++++++++++++++++++++++++++++++++++++----------------
Msource/xtLib/xtMidiTypes.h | 8++++++++
Msource/xtLib/xtState.cpp | 23+++++++++++++++++++----
Msource/xtLib/xtState.h | 9++++++---
8 files changed, 159 insertions(+), 39 deletions(-)

diff --git a/source/mqJucePlugin/mqPatchManager.cpp b/source/mqJucePlugin/mqPatchManager.cpp @@ -3,6 +3,7 @@ #include "mqController.h" #include "mqEditor.h" #include "jucePluginEditorLib/pluginProcessor.h" +#include "mqLib/mqstate.h" namespace mqJucePlugin { @@ -64,15 +65,25 @@ namespace mqJucePlugin pluginLib::patchDB::Data PatchManager::applyModifications(const pluginLib::patchDB::PatchPtr& _patch) const { - pluginLib::MidiPacket::Data data; - pluginLib::MidiPacket::AnyPartParamValues parameterValues; + auto result = _patch->sysex; - if (!m_controller.parseSingle(data, parameterValues, _patch->sysex)) - return _patch->sysex; + if (_patch->sysex.size() != std::tuple_size_v<mqLib::State::Single> && + _patch->sysex.size() != std::tuple_size_v<mqLib::State::SingleQ>) + return result; - // apply name if (!_patch->getName().empty()) - m_controller.setSingleName(parameterValues, _patch->getName()); + mqLib::State::setSingleName(result, _patch->getName()); + + // first set tag is category + const auto& tags = _patch->getTags(pluginLib::patchDB::TagType::Category).getAdded(); + + std::string category; + + if(!tags.empty()) + category = *tags.begin(); + + if (!category.empty()) + mqLib::State::setCategory(result, category); // apply program uint32_t program = 0; @@ -85,16 +96,10 @@ namespace mqJucePlugin program -= bank * 100; } - // apply category - const auto& tags = _patch->getTags(pluginLib::patchDB::TagType::Category).getAdded(); - - if(!tags.empty()) - m_controller.setCategory(parameterValues, *tags.begin()); + result[mqLib::IdxSingleBank] = static_cast<uint8_t>(bank); + result[mqLib::IdxSingleProgram] = static_cast<uint8_t>(program); - return m_controller.createSingleDump( - static_cast<mqLib::MidiBufferNum>(static_cast<uint8_t>(mqLib::MidiBufferNum::DeprecatedSingleBankA) + bank), - static_cast<mqLib::MidiSoundLocation>(static_cast<uint8_t>(mqLib::MidiSoundLocation::AllSinglesBankA) + bank), - static_cast<uint8_t>(program), parameterValues); + return result; } uint32_t PatchManager::getCurrentPart() const diff --git a/source/mqLib/mqmiditypes.h b/source/mqLib/mqmiditypes.h @@ -69,7 +69,25 @@ namespace mqLib EditBufferFirstSingleLayer = 0x00, EditBufferFirstDrumMapInstrument = 0x10, }; - + + namespace mq + { + constexpr uint32_t g_singleNameLength = 16; + constexpr uint32_t g_singleNameOffset = 370; + + constexpr uint32_t g_categoryLength = 4; + constexpr uint32_t g_categoryOffset = 386; + }; + + namespace q + { + constexpr uint32_t g_singleNameLength = 16; + constexpr uint32_t g_singleNameOffset = 371; + + constexpr uint32_t g_categoryLength = 4; + constexpr uint32_t g_categoryOffset = 387; + }; + enum class GlobalParameter : uint8_t { // Global data @@ -175,6 +193,8 @@ namespace mqLib enum SysexIndex { // first parameter of a dump + IdxSingleBank = 5, + IdxSingleProgram = 6, IdxSingleParamFirst = 7, IdxMultiParamFirst = IdxSingleParamFirst, IdxDrumParamFirst = IdxSingleParamFirst, diff --git a/source/mqLib/mqstate.cpp b/source/mqLib/mqstate.cpp @@ -235,6 +235,46 @@ namespace mqLib return loadState(_state); } + bool State::setSingleName(std::vector<uint8_t>& _sysex, const std::string& _name) + { + if (getCommand(_sysex) != SysexCommand::SingleDump) + return false; + + if (_sysex.size() == std::tuple_size_v<Single>) + { + for (size_t i=0; i<mq::g_singleNameLength; ++i) + _sysex[i + mq::g_singleNameOffset] = i >= _name.size() ? ' ' : _name[i]; + return true; + } + if (_sysex.size() == std::tuple_size_v<SingleQ>) + { + for (size_t i=0; i<q::g_singleNameLength; ++i) + _sysex[i + q::g_singleNameOffset] = i >= _name.size() ? ' ' : _name[i]; + return true; + } + return false; + } + + bool State::setCategory(std::vector<uint8_t>& _sysex, const std::string& _name) + { + if (getCommand(_sysex) != SysexCommand::SingleDump) + return false; + + if (_sysex.size() == std::tuple_size_v<Single>) + { + for (size_t i=0; i<mq::g_categoryLength; ++i) + _sysex[i + mq::g_categoryOffset] = i >= _name.size() ? ' ' : _name[i]; + return true; + } + if (_sysex.size() == std::tuple_size_v<SingleQ>) + { + for (size_t i=0; i<q::g_categoryLength; ++i) + _sysex[i + q::g_categoryOffset] = i >= _name.size() ? ' ' : _name[i]; + return true; + } + return false; + } + bool State::parseSingleDump(const SysEx& _data) { Single single; diff --git a/source/mqLib/mqstate.h b/source/mqLib/mqstate.h @@ -4,6 +4,7 @@ #include <vector> #include <cstddef> #include <cstdint> +#include <string> #include "mqmiditypes.h" @@ -89,6 +90,9 @@ namespace mqLib bool getState(std::vector<uint8_t>& _state, synthLib::StateType _type) const; bool setState(const std::vector<uint8_t>& _state, synthLib::StateType _type); + static bool setSingleName(std::vector<uint8_t>& _sysex, const std::string& _name); + static bool setCategory(std::vector<uint8_t>& _sysex, const std::string& _name); + static void createSequencerMultiData(std::vector<uint8_t>& _data); private: diff --git a/source/xtJucePlugin/xtPatchManager.cpp b/source/xtJucePlugin/xtPatchManager.cpp @@ -108,29 +108,54 @@ namespace xtJucePlugin pluginLib::patchDB::Data PatchManager::applyModifications(const pluginLib::patchDB::PatchPtr& _patch) const { - pluginLib::MidiPacket::Data data; - pluginLib::MidiPacket::AnyPartParamValues parameterValues; + auto applyModifications = [&_patch](pluginLib::patchDB::Data& _result) -> bool + { + if (xt::State::getCommand(_patch->sysex) != xt::SysexCommand::SingleDump) + return false; + + const auto dumpSize = _patch->sysex.size(); + + if (dumpSize != std::tuple_size_v<xt::State::Single>) + return false; + + // apply name + if (!_patch->getName().empty()) + xt::State::setSingleName(_result, _patch->getName()); + + // apply program + uint32_t program = 0; + uint32_t bank = 0; + if(_patch->program != pluginLib::patchDB::g_invalidProgram) + { + program = std::clamp(_patch->program, 0u, 299u); - if (!m_controller.parseSingle(data, parameterValues, _patch->sysex)) - return _patch->sysex; + bank = program / 128; + program -= bank * 128; + } + + _result[xt::SysexIndex::IdxSingleBank ] = static_cast<uint8_t>(bank); + _result[xt::SysexIndex::IdxSingleProgram] = static_cast<uint8_t>(program); - // apply name - if (!_patch->getName().empty()) - m_controller.setSingleName(parameterValues, _patch->getName()); + return true; + }; - // apply program - uint32_t program = 0; - uint32_t bank = 0; - if(_patch->program != pluginLib::patchDB::g_invalidProgram) + if (xt::State::getCommand(_patch->sysex) == xt::SysexCommand::SingleDump) { - program = std::clamp(_patch->program, 0u, 299u); + auto result = _patch->sysex; - bank = program / 128; - program -= bank * 128; + if (applyModifications(result)) + return result; + + std::vector<xt::SysEx> dumps; + + if (xt::State::splitCombinedPatch(dumps, _patch->sysex)) + { + if (applyModifications(dumps[0])) + return xt::State::createCombinedPatch(dumps); + } } - const auto sysex = m_controller.createSingleDump(static_cast<xt::LocationH>(static_cast<uint8_t>(xt::LocationH::SingleBankA) + bank), static_cast<uint8_t>(program), parameterValues); - return createCombinedDump(sysex); + return _patch->sysex; } uint32_t PatchManager::getCurrentPart() const diff --git a/source/xtLib/xtMidiTypes.h b/source/xtLib/xtMidiTypes.h @@ -59,6 +59,8 @@ namespace xt enum SysexIndex { // first parameter of a dump + IdxSingleBank = 5, + IdxSingleProgram = 6, IdxSingleParamFirst = 7, IdxSingleChecksumStart = IdxSingleParamFirst, IdxMultiParamFirst = IdxSingleParamFirst, @@ -194,6 +196,12 @@ namespace xt static constexpr uint32_t g_idmPreset = 0x42; }; + namespace mw2 + { + static constexpr uint32_t g_singleNameLength = 16; + static constexpr uint32_t g_singleNamePosition = 247; // in a dump including sysex header + } + namespace wave { static constexpr uint16_t g_romWaveCount = 506; diff --git a/source/xtLib/xtState.cpp b/source/xtLib/xtState.cpp @@ -327,6 +327,19 @@ namespace xt } } + bool State::setSingleName(std::vector<uint8_t>& _sysex, const std::string& _name) + { + if (_sysex.size() != std::tuple_size_v<Single>) + return false; + + if (getCommand(_sysex) != SysexCommand::SingleDump) + return false; + + for (size_t i=0; i<mw2::g_singleNameLength; ++i) + _sysex[i + mw2::g_singleNamePosition] = i >= _name.size() ? ' ' : _name[i]; + return true; + } + TableId State::getWavetableFromSingleDump(const SysEx& _single) { constexpr auto wavetableIndex = IdxSingleParamFirst + static_cast<uint32_t>(SingleParameter::Wavetable); @@ -1104,20 +1117,20 @@ namespace xt return sysex; } - void State::splitCombinedPatch(std::vector<SysEx>& _dumps, const SysEx& _combinedSingle) + bool State::splitCombinedPatch(std::vector<SysEx>& _dumps, const SysEx& _combinedSingle) { if(getCommand(_combinedSingle) != SysexCommand::SingleDump) - return; + return false; constexpr auto singleSize = std::tuple_size_v<Single>; constexpr auto tableSize = std::tuple_size_v<Table>; constexpr auto waveSize = std::tuple_size_v<Wave>; if(_combinedSingle.size() == singleSize) - return; + return false; if(_combinedSingle.size() < singleSize + tableSize - 2) - return; + return false; auto& single = _dumps.emplace_back(); size_t offBegin = 0; @@ -1143,6 +1156,8 @@ namespace xt wave.insert(wave.end(), _combinedSingle.begin() + static_cast<ptrdiff_t>(offBegin), _combinedSingle.begin() + static_cast<ptrdiff_t>(offEnd)); wave.push_back(0xf7); } + + return true; } SysEx State::createCombinedPatch(const std::vector<SysEx>& _dumps) diff --git a/source/xtLib/xtState.h b/source/xtLib/xtState.h @@ -5,6 +5,7 @@ #include <cstddef> #include <cstdint> #include <functional> +#include <string> #include "xtMidiTypes.h" #include "xtTypes.h" @@ -91,6 +92,8 @@ namespace xt void process(uint32_t _numSamples); + static bool setSingleName(std::vector<uint8_t>& _sysex, const std::string& _name); + static TableId getWavetableFromSingleDump(const SysEx& _single); static void createSequencerMultiData(std::vector<uint8_t>& _data); @@ -103,7 +106,9 @@ namespace xt static SysEx createTableData(const TableData& _table, uint32_t _tableIndex, bool _preview); static SysEx createCombinedPatch(const std::vector<SysEx>& _dumps); - static void splitCombinedPatch(std::vector<SysEx>& _dumps, const SysEx& _combinedSingle); + static bool splitCombinedPatch(std::vector<SysEx>& _dumps, const SysEx& _combinedSingle); + + static SysexCommand getCommand(const SysEx& _data); private: @@ -199,8 +204,6 @@ namespace xt return _mode.front() == 0xf0; } - static SysexCommand getCommand(const SysEx& _data); - void forwardToDevice(const SysEx& _data); void requestGlobal() const;