VST3 Bypass

I only create a bypass parameter if running in VST3 mode so it should have no effect on VST and vice versa.

I would love to see someone figure out how to prevent the bypass state from being save/loaded in the presets. This is the only thing preventing me from releasing my plugin in VST3 right now.

"I only create a bypass parameter if running in VST3 mode so it should have no effect on VST and vice versa."

I think it actually might matter. If people take sessions back and forth between systems that have VST3 plugs and VST2.4 plugs, you need the sessions to be compatible in both directions.  I have a few Pro Tools customers who do similar things, taking sessions back and forth between AAX and RTAS machines.  There are no compatibility problems there.

It's easy enough to argue that once you go to VST3, you should never go back.  But that's not what people do.  If there turned out to be an unrelated issue in a VST3 version of a plug, you're basically telling the user to scrap any work he'd done with the plug. That user would be justifiably angry.  You'd also be telling the user that any sessions created with VST2.4 plugs wouldn't quite port to VST3.  That's really tough if he's got to revisit work that was completed a year or two earlier.

 

Isn't this all just theoretical anyway, since JUCE doesn't currently support VST3 bypass? I have implemented a workaround but there are still issues, like session restore vs preset and the fact that "processBlockBypassed" is not called in the VST3 wrapper. I currently plan on doing my first release in VST2 only because of this and also the lack of sidechain support.

Once VST3 is fully implemented in JUCE, it's unclear to me what will happen if I create a bypass parameter in VST2 mode, since bypass is implemented differently in VST2 and I still don't know how JUCE is going to handle this.

I made a few changes in the VST3 wrapper to have a master bypass.

Could this code be integrated or adapted in the JUCE code ?


diff --git a/modules/juce_audio_plugin_client/VST3/juce_VST3_Wrapper.cpp b/modules/juce_audio_plugin_client/VST3/juce_VST3_Wrapper.cpp
index 6eb9cb3..4ea94d2 100644
--- a/modules/juce_audio_plugin_client/VST3/juce_VST3_Wrapper.cpp
+++ b/modules/juce_audio_plugin_client/VST3/juce_VST3_Wrapper.cpp
@@ -231,6 +231,61 @@ public:
     };
 
     //==============================================================================
+    struct BypassParam  : public Vst::Parameter
+    {
+        BypassParam (int index)
+        {
+            info.id = (Vst::ParamID) index;
+            toString128 (info.title, "Master Bypass");
+            toString128 (info.shortTitle, "MstByp");
+            toString128 (info.units, "");
+            info.stepCount = (Steinberg::int32) 1;
+            info.defaultNormalizedValue = 0.0f;
+            info.unitId = Vst::kRootUnitId;
+            info.flags = Vst::ParameterInfo::kCanAutomate | Vst::ParameterInfo::kIsBypass;
+        }
+        
+        virtual ~BypassParam() {}
+        
+        bool setNormalized (Vst::ParamValue v) override
+        {
+            v = jlimit (0.0, 1.0, v);
+            
+            if (v != valueNormalized)
+            {
+                valueNormalized = v;
+                changed();
+                return true;
+            }
+            
+            return false;
+        }
+        
+        void toString (Vst::ParamValue value, Vst::String128 result) const override
+        {
+            toString128 (result, valueNormalized >= 0.5f ? "Bypass" : "In");
+        }
+        
+        bool fromString (const Vst::TChar* text, Vst::ParamValue& outValueNormalized) const override
+        {
+            outValueNormalized = getStringFromVstTChars(text).startsWith ("B") ? 1.0f : 0.0f;
+            return true;
+        }
+        
+        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; }
+        
+    private:
+        
+        JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (BypassParam)
+    };
+    
+    //==============================================================================
     tresult PLUGIN_API setComponentState (IBStream* stream) override
     {
         // Cubase and Nuendo need to inform the host of the current parameter values
@@ -335,8 +390,11 @@ private:
             pluginInstance->addListener (this);
 
             if (parameters.getParameterCount() <= 0)
+            {
                 for (int i = 0; i < pluginInstance->getNumParameters(); ++i)
                     parameters.addParameter (new Param (*pluginInstance, i));
+                parameters.addParameter(new BypassParam(pluginInstance->getNumParameters()));
+            }
 
             audioProcessorChanged (pluginInstance);
         }
@@ -588,6 +646,7 @@ class JuceVST3Component : public Vst::IComponent,
 public:
     JuceVST3Component (Vst::IHostApplication* h)
       : refCount (1),
+        bypass (false),
         host (h),
         audioInputs  (Vst::kAudio, Vst::kInput),
         audioOutputs (Vst::kAudio, Vst::kOutput),
@@ -1250,8 +1309,15 @@ public:
                 if (paramQueue->getPoint (numPoints - 1,  offsetSamples, value) == kResultTrue)
                 {
                     const int id = (int) paramQueue->getParameterId();
-                    jassert (isPositiveAndBelow (id, pluginInstance->getNumParameters()));
-                    pluginInstance->setParameter (id, (float) value);
+                    if (id == pluginInstance->getNumParameters())
+                    {
+                        bypass = value >= 0.5;
+                    }
+                    else
+                    {
+                        jassert (isPositiveAndBelow (id, pluginInstance->getNumParameters()));
+                        pluginInstance->setParameter (id, (float) value);
+                    }
                 }
             }
         }
@@ -1313,6 +1379,8 @@ public:
 
             if (pluginInstance->isSuspended())
                 buffer.clear();
+            else if (bypass)
+                pluginInstance->processBlockBypassed (buffer, midiBuffer);
             else
                 pluginInstance->processBlock (buffer, midiBuffer);
         }
@@ -1353,6 +1421,8 @@ public:
 private:
     //==============================================================================
     Atomic<int> refCount;
+    
+    bool bypass;
 
     AudioProcessor* pluginInstance;
     ComSmartPtr<Vst::IHostApplication> host;

Thanks! This is already in our backlog so thanks for the code suggestions, we'll take a look asap!

VST3 bypass support is now on the latest tip!

Nice!yes

Thanks for adding the bypass support to the wrapper.

If I rebuild my plugins with the new version.. will existing projects load correctly? As I never really looked into how vst3 works I can't quite tell from looking at the changelog. I guess to the host it just looks like there is a new parameter at the end with the bypass flag.

So what happens if I decide to add another param in a future update? Won't that cause troubles with the bypass information?

 

Good question: Everything will work just as before when you upgrade your PlugIn to the newest JUCE version and load an old project. Your plug-in code will never see the extra bypass parameter in the setStateInformation and getStateInformation methods and therefore you can transparently load old projects.

The other way around is more difficult: If a user saves a project using the a Plug-In which uses the newest JUCE version and then opens that project on a computer which still uses the old PlugIn. In this case your setStateInformation may get extra data at the end of the data block. JUCE uses this data to save the bypass parameter internally. The newest JUCE version will always filter out this data so that you never see it. But old JUCE code does not know anything about this data so has no way of filtering it. However, the extra data starts with eight zero bytes, so if your plug-in loads the state information into a string/ValueTree/var/... then you will be safe even in this case.

Also, if you add parameters to your plug-in in the future then everything will still be fine. This is because your AudioProcessor class is responsible for saving/restoring the state and you never even see the bypass parameter. 

Just pulled this into my project. Thanks!

The bypass parameter has :

info.stepCount = 2;

but it has 2 states and one step between them. stepCount should be 1 here.

Thanks for spotting. This is fixed now.

Very cool. works great under Cubase 8.x.

I've tested it under SONAR X2 (says build v21.0.00.01) with latest and all plug-ins starts as bypassed.

This is reproducible with JuceDemoPlugin.

Will try to debug it but if anyone finds a possible reason for that it'll great.

Hi ttg,

I need some help with this. The host bypass button in Sonar does not seem to have anything to do with the VST3 bypass. The VST 3 bypass button is located at the left of the plug-in name in the audio tracks fx list (you need to hover your mouse over the name to see the button). This button works correctly and as expected (but is independent of the host bypass button).

I've also confirmed that the host bypass does not modify the VST3 bypass with other non-JUCE plug-ins. Therefore I don't know what SONAR is indicating when in it brings up the plug-in with the host bypass button enabled. In fact, my recent VST3 bypass has nothing to do with this bug: I've checked out an old JUCE version just before I added the VST3 bypass stuff and Sonar still brings up the plug-in with host bypass enabled.

It would be great if anybody more familar with Sonar could help me understand what Sonar is trying to indicate when it enables the host bypass button by default. This would help me narrow down the problem.

Fabian

I'll check it next time I'm at the office. however on my test system the scenario is as follows:

- Compiled with older JUCE with no VST3 Bypass, the SONAR bypass is grayed out.

- Compiled with current JUCE tip, SONAR bypass is working as VST3 bypass and it is started toggled on (yellowish/orange).

So I don't understand how it's conflicting if it's not VST3 Bypass.

When you say 'older JUCE' which version were you using?

Older would be previous commit / ref still.

I'll try to provide better reproduce of the issue and investigate it further.

I've compared JUCE based performance vs other VST plug-ins. you can take FabFilter Micro evaluation (if with demo expired you can see it).

 

FabFilter:

- Upper left corner when VST3 instance has Host Bypass = Not Bypassed.

JUCE Based VST2:

- Upper left corner grayed out. (it seems SONAR isn't implementing bypass for VST2 since I've been reproducing this with several other plug-ins)

 

JUCE built from tag v3.2.0:

VST2: Grayed Out, VST3: Grayed Out

 

JUCE build from lastest tip (was at time of writing: fbc81a757e6492ee29b8ae90114659350a80fb21)

JUCE Based VST3:

- Upper left corner starts as bypassed (yellowish-oranged).

 

I've tried a little debugging (though I'm very novice in developing...) and it seems that things gets messed up here:

When we arrive to:


// for now we only store the bypass value
        privateData.setProperty ("Bypass", var (isBypassed()), nullptr);
 

even isBypassed() returned false, setProperty gets true...

It seems to be messed up by var (var::var (const bool v) noexcept      : type (&VariantType_Bool::instance)   { value.boolValue = v; })

 

* You can check out non-JUCE based plug-ins to see it happens only with JUCE.

* I've tested all with JuceDemoPlugin and cloning JUCE from git checking-out each time to make sure I'm reproducing properly.

 

 

Thanks for adding this. The VST3 bypass cannot be automated in Studio One, though. This is supposed to work right out of the box, right? The JUCE Demo Plug-in doesn't allow me to right-click the bypass button and add automation like other VST3 plug-ins do.

 

Best,

Stian