VST3 Bypass


#1

Hi all,

I've received reports from customers and confirmed them here.  In Cubase and Nueundo, there's a little bypass button in the GUI wrapper.  This button appears in VST 2.4 but doesn't show up in VST3.  I've spent a frustrating day in VST3 documentation and in the Juce wrapper trying to figure it out.  But no luck.  There doesn't seem to be an equivalent in VST3 to the CanDo interface in VST2.4, but there must be something.  But I'm going down blind alleyways trying to figure out what to do.

Ring any bells with anyone?


#2

Same problem here with my Juce VST3 plugins. Steinberg hosts don't activate the plugin bypass button.


#3

Iirc, when I implemented the VST3 client and hosting stuff, there was a parameter that was optional to add for bypassing. It was a bit awkward, because JUCE allows the flexibility to set/get your plugins parameters by index - and we would need to reserve one for bypassing (which the only plugin formats don't support, I think... can't remember). Setting an index that a user-programmer can't use could make for a headache to work around - though I guess an integer value for parameter index that's "std::numeric_limits<int>::max() - 1" could do it.


#4

I've spent some time the VST3 SDK documentation and also going through the VST3 wrapper.  In the old VST2.4 stuff, the bypass business appears to have been taken care of with the CanDo call. I can't find any sort of equivalant in VST3, and the word bypass doesn't even appear in the VST3 docs. From the best I can tell (and I don't feel really confident about this), an extra parameter is needed in VST3 to handle bypass. This would seem to break the otherwise smooth ability to take a session back and forth between VST2 and VST3.  For that reason, I think I'm probably missing something.


#5

Juce, could we please have something like that to solve this problem ?

In AudioProcessor :

virtual bool isParameterBypass(int parameterIndex) const;

In juce_VST3_Wrapper.cpp, at the end of Param struct constructor :

if (p.isParameterBypass(index))

{

  info.flags |= Vst::ParameterInfo::kIsBypass;

}

Thanks


#6

Well.. I can't add it like that, but I can add another virtual method to the AudioProcessorParameter class, which is the new way that all this stuff works. Seems like a sensible plan!


#7

Another solution, not very clean but simpler: add the VST3 bypass flag if the parameter name is "Bypass"...


#8

That's too implicit, and doesn't deal with translations (what if you want to display the parameter name? you would have to hack your code to workaround/check and see if the name is/contains bypass to be able to translate it later...)


#9

OK, what about adding a master bypass parameter directly in the VST3 wrapper ? That's the solution used in the other wrappers.


#10

In a plug with a dedicated bypass control, I've indicated to VST3 which on/off control is the bypass by setting info.flags for that control in juce_VST3_Wrapper.cpp, at the end of Param struct constructor:

info.flags |= Vst::ParameterInfo::kIsBypass;

And now in Cubase 7 the bypass button appears in the VST3 plug-in header and toggles along with the UI... hooray!

But on session restore, the bypass button in the header is always off, even though the UI control restores state properly.

Anyone else seen this before?


#11

I have just implemented the kIsBypass flag in my plugin and I'm seeing the same issue with session restore. Has anyone figured out how to solve this?


#12

Huh? You simply added kIsBypass in JuceVST3EditController::Param's constructor - just like that?

If so, you just made every parameter the (what's supposed to be the only) bypass parameter. (At least, that's how I understood from the docs)


#13

Not exactly. Here's what I did.

I created an extra parameter just for the bypass.

I added the following code to the JuceVST3EditController::Param constructor

if (p.isParameterBypass(index))
    info.flags |= Vst::ParameterInfo::kIsBypass;

which calls a new method I added to AudioProcessor.

bool AudioProcessor::isParameterBypass (int index) const
{
    if (AudioProcessorParameter* p = managedParameters[index])
        return p->isBypass();
    return true;
}


This calls a new method I added to AudioProcessorParameter

bool AudioProcessorParameter::isBypass() const         { return false; }

which I overrode that method in my plugin's parameter class, derived from AudioProcessorParameter.


// return true if this is the bypass parameter
bool MyAudioParameter::isBypass() const
{
    return (getParameterIndex() == PARAM_BYPASS);
}

I have verified that the kIsBypass bit gets set only on the paramater I have reserved. The little bypass button now appears in the frame and I can toggle it off and on, and I can even control it via automation. However, there are still a couple of issues. First is that the VST3 wrapper doesn't check the flag, so it still calls processBlock instead of processBlockBypassed regardless of the bypass state. I can work around that by implementing my own bypass but it should really be taken care of automatically by the framework so I don't need to code a special case just for VST3. Second is that although MyAudioProcessor::setStateInformation correctly restores the bypass parameter on session restore, it also saves and restores the bypass state when saving and restoring presets. So if I save a preset with the bypass on, it will engage the bypass whenever I load the preset. This is because setStateInformation is used both for session restore and loading presets and I can't figure out if there is a way to distinguish one from the other in the code.


#14

I'm interested in this solution.  Have you found that sessions saved with this VST3 tweak will load and run properly when only VST2.4 is available (and vice versa)?


#15

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


#16

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.


#17

"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.

 


#18

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.


#19

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;


#20

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