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 7d32dd02ca0ab4ceb1f0403813cd0be3d791ac82
parent 8ed9b308f381bbaeaa5d25c351d76d377aa91e56
Author: dsp56300 <dsp56300@users.noreply.github.com>
Date:   Fri,  2 Aug 2024 17:21:10 +0200

edit single parameter via sysex if needed

Diffstat:
Msource/nord/n2x/n2xJucePlugin/n2xController.cpp | 51++++++++++++++++++++++++++++++++++++++++-----------
Msource/nord/n2x/n2xJucePlugin/n2xController.h | 3++-
Msource/nord/n2x/n2xLib/n2xstate.cpp | 10+++++++---
Msource/nord/n2x/n2xLib/n2xstate.h | 1+
4 files changed, 50 insertions(+), 15 deletions(-)

diff --git a/source/nord/n2x/n2xJucePlugin/n2xController.cpp b/source/nord/n2x/n2xJucePlugin/n2xController.cpp @@ -175,7 +175,6 @@ namespace n2xJucePlugin { auto* param = getParameter(paramIndex, part); assert(param && "parameter not found for control change"); - // TODO: part param->setValueFromSynth(_e.c, origin); } } @@ -192,16 +191,24 @@ namespace n2xJucePlugin return; } + constexpr uint32_t sysexRateLimitMs = 150; + + pluginLib::Parameter& nonConstParam = const_cast<pluginLib::Parameter&>(_parameter); + + const auto singleParam = static_cast<n2x::SingleParam>(_parameter.getDescription().index); + const uint8_t part = _parameter.getPart(); + const auto& controllerMap = getParameterDescriptions().getControllerMap(); - uint32_t paramIndex; - if(!getParameterDescriptions().getIndexByName(paramIndex, _parameter.getDescription().name)) + uint32_t descIndex; + if(!getParameterDescriptions().getIndexByName(descIndex, _parameter.getDescription().name)) assert(false && "parameter not found"); - const auto& ccs = controllerMap.getControlChanges(synthLib::M_CONTROLCHANGE, paramIndex); + const auto& ccs = controllerMap.getControlChanges(synthLib::M_CONTROLCHANGE, descIndex); if(ccs.empty()) { - assert(false && "TODO: implement parameter sending for non-CC params"); + nonConstParam.setRateLimitMilliseconds(sysexRateLimitMs); + setSingleParameter(part, singleParam, _value); return; } @@ -210,17 +217,39 @@ namespace n2xJucePlugin if(cc == n2x::ControlChange::CCSync) { // sync and ringmod have the same CC, combine them - const auto* paramSync = getParameter("Sync", _parameter.getPart()); - const auto* paramRingMod = getParameter("RingMod", _parameter.getPart()); + const auto* paramSync = getParameter("Sync", part); + const auto* paramRingMod = getParameter("RingMod", part); _value = static_cast<uint8_t>(paramSync->getUnnormalizedValue() | (paramRingMod->getUnnormalizedValue() << 1)); } - const auto ch = m_state.getPartMidiChannel(_parameter.getPart()); + const auto ch = m_state.getPartMidiChannel(part); + + const auto parts = m_state.getPartsForMidiChannel(ch); + const auto ev = synthLib::SMidiEvent{synthLib::MidiEventSource::Editor, static_cast<uint8_t>(synthLib::M_CONTROLCHANGE + ch), cc, _value}; - m_state.receive(ev); - sendMidiEvent(ev); + if(parts.size() > 1) + { + // this is problematic. We want to edit one part only but two parts receive on the same channel. We have to send a full dump + nonConstParam.setRateLimitMilliseconds(sysexRateLimitMs); + setSingleParameter(part, singleParam, _value); + } + else + { + nonConstParam.setRateLimitMilliseconds(0); + m_state.receive(ev); + sendMidiEvent(ev); + } + } + + void Controller::setSingleParameter(uint8_t _part, n2x::SingleParam _sp, uint8_t _value) + { + if(!m_state.changeSingleParameter(_part, _sp, _value)) + return; + + const auto& single = m_state.getSingle(_part); + pluginLib::Controller::sendSysEx(pluginLib::SysEx{single.begin(), single.end()}); } void Controller::setMultiParameter(n2x::MultiParam _mp, uint8_t _value) @@ -277,7 +306,7 @@ namespace n2xJucePlugin return dst; } - bool Controller::activatePatch(const std::vector<uint8_t>& _sysex, const uint32_t _part) + bool Controller::activatePatch(const std::vector<uint8_t>& _sysex, const uint32_t _part) const { if(_part >= getPartCount()) return false; diff --git a/source/nord/n2x/n2xJucePlugin/n2xController.h b/source/nord/n2x/n2xJucePlugin/n2xController.h @@ -42,6 +42,7 @@ namespace n2xJucePlugin void sendParameterChange(const pluginLib::Parameter& _parameter, uint8_t _value) override; + void setSingleParameter(uint8_t _part, n2x::SingleParam _sp, uint8_t _value); void setMultiParameter(n2x::MultiParam _mp, uint8_t _value); uint8_t getMultiParameter(n2x::MultiParam _param) const; @@ -51,7 +52,7 @@ namespace n2xJucePlugin void requestDump(uint8_t _bank, uint8_t _patch) const; std::vector<uint8_t> createSingleDump(uint8_t _bank, uint8_t _program, uint8_t _part) const; - bool activatePatch(const std::vector<uint8_t>& _sysex, uint32_t _part); + bool activatePatch(const std::vector<uint8_t>& _sysex, uint32_t _part) const; bool isDerivedParameter(pluginLib::Parameter& _derived, pluginLib::Parameter& _base) const override; diff --git a/source/nord/n2x/n2xLib/n2xstate.cpp b/source/nord/n2x/n2xLib/n2xstate.cpp @@ -408,13 +408,17 @@ namespace n2x std::vector<uint8_t> State::getPartsForMidiChannel(const synthLib::SMidiEvent& _ev) const { - std::vector<uint8_t> res; - const auto ch = _ev.a & 0xf; + return getPartsForMidiChannel(ch); + } + + std::vector<uint8_t> State::getPartsForMidiChannel(const uint8_t _channel) const + { + std::vector<uint8_t> res; for(uint8_t i=0; i<static_cast<uint8_t>(m_singles.size()); ++i) { - if(getPartMidiChannel(i) == ch) + if(getPartMidiChannel(i) == _channel) res.push_back(i); } return res; diff --git a/source/nord/n2x/n2xLib/n2xstate.h b/source/nord/n2x/n2xLib/n2xstate.h @@ -59,6 +59,7 @@ namespace n2x } std::vector<uint8_t> getPartsForMidiChannel(const synthLib::SMidiEvent& _ev) const; + std::vector<uint8_t> getPartsForMidiChannel(uint8_t _channel) const; template<typename TDump> static uint8_t getPartMidiChannel(const TDump& _dump, const uint8_t _part) {