[linux-audio-dev] XAP: Control events

Sami P Perttu perttu at cc.helsinki.fi
Tue Dec 17 10:17:01 UTC 2002


On Tue, 17 Dec 2002, David Olofson wrote:

> This is not our first shot at an audio plugin API. We've all learned
> a lot since I came here with Audiality/RT-Linux, and IMHO, we should
> do our best to make XAP reflect our current knowledge and experience.
> I think we can do better than a slightly enhanced LADSPA.

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.

While you're discussing loftier matters I might as well try to do
some low-level grunt work on controls. This is an amalgamation of several
posts:


1) A control is a signal whose value against time is determined by control
events.


2) The control events divide the signal into sections. The signal is
identically zero before the first control event arrives.


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.


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.


5) The event type SET_CONTROL (name?) begins a new section of a 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 */

All attributes must be real-valued. Duration must be greater than zero.
Slopes are given as "(instantaneous) change in value per sample".

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.

The section lasts until the next SET_CONTROL event. 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.


6) The plugin may approximate the control internally as piece-wise
constant or piece-wise linear. In the first case the value of
the section is the average of start and end values. The following macros
extract these representations:

#define GET_CONSTANT_VALUE(event) (((event).start_value \
   + (event).end_value) * 0.5)

#define GET_LINEAR_VALUE(event) ((event).start_value)
#define GET_LINEAR_SLOPE(event) (((event).end_value \
   - (event).start_value) / (event).duration)
#define GET_OVERSAMPLED_LINEAR_SLOPE(event, ratio) (((event).end_value \
   - (event).start_value) / ((event).duration * (ratio))

Note: GET_LINEAR_VALUE returns the initial value.

The last macro provides for plugins that oversample. The second argument
is the oversampling ratio.

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.


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? */
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;

/* Initializes a control variable from a SET_CONTROL event. */
#define SET_CONTROL_FROM_EVENT(var, event) \
{ \
   double tmp      = 1 / (event).duration; \
   (var).value     = (event).start_value; \
   (var).dvalue    = (event).start_slope; \
   (var).dddvalue  = ((event).start_slope + (event).end_slope \
                     - 2 * ((event).end_value - (event).start_value) \
                     * tmp) * tmp * tmp; \
   (var).ddvalue   = (((event).end_value - (event).start_value) * tmp \
                     - (event).start_slope) * tmp \
                     - (var).dddvalue / tmp; \
   (var).dvalue   += (var).dddvalue + (var).ddvalue; \
   (var).dddvalue *= 6; \
   (var).ddvalue  += (var).dddvalue + (var).ddvalue; \
}

/* Initializes an oversampled control variable from a SET_CONTROL event.
 */
#define SET_OVERSAMPLED_CONTROL_FROM_EVENT(var, event, ratio) \
{ \
   double tmp      = 1 / ((event).duration * (ratio)); \
   (var).value     = (event).start_value; \
   (var).dvalue    = (event).start_slope; \
   (var).dddvalue  = ((event).start_slope + (event).end_slope \
                     - 2 * ((event).end_value - (event).start_value) \
                     * tmp) * (tmp * tmp); \
   (var).ddvalue   = (((event).end_value - (event).start_value) * tmp \
                     - (event).start_slope) * tmp \
                     - (var).dddvalue / tmp; \
   (var).dvalue   += (var).dddvalue + (var).ddvalue; \
   (var).dddvalue *= 6; \
   (var).ddvalue  += (var).dddvalue + (var).ddvalue; \
}

/* 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.


8) The following macros generate control events.

/* TODO: set event type, too? */
#define SET_CONTROL_CONSTANT(event, value) \
   (event).start_value = (event).end_value = (value); \
   (event).start_slope = (event).end_slope = 0; \
   (event).duration    = 1;

/* TODO: set event type, too? */
#define SET_CONTROL_RAMP(event, start_value, slope) \
   (event).start_value = (start_value); \
   (event).start_slope = (event).end_slope = (slope); \
   (event).duration    = 1; \
   (event).end_value   = (event).start_value + (event).slope;

/* TODO: set event type, too? */
#define SET_CONTROL_POLYNOMIAL(event, start_value, start_slope, \
   duration, end_value, end_slope) \
   (event).start_value = (start_value); \
   (event).start_slope = (start_slope); \
   (event).duration    = (duration); \
   (event).end_value   = (end_value); \
   (event).end_slope   = (end_slope);


Comments?

--
Sami Perttu                       "Flower chase the sunshine"
Sami.Perttu at hiit.fi               http://www.cs.helsinki.fi/u/perttu




More information about the Linux-audio-dev mailing list