Re: [linux-audio-dev] XAP status : incomplete draft

New Message Reply About this list Date view Thread view Subject view Author view Other groups

Subject: Re: [linux-audio-dev] XAP status : incomplete draft
From: David Olofson (david_AT_olofson.net)
Date: Fri Dec 13 2002 - 15:30:32 EET


On Friday 13 December 2002 09.55, Tim Hockin wrote:
[...]
> #include <stdint.h>

I'm thinking about using that as well - but how portable is it these
days? (No big deal, though. Just get one, or hack a fake if you
platform shouldn't support it for some strange reason.)

> /*
> * The current version of the API: read as "major.minor.micro".
> */
> #define XAP_VERSION 0x000010
> #define XAP_MKVERSION(maj, min, mic) (((mar)<<16) | ((min)<<8) |
> (mic))

I generally prefer 8:8:16 format these days, so the minor version
field can be used as something like the serial field below. (Why have
both?)

(We're at 5.0.246 for our firmware + protocol at work. Unfortunately,
I have only 8 bits for "micro"... :-)

[...]
> struct XAP_descriptor {
[...]
> uint32_t xap_id;

Why the xap_ prefix on all the fields in the struct?

[...]
> /*
> * The serial is simply a number by which the host can compare two
> * versions of a plugin and pick the later version. The actual
> * value has no meaning to the host. The only requirement for
> this * field is that the value never gets smaller in new releases.
> */
> uint32_t xap_serial;

I think this belongs in version, as the micro field. Hosts should
only look at the major and minor fields for compatibility checks, and
look at the micro field only when there are multiple seemingly
identical versions of the plugin.

Then again, are you really supposed to use the same ID for
incompatible plugins in the first place...?

Well, why not? If you do, hosts will have a way of understanding that
version 2.0 of a plugin with a certain ID *should* be a slightly
incompatible upgrade of 1.0 with the same ID. So, after loading an
old net, you can ask the host for a list of newer versions of the
plugins used, instead of checking all of them manually.

So, it makes sense, and save ID space.

> /*
> * The label is a file-unique, non-whitespace, string identifier
> for * the plugin. Hosts can use the filename and label to identify
> a * plugin uniquely. The label is all upper case, by convention.
> */
> const char *xap_label;

Why is this needed? (There are unique IDs, I mean...)

[...]
> const char *xap_version;

This sort of doubles the 32 bit version field, so I'm not sure
there's a good reason to have it. Either you'll ignore this one, or
you'll have to update both.

I think major.minor.micro should be available to users as the *real*
version. Why ask for confusing bug reports? (Another reason for
frustration at work...)

[...]
> /* master controls and I/O */
> int xap_n_controls;
> XAP_control **xap_controls;

Maybe... I was thinking that you might as well require that the first
Control Port supports "maximum buffer size" (very important!) and
that kind of stuff - but where's the "first" Control Port!? If you
have multiple Channel Templates, this would force you to restrict the
count on one of them to be at least 1, which may not make sense - so
you'll end up having an extra Channel Template only for this Port.

> int xap_n_ports;
> XAP_port **ch_ports;

What are these for? Shortcut to avoid the more complex system below,
when you don't really need it?

> /* channel descriptors */
> int xap_n_channel_types;
> XAP_channel *xap_channel_types;
> //FIXME: how to get the num/info currently instantiated channels?

When and why? I mean, isn't the host supposed to remember what it's
done?

The only case I think this is needed for would be when plugins can
spontaneously add channels. Not sure if that's useful at all,
though... (If you add channels yourself, they won't be connected
anywhere anyway. And either way, you'd have to tell the host right
away, or you'd risk crashing in the next process() call, due to
missing audio buffers!)

[...]
> void *(*xap_create)(XAP_descriptor *descriptor, XAP_host *host,
> int rate);

Why only "rate" here - or rather, why "rate" at all? I think there
are more things that a Plugin might want to know, so it might be
better to pass this kind of info some other way.

In Audiality's FX API, they're just Controls that you may change only
in certain states. So, you switch from CLOSED ("nonexistent") to
OPEN, set these controls, and then climb up to whatever state you
want the plugin to be in.

> //FIXME: how to enumerate errors from this?
> //FIXME: return something better than void *?

If you use the state() thing, you have a *very* simple initialization
for the first state - but you have already established basic
plugin/host communication at that point, so the plugin can return
error code and stuff through the host's event queue.

[...]
> void (*xap_destroy)(void *plugin);

Switch back to state CLOSED. :-)

> /*
> * set_rate: set the sample rate
> *
> * This method may only be called on plugins that are not active.

(Same thing as controls that may only be changed in certain states.)

[...]
> int (*xap_set_rate)(void *plugin, int rate);

[...]
> int (*xap_activate)(void *plugin, int quality);
[...]
> int (*xap_deactivate)(void *plugin);

This is both hairy and incomplete, IMHO. One state() call or
state_up() + state_down() and a simple climbing wrapper in the host
SDK is much cleaner and easier to use in my experience. You never
have hosts calling these functions in the wrong order, and the order
of states is strictly defined. (Something you cannot say about VST
and some other APIs with the same design...)

Note: If there is enough motivation, one might have a plugin hint
        "must climb states", so that plugins that want to optimize
        direct switching between any two states can do so. The host
        SDK wrapper would just check this hint and act accordingly.

[...]
> /*
> * get an event port, XAP_CHANNEL_MASTER for master
> */
> XAP_event_port *(*xap_event_port)(void *plugin, int channel, int
> index);

I don't think it's a good idea to require that the plugin returns a
struct. This would mean that the plugin needs to allocate memory for
it for each call, and it's data that will probably be thrown away
anyway, as soon as the sender has received it.

[...]
> int (*xap_run)(void *plugin, int nsamples, XAP_timestamp now);

I think "now" should be in the host interface struct. Any plugin that
deals with events will have to mess with the host struct, and either
way, "now" changes only once per block and host; never for each
plugin. (It cannot, because plugins would be out of sync with the
host WRT timestamp conversion requests and the like...)

From the API POV, sending now as an argument means another argument
to pass to some of the event handling macros, since they still need
the host struct.

[...]
> /*
> * A XAP host: this provides callbacks for plugins to access
> host-provided * resources. This puts control of things such as
> failures in the hands of * the host, not the plugin.
> */
> struct XAP_host {
> //FIXME: some sort of host ID/version ?
> uint32_t host_api;
Yes!

> //FIXME: how does the host know where an event came from?
> XAP_event_queue *host_queue;

It basically doesn't - but it does (hopefully) know which plugin it
just called, so you can just check this queue after running each
plugin. Or, you just have one internal queue for each Plugin, and
change this field all the time - but that doesn't avoid any queue
checking; it just allows you to do all of it in one place. (Which may
not be a great idea anyway.)

Note that plugins hosting plugins is not an issue. Every host has
it's own host interface. In fact, hosts can have one host interface
per plugin, if desired!

> void *(*host_malloc)(size_t size);
> void (*host_free)(void *ptr);
> void *(*host_realloc)(void *ptr, size_t size);

Yes...

> void (*host_alloc_failure)(void);

Why? You can just return a suitable error code...

> //FIXME: get_buffer(int nsamples) ?
> //FIXME: free_buffer() ?
> //FIXME: get_silent_buffer(int nsamples) ?

Maybe... If you remove the nsamples argument, and assume that buffers
are of the same size as the maximum nsamples for run() (which plugins
must know anyway), these would be very easy to make RT safe. Just
hook them up to the host's audio buffer pool. (Which is a useful
thing to have anyway, because it can reduce cache thrashing
drastically.)

[...]
> /* flags to identify real-time behavior of controls */
> #define XAP_RTFL_NEVER 0x01 /* is never RT safe */

Fine.

> #define XAP_RTFL_MALLOC 0x02 /* allocates/frees memory */
> #define XAP_RTFL_HMALLOC 0x04 /* allocates memory through the host
> */

This should not be optional for normal plugins, IMHO. The host struct
*is* your source of resources - you may not use OS resources directly.

However, driver plugins will obviously have to break this rule, and
it may be useful to tell the host about it.

Speaking of driver plugins, those will also have to tell the host
whether or not they will/can act as "timing masters" for nets by
performing blocking I/O.

> #define XAP_RTFL_HFREE 0x08 /* frees memory through the host */

Well, you can't mix anyway... It's like mixing new/delete with
malloc()/free - only even worse with hosts that have RT memory
managers.

> #define XAP_RTFL_FILEIO 0x10 /* performs file I/O */

Yes. (This is related to the driver plugins issue.)

> #define XAP_RTFL_SLOW 0x20 /* is 'slow' in general */

Well, everything is relative...

I think it would be much more interesting to have a warning hint for
plugins that are significantly nondeterministic in their CPU usage.

Slowness is completely irrelevant as long as your CPU is fast enough.
Nondeterminism is *never* irrelevant in a real time system, except
when you know that the worst case still lets you meet your deadline.

> /* common control labels - a good source of hints to the host! */
> #define XAP_CTRLHINT_VELOCITY "VELOCITY" //voice

Ok. (There *are* real instruments with "channel velocity", but
considering that we're using velocity for note on/off, that's better
implemented as channel pressure.)

> #define XAP_CTRLHINT_AFTERTOUCH "AFTERTOUCH" //voice
> #define XAP_CTRLHINT_CHPRESSURE "CHPRESSURE" //channel

We should not separate these. They're exactly the same thing, only
one is a channel control and the other is a voice control. (Which
indeed *are* incompatible due to differing addressing needs, but
that's a different story.)

(If you actually *want* the two different MIDI messages for this to
end up controlling different things, that's a MIDI converter issue;
not something that goes into the API.)

> #define XAP_CTRLHINT_PITCH "PITCH" //channel,voice

...and the optional "NOTE_PITCH" that no one will ever use, of
course. ;-)

[...]
> #define XAP_CTRLHINT_HOLDPEDAL "HOLDPEDAL" //channel,voice: bool
> #define XAP_CTRLHINT_PORTAMENTO "PORTAMENTO" //channel,voice: bool
> #define XAP_CTRLHINT_SOSTENUTO "SOSTENUTO" //channel,voice: bool
> #define XAP_CTRLHINT_SOFTPEDAL "SOFTPEDAL" //channel,voice: bool
> #define XAP_CTRLHINT_HOLD2PEDAL "HOLD2PEDAL" //channel,voice: bool

Why boolean? (That's more annoying than helpful with MIDI, IMHO...
Real instruments rarely have *truly* boolean pedals and stuff, and
sometimes, you actually want to make use of that. Whether synths
check for anything but "value > 0.5" is another matter.)

[...]
> #define XAP_CTRLHINT_TEMPO "TEMPO" //channel

Are these from the sequencer?

Either way, this makes me realize that just asking the host to
convert timestamps back and forth may not be sufficient. If you have
multiple sequencers, you may have multiple independent timelines -
and there goes your nice and simple per-host idea of musical time...
*heh*

This is not an issue for plugins that are interested only in tempo,
but it *is* an issue if you also want to lock to the timeline that
tempo originates from.

[...]
> /* ramp styles */
> enum XAMP_event_ramp {
> XAP_RAMP_NONE = 0,
> XAP_RAMP_LINEAR = 1,
> };

This should be expressed as a different event type. Reduces event
decoding overhead, and simplifies plugin code. Here's my current list
of events:

typedef enum
{
        XAP_A_NOP = 0, /* Do nothing! */

        /*
         * Control Events.
         * From: Host or Plugin
         * To: Plugin
         */
        XAP_A_CONTROL, /* Change a Channel Control instantly */
        XAP_A_RAMP, /* Ramp a Channel Control linearly */

        XAP_A_VCONTROL, /* Change a Voice Control instantly */
        XAP_A_VRAMP, /* Ramp a Voice Control linearly */

        /*
         * Connection events.
         * From: Host
         * To: Plugin
         */
        XAP_A_CONNECT_IN, /* Connect input Port or Control */
        XAP_A_CONNECT_OUT, /* Connect output Port or Control */
        XAP_A_DISCONNECT_IN, /* Disconnect input Port or Control */
        XAP_A_DISCONNECT_OUT, /* Disconnect output Port or Control */

        /*
         * User Events.
         * From: Plugin
         * To: Plugin
         * For "private" protocols between between Plugins.
         * Parameters and semantics are defined by Plugin authors.
         */
        XAP_A_USER = 256 /* First free User Action */
} XAP_actions;

And now I'm trying to figure out a nice way of supporting something
like splines in a way that allows you to just:

        case XAP_A_RAMP:
        case XAP_A_SPLINE:
                ...code to set up linear ramping...
                break;

The "value" argument will be the target value in both cases, but I'm
open to suggestions for a nice, fast and simple format for the
"control point" argument... (Preferably something that isn't entirely
bound to one single spline formula/algorithm.)

[...]

//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 -'
.- M A I A -------------------------------------------------.
| The Multimedia Application Integration Architecture |
`----------------------------> http://www.linuxdj.com/maia -'
   --- http://olofson.net --- http://www.reologica.se ---


New Message Reply About this list Date view Thread view Subject view Author view Other groups

This archive was generated by hypermail 2b28 : Fri Dec 13 2002 - 15:36:50 EET