commit aecee505d8fb9a374d108b46289fc772acfab8af
parent b8ca98fab871ff1caa91c11a3a0b7a438bab892c
Author: dsp56300 <dsp56300@users.noreply.github.com>
Date: Sun, 15 May 2022 02:24:51 +0200
begin to split virus controller & parameter into generic and Virus specific parts
Diffstat:
10 files changed, 470 insertions(+), 400 deletions(-)
diff --git a/source/jucePlugin/VirusController.cpp b/source/jucePlugin/VirusController.cpp
@@ -12,9 +12,12 @@ 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), m_descriptions(BinaryData::parameterDescriptions_C_json)
+ Controller::Controller(AudioPluginAudioProcessor &p, unsigned char deviceId) : pluginLib::Controller(BinaryData::parameterDescriptions_C_json), m_processor(p), m_deviceId(deviceId)
{
- assert(m_descriptions.getDescriptions().size() == Param_Count && "size of enum must match size of parameter descriptions");
+// assert(m_descriptions.getDescriptions().size() == Param_Count && "size of enum must match size of parameter descriptions");
+
+ registerParams(p);
+
juce::PropertiesFile::Options opts;
opts.applicationName = "DSP56300 Emulator";
opts.filenameSuffix = ".settings";
@@ -22,7 +25,6 @@ namespace Virus
opts.osxLibrarySubFolder = "Application Support/DSP56300 Emulator";
m_config = new juce::PropertiesFile(opts);
- registerParams();
// add lambda to enforce updating patches when virus switch from/to multi/single.
const auto& params = findSynthParam(0, 0x72, 0x7a);
for (const auto& parameter : params)
@@ -52,103 +54,6 @@ namespace Virus
delete m_config;
}
- void Controller::registerParams()
- {
- // 16 parts * 3 pages * 128 params
- // TODO: not register internal/unused params?
- auto globalParams = std::make_unique<juce::AudioProcessorParameterGroup>("global", "Global", "|");
-
- std::map<ParamIndex, int> knownParameterIndices;
-
- for (uint8_t part = 0; part < 16; part++)
- {
- m_paramsByParamType[part].reserve(m_descriptions.getDescriptions().size());
-
- const auto partNumber = juce::String(part + 1);
- auto group =
- std::make_unique<juce::AudioProcessorParameterGroup>("ch" + partNumber, "Ch " + partNumber, "|");
-
- uint32_t parameterDescIndex = 0;
- for (const auto& desc : m_descriptions.getDescriptions())
- {
- ++parameterDescIndex;
-
- const ParamIndex idx = {static_cast<uint8_t>(desc.page), part, desc.index};
-
- int uid = 0;
-
- auto itKnownParamIdx = knownParameterIndices.find(idx);
-
- if(itKnownParamIdx == knownParameterIndices.end())
- knownParameterIndices.insert(std::make_pair(idx, 0));
- else
- uid = ++itKnownParamIdx->second;
-
- auto p = std::make_unique<Parameter>(*this, desc, part, uid);
-
- if(uid > 0)
- {
- const auto& existingParams = findSynthParam(idx);
-
- for (auto& existingParam : existingParams)
- existingParam->addLinkedParameter(p.get());
- }
-
- m_paramsByParamType[part].push_back(p.get());
-
- const bool isNonPartExclusive = (desc.classFlags & (int)pluginLib::ParameterClass::Global) || (desc.classFlags & (int)pluginLib::ParameterClass::NonPartSensitive);
- if (isNonPartExclusive)
- {
- if (part != 0)
- continue; // only register on first part!
- }
- if (p->getDescription().isPublic)
- {
- // lifecycle managed by Juce
-
- auto itExisting = m_synthParams.find(idx);
- if (itExisting != m_synthParams.end())
- {
- itExisting->second.push_back(p.get());
- }
- else
- {
- ParameterList params;
- params.emplace_back(p.get());
- m_synthParams.insert(std::make_pair(idx, std::move(params)));
- }
-
- if (isNonPartExclusive)
- {
- jassert(part == 0);
- globalParams->addChild(std::move(p));
- }
- else
- group->addChild(std::move(p));
- }
- else
- {
- // lifecycle handled by us
-
- auto itExisting = m_synthInternalParams.find(idx);
- if (itExisting != m_synthInternalParams.end())
- {
- itExisting->second.push_back(p.get());
- }
- else
- {
- ParameterList params;
- params.emplace_back(p.get());
- m_synthInternalParams.insert(std::make_pair(idx, std::move(params)));
- }
- m_synthInternalParamList.emplace_back(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)
@@ -199,31 +104,6 @@ namespace Virus
}
}
- const Controller::ParameterList& Controller::findSynthParam(const uint8_t _part, const uint8_t _page, const uint8_t _paramIndex)
- {
- const ParamIndex paramIndex{ _page, _part, _paramIndex };
-
- return findSynthParam(paramIndex);
- }
-
- const Controller::ParameterList& Controller::findSynthParam(const ParamIndex& _paramIndex)
- {
- const auto it = m_synthParams.find(_paramIndex);
-
- if (it != m_synthParams.end())
- return it->second;
-
- const auto iti = m_synthInternalParams.find(_paramIndex);
-
- if (iti == m_synthInternalParams.end())
- {
- static ParameterList empty;
- return empty;
- }
-
- return iti->second;
- }
-
juce::Value* Controller::getParamValue(uint8_t ch, uint8_t bank, uint8_t paramIndex)
{
const auto& params = findSynthParam(ch, static_cast<uint8_t>(virusLib::PAGE_A + bank), paramIndex);
@@ -235,28 +115,6 @@ namespace Virus
return ¶ms.front()->getValueObject();
}
- juce::Value* Controller::getParamValue(const ParameterType _param)
- {
- const auto res = getParameter(_param);
- return res ? &res->getValueObject() : nullptr;
- }
-
- Parameter* Controller::getParameter(const ParameterType _param) const
- {
- return getParameter(_param, 0);
- }
-
- Parameter* Controller::getParameter(const ParameterType _param, const uint8_t _part) const
- {
- if (_part >= m_paramsByParamType.size())
- return nullptr;
-
- if (_param >= m_paramsByParamType[_part].size())
- return nullptr;
-
- return m_paramsByParamType[_part][_param];
- }
-
void Controller::parseParamChange(const SysEx &msg)
{
constexpr auto pos = kHeaderWithMsgCodeLen - 1;
@@ -525,7 +383,7 @@ namespace Virus
return sum;
}
- template <typename T> juce::String Controller::parseAsciiText(const T &msg, const int start) const
+ template <typename T> juce::String Controller::parseAsciiText(const T &msg, const int start)
{
char text[kNameLength + 1];
text[kNameLength] = 0; // termination
@@ -534,7 +392,7 @@ namespace Virus
return {text};
}
- void Controller::printMessage(const SysEx &msg) const
+ void Controller::printMessage(const SysEx &msg)
{
std::stringstream ss;
for (auto &m : msg)
@@ -546,14 +404,10 @@ namespace Virus
ParameterType Controller::getParameterTypeByName(const std::string& _name) const
{
- for(size_t i=0; i<m_descriptions.getDescriptions().size(); ++i)
- {
- const auto& description = m_descriptions.getDescriptions()[i];
- if(description.name == _name)
- return static_cast<ParameterType>(i);
- }
-
- return Param_Invalid;
+ const auto i = getParameterIndexByName(_name);
+ if(i == InvalidParameterIndex)
+ return Param_Invalid;
+ return static_cast<ParameterType>(i);
}
void Controller::sendSysEx(const SysEx &msg) const
@@ -602,4 +456,15 @@ namespace Virus
m_virusOut.insert(m_virusOut.end(), newData.begin(), newData.end());
}
+
+ void Controller::sendParameterChange(const pluginLib::Parameter& _parameter, uint8_t _value)
+ {
+ const auto& desc = _parameter.getDescription();
+ sendSysEx(constructMessage({static_cast<uint8_t>(desc.page), _parameter.getPart(), desc.index, _value}));
+ }
+
+ pluginLib::Parameter* Controller::createParameter(pluginLib::Controller& _controller, const pluginLib::Description& _desc, uint8_t _part, int _uid)
+ {
+ return new Parameter(_controller, _desc, _part, _uid);
+ }
}; // namespace Virus
diff --git a/source/jucePlugin/VirusController.h b/source/jucePlugin/VirusController.h
@@ -4,6 +4,7 @@
#include "VirusParameterType.h"
#include "../jucePluginLib/parameterdescriptions.h"
+#include "../jucePluginLib/controller.h"
#include "../virusLib/microcontrollerTypes.h"
@@ -13,13 +14,8 @@ class AudioPluginAudioProcessor;
namespace Virus
{
- enum EnvelopeType
- {
- Env_Amp,
- Env_Filter
- };
using SysEx = std::vector<uint8_t>;
- class Controller : private juce::Timer
+ class Controller : public pluginLib::Controller, juce::Timer
{
public:
static constexpr size_t kDataSizeInBytes = 256; // same for multi and single
@@ -41,8 +37,6 @@ namespace Virus
using Singles = std::array<std::array<SinglePatch, 128>, 8>;
using Multis = std::array<MultiPatch, 128>;
- friend Parameter;
-
static constexpr auto kNameLength = 10;
Controller(AudioPluginAudioProcessor &, unsigned char deviceId = 0x00);
@@ -51,18 +45,15 @@ namespace Virus
// this is called by the plug-in on audio thread!
void dispatchVirusOut(const std::vector<synthLib::SMidiEvent> &);
- void printMessage(const SysEx &) const;
+ void sendParameterChange(const pluginLib::Parameter& _parameter, uint8_t _value) override;
- ParameterType getParameterTypeByName(const std::string& _name) const;
+ pluginLib::Parameter* createParameter(pluginLib::Controller& _controller, const pluginLib::Description& _desc, uint8_t _part, int _uid) override;
+
+ static void printMessage(const SysEx &);
- // currently Value as I figure out what's the best approach
- // ch - [0-15]
- // bank - [0-2] (ABC)
- // paramIndex - [0-127]
+ ParameterType getParameterTypeByName(const std::string& _name) const;
juce::Value* getParamValue(uint8_t ch, uint8_t bank, uint8_t paramIndex);
- juce::Value* getParamValue(ParameterType _param);
- Parameter* getParameter(ParameterType _param) const;
- Parameter *getParameter(ParameterType _param, uint8_t _part) const;
+
virusLib::VirusModel getVirusModel() const;
// bank - 0-1 (AB)
juce::StringArray getSinglePresetNames(virusLib::BankNumber bank) const;
@@ -105,46 +96,16 @@ namespace Virus
Singles m_singles;
MultiPatch m_currentMulti;
- 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;
- }
- };
-
- using ParameterList = std::vector<Parameter*>;
-
- std::map<ParamIndex, ParameterList> m_synthInternalParams;
- std::map<ParamIndex, ParameterList> m_synthParams; // exposed and managed by audio processor
- std::array<ParameterList, 16> m_paramsByParamType;
- std::vector<std::unique_ptr<Parameter>> m_synthInternalParamList;
-
- void registerParams();
- // tries to find synth param in both internal and host.
- // @return found parameter or nullptr if none found.
- const ParameterList& findSynthParam(uint8_t _part, uint8_t _page, uint8_t _paramIndex);
- const ParameterList& findSynthParam(const ParamIndex& _paramIndex);
-
// unchecked copy for patch data bytes
static inline uint8_t copyData(const SysEx &src, int startPos, std::array<uint8_t, kDataSizeInBytes>& dst);
- template <typename T> juce::String parseAsciiText(const T &, int startPos) const;
+ template <typename T> static juce::String parseAsciiText(const T &, int startPos);
void parseSingle(const SysEx &);
void parseMulti(const SysEx &);
void parseParamChange(const SysEx &);
void parseControllerDump(synthLib::SMidiEvent &);
- AudioPluginAudioProcessor &m_processor;
+ AudioPluginAudioProcessor& m_processor;
juce::CriticalSection m_eventQueueLock;
std::vector<synthLib::SMidiEvent> m_virusOut;
unsigned char m_deviceId;
@@ -152,6 +113,5 @@ namespace Virus
uint8_t m_currentProgram[16]{};
uint8_t m_currentPart = 0;
juce::PropertiesFile *m_config;
- pluginLib::ParameterDescriptions m_descriptions;
};
}; // namespace Virus
diff --git a/source/jucePlugin/VirusParameter.cpp b/source/jucePlugin/VirusParameter.cpp
@@ -1,126 +1,27 @@
#include "VirusParameter.h"
-#include "VirusController.h"
-
namespace Virus
{
- Parameter::Parameter(Controller &ctrl, const pluginLib::Description& desc, const uint8_t partNum, const int uniqueId) :
- RangedAudioParameter(genId(desc, partNum, uniqueId), "Ch " + juce::String(partNum + 1) + " " + desc.name), m_ctrl(ctrl),
- m_desc(desc), m_partNum(partNum), m_uniqueId(uniqueId)
+ Parameter::Parameter(pluginLib::Controller& _controller, const pluginLib::Description& _desc, uint8_t _partNum, int _uniqueId)
+ : pluginLib::Parameter(_controller, _desc, _partNum, _uniqueId)
{
- m_range.start = static_cast<float>(m_desc.range.getStart());
- m_range.end = static_cast<float>(m_desc.range.getEnd());
- m_range.interval = m_desc.isDiscrete || m_desc.isBool ? 1.0f : 0.0f;
- m_value.addListener(this);
- }
-
- void Parameter::valueChanged(juce::Value &)
- {
- const uint8_t value = roundToInt(m_value.getValue());
- jassert (m_range.getRange().contains(value) || m_range.end == value);
- if (value != m_lastValue)
- {
- // ignore initial update
- if(m_lastValue != -1)
- 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::setLinkedValue(const int _value)
- {
- const int newValue = juce::roundToInt(m_range.getRange().clipValue(static_cast<float>(_value)));
-
- if (newValue == m_lastValue)
- return;
-
- m_lastValue = newValue;
-
- if(getDescription().isPublic)
- {
- beginChangeGesture();
- setValueNotifyingHost(convertTo0to1(static_cast<float>(newValue)));
- endChangeGesture();
- }
- else
- {
- m_value.setValue(newValue);
- }
- }
-
- bool Parameter::isMetaParameter() const
- {
- return !m_linkedParameters.empty();
- }
-
- void Parameter::setValue(float newValue)
- {
- if (m_changingLinkedValues)
- return;
-
- m_value.setValue(convertFrom0to1(newValue));
-
- m_changingLinkedValues = true;
-
- for (const auto& parameter : m_linkedParameters)
- {
- if(!parameter->m_changingLinkedValues)
- parameter->setLinkedValue(m_value.getValue());
- }
-
- m_changingLinkedValues = false;
}
- void Parameter::setValueFromSynth(int newValue, const bool notifyHost)
+ float Parameter::getDefaultValue() const
{
- if (newValue == m_lastValue)
- return;
-
- m_lastValue = newValue;
-
- if (notifyHost && getDescription().isPublic)
- {
- beginChangeGesture();
- setValueNotifyingHost(convertTo0to1(static_cast<float>(newValue)));
- endChangeGesture();
- }
- else
- {
- m_value.setValue(newValue);
- }
-
- if (m_changingLinkedValues)
- return;
-
- m_changingLinkedValues = true;
-
- for (const auto& p : m_linkedParameters)
- p->setLinkedValue(newValue);
-
- m_changingLinkedValues = false;
- }
-
- juce::String Parameter::genId(const pluginLib::Description &d, const int part, const int uniqueId)
- {
- if(uniqueId > 0)
- return juce::String::formatted("%d_%d_%d_%d", static_cast<int>(d.page), part, d.index, uniqueId);
- return juce::String::formatted("%d_%d_%d", static_cast<int>(d.page), part, d.index);
- }
-
- void Parameter::addLinkedParameter(Parameter* _param)
- {
- if (_param == this)
- return;
-
- for (auto* p : m_linkedParameters)
- {
- _param->m_linkedParameters.insert(p);
- p->m_linkedParameters.insert(_param);
- }
-
- m_linkedParameters.insert(_param);
- _param->m_linkedParameters.insert(this);
+ const auto& desc = getDescription();
+
+ // default value should probably be in description instead of hardcoded like this.
+ if (desc.index == 21 || desc.index == 31) // osc keyfollows
+ return (64.0f + 32.0f) / 127.0f;
+ if (desc.index == 36) // osc vol / saturation
+ return 0.5f;
+ if (desc.index == 40) // filter1 cutoffs
+ return 1.0f;
+ if(desc.index == 41) //filter 2
+ return 0.5f;
+ if(desc.index == 91) // patch volume
+ return 100.0f / 127.0f;
+ return desc.isBipolar ? 0.5f : 0.0f; /* maybe return from ROM state? */
}
-} // namespace Virus
+}
diff --git a/source/jucePlugin/VirusParameter.h b/source/jucePlugin/VirusParameter.h
@@ -1,83 +1,15 @@
#pragma once
-#include <set>
-
-#include <juce_audio_processors/juce_audio_processors.h>
-
-#include "../jucePluginLib/parameterdescription.h"
+#include "../jucePluginLib/parameter.h"
namespace Virus
{
class Controller;
- class Parameter : juce::Value::Listener, public juce::RangedAudioParameter
+ class Parameter : public pluginLib::Parameter
{
public:
- Parameter(Controller &, const pluginLib::Description& desc, uint8_t partNum, int uniqueId);
-
- juce::Value &getValueObject() { return m_value; };
- const juce::Value &getValueObject() const { return m_value; };
-
- const pluginLib::Description& getDescription() const { return m_desc; };
-
- const juce::NormalisableRange<float> &getNormalisableRange() const override { return m_range; }
-
- bool isMetaParameter() const override;
-
- float getValue() const override { return convertTo0to1(m_value.getValue()); }
- void setValue(float newValue) override;
- void setValueFromSynth(int newValue, bool notifyHost = true);
- float getDefaultValue() const override {
- // default value should probably be in description instead of hardcoded like this.
- if (m_desc.index == 21 || m_desc.index == 31) // osc keyfollows
- return (64.0f + 32.0f) / 127.0f;
- if (m_desc.index == 36) // osc vol / saturation
- return 0.5f;
- if (m_desc.index == 40) // filter1 cutoffs
- return 1.0f;
- if(m_desc.index == 41) //filter 2
- return 0.5f;
- if(m_desc.index == 91) // patch volume
- return 100.0f / 127.0f;
- return m_desc.isBipolar ? 0.5f : 0.0f; /* maybe return from ROM state? */
- }
- bool isDiscrete() const override { return m_desc.isDiscrete; }
- bool isBoolean() const override { return m_desc.isBool; }
- bool isBipolar() const { return m_desc.isBipolar; }
-
- float getValueForText(const juce::String &text) const override
- {
- const auto res = m_desc.valueList.textToValue(std::string(text.getCharPointer()));
- return convertTo0to1(static_cast<float>(res));
- }
-
- juce::String getText(float normalisedValue, int /*maximumStringLength*/) const override
- {
- const auto v = convertFrom0to1(normalisedValue);
- return m_desc.valueList.valueToText(juce::roundToInt(v));
- }
-
- // allow 'injecting' additional code for specific parameter.
- // eg. multi/single value change requires triggering more logic.
- std::function<void()> onValueChanged = {};
-
- void addLinkedParameter(Parameter* _param);
-
- int getUniqueId() const { return m_uniqueId; }
-
- private:
- static juce::String genId(const pluginLib::Description &d, int part, int uniqueId);
- void valueChanged(juce::Value &) override;
- void setLinkedValue(int _value);
-
- Controller &m_ctrl;
- const pluginLib::Description m_desc;
- juce::NormalisableRange<float> m_range;
- const uint8_t m_partNum;
- const int m_uniqueId; // 0 for all unique parameters, > 0 if multiple Parameter instances reference a single synth parameter
- int m_lastValue{-1};
- juce::Value m_value;
- std::set<Parameter*> m_linkedParameters;
- bool m_changingLinkedValues = false;
- };
+ Parameter(pluginLib::Controller& _controller, const pluginLib::Description& _desc, uint8_t _partNum, int _uniqueId);
+ float getDefaultValue() const override;
+ };
} // namespace Virus
diff --git a/source/jucePlugin/VirusParameterBinding.cpp b/source/jucePlugin/VirusParameterBinding.cpp
@@ -74,7 +74,7 @@ void VirusParameterBinding::bind(juce::Slider &_slider, Virus::ParameterType _pa
}
void VirusParameterBinding::bind(juce::Slider &_slider, Virus::ParameterType _param, const uint8_t _part)
{
- const auto v = m_processor.getController().getParameter(_param, _part == CurrentPart ? m_processor.getController().getCurrentPart() : _part);
+ const auto v = dynamic_cast<Virus::Parameter*>(m_processor.getController().getParameter(_param, _part == CurrentPart ? m_processor.getController().getCurrentPart() : _part));
if (!v)
{
@@ -108,7 +108,7 @@ void VirusParameterBinding::bind(juce::ComboBox& _combo, Virus::ParameterType _p
void VirusParameterBinding::bind(juce::ComboBox& _combo, const Virus::ParameterType _param, uint8_t _part)
{
- const auto v = m_processor.getController().getParameter(_param, _part == CurrentPart ? m_processor.getController().getCurrentPart() : _part);
+ const auto v = dynamic_cast<Virus::Parameter*>(m_processor.getController().getParameter(_param, _part == CurrentPart ? m_processor.getController().getCurrentPart() : _part));
if (!v)
{
assert(false && "Failed to find parameter");
@@ -152,7 +152,7 @@ void VirusParameterBinding::bind(juce::ComboBox& _combo, const Virus::ParameterT
void VirusParameterBinding::bind(juce::Button &_btn, const Virus::ParameterType _param)
{
- const auto v = m_processor.getController().getParameter(_param, m_processor.getController().getCurrentPart());
+ const auto v = dynamic_cast<Virus::Parameter*>(m_processor.getController().getParameter(_param, m_processor.getController().getCurrentPart()));
if (!v)
{
assert(false && "Failed to find parameter");
diff --git a/source/jucePluginLib/CMakeLists.txt b/source/jucePluginLib/CMakeLists.txt
@@ -4,6 +4,7 @@ project(jucePluginLib VERSION ${CMAKE_PROJECT_VERSION})
set(SOURCES
controller.cpp controller.h
midipacket.cpp midipacket.h
+ parameter.cpp parameter.h
parameterdescription.cpp parameterdescription.h
parameterdescriptions.cpp parameterdescriptions.h
)
diff --git a/source/jucePluginLib/controller.cpp b/source/jucePluginLib/controller.cpp
@@ -1,5 +1,159 @@
#include "controller.h"
+#include "parameter.h"
+
namespace pluginLib
{
+ Controller::Controller(const std::string& _parameterDescJson) : m_descriptions(_parameterDescJson)
+ {
+ }
+
+ void Controller::registerParams(juce::AudioProcessor& _processor)
+ {
+ auto globalParams = std::make_unique<juce::AudioProcessorParameterGroup>("global", "Global", "|");
+
+ std::map<ParamIndex, int> knownParameterIndices;
+
+ for (uint8_t part = 0; part < 16; part++)
+ {
+ m_paramsByParamType[part].reserve(m_descriptions.getDescriptions().size());
+
+ const auto partNumber = juce::String(part + 1);
+ auto group =
+ std::make_unique<juce::AudioProcessorParameterGroup>("ch" + partNumber, "Ch " + partNumber, "|");
+
+ uint32_t parameterDescIndex = 0;
+ for (const auto& desc : m_descriptions.getDescriptions())
+ {
+ ++parameterDescIndex;
+
+ const ParamIndex idx = {static_cast<uint8_t>(desc.page), part, desc.index};
+
+ int uid = 0;
+
+ auto itKnownParamIdx = knownParameterIndices.find(idx);
+
+ if(itKnownParamIdx == knownParameterIndices.end())
+ knownParameterIndices.insert(std::make_pair(idx, 0));
+ else
+ uid = ++itKnownParamIdx->second;
+
+ std::unique_ptr<Parameter> p;
+ p.reset(createParameter(*this, desc, part, uid));
+
+ if(uid > 0)
+ {
+ const auto& existingParams = findSynthParam(idx);
+
+ for (auto& existingParam : existingParams)
+ existingParam->addLinkedParameter(p.get());
+ }
+
+ m_paramsByParamType[part].push_back(p.get());
+
+ const bool isNonPartExclusive = (desc.classFlags & (int)pluginLib::ParameterClass::Global) || (desc.classFlags & (int)pluginLib::ParameterClass::NonPartSensitive);
+ if (isNonPartExclusive)
+ {
+ if (part != 0)
+ continue; // only register on first part!
+ }
+ if (p->getDescription().isPublic)
+ {
+ // lifecycle managed by Juce
+
+ auto itExisting = m_synthParams.find(idx);
+ if (itExisting != m_synthParams.end())
+ {
+ itExisting->second.push_back(p.get());
+ }
+ else
+ {
+ ParameterList params;
+ params.emplace_back(p.get());
+ m_synthParams.insert(std::make_pair(idx, std::move(params)));
+ }
+
+ if (isNonPartExclusive)
+ {
+ jassert(part == 0);
+ globalParams->addChild(std::move(p));
+ }
+ else
+ group->addChild(std::move(p));
+ }
+ else
+ {
+ // lifecycle handled by us
+
+ auto itExisting = m_synthInternalParams.find(idx);
+ if (itExisting != m_synthInternalParams.end())
+ {
+ itExisting->second.push_back(p.get());
+ }
+ else
+ {
+ ParameterList params;
+ params.emplace_back(p.get());
+ m_synthInternalParams.insert(std::make_pair(idx, std::move(params)));
+ }
+ m_synthInternalParamList.emplace_back(std::move(p));
+ }
+ }
+ _processor.addParameterGroup(std::move(group));
+ }
+ _processor.addParameterGroup(std::move(globalParams));
+ }
+
+ const Controller::ParameterList& Controller::findSynthParam(const uint8_t _part, const uint8_t _page, const uint8_t _paramIndex)
+ {
+ const ParamIndex paramIndex{ _page, _part, _paramIndex };
+
+ return findSynthParam(paramIndex);
+ }
+
+ const Controller::ParameterList& Controller::findSynthParam(const ParamIndex& _paramIndex)
+ {
+ const auto it = m_synthParams.find(_paramIndex);
+
+ if (it != m_synthParams.end())
+ return it->second;
+
+ const auto iti = m_synthInternalParams.find(_paramIndex);
+
+ if (iti == m_synthInternalParams.end())
+ {
+ static ParameterList empty;
+ return empty;
+ }
+
+ return iti->second;
+ }
+
+ juce::Value* Controller::getParamValueObject(const uint32_t _index)
+ {
+ const auto res = getParameter(_index);
+ return res ? &res->getValueObject() : nullptr;
+ }
+
+ Parameter* Controller::getParameter(const uint32_t _index) const
+ {
+ return getParameter(_index, 0);
+ }
+
+ Parameter* Controller::getParameter(const uint32_t _index, const uint8_t _part) const
+ {
+ if (_part >= m_paramsByParamType.size())
+ return nullptr;
+
+ if (_index >= m_paramsByParamType[_part].size())
+ return nullptr;
+
+ return m_paramsByParamType[_part][_index];
+ }
+
+ uint32_t Controller::getParameterIndexByName(const std::string& _name) const
+ {
+ uint32_t index;
+ return m_descriptions.getIndexByName(index, _name) ? index : InvalidParameterIndex;
+ }
}
diff --git a/source/jucePluginLib/controller.h b/source/jucePluginLib/controller.h
@@ -1,5 +1,62 @@
#pragma once
+#include "parameterdescriptions.h"
+#include "parameter.h"
+
+#include <string>
+
namespace pluginLib
{
+ class Controller
+ {
+ public:
+ static constexpr uint32_t InvalidParameterIndex = 0xffffffff;
+
+ explicit Controller(const std::string& _parameterDescJson);
+
+ virtual void sendParameterChange(const Parameter& _parameter, uint8_t _value) = 0;
+
+ juce::Value* getParamValueObject(uint32_t _index);
+ Parameter* getParameter(uint32_t _index) const;
+ Parameter* getParameter(uint32_t _index, uint8_t _part) const;
+
+ uint32_t getParameterIndexByName(const std::string& _name) const;
+
+ protected:
+ virtual Parameter* createParameter(Controller& _controller, const Description& _desc, uint8_t _part, int _uid) = 0;
+ void registerParams(juce::AudioProcessor& _processor);
+
+ private:
+
+ ParameterDescriptions m_descriptions;
+
+ 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;
+ }
+ };
+
+ using ParameterList = std::vector<Parameter*>;
+
+ // tries to find synth param in both internal and host
+ protected:
+ const ParameterList& findSynthParam(uint8_t _part, uint8_t _page, uint8_t _paramIndex);
+ const ParameterList& findSynthParam(const ParamIndex& _paramIndex);
+
+ std::map<ParamIndex, ParameterList> m_synthInternalParams;
+ std::map<ParamIndex, ParameterList> m_synthParams; // exposed and managed by audio processor
+ std::array<ParameterList, 16> m_paramsByParamType;
+ std::vector<std::unique_ptr<Parameter>> m_synthInternalParamList;
+ };
}
diff --git a/source/jucePluginLib/parameter.cpp b/source/jucePluginLib/parameter.cpp
@@ -0,0 +1,126 @@
+#include "parameter.h"
+
+#include "controller.h"
+
+namespace pluginLib
+{
+ Parameter::Parameter(Controller &ctrl, const pluginLib::Description& _desc, const uint8_t _partNum, const int uniqueId) :
+ juce::RangedAudioParameter(genId(_desc, _partNum, uniqueId), "Ch " + juce::String(_partNum + 1) + " " + _desc.name), m_ctrl(ctrl),
+ m_desc(_desc), m_partNum(_partNum), m_uniqueId(uniqueId)
+ {
+ m_range.start = static_cast<float>(m_desc.range.getStart());
+ m_range.end = static_cast<float>(m_desc.range.getEnd());
+ m_range.interval = m_desc.isDiscrete || m_desc.isBool ? 1.0f : 0.0f;
+ m_value.addListener(this);
+ }
+
+ void Parameter::valueChanged(juce::Value &)
+ {
+ const uint8_t value = roundToInt(m_value.getValue());
+ jassert (m_range.getRange().contains(value) || m_range.end == value);
+ if (value != m_lastValue)
+ {
+ // ignore initial update
+ if(m_lastValue != -1)
+ m_ctrl.sendParameterChange(*this, value);
+ m_lastValue = value;
+ }
+ if (onValueChanged)
+ onValueChanged();
+ }
+
+ void Parameter::setLinkedValue(const int _value)
+ {
+ const int newValue = juce::roundToInt(m_range.getRange().clipValue(static_cast<float>(_value)));
+
+ if (newValue == m_lastValue)
+ return;
+
+ m_lastValue = newValue;
+
+ if(getDescription().isPublic)
+ {
+ beginChangeGesture();
+ setValueNotifyingHost(convertTo0to1(static_cast<float>(newValue)));
+ endChangeGesture();
+ }
+ else
+ {
+ m_value.setValue(newValue);
+ }
+ }
+
+ bool Parameter::isMetaParameter() const
+ {
+ return !m_linkedParameters.empty();
+ }
+
+ void Parameter::setValue(float newValue)
+ {
+ if (m_changingLinkedValues)
+ return;
+
+ m_value.setValue(convertFrom0to1(newValue));
+
+ m_changingLinkedValues = true;
+
+ for (const auto& parameter : m_linkedParameters)
+ {
+ if(!parameter->m_changingLinkedValues)
+ parameter->setLinkedValue(m_value.getValue());
+ }
+
+ m_changingLinkedValues = false;
+ }
+
+ void Parameter::setValueFromSynth(int newValue, const bool notifyHost)
+ {
+ if (newValue == m_lastValue)
+ return;
+
+ m_lastValue = newValue;
+
+ if (notifyHost && getDescription().isPublic)
+ {
+ beginChangeGesture();
+ setValueNotifyingHost(convertTo0to1(static_cast<float>(newValue)));
+ endChangeGesture();
+ }
+ else
+ {
+ m_value.setValue(newValue);
+ }
+
+ if (m_changingLinkedValues)
+ return;
+
+ m_changingLinkedValues = true;
+
+ for (const auto& p : m_linkedParameters)
+ p->setLinkedValue(newValue);
+
+ m_changingLinkedValues = false;
+ }
+
+ juce::String Parameter::genId(const pluginLib::Description &d, const int part, const int uniqueId)
+ {
+ if(uniqueId > 0)
+ return juce::String::formatted("%d_%d_%d_%d", static_cast<int>(d.page), part, d.index, uniqueId);
+ return juce::String::formatted("%d_%d_%d", static_cast<int>(d.page), part, d.index);
+ }
+
+ void Parameter::addLinkedParameter(Parameter* _param)
+ {
+ if (_param == this)
+ return;
+
+ for (auto* p : m_linkedParameters)
+ {
+ _param->m_linkedParameters.insert(p);
+ p->m_linkedParameters.insert(_param);
+ }
+
+ m_linkedParameters.insert(_param);
+ _param->m_linkedParameters.insert(this);
+ }
+}
diff --git a/source/jucePluginLib/parameter.h b/source/jucePluginLib/parameter.h
@@ -0,0 +1,74 @@
+#pragma once
+
+#include <juce_audio_processors/juce_audio_processors.h>
+
+#include <set>
+
+#include "parameterdescription.h"
+
+namespace pluginLib
+{
+ struct Description;
+
+ class Controller;
+
+ class Parameter : juce::Value::Listener, public juce::RangedAudioParameter
+ {
+ public:
+ Parameter(Controller& _controller, const Description& _desc, uint8_t _partNum, int uniqueId);
+
+ juce::Value &getValueObject() { return m_value; };
+ const juce::Value &getValueObject() const { return m_value; };
+
+ const Description& getDescription() const { return m_desc; };
+
+ uint8_t getPart() const { return m_partNum; }
+
+ const juce::NormalisableRange<float> &getNormalisableRange() const override { return m_range; }
+
+ bool isMetaParameter() const override;
+
+ float getValue() const override { return convertTo0to1(m_value.getValue()); }
+ void setValue(float newValue) override;
+ void setValueFromSynth(int newValue, bool notifyHost = true);
+
+ bool isDiscrete() const override { return m_desc.isDiscrete; }
+ bool isBoolean() const override { return m_desc.isBool; }
+ bool isBipolar() const { return m_desc.isBipolar; }
+
+ float getValueForText(const juce::String &text) const override
+ {
+ const auto res = m_desc.valueList.textToValue(std::string(text.getCharPointer()));
+ return convertTo0to1(static_cast<float>(res));
+ }
+
+ juce::String getText(float normalisedValue, int /*maximumStringLength*/) const override
+ {
+ const auto v = convertFrom0to1(normalisedValue);
+ return m_desc.valueList.valueToText(juce::roundToInt(v));
+ }
+
+ // allow 'injecting' additional code for specific parameter.
+ // eg. multi/single value change requires triggering more logic.
+ std::function<void()> onValueChanged = {};
+
+ void addLinkedParameter(Parameter* _param);
+
+ int getUniqueId() const { return m_uniqueId; }
+
+ private:
+ static juce::String genId(const Description &d, int part, int uniqueId);
+ void valueChanged(juce::Value &) override;
+ void setLinkedValue(int _value);
+
+ Controller &m_ctrl;
+ const Description m_desc;
+ juce::NormalisableRange<float> m_range;
+ const uint8_t m_partNum;
+ const int m_uniqueId; // 0 for all unique parameters, > 0 if multiple Parameter instances reference a single synth parameter
+ int m_lastValue{-1};
+ juce::Value m_value;
+ std::set<Parameter*> m_linkedParameters;
+ bool m_changingLinkedValues = false;
+ };
+}