Writing a Cscore Control Program

The general format for a Cscore control program is:

#include  "cscore.h" 
void cscore(CSOUND *cs) 
{ 
 /*  VARIABLE DECLARATIONS  */ 
 /*  PROGRAM BODY  */ 
}

The include statement will define the event and list structures and all of the Cscore API functions for the program. The name of the user function needs to be cscore if it will be linked with the standard main program in cscormai.c or linked as the internal Cscore routine for a personal Csound executable. This cscore() function receives one argument from cscormai.c or Csound -- CSOUND *cs -- which is a pointer to a Csound object. The pointer cs must be passed as the first parameter to every Cscore API function that the program calls.

The following C program will read from a standard numeric score, up to (but not including) the first s or e statement, then write that data (unaltered) as output.

#include  "cscore.h" 
void cscore(CSOUND *cs) 
{ 
     EVLIST *a;                    /* a is allowed to point to an event list */ 
     a = cscoreListGetSection(cs); /* read events in, return the list pointer */ 
     cscoreListPut(cs, a);         /* write these events out (unchanged) */ 
     cscorePutString(cs, "e");     /* write the string e to output */ 
}

After execution of cscoreListGetSection(), the variable a points to a list of event addresses, each of which points to a stored event. We have used that same pointer to enable another list function -- cscoreListPut() -- to access and write out all of the events that were read. If we now define another symbol e to be an event pointer, then the statement

e = a->e[4];

will set it to the contents of the 4th slot in the EVLIST structure, a. The contents is a pointer to an event, which is itself comprised of an array of parameter field values. Thus the term e->p[5] will mean the value of parameter field 5 of the 4th event in the EVLIST denoted by a. The program below will multiply the value of that pfield by 2 before writing it out.

#include  "cscore.h" 
void cscore(CSOUND *cs) 
{ 
     EVENT  *e;                    /* a pointer to an event   */ 
     EVLIST *a; 
     a = cscoreListGetSection(cs); /* read a score as a list of events */ 
     e = a->e[4];                  /* point to event 4 in event list a  */ 
     e->p[5] *= 2;                 /* find pfield 5, multiply its value by 2 */ 
     cscoreListPut(cs, a);         /* write out the list of events  */ 
     cscorePutString(cs, "e");     /* add a "score end" statement */ 
}

Now consider the following score, in which p[5] contains frequency in Hz.

f 1 0 257 10 1 
f 2 0 257 7 0 300 1 212 .8 
i 1 1 3 0 440 10000
i 1 4 3 0 256 10000
i 1 7 3 0 880 10000
e

If this score were given to the preceding main program, the resulting output would look like this:

f 1 0 257 10 1 
f 2 0 257 7 0 300 1 212 .8
i 1 1 3 0 440 10000
i 1 4 3 0 512 10000        ; p[5] has become 512 instead of 256.
i 1 7 3 0 880 10000 
e

Note that the 4th event is in fact the second note of the score. So far we have not distinguished between notes and function table setup in a numeric score. Both can be classed as events. Also note that our 4th event has been stored in e[4] of the structure. For compatibility with Csound pfield notation, we will ignore p[0] and e[0] of the event and list structures, storing p1 in p[1], event 1 in e[1], etc. The Cscore functions all adopt this convention.

As an extension to the above, we could decide to use the same pointers a and e to examine each of the events in the list. Note that e was not set to the numeral 4, but to the location of the 4th slot in the list. To inspect p5 of the previous event in the list, we need only redefine e with the assignment

e = a->e[3];

and reference the 5th slot of the pfield array using the expression

e->p[5]

More generally, we can use an integer variable as an index to the array e[], and access each event in sequence by using a loop and incrementing the index. The number of events stored in an EVLIST is contained in the nevents member of the struct.

int index;    /* start with e[1] because e[0] is not used */
for (index = 1; index <= a->nevents; index++)
{
    e = a->e[index];
    /* do something with e */
}

The above example starts with e[1] and increases the index each time through the loop (index++) until it is greater than a->nevents, the index of the last event in the list. The statements inside the for loop do execute a final time when index equals a->nevents.

In the following program we will use the same input score. This time we will separate the ftable statements from the note statements. We will next write the three note-events stored in the list a to the output, then create a second score section consisting of the original pitch set and a transposed version of itself. This will bring about an octave doubling.

Here, our index to the array is n and we increment n as part of a for block which iterates nevents times, allowing one statement to act upon the same pfield of each successive event.

#include  "cscore.h"
void cscore(CSOUND *cs)
{
      EVENT  *e, *f;
      EVLIST *a, *b;
      int n;
      
      a = cscoreListGetSection(cs);            /* read score into event list "a" */ 
      b = cscoreListSeparateF(cs, a);          /* separate f statements */ 
      cscoreListPut(cs, b);                    /* write f statements out to score */ 
      cscoreListFreeEvents(cs, b);             /* and release the spaces used */ 
      e = cscoreDefineEvent(cs, "t 0 120");    /* define event for tempo statement */ 
      cscorePutEvent(cs, e);                   /* write tempo statement to score */ 
      cscoreListPut(cs, a);                    /* write the notes */ 
      cscorePutString(cs, "s");                /* section end */ 
      cscorePutEvent(cs, e);                   /* write tempo statement again */ 
      b = cscoreListCopyEvents(cs, a);         /* make a copy of the notes in "a" */ 
      for (n = 1; n <= b->nevents; n++)        /* iterate the following lines nevents times: */
      { 
          f = b->e[n]; 
          f->p[5] *= 0.5;                      /* transpose pitch down one octave */
      }
      a = cscoreListAppendList(cs, a, b);      /* now add these notes to original pitches */ 
      cscoreListPut(cs, a); 
      cscorePutString(cs, "e");
}

The output of this program is:

f 1 0 257 10 1
f 2 0 257 7 0 300 1 212 .8
t 0 120
i 1 1 3 0 440 10000
i 1 4 3 0 256 10000
i 1 7 3 0 880 10000
s
t 0 120
i 1 1 3 0 440 10000
i 1 4 3 0 256 10000
i 1 7 3 0 880 10000
i 1 1 3 0 220 10000
i 1 4 3 0 128 10000
i 1 7 3 0 440 10000
e

If the output is only being written to a file, then the unsorted order of the events is not a problem. The output is written to a file (or standard output) whenever the function cscoreListPut() is used. However, if this program were to be called during a Csound performance and the function cscoreListPlay() replaced cscoreListPut(), then the events would be sent to the orchestra instead of to a file and they should then be sorted beforehand by calling the function cscoreListSort(). The details of score output and playing when using Cscore from within Csound are described in the next section.

Next we extend the above program by using the for loop to look at p[5] and p[6]. In the original score p[6] denotes amplitude. To create a diminuendo in the added lower octave, which is independent from the original set of notes, a variable called dim will be used.

#include "cscore.h" 
void cscore(CSOUND *cs)
{
      EVENT  *e, *f;
      EVLIST *a, *b;
      int n, dim;                              /* declare two integer variables */ 
      
      a = cscoreListGetSection(cs);
      b = cscoreListSeparateF(cs, a);
      cscoreListPut(cs, b);
      cscoreListFreeEvents(cs, b);
      e = cscoreDefineEvent(cs, "t 0 120");
      cscorePutEvent(cs, e);
      cscoreListPut(cs, a);
      cscorePutString(cs, "s");
      cscorePutEvent(cs, e);                   /* write out another tempo statement */
      b = cscoreListCopyEvents(cs, a);
      dim = 0;                                 /* initialize dim to 0 */ 
      for (n = 1; n <= b->nevents; n++)
      { 
          f = b->e[n]; 
          f->p[6] -= dim;                      /* subtract current value of dim */ 
          f->p[5] *= 0.5;                      /* transpose pitch down one octave */
          dim += 2000;                         /* increase dim for each note */ 
      }
      a = cscoreListAppendList(cs, a, b);      /* now add these notes to original pitches */ 
      cscoreListPut(cs, a); 
      cscorePutString(cs, "e");
}

Using the same input score again, the output from this program is:

f 1 0 257 10 1 
f 2 0 257 7 0 300 1 212 .8
t 0 120
i 1 1 3 0 440 10000
i 1 4 3 0 256 10000
i 1 7 3 0 880 10000
s
t 0 120
i 1 1 3 0 440 10000     ; Three original notes at
i 1 4 3 0 256 10000     ; beats 1,4 and 7 with no dim.
i 1 7 3 0 880 10000
i 1 1 3 0 220 10000     ; three notes transposed down one octave
i 1 4 3 0 128 8000      ; also at beats 1,4 and 7 with dim.
i 1 7 3 0 440 6000
e

In the following program the same three-note sequence will be repeated at various time intervals. The starting time of each group is determined by the values of the array cue. This time the dim will occur for each group of notes rather than each note. Note the position of the statement which increments the variable dim outside the inner for loop.

#include  "cscore.h" 
int cue[3] = {0,10,17};                        /* declare an array of 3 integers */ 
void cscore(CSOUND *cs) 
{
      EVENT  *e, *f;
      EVLIST *a, *b;
      int n, dim, cuecount;                    /* declare new variable cuecount */
      
      a = cscoreListGetSection(cs);
      b = cscoreListSeparateF(cs, a);
      cscoreListPut(cs, b);
      cscoreListFreeEvents(cs, b);
      e = cscoreDefineEvent(cs, "t 0 120");
      cscorePutEvent(cs, e);
      dim = 0; 
      for (cuecount = 0; cuecount <= 2; cuecount++) /* elements of cue are numbered 0, 1, 2 */
      {
          for (n = 1; n <= a->nevents; n++)
          { 
              f = a->e[n]; 
              f->p[6] -= dim; 
              f->p[2] += cue[cuecount];        /* add values of cue */ 
          } 
          printf("; diagnostic:  cue = %d\n", cue[cuecount]); 
          dim += 2000; 
          cscoreListPut(cs, a);
      } 
      cscorePutString(cs, "e");
}

Here the inner for loop looks at the events of list a (the notes) and the outer for loop looks at each repetition of the events of list a (the pitch group "cues"). This program also demonstrates a useful trouble-shooting device with the printf function. The semi-colon is first in the character string to produce a comment statement in the resulting score file. In this case the value of cue is being printed in the output to insure that the program is taking the proper array member at the proper time. When output data is wrong or error messages are encountered, the printf function can help to pinpoint the problem.

Using the same input file, the C program above will generate the following score. Can you determine why the last set of notes starts at the wrong time and how to correct the problem?

f 1 0 257 10 1
f 2 0 257 7 0 300 1 212 .8
t 0 120
; diagnostic:  cue = 0
i 1 1 3 0 440 10000
i 1 4 3 0 256 10000
i 1 7 3 0 880 10000
; diagnostic:  cue = 10
i 1 11 3 0 440 8000
i 1 14 3 0 256 8000
i 1 17 3 0 880 8000
; diagnostic:  cue = 17
i 1 28 3 0 440 4000
i 1 31 3 0 256 4000
i 1 34 3 0 880 4000
e