On Tuesday 17 December 2002 16.11, Sami P Perttu wrote:
[...]
Thanks for the explanations... there is still one
thing I don't
understand. Do you intend to have both frequency (Hz) and pitch
(logarithmic Hz) controls? It seems redundant.
There will not be Hz at all, if I can help it; just linear PITCH, and
hopefully "linear" pitch hinted as "I really mean *linear*" - ie
NOTEPITCH. :-)
[...]
1) A control is a signal whose value against time is
determined by
control events.
Yes.
2) The control events divide the signal into sections.
The signal
is identically zero before the first control event arrives.
You should probably not assume that. (0 might be an illegal value.)
The metadata will contain default values for all controls, it seems
to make sense that the host should load the defaults as part of the
instantiation of a plugin. That would make it a matter of "do it once
and for all in the host SDK."
3) Each section is a third-order polynomial. This
gives continuity
of first derivatives where it is needed and is a compromise between
efficiency and flexibility. Note that some or all of the
coefficients may be zero.
Yes, this looks nice.
4) What happens between sample values? This is up to
each plugin,
mainly because the continuous signal is, in general, not
bandlimited. However, to most practical purposes, it can be made
"smooth enough" . Most plugins that oversample will use the macros
below to get an interpolated version. Some might do additional
low-pass filtering before applying the control signal internally.
Yes. The best we can do is polynomial splines - but that doesn't
automatically eliminate the problem. It's just a bit better than
without ramps, or with just linear, but it's not perfect. Some
plugins might still need to do some conditioning.
5) The event type SET_CONTROL (name?) begins a new
section of a
I call it XAP_A_SET, IIRC. (XAP "Action" SET.)
control signal. It has these attributes:
float start_value; /* value now */
float start_slope; /* slope now */
float duration; /* in samples */
float end_value; /* value at now + duration */
float end_slope; /* slope at now + duration */
Well, we'd have to switch to 64 byte events for that. However,
there's no good reason to double the start/end data, for reasons
explained below.
All attributes must be real-valued.
Yes, probably. Not sure about the duration, though, as you'll need it
in both integer and float if you must implement both ramping and
"ramp stops here" handling efficiently.
With the "ramp stops when I tell you" design (target + duration or
just slope), this is not an issue, since the control data generator
will tell you what to do in time.
Duration must be greater than
zero.
Not sure. Short ramps easilly round to 0 length. Where you you want
the special case; in senders or receivers? Well, probably in senders.
(See below.)
Slopes are given as "(instantaneous) change in
value per
sample".
Yes.
As stated above, the attributes should be taken as
constraints
specifying a third-order polynomial. Denoting the polynomial as
f(x), the constraints are:
f(now) = start_value
f'(now) = start_slope
f(now + duration) = end_value
f'(now + duration) = end_slope
Note: the section is specified completely in each event to make the
handling of controls as stateless as possible.
That's basically a good idea, but in my experience, it's pointless in
real life applications. These events are not asynchronous messages,
but rather a continous stream of structured data. If you can't keep
track of what you're telling other plugins, you can't generate useful
control data anyway.
Besides, all you need to do to have a complete section is send
XAP_A_SET followed by XAP_A_RAMP, with the same timestamp value.
(Assuming that XAP_A_SET comes with value + slope, and XAP_A_RAMP
comes with target value + target slope. A normal SET would always
have 0 for slope.)
The section lasts until the next SET_CONTROL event.
How? Either it ends when the ramp is finished, or it doesn't.
BTW, Audiality's internal voice control events don't even keep track
of that, so you *explicitly* have to tell a voice when to stop
ramping a control, by sending a "SET" event with the exact target
value. (*) This isn't nearly as hard to manage as it may sound, since
you *have* to keep track of what you're doing anyway, as a sender.
That said, giving ramp events a specific duration, after which the
ramping should stop *might* be nice. I'm still suspecting that this
is an illusion that won't help real code at all, though. (It didn't
in Audiality, at least, and I can't think of a real situation where
it would be useful.)
Want to have safe disconnections? Just do what you should do anyway:
Load the default value with a slope of 0. That kills any ramping in
progress, and reverts to the default value.
Click free? Just have the host take over the port for a while, before
really disconnecting it. (The plugin that owns the input will never
notice this.) Send a ramp event of suitable duration, that takes the
control back to the default value. Then disconnect.
Obiously, this *requires* the chained ramp approach I'm using in
Audiality, since you can't read back the value of a XAP control
input. You have to eavesdrop to do that, if you really want to.
(Quite possible to do, of course.)
(*) This is to avoid ramping rounding errors affecting DC levels
between ramps. I'm basically just doing it to avoid a special
"STOP_RAMP" event. It's not required to avoid drift, since
ramping is always chasing a target value, rather than building
on the current state. You can actually ramp to any value
without even knowing the current value of the control!
The timing of
the next event need not agree with the duration given in the
current event - the next event may arrive at any time.
What's the point? As a control data generator, you can't just jump in
and out as you like. Either you're connected, or you're not. When
you're connected, you do your job, and deliver a stream of structured
data that represents a continous signal.
[...]
Note: it is assumed that plugins choose their
approximation
irregardless of the actual polynomial, which can be constant or
linear. I think this is a good assumption. Exact linearity cannot
be detected anyway due to the inaccuracy of floating-point
arithmetic.
Yes, that's an important point. Plugins should be concistent rather
than "smart" in this case. It would be rather annoying if some
plugins toggled "randomly" between two different sets of artifacts...
*heh* (That's a great way to make it glaringly obvious that there
*are* artifactsl even if they're very subtle.)
7) The following struct and macros provide a
"control iterator".
They are an efficient way of implementing the full third-order
form.
/* TODO: should some/all of these be floats? */
Well, given that control ramping is not reliable enough to be used
for long sections and big changes, I don't think there's much point
in using doubles at all. Control ramping is just an *approximation*,
and since you can't know how well the receiver actually implements
it, you can't rely too much on the accuracy anyway.
In short, if we can deal with ramps over a few hundred samples at
most, that should be just fine. If you do longer ramps than that,
you're in for unpleasant surprises with many of the plugins that
interpolate internal coefficients. Besides, you can't really make a
longer ramp than one block anyway, without breaking this "future"
rule. Process one block at a time...
As to "hitting the target", well, I think we must analyze this more
carefully, to see how big a difference float vs double makes here,
and when. How far off can you land?
struct control_variable {
double value;
double dvalue;
double ddvalue;
double dddvalue;
};
/* Initializes a control variable. */
#define INITIALIZE_CONTROL(var) \
(var).value = (var).dvalue = (var).ddvalue = (var).dddvalue = 0;
Or just use XAP_A_SET, and wait for the host to give you your default
values that way. I think that's the best way, actually, since it
eliminates lots of boring and potentially incorrect initialization
code in plugins.
/* Initializes a control variable from a SET_CONTROL
event. */
[...]
/* Initializes an oversampled control variable from a
SET_CONTROL
event. */
[...]
Yes. Thanks for doing the maths! :-)
/* Increments a control variable. Should be called
after each
sample is processed. */
#define INCREMENT_CONTROL(var) \
(var).value += (var).dvalue; \
(var).dvalue += (var).ddvalue; \
(var).ddvalue += (var).dddvalue;
I have checked the above for correctness.
Great. :-)
8) The following macros generate control events.
/* TODO: set event type, too? */
Well, in Audiality, I just have inline functions that allocate, fill
in and send events of the common kinds. That's the cleanest way by
far, from the plugin code POV.
As to inline funcs; how likely is it that we'll find a compiler these
days that won't do it, or will generaty "slow" code?
(Not that I care all that much, really. gcc seems to do well enough,
and that's what most real OSes come with anyway. ;-)
[...fill-in-event macros...]
Comments?
Nice work!
We'll have to settle on the conventions for control generators,
though. IMNSHO, *they* should be the "smart side" of the connection,
while the receiver just does what it's told. I'm basically trying to
achieve this:
1. Plugins that only receive control data become very
simple and easy to get right.
2. Control generators contain all logic, and as a result,
have more alternatives when it comes to optimizations.
3. Keeping the "active" logic in one place will most
probably make debugging easier, since you can
basically run the output into a "control tester"
plugin to find out if the output data is correct.
If you have a problem that's caused by *two* plugins
being broken, you're in for some great fun...
4. Keeping the most complex part right behind a standard
output interface is less likely to result in
weirdness that shows up in the middle of your plugin,
where you can't easily view the data in useful way.
5. Optimized control ramp implementations will probably
be hairy enough without conditionals and stuff
required by the API.
//David Olofson - Programmer, Composer, Open Source Advocate
.- The Return of Audiality! --------------------------------.
| Free/Open Source Audio Engine for use in Games or Studio. |
| RT and off-line synth. Scripting. Sample accurate timing. |
`--------------------------->
http://olofson.net/audiality -'
---
http://olofson.net ---
http://www.reologica.se ---