AnalogTapeModel

Physical modelling signal processing for analog tape recording
Log | Files | Refs | Submodules | README | LICENSE

commit 13dc87e37d1d97d4d3070c187758f1b2a1a1e014
parent a081d44151c1f3c124f84fedda8c246e4427ba03
Author: jatinchowdhury18 <[email protected]>
Date:   Mon, 14 Sep 2020 12:10:22 -0700

Manage mix groups through shared resource pointer, instead of OSC (#88)

* Manage mix groups through shared resource pointer, instead of OSC

* Update Mac builds

* Update Windows builds

Co-authored-by: jatinchowdhury18 <[email protected]>
Co-authored-by: Travis CI <[email protected]>
Diffstat:
MPlugin/CHOWTapeModel.jucer | 4----
MPlugin/Source/GUI/Assets/gui.xml | 2+-
MPlugin/Source/MixGroups/MixGroupsController.cpp | 144+++++++++++++++++++++++--------------------------------------------------------
MPlugin/Source/MixGroups/MixGroupsController.h | 21++++++++-------------
MPlugin/Source/MixGroups/MixGroupsParamReceiver.cpp | 64+++++++++++++++++++++++++++++++++++++++++++++-------------------
MPlugin/Source/MixGroups/MixGroupsParamReceiver.h | 49+++++++++++++++++++++++++++++++++----------------
6 files changed, 129 insertions(+), 155 deletions(-)

diff --git a/Plugin/CHOWTapeModel.jucer b/Plugin/CHOWTapeModel.jucer @@ -128,7 +128,6 @@ <MODULEPATH id="juce_dsp" path="Juce/modules"/> <MODULEPATH id="foleys_gui_magic" path="."/> <MODULEPATH id="juce_opengl" path="Juce/modules"/> - <MODULEPATH id="juce_osc" path="Juce/modules"/> </MODULEPATHS> </XCODE_MAC> <VS2017 targetFolder="Builds/VisualStudio2017"> @@ -153,7 +152,6 @@ <MODULEPATH id="juce_dsp" path="Juce/modules"/> <MODULEPATH id="foleys_gui_magic" path="."/> <MODULEPATH id="juce_opengl" path="Juce/modules"/> - <MODULEPATH id="juce_osc" path="Juce/modules"/> </MODULEPATHS> </VS2017> <LINUX_MAKE targetFolder="Builds/LinuxMakefile"> @@ -177,7 +175,6 @@ <MODULEPATH id="juce_dsp" path="Juce/modules"/> <MODULEPATH id="foleys_gui_magic" path="."/> <MODULEPATH id="juce_opengl" path="Juce/modules"/> - <MODULEPATH id="juce_osc" path="Juce/modules"/> </MODULEPATHS> </LINUX_MAKE> </EXPORTFORMATS> @@ -198,7 +195,6 @@ <MODULE id="juce_gui_basics" showAllCode="1" useLocalCopy="0" useGlobalPath="0"/> <MODULE id="juce_gui_extra" showAllCode="1" useLocalCopy="0" useGlobalPath="0"/> <MODULE id="juce_opengl" showAllCode="1" useLocalCopy="0" useGlobalPath="0"/> - <MODULE id="juce_osc" showAllCode="1" useLocalCopy="0" useGlobalPath="0"/> </MODULES> <LIVE_SETTINGS> <WINDOWS/> diff --git a/Plugin/Source/GUI/Assets/gui.xml b/Plugin/Source/GUI/Assets/gui.xml @@ -144,7 +144,7 @@ <ComboBox lookAndFeel="ComboBoxLNF" padding="0" border="0" background-color="00000000" name="Mix Group" caption="Mix Group" caption-size="0" flex-grow="0.9" combo-text="FFEAA92C" caption-color="FFFFFFFF" max-height="100" margin="0" - parameter="mix_group" combo-background="00000000" tooltip="Adds this plugin to a mix group. If you add this plugin to a group, the parameter settings will be copied to all other plugins in the group, and their parameters will remain in sync."/> + parameter="mix_group" combo-background="00000000" tooltip="Adds this plugin to a mix group. When the plugin is added to a group, the group parameters will be copied to this plugin, and their parameters will remain in sync."/> <presets margin="5" padding="0" background-color="00000000" border-color="595C6B" radius="" border="" lookAndFeel="ComboBoxLNF" tooltip="Selects a preset for the plugin." flex-grow="1.75" max-height="100"/> diff --git a/Plugin/Source/MixGroups/MixGroupsController.cpp b/Plugin/Source/MixGroups/MixGroupsController.cpp @@ -6,20 +6,22 @@ MixGroupsController::MixGroupsController (AudioProcessorValueTreeState& vts, AudioProcessor* proc) : vts (vts) { - // connect sender - sender.connect ("127.0.0.1", portNum); - // load parameters auto params = proc->getParameters(); loadParameterList (params); // set up receiver - paramReceiver->loadReceiverListeners (paramList); - paramReceiver->addChangeListener (this); + sharedData->loadParameterList (paramList); + sharedData->addListener (this); mixGroupParam = vts.getRawParameterValue (mixGroupParamID); } +MixGroupsController::~MixGroupsController() +{ + sharedData->removeListener (this); +} + void MixGroupsController::createParameterLayout (std::vector<std::unique_ptr<RangedAudioParameter>>& params) { params.push_back (std::make_unique<AudioParameterChoice> (mixGroupParamID, "Mix Group", StringArray ({"N/A", "1", "2", "3", "4"}), 0)); @@ -45,127 +47,65 @@ void MixGroupsController::loadParameterList (Array<AudioProcessorParameter*>& pa void MixGroupsController::parameterChanged (const String& parameterID, float newValue) { - int mixGroup = (int) mixGroupParam->load(); - if (mixGroup == 0) // no mix group, don't bother sending + if (parameterID == lastParameterChanged) // I just changed this param, don't want to get stuck in an endless loop... + { + lastParameterChanged = String(); return; + } + + int mixGroup = (int) mixGroupParam->load(); if (parameterID == mixGroupParamID) // mix group was changed { - OSCMessage message (allParamsAddress); - message.addArgument (uuid.toString()); - message.addArgument (mixGroup); + sharedData->pluginGroupChanged (uuid.toString(), mixGroup); + + if (mixGroup == 0) + return; - for (const auto& paramID : paramList) + int numPluginsInGroup = sharedData->getNumPluginsInGroup (mixGroup); + if (numPluginsInGroup == 1) // I'm the only plugin in this group { - auto param = vts.getParameter (paramID); - auto value = param->convertFrom0to1 (param->getValue()); - - message.addArgument (paramID); - message.addArgument (value); + sharedData->copyPluginState (mixGroup, vts); + } + else if (numPluginsInGroup > 1) // there are already plugins in this group + { + // copy shared state to me + for (const auto& paramID : paramList) + { + auto param = vts.getParameter (paramID); + auto value = sharedData->getParameter (paramID, mixGroup); + + lastParameterChanged = paramID; + param->setValueNotifyingHost (param->convertTo0to1 (value)); + } } - - sender.send (message); return; } - if (! paramList.contains (parameterID)) // parameter is not in list + if (mixGroup == 0) // no mix group, don't bother sending return; - sendParameter (parameterID, mixGroup, newValue); -} - -void MixGroupsController::sendParameter (const String& paramID, int mixGroup, float value) -{ - String address = oscAddressPrefix + paramID; - String pluginID = uuid.toString(); + if (! paramList.contains (parameterID)) // parameter is not in list + return; - sender.send (address, pluginID, mixGroup, value); + sharedData->setParameter (parameterID, mixGroup, newValue, uuid.toString()); } -void MixGroupsController::changeListenerCallback (ChangeBroadcaster* source) +void MixGroupsController::mixGroupParamChanged (const String& paramID, int mixGroup, float value, String otherUuid) { - if (source != paramReceiver) + if (uuid == otherUuid) // this message came from me! return; - // get message from receiver, we already know this will be a valid message! - auto& message = paramReceiver->getOSCMessage(); - - // get address - auto address = message.getAddressPattern().toString(); - - // special case: all parameters - if (address == allParamsAddress) - { - parseAllParams (message); - return; - } - - // get paramID from address - auto paramID = address.fromFirstOccurrenceOf (oscAddressPrefix, false, false); + // load parameter auto param = vts.getParameter (paramID); - if (param == nullptr) // invalid parameter return; - String pluginID = message[0].getString(); - if (uuid == pluginID) // this message came from me! - return; - - int mixGroup = message[1].getInt32(); if (mixGroup != (int) mixGroupParam->load()) // received message does not apply to this mix group return; - - auto value = message[2].getFloat32(); + + // set parameter value + lastParameterChanged = paramID; param->setValueNotifyingHost (param->convertTo0to1 (value)); } - -void MixGroupsController::parseAllParams (const OSCMessage& message) -{ - if (message.size() % 2 != 0) // must have even number of arguments! - return; - - String pluginID = message[0].getString(); - if (uuid == pluginID) // this message came from me! - return; - - int mixGroup = message[1].getInt32(); - if (mixGroup != (int) mixGroupParam->load()) // received message does not apply to this mix group - return; - - for (int argIdx = 2; argIdx < message.size(); argIdx += 2) - { - auto temp1 = message[argIdx]; - auto temp2 = message[argIdx+1]; - if (! (message[argIdx].isString() && message[argIdx + 1].isFloat32())) // incorrect format... - continue; - - String paramID = message[argIdx].getString(); - auto param = vts.getParameter (paramID); - - if (param == nullptr) // invalid parameter - continue; - - auto value = message[argIdx + 1].getFloat32(); - param->setValueNotifyingHost (param->convertTo0to1 (value)); - } -} - -bool MixGroupsController::isValidOSCMessage (const OSCMessage& message) -{ - // special case: all parameters - if (message.getAddressPattern() == allParamsAddress && message.size() > 2) - return true; - - // other valid messages will be of the form: string, int, float - if (message.size() != 3) - return false; - - bool isValid = true; - - isValid &= message[0].isString(); - isValid &= message[1].isInt32(); - isValid &= message[2].isFloat32(); - - return isValid; -} diff --git a/Plugin/Source/MixGroups/MixGroupsController.h b/Plugin/Source/MixGroups/MixGroupsController.h @@ -5,28 +5,22 @@ namespace MixGroupsConstants { - constexpr int portNum = 1818; - const String oscAddressPrefix = "/chowdsp/tape/"; - const String allParamsAddress = oscAddressPrefix + "all"; - const String mixGroupParamID = "mix_group"; + constexpr int numMixGroups = 4; + const String mixGroupParamID = "mix_group"; } /** Class to control syncing parameters between multiple mix groups */ class MixGroupsController : private AudioProcessorValueTreeState::Listener, - private ChangeListener + private MixGroupsSharedData::Listener { public: MixGroupsController (AudioProcessorValueTreeState& vts, AudioProcessor* proc); + ~MixGroupsController(); static void createParameterLayout (std::vector<std::unique_ptr<RangedAudioParameter>>& params); void parameterChanged (const String& parameterID, float newValue) override; - void changeListenerCallback (ChangeBroadcaster* source) override; - void sendParameter (const String& paramID, int mixGroup, float value); - - void parseAllParams (const OSCMessage& message); - - static bool isValidOSCMessage (const OSCMessage& message); + void mixGroupParamChanged (const String& paramID, int mixGroup, float value, String otherUuid) override; private: void loadParameterList (Array<AudioProcessorParameter*>& params); @@ -34,10 +28,11 @@ private: AudioProcessorValueTreeState& vts; std::atomic<float>* mixGroupParam = nullptr; Array<String> paramList; - OSCSender sender; Uuid uuid; - SharedResourcePointer<MixGroupsParamReceiver> paramReceiver; + String lastParameterChanged = ""; + + SharedResourcePointer<MixGroupsSharedData> sharedData; JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MixGroupsController) }; diff --git a/Plugin/Source/MixGroups/MixGroupsParamReceiver.cpp b/Plugin/Source/MixGroups/MixGroupsParamReceiver.cpp @@ -1,38 +1,64 @@ #include "MixGroupsParamReceiver.h" #include "MixGroupsController.h" -MixGroupsParamReceiver::MixGroupsParamReceiver() +MixGroupsSharedData::MixGroupsSharedData() { - // connect datagram socket - socket.bindToPort (MixGroupsConstants::portNum); - socket.setEnablePortReuse (true); - connectToSocket (socket); + for (int i = 0; i < MixGroupsConstants::numMixGroups; ++i) + pluginsInGroup.add (std::make_unique<StringArray>()); } -void MixGroupsParamReceiver::loadReceiverListeners (Array<String>& paramList) +void MixGroupsSharedData::loadParameterList (Array<String>& paramList) { - if (listenersInitialized) // listeners are already set up! + if (! paramMaps.empty()) // already loaded return; - for (auto& paramID : paramList) - addListener (this, MixGroupsConstants::oscAddressPrefix + paramID); + for (int i = 0; i < MixGroupsConstants::numMixGroups; ++i) + { + auto paramMap = std::make_unique<ParamMap>(); - addListener (this, MixGroupsConstants::oscAddressPrefix + "all"); + for (const auto& paramID : paramList) + paramMap->set (paramID, 0.0f); - listenersInitialized = true; + paramMaps.push_back(std::move (paramMap)); + } } -void MixGroupsParamReceiver::oscMessageReceived (const OSCMessage& message) +void MixGroupsSharedData::pluginGroupChanged (const String& pluginID, int mixGroup) { - // clear existing OSC message - oscMessage.clear(); + // remove plugin from any group it's currently in + for (auto& group : pluginsInGroup) + group->removeString (pluginID); - if (message.isEmpty()) // no message! + if (mixGroup == 0) return; - if (! MixGroupsController::isValidOSCMessage (message)) - return; + // add plugin to new group + pluginsInGroup[mixGroup - 1]->addIfNotAlreadyThere (pluginID); +} + +int MixGroupsSharedData::getNumPluginsInGroup (int mixGroup) const +{ + return pluginsInGroup[mixGroup - 1]->size(); +} + +void MixGroupsSharedData::copyPluginState (int mixGroup, AudioProcessorValueTreeState& vts) +{ + auto paramMap = paramMaps[mixGroup - 1].get(); + auto mapIter = paramMap->begin(); + while (mapIter.next()) + { + const auto param = vts.getRawParameterValue (mapIter.getKey()); + paramMap->set (mapIter.getKey(), param->load()); + } +} + +void MixGroupsSharedData::setParameter (const String& paramID, int mixGroup, float value, String uuid) +{ + paramMaps[mixGroup - 1]->set (paramID, value); + listeners.call (&Listener::mixGroupParamChanged, paramID, mixGroup, value, uuid); +} - oscMessage = message; - sendChangeMessage(); +float MixGroupsSharedData::getParameter (const String& paramID, int mixGroup) const +{ + return paramMaps[mixGroup - 1]->operator[] (paramID); } diff --git a/Plugin/Source/MixGroups/MixGroupsParamReceiver.h b/Plugin/Source/MixGroups/MixGroupsParamReceiver.h @@ -3,27 +3,44 @@ #include <JuceHeader.h> -/** - * Class to receive parameter info from other instances of the plugin. - * Create instances of this class with a SharedResourcePointer, otherwise - * the messages won't sync correctly between instances of the plugin. - */ -class MixGroupsParamReceiver : public ChangeBroadcaster, - private OSCReceiver, - private OSCReceiver::ListenerWithOSCAddress<OSCReceiver::MessageLoopCallback> +class MixGroupsSharedData { public: - MixGroupsParamReceiver(); + MixGroupsSharedData(); - void oscMessageReceived (const OSCMessage& message) override; - void loadReceiverListeners (Array<String>& paramList); - const OSCMessage& getOSCMessage() { return oscMessage; } + /** Create parameter maps from list of parameters */ + void loadParameterList (Array<String>& paramList); + + /** A plugin has changed mix group. */ + void pluginGroupChanged (const String& pluginID, int mixGroup); + + /** Get the number of plugins already in this mix group */ + int getNumPluginsInGroup (int mixGroup) const; + + /** Copy the plugin state into the parameter map for a given mix group */ + void copyPluginState (int mixGroup, AudioProcessorValueTreeState& vts); + + void setParameter (const String& paramID, int mixGroup, float value, String uuid); + float getParameter (const String& paramID, int mixGroup) const; + + struct Listener + { + ~Listener() {} + virtual void mixGroupParamChanged (const String& /*paramID*/, int /*mixGroup*/, float /*value*/, String /*uuid*/) {} + }; + + void addListener (Listener* l) { listeners.add (l); } + void removeListener (Listener* l) { listeners.remove (l); } private: - DatagramSocket socket; - OSCMessage oscMessage { "/noaddress" }; - bool listenersInitialized = false; + using ParamMap = HashMap<String, float>; + std::vector<std::unique_ptr<ParamMap>> paramMaps; + + OwnedArray<StringArray> pluginsInGroup; // list of plugin IDs of all the plugins in each mix group + + ListenerList<Listener> listeners; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MixGroupsSharedData) }; #endif // MIXGROUPSPARAMRECEIVER -