From 611971181fcaca431f1aff29c87d74db314f62d7 Mon Sep 17 00:00:00 2001 From: Tom Poole Date: Fri, 23 Feb 2018 09:27:35 +0000 Subject: [PATCH] Added host-side AudioProcessorParameter implementations, deprecated the old methods for managing parameters, and updated the GenericAudioProcessorEditor --- BREAKING-CHANGES.txt | 42 +- examples/AUv3Synth/Source/AUv3SynthEditor.h | 6 +- .../AU/juce_AU_Wrapper.mm | 2 +- .../AU/juce_AUv3_Wrapper.mm | 2 +- .../juce_AudioUnitPluginFormat.mm | 430 ++++++++---- .../format_types/juce_LADSPAPluginFormat.cpp | 346 ++++----- .../format_types/juce_VST3PluginFormat.cpp | 235 ++++--- .../format_types/juce_VSTPluginFormat.cpp | 660 ++++++++++++++++-- .../juce_audio_processors.cpp | 1 + .../processors/juce_AudioPluginInstance.cpp | 230 ++++++ .../processors/juce_AudioPluginInstance.h | 45 +- .../processors/juce_AudioProcessor.cpp | 33 +- .../processors/juce_AudioProcessor.h | 2 + .../processors/juce_AudioProcessorParameter.h | 23 + .../juce_GenericAudioProcessorEditor.cpp | 544 ++++++++++++++- .../juce_GenericAudioProcessorEditor.h | 4 +- 16 files changed, 2113 insertions(+), 492 deletions(-) create mode 100644 modules/juce_audio_processors/processors/juce_AudioPluginInstance.cpp diff --git a/BREAKING-CHANGES.txt b/BREAKING-CHANGES.txt index 60aa09869e..9cf202b331 100644 --- a/BREAKING-CHANGES.txt +++ b/BREAKING-CHANGES.txt @@ -6,36 +6,60 @@ Develop Change ------ -InAppPurchases class is now a JUCE Singleton. This means that you need -to get an instance via InAppPurchases::getInstance(), instead of storing a -InAppPurchases object yourself. +When hosting plug-ins all AudioProcessor methods of managing parameters that +take a parameter index as an argument have been deprecated. +Possible Issues +--------------- +A single assertion will be fired in debug builds on the first use of a +deprecated function. + +Workaround +---------- +When hosting plug-ins you should use the AudioProcessor::getParameters() method +and interact with parameters via the returned array of +AudioProcessorParameters. For a short-term fix you can also continue past the +assertion in your debugger, or temporarily modify the JUCE source code to +remove it. + +Rationale +--------- +Given the structure of JUCE's API it is impossible to deprecate these functions +using only complile-time messages. Therefore a single assertion, which can be +safely ignored, serves to indicate that these functions should no longer be +used. The move away from the AudioProcessor methods both improves the interface +to that class and makes ongoing development work much easier. + + +Change +------ +This InAppPurchases class is now a JUCE Singleton. This means that you need +to get an instance via InAppPurchases::getInstance(), instead of storing a +InAppPurchases object yourself. Possible Issues --------------- Any code using InAppPurchases needs to be updated to retrieve a singleton pointer to InAppPurchases. - Workaround ---------- -Instead of holding a InAppPurchase member yourself, you should get an instance +Instead of holding a InAppPurchase member yourself, you should get an instance via InAppPurchases::getInstance(), e.g. instead of: InAppPurchases iap; -iap.purchaseProduct (…); +iap.purchaseProduct (...); call: -InAppPurchases::getInstance()->purchaseProduct (…); - +InAppPurchases::getInstance()->purchaseProduct (...); Rationale --------- This change was required to fix an issue on Android where on failed transaction -a listener would not get called. +a listener would not get called. Change diff --git a/examples/AUv3Synth/Source/AUv3SynthEditor.h b/examples/AUv3Synth/Source/AUv3SynthEditor.h index 7ecde7cce2..7f37fc728a 100644 --- a/examples/AUv3Synth/Source/AUv3SynthEditor.h +++ b/examples/AUv3Synth/Source/AUv3SynthEditor.h @@ -35,9 +35,9 @@ class AUv3SynthEditor : public AudioProcessorEditor, public: //============================================================================== AUv3SynthEditor (AudioProcessor& processor) - : AudioProcessorEditor (processor), - recordButton ("Record"), - roomSizeSlider (Slider::LinearHorizontal, Slider::NoTextBox) + : AudioProcessorEditor (processor), + recordButton ("Record"), + roomSizeSlider (Slider::LinearHorizontal, Slider::NoTextBox) { LookAndFeel::setDefaultLookAndFeel (&materialLookAndFeel); diff --git a/modules/juce_audio_plugin_client/AU/juce_AU_Wrapper.mm b/modules/juce_audio_plugin_client/AU/juce_AU_Wrapper.mm index 75c875f0f1..1d0e9d0f4a 100644 --- a/modules/juce_audio_plugin_client/AU/juce_AU_Wrapper.mm +++ b/modules/juce_audio_plugin_client/AU/juce_AU_Wrapper.mm @@ -1835,7 +1835,7 @@ private: // using the default number of steps. for (auto* param : juceFilter->getParameters()) if (param->isDiscrete()) - jassert (param->getNumSteps() != juceFilter->getDefaultNumParameterSteps()); + jassert (param->getNumSteps() != AudioProcessor::getDefaultNumParameterSteps()); #endif parameterValueStringArrays.ensureStorageAllocated (numParams); diff --git a/modules/juce_audio_plugin_client/AU/juce_AUv3_Wrapper.mm b/modules/juce_audio_plugin_client/AU/juce_AUv3_Wrapper.mm index a09342e394..69e299ecd5 100644 --- a/modules/juce_audio_plugin_client/AU/juce_AUv3_Wrapper.mm +++ b/modules/juce_audio_plugin_client/AU/juce_AUv3_Wrapper.mm @@ -1483,7 +1483,7 @@ private: return 0; } - void valueChangedForObserver(AUParameterAddress, AUValue) + void valueChangedForObserver (AUParameterAddress, AUValue) { // this will have already been handled by valueChangedFromHost } diff --git a/modules/juce_audio_processors/format_types/juce_AudioUnitPluginFormat.mm b/modules/juce_audio_processors/format_types/juce_AudioUnitPluginFormat.mm index 99fd26fefc..938a7d0fd1 100644 --- a/modules/juce_audio_processors/format_types/juce_AudioUnitPluginFormat.mm +++ b/modules/juce_audio_processors/format_types/juce_AudioUnitPluginFormat.mm @@ -302,6 +302,229 @@ class AudioUnitPluginWindowCocoa; class AudioUnitPluginInstance : public AudioPluginInstance { public: + struct AUInstanceParameter final : public Parameter + { + AUInstanceParameter (AudioUnitPluginInstance& parent, + UInt32 parameterID, + const String& parameterName, + AudioUnitParameterValue minParameterValue, + AudioUnitParameterValue maxParameterValue, + AudioUnitParameterValue defaultParameterValue, + bool parameterIsAutomatable, + bool parameterIsDiscrete, + int numParameterSteps, + bool isBoolean, + const String& label, + bool parameterValuesHaveStrings) + : pluginInstance (parent), + paramID (parameterID), + name (parameterName), + minValue (minParameterValue), + maxValue (maxParameterValue), + range (maxValue - minValue), + automatable (parameterIsAutomatable), + discrete (parameterIsDiscrete), + numSteps (numParameterSteps), + valuesHaveStrings (parameterValuesHaveStrings), + isSwitch (isBoolean), + valueLabel (label), + defaultValue (normaliseParamValue (defaultParameterValue)) + { + auValueStrings = Parameter::getAllValueStrings(); + } + + virtual float getValue() const override + { + const ScopedLock sl (pluginInstance.lock); + + AudioUnitParameterValue value = 0; + + if (auto* au = pluginInstance.audioUnit) + { + AudioUnitGetParameter (au, + paramID, + kAudioUnitScope_Global, + 0, + &value); + + value = normaliseParamValue (value); + } + + return value; + } + + virtual void setValue (float newValue) override + { + const ScopedLock sl (pluginInstance.lock); + + if (auto* au = pluginInstance.audioUnit) + { + AudioUnitSetParameter (au, + paramID, + kAudioUnitScope_Global, + 0, + scaleParamValue (newValue), + 0); + + sendParameterChangeEvent(); + } + } + + float getDefaultValue() const override + { + return defaultValue; + } + + String getName (int /*maximumStringLength*/) const override + { + return name; + } + + String getLabel() const override + { + return valueLabel; + } + + String getText (float value, int maximumLength) const override + { + if (! auValueStrings.isEmpty()) + { + auto index = roundToInt (jlimit (0.0f, 1.0f, value) * (auValueStrings.size() - 1)); + return auValueStrings[index]; + } + + auto scaledValue = scaleParamValue (value); + + if (valuesHaveStrings) + { + if (auto* au = pluginInstance.audioUnit) + { + AudioUnitParameterStringFromValue stringValue; + stringValue.inParamID = paramID; + stringValue.inValue = &scaledValue; + stringValue.outString = nullptr; + + UInt32 propertySize = sizeof (stringValue); + + OSStatus err = AudioUnitGetProperty (au, + kAudioUnitProperty_ParameterStringFromValue, + kAudioUnitScope_Global, + 0, + &stringValue, + &propertySize); + + if (! err && stringValue.outString != nullptr) + return String::fromCFString (stringValue.outString).substring (0, maximumLength); + } + } + + return Parameter::getText (scaledValue, maximumLength); + } + + float getValueForText (const String& text) const override + { + if (! auValueStrings.isEmpty()) + { + auto index = auValueStrings.indexOf (text); + + if (index != -1) + return ((float) index) / (auValueStrings.size() - 1); + } + + if (valuesHaveStrings) + { + if (auto* au = pluginInstance.audioUnit) + { + AudioUnitParameterValueFromString valueString; + valueString.inParamID = paramID; + valueString.inString = text.toCFString(); + + UInt32 propertySize = sizeof (valueString); + + OSStatus err = AudioUnitGetProperty (au, + kAudioUnitProperty_ParameterValueFromString, + kAudioUnitScope_Global, + 0, + &valueString, + &propertySize); + + if (! err) + return normaliseParamValue (valueString.outValue); + } + } + + return Parameter::getValueForText (text); + } + + bool isAutomatable() const override + { + return automatable; + } + + bool isDiscrete() const override + { + return discrete; + } + + bool isBoolean() const override + { + return isSwitch; + } + + int getNumSteps() const override + { + return numSteps; + } + + StringArray getAllValueStrings() const override + { + return auValueStrings; + } + + void sendParameterChangeEvent() + { + #if JUCE_MAC + jassert (pluginInstance.audioUnit != nullptr); + + AudioUnitEvent ev; + ev.mEventType = kAudioUnitEvent_ParameterValueChange; + ev.mArgument.mParameter.mAudioUnit = pluginInstance.audioUnit; + ev.mArgument.mParameter.mParameterID = paramID; + ev.mArgument.mParameter.mScope = kAudioUnitScope_Global; + ev.mArgument.mParameter.mElement = 0; + + AUEventListenerNotify (pluginInstance.eventListenerRef, nullptr, &ev); + #endif + } + + float normaliseParamValue (float scaledValue) const noexcept + { + if (discrete) + return scaledValue / (getNumSteps() - 1); + + return (scaledValue - minValue) / range; + } + + float scaleParamValue (float normalisedValue) const noexcept + { + if (discrete) + return normalisedValue * (getNumSteps() - 1); + + return minValue + (range * normalisedValue); + } + + AudioUnitPluginInstance& pluginInstance; + const UInt32 paramID; + const String name; + const AudioUnitParameterValue minValue, maxValue, range; + const bool automatable, discrete; + const int numSteps; + const bool valuesHaveStrings, isSwitch; + const String valueLabel; + const AudioUnitParameterValue defaultValue; + StringArray auValueStrings; + }; + AudioUnitPluginInstance (AudioComponentInstance au) : AudioPluginInstance (getBusesProperties (au)), auComponent (AudioComponentInstanceGetComponent (au)), @@ -937,66 +1160,6 @@ public: bool isOutputChannelStereoPair (int index) const override { return isPositiveAndBelow (index, getTotalNumOutputChannels()); } //============================================================================== - int getNumParameters() override { return parameters.size(); } - - float getParameter (int index) override - { - const ScopedLock sl (lock); - - AudioUnitParameterValue value = 0; - - if (audioUnit != nullptr) - { - if (const ParamInfo* p = parameters[index]) - { - AudioUnitGetParameter (audioUnit, - p->paramID, - kAudioUnitScope_Global, 0, - &value); - - value = (value - p->minValue) / (p->maxValue - p->minValue); - } - } - - return value; - } - - void setParameter (int index, float newValue) override - { - const ScopedLock sl (lock); - - if (audioUnit != nullptr) - { - if (const ParamInfo* p = parameters[index]) - { - AudioUnitSetParameter (audioUnit, p->paramID, kAudioUnitScope_Global, 0, - p->minValue + (p->maxValue - p->minValue) * newValue, 0); - - sendParameterChangeEvent (index); - } - } - } - - void sendParameterChangeEvent (int index) - { - #if JUCE_MAC - jassert (audioUnit != nullptr); - - const ParamInfo& p = *parameters.getUnchecked (index); - - AudioUnitEvent ev; - ev.mEventType = kAudioUnitEvent_ParameterValueChange; - ev.mArgument.mParameter.mAudioUnit = audioUnit; - ev.mArgument.mParameter.mParameterID = p.paramID; - ev.mArgument.mParameter.mScope = kAudioUnitScope_Global; - ev.mArgument.mParameter.mElement = 0; - - AUEventListenerNotify (eventListenerRef, nullptr, &ev); - #else - ignoreUnused (index); - #endif - } - void sendAllParametersChangedEvents() { #if JUCE_MAC @@ -1010,40 +1173,6 @@ public: #endif } - const String getParameterName (int index) override - { - if (auto* p = parameters[index]) - return p->name; - - return {}; - } - - const String getParameterText (int index) override { return String (getParameter (index)); } - - int getParameterNumSteps (int index) override - { - if (auto* p = parameters[index]) - return p->numSteps; - - return AudioProcessor::getDefaultNumParameterSteps(); - } - - bool isParameterDiscrete (int index) const override - { - if (auto* p = parameters[index]) - return p->discrete; - - return false; - } - - bool isParameterAutomatable (int index) const override - { - if (auto* p = parameters[index]) - return p->automatable; - - return false; - } - //============================================================================== int getNumPrograms() override { @@ -1192,7 +1321,7 @@ public: void refreshParameterList() override { - parameters.clear(); + managedParameters.clear(); paramIDToIndex.clear(); if (audioUnit != nullptr) @@ -1221,27 +1350,60 @@ public: kAudioUnitScope_Global, ids[i], &info, &sz) == noErr) { - ParamInfo* const param = new ParamInfo(); - parameters.add (param); - param->paramID = ids[i]; paramIDToIndex.getReference (ids[i]) = i; - param->minValue = info.minValue; - param->maxValue = info.maxValue; - param->automatable = (info.flags & kAudioUnitParameterFlag_NonRealTime) == 0; - param->discrete = (info.unit == kAudioUnitParameterUnit_Indexed); - param->numSteps = param->discrete ? (int) (info.maxValue + 1.0f) : AudioProcessor::getDefaultNumParameterSteps(); + String paramName; if ((info.flags & kAudioUnitParameterFlag_HasCFNameString) != 0) { - param->name = String::fromCFString (info.cfNameString); + paramName = String::fromCFString (info.cfNameString); if ((info.flags & kAudioUnitParameterFlag_CFNameRelease) != 0) CFRelease (info.cfNameString); } else { - param->name = String (info.name, sizeof (info.name)); + paramName = String (info.name, sizeof (info.name)); } + + bool isDiscrete = (info.unit == kAudioUnitParameterUnit_Indexed + || info.unit == kAudioUnitParameterUnit_Boolean); + bool isBoolean = info.unit == kAudioUnitParameterUnit_Boolean; + + String label; + + switch (info.unit) + { + case kAudioUnitParameterUnit_Percent: + label = "%"; + break; + case kAudioUnitParameterUnit_Seconds: + label = "s"; + break; + case kAudioUnitParameterUnit_Hertz: + label = "Hz"; + break; + case kAudioUnitParameterUnit_Decibels: + label = "dB"; + break; + case kAudioUnitParameterUnit_Milliseconds: + label = "ms"; + break; + default: + break; + } + + addParameter (new AUInstanceParameter (*this, + ids[i], + paramName, + info.minValue, + info.maxValue, + info.defaultValue, + (info.flags & kAudioUnitParameterFlag_NonRealTime) == 0, + isDiscrete, + isDiscrete ? (int) (info.maxValue + 1.0f) : AudioProcessor::getDefaultNumParameterSteps(), + isBoolean, + label, + (info.flags & kAudioUnitParameterFlag_ValuesHaveStrings) != 0)); } } } @@ -1310,16 +1472,6 @@ private: AUEventListenerRef eventListenerRef; #endif - struct ParamInfo - { - UInt32 paramID; - String name; - AudioUnitParameterValue minValue, maxValue; - bool automatable, discrete; - int numSteps; - }; - - OwnedArray parameters; HashMap paramIDToIndex; MidiDataConcatenator midiConcatenator; @@ -1360,22 +1512,25 @@ private: AUEventListenerCreate (eventListenerCallback, this, CFRunLoopGetMain(), kCFRunLoopDefaultMode, 0, 0, &eventListenerRef); - for (int i = 0; i < parameters.size(); ++i) + for (auto* param : getParameters()) { - AudioUnitEvent event; - event.mArgument.mParameter.mAudioUnit = audioUnit; - event.mArgument.mParameter.mParameterID = parameters.getUnchecked(i)->paramID; - event.mArgument.mParameter.mScope = kAudioUnitScope_Global; - event.mArgument.mParameter.mElement = 0; + if (auto* auParam = dynamic_cast (param)) + { + AudioUnitEvent event; + event.mArgument.mParameter.mAudioUnit = audioUnit; + event.mArgument.mParameter.mParameterID = auParam->paramID; + event.mArgument.mParameter.mScope = kAudioUnitScope_Global; + event.mArgument.mParameter.mElement = 0; - event.mEventType = kAudioUnitEvent_ParameterValueChange; - AUEventListenerAddEventType (eventListenerRef, nullptr, &event); + event.mEventType = kAudioUnitEvent_ParameterValueChange; + AUEventListenerAddEventType (eventListenerRef, nullptr, &event); - event.mEventType = kAudioUnitEvent_BeginParameterChangeGesture; - AUEventListenerAddEventType (eventListenerRef, nullptr, &event); + event.mEventType = kAudioUnitEvent_BeginParameterChangeGesture; + AUEventListenerAddEventType (eventListenerRef, nullptr, &event); - event.mEventType = kAudioUnitEvent_EndParameterChangeGesture; - AUEventListenerAddEventType (eventListenerRef, nullptr, &event); + event.mEventType = kAudioUnitEvent_EndParameterChangeGesture; + AUEventListenerAddEventType (eventListenerRef, nullptr, &event); + } } addPropertyChangeListener (kAudioUnitProperty_PresentPreset); @@ -1413,25 +1568,36 @@ private: paramIndex = static_cast (paramIDToIndex [paramID]); - if (! isPositiveAndBelow (paramIndex, parameters.size())) + if (! isPositiveAndBelow (paramIndex, getParameters().size())) return; } switch (event.mEventType) { case kAudioUnitEvent_ParameterValueChange: + if (auto* param = getParameters().getUnchecked (paramIndex)) { - auto& p = *parameters.getUnchecked (paramIndex); - sendParamChangeMessageToListeners (paramIndex, (newValue - p.minValue) / (p.maxValue - p.minValue)); + jassert (dynamic_cast (param) != nullptr); + auto* auparam = static_cast (param); + param->sendValueChangedMessageToListeners (auparam->normaliseParamValue (newValue)); } + break; case kAudioUnitEvent_BeginParameterChangeGesture: - beginParameterChangeGesture (paramIndex); + if (auto* param = getParameters()[paramIndex]) + param->beginChangeGesture(); + else + jassertfalse; // Invalid parameter index + break; case kAudioUnitEvent_EndParameterChangeGesture: - endParameterChangeGesture (paramIndex); + if (auto* param = getParameters()[paramIndex]) + param->endChangeGesture(); + else + jassertfalse; // Invalid parameter index + break; default: diff --git a/modules/juce_audio_processors/format_types/juce_LADSPAPluginFormat.cpp b/modules/juce_audio_processors/format_types/juce_LADSPAPluginFormat.cpp index 46e2ad115f..bdb755488c 100644 --- a/modules/juce_audio_processors/format_types/juce_LADSPAPluginFormat.cpp +++ b/modules/juce_audio_processors/format_types/juce_LADSPAPluginFormat.cpp @@ -112,11 +112,173 @@ private: JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (LADSPAModuleHandle) }; - //============================================================================== class LADSPAPluginInstance : public AudioPluginInstance { public: + struct LADSPAParameter final : public Parameter + { + struct ParameterValue + { + inline ParameterValue() noexcept : scaled (0), unscaled (0) {} + inline ParameterValue (float s, float u) noexcept : scaled (s), unscaled (u) {} + + float scaled, unscaled; + }; + + LADSPAParameter (LADSPAPluginInstance& parent, + int parameterID, + const String& parameterName, + bool parameterIsAutomatable) + : pluginInstance (parent), + paramID (parameterID), + name (parameterName), + automatable (parameterIsAutomatable) + { + reset(); + } + + virtual float getValue() const override + { + if (pluginInstance.plugin != nullptr) + { + const ScopedLock sl (pluginInstance.lock); + + return paramValue.unscaled; + } + + return 0.0f; + } + + String getCurrentValueAsText() const override + { + if (auto* interface = pluginInstance.plugin) + { + const LADSPA_PortRangeHint& hint = interface->PortRangeHints[paramID]; + + if (LADSPA_IS_HINT_INTEGER (hint.HintDescriptor)) + return String ((int) paramValue.scaled); + + return String (paramValue.scaled, 4); + } + + return {}; + } + + virtual void setValue (float newValue) override + { + if (auto* interface = pluginInstance.plugin) + { + const ScopedLock sl (pluginInstance.lock); + + if (paramValue.unscaled != newValue) + paramValue = ParameterValue (getNewParamScaled (interface->PortRangeHints [paramID], newValue), newValue); + } + } + + float getDefaultValue() const override + { + return defaultValue; + } + + ParameterValue getDefaultParamValue() const + { + if (auto* interface = pluginInstance.plugin) + { + const LADSPA_PortRangeHint& hint = interface->PortRangeHints[paramID]; + const LADSPA_PortRangeHintDescriptor& desc = hint.HintDescriptor; + + if (LADSPA_IS_HINT_HAS_DEFAULT (desc)) + { + if (LADSPA_IS_HINT_DEFAULT_0 (desc)) return ParameterValue(); + if (LADSPA_IS_HINT_DEFAULT_1 (desc)) return ParameterValue (1.0f, 1.0f); + if (LADSPA_IS_HINT_DEFAULT_100 (desc)) return ParameterValue (100.0f, 0.5f); + if (LADSPA_IS_HINT_DEFAULT_440 (desc)) return ParameterValue (440.0f, 0.5f); + + const float scale = LADSPA_IS_HINT_SAMPLE_RATE (desc) ? (float) pluginInstance.getSampleRate() : 1.0f; + const float lower = hint.LowerBound * scale; + const float upper = hint.UpperBound * scale; + + if (LADSPA_IS_HINT_BOUNDED_BELOW (desc) && LADSPA_IS_HINT_DEFAULT_MINIMUM (desc)) return ParameterValue (lower, 0.0f); + if (LADSPA_IS_HINT_BOUNDED_ABOVE (desc) && LADSPA_IS_HINT_DEFAULT_MAXIMUM (desc)) return ParameterValue (upper, 1.0f); + + if (LADSPA_IS_HINT_BOUNDED_BELOW (desc)) + { + const bool useLog = LADSPA_IS_HINT_LOGARITHMIC (desc); + + if (LADSPA_IS_HINT_DEFAULT_LOW (desc)) return ParameterValue (scaledValue (lower, upper, 0.25f, useLog), 0.25f); + if (LADSPA_IS_HINT_DEFAULT_MIDDLE (desc)) return ParameterValue (scaledValue (lower, upper, 0.50f, useLog), 0.50f); + if (LADSPA_IS_HINT_DEFAULT_HIGH (desc)) return ParameterValue (scaledValue (lower, upper, 0.75f, useLog), 0.75f); + } + } + } + + return ParameterValue(); + } + + void reset() + { + paramValue = getDefaultParamValue(); + defaultValue = paramValue.unscaled; + } + + String getName (int /*maximumStringLength*/) const override + { + return name; + } + + String getLabel() const override + { + return {}; + } + + bool isAutomatable() const override + { + return automatable; + } + + static float scaledValue (float low, float high, float alpha, bool useLog) noexcept + { + if (useLog && low > 0 && high > 0) + return expf (logf (low) * (1.0f - alpha) + logf (high) * alpha); + + return low + (high - low) * alpha; + } + + static float toIntIfNecessary (const LADSPA_PortRangeHintDescriptor& desc, float value) + { + return LADSPA_IS_HINT_INTEGER (desc) ? ((float) (int) value) : value; + } + + float getNewParamScaled (const LADSPA_PortRangeHint& hint, float newValue) const + { + const LADSPA_PortRangeHintDescriptor& desc = hint.HintDescriptor; + + if (LADSPA_IS_HINT_TOGGLED (desc)) + return (newValue < 0.5f) ? 0.0f : 1.0f; + + const float scale = LADSPA_IS_HINT_SAMPLE_RATE (desc) ? (float) pluginInstance.getSampleRate() : 1.0f; + const float lower = hint.LowerBound * scale; + const float upper = hint.UpperBound * scale; + + if (LADSPA_IS_HINT_BOUNDED_BELOW (desc) && LADSPA_IS_HINT_BOUNDED_ABOVE (desc)) + return toIntIfNecessary (desc, scaledValue (lower, upper, newValue, LADSPA_IS_HINT_LOGARITHMIC (desc))); + + if (LADSPA_IS_HINT_BOUNDED_BELOW (desc)) return toIntIfNecessary (desc, newValue); + if (LADSPA_IS_HINT_BOUNDED_ABOVE (desc)) return toIntIfNecessary (desc, newValue * upper); + + return 0.0f; + } + + LADSPAPluginInstance& pluginInstance; + const int paramID; + const String name; + const bool automatable; + + ParameterValue paramValue; + float defaultValue = 0; + }; + LADSPAPluginInstance (const LADSPAModuleHandle::Ptr& m) : module (m), plugin (nullptr), handle (nullptr), initialised (false), tempBuffer (1, 1) @@ -180,14 +342,17 @@ public: inputs.clear(); outputs.clear(); - parameters.clear(); + managedParameters.clear(); for (unsigned int i = 0; i < plugin->PortCount; ++i) { const LADSPA_PortDescriptor portDesc = plugin->PortDescriptors[i]; if ((portDesc & LADSPA_PORT_CONTROL) != 0) - parameters.add (i); + addParameter (new LADSPAParameter (*this, + i, + String (plugin->PortNames[i]).trim(), + (portDesc & LADSPA_PORT_INPUT) != 0)); if ((portDesc & LADSPA_PORT_AUDIO) != 0) { @@ -196,10 +361,9 @@ public: } } - parameterValues.calloc (parameters.size()); - - for (int i = 0; i < parameters.size(); ++i) - plugin->connect_port (handle, parameters[i], &(parameterValues[i].scaled)); + for (auto* param : getParameters()) + if (auto* ladspaParam = dynamic_cast (param)) + plugin->connect_port (handle, ladspaParam->paramID, &(ladspaParam->paramValue.scaled)); setPlayConfigDetails (inputs.size(), outputs.size(), initialSampleRate, initialBlockSize); @@ -266,11 +430,11 @@ public: tempBuffer.setSize (jmax (1, outputs.size()), samplesPerBlockExpected); // dodgy hack to force some plugins to initialise the sample rate.. - if (getNumParameters() > 0) + if (auto* firstParam = getParameters()[0]) { - const float old = getParameter (0); - setParameter (0, (old < 0.5f) ? 1.0f : 0.0f); - setParameter (0, old); + const float old = firstParam->getValue(); + firstParam->setValue ((old < 0.5f) ? 1.0f : 0.0f); + firstParam->setValue (old); } if (plugin->activate != nullptr) @@ -349,76 +513,15 @@ public: return {}; } - //============================================================================== - int getNumParameters() { return handle != nullptr ? parameters.size() : 0; } - - bool isParameterAutomatable (int index) const - { - return plugin != nullptr - && (plugin->PortDescriptors [parameters[index]] & LADSPA_PORT_INPUT) != 0; - } - - float getParameter (int index) - { - if (plugin != nullptr && isPositiveAndBelow (index, parameters.size())) - { - const ScopedLock sl (lock); - return parameterValues[index].unscaled; - } - - return 0.0f; - } - - void setParameter (int index, float newValue) - { - if (plugin != nullptr && isPositiveAndBelow (index, parameters.size())) - { - const ScopedLock sl (lock); - - ParameterValue& p = parameterValues[index]; - - if (p.unscaled != newValue) - p = ParameterValue (getNewParamScaled (plugin->PortRangeHints [parameters[index]], newValue), newValue); - } - } - - const String getParameterName (int index) - { - if (plugin != nullptr) - { - jassert (isPositiveAndBelow (index, parameters.size())); - return String (plugin->PortNames [parameters [index]]).trim(); - } - - return {}; - } - - const String getParameterText (int index) - { - if (plugin != nullptr) - { - jassert (index >= 0 && index < parameters.size()); - - const LADSPA_PortRangeHint& hint = plugin->PortRangeHints [parameters [index]]; - - if (LADSPA_IS_HINT_INTEGER (hint.HintDescriptor)) - return String ((int) parameterValues[index].scaled); - - return String (parameterValues[index].scaled, 4); - } - - return {}; - } - //============================================================================== int getNumPrograms() { return 0; } int getCurrentProgram() { return 0; } - void setCurrentProgram (int newIndex) + void setCurrentProgram (int) { - if (plugin != nullptr) - for (int i = 0; i < parameters.size(); ++i) - parameterValues[i] = getParamValue (plugin->PortRangeHints [parameters[i]]); + for (auto* param : getParameters()) + if (auto* ladspaParam = dynamic_cast (param)) + ladspaParam->reset(); } const String getProgramName (int index) @@ -435,12 +538,15 @@ public: //============================================================================== void getStateInformation (MemoryBlock& destData) { - destData.setSize (sizeof (float) * getNumParameters()); + auto numParameters = getParameters().size(); + destData.setSize (sizeof (float) * numParameters); destData.fillWith (0); float* const p = (float*) ((char*) destData.getData()); - for (int i = 0; i < getNumParameters(); ++i) - p[i] = getParameter(i); + + for (int i = 0; i < numParameters; ++i) + if (auto* param = getParameters()[i]) + p[i] = param->getValue(); } void getCurrentProgramStateInformation (MemoryBlock& destData) @@ -452,8 +558,9 @@ public: { const float* p = static_cast (data); - for (int i = 0; i < getNumParameters(); ++i) - setParameter (i, p[i]); + for (int i = 0; i < getParameters().size(); ++i) + if (auto* param = getParameters()[i]) + param->setValue (p[i]); } void setCurrentProgramStateInformation (const void* data, int sizeInBytes) @@ -485,82 +592,7 @@ private: CriticalSection lock; bool initialised; AudioBuffer tempBuffer; - Array inputs, outputs, parameters; - - struct ParameterValue - { - inline ParameterValue() noexcept : scaled (0), unscaled (0) {} - inline ParameterValue (float s, float u) noexcept : scaled (s), unscaled (u) {} - - float scaled, unscaled; - }; - - HeapBlock parameterValues; - - //============================================================================== - static float scaledValue (float low, float high, float alpha, bool useLog) noexcept - { - if (useLog && low > 0 && high > 0) - return expf (logf (low) * (1.0f - alpha) + logf (high) * alpha); - - return low + (high - low) * alpha; - } - - static float toIntIfNecessary (const LADSPA_PortRangeHintDescriptor& desc, float value) - { - return LADSPA_IS_HINT_INTEGER (desc) ? ((float) (int) value) : value; - } - - float getNewParamScaled (const LADSPA_PortRangeHint& hint, float newValue) const - { - const LADSPA_PortRangeHintDescriptor& desc = hint.HintDescriptor; - - if (LADSPA_IS_HINT_TOGGLED (desc)) - return (newValue < 0.5f) ? 0.0f : 1.0f; - - const float scale = LADSPA_IS_HINT_SAMPLE_RATE (desc) ? (float) getSampleRate() : 1.0f; - const float lower = hint.LowerBound * scale; - const float upper = hint.UpperBound * scale; - - if (LADSPA_IS_HINT_BOUNDED_BELOW (desc) && LADSPA_IS_HINT_BOUNDED_ABOVE (desc)) - return toIntIfNecessary (desc, scaledValue (lower, upper, newValue, LADSPA_IS_HINT_LOGARITHMIC (desc))); - - if (LADSPA_IS_HINT_BOUNDED_BELOW (desc)) return toIntIfNecessary (desc, newValue); - if (LADSPA_IS_HINT_BOUNDED_ABOVE (desc)) return toIntIfNecessary (desc, newValue * upper); - - return 0.0f; - } - - ParameterValue getParamValue (const LADSPA_PortRangeHint& hint) const - { - const LADSPA_PortRangeHintDescriptor& desc = hint.HintDescriptor; - - if (LADSPA_IS_HINT_HAS_DEFAULT (desc)) - { - if (LADSPA_IS_HINT_DEFAULT_0 (desc)) return ParameterValue(); - if (LADSPA_IS_HINT_DEFAULT_1 (desc)) return ParameterValue (1.0f, 1.0f); - if (LADSPA_IS_HINT_DEFAULT_100 (desc)) return ParameterValue (100.0f, 0.5f); - if (LADSPA_IS_HINT_DEFAULT_440 (desc)) return ParameterValue (440.0f, 0.5f); - - const float scale = LADSPA_IS_HINT_SAMPLE_RATE (desc) ? (float) getSampleRate() : 1.0f; - const float lower = hint.LowerBound * scale; - const float upper = hint.UpperBound * scale; - - if (LADSPA_IS_HINT_BOUNDED_BELOW (desc) && LADSPA_IS_HINT_DEFAULT_MINIMUM (desc)) return ParameterValue (lower, 0.0f); - if (LADSPA_IS_HINT_BOUNDED_ABOVE (desc) && LADSPA_IS_HINT_DEFAULT_MAXIMUM (desc)) return ParameterValue (upper, 1.0f); - - if (LADSPA_IS_HINT_BOUNDED_BELOW (desc)) - { - const bool useLog = LADSPA_IS_HINT_LOGARITHMIC (desc); - - if (LADSPA_IS_HINT_DEFAULT_LOW (desc)) return ParameterValue (scaledValue (lower, upper, 0.25f, useLog), 0.25f); - if (LADSPA_IS_HINT_DEFAULT_MIDDLE (desc)) return ParameterValue (scaledValue (lower, upper, 0.50f, useLog), 0.50f); - if (LADSPA_IS_HINT_DEFAULT_HIGH (desc)) return ParameterValue (scaledValue (lower, upper, 0.75f, useLog), 0.75f); - } - } - - return ParameterValue(); - } + Array inputs, outputs; JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (LADSPAPluginInstance) }; diff --git a/modules/juce_audio_processors/format_types/juce_VST3PluginFormat.cpp b/modules/juce_audio_processors/format_types/juce_VST3PluginFormat.cpp index b8ea0ffa45..bf68a9e7cf 100644 --- a/modules/juce_audio_processors/format_types/juce_VST3PluginFormat.cpp +++ b/modules/juce_audio_processors/format_types/juce_VST3PluginFormat.cpp @@ -283,7 +283,10 @@ struct VST3HostContext : public Vst::IComponentHandler, // From VST V3.0.0 if (index < 0) return kResultFalse; - plugin->beginParameterChangeGesture (index); + if (auto* param = plugin->getParameters()[index]) + param->beginChangeGesture(); + else + jassertfalse; // Invalid parameter index! } return kResultTrue; @@ -298,7 +301,10 @@ struct VST3HostContext : public Vst::IComponentHandler, // From VST V3.0.0 if (index < 0) return kResultFalse; - plugin->sendParamChangeMessageToListeners (index, (float) valueNormalized); + if (auto* param = plugin->getParameters()[index]) + param->sendValueChangedMessageToListeners ((float) valueNormalized); + else + jassertfalse; // Invalid parameter index! { Steinberg::int32 eventIndex; @@ -322,7 +328,10 @@ struct VST3HostContext : public Vst::IComponentHandler, // From VST V3.0.0 if (index < 0) return kResultFalse; - plugin->endParameterChangeGesture (index); + if (auto* param = plugin->getParameters()[index]) + param->endChangeGesture(); + else + jassertfalse; // Invalid parameter index! } return kResultTrue; @@ -1685,6 +1694,118 @@ struct VST3ComponentHolder //============================================================================== struct VST3PluginInstance : public AudioPluginInstance { + struct VST3Parameter final : public Parameter + { + VST3Parameter (VST3PluginInstance& parent, + Steinberg::Vst::ParamID parameterID, + const String& parameterName, + const String& parameterLabel, + Steinberg::Vst::ParamValue defaultParameterValue, + bool parameterIsAutomatable, + bool parameterIsDiscrete, + int numParameterSteps) + : pluginInstance (parent), + paramID (parameterID), + name (parameterName), + label (parameterLabel), + defaultValue (defaultParameterValue), + automatable (parameterIsAutomatable), + discrete (parameterIsDiscrete), + numSteps (numParameterSteps) + { + } + + virtual float getValue() const override + { + if (pluginInstance.editController != nullptr) + { + return (float) pluginInstance.editController->getParamNormalized (paramID); + } + + return 0.0f; + } + + virtual void setValue (float newValue) override + { + if (pluginInstance.editController != nullptr) + { + pluginInstance.editController->setParamNormalized (paramID, (double) newValue); + + Steinberg::int32 index; + pluginInstance.inputParameterChanges->addParameterData (paramID, index) + ->addPoint (0, newValue, index); + } + } + + String getText (float value, int maximumLength) const override + { + if (pluginInstance.editController != nullptr) + { + Vst::String128 result; + + if (pluginInstance.editController->getParamStringByValue (paramID, value, result) == kResultOk) + return toString (result).substring (0, maximumLength); + } + + return Parameter::getText (value, maximumLength); + } + + float getValueForText (const String& text) const override + { + if (pluginInstance.editController != nullptr) + { + Vst::ParamValue result; + + if (pluginInstance.editController->getParamValueByString (paramID, toString (text), result) == kResultOk) + return (float) result; + } + + return Parameter::getValueForText (text); + } + + float getDefaultValue() const override + { + return (float) defaultValue; + } + + String getName (int /*maximumStringLength*/) const override + { + return name; + } + + String getLabel() const override + { + return label; + } + + bool isAutomatable() const override + { + return automatable; + } + + bool isDiscrete() const override + { + return discrete; + } + + int getNumSteps() const override + { + return numSteps; + } + + StringArray getAllValueStrings() const override + { + return {}; + } + + VST3PluginInstance& pluginInstance; + const Steinberg::Vst::ParamID paramID; + const String name, label; + const Steinberg::Vst::ParamValue defaultValue; + const bool automatable, discrete; + const int numSteps; + }; + VST3PluginInstance (VST3ComponentHolder* componentHolder) : AudioPluginInstance (getBusProperties (componentHolder->component)), holder (componentHolder), @@ -1752,9 +1873,30 @@ struct VST3PluginInstance : public AudioPluginInstance editController->setComponentHandler (holder->host); grabInformationObjects(); interconnectComponentAndController(); + + for (int i = 0; i < editController->getParameterCount(); ++i) + { + Vst::ParameterInfo paramInfo = { 0 }; + editController->getParameterInfo (i, paramInfo); + + bool isDiscrete = paramInfo.stepCount != 0; + int numSteps = isDiscrete ? paramInfo.stepCount + 1 + : AudioProcessor::getDefaultNumParameterSteps(); + + addParameter (new VST3Parameter (*this, + paramInfo.id, + toString (paramInfo.title), + toString (paramInfo.units), + paramInfo.defaultNormalizedValue, + (paramInfo.flags & Vst::ParameterInfo::kCanAutomate) != 0, + isDiscrete, + numSteps)); + } + synchroniseStates(); syncProgramNames(); setupIO(); + return true; } @@ -2158,93 +2300,6 @@ struct VST3PluginInstance : public AudioPluginInstance return view != nullptr; } - //============================================================================== - int getNumParameters() override - { - if (editController != nullptr) - return (int) editController->getParameterCount(); - - return 0; - } - - const String getParameterName (int parameterIndex) override - { - return toString (getParameterInfoForIndex (parameterIndex).title); - } - - const String getParameterText (int parameterIndex) override - { - if (editController != nullptr) - { - auto id = getParameterInfoForIndex (parameterIndex).id; - - Vst::String128 result; - warnOnFailure (editController->getParamStringByValue (id, editController->getParamNormalized (id), result)); - - return toString (result); - } - - return {}; - } - - int getParameterNumSteps (int parameterIndex) override - { - if (editController != nullptr) - { - const auto numSteps = getParameterInfoForIndex (parameterIndex).stepCount; - - if (numSteps > 0) - return numSteps; - } - - return AudioProcessor::getDefaultNumParameterSteps(); - } - - bool isParameterDiscrete (int parameterIndex) const override - { - if (editController != nullptr) - { - const auto numSteps = getParameterInfoForIndex (parameterIndex).stepCount; - return numSteps > 0; - } - - return false; - } - - bool isParameterAutomatable (int parameterIndex) const override - { - if (editController != nullptr) - { - auto flags = getParameterInfoForIndex (parameterIndex).flags; - return (flags & Steinberg::Vst::ParameterInfo::kCanAutomate) != 0; - } - - return true; - } - - float getParameter (int parameterIndex) override - { - if (editController != nullptr) - { - auto id = getParameterInfoForIndex (parameterIndex).id; - return (float) editController->getParamNormalized (id); - } - - return 0.0f; - } - - void setParameter (int parameterIndex, float newValue) override - { - if (editController != nullptr) - { - auto paramID = getParameterInfoForIndex (parameterIndex).id; - editController->setParamNormalized (paramID, (double) newValue); - - Steinberg::int32 index; - inputParameterChanges->addParameterData (paramID, index)->addPoint (0, newValue, index); - } - } - //============================================================================== int getNumPrograms() override { return programNames.size(); } const String getProgramName (int index) override { return programNames[index]; } diff --git a/modules/juce_audio_processors/format_types/juce_VSTPluginFormat.cpp b/modules/juce_audio_processors/format_types/juce_VSTPluginFormat.cpp index d171fba620..8b19413b82 100644 --- a/modules/juce_audio_processors/format_types/juce_VSTPluginFormat.cpp +++ b/modules/juce_audio_processors/format_types/juce_VSTPluginFormat.cpp @@ -248,6 +248,330 @@ namespace #endif +//============================================================================== +class VSTXMLInfo +{ +public: + static VSTXMLInfo* createFor (const juce::XmlElement& xml) + { + if (xml.hasTagName ("VSTParametersStructure")) + return new VSTXMLInfo (xml); + + if (const auto* x = xml.getChildByName ("VSTParametersStructure")) + return new VSTXMLInfo (*x); + + return nullptr; + } + + struct Group; + + struct Base + { + Base() noexcept {} + virtual ~Base() {} + + Group* parent = nullptr; + }; + + struct Param : public Base + { + int paramID; + juce::String expr, name, label; + juce::StringArray shortNames; + juce::String type; + int numberOfStates; + float defaultValue; + }; + + struct Group : public Base + { + juce::String name; + juce::OwnedArray paramTree; + }; + + struct Range + { + Range() noexcept {} + Range (const juce::String& s) { set (s); } + + void set (const juce::String& s) + { + inclusiveLow = s.startsWithChar ('['); + inclusiveHigh = s.endsWithChar (']'); + + auto str = s.removeCharacters ("[]"); + + low = str.upToFirstOccurrenceOf (",", false, false).getFloatValue(); + high = str.fromLastOccurrenceOf (",", false, false).getFloatValue(); + } + + bool contains (float f) const noexcept + { + return (inclusiveLow ? (f >= low) : (f > low)) + && (inclusiveHigh ? (f <= high) : (f < high)); + } + + float low = 0; + float high = 0; + + bool inclusiveLow = false; + bool inclusiveHigh = false; + }; + + struct Entry + { + juce::String name; + Range range; + }; + + struct ValueType + { + juce::String name, label; + juce::OwnedArray entries; + }; + + struct Template + { + juce::String name; + juce::OwnedArray params; + }; + + const Param* getParamForID (const int paramID, const Group* const grp) const + { + for (auto item : (grp != nullptr ? grp->paramTree : paramTree)) + { + if (auto param = dynamic_cast (item)) + if (param->paramID == paramID) + return param; + + if (auto group = dynamic_cast (item)) + if (auto res = getParamForID (paramID, group)) + return res; + } + + return nullptr; + } + + const ValueType* getValueType (const juce::String& name) const + { + for (auto v : valueTypes) + if (v->name == name) + return v; + + return nullptr; + } + + juce::OwnedArray paramTree; + juce::OwnedArray valueTypes; + juce::OwnedArray