From: David Robillard <d(a)drobilla.net>
I'm a modular head, I remain convinced that control ports are nothing
but a pain in the ass and CV for everything would be a wonderful
fantasy land :)
It's called "SynthEdit land" *everything* is CV ;) (not on Linux sorry).
As it happens, I am currently porting the blop plugins
to LV2, and
making a new extension in order to drop the many plugin variants (which
are a nightmare from the user POV). This simple extension lets you
switch a port from its default type (e.g. Control) to another type
(e.g.
CV). The pattern looks something like this:
/* plugin->frequency_is_cv is 1 if a CV buffer, 0 if a single float */
for (uint32_t i = 0; i < sample_count; ++i) {
const float freq = frequency[s * plugin->frequency_is_cv];
if (freq != plugin->last_frequency) {
recalculate_something(freq);
plugin->last_frequency = freq;
}
/* Do stuff */
}
That's smart. In a simple example this doesn't seem like much of a win.
Because A 1 port plugin has only two possible variants (frequency as
single-float/ buffer). But..
* A 2-port plugin has 4 varients.
* A 3-port plugin has 8 varients.
* A 10 port plugin has 1024 varients!
So you're avoiding that combinatorial nightmare.
I do something similar. The port is flagged as either 'streaming' (use the
entire buffer) or 'static' use a single float. My point of difference is -
the entire buffer is provided either way. So you have the option of writing
the plugin like..
const float freq = frequency[s];
..OR...
const float freq = frequency[s * plugin->frequency_is_cv];
.. and it works transparently either way. So the extension is backward
compatible with 'dumb' plugins, or 'dumb' plugin standards like VST (I
can
interface VST plugins with modular components).
Doing those comparisons to see if the value actually
changed since the
last sample in order to recalculate is not so great (branching).
I don't know if you can implement what I do. Once I know which ports are
single floats I 'switch' processing functions. i.e. use a function pointer
to select 1 of several optimised functions. So you write a general purpose
loop like the one above, this is your fallback. Then you write an optimised
one that assumes 'frequency' is a single float - This one has no branching
and no extra multiplication, it's super efficient. You get the best of both
worlds. Note I don't write loops optimised for every possible combination,
just pick a few key ones. The function pointer is one extra level of
indirection, but it's much faster than branching, esp when there's several
ports involved in the decision.
personally my interest in a solution here is very
real. More people
care about normal high level parameters and being able to interpolate
than low-level modular synth CV stuff, but to me it's telling that (it
seems...) one solution can solve both problems nicely.
<high five> ;)
Best Regards,
Jeff