commit 46eccdd8ba72e277d6bb450dd2678bc5b63ab3b7
parent 6f9fbdca74107e50c0bcb5356b505e832aac0946
Author: dsp56300 <dsp56300@users.noreply.github.com>
Date: Sun, 24 Nov 2024 13:11:24 +0100
use channel translation to prevent performance channel collision to be able to use CC instead of full patch-sysex to edit parameters, fixes slow parameter changes in case of >1 slot using the same midi channel
Diffstat:
5 files changed, 136 insertions(+), 68 deletions(-)
diff --git a/source/nord/n2x/n2xJucePlugin/n2xController.cpp b/source/nord/n2x/n2xJucePlugin/n2xController.cpp
@@ -9,6 +9,7 @@
#include "dsp56kEmu/logging.h"
#include "n2xLib/n2xmiditypes.h"
+#include "synthLib/midiTranslator.h"
namespace
{
@@ -31,7 +32,7 @@ namespace
namespace n2xJucePlugin
{
- Controller::Controller(AudioPluginAudioProcessor& _p) : pluginLib::Controller(_p, "parameterDescriptions_n2x.json"), m_state(nullptr)
+ Controller::Controller(AudioPluginAudioProcessor& _p) : pluginLib::Controller(_p, "parameterDescriptions_n2x.json"), m_state(nullptr, nullptr)
{
registerParams(_p, [](const uint8_t _part, const bool _isNonPartExclusive)
{
@@ -230,31 +231,11 @@ namespace n2xJucePlugin
const auto parts = m_state.getPartsForMidiChannel(ch);
- const auto ev = synthLib::SMidiEvent{synthLib::MidiEventSource::Editor, static_cast<uint8_t>(synthLib::M_CONTROLCHANGE + ch), cc, static_cast<uint8_t>(_value)};
+ auto ev = synthLib::SMidiEvent{synthLib::MidiEventSource::Editor, static_cast<uint8_t>(synthLib::M_CONTROLCHANGE + part), cc, static_cast<uint8_t>(_value)};
- 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);
-
- const auto& name = _parameter.getDescription().name;
-
- if(name == "Sync" || name == "RingMod" || name == "Distortion")
- {
- const auto value = combineSyncRingModDistortion(part, 0, false);
- setSingleParameter(part, n2x::Sync, value);
- }
- else
- {
- setSingleParameter(part, singleParam, static_cast<uint8_t>(_value));
- }
- }
- else
- {
- nonConstParam.setRateLimitMilliseconds(0);
- m_state.receive(ev);
- sendMidiEvent(ev);
- }
+ nonConstParam.setRateLimitMilliseconds(0);
+ m_state.changeSingleParameter(part, ev);
+ sendMidiEvent(n2x::State::createPartCC(part, ev));
}
void Controller::setSingleParameter(uint8_t _part, n2x::SingleParam _sp, uint8_t _value)
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, &getMidiTranslator())
{
}
diff --git a/source/nord/n2x/n2xLib/n2xmiditypes.h b/source/nord/n2x/n2xLib/n2xmiditypes.h
@@ -16,6 +16,7 @@ namespace n2x
EmuSetPotPosition = 90, // total dump is: f0, IdClavia, IdDevice, IdN2x, EmuSetPotPosition, KnobType / nibble high, nibble low / f7
EmuGetPotsPosition = 91,
+ EmuSetPartCC = 92,
};
enum SysexIndex
diff --git a/source/nord/n2x/n2xLib/n2xstate.cpp b/source/nord/n2x/n2xLib/n2xstate.cpp
@@ -4,6 +4,7 @@
#include "n2xhardware.h"
#include "synthLib/midiToSysex.h"
+#include "synthLib/midiTranslator.h"
#include "synthLib/midiTypes.h"
namespace n2x
@@ -180,7 +181,7 @@ namespace n2x
static const MultiDefaultData g_multiDefault = createMultiDefaultData();
- State::State(Hardware* _hardware) : m_hardware(_hardware)
+ State::State(Hardware* _hardware, synthLib::MidiTranslator* _midiTranslator) : m_hardware(_hardware), m_midiTranslator(_midiTranslator)
{
for(uint8_t i=0; i<static_cast<uint8_t>(m_singles.size()); ++i)
createDefaultSingle(m_singles[i], i);
@@ -252,7 +253,41 @@ namespace n2x
return false;
m_multi.fill(0);
std::copy(sysex.begin(), sysex.end(), m_multi.begin());
- send(_ev);
+
+ if (m_midiTranslator)
+ {
+ // As we need support for individual midi channels for the editor to adjus each part separately but
+ // knobs can only be modified via midi CC, we tell the device that parts 0-3 are on midi channels 0-3 even if
+ // they are not. The midi translator will translate regular midi messages and the editor uses messages that are
+ // not translated
+
+ m_midiTranslator->clear();
+
+ MultiDump dump = m_multi;
+ for (uint8_t i = 0; i < 4; ++i)
+ {
+ const auto ch = getPartMidiChannel(dump, i);
+ setPartMidiChannel(dump, i, i);
+ m_midiTranslator->addTargetChannel(ch, i);
+ }
+
+ synthLib::SMidiEvent e;
+ e.sysex.assign(dump.begin(), dump.end());
+ send(e);
+ }
+ else
+ {
+ send(_ev);
+ }
+
+ return true;
+ }
+
+ if (bank == SysexByte::MultiRequestBankEditBuffer)
+ {
+ _responses.emplace_back(synthLib::MidiEventSource::Internal);
+ _responses.back().sysex.assign(m_multi.begin(), m_multi.end());
+ _responses.back().sysex = validateDump(_responses.back().sysex);
return true;
}
@@ -278,6 +313,17 @@ namespace n2x
}
return true;
}
+ else if (bank == SysexByte::EmuSetPartCC)
+ {
+ synthLib::SMidiEvent e;
+ auto part = sysex[5];
+ e.a = sysex[6];
+ e.b = sysex[7];
+ e.c = sysex[8];
+ e.source = _ev.source;
+ e.offset = _ev.offset;
+ changeSingleParameter(part, e);
+ }
return false;
}
@@ -299,54 +345,59 @@ namespace n2x
const auto parts = getPartsForMidiChannel(_ev);
if(parts.empty())
return false;
-
- const auto cc = static_cast<ControlChange>(_ev.b);
- const auto it = g_controllerMap.find(cc);
- if(it == g_controllerMap.end())
- return false;
- const SingleParam param = it->second;
- const auto offset = getOffsetInSingleDump(param);
- switch (param)
+ for (const auto part : parts)
{
- case SingleParam::Sync:
- // this can either be sync or distortion, they end up in the same midi byte
- switch(cc)
- {
- case ControlChange::CCSync:
- for (const auto part : parts)
- {
- auto v = unpackNibbles(m_singles[part], offset);
- v &= ~0x3;
- v |= _ev.c & 0x3;
- packNibbles(m_singles[part], offset, v);
- }
- break;
- case ControlChange::CCDistortion:
- for (const auto part : parts)
- {
- auto v = unpackNibbles(m_singles[part], offset);
- v &= ~(1<<4);
- v |= _ev.c << 4;
- packNibbles(m_singles[part], offset, v);
- }
- break;
- default:
- assert(false && "unexpected control change type");
+ if (!changeSingleParameter(part, _ev))
return false;
- }
- break;
- default:
- for (const auto part : parts)
- packNibbles(m_singles[part], offset, _ev.c);
- return true;
}
+ return true;
}
- return false;
default:
return false;
}
}
+ bool State::changeSingleParameter(const uint8_t _part, const synthLib::SMidiEvent& _ev)
+ {
+ const auto cc = static_cast<ControlChange>(_ev.b);
+ const auto it = g_controllerMap.find(cc);
+ if(it == g_controllerMap.end())
+ return false;
+ const SingleParam param = it->second;
+ const auto offset = getOffsetInSingleDump(param);
+ switch (param)
+ {
+ case SingleParam::Sync:
+ // this can either be sync or distortion, they end up in the same midi byte
+ switch(cc)
+ {
+ case ControlChange::CCSync:
+ {
+ auto v = unpackNibbles(m_singles[_part], offset);
+ v &= ~0x3;
+ v |= _ev.c & 0x3;
+ packNibbles(m_singles[_part], offset, v);
+ }
+ return true;
+ case ControlChange::CCDistortion:
+ {
+ auto v = unpackNibbles(m_singles[_part], offset);
+ v &= ~(1<<4);
+ v |= _ev.c << 4;
+ packNibbles(m_singles[_part], offset, v);
+ }
+ return true;
+ default:
+ assert(false && "unexpected control change type");
+ return false;
+ }
+ break;
+ default:
+ packNibbles(m_singles[_part], offset, _ev.c);
+ return true;
+ }
+ }
+
bool State::changeSingleParameter(const uint8_t _part, const SingleParam _parameter, const uint8_t _value)
{
if(_part >= m_singles.size())
@@ -546,6 +597,19 @@ namespace n2x
return _dump;
}
+ synthLib::SMidiEvent& State::createPartCC(uint8_t _part, synthLib::SMidiEvent& _ccEvent)
+ {
+ _ccEvent.sysex = { 0xf0, IdClavia, SysexByte::DefaultDeviceId, IdN2X,
+ SysexByte::EmuSetPartCC,
+ _part,
+ _ccEvent.a,
+ _ccEvent.b,
+ _ccEvent.c,
+ 0xf7
+ };
+ return _ccEvent;
+ }
+
void State::send(const synthLib::SMidiEvent& _e) const
{
if(_e.source == synthLib::MidiEventSource::Plugin)
diff --git a/source/nord/n2x/n2xLib/n2xstate.h b/source/nord/n2x/n2xLib/n2xstate.h
@@ -11,6 +11,11 @@
#include "synthLib/midiTypes.h"
+namespace synthLib
+{
+ class MidiTranslator;
+}
+
namespace n2x
{
class Hardware;
@@ -21,7 +26,7 @@ namespace n2x
using SingleDump = std::array<uint8_t, g_singleDumpWithNameSize>;
using MultiDump = std::array<uint8_t, g_multiDumpWithNameSize>;
- explicit State(Hardware* _hardware);
+ explicit State(Hardware* _hardware, synthLib::MidiTranslator* _midiTranslator);
bool getState(std::vector<uint8_t>& _state);
bool setState(const std::vector<uint8_t>& _state);
@@ -36,6 +41,8 @@ namespace n2x
bool receiveNonSysex(const synthLib::SMidiEvent& _ev);
+ bool changeSingleParameter(uint8_t _part, const synthLib::SMidiEvent& _ev);
+
bool changeSingleParameter(uint8_t _part, SingleParam _parameter, uint8_t _value);
bool changeMultiParameter(MultiParam _parameter, uint8_t _value);
@@ -86,6 +93,11 @@ namespace n2x
return getMultiParam(_dump, static_cast<MultiParam>(SlotAMidiChannel + _part), 0);
}
+ template<typename TDump> static void setPartMidiChannel(TDump& _dump, const uint8_t _part, const uint8_t _channel)
+ {
+ setMultiParam(_dump, static_cast<MultiParam>(SlotAMidiChannel + _part), 0, _channel);
+ }
+
uint8_t getMultiParam(const MultiParam _param, const uint8_t _part) const
{
return getMultiParam(m_multi, _param, _part);
@@ -98,6 +110,13 @@ namespace n2x
return unpackNibbles<TDump>(_dump, off);
}
+ template<typename TDump> static void setMultiParam(TDump& _dump, const MultiParam _param, const uint8_t _part, const uint8_t _value)
+ {
+ const auto off = getOffsetInMultiDump(_param) + (_part << 2);
+
+ packNibbles(_dump, off, _value);
+ }
+
template<typename TDump> static uint8_t getSingleParam(const TDump& _dump, const SingleParam _param, const uint8_t _part)
{
const auto off = getOffsetInSingleDump(_param) + (_part << 2);
@@ -130,6 +149,8 @@ namespace n2x
static bool isValidPatchName(const std::vector<uint8_t>& _dump);
static std::vector<uint8_t> validateDump(const std::vector<uint8_t>& _dump);
+ static synthLib::SMidiEvent& createPartCC(uint8_t _part, synthLib::SMidiEvent& _ccEvent);
+
private:
template<size_t Size> bool receive(const std::array<uint8_t, Size>& _data)
{
@@ -142,6 +163,7 @@ namespace n2x
void send(const synthLib::SMidiEvent& _e) const;
Hardware* m_hardware;
+ synthLib::MidiTranslator* m_midiTranslator;
std::array<SingleDump, 4> m_singles;
MultiDump m_multi;
std::unordered_map<KnobType, uint8_t> m_knobStates;