<!-- Converted to DocBook/SGML by Kevin Conder -->
<chapter id="AddmodTop">
  <title>Adding your own Cmodules to Csound</title>

  <para>
    If the existing Csound generators do not suit your needs, you can write your own <indexterm id="IndexModules"><primary>modules</primary></indexterm> 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.
  </para>

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

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


/* newgen.c -  init and perf code */
#include &quot;cs.h&quot;
#include &quot;newgen.h&quot;

void rampset (RMP * p)  /* at note initialization: */
{
  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 (RMP * p)  /* during note performance: */
{
  float *rsltp = p - result;  /* init an output array pointer */
  int nn = ksmps;  /* array size from orchestra */
  do
    {
      *rsltp++ = p - curval;  /* copy current value to output */
      if (--p - countdown = 0)  /* for the first itime seconds, */
        p - curval += p - vincr;  /* ramp the value */
    }
  while (--nn);
}                         
      </programlisting>
    </informalexample>
  </para>

  <para>
    Now we add this module to the translator table entry.c, under the opcode name rampt:
    <informalexample>
      <programlisting>
#include &quot;newgen.h&quot;
  
void rampset(), ramp();
  
/*   opcode    dspace  thread    outarg    inargs      isub       ksub     asub    */
  
{ &quot;rampt&quot;,  S(RMP),  5,        &quot;a&quot;,      &quot;iiio&quot;,     rampset,   NULL,    ramp  },
      </programlisting>
    </informalexample>
  </para>

  <para>
    Finally we relink Csound to include the new module. If your Csound installation has created a libcsound.a, you can do this by typing
    <informalexample>
      <programlisting>
cc -o mycsound newgen.c entry.c -lcsound -lX11 -lm
(X11 if included at installation)
      </programlisting>
    </informalexample>
  </para>

  <para>
    Else copy *.c, *.h and Makefile from the Csound sources, add newgen.o to the Makefile list OBJS, add newgen.h as a dependency for entry.o, and a new dependency 'newgen.o: newgen.h', then run 'make CSound'. If your host is a Macintosh, simply add newgen.h and newgen.c to one of the segments in the Csound Project, and invoke the C compiler.
  </para>

  <para>
    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:
    <informalexample>
      <programlisting>
ar rampt istart, islope, itime [, icontin]
      </programlisting>
    </informalexample>
  </para>

  <para>
    <emphasis>istart</emphasis> -- beginning value of an audio-rate linear ramp. Optionally overridden by a continue flag.
  </para>

  <para>
    <emphasis>islope</emphasis>  -- slope of ramp, expressed as the y-interval change per second.
  </para>

  <para><emphasis>itime</emphasis> -- ramp time in seconds, after which the value is held for the remainder of the note.
  </para>

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

  <para>
    The file <emphasis>newgen.h</emphasis> 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 <emphasis>address</emphasis>, not <emphasis>value</emphasis>, 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 <emphasis>x</emphasis>, <emphasis>a</emphasis>, <emphasis>k</emphasis>, and <emphasis>i</emphasis>, in the normal Csound manual conventions; also available are o (optional, defaulting to 0), p (optional, defaulting to 1). Outarg types include <emphasis>a</emphasis>, <emphasis>k</emphasis>, <emphasis>i</emphasis> and <emphasis>s</emphasis> (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.
  </para>

  <para>
    The file <emphasis>newgen.c</emphasis> 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 these initialization, and either k-rate or a-rate subroutines which become inserted in various threaded lists of runnable tasks when an instrument is activated. The thread-types appear in entry.c in two forms: <emphasis>isub</emphasis>, <emphasis>ksub</emphasis> and <emphasis>asub</emphasis> 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 <emphasis>cs.h</emphasis> and <emphasis>oload.c</emphasis>, the most useful of which are:

    <informalexample>
      <programlisting>
  extern  OPARMS  O ;          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     O.odebug
      command-line -v flag         int     O.msglevel
      command-line -m level        float   pi, twopi    obvious
      constants                    float   tpidsr    twopi / esr float
      sstrcod                      special code for string arguments
      </programlisting>
    </informalexample>
  </para>

  <section id="AddmodFtables">
    <title>Function tables</title>

    <para>
      To access <indexterm id="IndexStoredFuncTables"><primary>tables</primary><secondary>stored function</secondary></indexterm> stored function tables, special help is available. The newly defined structure should include a pointer
      <informalexample>
        <programlisting>
FUNC        *ftp;
        </programlisting>
      </informalexample>
    </para>

    <para>
      initialized by the statement
      <informalexample>
        <programlisting>
ftp = ftpfind(p-ifuncno);
        </programlisting>
      </informalexample>
    </para>

    <para>
      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 opcodes2.c.
    </para>
  </section>

  <section id="AddmodAddSpace">
    <title>Additional Space</title>

    <para>
      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
      <informalexample>
        <programlisting>
AUXCH      auxch;
        </programlisting>
      </informalexample>
    </para>

    <para>
      in the defined structure (*p), then using the following style of code in the init module:
      <informalexample>
        <programlisting>
if (p-auxch.auxp == NULL)
  auxalloc(npoints * sizeof(float), &amp;p-auxch);
        </programlisting>
      </informalexample>
    </para>

    <para>
      The address of this auxiliary 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
      <informalexample>
        <programlisting>
char *auxp = p-auxch.auxp;
        </programlisting>
      </informalexample>
    </para>

    <para>
      will find the allocated space for init-time and perf-time use. See the LINSEG structure in opcodes1.h and the code for lsgset() and klnseg() in opcodes1.c.
    </para>
  </section>

  <section id="AddmodFsharing">
    <title>File Sharing</title>
      <para>
        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
      <informalexample>
        <programlisting>
MEMFIL    *mfp;
        </programlisting>
      </informalexample>
    </para>

    <para>
      in the defined structure (*p), then using the following style of code in the init module:
      <informalexample>
        <programlisting>
if (p-mfp == NULL)
  p-mfp = ldmemfile(filname);
        </programlisting>
      </informalexample>
    </para>

    <para>
      where char *filname is a string name of the file requested. The data read will be found between
      <informalexample>
        <programlisting>
(char *)  p-mfp-beginp; and (char *) p-mfp-endp;
        </programlisting>
      </informalexample>
    </para>

    <para>
      Loaded files do not belong to a particular instrument, but are automatically shared for multiple access. See the ADSYN structure in opcodes3.h and the code for adset() and adsyn() in opcodes3.c.
    </para>
  </section>

  <section id="AddmodStringargs">
    <title>String arguments</title>

    <para>
      To permit a quoted string input argument (float *ifilnam, say) in our defined structure (*p), assign it the argtype <emphasis>S</emphasis> in entry.c, include another member char *strarg in the structure, insert a line
      <informalexample>
        <programlisting>
TSTRARG( &quot;rampt&quot;, RMP) \
        </programlisting>
      </informalexample>
    </para>

    <para>
      in the file <emphasis>oload.h</emphasis>, and include the following code in the init module:
      <informalexample>
        <programlisting>
if (*p-ifilnam == sstrcod)
  strcpy(filename, unquote(p-strarg));
        </programlisting>
      </informalexample>
    </para>

    <para>
      See the code for adset() in opcodes3.c, lprdset() in opcodes5.c, and pvset() in opcodes8.c.
    </para>

    <para>
      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
      <informalexample>
        <programlisting>
MEMFIL    *mfp;
        </programlisting>
      </informalexample>
    </para>

    <para>
      in the defined structure (*p), then using the following style of code in the init module:
      <informalexample>
        <programlisting>
if (p-mfp == NULL)
  p-mfp = ldmemfile(filname);
        </programlisting>
      </informalexample>
    </para>

    <para>
      where char *filname is a string name of the file requested. The data read will be found between
      <informalexample>
        <programlisting>
(char *)  p-mfp-beginp; and (char *) p-mfp-endp;
        </programlisting>
      </informalexample>
    </para>

    <para>
      Loaded files do not belong to a particular instrument, but are automatically shared for multiple access. See the ADSYN structure in opcodes3.h and the code for adset() and adsyn() in opcodes3.c.
    </para>
  </section>
</chapter>
