Notes by Rasmus Ekman, February 1999 (Reviewed April 2000)
Somebody wanted to know the main program structure of Csound. If you're going to add new ugens you will not need to look at this section, instead see the manual entry Adding New Modules to Csound.
This is a brief overview of the orch/score parsers, and the program flow during a Csound run; a list of the functions that are called and in which files they reside. It may be of use to programmers who need to get into the parser or event management mechanisms, and need a map of where to begin looking.
File management, function tables and lots of other essentials are not covered.
main( ) (in Main.c) collects and checks command line options, opens input files, reads and sorts score etc.
The last few lines of main( ) calls otran( ) (in Otran.c) and musmon( ) (in Musmon.c).
otran( ) is top of orchestra parsing;
musmon( ) does the score playing or "performance" part.
musmon( ) sets up realtime and/or file output, calls oload( ) (in Oload.c) to initialise all opcodes (get ftables, set defaults etc), and then calls playevents( ) (in Musmon.c), which does the actual performance.
All structs used by Csound to parse the score and orchestra files, and maintain the performance, are declared in the Cs.h header file.
Now for some more details.
main( ) calls readOptions( ) (in one_file.c) to read the .csoundrc file.
argdecode( ) (in Argdecode.c) parses the command line. If Winsound is invoked, cwin_args( ) (in Cwin.cpp) is called (via dialog_arguments( ) in Main.c) to show the Winsound dialog. This is the entry point of Winsound performance, which is not covered further here.)
If a .csd file is used for orc/score, read_unified_file( ) (in one_file.c) is called to split it into temporary orc/sco files and collect any further program options.
The options for the session is stored in the global variable O, of type struct OPARMS, (declared in Cs.h).
After this, the collected info is checked for consistency (eg of sr/kr/ksmps and output soundfile options) and existence of input files, etc.
Note that any options appearing in several places will overwrite previous assigment of the same value. The order of precedence of options is:
After program options are collected, main( ) calls scsort( ) (in Scsort.c). This short function just calls sread( ) (in Sread.c) to read the score by the section. This function does all textual score preprocessing, including macro expansion, score maths operations, as well as syntax checks.
For each score section, scsort( ) calls sort( ), twarp( ) and swrite( ). Any time-warping (see eg t statement) is straightened out in twarp( ), and all events are sorted into strict starting-time order. The score event list is then written to a temporary file score.srt, which is closed, and then reopened to get new events during program run. (The functions reside in Twarp.c, Sort.c and Swrite.c, respectively.)
otran( ) (in Otran.c) calls rdorchfile( ) to input the full orchestra text, then it repeatedly calls getoptxt( ) to get single lines of instrument code (both functions are in Rdorch.c).
rdorchfile( ) does the macro collection and substitution, strips comments etc to get clean preprocessed orchestra code. getoptxt( ) does the orch code parsing and syntax checks.
From the orch text lines, otran( ) builds a list of instrument templates, ie the sequence of opcode calls that constitute the instrument's performance.
The text of each instr...endin block in the orch file is stored in an INSTRTXT struct. This begins with the equivalent of an OPTXT, followed by some info about the instrument (mostly things relevant for memory allocation: number of labels, max number of opcode arguments etc).
The instruments are stored in numerical order in a list. The list is accessible throughout performance either by index to an array of pointers: INSTRTXT *instrtxtp[ ]; or as a linked list via INSTRTXT instxtanchor. (The instrtxtp array will contain null pointers for each instr in range 0 through to maxinsno which has not been defined. There used to be a hardcoded maximum number of instruments (200), but this has recently been made dynamic).
The INSTRTXT struct of an instr is used as a template from which each new instrument instance is created as required by score (or other) events. (Note that INSTRTXT pointers are typecast to OPTXT structs as deemed convenient.)
The OPTXT-equiv part of the INSTRTXT holds a chain of TEXT structs. Each TEXT struct holds the arguments for one opcode in the instrument. As they are encountered by the parser (otran( )), orchestra variable names (eg garvb, kamp etc) and pointers to their values are stored in four lists:
The ARGLST and ARGOFFS in/out lists in the TEXT struct of an opcode contains pointers to the names and the values of opcode arguments in these lists. (The lists are declared locally in Otran.c, some relevant structs and constants are in Oload.h.)
So, all arguments to an opcode are stored as pointers to the actual constant or variable value in the appropriate list, and their names are also kept around throughout performance. This is set up in insprep( ) (in Otran.c), which is called from otran( ) after reading the orchestra. (See also the support functions lgbuild( ), plgndx( ) etc in Otran.c.)
musmon( ) sets up realtime and/or file output, and calls oload( ) (in Oload.c) to initialize "instr 0". This is the instrument which contains all instr-endin blocs and the orchestra header statements. oload( ) also allocates memory for global names in orch code, data space for all a-rate arguments etc.
musmon( ) then calls playevents( ) (in Musmon.c), which does the actual performance.
playevents( ) collects several kinds of input: MIDI (file or live input) events, score events from the sorted .sco file, or score events started in realtime by various means. The user arguments of each event is passed around in an EVTBLK struct. playevents( ) switches on the score event type (i, f, s, t etc) to determine action. Most event types except i- (instr) and f-statements (ftables) are handled locally.
playevents( ) also counts up the k-rate clock kcounter, and calls kperf( ) (in Insert.c). kperf( ) goes through the k-rate opcode list of each active instrument in the playlist (see below), and makes the actual calls to the k- and a-rate opcode functions used by each instrument. It returns to playevents( ) for each event scheduled from score (instrument turnon, ftable generation, or instrument turnoff). It may also return early if a "realtime" event is detected (this includes MIDI input or MIDI file events, and live text input through a named pipe).
The INSTRTXT array built up by the orchestra parser holds a raw chain of indexes to the list of opcodes (opcodlst[ ] in Entry.c), and all actual opcode arguments input by the user. In performance, however, there is a split between i-time and k-rate opcodes.
playevents( ) starts instrument events by calling insert( ) (in Insert.c), passing the EVTBLK for the concrete event. This function creates or reuses or ties instruments. To get a new instrument performance copy it calls instance( ) (in Oload.c) to allocate memory for opcodes, get ftables etc.
In instance( ) the requested instrument template (INSTRTXT struct) is used to init an (INSDS struct. The INSDS struct has one OPDS struct chain ("thread") for i-time opcodes, and one for k-rate (a-rate opcodes are of course called at k-rate). It also has pointers for the lists in which it can appear: The list of all allocated instrument instances in performance, and the list of currently active instr instances.
insert( ) then calls all the i-time opcodes with the user arguments in the concrete EVTBLK to init the instrument for performance.
If there are five events of instr 7, it will thus exist in five (simultaneous or sequentially used) copies throughout the performance. If copies are sequentially used, the instrument data space may be taken over by a later instr copy.
During performance, Csound reads all score events from score.srt up until the first event after the present k-cycle. The time of the next score event is calculated in k-cycles, and kperf( ) is invoked only for the number of k-cycles from present until this next event.
This has two notable consequences: First, because of this setup, the list of future events is not available to Csound during performance, even when they exist in the (sorted) score. Secondly, the onset time of an event can only occur with k-rate precision. The k-time of an event is clipped, not rounded (probably; this could vary for different event sources). Until recently (Csound 4.03), this would lead to problems with time drift in scores with a single stream of events at a regular tempo of fractional k-cycles (eg drum loop-type scores).
All other events are called "real-time events" in Csound source code. The presently available types are:
If any of these methods is used to input events, the program option O.RTevents is set, together with a flag for each used event source: O.Midiin, O.FMidiin, O.Linein or O.OrcEvents. kperf( ) will then check all flagged event sources at k-rate.
Most relevant structs are declared in Cs.h. As will be evident from the above, they contain pointers to the instrument text, to each other, and to the corresponding opcode functions, so it is fairly easy to see what's going on by single-stepping through a Csound performance in your favourite debugger.
All this is (scantily, but still) commented in the source, so you'll find your way in the program flow. Changing stuff without breaking it is trickier.
re, 25 Feb 1999 (reviewed April 2000)
This struct holds the info for one opcode in a concrete instrument instance in performance.
typedef struct opds { struct opds * nxti; /* Next opcode in init-time chain */ struct opds * nxtp; /* Next opcode in perf-time chain */ SUBR iopadr; /* Initialization (i-time) function pointer */ SUBR opadr; /* Perf-time (k- or a-rate) function pointer */ OPTXT *optext; /* Orch file template part for this opcode */ INSDS *insdshead; /* Owner instrument instance data structure */ } OPDS;
This struct holds the data for one score event.
typedef struct event { char *strarg; /* Original argument list string of event */ char opcod; /* Event type */ short pcnt; /* Number of p-fields */ float p2orig; /* Event start time */ float p3orig; /* Length */ float offtim; /* k-time to turn off this event */ float p[PMAX+1]; /* All p-fields for this event */ } EVTBLK;
This struct holds the info for a concrete instrument event instance in performance.
typedef struct insds { struct opds * nxti; /* Chain of init-time opcodes */ struct opds * nxtp; /* Chain of performance-time opcodes */ struct insds * nxtinstance; /* Next allocated instance */ struct insds * prvinstance; /* Previous allocated instance */ struct insds * nxtact; /* Next in list of active instruments */ struct insds * prvact; /* Previous in list of active instruments */ struct insds * nxtoff; /* Next instrument to terminate */ FDCH fdch; /* Chain of files used by opcodes in this instr */ AUXCH auxch; /* Extra memory used by opcodes in this instr */ MCHNBLK *m_chnbp; /* MIDI note info block if event started from MIDI */ short m_pitch; /* MIDI pitch, for simple access */ short m_veloc; /* ...ditto velocity */ short xtratim; /* Extra release time requested with xtratim opcode */ short relesing; /* Flag to indicate we're releasing, test with release opcode */ short insno; /* Instrument number */ short actflg; /* Set if instr instance is active (performing) */ float offbet; /* Time to turn off event, in score beats */ float offtim; /* Time to turn off event, in seconds (negative on indef/tie) */ struct insds * nxtolap; /* ptr to next overlapping voice */ /* end of overlap */ float p0; /* Copy of required p-field values for quick access */ float p1; float p2; float p3; } INSDS;
Storage for parsed orchestra code, for each opcode in an INSTRTXT.
typedef struct text { short linenum; /* Line number in orch file (currently buggy!) */ short opnum; /* Opcode index in opcodlst[] */ char *opcod; /* Pointer to opcode name in global pool */ char *strarg; /* (Unquoted) file name if needed by opcode */ ARGLST *inlist; /* Input arguments (pointer to item in name list) */ ARGLST *outlist; ARGOFFS *inoffs; /* Input args (index into list of values) */ ARGOFFS *outoffs; short xincod; /* Rate switch for multi-rate opcode functions */ char intype; /* Type of first input argument (g, k, a, w etc) */ char pftype; /* Type of output argument (k, a etc) */ } TEXT; typedef struct op { struct op * nxtop; TEXT t; } OPTXT;
This struct is filled out by otran( ) at orch parse time. It is used as a template for instrument events.
typedef struct instr { struct op * nxtop; /* Linked list of instr opcodes */ TEXT t; /* Text of instrument (same as in nxtop) */ short pmax, vmax, pextrab; /* Arg count, size of data for all opcodes in instr */ short mdepends; /* Opcode type (i/k/a) */ short lclkcnt, lcldcnt; /* Storage reqs for this instr */ short lclwcnt, lclacnt; short lclfixed, optxtcount; long localen; long opdstot; /* Total size of opds structs in instr */ long * inslist; /* Only used in parsing (?) */ float * psetdata; /* Used for pset opcode */ struct insds * instance; /* Chain of allocated instances of this instr */ struct instr * nxtinstxt; /* Next instrument in orchestra (numerical order) */ } INSTRTXT;