From f903695ba3a781388cf4c17148258ebb269da33f Mon Sep 17 00:00:00 2001 From: jules Date: Sun, 18 Nov 2012 18:03:44 +0000 Subject: [PATCH] Audio plugins: better bypass support, with an AudioProcessor::processBlockBypassed() method that can be used for custom behaviour. --- .../AAX/juce_AAX_Wrapper.cpp | 100 +++++++----------- .../AU/juce_AU_Wrapper.mm | 6 +- .../RTAS/juce_RTAS_Wrapper.cpp | 47 +++----- .../VST/juce_VST_Wrapper.cpp | 22 +++- .../processors/juce_AudioProcessor.cpp | 5 +- .../processors/juce_AudioProcessor.h | 11 ++ 6 files changed, 95 insertions(+), 96 deletions(-) diff --git a/modules/juce_audio_plugin_client/AAX/juce_AAX_Wrapper.cpp b/modules/juce_audio_plugin_client/AAX/juce_AAX_Wrapper.cpp index e8b9e102b5..92d32d24d1 100644 --- a/modules/juce_audio_plugin_client/AAX/juce_AAX_Wrapper.cpp +++ b/modules/juce_audio_plugin_client/AAX/juce_AAX_Wrapper.cpp @@ -65,7 +65,7 @@ struct AAXClasses { static void check (AAX_Result result) { - jassert (result == AAX_SUCCESS); + jassert (result == AAX_SUCCESS); (void) result; } struct FourCharConst @@ -148,22 +148,19 @@ struct AAXClasses //============================================================================== struct PluginInstanceInfo { - PluginInstanceInfo (AudioProcessor* pluginInstance_) - : pluginInstance (pluginInstance_) - { - } + PluginInstanceInfo (AudioProcessor& p) : pluginInstance (p) {} - void process (const float* const* inputs, float* const* outputs, const int bufferSize) + void process (const float* const* inputs, float* const* outputs, const int bufferSize, const bool bypass) { - const int numIns = pluginInstance->getNumInputChannels(); - const int numOuts = pluginInstance->getNumOutputChannels(); + const int numIns = pluginInstance.getNumInputChannels(); + const int numOuts = pluginInstance.getNumOutputChannels(); if (numOuts >= numIns) { for (int i = 0; i < numIns; ++i) memcpy (outputs[i], inputs[i], bufferSize * sizeof (float)); - process (outputs, numOuts, bufferSize); + process (outputs, numOuts, bufferSize, bypass); } else { @@ -181,11 +178,11 @@ struct AAXClasses for (int i = numOuts; i < numIns; ++i) channels[i] = const_cast (inputs[i]); - process (channels, numIns, bufferSize); + process (channels, numIns, bufferSize, bypass); } } - void process (float* const* channels, const int numChans, const int bufferSize) + void process (float* const* channels, const int numChans, const int bufferSize, const bool bypass) { AudioSampleBuffer buffer (channels, numChans, bufferSize); @@ -193,26 +190,16 @@ struct AAXClasses midiBuffer.clear(); { - const ScopedLock sl (pluginInstance->getCallbackLock()); - pluginInstance->processBlock (buffer, midiBuffer); - } - } + const ScopedLock sl (pluginInstance.getCallbackLock()); - void bypass (float* const* inputs, float* const* outputs, int bufferSize) - { - const int numIns = pluginInstance->getNumInputChannels(); - const int numOuts = pluginInstance->getNumOutputChannels(); - - for (int i = 0; i < numOuts; ++i) - { - if (i < numIns) - memcpy (outputs[i], inputs[i], sizeof (float) * bufferSize); + if (bypass) + pluginInstance.processBlockBypassed (buffer, midiBuffer); else - zeromem (outputs[i], sizeof (float) * bufferSize); + pluginInstance.processBlock (buffer, midiBuffer); } } - AudioProcessor* pluginInstance; + AudioProcessor& pluginInstance; MidiBuffer midiBuffer; Array channelList; @@ -257,20 +244,18 @@ struct AAXClasses { if (component == nullptr) { - JuceAAX_Parameters* params = dynamic_cast (GetEffectParameters()); - jassert (params != nullptr); - - if (params != nullptr) + if (JuceAAX_Parameters* params = dynamic_cast (GetEffectParameters())) component = new ContentWrapperComponent (*this, params->getPluginInstance()); + else + jassertfalse; } } void CreateViewContainer() { CreateViewContents(); - void* nativeViewToAttachTo = GetViewContainerPtr(); - if (nativeViewToAttachTo != nullptr) + if (void* nativeViewToAttachTo = GetViewContainerPtr()) { #if JUCE_MAC if (GetViewContainerType() == AAX_eViewContainer_Type_NSView) @@ -322,11 +307,11 @@ struct AAXClasses class ContentWrapperComponent : public juce::Component { public: - ContentWrapperComponent (JuceAAX_GUI& gui, AudioProcessor* plugin) + ContentWrapperComponent (JuceAAX_GUI& gui, AudioProcessor& plugin) : owner (gui) { setOpaque (true); - addAndMakeVisible (pluginEditor = plugin->createEditorIfNeeded()); + addAndMakeVisible (pluginEditor = plugin.createEditorIfNeeded()); setBounds (pluginEditor->getLocalBounds()); setBroughtToFrontOnMouseClick (true); } @@ -378,9 +363,9 @@ struct AAXClasses JuceAAX_Parameters() { pluginInstance = createPluginFilter(); + jassert (pluginInstance != nullptr); // your createPluginFilter() method must return an object! - if (pluginInstance != nullptr) - pluginInstance->wrapperType = AudioProcessor::wrapperType_AAX; + pluginInstance->wrapperType = AudioProcessor::wrapperType_AAX; } static AAX_CEffectParameters* AAX_CALLBACK Create() { return new JuceAAX_Parameters(); } @@ -408,7 +393,7 @@ struct AAXClasses jassert (numObjects == 1); // not sure how to handle more than one.. for (size_t i = 0; i < numObjects; ++i) - new (objects + i) PluginInstanceInfo (pluginInstance); + new (objects + i) PluginInstanceInfo (*pluginInstance); break; } @@ -431,7 +416,7 @@ struct AAXClasses //return AAX_ERROR_INVALID_FIELD_INDEX; } - AudioProcessor* getPluginInstance() const noexcept { return pluginInstance; } + AudioProcessor& getPluginInstance() const noexcept { return *pluginInstance; } private: void addBypassParameter() @@ -467,9 +452,9 @@ struct AAXClasses int32_t bufferSize = 0; check (Controller()->GetSignalLatency (&bufferSize)); - AudioProcessor* audioProcessor = getPluginInstance(); - audioProcessor->setPlayConfigDetails (numberOfInputChannels, numberOfOutputChannels, sampleRate, bufferSize); - audioProcessor->prepareToPlay (sampleRate, bufferSize); + AudioProcessor& audioProcessor = getPluginInstance(); + audioProcessor.setPlayConfigDetails (numberOfInputChannels, numberOfOutputChannels, sampleRate, bufferSize); + audioProcessor.prepareToPlay (sampleRate, bufferSize); } JUCELibraryRefCount juceCount; @@ -487,10 +472,8 @@ struct AAXClasses { const JUCEAlgorithmContext& i = **iter; - if (*(i.bypass) != 0) - i.pluginInstance->bypass (i.inputChannels, i.outputChannels, *(i.bufferSize)); - else - i.pluginInstance->process (i.inputChannels, i.outputChannels, *(i.bufferSize)); + i.pluginInstance->process (i.inputChannels, i.outputChannels, + *(i.bufferSize), *(i.bypass) != 0); } } @@ -544,9 +527,7 @@ struct AAXClasses for (int i = 0; i < numConfigs; ++i) { - AAX_IComponentDescriptor* const desc = descriptor.NewComponentDescriptor(); - - if (desc != nullptr) + if (AAX_IComponentDescriptor* const desc = descriptor.NewComponentDescriptor()) { createDescriptor (*desc, channelConfigs [i][0], @@ -563,20 +544,21 @@ AAX_Result JUCE_CDECL GetEffectDescriptions (AAX_ICollection* collection) { AAXClasses::JUCELibraryRefCount libraryRefCount; - AAX_IEffectDescriptor* const descriptor = collection->NewDescriptor(); - if (descriptor == nullptr) - return AAX_ERROR_NULL_OBJECT; + if (AAX_IEffectDescriptor* const descriptor = collection->NewDescriptor()) + { + AAXClasses::getPlugInDescription (*descriptor); + collection->AddEffect (JUCE_STRINGIFY (JucePlugin_AAXIdentifier), descriptor); - AAXClasses::getPlugInDescription (*descriptor); - collection->AddEffect (JUCE_STRINGIFY (JucePlugin_AAXIdentifier), descriptor); + collection->SetManufacturerName (JucePlugin_Manufacturer); + collection->AddPackageName (JucePlugin_Desc); + collection->AddPackageName (JucePlugin_Name); + collection->AddPackageName (AAXClasses::FourCharConst (JucePlugin_PluginCode).asString); + collection->SetPackageVersion (JucePlugin_VersionCode); - collection->SetManufacturerName (JucePlugin_Manufacturer); - collection->AddPackageName (JucePlugin_Desc); - collection->AddPackageName (JucePlugin_Name); - collection->AddPackageName (AAXClasses::FourCharConst (JucePlugin_PluginCode).asString); - collection->SetPackageVersion (JucePlugin_VersionCode); + return AAX_SUCCESS; + } - return AAX_SUCCESS; + return AAX_ERROR_NULL_OBJECT; } #endif diff --git a/modules/juce_audio_plugin_client/AU/juce_AU_Wrapper.mm b/modules/juce_audio_plugin_client/AU/juce_AU_Wrapper.mm index 65f9e5b5ba..2e18315b14 100644 --- a/modules/juce_audio_plugin_client/AU/juce_AU_Wrapper.mm +++ b/modules/juce_audio_plugin_client/AU/juce_AU_Wrapper.mm @@ -141,7 +141,7 @@ public: } juceFilter = createPluginFilter(); - jassert (juceFilter != nullptr); + jassert (juceFilter != nullptr); // your createPluginFilter() method must return an object! juceFilter->wrapperType = AudioProcessor::wrapperType_AudioUnit; @@ -781,6 +781,10 @@ public: for (int j = 0; j < numOut; ++j) zeromem (channels [j], sizeof (float) * numSamples); } + else if (ShouldBypassEffect()) + { + juceFilter->processBlockBypassed (buffer, midiEvents); + } else { juceFilter->processBlock (buffer, midiEvents); diff --git a/modules/juce_audio_plugin_client/RTAS/juce_RTAS_Wrapper.cpp b/modules/juce_audio_plugin_client/RTAS/juce_RTAS_Wrapper.cpp index 82be5fc15b..95c3a8d43f 100644 --- a/modules/juce_audio_plugin_client/RTAS/juce_RTAS_Wrapper.cpp +++ b/modules/juce_audio_plugin_client/RTAS/juce_RTAS_Wrapper.cpp @@ -156,7 +156,7 @@ public: { asyncUpdater = new InternalAsyncUpdater (*this); juceFilter = createPluginFilter(); - jassert (juceFilter != nullptr); + jassert (juceFilter != nullptr); // your createPluginFilter() method must return an object! juceFilter->wrapperType = AudioProcessor::wrapperType_RTAS; @@ -278,13 +278,8 @@ public: #if JUCE_WINDOWS if (wrapper != nullptr) { - ComponentPeer* const peer = wrapper->getPeer(); - - if (peer != nullptr) - { - // (seems to be required in PT6.4, but not in 7.x) - peer->repaint (wrapper->getLocalBounds()); - } + if (ComponentPeer* const peer = wrapper->getPeer()) + peer->repaint (wrapper->getLocalBounds()); // (seems to be required in PT6.4, but not in 7.x) } #endif } @@ -300,13 +295,12 @@ public: void deleteEditorComp() { - if (editorComp != 0 || wrapper != nullptr) + if (editorComp != nullptr || wrapper != nullptr) { JUCE_AUTORELEASEPOOL PopupMenu::dismissAllActiveMenus(); - juce::Component* const modalComponent = juce::Component::getCurrentlyModalComponent(); - if (modalComponent != nullptr) + if (juce::Component* const modalComponent = juce::Component::getCurrentlyModalComponent()) modalComponent->exitModalState (0); filter->editorBeingDeleted (editorComp); @@ -372,9 +366,7 @@ public: void resized() { - juce::Component* const ed = getEditor(); - - if (ed != nullptr) + if (juce::Component* const ed = getEditor()) ed->setBounds (getLocalBounds()); repaint(); @@ -432,8 +424,8 @@ public: void GetViewRect (Rect* size) { - if (getView() != nullptr) - getView()->updateSize(); + if (JuceCustomUIView* const v = getView()) + v->updateSize(); CEffectProcessRTAS::GetViewRect (size); } @@ -447,8 +439,8 @@ public: { CEffectProcessRTAS::SetViewPort (port); - if (getView() != nullptr) - getView()->attachToWindow (port); + if (JuceCustomUIView* const v = getView()) + v->attachToWindow (port); } //============================================================================== @@ -481,9 +473,7 @@ protected: if (MIDILogIn() == noErr) { #if JucePlugin_WantsMidiInput - CEffectType* const type = dynamic_cast (this->GetProcessType()); - - if (type != nullptr) + if (CEffectType* const type = dynamic_cast (this->GetProcessType())) { char nodeName [64]; type->GetProcessTypeName (63, nodeName); @@ -536,25 +526,19 @@ protected: return; } - if (mBypassed) - { - bypassBuffers (inputs, outputs, numSamples); - return; - } - #if JucePlugin_WantsMidiInput midiEvents.clear(); const Cmn_UInt32 bufferSize = mRTGlobals->mHWBufferSizeInSamples; - if (midiBufferNode != 0) + if (midiBufferNode != nullptr) { if (midiBufferNode->GetAdvanceScheduleTime() != bufferSize) midiBufferNode->SetAdvanceScheduleTime (bufferSize); if (midiBufferNode->FillMIDIBuffer (mRTGlobals->mRunningTime, numSamples) == noErr) { - jassert (midiBufferNode->GetBufferPtr() != 0); + jassert (midiBufferNode->GetBufferPtr() != nullptr); const int numMidiEvents = midiBufferNode->GetBufferSize(); for (int i = 0; i < numMidiEvents; ++i) @@ -605,7 +589,10 @@ protected: AudioSampleBuffer chans (channels, totalChans, numSamples); - juceFilter->processBlock (chans, midiEvents); + if (mBypassed) + juceFilter->processBlockBypassed (chans, midiEvents); + else + juceFilter->processBlock (chans, midiEvents); } } diff --git a/modules/juce_audio_plugin_client/VST/juce_VST_Wrapper.cpp b/modules/juce_audio_plugin_client/VST/juce_VST_Wrapper.cpp index 9a1cda591a..4acfc23097 100644 --- a/modules/juce_audio_plugin_client/VST/juce_VST_Wrapper.cpp +++ b/modules/juce_audio_plugin_client/VST/juce_VST_Wrapper.cpp @@ -271,6 +271,7 @@ public: numInChans (JucePlugin_MaxNumInputChannels), numOutChans (JucePlugin_MaxNumOutputChannels), isProcessing (false), + isBypassed (false), hasShutdown (false), firstProcessCallback (true), shouldDeleteEditor (false), @@ -397,7 +398,8 @@ public: #endif } else if (strcmp (text, "receiveVstTimeInfo") == 0 - || strcmp (text, "conformsToWindowRules") == 0) + || strcmp (text, "conformsToWindowRules") == 0 + || strcmp (text, "bypass") == 0) { result = 1; } @@ -452,6 +454,12 @@ public: } } + bool setBypass (bool b) + { + isBypassed = b; + return true; + } + //============================================================================== VstInt32 processEvents (VstEvents* events) { @@ -556,7 +564,11 @@ public: { AudioSampleBuffer chans (channels, jmax (numIn, numOut), numSamples); - filter->processBlock (chans, midiEvents); + + if (isBypassed) + filter->processBlockBypassed (chans, midiEvents); + else + filter->processBlock (chans, midiEvents); } // copy back any temp channels that may have been used.. @@ -1369,7 +1381,7 @@ private: VSTMidiEventList outgoingEvents; VstSpeakerArrangementType speakerIn, speakerOut; int numInChans, numOutChans; - bool isProcessing, hasShutdown, firstProcessCallback, shouldDeleteEditor; + bool isProcessing, isBypassed, hasShutdown, firstProcessCallback, shouldDeleteEditor; HeapBlock channels; Array tempChannels; // see note in processReplacing() @@ -1467,6 +1479,10 @@ namespace JuceVSTWrapper* const wrapper = new JuceVSTWrapper (audioMaster, filter); return wrapper->getAeffect(); } + else + { + jassertfalse; // your createPluginFilter() method must return an object! + } } } catch (...) diff --git a/modules/juce_audio_processors/processors/juce_AudioProcessor.cpp b/modules/juce_audio_processors/processors/juce_AudioProcessor.cpp index 32e9bed1bc..e2611160b8 100644 --- a/modules/juce_audio_processors/processors/juce_AudioProcessor.cpp +++ b/modules/juce_audio_processors/processors/juce_AudioProcessor.cpp @@ -207,9 +207,8 @@ void AudioProcessor::suspendProcessing (const bool shouldBeSuspended) suspended = shouldBeSuspended; } -void AudioProcessor::reset() -{ -} +void AudioProcessor::reset() {} +void AudioProcessor::processBlockBypassed (AudioSampleBuffer&, MidiBuffer&) {} //============================================================================== void AudioProcessor::editorBeingDeleted (AudioProcessorEditor* const editor) noexcept diff --git a/modules/juce_audio_processors/processors/juce_AudioProcessor.h b/modules/juce_audio_processors/processors/juce_AudioProcessor.h index e6b23d0790..44380831d0 100644 --- a/modules/juce_audio_processors/processors/juce_AudioProcessor.h +++ b/modules/juce_audio_processors/processors/juce_AudioProcessor.h @@ -134,6 +134,17 @@ public: virtual void processBlock (AudioSampleBuffer& buffer, MidiBuffer& midiMessages) = 0; + /** Renders the next block when the processor is being bypassed. + The default implementation of this method will pass-through any incoming audio, but + you may override this method e.g. to add latency compensation to the data to match + the processor's latency characteristics. This will avoid situations where bypassing + will shift the signal forward in time, possibly creating pre-echo effects and odd timings. + Another use for this method would be to cross-fade or morph between the wet (not bypassed) + and dry (bypassed) signals. + */ + virtual void processBlockBypassed (AudioSampleBuffer& buffer, + MidiBuffer& midiMessages); + //============================================================================== /** Returns the current AudioPlayHead object that should be used to find out the state and position of the playhead.