<- C I ->

Appendix 6:
Adding your own Cmodules to Csound

If the existing Csound generators do not suit your needs, you can write your own modules in C and add them to the run-time system. When you invoke Csound on an orchestra and score file, the orchestra is first read by a table-driven translator 'otran' and the instrument blocks converted to coded templates ready for loading into memory by 'oload' on request by the score reader. To use your own C-modules within a standard orchestra you need only add an entry in otran's table and relink Csound with your own code.

The translator, loader, and run-time monitor will treat your module just like any other provided you follow some conventions. You need a structure defining the inputs, outputs and workspace, plus some initialization code and some perf-time code. Let's put an example of these in two new files, newgen.h and newgen.c:

     typedef struct {                   /*  newgen.h  -  define a structure */
          OPDS      h;                  /* required header */
          float          *result, *istrt, *incr, *itime, *icontin; /* addr outarg, inargs   */
          float          curval, vincr;           /* private dataspace */
          long      countdown;                    /* ditto       */
     }  RMP;

     #include "cs.h"                    /*  newgen.c  -  init and perf code */
     #include "newgen.h"

     void rampset(p)                    /* at note initialization:     */
       register  RMP  *p;
     {
          if  (*p->icontin == 0.)
               p->curval = *p->istrt;   /* optionally get new start value */
          p->vincr = *p->incr / esr;    /* set s-rate increment per sec.   */
          p->countdown = *p->itime * esr; /* counter for itime seconds     */
     }

     void ramp(p)                            /* during note performance:   */
       register  RMP  *p;
     {
          register float *rsltp = p->result;      /* init an output array pointer  */
          register int nn = ksmps;           /* array size from orchestra */
          do {
               *rsltp++ = p->curval;              /* copy current value to ouput */
               if (--p->countdown >= 0)      /* for the first itime seconds, */
                    p->curval += p->vincr;   /*   ramp the value  */
          } while (--nn);
     }
Now we add this module to the translator table entry.c, under the opcode name rampt:
     #include "newgen.h"
     void rampset(), ramp();

     /*   opcode    dspace    thread    outarg    inargs      isub       ksub     asub    */
     { "rampt",     S(RMP),   5,   "a", "iiio",     rampset,  NULL,     ramp  },
Finally we relink Csound to include the new module. Under Unix this means changing the Makefile in three places:
1. Add the name newgen.o to the variable OBJS.
2. Add the name newgen.h as a dependency for entry.o
3. Create a new dependency, newgen.o: newgen.h
Now run 'make csound'. If your host is a Macintosh, simply add newgen.h and newgen.c to one of the Csound segments and invoke the C compiler.

The above actions have added a new generator to the Csound language. It is an audio-rate linear ramp function which modifies an input value at a user-defined slope for some period. A ramp can optionally continue from the previous note's last value. The Csound manual entry would look like:

          ar   rampt     istart,  islope, itime [,  icontin]
istart - beginning value of an audio-rate linear ramp. Optionally overridden by a continue flag.

islope - slope of ramp, expressed as the y-interval change per second.

itime - ramp time in seconds, after which the value is held for the remainder of the note.

icontin (optional) - continue flag. If zero, ramping will proceed from input istart . If non-zero, ramping will proceed from the last value of the previous note. The default value is zero.

The file newgen.h includes a one-line list of output and input parameters. These are the ports through which the new generator will communicate with the other generators in an instrument. Communication is by address, not value, and this is a list of pointers to floats. There are no restrictions on names, but the input-output argument types are further defined by character strings in entry.c (inargs, outargs). Inarg types are commonly x, a, k, and i, in the normal Csound manual conventions; also available are o (optional, defaulting to 0), p (optional, defaulting to 1). Outarg types include a, k, i and s (asig or ksig). It is important that all listed argument names be assigned a corresponding argument type in entry.c. Also, i-type args are valid only at initialization time, and other-type args are available only at perf time. Subsequent lines in the RMP structure declare the work space needed to keep the code re-entrant. These enable the module to be used multiple times in multiple instrument copies while preserving all data.

The file newgen.c contains two subroutines, each called with a pointer to the uniquely allocated RMP structure and its data. The subroutines can be of three types: note initialization, k-rate signal generation, a-rate signal generation. A module normally requires two of theseinitialization, and either k-rate or a-rate subroutineswhich become inserted in various threaded lists of runnable tasks when an instrument is activated. The thread-types appear in entry.c in two forms: isub, ksub and asub names; and a threading index which is the sum of isub=1, ksub=2, asub=4. The code itself may reference global variables defined in cs.h and oload.c, the most useful of which are:

       float     esr       user-defined sampling rate
       float     ekr       user-defined control rate
       float     ensmps    user-defined ksmps
       int       ksmps     user-defined ksmps
       int       nchnls    user-defined nchnls
       int       odebug    command-line -v flag
       int       initonly  command-line -I flag
       int       msglevel  command-line -m level
       float     pi, twopi obvious constants
       float     tpidsr    twopi / esr
       float     sstrcod   special code for string arguments

Function tables

To access stored function tables, special help is available. The newly defined structure should include a pointer
                    FUNC        *ftp;
initialized by the statement
                    ftp = ftpfind(p->ifuncno);
where float *ifuncno is an i-type input argument containing the ftable number. The stored table is then at ftp->ftable, and other data such as length, phase masks, cps-to-incr converters, are also accessed from this pointer. See the FUNC structure in cs.h, the ftfind() code in fgens.c, and the code for oscset() and koscil() in ugens2.c.

Additional space

Sometimes the space requirement of a module is too large to be part of a structure (upper limit 65535 bytes), or it is dependent on an i-arg value which is not known until initialization. Additional space can be dynamically allocated and properly managed by including the line
                    AUXCH      auxch;
in the defined structure (*p), then using the following style of code in the init module:
               if (p->auxch.auxp == NULL)
                    auxalloc(npoints * sizeof(float), &p->auxch);
The address of this auxilliary space is kept in a chain of such spaces belonging to this instrument, and is automatically managed while the instrument is being duplicated or garbage-collected during performance. The assignment
               char *auxp = p->auxch.auxp;
will find the allocated space for init-time and perf-time use. See the LINSEG structure in ugens1.h and the code for lsgset() and klnseg() in ugens1.c.

File sharing

When accessing an external file often, or doing it from multiple places, it is often efficient to read the entire file into memory. This is accomplished by including the line
                    MEMFIL    *mfp;
in the defined structure (*p), then using the following style of code in the init module:
               if (p->mfp == NULL)
                    p->mfp = ldmemfile(filname);
where char *filname is a string name of the file requested. The data read will be found between
     (char *)  p->mfp->beginp;          and       (char *) p->mfp->endp;
Loaded files do not belong to a particular instrument, but are automatically shared for multiple access. See the ADSYN structure in ugens3.h and the code for adset() and adsyn() in ugens3.c.

String arguments

To permit a quoted string input argument (float *ifilnam, say) in our defined structure (*p), assign it the argtype S in entry.c, include another member char *strarg in the structure, insert a line
                    TSTRARG( "rampt",  RMP)  \
in the file oload.h, and include the following code in the init module:
               if (*p->ifilnam == sstrcod)
                    strcpy(filename, unquote(p->strarg));
See the code for adset() in ugens3.c, lprdset() in ugens5.c, and pvset() in ugens8.c.

<- C I ->
Prepared from the MIT Media Lab Csound Manual, PJN, Nov 1994.