[LV2] Buffersize, Options and Externtion-Data

Filipe Coelho falktx at gmail.com
Wed Sep 9 08:48:49 PDT 2015

On 09.09.2015 16:40, Robin Gareus wrote:
> Hi David, LV2-team,
Hi Robin, clearing up some confusing here too ;)

> Backstory
> ---------
> Some Plugins expect maxBlockLength [1] to be the current block-length.
> Most lv2-hosts just passed jack_buffersize and users never changed the
> jack-buffersize at runtime so this worked for the most part in recent years.
Note that Carla has always supported reporting buffer size changes to 
the plugins
(similar to a JACK buffersize callback)

This is done by using the Options interface, which is provided by the 
plugin and called by the host.
Carla calls options->set() during JACK buffersize callbacks.
It also does the same for sample rate changes, but that's a discussion 
for another time..

I looked into when exactly I added this code into carla, see this link:
Date is "20 April 2013" ;)

I think talked with David about this, if options could be used for this 
or not.
I got the idea that yes, that was one of the actual intentions for the 
Options extension.

> A few months ago it was brought to the attention of ardour-devs [4] that
> some plugins mis-behave or crash during session-export, while looping or
> with pluigin-analysis. In those cases the cycle in Ardour does not
> correspond to the jack-buffersize, and the plugin was asked to process a
> larger or smaller amount of data.
> Ardour-4.2 now sets minBlockLength to 0, maxBlockLength to 8192, passes
> those fixed values as Instantiation Option and refuses to load plugins
> that require fixedBlockLength [3].
> Semantically that is correct with the LV2 specs [1, 2, 3] and fixes many
> crashes, particularly with plugins which use [1] to pre-allocate
> internal buffers.
> However some other plugins - esp convolution engines - are not happy
> with a buffersize-range without any means to query the current, nominal
> period-size. The most recent discussion about this is [7].
I think this needs some clarification before continuing.

When you refer to 'maxBlockLength' you're referring to what an 
application can actually use for its maximum,
not the maximum that the engine can do.
For example, in Ardour running JACK in realtime at 1024 buffer size the 
maximum the engine can do (while realtime) is 1024.
Correct me if I'm wrong, but Ardour will never call run() on plugins 
with more than 1024 frames, will it? (*realtime*)

Of course, Ardour has the analysis and offline/freewheel modes but those 
are not realtime.
My guess is that analysis spawns a new plugin instance, so it doesn't 
matter here.

In all three cases (realtime, analysis and realtime), Ardour actually 
has a 8192 maximum.
But, this is not the actual engine "realtime maximum" during a live session.

So Ardour is not doing anything wrong, technically.
It's still indeed its maximum value, just one that is fixed at all times.
That's why I still think this needs a new property.

Now, there's another thing that needs clear up.
Some plugins want to know the "current" buffer size ahead of a run() 
call, but some hosts (Ardour and Carla at least) will divide the audio 
buffers into small pieces (and not in a constant/fixed way).
Plugins that require a fixed number of frames can do so via required 
features in the ttl files.

What I want to make sure we agree on, is that this "current" value we're 
after it's the "engine realtime maximum",
not the actual number of frames used in run() since that is not 
guaranteed to be always the same.
Plugins can use this value to know that run() calls will always use 
equal or less frames than it, never more.

If the host needs to call run() with a higher number then it should 
report that to the plugin via options->set().
This is the case of Ardour offline/freewheel mode.

I really, really hope we can agree on this^

PS: Anyone got a suggestion for a "engine realtime maximum" property name?

> Specs Clarification
> -------------------
> Is it correct to interpret the spec as follows?
>   (1) When the buffer-size is passed as instantiation parameter it is a
> fixed value.
>   (2) When the min/maxBlockLength is used as opts:interface via
> LV2_Descriptor::extension_data() it can be dynamic, using get() set() as
> outlined in [6].

I think it's the same situation as in JACK.
The current buffer size is in place until the next callback.

If you count on blockLength being passed during instantiation to mean 
it's a static value,
then the plugin would have to specifically request the value via host 
Seems a little wasteful when the host can just pass those values directly.

When the spec says:
"There are two facilities for passing options to an instance: 
opts:options allows passing options at instantiation time,
and the opts:interface interface allows options to be dynamically set 
and retrieved after instantiation."

Seems to me that options:interface and instantiate-time feature serve 
the same purpose.
Giving special meaning to one of them seems wrong.

>   (3) the opts:interface [6] to get/set the value must not be used in the
> run() realtime-process function. It's only valid to get/set options
> during activate(), deactivate(), worker-thread or state-load/save.
Isn't that what the "instantiation thread class" exactly means?

> Discussion
> ----------
> If a above interpretation is correct there is no way for a plugin to
> know what the current period size it. (except maybe using a
> worker-thread to periodically poll the opts:interface)
As I said, the host can simply pass those values in its options feature.

> One possible solution would be to require a host to activate/deactivate
> plugins to dynamically set the buffersize (2,3) or to re-instantiate the
> plugin (1). This could be tied in with fixedBlockLength [3] without any
> changes to the spec, but will render some plugins useless.
I think for buffer size changes deactivate/activate should be required 
in between.
It's just a few lines added to the spec. :)

Re-instantiate plugins might be required if the plugin does not support 
options but requires some blockLength stuff.

Imagine a plugin that requires fixedBlockLength (yes, I know Ardour does 
not load them).
When the buffer size changes via JACK (or something else) the plugin 
will no longer work.
The host has to either inform the plugin about the change or 
re-instantiate it.

We're getting a bit sidetracked here...

> Alternatively, hosts could ring-buffer wrap plugins requiring
> fixedBlockLength, at the cost of adding latency and potentially increase
> CPU load (always run the plugin with fixed small chunk-size).
Yes, fixedBlockLength plugins are tricky to implement on some hosts.
This is not related to this discussion though.

> Another solution on the table is to create a new dedicated extension for
> this use-case (maybe even a synchronous callback).
No new callbacks or C structs/functions please.
Options is there for offline dynamic changes, atom messages are there 
for realtime stuff.
I don't think we need anything else.

I believe David shares the same opinion.

>   That poses some more
> questions:
> The run() function already passes the actual sample-count. So what
> number does "current" mean?  The total number of samples is expected to
> span various run() calls resulting in one hardware cycle?
I tried to answer this in a comment above.
If it makes sense to you please let me know (I find it very important).

> Since the hardware buffersize can change anytime at what intervals is it updated?
Isn't there a small stop in audio when the HW buffer size changes?

I'm mostly just familiar with the JACK API, but I've seen similar 
behaviour in some other APIs (PortAudio, Juce).
Ie, then HW buffer size changes:
1. it stops the audio temporarily
2. sends a callback to inform of the change
3. starts processing audio again, now with the new buffer size

If this is not always the case do let me know.

> A more general question is: Do we want to encourage DSP to be written in
> a way that requires a-priori knowledge about the nominal period-size?
Is having the property available encouraging to use it?
Instance-access has a 'use of this extension is highly discouraged' 
sentence on its documentation, but new plugins still use it.
(Looking at you v1 series...)

Morph extension exists and yet only 1 plugin set uses it.

You may not need it for you own plugins, but some of us do (specially 
when porting plugins from other specs)

More information about the Devel mailing list