VST Plug-In: How to implement a “lookahead” buffer?

DSP, Plugin and Host development discussion.
RELATED
PRODUCTS

Post

aciddose wrote:Tail size is the signal you generate after the input is zero.

For example in an envelope generator, tail size would be the release time.

This is of major consequence in hosts that disable processing during silence. In these hosts, the processing will not stop until the input and output has been zero for at least (latency + tail size).

For example take a delay effect. Both the input and output might be zero. There may be a signal stored in the delay buffer however and up until the total delay time has passed you can't be certain it won't make its way to the output.

Each time a signal is detected at either input or output the silence counter is reset.
MadBrain wrote:I think getTailSize() is really just to tell the approximate length of a reverb or delay time, so I'd be really surprised if it gave any sorts of exact results. After all, real time audio never really "ends", so in the normal context where you'd use a VST, your output is never really truncated, it just goes on "forever".
So, if we implement a “lookahead” buffer that delays the audio by N samples, then the correct Tail Size would be exactly N. That's because if the last non-silent sample is sent to the plug-in at time T, then another N (silent) samples need to be sent to the plug-in, in order to "flush" the pending outputs (it takes N samples for the last non-silent sample to propagate trough the FIFO buffer). Thus, the last non-silent sample will be returned from the plug-in at time T+N. Or in other words: The plug-in's response will not have "died to zero" (as the spec says) until another N samples of silence have been processed as input. That's because until then we still have non-silent samples in the buffer. Thus, the tail size must be N.

Still, this info seems kind of redundant! That's because the host application can deduce the same info only from the Initial Delay :neutral:

I only see two possibilities here:
  1. InitialDelay(X) tells the host to discard the first X samples of the plug-in's output, but nothing else. Subsidiary, TailSize(Y) tells the host to feed additional Y samples of silence to the plug-in in at the very end, in order to flush the pending outputs.
  2. InitialDelay(Z) tells the host to discard the first Z samples of the plug-in's output and to feed additional Z samples of silence to the plug-in in at the very end, in order to flush the pending outputs. TailSize is not needed or considered at all.
If case (1) did apply, then we would clearly have to expect truncation (i.e. pending outputs are not flushed at the end), if we implement only setInitialDely(), but not getTailSize(). But from "real world" testing I can say that this is not the case! Those applications, which consider the delay, work perfectly fine without getTailSize(). And for those apps that seem to ignore the delay entirely, implementing getTailSize() doesn't help at all. Consequently, this all looks like in practice case (2) applies. But in that case, getTailSize() is just pointless and misleading...

Image

MadBrain wrote:VSTs are really designed for real time processing, not processing on fixed audio, so if I were you I'd look at other plugin formats.
Well, even if VST originally was designed primarily with real-time processing in mind, it apparently is the most widely supported "plug-in" type in Wave Editors and similar applications - at least on the Windows platform. Most applications of this kind either support VST or no plug-ins at all. Some have their own non-standard plug-in types, yes. But having to write a separate plug-in for each application is kind of unrewarding! Consequently, we have to work with VST for this purpose in practice. That's simply due to the lack of suitable alternatives! And with some "well-behaved" Editors, like Acoustica, WaveLab, Audition, GoldWave and REAPER this seems to work fine. If only there weren't those other editors...

:roll:

MadBrain wrote:For instance, Audacity supports Ladspa plugins and Nyquist plugins (using some sort of LISP) and tbh I have no idea if these are better for non-realtime audio, but it might be a good idea to check.
Maybe I will have a look too. But if they are Audacity-only, their usefulness is limited. Do you know how difficult it will be call native C++ code from "Ladspa" or "Nyquist" plug-ins? Note that my code is written in the form of a general-purpose C++ library. I have written bindings for SoX and VST...

MadBrain wrote:I think VST itself also has a specific mode for non-realtime audio but I don't think it's well supported at all.
Any more details or pointers on this?

I cannot find anything about it in the specs:
http://ygrabit.steinberg.de/~ygrabit/pu ... fectX.html

[EDIT] Maybe you are referring to the wantAsyncOperation() function? Is there an example how to use it? [/EDIT]

Image

Post

Yes, getTailSize() is supposed to tell the host how long to process after input goes to silence .. but in practice it's pretty much "universally unsupported" as hosts that actually do silence-bypass just detect said silence instead.

Post

Tail doesn't need to factor in latency because the host already has that information.

Tail is absolutely required for silence-bypass to work correctly as otherwise the effect may be bypassed while signal is still contained in internal buffers.

Regarding the fact "offline processing" is mostly useless and unsupported these days; This is because it was never useful. Once the real-time processing and multiple instances were supported it became almost entirely obsolete.

While there are some issues, the majority of cases will work out fine by creating a new instance, configuring the instance and processing your clip, then destroying the instance.

There is no need to have the plugin do this internally.
Free plug-ins for Windows, MacOS and Linux. Xhip Synthesizer v8.0 and Xhip Effects Bundle v6.7.
The coder's credo: We believe our work is neither clever nor difficult; it is done because we thought it would be easy.
Work less; get more done.

Post

KarLoff wrote:
MadBrain wrote:I think VST itself also has a specific mode for non-realtime audio but I don't think it's well supported at all.
Any more details or pointers on this?

I cannot find anything about it in the specs:
http://ygrabit.steinberg.de/~ygrabit/pu ... fectX.html

[EDIT] Maybe you are referring to the wantAsyncOperation() function? Is there an example how to use it? [/EDIT]
About wantAsyncOperation(): At a first glance this looks very promising. If I understand this correctly, we would call wantAsyncOperation() inside of process() in order to start an "async" operation. Then we would return immediately, i.e. without returning any output samples to the host!. The host would then continuously poll reportCurrentPosition() to check how much output is actually available. And, finally, when the output has become available, the host would call reportDestinationBuffer() to fetch that data. Consequently, we would have a method to read more input samples without having to return all the input immediately. This way, the "lookahead" buffer could be realized without introducing a delay.

Unfortunately, the specification says that the plug-in needs to call canHostDo("asyncProcessing") to check whether to particular host application supports "async" operations. The specs don't say when I need to do this. In the constructor? In open(), in resume() or even in process()? Well, I tried them all. And it turns out exactly none of all the audio editors that I have does support this. Bummer!

:cry:

mystran wrote:Yes, getTailSize() is supposed to tell the host how long to process after input goes to silence .. but in practice it's pretty much "universally unsupported" as hosts that actually do silence-bypass just detect said silence instead.
So, to summarize:

The only VST functions that really can be relied on to work in "real world" VST host applications are processReplace() and, more or less, setInitialDelay(). Functions like getTailSize() and wantAsyncOperation() appear to exist merely for theoretical purposes, but have never been implemented in the "real world" VST host applications. That is kind of disillusioning, but something a VST developer has to know.


BTW: Is there a comprehensive list of which VST functions a host must support and which ones are optional? Or does this all depend on the "goodwill" of the individual VST host developer? There are canHostDo()'s for certain functions, like wantAsyncOperation(). But not for others, like setInitialDelay(). So I would assume that setInitialDelay() is a required function. However, it is not supported by some "real world" applications...

Post

KarLoff wrote:The specs don't say when I need to do this. In the constructor? In open(), in resume() or even in process()? Well, I tried them all. And it turns out exactly none of all the audio editors that I have does support this. Bummer!
You can't make any host callback in the constructor. This is because you haven't returned the aeffect pointer and the host hasn't assigned the pointers or other data it needs to service the callback.

In general, making any host callback or touching the audiomaster pointer at all outside resume(), process() and so on should be considered illegal.
KarLoff wrote:The only VST functions that really can be relied on to work in "real world" VST host applications are processReplace() and, more or less, setInitialDelay(). Functions like getTailSize() and wantAsyncOperation() appear to exist merely for theoretical purposes, but have never been implemented in the "real world" VST host applications. That is kind of disillusioning, but something a VST developer has to know.
Complete nonsense. For explaination of tail size see my previous post. This is used in numerous hosts.

Async was intended to support plugins which acted as an interface to hardware and were used in the past. This sort of thing proved to be unreliable however and I'm surprised VST2.4 didn't deprecate this function.

Your theory about "lookahead without latency" is amusing at best. So you've invented a time-machine then?
KarLoff wrote:BTW: Is there a comprehensive list of which VST functions a host must support and which ones are optional?
VST was never standardized in that way. The VST-SDK was released to allow developers to write plugins, not hosts.

As far as how you can answer questions about the functionality in the SDK, if it doesn't explain in the SDK itself or you question what it says (in a lot of cases the SDK lies); You have two options. One is to check what various versions of Cubase do. Second is you can ask on the mailing list, although note that VST2 is completely discontinued and unsupported at this point.

It was previously part of the VST3-SDK, but that has been removed.

Also, one note I have for you about when you're talking about these functions. You seem to be referring to functions from the AudioEffectX wrapper class. You ought to be looking at the dispatcher opcodes instead, as AudioEffect and ...X are just wrappers for this.

You are supposed to actually write your own wrapper, but they included these in the SDK because they must have known most people would be too lazy to understand enough about the dispatcher and so on to do so.
Free plug-ins for Windows, MacOS and Linux. Xhip Synthesizer v8.0 and Xhip Effects Bundle v6.7.
The coder's credo: We believe our work is neither clever nor difficult; it is done because we thought it would be easy.
Work less; get more done.

Post

aciddose wrote:
KarLoff wrote:The specs don't say when I need to do this. In the constructor? In open(), in resume() or even in process()? Well, I tried them all. And it turns out exactly none of all the audio editors that I have does support this. Bummer!
You can't make any host callback in the constructor. This is because you haven't returned the aeffect pointer and the host hasn't assigned the pointers or other data it needs to service the callback.

In general, making any host callback or touching the audiomaster pointer at all outside resume(), process() and so on should be considered illegal.
Okay, that makes sense :idea:

aciddose wrote:
KarLoff wrote:The only VST functions that really can be relied on to work in "real world" VST host applications are processReplace() and, more or less, setInitialDelay(). Functions like getTailSize() and wantAsyncOperation() appear to exist merely for theoretical purposes, but have never been implemented in the "real world" VST host applications. That is kind of disillusioning, but something a VST developer has to know.
Complete nonsense. For explaination of tail size see my previous post. This is used in numerous hosts.
Well, I can only speak from my experience with "real world" audio editors. And I have tested quite a number of those recently - not all that exist, of course. My test showed that there are two categories: Those hosts that compensate for the delay reported by my plug-in and those that don't. With all hosts of the first category, implementing getTailSize() (or not) did not make the slightest difference - they were flushing the pending outputs correctly anyway. With all hosts of the second category, implementing getTailSize() didn't help at all.

So the only conclusion is: While getTailSize() has a purpose in theory, it's apparently ignored in practice by the great majority of VST hosts.

aciddose wrote:Async was intended to support plugins which acted as an interface to hardware and were used in the past. This sort of thing proved to be unreliable however and I'm surprised VST2.4 didn't deprecate this function.

Your theory about "lookahead without latency" is amusing at best. So you've invented a time-machine then?
Okay, I need to explain this a bit more in detail then :wink:

First of all, we need to distinguish two types of "delay" (or "latency") here:
  1. The plug-in's internal latency, i.e. how long a sample takes to propagate through the plug-in's internal buffer, expressed in samples
  2. The time it takes for the audio to get from the sound card's line-in to the line-out, i.e. the total processing time, expressed in seconds
The second type of the delay only exists in "Live" applications, but is completely nonexisting and irrelevant when processing audio files. The first type of delay is a property of the plug-in, so it exists always. Though the first type of delay could be eliminated, with a suitable plug-in interface.

Now, if we implement a "lookahead" buffer, we have two options: (a) Actually delay the audio inside the plug-in, by returning some silence at the beginning of the process, which introduces a delay of the first type; or (b) read input samples from the host until we have enough data in the buffer but do not return anything to the host until our buffer is actually filled. The latter method completely avoids any delay of the first type! This hasn't anything to do with a "time-machine", it's simply a matter of the plug-in interface. We just read more input before we start writing the output! In "Live" applications - and only in those - there will still be the delay of the second type, of course. This cannot be avoided. I understand that.

Finally, with the VST interface, a delay of the first type can not be avoided, although this would be much desired. That's because processReplace() must return the same number of samples that it got as input. Thus, all we can do to realize our "lookahead" buffer is introducing a delay of the first type and then hope that the host is going to compensate correctly. With a more flexible effect interface, like the one used by SoX, we could avoid a delay of the first type entirely. So we wouldn't need to hope for the host to help us out with delay compensation.

Last but not least, and that was my point, if wantAsyncOperation() would actually be supported in "real world" applications, it could be used to avoid the delay of the first type altogether, even with VST. That's because we no longer would have to return the output samples immediately in process(). Instead, we could wait for more input from the host and return the outputs to the host as soon as it really is available. Very similar to how we would do it in a SoX effect. Anyway, the fact the wantAsyncOperation() doesn't seem to be supported in "real world" VST hosts, thwarts that hope... :?

aciddose wrote:
KarLoff wrote:BTW: Is there a comprehensive list of which VST functions a host must support and which ones are optional?
VST was never standardized in that way. The VST-SDK was released to allow developers to write plugins, not hosts. As far as how you can answer questions about the functionality in the SDK; You have two options. One is to check what various versions of Cubase do. Second is you can ask on the mailing list, although note that VST2 is completely discontinued and unsupported at this point.
IMO that's a serious shortcoming of VST. This way developers are pretty much forced to reverse engineer the behavior of various "real world" applications to learn what does work and what does not. And even then, I cannot be sure how application XYZ will have with my plug-in. I'm more used to read RFC's and ISO Standards, which usually are very detailed. The VST SDK is more like "here you have our header files, now go figure out yourself what every function does and how they are supposed to be called". Not what I'm used to from a proper specification/standard :roll:
Last edited by KarLoff on Sun Sep 07, 2014 3:01 pm, edited 4 times in total.

Post

KarLoff wrote:So while getTailSize() has a purpose in theory, it's apparently ignore in practice...
That is totally incorrect. I've explained the purpose of the function already. Tail size is required to know the length of audio that might be stored internally in buffers, such as in a delay effect.

It isn't needed for the actual "decay tail" itself, as the host can easily measure the output signal. This is an example of a confusing implementation in VST, like many other cases where we have no way to tell for certain whether this was just poorly named or poorly planned when it was written.

Ideally, the name should be something more like "memory length".
Free plug-ins for Windows, MacOS and Linux. Xhip Synthesizer v8.0 and Xhip Effects Bundle v6.7.
The coder's credo: We believe our work is neither clever nor difficult; it is done because we thought it would be easy.
Work less; get more done.

Post

KarLoff wrote: First of all, we need to distinguish two types of "delay" (or "latency") here:
...
It's a nice idea in theory, but it falls apart right at the beginning when you make a distinction between "real-time" and "offline" processing.

Yes, we could simply feed in samples ahead of time to a plugin. The problem however is how do you actually deal with that?

If you jump into a signal you'll be cutting off the beginning of the signal. The only way to process the complete signal is... Well, to process the complete signal.

So it simply doesn't work, at all.

Now if you're talking about adding delays and compensating in various places to line signals up, this works identically in both "real-time" and "offline" situations. It turns out in fact that there is no difference between these cases.

In the "offline" case it doesn't matter how much actual latency you have since the beginning of the signal (silence until latency has passed) can be chopped after processing. Other than that, the solution is identical. It works the same in "real-time" or not.

What would the purpose be to implement an entirely redundant functionality into the plugin interface when the host can already do this delay / chop on its own with the existing interface?
Free plug-ins for Windows, MacOS and Linux. Xhip Synthesizer v8.0 and Xhip Effects Bundle v6.7.
The coder's credo: We believe our work is neither clever nor difficult; it is done because we thought it would be easy.
Work less; get more done.

Post

KarLoff wrote: IMO that's a serious shortcoming of VST. This way developers are pretty much forced to reverse engineer the behavior of various "real world" applications to learn what does work and what does not. And even then, I cannot be sure how application XYZ will have with my plug-in. I'm more used to read RFC's and ISO Standards, which usually are very detailed. The VST SDK is more like "here you have our header files, now go figure out yourself what every function does and how they are supposed to be called". Not what I'm used to from a proper specification/standard :roll:
Well on this point I 100% agree with you.

VST is absolute crap.

Unfortunately, there is nothing significantly better available that covers all the bases VST does. If there were something it would be for the most part a duplicate of VST with some small changes/improvements combined with proper documentation and standardization.

Hopefully we can produce such a thing in the future. At this point however we're stuck.
Free plug-ins for Windows, MacOS and Linux. Xhip Synthesizer v8.0 and Xhip Effects Bundle v6.7.
The coder's credo: We believe our work is neither clever nor difficult; it is done because we thought it would be easy.
Work less; get more done.

Post

aciddose wrote:
KarLoff wrote:First of all, we need to distinguish two types of "delay" (or "latency") here:
...
It's a nice idea in theory, but it falls apart right at the beginning when you make a distinction between "real-time" and "offline" processing.

Yes, we could simply feed in samples ahead of time to a plugin. The problem however is how do you actually deal with that?

If you jump into a signal you'll be cutting off the beginning of the signal. The only way to process the complete signal is... Well, to process the complete signal.

So it simply doesn't work, at all.

Now if you're talking about adding delays and compensating in various places to line signals up, this works identically in both "real-time" and "offline" situations. It turns out in fact that there is no difference between these cases.

In the "offline" case it doesn't matter how much actual latency you have since the beginning of the signal (silence until latency has passed) can be chopped after processing. Other than that, the solution is identical. It works the same in "real-time" or not.

What would the purpose be to implement an entirely redundant functionality into the plugin interface when the host can already do this delay / chop on its own with the existing interface?
Okay, maybe it becomes more clear with an example :)

For a short moment, lets assume processRepalcing() was defined like this:

Code: Select all

VstInt32 processReplacing(float** inputs, float** outputs, VstInt32 sampleFrames)
Only difference is that the function now reports the number of output samples that it has written into outputs as its return value, which may be equal to sampleFrames, smaller than sampleFrames or even zero. This small change alone allows the plug-in to fill its internal "lookahead" buffer without introducing any delay at all! At the beginning, we simply don't return any outputs, until we actually do have something to return. No "silent" samples need to be returned. Nothing gets delayed. And here we are looking only at the plug-in's "internal" delay, independent of "live" processing or not.

Now let's see what happens if we process files or if we process "live" audio:
  • File-based audio: The host feeds N samples into the plug-in. It gets back M samples, with either M=N or M<N, maybe even M=0. But it doesn't really matter! We simply write as many output samples into the output file as we got back. Or, if we have multiple plug-in's in a chain, we simply forward as many output samples to the (i+1)-th plug-in as we got back from the i-th plug-in. Then we feed the next N input samples into the (first) plug-in. And so on. That all is very simple and straight forward.
  • Live audio: Actually not much different to the "file-based" case. Only difference really is that it may take a few moments until we have the next N input samples available - because they need to be recorded from the Line-In in "real time" speed. But that isn't a problem. We simply feed them into the plug-in as soon as they come in. The fact that the plug-in doesn't immediately start returning output samples isn't a big deal either! We simply don't start sending output samples to the Soundcard's output buffer until our plug-in has actually started returning output samples. And if we have multiple plug-in's in a chain, we don't start sending output samples to the Soundcard's output buffer until the last plug-in in the chain has actually started returning output samples. Should it really not be possible to not send any samples to the Soundcard's output buffer, for whatever reason, the host could still send silence to the Soundcard until actual output becomes available from the (last) plug-in. Rather simple.
As we have seen, this small extension of the interface completely eliminates any kind of delay, and therefore any need for delay compensation, on the plug-in level. The "file-based" case has been simplified quite a lot. Since there will never be any delay to begin with, we don't need to rely on the host to compensate. It just works! At the same time, the "LIVE" case is not complicated by our interface extension. Sure, there still is the unavoidable delay between the Soundcard's Line-In and Lin-out in "LIVE" mode. But that existed before. Without a time machine, that delay really can't be avoided :D

--

Addendum: How does the flushing work? In the "Live" case, there is not really any flushing needed, because audio goes on and on until forever. In the "file-based" case, the host can very simply compute the difference between the number of samples it has sent to the plug-in and the number of samples it has gotten back so far. This is exactly the number of outstanding output samples. At the end, the host simply feeds the plug-in with "silent" samples, until the number of outstanding samples has dropped to zero. Couldn't be easier.

Post

All you've done is move the function from the host to the plugin, which is just plain stupid. The interface for plugins needs to be simple and allow the host to do this kind of work. You're suggesting a needless complication.

The fact is it seems pretty obvious you've never written a host, and you should. It will give you a lot of insight into how the interfaces work and why, and allow you to see places where genuine improvements could be made.

You need to realize that the host can't do anything until it receives the full buffer of samples. If it gets zero or any number less than the size of the buffer, it will need to fill the buffer with something. It probably ends up having to fill it with silence, and you're right back where you started.

The host could pass the truncated buffer down the chain, but if you end up with zero samples in that buffer the host needs to halt, it can't process further until it gets the samples it needs. If the host is mixing, the buffers it mixes need to line up with each other. Until the host gets N samples from all sources to be mixed it can't produce the mixed result. Same thing, it can truncate the result but eventually it will need to halt.

The host could have managed this process on its own knowing only the latency of the plugin, which is already reported by the plugin. I believe the method you describe is vastly more complicated than the methods already employed by hosts with the existing interface.

If you believe your method is better, feel free to implement both and demonstrate the method. It won't be adopted if it isn't backward compatible, but you might convince someone if it is vastly superior.
Free plug-ins for Windows, MacOS and Linux. Xhip Synthesizer v8.0 and Xhip Effects Bundle v6.7.
The coder's credo: We believe our work is neither clever nor difficult; it is done because we thought it would be easy.
Work less; get more done.

Post

KarLoff wrote: Okay, maybe it becomes more clear with an example :)

For a short moment, lets assume processRepalcing() was defined like this:

Code: Select all

VstInt32 processReplacing(float** inputs, float** outputs, VstInt32 sampleFrames)
Only difference is that the function now reports the number of output samples that it has written into outputs as its return value, which may be equal to sampleFrames, smaller than sampleFrames or even zero. This small change alone allows the plug-in to fill its internal "lookahead" buffer without introducing any delay at all! At the beginning, we simply don't return any outputs, until we actually do have something to return. No "silent" samples need to be returned. Nothing gets delayed. And here we are looking only at the plug-in's "internal" delay, independent of "live" processing or not.
that's so elegant
now tell all the DAW developers to implement this on the DAW side somehow and i'll go get some popcorn :hihi:


EDIT: but seriously, do you realize that implementing the existing "plugin delay compensation" and/or "tail" functionality into a Host which lacks it, is easier than implementing support for your elegant/magical solution?
It doesn't matter how it sounds..
..as long as it has BASS and it's LOUD!

irc.libera.chat >>> #kvr

Post

antto wrote:
KarLoff wrote: Okay, maybe it becomes more clear with an example :)

For a short moment, lets assume processRepalcing() was defined like this:

Code: Select all

VstInt32 processReplacing(float** inputs, float** outputs, VstInt32 sampleFrames)
Only difference is that the function now reports the number of output samples that it has written into outputs as its return value, which may be equal to sampleFrames, smaller than sampleFrames or even zero. This small change alone allows the plug-in to fill its internal "lookahead" buffer without introducing any delay at all! At the beginning, we simply don't return any outputs, until we actually do have something to return. No "silent" samples need to be returned. Nothing gets delayed. And here we are looking only at the plug-in's "internal" delay, independent of "live" processing or not.
that's so elegant
now tell all the DAW developers to implement this on the DAW side somehow and i'll go get some popcorn :hihi:
You mean like this :wink:
http://imgs.xkcd.com/comics/standards.png

antto wrote:EDIT: but seriously, do you realize that implementing the existing "plugin delay compensation" and/or "tail" functionality into a Host which lacks it, is easier than implementing support for your elegant/magical solution?
Yes, I understand that, in the end, I will have to deal with the VST the way it is implemented in dozens of "real world" applications nowadays. My primary goal is to discuss the limitations of VST and how to deal with them in the most "compatible" way. After I read about the "async processing" capability of the VST, I was hoping that this could be used to implement a "lookahead" buffer without artificial delay, because (in theory) it allows a plug-in to return the pending output samples at a later time. Unfortunately, support for "async processing" is lacking in "real world" applications...

Post

That is because a real, concrete implementation doesn't work as imagined. It turns out there are better ways to handle this sort of thing, such as plugins which need async operation (as it was intended, not as you imagine) can implement it themselves as "mini hosts" with their own protocols. They then interface to the VST host like any other standard plugin.

For example you can write a plugin that provides audio I/O if you like. You'll need to manage your buffers and latency internally and provide the host with estimates/predictions. The only thing is in most cases this is pointless.

One case where it makes a lot of sense is for interface plugins for hardware. The modern Virus synthesizers are one example where this sort of thing makes sense.
Free plug-ins for Windows, MacOS and Linux. Xhip Synthesizer v8.0 and Xhip Effects Bundle v6.7.
The coder's credo: We believe our work is neither clever nor difficult; it is done because we thought it would be easy.
Work less; get more done.

Post

aciddose wrote:All you've done is move the function from the host to the plugin, which is just plain stupid. The interface for plugins needs to be simple and allow the host to do this kind of work. You're suggesting a needless complication.
It does more than that. It gives the plug-in the ability to report the actual amount of available output samples to the host. Without that, i.e. the current situation in VST, the plug-in must always return the pending output samples immediately. This means, in order to implement a "lookahead" buffer, I am forced to rettun fake output samples, even when there actually is nothing to be returned (yet). This introduces an artificial delay. And the host will need to compensate for that delay. If anything is "just plain stupid" (to use your words), then this is being forced to use such workarounds.

In other words: Giving the plug-in the ability to report the number of output samples, would eliminate both: The workaround (artificial delay on the plug-in's side) and the workaround for the workaround (delay compensation on the host's side). So this clearly reduces complexity and also fixes potential problems, like hosts not compensating for the artificial delay.

aciddose wrote:The fact is it seems pretty obvious you've never written a host, and you should. It will give you a lot of insight into how the interfaces work and why, and allow you to see places where genuine improvements could be made.
I have not written a fully-fledge VST host, yes. But I have written my own "host" for testing my effects. Of course my internal interface is designed flexible enough, so that a "lookahead" buffer can be implemented without introducing an artificial delay on the effect's side.

Furthermore, I have implemented effects in SoX. The effect interface by SoX makes a distinction between the number of input and output samples. So, in SoX, a "lookahead" buffer can be implemented without introducing an artificial delay on the effect's side.

Last but not least, I have been working on various commercial audio processing applications. I cannot give you any details, because I'm under NDA. But let my assure you that all library interfaces we have designed also made a distinction between the number of input and output samples. Our customers would not have accepted, if our library introduces a delay. Just telling them that they are supposed to compensate for the delay on their side certainly wouldn't have made them happy. But they are perfectly fine with the fact that not every single library call necessarily returns new output samples. That's a common practice (outside the "VST world"), so nobody ever complained or even wondered about it, as far as I remember.

Maybe you now better understand my confusion when working with the VST interface for the first time...

aciddose wrote:You need to realize that the host can't do anything until it receives the full buffer of samples. If it gets zero or any number less than the size of the buffer, it will need to fill the buffer with something. It probably ends up having to fill it with silence, and you're right back where you started.
Why should the host not be able to do anything, until it receives all pending outputs from the plug-in? That's just not true. The host simply feeds the available input samples, if any, into the first plug-in. Also, if any plug-in inside the chain returned some output, it forwards that output to the next plug-in in the chain. Otherwise not. Finally, if the last plug-in in the chain returned some output, it sends that output to the Soundcards output buffer (or the output file). That all is repeated in a large loop until the process ends.

aciddose wrote:The host could pass the truncated buffer down the chain, but if you end up with zero samples in that buffer the host needs to halt, it can't process further until it gets the samples it needs. If the host is mixing, the buffers it mixes need to line up with each other. Until the host gets N samples from all sources to be mixed it can't produce the mixed result. Same thing, it can truncate the result but eventually it will need to halt.
As long as your "LIVE" source still produces fresh input samples, which it should be doing in regular intervals (until the user clicks the "stop" button), new input can be sent to the plug-in continuously. And, as long as new input is sent to the plug-in continuously, the plug-in will produce new output, eventually. Consequently, nothing is going to halt. Not every plug-in is going to produce new output in every step. But that isn't required at all...

aciddose wrote:If you believe your method is better, feel free to implement both and demonstrate the method. It won't be adopted if it isn't backward compatible, but you might convince someone if it is vastly superior.
As mentioned above, it is implemented in widely used OpenSource software, such as SoX, as well as in commercial products.

This is pretty much the processReplacing() equivalent in SoX:

Code: Select all

/**
Client API:
Callback to process samples,
@returns SOX_SUCCESS if successful.
*/
int sox_effect_handler_flow(
    sox_effect_t *ffp,         /**< Effect pointer. */
    const sox_sample_t *ibuf,  /**< Buffer from which to read samples. */
    sox_sample_t *obuf,        /**< Buffer to which samples are written. */
    size_t *isamp,             /**< On entry, contains capacity of ibuf; on exit, contains number of samples consumed. */
    size_t *osamp              /**< On entry, contains capacity of obuf; on exit, contains number of samples written. */
);

Post Reply

Return to “DSP and Plugin Development”