| @@ -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 | |||
| @@ -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); | |||
| @@ -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); | |||
| @@ -1483,7 +1483,7 @@ private: | |||
| return 0; | |||
| } | |||
| void valueChangedForObserver(AUParameterAddress, AUValue) | |||
| void valueChangedForObserver (AUParameterAddress, AUValue) | |||
| { | |||
| // this will have already been handled by valueChangedFromHost | |||
| } | |||
| @@ -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<ParamInfo> parameters; | |||
| HashMap<uint32, size_t> 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<AUInstanceParameter*> (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<int> (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<AUInstanceParameter*> (param) != nullptr); | |||
| auto* auparam = static_cast<AUInstanceParameter*> (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: | |||
| @@ -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<LADSPAParameter*> (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<LADSPAParameter*> (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<const float*> (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<float> tempBuffer; | |||
| Array<int> 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<ParameterValue> 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<int> inputs, outputs; | |||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (LADSPAPluginInstance) | |||
| }; | |||
| @@ -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]; } | |||
| @@ -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<Base> 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<Entry> entries; | |||
| }; | |||
| struct Template | |||
| { | |||
| juce::String name; | |||
| juce::OwnedArray<Param> 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<const Param*> (item)) | |||
| if (param->paramID == paramID) | |||
| return param; | |||
| if (auto group = dynamic_cast<const Group*> (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<Base> paramTree; | |||
| juce::OwnedArray<ValueType> valueTypes; | |||
| juce::OwnedArray<Template> templates; | |||
| ValueType switchValueType; | |||
| private: | |||
| VSTXMLInfo (const juce::XmlElement& xml) | |||
| { | |||
| switchValueType.entries.add (new Entry({ TRANS("Off"), Range ("[0, 0.5[") })); | |||
| switchValueType.entries.add (new Entry({ TRANS("On"), Range ("[0.5, 1]") })); | |||
| forEachXmlChildElement (xml, item) | |||
| { | |||
| if (item->hasTagName ("Param")) parseParam (*item, nullptr, nullptr); | |||
| else if (item->hasTagName ("ValueType")) parseValueType (*item); | |||
| else if (item->hasTagName ("Template")) parseTemplate (*item); | |||
| else if (item->hasTagName ("Group")) parseGroup (*item, nullptr); | |||
| } | |||
| } | |||
| void parseParam (const juce::XmlElement& item, Group* group, Template* temp) | |||
| { | |||
| auto param = new Param(); | |||
| if (temp != nullptr) | |||
| param->expr = item.getStringAttribute ("id"); | |||
| else | |||
| param->paramID = item.getIntAttribute ("id"); | |||
| param->name = item.getStringAttribute ("name"); | |||
| param->label = item.getStringAttribute ("label"); | |||
| param->type = item.getStringAttribute ("type"); | |||
| param->numberOfStates = item.getIntAttribute ("numberOfStates"); | |||
| param->defaultValue = (float) item.getDoubleAttribute ("defaultValue"); | |||
| param->shortNames.addTokens (item.getStringAttribute ("shortName"), ",", juce::StringRef()); | |||
| param->shortNames.trim(); | |||
| param->shortNames.removeEmptyStrings(); | |||
| if (group != nullptr) | |||
| { | |||
| group->paramTree.add (param); | |||
| param->parent = group; | |||
| } | |||
| else if (temp != nullptr) | |||
| { | |||
| temp->params.add (param); | |||
| } | |||
| else | |||
| { | |||
| paramTree.add (param); | |||
| } | |||
| } | |||
| void parseValueType (const juce::XmlElement& item) | |||
| { | |||
| auto vt = new ValueType(); | |||
| valueTypes.add (vt); | |||
| vt->name = item.getStringAttribute ("name"); | |||
| vt->label = item.getStringAttribute ("label"); | |||
| int curEntry = 0; | |||
| const int numEntries = item.getNumChildElements(); | |||
| forEachXmlChildElementWithTagName (item, entryXml, "Entry") | |||
| { | |||
| auto entry = new Entry(); | |||
| entry->name = entryXml->getStringAttribute ("name"); | |||
| if (entryXml->hasAttribute ("value")) | |||
| { | |||
| entry->range.set(entryXml->getStringAttribute ("value")); | |||
| } | |||
| else | |||
| { | |||
| entry->range.low = curEntry / (float) numEntries; | |||
| entry->range.high = (curEntry + 1) / (float) numEntries; | |||
| entry->range.inclusiveLow = true; | |||
| entry->range.inclusiveHigh = (curEntry == numEntries - 1); | |||
| } | |||
| vt->entries.add (entry); | |||
| ++curEntry; | |||
| } | |||
| } | |||
| void parseTemplate (const juce::XmlElement& item) | |||
| { | |||
| auto temp = new Template(); | |||
| templates.add (temp); | |||
| temp->name = item.getStringAttribute ("name"); | |||
| forEachXmlChildElement (item, param) | |||
| parseParam (*param, nullptr, temp); | |||
| } | |||
| void parseGroup (const juce::XmlElement& item, Group* parentGroup) | |||
| { | |||
| auto group = new Group(); | |||
| if (parentGroup) | |||
| { | |||
| parentGroup->paramTree.add (group); | |||
| group->parent = parentGroup; | |||
| } | |||
| else | |||
| { | |||
| paramTree.add (group); | |||
| } | |||
| group->name = item.getStringAttribute ("name"); | |||
| if (item.hasAttribute ("template")) | |||
| { | |||
| juce::StringArray variables; | |||
| variables.addTokens (item.getStringAttribute ("values"), ";", juce::StringRef()); | |||
| variables.trim(); | |||
| for (auto temp : templates) | |||
| { | |||
| if (temp->name == item.getStringAttribute ("template")) | |||
| { | |||
| for (int i = 0; i < temp->params.size(); ++i) | |||
| { | |||
| auto param = new Param(); | |||
| group->paramTree.add (param); | |||
| param->parent = group; | |||
| param->paramID = evaluate (temp->params[i]->expr, variables); | |||
| param->defaultValue = temp->params[i]->defaultValue; | |||
| param->label = temp->params[i]->label; | |||
| param->name = temp->params[i]->name; | |||
| param->numberOfStates = temp->params[i]->numberOfStates; | |||
| param->shortNames = temp->params[i]->shortNames; | |||
| param->type = temp->params[i]->type; | |||
| } | |||
| } | |||
| } | |||
| } | |||
| else | |||
| { | |||
| forEachXmlChildElement (item, subItem) | |||
| { | |||
| if (subItem->hasTagName ("Param")) parseParam (*subItem, group, nullptr); | |||
| else if (subItem->hasTagName ("Group")) parseGroup (*subItem, group); | |||
| } | |||
| } | |||
| } | |||
| int evaluate (juce::String expr, const juce::StringArray& variables) const | |||
| { | |||
| juce::StringArray names; | |||
| juce::Array<int> vals; | |||
| for (auto& v : variables) | |||
| { | |||
| if (v.contains ("=")) | |||
| { | |||
| names.add (v.upToFirstOccurrenceOf ("=", false, false)); | |||
| vals.add (v.fromFirstOccurrenceOf ("=", false, false).getIntValue()); | |||
| } | |||
| } | |||
| for (int i = 0; i < names.size(); ++i) | |||
| { | |||
| for (;;) | |||
| { | |||
| const int idx = expr.indexOfWholeWord (names[i]); | |||
| if (idx < 0) | |||
| break; | |||
| expr = expr.replaceSection (idx, names[i].length(), juce::String (vals[i])); | |||
| } | |||
| } | |||
| expr = expr.retainCharacters ("01234567890-+") | |||
| .replace ("+", " + ") | |||
| .replace ("-", " - "); | |||
| juce::StringArray tokens; | |||
| tokens.addTokens (expr, " ", juce::StringRef()); | |||
| bool add = true; | |||
| int val = 0; | |||
| for (const auto& s : tokens) | |||
| { | |||
| if (s == "+") | |||
| { | |||
| add = true; | |||
| } | |||
| else if (s == "-") | |||
| { | |||
| add = false; | |||
| } | |||
| else | |||
| { | |||
| if (add) | |||
| val += s.getIntValue(); | |||
| else | |||
| val -= s.getIntValue(); | |||
| } | |||
| } | |||
| return val; | |||
| } | |||
| }; | |||
| //============================================================================== | |||
| struct ModuleHandle : public ReferenceCountedObject | |||
| { | |||
| @@ -498,12 +822,158 @@ static const int defaultVSTBlockSizeValue = 512; | |||
| #pragma warning (disable: 4996) // warning about overriding deprecated methods | |||
| #endif | |||
| //============================================================================== | |||
| //============================================================================== | |||
| struct VSTPluginInstance : public AudioPluginInstance, | |||
| private Timer, | |||
| private AsyncUpdater | |||
| { | |||
| struct VSTParameter final : public Parameter | |||
| { | |||
| VSTParameter (VSTPluginInstance& parent, | |||
| const String& paramName, | |||
| const Array<String>& shortParamNames, | |||
| float paramDefaultValue, | |||
| const String& paramLabel, | |||
| bool paramIsAutomatable, | |||
| bool paramIsDiscrete, | |||
| int numParamSteps, | |||
| bool isBoolSwitch, | |||
| const StringArray& paramValueStrings, | |||
| const VSTXMLInfo::ValueType* paramValueType) | |||
| : pluginInstance (parent), | |||
| name (paramName), | |||
| shortNames (shortParamNames), | |||
| defaultValue (paramDefaultValue), | |||
| label (paramLabel), | |||
| automatable (paramIsAutomatable), | |||
| discrete (paramIsDiscrete), | |||
| numSteps (numParamSteps), | |||
| isSwitch (isBoolSwitch), | |||
| vstValueStrings (paramValueStrings), | |||
| valueType (paramValueType) | |||
| { | |||
| } | |||
| virtual float getValue() const override | |||
| { | |||
| if (auto* effect = pluginInstance.vstEffect) | |||
| { | |||
| const ScopedLock sl (pluginInstance.lock); | |||
| return effect->getParameterValueFunction (effect, getParameterIndex()); | |||
| } | |||
| return 0.0f; | |||
| } | |||
| virtual void setValue (float newValue) override | |||
| { | |||
| if (auto* effect = pluginInstance.vstEffect) | |||
| { | |||
| const ScopedLock sl (pluginInstance.lock); | |||
| if (effect->getParameterValueFunction (effect, getParameterIndex()) != newValue) | |||
| effect->setParameterValueFunction (effect, getParameterIndex(), newValue); | |||
| } | |||
| } | |||
| String getText (float value, int maximumStringLength) const override | |||
| { | |||
| if (valueType != nullptr) | |||
| { | |||
| for (auto& v : valueType->entries) | |||
| if (v->range.contains (value)) | |||
| return v->name; | |||
| } | |||
| return Parameter::getText (value, maximumStringLength); | |||
| } | |||
| float getValueForText (const String& text) const override | |||
| { | |||
| if (valueType != nullptr) | |||
| { | |||
| for (auto& v : valueType->entries) | |||
| if (v->name == text) | |||
| return (v->range.high + v->range.low) / 2.0f; | |||
| } | |||
| return Parameter::getValueForText (text); | |||
| } | |||
| String getCurrentValueAsText() const override | |||
| { | |||
| if (valueType != nullptr || ! vstValueStrings.isEmpty()) | |||
| return getText (getValue(), 1024); | |||
| return pluginInstance.getTextForOpcode (getParameterIndex(), plugInOpcodeGetParameterText); | |||
| } | |||
| float getDefaultValue() const override | |||
| { | |||
| return defaultValue; | |||
| } | |||
| String getName (int maximumStringLength) const override | |||
| { | |||
| if (name.length() <= maximumStringLength) | |||
| return name; | |||
| if (! shortNames.isEmpty()) | |||
| { | |||
| for (auto& n : shortNames) | |||
| if (n.length() <= maximumStringLength) | |||
| return n; | |||
| return shortNames.getLast(); | |||
| } | |||
| return name; | |||
| } | |||
| String getLabel() const override | |||
| { | |||
| return label; | |||
| } | |||
| 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 vstValueStrings; | |||
| } | |||
| VSTPluginInstance& pluginInstance; | |||
| const String name; | |||
| const Array<String> shortNames; | |||
| const float defaultValue; | |||
| const String label; | |||
| const bool automatable, discrete; | |||
| const int numSteps; | |||
| const bool isSwitch; | |||
| const StringArray vstValueStrings; | |||
| const VSTXMLInfo::ValueType* const valueType; | |||
| }; | |||
| VSTPluginInstance (const ModuleHandle::Ptr& mh, const BusesProperties& ioConfig, VstEffectInterface* effect, | |||
| double sampleRateToUse, int blockSizeToUse) | |||
| : AudioPluginInstance (ioConfig), | |||
| @@ -511,6 +981,88 @@ struct VSTPluginInstance : public AudioPluginInstance, | |||
| vstModule (mh), | |||
| name (mh->pluginName) | |||
| { | |||
| jassert (vstEffect != nullptr); | |||
| if (auto* xml = vstModule->vstXml.get()) | |||
| xmlInfo.reset (VSTXMLInfo::createFor (*xml)); | |||
| for (int i = 0; i < vstEffect->numParameters; ++i) | |||
| { | |||
| String paramName (getTextForOpcode (i, plugInOpcodeGetParameterName)); | |||
| Array<String> shortParamNames; | |||
| float defaultValue = 0; | |||
| String label (getTextForOpcode (i, plugInOpcodeGetParameterLabel)); | |||
| bool isAutomatable = dispatch (plugInOpcodeIsParameterAutomatable, i, 0, 0, 0) != 0; | |||
| bool isDiscrete = false; | |||
| int numSteps = AudioProcessor::getDefaultNumParameterSteps(); | |||
| bool isBoolSwitch = false; | |||
| StringArray parameterValueStrings; | |||
| const VSTXMLInfo::ValueType* valueType = nullptr; | |||
| if (xmlInfo != nullptr) | |||
| { | |||
| if (auto* param = xmlInfo->getParamForID (i, nullptr)) | |||
| { | |||
| paramName = param->name; | |||
| for (auto& n : param->shortNames) | |||
| shortParamNames.add (n); | |||
| struct LengthComparator | |||
| { | |||
| static int compareElements (const juce::String& first, const juce::String& second) noexcept | |||
| { | |||
| return first.length() - second.length(); | |||
| } | |||
| }; | |||
| LengthComparator comp; | |||
| shortParamNames.sort (comp); | |||
| defaultValue = param->defaultValue; | |||
| label = param->label; | |||
| if (param->type == "switch") | |||
| { | |||
| isBoolSwitch = true; | |||
| numSteps = 2; | |||
| valueType = &xmlInfo->switchValueType; | |||
| } | |||
| else | |||
| { | |||
| valueType = xmlInfo->getValueType (param->type); | |||
| } | |||
| if (param->numberOfStates >= 2) | |||
| { | |||
| numSteps = param->numberOfStates; | |||
| if (valueType != nullptr) | |||
| { | |||
| for (auto* entry : valueType->entries) | |||
| parameterValueStrings.add (entry->name); | |||
| parameterValueStrings.removeEmptyStrings(); | |||
| } | |||
| } | |||
| isDiscrete = (numSteps != AudioProcessor::getDefaultNumParameterSteps()); | |||
| } | |||
| } | |||
| addParameter (new VSTParameter (*this, | |||
| paramName, | |||
| shortParamNames, | |||
| defaultValue, | |||
| label, | |||
| isAutomatable, | |||
| isDiscrete, | |||
| numSteps, | |||
| isBoolSwitch, | |||
| parameterValueStrings, | |||
| valueType)); | |||
| } | |||
| setRateAndBufferSizeDetails (sampleRateToUse, blockSizeToUse); | |||
| } | |||
| @@ -802,11 +1354,14 @@ struct VSTPluginInstance : public AudioPluginInstance, | |||
| setPower (true); | |||
| // dodgy hack to force some plugins to initialise the sample rate.. | |||
| if ((! hasEditor()) && getNumParameters() > 0) | |||
| if (! hasEditor()) | |||
| { | |||
| auto old = getParameter (0); | |||
| setParameter (0, (old < 0.5f) ? 1.0f : 0.0f); | |||
| setParameter (0, old); | |||
| if (auto* firstParam = getParameters()[0]) | |||
| { | |||
| auto old = firstParam->getValue(); | |||
| firstParam->setValue ((old < 0.5f) ? 1.0f : 0.0f); | |||
| firstParam->setValue (old); | |||
| } | |||
| } | |||
| dispatch (plugInOpcodeStartProcess, 0, 0, 0, 0); | |||
| @@ -943,46 +1498,6 @@ struct VSTPluginInstance : public AudioPluginInstance, | |||
| : getTotalNumOutputChannels()); | |||
| } | |||
| //============================================================================== | |||
| int getNumParameters() override { return vstEffect != nullptr ? vstEffect->numParameters : 0; } | |||
| float getParameter (int index) override | |||
| { | |||
| if (vstEffect != nullptr && isPositiveAndBelow (index, vstEffect->numParameters)) | |||
| { | |||
| const ScopedLock sl (lock); | |||
| return vstEffect->getParameterValueFunction (vstEffect, index); | |||
| } | |||
| return 0.0f; | |||
| } | |||
| void setParameter (int index, float newValue) override | |||
| { | |||
| if (vstEffect != nullptr && isPositiveAndBelow (index, vstEffect->numParameters)) | |||
| { | |||
| const ScopedLock sl (lock); | |||
| if (vstEffect->getParameterValueFunction (vstEffect, index) != newValue) | |||
| vstEffect->setParameterValueFunction (vstEffect, index, newValue); | |||
| } | |||
| } | |||
| const String getParameterName (int index) override { return getTextForOpcode (index, plugInOpcodeGetParameterName); } | |||
| const String getParameterText (int index) override { return getTextForOpcode (index, plugInOpcodeGetParameterText); } | |||
| String getParameterLabel (int index) const override { return getTextForOpcode (index, plugInOpcodeGetParameterLabel); } | |||
| bool isParameterAutomatable (int index) const override | |||
| { | |||
| if (vstEffect != nullptr) | |||
| { | |||
| jassert (index >= 0 && index < vstEffect->numParameters); | |||
| return dispatch (plugInOpcodeIsParameterAutomatable, index, 0, 0, 0) != 0; | |||
| } | |||
| return false; | |||
| } | |||
| //============================================================================== | |||
| int getNumPrograms() override { return vstEffect != nullptr ? jmax (0, vstEffect->numPrograms) : 0; } | |||
| @@ -1051,7 +1566,14 @@ struct VSTPluginInstance : public AudioPluginInstance, | |||
| { | |||
| switch (opcode) | |||
| { | |||
| case hostOpcodeParameterChanged: sendParamChangeMessageToListeners (index, opt); break; | |||
| case hostOpcodeParameterChanged: | |||
| if (auto* param = getParameters()[index]) | |||
| param->sendValueChangedMessageToListeners (opt); | |||
| else | |||
| jassertfalse; // Invalid parameter index! | |||
| break; | |||
| case hostOpcodePreAudioProcessingEvents: handleMidiFromPlugin ((const VstEventBlock*) ptr); break; | |||
| case hostOpcodeGetTimingInfo: return getVSTTime(); | |||
| case hostOpcodeIdle: handleIdle(); break; | |||
| @@ -1068,8 +1590,21 @@ struct VSTPluginInstance : public AudioPluginInstance, | |||
| case hostOpcodeTempoAt: return (pointer_sized_int) (extraFunctions != nullptr ? extraFunctions->getTempoAt ((int64) value) : 0); | |||
| case hostOpcodeGetAutomationState: return (pointer_sized_int) (extraFunctions != nullptr ? extraFunctions->getAutomationState() : 0); | |||
| case hostOpcodeParameterChangeGestureBegin: beginParameterChangeGesture (index); break; | |||
| case hostOpcodeParameterChangeGestureEnd: endParameterChangeGesture (index); break; | |||
| case hostOpcodeParameterChangeGestureBegin: | |||
| if (auto* param = getParameters()[index]) | |||
| param->beginChangeGesture(); | |||
| else | |||
| jassertfalse; // Invalid parameter index! | |||
| break; | |||
| case hostOpcodeParameterChangeGestureEnd: | |||
| if (auto* param = getParameters()[index]) | |||
| param->endChangeGesture(); | |||
| else | |||
| jassertfalse; // Invalid parameter index! | |||
| break; | |||
| case hostOpcodePinConnected: return isValidChannel (index, value == 0) ? 0 : 1; // (yes, 0 = true) | |||
| case hostOpcodeGetCurrentAudioProcessingLevel: return isNonRealtime() ? 4 : 0; | |||
| @@ -1226,7 +1761,8 @@ struct VSTPluginInstance : public AudioPluginInstance, | |||
| changeProgramName (getCurrentProgram(), prog->prgName); | |||
| for (int i = 0; i < fxbSwap (prog->numParams); ++i) | |||
| setParameter (i, fxbSwapFloat (prog->params[i])); | |||
| if (auto* param = getParameters()[i]) | |||
| param->setValue (fxbSwapFloat (prog->params[i])); | |||
| } | |||
| else if (compareMagic (set->fxMagic, "FBCh")) | |||
| { | |||
| @@ -1261,7 +1797,7 @@ struct VSTPluginInstance : public AudioPluginInstance, | |||
| bool saveToFXBFile (MemoryBlock& dest, bool isFXB, int maxSizeMB = 128) | |||
| { | |||
| auto numPrograms = getNumPrograms(); | |||
| auto numParams = getNumParameters(); | |||
| auto numParams = getParameters().size(); | |||
| if (usesChunks()) | |||
| { | |||
| @@ -1413,6 +1949,8 @@ private: | |||
| AudioBuffer<double> tmpBufferDouble; | |||
| HeapBlock<double*> channelBufferDouble; | |||
| ScopedPointer<VSTXMLInfo> xmlInfo; | |||
| static pointer_sized_int handleCanDo (const char* name) | |||
| { | |||
| static const char* canDos[] = { "supplyIdle", | |||
| @@ -1832,7 +2370,8 @@ private: | |||
| changeProgramName (getCurrentProgram(), prog->prgName); | |||
| for (int i = 0; i < fxbSwap (prog->numParams); ++i) | |||
| setParameter (i, fxbSwapFloat (prog->params[i])); | |||
| if (auto* param = getParameters()[i]) | |||
| param->setValue (fxbSwapFloat (prog->params[i])); | |||
| return true; | |||
| } | |||
| @@ -1879,7 +2418,7 @@ private: | |||
| void setParamsInProgramBlock (fxProgram* prog) | |||
| { | |||
| auto numParams = getNumParameters(); | |||
| auto numParams = getParameters().size(); | |||
| prog->chunkMagic = fxbName ("CcnK"); | |||
| prog->byteSize = 0; | |||
| @@ -1892,7 +2431,8 @@ private: | |||
| getCurrentProgramName().copyToUTF8 (prog->prgName, sizeof (prog->prgName) - 1); | |||
| for (int i = 0; i < numParams; ++i) | |||
| prog->params[i] = fxbSwapFloat (getParameter (i)); | |||
| if (auto* param = getParameters()[i]) | |||
| prog->params[i] = fxbSwapFloat (param->getValue()); | |||
| } | |||
| void updateStoredProgramNames() | |||
| @@ -1932,15 +2472,17 @@ private: | |||
| //============================================================================== | |||
| void createTempParameterStore (MemoryBlock& dest) | |||
| { | |||
| dest.setSize (64 + 4 * (size_t) getNumParameters()); | |||
| auto numParameters = getParameters().size(); | |||
| dest.setSize (64 + 4 * (size_t) numParameters); | |||
| dest.fillWith (0); | |||
| getCurrentProgramName().copyToUTF8 ((char*) dest.getData(), 63); | |||
| auto p = (float*) (((char*) dest.getData()) + 64); | |||
| 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 restoreFromTempParameterStore (const MemoryBlock& m) | |||
| @@ -1948,9 +2490,11 @@ private: | |||
| changeProgramName (getCurrentProgram(), (const char*) m.getData()); | |||
| auto p = (float*) (((char*) m.getData()) + 64); | |||
| auto numParameters = getParameters().size(); | |||
| for (int i = 0; i < getNumParameters(); ++i) | |||
| setParameter (i, p[i]); | |||
| for (int i = 0; i < numParameters; ++i) | |||
| if (auto* param = getParameters()[i]) | |||
| param->setValue (p[i]); | |||
| } | |||
| pointer_sized_int getVstDirectory() const | |||
| @@ -155,6 +155,7 @@ struct AutoResizingNSViewComponentWithParent : public AutoResizingNSViewCompone | |||
| #include "format/juce_AudioPluginFormat.cpp" | |||
| #include "format/juce_AudioPluginFormatManager.cpp" | |||
| #include "processors/juce_AudioProcessor.cpp" | |||
| #include "processors/juce_AudioPluginInstance.cpp" | |||
| #include "processors/juce_AudioProcessorEditor.cpp" | |||
| #include "processors/juce_AudioProcessorGraph.cpp" | |||
| #include "processors/juce_GenericAudioProcessorEditor.cpp" | |||
| @@ -0,0 +1,230 @@ | |||
| /* | |||
| ============================================================================== | |||
| This file is part of the JUCE library. | |||
| Copyright (c) 2017 - ROLI Ltd. | |||
| JUCE is an open source library subject to commercial or open-source | |||
| licensing. | |||
| By using JUCE, you agree to the terms of both the JUCE 5 End-User License | |||
| Agreement and JUCE 5 Privacy Policy (both updated and effective as of the | |||
| 27th April 2017). | |||
| End User License Agreement: www.juce.com/juce-5-licence | |||
| Privacy Policy: www.juce.com/juce-5-privacy-policy | |||
| Or: You may also use this code under the terms of the GPL v3 (see | |||
| www.gnu.org/licenses). | |||
| JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||
| EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||
| DISCLAIMED. | |||
| ============================================================================== | |||
| */ | |||
| namespace juce | |||
| { | |||
| PluginDescription AudioPluginInstance::getPluginDescription() const | |||
| { | |||
| PluginDescription desc; | |||
| fillInPluginDescription (desc); | |||
| return desc; | |||
| } | |||
| String AudioPluginInstance::getParameterID (int parameterIndex) | |||
| { | |||
| assertOnceOnDeprecatedMethodUse(); | |||
| return String (parameterIndex); | |||
| } | |||
| float AudioPluginInstance::getParameter (int parameterIndex) | |||
| { | |||
| assertOnceOnDeprecatedMethodUse(); | |||
| if (auto* param = getParameters()[parameterIndex]) | |||
| return param->getValue(); | |||
| return 0.0f; | |||
| } | |||
| void AudioPluginInstance::setParameter (int parameterIndex, float newValue) | |||
| { | |||
| assertOnceOnDeprecatedMethodUse(); | |||
| if (auto* param = getParameters()[parameterIndex]) | |||
| return param->setValue (newValue); | |||
| } | |||
| const String AudioPluginInstance::getParameterName (int parameterIndex) | |||
| { | |||
| assertOnceOnDeprecatedMethodUse(); | |||
| if (auto* param = getParameters()[parameterIndex]) | |||
| return param->getName (1024); | |||
| return {}; | |||
| } | |||
| const String AudioPluginInstance::getParameterText (int parameterIndex) | |||
| { | |||
| assertOnceOnDeprecatedMethodUse(); | |||
| if (auto* param = getParameters()[parameterIndex]) | |||
| return param->getCurrentValueAsText(); | |||
| return {}; | |||
| } | |||
| String AudioPluginInstance::getParameterText (int parameterIndex, int maximumStringLength) | |||
| { | |||
| assertOnceOnDeprecatedMethodUse(); | |||
| if (auto* param = getParameters()[parameterIndex]) | |||
| return param->getCurrentValueAsText().substring (0, maximumStringLength); | |||
| return {}; | |||
| } | |||
| float AudioPluginInstance::getParameterDefaultValue (int parameterIndex) | |||
| { | |||
| assertOnceOnDeprecatedMethodUse(); | |||
| if (auto* param = getParameters()[parameterIndex]) | |||
| return param->getDefaultValue(); | |||
| return 0.0f; | |||
| } | |||
| int AudioPluginInstance::getParameterNumSteps (int parameterIndex) | |||
| { | |||
| assertOnceOnDeprecatedMethodUse(); | |||
| if (auto* param = getParameters()[parameterIndex]) | |||
| return param->getNumSteps(); | |||
| return AudioProcessor::getDefaultNumParameterSteps(); | |||
| } | |||
| bool AudioPluginInstance::isParameterDiscrete (int parameterIndex) const | |||
| { | |||
| assertOnceOnDeprecatedMethodUse(); | |||
| if (auto* param = getParameters()[parameterIndex]) | |||
| return param->isDiscrete(); | |||
| return false; | |||
| } | |||
| bool AudioPluginInstance::isParameterAutomatable (int parameterIndex) const | |||
| { | |||
| assertOnceOnDeprecatedMethodUse(); | |||
| if (auto* param = getParameters()[parameterIndex]) | |||
| return param->isAutomatable(); | |||
| return true; | |||
| } | |||
| String AudioPluginInstance::getParameterLabel (int parameterIndex) const | |||
| { | |||
| assertOnceOnDeprecatedMethodUse(); | |||
| if (auto* param = getParameters()[parameterIndex]) | |||
| return param->getLabel(); | |||
| return {}; | |||
| } | |||
| bool AudioPluginInstance::isParameterOrientationInverted (int parameterIndex) const | |||
| { | |||
| assertOnceOnDeprecatedMethodUse(); | |||
| if (auto* param = getParameters()[parameterIndex]) | |||
| return param->isOrientationInverted(); | |||
| return false; | |||
| } | |||
| bool AudioPluginInstance::isMetaParameter (int parameterIndex) const | |||
| { | |||
| assertOnceOnDeprecatedMethodUse(); | |||
| if (auto* param = getParameters()[parameterIndex]) | |||
| return param->isMetaParameter(); | |||
| return false; | |||
| } | |||
| AudioProcessorParameter::Category AudioPluginInstance::getParameterCategory (int parameterIndex) const | |||
| { | |||
| assertOnceOnDeprecatedMethodUse(); | |||
| if (auto* param = getParameters()[parameterIndex]) | |||
| return param->getCategory(); | |||
| return AudioProcessorParameter::genericParameter; | |||
| } | |||
| void AudioPluginInstance::assertOnceOnDeprecatedMethodUse() const noexcept | |||
| { | |||
| if (! deprecationAssertiontriggered) | |||
| { | |||
| // If you hit this assertion then you are using at least one of the | |||
| // methods marked as deprecated in this class. For now you can simply | |||
| // continue past this point and subsequent uses of deprecated methods | |||
| // will not trigger additional assertions. However, we will shortly be | |||
| // removing these methods so you are strongly advised to look at the | |||
| // implementation of the corresponding method in this class and use | |||
| // that approach instead. | |||
| jassertfalse; | |||
| } | |||
| deprecationAssertiontriggered = true; | |||
| } | |||
| bool AudioPluginInstance::deprecationAssertiontriggered = false; | |||
| AudioPluginInstance::Parameter::Parameter() | |||
| { | |||
| onStrings.add (TRANS("on")); | |||
| onStrings.add (TRANS("yes")); | |||
| onStrings.add (TRANS("true")); | |||
| offStrings.add (TRANS("off")); | |||
| offStrings.add (TRANS("no")); | |||
| offStrings.add (TRANS("false")); | |||
| } | |||
| AudioPluginInstance::Parameter::~Parameter() {} | |||
| String AudioPluginInstance::Parameter::getText (float value, int maximumStringLength) const | |||
| { | |||
| if (isBoolean()) | |||
| return value < 0.5f ? TRANS("Off") : TRANS("On"); | |||
| return String (value).substring (0, maximumStringLength); | |||
| } | |||
| float AudioPluginInstance::Parameter::getValueForText (const String& text) const | |||
| { | |||
| auto floatValue = text.retainCharacters ("-0123456789.").getFloatValue(); | |||
| if (isBoolean()) | |||
| { | |||
| if (onStrings.contains (text, true)) | |||
| return 1.0f; | |||
| if (offStrings.contains (text, true)) | |||
| return 0.0f; | |||
| return floatValue < 0.5f ? 0.0f : 1.0f; | |||
| } | |||
| return floatValue; | |||
| } | |||
| } // namespace juce | |||
| @@ -58,12 +58,7 @@ public: | |||
| /** Returns a PluginDescription for this plugin. | |||
| This is just a convenience method to avoid calling fillInPluginDescription. | |||
| */ | |||
| PluginDescription getPluginDescription() const | |||
| { | |||
| PluginDescription desc; | |||
| fillInPluginDescription (desc); | |||
| return desc; | |||
| } | |||
| PluginDescription getPluginDescription() const; | |||
| /** Returns a pointer to some kind of platform-specific data about the plugin. | |||
| E.g. For a VST, this value can be cast to an AEffect*. For an AudioUnit, it can be | |||
| @@ -76,13 +71,51 @@ public: | |||
| */ | |||
| virtual void refreshParameterList() {} | |||
| // Rather than using these methods you should call the corresponding methods | |||
| // on the AudioProcessorParameter objects returned from getParameters(). | |||
| // See the implementations of the methods below for some examples of how to | |||
| // do this. | |||
| // | |||
| // In addition to being marked as deprecated these methods will assert on | |||
| // the first call. | |||
| JUCE_DEPRECATED (String getParameterID (int index) override); | |||
| JUCE_DEPRECATED (float getParameter (int parameterIndex) override); | |||
| JUCE_DEPRECATED (void setParameter (int parameterIndex, float newValue) override); | |||
| JUCE_DEPRECATED (const String getParameterName (int parameterIndex) override); | |||
| JUCE_DEPRECATED (const String getParameterText (int parameterIndex) override); | |||
| JUCE_DEPRECATED (String getParameterText (int parameterIndex, int maximumStringLength) override); | |||
| JUCE_DEPRECATED (int getParameterNumSteps (int parameterIndex) override); | |||
| JUCE_DEPRECATED (bool isParameterDiscrete (int parameterIndex) const override); | |||
| JUCE_DEPRECATED (bool isParameterAutomatable (int parameterIndex) const override); | |||
| JUCE_DEPRECATED (float getParameterDefaultValue (int parameterIndex) override); | |||
| JUCE_DEPRECATED (String getParameterLabel (int parameterIndex) const override); | |||
| JUCE_DEPRECATED (bool isParameterOrientationInverted (int parameterIndex) const override); | |||
| JUCE_DEPRECATED (bool isMetaParameter (int parameterIndex) const override); | |||
| JUCE_DEPRECATED (AudioProcessorParameter::Category getParameterCategory (int parameterIndex) const override); | |||
| protected: | |||
| //============================================================================== | |||
| struct Parameter : public AudioProcessorParameter | |||
| { | |||
| Parameter(); | |||
| virtual ~Parameter(); | |||
| virtual String getText (float value, int maximumStringLength) const override; | |||
| virtual float getValueForText (const String& text) const override; | |||
| StringArray onStrings, offStrings; | |||
| }; | |||
| AudioPluginInstance() {} | |||
| AudioPluginInstance (const BusesProperties& ioLayouts) : AudioProcessor (ioLayouts) {} | |||
| template <int numLayouts> | |||
| AudioPluginInstance (const short channelLayoutList[numLayouts][2]) : AudioProcessor (channelLayoutList) {} | |||
| private: | |||
| void assertOnceOnDeprecatedMethodUse() const noexcept; | |||
| static bool deprecationAssertiontriggered; | |||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AudioPluginInstance) | |||
| }; | |||
| @@ -673,10 +673,6 @@ void AudioProcessor::addParameter (AudioProcessorParameter* p) | |||
| p->parameterIndex = managedParameters.size(); | |||
| managedParameters.add (p); | |||
| // if you're using parameter objects, then you must not override the | |||
| // deprecated getNumParameters() method! | |||
| jassert (getNumParameters() == AudioProcessor::getNumParameters()); | |||
| #ifdef JUCE_DEBUG | |||
| shouldCheckParamsForDupeIDs = true; | |||
| #endif | |||
| @@ -1354,14 +1350,21 @@ int32 AudioProcessor::getAAXPluginIDForMainBusConfig (const AudioChannelSet& mai | |||
| return (idForAudioSuite ? 0x6a796161 /* 'jyaa' */ : 0x6a636161 /* 'jcaa' */) + uniqueFormatId; | |||
| } | |||
| //============================================================================== | |||
| void AudioProcessorListener::audioProcessorParameterChangeGestureBegin (AudioProcessor*, int) {} | |||
| void AudioProcessorListener::audioProcessorParameterChangeGestureEnd (AudioProcessor*, int) {} | |||
| //============================================================================== | |||
| AudioProcessorParameter::AudioProcessorParameter() noexcept {} | |||
| AudioProcessorParameter::~AudioProcessorParameter() {} | |||
| AudioProcessorParameter::~AudioProcessorParameter() | |||
| { | |||
| #if JUCE_DEBUG && ! JUCE_DISABLE_AUDIOPROCESSOR_BEGIN_END_GESTURE_CHECKING | |||
| // This will fail if you've called beginChangeGesture() without having made | |||
| // a corresponding call to endChangeGesture... | |||
| jassert (! isPerformingGesture); | |||
| #endif | |||
| } | |||
| void AudioProcessorParameter::setValueNotifyingHost (float newValue) | |||
| { | |||
| @@ -1452,6 +1455,24 @@ String AudioProcessorParameter::getText (float value, int /*maximumStringLength* | |||
| return String (value, 2); | |||
| } | |||
| String AudioProcessorParameter::getCurrentValueAsText() const | |||
| { | |||
| return getText (getValue(), 1024); | |||
| } | |||
| StringArray AudioProcessorParameter::getAllValueStrings() const | |||
| { | |||
| if (isDiscrete() && valueStrings.isEmpty()) | |||
| { | |||
| auto maxIndex = getNumSteps() - 1; | |||
| for (int i = 0; i < getNumSteps(); ++i) | |||
| valueStrings.add (getText ((float) i / maxIndex, 1024)); | |||
| } | |||
| return valueStrings; | |||
| } | |||
| void AudioProcessorParameter::addListener (AudioProcessorParameter::Listener* newListener) | |||
| { | |||
| const ScopedLock sl (listenerLock); | |||
| @@ -1620,6 +1620,8 @@ private: | |||
| friend class JuceVST3EditController; | |||
| friend class JuceVST3Component; | |||
| friend class AudioUnitPluginInstance; | |||
| friend class LADSPAPluginInstance; | |||
| Atomic<int> vst3IsPlaying { 0 }; | |||
| @@ -199,6 +199,28 @@ public: | |||
| /** Returns the index of this parameter in its parent processor's parameter list. */ | |||
| int getParameterIndex() const noexcept { return parameterIndex; } | |||
| //============================================================================== | |||
| /** Returns the current value of the parameter as a String. | |||
| This function can be called when you are hosting plug-ins to get a | |||
| more specialsed textual represenation of the current value from the | |||
| plug-in, for example "On" rather than "1.0". | |||
| If you are implementing a plug-in then you should ignore this function | |||
| and instead override getText. | |||
| */ | |||
| virtual String getCurrentValueAsText() const; | |||
| /** Returns the set of strings which represent the possible states a parameter | |||
| can be in. | |||
| If you are hosting a plug-in you can use the result of this funtion to | |||
| populate a ComboBox listing the allowed values. | |||
| If you are implementing a plug-in then you do not need to override this. | |||
| */ | |||
| virtual StringArray getAllValueStrings() const; | |||
| //============================================================================== | |||
| /** | |||
| A base class for listeners that want to know about changes to an | |||
| @@ -267,6 +289,7 @@ private: | |||
| int parameterIndex = -1; | |||
| CriticalSection listenerLock; | |||
| Array<Listener*> listeners; | |||
| mutable StringArray valueStrings; | |||
| #if JUCE_DEBUG | |||
| bool isPerformingGesture = false; | |||
| @@ -114,17 +114,17 @@ private: | |||
| void startedDragging() override | |||
| { | |||
| owner.beginParameterChangeGesture(index); | |||
| owner.beginParameterChangeGesture (index); | |||
| } | |||
| void stoppedDragging() override | |||
| { | |||
| owner.endParameterChangeGesture(index); | |||
| owner.endParameterChangeGesture (index); | |||
| } | |||
| String getTextFromValue (double /*value*/) override | |||
| { | |||
| return owner.getParameterText (index) + " " + owner.getParameterLabel (index).trimEnd(); | |||
| return (owner.getParameterText (index) + " " + owner.getParameterLabel (index)).trimEnd(); | |||
| } | |||
| private: | |||
| @@ -143,50 +143,540 @@ private: | |||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ProcessorParameterPropertyComp) | |||
| }; | |||
| struct LegacyParametersPanel : public Component | |||
| { | |||
| LegacyParametersPanel (AudioProcessor* const processor) | |||
| { | |||
| addAndMakeVisible (panel); | |||
| Array<PropertyComponent*> params; | |||
| auto numParams = processor->getNumParameters(); | |||
| int totalHeight = 0; | |||
| for (int i = 0; i < numParams; ++i) | |||
| { | |||
| String name (processor->getParameterName (i)); | |||
| if (name.trim().isEmpty()) | |||
| name = "Unnamed"; | |||
| auto* pc = new ProcessorParameterPropertyComp (name, *processor, i); | |||
| params.add (pc); | |||
| totalHeight += pc->getPreferredHeight(); | |||
| } | |||
| panel.addProperties (params); | |||
| setSize (400, jmax (25, totalHeight)); | |||
| } | |||
| void paint (Graphics& g) override | |||
| { | |||
| g.fillAll (getLookAndFeel().findColour (ResizableWindow::backgroundColourId)); | |||
| } | |||
| void resized() override | |||
| { | |||
| panel.setBounds (getLocalBounds()); | |||
| } | |||
| PropertyPanel panel; | |||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (LegacyParametersPanel) | |||
| }; | |||
| //============================================================================== | |||
| GenericAudioProcessorEditor::GenericAudioProcessorEditor (AudioProcessor* const p) | |||
| : AudioProcessorEditor (p) | |||
| class ParameterListener : private AudioProcessorParameter::Listener, | |||
| private Timer | |||
| { | |||
| jassert (p != nullptr); | |||
| setOpaque (true); | |||
| public: | |||
| ParameterListener (AudioProcessorParameter& param) | |||
| : parameter (param) | |||
| { | |||
| parameter.addListener (this); | |||
| addAndMakeVisible (panel); | |||
| startTimer (100); | |||
| } | |||
| Array<PropertyComponent*> params; | |||
| virtual ~ParameterListener() | |||
| { | |||
| parameter.removeListener (this); | |||
| } | |||
| auto numParams = p->getNumParameters(); | |||
| int totalHeight = 0; | |||
| AudioProcessorParameter& getParameter() noexcept | |||
| { | |||
| return parameter; | |||
| } | |||
| virtual void handleNewParameterValue() = 0; | |||
| for (int i = 0; i < numParams; ++i) | |||
| private: | |||
| void parameterValueChanged (int, float) override | |||
| { | |||
| auto name = p->getParameterName (i); | |||
| parameterValueHasChanged = 1; | |||
| } | |||
| if (name.trim().isEmpty()) | |||
| name = "Unnamed"; | |||
| void parameterGestureChanged (int, bool) override {} | |||
| auto* pc = new ProcessorParameterPropertyComp (name, *p, i); | |||
| params.add (pc); | |||
| totalHeight += pc->getPreferredHeight(); | |||
| void timerCallback() override | |||
| { | |||
| if (parameterValueHasChanged.compareAndSetBool (0, 1)) | |||
| { | |||
| handleNewParameterValue(); | |||
| startTimerHz (50); | |||
| } | |||
| else | |||
| { | |||
| startTimer (jmin (250, getTimerInterval() + 10)); | |||
| } | |||
| } | |||
| panel.addProperties (params); | |||
| AudioProcessorParameter& parameter; | |||
| Atomic<int> parameterValueHasChanged { 0 }; | |||
| setSize (400, jlimit (25, 400, totalHeight)); | |||
| } | |||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ParameterListener) | |||
| }; | |||
| GenericAudioProcessorEditor::~GenericAudioProcessorEditor() | |||
| class BooleanParameterComponent final : public Component, | |||
| private ParameterListener | |||
| { | |||
| public: | |||
| BooleanParameterComponent (AudioProcessorParameter& param) | |||
| : ParameterListener (param) | |||
| { | |||
| // Set the initial value. | |||
| handleNewParameterValue(); | |||
| button.onClick = [this]() { buttonClicked(); }; | |||
| addAndMakeVisible (button); | |||
| } | |||
| void paint (Graphics&) override {} | |||
| void resized() override | |||
| { | |||
| auto area = getLocalBounds(); | |||
| area.removeFromLeft (8); | |||
| button.setBounds (area.reduced (0, 10)); | |||
| } | |||
| private: | |||
| void handleNewParameterValue() override | |||
| { | |||
| auto parameterState = getParameterState (getParameter().getValue()); | |||
| if (button.getToggleState() != parameterState) | |||
| button.setToggleState (parameterState, dontSendNotification); | |||
| } | |||
| void buttonClicked() | |||
| { | |||
| if (getParameterState (getParameter().getValue()) != button.getToggleState()) | |||
| { | |||
| getParameter().beginChangeGesture(); | |||
| getParameter().setValueNotifyingHost (button.getToggleState() ? 1.0f : 0.0f); | |||
| getParameter().endChangeGesture(); | |||
| } | |||
| } | |||
| bool getParameterState (float value) const noexcept | |||
| { | |||
| return value >= 0.5f; | |||
| } | |||
| ToggleButton button; | |||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (BooleanParameterComponent) | |||
| }; | |||
| class SwitchParameterComponent final : public Component, | |||
| private ParameterListener | |||
| { | |||
| public: | |||
| SwitchParameterComponent (AudioProcessorParameter& param) | |||
| : ParameterListener (param) | |||
| { | |||
| auto* leftButton = buttons.add (new TextButton()); | |||
| auto* rightButton = buttons.add (new TextButton()); | |||
| for (auto* button : buttons) | |||
| { | |||
| button->setRadioGroupId (293847); | |||
| button->setClickingTogglesState (true); | |||
| } | |||
| leftButton ->setButtonText (getParameter().getText (0.0f, 16)); | |||
| rightButton->setButtonText (getParameter().getText (1.0f, 16)); | |||
| leftButton ->setConnectedEdges (Button::ConnectedOnRight); | |||
| rightButton->setConnectedEdges (Button::ConnectedOnLeft); | |||
| // Set the initial value. | |||
| leftButton->setToggleState (true, dontSendNotification); | |||
| handleNewParameterValue(); | |||
| rightButton->onStateChange = [this]() { rightButtonChanged(); }; | |||
| for (auto* button : buttons) | |||
| addAndMakeVisible (button); | |||
| } | |||
| void paint (Graphics&) override {} | |||
| void resized() override | |||
| { | |||
| auto area = getLocalBounds().reduced (0, 8); | |||
| area.removeFromLeft (8); | |||
| for (auto* button : buttons) | |||
| button->setBounds (area.removeFromLeft (80)); | |||
| } | |||
| private: | |||
| void handleNewParameterValue() override | |||
| { | |||
| bool newState = getParameterState(); | |||
| if (buttons[1]->getToggleState() != newState) | |||
| { | |||
| buttons[1]->setToggleState (newState, dontSendNotification); | |||
| buttons[0]->setToggleState (! newState, dontSendNotification); | |||
| } | |||
| } | |||
| void rightButtonChanged() | |||
| { | |||
| auto buttonState = buttons[1]->getToggleState(); | |||
| if (getParameterState() != buttonState) | |||
| { | |||
| getParameter().beginChangeGesture(); | |||
| if (getParameter().getAllValueStrings().isEmpty()) | |||
| { | |||
| getParameter().setValueNotifyingHost (buttonState ? 1.0f : 0.0f); | |||
| } | |||
| else | |||
| { | |||
| // When a parameter provides a list of strings we must set its | |||
| // value using those strings, rather than a float, because VSTs can | |||
| // have uneven spacing between the different allowed values and we | |||
| // want the snapping behaviour to be consistent with what we do with | |||
| // a combo box. | |||
| String selectedText = buttonState ? buttons[1]->getButtonText() : buttons[0]->getButtonText(); | |||
| getParameter().setValueNotifyingHost (getParameter().getValueForText (selectedText)); | |||
| } | |||
| getParameter().endChangeGesture(); | |||
| } | |||
| } | |||
| bool getParameterState() | |||
| { | |||
| if (getParameter().getAllValueStrings().isEmpty()) | |||
| return getParameter().getValue() > 0.5f; | |||
| auto index = getParameter().getAllValueStrings() | |||
| .indexOf (getParameter().getCurrentValueAsText()); | |||
| if (index < 0) | |||
| { | |||
| // The parameter is producing some unexpected text, so we'll do | |||
| // some linear interpolation. | |||
| index = roundToInt (getParameter().getValue()); | |||
| } | |||
| return index == 1; | |||
| } | |||
| OwnedArray<TextButton> buttons; | |||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (SwitchParameterComponent) | |||
| }; | |||
| class ChoiceParameterComponent final : public Component, | |||
| private ParameterListener | |||
| { | |||
| public: | |||
| ChoiceParameterComponent (AudioProcessorParameter& param) | |||
| : ParameterListener (param), | |||
| parameterValues (getParameter().getAllValueStrings()) | |||
| { | |||
| box.addItemList (parameterValues, 1); | |||
| // Set the initial value. | |||
| handleNewParameterValue(); | |||
| box.onChange = [this]() { boxChanged(); }; | |||
| addAndMakeVisible (box); | |||
| } | |||
| void paint (Graphics&) override {} | |||
| void resized() override | |||
| { | |||
| auto area = getLocalBounds(); | |||
| area.removeFromLeft (8); | |||
| box.setBounds (area.reduced (0, 10)); | |||
| } | |||
| private: | |||
| void handleNewParameterValue() override | |||
| { | |||
| auto index = parameterValues.indexOf (getParameter().getCurrentValueAsText()); | |||
| if (index < 0) | |||
| { | |||
| // The parameter is producing some unexpected text, so we'll do | |||
| // some linear interpolation. | |||
| index = roundToInt (getParameter().getValue() * (parameterValues.size() - 1)); | |||
| } | |||
| box.setSelectedItemIndex (index); | |||
| } | |||
| void boxChanged() | |||
| { | |||
| if (getParameter().getCurrentValueAsText() != box.getText()) | |||
| { | |||
| getParameter().beginChangeGesture(); | |||
| // When a parameter provides a list of strings we must set its | |||
| // value using those strings, rather than a float, because VSTs can | |||
| // have uneven spacing between the different allowed values. | |||
| getParameter().setValueNotifyingHost (getParameter().getValueForText (box.getText())); | |||
| getParameter().endChangeGesture(); | |||
| } | |||
| } | |||
| ComboBox box; | |||
| const StringArray parameterValues; | |||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ChoiceParameterComponent) | |||
| }; | |||
| class SliderParameterComponent final : public Component, | |||
| private ParameterListener | |||
| { | |||
| public: | |||
| SliderParameterComponent (AudioProcessorParameter& param) | |||
| : ParameterListener (param) | |||
| { | |||
| if (getParameter().getNumSteps() != AudioProcessor::getDefaultNumParameterSteps()) | |||
| slider.setRange (0.0, 1.0, 1.0 / (getParameter().getNumSteps() - 1.0)); | |||
| else | |||
| slider.setRange (0.0, 1.0); | |||
| slider.setScrollWheelEnabled (false); | |||
| addAndMakeVisible (slider); | |||
| valueLabel.setColour (Label::outlineColourId, slider.findColour (Slider::textBoxOutlineColourId)); | |||
| valueLabel.setBorderSize ({ 1, 1, 1, 1 }); | |||
| valueLabel.setJustificationType (Justification::centred); | |||
| addAndMakeVisible (valueLabel); | |||
| // Set the initial value. | |||
| handleNewParameterValue(); | |||
| slider.onValueChange = [this]() { sliderValueChanged(); }; | |||
| slider.onDragStart = [this]() { sliderStartedDragging(); }; | |||
| slider.onDragEnd = [this]() { sliderStoppedDragging(); }; | |||
| } | |||
| void paint (Graphics&) override {} | |||
| void resized() override | |||
| { | |||
| auto area = getLocalBounds().reduced (0, 10); | |||
| valueLabel.setBounds (area.removeFromRight (80)); | |||
| area.removeFromLeft (6); | |||
| slider.setBounds (area); | |||
| } | |||
| private: | |||
| void updateTextDisplay() | |||
| { | |||
| valueLabel.setText (getParameter().getCurrentValueAsText(), dontSendNotification); | |||
| } | |||
| void handleNewParameterValue() override | |||
| { | |||
| if (! isDragging) | |||
| { | |||
| slider.setValue (getParameter().getValue(), dontSendNotification); | |||
| updateTextDisplay(); | |||
| } | |||
| } | |||
| void sliderValueChanged() | |||
| { | |||
| auto newVal = (float) slider.getValue(); | |||
| if (getParameter().getValue() != newVal) | |||
| { | |||
| if (! isDragging) | |||
| getParameter().beginChangeGesture(); | |||
| getParameter().setValueNotifyingHost ((float) slider.getValue()); | |||
| updateTextDisplay(); | |||
| if (! isDragging) | |||
| getParameter().endChangeGesture(); | |||
| } | |||
| } | |||
| void sliderStartedDragging() | |||
| { | |||
| isDragging = true; | |||
| getParameter().beginChangeGesture(); | |||
| } | |||
| void sliderStoppedDragging() | |||
| { | |||
| isDragging = false; | |||
| getParameter().endChangeGesture(); | |||
| } | |||
| Slider slider { Slider::LinearHorizontal, Slider::TextEntryBoxPosition::NoTextBox }; | |||
| Label valueLabel; | |||
| bool isDragging = false; | |||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (SliderParameterComponent) | |||
| }; | |||
| class ParameterDisplayComponent : public Component | |||
| { | |||
| public: | |||
| ParameterDisplayComponent (AudioProcessorParameter& param) | |||
| : parameter (param) | |||
| { | |||
| parameterName.setText (parameter.getName (128), dontSendNotification); | |||
| parameterName.setJustificationType (Justification::centredRight); | |||
| addAndMakeVisible (parameterName); | |||
| parameterLabel.setText (parameter.getLabel(), dontSendNotification); | |||
| addAndMakeVisible (parameterLabel); | |||
| if (param.isBoolean()) | |||
| { | |||
| // The AU, AUv3 and VST (only via a .vstxml file) SDKs support | |||
| // marking a parameter as boolean. If you want consistency across | |||
| // all formats then it might be best to use a | |||
| // SwitchParameterComponent instead. | |||
| parameterComp.reset (new BooleanParameterComponent (param)); | |||
| } | |||
| else if (param.getNumSteps() == 2) | |||
| { | |||
| // Most hosts display any parameter with just two steps as a switch. | |||
| parameterComp.reset (new SwitchParameterComponent (param)); | |||
| } | |||
| else if (! param.getAllValueStrings().isEmpty()) | |||
| { | |||
| // If we have a list of strings to represent the different states a | |||
| // parameter can be in then we should present a dropdown allowing a | |||
| // user to pick one of them. | |||
| parameterComp.reset (new ChoiceParameterComponent (param)); | |||
| } | |||
| else | |||
| { | |||
| // Everything else can be represented as a slider. | |||
| parameterComp.reset (new SliderParameterComponent (param)); | |||
| } | |||
| addAndMakeVisible (parameterComp); | |||
| setSize (400, 40); | |||
| } | |||
| void paint (Graphics&) override {} | |||
| void resized() override | |||
| { | |||
| auto area = getLocalBounds(); | |||
| parameterName.setBounds (area.removeFromLeft (100)); | |||
| parameterLabel.setBounds (area.removeFromRight (50)); | |||
| parameterComp->setBounds (area); | |||
| } | |||
| private: | |||
| AudioProcessorParameter& parameter; | |||
| Label parameterName, parameterLabel; | |||
| ScopedPointer<Component> parameterComp; | |||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ParameterDisplayComponent) | |||
| }; | |||
| class ParametersPanel : public Component | |||
| { | |||
| public: | |||
| ParametersPanel (const OwnedArray<AudioProcessorParameter>& parameters) | |||
| { | |||
| for (auto* param : parameters) | |||
| if (param->isAutomatable()) | |||
| addAndMakeVisible (paramComponents.add (new ParameterDisplayComponent (*param))); | |||
| if (auto* comp = paramComponents[0]) | |||
| setSize (comp->getWidth(), comp->getHeight() * paramComponents.size()); | |||
| else | |||
| setSize (400, 100); | |||
| } | |||
| void paint (Graphics& g) override | |||
| { | |||
| g.fillAll (getLookAndFeel().findColour (ResizableWindow::backgroundColourId)); | |||
| } | |||
| void resized() override | |||
| { | |||
| auto area = getLocalBounds(); | |||
| for (auto* comp : paramComponents) | |||
| comp->setBounds (area.removeFromTop (comp->getHeight())); | |||
| } | |||
| private: | |||
| OwnedArray<ParameterDisplayComponent> paramComponents; | |||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ParametersPanel) | |||
| }; | |||
| //============================================================================== | |||
| GenericAudioProcessorEditor::GenericAudioProcessorEditor (AudioProcessor* const p) | |||
| : AudioProcessorEditor (p) | |||
| { | |||
| jassert (p != nullptr); | |||
| setOpaque (true); | |||
| auto& parameters = p->getParameters(); | |||
| if (parameters.size() == p->getNumParameters()) | |||
| view.setViewedComponent (new ParametersPanel (parameters)); | |||
| else | |||
| view.setViewedComponent (new LegacyParametersPanel (p)); | |||
| addAndMakeVisible (view); | |||
| view.setScrollBarsShown (true, false); | |||
| setSize (view.getViewedComponent()->getWidth() + view.getVerticalScrollBar().getWidth(), | |||
| jmin (view.getViewedComponent()->getHeight(), 400)); | |||
| } | |||
| GenericAudioProcessorEditor::~GenericAudioProcessorEditor() {} | |||
| void GenericAudioProcessorEditor::paint (Graphics& g) | |||
| { | |||
| g.fillAll (Colours::white); | |||
| g.fillAll (getLookAndFeel().findColour (ResizableWindow::backgroundColourId)); | |||
| } | |||
| void GenericAudioProcessorEditor::resized() | |||
| { | |||
| panel.setBounds (getLocalBounds()); | |||
| view.setBounds (getLocalBounds()); | |||
| } | |||
| } // namespace juce | |||
| @@ -30,7 +30,7 @@ namespace juce | |||
| //============================================================================== | |||
| /** | |||
| A type of UI component that displays the parameters of an AudioProcessor as | |||
| a simple list of sliders. | |||
| a simple list of sliders, combo boxes and switches. | |||
| This can be used for showing an editor for a processor that doesn't supply | |||
| its own custom editor. | |||
| @@ -50,7 +50,7 @@ public: | |||
| private: | |||
| //============================================================================== | |||
| PropertyPanel panel; | |||
| Viewport view; | |||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (GenericAudioProcessorEditor) | |||
| }; | |||