| @@ -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; | |||
| @@ -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; | |||
| @@ -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); | |||
| } | |||
| //============================================================================== | |||
| @@ -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; | |||
| @@ -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; | |||
| } | |||
| @@ -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); | |||
| @@ -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) | |||
| }; | |||
| @@ -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 | |||
| @@ -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) | |||
| }; | |||
| @@ -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. | |||
| @@ -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> {}; | |||
| @@ -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; | |||
| @@ -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() {} | |||