From The sfront Reference Manual by John Lazzaro and John Wawrzynek.

Part II/2C: Writing Control Drivers

Sections

MIDI Commands

NoteOn, NoteOff, Poly Aftertouch, Control Change, Program Change, Channel Aftertouch, Pitch Wheel, No-op, NewTempo, EndTime.

SASL Commands

Instr, Control, Table, Endtime, Tempo, No-op.

Introduction

This 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
Part II/2B: Control Drivers Data Structures

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:

sfront/src/lib/csys/

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 Names

Example:

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 Functions

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

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

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

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

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

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

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

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

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

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

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

MIDI 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

*extchan

The *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 Semantics

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

preset = program + bank*128

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_NOTEON

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

End a MIDI note on *extchan.

*cmd = CSYS_MIDI_NOTEOFF
*ndata = [note number, 0-127]

Note: MP4-SA does not support
release velocity.

CSYS_MIDI_PTOUCH

Polyphonic aftertouch on *extchan.

*cmd = CSYS_MIDI_PTOUCH
*ndata = [note number, 0-127]
*vdata = [touch value, 0-127]

CSYS_MIDI_CC

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

Change program on *extchan

*cmd = CSYS_MIDI_PROGRAM
*ndata = [program number, 0-127]

Note: See left panel for 
MP4-SA semantics.

CSYS_MIDI_CTOUCH

Channel aftertouch on *extchan.

*cmd = CSYS_MIDI_CTOUCH
*ndata = [touch value, 0-127]

CSYS_MIDI_WHEEL

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

Do nothing.

*cmd = CSYS_MIDI_NOOP

CSYS_MIDI_NEWTEMPO

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

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

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

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

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

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

Normal priority commands:

*priority = 0;

High priority commands:

*priority = 1;

See right panel for details.

Command Semantics

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

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

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

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

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

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

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

Do nothing.

*cmd = CSYS_MIDI_NOOP

Registration

The 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.c

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

Type "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 Reports

Please follow these guidelines when sending along bug reports and suggestions for the control driver interface. Thanks!

Next section: Part II/3: Audio Drivers.

 

Copyright 1999 John Lazzaro and John Wawrzynek.