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

David Olofson david at olofson.net
Fri Dec 13 17:07:00 UTC 2002


On Friday 13 December 2002 20.24, 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.)
>
> C99 is here, use it :)

I'd still like to see a list of fully compliant compilers. IIRC, I've 
received a few "bug reports" for Kobo Deluxe as a result of 
accidentally depending on some C99 features... (Then again, that runs 
on some 10 platforms or so, so what can you expect? :-)


> > > /*
> > >  * 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?)
>
> This is the current XAP API version.  Serial is the plugin version.

Doh! Sorry. :-)

I didn't even consider the thought that the Plugins don't have *real* 
version numbers. I think they should. You have the macro anyway, so 
why not use it?


> > > struct XAP_descriptor {
> >
> > [...]
> >
> > > 	uint32_t xap_id;
> >
> > Why the xap_ prefix on all the fields in the struct?
>
> Old habit - prefixing struct fields with soemthing to identify the
> struct. Makes it trivial to search for all uses of ->xap_label as
> opposed to ->label (which exists in several structs).  If people
> REALLY hate it, I can let it go.  I try to always do that in
> exported code.

Well... It reminds way too much of hungarian notation, IMHO. ;-)


> > I think this belongs in version, as the micro field. Hosts should
>
> Version is a string - it could be "1.0" or "1.0 rc26" or "foobar". 
> Host can not interpret a string.  Serial is for multiple builds of
> the same plugin. It makes life easier - think of it as a build
> number for binaries, maybe?

Yes (se above) - I just think that a real m.m.m version number would 
be more useful than just an integer. The low 16 bits (yes *16*! :-) 
would be the "build number", while the higher two fields are 
interpreted as "must be same, or we have an incompatible plugin".

One could even use the common interpretation used for version numbers 
these days;
	different major ==> no interface compatibility!
	different minor ==> higher has extra, but not modified features
	different micro ==> higher is newer; no interface changes

But that might be overcomplicating things a bit, dunno. I can see how 
it can be pretty useful when moving projects around, though.


> > > 	const char *xap_label;
> >
> > Why is this needed? (There are unique IDs, I mean...)
>
> LADSPA compat, and it is handy.  'name' may change.  It may be
> localized. 'label' is never localized.

Ok.


> > > 	const char *xap_version;
> >
> > This sort of doubles the 32 bit version field, so I'm not sure
>
> See above - is it stil unclear?

Nope. I just took for granted that there was a "real" version field 
in the plugin. :-)


> > > 	/* 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
>
> Which is why I threw in master controls - I don't want to have
> channel 0 always be master.  The channel numberspace is reserved
> for channels.

Right.


> > > 	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?
>
> Audio ports - master audio ports.

And what are those, really? Isn't that the same thing as having a 
bunch of Audio Ports somewhere? This looks redundant to me.


[...]
> > > 	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.
>
> As discussed before - setting the rate is something you ought not
> do often.

Well, that can apply to quite a few other things as well... To me, 
rate looks like just another control, that is potentially not RT 
safe, and may require memory allocation.


> > > 	//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
>
> I gave thought to the state model, and while it seems nice, there
> are only two states that are really needed: INACTIVE and ACTIVE. 
> Before you are instantiated, you have no state.  After you are
> instantiated you are INACTIVE.  activate() makes you ACTIVE. 
> deactivate() makes you INACTIVE. Are there really other states that
> are meaningful?

I'm not totally sure about other uses, but you don't get away with 
only that for driver plugins and the like, or with plugins that need 
to load data from disk. You will need to be explicit about what such 
plugins should do, and when.

For example, audio driver plugins will need to initialize the driver 
and put it in pause mode, or there is a great chance the playback 
will be delayed by a rather significant (and random) amount of time. 
This is mostly a "minor" driver problem with certain sound cards, but 
I think it might matter a whole lot if you happen to have some 
external device that is supposed to sync before you start playback. 
This is the only real issue I could think of right now...

Reverb plugins will have to know when to kill the tail, and when to 
just freeze, but I guess you don't need a state for that, but rather 
an event equivalent to "stop all notes" or something...

Either way, it's important to note that (at least the way I see it) 
the engine does not stop just because you stop the sequencer. For 
there to be any point in that, most plugins should just keep doing 
what they're doing and ignore that the sequencer stopped. Plugins 
that are interested in musical time or other timeline relative things 
will get a nice TRANSPORT_STOP event or something.


> > > 	 * 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.)
>
> ALL plugins must support set_rate.  The way I see it, controls are
> optional. There is no control that every plugin HAS to define. 
> OPtional things are controls.  It is a nice way of indicating I
> do/don't support 'feature'. Non-optional stuff ought not be
> controls, IMHO.

Yes, that's a good point.


> > > 	int (*xap_activate)(void *plugin, int quality);
> > > 	int (*xap_deactivate)(void *plugin);
> >
> > This is both hairy and incomplete, IMHO. One state() call or
>
> What's missing?  I thought about state() and decided this was
> complete.  Did I miss something?

prepare()/unprepare(), maybe, but I'm not sure. Can you expand on the 
ACTIVE and INACTIVE states, and the transitions. (What you may and 
may not do, what you're supposed to do, etc.)


> > have hosts calling these functions in the wrong order, and the
> > order
>
> Which is why I want two states.  It's easy to get right :)

Indeed - provided you only need two states. :-)

Most other APIs seem to have more states, and there seems to be for a 
reason. I'll dig through the VST and EASI docs again... (Yeah, EASI - 
I think wrapping drivers as plugins can be handy. After all, the only 
difference from normal plugins is that they perform I/O, and that 
some of them are blocking on it.)


> > > 	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
>
> ok, I'll pass a pointer to a struct to fill.  I like that better,
> actually.

Leaves allocation to the caller. A local variable will probably be 
used most of the time anyway. (The data is to be passed on to another 
plugin right away, and then the host doesn't have much use for it.)


> > 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...)
>
> What about buffer splitting?  You then force the host to have
> separate host structs for plugins?

Well, you could have a separate host struct for the subnet (which may 
be a single plugin), or you could just have you own internal "now", 
and just write the appropriate value into the host struct when 
jumping into a different subnet. This is all callback based and no 
threads (or rather, one host struct per thread), so there are no sync 
issues or anything.

Either way, this affects only "advanced" hosts (ie ones that want to 
handle feedback loops in a concistent way), and in's easy enough to 
deal with. The alternative would be to have an extra argument that 
has to be passed around inside plugins, and 1) that affects every 
plugin author, and 2) there will (hopefully) be many more plugins 
than "feedback enabled" hosts.


> > > struct XAP_host {
> > > 	//FIXME: some sort of host ID/version ?
> > > 	uint32_t host_api;
> >
> > Yes!
>
> just to clarify - host_api is the XAP_VERSION the host understands.

Yep, that's ok.


>  Do you think we also need a per-host identifier?  Is a string good
> enough? (I don't want to have to manage host IDs and plugin IDs :)

Well, let's put it like this: I *really* hope we won't have to do the 
VST thing and have plugins adapting to the bugs and quirks of the 
most popular hosts! :-)

Seriously, why would a plugin want to know anything but the XAP API 
version the host is using? (Unless it's a call-home trojan plugin 
that spreads like a virus to test hosts without people knowing about 
it. ;-)


> > > 	//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
>
> What if we have a host->get_event_port(..., XAP_event_port
> *evport); The host can then pass the event port it wants to the
> plugin with a cookie that it can use to ID a plugin (the plugin
> address or something else it wants).  It's elegant - it works the
> same both ways.

Yes. (I was thinking "cookie", but didn't get any further...)


BTW, how about calling that queue + cookie struct XAP_event_target or 
just XAP_target (I prefer short names :-) or something instead? 
Calling the actual struct anything with "port" in it is both 
confusing and incorrect, IMHO, since it's not a port in itself, and 
usually describes a control rather than a full port. It's just a 
reference to a queue, along with a "receiver's address tag" for 
events.


> > > 	void (*host_alloc_failure)(void);
> >
> > Why? You can just return a suitable error code...
>
> What if the host has some fallback actions?
> * Plugin calls some form of allocation NOT through the host
> * allocation fails
> * plugin call host->alloc_failed()
> * host can abort(), or free stuff, or sleep for a while, or
> whatever is appropriate.
> * host returns a code to plugin: 'try again' or 'give up'.
>
> It gives the host write a chance to trap a memory allocation
> failure and do something useful but consistent with it (print
> __FILE__:__LINE__ or whatever it does).

Yeah, that's what I was thinking actually, but I'm not sure it's 
worth the effort. Maybe it is, though - and it's just a call and and 
enum anyway.

But why not do it right, and just call it host->failure() or 
something, and have an enum argument describing what kind of problem 
you have? Then you could report I/O errors and stuff as well...

Or more interestingly; you could use *this* instead of the 
refill_pool() callback that MAIA and Audiality use when the event 
pool is empty. (The host can try to malloc() more events, or request 
some from another engine thread or something; that's why the call is 
there at all.)

Also applies to audio block allocation. If you can tell the host 
exactly what you failed to get, the host actually has a chance of 
fixing it - and only one "panic handling" entry is needed.


> > > #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.
>
> Well, some plugins will want to use library routines that use
> allocators other than host->malloc.  If a control does this, it
> should flag itself as RTFL_MALLOC.

Yeah, you're right... Not too different from doing file I/O and the 
like, though; you're talking to the outside world, which usually is a 
lot less RT than the host. Use the same flag for that, or rename this 
one? Both mean unbounded worst case latencies, but file I/O is 
probably more likely to be non-deterministic.


BTW, how about plugins creating threads? Linuxsampler as a plugin 
would be one example. Audiality loading and rendering sounds without 
stalling the host or stepping out of the net would be another.

On a related matter, I had this idea of providing a simple host 
service that lets you run a function in a background thread, and get 
an event from the host when it returns. (That would do the trick for 
Audiality, but maybe not for Linuxsampler; not sure.)

One might argue that it's pointless to try and implement a threading 
API, but there really aren't all that many sensible things a plugin 
can do with threads. You're not allowed to block in the plugin, so 
most sync constructs are either useless, or of limited use. The host 
thread cannot be woken up by worker threads, but it *is* running at 
regular intervals. Killing threads is evil, and you can't wait for 
them (blocking), so all you need is a way to start them - and that's 
basically that "do this in another thread" call. The rest can be done 
by sending events back and forth, through a thread safe gateway 
maintaned by the host, and/or through lock-free FIFOs between the 
plugin and it's worker thread.

I'll think some more about this, and see if I can come up with 
something dead simple that does the job.

Oh, and this can be in the host SDK lib as well, of course.


> HMALLOC can be noticed by smart
> hosts and if they support an RT-safe host->malloc, ignored.

Yes.


> > Speaking of driver plugins, those will also have to tell the host
>
> driver plugins?

Wrappers for ALSA, JACK or whatever.


[...]
> > > #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.
>
> That's really what SLOW meant - it may run in non-deterministic
> time.  but SLOW is shorter than RTFL_NONDETERMINISTIC :)

I see. :-)


>  Suggest
> alternative name?

Well... Only acronyms, and those should be avoided, of course.


[...]
> > > #define XAP_CTRLHINT_PITCH		"PITCH"		//channel,voice
> >
> > ...and the optional "NOTE_PITCH" that no one will ever use, of
> > course. ;-)
>
> I've completey ignored the pitch thread until I had time to digest
> it :)

I don't blame you...! :-)


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