Bypassing a hosted plugin

I made my (related) changes to the AudioProcessorGraph…

Rail

1 Like

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?

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…

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…

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.

1 Like

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?

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:

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;
         }
    }
2 Likes

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

1 Like

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.

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?

1 Like

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

1 Like

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

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.

@yfede There is still a problem with the processBlock, processBlockBypassed. Consider my job of implementing what the JUCE’s VST/VST3/AU AudioProcessor (which wraps a VST/VST3/AU) should do. Old JUCE hosts will call either processBlock or processBlockBypassed - as they don’t know anything about getBypassParameter. How should I implement these methods? processBlockBypassed still needs to bypass the effect - so the VST/VST3/AU wrapper implementation needs to bypass the VST/VST3/AU when this method is called. But what happens when processBlock is then called? Should the wrapper un-bypass the VST/VST3/AU now?

My current idea is that processBlockBypassed will always keep the VST/VST3/AU in a bypassed state (new hosts shouldn’t be calling this anyway). That means even if the user tries to disable the bypass, as the host keeps calling processBlockBypassed, JUCE just immediately enables it again. If you switch from a processBlockBypassed call to a processBlock (i.e. the last processBlock call was a processBlockBypassed) then the VST/VST3/AU will be un-bypassed but only once. After that, the plug-in will always follow whatever the parameter bypass dictates. This way, any new host, which is only ever calling processBlock will work as expected as the processBlock callback is completely controlled by the bypass parameter. Older hosts will still be able to bypass and unbypass the VST/VST3/AU and the VST/VST3/AU’s editor’s bypass button will work if the old host only ever calls processBlock.

1 Like

I’m sorry, I cannot really follow you in hosting territory because I’m only writing plug-ins.
I known very little about how the hosting code is interfaced with AudioProcessors. :sweat:

The only thing that I can recommend, if you intend to implement that logic, is to do it outside of the AudioProcessor class because it is only specific to the hosting side of things, and should therefore go among other hosting-only code. Perhaps in the AudioPluginInstance?

No I am implementing an AudioProcessor (just like you when you write a plug-in). An AudioProcessor which wraps a VST/VST3/AU and may be called by old JUCE code which does not know about getBypassParameter.

If we are going to consider implementing this via a parameter that is marked as a bypass parameter then I have suggested this before (twice)…

However keep in mind that not all wrappers implement the bypassing mechanism as a parameter (VST2 and AU for example).

I think reporting a bypass parameter to the host is a good solution to deal with it from the plugin side. However on the host side there will be no way to know if the parameter is a bypass parameter or not for VST2 and AU (I think that includes AUv3) - so in other words it would only work for VST3 forcing any host that wants to host VST2 and AU to fall back to calling processBlockBypassed().

You could add a special parameter for VST2 and AU that deals with this on the hosting side but then you would be reporting an extra parameter that isn’t really there, imagine showing a list of available parameters to a user, which bypass parameter should they automate?

So I think that from the host side there needs to be a set/isBypassed and a bypassChanged callback, however to reduce confusion IMO it should be in the AudioPluginInstance class.

Keeping processBlockBypassed in the host could continue to work as it always did (no need to bypass or un-bypass) this way existing hosts work as they always did, no change in functionality. Future hosts should probably rely on setBypassed() however if they wanted to override that behaviour they could. For example Cubase has both a bypass and a deactivate, so a host could release a plugins resources and use the processBlockBypassed to pass audio through without it actually having to call into the plugin, but for the normal bypass procedure it would call setBypassed() and continue calling processBlock.

Hopefully that makes some sense.

EDIT: Also thanks for taking the time to look into this with so much consideration.

1 Like

Just to add the other thing to consider as I’ve mentioned before (but might have been missed), plugins are not obliged to report a bypass parameter or to implement the setBypass callbacks in the wrappers (even though the JUCE wrappers do) therefore there has to be a backup, the host code should determine if the plugin implements the setBypass and if it doesn’t AND setBypassed (true) has been called it should forward a call to processBlock onto processBlockBypassed.

1 Like

Sorry, it might be my ignorance of the hosting code of JUCE, but I was under the impression that the AudioProcessor that’s created for hosting a (possibly non-JUCE) plug-in, is only an “implementation detail” of hosting, and thus it is always compiled together with the hosting code that ends up using it.

If that is the case, then the hosting code and the AudioProcessor that it uses for wrapping plug-ins to be hosted should and could be updated together, not having a case in which one interacts with a different version of the other.

That means that, once the getBypassParameter() is introduced, the AudioProcessor used for wrapping hosted plug-ins should also be updated to always override that callback and returning an internal parameter that only that specific "hosting AudioProcessor" should use for keeping track of whether the hosted plug-in is in bypass state or not.

Then, when the host wants to change the bypass state for that plug-in, it acts on that parameter, and the change of its value should call whatever format-specific code is necessary to put in bypass mode the wrapped plug-in.

Conversely, when the wrapped plug-in signals that it wants to change its bypass state, the hosting AudioProcessor should reflect that by keeping that information in its bypass AudioProcessorParameter.

Am I completely off-track?