How to react on VST3 Bypass

Hi,

I want to connect my own bypass procedures to the VST3 bypass state. I’ve found the Get/SetBypassed() method in IPlugVST3ProcessorBase, but this seems not what I need.

I want something like a “normal” IParam, to which I can connect to change the behavior of my plugin.

Is it possible without digging in VST3 code? Any idea?

Thank you in advance!

Hermann

My solution to this:

#if IPLUG_DSP
void AmbientReverb::ProcessBlock(sample **inputs, sample **outputs, int nFrames)
{
  const int nChans = NOutChansConnected();
  if (GetBypassed())
  {
    for (int s = 0; s < nFrames; ++s)
    {
      for (int c = 0; c < nChans; ++c)
      {
        outputs[c][s] = inputs[c][s];
      }
    }
  }
  else
    fReverb.ProcessBlock(inputs, outputs, nFrames, nChans);
}
#endif

Great idea, but ProcessBlock isn’t called, if the bypass is enabled… So we first must ensure. that this happens.

Hi vasyan,

I’ve just looked at your “Chorus GAS” VST3 plugin and you managed to ignore the hosts Bypass switch. What did you do for this?

Thanks!

This plugin does not provide a bypass at all.
Believe me, the traversal should be done by the host and not by the plugin. All modern DAWs have a plugin bypass function. Therefore, I do not consider it necessary to duplicate this function with a plugin.

Debugging helps…

In case of GetBypassed() == true, IPlugVST3ProcessorBase::ProcessAudio() calls PassThroughBuffers() instead of ProcessBuffers(). As this is not a virtual method, I can not overwrite PassThroughBuffers()…

Oh, if you have a latency in you DSP algorithm it makes sense to ensure a click free bypass. Most of the plugins I examined react on host bypass and I want to do it also…

EDIT: latency corrected bypass is done by iPlug2 itself. This helps a little…

There is nothing special here.
P.S. Since I have been programming in Delphi for a very long time, I have a habit of using Pascal identifiers to designate classes and their fields. :wink:

#if IPLUG_DSP
void TChorusGAS::ProcessBlock(sample** inputs, sample** outputs, int nFrames)
{
  const int nChans = NOutChansConnected();
  fBands->ProcessBlock(inputs, outputs, nFrames, nChans);
}
#endif

Still open and when I am more precise:

I want to couple the host Bypass mode with my own Bypass toggle switch.

bool Bypass;
    ...

    void Effect::ProcessBlock(sample **inputs, sample **outputs, int nFrames)
    {
      const int nChans = NOutChansConnected();
      if (Bypass)
      {
        for (int s = 0; s < nFrames; ++s)
        {
          for (int c = 0; c < nChans; ++c)
          {
            outputs[c][s] = inputs[c][s];
          }
        }
      }
      else
      {
        // Effect implementation
      }
    }

Sorry, you misunderstood me. Most of the VST3 plugins I have installed have a Bypass Button, which is linked to the hosts Bypass mode. Here an example, TDR Nova:

You see in the upper left corner the bypass switch of Studio One and in the lower right corner the bypass switch of TDR Nova. This is what I want. (TDR Nova was written using JUCE, but I hoped this would also possible with iPlug2 without forking my own version…)

Beside this: your bypass solution will have problems if latency > 0 and even the latency corrected version of iPlug2 results in audible clicks, as it jumps from the DSP output to the original signal. In my DSP code I have already a click free version which blends smoothly between these two states and therefore I want iPlug2 to use my version of ProcessBlock() when host bypass is enabled.

For the problem of calling ProcessBlock(), when Bypass is enabled, I’ve created a small patch. I will create a merge request for this later.

Linking the bypass states is a little bit more complicated. My solution:

In my bypass control I define:

void BypassSchalter::OnMouseDown(float x, float y, const IMouseMod& mod)
{
    double new_state = (GetValue() < 0.5 ? 1.0 : 0.0);

    SetValue(new_state);
    SetDirty(true);

#ifdef VST3_API
    IPlugVST3* p = dynamic_cast<IPlugVST3*>(GetDelegate());
    if (p != nullptr) {
        p->BeginInformHostOfParamChange(kBypassParam);
        p->InformHostOfParamChange(kBypassParam, new_state);
        p->EndInformHostOfParamChange(kBypassParam);
        while (p->GetBypassed() != new_state) {
            // Sometimes the param change doesn't work (timimg?). Wait and try it again...
            std::this_thread::sleep_for(std::chrono::milliseconds(5));
            p->BeginInformHostOfParamChange(kBypassParam);
            p->InformHostOfParamChange(kBypassParam, new_state);
            p->EndInformHostOfParamChange(kBypassParam);
        }
    }
#endif
}

I the DSP thread I define an ISender, which sends the current host bypass state to my bypass control:

void Oberton::ProcessBlock(sample** inputs, sample** outputs, int nFrames)
{
    bool bypass = (GetParam(kInternalBypass)->Value() > 0.5);

#ifdef VST3_API
    if (bypass != GetBypassed()) {
        bypass = GetBypassed();
        mBypassData.vals[0] = (bypass ? 1.0 : 0.0);
        mBypassSender.PushData(mBypassData);
    }
#endif

    // DPS code, which respects the bypass variable

}

A little complicated, but I see no other way, Has someone other ideas?

Btw. here the patch for iPlug2, which I mentioned in the previous post:

diff --git a/IPlug/IPlugProcessor.h b/IPlug/IPlugProcessor.h
index afc0e10a4..7779261e7 100644
--- a/IPlug/IPlugProcessor.h
+++ b/IPlug/IPlugProcessor.h
@@ -112,8 +112,11 @@ public:
   /** @return \c true if the plugin is currently bypassed*/
   bool GetBypassed() const { return mBypassed; }

+  /** @return \c true if ProcessBlock should be called even if the plugin is currently bypassed*/
+  bool GetProcessIfBypassed() const { return mProcessIfBypassed; }
+
   /** @return \c true if the plugin is currently rendering off-line */
-  bool GetRenderingOffline() const { return mRenderingOffline; };
+  bool GetRenderingOffline() const { return mRenderingOffline; }

 #pragma mark -
   /** @return The number of samples elapsed since start of project timeline. */
@@ -264,6 +267,7 @@ protected:
   void SetSampleRate(double sampleRate) { mSampleRate = sampleRate; }
   void SetBlockSize(int blockSize);
   void SetBypassed(bool bypassed) { mBypassed = bypassed; }
+  void SetProcessIfBypassed(bool process) { mProcessIfBypassed = process; }
   void SetTimeInfo(const ITimeInfo& timeInfo) { mTimeInfo = timeInfo; }
   void SetRenderingOffline(bool renderingOffline) { mRenderingOffline = renderingOffline; }
   const WDL_String& GetChannelLabel(ERoute direction, int idx) { return mChannelData[direction].Get(idx)->mLabel; }
@@ -287,6 +291,8 @@ private:
   int mTailSize = 0;
   /** \c true if the plug-in is bypassed */
   bool mBypassed = false;
+  /** \c true if the plug-in should call ProcessBlock even if bypassed */
+  bool mProcessIfBypassed = false;
   /** \c true if the plug-in is rendering off-line*/
   bool mRenderingOffline = false;
   /** A list of IOConfig structures populated by ParseChannelIOStr in the IPlugProcessor constructor */
diff --git a/IPlug/VST3/IPlugVST3_ProcessorBase.cpp b/IPlug/VST3/IPlugVST3_ProcessorBase.cpp
index 56267d38a..dbd3f99ec 100644
--- a/IPlug/VST3/IPlugVST3_ProcessorBase.cpp
+++ b/IPlug/VST3/IPlugVST3_ProcessorBase.cpp
@@ -398,7 +398,7 @@ void IPlugVST3ProcessorBase::ProcessAudio(ProcessData& data, ProcessSetup& setup
       chanOffset += busChannels;
     }

-    if (GetBypassed())
+    if (GetBypassed() && !GetProcessIfBypassed())
     {
       if (sampleSize == kSample32)
         PassThroughBuffers(0.f, data.numSamples); // single precision

Patching is the best and most reliable solution. Maybe some does a CR?

IPlugProcessor.h:

bool bcanhandlebypass=false;
SetCanHandleByPass(bool state){bcanhandlebypass = _state;}
GetCanHandleByPass(void){return bcanhandlebypass;}

each plugin api, process:

if (GetBypassed() && !GetCanHandleByPass())
PassThroughBuffers(…)
else
ProcessBuffers(…)

I will create a PR next week…

After reading the VST3 documentation I realized, that the problem is in iPlug2 itself, as it defines it’s own bypass parameter. I patched the VST3 part, added a new IParam flag IParam::kFlagBypass and now I can use a “normal” IParam as bypass. This also switches off the GetBypassed() logic of iPlug2, so all is fine for me.

diff --git a/IPlug/IPlugParameter.h b/IPlug/IPlugParameter.h
index 1ed0c9205..b8141a7cc 100644
--- a/IPlug/IPlugParameter.h
+++ b/IPlug/IPlugParameter.h
@@ -55,6 +55,8 @@ public:
     kFlagSignDisplay      = 0x8,
     /** Indicates that the parameter may influence the state of other parameters */
     kFlagMeta             = 0x10,
+    /** Indicates that the parameter may act as host bypass parameter */
+    kFlagBypass           = 0x20,
   };

   /** DisplayFunc allows custom parameter display functions, defined by a lambda matching this signature */
@@ -470,7 +472,10 @@ public:
   /** @return \c true If the parameter is flagged as a "meta" parameter, e.g. one that could modify other parameters */
   bool GetMeta() const { return mFlags & kFlagMeta; }

-  /** Get a JSON description of the parameter.
+  /** @return \c true If the parameter may act as host bypass parameter  */
+  bool GetHandlesBypass() const { return mFlags & kFlagBypass; }
+
+  /** Get a JSON description of the parameter.
    * @param json WDL_String to fill with the JSON
    * @param idx Index of the parameter, to place in the JSON */
   void GetJSON(WDL_String& json, int idx) const;
diff --git a/IPlug/VST3/IPlugVST3_ControllerBase.h b/IPlug/VST3/IPlugVST3_ControllerBase.h
index 90ded4eb8..d4362439e 100644
--- a/IPlug/VST3/IPlugVST3_ControllerBase.h
+++ b/IPlug/VST3/IPlugVST3_ControllerBase.h
@@ -76,10 +76,22 @@ public:
     }
     else
     #endif
-      unitInfo.programListId = Steinberg::Vst::kNoProgramListId;
-
-    if (!plugIsInstrument)
-      mParameters.addParameter(mBypassParameter = new IPlugVST3BypassParameter());
+    unitInfo.programListId = Steinberg::Vst::kNoProgramListId;
+
+    if (!plugIsInstrument) {
+      bool existsBypassParameter = false;
+      for (int i = 0; i < pPlug->NParams() && !existsBypassParameter; i++)
+      {
+        if (pPlug->GetParam(i)->GetHandlesBypass())
+        {
+          existsBypassParameter = true;
+        }
+      }
+
+      if (!existsBypassParameter) {
+        mParameters.addParameter(mBypassParameter = new IPlugVST3BypassParameter());
+      }
+    }

     pEditController->addUnit(new Steinberg::Vst::Unit(unitInfo));

diff --git a/IPlug/VST3/IPlugVST3_Parameter.h b/IPlug/VST3/IPlugVST3_Parameter.h
index a7158b34b..71b312263 100644
--- a/IPlug/VST3/IPlugVST3_Parameter.h
+++ b/IPlug/VST3/IPlugVST3_Parameter.h
@@ -39,6 +39,7 @@ public:

     if (pParam->GetCanAutomate()) flags |= Steinberg::Vst::ParameterInfo::kCanAutomate;
     if (pParam->Type() == IParam::kTypeEnum) flags |= Steinberg::Vst::ParameterInfo::kIsList;
+    if (pParam->GetHandlesBypass()) flags |= Steinberg::Vst::ParameterInfo::kIsBypass;

     info.defaultNormalizedValue = valueNormalized = pParam->ToNormalized(pParam->GetDefault());
     info.flags = flags;

This is only for VST3…

To use, just write:

GetParam(kBypass)->InitBool("Bypass", false, "", IParam::kFlagBypass);

1 Like