#include "cs.h" /* UGRW1.C */ #include "ugrw1.h" #include /* Do List: * * Clean up code in zak - use arrays rather than messy pointer stuff. * * Clean up doco file. * * Add maxampr to ugrw1.doc * * Convert to tab = 4?? * */ /* 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 a 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 fomr 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 * */