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 b97af0a395a2a1eb5427535986a2e63d6480532b
parent 6984e7c9f169f2df8a7abe63379d4939b43b0c55
Author: dsp56300 <dsp56300@users.noreply.github.com>
Date:   Tue, 17 May 2022 21:59:21 +0200

implement generic multi dump parsing

Diffstat:
Msource/jucePlugin/VirusController.cpp | 86+++++++++++++++++++++++++++++++------------------------------------------------
Msource/jucePlugin/VirusController.h | 3---
Msource/jucePluginLib/controller.cpp | 2+-
Msource/jucePluginLib/controller.h | 2+-
Msource/jucePluginLib/midipacket.cpp | 157++++++++++++++++++++++++++++++++++++++++++++++++++-----------------------------
Msource/jucePluginLib/midipacket.h | 30+++++++++++++++++++++++-------
Msource/jucePluginLib/parameterdescriptions.cpp | 55++++++++++++++++++++++++++++++++++++++++++++++---------
7 files changed, 205 insertions(+), 130 deletions(-)

diff --git a/source/jucePlugin/VirusController.cpp b/source/jucePlugin/VirusController.cpp @@ -118,8 +118,8 @@ namespace Virus void Controller::parseParamChange(const SysEx& msg) { - std::map<pluginLib::MidiDataType, uint8_t> params; - std::map<uint32_t, uint8_t> parameterValues; + pluginLib::MidiPacket::Data params; + pluginLib::MidiPacket::ParamValues parameterValues; if(!parseMidiPacket("parameterchange", params, parameterValues, msg)) return; @@ -188,13 +188,6 @@ namespace Virus bankNames.add(parseAsciiText(m_singles[bank][i].data, 128 + 112)); return bankNames; } - juce::StringArray Controller::getMultiPresetsName() const - { - juce::StringArray bankNames; - for (const auto& m_multi : m_multis) - bankNames.add(parseAsciiText(m_multi.data, 0)); - return bankNames; - } void Controller::setSinglePresetName(uint8_t part, juce::String _name) { constexpr uint8_t asciiStart = 112; _name = _name.substring(0,kNameLength).paddedRight(' ', kNameLength); @@ -251,8 +244,8 @@ namespace Virus uint8_t Controller::getCurrentPartProgram(uint8_t part) const { return m_currentProgram[part]; } void Controller::parseSingle(const SysEx &msg) { - std::map<pluginLib::MidiDataType, uint8_t> data; - std::map<uint32_t, uint8_t> parameterValues; + pluginLib::MidiPacket::Data data; + pluginLib::MidiPacket::ParamValues parameterValues; if(!parseMidiPacket("singledump", data, parameterValues, msg)) return; @@ -278,7 +271,7 @@ namespace Virus for(auto it = parameterValues.begin(); it != parameterValues.end(); ++it) { - auto* p = getParameter(it->first, ch); + auto* p = getParameter(it->first.second, ch); p->setValueFromSynth(it->second, true); for (const auto& linkedParam : p->getLinkedParameters()) @@ -297,51 +290,40 @@ namespace Virus DBG(progName); } - void Controller::parseMulti(const SysEx &msg) + void Controller::parseMulti(const SysEx& _msg) { - constexpr auto expectedDataSize = 2 + 256; - constexpr auto checkSumSize = 1; - const auto dataSize = msg.size() - kHeaderWithMsgCodeLen - 1; - const auto hasChecksum = dataSize == expectedDataSize + checkSumSize; - assert(hasChecksum || dataSize == expectedDataSize); + pluginLib::MidiPacket::Data data; + pluginLib::MidiPacket::ParamValues paramValues; + + if(!parseMidiPacket("multidump", data, paramValues, _msg)) + return; constexpr auto startPos = kHeaderWithMsgCodeLen; - MultiPatch patch; - patch.bankNumber = msg[startPos]; - patch.progNumber = msg[startPos + 1]; - auto progName = parseAsciiText(msg, startPos + 2 + 3); - [[maybe_unused]] auto dataSum = copyData(msg, startPos + 2, patch.data); - - /* If it's a multi edit buffer, set the part page C single parameters to their multi equivalents */ - if (patch.bankNumber == 0) { - for (uint8_t pt = 0; pt < 16; pt++) { - for(int i = 0; i < 8; i++) { - const auto& params = findSynthParam(pt, virusLib::PAGE_C, virusLib::PART_MIDI_CHANNEL + i); - for (const auto& p : params) - p->setValueFromSynth(patch.data[virusLib::MD_PART_MIDI_CHANNEL + (i * 16) + pt], true); - } - const auto& params = findSynthParam(pt, virusLib::PAGE_B, virusLib::CLOCK_TEMPO); - for (const auto& p : params) - p->setValueFromSynth(patch.data[virusLib::MD_CLOCK_TEMPO], true); -/* if (auto* p = findSynthParam(pt, virusLib::PAGE_A, virusLib::EFFECT_SEND)) { - p->setValueFromSynth(patch.data[virusLib::MD_PART_EFFECT_SEND], true); - }*/ - m_currentBank[pt] = static_cast<virusLib::BankNumber>(patch.data[virusLib::MD_PART_BANK_NUMBER + pt] + 1); - m_currentProgram[pt] = patch.data[virusLib::MD_PART_PROGRAM_NUMBER + pt]; - } - } - if (hasChecksum) + const auto bankNumber = data[pluginLib::MidiDataType::Bank]; + + auto progName = parseAsciiText(_msg, 13); + + /* If it's a multi edit buffer, set the part page C parameters to their multi equivalents */ + if (bankNumber == 0) { - const int expectedChecksum = msg[msg.size() - 2]; - const auto msgDeviceId = msg[5]; - const int checksum = (msgDeviceId + 0x11 + patch.bankNumber + patch.progNumber + dataSum) & 0x7f; - assert(checksum == expectedChecksum); - } - if (patch.bankNumber == 0) { - m_currentMulti = patch; - } else { - m_multis[patch.progNumber] = patch; + for (const auto & paramValue : paramValues) + { + const auto part = paramValue.first.first; + const auto index = paramValue.first.second; + const auto value = paramValue.second; + + auto* param = getParameter(index, part); + if(!param) + continue; + + const auto& desc = param->getDescription(); + + if(desc.page != virusLib::PAGE_C) + continue; + + param->setValueFromSynth(value); + } } } diff --git a/source/jucePlugin/VirusController.h b/source/jucePlugin/VirusController.h @@ -59,7 +59,6 @@ namespace Virus return m_singles; } - juce::StringArray getMultiPresetsName() const; void setSinglePresetName(uint8_t part, juce::String _name); bool isMultiMode() { @@ -102,9 +101,7 @@ namespace Virus private: void timerCallback() override; - Multis m_multis; // RAM has 128 Multi 'snapshots' Singles m_singles; - MultiPatch m_currentMulti; // unchecked copy for patch data bytes static inline uint8_t copyData(const SysEx &src, int startPos, std::array<uint8_t, kDataSizeInBytes>& dst); diff --git a/source/jucePluginLib/controller.cpp b/source/jucePluginLib/controller.cpp @@ -179,7 +179,7 @@ namespace pluginLib return true; } - bool Controller::parseMidiPacket(const std::string& _name, std::map<pluginLib::MidiDataType, uint8_t>& _data, std::map<uint32_t, uint8_t>& _parameterValues, const std::vector<uint8_t>& _src) const + bool Controller::parseMidiPacket(const std::string& _name, MidiPacket::Data& _data, MidiPacket::ParamValues& _parameterValues, const std::vector<uint8_t>& _src) const { auto* m = getMidiPacket(_name); assert(m); diff --git a/source/jucePluginLib/controller.h b/source/jucePluginLib/controller.h @@ -25,7 +25,7 @@ namespace pluginLib const MidiPacket* getMidiPacket(const std::string& _name) const; bool createMidiDataFromPacket(std::vector<uint8_t>& _sysex, const std::string& _packetName, const std::map<MidiDataType, uint8_t>& _params) const; - bool parseMidiPacket(const std::string& _name, std::map<pluginLib::MidiDataType, uint8_t>& _data, std::map<uint32_t, uint8_t>& _parameterValues, const std::vector<uint8_t>& _src) const; + bool parseMidiPacket(const std::string& _name, MidiPacket::Data& _data, MidiPacket::ParamValues& _parameterValues, const std::vector<uint8_t>& _src) const; protected: virtual Parameter* createParameter(Controller& _controller, const Description& _desc, uint8_t _part, int _uid) = 0; diff --git a/source/jucePluginLib/midipacket.cpp b/source/jucePluginLib/midipacket.cpp @@ -8,95 +8,138 @@ namespace pluginLib { + MidiPacket::MidiPacket(std::string _name, std::vector<MidiDataDefinition>&& _bytes) : m_name(std::move(_name)), m_definitions(std::move(_bytes)) + { + uint8_t usedMask = 0; + + uint32_t byteIndex = 0; + + if(m_name == "multidump") + int d=0; + for(uint32_t i=0; i<m_definitions.size(); ++i) + { + const auto& d = m_definitions[i]; + + const uint8_t masked = (0xff & d.paramMask) << d.paramShift; + + if(usedMask & masked) + { + // next byte starts if the current mask overlaps with an existing one + usedMask = 0; + ++byteIndex; + } + + m_definitionToByteIndex.insert(std::make_pair(i, byteIndex)); + m_byteToDefinitionIndex.insert(std::make_pair(byteIndex, i)); + + usedMask |= masked; + } + + m_byteSize = byteIndex + 1; + } + bool MidiPacket::create(std::vector<uint8_t>& _dst, const std::map<MidiDataType, uint8_t>& _data) const { - _dst.reserve(size()); + _dst.assign(size(), 0); for(size_t i=0; i<size(); ++i) { - const auto& b = m_bytes[i]; - switch (b.type) + const auto range = m_byteToDefinitionIndex.equal_range(static_cast<uint32_t>(i)); + + for(auto itRange = range.first; itRange != range.second; ++itRange) { - case MidiDataType::Null: - _dst.push_back(0); - break; - case MidiDataType::Byte: - _dst.push_back(b.byte); - break; - default: - { - const auto it = _data.find(b.type); + const auto& d = m_definitions[itRange->second]; - if(it == _data.end()) + switch (d.type) + { + case MidiDataType::Null: + _dst[i] = 0; + break; + case MidiDataType::Byte: + _dst[i] = d.byte; + break; + default: { - LOG("Failed to find data of type " << static_cast<int>(b.type) << " to fill byte " << i << " of midi packet"); - return false; - } + const auto it = _data.find(d.type); + + if(it == _data.end()) + { + LOG("Failed to find data of type " << static_cast<int>(d.type) << " to fill byte " << i << " of midi packet"); + return false; + } - _dst.push_back(it->second); + _dst[i] |= (it->second & d.paramMask) << d.paramShift; + } } } } return true; } - bool MidiPacket::parse(std::map<MidiDataType, uint8_t>& _data, std::map<uint32_t, uint8_t>& _parameterValues, const ParameterDescriptions& _parameters, const std::vector<uint8_t>& _src) const + bool MidiPacket::parse(Data& _data, ParamValues& _parameterValues, const ParameterDescriptions& _parameters, const Sysex& _src) const { if(_src.size() != size()) return false; for(size_t i=0; i<_src.size(); ++i) { - const auto& b = m_bytes[i]; const auto s = _src[i]; - switch (b.type) + const auto range = m_byteToDefinitionIndex.equal_range(static_cast<uint32_t>(i)); + + for(auto it = range.first; it != range.second; ++it) { - case MidiDataType::Null: - continue; - case MidiDataType::Byte: - if(b.byte != s) - return false; - break; - case MidiDataType::Checksum: + const auto& d = m_definitions[it->second]; + + switch (d.type) { - uint8_t checksum = 0; + case MidiDataType::Null: + continue; + case MidiDataType::Byte: + if(d.byte != s) + return false; + break; + case MidiDataType::Checksum: + { + uint8_t checksum = 0; - for(uint32_t c = b.checksumFirstIndex; c <= b.checksumLastIndex; ++c) - checksum += _src[c]; + for(uint32_t c = d.checksumFirstIndex; c <= d.checksumLastIndex; ++c) + checksum += _src[c]; - checksum &= 0x7f; + checksum &= 0x7f; - if(checksum != s) - { - LOG("Packet checksum error, calculated " << std::hex << checksum << " but data contains " << s); - return false; + if(checksum != s) + { + LOG("Packet checksum error, calculated " << std::hex << checksum << " but data contains " << s); + return false; + } } - } - continue; - case MidiDataType::DeviceId: - case MidiDataType::Bank: - case MidiDataType::Program: - case MidiDataType::ParameterIndex: - case MidiDataType::ParameterValue: - case MidiDataType::Page: - case MidiDataType::Part: - _data.insert(std::make_pair(b.type, s)); - break; - case MidiDataType::Parameter: - { - uint32_t idx; - if(!_parameters.getIndexByName(idx, b.name)) + continue; + case MidiDataType::DeviceId: + case MidiDataType::Bank: + case MidiDataType::Program: + case MidiDataType::ParameterIndex: + case MidiDataType::ParameterValue: + case MidiDataType::Page: + case MidiDataType::Part: + _data.insert(std::make_pair(d.type, s)); + break; + case MidiDataType::Parameter: { - LOG("Failed to find named parameter " << b.name << " while parsing midi packet, midi byte " << i); - return false; + uint32_t idx; + if(!_parameters.getIndexByName(idx, d.paramName)) + { + LOG("Failed to find named parameter " << d.paramName << " while parsing midi packet, midi byte " << i); + return false; + } + const auto sMasked = (s >> d.paramShift) & d.paramMask; + _parameterValues.insert(std::make_pair(std::make_pair(d.paramPart, idx), sMasked)); } - _parameterValues.insert(std::make_pair(idx, s)); + break; + default: + assert(false && "unknown data type"); + return false; } - break; - default: - assert(false && "unknown data type"); - return false; } } return true; diff --git a/source/jucePluginLib/midipacket.h b/source/jucePluginLib/midipacket.h @@ -27,26 +27,42 @@ namespace pluginLib class MidiPacket { public: - struct MidiByte + static constexpr uint8_t AnyPart = 0xff; + + struct MidiDataDefinition { MidiDataType type = MidiDataType::Null; + uint8_t byte = 0; - std::string name; + + std::string paramName; + uint8_t paramMask = 0xff; + uint8_t paramShift = 0; + uint8_t paramPart = AnyPart; + uint32_t checksumFirstIndex = 0; uint32_t checksumLastIndex = 0; uint32_t checksumInitValue = 0; }; + using Data = std::map<MidiDataType, uint8_t>; + using ParamValues = std::map<std::pair<uint8_t,uint32_t>, uint8_t>; // part, index => value + using Sysex = const std::vector<uint8_t>; + MidiPacket() = default; - explicit MidiPacket(std::vector<MidiByte>&& bytes) : m_bytes(std::move(bytes)) {} + explicit MidiPacket(std::string _name, std::vector<MidiDataDefinition>&& _bytes); - const std::vector<MidiByte>& bytes() { return m_bytes; } - size_t size() const { return m_bytes.size(); } + const std::vector<MidiDataDefinition>& definitions() { return m_definitions; } + uint32_t size() const { return m_byteSize; } bool create(std::vector<uint8_t>& _dst, const std::map<MidiDataType, uint8_t>& _data) const; - bool parse(std::map<MidiDataType, uint8_t>& _data, std::map<uint32_t, uint8_t>& _parameterValues, const ParameterDescriptions& _parameters, const std::vector<uint8_t>& _src) const; + bool parse(Data& _data, ParamValues& _parameterValues, const ParameterDescriptions& _parameters, Sysex& _src) const; private: - std::vector<MidiByte> m_bytes; + const std::string m_name; + std::vector<MidiDataDefinition> m_definitions; + std::map<uint32_t, uint32_t> m_definitionToByteIndex; + std::multimap<uint32_t, uint32_t> m_byteToDefinitionIndex; + uint32_t m_byteSize = 0; }; } diff --git a/source/jucePluginLib/parameterdescriptions.cpp b/source/jucePluginLib/parameterdescriptions.cpp @@ -342,7 +342,7 @@ namespace pluginLib return; } - std::vector<MidiPacket::MidiByte> bytes; + std::vector<MidiPacket::MidiDataDefinition> bytes; for(auto i=0; i<arr->size(); ++i) { @@ -350,7 +350,7 @@ namespace pluginLib auto type = entry["type"].toString().toStdString(); - MidiPacket::MidiByte byte; + MidiPacket::MidiDataDefinition byte; if(type == "byte") { @@ -375,14 +375,51 @@ namespace pluginLib } else if(type == "param") { - byte.name = entry["name"].toString().toStdString(); + byte.paramName = entry["name"].toString().toStdString(); - if(byte.name.empty()) + if(byte.paramName.empty()) { _errors << "no parameter name specified for type param, midi packet " << _key << ", index " << i << std::endl; return; } + const auto hasMask = entry.hasProperty("mask"); + const auto hasShift = entry.hasProperty("shift"); + const auto hasPart = entry.hasProperty("part"); + + if(hasMask) + { + const int mask = strtol(entry["mask"].toString().toStdString().c_str(), nullptr, 16); + if(mask < 0 || mask > 0xff) + { + _errors << "mask needs to be between 00 and ff but got " << std::hex << mask << std::endl; + return; + } + byte.paramMask = static_cast<uint8_t>(mask); + } + + if(hasShift) + { + const int shift = entry["shift"]; + if(shift < 0 || shift > 7) + { + _errors << "shift value needs to be between 0 and 7 but got " << shift << std::endl; + return; + } + byte.paramShift = static_cast<uint8_t>(shift); + } + + if(hasPart) + { + const int part= entry["part"]; + if(part < 0 || part > 15) + { + _errors << "part needs to be between 0 and 15 but got " << part << std::endl; + return; + } + byte.paramPart = static_cast<uint8_t>(part); + } + byte.type = MidiDataType::Parameter; } else if(type == "checksum") @@ -419,12 +456,12 @@ namespace pluginLib bytes.push_back(byte); } - MidiPacket packet(std::move(bytes)); + MidiPacket packet(_key, std::move(bytes)); // post-read validation - for(size_t i=0; i<packet.bytes().size(); ++i) + for(size_t i=0; i<packet.definitions().size(); ++i) { - const auto& p = packet.bytes()[i]; + const auto& p = packet.definitions()[i]; if(p.type == MidiDataType::Checksum) { @@ -438,9 +475,9 @@ namespace pluginLib { uint32_t index; - if(!getIndexByName(index, p.name)) + if(!getIndexByName(index, p.paramName)) { - _errors << "specified parameter " << p.name << " does not exist" << std::endl; + _errors << "specified parameter " << p.paramName << " does not exist" << std::endl; return; } }