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 6d9873f5d0576582725217618d326a503452aeb6
parent de86df75f8e665ea291898fc56893a095e6b8bf3
Author: dsp56300 <dsp56300@users.noreply.github.com>
Date:   Tue, 17 May 2022 23:54:23 +0200

implement generic version of single dump creation and use it in editor to save singles to disk

Diffstat:
Msource/jucePlugin/VirusController.cpp | 26+++++++++++++++++++++++++-
Msource/jucePlugin/VirusController.h | 10+++-------
Msource/jucePlugin/ui3/VirusEditor.cpp | 24+++++++-----------------
Msource/jucePluginLib/controller.cpp | 32+++++++++++++++++++++++++-------
Msource/jucePluginLib/controller.h | 3++-
Msource/jucePluginLib/midipacket.cpp | 79++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------
Msource/jucePluginLib/midipacket.h | 15++++++++++++---
Msource/jucePluginLib/parameterdescriptions.cpp | 2+-
8 files changed, 145 insertions(+), 46 deletions(-)

diff --git a/source/jucePlugin/VirusController.cpp b/source/jucePlugin/VirusController.cpp @@ -207,6 +207,14 @@ namespace Virus v.setValue(static_cast<uint8_t>(_name[i])); } } + + bool Controller::isMultiMode() + { + auto* value = getParamValue(0, 2, 0x7a); + jassert(value); + return value->getValue(); + } + juce::String Controller::getCurrentPartPresetName(const uint8_t _part) const { std::string name; @@ -459,7 +467,7 @@ namespace Virus _params.insert(std::make_pair(pluginLib::MidiDataType::DeviceId, m_deviceId)); - if(!createMidiDataFromPacket(sysex, _packetType, _params)) + if(!createMidiDataFromPacket(sysex, _packetType, _params, 0)) return false; sendSysEx(sysex); @@ -514,4 +522,20 @@ namespace Virus { return new Parameter(_controller, _desc, _part, _uid); } + + std::vector<uint8_t> Controller::createSingleDump(uint8_t _part, uint8_t _bank, uint8_t _program) + { + std::map<pluginLib::MidiDataType, uint8_t> data; + + 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)); + + std::vector<uint8_t> dst; + + if(!createMidiDataFromPacket(dst, "singledump", data, _part)) + return {}; + + return dst; + } }; // namespace Virus diff --git a/source/jucePlugin/VirusController.h b/source/jucePlugin/VirusController.h @@ -38,6 +38,7 @@ namespace Virus void dispatchVirusOut(const std::vector<synthLib::SMidiEvent> &); 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); static void printMessage(const SysEx &); @@ -53,13 +54,8 @@ namespace Virus } void setSinglePresetName(uint8_t _part, const juce::String& _name); - bool isMultiMode() - { - auto* value = getParamValue(0, 2, 0x7a); - jassert(value); - return value->getValue(); - } - // part 0 - 15 (ignored when single! 0x40...) + bool isMultiMode(); + // part 0 - 15 (ignored when single! 0x40...) void setCurrentPartPreset(uint8_t _part, virusLib::BankNumber _bank, uint8_t _prg); virusLib::BankNumber getCurrentPartBank(uint8_t _part) const; uint8_t getCurrentPartProgram(uint8_t _part) const; diff --git a/source/jucePlugin/ui3/VirusEditor.cpp b/source/jucePlugin/ui3/VirusEditor.cpp @@ -398,25 +398,15 @@ namespace genericVirusUI const auto result = chooser.getResult(); m_previousPath = result.getParentDirectory().getFullPathName(); const auto ext = result.getFileExtension().toLowerCase(); - const uint8_t syxHeader[9] = { 0xF0, 0x00, 0x20, 0x33, 0x01, 0x00, 0x10, 0x01, 0x00 }; - constexpr uint8_t syxEof[1] = { 0xF7 }; - uint8_t cs = syxHeader[5] + syxHeader[6] + syxHeader[7] + syxHeader[8]; - uint8_t data[256]; - for (int i = 0; i < 256; i++) - { - const auto param = getController().getParamValue(getController().getCurrentPart(), i < 128 ? 0 : 1, i & 127); - data[i] = param ? static_cast<int>(param->getValue()) : 0; - cs += data[i]; + const auto data = getController().createSingleDump(getController().getCurrentPart(), virusLib::toMidiByte(virusLib::BankNumber::A), 0); + + if(!data.empty()) + { + result.deleteFile(); + result.create(); + result.appendData(&data[0], data.size()); } - cs = cs & 0x7f; - - result.deleteFile(); - result.create(); - result.appendData(syxHeader, 9); - result.appendData(data, 256); - result.appendData(&cs, 1); - result.appendData(syxEof, 1); }; m_fileChooser->launchAsync(flags, onFileChooser); } diff --git a/source/jucePluginLib/controller.cpp b/source/jucePluginLib/controller.cpp @@ -163,20 +163,38 @@ namespace pluginLib return m_descriptions.getMidiPacket(_name); } - bool Controller::createMidiDataFromPacket(std::vector<uint8_t>& _sysex, const std::string& _packetName, const std::map<MidiDataType, uint8_t>& _params) const + bool Controller::createMidiDataFromPacket(std::vector<uint8_t>& _sysex, const std::string& _packetName, const std::map<MidiDataType, uint8_t>& _params, uint8_t _part) const { const auto* m = getMidiPacket(_packetName); assert(m && "midi packet not found"); if(!m) return false; - if(!m->create(_sysex, _params)) - { + MidiPacket::NamedParamValues paramValues; + + MidiPacket::ParamIndices indices; + m->getParameterIndices(indices, m_descriptions); + + if(!indices.empty()) + { + for (const auto& index : indices) + { + auto* p = getParameter(index.second, _part); + if(!p) + return false; + + auto v = roundToInt(p->getValueObject().getValue()); + paramValues.insert(std::make_pair(std::make_pair(index.first, p->getDescription().name), v)); + } + } + + if(!m->create(_sysex, _params, paramValues)) + { assert(false && "failed to create midi packet"); - _sysex.clear(); - return false; - } - return true; + _sysex.clear(); + return false; + } + return true; } bool Controller::parseMidiPacket(const std::string& _name, MidiPacket::Data& _data, MidiPacket::ParamValues& _parameterValues, const std::vector<uint8_t>& _src) const diff --git a/source/jucePluginLib/controller.h b/source/jucePluginLib/controller.h @@ -23,7 +23,8 @@ namespace pluginLib uint32_t getParameterIndexByName(const std::string& _name) const; 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 createMidiDataFromPacket(std::vector<uint8_t>& _sysex, const std::string& _packetName, const std::map<MidiDataType, uint8_t>& _params, uint8_t _part) const; bool parseMidiPacket(const std::string& _name, MidiPacket::Data& _data, MidiPacket::ParamValues& _parameterValues, const std::vector<uint8_t>& _src) const; diff --git a/source/jucePluginLib/midipacket.cpp b/source/jucePluginLib/midipacket.cpp @@ -16,11 +16,15 @@ namespace pluginLib 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(d.type == MidiDataType::Parameter) + m_hasParameters = true; + + const auto masked = static_cast<uint8_t>((0xff & d.paramMask) << d.paramShift); if(usedMask & masked) { @@ -38,10 +42,12 @@ namespace pluginLib m_byteSize = byteIndex + 1; } - bool MidiPacket::create(std::vector<uint8_t>& _dst, const std::map<MidiDataType, uint8_t>& _data) const + bool MidiPacket::create(std::vector<uint8_t>& _dst, const Data& _data, const NamedParamValues& _paramValues) const { _dst.assign(size(), 0); + std::map<uint32_t, uint32_t> pendingChecksums; // byte index => description index + for(size_t i=0; i<size(); ++i) { const auto range = m_byteToDefinitionIndex.equal_range(static_cast<uint32_t>(i)); @@ -58,6 +64,20 @@ namespace pluginLib case MidiDataType::Byte: _dst[i] = d.byte; break; + case MidiDataType::Parameter: + { + const auto it = _paramValues.find(std::make_pair(d.paramPart, d.paramName)); + if(it == _paramValues.end()) + { + LOG("Failed to find value for parameter " << d.paramName << ", part " << d.paramPart); + return false; + } + _dst[i] |= (it->second & d.paramMask) << d.paramShift; + } + break; + case MidiDataType::Checksum: + pendingChecksums.insert(std::make_pair(static_cast<uint32_t>(i), itRange->second)); + break; default: { const auto it = _data.find(d.type); @@ -68,14 +88,28 @@ namespace pluginLib return false; } - _dst[i] |= (it->second & d.paramMask) << d.paramShift; + _dst[i] = it->second; } } } } + + for (auto& pendingChecksum : pendingChecksums) + { + const auto byteIndex = pendingChecksum.first; + const auto descIndex = pendingChecksum.second; + + _dst[byteIndex] = calcChecksum(m_definitions[descIndex], _dst); + } + return true; } + bool MidiPacket::create(std::vector<uint8_t>& _dst, const Data& _data) const + { + return create(_dst, _data, {}); + } + bool MidiPacket::parse(Data& _data, ParamValues& _parameterValues, const ParameterDescriptions& _parameters, const Sysex& _src) const { if(_src.size() != size()) @@ -101,12 +135,7 @@ namespace pluginLib break; case MidiDataType::Checksum: { - uint8_t checksum = 0; - - for(uint32_t c = d.checksumFirstIndex; c <= d.checksumLastIndex; ++c) - checksum += _src[c]; - - checksum &= 0x7f; + const uint8_t checksum = calcChecksum(d, _src); if(checksum != s) { @@ -144,4 +173,36 @@ namespace pluginLib } return true; } + + bool MidiPacket::getParameterIndices(ParamIndices& _indices, const ParameterDescriptions& _parameters) const + { + if(!m_hasParameters) + return true; + + for (const auto & d : m_definitions) + { + if(d.type != MidiDataType::Parameter) + continue; + + uint32_t index; + if(!_parameters.getIndexByName(index, d.paramName)) + { + LOG("Failed to retrieve index for parameter " << d.paramName); + return false; + } + + _indices.insert(std::make_pair(d.paramPart, index)); + } + return true; + } + + uint8_t MidiPacket::calcChecksum(const MidiDataDefinition& _d, Sysex& _src) + { + auto checksum = _d.checksumInitValue; + + for(uint32_t c = _d.checksumFirstIndex; c <= _d.checksumLastIndex; ++c) + checksum += _src[c]; + + return checksum & 0x7f; + } } diff --git a/source/jucePluginLib/midipacket.h b/source/jucePluginLib/midipacket.h @@ -2,6 +2,7 @@ #include <cstdint> #include <map> +#include <set> #include <string> #include <vector> @@ -42,11 +43,14 @@ namespace pluginLib uint32_t checksumFirstIndex = 0; uint32_t checksumLastIndex = 0; - uint32_t checksumInitValue = 0; + uint8_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 ParamIndex = std::pair<uint8_t,uint32_t>; + using ParamIndices = std::set<ParamIndex>; + using ParamValues = std::map<ParamIndex, uint8_t>; // part, index => value + using NamedParamValues = std::map<std::pair<uint8_t,std::string>, uint8_t>; // part, name => value using Sysex = const std::vector<uint8_t>; MidiPacket() = default; @@ -55,14 +59,19 @@ namespace pluginLib 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 create(std::vector<uint8_t>& _dst, const Data& _data, const NamedParamValues& _paramValues) const; + bool create(std::vector<uint8_t>& _dst, const Data& _data) const; bool parse(Data& _data, ParamValues& _parameterValues, const ParameterDescriptions& _parameters, Sysex& _src) const; + bool getParameterIndices(ParamIndices& _indices, const ParameterDescriptions& _parameters) const; private: + static uint8_t calcChecksum(const MidiDataDefinition& _d, const Sysex& _src); + 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; + bool m_hasParameters = false; }; } diff --git a/source/jucePluginLib/parameterdescriptions.cpp b/source/jucePluginLib/parameterdescriptions.cpp @@ -437,7 +437,7 @@ namespace pluginLib byte.type = MidiDataType::Checksum; byte.checksumFirstIndex = first; byte.checksumLastIndex = last; - byte.checksumInitValue = init; + byte.checksumInitValue = static_cast<uint8_t>(init); } else if(type == "bank") byte.type = MidiDataType::Bank; else if(type == "program") byte.type = MidiDataType::Program;