commit aac9a163d517c2f5d7e69869ddb840683b11460d
parent 6cd2d4d6f0a53ff253dd1a4acae2d3bd30ef4943
Author: dsp56300 <87139854+dsp56300@users.noreply.github.com>
Date: Wed, 8 Sep 2021 21:09:28 +0200
Merge pull request #9 from talaviram/controller
Controller Feature
Diffstat:
9 files changed, 1707 insertions(+), 45 deletions(-)
diff --git a/source/jucePlugin/CMakeLists.txt b/source/jucePlugin/CMakeLists.txt
@@ -31,8 +31,12 @@ target_sources(jucePlugin
PRIVATE
PluginEditor.cpp
PluginProcessor.cpp
+ VirusController.cpp
+ VirusParameter.cpp
PluginEditor.h
PluginProcessor.h
+ VirusController.h
+ VirusParameter.h
version.h
)
diff --git a/source/jucePlugin/PluginEditor.cpp b/source/jucePlugin/PluginEditor.cpp
@@ -4,35 +4,54 @@
#include "version.h"
//==============================================================================
-AudioPluginAudioProcessorEditor::AudioPluginAudioProcessorEditor (AudioPluginAudioProcessor& p)
- : AudioProcessorEditor (&p), processorRef (p)
- , m_btSingleMode("Single Mode")
- , m_btMultiMode("Multi Mode")
+AudioPluginAudioProcessorEditor::AudioPluginAudioProcessorEditor(AudioPluginAudioProcessor &p) :
+ AudioProcessorEditor(&p), processorRef(p), m_btSingleMode("Single Mode"), m_btMultiMode("Multi Mode"),
+ m_tempEditor(p)
{
ignoreUnused (processorRef);
// Make sure that before the constructor has finished, you've set the
// editor's size to whatever you need it to be.
- setSize (400, 300);
+ setSize(800, 400);
+ m_btSingleMode.setRadioGroupId(0x3cf);
+ m_btMultiMode.setRadioGroupId(0x3cf);
addAndMakeVisible(m_btSingleMode);
addAndMakeVisible(m_btMultiMode);
-
m_btSingleMode.setTopLeftPosition(0,0);
m_btSingleMode.setSize(120,30);
-
+ m_btMultiMode.getToggleStateValue().referTo(*processorRef.getController().getParam(0, 2, 0x7a));
+ const auto isMulti = processorRef.getController().isMultiMode();
+ m_btSingleMode.setToggleState(!isMulti, juce::dontSendNotification);
+ m_btMultiMode.setToggleState(isMulti, juce::dontSendNotification);
+ m_btSingleMode.setClickingTogglesState(true);
+ m_btMultiMode.setClickingTogglesState(true);
m_btMultiMode.setTopLeftPosition(m_btSingleMode.getPosition().x + m_btSingleMode.getWidth() + 10, 0);
m_btMultiMode.setSize(120,30);
- m_btSingleMode.onClick = [this]()
- {
- switchPlayMode(0);
- };
-
- m_btMultiMode.onClick = [this]()
+ for (auto pt = 0; pt < 16; pt++)
{
- switchPlayMode(2);
- };
+ m_partSelectors[pt].onClick = [this, pt]() {
+ auto bankA = processorRef.getController().getSinglePresetNames(0);
+ auto bankB = processorRef.getController().getSinglePresetNames(1);
+ juce::PopupMenu pA;
+ juce::PopupMenu pB;
+ for (auto i = 0; i < 128; i++)
+ {
+ pA.addItem(bankA[i], [this, i, pt] { processorRef.getController().setCurrentPartPreset(pt, 0, i); });
+ pB.addItem(bankB[i], [this, i, pt] { processorRef.getController().setCurrentPartPreset(pt, 1, i); });
+ }
+ juce::PopupMenu selector;
+ selector.addSubMenu("Bank A", pA);
+ selector.addSubMenu("Bank B", pB);
+ selector.showMenu(juce::PopupMenu::Options());
+ };
+ addAndMakeVisible(m_partSelectors[pt]);
+ }
+
+ addAndMakeVisible(m_tempEditor);
+
+ startTimerHz(5);
}
AudioPluginAudioProcessorEditor::~AudioPluginAudioProcessorEditor()
@@ -53,19 +72,34 @@ void AudioPluginAudioProcessorEditor::paint (juce::Graphics& g)
if(!processorRef.isPluginValid())
message += "\n\nNo ROM, no sound!\nCopy ROM next to plugin, must end with .bin";
- g.drawFittedText(message, getLocalBounds(), juce::Justification::centred, 2);
- g.drawFittedText("To donate: paypal.me/dsp56300", getLocalBounds(), juce::Justification::centredBottom, 2);
+ g.drawFittedText(message, getLocalBounds().removeFromLeft(400).removeFromBottom(45), juce::Justification::centred,
+ 2);
+ g.drawFittedText("To donate: paypal.me/dsp56300", getLocalBounds().removeFromRight(400).removeFromTop(35),
+ juce::Justification::centred, 2);
}
-void AudioPluginAudioProcessorEditor::resized()
+void AudioPluginAudioProcessorEditor::timerCallback()
{
- // This is generally where you'll want to lay out the positions of any
- // subcomponents in your editor..
+ // ugly (polling!) way for refreshing presets names as this is temporary ui
+ const auto multiMode = processorRef.getController().isMultiMode();
+ for (auto pt = 0; pt < 16; pt++)
+ {
+ bool singlePartOrInMulti = pt == 0 || multiMode;
+ m_partSelectors[pt].setVisible(singlePartOrInMulti);
+ if (singlePartOrInMulti)
+ m_partSelectors[pt].setButtonText(processorRef.getController().getCurrentPartPresetName(pt));
+ }
}
-void AudioPluginAudioProcessorEditor::switchPlayMode(uint8_t _playMode) const
+void AudioPluginAudioProcessorEditor::resized()
{
- synthLib::SMidiEvent ev;
- ev.sysex = { 0xf0, 0x00, 0x20, 0x33, 0x01, 0x00, 0x72, 0x0, 0x7a, _playMode, 0xf7};
- processorRef.addMidiEvent(ev);
+ // This is generally where you'll want to lay out the positions of any
+ // subcomponents in your editor..
+ auto area = getLocalBounds();
+ area.removeFromTop(35);
+ m_tempEditor.setBounds(area.removeFromRight(400));
+ for (auto pt = 0; pt < 16; pt++)
+ {
+ m_partSelectors[pt].setBounds(area.removeFromTop(20));
+ }
}
diff --git a/source/jucePlugin/PluginEditor.h b/source/jucePlugin/PluginEditor.h
@@ -3,7 +3,7 @@
#include "PluginProcessor.h"
//==============================================================================
-class AudioPluginAudioProcessorEditor : public juce::AudioProcessorEditor
+class AudioPluginAudioProcessorEditor : public juce::AudioProcessorEditor, private juce::Timer
{
public:
explicit AudioPluginAudioProcessorEditor (AudioPluginAudioProcessor&);
@@ -14,11 +14,14 @@ public:
void resized() override;
private:
- void switchPlayMode(uint8_t _playMode) const;
-
- // This reference is provided as a quick way for your editor to
- // access the processor object that created it.
- AudioPluginAudioProcessor& processorRef;
+ void timerCallback() override;
+ // This reference is provided as a quick way for your editor to
+ // access the processor object that created it.
+ AudioPluginAudioProcessor& processorRef;
+
+ juce::GenericAudioProcessorEditor m_tempEditor;
+
+ juce::TextButton m_partSelectors[16];
juce::TextButton m_btSingleMode;
juce::TextButton m_btMultiMode;
diff --git a/source/jucePlugin/PluginProcessor.cpp b/source/jucePlugin/PluginProcessor.cpp
@@ -4,14 +4,13 @@
#include "../synthLib/os.h"
//==============================================================================
-AudioPluginAudioProcessor::AudioPluginAudioProcessor()
- : AudioProcessor (BusesProperties()
- .withInput ("Input", juce::AudioChannelSet::stereo(), true)
- .withOutput ("Output", juce::AudioChannelSet::stereo(), true)
- )
- , m_device(synthLib::findROM())
- , m_plugin(&m_device)
+AudioPluginAudioProcessor::AudioPluginAudioProcessor() :
+ AudioProcessor(BusesProperties()
+ .withInput("Input", juce::AudioChannelSet::stereo(), true)
+ .withOutput("Output", juce::AudioChannelSet::stereo(), true)),
+ m_device(synthLib::findROM()), m_plugin(&m_device)
{
+ auto &ctrl = getController(); // init controller
}
AudioPluginAudioProcessor::~AudioPluginAudioProcessor()
@@ -88,7 +87,6 @@ void AudioPluginAudioProcessor::prepareToPlay (double sampleRate, int samplesPer
{
// Use this method as the place to do any pre-playback
// initialisation that you need..
-
m_plugin.setSamplerate(static_cast<float>(sampleRate));
m_plugin.setBlockSize(samplesPerBlock);
setLatencySamples(m_plugin.getLatencySamples());
@@ -201,10 +199,12 @@ void AudioPluginAudioProcessor::processBlock (juce::AudioBuffer<float>& buffer,
m_midiOut.clear();
m_plugin.getMidiOut(m_midiOut);
+ if (m_midiOut.size() > 0)
+ m_controller->dispatchVirusOut(m_midiOut);
- for(size_t i=0; i<m_midiOut.size(); ++i)
- {
- const auto& e = m_midiOut[i];
+ for (size_t i = 0; i < m_midiOut.size(); ++i)
+ {
+ const auto& e = m_midiOut[i];
if(e.sysex.empty())
{
midiMessages.addEvent(juce::MidiMessage (e.a, e.b, e.c, 0.0), 0);
@@ -213,7 +213,7 @@ void AudioPluginAudioProcessor::processBlock (juce::AudioBuffer<float>& buffer,
{
midiMessages.addEvent(juce::MidiMessage (&e.sysex[0], static_cast<int>(e.sysex.size()), 0.0), 0);
}
- }
+ }
}
//==============================================================================
@@ -270,6 +270,17 @@ void AudioPluginAudioProcessor::addMidiEvent(const synthLib::SMidiEvent& ev)
m_plugin.addMidiEvent(ev);
}
+Virus::Controller &AudioPluginAudioProcessor::getController()
+{
+ if (m_controller == nullptr)
+ {
+ // initialize controller if not exists.
+ // assures PluginProcessor is fully constructed!
+ m_controller = std::make_unique<Virus::Controller>(*this);
+ }
+ return *m_controller;
+}
+
void AudioPluginAudioProcessor::setState(const void* _data, size_t _sizeInBytes)
{
if(_sizeInBytes < 1)
diff --git a/source/jucePlugin/PluginProcessor.h b/source/jucePlugin/PluginProcessor.h
@@ -4,6 +4,7 @@
#include "../synthLib/plugin.h"
#include "../virusLib/device.h"
+#include "VirusController.h"
//==============================================================================
class AudioPluginAudioProcessor : public juce::AudioProcessor
@@ -49,7 +50,7 @@ public:
// _____________
//
-
+ Virus::Controller &getController();
bool isPluginValid() const { return m_plugin.isValid(); }
void getLastMidiOut(std::vector<synthLib::SMidiEvent>& dst);
void addMidiEvent(const synthLib::SMidiEvent& ev);
@@ -57,8 +58,10 @@ public:
// _____________
//
private:
- void setState(const void* _data, size_t _sizeInBytes);
-
+ std::unique_ptr<Virus::Controller> m_controller;
+
+ void setState(const void *_data, size_t _sizeInBytes);
+
//==============================================================================
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AudioPluginAudioProcessor)
diff --git a/source/jucePlugin/VirusController.cpp b/source/jucePlugin/VirusController.cpp
@@ -0,0 +1,1361 @@
+#include "VirusController.h"
+#include "PluginProcessor.h"
+
+// TODO: all sysex structs can be refactored to common instead of including this!
+#include "../virusLib/microcontroller.h"
+
+using MessageType = virusLib::Microcontroller::SysexMessageType;
+
+namespace Virus
+{
+ static constexpr uint8_t kSysExStart[] = {0xf0, 0x00, 0x20, 0x33, 0x01};
+ static constexpr auto kHeaderWithMsgCodeLen = 7;
+
+ Controller::Controller(AudioPluginAudioProcessor &p, unsigned char deviceId) : m_processor(p), m_deviceId(deviceId)
+ {
+ registerParams();
+ // add lambda to enforce updating patches when virus switch from/to multi/single.
+ (findSynthParam(0, 0x72, 0x7a))->onValueChanged = [this] {
+ const uint8_t prg = isMultiMode() ? 0x0 : 0x40;
+ sendSysEx(constructMessage({MessageType::REQUEST_SINGLE, 0x0, prg}));
+ };
+ sendSysEx(constructMessage({MessageType::REQUEST_TOTAL}));
+ sendSysEx(constructMessage({MessageType::REQUEST_ARRANGEMENT}));
+ startTimer(5);
+ }
+
+ void Controller::registerParams()
+ {
+ // 16 parts * 3 pages * 128 params
+ // TODO: not register internal/unused params?
+ auto globalParams = std::make_unique<juce::AudioProcessorParameterGroup>("global", "Global", "|");
+ for (uint8_t pt = 0; pt < 16; pt++)
+ {
+ const auto partNumber = juce::String(pt + 1);
+ auto group =
+ std::make_unique<juce::AudioProcessorParameterGroup>("ch" + partNumber, "Ch " + partNumber, "|");
+ for (auto desc : m_paramsDescription)
+ {
+ ParamIndex idx = {static_cast<uint8_t>(desc.page), pt, desc.index};
+ auto p = std::make_unique<Parameter>(*this, desc, pt);
+ const bool isNonPartExclusive = (desc.classFlags & Parameter::Class::GLOBAL) ||
+ (desc.classFlags & Parameter::Class::NON_PART_SENSITIVE);
+ if (isNonPartExclusive)
+ {
+ if (pt != 0)
+ continue; // only register on first part!
+ }
+ if (p->getDescription().isPublic)
+ {
+ // lifecycle managed by host
+ m_synthParams.insert_or_assign(idx, p.get());
+ if (isNonPartExclusive)
+ {
+ jassert(pt == 0);
+ globalParams->addChild(std::move(p));
+ }
+ else
+ group->addChild(std::move(p));
+ }
+ else
+ m_synthInternalParams.insert_or_assign(idx, std::move(p));
+ }
+ m_processor.addParameterGroup(std::move(group));
+ }
+ m_processor.addParameterGroup(std::move(globalParams));
+ }
+
+ void Controller::parseMessage(const SysEx &msg)
+ {
+ if (msg.size() < 8)
+ return; // shorter than expected!
+
+ if (msg[msg.size() - 1] != 0xf7)
+ return; // invalid end?!?
+
+ for (auto i = 0; i < msg.size(); ++i)
+ {
+ if (i < 5)
+ {
+ if (msg[i] != kSysExStart[i])
+ return; // invalid header
+ }
+ else if (i == 5)
+ {
+ if (msg[i] != m_deviceId)
+ return; // not intended to this device!
+ }
+ else if (i == 6)
+ {
+ switch (msg[i])
+ {
+ case MessageType::DUMP_SINGLE:
+ parseSingle(msg);
+ break;
+ case MessageType::DUMP_MULTI:
+ parseMulti(msg);
+ break;
+ case MessageType::PARAM_CHANGE_A:
+ case MessageType::PARAM_CHANGE_B:
+ case MessageType::PARAM_CHANGE_C:
+ parseParamChange(msg);
+ break;
+ default:
+ std::cout << "Controller: Begin Unhandled SysEx! --" << std::endl;
+ printMessage(msg);
+ std::cout << "Controller: End Unhandled SysEx! --" << std::endl;
+ }
+ }
+ }
+ }
+
+ Parameter *Controller::findSynthParam(const uint8_t ch, const uint8_t bank, const uint8_t paramIndex)
+ {
+ auto it = m_synthParams.find({static_cast<uint8_t>(bank), ch, paramIndex});
+ if (it == m_synthParams.end())
+ {
+ auto iti = m_synthInternalParams.find({static_cast<uint8_t>(bank), ch, paramIndex});
+ if (iti == m_synthInternalParams.end())
+ return nullptr;
+ else
+ return iti->second.get();
+ }
+ return it->second;
+ }
+
+ juce::Value *Controller::getParam(uint8_t ch, uint8_t bank, uint8_t paramIndex)
+ {
+ auto *param = findSynthParam(ch, static_cast<uint8_t>(0x70 + bank), paramIndex);
+ if (param == nullptr)
+ {
+ // unregistered param?
+ jassertfalse;
+ return nullptr;
+ }
+ return ¶m->getValueObject();
+ }
+
+ void Controller::parseParamChange(const SysEx &msg)
+ {
+ const auto pos = kHeaderWithMsgCodeLen - 1;
+ const auto value = msg[pos + 3];
+ const auto bank = msg[pos];
+ const auto ch = msg[pos + 1];
+ const auto index = msg[pos + 2];
+ auto param = findSynthParam(ch, bank, index);
+ if (param == nullptr && ch != 0)
+ {
+ // ensure it's not global
+ param = findSynthParam(0, bank, index);
+ if (param == nullptr)
+ {
+ jassertfalse;
+ return;
+ }
+ auto flags = param->getDescription().classFlags;
+ if (!(flags & Parameter::Class::GLOBAL) && !(flags & Parameter::Class::NON_PART_SENSITIVE))
+ {
+ jassertfalse;
+ return;
+ }
+ }
+ param->setValueFromSynth(value, true);
+ // TODO:
+ /**
+ If a
+ global parameter or a Multi parameter is ac-
+ cessed, which is not part-sensitive (e.g. Input
+ Boost or Multi Delay Time), the part number is
+ ignored
+ */
+ }
+
+ juce::StringArray Controller::getSinglePresetNames(int bank) const
+ {
+ if (bank > 1 || bank < 0)
+ {
+ jassertfalse;
+ return {};
+ }
+
+ juce::StringArray bankNames;
+ for (auto i = 0; i < 128; i++)
+ bankNames.add(parseAsciiText(m_singles[0][i].data, 128 + 112));
+ return bankNames;
+ }
+ juce::StringArray Controller::getMultiPresetsName() const
+ {
+ juce::StringArray bankNames;
+ for (auto i = 0; i < 128; i++)
+ bankNames.add(parseAsciiText(m_multis[i].data, 0));
+ return bankNames;
+ }
+
+ juce::String Controller::getCurrentPartPresetName(uint8_t part)
+ {
+ // expensive but fresh!
+ constexpr uint8_t asciiStart = 112;
+ char text[kNameLength + 1];
+ text[kNameLength] = 0; // termination
+ for (auto pos = 0; pos < kNameLength; ++pos)
+ text[pos] = static_cast<int>(getParam(part, 1, asciiStart + pos)->getValue());
+ return juce::String(text);
+ }
+
+ void Controller::setCurrentPartPreset(uint8_t part, uint8_t bank, uint8_t prg)
+ {
+ if (bank < 0 || bank > 1 || prg < 0 || prg > 127)
+ {
+ jassertfalse;
+ return;
+ }
+
+ uint8_t pt = isMultiMode() ? part : 0x40;
+ auto &preset = m_singles[bank][prg];
+ SysEx patch = {MessageType::DUMP_SINGLE, 0x0, pt};
+ for (auto i = 0; i < kDataSizeInBytes; ++i)
+ patch.push_back(preset.data[i]);
+ sendSysEx(constructMessage(patch));
+ sendSysEx(constructMessage({MessageType::REQUEST_ARRANGEMENT}));
+ }
+
+ void Controller::parseSingle(const SysEx &msg)
+ {
+ constexpr auto pageSize = 128;
+ constexpr auto expectedDataSize = pageSize * 2 + 1 + 1; // we have 2 pages
+ constexpr auto checkSumSize = 1;
+ const auto dataSize = msg.size() - (kHeaderWithMsgCodeLen + 1); // 1 end byte, 1 bank, 1 prg
+ const auto hasChecksum = dataSize == expectedDataSize + checkSumSize;
+ assert(hasChecksum || dataSize == expectedDataSize);
+
+ SinglePatch patch;
+ patch.bankNumber = msg[kHeaderWithMsgCodeLen];
+ patch.progNumber = msg[kHeaderWithMsgCodeLen + 1];
+ [[maybe_unused]] const auto dataSum = copyData(msg, kHeaderWithMsgCodeLen + 2, patch.data);
+
+ if (hasChecksum)
+ {
+ const auto checksum = msg[msg.size() - 2];
+ const auto deviceId = msg[5];
+ [[maybe_unused]] const auto expectedSum =
+ (deviceId + 0x10 + patch.bankNumber + patch.progNumber + dataSum) & 0x7f;
+ assert(expectedSum == checksum);
+ }
+ if (patch.bankNumber == 0)
+ {
+ // virus sends also the single buffer not matter what's the mode.
+ // instead of keeping both, we 'encapsulate' this into first channel.
+ // the logic to maintain this is done by listening the global single/multi param.
+ if (isMultiMode() && patch.progNumber == 0x40)
+ return;
+ else if (!isMultiMode() && patch.progNumber == 0x0)
+ return;
+
+ constexpr auto bankSize = kDataSizeInBytes / 2;
+ const auto ch = patch.progNumber == 0x40 ? 0 : patch.progNumber;
+ for (auto i = 0; i < kDataSizeInBytes; i++)
+ {
+ if (auto *p = findSynthParam(ch, i > bankSize ? 0x71 : 0x70, i % bankSize))
+ p->setValueFromSynth(patch.data[i], true);
+ }
+ }
+ else
+ m_singles[patch.bankNumber - 1][patch.progNumber] = patch;
+
+ const auto namePos = kHeaderWithMsgCodeLen + 2 + 128 + 112;
+ assert(namePos < msg.size());
+ auto progName = parseAsciiText(msg, namePos);
+ DBG(progName);
+ }
+
+ 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);
+
+ 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 (hasChecksum)
+ {
+ 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);
+ }
+ m_multis[patch.progNumber] = patch;
+ }
+
+ void Controller::parseControllerDump(synthLib::SMidiEvent &m)
+ {
+ uint8_t bank = 0xFF;
+ uint8_t part = 0x40;
+ if (m.a & synthLib::M_CONTROLCHANGE)
+ {
+ part = m.a ^ synthLib::M_CONTROLCHANGE;
+ bank = 0x0;
+ }
+ else if (m.a & synthLib::M_POLYPRESSURE)
+ {
+ part = m.a ^ synthLib::M_POLYPRESSURE;
+ bank = 0x1;
+ }
+ else
+ {
+ jassertfalse;
+ return;
+ }
+ jassert(bank != 0xFF);
+ DBG(juce::String::formatted("Set part: %d bank: %s param: %d value: %d", part, bank == 0 ? "A" : "B", m.b,
+ m.c));
+ findSynthParam(part, bank, m.b)->setValueFromSynth(m.c, true);
+ }
+
+
+ juce::String numToBank(float bankIdx, Parameter::Description)
+ {
+ jassert(bankIdx >= 0);
+ juce::String prefix = "RAM";
+ if (bankIdx > 3)
+ {
+ prefix = bankIdx < 9 ? "ROM " : "ROM";
+ }
+ return prefix + juce::String(juce::roundToInt(bankIdx + 1));
+ }
+
+ juce::String numTo7bitSigned(const int idx)
+ {
+ const auto sign = idx > 64 ? "+" : "";
+ return sign + juce::String(juce::roundToInt(idx - 64));
+ }
+
+ juce::String paramTo7bitSigned(const float idx, Parameter::Description)
+ {
+ return numTo7bitSigned(juce::roundToInt(idx));
+ }
+
+ float textTo7bitSigned(juce::String text, Parameter::Description)
+ {
+ const auto val = std::clamp(text.getIntValue(), -64, 63);
+ return val + 64;
+ }
+
+ juce::String numToPan(float panIdx, Parameter::Description)
+ {
+ // handles rounding due to continuous...
+ const auto idx = juce::roundToInt(panIdx);
+ if (idx == 64)
+ return "Center";
+ if (idx == 0)
+ return "Left";
+ if (idx == 127)
+ return "Right";
+ return numTo7bitSigned(idx);
+ }
+
+ juce::String numToOscShape(float panIdx, Parameter::Description)
+ {
+ const auto idx = juce::roundToInt(panIdx);
+ if (idx == 64)
+ return "Wave";
+ if (idx == 0)
+ return "Saw";
+ if (idx == 127)
+ return "Pulse";
+ return numTo7bitSigned(idx);
+ }
+
+ juce::String numToOscWaveSelect(float panIdx, Parameter::Description)
+ {
+ const auto idx = juce::roundToInt(panIdx);
+ if (idx == 0)
+ return "Sine";
+ if (idx == 1)
+ return "Triangle";
+ return "Wave " + juce::String(idx);
+ }
+
+ juce::String numToKeyfollow(float panIdx, Parameter::Description)
+ {
+ const auto idx = juce::roundToInt(panIdx);
+ if (idx == 32)
+ return "Default";
+ return juce::String(idx);
+ }
+
+ juce::String numToSatCurv(float idx, Parameter::Description)
+ {
+ const auto ridx = juce::roundToInt(idx);
+ switch (ridx)
+ {
+ case 0: return "Off";
+ case 1: return "Light";
+ case 2: return "Soft";
+ case 3: return "Middle";
+ case 4: return "Hard";
+ case 5: return "Digital";
+ default: return juce::String(idx);
+ }
+ }
+
+ juce::String numToFilterMode(float idx, Parameter::Description)
+ {
+ const auto ridx = juce::roundToInt(idx);
+ switch (ridx)
+ {
+ case 0: return "Lowpass";
+ case 1: return "Highpass";
+ case 2: return "Bandpass";
+ case 3: return "Bandside";
+ default: return juce::String(idx);
+ }
+ }
+
+ juce::String numToFilterRouting(float idx, Parameter::Description)
+ {
+ const auto ridx = juce::roundToInt(idx);
+ switch (ridx)
+ {
+ case 0: return "Serial 4";
+ case 1: return "Serial 6";
+ case 2: return "Parallel 4";
+ case 3: return "Split";
+ default: return juce::String(idx);
+ }
+ }
+
+ juce::String numToEnvSusTime(float idx, Parameter::Description)
+ {
+ // handles rounding due to continuous...
+ const auto ridx = juce::roundToInt(idx);
+ if (ridx == 64)
+ return "Fall";
+ if (ridx == 0)
+ return "Infinite";
+ if (ridx == 127)
+ return "Rise";
+ return numTo7bitSigned(idx);
+ }
+
+ juce::String numToLfoShape(float idx, Parameter::Description)
+ {
+ const auto ridx = juce::roundToInt(idx);
+ switch (ridx)
+ {
+ case 0: return "Sine";
+ case 1: return "Triangle";
+ case 2: return "Saw";
+ case 3: return "Sqaure";
+ case 4: return "S&H";
+ case 5: return "S&G";
+ default: return juce::String(idx);
+ }
+ }
+
+ juce::String numToLfoMode(float v, Parameter::Description) { return juce::roundToInt(v) == 0 ? "Poly" : "Mono"; }
+
+ juce::String numToKeyMode(float idx, Parameter::Description)
+ {
+ const auto ridx = juce::roundToInt(idx);
+ switch (ridx)
+ {
+ case 0: return "Poly";
+ case 1: return "Mono 1";
+ case 2: return "Mono 2";
+ case 3: return "Mono 3";
+ case 4: return "Mono 4";
+ default: return juce::String(idx);
+ }
+ }
+
+ juce::String numToInputMode(float idx, Parameter::Description)
+ {
+ const auto ridx = juce::roundToInt(idx);
+ switch (ridx)
+ {
+ case 0: return "Off";
+ case 1: return "Dynamic";
+ case 2: return "Static";
+ case 3: return "To Effects";
+ default: return juce::String(idx);
+ }
+ }
+
+ juce::String numToDelayReverbMode(float idx, Parameter::Description)
+ {
+ const auto ridx = juce::roundToInt(idx);
+ switch (ridx)
+ {
+ case 0: return "Off";
+ case 1: return "Delay";
+ case 2: return "Reverb";
+ case 3: return "Reverb + Feedback";
+ default: return juce::String(idx);
+ }
+ }
+
+ juce::String numToArpMode(float idx, Parameter::Description)
+ {
+ const auto ridx = juce::roundToInt(idx);
+ switch (ridx)
+ {
+ case 0: return "Off";
+ case 1: return "Up";
+ case 2: return "Down";
+ case 3: return "Up&Down";
+ case 4: return "As Played";
+ case 5: return "Random";
+ case 6: return "Chord";
+ default: return juce::String(idx);
+ }
+ }
+
+ juce::String numToLfoDest(float idx, Parameter::Description)
+ {
+ const auto ridx = juce::roundToInt(idx);
+ switch (ridx)
+ {
+ case 0: return "Osc1";
+ case 1: return "Osc1+2";
+ case 2: return "Osc2";
+ case 3: return "PW1";
+ case 4: return "PW1+2";
+ case 5: return "PW2";
+ default: return juce::String(idx);
+ }
+ }
+
+ juce::String numToArpSwing(float idx, Parameter::Description)
+ {
+ return juce::String(juce::roundToInt(50 + (75 - 50) * (idx / 127))) + "%";
+ }
+
+ juce::String numToClockTempo(float idx, Parameter::Description)
+ {
+ return juce::String(juce::roundToInt(63 + (190 - 63) * (idx / 127))) + "BPM";
+ }
+
+ juce::String numToControlSmoothMode(float idx, Parameter::Description)
+ {
+ const auto ridx = juce::roundToInt(idx);
+ switch (ridx)
+ {
+ case 0: return "Off";
+ case 1: return "On";
+ case 2: return "Auto";
+ case 3: return "Note";
+ default: return juce::String(idx);
+ }
+ }
+
+ juce::String numToNegPos(float v, Parameter::Description)
+ {
+ return juce::roundToInt(v) == 0 ? "Negative" : "Positive";
+ }
+
+ juce::String numToLinExp(float v, Parameter::Description)
+ {
+ return juce::roundToInt(v) == 0 ? "Linear" : "Exponential";
+ }
+
+ juce::String numToModMatrixSource(float v, Parameter::Description)
+ {
+ const auto ridx = juce::roundToInt(v);
+ switch (ridx)
+ {
+ case 0: return "Off";
+ case 1: return "PitchBnd";
+ case 2: return "ChanPres";
+ case 3: return "ModWheel";
+ case 4: return "Breath";
+ case 5: return "Contr3";
+ case 6: return "Foot";
+ case 7: return "Data";
+ case 8: return "Balance";
+ case 9: return "Contr 9";
+ case 10: return "Express";
+ case 11: return "Contr 12";
+ case 12: return "Contr 13";
+ case 13: return "Contr 14";
+ case 14: return "Contr 15";
+ case 15: return "Contr 16";
+ case 16: return "HoldPed";
+ case 17: return "PortaSw";
+ case 18: return "SostPed";
+ case 19: return "AmpEnv";
+ case 20: return "FiltEnv";
+ case 21: return "Lfo 1";
+ case 22: return "Lfo 2";
+ case 23: return "Lfo 3";
+ case 24: return "VeloOn";
+ case 25: return "VeloOff";
+ case 26: return "KeyFlw";
+ case 27: return "Random";
+ default: return juce::String(v);
+ }
+ }
+
+ juce::String numToModMatrixDest(float v, Parameter::Description)
+ {
+ const auto ridx = juce::roundToInt(v);
+ switch (ridx)
+ {
+ case 0: return "Off";
+ case 1: return "PatchVol";
+ case 2: return "ChannelVol";
+ case 3: return "Panorama";
+ case 4: return "Transpose";
+ case 5: return "Portamento";
+ case 6: return "Osc1Shape";
+ case 7: return "Osc1PlsWdh";
+ case 8: return "Osc1WavSel";
+ case 9: return "Osc1Pitch";
+ case 10: return "Osc1Keyflw";
+ case 11: return "Osc2Shape";
+ case 12: return "Osc2PlsWdh";
+ case 13: return "Osc2WavSel";
+ case 14: return "Osc2Pitch";
+ case 15: return "Osc2Detune";
+ case 16: return "Osc2FmAmt";
+ case 17: return "Osc2EnvAmt";
+ case 18: return "FmEnvAmt";
+ case 19: return "Osc2Keyflw";
+ case 20: return "OscBalance";
+ case 21: return "SubOscVol";
+ case 22: return "OscMainVol";
+ case 23: return "NoiseVol";
+ case 24: return "Cutoff";
+ case 25: return "Cutoff2";
+ case 26: return "Filt1Reso";
+ case 27: return "Filt2Reso";
+ case 28: return "Flt1EnvAmt";
+ case 29: return "Flt2EnvAmt";
+ case 30: return "Flt1Keyflw";
+ case 31: return "Flt2Keyflw";
+ case 32: return "FltBalance";
+ case 33: return "FltAttack";
+ case 34: return "FltDecay";
+ case 35: return "FltSustain";
+ case 36: return "FltSusTime";
+ case 37: return "FltRelease";
+ case 38: return "AmpAttack";
+ case 39: return "AmpDecay";
+ case 40: return "AmpSustain";
+ case 41: return "AmpSusTime";
+ case 42: return "AmpRelease";
+ case 43: return "Lfo1Rate";
+ case 44: return "Lfo1Cont";
+ case 45: return "Lfo1>Osc1";
+ case 46: return "Lfo1>Osc2";
+ case 47: return "Lfo1>PlsWd";
+ case 48: return "Lfo1>Reso";
+ case 49: return "Lfo1>FltGn";
+ case 50: return "Lfo2Rate";
+ case 51: return "Lfo2Cont";
+ case 52: return "Lfo2>Shape";
+ case 53: return "Lfo2>Fm";
+ case 54: return "Lfo2>Cut1";
+ case 55: return "Lfo2>Cut2";
+ case 56: return "Lfo2>Pan";
+ case 57: return "Lfo3Rate";
+ case 58: return "Lfo3OscAmt";
+ case 59: return "UniDetune";
+ case 60: return "UniSpread";
+ case 61: return "UniLfoPhs";
+ case 62: return "ChorusMix";
+ case 63: return "ChorusRate";
+ case 64: return "ChorusDpth";
+ case 65: return "ChorusDly";
+ case 66: return "ChorusFeed";
+ case 67: return "EffectSend";
+ case 68: return "DelayTime";
+ case 69: return "DelayFeed";
+ case 70: return "DelayRate";
+ case 71: return "DelayDepth";
+ case 72: return "Osc1ShpVel";
+ case 73: return "Osc2ShpVel";
+ case 74: return "PlsWhdVel";
+ case 75: return "FmAmtVel";
+ case 76: return "Flt1EnvVel";
+ case 77: return "Flt2EnvVel";
+ case 78: return "Reso1Vel";
+ case 79: return "Reso2Vel";
+ case 80: return "AmpVel";
+ case 81: return "PanVel";
+ case 82: return "Ass1Amt1";
+ case 83: return "Ass2Amt1";
+ case 84: return "Ass2Amt2";
+ case 85: return "Ass3Amt1";
+ case 86: return "Ass3Amt2";
+ case 87: return "Ass3Amt3";
+ case 88: return "OscInitPhs";
+ case 89: return "PunchInt";
+ case 90: return "RingMod";
+ case 91: return "NoiseColor";
+ case 92: return "DelayColor";
+ case 93: return "ABoostInt";
+ case 94: return "ABoostTune";
+ case 95: return "DistInt";
+ case 96: return "RingmodMix";
+ case 97: return "Osc3Volume";
+ case 98: return "Osc3Semi";
+ case 99: return "Osc3Detune";
+ case 100: return "Lfo1AssAmt";
+ case 101: return "Lfo2AssAmt";
+ case 102: return "PhaserMix";
+ case 103: return "PhaserRate";
+ case 104: return "PhaserDept";
+ case 105: return "PhaserFreq";
+ case 106: return "PhaserFdbk";
+ case 107: return "PhaserSprd";
+ case 108: return "RevbDecay";
+ case 109: return "RevDamping";
+ case 110: return "RevbColor";
+ case 111: return "RevPredely";
+ case 112: return "RevFeedbck";
+ case 113: return "SecBalance";
+ case 114: return "ArpNoteLen";
+ case 115: return "ArpSwing";
+ case 116: return "ArpPattern";
+ case 117: return "EqMidGain";
+ case 118: return "EqMidFreq";
+ case 119: return "EqMidQFactor";
+ case 120: return "Assign4Amt";
+ case 121: return "Assign5Amt";
+ case 122: return "Assign6Amt";
+ default: return juce::String(v);
+ }
+ }
+
+ juce::String numToSoftKnobDest(float v, Parameter::Description)
+ {
+ const auto ridx = juce::roundToInt(v);
+ switch (ridx)
+ {
+ case 0: return "Off";
+ case 1: return "ModWheel";
+ case 2: return "Breath";
+ case 3: return "Contr3";
+ case 4: return "Foot";
+ case 5: return "Data";
+ case 6: return "Balance";
+ case 7: return "Contr9";
+ case 8: return "Expression";
+ case 9: return "Contr12";
+ case 10: return "Contr13";
+ case 11: return "Contr14";
+ case 12: return "Contr15";
+ case 13: return "Contr16";
+ case 14: return "PatchVolume";
+ case 15: return "ChannelVolume";
+ case 16: return "Panorama";
+ case 17: return "Transpose";
+ case 18: return "Portamento";
+ case 19: return "UnisonDetune";
+ case 20: return "UnisonPanSprd";
+ case 21: return "UnisoLfoPhase";
+ case 22: return "ChorusMix";
+ case 23: return "ChorusRate";
+ case 24: return "ChorusDepth";
+ case 25: return "ChorusDelay";
+ case 26: return "ChorusFeedback";
+ case 27: return "EffectSend";
+ case 28: return "DelayTime(ms)";
+ case 29: return "DelayFeedback";
+ case 30: return "DelayRate";
+ case 31: return "DelayDepth";
+ case 32: return "Osc1WavSelect";
+ case 33: return "Osc1PulseWidth";
+ case 34: return "Osc1Semitone";
+ case 35: return "Osc1Keyfollow";
+ case 36: return "Osc2WavSelect";
+ case 37: return "Os2PulseWidth";
+ case 38: return "Osc2EnvAmount";
+ case 39: return "FmEnvAmount";
+ case 40: return "Osc2Keyfollow";
+ case 41: return "NoiseVolume";
+ case 42: return "Filt1Resonance";
+ case 43: return "Filt2Resonance";
+ case 44: return "Filt1EnvAmount";
+ case 45: return "Filt2EnvAmount";
+ case 46: return "Filt1Keyfollow";
+ case 47: return "Filt2Keyfollow";
+ case 48: return "Lfo1Symmetry";
+ case 49: return "Lfo1>Osc1";
+ case 50: return "Lfo1>Osc2";
+ case 51: return "Lfo1>PulsWidth";
+ case 52: return "Lfo1>Resonance";
+ case 53: return "Lfo1>FiltGain";
+ case 54: return "Lfo2Symmetry";
+ case 55: return "Lfo2>Shape";
+ case 56: return "Lfo2>FmAmount";
+ case 57: return "Lfo2>Cutoff1";
+ case 58: return "Lfo2>Cutoff2";
+ case 59: return "Lfo2>Panorama";
+ case 60: return "Lfo3Rate";
+ case 61: return "Lfo3OscAmount";
+ case 62: return "Osc1ShapeVel";
+ case 63: return "Osc2ShapeVel";
+ case 64: return "PulsWidthVel";
+ case 65: return "FmAmountVel";
+ case 66: return "Filt1EnvVel";
+ case 67: return "Filt2EnvVel";
+ case 68: return "Resonance1Vel";
+ case 69: return "Resonance2Vel";
+ case 70: return "AmplifierVel";
+ case 71: return "PanoramaVel";
+ case 72: return "Assign1Amt1";
+ case 73: return "Assign2Amt1";
+ case 74: return "Assign2Amt2";
+ case 75: return "Assign3Amt1";
+ case 76: return "Assign3Amt2";
+ case 77: return "Assign3Amt3";
+ case 78: return "ClockTempo";
+ case 79: return "InputThru";
+ case 80: return "OscInitPhase";
+ case 81: return "PunchIntensity";
+ case 82: return "Ringmodulator";
+ case 83: return "NoiseColor";
+ case 84: return "DelayColor";
+ case 85: return "AnalogBoostInt";
+ case 86: return "AnalogBstTune";
+ case 87: return "DistortionInt";
+ case 88: return "RingModMix";
+ case 89: return "Osc3Volume";
+ case 90: return "Osc3Semitone";
+ case 91: return "Osc3Detune";
+ case 92: return "Lfo1AssignAmt";
+ case 93: return "Lfo2AssignAmt";
+ case 94: return "PhaserMix";
+ case 95: return "PhaserRate";
+ case 96: return "PhaserDepth";
+ case 97: return "PhaserFrequency";
+ case 98: return "PhaserFeedback";
+ case 99: return "PhaserSpread";
+ case 100: return "RevDecayTime";
+ case 101: return "ReverbDamping";
+ case 102: return "ReverbColor";
+ case 103: return "ReverbFeedback";
+ case 104: return "SecondBalance";
+ case 105: return "ArpMode";
+ case 106: return "ArpPattern";
+ case 107: return "ArpClock";
+ case 108: return "ArpNoteLength";
+ case 109: return "ArpSwing";
+ case 110: return "ArpOctaves";
+ case 111: return "ArpHold";
+ case 112: return "EqMidGain";
+ case 113: return "EqMidFreq";
+ case 114: return "EqMidQFactor";
+ case 115: return "Assign4Amt";
+ case 116: return "Assign5Amt";
+ case 117: return "Assign6Amt";
+ default: return juce::String(v);
+ }
+ }
+
+ juce::String numToKeytrigger(float v, Parameter::Description)
+ {
+ return juce::roundToInt(v) == 0 ? "Off" : "Keytrigger Phase " + juce::String(juce::roundToInt(v));
+ }
+
+ juce::String numToUnisonMode(float v, Parameter::Description)
+ {
+ return juce::roundToInt(v) == 0 ? "Off" : "Twin " + juce::String(juce::roundToInt(v));
+ }
+
+ juce::String numToOscFmMode(float idx, Parameter::Description)
+ {
+ const auto ridx = juce::roundToInt(idx);
+ switch (ridx)
+ {
+ case 0: return "Pos-Tri";
+ case 1: return "Tri";
+ case 2: return "Wave";
+ case 3: return "Noise";
+ case 4: return "In L";
+ case 5: return "In L+R";
+ default: return juce::String(idx);
+ }
+ }
+
+ juce::String numToMusicDivision(float idx, Parameter::Description)
+ {
+ const auto ridx = juce::roundToInt(idx);
+ switch (ridx)
+ {
+ case 0: return "Off";
+ case 1: return "1/64";
+ case 2: return "1/32";
+ case 3: return "1/16";
+ case 4: return "1/8";
+ case 5: return "1/4";
+ case 6: return "1/2";
+ case 7: return "3/64";
+ case 8: return "3/32";
+ case 9: return "3/16";
+ case 10: return "3/8";
+ case 11: return "1/24";
+ case 12: return "1/12";
+ case 13: return "1/6";
+ case 14: return "1/3";
+ case 15: return "2/3";
+ case 16: return "3/4";
+ case 17: return "1/1";
+ case 18: return "2/1";
+ case 19: return "4/1";
+ case 20: return "8/1";
+ case 21: return "16/1";
+ default: return juce::String(idx);
+ }
+ }
+
+ juce::String numToPhaserMode(float v, Parameter::Description)
+ {
+ return juce::roundToInt(v) == 0 ? "Off" : "Stage " + juce::String(juce::roundToInt(v));
+ }
+
+ const std::initializer_list<Parameter::Description> Controller::m_paramsDescription =
+{
+ {Parameter::Page::A, Parameter::Class::PERFORMANCE_CONTROLLER, 0, "Bank Select", {0, 3 + 26}, numToBank, {}, false, true, false}, // The Virus TI contains 4 banks of RAM, followed by 26 banks of ROM
+ {Parameter::Page::A, Parameter::Class::PERFORMANCE_CONTROLLER, 1, "Modulation Wheel", {0,127}, {},{}, false, false, false},
+ {Parameter::Page::A, Parameter::Class::PERFORMANCE_CONTROLLER, 2, "Breath Controller", {0,127}, {},{}, false, false, false},
+ {Parameter::Page::A, Parameter::Class::PERFORMANCE_CONTROLLER, 3, "Contr 3", {0,127}, {},{}, false, false, false},
+ {Parameter::Page::A, Parameter::Class::PERFORMANCE_CONTROLLER, 4, "Foot Controller", {0,127}, {},{}, false, false, false},
+ {Parameter::Page::A, Parameter::Class::SOUNDBANK_A, 5, "Portamento Time", {0,127}, {},{}, true, false, false},
+ {Parameter::Page::A, Parameter::Class::PERFORMANCE_CONTROLLER, 6, "Data Slider", {0,127}, {},{}, false, false, false},
+ {Parameter::Page::A, Parameter::Class::PERFORMANCE_CONTROLLER, 7, "Channel Volume", {0,127}, {},{}, true, false, false},
+ {Parameter::Page::A, Parameter::Class::PERFORMANCE_CONTROLLER, 8, "Balance", {0,127}, {},{}, true, false, false},
+ {Parameter::Page::A, Parameter::Class::PERFORMANCE_CONTROLLER, 9, "Contr 9", {0,127}, {},{}, false, false, false},
+ {Parameter::Page::A, Parameter::Class::SOUNDBANK_A, 10, "Panorama", {0,127}, numToPan,{}, true, false, false},
+ {Parameter::Page::A, Parameter::Class::PERFORMANCE_CONTROLLER, 11, "Expression", {0,127}, {},{}, false, false, false},
+ {Parameter::Page::A, Parameter::Class::PERFORMANCE_CONTROLLER, 12, "Contr 12", {0,127}, {},{}, false, false, false},
+ {Parameter::Page::A, Parameter::Class::PERFORMANCE_CONTROLLER, 13, "Contr 13", {0,127}, {},{}, false, false, false},
+ {Parameter::Page::A, Parameter::Class::PERFORMANCE_CONTROLLER, 14, "Contr 14", {0,127}, {},{}, false, false, false},
+ {Parameter::Page::A, Parameter::Class::PERFORMANCE_CONTROLLER, 15, "Contr 15", {0,127}, {},{}, false, false, false},
+ {Parameter::Page::A, Parameter::Class::PERFORMANCE_CONTROLLER, 16, "Contr 16", {0,127}, {},{}, false, false, false},
+ {Parameter::Page::A, Parameter::Class::SOUNDBANK_A, 17, "Osc1 Shape", {0,127}, numToOscShape,{}, true, false, false},
+ {Parameter::Page::A, Parameter::Class::SOUNDBANK_A, 18, "Osc1 Pulsewidth", {0,127}, {},{}, true, false, false},
+ {Parameter::Page::A, Parameter::Class::SOUNDBANK_A, 19, "Osc1 Wave Select", {0,64}, numToOscWaveSelect, {}, true, true, false},
+ {Parameter::Page::A, Parameter::Class::SOUNDBANK_A, 20, "Osc1 Semitone", {0,127}, paramTo7bitSigned, textTo7bitSigned, true, false, false},
+ {Parameter::Page::A, Parameter::Class::SOUNDBANK_A, 21, "Osc1 Keyfollow", {0,127}, numToKeyfollow, {}, true, false, false},
+ {Parameter::Page::A, Parameter::Class::SOUNDBANK_A, 22, "Osc2 Shape", {0,127}, numToOscShape, {}, true, false, false},
+ {Parameter::Page::A, Parameter::Class::SOUNDBANK_A, 23, "Osc2 Pulsewidth", {0,127}, {}, {}, true, false, false},
+ {Parameter::Page::A, Parameter::Class::SOUNDBANK_A, 24, "Osc2 Wave Select", {0,64}, numToOscWaveSelect,{}, true, true, false},
+ {Parameter::Page::A, Parameter::Class::SOUNDBANK_A, 25, "Osc2 Semitone", {0,127}, paramTo7bitSigned, textTo7bitSigned, true, false, false},
+ {Parameter::Page::A, Parameter::Class::SOUNDBANK_A, 26, "Osc2 Detune", {0,127}, {},{}, true, false, false},
+ {Parameter::Page::A, Parameter::Class::SOUNDBANK_A, 27, "Osc2 FM Amount", {0,127}, {},{}, true, false, false},
+ {Parameter::Page::A, Parameter::Class::SOUNDBANK_A, 28, "Osc2 Sync", {0,1}, {},{}, true, false, true},
+ {Parameter::Page::A, Parameter::Class::SOUNDBANK_A, 29, "Osc2 Filt Env Amt", {0,127}, paramTo7bitSigned, textTo7bitSigned, true, false, false},
+ {Parameter::Page::A, Parameter::Class::SOUNDBANK_A, 30, "FM Filt Env Amt", {0,127}, paramTo7bitSigned, textTo7bitSigned, true, false, false},
+ {Parameter::Page::A, Parameter::Class::SOUNDBANK_A, 31, "Osc2 Keyfollow", {0,127}, numToKeyfollow,{}, true, false, false},
+ {Parameter::Page::A, Parameter::Class::PERFORMANCE_CONTROLLER, 32, "Bank Select", {0, 3 + 26}, numToBank,{}, false, true, false},
+ {Parameter::Page::A, Parameter::Class::SOUNDBANK_A, 33, "Osc Balance", {0,127}, paramTo7bitSigned, textTo7bitSigned, true, false, false},
+ {Parameter::Page::A, Parameter::Class::SOUNDBANK_A, 34, "Suboscillator Volume", {0,127}, {},{}, true, false, false},
+ {Parameter::Page::A, Parameter::Class::SOUNDBANK_A, 35, "Suboscillator Shape", {0,1}, [](float v, Parameter::Description){ return juce::roundToInt(v) == 0 ? "Square" : "Triangle"; },{}, true, true, true},
+ {Parameter::Page::A, Parameter::Class::SOUNDBANK_A, 36, "Osc Mainvolume", {0,127}, {},{}, true, false, false},
+ {Parameter::Page::A, Parameter::Class::SOUNDBANK_A, 37, "Noise Volume", {0,127}, {},{}, true, false, false},
+ {Parameter::Page::A, Parameter::Class::SOUNDBANK_A, 38, "Ringmodulator Volume", {0,127}, {},{}, true, false, false},
+ {Parameter::Page::A, Parameter::Class::SOUNDBANK_A|Parameter::Class::VIRUS_C, 39, "Noise Color", {0,127}, paramTo7bitSigned, textTo7bitSigned, true, false, false},
+ {Parameter::Page::A, Parameter::Class::SOUNDBANK_A, 40, "Cutoff", {0,127}, {},{}, true, false, false},
+ {Parameter::Page::A, Parameter::Class::SOUNDBANK_A, 41, "Cutoff2", {0,127}, paramTo7bitSigned, textTo7bitSigned, true, false, false},
+ {Parameter::Page::A, Parameter::Class::SOUNDBANK_A, 42, "Filter1 Resonance", {0,127}, {},{}, true, false, false},
+ {Parameter::Page::A, Parameter::Class::SOUNDBANK_A, 43, "Filter2 Resonance", {0,127}, {},{}, true, false, false},
+ {Parameter::Page::A, Parameter::Class::SOUNDBANK_A, 44, "Filter1 Env Amt", {0,127}, {},{}, true, false, false},
+ {Parameter::Page::A, Parameter::Class::SOUNDBANK_A, 45, "Filter2 Env Amt", {0,127}, {},{}, true, false, false},
+ {Parameter::Page::A, Parameter::Class::SOUNDBANK_A, 46, "Filter1 Keyfollow", {0,127}, paramTo7bitSigned, textTo7bitSigned, true, false, false},
+ {Parameter::Page::A, Parameter::Class::SOUNDBANK_A, 47, "Filter2 Keyfollow", {0,127}, paramTo7bitSigned, textTo7bitSigned, true, false, false},
+ {Parameter::Page::A, Parameter::Class::SOUNDBANK_A, 48, "Filter Balance", {0,127}, paramTo7bitSigned, textTo7bitSigned, true, false, false},
+ {Parameter::Page::A, Parameter::Class::SOUNDBANK_A, 49, "Saturation Curve", {0,6}, numToSatCurv, {}, true, true, false},
+ {Parameter::Page::A, Parameter::Class::SOUNDBANK_A, 51, "Filter1 Mode", {0,3}, numToFilterMode, {}, true, true, false},
+ {Parameter::Page::A, Parameter::Class::SOUNDBANK_A, 52, "Filter2 Mode", {0,3}, numToFilterMode, {}, true, true, false},
+ {Parameter::Page::A, Parameter::Class::SOUNDBANK_A, 53, "Filter Routing", {0,3}, numToFilterRouting, {}, true, true, false},
+ {Parameter::Page::A, Parameter::Class::SOUNDBANK_A, 54, "Filter Env Attack", {0,127}, {},{}, true, false, false},
+ {Parameter::Page::A, Parameter::Class::SOUNDBANK_A, 55, "Filter Env Decay", {0,127}, {},{}, true, false, false},
+ {Parameter::Page::A, Parameter::Class::SOUNDBANK_A, 56, "Filter Env Sustain", {0,127}, {},{}, true, false, false},
+ {Parameter::Page::A, Parameter::Class::SOUNDBANK_A, 57, "Filter Env Sustain Time", {0,127}, numToEnvSusTime, {}, true, false, false},
+ {Parameter::Page::A, Parameter::Class::SOUNDBANK_A, 58, "Filter Env Release", {0,127}, {},{}, true, false, false},
+ {Parameter::Page::A, Parameter::Class::SOUNDBANK_A, 59, "Amp Env Attack", {0,127}, {},{}, true, false, false},
+ {Parameter::Page::A, Parameter::Class::SOUNDBANK_A, 60, "Amp Env Decay", {0,127}, {},{}, true, false, false},
+ {Parameter::Page::A, Parameter::Class::SOUNDBANK_A, 61, "Amp Env Sustain", {0,127}, {},{}, true, false, false},
+ {Parameter::Page::A, Parameter::Class::SOUNDBANK_A, 62, "Amp Env Sustain Time", {0,127}, numToEnvSusTime, {}, true, false, false},
+ {Parameter::Page::A, Parameter::Class::SOUNDBANK_A, 63, "Amp Env Release", {0,127}, {},{}, true, false, false},
+ {Parameter::Page::A, Parameter::Class::PERFORMANCE_CONTROLLER, 64, "Hold Pedal", {0,127}, {},{}, false, false, false},
+ {Parameter::Page::A, Parameter::Class::PERFORMANCE_CONTROLLER, 65, "Portamento Pedal", {0,127}, {},{}, false, false, false},
+ {Parameter::Page::A, Parameter::Class::PERFORMANCE_CONTROLLER, 66, "Sostenuto Pedal", {0,127}, {},{}, false, false, false},
+ {Parameter::Page::A, Parameter::Class::SOUNDBANK_A, 67, "Lfo1 Rate", {0,127}, {},{}, true, false, false},
+ {Parameter::Page::A, Parameter::Class::SOUNDBANK_A, 68, "Lfo1 Shape", {0,5}, numToLfoShape, {}, true, false, false},
+ {Parameter::Page::A, Parameter::Class::SOUNDBANK_A, 69, "Lfo1 Env Mode", {0,1}, {},{}, true, false, true},
+ {Parameter::Page::A, Parameter::Class::SOUNDBANK_A, 70, "Lfo1 Mode", {0,1}, numToLfoMode, {}, true, false, true},
+ {Parameter::Page::A, Parameter::Class::SOUNDBANK_A, 71, "Lfo1 Symmetry", {0,127}, paramTo7bitSigned, textTo7bitSigned, true, false, false},
+ {Parameter::Page::A, Parameter::Class::SOUNDBANK_A, 72, "Lfo1 Keyfollow", {0,127}, {},{}, true, false, false},
+ {Parameter::Page::A, Parameter::Class::SOUNDBANK_A, 73, "Lfo1 Keytrigger", {0,127}, numToKeytrigger, {}, true, false, false},
+ {Parameter::Page::A, Parameter::Class::SOUNDBANK_A, 74, "Osc1 Lfo1 Amount", {0,127}, paramTo7bitSigned, textTo7bitSigned, true, false, false},
+ {Parameter::Page::A, Parameter::Class::SOUNDBANK_A, 75, "Osc2 Lfo1 Amount", {0,127}, paramTo7bitSigned, textTo7bitSigned, true, false, false},
+ {Parameter::Page::A, Parameter::Class::SOUNDBANK_A, 76, "PW Lfo1 Amount", {0,127}, paramTo7bitSigned, textTo7bitSigned, true, false, false},
+ {Parameter::Page::A, Parameter::Class::SOUNDBANK_A, 77, "Reso Lfo1 Amount", {0,127}, paramTo7bitSigned, textTo7bitSigned, true, false, false},
+ {Parameter::Page::A, Parameter::Class::SOUNDBANK_A, 78, "FiltGain Lfo1 Amount", {0,127}, paramTo7bitSigned, textTo7bitSigned, true, false, false},
+ {Parameter::Page::A, Parameter::Class::SOUNDBANK_A, 79, "Lfo2 Rate", {0,127}, {},{}, true, false, false},
+ {Parameter::Page::A, Parameter::Class::SOUNDBANK_A, 80, "Lfo2 Shape", {0,5}, numToLfoShape,{}, true, true, false},
+ {Parameter::Page::A, Parameter::Class::SOUNDBANK_A, 81, "Lfo2 Env Mode", {0,1}, {},{}, true, false, true},
+ {Parameter::Page::A, Parameter::Class::SOUNDBANK_A, 82, "Lfo2 Mode", {0,1}, numToLfoMode, {}, true, false, true},
+ {Parameter::Page::A, Parameter::Class::SOUNDBANK_A, 83, "Lfo2 Symmetry", {0,127}, paramTo7bitSigned, textTo7bitSigned, true, false, false},
+ {Parameter::Page::A, Parameter::Class::SOUNDBANK_A, 84, "Lfo2 Keyfollow", {0,127}, {},{}, true, false, false},
+ {Parameter::Page::A, Parameter::Class::SOUNDBANK_A, 85, "Lfo2 Keytrigger", {0,127}, {},{}, true, false, false},
+ {Parameter::Page::A, Parameter::Class::SOUNDBANK_A, 86, "OscShape Lfo2 Amount", {0,127}, paramTo7bitSigned, textTo7bitSigned, true, false, false},
+ {Parameter::Page::A, Parameter::Class::SOUNDBANK_A, 87, "FmAmount Lfo2 Amount", {0,127}, paramTo7bitSigned, textTo7bitSigned, true, false, false},
+ {Parameter::Page::A, Parameter::Class::SOUNDBANK_A, 88, "Cutoff1 Lfo2 Amount", {0,127}, paramTo7bitSigned, textTo7bitSigned, true, false, false},
+ {Parameter::Page::A, Parameter::Class::SOUNDBANK_A, 89, "Cutoff2 Lfo2 Amount", {0,127}, paramTo7bitSigned, textTo7bitSigned, true, false, false},
+ {Parameter::Page::A, Parameter::Class::SOUNDBANK_A, 90, "Panorama Lfo2 Amount", {0,127}, paramTo7bitSigned, textTo7bitSigned, true, false, false},
+ {Parameter::Page::A, Parameter::Class::SOUNDBANK_A, 91, "Patch Volume", {0,127}, {},{}, true, false, false},
+ {Parameter::Page::A, Parameter::Class::SOUNDBANK_A, 93, "Transpose", {0,127}, paramTo7bitSigned, textTo7bitSigned, true, false, false},
+ {Parameter::Page::A, Parameter::Class::SOUNDBANK_A, 94, "Key Mode", {0,4}, numToKeyMode, {}, true, false, false},
+ {Parameter::Page::A, Parameter::Class::SOUNDBANK_A, 97, "Unison Mode", {0,15}, numToUnisonMode, {}, true, true, false},
+ {Parameter::Page::A, Parameter::Class::SOUNDBANK_A, 98, "Unison Detune", {0,127}, {},{}, true, false, false},
+ {Parameter::Page::A, Parameter::Class::SOUNDBANK_A, 99, "Unison Panorama Spread", {0,127}, {},{}, true, false, false},
+ {Parameter::Page::A, Parameter::Class::SOUNDBANK_A, 100, "Unison Lfo Phase", {0,127}, paramTo7bitSigned, textTo7bitSigned, true, false, false},
+ {Parameter::Page::A, Parameter::Class::SOUNDBANK_A, 101, "Input Mode", {0,2}, numToInputMode, {}, true, true, false},
+ {Parameter::Page::A, Parameter::Class::SOUNDBANK_A, 102, "Input Select", {0,8}, {},{}, true, false, false},
+ {Parameter::Page::A, Parameter::Class::SOUNDBANK_A, 105, "Chorus Mix", {0,127}, {},{}, true, false, false},
+ {Parameter::Page::A, Parameter::Class::SOUNDBANK_A, 106, "Chorus Rate", {0,127}, {},{}, true, false, false},
+ {Parameter::Page::A, Parameter::Class::SOUNDBANK_A, 107, "Chorus Depth", {0,127}, {},{}, true, false, false},
+ {Parameter::Page::A, Parameter::Class::SOUNDBANK_A, 108, "Chorus Delay", {0,127}, {},{}, true, false, false},
+ {Parameter::Page::A, Parameter::Class::SOUNDBANK_A, 109, "Chorus Feedback", {0,127}, paramTo7bitSigned, textTo7bitSigned, true, false, false},
+ {Parameter::Page::A, Parameter::Class::SOUNDBANK_A, 110, "Chorus Lfo Shape", {0,5}, numToLfoShape, {}, true, true, false},
+ {Parameter::Page::A, Parameter::Class::SOUNDBANK_A, 112, "Delay/Reverb Mode", {0,3}, numToDelayReverbMode, {}, true, false, true},
+ {Parameter::Page::A, Parameter::Class::SOUNDBANK_A|Parameter::Class::MULTI_OR_SINGLE, 113, "Effect Send", {0,127}, {},{}, true, false, false},
+ {Parameter::Page::A, Parameter::Class::SOUNDBANK_A|Parameter::Class::MULTI_OR_SINGLE|Parameter::Class::NON_PART_SENSITIVE, 114, "Delay Time", {0,127}, {},{}, true, false, false},
+ {Parameter::Page::A, Parameter::Class::SOUNDBANK_A|Parameter::Class::MULTI_OR_SINGLE|Parameter::Class::NON_PART_SENSITIVE, 115, "Delay Feedback", {0,127}, {},{}, true, false, false},
+ {Parameter::Page::A, Parameter::Class::SOUNDBANK_A|Parameter::Class::MULTI_OR_SINGLE|Parameter::Class::NON_PART_SENSITIVE, 116, "Delay Rate / Reverb Decay Time", {0,127}, {},{}, true, false, false},
+ {Parameter::Page::A, Parameter::Class::SOUNDBANK_A|Parameter::Class::MULTI_OR_SINGLE|Parameter::Class::NON_PART_SENSITIVE, 117, "Delay Depth / Reverb Room Size", {0,127}, {},{}, true, false, false},
+ {Parameter::Page::A, Parameter::Class::SOUNDBANK_A|Parameter::Class::MULTI_OR_SINGLE|Parameter::Class::NON_PART_SENSITIVE, 118, "Delay Lfo Shape", {0,5}, numToLfoShape, {}, true, true, false},
+// {Parameter::Page::A, Parameter::Class::SOUNDBANK_A|Parameter::Class::MULTI_OR_SINGLE|Parameter::Class::NON_PART_SENSITIVE, 118, "Reverb Damping", {0,127}, {},{}, true, false, false},
+ {Parameter::Page::A, Parameter::Class::SOUNDBANK_A|Parameter::Class::MULTI_OR_SINGLE|Parameter::Class::NON_PART_SENSITIVE, 119, "Delay Color", {0,127}, paramTo7bitSigned, textTo7bitSigned, true, false, false},
+ {Parameter::Page::A, Parameter::Class::SOUNDBANK_A|Parameter::Class::MULTI_OR_SINGLE|Parameter::Class::NON_PART_SENSITIVE, 122, "Keyb Local", {0,1}, {},{}, false, false, true},
+ {Parameter::Page::A, Parameter::Class::SOUNDBANK_A|Parameter::Class::MULTI_OR_SINGLE|Parameter::Class::NON_PART_SENSITIVE, 123, "All Notes Off", {0,127}, {},{}, false, false, false},
+
+ {Parameter::Page::B, Parameter::Class::SOUNDBANK_B, 1, "Arp Mode", {0,6}, numToArpMode, {}, true, true, false},
+ {Parameter::Page::B, Parameter::Class::SOUNDBANK_B, 2, "Arp Pattern Selct", {0,31}, {},{}, true, true, false},
+ {Parameter::Page::B, Parameter::Class::SOUNDBANK_B, 3, "Arp Octave Range", {0,3}, {},{}, true, true, false},
+ {Parameter::Page::B, Parameter::Class::SOUNDBANK_B, 4, "Arp Hold Enable", {0,1}, {},{}, true, false, true},
+ {Parameter::Page::B, Parameter::Class::SOUNDBANK_B, 5, "Arp Note Length", {0,127}, paramTo7bitSigned, textTo7bitSigned, true, false, false},
+ {Parameter::Page::B, Parameter::Class::SOUNDBANK_B, 6, "Arp Swing", {0,127}, numToArpSwing, {}, true, false, false},
+ {Parameter::Page::B, Parameter::Class::SOUNDBANK_B, 7, "Lfo3 Rate", {0,127}, {},{}, true, false, false},
+ {Parameter::Page::B, Parameter::Class::SOUNDBANK_B, 8, "Lfo3 Shape", {0,5}, numToLfoShape, {}, true, true, false},
+ {Parameter::Page::B, Parameter::Class::SOUNDBANK_B, 9, "Lfo3 Mode", {0,1}, numToLfoMode, {}, true, false, true},
+ {Parameter::Page::B, Parameter::Class::SOUNDBANK_B, 10, "Lfo3 Keyfollow", {0,127}, {},{}, true, false, false},
+ {Parameter::Page::B, Parameter::Class::SOUNDBANK_B, 11, "Lfo3 Destination", {0,5}, numToLfoDest, {}, true, true, false},
+ {Parameter::Page::B, Parameter::Class::SOUNDBANK_B, 12, "Osc Lfo3 Amount", {0,127}, {},{}, true, false, false},
+ {Parameter::Page::B, Parameter::Class::SOUNDBANK_B, 13, "Lfo3 Fade-In Time", {0,127}, {},{}, true, false, false},
+ {Parameter::Page::B, Parameter::Class::SOUNDBANK_B, 16, "Clock Tempo", {0,127}, numToClockTempo, {}, true, false, false},
+ {Parameter::Page::B, Parameter::Class::SOUNDBANK_B, 17, "Arp Clock", {0,17}, numToMusicDivision, {}, true, true, false},
+ {Parameter::Page::B, Parameter::Class::SOUNDBANK_B, 18, "Lfo1 Clock", {0,19}, numToMusicDivision, {}, true, true, false},
+ {Parameter::Page::B, Parameter::Class::SOUNDBANK_B, 19, "Lfo2 Clock", {0,19}, numToMusicDivision, {}, true, true, false},
+ {Parameter::Page::B, Parameter::Class::SOUNDBANK_B|Parameter::Class::MULTI_OR_SINGLE|Parameter::Class::NON_PART_SENSITIVE, 20, "Delay Clock", {0,16}, numToMusicDivision, {}, true, true, false},
+ {Parameter::Page::B, Parameter::Class::SOUNDBANK_B, 21, "Lfo3 Clock", {0,19}, numToMusicDivision, {}, true, true, false},
+ {Parameter::Page::B, Parameter::Class::SOUNDBANK_B, 25, "Control Smooth Mode", {0,3}, numToControlSmoothMode,{}, true, false, false},
+ {Parameter::Page::B, Parameter::Class::SOUNDBANK_B, 26, "Bender Range Up", {0,127}, paramTo7bitSigned, textTo7bitSigned, true, false, false},
+ {Parameter::Page::B, Parameter::Class::SOUNDBANK_B, 27, "Bender Range Down", {0,127}, paramTo7bitSigned, textTo7bitSigned, true, false, false},
+ {Parameter::Page::B, Parameter::Class::SOUNDBANK_B, 28, "Bender Scale", {0,1}, numToLinExp, {}, true, false, true},
+ {Parameter::Page::B, Parameter::Class::SOUNDBANK_B, 30, "Filter1 Env Polarity", {0,1}, numToNegPos, {}, true, false, true},
+ {Parameter::Page::B, Parameter::Class::SOUNDBANK_B, 31, "Filter1 Env Polarity", {0,1}, numToNegPos, {}, true, false, true},
+ {Parameter::Page::B, Parameter::Class::SOUNDBANK_B, 32, "Filter2 Cutoff Link", {0,1}, {},{}, true, false, true},
+ {Parameter::Page::B, Parameter::Class::SOUNDBANK_B, 33, "Filter Keytrack Base", {0,127}, {},{}, true, false, true},
+ {Parameter::Page::B, Parameter::Class::SOUNDBANK_B|Parameter::Class::VIRUS_C, 34, "Osc FM Mode", {0,12}, numToOscFmMode,{}, true, true, false},
+ {Parameter::Page::B, Parameter::Class::SOUNDBANK_B, 35, "Osc Init Phase", {0,127}, {},{}, true, false, false},
+ {Parameter::Page::B, Parameter::Class::SOUNDBANK_B, 36, "Punch Intensity", {0,127}, {},{}, true, false, false},
+ {Parameter::Page::B, Parameter::Class::SOUNDBANK_B|Parameter::Class::VIRUS_C, 38, "Input Follower Mode",
+ {0,9}, {},{}, true, true, false},
+ {Parameter::Page::B, Parameter::Class::SOUNDBANK_B, 39, "Vocoder Mode", {0,12}, {},{}, true, true, false},
+ {Parameter::Page::B, Parameter::Class::SOUNDBANK_B|Parameter::Class::VIRUS_C, 41, "Osc3 Mode", {0,67}, {},{}, true, false, false},
+ {Parameter::Page::B, Parameter::Class::SOUNDBANK_B|Parameter::Class::VIRUS_C, 42, "Osc3 Volume", {0,127}, {},{}, true, false, false},
+ {Parameter::Page::B, Parameter::Class::SOUNDBANK_B|Parameter::Class::VIRUS_C, 43, "Osc3 Semitone", {0,127}, paramTo7bitSigned, textTo7bitSigned, true, false, false},
+ {Parameter::Page::B, Parameter::Class::SOUNDBANK_B|Parameter::Class::VIRUS_C, 44, "Osc3 Detune", {0,127}, {},{}, true, false, false},
+ {Parameter::Page::B, Parameter::Class::SOUNDBANK_B|Parameter::Class::VIRUS_C, 45, "LowEQ Frequency", {0,127}, {},{}, true, false, false},
+ {Parameter::Page::B, Parameter::Class::SOUNDBANK_B|Parameter::Class::VIRUS_C, 46, "HighEQ Frequency", {0,127}, {},{}, true, false, false},
+ {Parameter::Page::B, Parameter::Class::SOUNDBANK_B, 47, "Osc1 Shape Velocity", {0,127}, paramTo7bitSigned, textTo7bitSigned, true, false, false},
+ {Parameter::Page::B, Parameter::Class::SOUNDBANK_B, 48, "Osc2 Shape Velocity", {0,127}, paramTo7bitSigned, textTo7bitSigned, true, false, false},
+ {Parameter::Page::B, Parameter::Class::SOUNDBANK_B, 49, "PulseWidth Velocity", {0,127}, paramTo7bitSigned, textTo7bitSigned, true, false, false},
+ {Parameter::Page::B, Parameter::Class::SOUNDBANK_B, 50, "Fm Amount Velocity", {0,127}, paramTo7bitSigned, textTo7bitSigned, true, false, false},
+ {Parameter::Page::B, Parameter::Class::SOUNDBANK_B|Parameter::Class::VIRUS_C, 51, "Soft Knob1 ShortName", {0,127}, {},{}, false, false, false},
+ {Parameter::Page::B, Parameter::Class::SOUNDBANK_B|Parameter::Class::VIRUS_C, 52, "Soft Knob2 ShortName", {0,127}, {},{}, false, false, false},
+ {Parameter::Page::B, Parameter::Class::SOUNDBANK_B, 54, "Filter1 EnvAmt Velocity", {0,127}, paramTo7bitSigned, textTo7bitSigned, true, false, false},
+ {Parameter::Page::B, Parameter::Class::SOUNDBANK_B, 55, "Filter2 EnvAmt Velocity", {0,127}, paramTo7bitSigned, textTo7bitSigned, true, false, false},
+ {Parameter::Page::B, Parameter::Class::SOUNDBANK_B, 56, "Resonance1 Velocity", {0,127}, paramTo7bitSigned, textTo7bitSigned, true, false, false},
+ {Parameter::Page::B, Parameter::Class::SOUNDBANK_B, 57, "Resonance2 Velocity", {0,127}, paramTo7bitSigned, textTo7bitSigned, true, false, false},
+ {Parameter::Page::B, Parameter::Class::SOUNDBANK_B, 58, "Second Output Balance", {0,127}, {},{}, true, false, false},
+ {Parameter::Page::B, Parameter::Class::SOUNDBANK_B, 60, "Amp Velocity", {0,127}, paramTo7bitSigned, textTo7bitSigned, true, false, false},
+ {Parameter::Page::B, Parameter::Class::SOUNDBANK_B, 61, "Panorama Velocity", {0,127}, paramTo7bitSigned, textTo7bitSigned, true, false, false},
+ {Parameter::Page::B, Parameter::Class::SOUNDBANK_B, 62, "Soft Knob-1 Single", {0,127}, numToSoftKnobDest, {}, true, true, false},
+ {Parameter::Page::B, Parameter::Class::SOUNDBANK_B, 63, "Soft Knob-2 Single", {0,127}, numToSoftKnobDest, {}, true, true, false},
+ {Parameter::Page::B, Parameter::Class::SOUNDBANK_B, 64, "Assign1 Source", {0,27}, numToModMatrixSource, {}, true, true, false},
+ {Parameter::Page::B, Parameter::Class::SOUNDBANK_B, 65, "Assign1 Destination", {0,122}, numToModMatrixDest, {}, true, true, false},
+ {Parameter::Page::B, Parameter::Class::SOUNDBANK_B, 66, "Assign1 Amount", {0,127}, paramTo7bitSigned, textTo7bitSigned, true, false, false},
+ {Parameter::Page::B, Parameter::Class::SOUNDBANK_B, 67, "Assign2 Source", {0,27}, numToModMatrixSource,{}, true, true, false},
+ {Parameter::Page::B, Parameter::Class::SOUNDBANK_B, 68, "Assign2 Destination1", {0,122}, numToModMatrixDest,{}, true, true, false},
+ {Parameter::Page::B, Parameter::Class::SOUNDBANK_B, 69, "Assign2 Amount1", {0,127}, paramTo7bitSigned, textTo7bitSigned, true, false, false},
+ {Parameter::Page::B, Parameter::Class::SOUNDBANK_B, 70, "Assign2 Destination2", {0,122}, numToModMatrixDest, {}, true, true, false},
+ {Parameter::Page::B, Parameter::Class::SOUNDBANK_B, 71, "Assign2 Amount2", {0,127}, paramTo7bitSigned, textTo7bitSigned, true, false, false},
+ {Parameter::Page::B, Parameter::Class::SOUNDBANK_B, 72, "Assign3 Source", {0,127}, numToModMatrixSource, {}, true, false, false},
+ {Parameter::Page::B, Parameter::Class::SOUNDBANK_B, 73, "Assign3 Destination1", {0,122}, numToModMatrixDest,{}, true, true, false},
+ {Parameter::Page::B, Parameter::Class::SOUNDBANK_B, 74, "Assign3 Amount1", {0,127}, paramTo7bitSigned, textTo7bitSigned, true, false, false},
+ {Parameter::Page::B, Parameter::Class::SOUNDBANK_B, 75, "Assign3 Destination2", {0,122}, numToModMatrixDest,{}, true, true, false},
+ {Parameter::Page::B, Parameter::Class::SOUNDBANK_B, 76, "Assign3 Amount2", {0,127}, paramTo7bitSigned, textTo7bitSigned, true, false, false},
+ {Parameter::Page::B, Parameter::Class::SOUNDBANK_B, 77, "Assign2 Destination3", {0,122}, numToModMatrixDest,{}, true, true, false},
+ {Parameter::Page::B, Parameter::Class::SOUNDBANK_B, 78, "Assign2 Amount3", {0,127}, paramTo7bitSigned, textTo7bitSigned, true, false, false},
+ {Parameter::Page::B, Parameter::Class::SOUNDBANK_B, 79, "LFO1 Assign Dest", {0,122}, numToModMatrixDest, {}, true, true, false},
+ {Parameter::Page::B, Parameter::Class::SOUNDBANK_B, 80, "LFO1 Assign Amount", {0,127}, paramTo7bitSigned, textTo7bitSigned, true, false, false},
+ {Parameter::Page::B, Parameter::Class::SOUNDBANK_B, 81, "LFO2 Assign Dest", {0,122}, numToModMatrixDest, {}, true, true, false},
+ {Parameter::Page::B, Parameter::Class::SOUNDBANK_B, 82, "LFO2 Assign Amount", {0,127}, paramTo7bitSigned, textTo7bitSigned, true, false, false},
+ {Parameter::Page::B, Parameter::Class::SOUNDBANK_B|Parameter::Class::VIRUS_C, 84, "Phaser Mode", {0,6}, numToPhaserMode, {}, true, true, false},
+ {Parameter::Page::B, Parameter::Class::SOUNDBANK_B|Parameter::Class::VIRUS_C, 85, "Phaser Mix", {0,127}, {},{}, true, false, false},
+ {Parameter::Page::B, Parameter::Class::SOUNDBANK_B|Parameter::Class::VIRUS_C, 86, "Phaser Rate", {0,127}, {},{}, true, false, false},
+ {Parameter::Page::B, Parameter::Class::SOUNDBANK_B|Parameter::Class::VIRUS_C, 87, "Phaser Depth", {0,127}, {},{}, true, false, false},
+ {Parameter::Page::B, Parameter::Class::SOUNDBANK_B|Parameter::Class::VIRUS_C, 88, "Phaser Frequency", {0,127}, {},{}, true, false, false},
+ {Parameter::Page::B, Parameter::Class::SOUNDBANK_B|Parameter::Class::VIRUS_C, 89, "Phaser Feedback", {0,127}, paramTo7bitSigned, textTo7bitSigned, true, false, false},
+ {Parameter::Page::B, Parameter::Class::SOUNDBANK_B|Parameter::Class::VIRUS_C, 90, "Phaser Spread", {0,127}, {},{}, true, false, false},
+ {Parameter::Page::B, Parameter::Class::SOUNDBANK_B|Parameter::Class::VIRUS_C, 92, "MidEQ Gain", {0,127}, {},{}, true, false, false},
+ {Parameter::Page::B, Parameter::Class::SOUNDBANK_B|Parameter::Class::VIRUS_C, 93, "MidEQ Frequency", {0,127}, {},{}, true, false, false},
+ {Parameter::Page::B, Parameter::Class::SOUNDBANK_B|Parameter::Class::VIRUS_C, 94, "MidEQ Q-Factor", {0,127}, {},{}, true, false, false},
+ {Parameter::Page::B, Parameter::Class::SOUNDBANK_B|Parameter::Class::VIRUS_C, 95, "LowEQ Gain", {0,127}, {},{}, true, false, false},
+ {Parameter::Page::B, Parameter::Class::SOUNDBANK_B|Parameter::Class::VIRUS_C, 96, "HighEQ Gain", {0,127}, {},{}, true, false, false},
+ {Parameter::Page::B, Parameter::Class::SOUNDBANK_B|Parameter::Class::VIRUS_C, 97, "Bass Intensity", {0,127}, {},{}, true, false, false},
+ {Parameter::Page::B, Parameter::Class::SOUNDBANK_B|Parameter::Class::VIRUS_C, 98, "Bass Tune", {0,127}, {},{}, true, false, false},
+ {Parameter::Page::B, Parameter::Class::SOUNDBANK_B|Parameter::Class::VIRUS_C, 99, "Input Ringmodulator", {0,127}, {},{}, true, false, false},
+ {Parameter::Page::B, Parameter::Class::SOUNDBANK_B|Parameter::Class::VIRUS_C, 100, "Distortion Curve", {0,6}, {},{}, true, true, false},
+ {Parameter::Page::B, Parameter::Class::SOUNDBANK_B|Parameter::Class::VIRUS_C, 101, "Distortion Intensity", {0,127}, {},{}, true, false, false},
+ {Parameter::Page::B, Parameter::Class::SOUNDBANK_B|Parameter::Class::VIRUS_C, 102, "Assign 4 Source", {0,27}, numToModMatrixSource,{}, true, true, false},
+ {Parameter::Page::B, Parameter::Class::SOUNDBANK_B|Parameter::Class::VIRUS_C, 103, "Assign 4 Destination", {0,122}, numToModMatrixSource, {}, true, true, false},
+ {Parameter::Page::B, Parameter::Class::SOUNDBANK_B|Parameter::Class::VIRUS_C,104, "Assign 4 Amount", {0,127}, {},{}, true, false, false},
+ {Parameter::Page::B, Parameter::Class::SOUNDBANK_B|Parameter::Class::VIRUS_C, 105, "Assign 5 Source", {0,27}, numToModMatrixSource, {}, true, true, false},
+ {Parameter::Page::B, Parameter::Class::SOUNDBANK_B|Parameter::Class::VIRUS_C, 106, "Assign 5 Destination", {0,122}, numToModMatrixDest, {}, true, true, false},
+ {Parameter::Page::B, Parameter::Class::SOUNDBANK_B|Parameter::Class::VIRUS_C, 107, "Assign 5 Amount", {0,127}, paramTo7bitSigned, textTo7bitSigned, true, false, false},
+ {Parameter::Page::B, Parameter::Class::SOUNDBANK_B|Parameter::Class::VIRUS_C, 108, "Assign 6 Source", {0,27}, numToModMatrixSource, {}, true, true, false},
+ {Parameter::Page::B, Parameter::Class::SOUNDBANK_B|Parameter::Class::VIRUS_C, 109, "Assign 6 Destination", {0,122}, numToModMatrixDest, {}, true, true, false},
+ {Parameter::Page::B, Parameter::Class::SOUNDBANK_B|Parameter::Class::VIRUS_C, 110, "Assign 6 Amount", {0,127}, paramTo7bitSigned, {}, true, false, false},
+ {Parameter::Page::B, Parameter::Class::SOUNDBANK_B|Parameter::Class::VIRUS_C, 122, "Filter Select", {0,2}, {},{}, true, true, false},
+
+ {Parameter::Page::C, Parameter::Class::MULTI_PARAM|Parameter::Class::NON_PART_SENSITIVE, 22, "Delay Output Select", {0,14}, {},{}, true, true, false},
+ {Parameter::Page::C, Parameter::Class::MULTI_PARAM|Parameter::Class::BANK_PROGRAM_CHANGE_PARAM_BANK_SELECT, 31, "Part Bank Select", {0, 3 + 26}, {},{}, false, true, false},
+ {Parameter::Page::C, Parameter::Class::MULTI_PARAM|Parameter::Class::BANK_PROGRAM_CHANGE_PARAM_BANK_SELECT, 32, "Part Bank Change", {0, 3 + 26}, {},{}, false, true, false},
+ {Parameter::Page::C, Parameter::Class::MULTI_PARAM|Parameter::Class::BANK_PROGRAM_CHANGE_PARAM_BANK_SELECT, 33, "Part Program Change", {0,127}, {},{}, false, false, false},
+ {Parameter::Page::C, Parameter::Class::MULTI_PARAM, 34, "Part Midi Channel", {0,15}, {},{}, false, true, false},
+ {Parameter::Page::C, Parameter::Class::MULTI_PARAM, 35, "Part Low Key", {0,127}, {},{}, false, true, false},
+ {Parameter::Page::C, Parameter::Class::MULTI_PARAM, 36, "Part High Key", {0,127}, {},{}, false, true, false},
+ {Parameter::Page::C, Parameter::Class::MULTI_PARAM, 37, "Part Transpose", {0,127}, paramTo7bitSigned, textTo7bitSigned, false, false, false},
+ {Parameter::Page::C, Parameter::Class::MULTI_PARAM, 38, "Part Detune", {0,127}, paramTo7bitSigned, textTo7bitSigned, false, false, false},
+ {Parameter::Page::C, Parameter::Class::MULTI_PARAM, 39, "Part Volume", {0,127}, {},{}, true, false, false},
+ {Parameter::Page::C, Parameter::Class::MULTI_PARAM, 40, "Part Midi Volume Init", {0,127}, {},{}, false, true, false},
+ {Parameter::Page::C, Parameter::Class::MULTI_PARAM, 41, "Part Output Select", {0,14}, {},{}, false, true, false},
+ {Parameter::Page::C, Parameter::Class::GLOBAL, 45, "Second Output Select", {0,15}, {},{}, false, true, false},
+ {Parameter::Page::C, Parameter::Class::GLOBAL, 63, "Keyb Transpose Buttons", {0,1}, {},{}, false, false, true},
+ {Parameter::Page::C, Parameter::Class::GLOBAL, 64, "Keyb Local", {0,1}, {},{}, false, false, true},
+ {Parameter::Page::C, Parameter::Class::GLOBAL, 65, "Keyb Mode", {0,1}, {},{}, false, false, true},
+ {Parameter::Page::C, Parameter::Class::GLOBAL, 66, "Keyb Transpose", {0,127}, paramTo7bitSigned, textTo7bitSigned, false, false, false},
+ {Parameter::Page::C, Parameter::Class::GLOBAL, 67, "Keyb ModWheel Contr", {0,127}, {},{}, false, false, false},
+ {Parameter::Page::C, Parameter::Class::GLOBAL, 68, "Keyb Pedal 1 Contr", {0,127}, {},{}, false, false, false},
+ {Parameter::Page::C, Parameter::Class::GLOBAL, 69, "Keyb Pedal 2 Contr", {0,127}, {},{}, false, false, false},
+ {Parameter::Page::C, Parameter::Class::GLOBAL, 70, "Keyb Pressure Sens", {0,127}, {},{}, false, false, false},
+ {Parameter::Page::C, Parameter::Class::MULTI_PARAM, 72, "Part Enable", {0,1}, {},{}, false, false, true},
+ {Parameter::Page::C, Parameter::Class::MULTI_PARAM, 73, "Part Midi Volume Enable", {0,1}, {},{}, false, false, true},
+ {Parameter::Page::C, Parameter::Class::MULTI_PARAM, 74, "Part Hold Pedal Enable", {0,1}, {},{}, false, false, true},
+ {Parameter::Page::C, Parameter::Class::MULTI_PARAM, 75, "Keyb To Midi", {0,1}, {},{}, false, false, true},
+ {Parameter::Page::C, Parameter::Class::MULTI_PARAM, 77, "Note Steal Priority", {0,1}, {},{}, false, false, true},
+ {Parameter::Page::C, Parameter::Class::MULTI_PARAM, 78, "Part Prog Change Enable", {0,1}, {},{}, false, false, true},
+ {Parameter::Page::C, Parameter::Class::GLOBAL, 85, "Glob Prog Change Enable", {0,1}, {},{}, false, false, true},
+ {Parameter::Page::C, Parameter::Class::GLOBAL, 86, "MultiProg Change Enable", {0,1}, {},{}, false, false, true},
+ {Parameter::Page::C, Parameter::Class::GLOBAL, 87, "Glob Midi Volume Enable", {0,1}, {},{}, false, false, true},
+ {Parameter::Page::C, Parameter::Class::GLOBAL, 90, "Input Thru Level", {0,127}, {},{}, false, false, false},
+ {Parameter::Page::C, Parameter::Class::GLOBAL, 91, "Input Boost", {0,127}, {},{}, false, false, false},
+ {Parameter::Page::C, Parameter::Class::GLOBAL, 92, "Master Tune", {0,127}, paramTo7bitSigned, textTo7bitSigned, false, false, false},
+ {Parameter::Page::C, Parameter::Class::GLOBAL, 93, "Device ID", {0,16}, {},{}, false, true, false},
+ {Parameter::Page::C, Parameter::Class::GLOBAL, 94, "Midi Control Low Page", {0,1}, {},{}, false, false, true},
+ {Parameter::Page::C, Parameter::Class::GLOBAL, 95, "Midi Control High Page", {0,1}, {},{}, false, false, true},
+ {Parameter::Page::C, Parameter::Class::GLOBAL, 96, "Midi Arpeggiator Send", {0,1}, {},{}, false, false, true},
+ {Parameter::Page::C, Parameter::Class::GLOBAL, 97, "Knob Display", {0,3}, {},{}, false, true, false},
+ {Parameter::Page::C, Parameter::Class::GLOBAL, 98, "Midi Dump Tx", {0,4}, {},{}, false, true, false},
+ {Parameter::Page::C, Parameter::Class::GLOBAL, 99, "Midi Dump Rx", {0,4}, {},{}, false, true, false},
+ {Parameter::Page::C, Parameter::Class::GLOBAL, 105, "Multi Program Change", {0,127}, {},{}, false, false, false},
+ {Parameter::Page::C, Parameter::Class::GLOBAL, 106, "Midi Clock Rx", {0,3}, {},{}, false, true, false},
+ {Parameter::Page::C, Parameter::Class::GLOBAL, 110, "Soft Knob-1 Mode", {0,2}, {},{}, false, true, false},
+ {Parameter::Page::C, Parameter::Class::GLOBAL, 111, "Soft Knob-2 Mode", {0,2}, {},{}, false, true, false},
+ {Parameter::Page::C, Parameter::Class::GLOBAL, 112, "Soft Knob-1 Global", {0,2}, {},{}, false, true, false},
+ {Parameter::Page::C, Parameter::Class::GLOBAL, 113, "Soft Knob-2 Global", {0,2}, {},{}, false, true, false},
+ {Parameter::Page::C, Parameter::Class::GLOBAL, 114, "Soft Knob-1 Midi", {0,127}, {},{}, false, false, false},
+ {Parameter::Page::C, Parameter::Class::GLOBAL, 115, "Soft Knob-2 Midi", {0,127}, {},{}, false, false, false},
+ {Parameter::Page::C, Parameter::Class::GLOBAL, 116, "Expert Mode", {0,2}, {},{}, false, true, false},
+ {Parameter::Page::C, Parameter::Class::GLOBAL, 117, "Knob Mode", {0,2}, {},{}, false, true, false},
+ {Parameter::Page::C, Parameter::Class::GLOBAL, 118, "Memory Protect", {0,1}, {},{}, false, false, true},
+ {Parameter::Page::C, Parameter::Class::GLOBAL, 120, "Soft Thru", {0,1}, {},{}, false, true, false},
+ {Parameter::Page::C, Parameter::Class::GLOBAL, 121, "Panel Destination", {0,2}, {},{}, false, true, false},
+ {Parameter::Page::C, Parameter::Class::GLOBAL, 122, "Play Mode", {0,2}, {},{}, false, true, false},
+ {Parameter::Page::C, Parameter::Class::GLOBAL, 123, "Part Number", {0,127}, {},{}, false, true, false}, // 0..15;40
+ {Parameter::Page::C, Parameter::Class::GLOBAL, 124, "Global Channel", {0,15}, {},{}, false, true, false},
+ {Parameter::Page::C, Parameter::Class::GLOBAL, 125, "Led Mode", {0,2}, {},{}, false, true, false},
+ {Parameter::Page::C, Parameter::Class::GLOBAL, 126, "LCD Contrast", {0,127}, {},{}, false, false, false},
+ {Parameter::Page::C, Parameter::Class::GLOBAL, 127, "Master Volume", {0,127}, {},{}, false, false, false},
+
+ // UNDEFINED / UNUSED / STUBS
+ {Parameter::Page::A, Parameter::Class::UNDEFINED, 92, {}, {0,127}, {},{}, false, false, false},
+ {Parameter::Page::A, Parameter::Class::UNDEFINED, 95, {}, {0,127}, {},{}, false, false, false},
+ {Parameter::Page::A, Parameter::Class::UNDEFINED, 96, {}, {0,127}, {},{}, false, false, false},
+ {Parameter::Page::A, Parameter::Class::UNDEFINED, 111, {}, {0,127}, {},{}, false, false, false},
+ {Parameter::Page::A, Parameter::Class::UNDEFINED, 120, {}, {0,127}, {},{}, false, false, false},
+ {Parameter::Page::A, Parameter::Class::UNDEFINED, 121, {}, {0,127}, {},{}, false, false, false},
+ {Parameter::Page::A, Parameter::Class::UNDEFINED, 124, {}, {0,127}, {},{}, false, false, false},
+ {Parameter::Page::A, Parameter::Class::UNDEFINED, 125, {}, {0,127}, {},{}, false, false, false},
+ {Parameter::Page::A, Parameter::Class::UNDEFINED, 126, {}, {0,127}, {},{}, false, false, false},
+ {Parameter::Page::A, Parameter::Class::UNDEFINED, 127, {}, {0,127}, {},{}, false, false, false},
+ {Parameter::Page::B, Parameter::Class::UNDEFINED, 0, {}, {0,127}, {},{}, false, false, false},
+ {Parameter::Page::B, Parameter::Class::UNDEFINED, 14, {}, {0,127}, {},{}, false, false, false},
+ {Parameter::Page::B, Parameter::Class::UNDEFINED, 15, {}, {0,127}, {},{}, false, false, false},
+ {Parameter::Page::B, Parameter::Class::UNDEFINED, 22, {}, {0,127}, {},{}, false, false, false},
+ {Parameter::Page::B, Parameter::Class::UNDEFINED, 23, {}, {0,127}, {},{}, false, false, false},
+ {Parameter::Page::B, Parameter::Class::UNDEFINED, 24, {}, {0,127}, {},{}, false, false, false},
+ {Parameter::Page::B, Parameter::Class::UNDEFINED, 29, {}, {0,127}, {},{}, false, false, false},
+ {Parameter::Page::B, Parameter::Class::UNDEFINED, 37, {}, {0,127}, {},{}, false, false, false},
+ {Parameter::Page::B, Parameter::Class::UNDEFINED, 40, {}, {0,127}, {},{}, false, false, false},
+ {Parameter::Page::B, Parameter::Class::UNDEFINED, 53, {}, {0,127}, {},{}, false, false, false},
+ {Parameter::Page::B, Parameter::Class::UNDEFINED, 69, {}, {0,127}, {},{}, false, false, false},
+ {Parameter::Page::B, Parameter::Class::UNDEFINED, 83, {}, {0,127}, {},{}, false, false, false},
+ {Parameter::Page::B, Parameter::Class::UNDEFINED, 91, {}, {0,127}, {},{}, false, false, false},
+ {Parameter::Page::B, Parameter::Class::UNDEFINED, 111, {}, {0,127}, {},{}, false, false, false},
+ {Parameter::Page::B, Parameter::Class::UNDEFINED, 123, {}, {0,127}, {},{}, false, false, false},
+ {Parameter::Page::B, Parameter::Class::UNDEFINED, 124, {}, {0,127}, {},{}, false, false, false},
+ {Parameter::Page::C, Parameter::Class::UNDEFINED, 0, {}, {0,127}, {},{}, false, false, false},
+ {Parameter::Page::C, Parameter::Class::UNDEFINED, 1, {}, {0,127}, {},{}, false, false, false},
+ {Parameter::Page::C, Parameter::Class::UNDEFINED, 2, {}, {0,127}, {},{}, false, false, false},
+ {Parameter::Page::C, Parameter::Class::UNDEFINED, 3, {}, {0,127}, {},{}, false, false, false},
+ {Parameter::Page::C, Parameter::Class::UNDEFINED, 4, {}, {0,127}, {},{}, false, false, false},
+ {Parameter::Page::C, Parameter::Class::UNDEFINED, 15, {}, {0,127}, {},{}, false, false, false},
+ {Parameter::Page::C, Parameter::Class::UNDEFINED, 16, {}, {0,127}, {},{}, false, false, false},
+ {Parameter::Page::C, Parameter::Class::UNDEFINED, 17, {}, {0,127}, {},{}, false, false, false},
+ {Parameter::Page::C, Parameter::Class::UNDEFINED, 18, {}, {0,127}, {},{}, false, false, false},
+ {Parameter::Page::C, Parameter::Class::UNDEFINED, 19, {}, {0,127}, {},{}, false, false, false},
+ {Parameter::Page::C, Parameter::Class::UNDEFINED, 20, {}, {0,127}, {},{}, false, false, false},
+ {Parameter::Page::C, Parameter::Class::UNDEFINED, 21, {}, {0,127}, {},{}, false, false, false},
+ {Parameter::Page::C, Parameter::Class::UNDEFINED, 23, {}, {0,127}, {},{}, false, false, false},
+ {Parameter::Page::C, Parameter::Class::UNDEFINED, 24, {}, {0,127}, {},{}, false, false, false},
+ {Parameter::Page::C, Parameter::Class::UNDEFINED, 25, {}, {0,127}, {},{}, false, false, false},
+ {Parameter::Page::C, Parameter::Class::UNDEFINED, 26, {}, {0,127}, {},{}, false, false, false},
+ {Parameter::Page::C, Parameter::Class::UNDEFINED, 27, {}, {0,127}, {},{}, false, false, false},
+ {Parameter::Page::C, Parameter::Class::UNDEFINED, 28, {}, {0,127}, {},{}, false, false, false},
+ {Parameter::Page::C, Parameter::Class::UNDEFINED, 29, {}, {0,127}, {},{}, false, false, false},
+ {Parameter::Page::C, Parameter::Class::UNDEFINED, 30, {}, {0,127}, {},{}, false, false, false},
+ {Parameter::Page::C, Parameter::Class::UNDEFINED, 76, {}, {0,127}, {},{}, false, false, false},
+ {Parameter::Page::C, Parameter::Class::UNDEFINED, 79, {}, {0,127}, {},{}, false, false, false},
+ {Parameter::Page::C, Parameter::Class::UNDEFINED, 80, {}, {0,127}, {},{}, false, false, false},
+ {Parameter::Page::C, Parameter::Class::UNDEFINED, 81, {}, {0,127}, {},{}, false, false, false},
+ {Parameter::Page::C, Parameter::Class::UNDEFINED, 82, {}, {0,127}, {},{}, false, false, false},
+ {Parameter::Page::C, Parameter::Class::UNDEFINED, 83, {}, {0,127}, {},{}, false, false, false},
+ {Parameter::Page::C, Parameter::Class::UNDEFINED, 84, {}, {0,127}, {},{}, false, false, false},
+ {Parameter::Page::C, Parameter::Class::UNDEFINED, 88, {}, {0,127}, {},{}, false, false, false},
+ {Parameter::Page::C, Parameter::Class::UNDEFINED, 89, {}, {0,127}, {},{}, false, false, false},
+ {Parameter::Page::C, Parameter::Class::UNDEFINED, 100, {}, {0,127}, {},{}, false, false, false},
+ {Parameter::Page::C, Parameter::Class::UNDEFINED, 101, {}, {0,127}, {},{}, false, false, false},
+ {Parameter::Page::C, Parameter::Class::UNDEFINED, 102, {}, {0,127}, {},{}, false, false, false},
+ {Parameter::Page::C, Parameter::Class::UNDEFINED, 103, {}, {0,127}, {},{}, false, false, false},
+ {Parameter::Page::C, Parameter::Class::UNDEFINED, 104, {}, {0,127}, {},{}, false, false, false},
+ {Parameter::Page::C, Parameter::Class::UNDEFINED, 119, {}, {0,127}, {},{}, false, false, false},
+ // Text Chars / Unused
+ {Parameter::Page::B, Parameter::Class::UNDEFINED, 110, {}, {0,127}, {},{}, false, false, false},
+ {Parameter::Page::B, Parameter::Class::UNDEFINED, 111, {}, {0,127}, {},{}, false, false, false},
+ {Parameter::Page::B, Parameter::Class::UNDEFINED, 112, {}, {0,127}, {},{}, false, false, false},
+ {Parameter::Page::B, Parameter::Class::UNDEFINED, 113, {}, {0,127}, {},{}, false, false, false},
+ {Parameter::Page::B, Parameter::Class::UNDEFINED, 114, {}, {0,127}, {},{}, false, false, false},
+ {Parameter::Page::B, Parameter::Class::UNDEFINED, 115, {}, {0,127}, {},{}, false, false, false},
+ {Parameter::Page::B, Parameter::Class::UNDEFINED, 116, {}, {0,127}, {},{}, false, false, false},
+ {Parameter::Page::B, Parameter::Class::UNDEFINED, 117, {}, {0,127}, {},{}, false, false, false},
+ {Parameter::Page::B, Parameter::Class::UNDEFINED, 118, {}, {0,127}, {},{}, false, false, false},
+ {Parameter::Page::B, Parameter::Class::UNDEFINED, 119, {}, {0,127}, {},{}, false, false, false},
+ {Parameter::Page::B, Parameter::Class::UNDEFINED, 120, {}, {0,127}, {},{}, false, false, false},
+ {Parameter::Page::B, Parameter::Class::UNDEFINED, 121, {}, {0,127}, {},{}, false, false, false},
+ {Parameter::Page::C, Parameter::Class::UNDEFINED, 5, {}, {0,127}, {},{}, false, false, false},
+ {Parameter::Page::C, Parameter::Class::UNDEFINED, 6, {}, {0,127}, {},{}, false, false, false},
+ {Parameter::Page::C, Parameter::Class::UNDEFINED, 7, {}, {0,127}, {},{}, false, false, false},
+ {Parameter::Page::C, Parameter::Class::UNDEFINED, 8, {}, {0,127}, {},{}, false, false, false},
+ {Parameter::Page::C, Parameter::Class::UNDEFINED, 9, {}, {0,127}, {},{}, false, false, false},
+ {Parameter::Page::C, Parameter::Class::UNDEFINED, 10, {}, {0,127}, {},{}, false, false, false},
+ {Parameter::Page::C, Parameter::Class::UNDEFINED, 11, {}, {0,127}, {},{}, false, false, false},
+ {Parameter::Page::C, Parameter::Class::UNDEFINED, 12, {}, {0,127}, {},{}, false, false, false},
+ {Parameter::Page::C, Parameter::Class::UNDEFINED, 13, {}, {0,127}, {},{}, false, false, false},
+ {Parameter::Page::C, Parameter::Class::UNDEFINED, 14, {}, {0,127}, {},{}, false, false, false},
+ // TODO: B51-52 shortname?!? B54-B55 (same name?!)
+};
+
+ uint8_t Controller::copyData(const SysEx &src, int startPos, uint8_t *dst)
+ {
+ uint8_t sum = 0;
+ for (auto i = 0; i < kDataSizeInBytes; i++)
+ {
+ dst[i] = src[startPos + i];
+ sum += dst[i];
+ }
+ return sum;
+ }
+
+ template <typename T> juce::String Controller::parseAsciiText(const T &msg, const int start) const
+ {
+ char text[kNameLength + 1];
+ text[kNameLength] = 0; // termination
+ for (auto pos = 0; pos < kNameLength; ++pos)
+ text[pos] = msg[start + pos];
+ return juce::String(text);
+ }
+
+ void Controller::printMessage(const SysEx &msg) const
+ {
+ for (auto &m : msg)
+ {
+ std::cout << std::hex << (int)m << ",";
+ }
+ std::cout << std::endl;
+ }
+
+ void Controller::sendSysEx(const SysEx &msg)
+ {
+ synthLib::SMidiEvent ev;
+ ev.sysex = msg;
+ m_processor.addMidiEvent(ev);
+ }
+
+ std::vector<uint8_t> Controller::constructMessage(SysEx msg)
+ {
+ const uint8_t start[] = {0xf0, 0x00, 0x20, 0x33, 0x01, static_cast<uint8_t>(m_deviceId)};
+ msg.insert(msg.begin(), std::begin(start), std::end(start));
+ msg.push_back(0xf7);
+ return msg;
+ }
+
+ void Controller::timerCallback()
+ {
+ const juce::ScopedLock sl(m_eventQueueLock);
+ for (auto msg : m_virusOut)
+ {
+ if (msg.sysex.size() == 0)
+ {
+ // no sysex
+ parseControllerDump(msg);
+ }
+ else
+ parseMessage(msg.sysex);
+ }
+ m_virusOut.clear();
+ }
+
+ void Controller::dispatchVirusOut(const std::vector<synthLib::SMidiEvent> &newData)
+ {
+ const juce::ScopedTryLock sl(m_eventQueueLock);
+ if (!sl.isLocked())
+ return;
+
+ m_virusOut.insert(m_virusOut.end(), newData.begin(), newData.end());
+ }
+}; // namespace Virus
diff --git a/source/jucePlugin/VirusController.h b/source/jucePlugin/VirusController.h
@@ -0,0 +1,105 @@
+#pragma once
+
+#include <juce_audio_processors/juce_audio_processors.h>
+#include "../synthLib/plugin.h"
+#include "VirusParameter.h"
+
+class AudioPluginAudioProcessor;
+
+namespace Virus
+{
+ using SysEx = std::vector<uint8_t>;
+ class Controller : private juce::Timer
+ {
+ public:
+ friend Parameter;
+ static constexpr auto kNameLength = 10;
+
+ Controller(AudioPluginAudioProcessor &, unsigned char deviceId = 0x00);
+
+ // this is called by the plug-in on audio thread!
+ void dispatchVirusOut(const std::vector<synthLib::SMidiEvent> &);
+
+ void printMessage(const SysEx &) const;
+
+ // currently Value as I figure out what's the best approach
+ // ch - [0-15]
+ // bank - [0-2] (ABC)
+ // paramIndex - [0-127]
+ juce::Value *getParam(uint8_t ch, uint8_t bank, uint8_t paramIndex);
+
+ // bank - 0-1 (AB)
+ juce::StringArray getSinglePresetNames(int bank) const;
+ juce::StringArray getMultiPresetsName() const;
+ bool isMultiMode() { return getParam(0, 2, 0x7a)->getValue(); }
+ // part 0 - 15 (ignored when single! 0x40...)
+ void setCurrentPartPreset(uint8_t part, uint8_t bank, uint8_t prg);
+ juce::String getCurrentPartPresetName(uint8_t part);
+
+ private:
+ void timerCallback() override;
+
+ static constexpr size_t kDataSizeInBytes = 256; // same for multi and single
+
+ struct MultiPatch
+ {
+ uint8_t bankNumber;
+ uint8_t progNumber;
+ uint8_t data[kDataSizeInBytes];
+ };
+
+ struct SinglePatch
+ {
+ uint8_t bankNumber;
+ uint8_t progNumber;
+ uint8_t data[kDataSizeInBytes];
+ };
+
+ MultiPatch m_multis[128]; // RAM has 128 Multi 'snapshots'
+ SinglePatch m_singles[2][128];
+
+ static const std::initializer_list<Parameter::Description> m_paramsDescription;
+
+ struct ParamIndex
+ {
+ uint8_t page;
+ uint8_t partNum;
+ uint8_t paramNum;
+ bool operator<(ParamIndex const &rhs) const
+ {
+ if (page < rhs.page) return false;
+ if (page > rhs.page) return true;
+ if (partNum < rhs.partNum) return false;
+ if (partNum > rhs.partNum) return true;
+ if (paramNum < rhs.paramNum) return false;
+ if (paramNum > rhs.paramNum) return true;
+ return false;
+ }
+ };
+
+ std::map<ParamIndex, std::unique_ptr<Parameter>> m_synthInternalParams;
+ std::map<ParamIndex, Parameter *> m_synthParams; // exposed and managed by audio processor
+
+ void registerParams();
+ // tries to find synth param in both internal and host.
+ // @return found parameter or nullptr if none found.
+ Parameter *findSynthParam(uint8_t ch, uint8_t bank, uint8_t paramIndex);
+
+ // unchecked copy for patch data bytes
+ static inline uint8_t copyData(const SysEx &src, int startPos, uint8_t *dst);
+
+ template <typename T> juce::String parseAsciiText(const T &, int startPos) const;
+ void parseMessage(const SysEx &);
+ void parseSingle(const SysEx &);
+ void parseMulti(const SysEx &);
+ void parseParamChange(const SysEx &);
+ void parseControllerDump(synthLib::SMidiEvent &);
+ void sendSysEx(const SysEx &);
+ std::vector<uint8_t> constructMessage(SysEx msg);
+
+ AudioPluginAudioProcessor &m_processor;
+ juce::CriticalSection m_eventQueueLock;
+ std::vector<synthLib::SMidiEvent> m_virusOut;
+ unsigned char m_deviceId;
+ };
+}; // namespace Virus
diff --git a/source/jucePlugin/VirusParameter.cpp b/source/jucePlugin/VirusParameter.cpp
@@ -0,0 +1,46 @@
+#include "VirusParameter.h"
+
+#include "VirusController.h"
+
+namespace Virus
+{
+ Parameter::Parameter(Controller &ctrl, const Description desc, const uint8_t partNum) :
+ m_ctrl(ctrl), m_desc(desc),
+ m_partNum(partNum), juce::RangedAudioParameter(genId(desc, partNum),
+ "Ch " + juce::String(partNum + 1) + " " + desc.name)
+ {
+ m_range.start = m_desc.range.getStart();
+ m_range.end = m_desc.range.getEnd();
+ m_range.interval = m_desc.isDiscrete || m_desc.isBool ? 1 : 0;
+ m_value.addListener(this);
+ }
+
+ void Parameter::valueChanged(juce::Value &)
+ {
+ const uint8_t value = static_cast<int>(m_value.getValue());
+ jassert (m_range.getRange().contains(value) || m_range.end == value);
+ if (value != m_lastValue)
+ {
+ m_ctrl.sendSysEx(
+ m_ctrl.constructMessage({static_cast<uint8_t>(m_desc.page), m_partNum, m_desc.index, value}));
+ m_lastValue = value;
+ }
+ if (onValueChanged)
+ onValueChanged();
+ }
+
+ void Parameter::setValueFromSynth(int newValue, const bool notifyHost)
+ {
+ m_lastValue = newValue;
+ if (notifyHost)
+ setValueNotifyingHost(convertTo0to1(newValue));
+ else
+ m_value.setValue(newValue);
+ }
+
+ juce::String Parameter::genId(const Description &d, const int part)
+ {
+ return juce::String::formatted("%d_%d_%d", (int)d.page, part, d.index);
+ }
+
+} // namespace Virus
diff --git a/source/jucePlugin/VirusParameter.h b/source/jucePlugin/VirusParameter.h
@@ -0,0 +1,95 @@
+#pragma once
+
+#include <juce_audio_processors/juce_audio_processors.h>
+
+namespace Virus
+{
+
+ class Controller;
+
+ class Parameter : private juce::Value::Listener, public juce::RangedAudioParameter
+ {
+ public:
+ enum Class
+ {
+ UNDEFINED = 0x0,
+ GLOBAL = 0x1,
+ PERFORMANCE_CONTROLLER = 0x2,
+ SOUNDBANK_A = 0x4,
+ SOUNDBANK_B = 0x8,
+ MULTI_OR_SINGLE = 0x10,
+ MULTI_PARAM = 0x20,
+ NON_PART_SENSITIVE = 0x40,
+ BANK_PROGRAM_CHANGE_PARAM_BANK_SELECT = 0x80,
+ VIRUS_C = 0x100,
+ };
+
+ enum Page
+ {
+ A = 0x70,
+ B = 0x71,
+ C = 0x72
+ };
+
+ struct Description
+ {
+ Page page;
+ int classFlags;
+ uint8_t index;
+ juce::String name;
+ juce::Range<int> range;
+ std::function<juce::String(float, Description ctx)> valueToTextFunction;
+ std::function<float(const juce::String &, Description ctx)> textToValueFunction;
+ bool isPublic;
+ bool isDiscrete;
+ bool isBool;
+ };
+
+ Parameter(Controller &, const Description desc, uint8_t partNum = 0x40);
+
+ juce::Value &getValueObject() { return m_value; };
+ const juce::Value &getValueObject() const { return m_value; };
+
+ const Description getDescription() const { return m_desc; };
+
+ const juce::NormalisableRange<float> &getNormalisableRange() const override { return m_range; }
+
+ float getValue() const override { return convertTo0to1(m_value.getValue()); }
+ void setValue(float newValue) override { return m_value.setValue(convertFrom0to1(newValue)); };
+ void setValueFromSynth(int newValue, bool notifyHost = true);
+ float getDefaultValue() const override { return 0; /* maybe return from ROM state? */ }
+ bool isDiscrete() const override { return m_desc.isDiscrete; }
+ bool isBoolean() const override { return m_desc.isBool; }
+
+ float getValueForText(const juce::String &text) const override
+ {
+ if (m_desc.textToValueFunction)
+ return convertTo0to1(m_desc.textToValueFunction(text, m_desc));
+ else
+ return convertTo0to1(text.getFloatValue());
+ }
+
+ juce::String getText(float normalisedValue, int /*maximumStringLength*/) const override
+ {
+ if (m_desc.valueToTextFunction)
+ return m_desc.valueToTextFunction(convertFrom0to1(normalisedValue), m_desc);
+ else
+ return juce::String(juce::roundToInt(convertFrom0to1(normalisedValue)));
+ }
+
+ // allow 'injecting' additional code for specific parameter.
+ // eg. multi/single value change requires triggering more logic.
+ std::function<void()> onValueChanged = {};
+
+ private:
+ juce::String genId(const Description &d, int part);
+ void valueChanged(juce::Value &) override;
+
+ Controller &m_ctrl;
+ const Description m_desc;
+ juce::NormalisableRange<float> m_range;
+ uint8_t m_paramNum, m_partNum;
+ int m_lastValue{-1};
+ juce::Value m_value;
+ };
+} // namespace Virus