#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
//
///