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