#include "cs.h"                     //  UGRW1.C
                                    //
#include "ugrw1.h"
#include <math.h>

// Do List:
//
// Clean up code in zak - use arrays rather than messy pointer stuff.
//
// Clean up doco file.
//
// Add maxampr to ugrw1.doc


//      27 August 1996:
//
//  Offset bug in ftkrchkw() fixed, as per my 3 Feb bug report.
// 
//  1 February 1996:
//
//  Offset bugs fixed in init time table write - same as in table read.
//
//  See itblchkw()
//
//  New ugen 7 January 1996 - peak.
// 
//  Unit generators by Robin Whittle    8 September 1995    
//  UGRW1.H contains data structures.
//  
//  See UGRW1.DOC for more details on using these ugens.  Essential
//  functional documentation for them is included here to ensure it
//  is never separated from the code.
//
// Changes required to other files:
//---------------------------------
//
// 1 -  entry.c needs new function prototypes (see end of this file) and
//      new lines in opcodlst - see notes below with lines in {}.
//
// 2 -  A new fgens.c is needed to provide ftfindp() - finding tables at
//      performance time.
//
// 3 -  prototype.h has a new entry for ftfindp() - see start of fgens.c.
//
// 4 -  The makefile has various additions - see end of this file.
//
//
// Table write ugens
//------------------
//  
//  These table write ugens are adapted from similar code in my modified, 
//  "bug free" version of the table read code in UGENS2.H & .C.
//
//      Ugens:      Subroutines:    Data structure:
//
//      tablew      tblsetw()       TABLEW 
//                  ktablew()       "
//                  tablew()        "
//              
//      itablew     itablew()       "
//
//      tablewkt    tblsetwkt()     TABLEW
//                  ktablewkt() "
//                  tablewkt()  "
//--                
//
//  These find out the length of a table, and write the guard point
//  of a table. 
//
//      Ugens:      Subroutines:    Data structure:
//
//      tableng     tableng()       TABLENG
//      itableng    itableng()
//
//      tablegpw    tablegpw()      TABLEGPW
//      itablegpw   itablegpw()
//
//  Two ugens to manipulate the entire contents, or part of the contents
//  of a table in a single k cycle.
//
//      Ugens:      Subroutines:    Data structure:
//
//      tablemix    tablemix()      TABLEMIX
//      itablemix   itablemix()     
//
//      tablecopy   tablecopy()     TABLECOPY
//      itablecopy  itablecopy()
//  
//  Two ugens which enable a rate reading and writing to sequential 
//  locations in tables.  Useful of a rate manipulation of table contents
//  and conversely for writing a rate data into a table, where k rate
//  code can manipulate it before it is written back to an a rate variable.
//
//      Ugens:      Subroutines:    Data structure:
//
//      tablera     tableraset()    TABLERA
//                  tablera()       
//
//      tablewa     tablewaset()    TABLEWA
//                  tablewa()       
//
//------
//  
//  The following ugens are fudges - it would be better to have arrays -
//  like ablah[kx][ky].
//
//  Arrays should be local and global, multidimensional, a, k and i rate.
//
//  I will use these to get things done until arrays are implemented in
//  the core of the language.
//
//  The zak system uses one area of memory as a global i or k rate 
//  patching area, and another for audio rate patching.
//  
//      Ugens:      Subroutines:    Data structure:
//
//      zakinit     zakinit()       ZAKINIT
//
//      zir         zkset()         -
//      zkr         zir()           ZKR
//                  zkr()           ZKR
//      ziw         ziw()           ZKW
//      zkw         zkw()           ZKW
//      ziwm        ziwm()          ZKWM
//      zkwm        zkwm()          ZKWM
//      zkmod       zkmod()         ZKMOD
//      zkcl        zkcl()          ZKCL
//
//      zar         zaset()         -
//      zaw         zar()           ZAR
//                  zaw()           ZAW
//      zarg        zarg()          ZARG
//
//      zawm        zawm()          ZAWM
//      zamod       zamod()         ZAMOD
//      zacl        zacl()          ZACL
//
//---
//
//  Four ugens for reading the abosolute time since the start of the
//  performance, and two for reading the time since the instrument was
//  initialised:
//
//      Ugens:      Subroutines:    Data structure:
//
//      timek       timek()         RDTIME
//      itimek      itimek()        "
//
//      times       times()         "
//      itimes      itimes()        "
//
//      instimek    instimset()     "
//      instimes    instimek()      "
//                  instimes()      "
//---
//
//  Two ugens for printing a k rate variable during k rate processing.
//
//      Ugens:      Subroutines:    Data structure:
//
//      printk      printk()        PRINTK
//      printks     printks()       PRINTKS


//****************************************************************************
//  
// Table write syntax
//-------------------
//
// Use itablew when all inputs are init time variables or constants 
// and you only want to run it at the initialisation of the instrument. 
//
// tablew is for writing at k or at a rates, with the table number being
// specified at init time.
//
// tablewkt is the same, but uses a k rate variable for selecting the table 
// number.  The valid combinations of variable types are shown by the first
// letter of the variable names:
//
//      itablew   isig, indx, ifn [,ixmode] [,ixoff] [,iwgmode]
//
//      tablew    ksig, kndx, ifn [,ixmode] [,ixoff] [,iwgmode] 
//      tablew    asig, andx, ifn [,ixmode] [,ixoff] [,iwgmode] 
//
//      tablewkt  ksig, kndx, kfn [,ixmode] [,ixoff] [,iwgmode] 
//      tablewkt  asig, andx, kfn [,ixmode] [,ixoff] [,iwgmode] 
//
// isig, ksig,  The value to be written into the table.
// asig     
//
// indx, kndx,  Index into table, either a positive number range
// andx         matching the table length (ixmode = 0) or a 0 to 1 
//              range (ixmode != 0)
//
// ifn, kfn     Table number. Must be >= 1. Floats are rounded down to 
//              an integer.  If a table number does not point to a 
//              valid table, or the table has not yet been loaded 
//              (gen01) then an error will result and the instrument 
//              will be de-activated.
//
// ixmode   Default 0  ==0  xndx and ixoff ranges match the length 
//                          of the table.
//
//                     !=0  xndx and ixoff have a 0 to 1 range.
//
//
// ixoff    Default 0  ==0  Total index is controlled directly by
//                          xndx.  ie. the indexing starts from the
//                          start of the table.
//
//                     !=0  Start indexing from somewhere else in 
//                          the table. Value must be positive and
//                          less than the table length (ixmode = 0) 
//                          or less than 1 (ixmode !=0
//
// iwgmode  Default 0  ==0  Limit mode      } See full explanation in
//                     ==1  Wrap mode       } ugrw1.doc
//                     ==2  Guardpoint mode }   
//
// Known bugs in table write:
//
//      Watch out for giving a number (like "5" or p6) instead of an a rate 
//      variable to the index of table write ugens operating at a rate.
//      The ugen gets a pointer to the number, but it is expecting a pointer
//      to an a rate variable - which is an array of numbers.  
//      Upsamp the number (or k rate variable) to an a rate variable first.
//
//      This pitfall could be fixed with a revamp of how Csound processes the
//      orchestra, or by making different named ugens for different types of
//      variables.


//****************************************************************************
//
// Other table manipulation ugens
// ------------------------------
//
//  ir  itableng ifn    
//  kr  tableng  kfn
//
// ifn  i rate number of function table
// kfn  k rate number of function table
//
//      These return the length of the specified table.  This will be a power
//      of two number in most circumstances - it will not show whether
//      a table has a guardpoint or not - it seems this information is not 
//      available in the table's data structure. If table is not found, then
//      0 will be returned.
//
//      Likely to be useful for setting up code for table manipulation 
//      operations, such as tablemix and tablecopy.
//
//
//      itablegpw ifn
//      tablegpw  kfn
//
//      For writing the table's guard point, with the value which is in
//      location 0.  Does nothing if table does not exist.
//
//      Likely to be useful after manipulating a table with tablemix or
//      tablecopy.
//
//------
//
//      tablemix  kdft, kdoff, klen, ks1ft, ks1off, ks1g, ks2ft, ks2off, ks2g
//      itablemix idft, idoff, ilen, is1ft, is1off, is1g, is2ft, is2off, is2g   
//
//      This ugen mixes from two tables, with separate gains into the 
//      destination table.  Writing is done for klen locations, usually
//      stepping forward through the table - if klen was positive.
//      If it is negative, then the writing and reading order is backwards - 
//      towards lower indexes in the tables.  This bidirectional option makes
//      it easy to shift the contents of a table by reading from it and 
//      writing back to it.
//
//      If klen is 0, no writing occurs. Note that the internal integer value 
//      of klen is derived from the ANSI C floor() function - which returns
//      the next most negative integer.  Hence a fractional negative klen
//      value of -2.3 would create an internal length of 3, and cause 
//      the copying to start from the offset locations and proceed for 
//      two locations to the left.
//
//      The total index for table reading and writing is calculated from the
//      starting offset for each table, plus the index value, which starts
//      at 0 and then increments (or decrements) by 1 as mixing proceeds.
//
//      These total indexes can potentially be very large, since there is no
//      restriction on the offset or the klen. However each total index for
//      each table is ANDed with a length mask (such as 0000 0111 for a table
//      of length 8) to form a final index which is actually used for 
//      reading or writing.  So no reading or writing can occur outside
//      the tables. 
//  
//      This is the same as "wrap" mode in table read and write. This process
//      does not read or write the guardpoint.
// 
//      If a table has been rewritten with one of these, then if it has a 
//      guardpoint which is supposed to contain the same value as the 
//      location 0, then call tablegpw afterwards.
//
//      The indexes and offsets are all in table steps - they are not
//      normalised to 0 - 1.  So for a table of length 256, klen would be
//      set to 256 if all the table was to be read or written.  
//
//      The tables do not need to be the same length - wrapping occurs 
//      individually for each table.
//
// kdft         Destination function table.
//
// kdoff        Offset to start writing from. Can be negative.
//
// klen         Number of write operations to perform. Negative means
//              work backwards.
//
// ks1ft ks2ft  Source function tables. These can be the same as the
//              destination table, if care is exercised about direction
//              of copying data.  
//
// ks1off ks2off Offsets to start reading from in source tables.
//
// ks1g ks2g    Gains to apply when reading from the source tables.  The 
//              results are added and the sum is written to the destination
//              table.
//  
//---
//
//      tablecopy  kdft, ksft   
//      itablecopy idft, isft
//
//      Simple, fast table copy ugens.  Takes the table length from the 
//      destination table, and reads from the start of the source table. 
//      For speed reasons, does not check the source length - just copies
//      regardless - in "wrap" mode.  This may read through the source 
//      table several times.  A source table with length 1 will cause
//      all values in the destination table to be written to its value.
//
//      Table copy cannot read or write the guardpoint.  To read it 
//      use table read, with ndx = the table length.  Likewise use
//      table write to write it.
//
//      To write the guardpoint to the value in location 0, use tablegpw.
//
//      This is primarily to change function tables quickly in a real-time
//      situation.
//
// kdft     Number of destination function table.
//
// ksft     Number of source function table.
//
//-----
//
// ar       tablera  kfn, kstart, koff
//
// kstart   tablewa  kfn, asig, koff    
//
// ar           a rate distination for reading ksmps values from a table.
//
// kfn          i oro k rate number of the table to read or write.
//
// kstart       Where in table to read or write.
//
// asig         a rate signal to read from when writing to the table.   
//
// koff         k rate offset into table. Range unlimited - see explanation 
//              at end of this section.
//
//      In one application, these are intended to be used in pairs, or with 
//      several tablera ugens before a tablewa - all sharing the same kstart 
//      variable.
//
//      These read from and write to sequential locations in a table at audio
//      rates, with ksmps floats being written and read each cycle.
//
//      tablera starts reading from location kstart.  
//
//      tablewa starts writing to location kstart, and then writes to kstart
//      with the number of the location one more than the one it last wrote.
//      (Note that for tablewa, kstart is both an input and output variable.)
//      If the writing index reaches the end of the table, then the no further
//      writing occurs and zero is written to kstart.
//
//      For instance, if the table's length was 16 (locations 0 to 15), and
//      ksmps was 5. Then the following steps would occur with repetitive
//      runs of the tablewa ugen, assuming that kstart started at 0.
//
//      Run no. Initial Final   locations written
//              kstart  kstart  
//      
//      1       0       5       0  1  2  3  4
//
//      2       5      10       5  6  7  8  9
//
//      3      10      15      10 11 12 13 14       
//  
//      4      15       0      15
//
//  This is to facilitate processing table data using standard a rate 
//  orchestra code between the tablera and tablewa ugens:
//
// ;-----------------------------
//                              ;
//  kstart = 0                  ;
//                              ; Read 5 values from table into an a rate
//                              ; variable.
//              
// lab1: atemp  tablera ktabsource, kstart, 0
//
//                              ; Process the values using a rate code.
// atemp = log(atemp)           ;
//                              ; Write it back to the table
//
// kstart tablewa ktabdest, atemp, 0    
//  
//                              ; Loop until all table locations have been
//                              ; processed.
// if ktemp > 0 goto lab1       ;
//                              ;
// ;-----------------------------
//
//      This example shows a processing loop, which runs every k cycle, 
//      reading each location in the table ktabsource, and writing the log
//      of those values into the same locations of table ktabdest.
//
//      This enables whole tables, parts of tables (with offsets and different
//      control loops) and data from several tables at once to be manipulated
//      with a rate code and written back to another (or the same) table.
//      This is a bit of a fudge, but it is faster than doing it with 
//      k rate table read and write code.
//
//  Another application is:
//
// ;-----------------------------
//                              ;
// kzero = 0                    ;
// kloop = 0                    ;
//                              ;   
// kzero tablewa 23, asignal, 0 ; ksmps a rate samples written into 
//                              ; locations 0 to (ksmps -1) of table 23.
//                              ;
// lab1: ktemp table kloop, 23  ; Start a loop which runs ksmps times, 
//                              ; in which each cycle processes one of 
// [ Some code to manipulate ]  ; table 23's values with k rate orchestra 
// [ the value of ktemp.     ]  ; code.
//                              ; 
//                              ;   
//  tablew ktemp, kloop, 23     ; Write the processed value to the table.
//                              ;
// kloop = kloop + 1            ; Increment the kloop, which is both the
//                              ; pointer into the table and the loop 
// if kloop < ksmps goto lab1   ; counter.  Keep looping until all values
//                              ; in the table have been processed.
//                              ;
// asignal tablera 23, 0, 0     ; Copy the table contents back to an a rate 
//                              ; variable.
// ;-----------------------------
//                              
//
// koff     This is an offset which is added to the sum of kstart and the internal
//          index variable which steps through the table.  The result is then
//          ANDed with the lengthmask (000 0111 for a table of length 8 - or 
//          9 with guardpoint) and that final index is used to read or write to
//          the table.  koff can be any value.  It is converted into a long using
//          the ANSI floor() function so that -4.3 becomes -5.  This is what we
//          would want when using wide rangeing offsets.
//
//          Ideally this would be an optional variable, defaulting to 0, however
//          with the existing Csount orchestra read code, such default parameters
//          must be init time only.  We want k rate here, so we cannot have a 
//          default.
//
// Notes on tablera and tablewa
//----------------------------- 
//
//      These are a fudge, but they allows all Csounds k rate operators to be
//      used (with caution) on a rate variables - something that would only
//      be possible otherwise by ksmps = 1, downsamp and upsamp.   
//                                  
//  Several cautions:
//  
// 1 -  The k rate code in the processing loop is really running at a rate,
//      so time dependant functions like port would not work normally.
//
// 2 -  This system will produce undesirable results unless the ksmps fits
//      within the table length.  For instance a table of length 16 will 
//      accomodate 1 to 16 samples, so this example will work with 
//      ksmps = 1 to 16.
// 
//      Both these ugens generate an error and deactivate the instrument if
//      a table with length < ksmps is selected. Likewise an error occurs
//      if kstart is below 0 or greater than the highest entry in the table - 
//      if kstart >= table length.
//
// 3 -  kstart is intended to contain integer values between 0 and (table 
//      length - 1).  Fractional values above this should not affect operation
//      but do not achieve anything useful.
//
// 4 -  These ugens do not do interpolation and the kstart and koff parameters
//      always have a range of 0 to (table length - 1) - not 0 to 1 as is 
//      available in other table read/write ugens.  koff can be outside this
//      range but it is wrapped around by the final AND operation.  
//
// 5 -  These ugnes are permanently in wrap mode.  When koff is 0, no wrapping
//      needs to occur, since the kstart++ index will always be within the
//      table's normal range.  koff != 0 can lead to wrapping. 
//
// 6 -  The offset does not affect the number of read/write cycles performed,
//      or the value written to kstart by tablewa.
//
// 7 -  These ugens cannot read or write the guardpoint.  Use tablegpw to 
//      write the guardpoint after manipulations have been done with tablewa.
//
//------
//
//>>>   Add these to the end of opcodlst in entry.c.
//
//    opcode   dspace       thread  outarg  inargs   isub    ksub    asub   
//
// 
// { "itablew", S(TABLEW),  1,      "",   "iiiooo", itablew, NULL,   NULL},
// { "tablew",  S(TABLEW),  7,      "",   "xxiooo", tblsetw, ktablew,tablew},
// { "tablewkt",S(TABLEW),  7,      "",   "xxkooo", tblsetwkt,ktablewkt,tablewkt},
// 
// { "itableng",S(TABLENG),1,       "i",    "i",     itableng, NULL,  NULL},
// { "tableng", S(TABLENG),2,       "k",    "k",     NULL,  tableng, NULL},
// 
// { "itablegpw",S(TABLENG),1,      "",     "i",     itablegpw, NULL,  NULL},
// { "tablegpw", S(TABLENG),2,      "",     "k",     NULL,  tablegpw, NULL},
// 
// { "itablemix",S(TABLEMIX),1,     "",   "iiiiiiiii", itablemix, NULL, NULL},
// { "tablemix", S(TABLEMIX),2,     "",   "kkkkkkkkk",tablemixset, tablemix, NULL},
// 
// { "itablecopy",S(TABLECOPY),1,   "",     "ii",    itablecopy, NULL, NULL},
// { "tablecopy", S(TABLECOPY),2,   "",     "kk", tablecopyset, tablecopy, NULL},
// 
// { "tablera", S(TABLERA),5,       "a",    "kkk",   tableraset, NULL, tablera},
// { "tablewa", S(TABLEWA),5,       "k",    "kak",   tablewaset, NULL, tablewa},




//****************************************************************************
//
// The "zak" system
// ----------------
//
//      The zak system uses one area of memory as a global i or k rate 
//      patching area, and another for audio rate patching. These are 
//      establised by a ugen which must be called once only.
//
//      zakinit isizea, isizek
//
//      isizek is the number of locations we want to reserve for floats
//      in the zk space.  These can be written and read at i and k rates.
//
//      isizea is the number of audio rate "locations" for a rate patching.
//      Each "location" is actually an array which is ksmps long.
//
//      eg. zakinit 100 30 reserves memory for locations 0 to 100 of zk space 
//      and for locations 0 to 30 of a rate za space. With ksmps = 8, this
//      would take 101 floats for zk and 248 floats for za space.
//  
//      At least one location is always allocated for both zk and za spaces.
// 
//      These patching locations can be referred to by number with the 
//      following ugens.  
//
//      There are two short, simple, fast opcodes which read a location in 
//      zk space, at either i time or at the k rate.
//
//  ir  zir indx            
//  kr  zkr kndx            
//
//      Likewise, two write to a location in zk space at i time or at the k
//      rate. 
//
//  ziw isig, indx      
//  zkw ksig, kndx      
//
//      These are fast and always check that the index is within the 
//      range of zk space.  If it is out of range, an error is reported 
//      and 0 is returned, or no writing takes place.
//
//  ziwm    isig, indx [,imix]      
//  zkwm    ksig, kndx [,imix]      
//
//      Like ziw and zkw above, except that they can mix - add the sig
//      to the current value of the variable.  If no imix is specified, 
//      they mix, but if imix is used, then 0 will cause writing (like
//      ziw and zkw) any other value will cause direct mixing.
//
//  kr  zkmod   ksig, kzkmod
//
//      zkmod is a unit generator intended to facilitate the modulation
//      of one signal by another, where the modulating signal comes from 
//      a zk variable.  Either additive or mulitiplicative modulation is 
//      provided.
//
//      ksig is the input signal, to be modulated and sent to the output
//      of the zkmod unit generator.
//
//      kzkmod controls which zk variable is used for modulation.  A positive
//      value means additive modulation, a negative value means multiplicative 
//      modulation.  A value of 0 means no change to ksig - it is transferred
//      directly to the output.
//
//      For instance kzkmod = 23 will read from zk variable 23, and add the 
//      value it finds there to ksig.  If kzkmod = -402, then ksig is 
//      multiplied by the value read from zk location 402. 
//  
//      kskmod can be an init time constant, or a k rate value.
//
//
//      zkcl    kfirst, klast
//
//      This will clear to zero one or more variables in the zk space.
//      Useful for those variables which are accumulators for mixing
//      things during the processing for each cycle, but which must be
//      cleared to zero before the next set of calculations.
//
//------
//
//      For a rate reading and writing, in the za space, we use similar 
//      opcodes:
//
// ar   zar     kndx                
//
//      kndx points to which za variable to read.  This reads the number kndx 
//      array of floats which are the ksmps number of audio rate floats to be
//      processed in a k cycle.
//
//  ar  zarg    kndx, kgain             
//
//      Similar to zar, but multiplies the a rate signal by a k rate value
//      kgain.
//
//      zaw     asig, kndx          
//
//      Writes into the array specified by kndx.
//
//      zawm    asig, kndx [,imix]      
//
//      Like zaw above, except that it can mix - add the asig to the current 
//      value of the destination za variable.  If no imix is specified, 
//      it mixes, but if imix is used, then 0 will cause a simple write (like
//      zaw) and any other value will cause mixing. 
//
//  ar  zamod   asig, kzamod
//
//      Modulation of one audio rate signal by a second one - which comes from
//      a za variable.  The location of the modulating variable is controlled
//      by the k rate variable kzamod.  This is the audio rate version of
//      zkmod described above.  
//
//      zacl    kfirst, klast
//
//      This will clear to zero one or more variables in the za space.
//      Useful for those variables which are accumulators for mixing
//      things during the processing for each cycle, but which must be
//      cleared to zero before the next set of calculations.
//
//------
//
//>>>   Add these to the end of opcodlst in entry.c.
//
//    opcode   dspace       thread  outarg  inargs  isub     ksub   asub    
//
// { "zakinit", S(ZAKINIT), 1,      "",     "ii",   zakinit, NULL,  NULL},
// 
// { "zir",    S(ZKR),  1,          "i",    "i",    zir,     NULL,  NULL},
// { "zkr",    S(ZKR),  3,          "k",    "k",    zkset,   zkr,   NULL},
// 
// { "ziw",    S(ZKW),  1,          "",     "ii",   ziw,     NULL,  NULL},
// { "zkw",    S(ZKW),  3,          "",     "kk",   zkset,   zkw,   NULL},
// 
// { "ziwm",   S(ZKWM), 1,          "",     "iip",  ziwm,    NULL,  NULL},
// { "zkwm",   S(ZKWM), 3,          "",     "kkp",  zkset,   zkwm,  NULL},
// 
// { "zkmod",  S(ZKMOD), 2,         "k",    "kk",   NULL,    zkmod, NULL},
// 
// { "zkcl",   S(ZKCL), 3,          "",     "kk",   zkset,   zkcl,  NULL},
// 
// 
// { "zar",    S(ZAR),  5,          "a",    "k",    zaset,   NULL,  zar},
// { "zarg",   S(ZARG), 5,          "a",    "kk",   zaset,   NULL,  zarg},
// 
// { "zaw",    S(ZAW),  5,          "",     "ak",   zaset,   NULL,  zaw},
// 
// { "zawm",   S(ZAWM), 5,          "",     "akp",  zaset,   NULL,  zawm},
// 
// { "zamod",  S(ZAMOD), 4,         "a",    "ak",   NULL,    NULL,  zamod},
// 
// { "zacl",   S(ZACL), 5,          "",     "kk",   zaset,   NULL,  zacl},
//  
// What types of input variables are used?  
//
//                                      Runs at time
// ir   zir     indx                    i
// kr   zkr     kndx                            k
// 
//      ziw     isig, indx              i
//      zkw     ksig, kndx                      k
//
//      ziwm    isig, indx, imix        i
//      zkwm    ksig, kndx, kmix                k
//
//      zkcl    kfirst, klast                   k
//
// ar   zar     kndx                            k but does arrays
// ar   zarg    kndx, kgain                     k but does arrays
// 
//      zaw     asig, kndx                      k but does arrays
//
//      zawm    asig, kndx, kmix                k but does arrays
//
//      zacl    kfirst, klast                   k but does arrays
//
//
// isig         }
// indx         } Known at init time
// imix         }
//
// ksig         }
// kndx         }
// kmix         } k rate variables 
// kfirst       }
// klast        }
// kgain        }
//
// asig         } a rate variable - an array of floats.


//*************************************************************************
//
// Simple time reading ugens
//--------------------------
//
// kr   timek
// kr   times
//
//      timek returns a float containing an integer - the number of k cycles 
//      since the start of the performance of the orchestra.  
//
//      times returns a float with the number of seconds. 
//
//      They both expect a k rate variable as their output. There are no 
//      input parameters.
//
// ir   itimek
// ir   itimes
//
//      itemek and itimes are similar ugens which only operate during the 
//      initialisation of an instance of the instrument.
//
// kr   instimek
// kr   instimes
//
//      These are similar to timek and times, except they return the
//      time since the start of this instance of the instrument.
//
//>>>   Add these to the end of opcodlst in entry.c.
//
//    opcode     dspace     thread  outarg  inargs  isub     ksub   asub    
//
// { "itimek",   S(RDTIME), 1,      "i",    "",     timek,   NULL,  NULL},
// { "itimes",   S(RDTIME), 1,      "i",    "",     times,   NULL,  NULL},
// { "timek",    S(RDTIME), 2,      "k",    "",     NULL,    timek, NULL},
// { "times",    S(RDTIME), 2,      "k",    "",     NULL,    times, NULL},
// { "instimek", S(RDTIME), 3,      "k",    "",   instimset, instimek, NULL},
// { "instimes", S(RDTIME), 3,      "k",    "",   instimset, instimes, NULL},


//*************************************************************************
//
// Two ugens for printing at k rate
//---------------------------------
//
//      printk prints one k rate value on every k cycle, every second or at 
//      intervals specified.  First the instrument number is printed, then 
//      the absolute time in seconds, then a specified number of spaces, then
//      the value.  The variable number of spaces enables different values to
//      be spaced out across the screen - so they are easier to view. 
//      This is for debugging orchestra code.
//
//      printk  itime, kval [, ispace]
//
//      itime How much time in seconds is to elapse between printings.  
//
//      kval  The number to be printed. 
//
//      ispace How many spaces to insert before it is printed.  (Max 120.)
//      Default = 0.
//
//      The first print is on the first k cycle of the instance of the 
//      instrument.
//
//------
//
//      printks prints numbers and text, with up to four printable numbers 
//      - which can be i or k rate values.
//
//  printks "txtstring", itime, kval1, kval2, kval3, kval4
//
//  txtstring   Text to be printed first - can be up to 130 characters at 
//              least.  _Must_ be in double quotes.
//      
//              The string is printed as is, but standard printf %f etc.
//              codes are interpreted to print the four parameters.
//  
//              However the \n style of character codes are not interpreted 
//              by printf.  (They are converted by the C compiler in 
//              string literals found in the C program.) This ugen therefore
//              provides certain specific codes which are expanded:
//
//              \n or \N        Newline
//              \t or \T        Tab
//
//              ^               Escape character
//              ^^              ^
//
//              ~               Escape and '[' These are the leadin codes for 
//                      MSDOS ANSI.SYS screen control characters.
//              ~~              ~
//
//              An init error is generated if the first parameter is not
//              a string of length > 0 enclosed in double quotes.
//
//              A special mode of operation allows this ugen to convert 
//              the first input parameter into a 0 to 255 character, and
//              to use it as the first character to be printed.  This enables
//              a Csound program to send arbitrary characters to the console -
//              albeit with a little awkwardness.  printf() does not have a
//              format specifier to read a float and turn it into a byte
//              for direct output. We could add extra code to do this if
//              we really wanted to put arbitrary characters out with ease.
//
//              To acheive this, make the first character of the string a
//              # and then, if desired continue with normal text and format
//              specifiers.  Three more format specifers may be used - they
//              access kval2, kval3 and kval4. 
//      
// itime How much time in seconds is to elapse between printings.  
//
// kvalx The k rate values to be printed. Use 0 for those which are not
//      used.   
//
//
//>>>   Add these to the end of opcodlst in entry.c.
//
//    opcode   dspace       thread  outarg  inargs  isub       ksub    asub 
//
// { "printk", S(PRINTK),   3,      "",     "iko",  printkset, printk, NULL},
// { "printks",S(PRINTKS),  3,      "",     "Sikkkk",printksset, printks, NULL},



//*************************************************************************
//
// Two ugens for tracking peak signal levels
//------------------------------------------
//
//      peakk takes k rate inputs and peak takes a rate inputs.
//
//      They maintain the output k rate variable as the peak absolute
//      level so far received.
//
//      kpeakout peakk  ksigin
//      kpeakout peaka  asigin
//
//
// kpeakout Output equal to the highest absolute value received 
//          so far.
//          This is effectively an input to the ugen as well, since it
//          reads kpeakout in order to decide whether to write 
//          something higher into it.
//
// ksigin   k rate input signal.
// asigin   a rate input signal.
//
//>>>   Add these to the end of opcodlst in entry.c.
//
//    opcode   dspace       thread  outarg  inargs  isub       ksub    asub 
//
// { "peakk", S(PEAK),      2,      "k",    "k",    NULL,   peakk,    NULL},
// { "peak", S(PEAK),       4,      "k",    "a",    NULL,   NULL,     peaka},

//  ! ! ! !
//
//      See the end of this file for details of what other things to 
//      add to entry.c and how to tweak the makefile.


//-----------------------------------------------------------------------------

                                    // Table write code
                                    //
                                    // The way that the k rate table
                                    // numbers are handled by different
                                    // ugens and functions is analogous
                                    // to the approach used in the new
                                    // ugens2.c.
                                    //
                                    // itblchkw()
                                    //
                                    // Internal function itblchkw().  
                                    // Called at init time by tblsetw() to 
                                    // initialise some of the variables in 
                                    // TABLEW - which is pointed to by p.
                                    // 
                                    // Also called by itablew().
                                    //
                                    // A similar function ptblchkw() does
                                    // the same job at performance time - 
                                    // k processing cycles.
                                    //
                                    // Both these functions are virtually
                                    // identical to those itblchk() and
                                    // ptblchk() in ugens2.c.  The 
                                    // differences are:
                                    //
                                    // 1 -  They use TABLEW instead of 
                                    //      TABLE.
                                    //
                                    // 2 -  There is no iwrap parameter.
                                    //
                                    // 3 -  There is an igwmode parameter.
                                    
int itblchkw(TABLEW *p)
{
                                    // Get pointer to the function table
                                    // data structure of the table number
                                    // specified in xfn. Return 0 if
                                    // it cannot be found.
                                    //
                                    // ftfind() generates an error message 
                                    // if the table cannot be found. This
                                    // works OK at init time.  It also
                                    // deactivates the instrument.
                                    //
                                    // It also checks for numbers < 0, 
                                    // and table 0 is never valid, so
                                    // we do not need to check here for 
                                    // the table number being < 1.

    if ((p->ftp = ftfind(p->xfn)) == NULL)
        return(NULL);

                                    // Table number is valid.
                                    //
                                    // Although TABLEW has an integer
                                    // variable for the table number
                                    // (p->pfn) we do not need to 
                                    // write it.  We know that
                                    // the k and a rate functions which
                                    // will follow will not be expecting
                                    // a changed table number.
                                    //
                                    // p->pfn exists only for checking
                                    // table number changes for functions
                                    // which are expecting a k rate
                                    // table number.
                
                                    // Set denormalisation factor to 1 or 
                                    // table length, depending on the 
                                    // state of ixmode.
                                    // 1L means a 32 bit 1.
    if (*p->ixmode)
        p->xbmul = p->ftp->flen;

    else    p->xbmul = 1L;

                                    // Multiply the ixoff value by the 
                                    // xbmul denormalisation factor and 
                                    // then check it is between 0 and the
                                    // table length.

    if (    ( p->offset = p->xbmul * *p->ixoff) < 0. 
|| p->offset > p->ftp->flen
) { sprintf(errmsg, "Table write offset %f < 0 or > tablelength", p->offset); initerror(errmsg); return(NULL); } p->iwgm = *p->iwgmode; // Return 1 to say everything is OK. return(1); } //************************************* // // ptblchkw() // // This is called at init time // by tblsetwkt() to set up the TABLEW // data structure for subsequent // k and a rate operations which are // expecting the table number to // change at k rate. // // tblsetwkt() does very little - // just setting up the iwgm variable // in TABLE. All the other variables // depend on the table number. This // is not available at init time, // so the following two functions must // look for the changed table number // and set up the variables accordingly // - generating error messages in a // way which works at performance time. // // k rate a rate // // ktablewkt tablewkt ptblchkw(TABLEW *p) { // TABLEW has an integer variable for // the previous table number (p->pfn). // // Now (at init time) we do not // know the function table number // which will be provided at perf // time, so set p->pfn to 0, so that // the k or a rate code will recognise // that the first table number is // different from the "previous" one. p->pfn = 0; // The only other thing to do is // write the iwgmode value into the // immediate copy of it in TABLEW. p->iwgm = *p->iwgmode; } //----------------------------------------------------------------------------- // tblsetw() // // This is called at init time // to set up TABLEW for the a and k // rate table read functions which // are expecting the table number to // be fixed at i time. // // Call the itblchkw() function to // do the work. void tblsetw(TABLEW *p) { itblchkw(p); } // tblsetwkt() // // This is called at init time // to set up TABLEW for the a and k // rate table read functions which // are expecting the table number to // be a k rate variable. // // Call the ptblchkw() function to // do the work. void tblsetwkt(TABLEW *p) { ptblchkw(p); } // itablew() // // Used to write to a table only at // init time. It is called (via the // opcodlst in entry.c) for the // itablew opcode. // // It sets up some variables in the // TABLEW data structure for this // instance and calls ktablew() once // to write to the table. void itablew(TABLEW *p) { void ktablew(); if (itblchkw(p)) ktablew(p); } //----------------------------------------------------------------------------- // ktablew is called with p pointing // to the TABLEW data structure - // which contains the input arguments. void ktablew(TABLEW *p) { // Local variables . . . // // Pointer to data structure for // accessing the table we will be // writing to. FUNC *ftp;

// 32 bit integers for pointing into // table and for the table length - // which is always a power of 2. // The table must actually be one // more longer than this if it has a // guard point. register long indx, length; // Float for calculating where we are // going to read from. register float ndx; // Pointer to float, we will use it // to write into table. register float *ptab; //------------------------------------- // Assume that TABLEW has been // set up correctly. // Set up local variables. ftp = p->ftp; ndx = *p->xndx; length = ftp->flen; // Multiply ndx by denormalisation // factor. and add in the offset - // already denormalised - by tblchkw(). // // xbmul = 1 or table length depending // on state of ixmode. ndx = ( ndx * p->xbmul) + p->offset; // ndx now includes the offset and is // ready to address the table. // However we have three modes to // consider: // // igwm = 0 Limit mode. // 1 Wrap mode. // 2 Guardpoint mode. if (p->iwgm == 0)
{ // Limit mode - when igmode = 0. // // Limit the final index to 0 and // the last location in the table. // // Wrap and guard point modes are // acheived by code following this // which only runs if igmode != 0. // Limit to (table length - 1). // // First we have to turn ndx into // a 32 bit integer. This is non- // trivial since we want it to // round to the next most negative // integer. // A simple cast from float to integer // does not do this - -2.3 is rounded // to -2, we want it rounded to -3. // // This behaviour may not depend on // ANSI C, but on the compiler and // machine - so we cannot use it. // // Instead use the ANSI maths function // float floor(float) // // There may be faster ways of doing // it, but the results could be // machine dependant. indx = (long) floor(ndx); // Limit the high values. if (indx > length - 1) indx = length - 1; // Now limit negative values to zero. // 0L is a macro for a long zero. // (Following the example set in // ktable(). Why is this used? else if (indx < 0L) indx = 0L; } // Wrap and guard point mode. // // In guard point mode only, add 0.5 // to the index. else { if (p->iwgm == 2) ndx += 0.5; indx = (long) floor(ndx); // Both wrap and guard point mode. // // The following code uses an AND with // an integer like 0000 0111 to // wrap the current index within the // range of the table. indx &= ftp->lenmask; // Calculate the address of where we // want to write to, from indx and the // starting address of the table. } ptab = ftp->ftable + indx; // Write the input value to the table. *ptab = *p->xsig; // If this is guard point mode and we // have just written to location 0, // then also write to the guard point. if ( (p->iwgm == 2) & indx == 0 )
{ ptab += ftp->flen; *ptab = *p->xsig; } } //----------------------------------------------------------------------------- // tablew() is similar to ktablew() // above, except that it processes // two arrays of input values and // indexes. // These arrays are ksmps long. void tablew(TABLEW *p) { // Local variables . . . // // Pointer to function table data // structure. register FUNC *ftp; // Pointers to floats: // // * psig Array of input values // to be written to // table. // // * pxndx Array of input index // values. // // * ptab Pointer to start of // table we will write. // // * pwrite Pointer to location in // table where we will // write. register float *psig, *pxndx, *ptab, *pwrite; // 32 bit integers: // // indx Used to read table. // // mask ANDed with indx to // make it wrap within // table. // // length Length of table - // always a power of two, // even if the table has // a guard point. // // For instance length = 8, mask = // 0000 0111, normal locations in table // are 0 to 7. Location 8 is the // guard point. table() does not read // the guard point - tabli() does. register long indx, mask, length; // Local copy of iwgm for speed. register long liwgm; // nsmps is the counter for the loop // which processes the data. // declare it as an integer, and // intialize it to the global variable // ksmps. register int nsmps = ksmps; // Float local variables. // // ndx Index. // // xbmul Normalisation factor // to adjust for ndx // having a 0 to 1 range. // Equal to 1 or length // depending on ixmode. // // offset Offset to add to ndx. // Already denormalised. register float ndx, xbmul, offset; //------------------------------------- // Assume that TABLEW has been // set up correctly. // Set up local variables. ftp = p->ftp;

psig = p->xsig; pxndx = p->xndx; ptab = ftp->ftable; mask = ftp->lenmask; length = ftp->flen; liwgm = p->iwgm; xbmul = p->xbmul; offset = p->offset; // Main loop - for the number of a // samples in a k cycle. do
{ // Read in the next raw index and // increment the pointer ready for // the next cycle. // // Then multiply the ndx by the // denormalising factor and add in // the offset. ndx = (*pxndx++ * xbmul) + offset; // ndx ready, now consider the // three iwgmode modes. // // igwm = 0 Limit mode. // 1 Wrap mode. // 2 Guardpoint mode. if (liwgm == 0)
{ // Limit mode - when igmode = 0. // Limit to (table length - 1). // // First we have to turn ndx into // a 32 bit integer. indx = (long) floor(ndx); // Limit the high values. if (indx > length - 1) indx = length - 1; // Now limit negative values to zero. else if (indx < 0L) indx = 0L; } // Wrap and guard point mode. // // In guard point mode only, add 0.5 // to the index. else { if (liwgm == 2) ndx += 0.5; indx = (long) floor(ndx); // Both wrap and guard point mode. // // AND with an integer like 0000 0111 // to wrap the index within the // range of the table. indx &= mask; // Calculate the address of where we // want to write to, from indx and the // starting address of the table. } pwrite = ptab + indx; // Write the input value to the table. // Auto increment pointer to input // array. *pwrite = *psig++; // If this is guard point mode and we // have just written to location 0, // then also write to the guard point. if ( (liwgm == 2) & indx == 0 )
{ // Note that since pwrite is a pointer // to a float, adding length to it // adds (4// length) to its value since // the length of a float is 4 bytes. pwrite += length; // Decrement psig to make it point // to the same input value. // Write to guard point. psig--; *pwrite = *psig++; } } while(--nsmps); } //************************************* // // ktablewkt() and tablewkt() // // These call ktablew() and tablew() // above - after they have set up // TABLEW after the k rate table number // changes. // // Prior to these running, we can // assume that tblsetwkt() has been // run at init time. // // tblsetwkt() does very little - // just setting up the iwgmode variable // in TABLEW. All the other variables // depend on the table number. This // is not available at init time, // so the following two functions must // look for the changed table number // and set up the variables accordingly // - generating error messages in a // way which works at performance time. // // k rate a rate // // ktablewkt tablewkt // // Since these perform identical // operations, apart from the // function they call, create a // common function to do this work: // // ftkrchkw() int ftkrchkw(TABLEW *p) { // Check the table number is >= 1. // Print error and deactivate // if it is. // Return 0 to tell calling function // not to proceed with a or k rate // operations. // // This was not necessary in the // versions of ktablew() and tablew() // because ftfind() would catch a // table number < 1. // // However, in this case, we only call // ftfindp() if the table number // changes from p->pfn, so we want // to generate an error message if // the ugen is ever called with a // table number of 0. While we // are about it, catch negative values // too. if (*p->xfn < 1)
{
sprintf(errmsg, "Table write k rate function table no. %f < 1", *p->xfn); perferror(errmsg); return (NULL); } // Check to see if table number // has changed from previous value. // // On the first run through, the // previous value will be 0. if (p->pfn != (long)*p->xfn) { // If it is different, check to see // if the table exists. // If it doesn't, an error message // should be produced by ftfindp() // which should also deactivate // the instrument. // Return 0 to tell calling function // not to proceed with a or k rate // operations. // // ftfind is in a new version of // fgens.c. A prototype for it // should be added to prototype.h. if ( (p->ftp = ftfindp(p->xfn) ) == NULL)
{ return (0); }

// p->ftp now points to the FUNC // data structure of the newly // selected table. // // Now we set up some variables in // TABLEW ready for the k or a rate // functions which follow. // Write the integer version of the // table number into pfn so we // can later decide whether subsequent // calls to the k and a rate functions // occur with a table number value // which points to a different table. // // p->pfn is an integer. p->pfn = *p->xfn; // Set denormalisation factor to 1 or // table length, depending on the // state of ixmode. // 1L means a 32 bit 1. if (*p->ixmode) p->xbmul = p->ftp->flen; else p->xbmul = 1L; // Multiply the ixoff value by the // xbmul denormalisation factor and // then check it is between 0 and the // table length. if ( (p->offset = p->xbmul * *p->ixoff) < 0. || p->offset > p->ftp->flen
)
{ sprintf(errmsg, "Table write offset %f < 0 or > tablelength", p->offset); perferror(errmsg); return(NULL); } // If all is well, return 1 to // tell calling function to proceed // with a or k rate operations. return(1); } } // Now for the two functions, which // are called as a result of being // listed in opcodlst in entry.c void ktablewkt(TABLEW *p) { if ( ftkrchkw(p) ) ktablew(p); } void tablewkt(TABLEW *p) { if ( ftkrchkw(p) ) tablew(p); } //***************************************************************************** // Reading the table length // tableng() // // At k rate - performance time. // See similar function to do it at // init time - itableng(). // // The means of finding the table, // and of reporting an error differ // between these i time and perf time. void tableng(TABLENG *p) { // Local variables // // Pointer to data structure for // accessing table. register FUNC *ftp;

// Check to see we can find the table // and find its location in memory. // Returns zero if not found. // Report and error, which will cause // this instrument to be de-activated. if ((ftp = ftfindp(p->xfn)) == NULL) { *p->kout = 0;
sprintf(errmsg, "Table %f not found\n", p->xfn); perferror(errmsg); }
// Return length as a float if we // do find the table. else *p->kout = (float) ftp->flen; } //------------------------------------- // itableng() // // At init time. void itableng(TABLENG *p) { // Local variables // // Pointer to data structure for // accessing table. register FUNC *ftp;
// Check to see we can find the table // and find its location in memory. // Returns zero if not found. // Report and error, which will cause // this instrument initialisation to // fail. if ((ftp = ftfind(p->xfn)) == NULL) { *p->kout = 0;
sprintf(errmsg, "Table %f not found\n", p->xfn); initerror(errmsg); }
// Return length as a float if we // do find the table. else *p->kout = (float) ftp->flen; } //----------------------------------------------------------------------------- // Writing the guardpoint // tablegpw() // // At k rate - performance time. // See similar function to do it at // init time - itablegpw(). // // The means of finding the table, // and of reporting an error differ // between these i time and perf time. // // Read the value in location 0 and // write it to the guard point. void tablegpw(TABLEGPW *p) { // Local variables // // Pointer to data structure for // accessing table. register FUNC *ftp;
// Pointer to start of table. register float *ptab; // Value read from location 0 in // table. register float val0; // Temporary storage for length - // in floats, not in bytes. register long length;
// Check to see we can find the table // and find its location in memory. if ((ftp = ftfindp(p->xfn)) == NULL) { sprintf(errmsg, "Table %f not found\n", p->xfn); perferror(errmsg); }

// Find length of table. else { length = (long) ftp->flen; ptab = ftp->ftable; // Now write from location 0 to // the guard point which is at // location length. val0 = *ptab; ptab = ptab + length; *ptab = val0; } } //------------------------------------- // itablegpw() // // At init time. void itablegpw(TABLEGPW *p) { // Local variables // // Pointer to data structure for // accessing table. register FUNC *ftp;

// Pointer to start of table. register float *ptab; // Value read from location 0 in // table. register float val0; // Temporary storage for length - // in floats, not in bytes. register long length;

// Check to see we can find the table // and find its location in memory. if ((ftp = ftfind(p->xfn)) == NULL) { sprintf(errmsg, "Table %f not found\n", p->xfn); initerror(errmsg); }
// Find length of table. else { length = (long) ftp->flen; ptab = ftp->ftable; // Now write from location 0 to // the guard point which is at // location length. val0 = *ptab; ptab = ptab + length; *ptab = val0; } } //----------------------------------------------------------------------------- // tablemix functions // tablemixset() // // Called at i time prior to // the k rate function tablemix(). void tablemixset(TABLEMIX *p) { // There may be no input values now - // they are typically k rate and so // are not present at i time. // // Set to zero the three variables // with which we check to see if // the k rate table numbers have // changed. These are in the // TABLEMIX structure which is // specific to this instance of the // ugen. However its values have not // been initialised, so they could // be anything. p->pdft = 0; p->ps1ft = 0; p->ps2ft = 0; } //------------------------------------- // tablemix() // // k rate version - see itablemix() // for the init time version. // // These are similar but require // two different approaches to // finding tables and reporting // errors. // // This adventurous ugen uses nine // parameters which are all assumed // to be k rate variables - which // could change at any time. // // Six of these will used directly. // // However, three of them will be // checked to see if they have changed // from last time - the three // variables which point to the // destination and source tables. // // If they change, then the new values // will be used to search for a new // table. Otherwise, existing pointers // will be used to access the data // structures for the tables. // // Both the i and k rate operations // have a lot in common, so use a // common function domix(). // // Declare it here. void domix(TABLEMIX *p); void tablemix(TABLEMIX *p) { // Check the state of the three // table number variables. // // Error message if any are < 1 // and no further action. // // We cannot leave it for ftfindp() // to find 0 values, since it is only // called if the input value is // different from the "previous" value // which is initially 0. if ( (*p->dft < 1) || (*p->s1ft < 1) || (*p->s2ft < 1)
)
{ sprintf(errmsg, "Table no. < 1 dft=%.2f s1ft=%.2f s2ft=%.2f\n", *p->dft, *p->s1ft, *p->s2ft); perferror(errmsg); return; } // Check each table number in turn. // Destination if (p->pdft != (int)*p->dft) { // Get pointer to the function table // data structure. // // ftfindp() for perf time. // ftfind() for init time. if ((p->funcd = ftfindp(p->dft)) == NULL) { sprintf(errmsg, "Destination dft table %.2f not found.\n", *p->dft); perferror(errmsg); return; } // Table number is valid. // // Save the integer version of the // table number for future reference. p->pdft = *p->dft; }
// Source 1 if (p->ps1ft != (int)*p->s1ft) { if ((p->funcs1 = ftfindp(p->s1ft)) == NULL) { sprintf(errmsg, "Source 1 s1ft table %.2f not found.\n",
*p->s1ft); perferror(errmsg); return; } p->ps1ft = *p->s1ft; }

// Source 2 if (p->ps2ft != (int)*p->s2ft) { if ((p->funcs2 = ftfindp(p->s2ft)) == NULL) { sprintf(errmsg, "Source 2 s2ft table %.2f not found.\n",
*p->s2ft); perferror(errmsg); return; } p->ps2ft = *p->s2ft; }
// OK all tables present and the // funcx pointers are pointing to // their data structures. // // The other parameters do not // need checking - and the remaining // proceedures are common to the // init time version, so call a // function to do the rest. domix(p); } //------------------------------------- // itablemix() // // identical to above, but we know it // runs at init time, so we do not // check for changes, we look for // the table with ftfind() instead of // ftfindp() and we use initerror() // instead of perferror(). void itablemix(TABLEMIX *p)
{ // Check the state of the three // table number variables. // // Error message if any are < 1 // and no further action. // // Technically we do not need to // check for values of 0 or negative // since they will all be fed to // ftfind(). // Do so anyway to be consistent // with tablemix(). // // This runs only once, so speed is // not an issue. if ( (*p->dft < 1) || (*p->s1ft < 1) || (*p->s2ft < 1)
)
{ sprintf(errmsg, "Table number < 1 dft=%.2f s1ft=%.2f s2ft=%.2f\n", *p->dft, *p->s1ft, *p->s2ft); initerror(errmsg); return; } // Check each table number in turn. // Destination // Get pointer to the function table // data structure. // // ftfind() for init time. if ((p->funcd = ftfind(p->dft)) == NULL) { sprintf(errmsg, "Destination dft table %.2f not found.\n", *p->dft); initerror(errmsg); return; } // Table number is valid. // // Save the integer version of the // table number for future reference. p->pdft = *p->dft; // Source 1 if ((p->funcs1 = ftfind(p->s1ft)) == NULL) { sprintf(errmsg, "Source 1 s1ft table %.2f not found.\n",
*p->s1ft); initerror(errmsg); return; } p->ps1ft = *p->s1ft; // Source 2 if ((p->funcs2 = ftfind(p->s2ft)) == NULL) { sprintf(errmsg, "Source 2 s2ft table %.2f not found.\n",
*p->s2ft); initerror(errmsg); return; } p->ps2ft = *p->s2ft; domix(p); } //------------------------------------- // domix() // // This is the business end of // tablemix and itablemix. // // We could be called at either // init or perf time. So we do not // make any error messages here. // // The three tables have been found // and are known to be of greater // than zero length - ftfind() and // ftfindp() check this. // // We will use the remaining input // parameters no matter what their // values are. // // Length is converted from a // float to a long, with floor() // so that -0.3 converts to -1. // // The resulting integer could be // negative - this tells us to // work backwards. // // The offsets could be anything // whatsoever - these will be added // to index to produce integers // which are rounded by the lenmask // of each table. // So we don't mind if the offsets // are all over the place. // // Likewise the gain parameters // for source tables 1 and 2, except // that if the gain of source 2 is // 0, then we do not bother reading // it. This is to save time when // all that the user wants is a copy. void domix(TABLEMIX *p) { // Local variables: // Gains for source tables 1 and 2. register float gains1, gains2; // Length - from len input parameter. // Could be negative. register long length;
// Counter for the processing loop. register long loopcount;

// Offsets for the three tables. register long offd, offs1, offs2;

// Index to be added to offsets as // we step through the tables. // If length was positive, this will // increase by one each cycle. // If length was negative, it will // step backards from 0. register long indx = 0;

// Base addresses of the three tables. // These are pointers to floats. register float *based, *bases1, *bases2; // Binary masks for the three tables // as 32 bit integers. register long maskd, masks1, masks2; // Pointers to floats in the three // tables. register float *pdest, *ps1, *ps2; // Get the two source gains. gains1 = *p->s1g; gains2 = *p->s2g; // Get the length and generate // the loop count. // // Return with no action if it is 0. if ( (length = floor(*p->len)) == 0 ) return;

if (length < 0) loopcount = 0 - length; else loopcount = length; // Get the offsets for the three tables // Use floor to reduce negative floats // to the next most negative integer. // This ensures that a sweeping // offset will wrap correctly into the // table's address space. offd = floor(*p->doff);
offs1 = floor(*p->s1off);
offs2 = floor(*p->s2off);

// Now get the base addresses and // length masks of the three tables. based = p->funcd->ftable; maskd = p->funcd->lenmask; bases1 = p->funcs1->ftable; masks1 = p->funcs1->lenmask; bases2 = p->funcs2->ftable; masks2 = p->funcs2->lenmask; // Decide which of four loops to do // based on: // // Forwards or backwards? // // Source 2 gain is zero or not? if (length > 0) { if (gains2 != 0) { // Forwards, full mix. do { // Create pointers by adding index to // offset, ANDing with mask, and // adding to base address. // // Mask, offset and index are all in // units of 1 - not the units of // 4 bytes (typically) needed to // step through memory to find // floats. // // So we make base a pointer to a // float and the compiler is smart // enough to know that when we add // an integer to a float pointer, // we want that pointers address // to change by sizeof(float)// // the value of the integer. pdest = based + (maskd & (offd + indx)); ps1 = bases1 + (masks1 & (offs1 + indx)); ps2 = bases2 + (masks2 & (offs2 + indx)); // Mix from source1 and source 2. *pdest = (*ps1 * gains1) + (*ps2 * gains2); indx++; } while (--loopcount); } else // Forwards, only read source 1 { do { pdest = based + (maskd & (offd + indx)); ps1 = bases1 + (masks1 & (offs1 + indx)); // Write from source1 to destination. *pdest = (*ps1 * gains1); indx++; } while (--loopcount); }
} else
{ // Negative length, so do things // backwards. if (gains2 != 0) { // Backwards, full mix. do { pdest = based + (maskd & (offd + indx)); ps1 = bases1 + (masks1 & (offs1 + indx)); ps2 = bases2 + (masks2 & (offs2 + indx)); // Mix from source1 and source 2. *pdest = (*ps1 * gains1) + (*ps2 * gains2); indx--; } while (--loopcount); } else // Backwards, only read source 1. { do { pdest = based + (maskd & (offd + indx)); ps1 = bases1 + (masks1 & (offs1 + indx)); // Write from source1 to destination. *pdest = (*ps1 * gains1); indx--; } while (--loopcount); }
} } //--------------------------------------------------------------------------- // tablecopy functions // tablecopyset() // // Called at i time prior to // the k rate function tablemix(). // Similar function to tablemixset(). void tablecopyset(TABLECOPY *p) { p->pdft = 0; p->psft = 0; } //------------------------------------- // tablecopy() // // k rate version - see itablecopy() // for the init time version. // // These two functions, and the // docopy() function they share are // simpler, faster, cut-down versions // of their equivalent in the // tablemix section above. // Read the comments there for a full // explanation. void docopy(TABLECOPY *p); void tablecopy(TABLECOPY *p) { // Check the state of the two // table number variables. // // Error message if any are < 1 // and no further action. if ( (*p->dft < 1) || (*p->sft < 1)
)
{ sprintf(errmsg, "Table no. < 1 dft=%.2f sft=%.2f\n", *p->dft, *p->sft); perferror(errmsg); return; } // Check each table number in turn. // Destination if (p->pdft != (int)*p->dft) { // Get pointer to the function table // data structure. // // ftfindp() for perf time. // ftfind() for init time. if ((p->funcd = ftfindp(p->dft)) == NULL) { sprintf(errmsg, "Destination dft table %.2f not found.\n", *p->dft); perferror(errmsg); return; } // Table number is valid. // // Save the integer version of the // table number for future reference. p->pdft = *p->dft; }
// Source if (p->psft != (int)*p->sft) { if ((p->funcs = ftfindp(p->sft)) == NULL) { sprintf(errmsg, "Source sft table %.2f not found.\n",
*p->sft); perferror(errmsg); return; } p->psft = *p->sft; }
// OK both tables present and the // funcx pointers are pointing to // their data structures. docopy(p); } //------------------------------------- void itablecopy(TABLECOPY *p) { // Check the state of the two // table number variables. // // Error message if any are < 1 // and no further action. if ( (*p->dft < 1) || (*p->sft < 1)
)
{ sprintf(errmsg, "Table no. < 1 dft=%.2f sft=%.2f\n", *p->dft, *p->sft); initerror(errmsg); return; } // Check each table number in turn. // Destination if (p->pdft != (int)*p->dft) { // Get pointer to the function table // data structure. // // ftfindp() for perf time. // ftfind() for init time. if ((p->funcd = ftfind(p->dft)) == NULL) { sprintf(errmsg, "Destination dft table %.2f not found.\n", *p->dft); initerror(errmsg); return; } // Table number is valid. // // Save the integer version of the // table number for future reference. p->pdft = *p->dft; }
// Source if (p->psft != (int)*p->sft) { if ((p->funcs = ftfind(p->sft)) == NULL) { sprintf(errmsg, "Source sft table %.2f not found.\n",
*p->sft); initerror(errmsg); return; } p->psft = *p->sft; }
// OK both tables present and the // funcx pointers are pointing to // their data structures. docopy(p); } //------------------------------------- // docopy() // // This is the business end of // tablecopy and itablecopy. // // See domix for full explanation. // make any error messages here. void docopy(TABLECOPY *p) { // Local variables: // Counter for the processing loop. // Set by the length of the destination // table. register long loopcount;

// Index to be added to offsets as // we step through the tables. register long indx = 0;

// Base addresses of the two tables. // These are pointers to floats. register float *based, *bases; // Binary masks for the source table // as a 32 bit integer. register long masks; // Pointers to floats in the two // tables. register float *pdest, *ps; // Set the loopcount to the destination // table length. loopcount = p->funcd->flen; // Now get the base addresses and // length masks of the tables. // Length mask should be redundant for // the destination since we are writing // to what we believe is its length - // so don't use one. based = p->funcd->ftable; bases = p->funcs->ftable; masks = p->funcs->lenmask; do { // Create source pointers by ANDing // index with mask, and adding to base // address. This causes source // addresses to wrap around if the // destination table is longer. // // Destination address is simply the // index plus the base address since // we know we will be writing within // the table. pdest = based + indx; ps = bases + (masks & indx); // Copy from source. *pdest = *ps; indx++; } while (--loopcount); } //----------------------------------------------------------------------------- // tablera functions // tableraset() // // Called at i time prior to // the k rate function tablemix(). // Similar function to tablemixset(). // Set the "previous table number" // to 0, so that at the first // k cycle, a positive table number // will be recognised as a new value. void tableraset(TABLERA *p) { p->pfn = 0; } //------------------------------------- // tablera() // // Read ksmps values from a table, // starting at location kstart. // Has an offset and wrap-around // index calculation. // // Write them to an a rate destination. // // Table number is k rate, so check // for it changing and for it being // 0. void tablera(TABLERA *p) { // Local variables // // Pointers for writing to a rate // destination, and for reading from // table. register float *writeloc, *readloc; // Local variable to hold kstart // input variable - we need to look at // it a few times. long kstart; // Local variable to hold integer // version of offset, and the length // mask for ANDing the total index // - wrapping it into the table length. long kioff, mask;

// Loop counter register int loopcount;

// Check the state of the table number // variable. // // Error message if it is < 1 // and no further action. if (*p->kfn < 1) { sprintf(errmsg, "Table kfn=%.2f < 1\n", *p->kfn); perferror(errmsg); return; } // Check to see if the table number // has changed. if (p->pfn != (int)*p->kfn) { // Get pointer to the function table // data structure. // // ftfindp() for perf time. if ((p->ftp = ftfindp(p->kfn)) == NULL) { sprintf(errmsg, "kfn table %.2f not found\n", *p->kfn); perferror(errmsg); return; } // Table number is valid. // // Save the integer version of the // table number for future reference. p->pfn = *p->kfn; // Check that the table length is // equal to or greater than ksmps. // Create error message if this is // not so. We must ensure that // the ksmps number of reads can // fit within the length of the table. if (p->ftp->flen < ksmps) { sprintf(errmsg, "Table kfn=%.2f length %ld shorter than ksmps %ld\n", *p->kfn, p->ftp->flen, ksmps); perferror(errmsg); return; } }
// Check that kstart is within the // range of the table. if ( ( (kstart = *p->kstart) < 0)
|| ( kstart >= p->ftp->flen ) )
{ sprintf(errmsg, "kstart %.2f is outside table %.2f range 0 to %ld\n", *p->kstart, *p->kfn, p->ftp->flen - 1); perferror(errmsg); return; } // Set up the offset integer rounding // float input argument to the next // more negative integer. // Also read the mask from the FUNC // data structure. kioff = floor(*p->koff); mask = p->ftp->lenmask; // We are almost ready to go, but // first check to see whether // ksmps loops from the starting // point of kstart, will take us // beyond the length of the table. // // Therefore calculate the number of // loop cycles to perform. // Eg. if kstart = 14, ksmps = 8 and // table length = 16, then we only // want to read 2 locations. // // koff is not considered here. It // changes the read location - wrapped // around the length of the table. // It does not change the number of // cycles of read/write. if ( (loopcount = p->ftp->flen - kstart) > ksmps) { // If we are not going to exceed the // length of the table, then loopcount // = ksmps. loopcount = ksmps;
} // Otherwise it is the number of // locations between kstart and // the end of the table - as calculated // above. // Main loop: // // Write sequentially into the a rate // variable. // // Read from masked totalindex // in the table, where the total index // is (kstart++ + kioff). // Initialise write location to start // of a rate destination array. writeloc = p->adest; // Transfer the data, whilst updating // pointers and masking to get // final read address. do
{ readloc = p->ftp->ftable + ( (kstart++ + kioff) & mask); *writeloc++ = *readloc; } while (--loopcount); } //----------------------------------------------------------------------------- // tablewa functions // tablewaset() // // Called at i time prior to // the k rate function tablemix(). // Same function to tablerset(). void tablewaset(TABLEWA *p) { p->pfn = 0; } //------------------------------------- // tablewa() // // Read ksmps values from an a rate // variable and write them into a // table, starting at location kstart. // // Similar to tablera() above, except // that writing is from a rate to table // and we rewrite kstart. void tablewa(TABLEWA *p) { // Local variables // // Pointers for writing to table // destination, and for reading from // a rate array. register float *writeloc, *readloc; // Local variable to hold kstart // input variable - we need to look at // it a few times. long kstart; // Local variable to hold integer // version of offset, and the lenght // mask for ANDing the total index // - wrapping it into the table length. long kioff, mask;

// Loop counter long loopcount;

// From here the main loop, (except // where noted "!!") this code is // the same as tablera above. // It is not in a common subroutine // for speed reasons and because there // are several local variables for // tablewa() that need to be written. // Check the state of the table number // variable. // // Error message if it is < 1 // and no further action. if (*p->kfn < 1) { sprintf(errmsg, "Table kfn=%.2f < 1\n", *p->kfn); perferror(errmsg); return; } // Check to see if the table number // has changed. if (p->pfn != (int)*p->kfn) { // Get pointer to the function table // data structure. // // ftfindp() for perf time. if ((p->ftp = ftfindp(p->kfn)) == NULL) { sprintf(errmsg, "kfn table %.2f not found\n", *p->kfn); perferror(errmsg); return; } // Table number is valid. // // Save the integer version of the // table number for future reference. p->pfn = *p->kfn; // Check that the table length is // equal to or greater than ksmps. // Create error message if this is // not so. We must ensure that // the ksmps number of reads can // fit within the length of the table. if (p->ftp->flen < ksmps) { sprintf(errmsg, "Table kfn=%.2f length %ld shorter than ksmps %ld\n", *p->kfn, p->ftp->flen, ksmps); perferror(errmsg); return; } }
// Check that kstart is within the // range of the table. if ( ( (kstart = *p->kstart) < 0)
|| ( kstart >= p->ftp->flen ) )
{ sprintf(errmsg, "kstart %.2f is outside table %.2f range 0 to %ld\n", *p->kstart, *p->kfn, p->ftp->flen - 1); perferror(errmsg); return; } // Set up the offset integer rounding // float input argument to the next // more negative integer. // Also read the mask from the FUNC // data structure. kioff = floor(*p->koff); mask = p->ftp->lenmask; // !! end of code identical to tablera. // We are almost ready to go, but // first check to see whether // ksmps loops from the starting // point of kstart, will take us // beyond the length of the table. // // Therefore calculate the number of // loop cycles to perform. // Eg. if kstart = 14, ksmps = 8 and // table length = 16, then we only // want to read 2 locations. // // koff is not considered here. It // changes the read location - wrapped // around the length of the table. // It does not change the number of // cycles of read/write. if ( (p->ftp->flen - kstart) > ksmps) { // If we are not going to exceed the // length of the table, then loopcount // = ksmps. loopcount = ksmps;

// Write the kstart input/output // variable to be ksmps higher than // before. *p->kstart += ksmps; } else { loopcount = p->ftp->flen - kstart; // Otherwise loopcount is the number of // locations between kstart and // the end of the table - as calculated // above. // // We have hit the end of the process // of stepping kstart up by ksmps. // Set it to 0 so the next time this // ugen is run, with the same variable // the cycle will start from the start // of the table. *p->kstart = 0; } // Main loop: // // Read sequentially from the a rate // variable. // // Write to masked total index // in the table, where the total index // is (kstart++ + kioff). // Initialise read location to start // of a rate source array. readloc = p->asig; // Transfer the data, whilst updating // pointers and masking to get // final write address. do
{ writeloc = p->ftp->ftable + ( (kstart++ + kioff) & mask); *writeloc = *readloc++; } while (--loopcount); } //***************************************************************************** //***************************************************************************** // The zak system - patching i, k and // a rate signals in a global set of // patch points - one set for i and k, // the other for a rate. // See doco at the start of this file. // There are four global variables // which are used by these ugens. // Two are pointers to floats: // // zkstart // zastart // // Starting addresses of zk and // za spaces respectively. // Initialise these to 0. // // The other two are 32 bit integers. // // zklast // // Number of the last location in // zk space - if 20 was specified // to zakinit, then this would be // 20. (zk space has 21 floats.) // // zalast // // Similarly for za space, except // that this refers to the last // array (each of ksps length) // and hence the highest numbered // of the a rate patch points. // // Initialise these to 0. // // There are currently no limits on // the size of these spaces. float *zkstart = 0, *zastart = 0; long zklast = 0, zalast = 0; //------------------------------------- // zakinit is an opcode which must be // called once to reserve the memory // for zk and za spaces. void zakinit(ZAKINIT *p) { // Local variables . . . // 32 bit integers. // // length How much RAM to // allocate. long length; //------------------------------------- // Check to see this is the first // time zakinit() has been called. // Global variables will be zero if it // has not been called. if ( (zkstart != 0) || (zastart != 0) ) { initerror("zakinit should only be called once."); return; }

if ( (*p->isizea <= 0) || (*p->isizek <= 0) ) { initerror("zakinit: both isizea and isizek should be > 0."); return; }
// Allocate memory for zk space. // This is all set to 0 and there // will be an error report if the // memory cannot be allocated. length = (*p->isizek + 1) * sizeof(float); // This (float//) cast may not be // necessary? zkstart = (float*) mcalloc(length); zklast = (long) *p->isizek;

// Likewise, allocate memory for za // space, but do it in arrays of // length ksmps. // // This is all set to 0 and there // will be an error report if the // memory cannot be allocated. length = (*p->isizea + 1) * sizeof(float) * ksmps; zastart = (float*) mcalloc(length); zalast = (long) *p->isizea;

// Mission accomplished. } //----------------------------------------------------------------------------- // I and K rate zak code. // zkset() is called at the init time // of the instance of the zir, zkr // zir and ziw ugens. It complains if // zk space has not been allocated yet. int zkset(void) { if (zkstart == 0) { initerror("No zk space: zakinit has not been called yet."); return(0); }
else return(1);
} //------------------------------------- // k rate READ code. // zkr reads from zk space at k rate. void zkr(ZKR *p) { // Local variables. // readloc Pointer to float at // location we want to // read from. register float *readloc; // Index value. register long indx; // Check to see this index is // within the limits of zk space. indx = (long) *p->ndx; if ( indx > zklast ) { perferror("zkr index > isizek. Returning 0."); *p->rslt = 0;

} else { if ( indx < 0 ) { perferror("zkr index < 0. Returning 0."); *p->rslt = 0;
} else { // Now read from the zk space and // write to the destination. // Note that for each step of 1 in // the indx, we want to increase // the memory location pointed to by // readloc by 4 (since a float takes // 4 bytes). However, since the // compiler knows that readloc // is a pointer to a float, it will // assume that any integer we add // to it (such as indx) is meant to // work in steps of 4. // // Since zkstart is already a pointer // to a float, its value is used // directly. // If readloc and zkstart were longs, // then we would need to multiply // indx by sizeof(float). readloc = zkstart + indx; *p->rslt = *readloc; } } } //------------------------------------- // zir reads from zk space, but only // at init time. // // Call zkset() to check that zk space // has been allocated, then do // similar code to zkr() above, except // with initerror() instead of // perferror(). void zir(ZKR *p) { // See zkr() for more comments. // Local variables. register float *readloc; register long indx; if ( zkset() == 0 ) return; // Check to see this index is // within the limits of zk space. indx = (long) *p->ndx; if ( indx > zklast ) { initerror("zir index > isizek. Returning 0."); *p->rslt = 0;

} else { if ( indx < 0 ) { initerror("zir index < 0. Returning 0."); *p->rslt = 0;
} else { // Now read from the zk space. readloc = zkstart + indx; *p->rslt = *readloc; } } } //------------------------------------- // Now the i and k rate WRITE code. // zkw writes to zk space at k rate. void zkw(ZKW *p) { // Local variables. // writeloc Location to write to. register float *writeloc; // Index value. register long indx; // Check to see this index is // within the limits of zk space. indx = (long) *p->ndx; if ( indx > zklast ) { perferror("zkw index > isizek. Not writing."); } else { if ( indx < 0 ) { perferror("zkw index < 0. Not writing."); } else { // Now write to the appropriate // location in zk space. // See notes in zkr() on pointer // arithmetic. writeloc = zkstart + indx; *writeloc = *p->sig; } } } //------------------------------------- // ziw writes to zk space, but only // at init time. // // Call zkset() to check that zk space // has been allocated, then use // same code as zkw() except that // errors go to initerror(). void ziw(ZKW *p) { // See zkw() for more comments. // Local variables. register float *writeloc; register long indx; if ( zkset() == 0 ) return; indx = (long) *p->ndx; if ( indx > zklast ) { perferror("zkw index > isizek. Not writing."); } else { if ( indx < 0 ) { perferror("zkw index < 0. Not writing."); } else { // Now write to the appropriate // location in zk space. /// writeloc = zkstart + indx; *writeloc = *p->sig; } } } //------------------------------------- // i and k rate zk WRITE code, with a // mix option. // zkwm writes to zk space at k rate. void zkwm(ZKWM *p) { // Local variables. // writeloc Location to write to. register float *writeloc; // Index value. register long indx; // Check to see this index is // within the limits of zk space. indx = (long) *p->ndx; if ( indx > zklast ) { perferror("zkwm index > isizek. Not writing."); } else { if ( indx < 0 ) { perferror("zkwm index < 0. Not writing."); } else { // Now write to the appropriate // location in zk space. // See notes in zkr() on pointer // arithmetic. writeloc = zkstart + indx; // If mix parameter is 0, then // overwrite the data in the // zk space variable, otherwise // read the old value, and write // the sum of it and the input sig. if (*p->mix == 0) *writeloc = *p->sig;

else *writeloc = *p->sig +//writeloc; } } } //------------------------------------- // ziwm writes to zk space, but only // at init time - with a mix option. // // Call zkset() to check that zk space // has been allocated, then run // similar code to zkwm() to do the // work - but with errors to // initerror(). void ziwm(ZKWM *p) { // See zkwm() for more comments. // Local variables. register float *writeloc; register long indx; if ( zkset() == 0 ) return; indx = (long) *p->ndx; if ( indx > zklast ) { initerror("ziwm index > isizek. Not writing."); } else { if ( indx < 0 ) { initerror("ziwm index < 0. Not writing."); } else { writeloc = zkstart + indx; if (*p->mix == 0) *writeloc = *p->sig;

else *writeloc = *p->sig +//writeloc; } } } //------------------------------------- // k rate ZKMOD subroutine. /// void zkmod(ZKMOD *p) { // Local variables. // readloc Location in zk space // to read from. register float *readloc; // Long integers: // Index value. register long indx; // Flag set to non zero if we should be // doing the modulation with // multiplication rather than addition. register int mflag = 0; // If zkmod = 0, then just copy // input to output. We want to make // this as fast as possible, because // in many instances, this will be // the case. // // Note that in converting the zkmod // index into a long, we want // the normal conversion rules to // apply to negative numbers - // so -2.3 is converted to -2. if ( (indx = (long)*p->zkmod) == 0) { *p->rslt = *p->sig;
return; } // Decide whether index is // positive or negative. // Make it postive. if (indx < 0) { indx = 0 - indx; mflag = 1;
} // Check to see this index is // within the limits of zk space. if ( indx > zklast ) { perferror("zkmod kzkmod > isizek. Not writing."); } else { // Now read the value from zk space. // See notes in zkr() on pointer // arithmetic. readloc = zkstart + indx; // If mflag is 0, then add the // modulation factor. Otherwise // multiply it. if (mflag == 0) *p->rslt = *p->sig +//readloc; else *p->rslt = *p->sig////readloc; } } //------------------------------------- // zkcl clears a range of variables // in zk space at k rate. void zkcl(ZKCL *p) { // Local variables. // writeloc Location to write to. register float *writeloc; // Starting and ending indexes into // zk space. // Loop repetition counter. register long first, last, loopcount; // Check to see both kfirst and klast // are within the limits of zk space // and that last is >= first. first = (long) *p->first; last = (long) *p->last; if (( first > zklast ) || (last > zklast)) perferror("zkcl first or last > isizek. Not clearing."); else { if (( first < 0 ) || (last < 0)) { perferror("zkcl first or last < 0. Not clearing."); } else { if ( first > last ) { perferror("zkcl first > last. Not clearing."); } else { // Now clear the appropriate // locations in zk space. // See notes in zkr() on pointer // arithmetic. loopcount = last - first + 1; writeloc = zkstart + first; do//writeloc++ = 0; while (--loopcount); } } } } //----------------------------------------------------------------------------- // AUDIO rate zak code. // zaset() is called at the init time // of the instance of the zar or zaw // ugens. // All it has to do is spit the dummy // if za space has not been allocated // yet. int zaset(ZAR *p) { if (zastart == 0) { initerror("No za space: zakinit has not been called yet."); return(0); }
else return(1);
} //------------------------------------- // a rate READ code. // zar reads from za space at a rate. void zar(ZAR *p) { // Local variables. // readloc Pointer to array of // floats at location we // want to read from. // // writeloc Pointer to destination // for results. register float *readloc, *writeloc; // Index value. register long indx; // nsmps is the counter for the loop // which processes the data. // declare it as an integer, and // intialize it to the global variable // ksmps. register int nsmps = ksmps; //------------------------------------- // Set up write location. writeloc = p->rslt; // Check to see this index is // within the limits of za space. indx = (long) *p->ndx; if ( indx > zalast ) { perferror("zar index > isizea. Returning 0."); do *writeloc++ = 0;
while(--nsmps); } else { if (indx < 0 ) { perferror("zar index < 0. Returning 0."); do *writeloc++ = 0;
while(--nsmps); } else { // Now read from the array in za space // and write to the destination. // See notes in zkr() on pointer // arithmetic. readloc = zastart + (indx// ksmps); do *writeloc++ = *readloc++; while(--nsmps); } } } //------------------------------------- // zarg() reads from za space at audio // rate, with gain controlled by a // k rate variable. Code is almost // identical to zar() above. void zarg(ZARG *p) { // Local variables. // readloc Pointer to array of // floats at location we // want to read from. // // writeloc Pointer to destination // for results. register float *readloc, *writeloc; // kgain Gain control. register float kgain; // Index value. register long indx; // nsmps is the counter for the loop // which processes the data. register int nsmps = ksmps; //------------------------------------- // Set up write location and gain. writeloc = p->rslt; kgain = *p->kgain; // Check to see this index is // within the limits of za space. indx = (long) *p->ndx; if ( indx > zalast ) { perferror("zarg index > isizea. Returning 0."); do *writeloc++ = 0;
while(--nsmps); } else { if (indx < 0 ) { perferror("zarg index < 0. Returning 0."); do *writeloc++ = 0;
while(--nsmps); } else { // Now read from the array in za space // multiply by kgain and write to the // destination. readloc = zastart + (indx// ksmps); do *writeloc++ =//readloc++// kgain; while(--nsmps); } } } //------------------------------------- // a rate WRITE code. // zaw writes to za space at a rate. void zaw(ZAW *p) { // Local variables. // readloc Pointer to input signal // values we want to // write to the array. // // writeloc Pointer into the // array of floats in za // space which we want to // write to. register float *readloc, *writeloc; // Index value. register long indx; // nsmps is the counter for the loop // which processes the data. // declare it as an integer, and // intialize it to the global variable // ksmps. register int nsmps = ksmps; //------------------------------------- // Set up the pointer for the source // of data to write. readloc = p->sig; // Check to see this index is // within the limits of za space. indx = (long) *p->ndx; if ( indx > zalast ) { perferror("zaw index > isizea. Not writing."); } else { if (indx < 0 ) { perferror("zaw index < 0. Not writing."); } else { // Now write to the array in za space // pointed to by indx. // See notes in zkr() on pointer // arithmetic. writeloc = zastart + (indx// ksmps); do *writeloc++ = *readloc++; while(--nsmps); } } } //------------------------------------- // a rate WRITE code with mix facility. // zawm writes to za space at a rate. void zawm(ZAWM *p) { // Local variables. // readloc Pointer to input signal // values we want to // write to the array. // // writeloc Pointer into the // array of floats in za // space which we want to // write to. register float *readloc, *writeloc; // Index value. register long indx; // nsmps is the counter for the loop // which processes the data. // declare it as an integer, and // intialize it to the global variable // ksmps. register int nsmps = ksmps; //------------------------------------- // Set up the pointer for the source // of data to write. readloc = p->sig; // Check to see this index is // within the limits of za space. indx = (long) *p->ndx; if ( indx > zalast ) { perferror("zaw index > isizea. Not writing."); } else { if (indx < 0 ) { perferror("zaw index < 0. Not writing."); } else { // Now write to the array in za space // pointed to by indx. // See notes in zkr() on pointer // arithmetic. writeloc = zastart + (indx * ksmps); if (*p->mix == 0) { // Normal write mode. do *writeloc++ = *readloc++; while(--nsmps); } else { // Mix mode - add to the existing // value. do { *writeloc++ = *readloc++ + *writeloc; } while(--nsmps); } } } } //------------------------------------- // audio rate ZAMOD subroutine. // // See zkmod() for fuller explanation // of code. /// void zamod(ZAMOD *p) { // Local variables. // writeloc Destination to write // to (from rslt). // // readloc Location in za space // to read from. // // readsig Array of input floats. register float *writeloc, *readloc, *readsig; // Long integers: // Index value. register long indx; // Flag set to non zero if we should be // doing the modulation with // multiplication rather than addition. register int mflag = 0; // nsmps is the counter for the loop // which processes the data. // declare it as an integer, and // intialize it to the global variable // ksmps. register int nsmps = ksmps; // Make a local copy of the pointer to // the input signal, so we can auto - // increment it. // Likewise the location to write the // result to. readsig = p->sig; writeloc = p->rslt; // If zkmod = 0, then just copy // input to output. if ( (indx = (long) *p->zamod) == 0) { do//writeloc++ =//readsig++; while (--nsmps); return; } // Decide whether index is // positive or negative. // Make it postive. if (indx < 0) { indx = 0 - indx; mflag = 1;
} // Check to see this index is // within the limits of za space. if ( indx > zalast ) { perferror("zamod kzamod > isizea. Not writing."); } else { // Now read the values from za space. readloc = zastart + (indx * ksmps); // If mflag is 0, then add the // modulation factor. Otherwise // multiply it. if (mflag == 0) { do writeloc++ = *readsig++ + *readloc++; while (--nsmps); } else {
do *writeloc++ = *readsig++ * *readloc++; while (--nsmps); }
} } //------------------------------------- // zacl clears a range of variables // in za space at k rate. void zacl(ZACL *p) { // Local variables. // writeloc Location to write to. register float *writeloc; // Starting and ending indexes into // za space. // Loop repetition counter. register long first, last, loopcount; // Check to see both kfirst and klast // are within the limits of za space // and that last is >= first. first = (long) *p->first; last = (long) *p->last; if (( first > zalast ) || (last > zalast)) perferror("zacl first or last > isizea. Not clearing."); else { if (( first < 0 ) || (last < 0)) { perferror("zacl first or last < 0. Not clearing."); } else {
if ( first > last ) { perferror("zacl first > last. Not clearing."); } else { // Now clear the appropriate // locations in za space. // See notes in zkr() on pointer // arithmetic. loopcount = (last - first + 1) * ksmps; writeloc = zastart + ( first * ksmps ); do *writeloc++ = 0; while (--loopcount); } } } } //***************************************************************************** //***************************************************************************** // Subroutines for reading // absolute time. /// // timek() // // Called at i rate or k rate, by // timek, itimek, times or itemes. // // This is based on global variable // kcounter in insert.c. // Since is apparently is not declared // in a header file, we must declare it // an external. extern long kcounter; void timek(RDTIME *p) { // Read the global variable kcounter // and turn it into a float. *p->rslt = (float) kcounter; } // times() void times(RDTIME *p) { // Read the global variable kcounter // divide it by the k rate. *p->rslt = (float) kcounter / (float) ekr ; } //------------------------------------- // Subroutines to read time for this // instance of the instrument. // instimset() runs at init time and // keeps a record of the time then // in the RDTIME data structure. // // Returns 0. void instimset(RDTIME *p) { p->instartk = kcounter; *p->rslt = 0; } // instimek() // // Read difference between the global // variable kcounter and the starting // time of this instance. // Return it as a float. void instimek(RDTIME *p) { *p->rslt = (float) (kcounter - p->instartk); } // insttimes() // // Read difference between the global // variable kcounter and the starting // time of this instance. // Return it as a float in seconds. void instimes(RDTIME *p) { *p->rslt = (float) (kcounter - p->instartk) / (float) ekr ; } //***************************************************************************** //***************************************************************************** // Printing at k rate - printk. // printkset is called when the // instance of the instrument is // initiallised. void printkset(PRINTK *p) { // Set up ctime so that if it was // 0 or negative, it is set to // a low value to ensure that the // print cycle happens every k // cycle. This low value is // 1 / ekr if (*p->ptime < 1 / ekr) p->ctime = 1 / ekr; else p->ctime = *p->ptime;


// Set up the number of spaces. // Limit to 120 for people with // big screens or printers. if (*p->space < 0) p->pspace = 0; else if (*p->space > 120) p->pspace = 120; else p->pspace = *p->space; // Set the initime variable - how // many seconds in absolute time // when this instance of the instrument // was initialised. p->initime = (float) kcounter / (float) ekr;

// Set cysofar to - 1 so that on the // first call to printk - when // cycle = 0, then there will be a // print cycle. p->cysofar = -1; } //************************************* // printk // // Called on every k cycle. // It must decide when to do a // print operation. void printk(PRINTK *p) { // Float local variables: // // timel Time in seconds // elapsed since the // instrument was // initialised. float timel;

// Integer local variables: // // cycles What print cycle // we are in at this // time. // // spcount How many spaces to // print. long cycles, spcount;


//------------------------------------- // Initialise variables. timel = ( (float) kcounter / (float) ekr ) - p->initime;

// Divide the current elapsed time by // the cycle time and round down to // an integer. Thus ctime = 5, // timel = 14, cycles = 2. cycles = timel / p->ctime; // Now test if the cycle number we are // in is higher than the one in which // we last printed. // If so, update cysofar and print. if (p->cysofar < cycles) { p->cysofar = cycles; // Do the print cycle. // // Print instrument number and // time. // Instrument number stuff from // printv() in disprep.c. printf(" i%4d ", p->h.insdshead->insno); printf("time %11.5f:", (float) kcounter / (float) ekr); // Print spaces and then the value // we want to read. spcount = p->pspace + 1; do printf(" "); while (--spcount); printf("%11.5f\n", *p->val); } } //----------------------------------------------------------------------------- // printks() and printksset() // Printing at k rate with a string // and up to four variables - printks. // printksset is called when the // instance of the instrument is // initiallised. void printksset(PRINTKS *p) { // Pointer to string from STRARG. char *sarg; // Pointer to string which will be // fed to printf() at print time. char *sdest; // Temp characters for decoding the // string. char temp, tempn; // Flag to tell character matching // code whether to keep looking. int look; // Timing code similar to printkset(). // Set up ctime so that if it was // 0 or negative, it is set to // a low value to ensure that the // print cycle happens every k // cycle. This low value is // 1 / ekr if (*p->ptime < 1 / ekr) p->ctime = 1 / ekr; else p->ctime = *p->ptime;

// Set the initime variable - how // many seconds in absolute time // when this instance of the instrument // was initialised. p->initime = (float) kcounter / (float) ekr;

// Set cysofar to - 1 so that on the // first call to printk - when // cycle = 0, then there will be a // print cycle. p->cysofar = -1; // Set up the string to print. // printf() will take care of the // %f format specifiers, but // we need to decode any other special // codes we may want to put in here. // \n etc. in a string literal in a // standard printf() string are // converted by the compiler - not // printf(). // We get the string via the same // mechanism used in ugens3.c for // adsyn(). I have not checked // everything which stands behind this. // Check to see if ifilcod == 0xFFFFFF. // If so, then a string was entered // as this string and can be found // using "p->STRARG" which translates // to" p->h.optext->t.strarg // // This goes through the following // data structures OPDS, OPTXT and // TEXT. // // If it was not, then complain. // Also complain if the string has // nothing in it. However the // program (under DJGPP at least) // seems to crash elsewhere if // the first parameter is "". if ( (*p->ifilcod != sstrcod)
|| (*p->STRARG == 0) ) { sprintf(errmsg, "printks parm 1 was not a \"quoted string\"\n"); initerror(errmsg); return; } else
{ sarg = p->STRARG; sdest = p->txtstring; // Copy the string to the storage // place in PRINTKS. // // We will look out for certain // special codes and write special // bytes directly to the string. // // There is probably a more elegant // way of doing this, then using // the look flag. I could use // goto - but I would rather not. do { look = 1; temp = *sarg++; tempn = *sarg--; // Look for a single caret and insert // an escape char. if ( (temp == '^')
&& (tempn != '^') ) {
*sdest++ = 0x1B; look = 0; } // Look for a double caret and insert // a single caret - stepping forward // one. if ( (look)
&& (temp == '^')
&& (tempn == '^') ) {
*sdest++ = '^'; sarg++; look = 0; } // Look for a single tilde and insert // an escape followed by a '['. // ESC[ is the leadin for all the // ANSI.SYS screen control codes for // MSDOS. if ( (look)
&& (temp == '~')
&& (tempn != '~') )
{
*sdest++ = 0x1B; *sdest++ = '['; look = 0; } // Look for a double tilde and insert // a tilde caret - stepping forward // one. if ( (look)
&& (temp == '~')
&& (tempn == '~') ) {
*sdest++ = '~'; sarg++; look = 0; } // Look for \n, \N and convert to a // newline character. // Step forwards. if ( (look)
&& (temp == '\\')
&& ( (tempn == 'n') || (tempn == 'N') ) ) { *sdest++ = 0x0A; sarg++; look = 0; }

// Similarly look for \t and \T for // tab. if ( (look)
&& (temp == '\\')
&& ( (tempn == 't') || (tempn == 'T') ) ) { *sdest++ = 0x09; sarg++; look = 0; }

if (look) { // If none of these match, then // copy the character directly // and try again. *sdest++ = temp; } // Increment pointer and process // next character until we get to // end of string. } while (*++sarg != 0); } } //************************************* // printks is called on every k cycle // It must decide when to do a // print operation. void printks(PRINTKS *p) { // Float local variables: // // timel Time in seconds // elapsed since the // instrument was // initialised. float timel;

// Integer local variables: // // cycles What print cycle // we are in at this // time. long cycles;

// txtptr Pointer to where in
// text string to start // printing. char *txtptr; //------------------------------------- // Initialise variables. timel = ( (float) kcounter / (float) ekr ) - p->initime;

// Divide the current elapsed time by // the cycle time and round down to // an integer. Thus ctime = 5, // timel = 14, cycles = 2. cycles = timel / p->ctime; // Now test if the cycle number we are // in is higher than the one in which // we last printed. // If so, update cysofar and print. if (p->cysofar < cycles) { p->cysofar = cycles; // Do the print cycle. txtptr = p->txtstring; // Special code to allow us to print // the direct byte value of the // first parameter - only if the // first character of the string is // a #. Then go on and // do a printf() with the three other // input variables as floats. // // This enables us to output any // value to the output if we really // want to. if (*txtptr == '#') { // print the 0 to 255 value of the // float - this gives wrap-around // for out of range values. putchar( 255 & (int)floor(*p->kval1) ); // printf the rest of the string, // if any. printf( ++txtptr, *p->kval2, *p->kval3, *p->kval4), 0, 0, 0; } else {
// Otherwise do the normal print // cycle of printf()ing the string // with four float input variables. // // Put a few dummy variables on the // end for Justin - just in case we // put too many % format specifiers // in the string. printf(txtptr, *p->kval1, *p->kval2, *p->kval3, *p->kval4, 0, 0); }
} } //***************************************************************************** // peakk and peak ugens /// // peakk() // // Write the absolute value of the // input argument to the output if // the former is higher. void peakk(PEAK *p) { if (*p->kpeakout < abs(*p->xsigin) )
{ *p->kpeakout = abs(*p->xsigin); } } // peaka() // // Similar to peakk, but looks at an // a rate input variable. void peaka(PEAK *p) { int loop; int change = 0; float peak; float *asigin; // Work with a local peak variable // inside the loop, and update the // output variable only if it is // exceeded. loop = ksmps; asigin = p->xsigin; peak = *p->kpeakout; do
{ if (peak < abs(*asigin++) )
{ peak = abs(*--asigin); asigin++;
change = 1; } } while (--loop); if (change)
{ *p->kpeakout = peak; } } //***************************************************************************** //*************************************************************************** // // What to add to other files to get this going?? // ---------------------------------------------- /// // Add two sets of entries to those at the end of opcodlst in entry.c. // // These are sets of lines in curly braces and are at the start of this // file and are marked//>>> // // In addition, add the following function prototypes to into entry.c: // // #include "ugrw1.h" // // void tblsetw(void*), ktablew(void*), tablew(void*), itablew(void*); // void tblsetwkt(void*), ktablewkt(void*), tablewkt(void*); // // void tableng(void*), itableng(void*); // void tablegpw(void*), itablegpw(void*); // // void tablemixset(void*), tablemix(void*), itablemix(void*); // void tablecopyset(void*), tablecopy(void*), itablecopy(void*); // // void tableraset(void*), tablera(void*); // void tablewaset(void*), tablewa(void*); // // void zakinit(); // void zkset(void*), zir(void*), zkr(void*), ziw(void*), zkw(void*); // void ziwm(void*), zkwm(void*), zkmod(void*), zkcl(void*); // // void zaset(void*), zar(void*), zarg(void*), zaw(void*); // void zawm(void*), zamod(void*), zacl(void*); // // void timek(void*), times(void*); // void instimset(void*), instimek(void*), instimes(void*); // // void printkset(void*), printk(void*); // void printksset(void*), printks(void*); // // void peakk(void*), peaka(void*); /// // Add some stuff to the makefile to make this module UGRW1.C and .H part of // the compilation process. // // Add ugrw1.o to variable OBJS. // // Add ugrw1.h as a dependency for entry.o. // // Create a new dependency, ugrw1.o: ugrw1.h // // . . . . well that is what Appendix 8 of the CSound manual told me to do. // // Now here is what I actually did: // // Add ugrw1.o to the ends of the lists called COBJS and LCOBJS. // (I have not looked into the details of makefiles, but I guess these // are for the compiler and linker.) // // In the list of things starting with "entry.o:", add ugrw1.h. The order // looks like it is significant, so I put it after soundio.h (the last one // that looked like an ordinary piece of code.) // // Create a new dependency to fire up the compiler to make a new ugrw1.o // object file. This is copied from similar lines for other modules. // // I put it after the cscormai.o: dependency. // // ugrw1.o: ugrw1.h sysdep.h prototypes.h cs.h ugrw1.c // $(CC) $(CFLAGS) -c $*.c /// // // (Some notes specific to DJGPP compiler for MSDOS) // // The second line above creates the command line for compiling this module. // "$(CC) is converted to CPP which is the DJGPP compiler. "$(CFLAGS) is // converted to other things. "-c" goes straight onto the command line. // "$*.c" is trasformed into ugrw1.c. // // To get a file of the problems that the compiler reports: // // 1: Add 2r1 to the GO32 environment variable. // // 2: Add "> bugs" to the end of the second line so it becomes: // // $(CC) $(CFLAGS) -c $*.c > bugs // ///