Input File Preprocessing

Preprocessing Overview

nGen contains four basic preprocessing levels: include files, #if/#endif statements (for conditionally included/excluded code), text-based macros, and numeric macros (i.e., constants). These levels, which are described in depth below, are processed in the following order:


Include Files

Often it is nice to put frequently used items in a separate "include" file. When specifying an existing file name with the #include directive, nGen will copy the file directly into the current file before parsing the current file.  For example:
#include |macros.txt|

will copy the file "macros.txt" (in the current directory or folder) and will insert it starting at the # sign.  Include files may also contain their own include directives (recursion).  When recursive includes are used, there is a limit of 12 files on the depth of the recursion. The file- or path-name must be enclosed in |s.

Path names

Include files may reside anywhere on your computer.  Under DOS, for example, the following includes the file "macros.txt" residing in a separate directory or folder:
#include |C:/ngen/includes/macros.txt|
  
On the Macintosh this will look something like this:
  #include |Macintosh HD:ngen stuff:includes:macros.txt|
  

On all platforms except the Macintosh GUI, the "/" character is used to separate directories (folders).  On these systems the forward slash (/)should be used but under DOS/Windows the backslash (\) may also be used. If a path name does not precede the file name, it will attempt to look for it in the current working directory.

Spaces are allowed in file names, which can be up to 255 characters (spaces must match the actual path and/or file name exactly as it is on your system). For extremely long file names you may put a carriage return in the file name but it must be an extra character (in addition to any spaces).

Macintosh GUI version (Mac-nGen)

(The following only applies to Version 1.0 -- The Classic Mac Version is no longer supported) On the Macintosh the ":" character is used to separate folders. In the Macintosh version, the full path must always be specified. Unfortunately, this means that several of the example files will have to be hand-edited with machine-specific path names in order for them to run properly.

#if/#endif statements

Format:

  #if(n)          (N.B. must be on its own line)
     ...          (any code to be conditionally included)
  #endif          (N.B. must be on its own line)
  

Similar to the C programming language, you can now use "#if" and "#endif" statements to omit or include chunks of code in the ngen input file (done after include file processing and before all macro preprocessing. If the value within ()s, and immediately after the "#if", which has to be an integer or floating-point value, is non-zero then the code within the statements will be included, otherwise it will be ignored. The value with ()s can also be a text-based or numerical macro (including an equation ~[...]) that evaluates to a zero-/non-zero value (macros within ()s in #if statements are processed with the #if statement and not during the regular macro preprocessing pass).

#if/#endif statement pairs may be nested and will not be written to the macro expansion file (-x command-line switch).

IMPORTANT: #if and #endif statements MUST be exclusive to the line they are on -- they may be preceded by whitespace but can be the only thing on that line (and they must EACH be on separate lines).

Example 1
One of the most useful things is to use #if/#endif as a simple switch to turn things on/off with. In this example it would be particularly useful if I had an input file of many i-blocks that were used as a big texture.

    #const ON  1
    #const OFF 0

    #const Inst1 ON
    #const Inst2 OFF

        #if(~Inst1)
            i1 = 5 0 22.1 {
            ...
            }
        #endif
        #if(~Inst2)
            i2 = 5 1.75 9.5 {
            ...
            }
        #endif
        #if(~Inst1)
            i1 = 5 10.25 -5 {
            ...
            }
        #endif
        #if(~Inst2)
            i2 = 5 14.25 -100 {
            ...
            }
        #endif
        #if(~Inst2)
            i2 = 5 0 -1 {
            ...
        }
        #endif
    
Example 2
The following advanced example shows how #if/#endif can be useful to create multiple "variables" for different renderings of one i-block. I have created two groups of macros, one of which will be initialized to create data for the i-block at the bottom of the input file -- this will depend on what I want to render. I have set-up the if statements to work as a typical #if/#else/#endif (as can be found in the C programming language). I set a constant called "FIRST"; if "FIRST" is 1 (TRUE) the first chunk will be used, otherwise, the second one will initialize. Note that I've done this by checking for even and odd numbers (in this particular case the first chunk would initialize whenever "FIRST" is even -- x mod 2 = 0 if even). nGen File:

    #const  FIRST 1  ;If "FIRST" = 0 use first #if, else use second #if

    ; This is a way to use #if/#endif like #if/#else/#endif
    #if(~[FIRST % 2])        ;If "FIRST" is non-zero do this:
        #const  AMPFAC .5 
        #const  TIME  15.5
        #define TEMPO #te(0, ~TIME, 56, 106, e)#
        #define WINSIZE #bn4#
        #define WINFUNC(dur) #se($dur 1. *[10 11 12 13 14 15])#
    #endif
    #if(~[FIRST + 1 % 2])    ;Otherwise, do this:
        #const  AMPFAC .97
        #const  TIME  30
        #define TEMPO #te(0, ~TIME, 100, 60, V.25)#
        #define WINSIZE #bn5#
        #define WINFUNC(dur) #se($dur 1. [12 13 14 15])#
    #endif


    $TEMPO
    i1 = 6 0 ~TIME
    {
        p2 ra(T 1. [L .01 .1])  ;granular w/ emphasis on quicker grains
        p3 dv(.1) 10            ;grain duration 10x start interval w/ 10% dev.
        p4 pf(~AMPFAC)  1.      ;grain amplitude
        p5($WINSIZE) no mo(T 1. E c2 b9) ;move across bins, low to high expon.
        p6(in) $WINFUNC(T*.5) se(T 1. [10 11])      ;grain window function
    }
    
/****/

    nGen Console Output:

            Expanding const "equation" (   1.00) into line 5...
            Expanding const "equation" (  -0.00) into line 12...
            Expanding macro "TEMPO" into line 21...
            Expanding const "TIME" (  15.50) into line 21...
            Expanding const "TIME" (  15.50) into line 22...
            Line 24: T evaluated at =   15.50
            Expanding const "AMPFAC" (   0.50) into line 26...
            Expanding macro "WINSIZE" into line 27...
            Line 27: T evaluated at =   15.50
            Expanding macro "dur" into line 28...
            Expanding macro "WINFUNC" into line 28...
            Line 28: T evaluated at =    7.75
            Line 28: T evaluated at =   15.50

    671 events generated in block #1 (i1).
    Total i-block duration:   13.53 (secs),   15.48 (beats)
    Cumulative i-block duration:   13.53 (secs)
    Global cumulative duration of all i-blocks =   13.53 (secs)


Macros (Text-Based)

Format:

#define MacroName #body#
OR
#define MacroName(argument1'argument2'...'argumentN) #$argument1 $argument2, etc.#
OR
#define MacroName(argument1#argument2#...#argumentN) #$argument1 $argument2, etc.#

Description

nGen includes a text-based macro pre-processor that is modeled on that found in the canonical version of Csound.  Like Csound's macros, this implementation is text only (doesn't recognize numerical values as such).  Macros are evaluated before the main program parses any information and can be quite complex.  Because of their potential complexity, macros can be expanded and written to an output file (use the -x option).  This file is a valid text input file for nGen -- if your macros get complicated you may wish to refer to the expansion file (or even use it as input to the program...).  Here are some macro examples followed by general comments on syntax.

You can define a macro using the #define directive.  Macros may be deleted from memory using the #undef directive (you only need to do this if you would like to use the macro name again but with new contents).


#define V1                   #0# 
#define V2                   #100# 
#define Range1               #[g $V1 $V2]# 
#define Range2               #[b $V1 $V2]# 
#define mo(percent'low'high) #mo(T*$percent 1. $low $high)# 
  

Now to call the "mo" macro:

$mo(.5'$Range1'$Range2) 
/* can also be called like this:$mo(.5#$Range1#$Range2) */ 
  

Expands to:

mo(T*.5 1. [g 0 100] [b 0 100])
  

Now, to remove the macro from memory (you need only do this when redefining the contents of a predefined macro name):

#undef mo

Notes on Macro Syntax:


Macros (Numeric) and Expressions

Numeric macros (or more aptly, numeric constants) are preprocessor macros that are recognized as numbers for the purpose of altering them or plugging them into expressions for special circumstances during the pre- or post- processing of an input file. They are parsed as preprocessor directives (like macros) *after* regular (textual) macros (i.e., a textual macro can include a numeric macro (variable)).

TO DEFINE A NUMERICAL MACRO

#const LABEL VALUE (e.g., #const END 22.45)

where LABEL can be any text string that begins with a letter and can contain any alphanumeric character including the '_' after the first character. This is just like textual macros.

CALLING NUMERICAL MACROS
There are two ways to call macros (the ~ (tilde) flags them):

  • A) as a name: ~LABEL (e.g. ~END)
  • B) as an equation (e.g.: ~[A o B o C o D ...])
  • EQUATIONS
    In the equation syntax, A-D represent "terms". A term can be an integer or a floating point number, a previously declared variable name, or a special token (see below). Equations must be on the same line.

    OPERATIONS
    These are identical to the "EX" DDF. The available operations are +, -, *, /, and %. IMPORTANT: the order of operation is from *left to right*. For example:

    ((((A o B) o C) o D) o E). So, 7 + 2 * 3 = 27 and 2 * 7 + 3 = 17.

    SPECIAL TOKENS
    There are three special tokens, "@1", "@2", and "?":

    N.B. At the moment numerical macros will always evaluate to a floating- point number with a decimal-point precision of 3. This will make for some problems if you have to use them as integers. In some cases, since the variable evaluation is preprocessed you can get around this by using the "IN" input mode. You can't, however, use a variable in the i-block header for the number of events. :(

    As stated above, numeric macros and equations are processed after textual macros. In this way, numeric macros and equations can be part of a textual macro argument and/or definition (since they won't be expanded until after all of the textual macros are expanded). For example, in:

    
    #const Interval 10 ; time between blocks
    #define NEXT #~[@2 + Interval]#
    
    ...later...
    
    i7=4 $NEXT -10 { 
    
      
    $NEXT (a text-based macro) can be placed anywhere but will not be expanded in the textual macro definition (but rather in the final macro-expanded file). In this example, $NEXT is serving as the start time for each i-block. "@2" will be changing its value at the end of each i-block in the input file -- it will be accruing time with each new i-block.