Re: [linux-audio-dev] back to the API

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

Subject: Re: [linux-audio-dev] back to the API
From: David Olofson (audiality_AT_swipnet.se)
Date: ti loka   19 1999 - 15:57:17 EDT


On Tue, 19 Oct 1999, Paul Barton-Davis wrote:
> an API *has* to include some struct definitions. event and plugin seem
> like the two most likely. a plugin has to be able to inspect events

Yes, and it will do so by explicit code, or macros. The structures
should be carefully designed so that you can safely drop the API
macros if you don't like them, and still sleep well at nights - the
part of the structures that you see in the API will be there in
future implementations. (So, we'd better make damn sure not to get
uggly cludges in here; we'll have to live with it for ages if this
project succeeds.)

> (OK, so you can wrap a void * in macros, but this seems silly), and it
> might want to modify itself. Or do you really want macros for all this ?

No, only where it makes things easier to read, and less bug prone.
*Binary* compatibility is a requirement, so wrapping things just for
the sake of abstraction is quite pointless.

As for "private" structs, this is one way to do it:

-----8<--------------------
struct public_stuff_t {
        .
        .
        .
};
-------------------->8-----
struct private_instance_t {
        struct public_stuff_t public_stuff;
        .
        .
        .
};
-----8<--------------------

Instantiate in a function on the private side. Only one typecast is
needed, and that can be done with an inline function for compile time
type checking.

> >> either way, i figure there should be two OOB mechanisms for events
> >> requiring more space than fits into the union: one would use a void *
> >> member of the union, and would point to space managed entirely by the
> >> plugin (via some allocation/deallocation API that we provide).
> >
> >Major problem; this still has to be implemented. And it has to work
> >without shared memory...
>
> in what sense ? do you mean without thread-shared memory, without
> shmem-like memory, or without shared memory period ?

"Accross networks" would explain it, I think. That is, situations
where *all* data has to be copied to get accross to the reciever.

> if its without
> shared memory period, then i cannot see how the client or plugin API
> can possibly function.

Why? As long as _every part of the protocol_ is defined in the API
spec, there's no real problem - a gateway/proxy would know how to find
the data to copy. My point is just, "Don't make that inherently
impossible or inefficient by design."

> if you're worried about an in-kernel
> implementation, you just mmap some space from/to the kernel. its
> easier to map it into the kernel, i think.

Yes, in there, many things get simpler and faster, although a lot
more dangerous... No bugs allowed. Anyway; not an implementation
problem.

> >> note that events take essentially 3 arguments:
> >>
> >> - what changed (ev_target_id) - this may refer (from a plugin's
> >> point of view) to some C parameter in memory, or it
> >> may not - the engine doesn't care
> >> - how it changed (delta from old value, absolute new value,
> >> increment or decrement from old value)
> >
> >Uhm, this seems to be asking for trouble with automation and editing
> >of automation data... Why deltas and inc/dec? What are plugins
> >allowed to send?
>
> they send events that say:
>
> "XXX has changed by +4"
> "YYY has changed, new value is 13.4554"
> "ZZZ has been incremented"
>
> in each case, XXX, YYY or ZZZ is the target_id of the change, which is
> assigned by the engine when the plugin declares its "parameters", and
> is looked up by the plugin during event processing to decide if it
> really cares.

Well, I believe plug-in code should be as simple as possible, so I
don't see why alternative ways of changing parameters makes sense...

> its really just an editlist, suprise, suprise!

An editlist belongs in the application, and shouldn't be decoded by
the plug-ins, IMO. That's unnecessary code duplication.

> >> - value (interpreted based on the kind of change)
>
> >Whether this is enough depends on what the event system will be used
> >for - and IMO, the design we're working on has too much potential to
> >be restricted to audio for the simple reason that you can't send more
> >than 32 bytes (or whatever) as a single event without extra trouble
> >and overhead. I believe things like a 3D sound FX system for games or
> >movie sound track editing would benefit from larger events... Should
> >we extend the "standard" when such needs become important?
>
> can you give an example of an event from those worlds which needs more
> space ?

Well, to trigger a sound effect in a 3D sound system, you need at
least:

        int soundfx_id;
        float pitch;
        float volume;
        float xpos,ypos,zpos;
        float xspeed,yspeed,zspeed;

However, since we have timestamps, this could be split into multiple
events (which has other advantages), but I think using a channel or
object system would be a *very* good idea in that case. It is needed
for a system of thi kind anyway.

However, we still have strings, and possibly arrays. One solution
would be to define a standard for array access, and then just fill in
the data using as many events as you need.

Bonuses:
        Random access.
        Structure.
        Fixed size events.

Problems:
        More complexity in plug-in code.
        Overhead.
        Sync.

The complexity could be hidden in some "nice" inline func or macro.
The sync problem can be solved by using a transaction terminator
event, or by using the first event as a header.

Sync is not a problem with true real time events within a single
thread - but is *is* for out-of-band events, as all events of a
transaction may not arrive in time... What should the plug-in do
about an unfinished transaction? If it's in the middle of throwing
data into it's internal variables, we're screwed; the plug-in can't
process!

Handle the sync problem in the interface between threads? If it can
be done nicely, and there are no more nasty surprises, fixed size
events may be the way to go, after all. It *is* indeed simple and
efficient in the average case, and that's a very strog argument _for_
going that way.

> >So, you have to force every event source to use a unique kind of
> >event? Ok, it's rather silly to connect two sources of the same kind
> >of events to the same input - it should probably just not be allowed.
>
> nope. two objects can generate the same kind of event. they would end
> up in two distinct event sources, and be merged by the engine into a
> single event list for presentation at a destination.

Well, as long as it's defined in the spec that all events with the
same timestamp from one source are kept together, it's safe. (IIRC,
this one started out with "is it safe to split events?")

> >Good point, but that's not exactly what I meant. How could you
> >possibly give the engine anything but a void * to you closure, if
> >it's not 100% defined in the API, or 100% private to the plugin?
> >Further, how do you optimize engines without breaking plugin binary
> >compatibility, if you have "private" implementation stuff in the API?
>
> you define a plugin struct :
>
> struct plugin {
> ... stuff the engine cares about ...
> };
>
> then in the plugin code, in the
> "run-me-when-instantiating-an-instance" function, you do this:
>
> struct my_kind_of_plugin {
> struct plugin std;
> ... stuff this plugin cares about ...
> };
>
> then just allocate a "my_kind_of_plugin", set up the fields that the
> engine needs to see, and send it up to the engine as a "struct
> plugin".

*hehe* I guess I should have read the whole post first... :-)

> yes, yes, this is entirely going back on my comments about header
> structs, but this is one case where it seems right.

Agreed. And no unsexy macros needed; this is a low level, hardcare,
high performance API, so there's not much we want to hide anyway. If
the engine needs some private stuff as well, this can be taken one
more step (but that's pushing it...):

In the engine:
        struct private_plugin_ext {
                .
                .
                .
        };

        struct private_plugin {
                struct private_plugin_ext ext;
                struct plugin std;
        };

        struct plugin *eng_create_plugin(int size)
        {
                struct private_plugin *p;
                p = malloc( sizeof(struct plugin) +
                                sizeof(struct private_plugin_ext)
                        );
                return &(p->std);
        }

In the plug-in init code:
        my_plugin_instance = eng_create_plugin (
                                sizeof(struct my_kind_of_plugin)
                                                );

...or something... *urgh* I don't like it, but at least it's
possible, if it's really needed. (BTW, is it compiler alignment safe
the way I've done it above? One not very nice problem that may occur
with this kind of code..)

//David

 ·A·U·D·I·A·L·I·T·Y· P r o f e s s i o n a l L i n u x A u d i o
- - ------------------------------------------------------------- - -
    ·Rock Solid David Olofson:
    ·Low Latency www.angelfire.com/or/audiality ·Audio Hacker
    ·Plug-Ins audiality_AT_swipnet.se ·Linux Advocate
    ·Open Source ·Singer/Composer


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

This archive was generated by hypermail 2b28 : pe maalis 10 2000 - 07:27:59 EST