[linux-audio-dev] XAP spec - early scribbles

David Olofson david at olofson.net
Wed Feb 5 20:07:00 UTC 2003


On Wednesday 05 February 2003 23.41, Tim Hockin wrote:
> > Well, you know my opinion about what controls plugins.... ;-)
>
> how about:
>
>   * Host
[...]
>   * Control:
[...]
>   * EventQueue

Good.


[...]
> > Yep. The effect of this might be cool enough to deserve a
> > comment;
>
> it is commented in the details section :)

Yeah, I noticed... :-)


> > There will never be "output EventQueues", since you're always
> > sending directly to the connected inputs, as far as plugins are
> > concerned. Whether you're actually sending directly to another
> > plugin, or to a host managed sort/merge proxy queue is pretty
> > irrelevant to the API and plugins, since it's a host
> > implementation thing.
>
> Well, there is an output-queue, it just might be someone else's
> input queue.

Yes, but I think it's more confusing than helpful to drag that in. 
What you *own* is your input Queues. When it comes to outputs, the 
only objects you own are these "XAP_target" structs.


[...normalized and audio...]
> I've always heard it called normailzed, and it jives with what I
> know.
[...]

Yeah, I know - but it's rather confusing when "normalized" in relation 
to *controls* means that out of range values are illegal.


[...]
> > And connecting an audio port means handing it a buffer pointer,
> > among other things? That is, buffer pointers aren't changed
> > frequently, and never silently.
> >
> > I *think* this should do, since it does not conflict with reuse
> > of buffers. Hosts can just figure out optimal buffer usage
> > patters when building nets. Right?
>
> That is how I have envisioned it - does this need special
> annotation?

Not really. Since we're using function calls (rather than shared 
variables), it's rather obvious. I was thinking out loud, and only 
came to the conclusion that this should work. :-)


[...]
> > I'm afraid this won't work for all hosts. There are situations
> > where you might want to run a plugin under a fake copy of the
> > host, in another thread, to perform non RRT safe operations
> > without disrupting the rest of the network.
[...]
>
> How does this apply to the host struct, though?  Do we need to
> change the host struct?  hmmm

The host struct is the plugin's interface to various host provided 
resources. Unfortunately, making these resources thread-safe would 
result in a major performance hit. (Consider the event system, for 
example.)

The easy way to deal with this is to just give plugins another host 
struct when you want to mess with them in another thread. The plugins 
won't be able to tell the difference, unless you want them to.

Of course, you'll have to disconnect any inputs before you start 
messing with that plugin, and if you're going to do something that 
makes the plugin send events, you have to make sure they're not sent 
directly to live Queues in the RT thread.

In short, just detach the plugin from any RT resources, and if you 
need to, hook it up with fakes. Do what you need, and then throw the 
plugin back into the RT network. (Or destroy it, or whatever you're 
up to.)


[...silence<...]
> We can track silence per Port, or just per Plugin (simpler, I
> think).

Simple, but insufficient for any but the simplest plugins. If you have 
multiple outputs, they can be completely independent. Your average 
multichannel/multitimbral synth without integrated mixer will have 
one or more outputs per channel, and you will most likely not have 
output from all of them at all times.


> Tail size is a good indicator - if we know that the plugin before
> the current plugin output silence, then we need only wait for the
> tail on this plugin to end and then it will be silent.  This relies
> on plugins getting their tail-sizes correct. Tail can be 0, finite,
> infinite.  Infinite tails always need processing

Yeah... Although I'm getting the feeling that it's more work for both 
hosts and plugins, than basically just "marking" buffers.

Speaking of which, the new Audiality mixer just has a flag to go with 
each bus accumulation buffer. Instead of clearing the buffers, I just 
set the flag to "silent". The first "unit" (mixer stage or synth 
voice) that hits a silent buffer gets to initialize it and set the 
flag to "in use". Whenever a mixer stage reads from a buffer, it 
checks the flag first, and then decides what to do - which in many 
cases means return instantly from process(). If you're doing 
something that results in no output (ie you're mixing silence into a 
buffer), just don't touch your output buffer. If it's marked as 
silent, it just stays that way.

The disadvantage is that everyone with an output has to be aware of 
this, because a buffer marked as silent is intended to be *ignored*. 
That is, it may (and generally will) contain garbage, which you'll 
have to clear out first, unless you have a replacing version of your 
inner loop.


> We can also do a call and answer style - the host tells the plugin
> that it's feed is silent.  When the plugin has died down and is
> also silent, it responds to the host that it is silent.

I like to think of "silent" as a property of the data. A boolean per 
buffer, basically. (Wherever we'd put that...) For inputs as well as 
outputs.

The FX plugins of Audiality get NULL pointers for silent input, but 
they use their state (multiple RUNNING states... *heh*) to tell the 
host whether or not they generated output for each block. The NULL 
pointers for inputs is nice and easy, and easy enough for plugins to 
fake themselves: Just point any NULL inputs at the host provided 
silent buffer. The output part "works", but only because FX plugins 
can only have one stereo in and one stereo out.


> Then the
> host can hot the bypass switch (is bypass optional?) or remove it
> from the net, or just stop processing it.

No, that won't work. A plugin being silent doesn't mean it's passive, 
or even that it won't start generating output again in a moment, even 
if there's no new input. A long feed-back delay fed with a short 
sound would intermittently generate non-silent buffers for a good 
while, and plugins following it could still have use for the "silence 
flag".

Besides, you cannot safely stop calling a connected plugin. If it has 
inputs directly connected to outputs of other plugins, there's no way 
you can know when to start calling the plugin again, since you won't 
even know about the events sent to it's Queues. What you get is a 
dead plugin draining your event pool. :-)


[...master and channel "queues"...]
> I didn't mean that they are separate queues - the host will ask the
> plugin for an event target for the master and each channel.  It may
> be the same Queue.

Ok.


> Should we define Event Target as a term and use
> it?

Well, it's an actual struct in Audiality. :-)


>   * EventTarget:
>         A tuple of an EventQueue and a cookie.  Plugins have an
> EventTarget for each Control, as well as a master and per-Channel
> EventTarget.

Not quite. Targets aren't really "input objects" in the API, but 
rather two other things:

	* The physical struct that you get filled in or
	  returned when you ask a plugin for one of it's
	  input controls.

	* An abstract object inside a plugin, representing
	  a control output.


In practical terms, something like:

	/* Get target for plug2 input control X */
	XAP_target target = plug2->get_control_input(X);

	/* Connect plug1 output control Y to that. */
	plug1->connect_control_output(Y, target);


and perhaps:

	/* Notify plug2 that it's input X is connected. */
	plug2->connect_control_input(X);


Plugins will most probably keep these in arrays or other structures 
internally, for their output controls, but the get_control_input() 
call will nearly always construct targets on the fly. All it does is 
pick the right Queue and transform the "full adress" of the control 
into something handy for internal decoding; ie a cookie.


> > >   The Host queries the Plugin for EventQueues via the
> > > get_input_queue() method.  In order to allow sharing of an
> >
> > I would prefer calling it get_control_target() or something,
> > given that what you actually get is not a Queue, but rather an
> > "atom" containing a Queue pointer and a cookie. Each of the two
> > fields of this "atom" (XAP_target) is completely useless on it's
> > own, but together, they form the complete address of an input
> > Control.
> >
> > >   Controls may also output Events.  The Host will set up output
> > > Controls with the set_output_queue() method.
> >
> > How about connect_control_output() or similar? Again because
> > queues have very little relevance on this level, and because
> > "connect" is
>
> Adjusted to get/set_event_target().  Good?

Still feels wrong... I have another idea, inspired by what's actually 
going on, as well as this "do we notify inputs after connection?" 
thing:

typedef struct
{
	XAP_queue	*queue;
	unsigned int	cookie;
} XAP_target;

typedef struct
{
	unsigned	class;
	unsigned	bay;
	unsigned	channel;
	unsigned	index;
} XAP_address;

Inputs:
	int open_control_input(XAP_address *a, XAP_target);
	void close_control_input(XAP_address *a);

Outputs:
	int connect_control_output(XAP_address *a, XAP_target *t);
	void disconnect_control_output(XAP_address *a);


Usage:
	XAP_address output = {...};
	XAP_address input = {...};
	XAP_target target;

	/* Connect */
	if(p2->open_control_input(&input, &target) < 0)
		return ERROR;

	if(p1->connect_control_output(&output, &target) < 0)
	{
		p2->close_control_input(&input);
		return ERROR;
	}
	return OK;

	/* Disconnect */
	p1->disconnect_control_output(&output);
	p2->close_control_input(&input);

("this" pointers left out for brevity.)


Looong names those, though... I don't want "event" in them, because 
events are not equivalent with controls; they're just used to operate 
them. Leaving out "control" might cause confusion with audio stuff, 
and dropping the "input" and "output" part would be generally 
confusing.


> > Another thought: Would plugins want to know when their inputs get
> > connected and disconnected?
>
> That is why these are methods - a guaranteed trap for connecting
> ins and outs, just like Ports..

Yeah, but I was thinking about the difference between asking for a 
target from an input and actually connecting it. As you can see 
above, this design makes things quite asymetric, but after throwing 
the close_control_input() counter part in, it looks rather nice, 
IMHO. The point is that both plugins now know when connections are 
made and broken. Without the close_control_input() call, receivers 
would be left in limbo if a connection fails in 
connect_control_output().


[...]
> > More importantly, when do you stop processing events? There are
> > two alternatives:
> >
> > 	1) Process only events that affect audio frames that
> > 	   you're supposed to process during this block.
>
> Which (not obviously) means 0 frames = no work.

Yes - but from a practical POV, why call run() in that case?


> > 	2) Process as many events you can, without skipping
> > 	   into the next block.
>
> Process all events up to and including 'now'.  For a 0 frame run(),
> all timestamps are 'now'.

No, timestamps are still valid, so there's no way to guarantee this. 
All events in the queue that match the current time will be 
processed, but any future events will be left in the queue.

Note that you get this behavior for free with a normal event/audio 
compound loop; it doesn't require any special cases or anything. (In 
fact, *guaranteeing* that there always is a "tail" event beyond the 
end of the block would eliminate the test for ev->next == NULL when 
readin events.)


>  The Plugin must process all events for
> now.

Yes.


> > >   Plugins can assume that VVIDs are at least global per-Plugin.
> > > The host will not activate the same VVID in different Channels
> > > at the same time.
> >
> > Well, as usual, I'd just like to whine some about it being
> > assumed that the sequencer is an integral part of the host. ;-)
> >
> > It could be an event processor or a phrase sequencer you're
> > listening to. Not that you'd care when you're a plugin in the
> > receiving end, but things get weird if you're about to implement
> > a plugin that outputs synth control events, while in the API
> > docs, it appears that only hosts can do such things...
>
> ok, ok - I'll reword it.  How should it be worded?  Assume an
> arpegiator or something that outputs VOICE.  Where does it get
> VVIDs?

Host call, just like allocating events and whatnot.


> Does it have to ask the host to reserve a bunch for it? 
> How?

>From the VVID prototype for Audiality:

/*--------------------------------------------------------------------
	VVID API
--------------------------------------------------------------------*/

typedef Uint32 ADY_vvid;	/* The reference */
typedef	Uint32 ADY_vvid_entry;	/* The object (for receivers only!) */

/*
 * Allocate 'count' Virtual Voice IDs.
 * The return value is the index of the first VVID in the host's
 * array, or a negative value if an error occurred.
 */
ADY_vvid ady_vvid_alloc(int count);

/*
 * Free a range of VVIDs previously allocated with ady_vvid_alloc().
 */
void ady_vvid_free(ADY_vvid first);

/*
 * Get the base address of the VVID table.
 *
 * IMPORTANT:
 *	This may *change* between engine cycles! It is therefore
 *	very important that plugins do not keep their own copies
 *	of the VVID table base address across process() calls.
 */
ADY_vvid_entry *ady_vvid_table(void);


Of course, in XAP they'd be function pointers in the host struct 
instead. (Audiality's "host struct" is still an implicit singleton - 
or "global stuff spread all over the place", if you like. :-)

ady_vvid_table() would probably be better off as a pointer in the 
struct. (Why have a function call? Can't remember why I have one 
here...)


> Good feedback - looking for more - specifically, help me flush out
> the Table of Contents so all the details get in there...

Well, I'm too tired to deal with structured things like that now, so 
I'll have to have another look at it tomorrow... ;-)


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




More information about the Linux-audio-dev mailing list