/***************************************************** OSPI 1.0 MAIN INTERFACE FILE This file is subject to OSPI license, that can be found under the following URL: http://www.yohng.com/ospi/ospilicense.txt Copyright (C)2003 by George Yohng OSPI is universal audio plugin standard Further information can be found at: http://www.yohng.com/ http://www.yohng.com/ospi/ *****************************************************/ /* NOTE: for now comments are written and never read back, they may contain mess */ /* TODO: add handling of patch/controller/NRPN names for MIDI, separately per port/per channel TODO: check implementation of std storage/retrieval */ #ifndef OSPI_H_INCLUDED #define OSPI_H_INCLUDED // high byte=version, low byte=subversion // interfaces across subversions should be compatible // if some major incompatibility is introduced, then major byte // is increased #define OSPI_ISSUE_ID 0x0100 #ifdef __GNUC__ typedef long long int64; #else typedef __int64 int64; #endif typedef signed int int32; typedef signed short int16; #pragma pack(1) /* Plugin is required to support either floating point processing, or 32-bit fixedpoint (the recommendation is to support both). Plugin is not required to support 16-bit audio. */ /* All buffers are of the same structure and concept - sampletype **aud_in,and sampletype **aud_out pointer arrays are passed to process function along with step parameter "step" parameter to processxxx() function is to control interleaving. 1st sample should be at buf[0], 2nd - buf[step], 3rd - buf[step*2] and so on. Normally, it just means that plugin inner loop does buf+=step instead of buf++. Note, that buf pointers should not be modified during execution of processxxx() functions. processxxx() function should copy buffer pointer array for internal usage */ /* floating point processing plugin is required to handle at least -4.0f...4.0f input and output. "What means `to handle N-bit output`?" - it means, that due to internal precision, plugin should not saturate probable output at levels less or equal to +12dB, which basically is -4.0..4.0 range. The recommendation is - to handle any possible input/output range. If it is possible to develop plugin which handles any range with no reasonable precision or performance losses, then it's recommended to employ one. Well, I personally think, that it is stupid to pipe audio in 64bits floating point, so thus there is a single precision structure only :) */ /* There is a performance issue with 32/16 fixedpoint processings - as all major parameters are float and automation is based on floating point calculations, while sample values can be 32/16 bit (for process16 and process32 functions). Thus, parameter conversion is needed in order to e.g. multiply integer sample value by floating point gain. Well, plugins are required to implement automation, but are NOT required to implement sliding automation. However it is strongly recommended to implement sliding sample-wise automation as well for essential parameters. Otherwise, parameters may be cached in intermediate integer variables to work faster, and do not care about sliding automation (within automation events, assume "velocity" and "accel" to be always zero). For example, Reverb's "room size" parameter do not need to employ sliding automation. There is unclear point - how will host know that parameter does not support sliding automation? Well, for now, it's recording's engineer responsibility to know which parameters support sliding automation and which not, so to place proper envelopes. TODO: add somehow query mechanism for host to find out, whether plugin's param can support sliding automation and in which modes of operation (f,32,16) it is supported (or unsupported). Plugin should receive automation event on each automation envelope knee, and repeated sliding automation events in the beginning of each block between two knee points. If plugins are not about to support sliding automation events, then plugin may assume velocity=accel=0 for all automation events (treat as normal automation events). */ /* this-endian (endian is one of current platform) 24-bit processing ( 0dB, 1.0 = 2^^24 ) ( +12dB, 4.0 = 2^^26 ) ( +48dB, 255.9 = 2^^32-1 ) (this is analogous to 8:24 fixedpoint processing) additional 8 bit (48dB) - a headroom for intermediates, plugin should handle correctly as large input value as possible, but should be able to handle at least of 12dB of input (should be able to handle at least 26-bit on input/output). Plugins are required NOT to clip signal, unless this is necessary for the algorithm. For example, EQ plugin can output anything up to full 32-bit value (for example, peaking EQ filter with +8dB on some freq with 24-bit 0dB input may exceed 0dB and go up to 48dB) If ultimate overflow occurs - it's desirable, that plugin would do a saturation (not wraparound). In ideal case, plugin should not saturate lower output levels than 48dB, but sometimes such requirement would degrade algorithm precision, introduce artifacts, or degrade performance. Thus, this is just a *recommendation* to handle as much as possible, although THE REQUIREMENT is to handle just up to +12dB (26 bits). It's recording engineer's and host application responsibility (NOT the plugin writer's one) to control that intermediate signal levels passed to plugin never exceed 12dB. If plugin is crucial for quality/precision - then in order to do fixed point processing, input may be pre-shifted by 6 bits (256/4 = 48dB-12dB), and in the output cascade saturated and scaled back - this is where 12dB may occur. Plugin is required to handle correctly (without saturation) 26bits of input/output. The recommendation is - to handle any possible input/output range. If it is possible to develop plugin which handles any range with no reasonable precision or performance losses, then it's recommended to employ one. */ /* this-endian (endian is one of current platform) standard this-endian 16-bit processing normally used for lo-fi hosts such as winamp and media player, plugin normally is used alone in the chain. winamp/media-player to OSPI wrapper should be able to recognize and use floating point and 32-bit fixed plugins (buffer conversion). */ struct osp_rect { int x,y,w,h; }; struct _osp_aux { enum {UNKNOWN,DUMMY,PRESET,PRESET_END,MIDI,AUTOMATION_INT,AUTOMATION_FLOAT, BINARY,TEXTMARK,BPM_CHANGE,BYPASS_SET,SYNC}; // custom events should have type<0 // TEXTMARK - is to insert marks into e.g. preset bundle file // (e.g., textmark comes before PRESET event) // // TEXTMARK uses osp_aux_char variable size structure // // BPM_CHANGE uses osp_aux_float // BYPASS_SET uses osp_aux_int // PRESET - indicates start of preset // PRESET_END - indicates end of preset // PRESET itself is dummy event to indicate starting of preset and // cannot contain data // between PRESET and PRESET_END there is a data for load() or processxxx() // to handle. PRESET_END event is just to recognize, that all other stream // events are not related to this preset, and any preset loading function // should not read past PRESET_END // process().samplestamp <= samplestamp < process().samplestamp + sz // // if samplestamp < process().samplestamp then event should be handled // right in the beginning of process() function // // if samplestamp >= process().samplestamp+sz - then event should be // executed right after all other processing is done. // // set to -1 when not used // // // stored (1 or 0) - means event is stored in a timeline, and could // be retrieved anytime from arbitrary position int64 samplestamp; int type,size; int stored; }; struct osp_aux : public _osp_aux { unsigned char data[1]; }; struct osp_aux_midi : public _osp_aux { /* event types */ enum {UNKNOWN,NOTEON,NOTEOFF,KEYAFT,CONTROL,PATCH,CHANAFT,WHEEL,RPN,NRPN, SHORTMSG,SYSX,TEXT,LYRIC,BINARY,OTHER=0x1000}; int port; // -1 = not used, for MIDI multiport plugins int chan; // -1 = not used, otherwise midi channel int type; int p1,p2,p3,p4; // 4 parameters unsigned char data[1]; // data for SYSX, TEXT, LYRIC, BINARY // size of this stuff should be stored in p1 }; // for general purpose events, like BPM_CHANGE,TEXT,etc... struct osp_aux_int : public _osp_aux { int i[1]; }; struct osp_aux_float : public _osp_aux { float f[1]; }; // in case of string, should have zero terminating character struct osp_aux_char : public _osp_aux { char c[1]; }; enum { OSP_SYNC_EXT = 1862340480 // divides by most of numbers that could be // used as a part of measure }; // syncronize to midi clock, timestamp value should be taken; // if timestamp not used - then block start is assumed // SHOULD NEVER HAPPEN WITH NO TIMESTAMP IN THE MIDDLE OF QUEUE struct osp_aux_sync : public _osp_aux { // if flag is set, then there is data for enum {MBT=1,EXT=2,A=4,B=8,KEY=16}; int flags; int m/*measures*/,b/*beats*/,t/*1/960 of beat*/; int extb,ext; // OSPI EXT format: absolute number of beat, // offset within beat // (in EXT units (1/OSP_SYNC_EXT)) int mA,mB; // for A/B meter (for example, waltz is 3/4) // a also means beats in measure int key; // base tune key, // &0x1000 for minor, &0xFF%12 - for relative key }; // AUTOMATION: on automation event, plugin is required to update parameter // immediately, however this is not clear when to update "sliding" automation // (floating parameters, velocity/accel). // DRAFT SOLUTION: Sliding automation is valid only // till the end of current processing block. Host should take care of re-sending // sliding automation events during next call to processxxx() // Plugin should update parameter value in the end of processxxx() call in // order to reflect changes after sliding automation, but when processxxx() // will be called next time - no sliding will occur initially - parameter will // remain constant until next sliding event. // Again - that means, that sliding is only valid until the end of block. // And - in the end of block, parameter values should be set to those // calculated within inner loop with use of sliding. Next block will start // with new parameter value (which was left after sliding in the end of last // block), but will not slide until sliding event within that block. // Of course, after sliding automation event, there could be normal automation // event (with velocity=0, accel=0) - this of course, will override parameter // value. struct osp_aux_automation : public _osp_aux { int paramno; }; struct osp_aux_automation_int : public osp_aux_automation { int value; // for bool/enum parameters }; struct osp_aux_automation_float : public osp_aux_automation { float value; // for float parameters - initial value float velocity; // step/sample - SLIDING AUTOMATION float accel; // velocity step/sample - SLIDING AUTOMATION }; #pragma pack() /* use osp_aux_init class to initialize aux struct for example: osp_aux_init evt; ... a->put */ template struct osp_aux_init : public T { osp_aux_init() { type=_type; size=sizeof(T); samplestamp=-1; stored=0; } }; class osp_aux_i { public: virtual ~osp_aux_i() {} // when buf==NULL, only sz is returned // all functions return 1 on success and 0 on failure (e.g. end of stream) // sz is output param only here, caller should ensure that buf // has enough space to store data (by doing pre-call with buf==0 or // calling peekx function) virtual int peek(int64 *samplestamp,_osp_aux *buf,int *sz) =0; // returns generic _osp_aux data (samplestamp, event type and size) virtual int peekx(_osp_aux *buf) =0; virtual int next() =0;/* first retrieve with peek, then shift to next with next(), returns 0 when no next */ /* size is taken from buf->size, inserts event into event queue according to timestamp */ virtual int put(_osp_aux *buf) =0; // if samplestamp is used, then stored events should be sorted by // samplestamp. this is REQUIREMENT - incoming events should be // sorted by samplestamp, // osp_auxext_i should use stamp automatically and insert into right // place (based on current position), and if stamp is -1 - then it // should use current point and insert into current position // after insertion, pointer goes AFTER inserted event (to next one), // so that new insertion will be correct }; // random access aux, planned for reading on-track events (stored events) // random access aux queues should NOT contain events without timestamp class osp_auxext_i : osp_aux_i { public: enum {START=0,MID=-1,END=-2}; virtual int prev() =0;/* previous, returns 0 when no previous */ virtual int seek(int64 stamp) =0;/* seeks to first event with this or larger samplestamp, START/MID/END should also work */ virtual int isat(int where) =0; /* check if pointer is at START/MID/END, no other values allowed */ virtual int remove() =0; // removes current event }; class osp_plugin_i; class osp_host_i { public: virtual ~osp_host_i() {} // notification to host that plugin is plugged and is ready to use virtual int plug(osp_plugin_i *plugin) =0; // notification to host that plugin is unplugged and can be deallocated virtual int unplug(osp_plugin_i *plugin) =0; ///// The following notification functions should return as soon as possible, ///// as they may be called from inner loop. // notification to host, that plugin has changed number of ports, // number of parameters, supported formats, rates, etc... virtual int struc_update(osp_plugin_i *plugin) =0; // plugin with midi ports has changed names of patches, banks, // controller names, etc... virtual int midi_update(osp_plugin_i *plugin) =0; // UI updation should be called if any of parameters was side-affected // within plugin thread, or due to passed events (e.g., due to changes // of one param, other one was changed), and not by explicit // call to paramint or paramfloat or explicit event to change param // which was requested to change // // exception is, that if any parameter state was changed, // (such as enabled/disabled) - ui_update should be called as well // // if several parameters were changed, it's ok to call ui_update once // in the end // // e.g., load() function should not cause ui_update, because it was // expected to do update // // this function SHOULD NOT block execution - should be executed as fast // as possible, as it may occur in the end of process function // virtual int ui_update(osp_plugin_i *plugin) =0; // for extensions // results: >0 - success, <=0 = failure, 0=unsupported virtual int extension(const char *name,void *pvt) =0; }; class osp_factory_i; /* SEQUENCE OF PLUGIN USAGE ------------------------ osp_plugin_i *plugin; plugin = factory->create(); supportflags() can be called anytime plugin->host(&myhost); extension() can be called from this point and further // query number of parameters, etc... // save/load query plugin->supportnports(type, n) // optional, default=plugin specific plugin->nports(type, n); // xxx_IN should be set FIRST query plugin->supportrate(rate) // optional, default=plugin specific plugin->rate(rate); // normally 44100 plugin->setop(REALTIME/MIXDOWN); // optional, default=plugin specific latency() buftype() ----- ...work with param... bufsz() bpm() // optional - default=plugin specific, typically 120 bypass()/processxxx() ...work with param... delete plugin; */ class osp_plugin_i { public: virtual ~osp_plugin_i() {} /* port types */ /* plugin should have at least one aux port in order to support "enveloping" automation */ enum { AUD_IN,AUD_OUT,MIDI_IN,MIDI_OUT, PORTTYPES }; // number of port types /* buftype - preferred buffer type plugin should be able to handle both, but may have more optimal processing for either interleaved or non-interleaved buffers normally, host won't use this, and neither VST (non-intlv) nor DX (interleaved) wrappers won't ask plugin which buffer it wants. but in the future, there could be such wrappers organized ANYBUF = don't care value */ enum { ANYBUF, INTERLEAVED, NONINTERLEAVED }; // mask for plugin native implementation flags // NATIVE_xxx - plugin implements // EMULATED_xxx - plugin emulates through other bitdepth // not set - plugin doesn't implement enum { NATIVE_FLOAT = 1, NATIVE_16BIT = 2, NATIVE_32BIT = 4, EMULATED_FLOAT = 8, EMULATED_16BIT = 16, EMULATED_32BIT = 32, SUPPORT_FLOAT = NATIVE_FLOAT | EMULATED_FLOAT, SUPPORT_16BIT = NATIVE_16BIT | EMULATED_16BIT, SUPPORT_32BIT = NATIVE_32BIT | EMULATED_32BIT, }; /* getop/setop */ enum { REALTIME,MIXDOWN }; enum { PARAMFLOAT=0x1000,PARAMINT=0x2000 }; /* parameter types */ enum { NULLPARAM =0x00, NUMBERF =0x00 | PARAMFLOAT, NUMBERI =0x00 | PARAMINT, CHECK =0x01 | PARAMINT, ENUM =0x02 | PARAMINT, EVENT =0x03 | PARAMINT, VOLUME =0x04 | PARAMFLOAT, PAN =0x05 | PARAMFLOAT, VU =0x06 | PARAMFLOAT, FREQUENCY =0x07 | PARAMFLOAT, PERCENT =0x08 | PARAMFLOAT, Q =0x09 | PARAMFLOAT, DELAY =0x0A | PARAMFLOAT, DB =0x0B | PARAMFLOAT, DATA =0xFF | PARAMFLOAT}; /* volume: 0 = -inf db, 1.0f = 0dB dB: stores value in decibels percent: 0=0%, 100=100% */ // scales for parameters enum { NULLSCALE, LINEAR, LOGARITHMIC, SQUARED, CURVE, INVERSE_LINEAR, INVERSE_LOGARITHMIC, INVERSE_SQUARED, INVERSE_CURVE }; // data - is float parameter, which you handle yourself // and do not rely on any graphical representation // HARD_UPPER and HARD_LOWER - are real limits, UPPER/LOWER - are // suggested limits for displaying enum { NULLLIMIT, UPPER, LOWER, HARD_UPPER, HARD_LOWER }; // NOAUTO - no automation supported // ORDER1 - only velocity is taken in account // ORDER2 - both velocity and accel are supported enum { NOAUTO, ORDER1, ORDER2 }; // return pointer to plugin factory virtual osp_factory_i *factory() =0; // plug into host (host should call this function right after // plugin is created) virtual void host(osp_host_i *v) =0; // latency in samples // -1 - if not applicable (hmm, how could it be not applicable?), // but normally plugins should return 0 virtual int latency() =0; // load() is equal to processxxx() calling with those events in aux // port, and should read events from the stream until PRESET_END event // // when processxxx() sees next event to be PRESET, it may // may call load() and rely on loading via this way. Note, that if it calls // load(), it should not remove PRESET event from the stream (so that // load() captures PRESET event as well // virtual int load(osp_aux_i *v) =0; // save() should put PRESET event first, then some other events, needed to // initialize plugin, then PRESET_END event virtual int save(osp_aux_i *v) =0; // realtime/mixdown virtual int setop(int op) =0; virtual int getop() =0; virtual int nparams() =0; virtual int paramtype(int paramno) =0; // parameters may be grouped virtual int paramgroupid(int paramno) = 0; virtual const char *groupname(int groupid) = 0; virtual int groupheight(int groupid) = 0; virtual void groupheight(int groupid,int newheight) = 0; /* groupheight < -1 // group is collapsed, height is known (invert sign) groupheight = -1 // group is collapsed, height should be recalculated groupheight = 0 // group is expanded, height should be calced and set groupheight > 0 // group is expanded, height is known */ // get param ID by textual alias, -1 = not found virtual int unalias(const char *alias) =0; // depending on other parameter values, // plugin may disable/hide param in GUI // enabled= 1 - ok // enabled= 0 - disabled // enabled=-1 - invisible virtual int paramenabled(int paramno) =0; virtual const char *paramname(int paramno) =0; // query list for enum parameters // pointer to static string is returned virtual const char *paramenum(int paramno,int enumnr) =0; // fot float parameters virtual float paramlimit(int paramno,int limtype) =0; // granularity - is 1/number_of_steps in graphic control, when // controlled by keyboard // should not affect internal data representation virtual float paramgran(int paramno) =0; // scale - is scale type for the control virtual int paramscale(int paramno) =0; // if paramscale is curve, then this is // curve non-linearity coefficient for parameter scaling // all other parms should return 1 virtual float paramcurve(int paramno) =0; // for general purpose parameters // (vu - returns maximum vu, to reset - set vu parm to 0) virtual float paramvalue(int paramno) =0; virtual void paramvalue(int paramno,float val) =0; // for bool/enum/event // (no query for event) virtual int paramint(int paramno) =0; // set to 1 to pass event virtual void paramint(int paramno,int val) =0; // get param automation supported type virtual int paramautotype(int paramno) =0; virtual int nports(int type) = 0; // set number of ports // should be called before processxxx() virtual void nports(int type,int n) =0; // check if given number of ports is supported // returns suggested number of paired ports // e.g., if type==AUD_IN, then returns suggested // number of AUD_OUT ports, and if type==AUD_OUT, then // suggested number of AUD_IN // // if -1 is returned - then number of ports is supported // but no suggestion could be made virtual int supportnports(int type,int n) =0; // check if plugin supports n_in -> n_out transforms // type should be AUD_IN or MIDI_IN virtual int supporttransform(int type,int n_in,int n_out) =0; // if ports are grouped (e.g. L/R stereo), returns next port, // otherwise returns -1 (single port or port chain reached its end) virtual int nextport(int type,int portno) = 0; /* check if sampling rate is supported */ virtual int supportrate(float rate) =0; /* check support flags (NATIVE_xxx/EMULATED_xxx) */ virtual int supportflags() =0; /* get plugin's preferred buffer type (interleaved/noninterleaved/dont_care) preferred buffer type */ virtual int buftype() =0; /* get/set current sampling rate */ virtual float rate() =0; virtual void rate(float _rate) =0; /* get/set current bpm */ virtual float bpm() =0; virtual void bpm(float _bpm) =0; // notification, that plugin will work in a thread with chunk size // not larger than that // for example - in ASIO environments, this should be set to buffer // size, and afterwards processxxx() should not be called with buffers // exceeding this size // // buffer size can be changed (notification via call bufsz(int) ) for // mixdown purposes // virtual void bufsz(int sz) =0; virtual int bufsz() =0; // resets the plugin. For example, reverb should generate no tail // after this function is called virtual void reset() =0; // set bypass mode 0/1 // if bypass mode is set - then no audio processing should be done, // and CPU usage of plugin should be minimal during bypass mode, // plugin is not required to handle note on/off, but should handle all // automation (except sliding automation) // plugin should be able to load/save presets in bypass mode // IMPORTANT NOTE: // in bypass mode, plugin should return data // (should fill output buffer) // virtual void bypass(int set) =0; // get bypass mode virtual int bypass() =0; // standard extensions (for example, for custom wrappers), e.g. // vst wrapper to pass unique plugin ID and additional VST parameters // and for DX wrapper to pass GUID/UUID of plugin, etc... // // results: >0 - success, <=0 = failure, 0=unsupported virtual int extension(const char *name,void *pvt) =0; // reset plugin, as if infinite number of samples with value v was // fed into the input. if this is not supported, it should call reset() and // return 0. So, reset2() returns 1 if succeeded, and 0 - if normal // reset() call was performed. // // this feature is done to prevent initial click, so that before processing // some buffer you feed in first sample // // if nch contains 1, while stereo processing mode is choosen - buffers // all set to v[0] // // if nch=3 and plugin has 10 input ports, channels are cycled through // v[0...2] values virtual int reset2(float *v,int nch) =0; // if sz=0, then all evens from aux should be processed immediately // (incl aux output) // aux may be NULL, that means - no data of that kind is supplied to // (and requested from) plugin // should return 0, if type of processing is not supported and 1 otherwise // // buffers should not be null // // for processing, no matter, even if sz=0 - then in and out anyway // should contain valid pointer array to buffers of 1 sample // // dummy channel buffer may look like tmp=buf=&single_sample_var, step=0 // these functions should be only if flags&NATIVE/EMULATED is supported virtual int processf(int64 samplestamp, float **in, int instep, float **out, int outstep, osp_aux_i *aux, int sz) =0; virtual int process32(int64 samplestamp, int32 **in, int instep, int32 **out, int outstep, osp_aux_i *aux, int sz) =0; virtual int process16(int64 samplestamp, int16 **in, int instep, int16 **out, int outstep, osp_aux_i *aux, int sz) =0; // set/get host data (for internal host purposes, can be called anytime) virtual void hostdata(void *pvt) =0; virtual void *hostdata() =0; }; class osp_gui_i { public: virtual ~osp_gui_i() {} // returns factory virtual osp_factory_i *factory() =0; // should be called before GUI creation and before obtaining // of width/height/resizable/caption virtual int setobject(osp_plugin_i *plugin) =0; // should be available before GUI creation virtual int width() =0; virtual int height() =0; virtual int resizable() =0; // is GUI resizable? normally should return 0 virtual const char *caption() =0; // on windows, wndhandle should contain HWND // plugin should create GUI there virtual int init(void *wndhandle,osp_rect r) =0; virtual int update() =0; // should reload param data from plugin // - normally passed by host after host // decides to change parameter by itself // // init() should call update() internally // for GUI extensions (e.g. GUID inquiry) // // results: >0 - success, <=0 = failure, 0=unsupported virtual int extension(const char *name,void *pvt) =0; // gui is deleted in destructor }; class osp_factory_i { public: virtual ~osp_factory_i() {} enum {UNIQUEID,NAME,VERSIONSTR,SHORTNAME,VENDOR,SUPPORT_URL,DESC,COPYRIGHT}; // UNIQUEID is a string (could be with spaces), which is suitable for filename, // or registry entry name (underscores, spaces, dots and dashes are allowed). // UNIQUEID could be used in case insensitive environment, and that means // that it should be unique enough. Also, it could be used in environment, // where underscores spaces dots and dashes are considered to be the same // character or even could be stripped out. // // may not exceed 20 characters in total // all other fields should not exceed 255 characters (255+zero terminator), // except DESC should not exceed 2047 characters (2047+zero terminator) // this is version - implementation should return OSPI_ISSUE_ID // once this interface is modified, OSPI_ISSUE_ID (beginning of this // header) should be changed as well virtual int issue_id() =0; // this function may not return NULL for defined strings ^^^^ // in description field, lines are separated with single \n virtual const char *getstr(int idx) =0; virtual osp_plugin_i *create() =0; virtual osp_gui_i *creategui() =0; // for factory extensions (e.g. GUID inquiry) // // results: >0 - success, <=0 = failure, 0=unsupported virtual int extension(const char *name,void *pvt) =0; }; typedef osp_factory_i *(*osp_func_t)(int); #endif //OSPI_H_INCLUDED