mp4-sa-> sfront reference manual-> writing control drivers |
Sections
|
MIDI CommandsNoteOn, NoteOff, Poly Aftertouch, Control Change, Program Change, Channel Aftertouch, Pitch Wheel, No-op, NewTempo, EndTime.SASL CommandsInstr, Control, Table, Endtime, Tempo, No-op. |
IntroductionThis section describes the process of writing a control driver in detail, building on the data structure and architecture descriptions of the two previous chapters. We begin by describing the programming conventions control drivers should follow, concerning issues such as variable, function, and file names. We then describe how to write the functions that control drivers must supply. These functions are introduced in an earlier section of this chapter. We conclude by describing the registration process for a new control driver, which involves making several small changes in the sfront source code distribution. |
Previous Sections
Part II/2A: Control Driver Overview
|
Conventions
Each control driver needs a name. The name
is the string that follows -cin on
the sfront command line. It also is the name
of the C file that contains the driver. This
driver file should be placed in the directory:
The right panel shows the control driver naming convention for the file streaming decoder that is a part of the sfront distribution. The control driver may include macro and constant defines. These defines must begin with the string CSYSI_. The control driver may also include C or C++ global variable and type definitions, as well as internal C or C++ function definitions. The names of these elements must begin with csysi_. We will use the csysi_ and CSYSI_ naming convention to enable backward compatible support of multiple control drivers in a future sfront release (presently sfront only supports a single control driver flag). If your control driver violates this naming scheme, it may break somewhere down the line. Control driver functions that begin with csys_ are called by the main sa.c program. The right panel lists the csys_ that a control driver may define. The bulk of this chapter describes these functions in detail. Note that either csys_midievent or csys_saslevent may be omitted from a control driver (but not both). Finally, a control driver may let the user specify command-line options for the sa.c program. We describe the naming conventions for control driver command-line options in an earlier section of this chapter.. |
Control Driver NamesExample: File streaming encoder (fstr). sfront invocation: sfront -cin fstr driver file/location: sfront/src/lib/csys/fstr.c Inside Control Drivers:symbolic constants and macros must begin with: CSYSI_ global variables must begin with: csysi_ internal functions must begin with: csysi_ All control drivers must define: int csys_setup(void) {} int csys_newdata(void) {} void csys_shutdown(void) {} control drivers that can return MIDI commands must define: int csys_midievent(...) {} control drivers that can return SASL commands must define: int csys_saslevent(...) {} see Part II/2B for the conventions for command-line flags a control driver defines for the sa.c file. |
Simple csys FunctionsThe three csys functions shown on the right panel must be defined in all control drivers. In this section, we describe the semantics of these functions. csys_setupThe csys_setup function is called near the start of execution of the sa.c file. A control driver usually initializes its own internal variables and opens files and devices during the csys_setup call. It may also parse sa.c command-line arguments, as described in an earlier section. If the control driver is able to support the execution of the sa.c, csys_setup should return the value CSYS_DONE. To signal an initialization problem (for example, not being able to open a file or device), csys_setup should return the value CSYS_ERROR. On receipt of CSYS_ERROR, the sa.c prints a generic error message indicating control driver failure on stderr, and terminates with a -1 error code. csys_newdataThe csys_newdata function is called once per i-pass. The return value of the function tells the sa.c program if there are new MIDI or SASL events the control driver wishes to execute on this i-pass. The right panel described the four return values (CSYS_NONE, CSYS_MIDIEVENTS, CSYS_SASLEVENTS, and CSYS_EVENTS) that the csys_newdata function may use. In some applications, the control driver may need to know the current execution time of the sa.c program, in order to determine if events are ready. A previous section of this chapter describes global variables that the csys_newdata function may access for this purpose. csys_shutdownThe csys_shutdown function is called near the end of execution of the sa.c program. The control driver can close files and devices, and do other tasks to ensure an orderly shutdown. This function is usually called as a part of the normal termination process. Normal termination happens when the current score time exceeds the value of the endtime variable. Since the endtime variable is available to the control driver to read, and can be changed during a csys_midievent or csys_saslevent call, the control driver can monitor and influence the total execution time of the sa.c program. However, by the time csys_shutdown is called, there is no way for the control driver to reverse the termination decision. If sfront is invoked with the -except option, the csys_shutdown option is also called in the event of the abnormal end of execution. An abnormal termination may happen if the SAOL program causes a floating-point exception, if the sa.c program process is terminated by the operating system, or for other reasons. |
csys_setupint csys_setup(void) { } Called once, near the start of execution of the sa.c file. Return values: CSYS_DONE : if control driver initialization is successful. CSYS_ERROR: if control driver wishes to terminate the sa.c file due to initialization problems. csys_newdataint csys_newdata(void) {} Called at the start of each i-pass. Legal return values: CSYS_NONE: No new MIDI or SASL events for this i-pass. The sa.c file will not call the csys_midievent() or the csys_saslevent() function on this i-pass. CSYS_MIDIEVENTS: New MIDI events, but no new SASL events, on this i-pass. The sa.c file will call csys_midievent() to receive the event(s). CSYS_SASLEVENTS: New SASL events, but no new MIDI events, on this i-pass. The sa.c file will call csys_saslevent() to receive the event(s). CSYS_EVENTS: New MIDI events and new SASL events on this i-pass. The sa.c file will call csys_midievent() and csys_saslevent() to receive the events. csys_shutdownvoid csys_shutdown(void) {} Called at the normal end of execution of the sa.c program, so that the control driver may close devices and files. If the sfront command-line option -except is invoked, the csys_shutdown function is also called in the case of abnormal termination, such as a floating point exception. |
csys_midieventThe csys_midievent function is called during an i-pass if the return value for the csys_newdata call is CSYS_MIDIEVENTS or CSYS_EVENTS. As shown on the right panel, the arguments for the csys_midievent function are all pointers. This function places information about the new MIDI event into the variables pointed to by these arguments. A csys_midievent call communicates a single MIDI event. If there is another pending MIDI event, the csys_midievent function should return CSYS_EVENTS to request another csys_midievent call. The csys_midievent function is repeatedly called until it returns the value CSYS_NONE. |
csys_midieventint csys_midievent( unsigned char * cmd, unsigned char * ndata, unsigned char * vdata, unsigned short * extchan, float * fval); Place MIDI event information in pointer, i.e: *cmd = CSYS_MIDI_NOTEON; *ndata = 40; *vdata = 64; *extchan = 1; *fval = 0.0F; Return values: CSYS_EVENTS: Requests another call to csys_midievent on this i-pass, to communicate another MIDI event. CSYS_NONE: Indicates there are no more MIDI events for this i-pass. |
Command SyntaxThe right panel lists the possible *cmd values for csys_midievent. These values code the MIDI commands supported by the MP4-SA standard, as well as several special commands. The numeric values for the MIDI commands match the syntax of MIDI events sent on a wire. However, the lower nibble of MIDI commands, which holds the MIDI channel number, is ignored by the sa.c program. Instead, the *extchan variable is used to code the MIDI channel information. This variable can be used to code multiple ports of 16-channel MIDI streams, using the formula shown on the right panel. The maximum number of MIDI channels for a control driver is arbitrary, and is specified as part of the registration process. The implementation of running status (where consecutive identical MIDI commands on a wire can skip the command byte) is the responsibility of the control driver. The sa.c program expects a valid *cmd value on every call. MIDI Channel Number SpacesIn the MP4 file format, it is possible to include a complete MIDI File in the configuration part of the file. The channel number space of this MIDI File is independent from the channel number space of the MIDI control driver. This independence is in keeping with the MP4-SA specification, and lets programmers use the control driver interface to write compliant MP4-SA streaming applications. |
*cmd valuesMIDI commands: symbolic hex CSYS_MIDI_NOTEOFF 80 CSYS_MIDI_NOTEON 90 CSYS_MIDI_PTOUCH A0 CSYS_MIDI_CC B0 CSYS_MIDI_PROGRAM C0 CSYS_MIDI_CTOUCH D0 CSYS_MIDI_WHEEL E0 Special commands: CSYS_MIDI_NOOP 70 CSYS_MIDI_NEWTEMPO 71 CSYS_MIDI_ENDTIME 72 *extchanThe *extchan field should be set to the extended MIDI channel for the command. For a simple system (a single MIDI stream with 16 channels) set *extchan to the MIDI channel number (0 to 15). For a complicated system with several 16-channel MIDI ports, set *extchan to 16*P + N where P is the MIDI port (numbered from 0) and N is the MIDI channel number (0-15). |
Command SemanticsThe right panel shows the semantics for each command supported by the csys_midievent function. The description specifies the meaning of the *ndata, *vdata, and *fval fields for each command. The MIDI program command is used to select the SAOL instrument that is created in response to the MIDI NoteOn command. The program number specified by the MIDI program command is mapped into a SAOL preset number, which unambiguously denotes a SAOL instrument.
To map MIDI program numbers into
SAOL instrument preset numbers,
use this formula:
where bank is the MIDI program bank number. This value defaults to 0, and may be changed by using the MIDI control change command for controller 0. In addition, as described in an earlier section, the control driver may access data structures that map SAOL preset numbers to SAOL instrument names. Use these links to jump to a particular command: |
CSYS_MIDI_NOTEONStart a MIDI note on *extchan. *cmd = CSYS_MIDI_NOTEON *ndata = [note number, 0-127] *vdata = [velocity, 0-127] Note: *vdata = 0 treated as a CSYS_MIDI_NOTEOFF. CSYS_MIDI_NOTEOFFEnd a MIDI note on *extchan. *cmd = CSYS_MIDI_NOTEOFF *ndata = [note number, 0-127] Note: MP4-SA does not support release velocity. CSYS_MIDI_PTOUCHPolyphonic aftertouch on *extchan. *cmd = CSYS_MIDI_PTOUCH *ndata = [note number, 0-127] *vdata = [touch value, 0-127] CSYS_MIDI_CCController change on *extchan *cmd = CSYS_MIDI_CC *ndata = [controller number, 0-127] *vdata = [controller value, 0-127] Note: See this section for symbolic MIDI controller numbers. CSYS_MIDI_PROGRAMChange program on *extchan *cmd = CSYS_MIDI_PROGRAM *ndata = [program number, 0-127] Note: See left panel for MP4-SA semantics. CSYS_MIDI_CTOUCHChannel aftertouch on *extchan. *cmd = CSYS_MIDI_CTOUCH *ndata = [touch value, 0-127] CSYS_MIDI_WHEELPitch wheel value on *extchan. *cmd = CSYS_MIDI_WHEEL *ndata = [wheel LSB, 0-127] *vdata = [wheel MSB, 0-127] Note: Value of MIDIbend will be read as (*vdata)*128 + (*ndata). Coding follows the MIDI command syntax for easy parsing. CSYS_MIDI_NOOPDo nothing. *cmd = CSYS_MIDI_NOOP CSYS_MIDI_NEWTEMPOChange the tempo value. *cmd = CSYS_MIDI_NEWTEMPO *fval = [new tempo, beats/min] Note: The current tempo is available in the global variable tempo. CSYS_MIDI_ENDTIMEChange the endtime value *cmd = CSYS_MIDI_ENDTIME *fval = [endtime, in beats] Note: Global variables are available that tell the current endtime status. To halt execution after the current execution cycle, set *fval to the value of the global variable scorebeats. |
csys_sasleventThe csys_saslevent function is called during an i-pass if the return value for the csys_newdata call is CSYS_SASLEVENTS or CSYS_EVENTS. As shown on the right panel, the arguments for the csys_saslevent function are all pointers. This function places information about the new SASL event into the variables pointed to by these arguments. A csys_saslevent call communicates a single SASL event. If there is another pending SASL event, the csys_saslevent function should return CSYS_EVENTS to request another csys_saslevent call. The csys_saslevent function is repeatedly called until it returns the value CSYS_DONE. |
csys_sasleventint csys_saslevent( unsigned char * cmd, unsigned char * priority, unsigned short * id, unsigned short *label, float * fval, unsigned int * pnum, float ** p) Place SASL event information in pointer, i.e: *cmd = CSYS_SASL_TEMPO; *fval = 61.0F; Return values: CSYS_EVENTS: Requests another call to csys_saslevent on this i-pass, to communicate another SASL event. CSYS_NONE: Indicates there are no more SASL events for this i-pass. |
Command SyntaxThe right panel lists the possible *cmd values for csys_saslevent. These values code the command set of the SASL language, as well as special commands. The numeric values for the SASL values maps to the MPEG 4 binary coding of class score_line. The *priority field of csys_saslevent can take two values. A zero value codes normal priority. It indicates that if CPU cycles are a scarce commodity, the sa.c should feel free to ignore the command. If the *priority field is set to 1, the SASL command is considered a high priority command, and is given preference for execution if computational resources are running low. |
*cmd valuesSASL commands: symbolic hex CSYS_SASL_INSTR 0x00 CSYS_SASL_CONTROL 0x01 CSYS_SASL_TABLE 0x02 CSYS_SASL_ENDTIME 0x04 CSYS_SASL_TEMPO 0x05 Special commands: CSYS_SASL_NOOP 0x06 Note that hexadecimal values for the SASL command encoding matches the MP4-SA binary encoding for class score_line. *priority valuesNormal priority commands: *priority = 0; High priority commands: *priority = 1; See right panel for details. |
Command SemanticsThe right panel shows the semantics for each command supported by the csys_saslevent function. The description specifies the meaning of the *id, *label, *fval, *pnum, and *p fields for each command. The right panel descriptions include many hyperlinks to the data structure descriptions in the previous section. These data structures map the sa.c coding of instruments and variables (the index field) to the SASL coding of instruments and variables (MP4 symbols and ASCII names). Use these links to jump to a particular command: |
CSYS_SASL_INSTRCreate a SAOL instr instance. *cmd = CSYS_SASL_INSTR *id = [instr to create] *label = [label for instr] *fval = [duration, or -1] *pnum = [number of parameters] *p = [pointer to float array] Notes: [1] The *id field specifies the SAOL instr to create, by using the csys_instr[].index number. [2] The *label field is the SASL label, if any, of the instrument. Use the constant CSYS_NOLABEL to signify an unlabelled instrument, use a constant value greater than CSYS_LABELNUM to create unique new labels, or see this section for details on how to specify label names from a SASL score in the configuration block. [3] The *fval field is the duration of the instr, in units of scorebeats, or is -1 if the instr has indefinite duration. [4] The *p field points to a float array of size *pnum that holds the parameter values for the instr. The csys_instr structure has information on the number and names of the instr parameters. Global CSYS_SASL_CONTROLUpdates a SAOL global variable. *cmd = CSYS_SASL_CONTROL *id = CSYS_SASL_NOINSTR *label = CSYS_NOLABEL *pnum = [variable to change] *fval = [new variable value] [1] Set *pnum to the csys_global[].index value of the variable to be changed. [2] Set *fval to the new value of this variable. Labelled CSYS_SASL_CONTROLUpdates an imports variable in a labelled instance of a SAOL instr. The variable must not have a matching variable in the global block. *cmd = CSYS_SASL_CONTROL *label = [label name] *id = [instr name] *pnum = [variable to change] *fval = [new variable value] [1] The *label field is set to the label number of the target instr instances. If the control driver is labelling its CSYS_SASL_INSTR commands using unique labels, it will know the correct label number to use. If the control command targets an instr created by a SASL instr command in the configuration block, see this section for details on how to specify the label names. [2] The same SASL label can be applied to instances of multiple instr types. The CSYS_SASL_CONTROL needs to be invoked multiple times in this case, one for each instr type. The *id field specifies the SAOL instr a CSYS_SASL_CONTROL command targets, by using the csys_instr[].index number. [3] The *pnum field is the csys_instr[].csys_vars[].index number for the variable to be changed (see Note below). [4] This data structure simplifies the search for the correct *id and *pnum values for some types of control driver applications. [5] Set *fval to the new value of the variable. Note: Even though the MP4-SA specification only allows imports variables to be changed, the CSYS_SASL_CONTROL command will change all instr signal variables, to support control drivers which help in SAOL program debugging. Changing a non-imports variable is not guaranteed to change SAOL program behavior unless the -O0 option is used, since sfront may have optimized away the variable that is being changed. CSYS_SASL_TABLEUpdates a SAOL global or future table. *cmd = CSYS_SASL_TABLE *id = [table to modify] *label = [wavetable generator] *pnum = [number of parameters] *p = [pointer to float array] [1] Set *id to the csys_global[].index value of the table to be changed. Note that both global and future tables are in this array. [2] Set *label to one of these constants to indicate the wavetable generator: symbolic hex CSYS_SASL_TGEN_SAMPLE 6F CSYS_SASL_TGEN_DATA 70 CSYS_SASL_TGEN_RANDOM 71 CSYS_SASL_TGEN_STEP 72 CSYS_SASL_TGEN_LINESEG 73 CSYS_SASL_TGEN_EXPSEG 74 CSYS_SASL_TGEN_CUBICSEG 75 CSYS_SASL_TGEN_POLYNOMIAL 76 CSYS_SASL_TGEN_SPLINE 77 CSYS_SASL_TGEN_WINDOW 78 CSYS_SASL_TGEN_HARM 79 CSYS_SASL_TGEN_HARM_PHASE 7A CSYS_SASL_TGEN_PERIODIC 7B CSYS_SASL_TGEN_BUZZ 7C CSYS_SASL_TGEN_CONCAT 7D CSYS_SASL_TGEN_EMPTY 7E CSYS_SASL_TGEN_DESTROY 7F Note that the numeric values match the MP4 symbol token values for these wavetable generators. Also note that opcode and wavetable "buzz" are different token! [3] With the exception of the SAMPLE generator, the *pnum field is the number of wavetable parameters, and the *p field points to an array of floats that are the parameters of the wavetable generator. Note the CONCAT generator has table values for some of its parameters. For these parameters, use the csys_global[].index value for the table, cast into a float. [4] The SAMPLE generator uses the *pnum and *p arguments in a different way. The *pnum value is 4 + the number of actual samples in the wavetable. The control driver is responsible for executing the semantics of the size and skip field, to figure out this actual size. The first four elements in the *p array have special meanings, that map into fields of the class sample of the MP4 binary file: (*p)[0] sampling rate (Hz) (srate) (*p)[1] loop start (index) (loopstart) (*p)[2] loop end (index) (loopend) (*p)[3] base frequency (Hz) (basecps) A value of -1 for these parameters codes "unknown". The actual sample elements are placed in elements (*p)[4] to (*p)[(*pnum)-1]. Note that the loop start and loop end values are not offset by 4 -- so a loop start at the top of the table would be set to 0 (not 4) and a loop end at the last element of the table would be set to (*pnum) - 4 (not *pnum). CSYS_SASL_ENDTIMEExecutes SASL endtime command. *cmd = CSYS_SASL_ENDTIME *fval = [endtime in scorebeats] Note: Global variables are available that tell the current endtime status. To halt execution after the current execution cycle, set *fval to the value of the global variable scorebeats. CSYS_SASL_TEMPOThe SASL tempo command *cmd = CSYS_SASL_TEMPO *fval = [new tempo, beats/min] Note: The current tempo is available in the global variable tempo. CSYS_SASL_NOOPDo nothing. *cmd = CSYS_MIDI_NOOP |
RegistrationThe right panel shows how to register your control driver with the sfront sources. Registration is necessary in order for sfront to add your control driver flag to the permissible arguments to the -cin command line switch. |
Step 1: Create Libraries[1] cd sfront/src/lib [2] Edit Makefile, and add name of your control driver to the CSYS list. [3] Type "make". This will create: sfront/src/csyslib.c sfront/src/csyslib.h which includes an embedded version of your driver. Search in the files for your driver name to verify. [4] Whenever you change your driver code in sfront/src/lib/csys you will need to remake the csyslib.c file. Step 2: Edit sfront/src/control.cIn sfront/src/control.c, make these additions, to add driver "mydriver". [1] Add the constant definition #define CDRIVER_MYDRIVER top the top of the file. Give it the current numerical value of CDRIVER_END, and then increase the value of CDRIVER_END by 1. [2] Add a printf line to the function void printcontrolhelp(void) that describes the -cin mydriver flag. This will be printed out when "sfront -help" is invoked. [3] Add an if statement to the funtion cinfilecheck that takes this form: if (!strcmp(fname,"mydriver")) { cin = CDRIVER_MYDRIVER; csasl = 1; cmidi = 1; cmaxchan = 3; clatency = ?_LATENCY_DRIVER; return 0; } [a] Set csasl to 1 if your driver supports SASL commands, else set it to 0. If you set it to 1, the sa.c file expects your driver to define the csys_saslevent() function. [b] Set cmidi to 1 if your driver supports MIDI commands, else set it to 0. If you set it to 1, the sa.c file expects your driver to define the csys_midievent() function. [c] If you set cmidi to 1, set cmaxchan to 1 + the number of extended MIDI channels your driver supports. Don't set this value unnecessarily high, since it makes the executable larger. [d] Set clatency to LOW_LATENCY_DRIVER if your driver is interactive, for example a a MIDI In jack driver. Otherwise, set clatency to HIGH_LATENCY_DRIVER; [4] Add this case entry to the switch statement in void makecontroldriver(int num) case CDRIVER_MYDRIVER: makemydriver(); break; This call actually does the code insertion into the sa.c file: it calls the function created in csyslib.c from your driver file. Step 3: Compile sfrontType "make" in sfront/src to compile sfront with your driver. During driver development, you should edit your original driver source in sfront/src/lib/csys/mydriver.c then to test first: cd sfront/src/lib/ make and then: cd sfront/src make |
Bug ReportsPlease follow these guidelines when sending along bug reports and suggestions for the control driver interface. Thanks! Next section: Part II/3: Audio Drivers. |
|
mp4-sa-> sfront reference manual-> writing control drivers |