commit 96b9ea847d1a10f387cb27c0be98173f6595d0ac
parent b21f2f77f1e644e90fb30e9357301fab9e2fcd71
Author: dsp56300 <dsp56300@users.noreply.github.com>
Date: Sat, 1 Jun 2024 01:45:34 +0200
parameter links WIP
Diffstat:
15 files changed, 353 insertions(+), 21 deletions(-)
diff --git a/source/jucePluginEditorLib/parameterOverlay.cpp b/source/jucePluginEditorLib/parameterOverlay.cpp
@@ -97,8 +97,10 @@ namespace jucePluginEditorLib
void ParameterOverlay::updateOverlays()
{
const auto isLocked = m_parameter != nullptr && m_parameter->isLocked();
+ const auto isLinked = m_parameter != nullptr && (m_parameter->getLinkState() & pluginLib::Source);
toggleOverlay(m_imageLock, ImagePool::Type::Lock, isLocked);
+ toggleOverlay(m_imageLock, ImagePool::Type::Link, isLinked);
}
void ParameterOverlay::setParameter(pluginLib::Parameter* _parameter)
@@ -109,7 +111,9 @@ namespace jucePluginEditorLib
if(m_parameter)
{
m_parameter->onLockedChanged.removeListener(m_parameterLockChangedListener);
+ m_parameter->onLinkStateChanged.removeListener(m_parameterLinkChangedListener);
m_parameterLockChangedListener = InvalidListenerId;
+ m_parameterLinkChangedListener = InvalidListenerId;
}
m_parameter = _parameter;
@@ -120,6 +124,10 @@ namespace jucePluginEditorLib
{
updateOverlays();
});
+ m_parameterLinkChangedListener = m_parameter->onLinkStateChanged.addListener([this](pluginLib::Parameter*, const pluginLib::ParameterLinkType&)
+ {
+ updateOverlays();
+ });
}
updateOverlays();
diff --git a/source/jucePluginEditorLib/parameterOverlay.h b/source/jucePluginEditorLib/parameterOverlay.h
@@ -43,6 +43,7 @@ namespace jucePluginEditorLib
pluginLib::Parameter* m_parameter = nullptr;
size_t m_parameterLockChangedListener = InvalidListenerId;
+ size_t m_parameterLinkChangedListener = InvalidListenerId;
juce::DrawableImage* m_imageLock = nullptr;
};
diff --git a/source/jucePluginEditorLib/pluginEditor.cpp b/source/jucePluginEditorLib/pluginEditor.cpp
@@ -274,6 +274,8 @@ namespace jucePluginEditorLib
juce::PopupMenu menu;
+ // Lock / Unlock
+
for (const auto& regionId : paramRegionIds)
{
const auto& regionName = regions.find(regionId)->second.getName();
@@ -290,6 +292,8 @@ namespace jucePluginEditorLib
});
}
+ // Copy to clipboard
+
menu.addSeparator();
for (const auto& regionId : paramRegionIds)
@@ -302,6 +306,8 @@ namespace jucePluginEditorLib
});
}
+ // Paste from clipboard
+
const auto data = pluginLib::Clipboard::getDataFromString(m_processor, juce::SystemClipboard::getTextFromClipboard().toStdString());
if(!data.parameterValuesByRegion.empty())
diff --git a/source/jucePluginLib/CMakeLists.txt b/source/jucePluginLib/CMakeLists.txt
@@ -15,6 +15,7 @@ set(SOURCES
parameterdescription.cpp parameterdescription.h
parameterdescriptions.cpp parameterdescriptions.h
parameterlink.cpp parameterlink.h
+ parameterlinks.cpp parameterlinks.h
parameterlistener.cpp parameterlistener.h
parameterlocking.cpp parameterlocking.h
parameterregion.cpp parameterregion.h
diff --git a/source/jucePluginLib/controller.cpp b/source/jucePluginLib/controller.cpp
@@ -15,7 +15,11 @@ namespace pluginLib
return static_cast<uint8_t>(roundToInt(_p->getValueObject().getValue()));
}
- Controller::Controller(pluginLib::Processor& _processor, const std::string& _parameterDescJson) : m_processor(_processor), m_descriptions(_parameterDescJson), m_locking(*this)
+ Controller::Controller(Processor& _processor, const std::string& _parameterDescJson)
+ : m_processor(_processor)
+ , m_descriptions(_parameterDescJson)
+ , m_locking(*this)
+ , m_parameterLinks(*this)
{
if(!m_descriptions.isValid())
{
diff --git a/source/jucePluginLib/controller.h b/source/jucePluginLib/controller.h
@@ -9,6 +9,8 @@
#include <string>
+#include "parameterlinks.h"
+
namespace juce
{
class AudioProcessor;
@@ -70,6 +72,7 @@ namespace pluginLib
void getPluginMidiOut(std::vector<synthLib::SMidiEvent>&);
ParameterLocking& getParameterLocking() { return m_locking; }
+ ParameterLinks& getParameterLinks() { return m_parameterLinks; }
std::set<std::string> getRegionIdsForParameter(const Parameter* _param) const;
std::set<std::string> getRegionIdsForParameter(const std::string& _name) const;
@@ -140,5 +143,6 @@ namespace pluginLib
std::array<ParameterList, 16> m_paramsByParamType;
std::vector<std::unique_ptr<Parameter>> m_synthInternalParamList;
ParameterLocking m_locking;
+ ParameterLinks m_parameterLinks;
};
}
diff --git a/source/jucePluginLib/parameter.cpp b/source/jucePluginLib/parameter.cpp
@@ -121,6 +121,22 @@ namespace pluginLib
m_rateLimit = _ms;
}
+ void Parameter::setLinkState(const ParameterLinkType _type)
+ {
+ const auto prev = m_linkType;
+ m_linkType = static_cast<ParameterLinkType>(m_linkType | _type);
+ if(m_linkType != prev)
+ onLinkStateChanged(this, m_linkType);
+ }
+
+ void Parameter::clearLinkState(const ParameterLinkType _type)
+ {
+ const auto prev = m_linkType;
+ m_linkType = static_cast<ParameterLinkType>(m_linkType & ~_type);
+ if(m_linkType != prev)
+ onLinkStateChanged(this, m_linkType);
+ }
+
bool Parameter::isMetaParameter() const
{
return !m_derivedParameters.empty();
diff --git a/source/jucePluginLib/parameter.h b/source/jucePluginLib/parameter.h
@@ -7,6 +7,7 @@
#include "juce_audio_processors/juce_audio_processors.h"
#include "event.h"
+#include "types.h"
namespace pluginLib
{
@@ -26,6 +27,7 @@ namespace pluginLib
};
Event<Parameter*, bool> onLockedChanged;
+ Event<Parameter*, ParameterLinkType> onLinkStateChanged;
Event<Parameter*> onValueChanged;
Parameter(Controller& _controller, const Description& _desc, uint8_t _partNum, int _uniqueId);
@@ -86,6 +88,11 @@ namespace pluginLib
void setRateLimitMilliseconds(uint32_t _ms);
+ void setLinkState(ParameterLinkType _type);
+ void clearLinkState(ParameterLinkType _type);
+
+ ParameterLinkType getLinkState() const { return m_linkType; }
+
private:
static juce::String genId(const Description &d, int part, int uniqueId);
void valueChanged(juce::Value &) override;
@@ -109,5 +116,6 @@ namespace pluginLib
uint64_t m_lastSendTime = 0;
uint32_t m_uniqueDelayCallbackId = 0;
bool m_isLocked = false;
+ ParameterLinkType m_linkType = None;
};
}
diff --git a/source/jucePluginLib/parameterlink.cpp b/source/jucePluginLib/parameterlink.cpp
@@ -0,0 +1,67 @@
+
+#include "parameterlink.h"
+
+#include "parameter.h"
+
+namespace pluginLib
+{
+ ParameterLink::ParameterLink(Parameter* _source, Parameter* _dest) : m_source(_source), m_sourceListener(_source)
+ {
+ m_sourceValue = _source->getUnnormalizedValue();
+
+ m_sourceListener = [this](Parameter*)
+ {
+ onSourceValueChanged();
+ };
+
+ _source->setLinkState(Source);
+ add(_dest);
+ }
+
+ ParameterLink::~ParameterLink()
+ {
+ m_source->clearLinkState(Source);
+ }
+
+ // ReSharper disable once CppParameterMayBeConstPtrOrRef
+ bool ParameterLink::add(Parameter* _target)
+ {
+ if(!_target)
+ return false;
+
+ if(!m_targets.insert(_target).second)
+ return false;
+
+ _target->setUnnormalizedValue(m_sourceValue, Parameter::ChangedBy::Ui);
+
+ return true;
+ }
+
+ // ReSharper disable once CppParameterMayBeConstPtrOrRef
+ bool ParameterLink::remove(Parameter* _target)
+ {
+ const auto it = m_targets.find(_target);
+ if(it == m_targets.end())
+ return false;
+ m_targets.erase(it);
+ return true;
+ }
+
+ void ParameterLink::onSourceValueChanged()
+ {
+ const auto newValue = m_source->getUnnormalizedValue();
+
+ if(newValue == m_sourceValue)
+ return;
+
+ m_sourceValue = newValue;
+
+ const auto origin = m_source->getChangeOrigin();
+
+ for (auto* p : m_targets)
+ {
+ const auto v = p->getDescription().range.clipValue(newValue);
+ p->setUnnormalizedValue(v, origin);
+ }
+ }
+}
diff --git a/source/jucePluginLib/parameterlink.h b/source/jucePluginLib/parameterlink.h
@@ -2,32 +2,29 @@
#include <set>
+#include "parameterlistener.h"
+
namespace pluginLib
{
- struct ParameterLink
- {
- enum class LinkMode
- {
- Absolute,
- Relative
- };
+ class Parameter;
- bool isValid() const
- {
- return source != dest;
- }
+ class ParameterLink
+ {
+ public:
+ ParameterLink(Parameter* _source, Parameter* _dest);
+ ~ParameterLink();
- bool hasConditions() const
- {
- return !conditionValues.empty();
- }
+ bool add(Parameter* _target);
+ bool remove(Parameter* _target);
- uint32_t source = 0;
- uint32_t dest = 0;
+ bool empty() const { return m_targets.empty(); }
- uint32_t conditionParameter;
- std::set<uint8_t> conditionValues;
+ private:
+ void onSourceValueChanged();
- LinkMode mode = LinkMode::Absolute;
+ Parameter* const m_source;
+ ParameterListener m_sourceListener;
+ std::set<Parameter*> m_targets;
+ int m_sourceValue;
};
}
diff --git a/source/jucePluginLib/parameterlinks.cpp b/source/jucePluginLib/parameterlinks.cpp
@@ -0,0 +1,150 @@
+#include "parameterlinks.h"
+
+#include "controller.h"
+
+namespace pluginLib
+{
+ ParameterLinks::ParameterLinks(Controller& _controller) : m_controller(_controller)
+ {
+ }
+
+ bool ParameterLinks::add(Parameter* _source, Parameter* _dest)
+ {
+ if(!_source || !_dest)
+ return false;
+
+ if(_source == _dest)
+ return false;
+
+ const auto it = m_parameterLinks.find(_source);
+
+ if(it != m_parameterLinks.end())
+ return it->second->add(_dest);
+
+ m_parameterLinks.insert({_source, std::make_unique<ParameterLink>(_source, _dest)});
+ m_destToSource[_dest].insert(_source);
+
+ _dest->setLinkState(Target);
+
+ return true;
+ }
+
+ bool ParameterLinks::remove(const Parameter* _source, Parameter* _dest)
+ {
+ const auto it = m_parameterLinks.find(_source);
+ if(it == m_parameterLinks.end())
+ return false;
+ if(!it->second->remove(_dest))
+ return false;
+ if(it->second->empty())
+ m_parameterLinks.erase(it);
+
+ auto& sources = m_destToSource[_dest];
+
+ sources.erase(_source);
+
+ if(sources.empty())
+ {
+ m_destToSource.erase(_dest);
+ _dest->clearLinkState(Target);
+ }
+
+ return true;
+ }
+
+ ParameterLinkType ParameterLinks::getRegionLinkType(const std::string& _regionId, const uint8_t _part) const
+ {
+ const auto& regions = m_controller.getParameterDescriptions().getRegions();
+
+ const auto itRegion = regions.find(_regionId);
+
+ if(itRegion == regions.end())
+ return None;
+
+ uint32_t totalCount = 0;
+ uint32_t sourceCount = 0;
+ uint32_t targetCount = 0;
+
+ for (const auto& [name, desc] : itRegion->second.getParams())
+ {
+ const auto* parameter = m_controller.getParameter(name, _part);
+
+ if(!parameter)
+ continue;
+
+ ++totalCount;
+
+ const auto state = parameter->getLinkState();
+
+ if(state & ParameterLinkType::Source) ++sourceCount;
+ if(state & ParameterLinkType::Target) ++targetCount;
+ }
+
+ ParameterLinkType result = None;
+
+ if(sourceCount > (totalCount>>1))
+ result = static_cast<ParameterLinkType>(result | ParameterLinkType::Source);
+ if(targetCount > (totalCount>>1))
+ result = static_cast<ParameterLinkType>(result | ParameterLinkType::Target);
+
+ return result;
+ }
+
+ bool ParameterLinks::linkRegion(const std::string& _regionId, const uint8_t _partSource, const uint8_t _partDest)
+ {
+ const auto& regions = m_controller.getParameterDescriptions().getRegions();
+
+ const auto itRegion = regions.find(_regionId);
+
+ if(itRegion == regions.end())
+ return false;
+
+ const RegionLink link{_regionId, _partSource, _partDest};
+
+ if(!m_linkedRegions.insert(link).second)
+ return false;
+
+ return updateRegionLinks(itRegion->second, _partSource, _partDest, true);
+ }
+
+ bool ParameterLinks::unlinkRegion(const std::string& _regionId, const uint8_t _partSource, const uint8_t _partDest)
+ {
+ const RegionLink link{_regionId, _partSource, _partDest};
+
+ if(!m_linkedRegions.erase(link))
+ return false;
+
+ const auto& regions = m_controller.getParameterDescriptions().getRegions();
+
+ const auto itRegion = regions.find(_regionId);
+
+ if(itRegion == regions.end())
+ return false;
+
+ return updateRegionLinks(itRegion->second, _partSource, _partDest, false);
+ }
+
+ bool ParameterLinks::isRegionLinked(const std::string& _regionId, const uint8_t _partSource, const uint8_t _partDest) const
+ {
+ const RegionLink link{_regionId, _partSource, _partDest};
+ return m_linkedRegions.find(link) != m_linkedRegions.end();
+ }
+
+ bool ParameterLinks::updateRegionLinks(const ParameterRegion& _region, const uint8_t _partSource, const uint8_t _partDest, const bool _enableLink)
+ {
+ bool res = false;
+
+ for(const auto& [name, desc] : _region.getParams())
+ {
+ auto* paramSource = m_controller.getParameter(name, _partSource);
+ auto* paramDest = m_controller.getParameter(name, _partDest);
+
+ if(_enableLink)
+ res |= add(paramSource, paramDest);
+ else
+ res |= remove(paramSource, paramDest);
+ }
+
+ return res;
+ }
+}
diff --git a/source/jucePluginLib/parameterlinks.h b/source/jucePluginLib/parameterlinks.h
@@ -0,0 +1,58 @@
+#pragma once
+
+#include <map>
+#include <memory>
+
+#include "parameterlink.h"
+#include "types.h"
+
+namespace pluginLib
+{
+ class ParameterRegion;
+ class Controller;
+ class Parameter;
+
+ class ParameterLinks
+ {
+ public:
+ ParameterLinks(Controller& _controller);
+
+ bool add(Parameter* _source, Parameter* _dest);
+ bool remove(const Parameter* _source, Parameter* _dest);
+
+ ParameterLinkType getRegionLinkType(const std::string& _regionId, uint8_t _part) const;
+
+ bool linkRegion(const std::string& _regionId, uint8_t _partSource, uint8_t _partDest);
+ bool unlinkRegion(const std::string& _regionId, uint8_t _partSource, uint8_t _partDest);
+ bool isRegionLinked(const std::string& _regionId, uint8_t _partSource, uint8_t _partDest) const;
+ private:
+ struct RegionLink
+ {
+ std::string regionId;
+ uint8_t sourcePart = 0;
+ uint8_t destPart = 0;
+
+ bool operator == (const RegionLink& _link) const
+ {
+ return sourcePart == _link.sourcePart && destPart == _link.destPart && regionId == _link.regionId;
+ }
+
+ bool operator < (const RegionLink& _link) const
+ {
+ if(sourcePart < _link.sourcePart) return true;
+ if(sourcePart > _link.sourcePart) return false;
+ if(destPart < _link.destPart) return true;
+ if(destPart > _link.destPart) return false;
+ if(regionId < _link.regionId) return true;
+ return false;
+ }
+ };
+
+ bool updateRegionLinks(const ParameterRegion& _region, uint8_t _partSource, uint8_t _partDest, bool _enableLink);
+
+ Controller& m_controller;
+ std::map<const Parameter*, std::unique_ptr<ParameterLink>> m_parameterLinks;
+ std::map<const Parameter*, std::set<const Parameter*>> m_destToSource;
+ std::set<RegionLink> m_linkedRegions;
+ };
+}
diff --git a/source/jucePluginLib/parameterlistener.cpp b/source/jucePluginLib/parameterlistener.cpp
@@ -2,6 +2,10 @@
#include "parameter.h"
+pluginLib::ParameterListener::ParameterListener(Parameter* _p) : EventListener(_p->onValueChanged)
+{
+}
+
pluginLib::ParameterListener::ParameterListener(Parameter* _p, const Callback& _callback): EventListener(_p->onValueChanged, _callback)
{
}
diff --git a/source/jucePluginLib/parameterlistener.h b/source/jucePluginLib/parameterlistener.h
@@ -16,6 +16,7 @@ namespace pluginLib
using Callback = Base::MyCallback;
ParameterListener() = default;
+ ParameterListener(Parameter* _p);
ParameterListener(Parameter* _p, const Callback& _callback);
void set(Parameter* _parameter, const Callback& _callback);
diff --git a/source/jucePluginLib/types.h b/source/jucePluginLib/types.h
@@ -5,4 +5,11 @@
namespace pluginLib
{
using PluginStream = synthLib::BinaryStream;
+
+ enum ParameterLinkType : uint32_t
+ {
+ None = 0,
+ Source = 1,
+ Target = 2
+ };
}