@@ -6,36 +6,60 @@ Develop | |||
Change | |||
------ | |||
InAppPurchases class is now a JUCE Singleton. This means that you need | |||
to get an instance via InAppPurchases::getInstance(), instead of storing a | |||
InAppPurchases object yourself. | |||
When hosting plug-ins all AudioProcessor methods of managing parameters that | |||
take a parameter index as an argument have been deprecated. | |||
Possible Issues | |||
--------------- | |||
A single assertion will be fired in debug builds on the first use of a | |||
deprecated function. | |||
Workaround | |||
---------- | |||
When hosting plug-ins you should use the AudioProcessor::getParameters() method | |||
and interact with parameters via the returned array of | |||
AudioProcessorParameters. For a short-term fix you can also continue past the | |||
assertion in your debugger, or temporarily modify the JUCE source code to | |||
remove it. | |||
Rationale | |||
--------- | |||
Given the structure of JUCE's API it is impossible to deprecate these functions | |||
using only complile-time messages. Therefore a single assertion, which can be | |||
safely ignored, serves to indicate that these functions should no longer be | |||
used. The move away from the AudioProcessor methods both improves the interface | |||
to that class and makes ongoing development work much easier. | |||
Change | |||
------ | |||
This InAppPurchases class is now a JUCE Singleton. This means that you need | |||
to get an instance via InAppPurchases::getInstance(), instead of storing a | |||
InAppPurchases object yourself. | |||
Possible Issues | |||
--------------- | |||
Any code using InAppPurchases needs to be updated to retrieve a singleton pointer | |||
to InAppPurchases. | |||
Workaround | |||
---------- | |||
Instead of holding a InAppPurchase member yourself, you should get an instance | |||
Instead of holding a InAppPurchase member yourself, you should get an instance | |||
via InAppPurchases::getInstance(), e.g. | |||
instead of: | |||
InAppPurchases iap; | |||
iap.purchaseProduct (…); | |||
iap.purchaseProduct (...); | |||
call: | |||
InAppPurchases::getInstance()->purchaseProduct (…); | |||
InAppPurchases::getInstance()->purchaseProduct (...); | |||
Rationale | |||
--------- | |||
This change was required to fix an issue on Android where on failed transaction | |||
a listener would not get called. | |||
a listener would not get called. | |||
Change | |||
@@ -35,9 +35,9 @@ class AUv3SynthEditor : public AudioProcessorEditor, | |||
public: | |||
//============================================================================== | |||
AUv3SynthEditor (AudioProcessor& processor) | |||
: AudioProcessorEditor (processor), | |||
recordButton ("Record"), | |||
roomSizeSlider (Slider::LinearHorizontal, Slider::NoTextBox) | |||
: AudioProcessorEditor (processor), | |||
recordButton ("Record"), | |||
roomSizeSlider (Slider::LinearHorizontal, Slider::NoTextBox) | |||
{ | |||
LookAndFeel::setDefaultLookAndFeel (&materialLookAndFeel); | |||
@@ -1835,7 +1835,7 @@ private: | |||
// using the default number of steps. | |||
for (auto* param : juceFilter->getParameters()) | |||
if (param->isDiscrete()) | |||
jassert (param->getNumSteps() != juceFilter->getDefaultNumParameterSteps()); | |||
jassert (param->getNumSteps() != AudioProcessor::getDefaultNumParameterSteps()); | |||
#endif | |||
parameterValueStringArrays.ensureStorageAllocated (numParams); | |||
@@ -1483,7 +1483,7 @@ private: | |||
return 0; | |||
} | |||
void valueChangedForObserver(AUParameterAddress, AUValue) | |||
void valueChangedForObserver (AUParameterAddress, AUValue) | |||
{ | |||
// this will have already been handled by valueChangedFromHost | |||
} | |||
@@ -302,6 +302,229 @@ class AudioUnitPluginWindowCocoa; | |||
class AudioUnitPluginInstance : public AudioPluginInstance | |||
{ | |||
public: | |||
struct AUInstanceParameter final : public Parameter | |||
{ | |||
AUInstanceParameter (AudioUnitPluginInstance& parent, | |||
UInt32 parameterID, | |||
const String& parameterName, | |||
AudioUnitParameterValue minParameterValue, | |||
AudioUnitParameterValue maxParameterValue, | |||
AudioUnitParameterValue defaultParameterValue, | |||
bool parameterIsAutomatable, | |||
bool parameterIsDiscrete, | |||
int numParameterSteps, | |||
bool isBoolean, | |||
const String& label, | |||
bool parameterValuesHaveStrings) | |||
: pluginInstance (parent), | |||
paramID (parameterID), | |||
name (parameterName), | |||
minValue (minParameterValue), | |||
maxValue (maxParameterValue), | |||
range (maxValue - minValue), | |||
automatable (parameterIsAutomatable), | |||
discrete (parameterIsDiscrete), | |||
numSteps (numParameterSteps), | |||
valuesHaveStrings (parameterValuesHaveStrings), | |||
isSwitch (isBoolean), | |||
valueLabel (label), | |||
defaultValue (normaliseParamValue (defaultParameterValue)) | |||
{ | |||
auValueStrings = Parameter::getAllValueStrings(); | |||
} | |||
virtual float getValue() const override | |||
{ | |||
const ScopedLock sl (pluginInstance.lock); | |||
AudioUnitParameterValue value = 0; | |||
if (auto* au = pluginInstance.audioUnit) | |||
{ | |||
AudioUnitGetParameter (au, | |||
paramID, | |||
kAudioUnitScope_Global, | |||
0, | |||
&value); | |||
value = normaliseParamValue (value); | |||
} | |||
return value; | |||
} | |||
virtual void setValue (float newValue) override | |||
{ | |||
const ScopedLock sl (pluginInstance.lock); | |||
if (auto* au = pluginInstance.audioUnit) | |||
{ | |||
AudioUnitSetParameter (au, | |||
paramID, | |||
kAudioUnitScope_Global, | |||
0, | |||
scaleParamValue (newValue), | |||
0); | |||
sendParameterChangeEvent(); | |||
} | |||
} | |||
float getDefaultValue() const override | |||
{ | |||
return defaultValue; | |||
} | |||
String getName (int /*maximumStringLength*/) const override | |||
{ | |||
return name; | |||
} | |||
String getLabel() const override | |||
{ | |||
return valueLabel; | |||
} | |||
String getText (float value, int maximumLength) const override | |||
{ | |||
if (! auValueStrings.isEmpty()) | |||
{ | |||
auto index = roundToInt (jlimit (0.0f, 1.0f, value) * (auValueStrings.size() - 1)); | |||
return auValueStrings[index]; | |||
} | |||
auto scaledValue = scaleParamValue (value); | |||
if (valuesHaveStrings) | |||
{ | |||
if (auto* au = pluginInstance.audioUnit) | |||
{ | |||
AudioUnitParameterStringFromValue stringValue; | |||
stringValue.inParamID = paramID; | |||
stringValue.inValue = &scaledValue; | |||
stringValue.outString = nullptr; | |||
UInt32 propertySize = sizeof (stringValue); | |||
OSStatus err = AudioUnitGetProperty (au, | |||
kAudioUnitProperty_ParameterStringFromValue, | |||
kAudioUnitScope_Global, | |||
0, | |||
&stringValue, | |||
&propertySize); | |||
if (! err && stringValue.outString != nullptr) | |||
return String::fromCFString (stringValue.outString).substring (0, maximumLength); | |||
} | |||
} | |||
return Parameter::getText (scaledValue, maximumLength); | |||
} | |||
float getValueForText (const String& text) const override | |||
{ | |||
if (! auValueStrings.isEmpty()) | |||
{ | |||
auto index = auValueStrings.indexOf (text); | |||
if (index != -1) | |||
return ((float) index) / (auValueStrings.size() - 1); | |||
} | |||
if (valuesHaveStrings) | |||
{ | |||
if (auto* au = pluginInstance.audioUnit) | |||
{ | |||
AudioUnitParameterValueFromString valueString; | |||
valueString.inParamID = paramID; | |||
valueString.inString = text.toCFString(); | |||
UInt32 propertySize = sizeof (valueString); | |||
OSStatus err = AudioUnitGetProperty (au, | |||
kAudioUnitProperty_ParameterValueFromString, | |||
kAudioUnitScope_Global, | |||
0, | |||
&valueString, | |||
&propertySize); | |||
if (! err) | |||
return normaliseParamValue (valueString.outValue); | |||
} | |||
} | |||
return Parameter::getValueForText (text); | |||
} | |||
bool isAutomatable() const override | |||
{ | |||
return automatable; | |||
} | |||
bool isDiscrete() const override | |||
{ | |||
return discrete; | |||
} | |||
bool isBoolean() const override | |||
{ | |||
return isSwitch; | |||
} | |||
int getNumSteps() const override | |||
{ | |||
return numSteps; | |||
} | |||
StringArray getAllValueStrings() const override | |||
{ | |||
return auValueStrings; | |||
} | |||
void sendParameterChangeEvent() | |||
{ | |||
#if JUCE_MAC | |||
jassert (pluginInstance.audioUnit != nullptr); | |||
AudioUnitEvent ev; | |||
ev.mEventType = kAudioUnitEvent_ParameterValueChange; | |||
ev.mArgument.mParameter.mAudioUnit = pluginInstance.audioUnit; | |||
ev.mArgument.mParameter.mParameterID = paramID; | |||
ev.mArgument.mParameter.mScope = kAudioUnitScope_Global; | |||
ev.mArgument.mParameter.mElement = 0; | |||
AUEventListenerNotify (pluginInstance.eventListenerRef, nullptr, &ev); | |||
#endif | |||
} | |||
float normaliseParamValue (float scaledValue) const noexcept | |||
{ | |||
if (discrete) | |||
return scaledValue / (getNumSteps() - 1); | |||
return (scaledValue - minValue) / range; | |||
} | |||
float scaleParamValue (float normalisedValue) const noexcept | |||
{ | |||
if (discrete) | |||
return normalisedValue * (getNumSteps() - 1); | |||
return minValue + (range * normalisedValue); | |||
} | |||
AudioUnitPluginInstance& pluginInstance; | |||
const UInt32 paramID; | |||
const String name; | |||
const AudioUnitParameterValue minValue, maxValue, range; | |||
const bool automatable, discrete; | |||
const int numSteps; | |||
const bool valuesHaveStrings, isSwitch; | |||
const String valueLabel; | |||
const AudioUnitParameterValue defaultValue; | |||
StringArray auValueStrings; | |||
}; | |||
AudioUnitPluginInstance (AudioComponentInstance au) | |||
: AudioPluginInstance (getBusesProperties (au)), | |||
auComponent (AudioComponentInstanceGetComponent (au)), | |||
@@ -937,66 +1160,6 @@ public: | |||
bool isOutputChannelStereoPair (int index) const override { return isPositiveAndBelow (index, getTotalNumOutputChannels()); } | |||
//============================================================================== | |||
int getNumParameters() override { return parameters.size(); } | |||
float getParameter (int index) override | |||
{ | |||
const ScopedLock sl (lock); | |||
AudioUnitParameterValue value = 0; | |||
if (audioUnit != nullptr) | |||
{ | |||
if (const ParamInfo* p = parameters[index]) | |||
{ | |||
AudioUnitGetParameter (audioUnit, | |||
p->paramID, | |||
kAudioUnitScope_Global, 0, | |||
&value); | |||
value = (value - p->minValue) / (p->maxValue - p->minValue); | |||
} | |||
} | |||
return value; | |||
} | |||
void setParameter (int index, float newValue) override | |||
{ | |||
const ScopedLock sl (lock); | |||
if (audioUnit != nullptr) | |||
{ | |||
if (const ParamInfo* p = parameters[index]) | |||
{ | |||
AudioUnitSetParameter (audioUnit, p->paramID, kAudioUnitScope_Global, 0, | |||
p->minValue + (p->maxValue - p->minValue) * newValue, 0); | |||
sendParameterChangeEvent (index); | |||
} | |||
} | |||
} | |||
void sendParameterChangeEvent (int index) | |||
{ | |||
#if JUCE_MAC | |||
jassert (audioUnit != nullptr); | |||
const ParamInfo& p = *parameters.getUnchecked (index); | |||
AudioUnitEvent ev; | |||
ev.mEventType = kAudioUnitEvent_ParameterValueChange; | |||
ev.mArgument.mParameter.mAudioUnit = audioUnit; | |||
ev.mArgument.mParameter.mParameterID = p.paramID; | |||
ev.mArgument.mParameter.mScope = kAudioUnitScope_Global; | |||
ev.mArgument.mParameter.mElement = 0; | |||
AUEventListenerNotify (eventListenerRef, nullptr, &ev); | |||
#else | |||
ignoreUnused (index); | |||
#endif | |||
} | |||
void sendAllParametersChangedEvents() | |||
{ | |||
#if JUCE_MAC | |||
@@ -1010,40 +1173,6 @@ public: | |||
#endif | |||
} | |||
const String getParameterName (int index) override | |||
{ | |||
if (auto* p = parameters[index]) | |||
return p->name; | |||
return {}; | |||
} | |||
const String getParameterText (int index) override { return String (getParameter (index)); } | |||
int getParameterNumSteps (int index) override | |||
{ | |||
if (auto* p = parameters[index]) | |||
return p->numSteps; | |||
return AudioProcessor::getDefaultNumParameterSteps(); | |||
} | |||
bool isParameterDiscrete (int index) const override | |||
{ | |||
if (auto* p = parameters[index]) | |||
return p->discrete; | |||
return false; | |||
} | |||
bool isParameterAutomatable (int index) const override | |||
{ | |||
if (auto* p = parameters[index]) | |||
return p->automatable; | |||
return false; | |||
} | |||
//============================================================================== | |||
int getNumPrograms() override | |||
{ | |||
@@ -1192,7 +1321,7 @@ public: | |||
void refreshParameterList() override | |||
{ | |||
parameters.clear(); | |||
managedParameters.clear(); | |||
paramIDToIndex.clear(); | |||
if (audioUnit != nullptr) | |||
@@ -1221,27 +1350,60 @@ public: | |||
kAudioUnitScope_Global, | |||
ids[i], &info, &sz) == noErr) | |||
{ | |||
ParamInfo* const param = new ParamInfo(); | |||
parameters.add (param); | |||
param->paramID = ids[i]; | |||
paramIDToIndex.getReference (ids[i]) = i; | |||
param->minValue = info.minValue; | |||
param->maxValue = info.maxValue; | |||
param->automatable = (info.flags & kAudioUnitParameterFlag_NonRealTime) == 0; | |||
param->discrete = (info.unit == kAudioUnitParameterUnit_Indexed); | |||
param->numSteps = param->discrete ? (int) (info.maxValue + 1.0f) : AudioProcessor::getDefaultNumParameterSteps(); | |||
String paramName; | |||
if ((info.flags & kAudioUnitParameterFlag_HasCFNameString) != 0) | |||
{ | |||
param->name = String::fromCFString (info.cfNameString); | |||
paramName = String::fromCFString (info.cfNameString); | |||
if ((info.flags & kAudioUnitParameterFlag_CFNameRelease) != 0) | |||
CFRelease (info.cfNameString); | |||
} | |||
else | |||
{ | |||
param->name = String (info.name, sizeof (info.name)); | |||
paramName = String (info.name, sizeof (info.name)); | |||
} | |||
bool isDiscrete = (info.unit == kAudioUnitParameterUnit_Indexed | |||
|| info.unit == kAudioUnitParameterUnit_Boolean); | |||
bool isBoolean = info.unit == kAudioUnitParameterUnit_Boolean; | |||
String label; | |||
switch (info.unit) | |||
{ | |||
case kAudioUnitParameterUnit_Percent: | |||
label = "%"; | |||
break; | |||
case kAudioUnitParameterUnit_Seconds: | |||
label = "s"; | |||
break; | |||
case kAudioUnitParameterUnit_Hertz: | |||
label = "Hz"; | |||
break; | |||
case kAudioUnitParameterUnit_Decibels: | |||
label = "dB"; | |||
break; | |||
case kAudioUnitParameterUnit_Milliseconds: | |||
label = "ms"; | |||
break; | |||
default: | |||
break; | |||
} | |||
addParameter (new AUInstanceParameter (*this, | |||
ids[i], | |||
paramName, | |||
info.minValue, | |||
info.maxValue, | |||
info.defaultValue, | |||
(info.flags & kAudioUnitParameterFlag_NonRealTime) == 0, | |||
isDiscrete, | |||
isDiscrete ? (int) (info.maxValue + 1.0f) : AudioProcessor::getDefaultNumParameterSteps(), | |||
isBoolean, | |||
label, | |||
(info.flags & kAudioUnitParameterFlag_ValuesHaveStrings) != 0)); | |||
} | |||
} | |||
} | |||
@@ -1310,16 +1472,6 @@ private: | |||
AUEventListenerRef eventListenerRef; | |||
#endif | |||
struct ParamInfo | |||
{ | |||
UInt32 paramID; | |||
String name; | |||
AudioUnitParameterValue minValue, maxValue; | |||
bool automatable, discrete; | |||
int numSteps; | |||
}; | |||
OwnedArray<ParamInfo> parameters; | |||
HashMap<uint32, size_t> paramIDToIndex; | |||
MidiDataConcatenator midiConcatenator; | |||
@@ -1360,22 +1512,25 @@ private: | |||
AUEventListenerCreate (eventListenerCallback, this, CFRunLoopGetMain(), | |||
kCFRunLoopDefaultMode, 0, 0, &eventListenerRef); | |||
for (int i = 0; i < parameters.size(); ++i) | |||
for (auto* param : getParameters()) | |||
{ | |||
AudioUnitEvent event; | |||
event.mArgument.mParameter.mAudioUnit = audioUnit; | |||
event.mArgument.mParameter.mParameterID = parameters.getUnchecked(i)->paramID; | |||
event.mArgument.mParameter.mScope = kAudioUnitScope_Global; | |||
event.mArgument.mParameter.mElement = 0; | |||
if (auto* auParam = dynamic_cast<AUInstanceParameter*> (param)) | |||
{ | |||
AudioUnitEvent event; | |||
event.mArgument.mParameter.mAudioUnit = audioUnit; | |||
event.mArgument.mParameter.mParameterID = auParam->paramID; | |||
event.mArgument.mParameter.mScope = kAudioUnitScope_Global; | |||
event.mArgument.mParameter.mElement = 0; | |||
event.mEventType = kAudioUnitEvent_ParameterValueChange; | |||
AUEventListenerAddEventType (eventListenerRef, nullptr, &event); | |||
event.mEventType = kAudioUnitEvent_ParameterValueChange; | |||
AUEventListenerAddEventType (eventListenerRef, nullptr, &event); | |||
event.mEventType = kAudioUnitEvent_BeginParameterChangeGesture; | |||
AUEventListenerAddEventType (eventListenerRef, nullptr, &event); | |||
event.mEventType = kAudioUnitEvent_BeginParameterChangeGesture; | |||
AUEventListenerAddEventType (eventListenerRef, nullptr, &event); | |||
event.mEventType = kAudioUnitEvent_EndParameterChangeGesture; | |||
AUEventListenerAddEventType (eventListenerRef, nullptr, &event); | |||
event.mEventType = kAudioUnitEvent_EndParameterChangeGesture; | |||
AUEventListenerAddEventType (eventListenerRef, nullptr, &event); | |||
} | |||
} | |||
addPropertyChangeListener (kAudioUnitProperty_PresentPreset); | |||
@@ -1413,25 +1568,36 @@ private: | |||
paramIndex = static_cast<int> (paramIDToIndex [paramID]); | |||
if (! isPositiveAndBelow (paramIndex, parameters.size())) | |||
if (! isPositiveAndBelow (paramIndex, getParameters().size())) | |||
return; | |||
} | |||
switch (event.mEventType) | |||
{ | |||
case kAudioUnitEvent_ParameterValueChange: | |||
if (auto* param = getParameters().getUnchecked (paramIndex)) | |||
{ | |||
auto& p = *parameters.getUnchecked (paramIndex); | |||
sendParamChangeMessageToListeners (paramIndex, (newValue - p.minValue) / (p.maxValue - p.minValue)); | |||
jassert (dynamic_cast<AUInstanceParameter*> (param) != nullptr); | |||
auto* auparam = static_cast<AUInstanceParameter*> (param); | |||
param->sendValueChangedMessageToListeners (auparam->normaliseParamValue (newValue)); | |||
} | |||
break; | |||
case kAudioUnitEvent_BeginParameterChangeGesture: | |||
beginParameterChangeGesture (paramIndex); | |||
if (auto* param = getParameters()[paramIndex]) | |||
param->beginChangeGesture(); | |||
else | |||
jassertfalse; // Invalid parameter index | |||
break; | |||
case kAudioUnitEvent_EndParameterChangeGesture: | |||
endParameterChangeGesture (paramIndex); | |||
if (auto* param = getParameters()[paramIndex]) | |||
param->endChangeGesture(); | |||
else | |||
jassertfalse; // Invalid parameter index | |||
break; | |||
default: | |||
@@ -112,11 +112,173 @@ private: | |||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (LADSPAModuleHandle) | |||
}; | |||
//============================================================================== | |||
class LADSPAPluginInstance : public AudioPluginInstance | |||
{ | |||
public: | |||
struct LADSPAParameter final : public Parameter | |||
{ | |||
struct ParameterValue | |||
{ | |||
inline ParameterValue() noexcept : scaled (0), unscaled (0) {} | |||
inline ParameterValue (float s, float u) noexcept : scaled (s), unscaled (u) {} | |||
float scaled, unscaled; | |||
}; | |||
LADSPAParameter (LADSPAPluginInstance& parent, | |||
int parameterID, | |||
const String& parameterName, | |||
bool parameterIsAutomatable) | |||
: pluginInstance (parent), | |||
paramID (parameterID), | |||
name (parameterName), | |||
automatable (parameterIsAutomatable) | |||
{ | |||
reset(); | |||
} | |||
virtual float getValue() const override | |||
{ | |||
if (pluginInstance.plugin != nullptr) | |||
{ | |||
const ScopedLock sl (pluginInstance.lock); | |||
return paramValue.unscaled; | |||
} | |||
return 0.0f; | |||
} | |||
String getCurrentValueAsText() const override | |||
{ | |||
if (auto* interface = pluginInstance.plugin) | |||
{ | |||
const LADSPA_PortRangeHint& hint = interface->PortRangeHints[paramID]; | |||
if (LADSPA_IS_HINT_INTEGER (hint.HintDescriptor)) | |||
return String ((int) paramValue.scaled); | |||
return String (paramValue.scaled, 4); | |||
} | |||
return {}; | |||
} | |||
virtual void setValue (float newValue) override | |||
{ | |||
if (auto* interface = pluginInstance.plugin) | |||
{ | |||
const ScopedLock sl (pluginInstance.lock); | |||
if (paramValue.unscaled != newValue) | |||
paramValue = ParameterValue (getNewParamScaled (interface->PortRangeHints [paramID], newValue), newValue); | |||
} | |||
} | |||
float getDefaultValue() const override | |||
{ | |||
return defaultValue; | |||
} | |||
ParameterValue getDefaultParamValue() const | |||
{ | |||
if (auto* interface = pluginInstance.plugin) | |||
{ | |||
const LADSPA_PortRangeHint& hint = interface->PortRangeHints[paramID]; | |||
const LADSPA_PortRangeHintDescriptor& desc = hint.HintDescriptor; | |||
if (LADSPA_IS_HINT_HAS_DEFAULT (desc)) | |||
{ | |||
if (LADSPA_IS_HINT_DEFAULT_0 (desc)) return ParameterValue(); | |||
if (LADSPA_IS_HINT_DEFAULT_1 (desc)) return ParameterValue (1.0f, 1.0f); | |||
if (LADSPA_IS_HINT_DEFAULT_100 (desc)) return ParameterValue (100.0f, 0.5f); | |||
if (LADSPA_IS_HINT_DEFAULT_440 (desc)) return ParameterValue (440.0f, 0.5f); | |||
const float scale = LADSPA_IS_HINT_SAMPLE_RATE (desc) ? (float) pluginInstance.getSampleRate() : 1.0f; | |||
const float lower = hint.LowerBound * scale; | |||
const float upper = hint.UpperBound * scale; | |||
if (LADSPA_IS_HINT_BOUNDED_BELOW (desc) && LADSPA_IS_HINT_DEFAULT_MINIMUM (desc)) return ParameterValue (lower, 0.0f); | |||
if (LADSPA_IS_HINT_BOUNDED_ABOVE (desc) && LADSPA_IS_HINT_DEFAULT_MAXIMUM (desc)) return ParameterValue (upper, 1.0f); | |||
if (LADSPA_IS_HINT_BOUNDED_BELOW (desc)) | |||
{ | |||
const bool useLog = LADSPA_IS_HINT_LOGARITHMIC (desc); | |||
if (LADSPA_IS_HINT_DEFAULT_LOW (desc)) return ParameterValue (scaledValue (lower, upper, 0.25f, useLog), 0.25f); | |||
if (LADSPA_IS_HINT_DEFAULT_MIDDLE (desc)) return ParameterValue (scaledValue (lower, upper, 0.50f, useLog), 0.50f); | |||
if (LADSPA_IS_HINT_DEFAULT_HIGH (desc)) return ParameterValue (scaledValue (lower, upper, 0.75f, useLog), 0.75f); | |||
} | |||
} | |||
} | |||
return ParameterValue(); | |||
} | |||
void reset() | |||
{ | |||
paramValue = getDefaultParamValue(); | |||
defaultValue = paramValue.unscaled; | |||
} | |||
String getName (int /*maximumStringLength*/) const override | |||
{ | |||
return name; | |||
} | |||
String getLabel() const override | |||
{ | |||
return {}; | |||
} | |||
bool isAutomatable() const override | |||
{ | |||
return automatable; | |||
} | |||
static float scaledValue (float low, float high, float alpha, bool useLog) noexcept | |||
{ | |||
if (useLog && low > 0 && high > 0) | |||
return expf (logf (low) * (1.0f - alpha) + logf (high) * alpha); | |||
return low + (high - low) * alpha; | |||
} | |||
static float toIntIfNecessary (const LADSPA_PortRangeHintDescriptor& desc, float value) | |||
{ | |||
return LADSPA_IS_HINT_INTEGER (desc) ? ((float) (int) value) : value; | |||
} | |||
float getNewParamScaled (const LADSPA_PortRangeHint& hint, float newValue) const | |||
{ | |||
const LADSPA_PortRangeHintDescriptor& desc = hint.HintDescriptor; | |||
if (LADSPA_IS_HINT_TOGGLED (desc)) | |||
return (newValue < 0.5f) ? 0.0f : 1.0f; | |||
const float scale = LADSPA_IS_HINT_SAMPLE_RATE (desc) ? (float) pluginInstance.getSampleRate() : 1.0f; | |||
const float lower = hint.LowerBound * scale; | |||
const float upper = hint.UpperBound * scale; | |||
if (LADSPA_IS_HINT_BOUNDED_BELOW (desc) && LADSPA_IS_HINT_BOUNDED_ABOVE (desc)) | |||
return toIntIfNecessary (desc, scaledValue (lower, upper, newValue, LADSPA_IS_HINT_LOGARITHMIC (desc))); | |||
if (LADSPA_IS_HINT_BOUNDED_BELOW (desc)) return toIntIfNecessary (desc, newValue); | |||
if (LADSPA_IS_HINT_BOUNDED_ABOVE (desc)) return toIntIfNecessary (desc, newValue * upper); | |||
return 0.0f; | |||
} | |||
LADSPAPluginInstance& pluginInstance; | |||
const int paramID; | |||
const String name; | |||
const bool automatable; | |||
ParameterValue paramValue; | |||
float defaultValue = 0; | |||
}; | |||
LADSPAPluginInstance (const LADSPAModuleHandle::Ptr& m) | |||
: module (m), plugin (nullptr), handle (nullptr), | |||
initialised (false), tempBuffer (1, 1) | |||
@@ -180,14 +342,17 @@ public: | |||
inputs.clear(); | |||
outputs.clear(); | |||
parameters.clear(); | |||
managedParameters.clear(); | |||
for (unsigned int i = 0; i < plugin->PortCount; ++i) | |||
{ | |||
const LADSPA_PortDescriptor portDesc = plugin->PortDescriptors[i]; | |||
if ((portDesc & LADSPA_PORT_CONTROL) != 0) | |||
parameters.add (i); | |||
addParameter (new LADSPAParameter (*this, | |||
i, | |||
String (plugin->PortNames[i]).trim(), | |||
(portDesc & LADSPA_PORT_INPUT) != 0)); | |||
if ((portDesc & LADSPA_PORT_AUDIO) != 0) | |||
{ | |||
@@ -196,10 +361,9 @@ public: | |||
} | |||
} | |||
parameterValues.calloc (parameters.size()); | |||
for (int i = 0; i < parameters.size(); ++i) | |||
plugin->connect_port (handle, parameters[i], &(parameterValues[i].scaled)); | |||
for (auto* param : getParameters()) | |||
if (auto* ladspaParam = dynamic_cast<LADSPAParameter*> (param)) | |||
plugin->connect_port (handle, ladspaParam->paramID, &(ladspaParam->paramValue.scaled)); | |||
setPlayConfigDetails (inputs.size(), outputs.size(), initialSampleRate, initialBlockSize); | |||
@@ -266,11 +430,11 @@ public: | |||
tempBuffer.setSize (jmax (1, outputs.size()), samplesPerBlockExpected); | |||
// dodgy hack to force some plugins to initialise the sample rate.. | |||
if (getNumParameters() > 0) | |||
if (auto* firstParam = getParameters()[0]) | |||
{ | |||
const float old = getParameter (0); | |||
setParameter (0, (old < 0.5f) ? 1.0f : 0.0f); | |||
setParameter (0, old); | |||
const float old = firstParam->getValue(); | |||
firstParam->setValue ((old < 0.5f) ? 1.0f : 0.0f); | |||
firstParam->setValue (old); | |||
} | |||
if (plugin->activate != nullptr) | |||
@@ -349,76 +513,15 @@ public: | |||
return {}; | |||
} | |||
//============================================================================== | |||
int getNumParameters() { return handle != nullptr ? parameters.size() : 0; } | |||
bool isParameterAutomatable (int index) const | |||
{ | |||
return plugin != nullptr | |||
&& (plugin->PortDescriptors [parameters[index]] & LADSPA_PORT_INPUT) != 0; | |||
} | |||
float getParameter (int index) | |||
{ | |||
if (plugin != nullptr && isPositiveAndBelow (index, parameters.size())) | |||
{ | |||
const ScopedLock sl (lock); | |||
return parameterValues[index].unscaled; | |||
} | |||
return 0.0f; | |||
} | |||
void setParameter (int index, float newValue) | |||
{ | |||
if (plugin != nullptr && isPositiveAndBelow (index, parameters.size())) | |||
{ | |||
const ScopedLock sl (lock); | |||
ParameterValue& p = parameterValues[index]; | |||
if (p.unscaled != newValue) | |||
p = ParameterValue (getNewParamScaled (plugin->PortRangeHints [parameters[index]], newValue), newValue); | |||
} | |||
} | |||
const String getParameterName (int index) | |||
{ | |||
if (plugin != nullptr) | |||
{ | |||
jassert (isPositiveAndBelow (index, parameters.size())); | |||
return String (plugin->PortNames [parameters [index]]).trim(); | |||
} | |||
return {}; | |||
} | |||
const String getParameterText (int index) | |||
{ | |||
if (plugin != nullptr) | |||
{ | |||
jassert (index >= 0 && index < parameters.size()); | |||
const LADSPA_PortRangeHint& hint = plugin->PortRangeHints [parameters [index]]; | |||
if (LADSPA_IS_HINT_INTEGER (hint.HintDescriptor)) | |||
return String ((int) parameterValues[index].scaled); | |||
return String (parameterValues[index].scaled, 4); | |||
} | |||
return {}; | |||
} | |||
//============================================================================== | |||
int getNumPrograms() { return 0; } | |||
int getCurrentProgram() { return 0; } | |||
void setCurrentProgram (int newIndex) | |||
void setCurrentProgram (int) | |||
{ | |||
if (plugin != nullptr) | |||
for (int i = 0; i < parameters.size(); ++i) | |||
parameterValues[i] = getParamValue (plugin->PortRangeHints [parameters[i]]); | |||
for (auto* param : getParameters()) | |||
if (auto* ladspaParam = dynamic_cast<LADSPAParameter*> (param)) | |||
ladspaParam->reset(); | |||
} | |||
const String getProgramName (int index) | |||
@@ -435,12 +538,15 @@ public: | |||
//============================================================================== | |||
void getStateInformation (MemoryBlock& destData) | |||
{ | |||
destData.setSize (sizeof (float) * getNumParameters()); | |||
auto numParameters = getParameters().size(); | |||
destData.setSize (sizeof (float) * numParameters); | |||
destData.fillWith (0); | |||
float* const p = (float*) ((char*) destData.getData()); | |||
for (int i = 0; i < getNumParameters(); ++i) | |||
p[i] = getParameter(i); | |||
for (int i = 0; i < numParameters; ++i) | |||
if (auto* param = getParameters()[i]) | |||
p[i] = param->getValue(); | |||
} | |||
void getCurrentProgramStateInformation (MemoryBlock& destData) | |||
@@ -452,8 +558,9 @@ public: | |||
{ | |||
const float* p = static_cast<const float*> (data); | |||
for (int i = 0; i < getNumParameters(); ++i) | |||
setParameter (i, p[i]); | |||
for (int i = 0; i < getParameters().size(); ++i) | |||
if (auto* param = getParameters()[i]) | |||
param->setValue (p[i]); | |||
} | |||
void setCurrentProgramStateInformation (const void* data, int sizeInBytes) | |||
@@ -485,82 +592,7 @@ private: | |||
CriticalSection lock; | |||
bool initialised; | |||
AudioBuffer<float> tempBuffer; | |||
Array<int> inputs, outputs, parameters; | |||
struct ParameterValue | |||
{ | |||
inline ParameterValue() noexcept : scaled (0), unscaled (0) {} | |||
inline ParameterValue (float s, float u) noexcept : scaled (s), unscaled (u) {} | |||
float scaled, unscaled; | |||
}; | |||
HeapBlock<ParameterValue> parameterValues; | |||
//============================================================================== | |||
static float scaledValue (float low, float high, float alpha, bool useLog) noexcept | |||
{ | |||
if (useLog && low > 0 && high > 0) | |||
return expf (logf (low) * (1.0f - alpha) + logf (high) * alpha); | |||
return low + (high - low) * alpha; | |||
} | |||
static float toIntIfNecessary (const LADSPA_PortRangeHintDescriptor& desc, float value) | |||
{ | |||
return LADSPA_IS_HINT_INTEGER (desc) ? ((float) (int) value) : value; | |||
} | |||
float getNewParamScaled (const LADSPA_PortRangeHint& hint, float newValue) const | |||
{ | |||
const LADSPA_PortRangeHintDescriptor& desc = hint.HintDescriptor; | |||
if (LADSPA_IS_HINT_TOGGLED (desc)) | |||
return (newValue < 0.5f) ? 0.0f : 1.0f; | |||
const float scale = LADSPA_IS_HINT_SAMPLE_RATE (desc) ? (float) getSampleRate() : 1.0f; | |||
const float lower = hint.LowerBound * scale; | |||
const float upper = hint.UpperBound * scale; | |||
if (LADSPA_IS_HINT_BOUNDED_BELOW (desc) && LADSPA_IS_HINT_BOUNDED_ABOVE (desc)) | |||
return toIntIfNecessary (desc, scaledValue (lower, upper, newValue, LADSPA_IS_HINT_LOGARITHMIC (desc))); | |||
if (LADSPA_IS_HINT_BOUNDED_BELOW (desc)) return toIntIfNecessary (desc, newValue); | |||
if (LADSPA_IS_HINT_BOUNDED_ABOVE (desc)) return toIntIfNecessary (desc, newValue * upper); | |||
return 0.0f; | |||
} | |||
ParameterValue getParamValue (const LADSPA_PortRangeHint& hint) const | |||
{ | |||
const LADSPA_PortRangeHintDescriptor& desc = hint.HintDescriptor; | |||
if (LADSPA_IS_HINT_HAS_DEFAULT (desc)) | |||
{ | |||
if (LADSPA_IS_HINT_DEFAULT_0 (desc)) return ParameterValue(); | |||
if (LADSPA_IS_HINT_DEFAULT_1 (desc)) return ParameterValue (1.0f, 1.0f); | |||
if (LADSPA_IS_HINT_DEFAULT_100 (desc)) return ParameterValue (100.0f, 0.5f); | |||
if (LADSPA_IS_HINT_DEFAULT_440 (desc)) return ParameterValue (440.0f, 0.5f); | |||
const float scale = LADSPA_IS_HINT_SAMPLE_RATE (desc) ? (float) getSampleRate() : 1.0f; | |||
const float lower = hint.LowerBound * scale; | |||
const float upper = hint.UpperBound * scale; | |||
if (LADSPA_IS_HINT_BOUNDED_BELOW (desc) && LADSPA_IS_HINT_DEFAULT_MINIMUM (desc)) return ParameterValue (lower, 0.0f); | |||
if (LADSPA_IS_HINT_BOUNDED_ABOVE (desc) && LADSPA_IS_HINT_DEFAULT_MAXIMUM (desc)) return ParameterValue (upper, 1.0f); | |||
if (LADSPA_IS_HINT_BOUNDED_BELOW (desc)) | |||
{ | |||
const bool useLog = LADSPA_IS_HINT_LOGARITHMIC (desc); | |||
if (LADSPA_IS_HINT_DEFAULT_LOW (desc)) return ParameterValue (scaledValue (lower, upper, 0.25f, useLog), 0.25f); | |||
if (LADSPA_IS_HINT_DEFAULT_MIDDLE (desc)) return ParameterValue (scaledValue (lower, upper, 0.50f, useLog), 0.50f); | |||
if (LADSPA_IS_HINT_DEFAULT_HIGH (desc)) return ParameterValue (scaledValue (lower, upper, 0.75f, useLog), 0.75f); | |||
} | |||
} | |||
return ParameterValue(); | |||
} | |||
Array<int> inputs, outputs; | |||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (LADSPAPluginInstance) | |||
}; | |||
@@ -283,7 +283,10 @@ struct VST3HostContext : public Vst::IComponentHandler, // From VST V3.0.0 | |||
if (index < 0) | |||
return kResultFalse; | |||
plugin->beginParameterChangeGesture (index); | |||
if (auto* param = plugin->getParameters()[index]) | |||
param->beginChangeGesture(); | |||
else | |||
jassertfalse; // Invalid parameter index! | |||
} | |||
return kResultTrue; | |||
@@ -298,7 +301,10 @@ struct VST3HostContext : public Vst::IComponentHandler, // From VST V3.0.0 | |||
if (index < 0) | |||
return kResultFalse; | |||
plugin->sendParamChangeMessageToListeners (index, (float) valueNormalized); | |||
if (auto* param = plugin->getParameters()[index]) | |||
param->sendValueChangedMessageToListeners ((float) valueNormalized); | |||
else | |||
jassertfalse; // Invalid parameter index! | |||
{ | |||
Steinberg::int32 eventIndex; | |||
@@ -322,7 +328,10 @@ struct VST3HostContext : public Vst::IComponentHandler, // From VST V3.0.0 | |||
if (index < 0) | |||
return kResultFalse; | |||
plugin->endParameterChangeGesture (index); | |||
if (auto* param = plugin->getParameters()[index]) | |||
param->endChangeGesture(); | |||
else | |||
jassertfalse; // Invalid parameter index! | |||
} | |||
return kResultTrue; | |||
@@ -1685,6 +1694,118 @@ struct VST3ComponentHolder | |||
//============================================================================== | |||
struct VST3PluginInstance : public AudioPluginInstance | |||
{ | |||
struct VST3Parameter final : public Parameter | |||
{ | |||
VST3Parameter (VST3PluginInstance& parent, | |||
Steinberg::Vst::ParamID parameterID, | |||
const String& parameterName, | |||
const String& parameterLabel, | |||
Steinberg::Vst::ParamValue defaultParameterValue, | |||
bool parameterIsAutomatable, | |||
bool parameterIsDiscrete, | |||
int numParameterSteps) | |||
: pluginInstance (parent), | |||
paramID (parameterID), | |||
name (parameterName), | |||
label (parameterLabel), | |||
defaultValue (defaultParameterValue), | |||
automatable (parameterIsAutomatable), | |||
discrete (parameterIsDiscrete), | |||
numSteps (numParameterSteps) | |||
{ | |||
} | |||
virtual float getValue() const override | |||
{ | |||
if (pluginInstance.editController != nullptr) | |||
{ | |||
return (float) pluginInstance.editController->getParamNormalized (paramID); | |||
} | |||
return 0.0f; | |||
} | |||
virtual void setValue (float newValue) override | |||
{ | |||
if (pluginInstance.editController != nullptr) | |||
{ | |||
pluginInstance.editController->setParamNormalized (paramID, (double) newValue); | |||
Steinberg::int32 index; | |||
pluginInstance.inputParameterChanges->addParameterData (paramID, index) | |||
->addPoint (0, newValue, index); | |||
} | |||
} | |||
String getText (float value, int maximumLength) const override | |||
{ | |||
if (pluginInstance.editController != nullptr) | |||
{ | |||
Vst::String128 result; | |||
if (pluginInstance.editController->getParamStringByValue (paramID, value, result) == kResultOk) | |||
return toString (result).substring (0, maximumLength); | |||
} | |||
return Parameter::getText (value, maximumLength); | |||
} | |||
float getValueForText (const String& text) const override | |||
{ | |||
if (pluginInstance.editController != nullptr) | |||
{ | |||
Vst::ParamValue result; | |||
if (pluginInstance.editController->getParamValueByString (paramID, toString (text), result) == kResultOk) | |||
return (float) result; | |||
} | |||
return Parameter::getValueForText (text); | |||
} | |||
float getDefaultValue() const override | |||
{ | |||
return (float) defaultValue; | |||
} | |||
String getName (int /*maximumStringLength*/) const override | |||
{ | |||
return name; | |||
} | |||
String getLabel() const override | |||
{ | |||
return label; | |||
} | |||
bool isAutomatable() const override | |||
{ | |||
return automatable; | |||
} | |||
bool isDiscrete() const override | |||
{ | |||
return discrete; | |||
} | |||
int getNumSteps() const override | |||
{ | |||
return numSteps; | |||
} | |||
StringArray getAllValueStrings() const override | |||
{ | |||
return {}; | |||
} | |||
VST3PluginInstance& pluginInstance; | |||
const Steinberg::Vst::ParamID paramID; | |||
const String name, label; | |||
const Steinberg::Vst::ParamValue defaultValue; | |||
const bool automatable, discrete; | |||
const int numSteps; | |||
}; | |||
VST3PluginInstance (VST3ComponentHolder* componentHolder) | |||
: AudioPluginInstance (getBusProperties (componentHolder->component)), | |||
holder (componentHolder), | |||
@@ -1752,9 +1873,30 @@ struct VST3PluginInstance : public AudioPluginInstance | |||
editController->setComponentHandler (holder->host); | |||
grabInformationObjects(); | |||
interconnectComponentAndController(); | |||
for (int i = 0; i < editController->getParameterCount(); ++i) | |||
{ | |||
Vst::ParameterInfo paramInfo = { 0 }; | |||
editController->getParameterInfo (i, paramInfo); | |||
bool isDiscrete = paramInfo.stepCount != 0; | |||
int numSteps = isDiscrete ? paramInfo.stepCount + 1 | |||
: AudioProcessor::getDefaultNumParameterSteps(); | |||
addParameter (new VST3Parameter (*this, | |||
paramInfo.id, | |||
toString (paramInfo.title), | |||
toString (paramInfo.units), | |||
paramInfo.defaultNormalizedValue, | |||
(paramInfo.flags & Vst::ParameterInfo::kCanAutomate) != 0, | |||
isDiscrete, | |||
numSteps)); | |||
} | |||
synchroniseStates(); | |||
syncProgramNames(); | |||
setupIO(); | |||
return true; | |||
} | |||
@@ -2158,93 +2300,6 @@ struct VST3PluginInstance : public AudioPluginInstance | |||
return view != nullptr; | |||
} | |||
//============================================================================== | |||
int getNumParameters() override | |||
{ | |||
if (editController != nullptr) | |||
return (int) editController->getParameterCount(); | |||
return 0; | |||
} | |||
const String getParameterName (int parameterIndex) override | |||
{ | |||
return toString (getParameterInfoForIndex (parameterIndex).title); | |||
} | |||
const String getParameterText (int parameterIndex) override | |||
{ | |||
if (editController != nullptr) | |||
{ | |||
auto id = getParameterInfoForIndex (parameterIndex).id; | |||
Vst::String128 result; | |||
warnOnFailure (editController->getParamStringByValue (id, editController->getParamNormalized (id), result)); | |||
return toString (result); | |||
} | |||
return {}; | |||
} | |||
int getParameterNumSteps (int parameterIndex) override | |||
{ | |||
if (editController != nullptr) | |||
{ | |||
const auto numSteps = getParameterInfoForIndex (parameterIndex).stepCount; | |||
if (numSteps > 0) | |||
return numSteps; | |||
} | |||
return AudioProcessor::getDefaultNumParameterSteps(); | |||
} | |||
bool isParameterDiscrete (int parameterIndex) const override | |||
{ | |||
if (editController != nullptr) | |||
{ | |||
const auto numSteps = getParameterInfoForIndex (parameterIndex).stepCount; | |||
return numSteps > 0; | |||
} | |||
return false; | |||
} | |||
bool isParameterAutomatable (int parameterIndex) const override | |||
{ | |||
if (editController != nullptr) | |||
{ | |||
auto flags = getParameterInfoForIndex (parameterIndex).flags; | |||
return (flags & Steinberg::Vst::ParameterInfo::kCanAutomate) != 0; | |||
} | |||
return true; | |||
} | |||
float getParameter (int parameterIndex) override | |||
{ | |||
if (editController != nullptr) | |||
{ | |||
auto id = getParameterInfoForIndex (parameterIndex).id; | |||
return (float) editController->getParamNormalized (id); | |||
} | |||
return 0.0f; | |||
} | |||
void setParameter (int parameterIndex, float newValue) override | |||
{ | |||
if (editController != nullptr) | |||
{ | |||
auto paramID = getParameterInfoForIndex (parameterIndex).id; | |||
editController->setParamNormalized (paramID, (double) newValue); | |||
Steinberg::int32 index; | |||
inputParameterChanges->addParameterData (paramID, index)->addPoint (0, newValue, index); | |||
} | |||
} | |||
//============================================================================== | |||
int getNumPrograms() override { return programNames.size(); } | |||
const String getProgramName (int index) override { return programNames[index]; } | |||
@@ -248,6 +248,330 @@ namespace | |||
#endif | |||
//============================================================================== | |||
class VSTXMLInfo | |||
{ | |||
public: | |||
static VSTXMLInfo* createFor (const juce::XmlElement& xml) | |||
{ | |||
if (xml.hasTagName ("VSTParametersStructure")) | |||
return new VSTXMLInfo (xml); | |||
if (const auto* x = xml.getChildByName ("VSTParametersStructure")) | |||
return new VSTXMLInfo (*x); | |||
return nullptr; | |||
} | |||
struct Group; | |||
struct Base | |||
{ | |||
Base() noexcept {} | |||
virtual ~Base() {} | |||
Group* parent = nullptr; | |||
}; | |||
struct Param : public Base | |||
{ | |||
int paramID; | |||
juce::String expr, name, label; | |||
juce::StringArray shortNames; | |||
juce::String type; | |||
int numberOfStates; | |||
float defaultValue; | |||
}; | |||
struct Group : public Base | |||
{ | |||
juce::String name; | |||
juce::OwnedArray<Base> paramTree; | |||
}; | |||
struct Range | |||
{ | |||
Range() noexcept {} | |||
Range (const juce::String& s) { set (s); } | |||
void set (const juce::String& s) | |||
{ | |||
inclusiveLow = s.startsWithChar ('['); | |||
inclusiveHigh = s.endsWithChar (']'); | |||
auto str = s.removeCharacters ("[]"); | |||
low = str.upToFirstOccurrenceOf (",", false, false).getFloatValue(); | |||
high = str.fromLastOccurrenceOf (",", false, false).getFloatValue(); | |||
} | |||
bool contains (float f) const noexcept | |||
{ | |||
return (inclusiveLow ? (f >= low) : (f > low)) | |||
&& (inclusiveHigh ? (f <= high) : (f < high)); | |||
} | |||
float low = 0; | |||
float high = 0; | |||
bool inclusiveLow = false; | |||
bool inclusiveHigh = false; | |||
}; | |||
struct Entry | |||
{ | |||
juce::String name; | |||
Range range; | |||
}; | |||
struct ValueType | |||
{ | |||
juce::String name, label; | |||
juce::OwnedArray<Entry> entries; | |||
}; | |||
struct Template | |||
{ | |||
juce::String name; | |||
juce::OwnedArray<Param> params; | |||
}; | |||
const Param* getParamForID (const int paramID, const Group* const grp) const | |||
{ | |||
for (auto item : (grp != nullptr ? grp->paramTree : paramTree)) | |||
{ | |||
if (auto param = dynamic_cast<const Param*> (item)) | |||
if (param->paramID == paramID) | |||
return param; | |||
if (auto group = dynamic_cast<const Group*> (item)) | |||
if (auto res = getParamForID (paramID, group)) | |||
return res; | |||
} | |||
return nullptr; | |||
} | |||
const ValueType* getValueType (const juce::String& name) const | |||
{ | |||
for (auto v : valueTypes) | |||
if (v->name == name) | |||
return v; | |||
return nullptr; | |||
} | |||
juce::OwnedArray<Base> paramTree; | |||
juce::OwnedArray<ValueType> valueTypes; | |||
juce::OwnedArray<Template> templates; | |||
ValueType switchValueType; | |||
private: | |||
VSTXMLInfo (const juce::XmlElement& xml) | |||
{ | |||
switchValueType.entries.add (new Entry({ TRANS("Off"), Range ("[0, 0.5[") })); | |||
switchValueType.entries.add (new Entry({ TRANS("On"), Range ("[0.5, 1]") })); | |||
forEachXmlChildElement (xml, item) | |||
{ | |||
if (item->hasTagName ("Param")) parseParam (*item, nullptr, nullptr); | |||
else if (item->hasTagName ("ValueType")) parseValueType (*item); | |||
else if (item->hasTagName ("Template")) parseTemplate (*item); | |||
else if (item->hasTagName ("Group")) parseGroup (*item, nullptr); | |||
} | |||
} | |||
void parseParam (const juce::XmlElement& item, Group* group, Template* temp) | |||
{ | |||
auto param = new Param(); | |||
if (temp != nullptr) | |||
param->expr = item.getStringAttribute ("id"); | |||
else | |||
param->paramID = item.getIntAttribute ("id"); | |||
param->name = item.getStringAttribute ("name"); | |||
param->label = item.getStringAttribute ("label"); | |||
param->type = item.getStringAttribute ("type"); | |||
param->numberOfStates = item.getIntAttribute ("numberOfStates"); | |||
param->defaultValue = (float) item.getDoubleAttribute ("defaultValue"); | |||
param->shortNames.addTokens (item.getStringAttribute ("shortName"), ",", juce::StringRef()); | |||
param->shortNames.trim(); | |||
param->shortNames.removeEmptyStrings(); | |||
if (group != nullptr) | |||
{ | |||
group->paramTree.add (param); | |||
param->parent = group; | |||
} | |||
else if (temp != nullptr) | |||
{ | |||
temp->params.add (param); | |||
} | |||
else | |||
{ | |||
paramTree.add (param); | |||
} | |||
} | |||
void parseValueType (const juce::XmlElement& item) | |||
{ | |||
auto vt = new ValueType(); | |||
valueTypes.add (vt); | |||
vt->name = item.getStringAttribute ("name"); | |||
vt->label = item.getStringAttribute ("label"); | |||
int curEntry = 0; | |||
const int numEntries = item.getNumChildElements(); | |||
forEachXmlChildElementWithTagName (item, entryXml, "Entry") | |||
{ | |||
auto entry = new Entry(); | |||
entry->name = entryXml->getStringAttribute ("name"); | |||
if (entryXml->hasAttribute ("value")) | |||
{ | |||
entry->range.set(entryXml->getStringAttribute ("value")); | |||
} | |||
else | |||
{ | |||
entry->range.low = curEntry / (float) numEntries; | |||
entry->range.high = (curEntry + 1) / (float) numEntries; | |||
entry->range.inclusiveLow = true; | |||
entry->range.inclusiveHigh = (curEntry == numEntries - 1); | |||
} | |||
vt->entries.add (entry); | |||
++curEntry; | |||
} | |||
} | |||
void parseTemplate (const juce::XmlElement& item) | |||
{ | |||
auto temp = new Template(); | |||
templates.add (temp); | |||
temp->name = item.getStringAttribute ("name"); | |||
forEachXmlChildElement (item, param) | |||
parseParam (*param, nullptr, temp); | |||
} | |||
void parseGroup (const juce::XmlElement& item, Group* parentGroup) | |||
{ | |||
auto group = new Group(); | |||
if (parentGroup) | |||
{ | |||
parentGroup->paramTree.add (group); | |||
group->parent = parentGroup; | |||
} | |||
else | |||
{ | |||
paramTree.add (group); | |||
} | |||
group->name = item.getStringAttribute ("name"); | |||
if (item.hasAttribute ("template")) | |||
{ | |||
juce::StringArray variables; | |||
variables.addTokens (item.getStringAttribute ("values"), ";", juce::StringRef()); | |||
variables.trim(); | |||
for (auto temp : templates) | |||
{ | |||
if (temp->name == item.getStringAttribute ("template")) | |||
{ | |||
for (int i = 0; i < temp->params.size(); ++i) | |||
{ | |||
auto param = new Param(); | |||
group->paramTree.add (param); | |||
param->parent = group; | |||
param->paramID = evaluate (temp->params[i]->expr, variables); | |||
param->defaultValue = temp->params[i]->defaultValue; | |||
param->label = temp->params[i]->label; | |||
param->name = temp->params[i]->name; | |||
param->numberOfStates = temp->params[i]->numberOfStates; | |||
param->shortNames = temp->params[i]->shortNames; | |||
param->type = temp->params[i]->type; | |||
} | |||
} | |||
} | |||
} | |||
else | |||
{ | |||
forEachXmlChildElement (item, subItem) | |||
{ | |||
if (subItem->hasTagName ("Param")) parseParam (*subItem, group, nullptr); | |||
else if (subItem->hasTagName ("Group")) parseGroup (*subItem, group); | |||
} | |||
} | |||
} | |||
int evaluate (juce::String expr, const juce::StringArray& variables) const | |||
{ | |||
juce::StringArray names; | |||
juce::Array<int> vals; | |||
for (auto& v : variables) | |||
{ | |||
if (v.contains ("=")) | |||
{ | |||
names.add (v.upToFirstOccurrenceOf ("=", false, false)); | |||
vals.add (v.fromFirstOccurrenceOf ("=", false, false).getIntValue()); | |||
} | |||
} | |||
for (int i = 0; i < names.size(); ++i) | |||
{ | |||
for (;;) | |||
{ | |||
const int idx = expr.indexOfWholeWord (names[i]); | |||
if (idx < 0) | |||
break; | |||
expr = expr.replaceSection (idx, names[i].length(), juce::String (vals[i])); | |||
} | |||
} | |||
expr = expr.retainCharacters ("01234567890-+") | |||
.replace ("+", " + ") | |||
.replace ("-", " - "); | |||
juce::StringArray tokens; | |||
tokens.addTokens (expr, " ", juce::StringRef()); | |||
bool add = true; | |||
int val = 0; | |||
for (const auto& s : tokens) | |||
{ | |||
if (s == "+") | |||
{ | |||
add = true; | |||
} | |||
else if (s == "-") | |||
{ | |||
add = false; | |||
} | |||
else | |||
{ | |||
if (add) | |||
val += s.getIntValue(); | |||
else | |||
val -= s.getIntValue(); | |||
} | |||
} | |||
return val; | |||
} | |||
}; | |||
//============================================================================== | |||
struct ModuleHandle : public ReferenceCountedObject | |||
{ | |||
@@ -498,12 +822,158 @@ static const int defaultVSTBlockSizeValue = 512; | |||
#pragma warning (disable: 4996) // warning about overriding deprecated methods | |||
#endif | |||
//============================================================================== | |||
//============================================================================== | |||
struct VSTPluginInstance : public AudioPluginInstance, | |||
private Timer, | |||
private AsyncUpdater | |||
{ | |||
struct VSTParameter final : public Parameter | |||
{ | |||
VSTParameter (VSTPluginInstance& parent, | |||
const String& paramName, | |||
const Array<String>& shortParamNames, | |||
float paramDefaultValue, | |||
const String& paramLabel, | |||
bool paramIsAutomatable, | |||
bool paramIsDiscrete, | |||
int numParamSteps, | |||
bool isBoolSwitch, | |||
const StringArray& paramValueStrings, | |||
const VSTXMLInfo::ValueType* paramValueType) | |||
: pluginInstance (parent), | |||
name (paramName), | |||
shortNames (shortParamNames), | |||
defaultValue (paramDefaultValue), | |||
label (paramLabel), | |||
automatable (paramIsAutomatable), | |||
discrete (paramIsDiscrete), | |||
numSteps (numParamSteps), | |||
isSwitch (isBoolSwitch), | |||
vstValueStrings (paramValueStrings), | |||
valueType (paramValueType) | |||
{ | |||
} | |||
virtual float getValue() const override | |||
{ | |||
if (auto* effect = pluginInstance.vstEffect) | |||
{ | |||
const ScopedLock sl (pluginInstance.lock); | |||
return effect->getParameterValueFunction (effect, getParameterIndex()); | |||
} | |||
return 0.0f; | |||
} | |||
virtual void setValue (float newValue) override | |||
{ | |||
if (auto* effect = pluginInstance.vstEffect) | |||
{ | |||
const ScopedLock sl (pluginInstance.lock); | |||
if (effect->getParameterValueFunction (effect, getParameterIndex()) != newValue) | |||
effect->setParameterValueFunction (effect, getParameterIndex(), newValue); | |||
} | |||
} | |||
String getText (float value, int maximumStringLength) const override | |||
{ | |||
if (valueType != nullptr) | |||
{ | |||
for (auto& v : valueType->entries) | |||
if (v->range.contains (value)) | |||
return v->name; | |||
} | |||
return Parameter::getText (value, maximumStringLength); | |||
} | |||
float getValueForText (const String& text) const override | |||
{ | |||
if (valueType != nullptr) | |||
{ | |||
for (auto& v : valueType->entries) | |||
if (v->name == text) | |||
return (v->range.high + v->range.low) / 2.0f; | |||
} | |||
return Parameter::getValueForText (text); | |||
} | |||
String getCurrentValueAsText() const override | |||
{ | |||
if (valueType != nullptr || ! vstValueStrings.isEmpty()) | |||
return getText (getValue(), 1024); | |||
return pluginInstance.getTextForOpcode (getParameterIndex(), plugInOpcodeGetParameterText); | |||
} | |||
float getDefaultValue() const override | |||
{ | |||
return defaultValue; | |||
} | |||
String getName (int maximumStringLength) const override | |||
{ | |||
if (name.length() <= maximumStringLength) | |||
return name; | |||
if (! shortNames.isEmpty()) | |||
{ | |||
for (auto& n : shortNames) | |||
if (n.length() <= maximumStringLength) | |||
return n; | |||
return shortNames.getLast(); | |||
} | |||
return name; | |||
} | |||
String getLabel() const override | |||
{ | |||
return label; | |||
} | |||
bool isAutomatable() const override | |||
{ | |||
return automatable; | |||
} | |||
bool isDiscrete() const override | |||
{ | |||
return discrete; | |||
} | |||
bool isBoolean() const override | |||
{ | |||
return isSwitch; | |||
} | |||
int getNumSteps() const override | |||
{ | |||
return numSteps; | |||
} | |||
StringArray getAllValueStrings() const override | |||
{ | |||
return vstValueStrings; | |||
} | |||
VSTPluginInstance& pluginInstance; | |||
const String name; | |||
const Array<String> shortNames; | |||
const float defaultValue; | |||
const String label; | |||
const bool automatable, discrete; | |||
const int numSteps; | |||
const bool isSwitch; | |||
const StringArray vstValueStrings; | |||
const VSTXMLInfo::ValueType* const valueType; | |||
}; | |||
VSTPluginInstance (const ModuleHandle::Ptr& mh, const BusesProperties& ioConfig, VstEffectInterface* effect, | |||
double sampleRateToUse, int blockSizeToUse) | |||
: AudioPluginInstance (ioConfig), | |||
@@ -511,6 +981,88 @@ struct VSTPluginInstance : public AudioPluginInstance, | |||
vstModule (mh), | |||
name (mh->pluginName) | |||
{ | |||
jassert (vstEffect != nullptr); | |||
if (auto* xml = vstModule->vstXml.get()) | |||
xmlInfo.reset (VSTXMLInfo::createFor (*xml)); | |||
for (int i = 0; i < vstEffect->numParameters; ++i) | |||
{ | |||
String paramName (getTextForOpcode (i, plugInOpcodeGetParameterName)); | |||
Array<String> shortParamNames; | |||
float defaultValue = 0; | |||
String label (getTextForOpcode (i, plugInOpcodeGetParameterLabel)); | |||
bool isAutomatable = dispatch (plugInOpcodeIsParameterAutomatable, i, 0, 0, 0) != 0; | |||
bool isDiscrete = false; | |||
int numSteps = AudioProcessor::getDefaultNumParameterSteps(); | |||
bool isBoolSwitch = false; | |||
StringArray parameterValueStrings; | |||
const VSTXMLInfo::ValueType* valueType = nullptr; | |||
if (xmlInfo != nullptr) | |||
{ | |||
if (auto* param = xmlInfo->getParamForID (i, nullptr)) | |||
{ | |||
paramName = param->name; | |||
for (auto& n : param->shortNames) | |||
shortParamNames.add (n); | |||
struct LengthComparator | |||
{ | |||
static int compareElements (const juce::String& first, const juce::String& second) noexcept | |||
{ | |||
return first.length() - second.length(); | |||
} | |||
}; | |||
LengthComparator comp; | |||
shortParamNames.sort (comp); | |||
defaultValue = param->defaultValue; | |||
label = param->label; | |||
if (param->type == "switch") | |||
{ | |||
isBoolSwitch = true; | |||
numSteps = 2; | |||
valueType = &xmlInfo->switchValueType; | |||
} | |||
else | |||
{ | |||
valueType = xmlInfo->getValueType (param->type); | |||
} | |||
if (param->numberOfStates >= 2) | |||
{ | |||
numSteps = param->numberOfStates; | |||
if (valueType != nullptr) | |||
{ | |||
for (auto* entry : valueType->entries) | |||
parameterValueStrings.add (entry->name); | |||
parameterValueStrings.removeEmptyStrings(); | |||
} | |||
} | |||
isDiscrete = (numSteps != AudioProcessor::getDefaultNumParameterSteps()); | |||
} | |||
} | |||
addParameter (new VSTParameter (*this, | |||
paramName, | |||
shortParamNames, | |||
defaultValue, | |||
label, | |||
isAutomatable, | |||
isDiscrete, | |||
numSteps, | |||
isBoolSwitch, | |||
parameterValueStrings, | |||
valueType)); | |||
} | |||
setRateAndBufferSizeDetails (sampleRateToUse, blockSizeToUse); | |||
} | |||
@@ -802,11 +1354,14 @@ struct VSTPluginInstance : public AudioPluginInstance, | |||
setPower (true); | |||
// dodgy hack to force some plugins to initialise the sample rate.. | |||
if ((! hasEditor()) && getNumParameters() > 0) | |||
if (! hasEditor()) | |||
{ | |||
auto old = getParameter (0); | |||
setParameter (0, (old < 0.5f) ? 1.0f : 0.0f); | |||
setParameter (0, old); | |||
if (auto* firstParam = getParameters()[0]) | |||
{ | |||
auto old = firstParam->getValue(); | |||
firstParam->setValue ((old < 0.5f) ? 1.0f : 0.0f); | |||
firstParam->setValue (old); | |||
} | |||
} | |||
dispatch (plugInOpcodeStartProcess, 0, 0, 0, 0); | |||
@@ -943,46 +1498,6 @@ struct VSTPluginInstance : public AudioPluginInstance, | |||
: getTotalNumOutputChannels()); | |||
} | |||
//============================================================================== | |||
int getNumParameters() override { return vstEffect != nullptr ? vstEffect->numParameters : 0; } | |||
float getParameter (int index) override | |||
{ | |||
if (vstEffect != nullptr && isPositiveAndBelow (index, vstEffect->numParameters)) | |||
{ | |||
const ScopedLock sl (lock); | |||
return vstEffect->getParameterValueFunction (vstEffect, index); | |||
} | |||
return 0.0f; | |||
} | |||
void setParameter (int index, float newValue) override | |||
{ | |||
if (vstEffect != nullptr && isPositiveAndBelow (index, vstEffect->numParameters)) | |||
{ | |||
const ScopedLock sl (lock); | |||
if (vstEffect->getParameterValueFunction (vstEffect, index) != newValue) | |||
vstEffect->setParameterValueFunction (vstEffect, index, newValue); | |||
} | |||
} | |||
const String getParameterName (int index) override { return getTextForOpcode (index, plugInOpcodeGetParameterName); } | |||
const String getParameterText (int index) override { return getTextForOpcode (index, plugInOpcodeGetParameterText); } | |||
String getParameterLabel (int index) const override { return getTextForOpcode (index, plugInOpcodeGetParameterLabel); } | |||
bool isParameterAutomatable (int index) const override | |||
{ | |||
if (vstEffect != nullptr) | |||
{ | |||
jassert (index >= 0 && index < vstEffect->numParameters); | |||
return dispatch (plugInOpcodeIsParameterAutomatable, index, 0, 0, 0) != 0; | |||
} | |||
return false; | |||
} | |||
//============================================================================== | |||
int getNumPrograms() override { return vstEffect != nullptr ? jmax (0, vstEffect->numPrograms) : 0; } | |||
@@ -1051,7 +1566,14 @@ struct VSTPluginInstance : public AudioPluginInstance, | |||
{ | |||
switch (opcode) | |||
{ | |||
case hostOpcodeParameterChanged: sendParamChangeMessageToListeners (index, opt); break; | |||
case hostOpcodeParameterChanged: | |||
if (auto* param = getParameters()[index]) | |||
param->sendValueChangedMessageToListeners (opt); | |||
else | |||
jassertfalse; // Invalid parameter index! | |||
break; | |||
case hostOpcodePreAudioProcessingEvents: handleMidiFromPlugin ((const VstEventBlock*) ptr); break; | |||
case hostOpcodeGetTimingInfo: return getVSTTime(); | |||
case hostOpcodeIdle: handleIdle(); break; | |||
@@ -1068,8 +1590,21 @@ struct VSTPluginInstance : public AudioPluginInstance, | |||
case hostOpcodeTempoAt: return (pointer_sized_int) (extraFunctions != nullptr ? extraFunctions->getTempoAt ((int64) value) : 0); | |||
case hostOpcodeGetAutomationState: return (pointer_sized_int) (extraFunctions != nullptr ? extraFunctions->getAutomationState() : 0); | |||
case hostOpcodeParameterChangeGestureBegin: beginParameterChangeGesture (index); break; | |||
case hostOpcodeParameterChangeGestureEnd: endParameterChangeGesture (index); break; | |||
case hostOpcodeParameterChangeGestureBegin: | |||
if (auto* param = getParameters()[index]) | |||
param->beginChangeGesture(); | |||
else | |||
jassertfalse; // Invalid parameter index! | |||
break; | |||
case hostOpcodeParameterChangeGestureEnd: | |||
if (auto* param = getParameters()[index]) | |||
param->endChangeGesture(); | |||
else | |||
jassertfalse; // Invalid parameter index! | |||
break; | |||
case hostOpcodePinConnected: return isValidChannel (index, value == 0) ? 0 : 1; // (yes, 0 = true) | |||
case hostOpcodeGetCurrentAudioProcessingLevel: return isNonRealtime() ? 4 : 0; | |||
@@ -1226,7 +1761,8 @@ struct VSTPluginInstance : public AudioPluginInstance, | |||
changeProgramName (getCurrentProgram(), prog->prgName); | |||
for (int i = 0; i < fxbSwap (prog->numParams); ++i) | |||
setParameter (i, fxbSwapFloat (prog->params[i])); | |||
if (auto* param = getParameters()[i]) | |||
param->setValue (fxbSwapFloat (prog->params[i])); | |||
} | |||
else if (compareMagic (set->fxMagic, "FBCh")) | |||
{ | |||
@@ -1261,7 +1797,7 @@ struct VSTPluginInstance : public AudioPluginInstance, | |||
bool saveToFXBFile (MemoryBlock& dest, bool isFXB, int maxSizeMB = 128) | |||
{ | |||
auto numPrograms = getNumPrograms(); | |||
auto numParams = getNumParameters(); | |||
auto numParams = getParameters().size(); | |||
if (usesChunks()) | |||
{ | |||
@@ -1413,6 +1949,8 @@ private: | |||
AudioBuffer<double> tmpBufferDouble; | |||
HeapBlock<double*> channelBufferDouble; | |||
ScopedPointer<VSTXMLInfo> xmlInfo; | |||
static pointer_sized_int handleCanDo (const char* name) | |||
{ | |||
static const char* canDos[] = { "supplyIdle", | |||
@@ -1832,7 +2370,8 @@ private: | |||
changeProgramName (getCurrentProgram(), prog->prgName); | |||
for (int i = 0; i < fxbSwap (prog->numParams); ++i) | |||
setParameter (i, fxbSwapFloat (prog->params[i])); | |||
if (auto* param = getParameters()[i]) | |||
param->setValue (fxbSwapFloat (prog->params[i])); | |||
return true; | |||
} | |||
@@ -1879,7 +2418,7 @@ private: | |||
void setParamsInProgramBlock (fxProgram* prog) | |||
{ | |||
auto numParams = getNumParameters(); | |||
auto numParams = getParameters().size(); | |||
prog->chunkMagic = fxbName ("CcnK"); | |||
prog->byteSize = 0; | |||
@@ -1892,7 +2431,8 @@ private: | |||
getCurrentProgramName().copyToUTF8 (prog->prgName, sizeof (prog->prgName) - 1); | |||
for (int i = 0; i < numParams; ++i) | |||
prog->params[i] = fxbSwapFloat (getParameter (i)); | |||
if (auto* param = getParameters()[i]) | |||
prog->params[i] = fxbSwapFloat (param->getValue()); | |||
} | |||
void updateStoredProgramNames() | |||
@@ -1932,15 +2472,17 @@ private: | |||
//============================================================================== | |||
void createTempParameterStore (MemoryBlock& dest) | |||
{ | |||
dest.setSize (64 + 4 * (size_t) getNumParameters()); | |||
auto numParameters = getParameters().size(); | |||
dest.setSize (64 + 4 * (size_t) numParameters); | |||
dest.fillWith (0); | |||
getCurrentProgramName().copyToUTF8 ((char*) dest.getData(), 63); | |||
auto p = (float*) (((char*) dest.getData()) + 64); | |||
for (int i = 0; i < getNumParameters(); ++i) | |||
p[i] = getParameter(i); | |||
for (int i = 0; i < numParameters; ++i) | |||
if (auto* param = getParameters()[i]) | |||
p[i] = param->getValue(); | |||
} | |||
void restoreFromTempParameterStore (const MemoryBlock& m) | |||
@@ -1948,9 +2490,11 @@ private: | |||
changeProgramName (getCurrentProgram(), (const char*) m.getData()); | |||
auto p = (float*) (((char*) m.getData()) + 64); | |||
auto numParameters = getParameters().size(); | |||
for (int i = 0; i < getNumParameters(); ++i) | |||
setParameter (i, p[i]); | |||
for (int i = 0; i < numParameters; ++i) | |||
if (auto* param = getParameters()[i]) | |||
param->setValue (p[i]); | |||
} | |||
pointer_sized_int getVstDirectory() const | |||
@@ -155,6 +155,7 @@ struct AutoResizingNSViewComponentWithParent : public AutoResizingNSViewCompone | |||
#include "format/juce_AudioPluginFormat.cpp" | |||
#include "format/juce_AudioPluginFormatManager.cpp" | |||
#include "processors/juce_AudioProcessor.cpp" | |||
#include "processors/juce_AudioPluginInstance.cpp" | |||
#include "processors/juce_AudioProcessorEditor.cpp" | |||
#include "processors/juce_AudioProcessorGraph.cpp" | |||
#include "processors/juce_GenericAudioProcessorEditor.cpp" | |||
@@ -0,0 +1,230 @@ | |||
/* | |||
============================================================================== | |||
This file is part of the JUCE library. | |||
Copyright (c) 2017 - ROLI Ltd. | |||
JUCE is an open source library subject to commercial or open-source | |||
licensing. | |||
By using JUCE, you agree to the terms of both the JUCE 5 End-User License | |||
Agreement and JUCE 5 Privacy Policy (both updated and effective as of the | |||
27th April 2017). | |||
End User License Agreement: www.juce.com/juce-5-licence | |||
Privacy Policy: www.juce.com/juce-5-privacy-policy | |||
Or: You may also use this code under the terms of the GPL v3 (see | |||
www.gnu.org/licenses). | |||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||
DISCLAIMED. | |||
============================================================================== | |||
*/ | |||
namespace juce | |||
{ | |||
PluginDescription AudioPluginInstance::getPluginDescription() const | |||
{ | |||
PluginDescription desc; | |||
fillInPluginDescription (desc); | |||
return desc; | |||
} | |||
String AudioPluginInstance::getParameterID (int parameterIndex) | |||
{ | |||
assertOnceOnDeprecatedMethodUse(); | |||
return String (parameterIndex); | |||
} | |||
float AudioPluginInstance::getParameter (int parameterIndex) | |||
{ | |||
assertOnceOnDeprecatedMethodUse(); | |||
if (auto* param = getParameters()[parameterIndex]) | |||
return param->getValue(); | |||
return 0.0f; | |||
} | |||
void AudioPluginInstance::setParameter (int parameterIndex, float newValue) | |||
{ | |||
assertOnceOnDeprecatedMethodUse(); | |||
if (auto* param = getParameters()[parameterIndex]) | |||
return param->setValue (newValue); | |||
} | |||
const String AudioPluginInstance::getParameterName (int parameterIndex) | |||
{ | |||
assertOnceOnDeprecatedMethodUse(); | |||
if (auto* param = getParameters()[parameterIndex]) | |||
return param->getName (1024); | |||
return {}; | |||
} | |||
const String AudioPluginInstance::getParameterText (int parameterIndex) | |||
{ | |||
assertOnceOnDeprecatedMethodUse(); | |||
if (auto* param = getParameters()[parameterIndex]) | |||
return param->getCurrentValueAsText(); | |||
return {}; | |||
} | |||
String AudioPluginInstance::getParameterText (int parameterIndex, int maximumStringLength) | |||
{ | |||
assertOnceOnDeprecatedMethodUse(); | |||
if (auto* param = getParameters()[parameterIndex]) | |||
return param->getCurrentValueAsText().substring (0, maximumStringLength); | |||
return {}; | |||
} | |||
float AudioPluginInstance::getParameterDefaultValue (int parameterIndex) | |||
{ | |||
assertOnceOnDeprecatedMethodUse(); | |||
if (auto* param = getParameters()[parameterIndex]) | |||
return param->getDefaultValue(); | |||
return 0.0f; | |||
} | |||
int AudioPluginInstance::getParameterNumSteps (int parameterIndex) | |||
{ | |||
assertOnceOnDeprecatedMethodUse(); | |||
if (auto* param = getParameters()[parameterIndex]) | |||
return param->getNumSteps(); | |||
return AudioProcessor::getDefaultNumParameterSteps(); | |||
} | |||
bool AudioPluginInstance::isParameterDiscrete (int parameterIndex) const | |||
{ | |||
assertOnceOnDeprecatedMethodUse(); | |||
if (auto* param = getParameters()[parameterIndex]) | |||
return param->isDiscrete(); | |||
return false; | |||
} | |||
bool AudioPluginInstance::isParameterAutomatable (int parameterIndex) const | |||
{ | |||
assertOnceOnDeprecatedMethodUse(); | |||
if (auto* param = getParameters()[parameterIndex]) | |||
return param->isAutomatable(); | |||
return true; | |||
} | |||
String AudioPluginInstance::getParameterLabel (int parameterIndex) const | |||
{ | |||
assertOnceOnDeprecatedMethodUse(); | |||
if (auto* param = getParameters()[parameterIndex]) | |||
return param->getLabel(); | |||
return {}; | |||
} | |||
bool AudioPluginInstance::isParameterOrientationInverted (int parameterIndex) const | |||
{ | |||
assertOnceOnDeprecatedMethodUse(); | |||
if (auto* param = getParameters()[parameterIndex]) | |||
return param->isOrientationInverted(); | |||
return false; | |||
} | |||
bool AudioPluginInstance::isMetaParameter (int parameterIndex) const | |||
{ | |||
assertOnceOnDeprecatedMethodUse(); | |||
if (auto* param = getParameters()[parameterIndex]) | |||
return param->isMetaParameter(); | |||
return false; | |||
} | |||
AudioProcessorParameter::Category AudioPluginInstance::getParameterCategory (int parameterIndex) const | |||
{ | |||
assertOnceOnDeprecatedMethodUse(); | |||
if (auto* param = getParameters()[parameterIndex]) | |||
return param->getCategory(); | |||
return AudioProcessorParameter::genericParameter; | |||
} | |||
void AudioPluginInstance::assertOnceOnDeprecatedMethodUse() const noexcept | |||
{ | |||
if (! deprecationAssertiontriggered) | |||
{ | |||
// If you hit this assertion then you are using at least one of the | |||
// methods marked as deprecated in this class. For now you can simply | |||
// continue past this point and subsequent uses of deprecated methods | |||
// will not trigger additional assertions. However, we will shortly be | |||
// removing these methods so you are strongly advised to look at the | |||
// implementation of the corresponding method in this class and use | |||
// that approach instead. | |||
jassertfalse; | |||
} | |||
deprecationAssertiontriggered = true; | |||
} | |||
bool AudioPluginInstance::deprecationAssertiontriggered = false; | |||
AudioPluginInstance::Parameter::Parameter() | |||
{ | |||
onStrings.add (TRANS("on")); | |||
onStrings.add (TRANS("yes")); | |||
onStrings.add (TRANS("true")); | |||
offStrings.add (TRANS("off")); | |||
offStrings.add (TRANS("no")); | |||
offStrings.add (TRANS("false")); | |||
} | |||
AudioPluginInstance::Parameter::~Parameter() {} | |||
String AudioPluginInstance::Parameter::getText (float value, int maximumStringLength) const | |||
{ | |||
if (isBoolean()) | |||
return value < 0.5f ? TRANS("Off") : TRANS("On"); | |||
return String (value).substring (0, maximumStringLength); | |||
} | |||
float AudioPluginInstance::Parameter::getValueForText (const String& text) const | |||
{ | |||
auto floatValue = text.retainCharacters ("-0123456789.").getFloatValue(); | |||
if (isBoolean()) | |||
{ | |||
if (onStrings.contains (text, true)) | |||
return 1.0f; | |||
if (offStrings.contains (text, true)) | |||
return 0.0f; | |||
return floatValue < 0.5f ? 0.0f : 1.0f; | |||
} | |||
return floatValue; | |||
} | |||
} // namespace juce |
@@ -58,12 +58,7 @@ public: | |||
/** Returns a PluginDescription for this plugin. | |||
This is just a convenience method to avoid calling fillInPluginDescription. | |||
*/ | |||
PluginDescription getPluginDescription() const | |||
{ | |||
PluginDescription desc; | |||
fillInPluginDescription (desc); | |||
return desc; | |||
} | |||
PluginDescription getPluginDescription() const; | |||
/** Returns a pointer to some kind of platform-specific data about the plugin. | |||
E.g. For a VST, this value can be cast to an AEffect*. For an AudioUnit, it can be | |||
@@ -76,13 +71,51 @@ public: | |||
*/ | |||
virtual void refreshParameterList() {} | |||
// Rather than using these methods you should call the corresponding methods | |||
// on the AudioProcessorParameter objects returned from getParameters(). | |||
// See the implementations of the methods below for some examples of how to | |||
// do this. | |||
// | |||
// In addition to being marked as deprecated these methods will assert on | |||
// the first call. | |||
JUCE_DEPRECATED (String getParameterID (int index) override); | |||
JUCE_DEPRECATED (float getParameter (int parameterIndex) override); | |||
JUCE_DEPRECATED (void setParameter (int parameterIndex, float newValue) override); | |||
JUCE_DEPRECATED (const String getParameterName (int parameterIndex) override); | |||
JUCE_DEPRECATED (const String getParameterText (int parameterIndex) override); | |||
JUCE_DEPRECATED (String getParameterText (int parameterIndex, int maximumStringLength) override); | |||
JUCE_DEPRECATED (int getParameterNumSteps (int parameterIndex) override); | |||
JUCE_DEPRECATED (bool isParameterDiscrete (int parameterIndex) const override); | |||
JUCE_DEPRECATED (bool isParameterAutomatable (int parameterIndex) const override); | |||
JUCE_DEPRECATED (float getParameterDefaultValue (int parameterIndex) override); | |||
JUCE_DEPRECATED (String getParameterLabel (int parameterIndex) const override); | |||
JUCE_DEPRECATED (bool isParameterOrientationInverted (int parameterIndex) const override); | |||
JUCE_DEPRECATED (bool isMetaParameter (int parameterIndex) const override); | |||
JUCE_DEPRECATED (AudioProcessorParameter::Category getParameterCategory (int parameterIndex) const override); | |||
protected: | |||
//============================================================================== | |||
struct Parameter : public AudioProcessorParameter | |||
{ | |||
Parameter(); | |||
virtual ~Parameter(); | |||
virtual String getText (float value, int maximumStringLength) const override; | |||
virtual float getValueForText (const String& text) const override; | |||
StringArray onStrings, offStrings; | |||
}; | |||
AudioPluginInstance() {} | |||
AudioPluginInstance (const BusesProperties& ioLayouts) : AudioProcessor (ioLayouts) {} | |||
template <int numLayouts> | |||
AudioPluginInstance (const short channelLayoutList[numLayouts][2]) : AudioProcessor (channelLayoutList) {} | |||
private: | |||
void assertOnceOnDeprecatedMethodUse() const noexcept; | |||
static bool deprecationAssertiontriggered; | |||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AudioPluginInstance) | |||
}; | |||
@@ -673,10 +673,6 @@ void AudioProcessor::addParameter (AudioProcessorParameter* p) | |||
p->parameterIndex = managedParameters.size(); | |||
managedParameters.add (p); | |||
// if you're using parameter objects, then you must not override the | |||
// deprecated getNumParameters() method! | |||
jassert (getNumParameters() == AudioProcessor::getNumParameters()); | |||
#ifdef JUCE_DEBUG | |||
shouldCheckParamsForDupeIDs = true; | |||
#endif | |||
@@ -1354,14 +1350,21 @@ int32 AudioProcessor::getAAXPluginIDForMainBusConfig (const AudioChannelSet& mai | |||
return (idForAudioSuite ? 0x6a796161 /* 'jyaa' */ : 0x6a636161 /* 'jcaa' */) + uniqueFormatId; | |||
} | |||
//============================================================================== | |||
void AudioProcessorListener::audioProcessorParameterChangeGestureBegin (AudioProcessor*, int) {} | |||
void AudioProcessorListener::audioProcessorParameterChangeGestureEnd (AudioProcessor*, int) {} | |||
//============================================================================== | |||
AudioProcessorParameter::AudioProcessorParameter() noexcept {} | |||
AudioProcessorParameter::~AudioProcessorParameter() {} | |||
AudioProcessorParameter::~AudioProcessorParameter() | |||
{ | |||
#if JUCE_DEBUG && ! JUCE_DISABLE_AUDIOPROCESSOR_BEGIN_END_GESTURE_CHECKING | |||
// This will fail if you've called beginChangeGesture() without having made | |||
// a corresponding call to endChangeGesture... | |||
jassert (! isPerformingGesture); | |||
#endif | |||
} | |||
void AudioProcessorParameter::setValueNotifyingHost (float newValue) | |||
{ | |||
@@ -1452,6 +1455,24 @@ String AudioProcessorParameter::getText (float value, int /*maximumStringLength* | |||
return String (value, 2); | |||
} | |||
String AudioProcessorParameter::getCurrentValueAsText() const | |||
{ | |||
return getText (getValue(), 1024); | |||
} | |||
StringArray AudioProcessorParameter::getAllValueStrings() const | |||
{ | |||
if (isDiscrete() && valueStrings.isEmpty()) | |||
{ | |||
auto maxIndex = getNumSteps() - 1; | |||
for (int i = 0; i < getNumSteps(); ++i) | |||
valueStrings.add (getText ((float) i / maxIndex, 1024)); | |||
} | |||
return valueStrings; | |||
} | |||
void AudioProcessorParameter::addListener (AudioProcessorParameter::Listener* newListener) | |||
{ | |||
const ScopedLock sl (listenerLock); | |||
@@ -1620,6 +1620,8 @@ private: | |||
friend class JuceVST3EditController; | |||
friend class JuceVST3Component; | |||
friend class AudioUnitPluginInstance; | |||
friend class LADSPAPluginInstance; | |||
Atomic<int> vst3IsPlaying { 0 }; | |||
@@ -199,6 +199,28 @@ public: | |||
/** Returns the index of this parameter in its parent processor's parameter list. */ | |||
int getParameterIndex() const noexcept { return parameterIndex; } | |||
//============================================================================== | |||
/** Returns the current value of the parameter as a String. | |||
This function can be called when you are hosting plug-ins to get a | |||
more specialsed textual represenation of the current value from the | |||
plug-in, for example "On" rather than "1.0". | |||
If you are implementing a plug-in then you should ignore this function | |||
and instead override getText. | |||
*/ | |||
virtual String getCurrentValueAsText() const; | |||
/** Returns the set of strings which represent the possible states a parameter | |||
can be in. | |||
If you are hosting a plug-in you can use the result of this funtion to | |||
populate a ComboBox listing the allowed values. | |||
If you are implementing a plug-in then you do not need to override this. | |||
*/ | |||
virtual StringArray getAllValueStrings() const; | |||
//============================================================================== | |||
/** | |||
A base class for listeners that want to know about changes to an | |||
@@ -267,6 +289,7 @@ private: | |||
int parameterIndex = -1; | |||
CriticalSection listenerLock; | |||
Array<Listener*> listeners; | |||
mutable StringArray valueStrings; | |||
#if JUCE_DEBUG | |||
bool isPerformingGesture = false; | |||
@@ -114,17 +114,17 @@ private: | |||
void startedDragging() override | |||
{ | |||
owner.beginParameterChangeGesture(index); | |||
owner.beginParameterChangeGesture (index); | |||
} | |||
void stoppedDragging() override | |||
{ | |||
owner.endParameterChangeGesture(index); | |||
owner.endParameterChangeGesture (index); | |||
} | |||
String getTextFromValue (double /*value*/) override | |||
{ | |||
return owner.getParameterText (index) + " " + owner.getParameterLabel (index).trimEnd(); | |||
return (owner.getParameterText (index) + " " + owner.getParameterLabel (index)).trimEnd(); | |||
} | |||
private: | |||
@@ -143,50 +143,540 @@ private: | |||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ProcessorParameterPropertyComp) | |||
}; | |||
struct LegacyParametersPanel : public Component | |||
{ | |||
LegacyParametersPanel (AudioProcessor* const processor) | |||
{ | |||
addAndMakeVisible (panel); | |||
Array<PropertyComponent*> params; | |||
auto numParams = processor->getNumParameters(); | |||
int totalHeight = 0; | |||
for (int i = 0; i < numParams; ++i) | |||
{ | |||
String name (processor->getParameterName (i)); | |||
if (name.trim().isEmpty()) | |||
name = "Unnamed"; | |||
auto* pc = new ProcessorParameterPropertyComp (name, *processor, i); | |||
params.add (pc); | |||
totalHeight += pc->getPreferredHeight(); | |||
} | |||
panel.addProperties (params); | |||
setSize (400, jmax (25, totalHeight)); | |||
} | |||
void paint (Graphics& g) override | |||
{ | |||
g.fillAll (getLookAndFeel().findColour (ResizableWindow::backgroundColourId)); | |||
} | |||
void resized() override | |||
{ | |||
panel.setBounds (getLocalBounds()); | |||
} | |||
PropertyPanel panel; | |||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (LegacyParametersPanel) | |||
}; | |||
//============================================================================== | |||
GenericAudioProcessorEditor::GenericAudioProcessorEditor (AudioProcessor* const p) | |||
: AudioProcessorEditor (p) | |||
class ParameterListener : private AudioProcessorParameter::Listener, | |||
private Timer | |||
{ | |||
jassert (p != nullptr); | |||
setOpaque (true); | |||
public: | |||
ParameterListener (AudioProcessorParameter& param) | |||
: parameter (param) | |||
{ | |||
parameter.addListener (this); | |||
addAndMakeVisible (panel); | |||
startTimer (100); | |||
} | |||
Array<PropertyComponent*> params; | |||
virtual ~ParameterListener() | |||
{ | |||
parameter.removeListener (this); | |||
} | |||
auto numParams = p->getNumParameters(); | |||
int totalHeight = 0; | |||
AudioProcessorParameter& getParameter() noexcept | |||
{ | |||
return parameter; | |||
} | |||
virtual void handleNewParameterValue() = 0; | |||
for (int i = 0; i < numParams; ++i) | |||
private: | |||
void parameterValueChanged (int, float) override | |||
{ | |||
auto name = p->getParameterName (i); | |||
parameterValueHasChanged = 1; | |||
} | |||
if (name.trim().isEmpty()) | |||
name = "Unnamed"; | |||
void parameterGestureChanged (int, bool) override {} | |||
auto* pc = new ProcessorParameterPropertyComp (name, *p, i); | |||
params.add (pc); | |||
totalHeight += pc->getPreferredHeight(); | |||
void timerCallback() override | |||
{ | |||
if (parameterValueHasChanged.compareAndSetBool (0, 1)) | |||
{ | |||
handleNewParameterValue(); | |||
startTimerHz (50); | |||
} | |||
else | |||
{ | |||
startTimer (jmin (250, getTimerInterval() + 10)); | |||
} | |||
} | |||
panel.addProperties (params); | |||
AudioProcessorParameter& parameter; | |||
Atomic<int> parameterValueHasChanged { 0 }; | |||
setSize (400, jlimit (25, 400, totalHeight)); | |||
} | |||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ParameterListener) | |||
}; | |||
GenericAudioProcessorEditor::~GenericAudioProcessorEditor() | |||
class BooleanParameterComponent final : public Component, | |||
private ParameterListener | |||
{ | |||
public: | |||
BooleanParameterComponent (AudioProcessorParameter& param) | |||
: ParameterListener (param) | |||
{ | |||
// Set the initial value. | |||
handleNewParameterValue(); | |||
button.onClick = [this]() { buttonClicked(); }; | |||
addAndMakeVisible (button); | |||
} | |||
void paint (Graphics&) override {} | |||
void resized() override | |||
{ | |||
auto area = getLocalBounds(); | |||
area.removeFromLeft (8); | |||
button.setBounds (area.reduced (0, 10)); | |||
} | |||
private: | |||
void handleNewParameterValue() override | |||
{ | |||
auto parameterState = getParameterState (getParameter().getValue()); | |||
if (button.getToggleState() != parameterState) | |||
button.setToggleState (parameterState, dontSendNotification); | |||
} | |||
void buttonClicked() | |||
{ | |||
if (getParameterState (getParameter().getValue()) != button.getToggleState()) | |||
{ | |||
getParameter().beginChangeGesture(); | |||
getParameter().setValueNotifyingHost (button.getToggleState() ? 1.0f : 0.0f); | |||
getParameter().endChangeGesture(); | |||
} | |||
} | |||
bool getParameterState (float value) const noexcept | |||
{ | |||
return value >= 0.5f; | |||
} | |||
ToggleButton button; | |||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (BooleanParameterComponent) | |||
}; | |||
class SwitchParameterComponent final : public Component, | |||
private ParameterListener | |||
{ | |||
public: | |||
SwitchParameterComponent (AudioProcessorParameter& param) | |||
: ParameterListener (param) | |||
{ | |||
auto* leftButton = buttons.add (new TextButton()); | |||
auto* rightButton = buttons.add (new TextButton()); | |||
for (auto* button : buttons) | |||
{ | |||
button->setRadioGroupId (293847); | |||
button->setClickingTogglesState (true); | |||
} | |||
leftButton ->setButtonText (getParameter().getText (0.0f, 16)); | |||
rightButton->setButtonText (getParameter().getText (1.0f, 16)); | |||
leftButton ->setConnectedEdges (Button::ConnectedOnRight); | |||
rightButton->setConnectedEdges (Button::ConnectedOnLeft); | |||
// Set the initial value. | |||
leftButton->setToggleState (true, dontSendNotification); | |||
handleNewParameterValue(); | |||
rightButton->onStateChange = [this]() { rightButtonChanged(); }; | |||
for (auto* button : buttons) | |||
addAndMakeVisible (button); | |||
} | |||
void paint (Graphics&) override {} | |||
void resized() override | |||
{ | |||
auto area = getLocalBounds().reduced (0, 8); | |||
area.removeFromLeft (8); | |||
for (auto* button : buttons) | |||
button->setBounds (area.removeFromLeft (80)); | |||
} | |||
private: | |||
void handleNewParameterValue() override | |||
{ | |||
bool newState = getParameterState(); | |||
if (buttons[1]->getToggleState() != newState) | |||
{ | |||
buttons[1]->setToggleState (newState, dontSendNotification); | |||
buttons[0]->setToggleState (! newState, dontSendNotification); | |||
} | |||
} | |||
void rightButtonChanged() | |||
{ | |||
auto buttonState = buttons[1]->getToggleState(); | |||
if (getParameterState() != buttonState) | |||
{ | |||
getParameter().beginChangeGesture(); | |||
if (getParameter().getAllValueStrings().isEmpty()) | |||
{ | |||
getParameter().setValueNotifyingHost (buttonState ? 1.0f : 0.0f); | |||
} | |||
else | |||
{ | |||
// When a parameter provides a list of strings we must set its | |||
// value using those strings, rather than a float, because VSTs can | |||
// have uneven spacing between the different allowed values and we | |||
// want the snapping behaviour to be consistent with what we do with | |||
// a combo box. | |||
String selectedText = buttonState ? buttons[1]->getButtonText() : buttons[0]->getButtonText(); | |||
getParameter().setValueNotifyingHost (getParameter().getValueForText (selectedText)); | |||
} | |||
getParameter().endChangeGesture(); | |||
} | |||
} | |||
bool getParameterState() | |||
{ | |||
if (getParameter().getAllValueStrings().isEmpty()) | |||
return getParameter().getValue() > 0.5f; | |||
auto index = getParameter().getAllValueStrings() | |||
.indexOf (getParameter().getCurrentValueAsText()); | |||
if (index < 0) | |||
{ | |||
// The parameter is producing some unexpected text, so we'll do | |||
// some linear interpolation. | |||
index = roundToInt (getParameter().getValue()); | |||
} | |||
return index == 1; | |||
} | |||
OwnedArray<TextButton> buttons; | |||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (SwitchParameterComponent) | |||
}; | |||
class ChoiceParameterComponent final : public Component, | |||
private ParameterListener | |||
{ | |||
public: | |||
ChoiceParameterComponent (AudioProcessorParameter& param) | |||
: ParameterListener (param), | |||
parameterValues (getParameter().getAllValueStrings()) | |||
{ | |||
box.addItemList (parameterValues, 1); | |||
// Set the initial value. | |||
handleNewParameterValue(); | |||
box.onChange = [this]() { boxChanged(); }; | |||
addAndMakeVisible (box); | |||
} | |||
void paint (Graphics&) override {} | |||
void resized() override | |||
{ | |||
auto area = getLocalBounds(); | |||
area.removeFromLeft (8); | |||
box.setBounds (area.reduced (0, 10)); | |||
} | |||
private: | |||
void handleNewParameterValue() override | |||
{ | |||
auto index = parameterValues.indexOf (getParameter().getCurrentValueAsText()); | |||
if (index < 0) | |||
{ | |||
// The parameter is producing some unexpected text, so we'll do | |||
// some linear interpolation. | |||
index = roundToInt (getParameter().getValue() * (parameterValues.size() - 1)); | |||
} | |||
box.setSelectedItemIndex (index); | |||
} | |||
void boxChanged() | |||
{ | |||
if (getParameter().getCurrentValueAsText() != box.getText()) | |||
{ | |||
getParameter().beginChangeGesture(); | |||
// When a parameter provides a list of strings we must set its | |||
// value using those strings, rather than a float, because VSTs can | |||
// have uneven spacing between the different allowed values. | |||
getParameter().setValueNotifyingHost (getParameter().getValueForText (box.getText())); | |||
getParameter().endChangeGesture(); | |||
} | |||
} | |||
ComboBox box; | |||
const StringArray parameterValues; | |||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ChoiceParameterComponent) | |||
}; | |||
class SliderParameterComponent final : public Component, | |||
private ParameterListener | |||
{ | |||
public: | |||
SliderParameterComponent (AudioProcessorParameter& param) | |||
: ParameterListener (param) | |||
{ | |||
if (getParameter().getNumSteps() != AudioProcessor::getDefaultNumParameterSteps()) | |||
slider.setRange (0.0, 1.0, 1.0 / (getParameter().getNumSteps() - 1.0)); | |||
else | |||
slider.setRange (0.0, 1.0); | |||
slider.setScrollWheelEnabled (false); | |||
addAndMakeVisible (slider); | |||
valueLabel.setColour (Label::outlineColourId, slider.findColour (Slider::textBoxOutlineColourId)); | |||
valueLabel.setBorderSize ({ 1, 1, 1, 1 }); | |||
valueLabel.setJustificationType (Justification::centred); | |||
addAndMakeVisible (valueLabel); | |||
// Set the initial value. | |||
handleNewParameterValue(); | |||
slider.onValueChange = [this]() { sliderValueChanged(); }; | |||
slider.onDragStart = [this]() { sliderStartedDragging(); }; | |||
slider.onDragEnd = [this]() { sliderStoppedDragging(); }; | |||
} | |||
void paint (Graphics&) override {} | |||
void resized() override | |||
{ | |||
auto area = getLocalBounds().reduced (0, 10); | |||
valueLabel.setBounds (area.removeFromRight (80)); | |||
area.removeFromLeft (6); | |||
slider.setBounds (area); | |||
} | |||
private: | |||
void updateTextDisplay() | |||
{ | |||
valueLabel.setText (getParameter().getCurrentValueAsText(), dontSendNotification); | |||
} | |||
void handleNewParameterValue() override | |||
{ | |||
if (! isDragging) | |||
{ | |||
slider.setValue (getParameter().getValue(), dontSendNotification); | |||
updateTextDisplay(); | |||
} | |||
} | |||
void sliderValueChanged() | |||
{ | |||
auto newVal = (float) slider.getValue(); | |||
if (getParameter().getValue() != newVal) | |||
{ | |||
if (! isDragging) | |||
getParameter().beginChangeGesture(); | |||
getParameter().setValueNotifyingHost ((float) slider.getValue()); | |||
updateTextDisplay(); | |||
if (! isDragging) | |||
getParameter().endChangeGesture(); | |||
} | |||
} | |||
void sliderStartedDragging() | |||
{ | |||
isDragging = true; | |||
getParameter().beginChangeGesture(); | |||
} | |||
void sliderStoppedDragging() | |||
{ | |||
isDragging = false; | |||
getParameter().endChangeGesture(); | |||
} | |||
Slider slider { Slider::LinearHorizontal, Slider::TextEntryBoxPosition::NoTextBox }; | |||
Label valueLabel; | |||
bool isDragging = false; | |||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (SliderParameterComponent) | |||
}; | |||
class ParameterDisplayComponent : public Component | |||
{ | |||
public: | |||
ParameterDisplayComponent (AudioProcessorParameter& param) | |||
: parameter (param) | |||
{ | |||
parameterName.setText (parameter.getName (128), dontSendNotification); | |||
parameterName.setJustificationType (Justification::centredRight); | |||
addAndMakeVisible (parameterName); | |||
parameterLabel.setText (parameter.getLabel(), dontSendNotification); | |||
addAndMakeVisible (parameterLabel); | |||
if (param.isBoolean()) | |||
{ | |||
// The AU, AUv3 and VST (only via a .vstxml file) SDKs support | |||
// marking a parameter as boolean. If you want consistency across | |||
// all formats then it might be best to use a | |||
// SwitchParameterComponent instead. | |||
parameterComp.reset (new BooleanParameterComponent (param)); | |||
} | |||
else if (param.getNumSteps() == 2) | |||
{ | |||
// Most hosts display any parameter with just two steps as a switch. | |||
parameterComp.reset (new SwitchParameterComponent (param)); | |||
} | |||
else if (! param.getAllValueStrings().isEmpty()) | |||
{ | |||
// If we have a list of strings to represent the different states a | |||
// parameter can be in then we should present a dropdown allowing a | |||
// user to pick one of them. | |||
parameterComp.reset (new ChoiceParameterComponent (param)); | |||
} | |||
else | |||
{ | |||
// Everything else can be represented as a slider. | |||
parameterComp.reset (new SliderParameterComponent (param)); | |||
} | |||
addAndMakeVisible (parameterComp); | |||
setSize (400, 40); | |||
} | |||
void paint (Graphics&) override {} | |||
void resized() override | |||
{ | |||
auto area = getLocalBounds(); | |||
parameterName.setBounds (area.removeFromLeft (100)); | |||
parameterLabel.setBounds (area.removeFromRight (50)); | |||
parameterComp->setBounds (area); | |||
} | |||
private: | |||
AudioProcessorParameter& parameter; | |||
Label parameterName, parameterLabel; | |||
ScopedPointer<Component> parameterComp; | |||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ParameterDisplayComponent) | |||
}; | |||
class ParametersPanel : public Component | |||
{ | |||
public: | |||
ParametersPanel (const OwnedArray<AudioProcessorParameter>& parameters) | |||
{ | |||
for (auto* param : parameters) | |||
if (param->isAutomatable()) | |||
addAndMakeVisible (paramComponents.add (new ParameterDisplayComponent (*param))); | |||
if (auto* comp = paramComponents[0]) | |||
setSize (comp->getWidth(), comp->getHeight() * paramComponents.size()); | |||
else | |||
setSize (400, 100); | |||
} | |||
void paint (Graphics& g) override | |||
{ | |||
g.fillAll (getLookAndFeel().findColour (ResizableWindow::backgroundColourId)); | |||
} | |||
void resized() override | |||
{ | |||
auto area = getLocalBounds(); | |||
for (auto* comp : paramComponents) | |||
comp->setBounds (area.removeFromTop (comp->getHeight())); | |||
} | |||
private: | |||
OwnedArray<ParameterDisplayComponent> paramComponents; | |||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ParametersPanel) | |||
}; | |||
//============================================================================== | |||
GenericAudioProcessorEditor::GenericAudioProcessorEditor (AudioProcessor* const p) | |||
: AudioProcessorEditor (p) | |||
{ | |||
jassert (p != nullptr); | |||
setOpaque (true); | |||
auto& parameters = p->getParameters(); | |||
if (parameters.size() == p->getNumParameters()) | |||
view.setViewedComponent (new ParametersPanel (parameters)); | |||
else | |||
view.setViewedComponent (new LegacyParametersPanel (p)); | |||
addAndMakeVisible (view); | |||
view.setScrollBarsShown (true, false); | |||
setSize (view.getViewedComponent()->getWidth() + view.getVerticalScrollBar().getWidth(), | |||
jmin (view.getViewedComponent()->getHeight(), 400)); | |||
} | |||
GenericAudioProcessorEditor::~GenericAudioProcessorEditor() {} | |||
void GenericAudioProcessorEditor::paint (Graphics& g) | |||
{ | |||
g.fillAll (Colours::white); | |||
g.fillAll (getLookAndFeel().findColour (ResizableWindow::backgroundColourId)); | |||
} | |||
void GenericAudioProcessorEditor::resized() | |||
{ | |||
panel.setBounds (getLocalBounds()); | |||
view.setBounds (getLocalBounds()); | |||
} | |||
} // namespace juce |
@@ -30,7 +30,7 @@ namespace juce | |||
//============================================================================== | |||
/** | |||
A type of UI component that displays the parameters of an AudioProcessor as | |||
a simple list of sliders. | |||
a simple list of sliders, combo boxes and switches. | |||
This can be used for showing an editor for a processor that doesn't supply | |||
its own custom editor. | |||
@@ -50,7 +50,7 @@ public: | |||
private: | |||
//============================================================================== | |||
PropertyPanel panel; | |||
Viewport view; | |||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (GenericAudioProcessorEditor) | |||
}; | |||