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 b2b50555805ac8393eb2c1c9be713744d3ceef3b
parent a4b0b8f737ff68d3f151337a3f70cf9f210a1b4f
Author: dsp56300 <dsp56300@users.noreply.github.com>
Date:   Thu, 19 May 2022 18:25:58 +0200

midi packet parsing & preset management is now completely generic

Diffstat:
Msource/jucePlugin/VirusController.cpp | 274+++++++++++++++++++++++++++++++++++++++++++++++--------------------------------
Msource/jucePlugin/VirusController.h | 42+++++++++++++++++++++++++++++++-----------
Msource/jucePlugin/ui3/PatchBrowser.cpp | 176++++++++++++++++++++++++++++++++++++-------------------------------------------
Msource/jucePlugin/ui3/PatchBrowser.h | 14++++++++------
Msource/jucePlugin/ui3/VirusEditor.cpp | 17+++++++----------
5 files changed, 287 insertions(+), 236 deletions(-)

diff --git a/source/jucePlugin/VirusController.cpp b/source/jucePlugin/VirusController.cpp @@ -11,13 +11,30 @@ using MessageType = virusLib::SysexMessageType; namespace Virus { - static constexpr uint8_t kSysExStart[] = {0xf0, 0x00, 0x20, 0x33, 0x01}; - static constexpr auto kHeaderWithMsgCodeLen = 7; + constexpr const char* g_midiPacketNames[] = + { + "requestsingle", + "requestmulti", + "requestsinglebank", + "requestmultibank", + "requestarrangement", + "requestglobal", + "requesttotal", + "requestcontrollerdump", + "parameterchange", + "singledump", + "multidump" + }; + + static_assert(std::size(g_midiPacketNames) == static_cast<size_t>(Controller::MidiPacketType::Count)); + + const char* midiPacketName(Controller::MidiPacketType _type) + { + return g_midiPacketNames[static_cast<uint32_t>(_type)]; + } Controller::Controller(AudioPluginAudioProcessor &p, unsigned char deviceId) : pluginLib::Controller(BinaryData::parameterDescriptions_C_json), m_processor(p), m_deviceId(deviceId) { -// assert(m_descriptions.getDescriptions().size() == Param_Count && "size of enum must match size of parameter descriptions"); - registerParams(p); juce::PropertiesFile::Options opts; @@ -57,54 +74,37 @@ namespace Virus delete m_config; } - void Controller::parseMessage(const SysEx &msg) + void Controller::parseMessage(const SysEx& _msg) { - if (msg.size() < 8) - return; // shorter than expected! - - if (msg[msg.size() - 1] != 0xf7) - return; // invalid end?!? + std::string name; + pluginLib::MidiPacket::Data data; + pluginLib::MidiPacket::ParamValues parameterValues; - for (size_t i = 0; i < msg.size(); ++i) + if(parseMidiPacket(name, data, parameterValues, _msg)) { - if (i < 5) - { - if (msg[i] != kSysExStart[i]) - return; // invalid header - } - else if (i == 5) - { - if (msg[i] != m_deviceId && msg[i] != 0x10) - return; // not intended to this device! - } - else if (i == 6) + const auto deviceId = data[pluginLib::MidiDataType::DeviceId]; + + if(deviceId != m_deviceId && deviceId != virusLib::OMNI_DEVICE_ID) + return; // not intended to this device! + + if(name == midiPacketName(MidiPacketType::SingleDump)) + parseSingle(_msg, data, parameterValues); + else if(name == midiPacketName(MidiPacketType::MultiDump)) + parseMulti(data, parameterValues); + else if(name == midiPacketName(MidiPacketType::ParameterChange)) + parseParamChange(data); + else { - switch (msg[i]) - { - case MessageType::DUMP_SINGLE: - parseSingle(msg); - break; - case MessageType::DUMP_MULTI: - parseMulti(msg); - break; - case MessageType::PARAM_CHANGE_A: - case MessageType::PARAM_CHANGE_B: - case MessageType::PARAM_CHANGE_C: - parseParamChange(msg); - break; - case MessageType::REQUEST_SINGLE: - case MessageType::REQUEST_MULTI: - case MessageType::REQUEST_GLOBAL: - case MessageType::REQUEST_TOTAL: - sendSysEx(msg); - break; - default: - LOG("Controller: Begin Unhandled SysEx! --"); - printMessage(msg); - LOG("Controller: End Unhandled SysEx! --"); - } + LOG("Controller: Begin unhandled SysEx! --"); + printMessage(_msg); + LOG("Controller: End unhandled SysEx! --"); } + return; } + + LOG("Controller: Begin unknown SysEx! --"); + printMessage(_msg); + LOG("Controller: End unknown SysEx! --"); } juce::Value* Controller::getParamValue(uint8_t ch, uint8_t bank, uint8_t paramIndex) @@ -118,18 +118,12 @@ namespace Virus return &params.front()->getValueObject(); } - void Controller::parseParamChange(const SysEx& msg) + void Controller::parseParamChange(const pluginLib::MidiPacket::Data& _data) { - pluginLib::MidiPacket::Data params; - pluginLib::MidiPacket::ParamValues parameterValues; - - if(!parseMidiPacket("parameterchange", params, parameterValues, msg)) - return; - - const auto page = params[pluginLib::MidiDataType::Page]; - const auto part = params[pluginLib::MidiDataType::Part]; - const auto index = params[pluginLib::MidiDataType::ParameterIndex]; - const auto value = params[pluginLib::MidiDataType::ParameterValue]; + const auto page = _data.find(pluginLib::MidiDataType::Page)->second; + const auto part = _data.find(pluginLib::MidiDataType::Part)->second; + const auto index = _data.find(pluginLib::MidiDataType::ParameterIndex)->second; + const auto value = _data.find(pluginLib::MidiDataType::ParameterValue)->second; const auto& partParams = findSynthParam(part, page, index); @@ -190,7 +184,27 @@ namespace Virus bankNames.add(m_singles[bank][i].name); return bankNames; } - void Controller::setSinglePresetName(uint8_t _part, const juce::String& _name) + + std::string Controller::getSinglePresetName(const pluginLib::MidiPacket::ParamValues& _values) const + { + std::string name; + for(uint32_t i=0; i<kNameLength; ++i) + { + const std::string paramName = "SingleName" + std::to_string(i); + const auto idx = getParameterIndexByName(paramName); + if(idx == InvalidParameterIndex) + break; + + const auto it = _values.find(std::make_pair(pluginLib::MidiPacket::AnyPart, idx)); + if(it == _values.end()) + break; + + name += static_cast<char>(it->second); + } + return name; + } + + void Controller::setSinglePresetName(uint8_t _part, const juce::String& _name) { for (int i=0; i<kNameLength; i++) { @@ -257,7 +271,7 @@ namespace Virus sendParameterChange(MessageType::PARAM_CHANGE_C, pt, virusLib::PART_BANK_SELECT, virusLib::toMidiByte(_bank)); sendParameterChange(MessageType::PARAM_CHANGE_C, pt, virusLib::PART_PROGRAM_CHANGE, _prg); - requestSingle(0x0, pt); + requestSingle(virusLib::toMidiByte(virusLib::BankNumber::EditBuffer), pt); m_currentBank[_part] = _bank; m_currentProgram[_part] = _prg; @@ -271,34 +285,48 @@ namespace Virus { return m_currentProgram[_part]; } + + bool Controller::parseSingle(pluginLib::MidiPacket::Data& _data, pluginLib::MidiPacket::ParamValues& _parameterValues, const SysEx& _msg) const + { + const auto packetName = midiPacketName(MidiPacketType::SingleDump); + + auto* m = getMidiPacket(packetName); + + if(!m) + return false; + + if(_msg.size() > m->size()) + { + SysEx temp; + temp.insert(temp.begin(), _msg.begin(), _msg.begin() + (m->size()-1)); + temp.push_back(0xf7); + return parseMidiPacket(*m, _data, _parameterValues, temp); + } + + return parseMidiPacket(*m, _data, _parameterValues, _msg); + } + void Controller::parseSingle(const SysEx& msg) { pluginLib::MidiPacket::Data data; pluginLib::MidiPacket::ParamValues parameterValues; - if(!parseMidiPacket("singledump", data, parameterValues, msg)) + if(!parseSingle(data, parameterValues, msg)) return; - SinglePatch patch; + parseSingle(msg, data, parameterValues); + } - patch.bankNumber = virusLib::fromMidiByte(data[pluginLib::MidiDataType::Bank]); - patch.progNumber = data[pluginLib::MidiDataType::Program]; - - for(uint32_t i=0; i<kNameLength; ++i) - { - const std::string paramName = "SingleName" + std::to_string(i); - const auto idx = getParameterIndexByName(paramName); - if(idx == InvalidParameterIndex) - break; + void Controller::parseSingle(const SysEx& _msg, const pluginLib::MidiPacket::Data& _data, const pluginLib::MidiPacket::ParamValues& _parameterValues) + { + SinglePatch patch; - const auto it = parameterValues.find(std::make_pair(pluginLib::MidiPacket::AnyPart, idx)); - if(it == parameterValues.end()) - break; + patch.bankNumber = virusLib::fromMidiByte(_data.find(pluginLib::MidiDataType::Bank)->second); + patch.progNumber = _data.find(pluginLib::MidiDataType::Program)->second; - patch.name += static_cast<char>(it->second); - } + patch.name = getSinglePresetName(_parameterValues); - copyData(msg, kHeaderWithMsgCodeLen + 2, patch.data); + patch.data = _msg; if (patch.bankNumber == virusLib::BankNumber::EditBuffer) { @@ -312,7 +340,7 @@ namespace Virus const uint8_t ch = patch.progNumber == virusLib::SINGLE ? 0 : patch.progNumber; - for(auto it = parameterValues.begin(); it != parameterValues.end(); ++it) + for(auto it = _parameterValues.begin(); it != _parameterValues.end(); ++it) { auto* p = getParameter(it->first.second, ch); p->setValueFromSynth(it->second, true); @@ -326,22 +354,16 @@ namespace Virus } else m_singles[virusLib::toArrayIndex(patch.bankNumber)][patch.progNumber] = patch; - } + } - void Controller::parseMulti(const SysEx& _msg) + void Controller::parseMulti(const pluginLib::MidiPacket::Data& _data, const pluginLib::MidiPacket::ParamValues& _parameterValues) { - pluginLib::MidiPacket::Data data; - pluginLib::MidiPacket::ParamValues paramValues; - - if(!parseMidiPacket("multidump", data, paramValues, _msg)) - return; - - const auto bankNumber = data[pluginLib::MidiDataType::Bank]; + const auto bankNumber = _data.find(pluginLib::MidiDataType::Bank)->second; /* If it's a multi edit buffer, set the part page C parameters to their multi equivalents */ if (bankNumber == 0) { - for (const auto & paramValue : paramValues) + for (const auto & paramValue : _parameterValues) { const auto part = paramValue.first.first; const auto index = paramValue.first.second; @@ -381,20 +403,6 @@ namespace Virus p->setValueFromSynth(m.c, true); } - uint8_t Controller::copyData(const SysEx &src, int startPos, std::array<uint8_t, kDataSizeInBytes>& dst) - { - uint8_t sum = 0; - - size_t iSrc = startPos; - - for (size_t iDst = 0; iSrc < src.size() && iDst < dst.size(); ++iSrc, ++iDst) - { - dst[iDst] = src[iSrc]; - sum += dst[iDst]; - } - return sum; - } - void Controller::printMessage(const SysEx &msg) { std::stringstream ss; @@ -426,7 +434,7 @@ namespace Virus data.insert(std::make_pair(pluginLib::MidiDataType::Bank, _bank)); data.insert(std::make_pair(pluginLib::MidiDataType::Program, _program)); - return sendSysEx(_multi ? "requestmulti" : "requestsingle", data); + return sendSysEx(_multi ? MidiPacketType::RequestMulti : MidiPacketType::RequestSingle, data); } bool Controller::requestSingle(uint8_t _bank, uint8_t _program) const @@ -444,32 +452,32 @@ namespace Virus std::map<pluginLib::MidiDataType, uint8_t> data; data.insert(std::make_pair(pluginLib::MidiDataType::Bank, _bank)); - return sendSysEx("requestsinglebank", data); + return sendSysEx(MidiPacketType::RequestSingleBank, data); } bool Controller::requestTotal() const { - return sendSysEx("requesttotal"); + return sendSysEx(MidiPacketType::RequestTotal); } bool Controller::requestArrangement() const { - return sendSysEx("requestarrangement"); + return sendSysEx(MidiPacketType::RequestArrangement); } - bool Controller::sendSysEx(const std::string& _packetType) const + bool Controller::sendSysEx(MidiPacketType _type) const { std::map<pluginLib::MidiDataType, uint8_t> params; - return sendSysEx(_packetType, params); + return sendSysEx(_type, params); } - bool Controller::sendSysEx(const std::string& _packetType, std::map<pluginLib::MidiDataType, uint8_t>& _params) const + bool Controller::sendSysEx(MidiPacketType _type, std::map<pluginLib::MidiDataType, uint8_t>& _params) const { std::vector<uint8_t> sysex; _params.insert(std::make_pair(pluginLib::MidiDataType::DeviceId, m_deviceId)); - if(!createMidiDataFromPacket(sysex, _packetType, _params, 0)) + if(!createMidiDataFromPacket(sysex, midiPacketName(_type), _params, 0)) return false; sendSysEx(sysex); @@ -517,7 +525,7 @@ namespace Virus data.insert(std::make_pair(pluginLib::MidiDataType::ParameterIndex, _index)); data.insert(std::make_pair(pluginLib::MidiDataType::ParameterValue, _value)); - return sendSysEx("parameterchange", data); + return sendSysEx(MidiPacketType::ParameterChange, data); } pluginLib::Parameter* Controller::createParameter(pluginLib::Controller& _controller, const pluginLib::Description& _desc, uint8_t _part, int _uid) @@ -527,7 +535,7 @@ namespace Virus std::vector<uint8_t> Controller::createSingleDump(uint8_t _part, uint8_t _bank, uint8_t _program) { - std::map<pluginLib::MidiDataType, uint8_t> data; + pluginLib::MidiPacket::Data data; data.insert(std::make_pair(pluginLib::MidiDataType::DeviceId, m_deviceId)); data.insert(std::make_pair(pluginLib::MidiDataType::Bank, _bank)); @@ -535,9 +543,51 @@ namespace Virus std::vector<uint8_t> dst; - if(!createMidiDataFromPacket(dst, "singledump", data, _part)) + if(!createMidiDataFromPacket(dst, midiPacketName(MidiPacketType::SingleDump), data, _part)) return {}; return dst; } + + std::vector<uint8_t> Controller::createSingleDump(uint8_t _bank, uint8_t _program, const pluginLib::MidiPacket::ParamValues& _paramValues) + { + const auto* m = getMidiPacket(midiPacketName(MidiPacketType::SingleDump)); + assert(m && "midi packet not found"); + + if(!m) + return {}; + + pluginLib::MidiPacket::Data data; + pluginLib::MidiPacket::NamedParamValues paramValues; + + data.insert(std::make_pair(pluginLib::MidiDataType::DeviceId, m_deviceId)); + data.insert(std::make_pair(pluginLib::MidiDataType::Bank, _bank)); + data.insert(std::make_pair(pluginLib::MidiDataType::Program, _program)); + + for (const auto& it : _paramValues) + { + const auto* p = getParameter(it.first.second); + assert(p); + if(!p) + return {}; + const auto key = std::make_pair(it.first.first, p->getDescription().name); + paramValues.insert(std::make_pair(key, it.second)); + } + + pluginLib::MidiPacket::Sysex dst; + if(!m->create(dst, data, paramValues)) + return {}; + return dst; + } + + std::vector<uint8_t> Controller::modifySingleDump(const std::vector<uint8_t>& _sysex, const virusLib::BankNumber _newBank, const uint8_t _newProgram, const bool _modifyBank, const bool _modifyProgram) + { + pluginLib::MidiPacket::Data data; + pluginLib::MidiPacket::ParamValues parameterValues; + + if(!parseSingle(data, parameterValues, _sysex)) + return {}; + + return createSingleDump(_modifyBank ? toMidiByte(_newBank) : data[pluginLib::MidiDataType::Bank], _modifyProgram ? _newProgram : data[pluginLib::MidiDataType::Program], parameterValues); + } }; // namespace Virus diff --git a/source/jucePlugin/VirusController.h b/source/jucePlugin/VirusController.h @@ -22,14 +22,31 @@ namespace Virus virusLib::BankNumber bankNumber = static_cast<virusLib::BankNumber>(0); uint8_t progNumber = 0; std::string name; - std::array<uint8_t, kDataSizeInBytes> data{}; + std::vector<uint8_t> data; }; using Singles = std::array<std::array<SinglePatch, 128>, 8>; static constexpr auto kNameLength = 10; - Controller(AudioPluginAudioProcessor &, unsigned char deviceId = 0x00); + enum class MidiPacketType + { + RequestSingle, + RequestMulti, + RequestSingleBank, + RequestMultiBank, + RequestArrangement, + RequestGlobal, + RequestTotal, + RequestControllerDump, + ParameterChange, + SingleDump, + MultiDump, + + Count + }; + + Controller(AudioPluginAudioProcessor &, unsigned char deviceId = 0x00); ~Controller() override; // this is called by the plug-in on audio thread! @@ -37,14 +54,17 @@ namespace Virus pluginLib::Parameter* createParameter(pluginLib::Controller& _controller, const pluginLib::Description& _desc, uint8_t _part, int _uid) override; std::vector<uint8_t> createSingleDump(uint8_t _part, uint8_t _bank, uint8_t _program); + std::vector<uint8_t> createSingleDump(uint8_t _bank, uint8_t _program, const pluginLib::MidiPacket::ParamValues& _paramValues); + std::vector<uint8_t> modifySingleDump(const std::vector<uint8_t>& _sysex, virusLib::BankNumber _newBank, uint8_t _newProgram, bool _modifyBank, bool _modifyProgram); static void printMessage(const SysEx &); juce::Value* getParamValue(uint8_t ch, uint8_t bank, uint8_t paramIndex); virusLib::VirusModel getVirusModel() const; - // bank - 0-1 (AB) + juce::StringArray getSinglePresetNames(virusLib::BankNumber bank) const; + std::string getSinglePresetName(const pluginLib::MidiPacket::ParamValues& _values) const; const Singles& getSinglePresets() const { @@ -80,23 +100,23 @@ namespace Virus void sendParameterChange(const pluginLib::Parameter& _parameter, uint8_t _value) override; bool sendParameterChange(uint8_t _page, uint8_t _part, uint8_t _index, uint8_t _value) const; - bool sendSysEx(const std::string& _packetType) const; - bool sendSysEx(const std::string& _packetType, std::map<pluginLib::MidiDataType, uint8_t>& _params) const; + bool sendSysEx(MidiPacketType _type) const; + bool sendSysEx(MidiPacketType _type, std::map<pluginLib::MidiDataType, uint8_t>& _params) const; uint8_t getDeviceId() const { return m_deviceId; } + bool parseSingle(pluginLib::MidiPacket::Data& _data, pluginLib::MidiPacket::ParamValues& _parameterValues, const SysEx& _msg) const; + private: void timerCallback() override; Singles m_singles; - // unchecked copy for patch data bytes - static inline uint8_t copyData(const SysEx &src, int startPos, std::array<uint8_t, kDataSizeInBytes>& dst); + void parseSingle(const SysEx& _msg); + void parseSingle(const SysEx& _msg, const pluginLib::MidiPacket::Data& _data, const pluginLib::MidiPacket::ParamValues& _parameterValues); - template <typename T> static juce::String parseAsciiText(const T &, int startPos); - void parseSingle(const SysEx &); - void parseMulti(const SysEx &); - void parseParamChange(const SysEx &); + void parseMulti(const pluginLib::MidiPacket::Data& _data, const pluginLib::MidiPacket::ParamValues& _parameterValues); + void parseParamChange(const pluginLib::MidiPacket::Data& _data); void parseControllerDump(synthLib::SMidiEvent &); AudioPluginAudioProcessor& m_processor; diff --git a/source/jucePlugin/ui3/PatchBrowser.cpp b/source/jucePlugin/ui3/PatchBrowser.cpp @@ -13,16 +13,10 @@ using namespace juce; const juce::Array<juce::String> ModelList = {"A","B","C","TI"}; -const Array<String> g_categories = { "", "Lead", "Bass", "Pad", "Decay", "Pluck", - "Acid", "Classic", "Arpeggiator", "Effects", "Drums", "Percussion", - "Input", "Vocoder", "Favourite 1", "Favourite 2", "Favourite 3" }; - namespace genericVirusUI { - virusLib::VirusModel guessVersion(const uint8_t* _data) + virusLib::VirusModel guessVersion(const uint8_t v) { - const auto v = _data[0]; - if (v < 5) return virusLib::A; if (v == 6) @@ -32,25 +26,6 @@ namespace genericVirusUI return virusLib::TI; } - static juce::String parseAsciiText(const std::vector<uint8_t>& msg, const int start) - { - char text[Virus::Controller::kNameLength + 1]; - text[Virus::Controller::kNameLength] = 0; // termination - for (int pos = 0; pos < Virus::Controller::kNameLength; ++pos) - text[pos] = static_cast<char>(msg[start + pos]); - return {text}; - } - - static void initializePatch(Patch& _patch) - { - _patch.name = parseAsciiText(_patch.data, 128 + 112); - _patch.category1 = _patch.data[251]; - _patch.category2 = _patch.data[252]; - _patch.unison = _patch.data[97]; - _patch.transpose = _patch.data[93]; - _patch.model = guessVersion(&_patch.data[0]); - } - PatchBrowser::PatchBrowser(const VirusEditor& _editor) : m_editor(_editor), m_controller(_editor.getController()), m_fileFilter("*.syx;*.mid;*.midi", "*", "Virus Patch Dumps"), m_bankList(FileBrowserComponent::openMode | FileBrowserComponent::canSelectFiles, File::getSpecialLocation(File::SpecialLocationType::currentApplicationFile), &m_fileFilter, nullptr), @@ -104,7 +79,7 @@ namespace genericVirusUI m_romBankSelect->addItem("-", 1); - for(uint32_t i=0; i<m_editor.getController().getBankCount(); ++i) + for(uint32_t i=0; i<m_controller.getBankCount(); ++i) { std::stringstream ss; ss << "Bank " << static_cast<char>('A' + i); @@ -137,58 +112,48 @@ namespace genericVirusUI void PatchBrowser::selectionChanged() {} - uint32_t PatchBrowser::load(std::vector<Patch>& _result, std::set<std::string>* _dedupeChecksums, const std::vector<std::vector<uint8_t>>& _packets) + uint32_t PatchBrowser::load(const Virus::Controller& _controller, std::vector<Patch>& _result, std::set<std::string>* _dedupeChecksums, const std::vector<std::vector<uint8_t>>& _packets) { uint32_t count = 0; for (const auto& packet : _packets) { - if (load(_result, _dedupeChecksums, packet)) + if (load(_controller, _result, _dedupeChecksums, packet)) ++count; } return count; } - bool PatchBrowser::load(std::vector<Patch>& _result, std::set<std::string>* _dedupeChecksums, const std::vector<uint8_t>& _data) + bool PatchBrowser::load(const Virus::Controller& _controller, std::vector<Patch>& _result, std::set<std::string>* _dedupeChecksums, const std::vector<uint8_t>& _data) { if (_data.size() < 267) return false; - auto* it = &_data.front(); + Patch patch; + patch.sysex = _data; + if(!initializePatch(_controller, patch)) + return false; + + patch.progNumber = static_cast<int>(_result.size()); - if (*it == (uint8_t)0xf0 - && *(it + 1) == (uint8_t)0x00 - && *(it + 2) == (uint8_t)0x20 - && *(it + 3) == (uint8_t)0x33 - && *(it + 4) == (uint8_t)0x01 - && *(it + 6) == (uint8_t)virusLib::DUMP_SINGLE) + if (!_dedupeChecksums) { - Patch patch; - patch.progNumber = static_cast<int>(_result.size()); - patch.sysex = _data; - patch.data.insert(patch.data.begin(), _data.begin() + 9, _data.end()); - initializePatch(patch); + _result.push_back(patch); + } + else + { + const auto md5 = std::string(MD5(&_data.front() + 9 + 17, 256 - 17 - 3).toHexString().toRawUTF8()); - if (!_dedupeChecksums) + if (_dedupeChecksums->find(md5) == _dedupeChecksums->end()) { + _dedupeChecksums->insert(md5); _result.push_back(patch); } - else - { - const auto md5 = std::string(MD5(it + 9 + 17, 256 - 17 - 3).toHexString().toRawUTF8()); - - if (_dedupeChecksums->find(md5) == _dedupeChecksums->end()) - { - _dedupeChecksums->insert(md5); - _result.push_back(patch); - } - } - - return true; } - return false; + + return true; } - uint32_t PatchBrowser::loadBankFile(std::vector<Patch>& _result, std::set<std::string>* _dedupeChecksums, const File& file) + uint32_t PatchBrowser::loadBankFile(const Virus::Controller& _controller, std::vector<Patch>& _result, std::set<std::string>* _dedupeChecksums, const File& file) { const auto ext = file.getFileExtension().toLowerCase(); const auto path = file.getParentDirectory().getFullPathName(); @@ -207,7 +172,7 @@ namespace genericVirusUI std::vector<std::vector<uint8_t>> packets; synthLib::MidiToSysex::splitMultipleSysex(packets, d); - return load(_result, _dedupeChecksums, packets); + return load(_controller, _result, _dedupeChecksums, packets); } if (ext == ".mid" || ext == ".midi") @@ -222,7 +187,7 @@ namespace genericVirusUI std::vector<std::vector<uint8_t>> packets; synthLib::MidiToSysex::splitMultipleSysex(packets, data); - return load(_result, _dedupeChecksums, packets); + return load(_controller, _result, _dedupeChecksums, packets); } return 0; @@ -244,7 +209,7 @@ namespace genericVirusUI std::vector<Patch> patches; for (const auto& f : RangedDirectoryIterator(file, false, "*.syx;*.mid;*.midi", File::findFiles)) - loadBankFile(patches, &dedupeChecksums, f.getFile()); + loadBankFile(m_controller, patches, &dedupeChecksums, f.getFile()); m_filteredPatches.clear(); @@ -271,7 +236,7 @@ namespace genericVirusUI { m_patches.clear(); std::vector<Patch> patches; - loadBankFile(patches, nullptr, file); + loadBankFile(m_controller, patches, nullptr, file); m_filteredPatches.clear(); for (const auto& patch : patches) { @@ -317,11 +282,11 @@ namespace genericVirusUI else if (columnId == Columns::NAME) text = rowElement.name; else if (columnId == Columns::CAT1) - text = g_categories[rowElement.category1]; + text = rowElement.category1; else if (columnId == Columns::CAT2) - text = g_categories[rowElement.category2]; + text = rowElement.category2; else if (columnId == Columns::ARP) - text = rowElement.data[129] != 0 ? "Y" : " "; + text = rowElement.arpMode != 0 ? "Y" : " "; else if (columnId == Columns::UNI) text = rowElement.unison == 0 ? " " : String(rowElement.unison + 1); else if (columnId == Columns::ST) @@ -342,14 +307,15 @@ namespace genericVirusUI if (idx == -1) return; - // force to edit buffer + // re-pack single, force to edit buffer const auto program = m_controller.isMultiMode() ? m_controller.getCurrentPart() : static_cast<uint8_t>(virusLib::ProgramType::SINGLE); - auto sysex = m_filteredPatches[idx].sysex; - sysex[7] = toMidiByte(virusLib::BankNumber::EditBuffer); - sysex[8] = program; + const auto msg = m_controller.modifySingleDump(m_filteredPatches[idx].sysex, virusLib::BankNumber::EditBuffer, program, true, true); + + if(msg.empty()) + return; - m_controller.sendSysEx(sysex); + m_controller.sendSysEx(msg); m_controller.requestSingle(0x0, program); } @@ -374,11 +340,11 @@ namespace genericVirusUI if (m_attributeToSort == Columns::NAME) return m_direction * first.name.compareIgnoreCase(second.name); if (m_attributeToSort == Columns::CAT1) - return m_direction * (first.category1 - second.category1); + return m_direction * first.category1.compare(second.category1); if (m_attributeToSort == Columns::CAT2) - return m_direction * (first.category2 - second.category2); + return m_direction * first.category2.compare(second.category2); if (m_attributeToSort == Columns::ARP) - return m_direction * (first.data[129] - second.data[129]); + return m_direction * (first.arpMode - second.arpMode); if (m_attributeToSort == Columns::UNI) return m_direction * (first.unison - second.unison); if (m_attributeToSort == Columns::VER) @@ -405,7 +371,7 @@ namespace genericVirusUI void PatchBrowser::loadRomBank(uint32_t _bankIndex) { - const auto& singles = m_editor.getController().getSinglePresets(); + const auto& singles = m_controller.getSinglePresets(); if(_bankIndex >= singles.size()) return; @@ -421,32 +387,12 @@ namespace genericVirusUI { Patch patch; - patch.progNumber = static_cast<int>(s); - patch.data.insert(patch.data.begin(), bank[s].data.begin(), bank[s].data.end()); - - initializePatch(patch); + patch.sysex = bank[s].data; - // build sysex message + if(!initializePatch(m_controller, patch)) + continue; - patch.sysex.push_back(0xf0); - patch.sysex.push_back(0x00); // Manufacturer - patch.sysex.push_back(0x20); - patch.sysex.push_back(0x33); - patch.sysex.push_back(0x01); // Product Id = Virus - patch.sysex.push_back(m_editor.getController().getDeviceId()); - patch.sysex.push_back(virusLib::DUMP_SINGLE); - patch.sysex.push_back(static_cast<uint8_t>(_bankIndex)); // bank - patch.sysex.push_back(static_cast<uint8_t>(patch.progNumber)); - patch.sysex.insert(patch.sysex.end(), patch.data.begin(), patch.data.end()); - - // checksum - uint8_t cs = 0; - - for(size_t i=5; i<patch.sysex.size(); ++i) - cs += patch.sysex[i]; - - patch.sysex.push_back(cs & 0x7f); - patch.sysex.push_back(0xf7); + patch.progNumber = static_cast<int>(s); m_patches.add(patch); @@ -458,4 +404,40 @@ namespace genericVirusUI m_patchList.deselectAllRows(); m_patchList.repaint(); } + + bool PatchBrowser::initializePatch(const Virus::Controller& _controller, Patch& _patch) + { + const auto& c = _controller; + + pluginLib::MidiPacket::Data data; + pluginLib::MidiPacket::ParamValues parameterValues; + + if(!c.parseSingle(data, parameterValues, _patch.sysex)) + return false; + + const auto idxVersion = c.getParameterIndexByName("Version"); + const auto idxCategory1 = c.getParameterIndexByName("Category1"); + const auto idxCategory2 = c.getParameterIndexByName("Category2"); + const auto idxUnison = c.getParameterIndexByName("Unison Mode"); + const auto idxTranspose = c.getParameterIndexByName("Transpose"); + const auto idxArpMode = c.getParameterIndexByName("Arp Mode"); + + _patch.name = c.getSinglePresetName(parameterValues); + _patch.model = guessVersion(parameterValues.find(std::make_pair(pluginLib::MidiPacket::AnyPart, idxVersion))->second); + _patch.unison = parameterValues.find(std::make_pair(pluginLib::MidiPacket::AnyPart, idxUnison))->second; + _patch.transpose = parameterValues.find(std::make_pair(pluginLib::MidiPacket::AnyPart, idxTranspose))->second; + _patch.arpMode = parameterValues.find(std::make_pair(pluginLib::MidiPacket::AnyPart, idxArpMode))->second; + + const auto category1 = parameterValues.find(std::make_pair(pluginLib::MidiPacket::AnyPart, idxCategory1))->second; + const auto category2 = parameterValues.find(std::make_pair(pluginLib::MidiPacket::AnyPart, idxCategory2))->second; + + const auto* paramCategory1 = c.getParameter(idxCategory1, 0); + const auto* paramCategory2 = c.getParameter(idxCategory2, 0); + + _patch.category1 = paramCategory1->getDescription().valueList.valueToText(category1); + _patch.category2 = paramCategory2->getDescription().valueList.valueToText(category2); + + return true; + } + } diff --git a/source/jucePlugin/ui3/PatchBrowser.h b/source/jucePlugin/ui3/PatchBrowser.h @@ -22,13 +22,13 @@ namespace genericVirusUI { int progNumber = 0; juce::String name; - uint8_t category1 = 0; - uint8_t category2 = 0; - std::vector<uint8_t> data; + std::string category1; + std::string category2; std::vector<uint8_t> sysex; virusLib::VirusModel model; uint8_t unison = 0; uint8_t transpose = 0; + uint8_t arpMode = 0; }; class PatchBrowser : public juce::FileBrowserListener, juce::TableListBoxModel @@ -36,11 +36,13 @@ namespace genericVirusUI public: explicit PatchBrowser(const VirusEditor& _editor); - static uint32_t load(std::vector<Patch>& _result, std::set<std::string>* _dedupeChecksums, const std::vector<std::vector<uint8_t>>& _packets); - static bool load(std::vector<Patch>& _result, std::set<std::string>* _dedupeChecksums, const std::vector<uint8_t>& _data); - static uint32_t loadBankFile(std::vector<Patch>& _result, std::set<std::string>* _dedupeChecksums, const juce::File& file); + static uint32_t load(const Virus::Controller& _controller, std::vector<Patch>& _result, std::set<std::string>* _dedupeChecksums, const std::vector<std::vector<uint8_t>>& _packets); + static bool load(const Virus::Controller& _controller, std::vector<Patch>& _result, std::set<std::string>* _dedupeChecksums, const std::vector<uint8_t>& _data); + static uint32_t loadBankFile(const Virus::Controller& _controller, std::vector<Patch>& _result, std::set<std::string>* _dedupeChecksums, const juce::File& file); private: + static bool initializePatch(const Virus::Controller& _controller, Patch& _patch); + juce::FileBrowserComponent& getBankList() {return m_bankList; } juce::TableListBox& getPatchList() {return m_patchList; } juce::TextEditor& getSearchBox() {return m_search; } diff --git a/source/jucePlugin/ui3/VirusEditor.cpp b/source/jucePlugin/ui3/VirusEditor.cpp @@ -432,7 +432,7 @@ namespace genericVirusUI const auto ext = result.getFileExtension().toLowerCase(); std::vector<Patch> patches; - PatchBrowser::loadBankFile(patches, nullptr, result); + PatchBrowser::loadBankFile(getController(), patches, nullptr, result); if (patches.empty()) return; @@ -440,12 +440,8 @@ namespace genericVirusUI if (patches.size() == 1) { // load to edit buffer of current part - auto data = patches.front().sysex; - data[7] = virusLib::toMidiByte(virusLib::BankNumber::EditBuffer); - if (getController().isMultiMode()) - data[8] = getController().getCurrentPart(); - else - data[8] = virusLib::SINGLE; + const auto data = getController().modifySingleDump(patches.front().sysex, virusLib::BankNumber::EditBuffer, + getController().isMultiMode() ? getController().getCurrentPart() : virusLib::SINGLE, true, true); getController().sendSysEx(data); } else @@ -453,8 +449,7 @@ namespace genericVirusUI // load to bank A for (const auto& p : patches) { - auto data = p.sysex; - data[7] = virusLib::toMidiByte(virusLib::BankNumber::A); + const auto data = getController().modifySingleDump(p.sysex, virusLib::BankNumber::A, 0, true, false); getController().sendSysEx(data); } } @@ -467,7 +462,9 @@ namespace genericVirusUI void VirusEditor::setPlayMode(uint8_t _playMode) { const auto playMode = getController().getParameterIndexByName(Virus::g_paramPlayMode); - getController().getParameter(playMode)->setValue(_playMode); + + getController().getParameter(playMode)->setValue(_playMode); + if (_playMode == virusLib::PlayModeSingle && getController().getCurrentPart() != 0) m_parameterBinding.setPart(0);