Browse Source

Added host-side AudioProcessorParameter implementations, deprecated the old methods for managing parameters, and updated the GenericAudioProcessorEditor

tags/2021-05-28
Tom Poole 7 years ago
parent
commit
611971181f
16 changed files with 2110 additions and 489 deletions
  1. +33
    -9
      BREAKING-CHANGES.txt
  2. +3
    -3
      examples/AUv3Synth/Source/AUv3SynthEditor.h
  3. +1
    -1
      modules/juce_audio_plugin_client/AU/juce_AU_Wrapper.mm
  4. +1
    -1
      modules/juce_audio_plugin_client/AU/juce_AUv3_Wrapper.mm
  5. +298
    -132
      modules/juce_audio_processors/format_types/juce_AudioUnitPluginFormat.mm
  6. +189
    -157
      modules/juce_audio_processors/format_types/juce_LADSPAPluginFormat.cpp
  7. +145
    -90
      modules/juce_audio_processors/format_types/juce_VST3PluginFormat.cpp
  8. +602
    -58
      modules/juce_audio_processors/format_types/juce_VSTPluginFormat.cpp
  9. +1
    -0
      modules/juce_audio_processors/juce_audio_processors.cpp
  10. +230
    -0
      modules/juce_audio_processors/processors/juce_AudioPluginInstance.cpp
  11. +39
    -6
      modules/juce_audio_processors/processors/juce_AudioPluginInstance.h
  12. +27
    -6
      modules/juce_audio_processors/processors/juce_AudioProcessor.cpp
  13. +2
    -0
      modules/juce_audio_processors/processors/juce_AudioProcessor.h
  14. +23
    -0
      modules/juce_audio_processors/processors/juce_AudioProcessorParameter.h
  15. +514
    -24
      modules/juce_audio_processors/processors/juce_GenericAudioProcessorEditor.cpp
  16. +2
    -2
      modules/juce_audio_processors/processors/juce_GenericAudioProcessorEditor.h

+ 33
- 9
BREAKING-CHANGES.txt View File

@@ -6,36 +6,60 @@ Develop
Change
------
InAppPurchases class is now a JUCE Singleton. This means that you need
to get an instance via InAppPurchases::getInstance(), instead of storing a
InAppPurchases object yourself.
When hosting plug-ins all AudioProcessor methods of managing parameters that
take a parameter index as an argument have been deprecated.
Possible Issues
---------------
A single assertion will be fired in debug builds on the first use of a
deprecated function.
Workaround
----------
When hosting plug-ins you should use the AudioProcessor::getParameters() method
and interact with parameters via the returned array of
AudioProcessorParameters. For a short-term fix you can also continue past the
assertion in your debugger, or temporarily modify the JUCE source code to
remove it.
Rationale
---------
Given the structure of JUCE's API it is impossible to deprecate these functions
using only complile-time messages. Therefore a single assertion, which can be
safely ignored, serves to indicate that these functions should no longer be
used. The move away from the AudioProcessor methods both improves the interface
to that class and makes ongoing development work much easier.
Change
------
This InAppPurchases class is now a JUCE Singleton. This means that you need
to get an instance via InAppPurchases::getInstance(), instead of storing a
InAppPurchases object yourself.
Possible Issues
---------------
Any code using InAppPurchases needs to be updated to retrieve a singleton pointer
to InAppPurchases.
Workaround
----------
Instead of holding a InAppPurchase member yourself, you should get an instance
Instead of holding a InAppPurchase member yourself, you should get an instance
via InAppPurchases::getInstance(), e.g.
instead of:
InAppPurchases iap;
iap.purchaseProduct (…);
iap.purchaseProduct (...);
call:
InAppPurchases::getInstance()->purchaseProduct (…);
InAppPurchases::getInstance()->purchaseProduct (...);
Rationale
---------
This change was required to fix an issue on Android where on failed transaction
a listener would not get called.
a listener would not get called.
Change


+ 3
- 3
examples/AUv3Synth/Source/AUv3SynthEditor.h View File

@@ -35,9 +35,9 @@ class AUv3SynthEditor : public AudioProcessorEditor,
public:
//==============================================================================
AUv3SynthEditor (AudioProcessor& processor)
: AudioProcessorEditor (processor),
recordButton ("Record"),
roomSizeSlider (Slider::LinearHorizontal, Slider::NoTextBox)
: AudioProcessorEditor (processor),
recordButton ("Record"),
roomSizeSlider (Slider::LinearHorizontal, Slider::NoTextBox)
{
LookAndFeel::setDefaultLookAndFeel (&materialLookAndFeel);


+ 1
- 1
modules/juce_audio_plugin_client/AU/juce_AU_Wrapper.mm View File

@@ -1835,7 +1835,7 @@ private:
// using the default number of steps.
for (auto* param : juceFilter->getParameters())
if (param->isDiscrete())
jassert (param->getNumSteps() != juceFilter->getDefaultNumParameterSteps());
jassert (param->getNumSteps() != AudioProcessor::getDefaultNumParameterSteps());
#endif
parameterValueStringArrays.ensureStorageAllocated (numParams);


+ 1
- 1
modules/juce_audio_plugin_client/AU/juce_AUv3_Wrapper.mm View File

@@ -1483,7 +1483,7 @@ private:
return 0;
}
void valueChangedForObserver(AUParameterAddress, AUValue)
void valueChangedForObserver (AUParameterAddress, AUValue)
{
// this will have already been handled by valueChangedFromHost
}


+ 298
- 132
modules/juce_audio_processors/format_types/juce_AudioUnitPluginFormat.mm View File

@@ -302,6 +302,229 @@ class AudioUnitPluginWindowCocoa;
class AudioUnitPluginInstance : public AudioPluginInstance
{
public:
struct AUInstanceParameter final : public Parameter
{
AUInstanceParameter (AudioUnitPluginInstance& parent,
UInt32 parameterID,
const String& parameterName,
AudioUnitParameterValue minParameterValue,
AudioUnitParameterValue maxParameterValue,
AudioUnitParameterValue defaultParameterValue,
bool parameterIsAutomatable,
bool parameterIsDiscrete,
int numParameterSteps,
bool isBoolean,
const String& label,
bool parameterValuesHaveStrings)
: pluginInstance (parent),
paramID (parameterID),
name (parameterName),
minValue (minParameterValue),
maxValue (maxParameterValue),
range (maxValue - minValue),
automatable (parameterIsAutomatable),
discrete (parameterIsDiscrete),
numSteps (numParameterSteps),
valuesHaveStrings (parameterValuesHaveStrings),
isSwitch (isBoolean),
valueLabel (label),
defaultValue (normaliseParamValue (defaultParameterValue))
{
auValueStrings = Parameter::getAllValueStrings();
}
virtual float getValue() const override
{
const ScopedLock sl (pluginInstance.lock);
AudioUnitParameterValue value = 0;
if (auto* au = pluginInstance.audioUnit)
{
AudioUnitGetParameter (au,
paramID,
kAudioUnitScope_Global,
0,
&value);
value = normaliseParamValue (value);
}
return value;
}
virtual void setValue (float newValue) override
{
const ScopedLock sl (pluginInstance.lock);
if (auto* au = pluginInstance.audioUnit)
{
AudioUnitSetParameter (au,
paramID,
kAudioUnitScope_Global,
0,
scaleParamValue (newValue),
0);
sendParameterChangeEvent();
}
}
float getDefaultValue() const override
{
return defaultValue;
}
String getName (int /*maximumStringLength*/) const override
{
return name;
}
String getLabel() const override
{
return valueLabel;
}
String getText (float value, int maximumLength) const override
{
if (! auValueStrings.isEmpty())
{
auto index = roundToInt (jlimit (0.0f, 1.0f, value) * (auValueStrings.size() - 1));
return auValueStrings[index];
}
auto scaledValue = scaleParamValue (value);
if (valuesHaveStrings)
{
if (auto* au = pluginInstance.audioUnit)
{
AudioUnitParameterStringFromValue stringValue;
stringValue.inParamID = paramID;
stringValue.inValue = &scaledValue;
stringValue.outString = nullptr;
UInt32 propertySize = sizeof (stringValue);
OSStatus err = AudioUnitGetProperty (au,
kAudioUnitProperty_ParameterStringFromValue,
kAudioUnitScope_Global,
0,
&stringValue,
&propertySize);
if (! err && stringValue.outString != nullptr)
return String::fromCFString (stringValue.outString).substring (0, maximumLength);
}
}
return Parameter::getText (scaledValue, maximumLength);
}
float getValueForText (const String& text) const override
{
if (! auValueStrings.isEmpty())
{
auto index = auValueStrings.indexOf (text);
if (index != -1)
return ((float) index) / (auValueStrings.size() - 1);
}
if (valuesHaveStrings)
{
if (auto* au = pluginInstance.audioUnit)
{
AudioUnitParameterValueFromString valueString;
valueString.inParamID = paramID;
valueString.inString = text.toCFString();
UInt32 propertySize = sizeof (valueString);
OSStatus err = AudioUnitGetProperty (au,
kAudioUnitProperty_ParameterValueFromString,
kAudioUnitScope_Global,
0,
&valueString,
&propertySize);
if (! err)
return normaliseParamValue (valueString.outValue);
}
}
return Parameter::getValueForText (text);
}
bool isAutomatable() const override
{
return automatable;
}
bool isDiscrete() const override
{
return discrete;
}
bool isBoolean() const override
{
return isSwitch;
}
int getNumSteps() const override
{
return numSteps;
}
StringArray getAllValueStrings() const override
{
return auValueStrings;
}
void sendParameterChangeEvent()
{
#if JUCE_MAC
jassert (pluginInstance.audioUnit != nullptr);
AudioUnitEvent ev;
ev.mEventType = kAudioUnitEvent_ParameterValueChange;
ev.mArgument.mParameter.mAudioUnit = pluginInstance.audioUnit;
ev.mArgument.mParameter.mParameterID = paramID;
ev.mArgument.mParameter.mScope = kAudioUnitScope_Global;
ev.mArgument.mParameter.mElement = 0;
AUEventListenerNotify (pluginInstance.eventListenerRef, nullptr, &ev);
#endif
}
float normaliseParamValue (float scaledValue) const noexcept
{
if (discrete)
return scaledValue / (getNumSteps() - 1);
return (scaledValue - minValue) / range;
}
float scaleParamValue (float normalisedValue) const noexcept
{
if (discrete)
return normalisedValue * (getNumSteps() - 1);
return minValue + (range * normalisedValue);
}
AudioUnitPluginInstance& pluginInstance;
const UInt32 paramID;
const String name;
const AudioUnitParameterValue minValue, maxValue, range;
const bool automatable, discrete;
const int numSteps;
const bool valuesHaveStrings, isSwitch;
const String valueLabel;
const AudioUnitParameterValue defaultValue;
StringArray auValueStrings;
};
AudioUnitPluginInstance (AudioComponentInstance au)
: AudioPluginInstance (getBusesProperties (au)),
auComponent (AudioComponentInstanceGetComponent (au)),
@@ -937,66 +1160,6 @@ public:
bool isOutputChannelStereoPair (int index) const override { return isPositiveAndBelow (index, getTotalNumOutputChannels()); }
//==============================================================================
int getNumParameters() override { return parameters.size(); }
float getParameter (int index) override
{
const ScopedLock sl (lock);
AudioUnitParameterValue value = 0;
if (audioUnit != nullptr)
{
if (const ParamInfo* p = parameters[index])
{
AudioUnitGetParameter (audioUnit,
p->paramID,
kAudioUnitScope_Global, 0,
&value);
value = (value - p->minValue) / (p->maxValue - p->minValue);
}
}
return value;
}
void setParameter (int index, float newValue) override
{
const ScopedLock sl (lock);
if (audioUnit != nullptr)
{
if (const ParamInfo* p = parameters[index])
{
AudioUnitSetParameter (audioUnit, p->paramID, kAudioUnitScope_Global, 0,
p->minValue + (p->maxValue - p->minValue) * newValue, 0);
sendParameterChangeEvent (index);
}
}
}
void sendParameterChangeEvent (int index)
{
#if JUCE_MAC
jassert (audioUnit != nullptr);
const ParamInfo& p = *parameters.getUnchecked (index);
AudioUnitEvent ev;
ev.mEventType = kAudioUnitEvent_ParameterValueChange;
ev.mArgument.mParameter.mAudioUnit = audioUnit;
ev.mArgument.mParameter.mParameterID = p.paramID;
ev.mArgument.mParameter.mScope = kAudioUnitScope_Global;
ev.mArgument.mParameter.mElement = 0;
AUEventListenerNotify (eventListenerRef, nullptr, &ev);
#else
ignoreUnused (index);
#endif
}
void sendAllParametersChangedEvents()
{
#if JUCE_MAC
@@ -1010,40 +1173,6 @@ public:
#endif
}
const String getParameterName (int index) override
{
if (auto* p = parameters[index])
return p->name;
return {};
}
const String getParameterText (int index) override { return String (getParameter (index)); }
int getParameterNumSteps (int index) override
{
if (auto* p = parameters[index])
return p->numSteps;
return AudioProcessor::getDefaultNumParameterSteps();
}
bool isParameterDiscrete (int index) const override
{
if (auto* p = parameters[index])
return p->discrete;
return false;
}
bool isParameterAutomatable (int index) const override
{
if (auto* p = parameters[index])
return p->automatable;
return false;
}
//==============================================================================
int getNumPrograms() override
{
@@ -1192,7 +1321,7 @@ public:
void refreshParameterList() override
{
parameters.clear();
managedParameters.clear();
paramIDToIndex.clear();
if (audioUnit != nullptr)
@@ -1221,27 +1350,60 @@ public:
kAudioUnitScope_Global,
ids[i], &info, &sz) == noErr)
{
ParamInfo* const param = new ParamInfo();
parameters.add (param);
param->paramID = ids[i];
paramIDToIndex.getReference (ids[i]) = i;
param->minValue = info.minValue;
param->maxValue = info.maxValue;
param->automatable = (info.flags & kAudioUnitParameterFlag_NonRealTime) == 0;
param->discrete = (info.unit == kAudioUnitParameterUnit_Indexed);
param->numSteps = param->discrete ? (int) (info.maxValue + 1.0f) : AudioProcessor::getDefaultNumParameterSteps();
String paramName;
if ((info.flags & kAudioUnitParameterFlag_HasCFNameString) != 0)
{
param->name = String::fromCFString (info.cfNameString);
paramName = String::fromCFString (info.cfNameString);
if ((info.flags & kAudioUnitParameterFlag_CFNameRelease) != 0)
CFRelease (info.cfNameString);
}
else
{
param->name = String (info.name, sizeof (info.name));
paramName = String (info.name, sizeof (info.name));
}
bool isDiscrete = (info.unit == kAudioUnitParameterUnit_Indexed
|| info.unit == kAudioUnitParameterUnit_Boolean);
bool isBoolean = info.unit == kAudioUnitParameterUnit_Boolean;
String label;
switch (info.unit)
{
case kAudioUnitParameterUnit_Percent:
label = "%";
break;
case kAudioUnitParameterUnit_Seconds:
label = "s";
break;
case kAudioUnitParameterUnit_Hertz:
label = "Hz";
break;
case kAudioUnitParameterUnit_Decibels:
label = "dB";
break;
case kAudioUnitParameterUnit_Milliseconds:
label = "ms";
break;
default:
break;
}
addParameter (new AUInstanceParameter (*this,
ids[i],
paramName,
info.minValue,
info.maxValue,
info.defaultValue,
(info.flags & kAudioUnitParameterFlag_NonRealTime) == 0,
isDiscrete,
isDiscrete ? (int) (info.maxValue + 1.0f) : AudioProcessor::getDefaultNumParameterSteps(),
isBoolean,
label,
(info.flags & kAudioUnitParameterFlag_ValuesHaveStrings) != 0));
}
}
}
@@ -1310,16 +1472,6 @@ private:
AUEventListenerRef eventListenerRef;
#endif
struct ParamInfo
{
UInt32 paramID;
String name;
AudioUnitParameterValue minValue, maxValue;
bool automatable, discrete;
int numSteps;
};
OwnedArray<ParamInfo> parameters;
HashMap<uint32, size_t> paramIDToIndex;
MidiDataConcatenator midiConcatenator;
@@ -1360,22 +1512,25 @@ private:
AUEventListenerCreate (eventListenerCallback, this, CFRunLoopGetMain(),
kCFRunLoopDefaultMode, 0, 0, &eventListenerRef);
for (int i = 0; i < parameters.size(); ++i)
for (auto* param : getParameters())
{
AudioUnitEvent event;
event.mArgument.mParameter.mAudioUnit = audioUnit;
event.mArgument.mParameter.mParameterID = parameters.getUnchecked(i)->paramID;
event.mArgument.mParameter.mScope = kAudioUnitScope_Global;
event.mArgument.mParameter.mElement = 0;
if (auto* auParam = dynamic_cast<AUInstanceParameter*> (param))
{
AudioUnitEvent event;
event.mArgument.mParameter.mAudioUnit = audioUnit;
event.mArgument.mParameter.mParameterID = auParam->paramID;
event.mArgument.mParameter.mScope = kAudioUnitScope_Global;
event.mArgument.mParameter.mElement = 0;
event.mEventType = kAudioUnitEvent_ParameterValueChange;
AUEventListenerAddEventType (eventListenerRef, nullptr, &event);
event.mEventType = kAudioUnitEvent_ParameterValueChange;
AUEventListenerAddEventType (eventListenerRef, nullptr, &event);
event.mEventType = kAudioUnitEvent_BeginParameterChangeGesture;
AUEventListenerAddEventType (eventListenerRef, nullptr, &event);
event.mEventType = kAudioUnitEvent_BeginParameterChangeGesture;
AUEventListenerAddEventType (eventListenerRef, nullptr, &event);
event.mEventType = kAudioUnitEvent_EndParameterChangeGesture;
AUEventListenerAddEventType (eventListenerRef, nullptr, &event);
event.mEventType = kAudioUnitEvent_EndParameterChangeGesture;
AUEventListenerAddEventType (eventListenerRef, nullptr, &event);
}
}
addPropertyChangeListener (kAudioUnitProperty_PresentPreset);
@@ -1413,25 +1568,36 @@ private:
paramIndex = static_cast<int> (paramIDToIndex [paramID]);
if (! isPositiveAndBelow (paramIndex, parameters.size()))
if (! isPositiveAndBelow (paramIndex, getParameters().size()))
return;
}
switch (event.mEventType)
{
case kAudioUnitEvent_ParameterValueChange:
if (auto* param = getParameters().getUnchecked (paramIndex))
{
auto& p = *parameters.getUnchecked (paramIndex);
sendParamChangeMessageToListeners (paramIndex, (newValue - p.minValue) / (p.maxValue - p.minValue));
jassert (dynamic_cast<AUInstanceParameter*> (param) != nullptr);
auto* auparam = static_cast<AUInstanceParameter*> (param);
param->sendValueChangedMessageToListeners (auparam->normaliseParamValue (newValue));
}
break;
case kAudioUnitEvent_BeginParameterChangeGesture:
beginParameterChangeGesture (paramIndex);
if (auto* param = getParameters()[paramIndex])
param->beginChangeGesture();
else
jassertfalse; // Invalid parameter index
break;
case kAudioUnitEvent_EndParameterChangeGesture:
endParameterChangeGesture (paramIndex);
if (auto* param = getParameters()[paramIndex])
param->endChangeGesture();
else
jassertfalse; // Invalid parameter index
break;
default:


+ 189
- 157
modules/juce_audio_processors/format_types/juce_LADSPAPluginFormat.cpp View File

@@ -112,11 +112,173 @@ private:
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (LADSPAModuleHandle)
};
//==============================================================================
class LADSPAPluginInstance : public AudioPluginInstance
{
public:
struct LADSPAParameter final : public Parameter
{
struct ParameterValue
{
inline ParameterValue() noexcept : scaled (0), unscaled (0) {}
inline ParameterValue (float s, float u) noexcept : scaled (s), unscaled (u) {}
float scaled, unscaled;
};
LADSPAParameter (LADSPAPluginInstance& parent,
int parameterID,
const String& parameterName,
bool parameterIsAutomatable)
: pluginInstance (parent),
paramID (parameterID),
name (parameterName),
automatable (parameterIsAutomatable)
{
reset();
}
virtual float getValue() const override
{
if (pluginInstance.plugin != nullptr)
{
const ScopedLock sl (pluginInstance.lock);
return paramValue.unscaled;
}
return 0.0f;
}
String getCurrentValueAsText() const override
{
if (auto* interface = pluginInstance.plugin)
{
const LADSPA_PortRangeHint& hint = interface->PortRangeHints[paramID];
if (LADSPA_IS_HINT_INTEGER (hint.HintDescriptor))
return String ((int) paramValue.scaled);
return String (paramValue.scaled, 4);
}
return {};
}
virtual void setValue (float newValue) override
{
if (auto* interface = pluginInstance.plugin)
{
const ScopedLock sl (pluginInstance.lock);
if (paramValue.unscaled != newValue)
paramValue = ParameterValue (getNewParamScaled (interface->PortRangeHints [paramID], newValue), newValue);
}
}
float getDefaultValue() const override
{
return defaultValue;
}
ParameterValue getDefaultParamValue() const
{
if (auto* interface = pluginInstance.plugin)
{
const LADSPA_PortRangeHint& hint = interface->PortRangeHints[paramID];
const LADSPA_PortRangeHintDescriptor& desc = hint.HintDescriptor;
if (LADSPA_IS_HINT_HAS_DEFAULT (desc))
{
if (LADSPA_IS_HINT_DEFAULT_0 (desc)) return ParameterValue();
if (LADSPA_IS_HINT_DEFAULT_1 (desc)) return ParameterValue (1.0f, 1.0f);
if (LADSPA_IS_HINT_DEFAULT_100 (desc)) return ParameterValue (100.0f, 0.5f);
if (LADSPA_IS_HINT_DEFAULT_440 (desc)) return ParameterValue (440.0f, 0.5f);
const float scale = LADSPA_IS_HINT_SAMPLE_RATE (desc) ? (float) pluginInstance.getSampleRate() : 1.0f;
const float lower = hint.LowerBound * scale;
const float upper = hint.UpperBound * scale;
if (LADSPA_IS_HINT_BOUNDED_BELOW (desc) && LADSPA_IS_HINT_DEFAULT_MINIMUM (desc)) return ParameterValue (lower, 0.0f);
if (LADSPA_IS_HINT_BOUNDED_ABOVE (desc) && LADSPA_IS_HINT_DEFAULT_MAXIMUM (desc)) return ParameterValue (upper, 1.0f);
if (LADSPA_IS_HINT_BOUNDED_BELOW (desc))
{
const bool useLog = LADSPA_IS_HINT_LOGARITHMIC (desc);
if (LADSPA_IS_HINT_DEFAULT_LOW (desc)) return ParameterValue (scaledValue (lower, upper, 0.25f, useLog), 0.25f);
if (LADSPA_IS_HINT_DEFAULT_MIDDLE (desc)) return ParameterValue (scaledValue (lower, upper, 0.50f, useLog), 0.50f);
if (LADSPA_IS_HINT_DEFAULT_HIGH (desc)) return ParameterValue (scaledValue (lower, upper, 0.75f, useLog), 0.75f);
}
}
}
return ParameterValue();
}
void reset()
{
paramValue = getDefaultParamValue();
defaultValue = paramValue.unscaled;
}
String getName (int /*maximumStringLength*/) const override
{
return name;
}
String getLabel() const override
{
return {};
}
bool isAutomatable() const override
{
return automatable;
}
static float scaledValue (float low, float high, float alpha, bool useLog) noexcept
{
if (useLog && low > 0 && high > 0)
return expf (logf (low) * (1.0f - alpha) + logf (high) * alpha);
return low + (high - low) * alpha;
}
static float toIntIfNecessary (const LADSPA_PortRangeHintDescriptor& desc, float value)
{
return LADSPA_IS_HINT_INTEGER (desc) ? ((float) (int) value) : value;
}
float getNewParamScaled (const LADSPA_PortRangeHint& hint, float newValue) const
{
const LADSPA_PortRangeHintDescriptor& desc = hint.HintDescriptor;
if (LADSPA_IS_HINT_TOGGLED (desc))
return (newValue < 0.5f) ? 0.0f : 1.0f;
const float scale = LADSPA_IS_HINT_SAMPLE_RATE (desc) ? (float) pluginInstance.getSampleRate() : 1.0f;
const float lower = hint.LowerBound * scale;
const float upper = hint.UpperBound * scale;
if (LADSPA_IS_HINT_BOUNDED_BELOW (desc) && LADSPA_IS_HINT_BOUNDED_ABOVE (desc))
return toIntIfNecessary (desc, scaledValue (lower, upper, newValue, LADSPA_IS_HINT_LOGARITHMIC (desc)));
if (LADSPA_IS_HINT_BOUNDED_BELOW (desc)) return toIntIfNecessary (desc, newValue);
if (LADSPA_IS_HINT_BOUNDED_ABOVE (desc)) return toIntIfNecessary (desc, newValue * upper);
return 0.0f;
}
LADSPAPluginInstance& pluginInstance;
const int paramID;
const String name;
const bool automatable;
ParameterValue paramValue;
float defaultValue = 0;
};
LADSPAPluginInstance (const LADSPAModuleHandle::Ptr& m)
: module (m), plugin (nullptr), handle (nullptr),
initialised (false), tempBuffer (1, 1)
@@ -180,14 +342,17 @@ public:
inputs.clear();
outputs.clear();
parameters.clear();
managedParameters.clear();
for (unsigned int i = 0; i < plugin->PortCount; ++i)
{
const LADSPA_PortDescriptor portDesc = plugin->PortDescriptors[i];
if ((portDesc & LADSPA_PORT_CONTROL) != 0)
parameters.add (i);
addParameter (new LADSPAParameter (*this,
i,
String (plugin->PortNames[i]).trim(),
(portDesc & LADSPA_PORT_INPUT) != 0));
if ((portDesc & LADSPA_PORT_AUDIO) != 0)
{
@@ -196,10 +361,9 @@ public:
}
}
parameterValues.calloc (parameters.size());
for (int i = 0; i < parameters.size(); ++i)
plugin->connect_port (handle, parameters[i], &(parameterValues[i].scaled));
for (auto* param : getParameters())
if (auto* ladspaParam = dynamic_cast<LADSPAParameter*> (param))
plugin->connect_port (handle, ladspaParam->paramID, &(ladspaParam->paramValue.scaled));
setPlayConfigDetails (inputs.size(), outputs.size(), initialSampleRate, initialBlockSize);
@@ -266,11 +430,11 @@ public:
tempBuffer.setSize (jmax (1, outputs.size()), samplesPerBlockExpected);
// dodgy hack to force some plugins to initialise the sample rate..
if (getNumParameters() > 0)
if (auto* firstParam = getParameters()[0])
{
const float old = getParameter (0);
setParameter (0, (old < 0.5f) ? 1.0f : 0.0f);
setParameter (0, old);
const float old = firstParam->getValue();
firstParam->setValue ((old < 0.5f) ? 1.0f : 0.0f);
firstParam->setValue (old);
}
if (plugin->activate != nullptr)
@@ -349,76 +513,15 @@ public:
return {};
}
//==============================================================================
int getNumParameters() { return handle != nullptr ? parameters.size() : 0; }
bool isParameterAutomatable (int index) const
{
return plugin != nullptr
&& (plugin->PortDescriptors [parameters[index]] & LADSPA_PORT_INPUT) != 0;
}
float getParameter (int index)
{
if (plugin != nullptr && isPositiveAndBelow (index, parameters.size()))
{
const ScopedLock sl (lock);
return parameterValues[index].unscaled;
}
return 0.0f;
}
void setParameter (int index, float newValue)
{
if (plugin != nullptr && isPositiveAndBelow (index, parameters.size()))
{
const ScopedLock sl (lock);
ParameterValue& p = parameterValues[index];
if (p.unscaled != newValue)
p = ParameterValue (getNewParamScaled (plugin->PortRangeHints [parameters[index]], newValue), newValue);
}
}
const String getParameterName (int index)
{
if (plugin != nullptr)
{
jassert (isPositiveAndBelow (index, parameters.size()));
return String (plugin->PortNames [parameters [index]]).trim();
}
return {};
}
const String getParameterText (int index)
{
if (plugin != nullptr)
{
jassert (index >= 0 && index < parameters.size());
const LADSPA_PortRangeHint& hint = plugin->PortRangeHints [parameters [index]];
if (LADSPA_IS_HINT_INTEGER (hint.HintDescriptor))
return String ((int) parameterValues[index].scaled);
return String (parameterValues[index].scaled, 4);
}
return {};
}
//==============================================================================
int getNumPrograms() { return 0; }
int getCurrentProgram() { return 0; }
void setCurrentProgram (int newIndex)
void setCurrentProgram (int)
{
if (plugin != nullptr)
for (int i = 0; i < parameters.size(); ++i)
parameterValues[i] = getParamValue (plugin->PortRangeHints [parameters[i]]);
for (auto* param : getParameters())
if (auto* ladspaParam = dynamic_cast<LADSPAParameter*> (param))
ladspaParam->reset();
}
const String getProgramName (int index)
@@ -435,12 +538,15 @@ public:
//==============================================================================
void getStateInformation (MemoryBlock& destData)
{
destData.setSize (sizeof (float) * getNumParameters());
auto numParameters = getParameters().size();
destData.setSize (sizeof (float) * numParameters);
destData.fillWith (0);
float* const p = (float*) ((char*) destData.getData());
for (int i = 0; i < getNumParameters(); ++i)
p[i] = getParameter(i);
for (int i = 0; i < numParameters; ++i)
if (auto* param = getParameters()[i])
p[i] = param->getValue();
}
void getCurrentProgramStateInformation (MemoryBlock& destData)
@@ -452,8 +558,9 @@ public:
{
const float* p = static_cast<const float*> (data);
for (int i = 0; i < getNumParameters(); ++i)
setParameter (i, p[i]);
for (int i = 0; i < getParameters().size(); ++i)
if (auto* param = getParameters()[i])
param->setValue (p[i]);
}
void setCurrentProgramStateInformation (const void* data, int sizeInBytes)
@@ -485,82 +592,7 @@ private:
CriticalSection lock;
bool initialised;
AudioBuffer<float> tempBuffer;
Array<int> inputs, outputs, parameters;
struct ParameterValue
{
inline ParameterValue() noexcept : scaled (0), unscaled (0) {}
inline ParameterValue (float s, float u) noexcept : scaled (s), unscaled (u) {}
float scaled, unscaled;
};
HeapBlock<ParameterValue> parameterValues;
//==============================================================================
static float scaledValue (float low, float high, float alpha, bool useLog) noexcept
{
if (useLog && low > 0 && high > 0)
return expf (logf (low) * (1.0f - alpha) + logf (high) * alpha);
return low + (high - low) * alpha;
}
static float toIntIfNecessary (const LADSPA_PortRangeHintDescriptor& desc, float value)
{
return LADSPA_IS_HINT_INTEGER (desc) ? ((float) (int) value) : value;
}
float getNewParamScaled (const LADSPA_PortRangeHint& hint, float newValue) const
{
const LADSPA_PortRangeHintDescriptor& desc = hint.HintDescriptor;
if (LADSPA_IS_HINT_TOGGLED (desc))
return (newValue < 0.5f) ? 0.0f : 1.0f;
const float scale = LADSPA_IS_HINT_SAMPLE_RATE (desc) ? (float) getSampleRate() : 1.0f;
const float lower = hint.LowerBound * scale;
const float upper = hint.UpperBound * scale;
if (LADSPA_IS_HINT_BOUNDED_BELOW (desc) && LADSPA_IS_HINT_BOUNDED_ABOVE (desc))
return toIntIfNecessary (desc, scaledValue (lower, upper, newValue, LADSPA_IS_HINT_LOGARITHMIC (desc)));
if (LADSPA_IS_HINT_BOUNDED_BELOW (desc)) return toIntIfNecessary (desc, newValue);
if (LADSPA_IS_HINT_BOUNDED_ABOVE (desc)) return toIntIfNecessary (desc, newValue * upper);
return 0.0f;
}
ParameterValue getParamValue (const LADSPA_PortRangeHint& hint) const
{
const LADSPA_PortRangeHintDescriptor& desc = hint.HintDescriptor;
if (LADSPA_IS_HINT_HAS_DEFAULT (desc))
{
if (LADSPA_IS_HINT_DEFAULT_0 (desc)) return ParameterValue();
if (LADSPA_IS_HINT_DEFAULT_1 (desc)) return ParameterValue (1.0f, 1.0f);
if (LADSPA_IS_HINT_DEFAULT_100 (desc)) return ParameterValue (100.0f, 0.5f);
if (LADSPA_IS_HINT_DEFAULT_440 (desc)) return ParameterValue (440.0f, 0.5f);
const float scale = LADSPA_IS_HINT_SAMPLE_RATE (desc) ? (float) getSampleRate() : 1.0f;
const float lower = hint.LowerBound * scale;
const float upper = hint.UpperBound * scale;
if (LADSPA_IS_HINT_BOUNDED_BELOW (desc) && LADSPA_IS_HINT_DEFAULT_MINIMUM (desc)) return ParameterValue (lower, 0.0f);
if (LADSPA_IS_HINT_BOUNDED_ABOVE (desc) && LADSPA_IS_HINT_DEFAULT_MAXIMUM (desc)) return ParameterValue (upper, 1.0f);
if (LADSPA_IS_HINT_BOUNDED_BELOW (desc))
{
const bool useLog = LADSPA_IS_HINT_LOGARITHMIC (desc);
if (LADSPA_IS_HINT_DEFAULT_LOW (desc)) return ParameterValue (scaledValue (lower, upper, 0.25f, useLog), 0.25f);
if (LADSPA_IS_HINT_DEFAULT_MIDDLE (desc)) return ParameterValue (scaledValue (lower, upper, 0.50f, useLog), 0.50f);
if (LADSPA_IS_HINT_DEFAULT_HIGH (desc)) return ParameterValue (scaledValue (lower, upper, 0.75f, useLog), 0.75f);
}
}
return ParameterValue();
}
Array<int> inputs, outputs;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (LADSPAPluginInstance)
};


+ 145
- 90
modules/juce_audio_processors/format_types/juce_VST3PluginFormat.cpp View File

@@ -283,7 +283,10 @@ struct VST3HostContext : public Vst::IComponentHandler, // From VST V3.0.0
if (index < 0)
return kResultFalse;
plugin->beginParameterChangeGesture (index);
if (auto* param = plugin->getParameters()[index])
param->beginChangeGesture();
else
jassertfalse; // Invalid parameter index!
}
return kResultTrue;
@@ -298,7 +301,10 @@ struct VST3HostContext : public Vst::IComponentHandler, // From VST V3.0.0
if (index < 0)
return kResultFalse;
plugin->sendParamChangeMessageToListeners (index, (float) valueNormalized);
if (auto* param = plugin->getParameters()[index])
param->sendValueChangedMessageToListeners ((float) valueNormalized);
else
jassertfalse; // Invalid parameter index!
{
Steinberg::int32 eventIndex;
@@ -322,7 +328,10 @@ struct VST3HostContext : public Vst::IComponentHandler, // From VST V3.0.0
if (index < 0)
return kResultFalse;
plugin->endParameterChangeGesture (index);
if (auto* param = plugin->getParameters()[index])
param->endChangeGesture();
else
jassertfalse; // Invalid parameter index!
}
return kResultTrue;
@@ -1685,6 +1694,118 @@ struct VST3ComponentHolder
//==============================================================================
struct VST3PluginInstance : public AudioPluginInstance
{
struct VST3Parameter final : public Parameter
{
VST3Parameter (VST3PluginInstance& parent,
Steinberg::Vst::ParamID parameterID,
const String& parameterName,
const String& parameterLabel,
Steinberg::Vst::ParamValue defaultParameterValue,
bool parameterIsAutomatable,
bool parameterIsDiscrete,
int numParameterSteps)
: pluginInstance (parent),
paramID (parameterID),
name (parameterName),
label (parameterLabel),
defaultValue (defaultParameterValue),
automatable (parameterIsAutomatable),
discrete (parameterIsDiscrete),
numSteps (numParameterSteps)
{
}
virtual float getValue() const override
{
if (pluginInstance.editController != nullptr)
{
return (float) pluginInstance.editController->getParamNormalized (paramID);
}
return 0.0f;
}
virtual void setValue (float newValue) override
{
if (pluginInstance.editController != nullptr)
{
pluginInstance.editController->setParamNormalized (paramID, (double) newValue);
Steinberg::int32 index;
pluginInstance.inputParameterChanges->addParameterData (paramID, index)
->addPoint (0, newValue, index);
}
}
String getText (float value, int maximumLength) const override
{
if (pluginInstance.editController != nullptr)
{
Vst::String128 result;
if (pluginInstance.editController->getParamStringByValue (paramID, value, result) == kResultOk)
return toString (result).substring (0, maximumLength);
}
return Parameter::getText (value, maximumLength);
}
float getValueForText (const String& text) const override
{
if (pluginInstance.editController != nullptr)
{
Vst::ParamValue result;
if (pluginInstance.editController->getParamValueByString (paramID, toString (text), result) == kResultOk)
return (float) result;
}
return Parameter::getValueForText (text);
}
float getDefaultValue() const override
{
return (float) defaultValue;
}
String getName (int /*maximumStringLength*/) const override
{
return name;
}
String getLabel() const override
{
return label;
}
bool isAutomatable() const override
{
return automatable;
}
bool isDiscrete() const override
{
return discrete;
}
int getNumSteps() const override
{
return numSteps;
}
StringArray getAllValueStrings() const override
{
return {};
}
VST3PluginInstance& pluginInstance;
const Steinberg::Vst::ParamID paramID;
const String name, label;
const Steinberg::Vst::ParamValue defaultValue;
const bool automatable, discrete;
const int numSteps;
};
VST3PluginInstance (VST3ComponentHolder* componentHolder)
: AudioPluginInstance (getBusProperties (componentHolder->component)),
holder (componentHolder),
@@ -1752,9 +1873,30 @@ struct VST3PluginInstance : public AudioPluginInstance
editController->setComponentHandler (holder->host);
grabInformationObjects();
interconnectComponentAndController();
for (int i = 0; i < editController->getParameterCount(); ++i)
{
Vst::ParameterInfo paramInfo = { 0 };
editController->getParameterInfo (i, paramInfo);
bool isDiscrete = paramInfo.stepCount != 0;
int numSteps = isDiscrete ? paramInfo.stepCount + 1
: AudioProcessor::getDefaultNumParameterSteps();
addParameter (new VST3Parameter (*this,
paramInfo.id,
toString (paramInfo.title),
toString (paramInfo.units),
paramInfo.defaultNormalizedValue,
(paramInfo.flags & Vst::ParameterInfo::kCanAutomate) != 0,
isDiscrete,
numSteps));
}
synchroniseStates();
syncProgramNames();
setupIO();
return true;
}
@@ -2158,93 +2300,6 @@ struct VST3PluginInstance : public AudioPluginInstance
return view != nullptr;
}
//==============================================================================
int getNumParameters() override
{
if (editController != nullptr)
return (int) editController->getParameterCount();
return 0;
}
const String getParameterName (int parameterIndex) override
{
return toString (getParameterInfoForIndex (parameterIndex).title);
}
const String getParameterText (int parameterIndex) override
{
if (editController != nullptr)
{
auto id = getParameterInfoForIndex (parameterIndex).id;
Vst::String128 result;
warnOnFailure (editController->getParamStringByValue (id, editController->getParamNormalized (id), result));
return toString (result);
}
return {};
}
int getParameterNumSteps (int parameterIndex) override
{
if (editController != nullptr)
{
const auto numSteps = getParameterInfoForIndex (parameterIndex).stepCount;
if (numSteps > 0)
return numSteps;
}
return AudioProcessor::getDefaultNumParameterSteps();
}
bool isParameterDiscrete (int parameterIndex) const override
{
if (editController != nullptr)
{
const auto numSteps = getParameterInfoForIndex (parameterIndex).stepCount;
return numSteps > 0;
}
return false;
}
bool isParameterAutomatable (int parameterIndex) const override
{
if (editController != nullptr)
{
auto flags = getParameterInfoForIndex (parameterIndex).flags;
return (flags & Steinberg::Vst::ParameterInfo::kCanAutomate) != 0;
}
return true;
}
float getParameter (int parameterIndex) override
{
if (editController != nullptr)
{
auto id = getParameterInfoForIndex (parameterIndex).id;
return (float) editController->getParamNormalized (id);
}
return 0.0f;
}
void setParameter (int parameterIndex, float newValue) override
{
if (editController != nullptr)
{
auto paramID = getParameterInfoForIndex (parameterIndex).id;
editController->setParamNormalized (paramID, (double) newValue);
Steinberg::int32 index;
inputParameterChanges->addParameterData (paramID, index)->addPoint (0, newValue, index);
}
}
//==============================================================================
int getNumPrograms() override { return programNames.size(); }
const String getProgramName (int index) override { return programNames[index]; }


+ 602
- 58
modules/juce_audio_processors/format_types/juce_VSTPluginFormat.cpp View File

@@ -248,6 +248,330 @@ namespace
#endif
//==============================================================================
class VSTXMLInfo
{
public:
static VSTXMLInfo* createFor (const juce::XmlElement& xml)
{
if (xml.hasTagName ("VSTParametersStructure"))
return new VSTXMLInfo (xml);
if (const auto* x = xml.getChildByName ("VSTParametersStructure"))
return new VSTXMLInfo (*x);
return nullptr;
}
struct Group;
struct Base
{
Base() noexcept {}
virtual ~Base() {}
Group* parent = nullptr;
};
struct Param : public Base
{
int paramID;
juce::String expr, name, label;
juce::StringArray shortNames;
juce::String type;
int numberOfStates;
float defaultValue;
};
struct Group : public Base
{
juce::String name;
juce::OwnedArray<Base> paramTree;
};
struct Range
{
Range() noexcept {}
Range (const juce::String& s) { set (s); }
void set (const juce::String& s)
{
inclusiveLow = s.startsWithChar ('[');
inclusiveHigh = s.endsWithChar (']');
auto str = s.removeCharacters ("[]");
low = str.upToFirstOccurrenceOf (",", false, false).getFloatValue();
high = str.fromLastOccurrenceOf (",", false, false).getFloatValue();
}
bool contains (float f) const noexcept
{
return (inclusiveLow ? (f >= low) : (f > low))
&& (inclusiveHigh ? (f <= high) : (f < high));
}
float low = 0;
float high = 0;
bool inclusiveLow = false;
bool inclusiveHigh = false;
};
struct Entry
{
juce::String name;
Range range;
};
struct ValueType
{
juce::String name, label;
juce::OwnedArray<Entry> entries;
};
struct Template
{
juce::String name;
juce::OwnedArray<Param> params;
};
const Param* getParamForID (const int paramID, const Group* const grp) const
{
for (auto item : (grp != nullptr ? grp->paramTree : paramTree))
{
if (auto param = dynamic_cast<const Param*> (item))
if (param->paramID == paramID)
return param;
if (auto group = dynamic_cast<const Group*> (item))
if (auto res = getParamForID (paramID, group))
return res;
}
return nullptr;
}
const ValueType* getValueType (const juce::String& name) const
{
for (auto v : valueTypes)
if (v->name == name)
return v;
return nullptr;
}
juce::OwnedArray<Base> paramTree;
juce::OwnedArray<ValueType> valueTypes;
juce::OwnedArray<Template> templates;
ValueType switchValueType;
private:
VSTXMLInfo (const juce::XmlElement& xml)
{
switchValueType.entries.add (new Entry({ TRANS("Off"), Range ("[0, 0.5[") }));
switchValueType.entries.add (new Entry({ TRANS("On"), Range ("[0.5, 1]") }));
forEachXmlChildElement (xml, item)
{
if (item->hasTagName ("Param")) parseParam (*item, nullptr, nullptr);
else if (item->hasTagName ("ValueType")) parseValueType (*item);
else if (item->hasTagName ("Template")) parseTemplate (*item);
else if (item->hasTagName ("Group")) parseGroup (*item, nullptr);
}
}
void parseParam (const juce::XmlElement& item, Group* group, Template* temp)
{
auto param = new Param();
if (temp != nullptr)
param->expr = item.getStringAttribute ("id");
else
param->paramID = item.getIntAttribute ("id");
param->name = item.getStringAttribute ("name");
param->label = item.getStringAttribute ("label");
param->type = item.getStringAttribute ("type");
param->numberOfStates = item.getIntAttribute ("numberOfStates");
param->defaultValue = (float) item.getDoubleAttribute ("defaultValue");
param->shortNames.addTokens (item.getStringAttribute ("shortName"), ",", juce::StringRef());
param->shortNames.trim();
param->shortNames.removeEmptyStrings();
if (group != nullptr)
{
group->paramTree.add (param);
param->parent = group;
}
else if (temp != nullptr)
{
temp->params.add (param);
}
else
{
paramTree.add (param);
}
}
void parseValueType (const juce::XmlElement& item)
{
auto vt = new ValueType();
valueTypes.add (vt);
vt->name = item.getStringAttribute ("name");
vt->label = item.getStringAttribute ("label");
int curEntry = 0;
const int numEntries = item.getNumChildElements();
forEachXmlChildElementWithTagName (item, entryXml, "Entry")
{
auto entry = new Entry();
entry->name = entryXml->getStringAttribute ("name");
if (entryXml->hasAttribute ("value"))
{
entry->range.set(entryXml->getStringAttribute ("value"));
}
else
{
entry->range.low = curEntry / (float) numEntries;
entry->range.high = (curEntry + 1) / (float) numEntries;
entry->range.inclusiveLow = true;
entry->range.inclusiveHigh = (curEntry == numEntries - 1);
}
vt->entries.add (entry);
++curEntry;
}
}
void parseTemplate (const juce::XmlElement& item)
{
auto temp = new Template();
templates.add (temp);
temp->name = item.getStringAttribute ("name");
forEachXmlChildElement (item, param)
parseParam (*param, nullptr, temp);
}
void parseGroup (const juce::XmlElement& item, Group* parentGroup)
{
auto group = new Group();
if (parentGroup)
{
parentGroup->paramTree.add (group);
group->parent = parentGroup;
}
else
{
paramTree.add (group);
}
group->name = item.getStringAttribute ("name");
if (item.hasAttribute ("template"))
{
juce::StringArray variables;
variables.addTokens (item.getStringAttribute ("values"), ";", juce::StringRef());
variables.trim();
for (auto temp : templates)
{
if (temp->name == item.getStringAttribute ("template"))
{
for (int i = 0; i < temp->params.size(); ++i)
{
auto param = new Param();
group->paramTree.add (param);
param->parent = group;
param->paramID = evaluate (temp->params[i]->expr, variables);
param->defaultValue = temp->params[i]->defaultValue;
param->label = temp->params[i]->label;
param->name = temp->params[i]->name;
param->numberOfStates = temp->params[i]->numberOfStates;
param->shortNames = temp->params[i]->shortNames;
param->type = temp->params[i]->type;
}
}
}
}
else
{
forEachXmlChildElement (item, subItem)
{
if (subItem->hasTagName ("Param")) parseParam (*subItem, group, nullptr);
else if (subItem->hasTagName ("Group")) parseGroup (*subItem, group);
}
}
}
int evaluate (juce::String expr, const juce::StringArray& variables) const
{
juce::StringArray names;
juce::Array<int> vals;
for (auto& v : variables)
{
if (v.contains ("="))
{
names.add (v.upToFirstOccurrenceOf ("=", false, false));
vals.add (v.fromFirstOccurrenceOf ("=", false, false).getIntValue());
}
}
for (int i = 0; i < names.size(); ++i)
{
for (;;)
{
const int idx = expr.indexOfWholeWord (names[i]);
if (idx < 0)
break;
expr = expr.replaceSection (idx, names[i].length(), juce::String (vals[i]));
}
}
expr = expr.retainCharacters ("01234567890-+")
.replace ("+", " + ")
.replace ("-", " - ");
juce::StringArray tokens;
tokens.addTokens (expr, " ", juce::StringRef());
bool add = true;
int val = 0;
for (const auto& s : tokens)
{
if (s == "+")
{
add = true;
}
else if (s == "-")
{
add = false;
}
else
{
if (add)
val += s.getIntValue();
else
val -= s.getIntValue();
}
}
return val;
}
};
//==============================================================================
struct ModuleHandle : public ReferenceCountedObject
{
@@ -498,12 +822,158 @@ static const int defaultVSTBlockSizeValue = 512;
#pragma warning (disable: 4996) // warning about overriding deprecated methods
#endif
//==============================================================================
//==============================================================================
struct VSTPluginInstance : public AudioPluginInstance,
private Timer,
private AsyncUpdater
{
struct VSTParameter final : public Parameter
{
VSTParameter (VSTPluginInstance& parent,
const String& paramName,
const Array<String>& shortParamNames,
float paramDefaultValue,
const String& paramLabel,
bool paramIsAutomatable,
bool paramIsDiscrete,
int numParamSteps,
bool isBoolSwitch,
const StringArray& paramValueStrings,
const VSTXMLInfo::ValueType* paramValueType)
: pluginInstance (parent),
name (paramName),
shortNames (shortParamNames),
defaultValue (paramDefaultValue),
label (paramLabel),
automatable (paramIsAutomatable),
discrete (paramIsDiscrete),
numSteps (numParamSteps),
isSwitch (isBoolSwitch),
vstValueStrings (paramValueStrings),
valueType (paramValueType)
{
}
virtual float getValue() const override
{
if (auto* effect = pluginInstance.vstEffect)
{
const ScopedLock sl (pluginInstance.lock);
return effect->getParameterValueFunction (effect, getParameterIndex());
}
return 0.0f;
}
virtual void setValue (float newValue) override
{
if (auto* effect = pluginInstance.vstEffect)
{
const ScopedLock sl (pluginInstance.lock);
if (effect->getParameterValueFunction (effect, getParameterIndex()) != newValue)
effect->setParameterValueFunction (effect, getParameterIndex(), newValue);
}
}
String getText (float value, int maximumStringLength) const override
{
if (valueType != nullptr)
{
for (auto& v : valueType->entries)
if (v->range.contains (value))
return v->name;
}
return Parameter::getText (value, maximumStringLength);
}
float getValueForText (const String& text) const override
{
if (valueType != nullptr)
{
for (auto& v : valueType->entries)
if (v->name == text)
return (v->range.high + v->range.low) / 2.0f;
}
return Parameter::getValueForText (text);
}
String getCurrentValueAsText() const override
{
if (valueType != nullptr || ! vstValueStrings.isEmpty())
return getText (getValue(), 1024);
return pluginInstance.getTextForOpcode (getParameterIndex(), plugInOpcodeGetParameterText);
}
float getDefaultValue() const override
{
return defaultValue;
}
String getName (int maximumStringLength) const override
{
if (name.length() <= maximumStringLength)
return name;
if (! shortNames.isEmpty())
{
for (auto& n : shortNames)
if (n.length() <= maximumStringLength)
return n;
return shortNames.getLast();
}
return name;
}
String getLabel() const override
{
return label;
}
bool isAutomatable() const override
{
return automatable;
}
bool isDiscrete() const override
{
return discrete;
}
bool isBoolean() const override
{
return isSwitch;
}
int getNumSteps() const override
{
return numSteps;
}
StringArray getAllValueStrings() const override
{
return vstValueStrings;
}
VSTPluginInstance& pluginInstance;
const String name;
const Array<String> shortNames;
const float defaultValue;
const String label;
const bool automatable, discrete;
const int numSteps;
const bool isSwitch;
const StringArray vstValueStrings;
const VSTXMLInfo::ValueType* const valueType;
};
VSTPluginInstance (const ModuleHandle::Ptr& mh, const BusesProperties& ioConfig, VstEffectInterface* effect,
double sampleRateToUse, int blockSizeToUse)
: AudioPluginInstance (ioConfig),
@@ -511,6 +981,88 @@ struct VSTPluginInstance : public AudioPluginInstance,
vstModule (mh),
name (mh->pluginName)
{
jassert (vstEffect != nullptr);
if (auto* xml = vstModule->vstXml.get())
xmlInfo.reset (VSTXMLInfo::createFor (*xml));
for (int i = 0; i < vstEffect->numParameters; ++i)
{
String paramName (getTextForOpcode (i, plugInOpcodeGetParameterName));
Array<String> shortParamNames;
float defaultValue = 0;
String label (getTextForOpcode (i, plugInOpcodeGetParameterLabel));
bool isAutomatable = dispatch (plugInOpcodeIsParameterAutomatable, i, 0, 0, 0) != 0;
bool isDiscrete = false;
int numSteps = AudioProcessor::getDefaultNumParameterSteps();
bool isBoolSwitch = false;
StringArray parameterValueStrings;
const VSTXMLInfo::ValueType* valueType = nullptr;
if (xmlInfo != nullptr)
{
if (auto* param = xmlInfo->getParamForID (i, nullptr))
{
paramName = param->name;
for (auto& n : param->shortNames)
shortParamNames.add (n);
struct LengthComparator
{
static int compareElements (const juce::String& first, const juce::String& second) noexcept
{
return first.length() - second.length();
}
};
LengthComparator comp;
shortParamNames.sort (comp);
defaultValue = param->defaultValue;
label = param->label;
if (param->type == "switch")
{
isBoolSwitch = true;
numSteps = 2;
valueType = &xmlInfo->switchValueType;
}
else
{
valueType = xmlInfo->getValueType (param->type);
}
if (param->numberOfStates >= 2)
{
numSteps = param->numberOfStates;
if (valueType != nullptr)
{
for (auto* entry : valueType->entries)
parameterValueStrings.add (entry->name);
parameterValueStrings.removeEmptyStrings();
}
}
isDiscrete = (numSteps != AudioProcessor::getDefaultNumParameterSteps());
}
}
addParameter (new VSTParameter (*this,
paramName,
shortParamNames,
defaultValue,
label,
isAutomatable,
isDiscrete,
numSteps,
isBoolSwitch,
parameterValueStrings,
valueType));
}
setRateAndBufferSizeDetails (sampleRateToUse, blockSizeToUse);
}
@@ -802,11 +1354,14 @@ struct VSTPluginInstance : public AudioPluginInstance,
setPower (true);
// dodgy hack to force some plugins to initialise the sample rate..
if ((! hasEditor()) && getNumParameters() > 0)
if (! hasEditor())
{
auto old = getParameter (0);
setParameter (0, (old < 0.5f) ? 1.0f : 0.0f);
setParameter (0, old);
if (auto* firstParam = getParameters()[0])
{
auto old = firstParam->getValue();
firstParam->setValue ((old < 0.5f) ? 1.0f : 0.0f);
firstParam->setValue (old);
}
}
dispatch (plugInOpcodeStartProcess, 0, 0, 0, 0);
@@ -943,46 +1498,6 @@ struct VSTPluginInstance : public AudioPluginInstance,
: getTotalNumOutputChannels());
}
//==============================================================================
int getNumParameters() override { return vstEffect != nullptr ? vstEffect->numParameters : 0; }
float getParameter (int index) override
{
if (vstEffect != nullptr && isPositiveAndBelow (index, vstEffect->numParameters))
{
const ScopedLock sl (lock);
return vstEffect->getParameterValueFunction (vstEffect, index);
}
return 0.0f;
}
void setParameter (int index, float newValue) override
{
if (vstEffect != nullptr && isPositiveAndBelow (index, vstEffect->numParameters))
{
const ScopedLock sl (lock);
if (vstEffect->getParameterValueFunction (vstEffect, index) != newValue)
vstEffect->setParameterValueFunction (vstEffect, index, newValue);
}
}
const String getParameterName (int index) override { return getTextForOpcode (index, plugInOpcodeGetParameterName); }
const String getParameterText (int index) override { return getTextForOpcode (index, plugInOpcodeGetParameterText); }
String getParameterLabel (int index) const override { return getTextForOpcode (index, plugInOpcodeGetParameterLabel); }
bool isParameterAutomatable (int index) const override
{
if (vstEffect != nullptr)
{
jassert (index >= 0 && index < vstEffect->numParameters);
return dispatch (plugInOpcodeIsParameterAutomatable, index, 0, 0, 0) != 0;
}
return false;
}
//==============================================================================
int getNumPrograms() override { return vstEffect != nullptr ? jmax (0, vstEffect->numPrograms) : 0; }
@@ -1051,7 +1566,14 @@ struct VSTPluginInstance : public AudioPluginInstance,
{
switch (opcode)
{
case hostOpcodeParameterChanged: sendParamChangeMessageToListeners (index, opt); break;
case hostOpcodeParameterChanged:
if (auto* param = getParameters()[index])
param->sendValueChangedMessageToListeners (opt);
else
jassertfalse; // Invalid parameter index!
break;
case hostOpcodePreAudioProcessingEvents: handleMidiFromPlugin ((const VstEventBlock*) ptr); break;
case hostOpcodeGetTimingInfo: return getVSTTime();
case hostOpcodeIdle: handleIdle(); break;
@@ -1068,8 +1590,21 @@ struct VSTPluginInstance : public AudioPluginInstance,
case hostOpcodeTempoAt: return (pointer_sized_int) (extraFunctions != nullptr ? extraFunctions->getTempoAt ((int64) value) : 0);
case hostOpcodeGetAutomationState: return (pointer_sized_int) (extraFunctions != nullptr ? extraFunctions->getAutomationState() : 0);
case hostOpcodeParameterChangeGestureBegin: beginParameterChangeGesture (index); break;
case hostOpcodeParameterChangeGestureEnd: endParameterChangeGesture (index); break;
case hostOpcodeParameterChangeGestureBegin:
if (auto* param = getParameters()[index])
param->beginChangeGesture();
else
jassertfalse; // Invalid parameter index!
break;
case hostOpcodeParameterChangeGestureEnd:
if (auto* param = getParameters()[index])
param->endChangeGesture();
else
jassertfalse; // Invalid parameter index!
break;
case hostOpcodePinConnected: return isValidChannel (index, value == 0) ? 0 : 1; // (yes, 0 = true)
case hostOpcodeGetCurrentAudioProcessingLevel: return isNonRealtime() ? 4 : 0;
@@ -1226,7 +1761,8 @@ struct VSTPluginInstance : public AudioPluginInstance,
changeProgramName (getCurrentProgram(), prog->prgName);
for (int i = 0; i < fxbSwap (prog->numParams); ++i)
setParameter (i, fxbSwapFloat (prog->params[i]));
if (auto* param = getParameters()[i])
param->setValue (fxbSwapFloat (prog->params[i]));
}
else if (compareMagic (set->fxMagic, "FBCh"))
{
@@ -1261,7 +1797,7 @@ struct VSTPluginInstance : public AudioPluginInstance,
bool saveToFXBFile (MemoryBlock& dest, bool isFXB, int maxSizeMB = 128)
{
auto numPrograms = getNumPrograms();
auto numParams = getNumParameters();
auto numParams = getParameters().size();
if (usesChunks())
{
@@ -1413,6 +1949,8 @@ private:
AudioBuffer<double> tmpBufferDouble;
HeapBlock<double*> channelBufferDouble;
ScopedPointer<VSTXMLInfo> xmlInfo;
static pointer_sized_int handleCanDo (const char* name)
{
static const char* canDos[] = { "supplyIdle",
@@ -1832,7 +2370,8 @@ private:
changeProgramName (getCurrentProgram(), prog->prgName);
for (int i = 0; i < fxbSwap (prog->numParams); ++i)
setParameter (i, fxbSwapFloat (prog->params[i]));
if (auto* param = getParameters()[i])
param->setValue (fxbSwapFloat (prog->params[i]));
return true;
}
@@ -1879,7 +2418,7 @@ private:
void setParamsInProgramBlock (fxProgram* prog)
{
auto numParams = getNumParameters();
auto numParams = getParameters().size();
prog->chunkMagic = fxbName ("CcnK");
prog->byteSize = 0;
@@ -1892,7 +2431,8 @@ private:
getCurrentProgramName().copyToUTF8 (prog->prgName, sizeof (prog->prgName) - 1);
for (int i = 0; i < numParams; ++i)
prog->params[i] = fxbSwapFloat (getParameter (i));
if (auto* param = getParameters()[i])
prog->params[i] = fxbSwapFloat (param->getValue());
}
void updateStoredProgramNames()
@@ -1932,15 +2472,17 @@ private:
//==============================================================================
void createTempParameterStore (MemoryBlock& dest)
{
dest.setSize (64 + 4 * (size_t) getNumParameters());
auto numParameters = getParameters().size();
dest.setSize (64 + 4 * (size_t) numParameters);
dest.fillWith (0);
getCurrentProgramName().copyToUTF8 ((char*) dest.getData(), 63);
auto p = (float*) (((char*) dest.getData()) + 64);
for (int i = 0; i < getNumParameters(); ++i)
p[i] = getParameter(i);
for (int i = 0; i < numParameters; ++i)
if (auto* param = getParameters()[i])
p[i] = param->getValue();
}
void restoreFromTempParameterStore (const MemoryBlock& m)
@@ -1948,9 +2490,11 @@ private:
changeProgramName (getCurrentProgram(), (const char*) m.getData());
auto p = (float*) (((char*) m.getData()) + 64);
auto numParameters = getParameters().size();
for (int i = 0; i < getNumParameters(); ++i)
setParameter (i, p[i]);
for (int i = 0; i < numParameters; ++i)
if (auto* param = getParameters()[i])
param->setValue (p[i]);
}
pointer_sized_int getVstDirectory() const


+ 1
- 0
modules/juce_audio_processors/juce_audio_processors.cpp View File

@@ -155,6 +155,7 @@ struct AutoResizingNSViewComponentWithParent : public AutoResizingNSViewCompone
#include "format/juce_AudioPluginFormat.cpp"
#include "format/juce_AudioPluginFormatManager.cpp"
#include "processors/juce_AudioProcessor.cpp"
#include "processors/juce_AudioPluginInstance.cpp"
#include "processors/juce_AudioProcessorEditor.cpp"
#include "processors/juce_AudioProcessorGraph.cpp"
#include "processors/juce_GenericAudioProcessorEditor.cpp"


+ 230
- 0
modules/juce_audio_processors/processors/juce_AudioPluginInstance.cpp View File

@@ -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

+ 39
- 6
modules/juce_audio_processors/processors/juce_AudioPluginInstance.h View File

@@ -58,12 +58,7 @@ public:
/** Returns a PluginDescription for this plugin.
This is just a convenience method to avoid calling fillInPluginDescription.
*/
PluginDescription getPluginDescription() const
{
PluginDescription desc;
fillInPluginDescription (desc);
return desc;
}
PluginDescription getPluginDescription() const;
/** Returns a pointer to some kind of platform-specific data about the plugin.
E.g. For a VST, this value can be cast to an AEffect*. For an AudioUnit, it can be
@@ -76,13 +71,51 @@ public:
*/
virtual void refreshParameterList() {}
// Rather than using these methods you should call the corresponding methods
// on the AudioProcessorParameter objects returned from getParameters().
// See the implementations of the methods below for some examples of how to
// do this.
//
// In addition to being marked as deprecated these methods will assert on
// the first call.
JUCE_DEPRECATED (String getParameterID (int index) override);
JUCE_DEPRECATED (float getParameter (int parameterIndex) override);
JUCE_DEPRECATED (void setParameter (int parameterIndex, float newValue) override);
JUCE_DEPRECATED (const String getParameterName (int parameterIndex) override);
JUCE_DEPRECATED (const String getParameterText (int parameterIndex) override);
JUCE_DEPRECATED (String getParameterText (int parameterIndex, int maximumStringLength) override);
JUCE_DEPRECATED (int getParameterNumSteps (int parameterIndex) override);
JUCE_DEPRECATED (bool isParameterDiscrete (int parameterIndex) const override);
JUCE_DEPRECATED (bool isParameterAutomatable (int parameterIndex) const override);
JUCE_DEPRECATED (float getParameterDefaultValue (int parameterIndex) override);
JUCE_DEPRECATED (String getParameterLabel (int parameterIndex) const override);
JUCE_DEPRECATED (bool isParameterOrientationInverted (int parameterIndex) const override);
JUCE_DEPRECATED (bool isMetaParameter (int parameterIndex) const override);
JUCE_DEPRECATED (AudioProcessorParameter::Category getParameterCategory (int parameterIndex) const override);
protected:
//==============================================================================
struct Parameter : public AudioProcessorParameter
{
Parameter();
virtual ~Parameter();
virtual String getText (float value, int maximumStringLength) const override;
virtual float getValueForText (const String& text) const override;
StringArray onStrings, offStrings;
};
AudioPluginInstance() {}
AudioPluginInstance (const BusesProperties& ioLayouts) : AudioProcessor (ioLayouts) {}
template <int numLayouts>
AudioPluginInstance (const short channelLayoutList[numLayouts][2]) : AudioProcessor (channelLayoutList) {}
private:
void assertOnceOnDeprecatedMethodUse() const noexcept;
static bool deprecationAssertiontriggered;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AudioPluginInstance)
};


+ 27
- 6
modules/juce_audio_processors/processors/juce_AudioProcessor.cpp View File

@@ -673,10 +673,6 @@ void AudioProcessor::addParameter (AudioProcessorParameter* p)
p->parameterIndex = managedParameters.size();
managedParameters.add (p);
// if you're using parameter objects, then you must not override the
// deprecated getNumParameters() method!
jassert (getNumParameters() == AudioProcessor::getNumParameters());
#ifdef JUCE_DEBUG
shouldCheckParamsForDupeIDs = true;
#endif
@@ -1354,14 +1350,21 @@ int32 AudioProcessor::getAAXPluginIDForMainBusConfig (const AudioChannelSet& mai
return (idForAudioSuite ? 0x6a796161 /* 'jyaa' */ : 0x6a636161 /* 'jcaa' */) + uniqueFormatId;
}
//==============================================================================
void AudioProcessorListener::audioProcessorParameterChangeGestureBegin (AudioProcessor*, int) {}
void AudioProcessorListener::audioProcessorParameterChangeGestureEnd (AudioProcessor*, int) {}
//==============================================================================
AudioProcessorParameter::AudioProcessorParameter() noexcept {}
AudioProcessorParameter::~AudioProcessorParameter() {}
AudioProcessorParameter::~AudioProcessorParameter()
{
#if JUCE_DEBUG && ! JUCE_DISABLE_AUDIOPROCESSOR_BEGIN_END_GESTURE_CHECKING
// This will fail if you've called beginChangeGesture() without having made
// a corresponding call to endChangeGesture...
jassert (! isPerformingGesture);
#endif
}
void AudioProcessorParameter::setValueNotifyingHost (float newValue)
{
@@ -1452,6 +1455,24 @@ String AudioProcessorParameter::getText (float value, int /*maximumStringLength*
return String (value, 2);
}
String AudioProcessorParameter::getCurrentValueAsText() const
{
return getText (getValue(), 1024);
}
StringArray AudioProcessorParameter::getAllValueStrings() const
{
if (isDiscrete() && valueStrings.isEmpty())
{
auto maxIndex = getNumSteps() - 1;
for (int i = 0; i < getNumSteps(); ++i)
valueStrings.add (getText ((float) i / maxIndex, 1024));
}
return valueStrings;
}
void AudioProcessorParameter::addListener (AudioProcessorParameter::Listener* newListener)
{
const ScopedLock sl (listenerLock);


+ 2
- 0
modules/juce_audio_processors/processors/juce_AudioProcessor.h View File

@@ -1620,6 +1620,8 @@ private:
friend class JuceVST3EditController;
friend class JuceVST3Component;
friend class AudioUnitPluginInstance;
friend class LADSPAPluginInstance;
Atomic<int> vst3IsPlaying { 0 };


+ 23
- 0
modules/juce_audio_processors/processors/juce_AudioProcessorParameter.h View File

@@ -199,6 +199,28 @@ public:
/** Returns the index of this parameter in its parent processor's parameter list. */
int getParameterIndex() const noexcept { return parameterIndex; }
//==============================================================================
/** Returns the current value of the parameter as a String.
This function can be called when you are hosting plug-ins to get a
more specialsed textual represenation of the current value from the
plug-in, for example "On" rather than "1.0".
If you are implementing a plug-in then you should ignore this function
and instead override getText.
*/
virtual String getCurrentValueAsText() const;
/** Returns the set of strings which represent the possible states a parameter
can be in.
If you are hosting a plug-in you can use the result of this funtion to
populate a ComboBox listing the allowed values.
If you are implementing a plug-in then you do not need to override this.
*/
virtual StringArray getAllValueStrings() const;
//==============================================================================
/**
A base class for listeners that want to know about changes to an
@@ -267,6 +289,7 @@ private:
int parameterIndex = -1;
CriticalSection listenerLock;
Array<Listener*> listeners;
mutable StringArray valueStrings;
#if JUCE_DEBUG
bool isPerformingGesture = false;


+ 514
- 24
modules/juce_audio_processors/processors/juce_GenericAudioProcessorEditor.cpp View File

@@ -114,17 +114,17 @@ private:
void startedDragging() override
{
owner.beginParameterChangeGesture(index);
owner.beginParameterChangeGesture (index);
}
void stoppedDragging() override
{
owner.endParameterChangeGesture(index);
owner.endParameterChangeGesture (index);
}
String getTextFromValue (double /*value*/) override
{
return owner.getParameterText (index) + " " + owner.getParameterLabel (index).trimEnd();
return (owner.getParameterText (index) + " " + owner.getParameterLabel (index)).trimEnd();
}
private:
@@ -143,50 +143,540 @@ private:
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ProcessorParameterPropertyComp)
};
struct LegacyParametersPanel : public Component
{
LegacyParametersPanel (AudioProcessor* const processor)
{
addAndMakeVisible (panel);
Array<PropertyComponent*> params;
auto numParams = processor->getNumParameters();
int totalHeight = 0;
for (int i = 0; i < numParams; ++i)
{
String name (processor->getParameterName (i));
if (name.trim().isEmpty())
name = "Unnamed";
auto* pc = new ProcessorParameterPropertyComp (name, *processor, i);
params.add (pc);
totalHeight += pc->getPreferredHeight();
}
panel.addProperties (params);
setSize (400, jmax (25, totalHeight));
}
void paint (Graphics& g) override
{
g.fillAll (getLookAndFeel().findColour (ResizableWindow::backgroundColourId));
}
void resized() override
{
panel.setBounds (getLocalBounds());
}
PropertyPanel panel;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (LegacyParametersPanel)
};
//==============================================================================
GenericAudioProcessorEditor::GenericAudioProcessorEditor (AudioProcessor* const p)
: AudioProcessorEditor (p)
class ParameterListener : private AudioProcessorParameter::Listener,
private Timer
{
jassert (p != nullptr);
setOpaque (true);
public:
ParameterListener (AudioProcessorParameter& param)
: parameter (param)
{
parameter.addListener (this);
addAndMakeVisible (panel);
startTimer (100);
}
Array<PropertyComponent*> params;
virtual ~ParameterListener()
{
parameter.removeListener (this);
}
auto numParams = p->getNumParameters();
int totalHeight = 0;
AudioProcessorParameter& getParameter() noexcept
{
return parameter;
}
virtual void handleNewParameterValue() = 0;
for (int i = 0; i < numParams; ++i)
private:
void parameterValueChanged (int, float) override
{
auto name = p->getParameterName (i);
parameterValueHasChanged = 1;
}
if (name.trim().isEmpty())
name = "Unnamed";
void parameterGestureChanged (int, bool) override {}
auto* pc = new ProcessorParameterPropertyComp (name, *p, i);
params.add (pc);
totalHeight += pc->getPreferredHeight();
void timerCallback() override
{
if (parameterValueHasChanged.compareAndSetBool (0, 1))
{
handleNewParameterValue();
startTimerHz (50);
}
else
{
startTimer (jmin (250, getTimerInterval() + 10));
}
}
panel.addProperties (params);
AudioProcessorParameter& parameter;
Atomic<int> parameterValueHasChanged { 0 };
setSize (400, jlimit (25, 400, totalHeight));
}
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ParameterListener)
};
GenericAudioProcessorEditor::~GenericAudioProcessorEditor()
class BooleanParameterComponent final : public Component,
private ParameterListener
{
public:
BooleanParameterComponent (AudioProcessorParameter& param)
: ParameterListener (param)
{
// Set the initial value.
handleNewParameterValue();
button.onClick = [this]() { buttonClicked(); };
addAndMakeVisible (button);
}
void paint (Graphics&) override {}
void resized() override
{
auto area = getLocalBounds();
area.removeFromLeft (8);
button.setBounds (area.reduced (0, 10));
}
private:
void handleNewParameterValue() override
{
auto parameterState = getParameterState (getParameter().getValue());
if (button.getToggleState() != parameterState)
button.setToggleState (parameterState, dontSendNotification);
}
void buttonClicked()
{
if (getParameterState (getParameter().getValue()) != button.getToggleState())
{
getParameter().beginChangeGesture();
getParameter().setValueNotifyingHost (button.getToggleState() ? 1.0f : 0.0f);
getParameter().endChangeGesture();
}
}
bool getParameterState (float value) const noexcept
{
return value >= 0.5f;
}
ToggleButton button;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (BooleanParameterComponent)
};
class SwitchParameterComponent final : public Component,
private ParameterListener
{
public:
SwitchParameterComponent (AudioProcessorParameter& param)
: ParameterListener (param)
{
auto* leftButton = buttons.add (new TextButton());
auto* rightButton = buttons.add (new TextButton());
for (auto* button : buttons)
{
button->setRadioGroupId (293847);
button->setClickingTogglesState (true);
}
leftButton ->setButtonText (getParameter().getText (0.0f, 16));
rightButton->setButtonText (getParameter().getText (1.0f, 16));
leftButton ->setConnectedEdges (Button::ConnectedOnRight);
rightButton->setConnectedEdges (Button::ConnectedOnLeft);
// Set the initial value.
leftButton->setToggleState (true, dontSendNotification);
handleNewParameterValue();
rightButton->onStateChange = [this]() { rightButtonChanged(); };
for (auto* button : buttons)
addAndMakeVisible (button);
}
void paint (Graphics&) override {}
void resized() override
{
auto area = getLocalBounds().reduced (0, 8);
area.removeFromLeft (8);
for (auto* button : buttons)
button->setBounds (area.removeFromLeft (80));
}
private:
void handleNewParameterValue() override
{
bool newState = getParameterState();
if (buttons[1]->getToggleState() != newState)
{
buttons[1]->setToggleState (newState, dontSendNotification);
buttons[0]->setToggleState (! newState, dontSendNotification);
}
}
void rightButtonChanged()
{
auto buttonState = buttons[1]->getToggleState();
if (getParameterState() != buttonState)
{
getParameter().beginChangeGesture();
if (getParameter().getAllValueStrings().isEmpty())
{
getParameter().setValueNotifyingHost (buttonState ? 1.0f : 0.0f);
}
else
{
// When a parameter provides a list of strings we must set its
// value using those strings, rather than a float, because VSTs can
// have uneven spacing between the different allowed values and we
// want the snapping behaviour to be consistent with what we do with
// a combo box.
String selectedText = buttonState ? buttons[1]->getButtonText() : buttons[0]->getButtonText();
getParameter().setValueNotifyingHost (getParameter().getValueForText (selectedText));
}
getParameter().endChangeGesture();
}
}
bool getParameterState()
{
if (getParameter().getAllValueStrings().isEmpty())
return getParameter().getValue() > 0.5f;
auto index = getParameter().getAllValueStrings()
.indexOf (getParameter().getCurrentValueAsText());
if (index < 0)
{
// The parameter is producing some unexpected text, so we'll do
// some linear interpolation.
index = roundToInt (getParameter().getValue());
}
return index == 1;
}
OwnedArray<TextButton> buttons;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (SwitchParameterComponent)
};
class ChoiceParameterComponent final : public Component,
private ParameterListener
{
public:
ChoiceParameterComponent (AudioProcessorParameter& param)
: ParameterListener (param),
parameterValues (getParameter().getAllValueStrings())
{
box.addItemList (parameterValues, 1);
// Set the initial value.
handleNewParameterValue();
box.onChange = [this]() { boxChanged(); };
addAndMakeVisible (box);
}
void paint (Graphics&) override {}
void resized() override
{
auto area = getLocalBounds();
area.removeFromLeft (8);
box.setBounds (area.reduced (0, 10));
}
private:
void handleNewParameterValue() override
{
auto index = parameterValues.indexOf (getParameter().getCurrentValueAsText());
if (index < 0)
{
// The parameter is producing some unexpected text, so we'll do
// some linear interpolation.
index = roundToInt (getParameter().getValue() * (parameterValues.size() - 1));
}
box.setSelectedItemIndex (index);
}
void boxChanged()
{
if (getParameter().getCurrentValueAsText() != box.getText())
{
getParameter().beginChangeGesture();
// When a parameter provides a list of strings we must set its
// value using those strings, rather than a float, because VSTs can
// have uneven spacing between the different allowed values.
getParameter().setValueNotifyingHost (getParameter().getValueForText (box.getText()));
getParameter().endChangeGesture();
}
}
ComboBox box;
const StringArray parameterValues;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ChoiceParameterComponent)
};
class SliderParameterComponent final : public Component,
private ParameterListener
{
public:
SliderParameterComponent (AudioProcessorParameter& param)
: ParameterListener (param)
{
if (getParameter().getNumSteps() != AudioProcessor::getDefaultNumParameterSteps())
slider.setRange (0.0, 1.0, 1.0 / (getParameter().getNumSteps() - 1.0));
else
slider.setRange (0.0, 1.0);
slider.setScrollWheelEnabled (false);
addAndMakeVisible (slider);
valueLabel.setColour (Label::outlineColourId, slider.findColour (Slider::textBoxOutlineColourId));
valueLabel.setBorderSize ({ 1, 1, 1, 1 });
valueLabel.setJustificationType (Justification::centred);
addAndMakeVisible (valueLabel);
// Set the initial value.
handleNewParameterValue();
slider.onValueChange = [this]() { sliderValueChanged(); };
slider.onDragStart = [this]() { sliderStartedDragging(); };
slider.onDragEnd = [this]() { sliderStoppedDragging(); };
}
void paint (Graphics&) override {}
void resized() override
{
auto area = getLocalBounds().reduced (0, 10);
valueLabel.setBounds (area.removeFromRight (80));
area.removeFromLeft (6);
slider.setBounds (area);
}
private:
void updateTextDisplay()
{
valueLabel.setText (getParameter().getCurrentValueAsText(), dontSendNotification);
}
void handleNewParameterValue() override
{
if (! isDragging)
{
slider.setValue (getParameter().getValue(), dontSendNotification);
updateTextDisplay();
}
}
void sliderValueChanged()
{
auto newVal = (float) slider.getValue();
if (getParameter().getValue() != newVal)
{
if (! isDragging)
getParameter().beginChangeGesture();
getParameter().setValueNotifyingHost ((float) slider.getValue());
updateTextDisplay();
if (! isDragging)
getParameter().endChangeGesture();
}
}
void sliderStartedDragging()
{
isDragging = true;
getParameter().beginChangeGesture();
}
void sliderStoppedDragging()
{
isDragging = false;
getParameter().endChangeGesture();
}
Slider slider { Slider::LinearHorizontal, Slider::TextEntryBoxPosition::NoTextBox };
Label valueLabel;
bool isDragging = false;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (SliderParameterComponent)
};
class ParameterDisplayComponent : public Component
{
public:
ParameterDisplayComponent (AudioProcessorParameter& param)
: parameter (param)
{
parameterName.setText (parameter.getName (128), dontSendNotification);
parameterName.setJustificationType (Justification::centredRight);
addAndMakeVisible (parameterName);
parameterLabel.setText (parameter.getLabel(), dontSendNotification);
addAndMakeVisible (parameterLabel);
if (param.isBoolean())
{
// The AU, AUv3 and VST (only via a .vstxml file) SDKs support
// marking a parameter as boolean. If you want consistency across
// all formats then it might be best to use a
// SwitchParameterComponent instead.
parameterComp.reset (new BooleanParameterComponent (param));
}
else if (param.getNumSteps() == 2)
{
// Most hosts display any parameter with just two steps as a switch.
parameterComp.reset (new SwitchParameterComponent (param));
}
else if (! param.getAllValueStrings().isEmpty())
{
// If we have a list of strings to represent the different states a
// parameter can be in then we should present a dropdown allowing a
// user to pick one of them.
parameterComp.reset (new ChoiceParameterComponent (param));
}
else
{
// Everything else can be represented as a slider.
parameterComp.reset (new SliderParameterComponent (param));
}
addAndMakeVisible (parameterComp);
setSize (400, 40);
}
void paint (Graphics&) override {}
void resized() override
{
auto area = getLocalBounds();
parameterName.setBounds (area.removeFromLeft (100));
parameterLabel.setBounds (area.removeFromRight (50));
parameterComp->setBounds (area);
}
private:
AudioProcessorParameter& parameter;
Label parameterName, parameterLabel;
ScopedPointer<Component> parameterComp;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ParameterDisplayComponent)
};
class ParametersPanel : public Component
{
public:
ParametersPanel (const OwnedArray<AudioProcessorParameter>& parameters)
{
for (auto* param : parameters)
if (param->isAutomatable())
addAndMakeVisible (paramComponents.add (new ParameterDisplayComponent (*param)));
if (auto* comp = paramComponents[0])
setSize (comp->getWidth(), comp->getHeight() * paramComponents.size());
else
setSize (400, 100);
}
void paint (Graphics& g) override
{
g.fillAll (getLookAndFeel().findColour (ResizableWindow::backgroundColourId));
}
void resized() override
{
auto area = getLocalBounds();
for (auto* comp : paramComponents)
comp->setBounds (area.removeFromTop (comp->getHeight()));
}
private:
OwnedArray<ParameterDisplayComponent> paramComponents;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ParametersPanel)
};
//==============================================================================
GenericAudioProcessorEditor::GenericAudioProcessorEditor (AudioProcessor* const p)
: AudioProcessorEditor (p)
{
jassert (p != nullptr);
setOpaque (true);
auto& parameters = p->getParameters();
if (parameters.size() == p->getNumParameters())
view.setViewedComponent (new ParametersPanel (parameters));
else
view.setViewedComponent (new LegacyParametersPanel (p));
addAndMakeVisible (view);
view.setScrollBarsShown (true, false);
setSize (view.getViewedComponent()->getWidth() + view.getVerticalScrollBar().getWidth(),
jmin (view.getViewedComponent()->getHeight(), 400));
}
GenericAudioProcessorEditor::~GenericAudioProcessorEditor() {}
void GenericAudioProcessorEditor::paint (Graphics& g)
{
g.fillAll (Colours::white);
g.fillAll (getLookAndFeel().findColour (ResizableWindow::backgroundColourId));
}
void GenericAudioProcessorEditor::resized()
{
panel.setBounds (getLocalBounds());
view.setBounds (getLocalBounds());
}
} // namespace juce

+ 2
- 2
modules/juce_audio_processors/processors/juce_GenericAudioProcessorEditor.h View File

@@ -30,7 +30,7 @@ namespace juce
//==============================================================================
/**
A type of UI component that displays the parameters of an AudioProcessor as
a simple list of sliders.
a simple list of sliders, combo boxes and switches.
This can be used for showing an editor for a processor that doesn't supply
its own custom editor.
@@ -50,7 +50,7 @@ public:
private:
//==============================================================================
PropertyPanel panel;
Viewport view;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (GenericAudioProcessorEditor)
};


Loading…
Cancel
Save