Bypassing a hosted plugin


#1

Is there a simple way to indicate to a plugin that it should bypass (but still respect the reported latency)?

As a plugin this is indicated to you via the processBlockBypassed() call which can be overridden. Unfortunately, unless I’m sadly mistaken, it appears as a host there is no cross-format way to tell a plugin to bypass. Calling processBlockBypassed() just passes the audio straight through will no delay, and on top of that a good plugin should ideally interpolate a bypass parameter anyway. Some formats make you report a bypass parameter but if there is a bypass parameter this is not indicated in any way that I can see to a JUCE host.

I could as a host do the work myself by checking the latency reported and implementing a delay. However…

  1. Some plugins have a visible bypass button that is synchronized with the bypass state reported to it by the host (except VST2 where this isn’t entirely possible because the plugin can’t then tell the host to change the bypass state).

  2. I’m writing a command line host application and I want to test that the reported latency is being correctly implemented by bypassing the plugin, passing an impulse signal and checking the correct amount of delay was applied.

I would be happy to make some suggestions to the code base however I’m not sure how best to approach it. IMO I’ve always thought the processBlock and processBlockBypassed was a slightly odd decision, a simple setBypassed() in the AudioProcessor would have sufficed, and certainly would have made more sense in this particular case.

I guess what would be needed is a set/get bypass and the possibility to receive a callback when the state changes?


Standalone app and AudioMidiSettings and AudioDeviceManager
#2

You have to add your own delay… My tangential thread:

Cheers,

Rail


#3

Thanks Rail.

Unfortunately as I say above there I don’t think that’s good enough in my case. First is that some plugins will use the bypass state to make it clear in the plugin that it’s bypassed and to keep any bypass parameter in the GUI in sync. Also in some cases a plugin will actually do some processing while bypassed, for example if you want equal loudness/gain matched bypass behaviour, iZotope’s Ozone is a good example of this…

Note in the video when the bypass is engaged from the GUI the bypass button implemented by the host is also engaged, this works the other way too, click the host bypass and the plugin bypass button will be engaged. Then latter in the video the use of the equal loudness/gain match button changes the behaviour of the bypass.

However ignoring the above in my case I am making a command line host for testing plugins (like aual), and I want to test that the delay is correctly implemented in the plugin when the plugin is bypassed.

I could probably do it by calling getPlatformSpecificData() and implementing something for each format, but as it’s something shared by all formats I think it should be in the JUCE library.


#4

I’ve found that not all plug-ins use the name “bypass” as a Master Bypass… so there was no single solution for a host just to look for a parameter named “Bypass” or “Master Bypass”… on some it would work and with others it would bypass EQ bands, etc…

If there were some conformity it would be great, but I didn’t find that to be the case.

I ended up just having my own plug-in bypass button since there was no way to guarantee the behavior otherwise.

Cheers,

Rail


#5

I believe for AAX and VST3 the parameter used for bypass is reported to the host so it knows without having to call it something specific, and for AU and VST2 there are calls made to the plugin from the host that tell it the bypass state. Additionally in AU you can set the bypass state from the plugin on the host. A few hosts don’t implement these features, oddly Logic is one (although I think AULab does!), as is Reaper I think. I can’t recall but Ableton may not have a bypass button in the plugin window but it might be accessible via automation.

After having a quick look at the wrappers I think this is what each of the wrappers uses to communicate with the plugin the bypass state.

  • AUv2 - kAudioUnitProperty_BypassEffect
  • AUv3 - shouldBypassEffect
  • RTAS - DefineMasterBypassControlIndex
  • VST2 - plugInOpcodeSetBypass
  • VST3 - Vst::ParameterInfo::kIsBypass
  • AAX - cDefaultMasterBypassID

#6

OK I’ve been having a little play. What I have done so far is to add two pure virtual methods to the AudioPluginInstance class

virtual void setBypassed (bool shouldBypass) = 0;
virtual bool isBypassed() const = 0;

I chose to put them here to prevent any confusion that might be caused by adding them to the AudioProcessor class.

In each of the formats, where possible, I’ve tried to communicate with the plugin to tell it to do the bypassing. However I’ve also including some private member variables, bypassed and canPluginBypass. These keep track of not only when setBypassed() is called, but also when the plugin tries to change the bypass state, and if the plugin has indicated that it is able to perform the bypassing.

In processBlock() I’ve added a check to say if the plugin can not perform the bypass, but the plugin should be bypassed, then call processBlockBypassed() and return.

As far as I can tell LADSPA doesn’t have any bypass mechanism, so this relies entirely on the private member variable bypassed.

The next step will be to add an AudioPluginInstance::Listener class with a callback method to indicate when the bypass state has changed so hosts can update gui components for example.

It might also be nice to report to the host if the plugin can perform the bypass, maybe this could be added to the plugin description.

If the JUCE team are interested, what would be the best way to pass these changes to you for review? fork the JUCE repo and generate a pull-request on github? Does the above sound like something you would want to consider adding? Any suggestions on changes of direction based on what I’ve said so far?


#7

I made my (related) changes to the AudioProcessorGraph…

Rail


#8

Pull requests are the easiest way to communicate source code changes with the JUCE team.

I think we might be able to achieve the same effect without making any changes to the API; when processBlockBypassed is called we can set the bypass parameter, then unset it again once processBlock is called. Can you see any problems with this approach?


#9

That’s what I do in my plugins as well, having a bool “wasBypassed”. I use that also to fade between the bypassed and processed signal.
It’s just one step more to the mercy of the host, and with logics funny “improvements” to not call it, if it doesn’t feel like, is a bit worrying…


#10

Maybe I am viewing this from the wrong point of view, but remember that not all AudioProcessors are plug-ins, so how would an AudioProcessor that’s being used internally (into a graph, for example) deal with that? In theory, such an AudioProcessor hasn’t even (and doesn’t need to) know about a master bypass parameter for the whole plug-in.
Perhaps I’m missing something…


#11

I did think about that, but I don’t think it’s as intuitive as it may first appear. If the host was always the one in charge of the bypass status i would agree it makes sense, but…

Imagine you have a host that you want your bypass button to be linked with a plugins bypass status, for example…

So the host calls processBlockBypassed which tells the plugin to bypass and continues to process as normal. Now a user interacts with the plugin and disables the bypass. What happens when the host continues to call processBlockBypassed does it bypass the plugin again? that would feel odd to a user, as it would essentially feel like the plugin bypass didn’t work. Does it continue to call processBlockBypassed despite it actually not bypassing? how does the host update it’s own status? maybe there would be a callback, I guess in this case you could react to the callback and start calling processBlock again but why? why be made to do work in which you are forced to switch between the two when you don’t have to? setBypass / isBypassed / plus a callback to update your GUI if needs be, seems more intuitive to me.

In other words I think it could be done that way but it seems a little hacky to me.


#12

Hmmm… adding this feature in a non-breaking way that makes sense is actually quite hard. I’ve got @t0m 's suggestion sitting on a branch but @Anthony_Nicholls correctly points out that this would result in confusing behaviour. But then we also have processBlockBypassed vs. processBlock which all still needs to make sense no matter if you are a plug-in or a host. We also need to think of the use case when you are implementing an editor for your plug-in and want to bypass your own plug-in (when the user clicks on a bypass button in your editor).

The only way I can think of making this all work is the following:

  1. we add setBypass/isBypassed to the AudioProcessor class. These are not callbacks but methods that both a host and a plug-in can call. As a host you can call these methods to bypass the plug-in or check if the user has bypassed the plug-in (to update your host’s UI, for example). As a plug-in you can also call setBypass to bypass your own plug-in (for example from your plug-in’s editor).
  2. We add a bypassChanged callback to AudioProcessorListener
  3. processBlock/processBlockBypassed: to remain backward compatible, your plug-in will still receive processBlockBypassed calls when the plug-in is bypassed. To ensure this even if you are hosting an internal AudioProcessor (i.e. not a plug-in: what @yfede points out), any code hosting an AudioProcessor will always need to query AudioProcessor::isBypassed and then call either processBlock/processsBlockBypassed depending on the value. This makes hosting AudioProcessors more complicated but I don’t see a way around this.
  4. What happens if you, as a host of a VST/VST3/AU, call processsBlockBypassed even if isBypassed is currently false (or vice versa: call processsBlock when isBypassed is true)? First of all, in the future, hosts should not do this, but for the sake of backward compatibility, I think that processsBlockBypassed always takes precedence regardless of what isBypassed actually is currently. If the plug-in is currently not bypassed then a call to processsBlockBypassed will bypass the plug-in until processBlock is called again. Getting this right (without any race conditions) in JUCE’s plug-in code will be a bit of a challenge but I think it’s doable.

What do you guys think? I’m I missing anything?


#13

Arggghh thinking about this a bit more: Most JUCE hosts will only ever call processBlock. Currently, if you click bypass in VST/VST3/AU’s editor, it will still bypass the plug-in (even though the host is only ever calling processBlock). Point 4) above would break this behaviour.

Maybe we need to change the meaning of processBlock to do whatever the current bypass setting of the AudioProcessor is and processBlockBypassed will always bypass the plug-in (when called as a host). As a plug-in you will still get processBlockBypassed when you are bypassed for now.

This is making my head hurt… :frowning:


#14

Uhm, point 1 and 2 above seem to essentially replicate the behavior of what a parameter does, so, what do you think about the following:

Add a virtual getBypassParameter() method to AudioProcessor that returns a pointer to an AudioProcessorParameter. To that, the plug-in can return either nullptr or a valid pointer:

  1. If the plug-in returns a null pointer (default behavior to retain backward compatibiliy) it means that it lets the wrapper/host handle the bypass. In this case the plug-in will have both its processBlock() and processBlockBypassed() methods called by the host/wrapper, which in turn is the only holder of the “bypassed” information.

  2. If the plug-in returns a non-null pointer for the above callback, it means that it intends to handle the bypass with the returned AudioProcessorParameter that it created for that. That way, the plug-in can also see the parameter and act upon it, for example attaching a button in its GUI.
    By doing so, the plug-in also declares that it will handle the bypass condition in its own way inside the processBlock(), so the hosting/wrapper code will only call that, and not the processBlockBypassed() directly.
    Of course, the plug-in can still, in its own processBlock(), query the state of the bypass parameter and, if it indicates a bypassed state, it can internally forward the call to processBlockBypassed(), like

    MyAudioProcessor::processBlock(...)
    {
         if (bypassParameter == true)
         {
            processBlockBypassed (...)
            return;
         }
    }

#15

Not to break anything I’d simply provide simple methods to query (set/get) plug-in bypass state.

Also it is worth testing some hosts to validate it works properly across them. as always some “might” not interact nicely with you.
An example is transport record state isn’t being reported by Ableton Live for example. or Logic X not implementing bypass (while MOTU DP does).


#16

Thanks @yfede, sounds like a good idea. Let me explore this a bit more…

@ttg

It’s not that simple as these methods must live in the AudioProcessor class which then always begs the question of what these methods mean to an AudioProcessor which is implementing a plug-in and/or an internal AudioProcessor living in an AudioProcessorGraph etc.


#17

That’s correct.
But keep in mind that still the processBlock and processBlockBypassed will be preserved. only difference is you have proper getter/setter for the host bypass parameter.

@yfede feedback is great. the only concern is for backward compatibility. for example, we have redundant bypass parameter not linked to the host bypass.
For future products having a single bypass is great,

My only concern is with backward compatibility. for example AAX wrapper makes “Master Bypass” in addition to our internal bypass. how would we be able to benefit from a newer implementation if we won’t be able to keep and handle both?


#18

Yes but I’ve mentioned the issues with those methods in my post above.


#19

@yfede Are there situations (plug-in formats, certain hosts) where you don’t want the bypass to be a “real” parameter? I could imagine that not everyone might want that.


#20

Uhm, none that I can think of honestly, but perhaps it’s better to wait if someone else can prove the contrary.

Anyway, as a mitigation, it can be made extra easy for someone who doesn’t use AudioProcessorParameters at all in its plug-in, to find if it’s bypassed or not by the host, by adding a utility class like this:

/** If you want to find out whether the host has put your plug-in in bypassed state,
add one of these as a member of your AudioProcessor and return it in 
AudioProcessor::getBypassParameter().
*/
class DefaultBypassParameter : public AudioProcessorParameter
{
   ...
   /** Returns true if the host has put the plug-in in bypass state */ 
   bool isBypassed () const { return getValue () > 0.5f; }
   ... 
}

Edit: I called it DefaultBypassParameter, but it could either be Standard-, Basic-, Common-, etc.