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