Browse Source

Add bypass support to both hosting and plug-in client code

tags/2021-05-28
hogliux 7 years ago
parent
commit
0db9415de6
13 changed files with 721 additions and 221 deletions
  1. +49
    -2
      extras/AudioPluginHost/Source/GraphEditorPanel.cpp
  2. +54
    -77
      modules/juce_audio_plugin_client/AAX/juce_AAX_Wrapper.cpp
  3. +32
    -6
      modules/juce_audio_plugin_client/AU/juce_AU_Wrapper.mm
  4. +50
    -3
      modules/juce_audio_plugin_client/AU/juce_AUv3_Wrapper.mm
  5. +17
    -1
      modules/juce_audio_plugin_client/VST/juce_VST_Wrapper.cpp
  6. +112
    -106
      modules/juce_audio_plugin_client/VST3/juce_VST3_Wrapper.cpp
  7. +147
    -2
      modules/juce_audio_processors/format_types/juce_AudioUnitPluginFormat.mm
  8. +76
    -12
      modules/juce_audio_processors/format_types/juce_VST3PluginFormat.cpp
  9. +100
    -4
      modules/juce_audio_processors/format_types/juce_VSTPluginFormat.cpp
  10. +29
    -0
      modules/juce_audio_processors/processors/juce_AudioProcessor.h
  11. +43
    -4
      modules/juce_audio_processors/processors/juce_AudioProcessorGraph.cpp
  12. +8
    -1
      modules/juce_audio_processors/processors/juce_AudioProcessorGraph.h
  13. +4
    -3
      modules/juce_audio_processors/processors/juce_GenericAudioProcessorEditor.cpp

+ 49
- 2
extras/AudioPluginHost/Source/GraphEditorPanel.cpp View File

@@ -109,19 +109,40 @@ struct GraphEditorPanel::PinComponent : public Component,
};
//==============================================================================
struct GraphEditorPanel::FilterComponent : public Component
struct GraphEditorPanel::FilterComponent : public Component, private AudioProcessorParameter::Listener
{
FilterComponent (GraphEditorPanel& p, uint32 id) : panel (p), graph (p.graph), pluginID (id)
{
shadow.setShadowProperties (DropShadow (Colours::black.withAlpha (0.5f), 3, { 0, 1 }));
setComponentEffect (&shadow);
if (auto f = graph.graph.getNodeForId (pluginID))
{
if (auto* processor = f->getProcessor())
{
if (auto* bypassParam = processor->getBypassParameter())
bypassParam->addListener (this);
}
}
setSize (150, 60);
}
FilterComponent (const FilterComponent&) = delete;
FilterComponent& operator= (const FilterComponent&) = delete;
~FilterComponent()
{
if (auto f = graph.graph.getNodeForId (pluginID))
{
if (auto* processor = f->getProcessor())
{
if (auto* bypassParam = processor->getBypassParameter())
bypassParam->removeListener (this);
}
}
}
void mouseDown (const MouseEvent& e) override
{
originalPos = localPointToGlobal (Point<int>());
@@ -177,8 +198,17 @@ struct GraphEditorPanel::FilterComponent : public Component
void paint (Graphics& g) override
{
auto boxArea = getLocalBounds().reduced (4, pinSize);
bool isBypassed = false;
if (auto* f = graph.graph.getNodeForId (pluginID))
isBypassed = f->isBypassed();
auto boxColour = findColour (TextEditor::backgroundColourId);
g.setColour (findColour (TextEditor::backgroundColourId));
if (isBypassed)
boxColour = boxColour.brighter();
g.setColour (boxColour);
g.fillRect (boxArea.toFloat());
g.setColour (findColour (TextEditor::textColourId));
@@ -290,6 +320,7 @@ struct GraphEditorPanel::FilterComponent : public Component
PopupMenu m;
m.addItem (1, "Delete this filter");
m.addItem (2, "Disconnect all pins");
m.addItem (3, "Toggle Bypass");
m.addSeparator();
m.addItem (10, "Show plugin GUI");
m.addItem (11, "Show all programs");
@@ -302,6 +333,15 @@ struct GraphEditorPanel::FilterComponent : public Component
{
case 1: graph.graph.removeNode (pluginID); break;
case 2: graph.graph.disconnectNode (pluginID); break;
case 3:
{
if (auto* node = graph.graph.getNodeForId (pluginID))
node->setBypassed (! node->isBypassed());
repaint();
break;
}
case 10: showWindow (PluginWindow::Type::normal); break;
case 11: showWindow (PluginWindow::Type::programs); break;
case 12: showWindow (PluginWindow::Type::generic); break;
@@ -329,6 +369,13 @@ struct GraphEditorPanel::FilterComponent : public Component
w->toFront (true);
}
void parameterValueChanged (int, float) override
{
repaint();
}
void parameterGestureChanged (int, bool) override {}
GraphEditorPanel& panel;
FilterGraph& graph;
const AudioProcessorGraph::NodeID pluginID;


+ 54
- 77
modules/juce_audio_plugin_client/AAX/juce_AAX_Wrapper.cpp View File

@@ -133,11 +133,6 @@ namespace AAXClasses
jassert (result == AAX_SUCCESS); ignoreUnused (result);
}
static bool isBypassParam (AAX_CParamID paramID) noexcept
{
return AAX::IsParameterIDEqual (paramID, cDefaultMasterBypassID) != 0;
}
// maps a channel index of an AAX format to an index of a juce format
struct AAXChannelStreamOrder
{
@@ -483,10 +478,12 @@ namespace AAXClasses
{
if (component != nullptr && component->pluginEditor != nullptr)
{
if (! isBypassParam (paramID))
auto index = getParamIndexFromID (paramID);
if (index >= 0)
{
AudioProcessorEditor::ParameterControlHighlightInfo info;
info.parameterIndex = getParamIndexFromID (paramID);
info.parameterIndex = index;
info.isHighlighted = (isHighlighted != 0);
info.suggestedColour = getColourFromHighlightEnum (colour);
@@ -660,7 +657,6 @@ namespace AAXClasses
if (err != AAX_SUCCESS)
return err;
addBypassParameter();
addAudioProcessorParameters();
return AAX_SUCCESS;
@@ -804,21 +800,13 @@ namespace AAXClasses
AAX_Result UpdateParameterNormalizedValue (AAX_CParamID paramID, double value, AAX_EUpdateSource source) override
{
auto result = AAX_CEffectParameters::UpdateParameterNormalizedValue (paramID, value, source);
if (! isBypassParam (paramID))
setAudioProcessorParameter (paramID, value);
setAudioProcessorParameter (paramID, value);
return result;
}
AAX_Result GetParameterValueFromString (AAX_CParamID paramID, double* result, const AAX_IString& text) const override
{
if (isBypassParam (paramID))
{
*result = (text.Get()[0] == 'B') ? 1.0 : 0.0;
return AAX_SUCCESS;
}
if (auto* param = getParameterFromID (paramID))
{
if (! LegacyAudioParameter::isLegacy (param))
@@ -833,39 +821,22 @@ namespace AAXClasses
AAX_Result GetParameterStringFromValue (AAX_CParamID paramID, double value, AAX_IString* result, int32_t maxLen) const override
{
if (isBypassParam (paramID))
{
result->Set (value == 0 ? "Off" : (maxLen >= 8 ? "Bypassed" : "Byp"));
}
else
{
if (auto* param = getParameterFromID (paramID))
result->Set (param->getText ((float) value, maxLen).toRawUTF8());
}
if (auto* param = getParameterFromID (paramID))
result->Set (param->getText ((float) value, maxLen).toRawUTF8());
return AAX_SUCCESS;
}
AAX_Result GetParameterNumberofSteps (AAX_CParamID paramID, int32_t* result) const
{
if (isBypassParam (paramID))
{
*result = 2;
}
else
{
if (auto* param = getParameterFromID (paramID))
*result = param->getNumSteps();
}
if (auto* param = getParameterFromID (paramID))
*result = param->getNumSteps();
return AAX_SUCCESS;
}
AAX_Result GetParameterNormalizedValue (AAX_CParamID paramID, double* result) const override
{
if (isBypassParam (paramID))
return AAX_CEffectParameters::GetParameterNormalizedValue (paramID, result);
if (auto* param = getParameterFromID (paramID))
*result = (double) param->getValue();
else
@@ -876,9 +847,6 @@ namespace AAXClasses
AAX_Result SetParameterNormalizedValue (AAX_CParamID paramID, double newValue) override
{
if (isBypassParam (paramID))
return AAX_CEffectParameters::SetParameterNormalizedValue (paramID, newValue);
if (auto* p = mParameterManager.GetParameterByID (paramID))
p->SetValueWithFloat ((float) newValue);
@@ -889,9 +857,6 @@ namespace AAXClasses
AAX_Result SetParameterNormalizedRelative (AAX_CParamID paramID, double newDeltaValue) override
{
if (isBypassParam (paramID))
return AAX_CEffectParameters::SetParameterNormalizedRelative (paramID, newDeltaValue);
if (auto* param = getParameterFromID (paramID))
{
auto newValue = param->getValue() + (float) newDeltaValue;
@@ -907,25 +872,15 @@ namespace AAXClasses
AAX_Result GetParameterNameOfLength (AAX_CParamID paramID, AAX_IString* result, int32_t maxLen) const override
{
if (isBypassParam (paramID))
{
result->Set (maxLen >= 13 ? "Master Bypass"
: (maxLen >= 8 ? "Mast Byp"
: (maxLen >= 6 ? "MstByp" : "MByp")));
}
else if (auto* param = getParameterFromID (paramID))
{
if (auto* param = getParameterFromID (paramID))
result->Set (param->getName (maxLen).toRawUTF8());
}
return AAX_SUCCESS;
}
AAX_Result GetParameterName (AAX_CParamID paramID, AAX_IString* result) const override
{
if (isBypassParam (paramID))
result->Set ("Master Bypass");
else if (auto* param = getParameterFromID (paramID))
if (auto* param = getParameterFromID (paramID))
result->Set (param->getName (31).toRawUTF8());
return AAX_SUCCESS;
@@ -933,15 +888,12 @@ namespace AAXClasses
AAX_Result GetParameterDefaultNormalizedValue (AAX_CParamID paramID, double* result) const override
{
if (! isBypassParam (paramID))
{
if (auto* param = getParameterFromID (paramID))
*result = (double) param->getDefaultValue();
else
*result = 0.0;
if (auto* param = getParameterFromID (paramID))
*result = (double) param->getDefaultValue();
else
*result = 0.0;
jassert (*result >= 0 && *result <= 1.0f);
}
jassert (*result >= 0 && *result <= 1.0f);
return AAX_SUCCESS;
}
@@ -1420,18 +1372,18 @@ namespace AAXClasses
#endif
}
void addBypassParameter()
bool isBypassPartOfRegularParemeters() const
{
auto* masterBypass = new AAX_CParameter<bool> (cDefaultMasterBypassID,
AAX_CString ("Master Bypass"),
false,
AAX_CBinaryTaperDelegate<bool>(),
AAX_CBinaryDisplayDelegate<bool> ("bypass", "on"),
true);
masterBypass->SetNumberOfSteps (2);
masterBypass->SetType (AAX_eParameterType_Discrete);
mParameterManager.AddParameter (masterBypass);
mPacketDispatcher.RegisterPacket (cDefaultMasterBypassID, JUCEAlgorithmIDs::bypass);
auto& audioProcessor = getPluginInstance();
int n = juceParameters.getNumParameters();
if (auto* bypassParam = audioProcessor.getBypassParameter())
for (int i = 0; i < n; ++i)
if (juceParameters.getParamForIndex (i) == bypassParam)
return true;
return false;
}
void addAudioProcessorParameters()
@@ -1444,14 +1396,35 @@ namespace AAXClasses
const bool forceLegacyParamIDs = false;
#endif
auto bypassPartOfRegularParams = isBypassPartOfRegularParemeters();
juceParameters.update (audioProcessor, forceLegacyParamIDs);
bool aaxWrapperProvidedBypassParam = false;
auto* bypassParameter = pluginInstance->getBypassParameter();
if (bypassParameter == nullptr)
{
aaxWrapperProvidedBypassParam = true;
ownedBypassParameter = new AudioParameterBool (cDefaultMasterBypassID, "Master Bypass", false, {}, {}, {});
bypassParameter = ownedBypassParameter;
}
if (! bypassPartOfRegularParams)
juceParameters.params.add (bypassParameter);
int parameterIndex = 0;
for (auto* juceParam : juceParameters.params)
{
auto isBypassParameter = (juceParam == bypassParameter);
auto category = juceParam->getCategory();
auto paramID = juceParameters.getParamID (audioProcessor, parameterIndex)
.toRawUTF8();
auto paramID = isBypassParameter ? cDefaultMasterBypassID
: juceParameters.getParamID (audioProcessor, parameterIndex)
.toRawUTF8();
aaxParamIDs.add (paramID);
auto aaxParamID = aaxParamIDs.getReference (parameterIndex++).getCharPointer();
@@ -1493,6 +1466,9 @@ namespace AAXClasses
| AAX_eParameterOrientation_RotarySingleDotMode | AAX_eParameterOrientation_RotaryLeftMinRightMax));
mParameterManager.AddParameter (parameter);
if (isBypassParameter)
mPacketDispatcher.RegisterPacket (aaxParamID, JUCEAlgorithmIDs::bypass);
}
}
@@ -1785,6 +1761,7 @@ namespace AAXClasses
Array<String> aaxParamIDs;
HashMap<int32, AudioProcessorParameter*> paramMap;
LegacyAudioParametersWrapper juceParameters;
ScopedPointer<AudioProcessorParameter> ownedBypassParameter;
Array<AudioProcessorParameter*> aaxMeters;


+ 32
- 6
modules/juce_audio_plugin_client/AU/juce_AU_Wrapper.mm View File

@@ -121,7 +121,8 @@ class JuceAU : public AudioProcessorHolder,
public MusicDeviceBase,
public AudioProcessorListener,
public AudioPlayHead,
public ComponentListener
public ComponentListener,
public AudioProcessorParameter::Listener
{
public:
JuceAU (AudioUnit component)
@@ -181,6 +182,9 @@ public:
~JuceAU()
{
if (bypassParam != nullptr)
bypassParam->removeListener (this);
deleteActiveEditors();
juceFilter = nullptr;
clearPresetsArray();
@@ -484,7 +488,10 @@ public:
return noErr;
case kAudioUnitProperty_BypassEffect:
*(UInt32*) outData = isBypassed ? 1 : 0;
if (bypassParam != nullptr)
*(UInt32*) outData = (bypassParam->getValue() != 0.0f ? 1 : 0);
else
*(UInt32*) outData = isBypassed ? 1 : 0;
return noErr;
case kAudioUnitProperty_SupportsMPE:
@@ -605,12 +612,16 @@ public:
return kAudioUnitErr_InvalidPropertyValue;
const bool newBypass = *((UInt32*) inData) != 0;
const bool currentlyBypassed = (bypassParam != nullptr ? (bypassParam->getValue() != 0.0f) : isBypassed);
if (newBypass != isBypassed)
if (newBypass != currentlyBypassed)
{
isBypassed = newBypass;
if (bypassParam != nullptr)
bypassParam->setValueNotifyingHost (newBypass ? 1.0f : 0.0f);
else
isBypassed = newBypass;
if (! isBypassed && IsInitialized()) // turning bypass off and we're initialized
if (! currentlyBypassed && IsInitialized()) // turning bypass off and we're initialized
Reset (0, 0);
}
@@ -1117,6 +1128,15 @@ public:
PropertyChanged (kAudioUnitProperty_PresentPreset, kAudioUnitScope_Global, 0);
}
//==============================================================================
// this will only ever be called by the bypass parameter
void parameterValueChanged (int, float) override
{
PropertyChanged (kAudioUnitProperty_BypassEffect, kAudioUnitScope_Global, 0);
}
void parameterGestureChanged (int, bool) override {}
//==============================================================================
bool StreamFormatWritable (AudioUnitScope scope, AudioUnitElement element) override
{
@@ -1690,6 +1710,9 @@ private:
//==============================================================================
OwnedArray<OwnedArray<const __CFString>> parameterValueStringArrays;
//==============================================================================
AudioProcessorParameter* bypassParam = nullptr;
//==============================================================================
void pullInputAudio (AudioUnitRenderActionFlags& flags, const AudioTimeStamp& timestamp, const UInt32 nFrames) noexcept
{
@@ -1730,7 +1753,7 @@ private:
{
buffer.clear();
}
else if (isBypassed)
else if (bypassParam == nullptr && isBypassed)
{
juceFilter->processBlockBypassed (buffer, midiBuffer);
}
@@ -1878,6 +1901,9 @@ private:
parameterValueStringArrays.add (stringValues);
}
if ((bypassParam = juceFilter->getBypassParameter()) != nullptr)
bypassParam->addListener (this);
}
//==============================================================================


+ 50
- 3
modules/juce_audio_plugin_client/AU/juce_AUv3_Wrapper.mm View File

@@ -195,6 +195,18 @@ public:
virtual bool getRenderingOffline() = 0;
virtual void setRenderingOffline (bool offline) = 0;
virtual bool getShouldBypassEffect()
{
objc_super s = { getAudioUnit(), [AUAudioUnit class] };
return (ObjCMsgSendSuper<BOOL> (&s, @selector (shouldBypassEffect)) == YES);
}
virtual void setShouldBypassEffect (bool shouldBypass)
{
objc_super s = { getAudioUnit(), [AUAudioUnit class] };
ObjCMsgSendSuper<void, BOOL> (&s, @selector (setShouldBypassEffect:), shouldBypass ? YES : NO);
}
//==============================================================================
virtual NSString* getContextName() const = 0;
virtual void setContextName (NSString*) = 0;
@@ -274,6 +286,8 @@ private:
addMethod (@selector (canProcessInPlace), getCanProcessInPlace, @encode (BOOL), "@:");
addMethod (@selector (isRenderingOffline), getRenderingOffline, @encode (BOOL), "@:");
addMethod (@selector (setRenderingOffline:), setRenderingOffline, "v@:", @encode (BOOL));
addMethod (@selector (shouldBypassEffect), getShouldBypassEffect, @encode (BOOL), "@:");
addMethod (@selector (setShouldBypassEffect:), setShouldBypassEffect, "v@:", @encode (BOOL));
addMethod (@selector (allocateRenderResourcesAndReturnError:), allocateRenderResourcesAndReturnError, "B@:^@");
addMethod (@selector (deallocateRenderResources), deallocateRenderResources, "v@:");
@@ -388,6 +402,8 @@ private:
static void setRenderingOffline (id self, SEL, BOOL renderingOffline) { _this (self)->setRenderingOffline (renderingOffline); }
static BOOL allocateRenderResourcesAndReturnError (id self, SEL, NSError** error) { return _this (self)->allocateRenderResourcesAndReturnError (error) ? YES : NO; }
static void deallocateRenderResources (id self, SEL) { _this (self)->deallocateRenderResources(); }
static BOOL getShouldBypassEffect (id self, SEL) { return _this (self)->getShouldBypassEffect() ? YES : NO; }
static void setShouldBypassEffect (id self, SEL, BOOL shouldBypass) { _this (self)->setShouldBypassEffect (shouldBypass); }
//==============================================================================
static NSString* getContextName (id self, SEL) { return _this (self)->getContextName(); }
@@ -417,7 +433,8 @@ JuceAudioUnitv3Base::Class JuceAudioUnitv3Base::audioUnitObjCClass;
//==============================================================================
class JuceAudioUnitv3 : public JuceAudioUnitv3Base,
public AudioProcessorListener,
public AudioPlayHead
public AudioPlayHead,
private AudioProcessorParameter::Listener
{
public:
JuceAudioUnitv3 (const AudioProcessorHolder::Ptr& processor,
@@ -444,6 +461,9 @@ public:
auto& processor = getAudioProcessor();
processor.removeListener (this);
if (bypassParam != nullptr)
bypassParam->removeListener (this);
removeEditor (processor);
if (editorObserverToken != nullptr)
@@ -735,6 +755,22 @@ public:
}
}
bool getShouldBypassEffect() override
{
if (bypassParam != nullptr)
return (bypassParam->getValue() != 0.0f);
return JuceAudioUnitv3Base::getShouldBypassEffect();
}
void setShouldBypassEffect (bool shouldBypass) override
{
if (bypassParam != nullptr)
bypassParam->setValue (shouldBypass ? 1.0f : 0.0f);
JuceAudioUnitv3Base::setShouldBypassEffect (shouldBypass);
}
//==============================================================================
NSString* getContextName() const override { return juceStringToNS (contextName); }
void setContextName (NSString* str) override
@@ -1216,7 +1252,6 @@ private:
#endif
// create methods in AUParameterTree return unretained objects (!) -> see Apple header AUAudioUnitImplementation.h
ScopedPointer<AUParameter> param = [[AUParameterTree createParameterWithIdentifier: juceStringToNS (identifier)
name: juceStringToNS (name)
address: address
@@ -1252,6 +1287,9 @@ private:
editorParamObserver = CreateObjCBlock (this, &JuceAudioUnitv3::valueChangedForObserver);
editorObserverToken = [paramTree tokenByAddingParameterObserver: editorParamObserver];
}
if ((bypassParam = processor.getBypassParameter()) != nullptr)
bypassParam->addListener (this);
}
void setAudioProcessorParameter (AudioProcessorParameter* juceParam, float value)
@@ -1454,7 +1492,7 @@ private:
if (processor.isSuspended())
buffer.clear();
else if ([au shouldBypassEffect])
else if (bypassParam != nullptr && [au shouldBypassEffect])
processor.processBlockBypassed (buffer, midiBuffer);
else
processor.processBlock (buffer, midiBuffer);
@@ -1526,6 +1564,14 @@ private:
}
//==============================================================================
// this is only ever called for the bypass parameter
void parameterValueChanged (int, float newValue) override
{
JuceAudioUnitv3Base::setShouldBypassEffect (newValue != 0.0f);
}
void parameterGestureChanged (int, bool) override {}
//==============================================================================
#if JUCE_FORCE_USE_LEGACY_PARAM_IDS
inline AUParameterAddress getAUParameterAddressForIndex (int paramIndex) const noexcept { return static_cast<AUParameterAddress> (paramIndex); }
inline int getJuceParameterIndexForAUAddress (AUParameterAddress address) const noexcept { return static_cast<int> (address); }
@@ -1610,6 +1656,7 @@ private:
#else
static constexpr bool forceLegacyParamIDs = false;
#endif
AudioProcessorParameter* bypassParam = nullptr;
};
const double JuceAudioUnitv3::kDefaultSampleRate = 44100.0;


+ 17
- 1
modules/juce_audio_plugin_client/VST/juce_VST_Wrapper.cpp View File

@@ -223,7 +223,8 @@ struct AbletonLiveHostSpecific
class JuceVSTWrapper : public AudioProcessorListener,
public AudioPlayHead,
private Timer,
private AsyncUpdater
private AsyncUpdater,
private AudioProcessorParameter::Listener
{
private:
//==============================================================================
@@ -282,6 +283,9 @@ public:
processor->setPlayHead (this);
processor->addListener (this);
if (auto* juceParam = processor->getBypassParameter())
juceParam->addListener (this);
juceParameters.update (*processor, false);
memset (&vstEffect, 0, sizeof (vstEffect));
@@ -760,6 +764,14 @@ public:
hostCallback (&vstEffect, hostOpcodeParameterChangeGestureEnd, index, 0, 0, 0);
}
void parameterValueChanged (int, float newValue) override
{
// this can only come from the bypass parameter
isBypassed = (newValue != 0.0f);
}
void parameterGestureChanged (int, bool) override {}
void audioProcessorChanged (AudioProcessor*) override
{
vstEffect.latency = processor->getLatencySamples();
@@ -1921,6 +1933,10 @@ private:
pointer_sized_int handleSetBypass (VstOpCodeArguments args)
{
isBypassed = (args.value != 0);
if (auto* bypass = processor->getBypassParameter())
bypass->setValueNotifyingHost (isBypassed ? 1.0f : 0.0f);
return 1;
}


+ 112
- 106
modules/juce_audio_plugin_client/VST3/juce_VST3_Wrapper.cpp View File

@@ -119,6 +119,11 @@ public:
return paramMap[static_cast<int32> (paramID)];
}
AudioProcessorParameter* getBypassParameter() const noexcept
{
return getParamForVSTParamID (bypassParamID);
}
int getNumParameters() const noexcept { return vstParamIDs.size(); }
bool isUsingManagedParameters() const noexcept { return juceParameters.isUsingManagedParameters(); }
@@ -126,7 +131,7 @@ public:
static const FUID iid;
Array<Vst::ParamID> vstParamIDs;
Vst::ParamID bypassParamID = 0;
bool isBypassed = false;
bool bypassIsRegularParameter = false;
private:
enum InternalParameters
@@ -135,6 +140,18 @@ private:
};
//==============================================================================
bool isBypassPartOfRegularParemeters() const
{
int n = juceParameters.getNumParameters();
if (auto* bypassParam = audioProcessor->getBypassParameter())
for (int i = 0; i < n; ++i)
if (juceParameters.getParamForIndex (i) == bypassParam)
return true;
return false;
}
void setupParameters()
{
#if JUCE_FORCE_USE_LEGACY_PARAM_IDS
@@ -146,17 +163,42 @@ private:
juceParameters.update (*audioProcessor, forceLegacyParamIDs);
auto numParameters = juceParameters.getNumParameters();
bool vst3WrapperProvidedBypassParam = false;
auto* bypassParameter = audioProcessor->getBypassParameter();
if (bypassParameter == nullptr)
{
vst3WrapperProvidedBypassParam = true;
bypassParameter = ownedBypassParameter = new AudioParameterBool ("byps", "Bypass", false, {}, {}, {});
}
// if the bypass parameter is not part of the exported parameters that the plug-in supports
// then add it to the end of the list as VST3 requires the bypass parameter to be exported!
bypassIsRegularParameter = isBypassPartOfRegularParemeters();
if (! bypassIsRegularParameter)
juceParameters.params.add (bypassParameter);
int i = 0;
for (auto* juceParam : juceParameters.params)
{
bool isBypassParameter = (juceParam == bypassParameter);
Vst::ParamID vstParamID = forceLegacyParamIDs ? static_cast<Vst::ParamID> (i++)
: generateVSTParamIDForParam (juceParam);
if (isBypassParameter)
{
// we need to remain backward compatible with the old bypass id
if (vst3WrapperProvidedBypassParam)
vstParamID = static_cast<Vst::ParamID> (isUsingManagedParameters() ? paramBypass : numParameters);
bypassParamID = vstParamID;
}
vstParamIDs.add (vstParamID);
paramMap.set (static_cast<int32> (vstParamID), juceParam);
}
bypassParamID = static_cast<Vst::ParamID> (isUsingManagedParameters() ? paramBypass : numParameters);
}
Vst::ParamID generateVSTParamIDForParam (AudioProcessorParameter* param)
@@ -181,6 +223,7 @@ private:
//==============================================================================
LegacyAudioParametersWrapper juceParameters;
HashMap<int32, AudioProcessorParameter*> paramMap;
ScopedPointer<AudioProcessorParameter> ownedBypassParameter;
JuceAudioProcessor() = delete;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (JuceAudioProcessor)
@@ -194,7 +237,8 @@ static ThreadLocalValue<bool> inParameterChangedCallback;
class JuceVST3EditController : public Vst::EditController,
public Vst::IMidiMapping,
public Vst::ChannelContext::IInfoListener,
public AudioProcessorListener
public AudioProcessorListener,
private AudioProcessorParameter::Listener
{
public:
JuceVST3EditController (Vst::IHostApplication* host)
@@ -279,7 +323,7 @@ public:
struct Param : public Vst::Parameter
{
Param (JuceVST3EditController& editController, AudioProcessorParameter& p,
Vst::ParamID vstParamID, bool forceLegacyParamIDs)
Vst::ParamID vstParamID, bool isBypassParameter, bool forceLegacyParamIDs)
: owner (editController), param (p)
{
info.id = vstParamID;
@@ -306,6 +350,9 @@ public:
else
info.flags = param.isAutomatable() ? Vst::ParameterInfo::kCanAutomate : 0;
if (isBypassParameter)
info.flags |= Vst::ParameterInfo::kIsBypass;
valueNormalized = info.defaultNormalizedValue;
}
@@ -370,88 +417,6 @@ public:
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Param)
};
//==============================================================================
struct BypassParam : public Vst::Parameter
{
BypassParam (Vst::ParamID vstParamID)
{
info.id = vstParamID;
toString128 (info.title, "Bypass");
toString128 (info.shortTitle, "Bypass");
toString128 (info.units, "");
info.stepCount = 1;
info.defaultNormalizedValue = 0.0f;
info.unitId = Vst::kRootUnitId;
info.flags = Vst::ParameterInfo::kIsBypass | Vst::ParameterInfo::kCanAutomate;
}
virtual ~BypassParam() {}
bool setNormalized (Vst::ParamValue v) override
{
bool bypass = (v != 0.0f);
v = (bypass ? 1.0f : 0.0f);
if (valueNormalized != v)
{
valueNormalized = v;
changed();
return true;
}
return false;
}
void toString (Vst::ParamValue value, Vst::String128 result) const override
{
bool bypass = (value != 0.0f);
toString128 (result, bypass ? "On" : "Off");
}
bool fromString (const Vst::TChar* text, Vst::ParamValue& outValueNormalized) const override
{
auto paramValueString = getStringFromVstTChars (text);
if (paramValueString.equalsIgnoreCase ("on")
|| paramValueString.equalsIgnoreCase ("yes")
|| paramValueString.equalsIgnoreCase ("true"))
{
outValueNormalized = 1.0f;
return true;
}
if (paramValueString.equalsIgnoreCase ("off")
|| paramValueString.equalsIgnoreCase ("no")
|| paramValueString.equalsIgnoreCase ("false"))
{
outValueNormalized = 0.0f;
return true;
}
var varValue = JSON::fromString (paramValueString);
if (varValue.isDouble() || varValue.isInt()
|| varValue.isInt64() || varValue.isBool())
{
double value = varValue;
outValueNormalized = (value != 0.0) ? 1.0f : 0.0f;
return true;
}
return false;
}
static String getStringFromVstTChars (const Vst::TChar* text)
{
return juce::String (juce::CharPointer_UTF16 (reinterpret_cast<const juce::CharPointer_UTF16::CharType*> (text)));
}
Vst::ParamValue toPlain (Vst::ParamValue v) const override { return v; }
Vst::ParamValue toNormalized (Vst::ParamValue v) const override { return v; }
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (BypassParam)
};
//==============================================================================
struct ProgramChangeParameter : public Vst::Parameter
{
@@ -696,6 +661,19 @@ public:
componentHandler->restartComponent (Vst::kLatencyChanged | Vst::kParamValuesChanged);
}
void parameterValueChanged (int, float newValue) override
{
// this can only come from the bypass parameter
paramChanged (audioProcessor->bypassParamID, newValue);
}
void parameterGestureChanged (int, bool gestureIsStarting) override
{
// this can only come from the bypass parameter
if (gestureIsStarting) beginEdit (audioProcessor->bypassParamID);
else endEdit (audioProcessor->bypassParamID);
}
//==============================================================================
AudioProcessor* getPluginInstance() const noexcept
{
@@ -732,6 +710,11 @@ private:
{
pluginInstance->addListener (this);
// as the bypass is not part of the regular parameters
// we need to listen for it explicitly
if (! audioProcessor->bypassIsRegularParameter)
audioProcessor->getBypassParameter()->addListener (this);
if (parameters.getParameterCount() <= 0)
{
#if JUCE_FORCE_USE_LEGACY_PARAM_IDS
@@ -747,11 +730,10 @@ private:
auto vstParamID = audioProcessor->getVSTParamIDForIndex (i);
auto* juceParam = audioProcessor->getParamForVSTParamID (vstParamID);
parameters.addParameter (new Param (*this, *juceParam, vstParamID, forceLegacyParamIDs));
parameters.addParameter (new Param (*this, *juceParam, vstParamID,
(vstParamID == audioProcessor->bypassParamID), forceLegacyParamIDs));
}
parameters.addParameter (new BypassParam (audioProcessor->bypassParamID));
if (pluginInstance->getNumPrograms() > 1)
parameters.addParameter (new ProgramChangeParameter (*pluginInstance));
}
@@ -1331,24 +1313,50 @@ public:
tresult PLUGIN_API setIoMode (Vst::IoMode) override { return kNotImplemented; }
tresult PLUGIN_API getRoutingInfo (Vst::RoutingInfo&, Vst::RoutingInfo&) override { return kNotImplemented; }
bool isBypassed() { return comPluginInstance->isBypassed; }
void setBypassed (bool bypassed) { comPluginInstance->isBypassed = bypassed; }
//==============================================================================
bool isBypassed()
{
if (auto* bypassParam = comPluginInstance->getBypassParameter())
return (bypassParam->getValue() != 0.0f);
return false;
}
void setBypassed (bool shouldBeBypassed)
{
if (auto* bypassParam = comPluginInstance->getBypassParameter())
{
auto floatValue = (shouldBeBypassed ? 1.0f : 0.0f);
bypassParam->setValue (floatValue);
inParameterChangedCallback = true;
bypassParam->sendValueChangedMessageToListeners (floatValue);
}
}
//==============================================================================
void writeJucePrivateStateInformation (MemoryOutputStream& out)
{
ValueTree privateData (kJucePrivateDataIdentifier);
// for now we only store the bypass value
privateData.setProperty ("Bypass", var (isBypassed()), nullptr);
if (pluginInstance->getBypassParameter() == nullptr)
{
ValueTree privateData (kJucePrivateDataIdentifier);
privateData.writeToStream (out);
// for now we only store the bypass value
privateData.setProperty ("Bypass", var (isBypassed()), nullptr);
privateData.writeToStream (out);
}
}
void setJucePrivateStateInformation (const void* data, int sizeInBytes)
{
auto privateData = ValueTree::readFromData (data, static_cast<size_t> (sizeInBytes));
setBypassed (static_cast<bool> (privateData.getProperty ("Bypass", var (false))));
if (pluginInstance->getBypassParameter() == nullptr)
{
auto privateData = ValueTree::readFromData (data, static_cast<size_t> (sizeInBytes));
auto isBypassed = static_cast<bool> (privateData.getProperty ("Bypass", var (false)));
if (auto* bypassParam = comPluginInstance->getBypassParameter())
setBypassed (isBypassed ? 1.0f : 0.0f);
}
}
void getStateInformation (MemoryBlock& destData)
@@ -2040,13 +2048,7 @@ public:
{
auto vstParamID = paramQueue->getParameterId();
if (vstParamID == comPluginInstance->bypassParamID)
setBypassed (static_cast<float> (value) != 0.0f);
#if JUCE_VST3_EMULATE_MIDI_CC_WITH_PARAMETERS
else if (juceVST3EditController->isMidiControllerParamID (vstParamID))
addParameterChangeToMidiBuffer (offsetSamples, vstParamID, value);
#endif
else if (vstParamID == JuceVST3EditController::paramPreset)
if (vstParamID == JuceVST3EditController::paramPreset)
{
auto numPrograms = pluginInstance->getNumPrograms();
auto programValue = roundToInt (value * (jmax (0, numPrograms - 1)));
@@ -2055,6 +2057,10 @@ public:
&& programValue != pluginInstance->getCurrentProgram())
pluginInstance->setCurrentProgram (programValue);
}
#if JUCE_VST3_EMULATE_MIDI_CC_WITH_PARAMETERS
else if (juceVST3EditController->isMidiControllerParamID (vstParamID))
addParameterChangeToMidiBuffer (offsetSamples, vstParamID, value);
#endif
else
{
auto floatValue = static_cast<float> (value);


+ 147
- 2
modules/juce_audio_processors/format_types/juce_AudioUnitPluginFormat.mm View File

@@ -1010,10 +1010,20 @@ public:
for (int i = 0; i < getBusCount (false); ++i) AudioUnitReset (audioUnit, kAudioUnitScope_Output, static_cast<UInt32> (i));
}
void processBlock (AudioBuffer<float>& buffer, MidiBuffer& midiMessages) override
void processAudio (AudioBuffer<float>& buffer, MidiBuffer& midiMessages, bool processBlockBypassedCalled)
{
auto numSamples = buffer.getNumSamples();
if (auSupportsBypass)
{
updateBypass (processBlockBypassedCalled);
}
else if (processBlockBypassedCalled)
{
AudioProcessor::processBlockBypassed (buffer, midiMessages);
return;
}
if (prepared)
{
timeStamp.mHostTime = GetCurrentHostTime (numSamples, getSampleRate(), isAUv3);
@@ -1111,7 +1121,18 @@ public:
}
}
void processBlock (AudioBuffer<float>& buffer, MidiBuffer& midiMessages) override
{
processAudio (buffer, midiMessages, false);
}
void processBlockBypassed (AudioBuffer<float>& buffer, MidiBuffer& midiMessages) override
{
processAudio (buffer, midiMessages, true);
}
//==============================================================================
AudioProcessorParameter* getBypassParameter() const override { return auSupportsBypass ? bypassParam.get() : nullptr; }
bool hasEditor() const override { return true; }
AudioProcessorEditor* createEditor() override;
@@ -1408,6 +1429,14 @@ public:
}
}
}
UInt32 propertySize = 0;
Boolean writable = false;
auSupportsBypass = (AudioUnitGetPropertyInfo (audioUnit, kAudioUnitProperty_BypassEffect,
kAudioUnitScope_Global, 0, &propertySize, &writable) == noErr
&& propertySize >= sizeof (UInt32) && writable);
bypassParam = new AUBypassParameter (*this);
}
void updateLatency()
@@ -1439,7 +1468,7 @@ private:
String pluginName, manufacturer, version;
String fileOrIdentifier;
CriticalSection lock;
bool wantsMidiMessages, producesMidiMessages, wasPlaying, prepared, isAUv3, isMidiEffectPlugin;
bool wantsMidiMessages, producesMidiMessages, wasPlaying, prepared, isAUv3, isMidiEffectPlugin, lastBypassValue = false;
struct AUBuffer
{
@@ -1459,6 +1488,99 @@ private:
HeapBlock<AudioBufferList> bufferList;
};
//==============================================================================
struct AUBypassParameter : Parameter
{
AUBypassParameter (AudioUnitPluginInstance& effectToUse)
: parent (effectToUse), currentValue (getCurrentHostValue())
{}
bool getCurrentHostValue()
{
if (parent.auSupportsBypass)
{
UInt32 dataSize = sizeof (UInt32);
UInt32 value = 0;
if (AudioUnitGetProperty (parent.audioUnit, kAudioUnitProperty_BypassEffect,
kAudioUnitScope_Global, 0, &value, &dataSize) == noErr
&& dataSize == sizeof (UInt32))
return (value != 0);
}
return false;
}
float getValue() const override
{
return currentValue ? 1.0f : 0.0f;
}
void setValue (float newValue) override
{
auto newBypassValue = (newValue != 0.0f);
const ScopedLock sl (parent.lock);
if (newBypassValue != currentValue)
{
currentValue = newBypassValue;
if (parent.auSupportsBypass)
{
UInt32 value = (newValue != 0.0f ? 1 : 0);
AudioUnitSetProperty (parent.audioUnit, kAudioUnitProperty_BypassEffect,
kAudioUnitScope_Global, 0, &value, sizeof (UInt32));
#if JUCE_MAC
jassert (parent.audioUnit != nullptr);
AudioUnitEvent ev;
ev.mEventType = kAudioUnitEvent_PropertyChange;
ev.mArgument.mProperty.mAudioUnit = parent.audioUnit;
ev.mArgument.mProperty.mPropertyID = kAudioUnitProperty_BypassEffect;
ev.mArgument.mProperty.mScope = kAudioUnitScope_Global;
ev.mArgument.mProperty.mElement = 0;
AUEventListenerNotify (parent.eventListenerRef, nullptr, &ev);
#endif
}
}
}
float getValueForText (const String& text) const override
{
String lowercaseText (text.toLowerCase());
for (auto& testText : onStrings)
if (lowercaseText == testText)
return 1.0f;
for (auto& testText : offStrings)
if (lowercaseText == testText)
return 0.0f;
return text.getIntValue() != 0 ? 1.0f : 0.0f;
}
float getDefaultValue() const override { return 0.0f; }
String getName (int /*maximumStringLength*/) const override { return "Bypass"; }
String getText (float value, int) const override { return (value != 0.0f ? TRANS("On") : TRANS("Off")); }
bool isAutomatable() const override { return true; }
bool isDiscrete() const override { return true; }
bool isBoolean() const override { return true; }
int getNumSteps() const override { return 2; }
StringArray getAllValueStrings() const override { return values; }
String getLabel() const override { return {}; }
AudioUnitPluginInstance& parent;
const StringArray onStrings { TRANS("on"), TRANS("yes"), TRANS("true") };
const StringArray offStrings { TRANS("off"), TRANS("no"), TRANS("false") };
const StringArray values { TRANS("Off"), TRANS("On") };
bool currentValue = false;
};
OwnedArray<AUBuffer> outputBufferList;
AudioTimeStamp timeStamp;
AudioBuffer<float>* currentBuffer;
@@ -1477,6 +1599,8 @@ private:
MidiDataConcatenator midiConcatenator;
CriticalSection midiInLock;
MidiBuffer incomingMidi;
ScopedPointer<AUBypassParameter> bypassParam;
bool lastProcessBlockCallWasBypass = false, auSupportsBypass = false;
void createPluginCallbacks()
{
@@ -1536,6 +1660,7 @@ private:
addPropertyChangeListener (kAudioUnitProperty_PresentPreset);
addPropertyChangeListener (kAudioUnitProperty_ParameterList);
addPropertyChangeListener (kAudioUnitProperty_Latency);
addPropertyChangeListener (kAudioUnitProperty_BypassEffect);
#endif
}
}
@@ -1607,6 +1732,9 @@ private:
sendAllParametersChangedEvents();
else if (event.mArgument.mProperty.mPropertyID == kAudioUnitProperty_Latency)
updateLatency();
else if (event.mArgument.mProperty.mPropertyID == kAudioUnitProperty_BypassEffect)
if (bypassParam != nullptr)
bypassParam->setValueNotifyingHost (bypassParam->getValue());
break;
}
@@ -2038,6 +2166,23 @@ private:
return false;
}
//==============================================================================
void updateBypass (bool processBlockBypassedCalled)
{
if (processBlockBypassedCalled && bypassParam != nullptr)
{
if (bypassParam->getValue() == 0.0f || ! lastProcessBlockCallWasBypass)
bypassParam->setValue (1.0f);
}
else
{
if (lastProcessBlockCallWasBypass && bypassParam != nullptr)
bypassParam->setValue (0.0f);
}
lastProcessBlockCallWasBypass = processBlockBypassedCalled;
}
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AudioUnitPluginInstance)
};


+ 76
- 12
modules/juce_audio_processors/format_types/juce_VST3PluginFormat.cpp View File

@@ -1883,14 +1883,18 @@ struct VST3PluginInstance : public AudioPluginInstance
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));
VST3Parameter* p = new VST3Parameter (*this,
paramInfo.id,
toString (paramInfo.title),
toString (paramInfo.units),
paramInfo.defaultNormalizedValue,
(paramInfo.flags & Vst::ParameterInfo::kCanAutomate) != 0,
isDiscrete,
numSteps);
addParameter (p);
if ((paramInfo.flags & Vst::ParameterInfo::kIsBypass) != 0)
bypassParam = p;
}
synchroniseStates();
@@ -2015,12 +2019,13 @@ struct VST3PluginInstance : public AudioPluginInstance
return (processor->canProcessSampleSize (Vst::kSample64) == kResultTrue);
}
//==============================================================================
void processBlock (AudioBuffer<float>& buffer, MidiBuffer& midiMessages) override
{
jassert (! isUsingDoublePrecision());
if (isActive && processor != nullptr)
processAudio (buffer, midiMessages, Vst::kSample32);
processAudio (buffer, midiMessages, Vst::kSample32, false);
}
void processBlock (AudioBuffer<double>& buffer, MidiBuffer& midiMessages) override
@@ -2028,12 +2033,43 @@ struct VST3PluginInstance : public AudioPluginInstance
jassert (isUsingDoublePrecision());
if (isActive && processor != nullptr)
processAudio (buffer, midiMessages, Vst::kSample64);
processAudio (buffer, midiMessages, Vst::kSample64, false);
}
void processBlockBypassed (AudioBuffer<float>& buffer, MidiBuffer& midiMessages) override
{
jassert (! isUsingDoublePrecision());
if (bypassParam != nullptr)
{
if (isActive && processor != nullptr)
processAudio (buffer, midiMessages, Vst::kSample32, true);
}
else
{
AudioProcessor::processBlockBypassed (buffer, midiMessages);
}
}
void processBlockBypassed (AudioBuffer<double>& buffer, MidiBuffer& midiMessages) override
{
jassert (isUsingDoublePrecision());
if (bypassParam != nullptr)
{
if (isActive && processor != nullptr)
processAudio (buffer, midiMessages, Vst::kSample64, true);
}
else
{
AudioProcessor::processBlockBypassed (buffer, midiMessages);
}
}
//==============================================================================
template <typename FloatType>
void processAudio (AudioBuffer<FloatType>& buffer, MidiBuffer& midiMessages,
Vst::SymbolicSampleSizes sampleSize)
Vst::SymbolicSampleSizes sampleSize, bool isProcessBlockBypassedCall)
{
using namespace Vst;
auto numSamples = buffer.getNumSamples();
@@ -2041,6 +2077,8 @@ struct VST3PluginInstance : public AudioPluginInstance
auto numInputAudioBuses = getBusCount (true);
auto numOutputAudioBuses = getBusCount (false);
updateBypass (isProcessBlockBypassedCall);
ProcessData data;
data.processMode = isNonRealtime() ? kOffline : kRealtime;
data.symbolicSampleSize = sampleSize;
@@ -2266,6 +2304,9 @@ struct VST3PluginInstance : public AudioPluginInstance
bool acceptsMidi() const override { return getNumSingleDirectionBusesFor (holder->component, true, false) > 0; }
bool producesMidi() const override { return getNumSingleDirectionBusesFor (holder->component, false, false) > 0; }
//==============================================================================
AudioProcessorParameter* getBypassParameter() const override { return bypassParam; }
//==============================================================================
/** May return a negative value as a means of informing us that the plugin has "infinite tail," or 0 for "no tail." */
double getTailLengthSeconds() const override
@@ -2588,7 +2629,8 @@ private:
ComSmartPtr<ParamValueQueueList> inputParameterChanges, outputParameterChanges;
ComSmartPtr<MidiEventList> midiInputs, midiOutputs;
Vst::ProcessContext timingInfo; //< Only use this in processBlock()!
bool isControllerInitialised = false, isActive = false;
bool isControllerInitialised = false, isActive = false, lastProcessBlockCallWasBypass = false;
VST3Parameter* bypassParam = nullptr;
//==============================================================================
/** Some plugins need to be "connected" to intercommunicate between their implemented classes */
@@ -2705,6 +2747,28 @@ private:
return busInfo;
}
//==============================================================================
void updateBypass (bool processBlockBypassedCalled)
{
// to remain backward compatible, the logic needs to be the following:
// - if processBlockBypassed was called then definitely bypass the VST3
// - if processBlock was called then only un-bypass the VST3 if the previous
// call was processBlockBypassed, otherwise do nothing
if (processBlockBypassedCalled)
{
if (bypassParam != nullptr && (bypassParam->getValue() == 0.0f || ! lastProcessBlockCallWasBypass))
bypassParam->setValue (1.0f);
}
else
{
if (lastProcessBlockCallWasBypass && bypassParam != nullptr)
bypassParam->setValue (0.0f);
}
lastProcessBlockCallWasBypass = processBlockBypassedCalled;
}
//==============================================================================
/** @note An IPlugView, when first created, should start with a ref-count of 1! */
IPlugView* tryCreatingView() const


+ 100
- 4
modules/juce_audio_processors/format_types/juce_VSTPluginFormat.cpp View File

@@ -979,7 +979,8 @@ struct VSTPluginInstance : public AudioPluginInstance,
: AudioPluginInstance (ioConfig),
vstEffect (effect),
vstModule (mh),
name (mh->pluginName)
name (mh->pluginName),
bypassParam (new VST2BypassParameter (*this))
{
jassert (vstEffect != nullptr);
@@ -1063,6 +1064,7 @@ struct VSTPluginInstance : public AudioPluginInstance,
valueType));
}
vstSupportsBypass = pluginCanDo ("bypass");
setRateAndBufferSizeDetails (sampleRateToUse, blockSizeToUse);
}
@@ -1399,24 +1401,40 @@ struct VSTPluginInstance : public AudioPluginInstance,
}
}
//==============================================================================
void processBlock (AudioBuffer<float>& buffer, MidiBuffer& midiMessages) override
{
jassert (! isUsingDoublePrecision());
processAudio (buffer, midiMessages, tmpBufferFloat, channelBufferFloat);
processAudio (buffer, midiMessages, tmpBufferFloat, channelBufferFloat, false);
}
void processBlock (AudioBuffer<double>& buffer, MidiBuffer& midiMessages) override
{
jassert (isUsingDoublePrecision());
processAudio (buffer, midiMessages, tmpBufferDouble, channelBufferDouble);
processAudio (buffer, midiMessages, tmpBufferDouble, channelBufferDouble, false);
}
void processBlockBypassed (AudioBuffer<float>& buffer, MidiBuffer& midiMessages) override
{
jassert (! isUsingDoublePrecision());
processAudio (buffer, midiMessages, tmpBufferFloat, channelBufferFloat, true);
}
void processBlockBypassed (AudioBuffer<double>& buffer, MidiBuffer& midiMessages) override
{
jassert (isUsingDoublePrecision());
processAudio (buffer, midiMessages, tmpBufferDouble, channelBufferDouble, true);
}
//==============================================================================
bool supportsDoublePrecisionProcessing() const override
{
return ((vstEffect->flags & vstEffectFlagInplaceAudio) != 0
&& (vstEffect->flags & vstEffectFlagInplaceDoubleAudio) != 0);
}
AudioProcessorParameter* getBypassParameter() const override { return vstSupportsBypass ? bypassParam.get() : nullptr; }
//==============================================================================
bool canAddBus (bool) const override { return false; }
bool canRemoveBus (bool) const override { return false; }
@@ -1932,9 +1950,57 @@ struct VSTPluginInstance : public AudioPluginInstance,
bool usesCocoaNSView = false;
private:
//==============================================================================
struct VST2BypassParameter : Parameter
{
VST2BypassParameter (VSTPluginInstance& effectToUse) : parent (effectToUse) {}
void setValue (float newValue) override
{
currentValue = (newValue != 0.0f);
if (parent.vstSupportsBypass)
parent.dispatch (plugInOpcodeSetBypass, 0, currentValue ? 1 : 0, nullptr, 0.0f);
}
float getValueForText (const String& text) const override
{
String lowercaseText (text.toLowerCase());
for (auto& testText : onStrings)
if (lowercaseText == testText)
return 1.0f;
for (auto& testText : offStrings)
if (lowercaseText == testText)
return 0.0f;
return text.getIntValue() != 0 ? 1.0f : 0.0f;
}
float getValue() const override { return currentValue; }
float getDefaultValue() const override { return 0.0f; }
String getName (int /*maximumStringLength*/) const override { return "Bypass"; }
String getText (float value, int) const override { return (value != 0.0f ? TRANS("On") : TRANS("Off")); }
bool isAutomatable() const override { return true; }
bool isDiscrete() const override { return true; }
bool isBoolean() const override { return true; }
int getNumSteps() const override { return 2; }
StringArray getAllValueStrings() const override { return values; }
String getLabel() const override { return {}; }
VSTPluginInstance& parent;
bool currentValue = false;
StringArray onStrings { TRANS("on"), TRANS("yes"), TRANS("true") };
StringArray offStrings { TRANS("off"), TRANS("no"), TRANS("false") };
StringArray values { TRANS("Off"), TRANS("On") };
};
//==============================================================================
String name;
CriticalSection lock;
bool wantsMidiMessages = false, initialised = false, isPowerOn = false;
bool lastProcessBlockCallWasBypass = false, vstSupportsBypass = false;
mutable StringArray programNames;
AudioBuffer<float> outOfPlaceBuffer;
@@ -1948,6 +2014,7 @@ private:
AudioBuffer<double> tmpBufferDouble;
HeapBlock<double*> channelBufferDouble;
ScopedPointer<VST2BypassParameter> bypassParam;
ScopedPointer<VSTXMLInfo> xmlInfo;
@@ -2202,8 +2269,20 @@ private:
template <typename FloatType>
void processAudio (AudioBuffer<FloatType>& buffer, MidiBuffer& midiMessages,
AudioBuffer<FloatType>& tmpBuffer,
HeapBlock<FloatType*>& channelBuffer)
HeapBlock<FloatType*>& channelBuffer,
bool processBlockBypassedCalled)
{
if (vstSupportsBypass)
{
updateBypass (processBlockBypassedCalled);
}
else if (processBlockBypassedCalled)
{
// if this vst does not support bypass then we will have to do this ourselves
AudioProcessor::processBlockBypassed (buffer, midiMessages);
return;
}
auto numSamples = buffer.getNumSamples();
auto numChannels = buffer.getNumChannels();
@@ -2588,6 +2667,23 @@ private:
isPowerOn = on;
}
//==============================================================================
void updateBypass (bool processBlockBypassedCalled)
{
if (processBlockBypassedCalled)
{
if (bypassParam->getValue() == 0.0f || ! lastProcessBlockCallWasBypass)
bypassParam->setValue (1.0f);
}
else
{
if (lastProcessBlockCallWasBypass)
bypassParam->setValue (0.0f);
}
lastProcessBlockCallWasBypass = processBlockBypassedCalled;
}
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (VSTPluginInstance)
};


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

@@ -188,6 +188,13 @@ public:
be the processor's MIDI output. This means that your processor should be careful to
clear any incoming messages from the array if it doesn't want them to be passed-on.
If you have implemented the getBypassParameter method, then you need to check the
value of this parameter in this callback and bypass your processing if the parameter
has a non-zero value.
Note that when calling this method as a host, the result may still be bypassed as
the parameter that controls the bypass may be non-zero.
Be very careful about what you do in this callback - it's going to be called by
the audio thread, so any kind of interaction with the UI is absolutely
out of the question. If you change a parameter in here and need to tell your UI to
@@ -252,6 +259,13 @@ public:
be the processor's MIDI output. This means that your processor should be careful to
clear any incoming messages from the array if it doesn't want them to be passed-on.
If you have implemented the getBypassParameter method, then you need to check the
value of this parameter in this callback and bypass your processing if the parameter
has a non-zero value.
Note that when calling this method as a host, the result may still be bypassed as
the parameter that controls the bypass may be non-zero.
Be very careful about what you do in this callback - it's going to be called by
the audio thread, so any kind of interaction with the UI is absolutely
out of the question. If you change a parameter in here and need to tell your UI to
@@ -890,6 +904,21 @@ public:
*/
virtual void reset();
//==============================================================================
/** Returns the parameter that controls the AudioProcessor's bypass state.
If this method returns a nullptr then you can still control the bypass by
calling processBlockBypassed instaed of processBlock. On the other hand,
if this method returns a non-null value, you should never call
processBlockBypassed but use the returned parameter to conrol the bypass
state instead.
A plug-in can override this function to return a parameter which control's your
plug-in's bypass. You should always check the value of this parameter in your
processBlock callback and bypass any effects if it is non-zero.
*/
virtual AudioProcessorParameter* getBypassParameter() const { return nullptr; }
//==============================================================================
/** Returns true if the processor is being run in an offline mode for rendering.


+ 43
- 4
modules/juce_audio_processors/processors/juce_AudioProcessorGraph.cpp View File

@@ -270,12 +270,20 @@ private:
if (processor.isUsingDoublePrecision())
{
tempBufferDouble.makeCopyOf (buffer, true);
processor.processBlock (tempBufferDouble, midiMessages);
if (node->isBypassed())
processor.processBlockBypassed (tempBufferDouble, midiMessages);
else
processor.processBlock (tempBufferDouble, midiMessages);
buffer.makeCopyOf (tempBufferDouble, true);
}
else
{
processor.processBlock (buffer, midiMessages);
if (node->isBypassed())
processor.processBlockBypassed (buffer, midiMessages);
else
processor.processBlock (buffer, midiMessages);
}
}
@@ -283,12 +291,20 @@ private:
{
if (processor.isUsingDoublePrecision())
{
processor.processBlock (buffer, midiMessages);
if (node->isBypassed())
processor.processBlockBypassed (buffer, midiMessages);
else
processor.processBlock (buffer, midiMessages);
}
else
{
tempBufferFloat.makeCopyOf (buffer, true);
processor.processBlock (tempBufferFloat, midiMessages);
if (node->isBypassed())
processor.processBlockBypassed (tempBufferFloat, midiMessages);
else
processor.processBlock (tempBufferFloat, midiMessages);
buffer.makeCopyOf (tempBufferFloat, true);
}
}
@@ -826,6 +842,29 @@ bool AudioProcessorGraph::Node::Connection::operator== (const Connection& other)
&& otherChannel == other.otherChannel;
}
//==============================================================================
bool AudioProcessorGraph::Node::isBypassed() const noexcept
{
if (processor != nullptr)
{
if (auto* bypassParam = processor->getBypassParameter())
return (bypassParam->getValue() != 0.0f);
}
return bypassed;
}
void AudioProcessorGraph::Node::setBypassed (bool shouldBeBypassed) noexcept
{
if (processor != nullptr)
{
if (auto* bypassParam = processor->getBypassParameter())
bypassParam->setValueNotifyingHost (shouldBeBypassed ? 1.0f : 0.0f);
}
bypassed = shouldBeBypassed;
}
//==============================================================================
struct AudioProcessorGraph::RenderSequenceFloat : public GraphRenderSequence<float> {};
struct AudioProcessorGraph::RenderSequenceDouble : public GraphRenderSequence<double> {};


+ 8
- 1
modules/juce_audio_processors/processors/juce_AudioProcessorGraph.h View File

@@ -108,6 +108,13 @@ public:
*/
NamedValueSet properties;
//==============================================================================
/** Returns if the node is bypassed or not. */
bool isBypassed() const noexcept;
/** Tell this node to bypass processing. */
void setBypassed (bool shouldBeBypassed) noexcept;
//==============================================================================
/** A convenient typedef for referring to a pointer to a node object. */
typedef ReferenceCountedObjectPtr<Node> Ptr;
@@ -126,7 +133,7 @@ public:
const ScopedPointer<AudioProcessor> processor;
Array<Connection> inputs, outputs;
bool isPrepared = false;
bool isPrepared = false, bypassed = false;
Node (NodeID, AudioProcessor*) noexcept;


+ 4
- 3
modules/juce_audio_processors/processors/juce_GenericAudioProcessorEditor.cpp View File

@@ -503,8 +503,6 @@ struct GenericAudioProcessorEditor::Pimpl
owner.addAndMakeVisible (view);
view.setScrollBarsShown (true, false);
owner.setSize (view.getViewedComponent()->getWidth() + view.getVerticalScrollBar().getWidth(),
jmin (view.getViewedComponent()->getHeight(), 400));
}
@@ -520,7 +518,10 @@ struct GenericAudioProcessorEditor::Pimpl
//==============================================================================
GenericAudioProcessorEditor::GenericAudioProcessorEditor (AudioProcessor* const p)
: AudioProcessorEditor (p), pimpl (new Pimpl (*this))
{}
{
setSize (pimpl->view.getViewedComponent()->getWidth() + pimpl->view.getVerticalScrollBar().getWidth(),
jmin (pimpl->view.getViewedComponent()->getHeight(), 400));
}
GenericAudioProcessorEditor::~GenericAudioProcessorEditor() {}


Loading…
Cancel
Save