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

David Olofson david at olofson.net
Fri Dec 13 08:36:01 UTC 2002


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 ---



More information about the Linux-audio-dev mailing list