gearmulator

Emulation of classic VA synths of the late 90s/2000s that are based on Motorola 56300 family DSPs
Log | Files | Refs | Submodules | README | LICENSE

commit 96b9ea847d1a10f387cb27c0be98173f6595d0ac
parent b21f2f77f1e644e90fb30e9357301fab9e2fcd71
Author: dsp56300 <dsp56300@users.noreply.github.com>
Date:   Sat,  1 Jun 2024 01:45:34 +0200

parameter links WIP

Diffstat:
Msource/jucePluginEditorLib/parameterOverlay.cpp | 8++++++++
Msource/jucePluginEditorLib/parameterOverlay.h | 1+
Msource/jucePluginEditorLib/pluginEditor.cpp | 6++++++
Msource/jucePluginLib/CMakeLists.txt | 1+
Msource/jucePluginLib/controller.cpp | 6+++++-
Msource/jucePluginLib/controller.h | 4++++
Msource/jucePluginLib/parameter.cpp | 16++++++++++++++++
Msource/jucePluginLib/parameter.h | 8++++++++
Msource/jucePluginLib/parameterlink.cpp | 67+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msource/jucePluginLib/parameterlink.h | 37+++++++++++++++++--------------------
Asource/jucePluginLib/parameterlinks.cpp | 150+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asource/jucePluginLib/parameterlinks.h | 58++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msource/jucePluginLib/parameterlistener.cpp | 4++++
Msource/jucePluginLib/parameterlistener.h | 1+
Msource/jucePluginLib/types.h | 7+++++++
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 + }; }