| @@ -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) | FilterComponent (GraphEditorPanel& p, uint32 id) : panel (p), graph (p.graph), pluginID (id) | ||||
| { | { | ||||
| shadow.setShadowProperties (DropShadow (Colours::black.withAlpha (0.5f), 3, { 0, 1 })); | shadow.setShadowProperties (DropShadow (Colours::black.withAlpha (0.5f), 3, { 0, 1 })); | ||||
| setComponentEffect (&shadow); | 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); | setSize (150, 60); | ||||
| } | } | ||||
| FilterComponent (const FilterComponent&) = delete; | FilterComponent (const FilterComponent&) = delete; | ||||
| FilterComponent& operator= (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 | void mouseDown (const MouseEvent& e) override | ||||
| { | { | ||||
| originalPos = localPointToGlobal (Point<int>()); | originalPos = localPointToGlobal (Point<int>()); | ||||
| @@ -177,8 +198,17 @@ struct GraphEditorPanel::FilterComponent : public Component | |||||
| void paint (Graphics& g) override | void paint (Graphics& g) override | ||||
| { | { | ||||
| auto boxArea = getLocalBounds().reduced (4, pinSize); | 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.fillRect (boxArea.toFloat()); | ||||
| g.setColour (findColour (TextEditor::textColourId)); | g.setColour (findColour (TextEditor::textColourId)); | ||||
| @@ -290,6 +320,7 @@ struct GraphEditorPanel::FilterComponent : public Component | |||||
| PopupMenu m; | PopupMenu m; | ||||
| m.addItem (1, "Delete this filter"); | m.addItem (1, "Delete this filter"); | ||||
| m.addItem (2, "Disconnect all pins"); | m.addItem (2, "Disconnect all pins"); | ||||
| m.addItem (3, "Toggle Bypass"); | |||||
| m.addSeparator(); | m.addSeparator(); | ||||
| m.addItem (10, "Show plugin GUI"); | m.addItem (10, "Show plugin GUI"); | ||||
| m.addItem (11, "Show all programs"); | m.addItem (11, "Show all programs"); | ||||
| @@ -302,6 +333,15 @@ struct GraphEditorPanel::FilterComponent : public Component | |||||
| { | { | ||||
| case 1: graph.graph.removeNode (pluginID); break; | case 1: graph.graph.removeNode (pluginID); break; | ||||
| case 2: graph.graph.disconnectNode (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 10: showWindow (PluginWindow::Type::normal); break; | ||||
| case 11: showWindow (PluginWindow::Type::programs); break; | case 11: showWindow (PluginWindow::Type::programs); break; | ||||
| case 12: showWindow (PluginWindow::Type::generic); break; | case 12: showWindow (PluginWindow::Type::generic); break; | ||||
| @@ -329,6 +369,13 @@ struct GraphEditorPanel::FilterComponent : public Component | |||||
| w->toFront (true); | w->toFront (true); | ||||
| } | } | ||||
| void parameterValueChanged (int, float) override | |||||
| { | |||||
| repaint(); | |||||
| } | |||||
| void parameterGestureChanged (int, bool) override {} | |||||
| GraphEditorPanel& panel; | GraphEditorPanel& panel; | ||||
| FilterGraph& graph; | FilterGraph& graph; | ||||
| const AudioProcessorGraph::NodeID pluginID; | const AudioProcessorGraph::NodeID pluginID; | ||||
| @@ -133,11 +133,6 @@ namespace AAXClasses | |||||
| jassert (result == AAX_SUCCESS); ignoreUnused (result); | 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 | // maps a channel index of an AAX format to an index of a juce format | ||||
| struct AAXChannelStreamOrder | struct AAXChannelStreamOrder | ||||
| { | { | ||||
| @@ -483,10 +478,12 @@ namespace AAXClasses | |||||
| { | { | ||||
| if (component != nullptr && component->pluginEditor != nullptr) | if (component != nullptr && component->pluginEditor != nullptr) | ||||
| { | { | ||||
| if (! isBypassParam (paramID)) | |||||
| auto index = getParamIndexFromID (paramID); | |||||
| if (index >= 0) | |||||
| { | { | ||||
| AudioProcessorEditor::ParameterControlHighlightInfo info; | AudioProcessorEditor::ParameterControlHighlightInfo info; | ||||
| info.parameterIndex = getParamIndexFromID (paramID); | |||||
| info.parameterIndex = index; | |||||
| info.isHighlighted = (isHighlighted != 0); | info.isHighlighted = (isHighlighted != 0); | ||||
| info.suggestedColour = getColourFromHighlightEnum (colour); | info.suggestedColour = getColourFromHighlightEnum (colour); | ||||
| @@ -660,7 +657,6 @@ namespace AAXClasses | |||||
| if (err != AAX_SUCCESS) | if (err != AAX_SUCCESS) | ||||
| return err; | return err; | ||||
| addBypassParameter(); | |||||
| addAudioProcessorParameters(); | addAudioProcessorParameters(); | ||||
| return AAX_SUCCESS; | return AAX_SUCCESS; | ||||
| @@ -804,21 +800,13 @@ namespace AAXClasses | |||||
| AAX_Result UpdateParameterNormalizedValue (AAX_CParamID paramID, double value, AAX_EUpdateSource source) override | AAX_Result UpdateParameterNormalizedValue (AAX_CParamID paramID, double value, AAX_EUpdateSource source) override | ||||
| { | { | ||||
| auto result = AAX_CEffectParameters::UpdateParameterNormalizedValue (paramID, value, source); | auto result = AAX_CEffectParameters::UpdateParameterNormalizedValue (paramID, value, source); | ||||
| if (! isBypassParam (paramID)) | |||||
| setAudioProcessorParameter (paramID, value); | |||||
| setAudioProcessorParameter (paramID, value); | |||||
| return result; | return result; | ||||
| } | } | ||||
| AAX_Result GetParameterValueFromString (AAX_CParamID paramID, double* result, const AAX_IString& text) const override | 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 (auto* param = getParameterFromID (paramID)) | ||||
| { | { | ||||
| if (! LegacyAudioParameter::isLegacy (param)) | 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 | 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; | return AAX_SUCCESS; | ||||
| } | } | ||||
| AAX_Result GetParameterNumberofSteps (AAX_CParamID paramID, int32_t* result) const | 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; | return AAX_SUCCESS; | ||||
| } | } | ||||
| AAX_Result GetParameterNormalizedValue (AAX_CParamID paramID, double* result) const override | AAX_Result GetParameterNormalizedValue (AAX_CParamID paramID, double* result) const override | ||||
| { | { | ||||
| if (isBypassParam (paramID)) | |||||
| return AAX_CEffectParameters::GetParameterNormalizedValue (paramID, result); | |||||
| if (auto* param = getParameterFromID (paramID)) | if (auto* param = getParameterFromID (paramID)) | ||||
| *result = (double) param->getValue(); | *result = (double) param->getValue(); | ||||
| else | else | ||||
| @@ -876,9 +847,6 @@ namespace AAXClasses | |||||
| AAX_Result SetParameterNormalizedValue (AAX_CParamID paramID, double newValue) override | AAX_Result SetParameterNormalizedValue (AAX_CParamID paramID, double newValue) override | ||||
| { | { | ||||
| if (isBypassParam (paramID)) | |||||
| return AAX_CEffectParameters::SetParameterNormalizedValue (paramID, newValue); | |||||
| if (auto* p = mParameterManager.GetParameterByID (paramID)) | if (auto* p = mParameterManager.GetParameterByID (paramID)) | ||||
| p->SetValueWithFloat ((float) newValue); | p->SetValueWithFloat ((float) newValue); | ||||
| @@ -889,9 +857,6 @@ namespace AAXClasses | |||||
| AAX_Result SetParameterNormalizedRelative (AAX_CParamID paramID, double newDeltaValue) override | AAX_Result SetParameterNormalizedRelative (AAX_CParamID paramID, double newDeltaValue) override | ||||
| { | { | ||||
| if (isBypassParam (paramID)) | |||||
| return AAX_CEffectParameters::SetParameterNormalizedRelative (paramID, newDeltaValue); | |||||
| if (auto* param = getParameterFromID (paramID)) | if (auto* param = getParameterFromID (paramID)) | ||||
| { | { | ||||
| auto newValue = param->getValue() + (float) newDeltaValue; | 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 | 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()); | result->Set (param->getName (maxLen).toRawUTF8()); | ||||
| } | |||||
| return AAX_SUCCESS; | return AAX_SUCCESS; | ||||
| } | } | ||||
| AAX_Result GetParameterName (AAX_CParamID paramID, AAX_IString* result) const override | 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()); | result->Set (param->getName (31).toRawUTF8()); | ||||
| return AAX_SUCCESS; | return AAX_SUCCESS; | ||||
| @@ -933,15 +888,12 @@ namespace AAXClasses | |||||
| AAX_Result GetParameterDefaultNormalizedValue (AAX_CParamID paramID, double* result) const override | 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; | return AAX_SUCCESS; | ||||
| } | } | ||||
| @@ -1420,18 +1372,18 @@ namespace AAXClasses | |||||
| #endif | #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() | void addAudioProcessorParameters() | ||||
| @@ -1444,14 +1396,35 @@ namespace AAXClasses | |||||
| const bool forceLegacyParamIDs = false; | const bool forceLegacyParamIDs = false; | ||||
| #endif | #endif | ||||
| auto bypassPartOfRegularParams = isBypassPartOfRegularParemeters(); | |||||
| juceParameters.update (audioProcessor, forceLegacyParamIDs); | 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; | int parameterIndex = 0; | ||||
| for (auto* juceParam : juceParameters.params) | for (auto* juceParam : juceParameters.params) | ||||
| { | { | ||||
| auto isBypassParameter = (juceParam == bypassParameter); | |||||
| auto category = juceParam->getCategory(); | auto category = juceParam->getCategory(); | ||||
| auto paramID = juceParameters.getParamID (audioProcessor, parameterIndex) | |||||
| .toRawUTF8(); | |||||
| auto paramID = isBypassParameter ? cDefaultMasterBypassID | |||||
| : juceParameters.getParamID (audioProcessor, parameterIndex) | |||||
| .toRawUTF8(); | |||||
| aaxParamIDs.add (paramID); | aaxParamIDs.add (paramID); | ||||
| auto aaxParamID = aaxParamIDs.getReference (parameterIndex++).getCharPointer(); | auto aaxParamID = aaxParamIDs.getReference (parameterIndex++).getCharPointer(); | ||||
| @@ -1493,6 +1466,9 @@ namespace AAXClasses | |||||
| | AAX_eParameterOrientation_RotarySingleDotMode | AAX_eParameterOrientation_RotaryLeftMinRightMax)); | | AAX_eParameterOrientation_RotarySingleDotMode | AAX_eParameterOrientation_RotaryLeftMinRightMax)); | ||||
| mParameterManager.AddParameter (parameter); | mParameterManager.AddParameter (parameter); | ||||
| if (isBypassParameter) | |||||
| mPacketDispatcher.RegisterPacket (aaxParamID, JUCEAlgorithmIDs::bypass); | |||||
| } | } | ||||
| } | } | ||||
| @@ -1785,6 +1761,7 @@ namespace AAXClasses | |||||
| Array<String> aaxParamIDs; | Array<String> aaxParamIDs; | ||||
| HashMap<int32, AudioProcessorParameter*> paramMap; | HashMap<int32, AudioProcessorParameter*> paramMap; | ||||
| LegacyAudioParametersWrapper juceParameters; | LegacyAudioParametersWrapper juceParameters; | ||||
| ScopedPointer<AudioProcessorParameter> ownedBypassParameter; | |||||
| Array<AudioProcessorParameter*> aaxMeters; | Array<AudioProcessorParameter*> aaxMeters; | ||||
| @@ -121,7 +121,8 @@ class JuceAU : public AudioProcessorHolder, | |||||
| public MusicDeviceBase, | public MusicDeviceBase, | ||||
| public AudioProcessorListener, | public AudioProcessorListener, | ||||
| public AudioPlayHead, | public AudioPlayHead, | ||||
| public ComponentListener | |||||
| public ComponentListener, | |||||
| public AudioProcessorParameter::Listener | |||||
| { | { | ||||
| public: | public: | ||||
| JuceAU (AudioUnit component) | JuceAU (AudioUnit component) | ||||
| @@ -181,6 +182,9 @@ public: | |||||
| ~JuceAU() | ~JuceAU() | ||||
| { | { | ||||
| if (bypassParam != nullptr) | |||||
| bypassParam->removeListener (this); | |||||
| deleteActiveEditors(); | deleteActiveEditors(); | ||||
| juceFilter = nullptr; | juceFilter = nullptr; | ||||
| clearPresetsArray(); | clearPresetsArray(); | ||||
| @@ -484,7 +488,10 @@ public: | |||||
| return noErr; | return noErr; | ||||
| case kAudioUnitProperty_BypassEffect: | 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; | return noErr; | ||||
| case kAudioUnitProperty_SupportsMPE: | case kAudioUnitProperty_SupportsMPE: | ||||
| @@ -605,12 +612,16 @@ public: | |||||
| return kAudioUnitErr_InvalidPropertyValue; | return kAudioUnitErr_InvalidPropertyValue; | ||||
| const bool newBypass = *((UInt32*) inData) != 0; | 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); | Reset (0, 0); | ||||
| } | } | ||||
| @@ -1117,6 +1128,15 @@ public: | |||||
| PropertyChanged (kAudioUnitProperty_PresentPreset, kAudioUnitScope_Global, 0); | 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 | bool StreamFormatWritable (AudioUnitScope scope, AudioUnitElement element) override | ||||
| { | { | ||||
| @@ -1690,6 +1710,9 @@ private: | |||||
| //============================================================================== | //============================================================================== | ||||
| OwnedArray<OwnedArray<const __CFString>> parameterValueStringArrays; | OwnedArray<OwnedArray<const __CFString>> parameterValueStringArrays; | ||||
| //============================================================================== | |||||
| AudioProcessorParameter* bypassParam = nullptr; | |||||
| //============================================================================== | //============================================================================== | ||||
| void pullInputAudio (AudioUnitRenderActionFlags& flags, const AudioTimeStamp& timestamp, const UInt32 nFrames) noexcept | void pullInputAudio (AudioUnitRenderActionFlags& flags, const AudioTimeStamp& timestamp, const UInt32 nFrames) noexcept | ||||
| { | { | ||||
| @@ -1730,7 +1753,7 @@ private: | |||||
| { | { | ||||
| buffer.clear(); | buffer.clear(); | ||||
| } | } | ||||
| else if (isBypassed) | |||||
| else if (bypassParam == nullptr && isBypassed) | |||||
| { | { | ||||
| juceFilter->processBlockBypassed (buffer, midiBuffer); | juceFilter->processBlockBypassed (buffer, midiBuffer); | ||||
| } | } | ||||
| @@ -1878,6 +1901,9 @@ private: | |||||
| parameterValueStringArrays.add (stringValues); | parameterValueStringArrays.add (stringValues); | ||||
| } | } | ||||
| if ((bypassParam = juceFilter->getBypassParameter()) != nullptr) | |||||
| bypassParam->addListener (this); | |||||
| } | } | ||||
| //============================================================================== | //============================================================================== | ||||
| @@ -195,6 +195,18 @@ public: | |||||
| virtual bool getRenderingOffline() = 0; | virtual bool getRenderingOffline() = 0; | ||||
| virtual void setRenderingOffline (bool offline) = 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 NSString* getContextName() const = 0; | ||||
| virtual void setContextName (NSString*) = 0; | virtual void setContextName (NSString*) = 0; | ||||
| @@ -274,6 +286,8 @@ private: | |||||
| addMethod (@selector (canProcessInPlace), getCanProcessInPlace, @encode (BOOL), "@:"); | addMethod (@selector (canProcessInPlace), getCanProcessInPlace, @encode (BOOL), "@:"); | ||||
| addMethod (@selector (isRenderingOffline), getRenderingOffline, @encode (BOOL), "@:"); | addMethod (@selector (isRenderingOffline), getRenderingOffline, @encode (BOOL), "@:"); | ||||
| addMethod (@selector (setRenderingOffline:), setRenderingOffline, "v@:", @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 (allocateRenderResourcesAndReturnError:), allocateRenderResourcesAndReturnError, "B@:^@"); | ||||
| addMethod (@selector (deallocateRenderResources), deallocateRenderResources, "v@:"); | addMethod (@selector (deallocateRenderResources), deallocateRenderResources, "v@:"); | ||||
| @@ -388,6 +402,8 @@ private: | |||||
| static void setRenderingOffline (id self, SEL, BOOL renderingOffline) { _this (self)->setRenderingOffline (renderingOffline); } | 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 BOOL allocateRenderResourcesAndReturnError (id self, SEL, NSError** error) { return _this (self)->allocateRenderResourcesAndReturnError (error) ? YES : NO; } | ||||
| static void deallocateRenderResources (id self, SEL) { _this (self)->deallocateRenderResources(); } | 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(); } | static NSString* getContextName (id self, SEL) { return _this (self)->getContextName(); } | ||||
| @@ -417,7 +433,8 @@ JuceAudioUnitv3Base::Class JuceAudioUnitv3Base::audioUnitObjCClass; | |||||
| //============================================================================== | //============================================================================== | ||||
| class JuceAudioUnitv3 : public JuceAudioUnitv3Base, | class JuceAudioUnitv3 : public JuceAudioUnitv3Base, | ||||
| public AudioProcessorListener, | public AudioProcessorListener, | ||||
| public AudioPlayHead | |||||
| public AudioPlayHead, | |||||
| private AudioProcessorParameter::Listener | |||||
| { | { | ||||
| public: | public: | ||||
| JuceAudioUnitv3 (const AudioProcessorHolder::Ptr& processor, | JuceAudioUnitv3 (const AudioProcessorHolder::Ptr& processor, | ||||
| @@ -444,6 +461,9 @@ public: | |||||
| auto& processor = getAudioProcessor(); | auto& processor = getAudioProcessor(); | ||||
| processor.removeListener (this); | processor.removeListener (this); | ||||
| if (bypassParam != nullptr) | |||||
| bypassParam->removeListener (this); | |||||
| removeEditor (processor); | removeEditor (processor); | ||||
| if (editorObserverToken != nullptr) | 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); } | NSString* getContextName() const override { return juceStringToNS (contextName); } | ||||
| void setContextName (NSString* str) override | void setContextName (NSString* str) override | ||||
| @@ -1216,7 +1252,6 @@ private: | |||||
| #endif | #endif | ||||
| // create methods in AUParameterTree return unretained objects (!) -> see Apple header AUAudioUnitImplementation.h | // create methods in AUParameterTree return unretained objects (!) -> see Apple header AUAudioUnitImplementation.h | ||||
| ScopedPointer<AUParameter> param = [[AUParameterTree createParameterWithIdentifier: juceStringToNS (identifier) | ScopedPointer<AUParameter> param = [[AUParameterTree createParameterWithIdentifier: juceStringToNS (identifier) | ||||
| name: juceStringToNS (name) | name: juceStringToNS (name) | ||||
| address: address | address: address | ||||
| @@ -1252,6 +1287,9 @@ private: | |||||
| editorParamObserver = CreateObjCBlock (this, &JuceAudioUnitv3::valueChangedForObserver); | editorParamObserver = CreateObjCBlock (this, &JuceAudioUnitv3::valueChangedForObserver); | ||||
| editorObserverToken = [paramTree tokenByAddingParameterObserver: editorParamObserver]; | editorObserverToken = [paramTree tokenByAddingParameterObserver: editorParamObserver]; | ||||
| } | } | ||||
| if ((bypassParam = processor.getBypassParameter()) != nullptr) | |||||
| bypassParam->addListener (this); | |||||
| } | } | ||||
| void setAudioProcessorParameter (AudioProcessorParameter* juceParam, float value) | void setAudioProcessorParameter (AudioProcessorParameter* juceParam, float value) | ||||
| @@ -1454,7 +1492,7 @@ private: | |||||
| if (processor.isSuspended()) | if (processor.isSuspended()) | ||||
| buffer.clear(); | buffer.clear(); | ||||
| else if ([au shouldBypassEffect]) | |||||
| else if (bypassParam != nullptr && [au shouldBypassEffect]) | |||||
| processor.processBlockBypassed (buffer, midiBuffer); | processor.processBlockBypassed (buffer, midiBuffer); | ||||
| else | else | ||||
| processor.processBlock (buffer, midiBuffer); | 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 | #if JUCE_FORCE_USE_LEGACY_PARAM_IDS | ||||
| inline AUParameterAddress getAUParameterAddressForIndex (int paramIndex) const noexcept { return static_cast<AUParameterAddress> (paramIndex); } | inline AUParameterAddress getAUParameterAddressForIndex (int paramIndex) const noexcept { return static_cast<AUParameterAddress> (paramIndex); } | ||||
| inline int getJuceParameterIndexForAUAddress (AUParameterAddress address) const noexcept { return static_cast<int> (address); } | inline int getJuceParameterIndexForAUAddress (AUParameterAddress address) const noexcept { return static_cast<int> (address); } | ||||
| @@ -1610,6 +1656,7 @@ private: | |||||
| #else | #else | ||||
| static constexpr bool forceLegacyParamIDs = false; | static constexpr bool forceLegacyParamIDs = false; | ||||
| #endif | #endif | ||||
| AudioProcessorParameter* bypassParam = nullptr; | |||||
| }; | }; | ||||
| const double JuceAudioUnitv3::kDefaultSampleRate = 44100.0; | const double JuceAudioUnitv3::kDefaultSampleRate = 44100.0; | ||||
| @@ -223,7 +223,8 @@ struct AbletonLiveHostSpecific | |||||
| class JuceVSTWrapper : public AudioProcessorListener, | class JuceVSTWrapper : public AudioProcessorListener, | ||||
| public AudioPlayHead, | public AudioPlayHead, | ||||
| private Timer, | private Timer, | ||||
| private AsyncUpdater | |||||
| private AsyncUpdater, | |||||
| private AudioProcessorParameter::Listener | |||||
| { | { | ||||
| private: | private: | ||||
| //============================================================================== | //============================================================================== | ||||
| @@ -282,6 +283,9 @@ public: | |||||
| processor->setPlayHead (this); | processor->setPlayHead (this); | ||||
| processor->addListener (this); | processor->addListener (this); | ||||
| if (auto* juceParam = processor->getBypassParameter()) | |||||
| juceParam->addListener (this); | |||||
| juceParameters.update (*processor, false); | juceParameters.update (*processor, false); | ||||
| memset (&vstEffect, 0, sizeof (vstEffect)); | memset (&vstEffect, 0, sizeof (vstEffect)); | ||||
| @@ -760,6 +764,14 @@ public: | |||||
| hostCallback (&vstEffect, hostOpcodeParameterChangeGestureEnd, index, 0, 0, 0); | 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 | void audioProcessorChanged (AudioProcessor*) override | ||||
| { | { | ||||
| vstEffect.latency = processor->getLatencySamples(); | vstEffect.latency = processor->getLatencySamples(); | ||||
| @@ -1921,6 +1933,10 @@ private: | |||||
| pointer_sized_int handleSetBypass (VstOpCodeArguments args) | pointer_sized_int handleSetBypass (VstOpCodeArguments args) | ||||
| { | { | ||||
| isBypassed = (args.value != 0); | isBypassed = (args.value != 0); | ||||
| if (auto* bypass = processor->getBypassParameter()) | |||||
| bypass->setValueNotifyingHost (isBypassed ? 1.0f : 0.0f); | |||||
| return 1; | return 1; | ||||
| } | } | ||||
| @@ -119,6 +119,11 @@ public: | |||||
| return paramMap[static_cast<int32> (paramID)]; | return paramMap[static_cast<int32> (paramID)]; | ||||
| } | } | ||||
| AudioProcessorParameter* getBypassParameter() const noexcept | |||||
| { | |||||
| return getParamForVSTParamID (bypassParamID); | |||||
| } | |||||
| int getNumParameters() const noexcept { return vstParamIDs.size(); } | int getNumParameters() const noexcept { return vstParamIDs.size(); } | ||||
| bool isUsingManagedParameters() const noexcept { return juceParameters.isUsingManagedParameters(); } | bool isUsingManagedParameters() const noexcept { return juceParameters.isUsingManagedParameters(); } | ||||
| @@ -126,7 +131,7 @@ public: | |||||
| static const FUID iid; | static const FUID iid; | ||||
| Array<Vst::ParamID> vstParamIDs; | Array<Vst::ParamID> vstParamIDs; | ||||
| Vst::ParamID bypassParamID = 0; | Vst::ParamID bypassParamID = 0; | ||||
| bool isBypassed = false; | |||||
| bool bypassIsRegularParameter = false; | |||||
| private: | private: | ||||
| enum InternalParameters | 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() | void setupParameters() | ||||
| { | { | ||||
| #if JUCE_FORCE_USE_LEGACY_PARAM_IDS | #if JUCE_FORCE_USE_LEGACY_PARAM_IDS | ||||
| @@ -146,17 +163,42 @@ private: | |||||
| juceParameters.update (*audioProcessor, forceLegacyParamIDs); | juceParameters.update (*audioProcessor, forceLegacyParamIDs); | ||||
| auto numParameters = juceParameters.getNumParameters(); | 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; | int i = 0; | ||||
| for (auto* juceParam : juceParameters.params) | for (auto* juceParam : juceParameters.params) | ||||
| { | { | ||||
| bool isBypassParameter = (juceParam == bypassParameter); | |||||
| Vst::ParamID vstParamID = forceLegacyParamIDs ? static_cast<Vst::ParamID> (i++) | Vst::ParamID vstParamID = forceLegacyParamIDs ? static_cast<Vst::ParamID> (i++) | ||||
| : generateVSTParamIDForParam (juceParam); | : 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); | vstParamIDs.add (vstParamID); | ||||
| paramMap.set (static_cast<int32> (vstParamID), juceParam); | paramMap.set (static_cast<int32> (vstParamID), juceParam); | ||||
| } | } | ||||
| bypassParamID = static_cast<Vst::ParamID> (isUsingManagedParameters() ? paramBypass : numParameters); | |||||
| } | } | ||||
| Vst::ParamID generateVSTParamIDForParam (AudioProcessorParameter* param) | Vst::ParamID generateVSTParamIDForParam (AudioProcessorParameter* param) | ||||
| @@ -181,6 +223,7 @@ private: | |||||
| //============================================================================== | //============================================================================== | ||||
| LegacyAudioParametersWrapper juceParameters; | LegacyAudioParametersWrapper juceParameters; | ||||
| HashMap<int32, AudioProcessorParameter*> paramMap; | HashMap<int32, AudioProcessorParameter*> paramMap; | ||||
| ScopedPointer<AudioProcessorParameter> ownedBypassParameter; | |||||
| JuceAudioProcessor() = delete; | JuceAudioProcessor() = delete; | ||||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (JuceAudioProcessor) | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (JuceAudioProcessor) | ||||
| @@ -194,7 +237,8 @@ static ThreadLocalValue<bool> inParameterChangedCallback; | |||||
| class JuceVST3EditController : public Vst::EditController, | class JuceVST3EditController : public Vst::EditController, | ||||
| public Vst::IMidiMapping, | public Vst::IMidiMapping, | ||||
| public Vst::ChannelContext::IInfoListener, | public Vst::ChannelContext::IInfoListener, | ||||
| public AudioProcessorListener | |||||
| public AudioProcessorListener, | |||||
| private AudioProcessorParameter::Listener | |||||
| { | { | ||||
| public: | public: | ||||
| JuceVST3EditController (Vst::IHostApplication* host) | JuceVST3EditController (Vst::IHostApplication* host) | ||||
| @@ -279,7 +323,7 @@ public: | |||||
| struct Param : public Vst::Parameter | struct Param : public Vst::Parameter | ||||
| { | { | ||||
| Param (JuceVST3EditController& editController, AudioProcessorParameter& p, | Param (JuceVST3EditController& editController, AudioProcessorParameter& p, | ||||
| Vst::ParamID vstParamID, bool forceLegacyParamIDs) | |||||
| Vst::ParamID vstParamID, bool isBypassParameter, bool forceLegacyParamIDs) | |||||
| : owner (editController), param (p) | : owner (editController), param (p) | ||||
| { | { | ||||
| info.id = vstParamID; | info.id = vstParamID; | ||||
| @@ -306,6 +350,9 @@ public: | |||||
| else | else | ||||
| info.flags = param.isAutomatable() ? Vst::ParameterInfo::kCanAutomate : 0; | info.flags = param.isAutomatable() ? Vst::ParameterInfo::kCanAutomate : 0; | ||||
| if (isBypassParameter) | |||||
| info.flags |= Vst::ParameterInfo::kIsBypass; | |||||
| valueNormalized = info.defaultNormalizedValue; | valueNormalized = info.defaultNormalizedValue; | ||||
| } | } | ||||
| @@ -370,88 +417,6 @@ public: | |||||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Param) | 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 | struct ProgramChangeParameter : public Vst::Parameter | ||||
| { | { | ||||
| @@ -696,6 +661,19 @@ public: | |||||
| componentHandler->restartComponent (Vst::kLatencyChanged | Vst::kParamValuesChanged); | 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 | AudioProcessor* getPluginInstance() const noexcept | ||||
| { | { | ||||
| @@ -732,6 +710,11 @@ private: | |||||
| { | { | ||||
| pluginInstance->addListener (this); | 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 (parameters.getParameterCount() <= 0) | ||||
| { | { | ||||
| #if JUCE_FORCE_USE_LEGACY_PARAM_IDS | #if JUCE_FORCE_USE_LEGACY_PARAM_IDS | ||||
| @@ -747,11 +730,10 @@ private: | |||||
| auto vstParamID = audioProcessor->getVSTParamIDForIndex (i); | auto vstParamID = audioProcessor->getVSTParamIDForIndex (i); | ||||
| auto* juceParam = audioProcessor->getParamForVSTParamID (vstParamID); | 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) | if (pluginInstance->getNumPrograms() > 1) | ||||
| parameters.addParameter (new ProgramChangeParameter (*pluginInstance)); | parameters.addParameter (new ProgramChangeParameter (*pluginInstance)); | ||||
| } | } | ||||
| @@ -1331,24 +1313,50 @@ public: | |||||
| tresult PLUGIN_API setIoMode (Vst::IoMode) override { return kNotImplemented; } | tresult PLUGIN_API setIoMode (Vst::IoMode) override { return kNotImplemented; } | ||||
| tresult PLUGIN_API getRoutingInfo (Vst::RoutingInfo&, Vst::RoutingInfo&) 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) | 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) | 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) | void getStateInformation (MemoryBlock& destData) | ||||
| @@ -2040,13 +2048,7 @@ public: | |||||
| { | { | ||||
| auto vstParamID = paramQueue->getParameterId(); | 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 numPrograms = pluginInstance->getNumPrograms(); | ||||
| auto programValue = roundToInt (value * (jmax (0, numPrograms - 1))); | auto programValue = roundToInt (value * (jmax (0, numPrograms - 1))); | ||||
| @@ -2055,6 +2057,10 @@ public: | |||||
| && programValue != pluginInstance->getCurrentProgram()) | && programValue != pluginInstance->getCurrentProgram()) | ||||
| pluginInstance->setCurrentProgram (programValue); | pluginInstance->setCurrentProgram (programValue); | ||||
| } | } | ||||
| #if JUCE_VST3_EMULATE_MIDI_CC_WITH_PARAMETERS | |||||
| else if (juceVST3EditController->isMidiControllerParamID (vstParamID)) | |||||
| addParameterChangeToMidiBuffer (offsetSamples, vstParamID, value); | |||||
| #endif | |||||
| else | else | ||||
| { | { | ||||
| auto floatValue = static_cast<float> (value); | auto floatValue = static_cast<float> (value); | ||||
| @@ -1010,10 +1010,20 @@ public: | |||||
| for (int i = 0; i < getBusCount (false); ++i) AudioUnitReset (audioUnit, kAudioUnitScope_Output, static_cast<UInt32> (i)); | 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(); | auto numSamples = buffer.getNumSamples(); | ||||
| if (auSupportsBypass) | |||||
| { | |||||
| updateBypass (processBlockBypassedCalled); | |||||
| } | |||||
| else if (processBlockBypassedCalled) | |||||
| { | |||||
| AudioProcessor::processBlockBypassed (buffer, midiMessages); | |||||
| return; | |||||
| } | |||||
| if (prepared) | if (prepared) | ||||
| { | { | ||||
| timeStamp.mHostTime = GetCurrentHostTime (numSamples, getSampleRate(), isAUv3); | 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; } | bool hasEditor() const override { return true; } | ||||
| AudioProcessorEditor* createEditor() override; | 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() | void updateLatency() | ||||
| @@ -1439,7 +1468,7 @@ private: | |||||
| String pluginName, manufacturer, version; | String pluginName, manufacturer, version; | ||||
| String fileOrIdentifier; | String fileOrIdentifier; | ||||
| CriticalSection lock; | CriticalSection lock; | ||||
| bool wantsMidiMessages, producesMidiMessages, wasPlaying, prepared, isAUv3, isMidiEffectPlugin; | |||||
| bool wantsMidiMessages, producesMidiMessages, wasPlaying, prepared, isAUv3, isMidiEffectPlugin, lastBypassValue = false; | |||||
| struct AUBuffer | struct AUBuffer | ||||
| { | { | ||||
| @@ -1459,6 +1488,99 @@ private: | |||||
| HeapBlock<AudioBufferList> bufferList; | 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; | OwnedArray<AUBuffer> outputBufferList; | ||||
| AudioTimeStamp timeStamp; | AudioTimeStamp timeStamp; | ||||
| AudioBuffer<float>* currentBuffer; | AudioBuffer<float>* currentBuffer; | ||||
| @@ -1477,6 +1599,8 @@ private: | |||||
| MidiDataConcatenator midiConcatenator; | MidiDataConcatenator midiConcatenator; | ||||
| CriticalSection midiInLock; | CriticalSection midiInLock; | ||||
| MidiBuffer incomingMidi; | MidiBuffer incomingMidi; | ||||
| ScopedPointer<AUBypassParameter> bypassParam; | |||||
| bool lastProcessBlockCallWasBypass = false, auSupportsBypass = false; | |||||
| void createPluginCallbacks() | void createPluginCallbacks() | ||||
| { | { | ||||
| @@ -1536,6 +1660,7 @@ private: | |||||
| addPropertyChangeListener (kAudioUnitProperty_PresentPreset); | addPropertyChangeListener (kAudioUnitProperty_PresentPreset); | ||||
| addPropertyChangeListener (kAudioUnitProperty_ParameterList); | addPropertyChangeListener (kAudioUnitProperty_ParameterList); | ||||
| addPropertyChangeListener (kAudioUnitProperty_Latency); | addPropertyChangeListener (kAudioUnitProperty_Latency); | ||||
| addPropertyChangeListener (kAudioUnitProperty_BypassEffect); | |||||
| #endif | #endif | ||||
| } | } | ||||
| } | } | ||||
| @@ -1607,6 +1732,9 @@ private: | |||||
| sendAllParametersChangedEvents(); | sendAllParametersChangedEvents(); | ||||
| else if (event.mArgument.mProperty.mPropertyID == kAudioUnitProperty_Latency) | else if (event.mArgument.mProperty.mPropertyID == kAudioUnitProperty_Latency) | ||||
| updateLatency(); | updateLatency(); | ||||
| else if (event.mArgument.mProperty.mPropertyID == kAudioUnitProperty_BypassEffect) | |||||
| if (bypassParam != nullptr) | |||||
| bypassParam->setValueNotifyingHost (bypassParam->getValue()); | |||||
| break; | break; | ||||
| } | } | ||||
| @@ -2038,6 +2166,23 @@ private: | |||||
| return false; | 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) | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AudioUnitPluginInstance) | ||||
| }; | }; | ||||
| @@ -1883,14 +1883,18 @@ struct VST3PluginInstance : public AudioPluginInstance | |||||
| int numSteps = isDiscrete ? paramInfo.stepCount + 1 | int numSteps = isDiscrete ? paramInfo.stepCount + 1 | ||||
| : AudioProcessor::getDefaultNumParameterSteps(); | : 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(); | synchroniseStates(); | ||||
| @@ -2015,12 +2019,13 @@ struct VST3PluginInstance : public AudioPluginInstance | |||||
| return (processor->canProcessSampleSize (Vst::kSample64) == kResultTrue); | return (processor->canProcessSampleSize (Vst::kSample64) == kResultTrue); | ||||
| } | } | ||||
| //============================================================================== | |||||
| void processBlock (AudioBuffer<float>& buffer, MidiBuffer& midiMessages) override | void processBlock (AudioBuffer<float>& buffer, MidiBuffer& midiMessages) override | ||||
| { | { | ||||
| jassert (! isUsingDoublePrecision()); | jassert (! isUsingDoublePrecision()); | ||||
| if (isActive && processor != nullptr) | if (isActive && processor != nullptr) | ||||
| processAudio (buffer, midiMessages, Vst::kSample32); | |||||
| processAudio (buffer, midiMessages, Vst::kSample32, false); | |||||
| } | } | ||||
| void processBlock (AudioBuffer<double>& buffer, MidiBuffer& midiMessages) override | void processBlock (AudioBuffer<double>& buffer, MidiBuffer& midiMessages) override | ||||
| @@ -2028,12 +2033,43 @@ struct VST3PluginInstance : public AudioPluginInstance | |||||
| jassert (isUsingDoublePrecision()); | jassert (isUsingDoublePrecision()); | ||||
| if (isActive && processor != nullptr) | 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> | template <typename FloatType> | ||||
| void processAudio (AudioBuffer<FloatType>& buffer, MidiBuffer& midiMessages, | void processAudio (AudioBuffer<FloatType>& buffer, MidiBuffer& midiMessages, | ||||
| Vst::SymbolicSampleSizes sampleSize) | |||||
| Vst::SymbolicSampleSizes sampleSize, bool isProcessBlockBypassedCall) | |||||
| { | { | ||||
| using namespace Vst; | using namespace Vst; | ||||
| auto numSamples = buffer.getNumSamples(); | auto numSamples = buffer.getNumSamples(); | ||||
| @@ -2041,6 +2077,8 @@ struct VST3PluginInstance : public AudioPluginInstance | |||||
| auto numInputAudioBuses = getBusCount (true); | auto numInputAudioBuses = getBusCount (true); | ||||
| auto numOutputAudioBuses = getBusCount (false); | auto numOutputAudioBuses = getBusCount (false); | ||||
| updateBypass (isProcessBlockBypassedCall); | |||||
| ProcessData data; | ProcessData data; | ||||
| data.processMode = isNonRealtime() ? kOffline : kRealtime; | data.processMode = isNonRealtime() ? kOffline : kRealtime; | ||||
| data.symbolicSampleSize = sampleSize; | data.symbolicSampleSize = sampleSize; | ||||
| @@ -2266,6 +2304,9 @@ struct VST3PluginInstance : public AudioPluginInstance | |||||
| bool acceptsMidi() const override { return getNumSingleDirectionBusesFor (holder->component, true, false) > 0; } | bool acceptsMidi() const override { return getNumSingleDirectionBusesFor (holder->component, true, false) > 0; } | ||||
| bool producesMidi() const override { return getNumSingleDirectionBusesFor (holder->component, false, 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." */ | /** 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 | double getTailLengthSeconds() const override | ||||
| @@ -2588,7 +2629,8 @@ private: | |||||
| ComSmartPtr<ParamValueQueueList> inputParameterChanges, outputParameterChanges; | ComSmartPtr<ParamValueQueueList> inputParameterChanges, outputParameterChanges; | ||||
| ComSmartPtr<MidiEventList> midiInputs, midiOutputs; | ComSmartPtr<MidiEventList> midiInputs, midiOutputs; | ||||
| Vst::ProcessContext timingInfo; //< Only use this in processBlock()! | 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 */ | /** Some plugins need to be "connected" to intercommunicate between their implemented classes */ | ||||
| @@ -2705,6 +2747,28 @@ private: | |||||
| return busInfo; | 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! */ | /** @note An IPlugView, when first created, should start with a ref-count of 1! */ | ||||
| IPlugView* tryCreatingView() const | IPlugView* tryCreatingView() const | ||||
| @@ -979,7 +979,8 @@ struct VSTPluginInstance : public AudioPluginInstance, | |||||
| : AudioPluginInstance (ioConfig), | : AudioPluginInstance (ioConfig), | ||||
| vstEffect (effect), | vstEffect (effect), | ||||
| vstModule (mh), | vstModule (mh), | ||||
| name (mh->pluginName) | |||||
| name (mh->pluginName), | |||||
| bypassParam (new VST2BypassParameter (*this)) | |||||
| { | { | ||||
| jassert (vstEffect != nullptr); | jassert (vstEffect != nullptr); | ||||
| @@ -1063,6 +1064,7 @@ struct VSTPluginInstance : public AudioPluginInstance, | |||||
| valueType)); | valueType)); | ||||
| } | } | ||||
| vstSupportsBypass = pluginCanDo ("bypass"); | |||||
| setRateAndBufferSizeDetails (sampleRateToUse, blockSizeToUse); | setRateAndBufferSizeDetails (sampleRateToUse, blockSizeToUse); | ||||
| } | } | ||||
| @@ -1399,24 +1401,40 @@ struct VSTPluginInstance : public AudioPluginInstance, | |||||
| } | } | ||||
| } | } | ||||
| //============================================================================== | |||||
| void processBlock (AudioBuffer<float>& buffer, MidiBuffer& midiMessages) override | void processBlock (AudioBuffer<float>& buffer, MidiBuffer& midiMessages) override | ||||
| { | { | ||||
| jassert (! isUsingDoublePrecision()); | jassert (! isUsingDoublePrecision()); | ||||
| processAudio (buffer, midiMessages, tmpBufferFloat, channelBufferFloat); | |||||
| processAudio (buffer, midiMessages, tmpBufferFloat, channelBufferFloat, false); | |||||
| } | } | ||||
| void processBlock (AudioBuffer<double>& buffer, MidiBuffer& midiMessages) override | void processBlock (AudioBuffer<double>& buffer, MidiBuffer& midiMessages) override | ||||
| { | { | ||||
| jassert (isUsingDoublePrecision()); | 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 | bool supportsDoublePrecisionProcessing() const override | ||||
| { | { | ||||
| return ((vstEffect->flags & vstEffectFlagInplaceAudio) != 0 | return ((vstEffect->flags & vstEffectFlagInplaceAudio) != 0 | ||||
| && (vstEffect->flags & vstEffectFlagInplaceDoubleAudio) != 0); | && (vstEffect->flags & vstEffectFlagInplaceDoubleAudio) != 0); | ||||
| } | } | ||||
| AudioProcessorParameter* getBypassParameter() const override { return vstSupportsBypass ? bypassParam.get() : nullptr; } | |||||
| //============================================================================== | //============================================================================== | ||||
| bool canAddBus (bool) const override { return false; } | bool canAddBus (bool) const override { return false; } | ||||
| bool canRemoveBus (bool) const override { return false; } | bool canRemoveBus (bool) const override { return false; } | ||||
| @@ -1932,9 +1950,57 @@ struct VSTPluginInstance : public AudioPluginInstance, | |||||
| bool usesCocoaNSView = false; | bool usesCocoaNSView = false; | ||||
| private: | 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; | String name; | ||||
| CriticalSection lock; | CriticalSection lock; | ||||
| bool wantsMidiMessages = false, initialised = false, isPowerOn = false; | bool wantsMidiMessages = false, initialised = false, isPowerOn = false; | ||||
| bool lastProcessBlockCallWasBypass = false, vstSupportsBypass = false; | |||||
| mutable StringArray programNames; | mutable StringArray programNames; | ||||
| AudioBuffer<float> outOfPlaceBuffer; | AudioBuffer<float> outOfPlaceBuffer; | ||||
| @@ -1948,6 +2014,7 @@ private: | |||||
| AudioBuffer<double> tmpBufferDouble; | AudioBuffer<double> tmpBufferDouble; | ||||
| HeapBlock<double*> channelBufferDouble; | HeapBlock<double*> channelBufferDouble; | ||||
| ScopedPointer<VST2BypassParameter> bypassParam; | |||||
| ScopedPointer<VSTXMLInfo> xmlInfo; | ScopedPointer<VSTXMLInfo> xmlInfo; | ||||
| @@ -2202,8 +2269,20 @@ private: | |||||
| template <typename FloatType> | template <typename FloatType> | ||||
| void processAudio (AudioBuffer<FloatType>& buffer, MidiBuffer& midiMessages, | void processAudio (AudioBuffer<FloatType>& buffer, MidiBuffer& midiMessages, | ||||
| AudioBuffer<FloatType>& tmpBuffer, | 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 numSamples = buffer.getNumSamples(); | ||||
| auto numChannels = buffer.getNumChannels(); | auto numChannels = buffer.getNumChannels(); | ||||
| @@ -2588,6 +2667,23 @@ private: | |||||
| isPowerOn = on; | 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) | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (VSTPluginInstance) | ||||
| }; | }; | ||||
| @@ -188,6 +188,13 @@ public: | |||||
| be the processor's MIDI output. This means that your processor should be careful to | 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. | 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 | 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 | 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 | 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 | 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. | 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 | 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 | 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 | 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(); | 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. | /** Returns true if the processor is being run in an offline mode for rendering. | ||||
| @@ -270,12 +270,20 @@ private: | |||||
| if (processor.isUsingDoublePrecision()) | if (processor.isUsingDoublePrecision()) | ||||
| { | { | ||||
| tempBufferDouble.makeCopyOf (buffer, true); | tempBufferDouble.makeCopyOf (buffer, true); | ||||
| processor.processBlock (tempBufferDouble, midiMessages); | |||||
| if (node->isBypassed()) | |||||
| processor.processBlockBypassed (tempBufferDouble, midiMessages); | |||||
| else | |||||
| processor.processBlock (tempBufferDouble, midiMessages); | |||||
| buffer.makeCopyOf (tempBufferDouble, true); | buffer.makeCopyOf (tempBufferDouble, true); | ||||
| } | } | ||||
| else | 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()) | if (processor.isUsingDoublePrecision()) | ||||
| { | { | ||||
| processor.processBlock (buffer, midiMessages); | |||||
| if (node->isBypassed()) | |||||
| processor.processBlockBypassed (buffer, midiMessages); | |||||
| else | |||||
| processor.processBlock (buffer, midiMessages); | |||||
| } | } | ||||
| else | else | ||||
| { | { | ||||
| tempBufferFloat.makeCopyOf (buffer, true); | tempBufferFloat.makeCopyOf (buffer, true); | ||||
| processor.processBlock (tempBufferFloat, midiMessages); | |||||
| if (node->isBypassed()) | |||||
| processor.processBlockBypassed (tempBufferFloat, midiMessages); | |||||
| else | |||||
| processor.processBlock (tempBufferFloat, midiMessages); | |||||
| buffer.makeCopyOf (tempBufferFloat, true); | buffer.makeCopyOf (tempBufferFloat, true); | ||||
| } | } | ||||
| } | } | ||||
| @@ -826,6 +842,29 @@ bool AudioProcessorGraph::Node::Connection::operator== (const Connection& other) | |||||
| && otherChannel == other.otherChannel; | && 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::RenderSequenceFloat : public GraphRenderSequence<float> {}; | ||||
| struct AudioProcessorGraph::RenderSequenceDouble : public GraphRenderSequence<double> {}; | struct AudioProcessorGraph::RenderSequenceDouble : public GraphRenderSequence<double> {}; | ||||
| @@ -108,6 +108,13 @@ public: | |||||
| */ | */ | ||||
| NamedValueSet properties; | 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. */ | /** A convenient typedef for referring to a pointer to a node object. */ | ||||
| typedef ReferenceCountedObjectPtr<Node> Ptr; | typedef ReferenceCountedObjectPtr<Node> Ptr; | ||||
| @@ -126,7 +133,7 @@ public: | |||||
| const ScopedPointer<AudioProcessor> processor; | const ScopedPointer<AudioProcessor> processor; | ||||
| Array<Connection> inputs, outputs; | Array<Connection> inputs, outputs; | ||||
| bool isPrepared = false; | |||||
| bool isPrepared = false, bypassed = false; | |||||
| Node (NodeID, AudioProcessor*) noexcept; | Node (NodeID, AudioProcessor*) noexcept; | ||||
| @@ -503,8 +503,6 @@ struct GenericAudioProcessorEditor::Pimpl | |||||
| owner.addAndMakeVisible (view); | owner.addAndMakeVisible (view); | ||||
| view.setScrollBarsShown (true, false); | 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) | GenericAudioProcessorEditor::GenericAudioProcessorEditor (AudioProcessor* const p) | ||||
| : AudioProcessorEditor (p), pimpl (new Pimpl (*this)) | : AudioProcessorEditor (p), pimpl (new Pimpl (*this)) | ||||
| {} | |||||
| { | |||||
| setSize (pimpl->view.getViewedComponent()->getWidth() + pimpl->view.getVerticalScrollBar().getWidth(), | |||||
| jmin (pimpl->view.getViewedComponent()->getHeight(), 400)); | |||||
| } | |||||
| GenericAudioProcessorEditor::~GenericAudioProcessorEditor() {} | GenericAudioProcessorEditor::~GenericAudioProcessorEditor() {} | ||||