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 9efae1c73459eaa91a293a6c4591487b3f0fcaf9
parent a579a7ee30785aed79dc260fdc6e1adc8f5d1559
Author: dsp56300 <dsp56300@users.noreply.github.com>
Date:   Thu,  1 Aug 2024 21:47:54 +0200

support performance parameter editing

Diffstat:
Msource/nord/n2x/n2xJucePlugin/n2xController.cpp | 64+++++++++++++++++++++++++++++++++++++++++++++-------------------
Msource/nord/n2x/n2xJucePlugin/n2xController.h | 6+++---
Msource/nord/n2x/n2xLib/n2xdevice.cpp | 2+-
Msource/nord/n2x/n2xLib/n2xstate.cpp | 66+++++++++++++++++++++++++++++++++++++++++++++++++++++-------------
Msource/nord/n2x/n2xLib/n2xstate.h | 21+++++++++++++++++++--
5 files changed, 121 insertions(+), 38 deletions(-)

diff --git a/source/nord/n2x/n2xJucePlugin/n2xController.cpp b/source/nord/n2x/n2xJucePlugin/n2xController.cpp @@ -29,7 +29,7 @@ namespace namespace n2xJucePlugin { - Controller::Controller(AudioPluginAudioProcessor& _p) : pluginLib::Controller(_p, loadParameterDescriptions()) + Controller::Controller(AudioPluginAudioProcessor& _p) : pluginLib::Controller(_p, loadParameterDescriptions()), m_state(nullptr) { registerParams(_p); @@ -100,7 +100,7 @@ namespace n2xJucePlugin if(bank == n2x::SysexByte::SingleDumpBankEditBuffer && program < getPartCount()) { - std::copy(_msg.begin(), _msg.end(), m_singles[program].begin()); + m_state.receive(_msg, synthLib::MidiEventSource::Plugin); applyPatchParameters(params, program); return true; } @@ -122,7 +122,7 @@ namespace n2xJucePlugin if(bank != n2x::SysexByte::MultiDumpBankEditBuffer) return false; - std::copy(_msg.begin(), _msg.end(), m_multi.begin()); + m_state.receive(_msg, synthLib::MidiEventSource::Plugin); applyPatchParameters(params, 0); @@ -139,24 +139,31 @@ namespace n2xJucePlugin const auto origin = midiEventSourceToParameterOrigin(_e.source); - if(_e.b == n2x::ControlChange::CCSync) - { - // this controls both Sync and RingMod - // Sync = bit 0 - // RingMod = bit 1 - auto* paramSync = getParameter("Sync", _e.a & 0xf); - auto* paramRingMod = getParameter("RingMod", _e.a & 0xf); - paramSync->setValueFromSynth(_e.c & 1, origin); - paramRingMod->setValueFromSynth((_e.c>>1) & 1, origin); - } - else + m_state.receive(_e); + + const auto parts = m_state.getPartsForMidiChannel(_e); + + for (const uint8_t part : parts) { - for (const auto paramIndex : paramIndices) + if(_e.b == n2x::ControlChange::CCSync) { - auto* param = getParameter(paramIndex); - assert(param && "parameter not found for control change"); - // TODO: part - param->setValueFromSynth(_e.c, origin); + // this controls both Sync and RingMod + // Sync = bit 0 + // RingMod = bit 1 + auto* paramSync = getParameter("Sync", part); + auto* paramRingMod = getParameter("RingMod", part); + paramSync->setValueFromSynth(_e.c & 1, origin); + paramRingMod->setValueFromSynth((_e.c>>1) & 1, origin); + } + else + { + for (const auto paramIndex : paramIndices) + { + auto* param = getParameter(paramIndex, part); + assert(param && "parameter not found for control change"); + // TODO: part + param->setValueFromSynth(_e.c, origin); + } } } @@ -165,6 +172,12 @@ namespace n2xJucePlugin void Controller::sendParameterChange(const pluginLib::Parameter& _parameter, uint8_t _value) { + if(_parameter.getDescription().page >= 10) + { + sendPerformanceParameter(_parameter, _value); + return; + } + const auto& controllerMap = getParameterDescriptions().getControllerMap(); const auto& ccs = controllerMap.getControlChanges(synthLib::M_CONTROLCHANGE, _parameter.getParameterIndex()); @@ -182,9 +195,22 @@ namespace n2xJucePlugin _value = static_cast<uint8_t>(paramSync->getUnnormalizedValue() | (paramRingMod->getUnnormalizedValue() << 1)); } + m_state.receive(synthLib::SMidiEvent{synthLib::MidiEventSource::Editor, synthLib::M_CONTROLCHANGE, cc, _value}); sendMidiEvent(synthLib::M_CONTROLCHANGE, cc, _value); } + void Controller::sendPerformanceParameter(const pluginLib::Parameter& _parameter, const uint8_t _value) + { + const auto& desc = _parameter.getDescription(); + + const auto mp = static_cast<n2x::MultiParam>(desc.index + (desc.page - 10) * 128); + + if(!m_state.changeMultiParameter(mp, _value)) + return; + const auto& multi = m_state.updateAndGetMulti(); + pluginLib::Controller::sendSysEx(pluginLib::SysEx{multi.begin(), multi.end()}); + } + bool Controller::sendSysEx(MidiPacketType _packet, const std::map<pluginLib::MidiDataType, uint8_t>& _params) const { return pluginLib::Controller::sendSysEx(midiPacketName(_packet), _params); diff --git a/source/nord/n2x/n2xJucePlugin/n2xController.h b/source/nord/n2x/n2xJucePlugin/n2xController.h @@ -30,7 +30,7 @@ namespace n2xJucePlugin uint8_t getPartCount() override { - return static_cast<uint8_t>(m_singles.size()); + return 4; } bool parseSysexMessage(const pluginLib::SysEx&, synthLib::MidiEventSource) override; @@ -39,6 +39,7 @@ namespace n2xJucePlugin bool parseControllerMessage(const synthLib::SMidiEvent&) override; void sendParameterChange(const pluginLib::Parameter& _parameter, uint8_t _value) override; + void sendPerformanceParameter(const pluginLib::Parameter& _parameter, uint8_t _value); bool sendSysEx(MidiPacketType _packet, const std::map<pluginLib::MidiDataType, uint8_t>& _params) const; void requestDump(uint8_t _bank, uint8_t _patch) const; @@ -49,7 +50,6 @@ namespace n2xJucePlugin bool isDerivedParameter(pluginLib::Parameter& _derived, pluginLib::Parameter& _base) const override; private: - std::array<n2x::State::SingleDump, 4> m_singles; - n2x::State::MultiDump m_multi; + n2x::State m_state; }; } diff --git a/source/nord/n2x/n2xLib/n2xdevice.cpp b/source/nord/n2x/n2xLib/n2xdevice.cpp @@ -5,7 +5,7 @@ namespace n2x { - Device::Device() : m_state(m_hardware) + Device::Device() : m_state(&m_hardware) { } diff --git a/source/nord/n2x/n2xLib/n2xstate.cpp b/source/nord/n2x/n2xLib/n2xstate.cpp @@ -180,7 +180,7 @@ namespace n2x static const MultiDefaultData g_multiDefault = createMultiDefaultData(); - State::State(Hardware& _hardware) : m_hardware(_hardware) + State::State(Hardware* _hardware) : m_hardware(_hardware) { for(uint8_t i=0; i<static_cast<uint8_t>(m_singles.size()); ++i) createDefaultSingle(m_singles[i], i); @@ -193,9 +193,8 @@ namespace n2x bool State::getState(std::vector<uint8_t>& _state) { + updateMultiFromSingles(); _state.insert(_state.end(), m_multi.begin(), m_multi.end()); - for (const auto& single : m_singles) - _state.insert(_state.end(), single.begin(), single.end()); return true; } @@ -205,12 +204,7 @@ namespace n2x synthLib::MidiToSysex::splitMultipleSysex(msgs, _state); for (auto& msg : msgs) - { - synthLib::SMidiEvent e; - e.source = synthLib::MidiEventSource::Host; - e.sysex = std::move(msg); - receive(e); - } + receive(msg, synthLib::MidiEventSource::Host); return false; } @@ -258,6 +252,14 @@ namespace n2x return false; } + bool State::receive(const std::vector<uint8_t>& _data, synthLib::MidiEventSource _source) + { + synthLib::SMidiEvent e; + e.sysex = _data; + e.source = _source; + return receive(e); + } + bool State::receiveNonSysex(const synthLib::SMidiEvent& _ev) { switch (_ev.a & 0xf0) @@ -306,11 +308,41 @@ namespace n2x default: for (const auto part : parts) packNibbles(m_singles[part], offset, _ev.c); + return true; } } - break; + return false; + default: + return false; } - return false; + } + + bool State::changeSingleParameter(const uint8_t _part, const SingleParam _parameter, uint8_t _value) + { + if(_part >= m_singles.size()) + return false; + const auto off = getOffsetInSingleDump(_parameter); + const auto current = unpackNibbles(m_singles[_part], _parameter); + if(current == _value) + return false; + packNibbles(m_singles[_part], off, _value); + return true; + } + + bool State::changeMultiParameter(const MultiParam _parameter, const uint8_t _value) + { + const auto off = getOffsetInMultiDump(_parameter); + const auto current = unpackNibbles(m_multi, _parameter); + if(current == _value) + return false; + packNibbles(m_multi, off, _value); + return true; + } + + void State::updateMultiFromSingles() + { + for(uint8_t i=0; i<static_cast<uint8_t>(m_singles.size()); ++i) + copySingleToMulti(m_multi, m_singles[i], i); } void State::createDefaultSingle(SingleDump& _single, const uint8_t _program, const uint8_t _bank/* = n2x::SingleDumpBankEditBuffer*/) @@ -331,7 +363,14 @@ namespace n2x { uint32_t i = SysexIndex::IdxMsgSpec + 1; i += g_singleDataSize * _index; - std::copy(_single.begin() + g_sysexHeaderSize, _single.end() - g_sysexFooterSize, _multi.begin() + i); + std::copy_n(_single.begin() + g_sysexHeaderSize, g_singleDataSize, _multi.begin() + i); + } + + void State::extractSingleFromMulti(SingleDump& _single, const MultiDump& _multi, uint8_t _index) + { + uint32_t i = SysexIndex::IdxMsgSpec + 1; + i += g_singleDataSize * _index; + std::copy_n(_multi.begin() + i, g_singleDataSize, _single.begin() + g_sysexHeaderSize); } void State::createDefaultMulti(MultiDump& _multi, const uint8_t _bank/* = SysexByte::MultiDumpBankEditBuffer*/) @@ -385,7 +424,8 @@ namespace n2x { if(_e.source == synthLib::MidiEventSource::Plugin) return; - m_hardware.sendMidi(_e); + if(m_hardware) + m_hardware->sendMidi(_e); } template<size_t Size> diff --git a/source/nord/n2x/n2xLib/n2xstate.h b/source/nord/n2x/n2xLib/n2xstate.h @@ -17,16 +17,33 @@ namespace n2x using SingleDump = std::array<uint8_t, g_singleDumpSize>; using MultiDump = std::array<uint8_t, g_multiDumpSize>; - State(Hardware& _hardware); + explicit State(Hardware* _hardware); bool getState(std::vector<uint8_t>& _state); bool setState(const std::vector<uint8_t>& _state); bool receive(const synthLib::SMidiEvent& _ev); + bool receive(const std::vector<uint8_t>& _data, synthLib::MidiEventSource _source); + bool receiveNonSysex(const synthLib::SMidiEvent& _ev); + bool changeSingleParameter(uint8_t _part, SingleParam _parameter, uint8_t _value); + bool changeMultiParameter(MultiParam _parameter, uint8_t _value); + + void updateMultiFromSingles(); + + const auto& getMulti() const { return m_multi; } + const auto& getSingle(uint8_t _part) const { return m_singles[_part]; } + + const auto& updateAndGetMulti() + { + updateMultiFromSingles(); + return getMulti(); + } + static void createDefaultSingle(SingleDump& _single, uint8_t _program, uint8_t _bank = n2x::SingleDumpBankEditBuffer); static void copySingleToMulti(MultiDump& _multi, const SingleDump& _single, uint8_t _index); + static void extractSingleFromMulti(SingleDump& _single, const MultiDump& _multi, uint8_t _index); static void createDefaultMulti(MultiDump& _multi, uint8_t _bank = SysexByte::MultiDumpBankEditBuffer); template<size_t Size> @@ -88,7 +105,7 @@ namespace n2x void send(const synthLib::SMidiEvent& _e) const; - Hardware& m_hardware; + Hardware* m_hardware; std::array<SingleDump, 4> m_singles; MultiDump m_multi; };