| @@ -6,36 +6,60 @@ Develop | |||||
| Change | 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 | Possible Issues | ||||
| --------------- | --------------- | ||||
| Any code using InAppPurchases needs to be updated to retrieve a singleton pointer | Any code using InAppPurchases needs to be updated to retrieve a singleton pointer | ||||
| to InAppPurchases. | to InAppPurchases. | ||||
| Workaround | 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. | via InAppPurchases::getInstance(), e.g. | ||||
| instead of: | instead of: | ||||
| InAppPurchases iap; | InAppPurchases iap; | ||||
| iap.purchaseProduct (…); | |||||
| iap.purchaseProduct (...); | |||||
| call: | call: | ||||
| InAppPurchases::getInstance()->purchaseProduct (…); | |||||
| InAppPurchases::getInstance()->purchaseProduct (...); | |||||
| Rationale | Rationale | ||||
| --------- | --------- | ||||
| This change was required to fix an issue on Android where on failed transaction | 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 | Change | ||||
| @@ -35,9 +35,9 @@ class AUv3SynthEditor : public AudioProcessorEditor, | |||||
| public: | public: | ||||
| //============================================================================== | //============================================================================== | ||||
| AUv3SynthEditor (AudioProcessor& processor) | AUv3SynthEditor (AudioProcessor& processor) | ||||
| : AudioProcessorEditor (processor), | |||||
| recordButton ("Record"), | |||||
| roomSizeSlider (Slider::LinearHorizontal, Slider::NoTextBox) | |||||
| : AudioProcessorEditor (processor), | |||||
| recordButton ("Record"), | |||||
| roomSizeSlider (Slider::LinearHorizontal, Slider::NoTextBox) | |||||
| { | { | ||||
| LookAndFeel::setDefaultLookAndFeel (&materialLookAndFeel); | LookAndFeel::setDefaultLookAndFeel (&materialLookAndFeel); | ||||
| @@ -1835,7 +1835,7 @@ private: | |||||
| // using the default number of steps. | // using the default number of steps. | ||||
| for (auto* param : juceFilter->getParameters()) | for (auto* param : juceFilter->getParameters()) | ||||
| if (param->isDiscrete()) | if (param->isDiscrete()) | ||||
| jassert (param->getNumSteps() != juceFilter->getDefaultNumParameterSteps()); | |||||
| jassert (param->getNumSteps() != AudioProcessor::getDefaultNumParameterSteps()); | |||||
| #endif | #endif | ||||
| parameterValueStringArrays.ensureStorageAllocated (numParams); | parameterValueStringArrays.ensureStorageAllocated (numParams); | ||||
| @@ -1483,7 +1483,7 @@ private: | |||||
| return 0; | return 0; | ||||
| } | } | ||||
| void valueChangedForObserver(AUParameterAddress, AUValue) | |||||
| void valueChangedForObserver (AUParameterAddress, AUValue) | |||||
| { | { | ||||
| // this will have already been handled by valueChangedFromHost | // this will have already been handled by valueChangedFromHost | ||||
| } | } | ||||
| @@ -302,6 +302,229 @@ class AudioUnitPluginWindowCocoa; | |||||
| class AudioUnitPluginInstance : public AudioPluginInstance | class AudioUnitPluginInstance : public AudioPluginInstance | ||||
| { | { | ||||
| public: | 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) | AudioUnitPluginInstance (AudioComponentInstance au) | ||||
| : AudioPluginInstance (getBusesProperties (au)), | : AudioPluginInstance (getBusesProperties (au)), | ||||
| auComponent (AudioComponentInstanceGetComponent (au)), | auComponent (AudioComponentInstanceGetComponent (au)), | ||||
| @@ -937,66 +1160,6 @@ public: | |||||
| bool isOutputChannelStereoPair (int index) const override { return isPositiveAndBelow (index, getTotalNumOutputChannels()); } | 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() | void sendAllParametersChangedEvents() | ||||
| { | { | ||||
| #if JUCE_MAC | #if JUCE_MAC | ||||
| @@ -1010,40 +1173,6 @@ public: | |||||
| #endif | #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 | int getNumPrograms() override | ||||
| { | { | ||||
| @@ -1192,7 +1321,7 @@ public: | |||||
| void refreshParameterList() override | void refreshParameterList() override | ||||
| { | { | ||||
| parameters.clear(); | |||||
| managedParameters.clear(); | |||||
| paramIDToIndex.clear(); | paramIDToIndex.clear(); | ||||
| if (audioUnit != nullptr) | if (audioUnit != nullptr) | ||||
| @@ -1221,27 +1350,60 @@ public: | |||||
| kAudioUnitScope_Global, | kAudioUnitScope_Global, | ||||
| ids[i], &info, &sz) == noErr) | ids[i], &info, &sz) == noErr) | ||||
| { | { | ||||
| ParamInfo* const param = new ParamInfo(); | |||||
| parameters.add (param); | |||||
| param->paramID = ids[i]; | |||||
| paramIDToIndex.getReference (ids[i]) = 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) | if ((info.flags & kAudioUnitParameterFlag_HasCFNameString) != 0) | ||||
| { | { | ||||
| param->name = String::fromCFString (info.cfNameString); | |||||
| paramName = String::fromCFString (info.cfNameString); | |||||
| if ((info.flags & kAudioUnitParameterFlag_CFNameRelease) != 0) | if ((info.flags & kAudioUnitParameterFlag_CFNameRelease) != 0) | ||||
| CFRelease (info.cfNameString); | CFRelease (info.cfNameString); | ||||
| } | } | ||||
| else | 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; | AUEventListenerRef eventListenerRef; | ||||
| #endif | #endif | ||||
| struct ParamInfo | |||||
| { | |||||
| UInt32 paramID; | |||||
| String name; | |||||
| AudioUnitParameterValue minValue, maxValue; | |||||
| bool automatable, discrete; | |||||
| int numSteps; | |||||
| }; | |||||
| OwnedArray<ParamInfo> parameters; | |||||
| HashMap<uint32, size_t> paramIDToIndex; | HashMap<uint32, size_t> paramIDToIndex; | ||||
| MidiDataConcatenator midiConcatenator; | MidiDataConcatenator midiConcatenator; | ||||
| @@ -1360,22 +1512,25 @@ private: | |||||
| AUEventListenerCreate (eventListenerCallback, this, CFRunLoopGetMain(), | AUEventListenerCreate (eventListenerCallback, this, CFRunLoopGetMain(), | ||||
| kCFRunLoopDefaultMode, 0, 0, &eventListenerRef); | 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); | addPropertyChangeListener (kAudioUnitProperty_PresentPreset); | ||||
| @@ -1413,25 +1568,36 @@ private: | |||||
| paramIndex = static_cast<int> (paramIDToIndex [paramID]); | paramIndex = static_cast<int> (paramIDToIndex [paramID]); | ||||
| if (! isPositiveAndBelow (paramIndex, parameters.size())) | |||||
| if (! isPositiveAndBelow (paramIndex, getParameters().size())) | |||||
| return; | return; | ||||
| } | } | ||||
| switch (event.mEventType) | switch (event.mEventType) | ||||
| { | { | ||||
| case kAudioUnitEvent_ParameterValueChange: | 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; | break; | ||||
| case kAudioUnitEvent_BeginParameterChangeGesture: | case kAudioUnitEvent_BeginParameterChangeGesture: | ||||
| beginParameterChangeGesture (paramIndex); | |||||
| if (auto* param = getParameters()[paramIndex]) | |||||
| param->beginChangeGesture(); | |||||
| else | |||||
| jassertfalse; // Invalid parameter index | |||||
| break; | break; | ||||
| case kAudioUnitEvent_EndParameterChangeGesture: | case kAudioUnitEvent_EndParameterChangeGesture: | ||||
| endParameterChangeGesture (paramIndex); | |||||
| if (auto* param = getParameters()[paramIndex]) | |||||
| param->endChangeGesture(); | |||||
| else | |||||
| jassertfalse; // Invalid parameter index | |||||
| break; | break; | ||||
| default: | default: | ||||
| @@ -112,11 +112,173 @@ private: | |||||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (LADSPAModuleHandle) | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (LADSPAModuleHandle) | ||||
| }; | }; | ||||
| //============================================================================== | //============================================================================== | ||||
| class LADSPAPluginInstance : public AudioPluginInstance | class LADSPAPluginInstance : public AudioPluginInstance | ||||
| { | { | ||||
| public: | 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) | LADSPAPluginInstance (const LADSPAModuleHandle::Ptr& m) | ||||
| : module (m), plugin (nullptr), handle (nullptr), | : module (m), plugin (nullptr), handle (nullptr), | ||||
| initialised (false), tempBuffer (1, 1) | initialised (false), tempBuffer (1, 1) | ||||
| @@ -180,14 +342,17 @@ public: | |||||
| inputs.clear(); | inputs.clear(); | ||||
| outputs.clear(); | outputs.clear(); | ||||
| parameters.clear(); | |||||
| managedParameters.clear(); | |||||
| for (unsigned int i = 0; i < plugin->PortCount; ++i) | for (unsigned int i = 0; i < plugin->PortCount; ++i) | ||||
| { | { | ||||
| const LADSPA_PortDescriptor portDesc = plugin->PortDescriptors[i]; | const LADSPA_PortDescriptor portDesc = plugin->PortDescriptors[i]; | ||||
| if ((portDesc & LADSPA_PORT_CONTROL) != 0) | 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) | 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); | setPlayConfigDetails (inputs.size(), outputs.size(), initialSampleRate, initialBlockSize); | ||||
| @@ -266,11 +430,11 @@ public: | |||||
| tempBuffer.setSize (jmax (1, outputs.size()), samplesPerBlockExpected); | tempBuffer.setSize (jmax (1, outputs.size()), samplesPerBlockExpected); | ||||
| // dodgy hack to force some plugins to initialise the sample rate.. | // 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) | if (plugin->activate != nullptr) | ||||
| @@ -349,76 +513,15 @@ public: | |||||
| return {}; | 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 getNumPrograms() { return 0; } | ||||
| int getCurrentProgram() { 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) | const String getProgramName (int index) | ||||
| @@ -435,12 +538,15 @@ public: | |||||
| //============================================================================== | //============================================================================== | ||||
| void getStateInformation (MemoryBlock& destData) | void getStateInformation (MemoryBlock& destData) | ||||
| { | { | ||||
| destData.setSize (sizeof (float) * getNumParameters()); | |||||
| auto numParameters = getParameters().size(); | |||||
| destData.setSize (sizeof (float) * numParameters); | |||||
| destData.fillWith (0); | destData.fillWith (0); | ||||
| float* const p = (float*) ((char*) destData.getData()); | 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) | void getCurrentProgramStateInformation (MemoryBlock& destData) | ||||
| @@ -452,8 +558,9 @@ public: | |||||
| { | { | ||||
| const float* p = static_cast<const float*> (data); | 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) | void setCurrentProgramStateInformation (const void* data, int sizeInBytes) | ||||
| @@ -485,82 +592,7 @@ private: | |||||
| CriticalSection lock; | CriticalSection lock; | ||||
| bool initialised; | bool initialised; | ||||
| AudioBuffer<float> tempBuffer; | 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) | 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) | if (index < 0) | ||||
| return kResultFalse; | return kResultFalse; | ||||
| plugin->beginParameterChangeGesture (index); | |||||
| if (auto* param = plugin->getParameters()[index]) | |||||
| param->beginChangeGesture(); | |||||
| else | |||||
| jassertfalse; // Invalid parameter index! | |||||
| } | } | ||||
| return kResultTrue; | return kResultTrue; | ||||
| @@ -298,7 +301,10 @@ struct VST3HostContext : public Vst::IComponentHandler, // From VST V3.0.0 | |||||
| if (index < 0) | if (index < 0) | ||||
| return kResultFalse; | 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; | Steinberg::int32 eventIndex; | ||||
| @@ -322,7 +328,10 @@ struct VST3HostContext : public Vst::IComponentHandler, // From VST V3.0.0 | |||||
| if (index < 0) | if (index < 0) | ||||
| return kResultFalse; | return kResultFalse; | ||||
| plugin->endParameterChangeGesture (index); | |||||
| if (auto* param = plugin->getParameters()[index]) | |||||
| param->endChangeGesture(); | |||||
| else | |||||
| jassertfalse; // Invalid parameter index! | |||||
| } | } | ||||
| return kResultTrue; | return kResultTrue; | ||||
| @@ -1685,6 +1694,118 @@ struct VST3ComponentHolder | |||||
| //============================================================================== | //============================================================================== | ||||
| struct VST3PluginInstance : public AudioPluginInstance | 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) | VST3PluginInstance (VST3ComponentHolder* componentHolder) | ||||
| : AudioPluginInstance (getBusProperties (componentHolder->component)), | : AudioPluginInstance (getBusProperties (componentHolder->component)), | ||||
| holder (componentHolder), | holder (componentHolder), | ||||
| @@ -1752,9 +1873,30 @@ struct VST3PluginInstance : public AudioPluginInstance | |||||
| editController->setComponentHandler (holder->host); | editController->setComponentHandler (holder->host); | ||||
| grabInformationObjects(); | grabInformationObjects(); | ||||
| interconnectComponentAndController(); | 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(); | synchroniseStates(); | ||||
| syncProgramNames(); | syncProgramNames(); | ||||
| setupIO(); | setupIO(); | ||||
| return true; | return true; | ||||
| } | } | ||||
| @@ -2158,93 +2300,6 @@ struct VST3PluginInstance : public AudioPluginInstance | |||||
| return view != nullptr; | 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(); } | int getNumPrograms() override { return programNames.size(); } | ||||
| const String getProgramName (int index) override { return programNames[index]; } | const String getProgramName (int index) override { return programNames[index]; } | ||||
| @@ -248,6 +248,330 @@ namespace | |||||
| #endif | #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 | struct ModuleHandle : public ReferenceCountedObject | ||||
| { | { | ||||
| @@ -498,12 +822,158 @@ static const int defaultVSTBlockSizeValue = 512; | |||||
| #pragma warning (disable: 4996) // warning about overriding deprecated methods | #pragma warning (disable: 4996) // warning about overriding deprecated methods | ||||
| #endif | #endif | ||||
| //============================================================================== | |||||
| //============================================================================== | //============================================================================== | ||||
| struct VSTPluginInstance : public AudioPluginInstance, | struct VSTPluginInstance : public AudioPluginInstance, | ||||
| private Timer, | private Timer, | ||||
| private AsyncUpdater | 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, | VSTPluginInstance (const ModuleHandle::Ptr& mh, const BusesProperties& ioConfig, VstEffectInterface* effect, | ||||
| double sampleRateToUse, int blockSizeToUse) | double sampleRateToUse, int blockSizeToUse) | ||||
| : AudioPluginInstance (ioConfig), | : AudioPluginInstance (ioConfig), | ||||
| @@ -511,6 +981,88 @@ struct VSTPluginInstance : public AudioPluginInstance, | |||||
| vstModule (mh), | vstModule (mh), | ||||
| name (mh->pluginName) | 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); | setRateAndBufferSizeDetails (sampleRateToUse, blockSizeToUse); | ||||
| } | } | ||||
| @@ -802,11 +1354,14 @@ struct VSTPluginInstance : public AudioPluginInstance, | |||||
| setPower (true); | setPower (true); | ||||
| // dodgy hack to force some plugins to initialise the sample rate.. | // 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); | dispatch (plugInOpcodeStartProcess, 0, 0, 0, 0); | ||||
| @@ -943,46 +1498,6 @@ struct VSTPluginInstance : public AudioPluginInstance, | |||||
| : getTotalNumOutputChannels()); | : 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; } | int getNumPrograms() override { return vstEffect != nullptr ? jmax (0, vstEffect->numPrograms) : 0; } | ||||
| @@ -1051,7 +1566,14 @@ struct VSTPluginInstance : public AudioPluginInstance, | |||||
| { | { | ||||
| switch (opcode) | 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 hostOpcodePreAudioProcessingEvents: handleMidiFromPlugin ((const VstEventBlock*) ptr); break; | ||||
| case hostOpcodeGetTimingInfo: return getVSTTime(); | case hostOpcodeGetTimingInfo: return getVSTTime(); | ||||
| case hostOpcodeIdle: handleIdle(); break; | 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 hostOpcodeTempoAt: return (pointer_sized_int) (extraFunctions != nullptr ? extraFunctions->getTempoAt ((int64) value) : 0); | ||||
| case hostOpcodeGetAutomationState: return (pointer_sized_int) (extraFunctions != nullptr ? extraFunctions->getAutomationState() : 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 hostOpcodePinConnected: return isValidChannel (index, value == 0) ? 0 : 1; // (yes, 0 = true) | ||||
| case hostOpcodeGetCurrentAudioProcessingLevel: return isNonRealtime() ? 4 : 0; | case hostOpcodeGetCurrentAudioProcessingLevel: return isNonRealtime() ? 4 : 0; | ||||
| @@ -1226,7 +1761,8 @@ struct VSTPluginInstance : public AudioPluginInstance, | |||||
| changeProgramName (getCurrentProgram(), prog->prgName); | changeProgramName (getCurrentProgram(), prog->prgName); | ||||
| for (int i = 0; i < fxbSwap (prog->numParams); ++i) | 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")) | else if (compareMagic (set->fxMagic, "FBCh")) | ||||
| { | { | ||||
| @@ -1261,7 +1797,7 @@ struct VSTPluginInstance : public AudioPluginInstance, | |||||
| bool saveToFXBFile (MemoryBlock& dest, bool isFXB, int maxSizeMB = 128) | bool saveToFXBFile (MemoryBlock& dest, bool isFXB, int maxSizeMB = 128) | ||||
| { | { | ||||
| auto numPrograms = getNumPrograms(); | auto numPrograms = getNumPrograms(); | ||||
| auto numParams = getNumParameters(); | |||||
| auto numParams = getParameters().size(); | |||||
| if (usesChunks()) | if (usesChunks()) | ||||
| { | { | ||||
| @@ -1413,6 +1949,8 @@ private: | |||||
| AudioBuffer<double> tmpBufferDouble; | AudioBuffer<double> tmpBufferDouble; | ||||
| HeapBlock<double*> channelBufferDouble; | HeapBlock<double*> channelBufferDouble; | ||||
| ScopedPointer<VSTXMLInfo> xmlInfo; | |||||
| static pointer_sized_int handleCanDo (const char* name) | static pointer_sized_int handleCanDo (const char* name) | ||||
| { | { | ||||
| static const char* canDos[] = { "supplyIdle", | static const char* canDos[] = { "supplyIdle", | ||||
| @@ -1832,7 +2370,8 @@ private: | |||||
| changeProgramName (getCurrentProgram(), prog->prgName); | changeProgramName (getCurrentProgram(), prog->prgName); | ||||
| for (int i = 0; i < fxbSwap (prog->numParams); ++i) | 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; | return true; | ||||
| } | } | ||||
| @@ -1879,7 +2418,7 @@ private: | |||||
| void setParamsInProgramBlock (fxProgram* prog) | void setParamsInProgramBlock (fxProgram* prog) | ||||
| { | { | ||||
| auto numParams = getNumParameters(); | |||||
| auto numParams = getParameters().size(); | |||||
| prog->chunkMagic = fxbName ("CcnK"); | prog->chunkMagic = fxbName ("CcnK"); | ||||
| prog->byteSize = 0; | prog->byteSize = 0; | ||||
| @@ -1892,7 +2431,8 @@ private: | |||||
| getCurrentProgramName().copyToUTF8 (prog->prgName, sizeof (prog->prgName) - 1); | getCurrentProgramName().copyToUTF8 (prog->prgName, sizeof (prog->prgName) - 1); | ||||
| for (int i = 0; i < numParams; ++i) | 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() | void updateStoredProgramNames() | ||||
| @@ -1932,15 +2472,17 @@ private: | |||||
| //============================================================================== | //============================================================================== | ||||
| void createTempParameterStore (MemoryBlock& dest) | 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); | dest.fillWith (0); | ||||
| getCurrentProgramName().copyToUTF8 ((char*) dest.getData(), 63); | getCurrentProgramName().copyToUTF8 ((char*) dest.getData(), 63); | ||||
| auto p = (float*) (((char*) dest.getData()) + 64); | 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) | void restoreFromTempParameterStore (const MemoryBlock& m) | ||||
| @@ -1948,9 +2490,11 @@ private: | |||||
| changeProgramName (getCurrentProgram(), (const char*) m.getData()); | changeProgramName (getCurrentProgram(), (const char*) m.getData()); | ||||
| auto p = (float*) (((char*) m.getData()) + 64); | 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 | pointer_sized_int getVstDirectory() const | ||||
| @@ -155,6 +155,7 @@ struct AutoResizingNSViewComponentWithParent : public AutoResizingNSViewCompone | |||||
| #include "format/juce_AudioPluginFormat.cpp" | #include "format/juce_AudioPluginFormat.cpp" | ||||
| #include "format/juce_AudioPluginFormatManager.cpp" | #include "format/juce_AudioPluginFormatManager.cpp" | ||||
| #include "processors/juce_AudioProcessor.cpp" | #include "processors/juce_AudioProcessor.cpp" | ||||
| #include "processors/juce_AudioPluginInstance.cpp" | |||||
| #include "processors/juce_AudioProcessorEditor.cpp" | #include "processors/juce_AudioProcessorEditor.cpp" | ||||
| #include "processors/juce_AudioProcessorGraph.cpp" | #include "processors/juce_AudioProcessorGraph.cpp" | ||||
| #include "processors/juce_GenericAudioProcessorEditor.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. | /** Returns a PluginDescription for this plugin. | ||||
| This is just a convenience method to avoid calling fillInPluginDescription. | 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. | /** 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 | 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() {} | 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: | 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() {} | ||||
| AudioPluginInstance (const BusesProperties& ioLayouts) : AudioProcessor (ioLayouts) {} | AudioPluginInstance (const BusesProperties& ioLayouts) : AudioProcessor (ioLayouts) {} | ||||
| template <int numLayouts> | template <int numLayouts> | ||||
| AudioPluginInstance (const short channelLayoutList[numLayouts][2]) : AudioProcessor (channelLayoutList) {} | AudioPluginInstance (const short channelLayoutList[numLayouts][2]) : AudioProcessor (channelLayoutList) {} | ||||
| private: | |||||
| void assertOnceOnDeprecatedMethodUse() const noexcept; | |||||
| static bool deprecationAssertiontriggered; | |||||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AudioPluginInstance) | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AudioPluginInstance) | ||||
| }; | }; | ||||
| @@ -673,10 +673,6 @@ void AudioProcessor::addParameter (AudioProcessorParameter* p) | |||||
| p->parameterIndex = managedParameters.size(); | p->parameterIndex = managedParameters.size(); | ||||
| managedParameters.add (p); | 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 | #ifdef JUCE_DEBUG | ||||
| shouldCheckParamsForDupeIDs = true; | shouldCheckParamsForDupeIDs = true; | ||||
| #endif | #endif | ||||
| @@ -1354,14 +1350,21 @@ int32 AudioProcessor::getAAXPluginIDForMainBusConfig (const AudioChannelSet& mai | |||||
| return (idForAudioSuite ? 0x6a796161 /* 'jyaa' */ : 0x6a636161 /* 'jcaa' */) + uniqueFormatId; | return (idForAudioSuite ? 0x6a796161 /* 'jyaa' */ : 0x6a636161 /* 'jcaa' */) + uniqueFormatId; | ||||
| } | } | ||||
| //============================================================================== | //============================================================================== | ||||
| void AudioProcessorListener::audioProcessorParameterChangeGestureBegin (AudioProcessor*, int) {} | void AudioProcessorListener::audioProcessorParameterChangeGestureBegin (AudioProcessor*, int) {} | ||||
| void AudioProcessorListener::audioProcessorParameterChangeGestureEnd (AudioProcessor*, int) {} | void AudioProcessorListener::audioProcessorParameterChangeGestureEnd (AudioProcessor*, int) {} | ||||
| //============================================================================== | //============================================================================== | ||||
| AudioProcessorParameter::AudioProcessorParameter() noexcept {} | 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) | void AudioProcessorParameter::setValueNotifyingHost (float newValue) | ||||
| { | { | ||||
| @@ -1452,6 +1455,24 @@ String AudioProcessorParameter::getText (float value, int /*maximumStringLength* | |||||
| return String (value, 2); | 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) | void AudioProcessorParameter::addListener (AudioProcessorParameter::Listener* newListener) | ||||
| { | { | ||||
| const ScopedLock sl (listenerLock); | const ScopedLock sl (listenerLock); | ||||
| @@ -1620,6 +1620,8 @@ private: | |||||
| friend class JuceVST3EditController; | friend class JuceVST3EditController; | ||||
| friend class JuceVST3Component; | friend class JuceVST3Component; | ||||
| friend class AudioUnitPluginInstance; | |||||
| friend class LADSPAPluginInstance; | |||||
| Atomic<int> vst3IsPlaying { 0 }; | Atomic<int> vst3IsPlaying { 0 }; | ||||
| @@ -199,6 +199,28 @@ public: | |||||
| /** Returns the index of this parameter in its parent processor's parameter list. */ | /** Returns the index of this parameter in its parent processor's parameter list. */ | ||||
| int getParameterIndex() const noexcept { return parameterIndex; } | 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 | A base class for listeners that want to know about changes to an | ||||
| @@ -267,6 +289,7 @@ private: | |||||
| int parameterIndex = -1; | int parameterIndex = -1; | ||||
| CriticalSection listenerLock; | CriticalSection listenerLock; | ||||
| Array<Listener*> listeners; | Array<Listener*> listeners; | ||||
| mutable StringArray valueStrings; | |||||
| #if JUCE_DEBUG | #if JUCE_DEBUG | ||||
| bool isPerformingGesture = false; | bool isPerformingGesture = false; | ||||
| @@ -114,17 +114,17 @@ private: | |||||
| void startedDragging() override | void startedDragging() override | ||||
| { | { | ||||
| owner.beginParameterChangeGesture(index); | |||||
| owner.beginParameterChangeGesture (index); | |||||
| } | } | ||||
| void stoppedDragging() override | void stoppedDragging() override | ||||
| { | { | ||||
| owner.endParameterChangeGesture(index); | |||||
| owner.endParameterChangeGesture (index); | |||||
| } | } | ||||
| String getTextFromValue (double /*value*/) override | String getTextFromValue (double /*value*/) override | ||||
| { | { | ||||
| return owner.getParameterText (index) + " " + owner.getParameterLabel (index).trimEnd(); | |||||
| return (owner.getParameterText (index) + " " + owner.getParameterLabel (index)).trimEnd(); | |||||
| } | } | ||||
| private: | private: | ||||
| @@ -143,50 +143,540 @@ private: | |||||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ProcessorParameterPropertyComp) | 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) | void GenericAudioProcessorEditor::paint (Graphics& g) | ||||
| { | { | ||||
| g.fillAll (Colours::white); | |||||
| g.fillAll (getLookAndFeel().findColour (ResizableWindow::backgroundColourId)); | |||||
| } | } | ||||
| void GenericAudioProcessorEditor::resized() | void GenericAudioProcessorEditor::resized() | ||||
| { | { | ||||
| panel.setBounds (getLocalBounds()); | |||||
| view.setBounds (getLocalBounds()); | |||||
| } | } | ||||
| } // namespace juce | } // namespace juce | ||||
| @@ -30,7 +30,7 @@ namespace juce | |||||
| //============================================================================== | //============================================================================== | ||||
| /** | /** | ||||
| A type of UI component that displays the parameters of an AudioProcessor as | 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 | This can be used for showing an editor for a processor that doesn't supply | ||||
| its own custom editor. | its own custom editor. | ||||
| @@ -50,7 +50,7 @@ public: | |||||
| private: | private: | ||||
| //============================================================================== | //============================================================================== | ||||
| PropertyPanel panel; | |||||
| Viewport view; | |||||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (GenericAudioProcessorEditor) | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (GenericAudioProcessorEditor) | ||||
| }; | }; | ||||