Possible bug in VST3 hosting code (tail time)


#1

Hi,

I was testing the awesome pluginval by @dave96,
and was seeing my Superchord plug-in’s VST3 tail time being reported as 0, where it actually sends ‘infinite’. I do this because the plug-in can self-oscillate, like a generator, and therefore never stop making sound :scream:

I think I stumbled upon a Juce bug in the VST3 host code: the plug-in returns Steinberg::Vst::kInfiniteTail (aka UINT32_MAX=0xFFFFFFFF), but the latency is reported as 0 in pluginval.

Digging into Juce, I’ve narrowed it down to this piece of code in juce_audio_processors\format_types\juce_VST3PluginFormat.cpp line 2299:

return jlimit (0, 0x7fffffff, (int) processor->getTailSamples()) / sampleRate;

processor->getTailSamples() returns a unsigned 32bit int which is cast to an int, so kInfiniteTail=0xFFFFFFFF becomes -1, which is limited to 0 by jlimit().
Meaning my audio might get cut-off abruptly if the host decides to enforce the tail spec …
The plug-in itself is built with our own in-house framework.

Best,
Lorcan


#2

Yeah this needs fixing.

Unfortunately, getTailSamples used to have a bit of a special meaning in JUCE. If it is zero it means that the tail size is infinity or zero. To determine which, you would also need to call AudioProcessor::silenceInProducesSilenceOut - which we unfortunately deprecated a while back.

Changing the behaviour so that AudioProcessor::getTailSamples returns std::numeric_limits<double>::infinity on for infinite tail size plug-ins, could instantly cause crashes in a lot of JUCE hosts which allocate a buffer depending on the return value of this method. I’m not quite sure how to fix this one yet.

JUCE host people (@drowaudio, @Anthony_Nicholls): how do you deal with the return value of AudioProcessor::getTailSamples? Any good ideas?


#3

I’ve been speaking to @tpoole about this. We use getTailLength to determine extra time to be added to renders to avoid clipping the end off. If this suddenly could return inf, I’m fairly sure this would crash or render forever.

The other main use case would be an optimisation to not play clips when the transport isn’t over them. The tail length would need to be used determine when we can stop processing plugins and hence save CPU. This would save a lot in the grand scheme of things and almost certainly something we’ll add at some point.

I’m much more in favour of keeping the existing behaviour and introducing a new concept of a “generator”, similar to the old silenceInProducesSilenceOut. We could then check this to see if the plugin should be treated differently and distinct from the tail length (which would still return 0 as inf doesn’t really make sense).

I did suggest making a TailLength struct that’s convertible to double. At least this way it would behave similarly to before and you could add an extra flag to indicate it’s a generator. I’m also thinking if there’s anything else that could be added in time which would reinforce adding a struct as that’s less breaking changes over time.


#4

I’m not sure of the best way to deal with this to be honest. However my guess is if a host was passed a flag to say that there is an infinite tail, it will never stop processing audio when paused, but when exporting tracks it will treat the tail as if it is 0? Therefore what I would probably want is for getTailSamples() to return 0 but for there to be another function, such as hasInfiniteTail() to indicate this special behaviour. I can’t remember, why was silenceInProducesSilenceOut() dropped?

This isn’t particularly well thought out, so don’t take it as gospel. It might be interesting to test any hosts that export with a tail what they do when an infinite flag is passed, but my guess is they will treat it as if the tail size is 0 rather than some arbitrary length. Then the only other thing I can think of for a host is deciding how long they need to continue passing audio when nothing is playing when a plugin has passed an infinite value, either they will continue passing audio indefinitely or in some cases they might still treat it as though it has a tail length of 0. For example imagine an audio editor like WaveLab it might want to let reverbs ring out when paused but it probably won’t be fussed about continuing to pass audio for something that self oscillates.


#5

@fabian Just to be clear, I was talking about how Juce hosts interpret the VST3 spec, not Juce plug-ins (my plug-in is not using Juce / AudioProcessor for that matter).

Although this is not always the case, the VST3 spec is quite clear about this particular topic -
this is from Steinberg::Vst::IAudioProcessor::getTailSamples():

Gets tail size in samples.

For example, if the Plug-in is a Reverb Plug-in and it knows that the maximum length of the Reverb is 2sec, then it has to return in getTailSamples() (in VST2 it was getGetTailSize ()): 2*sampleRate. This information could be used by host for offline processing, process optimization and downmix (avoiding signal cut (clicks)). It should return:

  • kNoTail when no tail
  • x * sampleRate when x Sec tail.
  • kInfiniteTail when infinite tail.

There is also this in the docs:

Q: How to make sure that a plug-in is always processed?

If your Plug-in always generates sound without need of any audio input, you can add the category “Generator” as subCategories (for example use kFxGenerator) or you can return kInfiniteTail in the function IAudioProcessor::getTailSamples

On the host to plug-in side there is also the notion of silence flag, but here its interpretation is left to the plug-in

The silence flag is set when every sample of the according buffer has the value ‘0’. It is intended to be used as help for optimizations allowing a Plug-in to reduce processing activities. But even if this flag is set for a channel, the channel buffers must still point to valid memory! This flag is optional. A host is free to support it or not.

If the host decides to use this information, it should stick to the standard as per spec, otherwise this creates a chicken and egg situation which quickly becomes hard to follow !

Cheers,
Lorcan


#6

Yes I understand this. But the AudioProcessor class is used both by hosts and by plug-in code. So if we suddenly interpret the VST3 spec correctly, all the hosts built with JUCE will suddenly get the correct number of tail samples (infinite). They will likely crash as all these hosts weren’t tested with a return value like this (because faulty JUCE previously never returned infinite). Like Dave points out: his host was not programmed with this in mind so would crash if he upgrades to the newest JUCE. We are just thinking of ways to avoid this, i.e. to have some kind of compile-time error when people update JUCE so that they are forced to read a comment about the new correct behaviour and that they need to make sure that there hosts can detail with in infinite value.


#7

Ah ok, I didn’t get that AudioProcessor is also used in the plug-in code, sorry about that!


#8

See here: