#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 // 27 August 1996: // // Offset bug in ftkrchkw() fixed, as per my 3 Feb bug report. // // 1 February 1996: // // Offset bugs fixed in init time table write - same as in table read. // // See itblchkw() // // New ugen 7 January 1996 - peak. // // Unit generators by Robin Whittle 8 September 1995 // UGRW1.H contains data structures. // // See UGRW1.DOC for more details on using these ugens. Essential // functional documentation for them is included here to ensure it // is never separated from the code. // // Changes required to other files: //--------------------------------- // // 1 - entry.c needs new function prototypes (see end of this file) and // new lines in opcodlst - see notes below with lines in {}. // // 2 - A new fgens.c is needed to provide ftfindp() - finding tables at // performance time. // // 3 - prototype.h has a new entry for ftfindp() - see start of fgens.c. // // 4 - The makefile has various additions - see end of this file. // // // Table write ugens //------------------ // // These table write ugens are adapted from similar code in my modified, // "bug free" version of the table read code in UGENS2.H & .C. // // Ugens: Subroutines: Data structure: // // tablew tblsetw() TABLEW // ktablew() " // tablew() " // // itablew itablew() " // // tablewkt tblsetwkt() TABLEW // ktablewkt() " // tablewkt() " //-- // // These find out the length of a table, and write the guard point // of a table. // // Ugens: Subroutines: Data structure: // // tableng tableng() TABLENG // itableng itableng() // // tablegpw tablegpw() TABLEGPW // itablegpw itablegpw() // // Two ugens to manipulate the entire contents, or part of the contents // of a table in a single k cycle. // // Ugens: Subroutines: Data structure: // // tablemix tablemix() TABLEMIX // itablemix itablemix() // // tablecopy tablecopy() TABLECOPY // itablecopy itablecopy() // // Two ugens which enable a rate reading and writing to sequential // locations in tables. Useful of a rate manipulation of table contents // and conversely for writing a rate data into a table, where k rate // code can manipulate it before it is written back to an a rate variable. // // Ugens: Subroutines: Data structure: // // tablera tableraset() TABLERA // tablera() // // tablewa tablewaset() TABLEWA // tablewa() // //------ // // The following ugens are fudges - it would be better to have arrays - // like ablah[kx][ky]. // // Arrays should be local and global, multidimensional, a, k and i rate. // // I will use these to get things done until arrays are implemented in // the core of the language. // // The zak system uses one area of memory as a global i or k rate // patching area, and another for audio rate patching. // // Ugens: Subroutines: Data structure: // // zakinit zakinit() ZAKINIT // // zir zkset() - // zkr zir() ZKR // zkr() ZKR // ziw ziw() ZKW // zkw zkw() ZKW // ziwm ziwm() ZKWM // zkwm zkwm() ZKWM // zkmod zkmod() ZKMOD // zkcl zkcl() ZKCL // // zar zaset() - // zaw zar() ZAR // zaw() ZAW // zarg zarg() ZARG // // zawm zawm() ZAWM // zamod zamod() ZAMOD // zacl zacl() ZACL // //--- // // Four ugens for reading the abosolute time since the start of the // performance, and two for reading the time since the instrument was // initialised: // // Ugens: Subroutines: Data structure: // // timek timek() RDTIME // itimek itimek() " // // times times() " // itimes itimes() " // // instimek instimset() " // instimes instimek() " // instimes() " //--- // // Two ugens for printing a k rate variable during k rate processing. // // Ugens: Subroutines: Data structure: // // printk printk() PRINTK // printks printks() PRINTKS //**************************************************************************** // // Table write syntax //------------------- // // Use itablew when all inputs are init time variables or constants // and you only want to run it at the initialisation of the instrument. // // tablew is for writing at k or at a rates, with the table number being // specified at init time. // // tablewkt is the same, but uses a k rate variable for selecting the table // number. The valid combinations of variable types are shown by the first // letter of the variable names: // // itablew isig, indx, ifn [,ixmode] [,ixoff] [,iwgmode] // // tablew ksig, kndx, ifn [,ixmode] [,ixoff] [,iwgmode] // tablew asig, andx, ifn [,ixmode] [,ixoff] [,iwgmode] // // tablewkt ksig, kndx, kfn [,ixmode] [,ixoff] [,iwgmode] // tablewkt asig, andx, kfn [,ixmode] [,ixoff] [,iwgmode] // // isig, ksig, The value to be written into the table. // asig // // indx, kndx, Index into table, either a positive number range // andx matching the table length (ixmode = 0) or a 0 to 1 // range (ixmode != 0) // // ifn, kfn Table number. Must be >= 1. Floats are rounded down to // an integer. If a table number does not point to a // valid table, or the table has not yet been loaded // (gen01) then an error will result and the instrument // will be de-activated. // // ixmode Default 0 ==0 xndx and ixoff ranges match the length // of the table. // // !=0 xndx and ixoff have a 0 to 1 range. // // // ixoff Default 0 ==0 Total index is controlled directly by // xndx. ie. the indexing starts from the // start of the table. // // !=0 Start indexing from somewhere else in // the table. Value must be positive and // less than the table length (ixmode = 0) // or less than 1 (ixmode !=0 // // iwgmode Default 0 ==0 Limit mode } See full explanation in // ==1 Wrap mode } ugrw1.doc // ==2 Guardpoint mode } // // Known bugs in table write: // // Watch out for giving a number (like "5" or p6) instead of an a rate // variable to the index of table write ugens operating at a rate. // The ugen gets a pointer to the number, but it is expecting a pointer // to an a rate variable - which is an array of numbers. // Upsamp the number (or k rate variable) to an a rate variable first. // // This pitfall could be fixed with a revamp of how Csound processes the // orchestra, or by making different named ugens for different types of // variables. //**************************************************************************** // // Other table manipulation ugens // ------------------------------ // // ir itableng ifn // kr tableng kfn // // ifn i rate number of function table // kfn k rate number of function table // // These return the length of the specified table. This will be a power // of two number in most circumstances - it will not show whether // a table has a guardpoint or not - it seems this information is not // available in the table's data structure. If table is not found, then // 0 will be returned. // // Likely to be useful for setting up code for table manipulation // operations, such as tablemix and tablecopy. // // // itablegpw ifn // tablegpw kfn // // For writing the table's guard point, with the value which is in // location 0. Does nothing if table does not exist. // // Likely to be useful after manipulating a table with tablemix or // tablecopy. // //------ // // tablemix kdft, kdoff, klen, ks1ft, ks1off, ks1g, ks2ft, ks2off, ks2g // itablemix idft, idoff, ilen, is1ft, is1off, is1g, is2ft, is2off, is2g // // This ugen mixes from two tables, with separate gains into the // destination table. Writing is done for klen locations, usually // stepping forward through the table - if klen was positive. // If it is negative, then the writing and reading order is backwards - // towards lower indexes in the tables. This bidirectional option makes // it easy to shift the contents of a table by reading from it and // writing back to it. // // If klen is 0, no writing occurs. Note that the internal integer value // of klen is derived from the ANSI C floor() function - which returns // the next most negative integer. Hence a fractional negative klen // value of -2.3 would create an internal length of 3, and cause // the copying to start from the offset locations and proceed for // two locations to the left. // // The total index for table reading and writing is calculated from the // starting offset for each table, plus the index value, which starts // at 0 and then increments (or decrements) by 1 as mixing proceeds. // // These total indexes can potentially be very large, since there is no // restriction on the offset or the klen. However each total index for // each table is ANDed with a length mask (such as 0000 0111 for a table // of length 8) to form a final index which is actually used for // reading or writing. So no reading or writing can occur outside // the tables. // // This is the same as "wrap" mode in table read and write. This process // does not read or write the guardpoint. // // If a table has been rewritten with one of these, then if it has a // guardpoint which is supposed to contain the same value as the // location 0, then call tablegpw afterwards. // // The indexes and offsets are all in table steps - they are not // normalised to 0 - 1. So for a table of length 256, klen would be // set to 256 if all the table was to be read or written. // // The tables do not need to be the same length - wrapping occurs // individually for each table. // // kdft Destination function table. // // kdoff Offset to start writing from. Can be negative. // // klen Number of write operations to perform. Negative means // work backwards. // // ks1ft ks2ft Source function tables. These can be the same as the // destination table, if care is exercised about direction // of copying data. // // ks1off ks2off Offsets to start reading from in source tables. // // ks1g ks2g Gains to apply when reading from the source tables. The // results are added and the sum is written to the destination // table. // //--- // // tablecopy kdft, ksft // itablecopy idft, isft // // Simple, fast table copy ugens. Takes the table length from the // destination table, and reads from the start of the source table. // For speed reasons, does not check the source length - just copies // regardless - in "wrap" mode. This may read through the source // table several times. A source table with length 1 will cause // all values in the destination table to be written to its value. // // Table copy cannot read or write the guardpoint. To read it // use table read, with ndx = the table length. Likewise use // table write to write it. // // To write the guardpoint to the value in location 0, use tablegpw. // // This is primarily to change function tables quickly in a real-time // situation. // // kdft Number of destination function table. // // ksft Number of source function table. // //----- // // ar tablera kfn, kstart, koff // // kstart tablewa kfn, asig, koff // // ar a rate distination for reading ksmps values from a table. // // kfn i oro k rate number of the table to read or write. // // kstart Where in table to read or write. // // asig a rate signal to read from when writing to the table. // // koff k rate offset into table. Range unlimited - see explanation // at end of this section. // // In one application, these are intended to be used in pairs, or with // several tablera ugens before a tablewa - all sharing the same kstart // variable. // // These read from and write to sequential locations in a table at audio // rates, with ksmps floats being written and read each cycle. // // tablera starts reading from location kstart. // // tablewa starts writing to location kstart, and then writes to kstart // with the number of the location one more than the one it last wrote. // (Note that for tablewa, kstart is both an input and output variable.) // If the writing index reaches the end of the table, then the no further // writing occurs and zero is written to kstart. // // For instance, if the table's length was 16 (locations 0 to 15), and // ksmps was 5. Then the following steps would occur with repetitive // runs of the tablewa ugen, assuming that kstart started at 0. // // Run no. Initial Final locations written // kstart kstart // // 1 0 5 0 1 2 3 4 // // 2 5 10 5 6 7 8 9 // // 3 10 15 10 11 12 13 14 // // 4 15 0 15 // // This is to facilitate processing table data using standard a rate // orchestra code between the tablera and tablewa ugens: // // ;----------------------------- // ; // kstart = 0 ; // ; Read 5 values from table into an a rate // ; variable. // // lab1: atemp tablera ktabsource, kstart, 0 // // ; Process the values using a rate code. // atemp = log(atemp) ; // ; Write it back to the table // // kstart tablewa ktabdest, atemp, 0 // // ; Loop until all table locations have been // ; processed. // if ktemp > 0 goto lab1 ; // ; // ;----------------------------- // // This example shows a processing loop, which runs every k cycle, // reading each location in the table ktabsource, and writing the log // of those values into the same locations of table ktabdest. // // This enables whole tables, parts of tables (with offsets and different // control loops) and data from several tables at once to be manipulated // with a rate code and written back to another (or the same) table. // This is a bit of a fudge, but it is faster than doing it with // k rate table read and write code. // // Another application is: // // ;----------------------------- // ; // kzero = 0 ; // kloop = 0 ; // ; // kzero tablewa 23, asignal, 0 ; ksmps a rate samples written into // ; locations 0 to (ksmps -1) of table 23. // ; // lab1: ktemp table kloop, 23 ; Start a loop which runs ksmps times, // ; in which each cycle processes one of // [ Some code to manipulate ] ; table 23's values with k rate orchestra // [ the value of ktemp. ] ; code. // ; // ; // tablew ktemp, kloop, 23 ; Write the processed value to the table. // ; // kloop = kloop + 1 ; Increment the kloop, which is both the // ; pointer into the table and the loop // if kloop < ksmps goto lab1 ; counter. Keep looping until all values // ; in the table have been processed. // ; // asignal tablera 23, 0, 0 ; Copy the table contents back to an a rate // ; variable. // ;----------------------------- // // // koff This is an offset which is added to the sum of kstart and the internal // index variable which steps through the table. The result is then // ANDed with the lengthmask (000 0111 for a table of length 8 - or // 9 with guardpoint) and that final index is used to read or write to // the table. koff can be any value. It is converted into a long using // the ANSI floor() function so that -4.3 becomes -5. This is what we // would want when using wide rangeing offsets. // // Ideally this would be an optional variable, defaulting to 0, however // with the existing Csount orchestra read code, such default parameters // must be init time only. We want k rate here, so we cannot have a // default. // // Notes on tablera and tablewa //----------------------------- // // These are a fudge, but they allows all Csounds k rate operators to be // used (with caution) on a rate variables - something that would only // be possible otherwise by ksmps = 1, downsamp and upsamp. // // Several cautions: // // 1 - The k rate code in the processing loop is really running at a rate, // so time dependant functions like port would not work normally. // // 2 - This system will produce undesirable results unless the ksmps fits // within the table length. For instance a table of length 16 will // accomodate 1 to 16 samples, so this example will work with // ksmps = 1 to 16. // // Both these ugens generate an error and deactivate the instrument if // a table with length < ksmps is selected. Likewise an error occurs // if kstart is below 0 or greater than the highest entry in the table - // if kstart >= table length. // // 3 - kstart is intended to contain integer values between 0 and (table // length - 1). Fractional values above this should not affect operation // but do not achieve anything useful. // // 4 - These ugens do not do interpolation and the kstart and koff parameters // always have a range of 0 to (table length - 1) - not 0 to 1 as is // available in other table read/write ugens. koff can be outside this // range but it is wrapped around by the final AND operation. // // 5 - These ugnes are permanently in wrap mode. When koff is 0, no wrapping // needs to occur, since the kstart++ index will always be within the // table's normal range. koff != 0 can lead to wrapping. // // 6 - The offset does not affect the number of read/write cycles performed, // or the value written to kstart by tablewa. // // 7 - These ugens cannot read or write the guardpoint. Use tablegpw to // write the guardpoint after manipulations have been done with tablewa. // //------ // //>>> Add these to the end of opcodlst in entry.c. // // opcode dspace thread outarg inargs isub ksub asub // // // { "itablew", S(TABLEW), 1, "", "iiiooo", itablew, NULL, NULL}, // { "tablew", S(TABLEW), 7, "", "xxiooo", tblsetw, ktablew,tablew}, // { "tablewkt",S(TABLEW), 7, "", "xxkooo", tblsetwkt,ktablewkt,tablewkt}, // // { "itableng",S(TABLENG),1, "i", "i", itableng, NULL, NULL}, // { "tableng", S(TABLENG),2, "k", "k", NULL, tableng, NULL}, // // { "itablegpw",S(TABLENG),1, "", "i", itablegpw, NULL, NULL}, // { "tablegpw", S(TABLENG),2, "", "k", NULL, tablegpw, NULL}, // // { "itablemix",S(TABLEMIX),1, "", "iiiiiiiii", itablemix, NULL, NULL}, // { "tablemix", S(TABLEMIX),2, "", "kkkkkkkkk",tablemixset, tablemix, NULL}, // // { "itablecopy",S(TABLECOPY),1, "", "ii", itablecopy, NULL, NULL}, // { "tablecopy", S(TABLECOPY),2, "", "kk", tablecopyset, tablecopy, NULL}, // // { "tablera", S(TABLERA),5, "a", "kkk", tableraset, NULL, tablera}, // { "tablewa", S(TABLEWA),5, "k", "kak", tablewaset, NULL, tablewa}, //**************************************************************************** // // The "zak" system // ---------------- // // The zak system uses one area of memory as a global i or k rate // patching area, and another for audio rate patching. These are // establised by a ugen which must be called once only. // // zakinit isizea, isizek // // isizek is the number of locations we want to reserve for floats // in the zk space. These can be written and read at i and k rates. // // isizea is the number of audio rate "locations" for a rate patching. // Each "location" is actually an array which is ksmps long. // // eg. zakinit 100 30 reserves memory for locations 0 to 100 of zk space // and for locations 0 to 30 of a rate za space. With ksmps = 8, this // would take 101 floats for zk and 248 floats for za space. // // At least one location is always allocated for both zk and za spaces. // // These patching locations can be referred to by number with the // following ugens. // // There are two short, simple, fast opcodes which read a location in // zk space, at either i time or at the k rate. // // ir zir indx // kr zkr kndx // // Likewise, two write to a location in zk space at i time or at the k // rate. // // ziw isig, indx // zkw ksig, kndx // // These are fast and always check that the index is within the // range of zk space. If it is out of range, an error is reported // and 0 is returned, or no writing takes place. // // ziwm isig, indx [,imix] // zkwm ksig, kndx [,imix] // // Like ziw and zkw above, except that they can mix - add the sig // to the current value of the variable. If no imix is specified, // they mix, but if imix is used, then 0 will cause writing (like // ziw and zkw) any other value will cause direct mixing. // // kr zkmod ksig, kzkmod // // zkmod is a unit generator intended to facilitate the modulation // of one signal by another, where the modulating signal comes from // a zk variable. Either additive or mulitiplicative modulation is // provided. // // ksig is the input signal, to be modulated and sent to the output // of the zkmod unit generator. // // kzkmod controls which zk variable is used for modulation. A positive // value means additive modulation, a negative value means multiplicative // modulation. A value of 0 means no change to ksig - it is transferred // directly to the output. // // For instance kzkmod = 23 will read from zk variable 23, and add the // value it finds there to ksig. If kzkmod = -402, then ksig is // multiplied by the value read from zk location 402. // // kskmod can be an init time constant, or a k rate value. // // // zkcl kfirst, klast // // This will clear to zero one or more variables in the zk space. // Useful for those variables which are accumulators for mixing // things during the processing for each cycle, but which must be // cleared to zero before the next set of calculations. // //------ // // For a rate reading and writing, in the za space, we use similar // opcodes: // // ar zar kndx // // kndx points to which za variable to read. This reads the number kndx // array of floats which are the ksmps number of audio rate floats to be // processed in a k cycle. // // ar zarg kndx, kgain // // Similar to zar, but multiplies the a rate signal by a k rate value // kgain. // // zaw asig, kndx // // Writes into the array specified by kndx. // // zawm asig, kndx [,imix] // // Like zaw above, except that it can mix - add the asig to the current // value of the destination za variable. If no imix is specified, // it mixes, but if imix is used, then 0 will cause a simple write (like // zaw) and any other value will cause mixing. // // ar zamod asig, kzamod // // Modulation of one audio rate signal by a second one - which comes from // a za variable. The location of the modulating variable is controlled // by the k rate variable kzamod. This is the audio rate version of // zkmod described above. // // zacl kfirst, klast // // This will clear to zero one or more variables in the za space. // Useful for those variables which are accumulators for mixing // things during the processing for each cycle, but which must be // cleared to zero before the next set of calculations. // //------ // //>>> Add these to the end of opcodlst in entry.c. // // opcode dspace thread outarg inargs isub ksub asub // // { "zakinit", S(ZAKINIT), 1, "", "ii", zakinit, NULL, NULL}, // // { "zir", S(ZKR), 1, "i", "i", zir, NULL, NULL}, // { "zkr", S(ZKR), 3, "k", "k", zkset, zkr, NULL}, // // { "ziw", S(ZKW), 1, "", "ii", ziw, NULL, NULL}, // { "zkw", S(ZKW), 3, "", "kk", zkset, zkw, NULL}, // // { "ziwm", S(ZKWM), 1, "", "iip", ziwm, NULL, NULL}, // { "zkwm", S(ZKWM), 3, "", "kkp", zkset, zkwm, NULL}, // // { "zkmod", S(ZKMOD), 2, "k", "kk", NULL, zkmod, NULL}, // // { "zkcl", S(ZKCL), 3, "", "kk", zkset, zkcl, NULL}, // // // { "zar", S(ZAR), 5, "a", "k", zaset, NULL, zar}, // { "zarg", S(ZARG), 5, "a", "kk", zaset, NULL, zarg}, // // { "zaw", S(ZAW), 5, "", "ak", zaset, NULL, zaw}, // // { "zawm", S(ZAWM), 5, "", "akp", zaset, NULL, zawm}, // // { "zamod", S(ZAMOD), 4, "a", "ak", NULL, NULL, zamod}, // // { "zacl", S(ZACL), 5, "", "kk", zaset, NULL, zacl}, // // What types of input variables are used? // // Runs at time // ir zir indx i // kr zkr kndx k // // ziw isig, indx i // zkw ksig, kndx k // // ziwm isig, indx, imix i // zkwm ksig, kndx, kmix k // // zkcl kfirst, klast k // // ar zar kndx k but does arrays // ar zarg kndx, kgain k but does arrays // // zaw asig, kndx k but does arrays // // zawm asig, kndx, kmix k but does arrays // // zacl kfirst, klast k but does arrays // // // isig } // indx } Known at init time // imix } // // ksig } // kndx } // kmix } k rate variables // kfirst } // klast } // kgain } // // asig } a rate variable - an array of floats. //************************************************************************* // // Simple time reading ugens //-------------------------- // // kr timek // kr times // // timek returns a float containing an integer - the number of k cycles // since the start of the performance of the orchestra. // // times returns a float with the number of seconds. // // They both expect a k rate variable as their output. There are no // input parameters. // // ir itimek // ir itimes // // itemek and itimes are similar ugens which only operate during the // initialisation of an instance of the instrument. // // kr instimek // kr instimes // // These are similar to timek and times, except they return the // time since the start of this instance of the instrument. // //>>> Add these to the end of opcodlst in entry.c. // // opcode dspace thread outarg inargs isub ksub asub // // { "itimek", S(RDTIME), 1, "i", "", timek, NULL, NULL}, // { "itimes", S(RDTIME), 1, "i", "", times, NULL, NULL}, // { "timek", S(RDTIME), 2, "k", "", NULL, timek, NULL}, // { "times", S(RDTIME), 2, "k", "", NULL, times, NULL}, // { "instimek", S(RDTIME), 3, "k", "", instimset, instimek, NULL}, // { "instimes", S(RDTIME), 3, "k", "", instimset, instimes, NULL}, //************************************************************************* // // Two ugens for printing at k rate //--------------------------------- // // printk prints one k rate value on every k cycle, every second or at // intervals specified. First the instrument number is printed, then // the absolute time in seconds, then a specified number of spaces, then // the value. The variable number of spaces enables different values to // be spaced out across the screen - so they are easier to view. // This is for debugging orchestra code. // // printk itime, kval [, ispace] // // itime How much time in seconds is to elapse between printings. // // kval The number to be printed. // // ispace How many spaces to insert before it is printed. (Max 120.) // Default = 0. // // The first print is on the first k cycle of the instance of the // instrument. // //------ // // printks prints numbers and text, with up to four printable numbers // - which can be i or k rate values. // // printks "txtstring", itime, kval1, kval2, kval3, kval4 // // txtstring Text to be printed first - can be up to 130 characters at // least. _Must_ be in double quotes. // // The string is printed as is, but standard printf %f etc. // codes are interpreted to print the four parameters. // // However the \n style of character codes are not interpreted // by printf. (They are converted by the C compiler in // string literals found in the C program.) This ugen therefore // provides certain specific codes which are expanded: // // \n or \N Newline // \t or \T Tab // // ^ Escape character // ^^ ^ // // ~ Escape and '[' These are the leadin codes for // MSDOS ANSI.SYS screen control characters. // ~~ ~ // // An init error is generated if the first parameter is not // a string of length > 0 enclosed in double quotes. // // A special mode of operation allows this ugen to convert // the first input parameter into a 0 to 255 character, and // to use it as the first character to be printed. This enables // a Csound program to send arbitrary characters to the console - // albeit with a little awkwardness. printf() does not have a // format specifier to read a float and turn it into a byte // for direct output. We could add extra code to do this if // we really wanted to put arbitrary characters out with ease. // // To acheive this, make the first character of the string a // # and then, if desired continue with normal text and format // specifiers. Three more format specifers may be used - they // access kval2, kval3 and kval4. // // itime How much time in seconds is to elapse between printings. // // kvalx The k rate values to be printed. Use 0 for those which are not // used. // // //>>> Add these to the end of opcodlst in entry.c. // // opcode dspace thread outarg inargs isub ksub asub // // { "printk", S(PRINTK), 3, "", "iko", printkset, printk, NULL}, // { "printks",S(PRINTKS), 3, "", "Sikkkk",printksset, printks, NULL}, //************************************************************************* // // Two ugens for tracking peak signal levels //------------------------------------------ // // peakk takes k rate inputs and peak takes a rate inputs. // // They maintain the output k rate variable as the peak absolute // level so far received. // // kpeakout peakk ksigin // kpeakout peaka asigin // // // kpeakout Output equal to the highest absolute value received // so far. // This is effectively an input to the ugen as well, since it // reads kpeakout in order to decide whether to write // something higher into it. // // ksigin k rate input signal. // asigin a rate input signal. // //>>> Add these to the end of opcodlst in entry.c. // // opcode dspace thread outarg inargs isub ksub asub // // { "peakk", S(PEAK), 2, "k", "k", NULL, peakk, NULL}, // { "peak", S(PEAK), 4, "k", "a", NULL, NULL, peaka}, // ! ! ! ! // // See the end of this file for details of what other things to // add to entry.c and how to tweak the makefile. //----------------------------------------------------------------------------- // Table write code // // The way that the k rate table // numbers are handled by different // ugens and functions is analogous // to the approach used in the new // ugens2.c. // // itblchkw() // // Internal function itblchkw(). // Called at init time by tblsetw() to // initialise some of the variables in // TABLEW - which is pointed to by p. // // Also called by itablew(). // // A similar function ptblchkw() does // the same job at performance time - // k processing cycles. // // Both these functions are virtually // identical to those itblchk() and // ptblchk() in ugens2.c. The // differences are: // // 1 - They use TABLEW instead of // TABLE. // // 2 - There is no iwrap parameter. // // 3 - There is an igwmode parameter. int itblchkw(TABLEW *p) { // Get pointer to the function table // data structure of the table number // specified in xfn. Return 0 if // it cannot be found. // // ftfind() generates an error message // if the table cannot be found. This // works OK at init time. It also // deactivates the instrument. // // It also checks for numbers < 0, // and table 0 is never valid, so // we do not need to check here for // the table number being < 1. if ((p->ftp = ftfind(p->xfn)) == NULL) return(NULL); // Table number is valid. // // Although TABLEW has an integer // variable for the table number // (p->pfn) we do not need to // write it. We know that // the k and a rate functions which // will follow will not be expecting // a changed table number. // // p->pfn exists only for checking // table number changes for functions // which are expecting a k rate // table number. // Set denormalisation factor to 1 or // table length, depending on the // state of ixmode. // 1L means a 32 bit 1. if (*p->ixmode) p->xbmul = p->ftp->flen; else p->xbmul = 1L; // Multiply the ixoff value by the // xbmul denormalisation factor and // then check it is between 0 and the // table length. if ( ( p->offset = p->xbmul * *p->ixoff) < 0. || p->offset > p->ftp->flen ) { sprintf(errmsg, "Table write offset %f < 0 or > tablelength", p->offset); initerror(errmsg); return(NULL); } p->iwgm = *p->iwgmode; // Return 1 to say everything is OK. return(1); } //************************************* // // ptblchkw() // // This is called at init time // by tblsetwkt() to set up the TABLEW // data structure for subsequent // k and a rate operations which are // expecting the table number to // change at k rate. // // tblsetwkt() does very little - // just setting up the iwgm variable // in TABLE. All the other variables // depend on the table number. This // is not available at init time, // so the following two functions must // look for the changed table number // and set up the variables accordingly // - generating error messages in a // way which works at performance time. // // k rate a rate // // ktablewkt tablewkt ptblchkw(TABLEW *p) { // TABLEW has an integer variable for // the previous table number (p->pfn). // // Now (at init time) we do not // know the function table number // which will be provided at perf // time, so set p->pfn to 0, so that // the k or a rate code will recognise // that the first table number is // different from the "previous" one. p->pfn = 0; // The only other thing to do is // write the iwgmode value into the // immediate copy of it in TABLEW. p->iwgm = *p->iwgmode; } //----------------------------------------------------------------------------- // tblsetw() // // This is called at init time // to set up TABLEW for the a and k // rate table read functions which // are expecting the table number to // be fixed at i time. // // Call the itblchkw() function to // do the work. void tblsetw(TABLEW *p) { itblchkw(p); } // tblsetwkt() // // This is called at init time // to set up TABLEW for the a and k // rate table read functions which // are expecting the table number to // be a k rate variable. // // Call the ptblchkw() function to // do the work. void tblsetwkt(TABLEW *p) { ptblchkw(p); } // itablew() // // Used to write to a table only at // init time. It is called (via the // opcodlst in entry.c) for the // itablew opcode. // // It sets up some variables in the // TABLEW data structure for this // instance and calls ktablew() once // to write to the table. void itablew(TABLEW *p) { void ktablew(); if (itblchkw(p)) ktablew(p); } //----------------------------------------------------------------------------- // ktablew is called with p pointing // to the TABLEW data structure - // which contains the input arguments. void ktablew(TABLEW *p) { // Local variables . . . // // Pointer to data structure for // accessing the table we will be // writing to. FUNC *ftp; // 32 bit integers for pointing into // table and for the table length - // which is always a power of 2. // The table must actually be one // more longer than this if it has a // guard point. register long indx, length; // Float for calculating where we are // going to read from. register float ndx; // Pointer to float, we will use it // to write into table. register float *ptab; //------------------------------------- // Assume that TABLEW has been // set up correctly. // Set up local variables. ftp = p->ftp; ndx = *p->xndx; length = ftp->flen; // Multiply ndx by denormalisation // factor. and add in the offset - // already denormalised - by tblchkw(). // // xbmul = 1 or table length depending // on state of ixmode. ndx = ( ndx * p->xbmul) + p->offset; // ndx now includes the offset and is // ready to address the table. // However we have three modes to // consider: // // igwm = 0 Limit mode. // 1 Wrap mode. // 2 Guardpoint mode. if (p->iwgm == 0) { // Limit mode - when igmode = 0. // // Limit the final index to 0 and // the last location in the table. // // Wrap and guard point modes are // acheived by code following this // which only runs if igmode != 0. // Limit to (table length - 1). // // First we have to turn ndx into // a 32 bit integer. This is non- // trivial since we want it to // round to the next most negative // integer. // A simple cast from float to integer // does not do this - -2.3 is rounded // to -2, we want it rounded to -3. // // This behaviour may not depend on // ANSI C, but on the compiler and // machine - so we cannot use it. // // Instead use the ANSI maths function // float floor(float) // // There may be faster ways of doing // it, but the results could be // machine dependant. indx = (long) floor(ndx); // Limit the high values. if (indx > length - 1) indx = length - 1; // Now limit negative values to zero. // 0L is a macro for a long zero. // (Following the example set in // ktable(). Why is this used? else if (indx < 0L) indx = 0L; } // Wrap and guard point mode. // // In guard point mode only, add 0.5 // to the index. else { if (p->iwgm == 2) ndx += 0.5; indx = (long) floor(ndx); // Both wrap and guard point mode. // // The following code uses an AND with // an integer like 0000 0111 to // wrap the current index within the // range of the table. indx &= ftp->lenmask; // Calculate the address of where we // want to write to, from indx and the // starting address of the table. } ptab = ftp->ftable + indx; // Write the input value to the table. *ptab = *p->xsig; // If this is guard point mode and we // have just written to location 0, // then also write to the guard point. if ( (p->iwgm == 2) & indx == 0 ) { ptab += ftp->flen; *ptab = *p->xsig; } } //----------------------------------------------------------------------------- // tablew() is similar to ktablew() // above, except that it processes // two arrays of input values and // indexes. // These arrays are ksmps long. void tablew(TABLEW *p) { // Local variables . . . // // Pointer to function table data // structure. register FUNC *ftp; // Pointers to floats: // // * psig Array of input values // to be written to // table. // // * pxndx Array of input index // values. // // * ptab Pointer to start of // table we will write. // // * pwrite Pointer to location in // table where we will // write. register float *psig, *pxndx, *ptab, *pwrite; // 32 bit integers: // // indx Used to read table. // // mask ANDed with indx to // make it wrap within // table. // // length Length of table - // always a power of two, // even if the table has // a guard point. // // For instance length = 8, mask = // 0000 0111, normal locations in table // are 0 to 7. Location 8 is the // guard point. table() does not read // the guard point - tabli() does. register long indx, mask, length; // Local copy of iwgm for speed. register long liwgm; // nsmps is the counter for the loop // which processes the data. // declare it as an integer, and // intialize it to the global variable // ksmps. register int nsmps = ksmps; // Float local variables. // // ndx Index. // // xbmul Normalisation factor // to adjust for ndx // having a 0 to 1 range. // Equal to 1 or length // depending on ixmode. // // offset Offset to add to ndx. // Already denormalised. register float ndx, xbmul, offset; //------------------------------------- // Assume that TABLEW has been // set up correctly. // Set up local variables. ftp = p->ftp; psig = p->xsig; pxndx = p->xndx; ptab = ftp->ftable; mask = ftp->lenmask; length = ftp->flen; liwgm = p->iwgm; xbmul = p->xbmul; offset = p->offset; // Main loop - for the number of a // samples in a k cycle. do { // Read in the next raw index and // increment the pointer ready for // the next cycle. // // Then multiply the ndx by the // denormalising factor and add in // the offset. ndx = (*pxndx++// xbmul) + offset; // ndx ready, now consider the // three iwgmode modes. // // igwm = 0 Limit mode. // 1 Wrap mode. // 2 Guardpoint mode. if (liwgm == 0) { // Limit mode - when igmode = 0. // Limit to (table length - 1). // // First we have to turn ndx into // a 32 bit integer. indx = (long) floor(ndx); // Limit the high values. if (indx > length - 1) indx = length - 1; // Now limit negative values to zero. else if (indx < 0L) indx = 0L; } // Wrap and guard point mode. // // In guard point mode only, add 0.5 // to the index. else { if (liwgm == 2) ndx += 0.5; indx = (long) floor(ndx); // Both wrap and guard point mode. // // AND with an integer like 0000 0111 // to wrap the index within the // range of the table. indx &= mask; // Calculate the address of where we // want to write to, from indx and the // starting address of the table. } pwrite = ptab + indx; // Write the input value to the table. // Auto increment pointer to input // array. *pwrite = *psig++; // If this is guard point mode and we // have just written to location 0, // then also write to the guard point. if ( (liwgm == 2) & indx == 0 ) { // Note that since pwrite is a pointer // to a float, adding length to it // adds (4 * length) to its value since // the length of a float is 4 bytes. pwrite += length; // Decrement psig to make it point // to the same input value. // Write to guard point. psig--; *pwrite = *psig++; } } while(--nsmps); } //************************************* // // ktablewkt() and tablewkt() // // These call ktablew() and tablew() // above - after they have set up // TABLEW after the k rate table number // changes. // // Prior to these running, we can // assume that tblsetwkt() has been // run at init time. // // tblsetwkt() does very little - // just setting up the iwgmode variable // in TABLEW. All the other variables // depend on the table number. This // is not available at init time, // so the following two functions must // look for the changed table number // and set up the variables accordingly // - generating error messages in a // way which works at performance time. // // k rate a rate // // ktablewkt tablewkt // // Since these perform identical // operations, apart from the // function they call, create a // common function to do this work: // // ftkrchkw() int ftkrchkw(TABLEW *p) { // Check the table number is >= 1. // Print error and deactivate // if it is. // Return 0 to tell calling function // not to proceed with a or k rate // operations. // // This was not necessary in the // versions of ktablew() and tablew() // because ftfind() would catch a // table number < 1. // // However, in this case, we only call // ftfindp() if the table number // changes from p->pfn, so we want // to generate an error message if // the ugen is ever called with a // table number of 0. While we // are about it, catch negative values // too. if (*p->xfn < 1) { sprintf(errmsg, "Table write k rate function table no. %f < 1", *p->xfn); perferror(errmsg); return (NULL); } // Check to see if table number // has changed from previous value. // // On the first run through, the // previous value will be 0. if (p->pfn != (long)*p->xfn) { // If it is different, check to see // if the table exists. // If it doesn't, an error message // should be produced by ftfindp() // which should also deactivate // the instrument. // Return 0 to tell calling function // not to proceed with a or k rate // operations. // // ftfind is in a new version of // fgens.c. A prototype for it // should be added to prototype.h. if ( (p->ftp = ftfindp(p->xfn) ) == NULL) { return (0); } // p->ftp now points to the FUNC // data structure of the newly // selected table. // // Now we set up some variables in // TABLEW ready for the k or a rate // functions which follow. // Write the integer version of the // table number into pfn so we // can later decide whether subsequent // calls to the k and a rate functions // occur with a table number value // which points to a different table. // // p->pfn is an integer. p->pfn = *p->xfn; // Set denormalisation factor to 1 or // table length, depending on the // state of ixmode. // 1L means a 32 bit 1. if (*p->ixmode) p->xbmul = p->ftp->flen; else p->xbmul = 1L; // Multiply the ixoff value by the // xbmul denormalisation factor and // then check it is between 0 and the // table length. if ( (p->offset = p->xbmul * *p->ixoff) < 0. || p->offset > p->ftp->flen ) { sprintf(errmsg, "Table write offset %f < 0 or > tablelength", p->offset); perferror(errmsg); return(NULL); } // If all is well, return 1 to // tell calling function to proceed // with a or k rate operations. return(1); } } // Now for the two functions, which // are called as a result of being // listed in opcodlst in entry.c void ktablewkt(TABLEW *p) { if ( ftkrchkw(p) ) ktablew(p); } void tablewkt(TABLEW *p) { if ( ftkrchkw(p) ) tablew(p); } //***************************************************************************** // Reading the table length // tableng() // // At k rate - performance time. // See similar function to do it at // init time - itableng(). // // The means of finding the table, // and of reporting an error differ // between these i time and perf time. void tableng(TABLENG *p) { // Local variables // // Pointer to data structure for // accessing table. register FUNC *ftp; // Check to see we can find the table // and find its location in memory. // Returns zero if not found. // Report and error, which will cause // this instrument to be de-activated. if ((ftp = ftfindp(p->xfn)) == NULL) { *p->kout = 0; sprintf(errmsg, "Table %f not found\n", p->xfn); perferror(errmsg); } // Return length as a float if we // do find the table. else *p->kout = (float) ftp->flen; } //------------------------------------- // itableng() // // At init time. void itableng(TABLENG *p) { // Local variables // // Pointer to data structure for // accessing table. register FUNC *ftp; // Check to see we can find the table // and find its location in memory. // Returns zero if not found. // Report and error, which will cause // this instrument initialisation to // fail. if ((ftp = ftfind(p->xfn)) == NULL) { *p->kout = 0; sprintf(errmsg, "Table %f not found\n", p->xfn); initerror(errmsg); } // Return length as a float if we // do find the table. else *p->kout = (float) ftp->flen; } //----------------------------------------------------------------------------- // Writing the guardpoint // tablegpw() // // At k rate - performance time. // See similar function to do it at // init time - itablegpw(). // // The means of finding the table, // and of reporting an error differ // between these i time and perf time. // // Read the value in location 0 and // write it to the guard point. void tablegpw(TABLEGPW *p) { // Local variables // // Pointer to data structure for // accessing table. register FUNC *ftp; // Pointer to start of table. register float *ptab; // Value read from location 0 in // table. register float val0; // Temporary storage for length - // in floats, not in bytes. register long length; // Check to see we can find the table // and find its location in memory. if ((ftp = ftfindp(p->xfn)) == NULL) { sprintf(errmsg, "Table %f not found\n", p->xfn); perferror(errmsg); } // Find length of table. else { length = (long) ftp->flen; ptab = ftp->ftable; // Now write from location 0 to // the guard point which is at // location length. val0 = *ptab; ptab = ptab + length; *ptab = val0; } } //------------------------------------- // itablegpw() // // At init time. void itablegpw(TABLEGPW *p) { // Local variables // // Pointer to data structure for // accessing table. register FUNC *ftp; // Pointer to start of table. register float *ptab; // Value read from location 0 in // table. register float val0; // Temporary storage for length - // in floats, not in bytes. register long length; // Check to see we can find the table // and find its location in memory. if ((ftp = ftfind(p->xfn)) == NULL) { sprintf(errmsg, "Table %f not found\n", p->xfn); initerror(errmsg); } // Find length of table. else { length = (long) ftp->flen; ptab = ftp->ftable; // Now write from location 0 to // the guard point which is at // location length. val0 = *ptab; ptab = ptab + length; *ptab = val0; } } //----------------------------------------------------------------------------- // tablemix functions // tablemixset() // // Called at i time prior to // the k rate function tablemix(). void tablemixset(TABLEMIX *p) { // There may be no input values now - // they are typically k rate and so // are not present at i time. // // Set to zero the three variables // with which we check to see if // the k rate table numbers have // changed. These are in the // TABLEMIX structure which is // specific to this instance of the // ugen. However its values have not // been initialised, so they could // be anything. p->pdft = 0; p->ps1ft = 0; p->ps2ft = 0; } //------------------------------------- // tablemix() // // k rate version - see itablemix() // for the init time version. // // These are similar but require // two different approaches to // finding tables and reporting // errors. // // This adventurous ugen uses nine // parameters which are all assumed // to be k rate variables - which // could change at any time. // // Six of these will used directly. // // However, three of them will be // checked to see if they have changed // from last time - the three // variables which point to the // destination and source tables. // // If they change, then the new values // will be used to search for a new // table. Otherwise, existing pointers // will be used to access the data // structures for the tables. // // Both the i and k rate operations // have a lot in common, so use a // common function domix(). // // Declare it here. void domix(TABLEMIX *p); void tablemix(TABLEMIX *p) { // Check the state of the three // table number variables. // // Error message if any are < 1 // and no further action. // // We cannot leave it for ftfindp() // to find 0 values, since it is only // called if the input value is // different from the "previous" value // which is initially 0. if ( (*p->dft < 1) || (*p->s1ft < 1) || (*p->s2ft < 1) ) { sprintf(errmsg, "Table no. < 1 dft=%.2f s1ft=%.2f s2ft=%.2f\n", *p->dft, *p->s1ft, *p->s2ft); perferror(errmsg); return; } // Check each table number in turn. // Destination if (p->pdft != (int)*p->dft) { // Get pointer to the function table // data structure. // // ftfindp() for perf time. // ftfind() for init time. if ((p->funcd = ftfindp(p->dft)) == NULL) { sprintf(errmsg, "Destination dft table %.2f not found.\n", *p->dft); perferror(errmsg); return; } // Table number is valid. // // Save the integer version of the // table number for future reference. p->pdft = *p->dft; } // Source 1 if (p->ps1ft != (int)*p->s1ft) { if ((p->funcs1 = ftfindp(p->s1ft)) == NULL) { sprintf(errmsg, "Source 1 s1ft table %.2f not found.\n", *p->s1ft); perferror(errmsg); return; } p->ps1ft = *p->s1ft; } // Source 2 if (p->ps2ft != (int)*p->s2ft) { if ((p->funcs2 = ftfindp(p->s2ft)) == NULL) { sprintf(errmsg, "Source 2 s2ft table %.2f not found.\n", *p->s2ft); perferror(errmsg); return; } p->ps2ft = *p->s2ft; } // OK all tables present and the // funcx pointers are pointing to // their data structures. // // The other parameters do not // need checking - and the remaining // proceedures are common to the // init time version, so call a // function to do the rest. domix(p); } //------------------------------------- // itablemix() // // identical to above, but we know it // runs at init time, so we do not // check for changes, we look for // the table with ftfind() instead of // ftfindp() and we use initerror() // instead of perferror(). void itablemix(TABLEMIX *p) { // Check the state of the three // table number variables. // // Error message if any are < 1 // and no further action. // // Technically we do not need to // check for values of 0 or negative // since they will all be fed to // ftfind(). // Do so anyway to be consistent // with tablemix(). // // This runs only once, so speed is // not an issue. if ( (*p->dft < 1) || (*p->s1ft < 1) || (*p->s2ft < 1) ) { sprintf(errmsg, "Table number < 1 dft=%.2f s1ft=%.2f s2ft=%.2f\n", *p->dft, *p->s1ft, *p->s2ft); initerror(errmsg); return; } // Check each table number in turn. // Destination // Get pointer to the function table // data structure. // // ftfind() for init time. if ((p->funcd = ftfind(p->dft)) == NULL) { sprintf(errmsg, "Destination dft table %.2f not found.\n", *p->dft); initerror(errmsg); return; } // Table number is valid. // // Save the integer version of the // table number for future reference. p->pdft = *p->dft; // Source 1 if ((p->funcs1 = ftfind(p->s1ft)) == NULL) { sprintf(errmsg, "Source 1 s1ft table %.2f not found.\n", *p->s1ft); initerror(errmsg); return; } p->ps1ft = *p->s1ft; // Source 2 if ((p->funcs2 = ftfind(p->s2ft)) == NULL) { sprintf(errmsg, "Source 2 s2ft table %.2f not found.\n", *p->s2ft); initerror(errmsg); return; } p->ps2ft = *p->s2ft; domix(p); } //------------------------------------- // domix() // // This is the business end of // tablemix and itablemix. // // We could be called at either // init or perf time. So we do not // make any error messages here. // // The three tables have been found // and are known to be of greater // than zero length - ftfind() and // ftfindp() check this. // // We will use the remaining input // parameters no matter what their // values are. // // Length is converted from a // float to a long, with floor() // so that -0.3 converts to -1. // // The resulting integer could be // negative - this tells us to // work backwards. // // The offsets could be anything // whatsoever - these will be added // to index to produce integers // which are rounded by the lenmask // of each table. // So we don't mind if the offsets // are all over the place. // // Likewise the gain parameters // for source tables 1 and 2, except // that if the gain of source 2 is // 0, then we do not bother reading // it. This is to save time when // all that the user wants is a copy. void domix(TABLEMIX *p) { // Local variables: // Gains for source tables 1 and 2. register float gains1, gains2; // Length - from len input parameter. // Could be negative. register long length; // Counter for the processing loop. register long loopcount; // Offsets for the three tables. register long offd, offs1, offs2; // Index to be added to offsets as // we step through the tables. // If length was positive, this will // increase by one each cycle. // If length was negative, it will // step backards from 0. register long indx = 0; // Base addresses of the three tables. // These are pointers to floats. register float *based, *bases1, *bases2; // Binary masks for the three tables // as 32 bit integers. register long maskd, masks1, masks2; // Pointers to floats in the three // tables. register float *pdest, *ps1, *ps2; // Get the two source gains. gains1 = *p->s1g; gains2 = *p->s2g; // Get the length and generate // the loop count. // // Return with no action if it is 0. if ( (length = floor(*p->len)) == 0 ) return; if (length < 0) loopcount = 0 - length; else loopcount = length; // Get the offsets for the three tables // Use floor to reduce negative floats // to the next most negative integer. // This ensures that a sweeping // offset will wrap correctly into the // table's address space. offd = floor(*p->doff); offs1 = floor(*p->s1off); offs2 = floor(*p->s2off); // Now get the base addresses and // length masks of the three tables. based = p->funcd->ftable; maskd = p->funcd->lenmask; bases1 = p->funcs1->ftable; masks1 = p->funcs1->lenmask; bases2 = p->funcs2->ftable; masks2 = p->funcs2->lenmask; // Decide which of four loops to do // based on: // // Forwards or backwards? // // Source 2 gain is zero or not? if (length > 0) { if (gains2 != 0) { // Forwards, full mix. do { // Create pointers by adding index to // offset, ANDing with mask, and // adding to base address. // // Mask, offset and index are all in // units of 1 - not the units of // 4 bytes (typically) needed to // step through memory to find // floats. // // So we make base a pointer to a // float and the compiler is smart // enough to know that when we add // an integer to a float pointer, // we want that pointers address // to change by sizeof(float)// // the value of the integer. pdest = based + (maskd & (offd + indx)); ps1 = bases1 + (masks1 & (offs1 + indx)); ps2 = bases2 + (masks2 & (offs2 + indx)); // Mix from source1 and source 2. *pdest = (*ps1 * gains1) + (*ps2 * gains2); indx++; } while (--loopcount); } else // Forwards, only read source 1 { do { pdest = based + (maskd & (offd + indx)); ps1 = bases1 + (masks1 & (offs1 + indx)); // Write from source1 to destination. *pdest = (*ps1 * gains1); indx++; } while (--loopcount); } } else { // Negative length, so do things // backwards. if (gains2 != 0) { // Backwards, full mix. do { pdest = based + (maskd & (offd + indx)); ps1 = bases1 + (masks1 & (offs1 + indx)); ps2 = bases2 + (masks2 & (offs2 + indx)); // Mix from source1 and source 2. *pdest = (*ps1 * gains1) + (*ps2 * gains2); indx--; } while (--loopcount); } else // Backwards, only read source 1. { do { pdest = based + (maskd & (offd + indx)); ps1 = bases1 + (masks1 & (offs1 + indx)); // Write from source1 to destination. *pdest = (*ps1 * gains1); indx--; } while (--loopcount); } } } //--------------------------------------------------------------------------- // tablecopy functions // tablecopyset() // // Called at i time prior to // the k rate function tablemix(). // Similar function to tablemixset(). void tablecopyset(TABLECOPY *p) { p->pdft = 0; p->psft = 0; } //------------------------------------- // tablecopy() // // k rate version - see itablecopy() // for the init time version. // // These two functions, and the // docopy() function they share are // simpler, faster, cut-down versions // of their equivalent in the // tablemix section above. // Read the comments there for a full // explanation. void docopy(TABLECOPY *p); void tablecopy(TABLECOPY *p) { // Check the state of the two // table number variables. // // Error message if any are < 1 // and no further action. if ( (*p->dft < 1) || (*p->sft < 1) ) { sprintf(errmsg, "Table no. < 1 dft=%.2f sft=%.2f\n", *p->dft, *p->sft); perferror(errmsg); return; } // Check each table number in turn. // Destination if (p->pdft != (int)*p->dft) { // Get pointer to the function table // data structure. // // ftfindp() for perf time. // ftfind() for init time. if ((p->funcd = ftfindp(p->dft)) == NULL) { sprintf(errmsg, "Destination dft table %.2f not found.\n", *p->dft); perferror(errmsg); return; } // Table number is valid. // // Save the integer version of the // table number for future reference. p->pdft = *p->dft; } // Source if (p->psft != (int)*p->sft) { if ((p->funcs = ftfindp(p->sft)) == NULL) { sprintf(errmsg, "Source sft table %.2f not found.\n", *p->sft); perferror(errmsg); return; } p->psft = *p->sft; } // OK both tables present and the // funcx pointers are pointing to // their data structures. docopy(p); } //------------------------------------- void itablecopy(TABLECOPY *p) { // Check the state of the two // table number variables. // // Error message if any are < 1 // and no further action. if ( (*p->dft < 1) || (*p->sft < 1) ) { sprintf(errmsg, "Table no. < 1 dft=%.2f sft=%.2f\n", *p->dft, *p->sft); initerror(errmsg); return; } // Check each table number in turn. // Destination if (p->pdft != (int)*p->dft) { // Get pointer to the function table // data structure. // // ftfindp() for perf time. // ftfind() for init time. if ((p->funcd = ftfind(p->dft)) == NULL) { sprintf(errmsg, "Destination dft table %.2f not found.\n", *p->dft); initerror(errmsg); return; } // Table number is valid. // // Save the integer version of the // table number for future reference. p->pdft = *p->dft; } // Source if (p->psft != (int)*p->sft) { if ((p->funcs = ftfind(p->sft)) == NULL) { sprintf(errmsg, "Source sft table %.2f not found.\n", *p->sft); initerror(errmsg); return; } p->psft = *p->sft; } // OK both tables present and the // funcx pointers are pointing to // their data structures. docopy(p); } //------------------------------------- // docopy() // // This is the business end of // tablecopy and itablecopy. // // See domix for full explanation. // make any error messages here. void docopy(TABLECOPY *p) { // Local variables: // Counter for the processing loop. // Set by the length of the destination // table. register long loopcount; // Index to be added to offsets as // we step through the tables. register long indx = 0; // Base addresses of the two tables. // These are pointers to floats. register float//based,//bases; // Binary masks for the source table // as a 32 bit integer. register long masks; // Pointers to floats in the two // tables. register float *pdest, *ps; // Set the loopcount to the destination // table length. loopcount = p->funcd->flen; // Now get the base addresses and // length masks of the tables. // Length mask should be redundant for // the destination since we are writing // to what we believe is its length - // so don't use one. based = p->funcd->ftable; bases = p->funcs->ftable; masks = p->funcs->lenmask; do { // Create source pointers by ANDing // index with mask, and adding to base // address. This causes source // addresses to wrap around if the // destination table is longer. // // Destination address is simply the // index plus the base address since // we know we will be writing within // the table. pdest = based + indx; ps = bases + (masks & indx); // Copy from source. *pdest = *ps; indx++; } while (--loopcount); } //----------------------------------------------------------------------------- // tablera functions // tableraset() // // Called at i time prior to // the k rate function tablemix(). // Similar function to tablemixset(). // Set the "previous table number" // to 0, so that at the first // k cycle, a positive table number // will be recognised as a new value. void tableraset(TABLERA *p) { p->pfn = 0; } //------------------------------------- // tablera() // // Read ksmps values from a table, // starting at location kstart. // Has an offset and wrap-around // index calculation. // // Write them to an a rate destination. // // Table number is k rate, so check // for it changing and for it being // 0. void tablera(TABLERA *p) { // Local variables // // Pointers for writing to a rate // destination, and for reading from // table. register float *writeloc, *readloc; // Local variable to hold kstart // input variable - we need to look at // it a few times. long kstart; // Local variable to hold integer // version of offset, and the length // mask for ANDing the total index // - wrapping it into the table length. long kioff, mask; // Loop counter register int loopcount; // Check the state of the table number // variable. // // Error message if it is < 1 // and no further action. if (*p->kfn < 1) { sprintf(errmsg, "Table kfn=%.2f < 1\n", *p->kfn); perferror(errmsg); return; } // Check to see if the table number // has changed. if (p->pfn != (int)*p->kfn) { // Get pointer to the function table // data structure. // // ftfindp() for perf time. if ((p->ftp = ftfindp(p->kfn)) == NULL) { sprintf(errmsg, "kfn table %.2f not found\n", *p->kfn); perferror(errmsg); return; } // Table number is valid. // // Save the integer version of the // table number for future reference. p->pfn = *p->kfn; // Check that the table length is // equal to or greater than ksmps. // Create error message if this is // not so. We must ensure that // the ksmps number of reads can // fit within the length of the table. if (p->ftp->flen < ksmps) { sprintf(errmsg, "Table kfn=%.2f length %ld shorter than ksmps %ld\n", *p->kfn, p->ftp->flen, ksmps); perferror(errmsg); return; } } // Check that kstart is within the // range of the table. if ( ( (kstart = *p->kstart) < 0) || ( kstart >= p->ftp->flen ) ) { sprintf(errmsg, "kstart %.2f is outside table %.2f range 0 to %ld\n", *p->kstart, *p->kfn, p->ftp->flen - 1); perferror(errmsg); return; } // Set up the offset integer rounding // float input argument to the next // more negative integer. // Also read the mask from the FUNC // data structure. kioff = floor(*p->koff); mask = p->ftp->lenmask; // We are almost ready to go, but // first check to see whether // ksmps loops from the starting // point of kstart, will take us // beyond the length of the table. // // Therefore calculate the number of // loop cycles to perform. // Eg. if kstart = 14, ksmps = 8 and // table length = 16, then we only // want to read 2 locations. // // koff is not considered here. It // changes the read location - wrapped // around the length of the table. // It does not change the number of // cycles of read/write. if ( (loopcount = p->ftp->flen - kstart) > ksmps) { // If we are not going to exceed the // length of the table, then loopcount // = ksmps. loopcount = ksmps; } // Otherwise it is the number of // locations between kstart and // the end of the table - as calculated // above. // Main loop: // // Write sequentially into the a rate // variable. // // Read from masked totalindex // in the table, where the total index // is (kstart++ + kioff). // Initialise write location to start // of a rate destination array. writeloc = p->adest; // Transfer the data, whilst updating // pointers and masking to get // final read address. do { readloc = p->ftp->ftable + ( (kstart++ + kioff) & mask); *writeloc++ =//readloc; } while (--loopcount); } //----------------------------------------------------------------------------- // tablewa functions // tablewaset() // // Called at i time prior to // the k rate function tablemix(). // Same function to tablerset(). void tablewaset(TABLEWA *p) { p->pfn = 0; } //------------------------------------- // tablewa() // // Read ksmps values from an a rate // variable and write them into a // table, starting at location kstart. // // Similar to tablera() above, except // that writing is from a rate to table // and we rewrite kstart. void tablewa(TABLEWA *p) { // Local variables // // Pointers for writing to table // destination, and for reading from // a rate array. register float//writeloc,//readloc; // Local variable to hold kstart // input variable - we need to look at // it a few times. long kstart; // Local variable to hold integer // version of offset, and the lenght // mask for ANDing the total index // - wrapping it into the table length. long kioff, mask; // Loop counter long loopcount; // From here the main loop, (except // where noted "!!") this code is // the same as tablera above. // It is not in a common subroutine // for speed reasons and because there // are several local variables for // tablewa() that need to be written. // Check the state of the table number // variable. // // Error message if it is < 1 // and no further action. if (*p->kfn < 1) { sprintf(errmsg, "Table kfn=%.2f < 1\n", *p->kfn); perferror(errmsg); return; } // Check to see if the table number // has changed. if (p->pfn != (int)*p->kfn) { // Get pointer to the function table // data structure. // // ftfindp() for perf time. if ((p->ftp = ftfindp(p->kfn)) == NULL) { sprintf(errmsg, "kfn table %.2f not found\n", *p->kfn); perferror(errmsg); return; } // Table number is valid. // // Save the integer version of the // table number for future reference. p->pfn = *p->kfn; // Check that the table length is // equal to or greater than ksmps. // Create error message if this is // not so. We must ensure that // the ksmps number of reads can // fit within the length of the table. if (p->ftp->flen < ksmps) { sprintf(errmsg, "Table kfn=%.2f length %ld shorter than ksmps %ld\n", *p->kfn, p->ftp->flen, ksmps); perferror(errmsg); return; } } // Check that kstart is within the // range of the table. if ( ( (kstart = *p->kstart) < 0) || ( kstart >= p->ftp->flen ) ) { sprintf(errmsg, "kstart %.2f is outside table %.2f range 0 to %ld\n", *p->kstart, *p->kfn, p->ftp->flen - 1); perferror(errmsg); return; } // Set up the offset integer rounding // float input argument to the next // more negative integer. // Also read the mask from the FUNC // data structure. kioff = floor(*p->koff); mask = p->ftp->lenmask; // !! end of code identical to tablera. // We are almost ready to go, but // first check to see whether // ksmps loops from the starting // point of kstart, will take us // beyond the length of the table. // // Therefore calculate the number of // loop cycles to perform. // Eg. if kstart = 14, ksmps = 8 and // table length = 16, then we only // want to read 2 locations. // // koff is not considered here. It // changes the read location - wrapped // around the length of the table. // It does not change the number of // cycles of read/write. if ( (p->ftp->flen - kstart) > ksmps) { // If we are not going to exceed the // length of the table, then loopcount // = ksmps. loopcount = ksmps; // Write the kstart input/output // variable to be ksmps higher than // before. *p->kstart += ksmps; } else { loopcount = p->ftp->flen - kstart; // Otherwise loopcount is the number of // locations between kstart and // the end of the table - as calculated // above. // // We have hit the end of the process // of stepping kstart up by ksmps. // Set it to 0 so the next time this // ugen is run, with the same variable // the cycle will start from the start // of the table. *p->kstart = 0; } // Main loop: // // Read sequentially from the a rate // variable. // // Write to masked total index // in the table, where the total index // is (kstart++ + kioff). // Initialise read location to start // of a rate source array. readloc = p->asig; // Transfer the data, whilst updating // pointers and masking to get // final write address. do { writeloc = p->ftp->ftable + ( (kstart++ + kioff) & mask); *writeloc =//readloc++; } while (--loopcount); } //***************************************************************************** //***************************************************************************** // The zak system - patching i, k and // a rate signals in a global set of // patch points - one set for i and k, // the other for a rate. // See doco at the start of this file. // There are four global variables // which are used by these ugens. // Two are pointers to floats: // // zkstart // zastart // // Starting addresses of zk and // za spaces respectively. // Initialise these to 0. // // The other two are 32 bit integers. // // zklast // // Number of the last location in // zk space - if 20 was specified // to zakinit, then this would be // 20. (zk space has 21 floats.) // // zalast // // Similarly for za space, except // that this refers to the last // array (each of ksps length) // and hence the highest numbered // of the a rate patch points. // // Initialise these to 0. // // There are currently no limits on // the size of these spaces. float *zkstart = 0, *zastart = 0; long zklast = 0, zalast = 0; //------------------------------------- // zakinit is an opcode which must be // called once to reserve the memory // for zk and za spaces. void zakinit(ZAKINIT *p) { // Local variables . . . // 32 bit integers. // // length How much RAM to // allocate. long length; //------------------------------------- // Check to see this is the first // time zakinit() has been called. // Global variables will be zero if it // has not been called. if ( (zkstart != 0) || (zastart != 0) ) { initerror("zakinit should only be called once."); return; } if ( (*p->isizea <= 0) || (*p->isizek <= 0) ) { initerror("zakinit: both isizea and isizek should be > 0."); return; } // Allocate memory for zk space. // This is all set to 0 and there // will be an error report if the // memory cannot be allocated. length = (*p->isizek + 1) * sizeof(float); // This (float//) cast may not be // necessary? zkstart = (float*) mcalloc(length); zklast = (long) *p->isizek; // Likewise, allocate memory for za // space, but do it in arrays of // length ksmps. // // This is all set to 0 and there // will be an error report if the // memory cannot be allocated. length = (*p->isizea + 1)// sizeof(float)// ksmps; zastart = (float*) mcalloc(length); zalast = (long) *p->isizea; // Mission accomplished. } //----------------------------------------------------------------------------- // I and K rate zak code. // zkset() is called at the init time // of the instance of the zir, zkr // zir and ziw ugens. It complains if // zk space has not been allocated yet. int zkset(void) { if (zkstart == 0) { initerror("No zk space: zakinit has not been called yet."); return(0); } else return(1); } //------------------------------------- // k rate READ code. // zkr reads from zk space at k rate. void zkr(ZKR *p) { // Local variables. // readloc Pointer to float at // location we want to // read from. register float *readloc; // Index value. register long indx; // Check to see this index is // within the limits of zk space. indx = (long) *p->ndx; if ( indx > zklast ) { perferror("zkr index > isizek. Returning 0."); *p->rslt = 0; } else { if ( indx < 0 ) { perferror("zkr index < 0. Returning 0."); *p->rslt = 0; } else { // Now read from the zk space and // write to the destination. // Note that for each step of 1 in // the indx, we want to increase // the memory location pointed to by // readloc by 4 (since a float takes // 4 bytes). However, since the // compiler knows that readloc // is a pointer to a float, it will // assume that any integer we add // to it (such as indx) is meant to // work in steps of 4. // // Since zkstart is already a pointer // to a float, its value is used // directly. // If readloc and zkstart were longs, // then we would need to multiply // indx by sizeof(float). readloc = zkstart + indx; *p->rslt =//readloc; } } } //------------------------------------- // zir reads from zk space, but only // at init time. // // Call zkset() to check that zk space // has been allocated, then do // similar code to zkr() above, except // with initerror() instead of // perferror(). void zir(ZKR *p) { // See zkr() for more comments. // Local variables. register float *readloc; register long indx; if ( zkset() == 0 ) return; // Check to see this index is // within the limits of zk space. indx = (long) *p->ndx; if ( indx > zklast ) { initerror("zir index > isizek. Returning 0."); *p->rslt = 0; } else { if ( indx < 0 ) { initerror("zir index < 0. Returning 0."); *p->rslt = 0; } else { // Now read from the zk space. readloc = zkstart + indx; *p->rslt =//readloc; } } } //------------------------------------- // Now the i and k rate WRITE code. // zkw writes to zk space at k rate. void zkw(ZKW *p) { // Local variables. // writeloc Location to write to. register float *writeloc; // Index value. register long indx; // Check to see this index is // within the limits of zk space. indx = (long) *p->ndx; if ( indx > zklast ) { perferror("zkw index > isizek. Not writing."); } else { if ( indx < 0 ) { perferror("zkw index < 0. Not writing."); } else { // Now write to the appropriate // location in zk space. // See notes in zkr() on pointer // arithmetic. writeloc = zkstart + indx; *writeloc = *p->sig; } } } //------------------------------------- // ziw writes to zk space, but only // at init time. // // Call zkset() to check that zk space // has been allocated, then use // same code as zkw() except that // errors go to initerror(). void ziw(ZKW *p) { // See zkw() for more comments. // Local variables. register float *writeloc; register long indx; if ( zkset() == 0 ) return; indx = (long) *p->ndx; if ( indx > zklast ) { perferror("zkw index > isizek. Not writing."); } else { if ( indx < 0 ) { perferror("zkw index < 0. Not writing."); } else { // Now write to the appropriate // location in zk space. /// writeloc = zkstart + indx; *writeloc = *p->sig; } } } //------------------------------------- // i and k rate zk WRITE code, with a // mix option. // zkwm writes to zk space at k rate. void zkwm(ZKWM *p) { // Local variables. // writeloc Location to write to. register float *writeloc; // Index value. register long indx; // Check to see this index is // within the limits of zk space. indx = (long) *p->ndx; if ( indx > zklast ) { perferror("zkwm index > isizek. Not writing."); } else { if ( indx < 0 ) { perferror("zkwm index < 0. Not writing."); } else { // Now write to the appropriate // location in zk space. // See notes in zkr() on pointer // arithmetic. writeloc = zkstart + indx; // If mix parameter is 0, then // overwrite the data in the // zk space variable, otherwise // read the old value, and write // the sum of it and the input sig. if (*p->mix == 0) *writeloc = *p->sig; else *writeloc = *p->sig +//writeloc; } } } //------------------------------------- // ziwm writes to zk space, but only // at init time - with a mix option. // // Call zkset() to check that zk space // has been allocated, then run // similar code to zkwm() to do the // work - but with errors to // initerror(). void ziwm(ZKWM *p) { // See zkwm() for more comments. // Local variables. register float *writeloc; register long indx; if ( zkset() == 0 ) return; indx = (long) *p->ndx; if ( indx > zklast ) { initerror("ziwm index > isizek. Not writing."); } else { if ( indx < 0 ) { initerror("ziwm index < 0. Not writing."); } else { writeloc = zkstart + indx; if (*p->mix == 0) *writeloc = *p->sig; else *writeloc = *p->sig +//writeloc; } } } //------------------------------------- // k rate ZKMOD subroutine. /// void zkmod(ZKMOD *p) { // Local variables. // readloc Location in zk space // to read from. register float//readloc; // Long integers: // Index value. register long indx; // Flag set to non zero if we should be // doing the modulation with // multiplication rather than addition. register int mflag = 0; // If zkmod = 0, then just copy // input to output. We want to make // this as fast as possible, because // in many instances, this will be // the case. // // Note that in converting the zkmod // index into a long, we want // the normal conversion rules to // apply to negative numbers - // so -2.3 is converted to -2. if ( (indx = (long)*p->zkmod) == 0) { *p->rslt = *p->sig; return; } // Decide whether index is // positive or negative. // Make it postive. if (indx < 0) { indx = 0 - indx; mflag = 1; } // Check to see this index is // within the limits of zk space. if ( indx > zklast ) { perferror("zkmod kzkmod > isizek. Not writing."); } else { // Now read the value from zk space. // See notes in zkr() on pointer // arithmetic. readloc = zkstart + indx; // If mflag is 0, then add the // modulation factor. Otherwise // multiply it. if (mflag == 0) *p->rslt = *p->sig +//readloc; else *p->rslt = *p->sig////readloc; } } //------------------------------------- // zkcl clears a range of variables // in zk space at k rate. void zkcl(ZKCL *p) { // Local variables. // writeloc Location to write to. register float *writeloc; // Starting and ending indexes into // zk space. // Loop repetition counter. register long first, last, loopcount; // Check to see both kfirst and klast // are within the limits of zk space // and that last is >= first. first = (long) *p->first; last = (long) *p->last; if (( first > zklast ) || (last > zklast)) perferror("zkcl first or last > isizek. Not clearing."); else { if (( first < 0 ) || (last < 0)) { perferror("zkcl first or last < 0. Not clearing."); } else { if ( first > last ) { perferror("zkcl first > last. Not clearing."); } else { // Now clear the appropriate // locations in zk space. // See notes in zkr() on pointer // arithmetic. loopcount = last - first + 1; writeloc = zkstart + first; do//writeloc++ = 0; while (--loopcount); } } } } //----------------------------------------------------------------------------- // AUDIO rate zak code. // zaset() is called at the init time // of the instance of the zar or zaw // ugens. // All it has to do is spit the dummy // if za space has not been allocated // yet. int zaset(ZAR *p) { if (zastart == 0) { initerror("No za space: zakinit has not been called yet."); return(0); } else return(1); } //------------------------------------- // a rate READ code. // zar reads from za space at a rate. void zar(ZAR *p) { // Local variables. // readloc Pointer to array of // floats at location we // want to read from. // // writeloc Pointer to destination // for results. register float *readloc,//writeloc; // Index value. register long indx; // nsmps is the counter for the loop // which processes the data. // declare it as an integer, and // intialize it to the global variable // ksmps. register int nsmps = ksmps; //------------------------------------- // Set up write location. writeloc = p->rslt; // Check to see this index is // within the limits of za space. indx = (long) *p->ndx; if ( indx > zalast ) { perferror("zar index > isizea. Returning 0."); do *writeloc++ = 0; while(--nsmps); } else { if (indx < 0 ) { perferror("zar index < 0. Returning 0."); do *writeloc++ = 0; while(--nsmps); } else { // Now read from the array in za space // and write to the destination. // See notes in zkr() on pointer // arithmetic. readloc = zastart + (indx// ksmps); do *writeloc++ =//readloc++; while(--nsmps); } } } //------------------------------------- // zarg() reads from za space at audio // rate, with gain controlled by a // k rate variable. Code is almost // identical to zar() above. void zarg(ZARG *p) { // Local variables. // readloc Pointer to array of // floats at location we // want to read from. // // writeloc Pointer to destination // for results. register float *readloc,//writeloc; // kgain Gain control. register float kgain; // Index value. register long indx; // nsmps is the counter for the loop // which processes the data. register int nsmps = ksmps; //------------------------------------- // Set up write location and gain. writeloc = p->rslt; kgain = *p->kgain; // Check to see this index is // within the limits of za space. indx = (long) *p->ndx; if ( indx > zalast ) { perferror("zarg index > isizea. Returning 0."); do *writeloc++ = 0; while(--nsmps); } else { if (indx < 0 ) { perferror("zarg index < 0. Returning 0."); do *writeloc++ = 0; while(--nsmps); } else { // Now read from the array in za space // multiply by kgain and write to the // destination. readloc = zastart + (indx// ksmps); do *writeloc++ =//readloc++// kgain; while(--nsmps); } } } //------------------------------------- // a rate WRITE code. // zaw writes to za space at a rate. void zaw(ZAW *p) { // Local variables. // readloc Pointer to input signal // values we want to // write to the array. // // writeloc Pointer into the // array of floats in za // space which we want to // write to. register float *readloc,//writeloc; // Index value. register long indx; // nsmps is the counter for the loop // which processes the data. // declare it as an integer, and // intialize it to the global variable // ksmps. register int nsmps = ksmps; //------------------------------------- // Set up the pointer for the source // of data to write. readloc = p->sig; // Check to see this index is // within the limits of za space. indx = (long) *p->ndx; if ( indx > zalast ) { perferror("zaw index > isizea. Not writing."); } else { if (indx < 0 ) { perferror("zaw index < 0. Not writing."); } else { // Now write to the array in za space // pointed to by indx. // See notes in zkr() on pointer // arithmetic. writeloc = zastart + (indx// ksmps); do *writeloc++ =//readloc++; while(--nsmps); } } } //------------------------------------- // a rate WRITE code with mix facility. // zawm writes to za space at a rate. void zawm(ZAWM *p) { // Local variables. // readloc Pointer to input signal // values we want to // write to the array. // // writeloc Pointer into the // array of floats in za // space which we want to // write to. register float *readloc,//writeloc; // Index value. register long indx; // nsmps is the counter for the loop // which processes the data. // declare it as an integer, and // intialize it to the global variable // ksmps. register int nsmps = ksmps; //------------------------------------- // Set up the pointer for the source // of data to write. readloc = p->sig; // Check to see this index is // within the limits of za space. indx = (long) *p->ndx; if ( indx > zalast ) { perferror("zaw index > isizea. Not writing."); } else { if (indx < 0 ) { perferror("zaw index < 0. Not writing."); } else { // Now write to the array in za space // pointed to by indx. // See notes in zkr() on pointer // arithmetic. writeloc = zastart + (indx// ksmps); if (*p->mix == 0) { // Normal write mode. do *writeloc++ =//readloc++; while(--nsmps); } else { // Mix mode - add to the existing // value. do { *writeloc++ =//readloc++ +//writeloc; } while(--nsmps); } } } } //------------------------------------- // audio rate ZAMOD subroutine. // // See zkmod() for fuller explanation // of code. /// void zamod(ZAMOD *p) { // Local variables. // writeloc Destination to write // to (from rslt). // // readloc Location in za space // to read from. // // readsig Array of input floats. register float//writeloc,//readloc,//readsig; // Long integers: // Index value. register long indx; // Flag set to non zero if we should be // doing the modulation with // multiplication rather than addition. register int mflag = 0; // nsmps is the counter for the loop // which processes the data. // declare it as an integer, and // intialize it to the global variable // ksmps. register int nsmps = ksmps; // Make a local copy of the pointer to // the input signal, so we can auto - // increment it. // Likewise the location to write the // result to. readsig = p->sig; writeloc = p->rslt; // If zkmod = 0, then just copy // input to output. if ( (indx = (long) *p->zamod) == 0) { do//writeloc++ =//readsig++; while (--nsmps); return; } // Decide whether index is // positive or negative. // Make it postive. if (indx < 0) { indx = 0 - indx; mflag = 1; } // Check to see this index is // within the limits of za space. if ( indx > zalast ) { perferror("zamod kzamod > isizea. Not writing."); } else { // Now read the values from za space. readloc = zastart + (indx// ksmps); // If mflag is 0, then add the // modulation factor. Otherwise // multiply it. if (mflag == 0) { do //writeloc++ =//readsig++ +//readloc++; while (--nsmps); } else { do //writeloc++ =//readsig++////readloc++; while (--nsmps); } } } //------------------------------------- // zacl clears a range of variables // in za space at k rate. void zacl(ZACL *p) { // Local variables. // writeloc Location to write to. register float *writeloc; // Starting and ending indexes into // za space. // Loop repetition counter. register long first, last, loopcount; // Check to see both kfirst and klast // are within the limits of za space // and that last is >= first. first = (long) *p->first; last = (long) *p->last; if (( first > zalast ) || (last > zalast)) perferror("zacl first or last > isizea. Not clearing."); else { if (( first < 0 ) || (last < 0)) { perferror("zacl first or last < 0. Not clearing."); } else { if ( first > last ) { perferror("zacl first > last. Not clearing."); } else { // Now clear the appropriate // locations in za space. // See notes in zkr() on pointer // arithmetic. loopcount = (last - first + 1)// ksmps; writeloc = zastart + ( first// ksmps ); do *writeloc++ = 0; while (--loopcount); } } } } //***************************************************************************** //***************************************************************************** // Subroutines for reading // absolute time. /// // timek() // // Called at i rate or k rate, by // timek, itimek, times or itemes. // // This is based on global variable // kcounter in insert.c. // Since is apparently is not declared // in a header file, we must declare it // an external. extern long kcounter; void timek(RDTIME *p) { // Read the global variable kcounter // and turn it into a float. *p->rslt = (float) kcounter; } // times() void times(RDTIME *p) { // Read the global variable kcounter // divide it by the k rate. *p->rslt = (float) kcounter / (float) ekr ; } //------------------------------------- // Subroutines to read time for this // instance of the instrument. // instimset() runs at init time and // keeps a record of the time then // in the RDTIME data structure. // // Returns 0. void instimset(RDTIME *p) { p->instartk = kcounter; *p->rslt = 0; } // instimek() // // Read difference between the global // variable kcounter and the starting // time of this instance. // Return it as a float. void instimek(RDTIME *p) { *p->rslt = (float) (kcounter - p->instartk); } // insttimes() // // Read difference between the global // variable kcounter and the starting // time of this instance. // Return it as a float in seconds. void instimes(RDTIME *p) { *p->rslt = (float) (kcounter - p->instartk) / (float) ekr ; } //***************************************************************************** //***************************************************************************** // Printing at k rate - printk. // printkset is called when the // instance of the instrument is // initiallised. void printkset(PRINTK *p) { // Set up ctime so that if it was // 0 or negative, it is set to // a low value to ensure that the // print cycle happens every k // cycle. This low value is // 1 / ekr if (*p->ptime < 1 / ekr) p->ctime = 1 / ekr; else p->ctime = *p->ptime; // Set up the number of spaces. // Limit to 120 for people with // big screens or printers. if (*p->space < 0) p->pspace = 0; else if (*p->space > 120) p->pspace = 120; else p->pspace = *p->space; // Set the initime variable - how // many seconds in absolute time // when this instance of the instrument // was initialised. p->initime = (float) kcounter / (float) ekr; // Set cysofar to - 1 so that on the // first call to printk - when // cycle = 0, then there will be a // print cycle. p->cysofar = -1; } //************************************* // printk // // Called on every k cycle. // It must decide when to do a // print operation. void printk(PRINTK *p) { // Float local variables: // // timel Time in seconds // elapsed since the // instrument was // initialised. float timel; // Integer local variables: // // cycles What print cycle // we are in at this // time. // // spcount How many spaces to // print. long cycles, spcount; //------------------------------------- // Initialise variables. timel = ( (float) kcounter / (float) ekr ) - p->initime; // Divide the current elapsed time by // the cycle time and round down to // an integer. Thus ctime = 5, // timel = 14, cycles = 2. cycles = timel / p->ctime; // Now test if the cycle number we are // in is higher than the one in which // we last printed. // If so, update cysofar and print. if (p->cysofar < cycles) { p->cysofar = cycles; // Do the print cycle. // // Print instrument number and // time. // Instrument number stuff from // printv() in disprep.c. printf(" i%4d ", p->h.insdshead->insno); printf("time %11.5f:", (float) kcounter / (float) ekr); // Print spaces and then the value // we want to read. spcount = p->pspace + 1; do printf(" "); while (--spcount); printf("%11.5f\n", *p->val); } } //----------------------------------------------------------------------------- // printks() and printksset() // Printing at k rate with a string // and up to four variables - printks. // printksset is called when the // instance of the instrument is // initiallised. void printksset(PRINTKS *p) { // Pointer to string from STRARG. char *sarg; // Pointer to string which will be // fed to printf() at print time. char *sdest; // Temp characters for decoding the // string. char temp, tempn; // Flag to tell character matching // code whether to keep looking. int look; // Timing code similar to printkset(). // Set up ctime so that if it was // 0 or negative, it is set to // a low value to ensure that the // print cycle happens every k // cycle. This low value is // 1 / ekr if (*p->ptime < 1 / ekr) p->ctime = 1 / ekr; else p->ctime = *p->ptime; // Set the initime variable - how // many seconds in absolute time // when this instance of the instrument // was initialised. p->initime = (float) kcounter / (float) ekr; // Set cysofar to - 1 so that on the // first call to printk - when // cycle = 0, then there will be a // print cycle. p->cysofar = -1; // Set up the string to print. // printf() will take care of the // %f format specifiers, but // we need to decode any other special // codes we may want to put in here. // \n etc. in a string literal in a // standard printf() string are // converted by the compiler - not // printf(). // We get the string via the same // mechanism used in ugens3.c for // adsyn(). I have not checked // everything which stands behind this. // Check to see if ifilcod == 0xFFFFFF. // If so, then a string was entered // as this string and can be found // using "p->STRARG" which translates // to" p->h.optext->t.strarg // // This goes through the following // data structures OPDS, OPTXT and // TEXT. // // If it was not, then complain. // Also complain if the string has // nothing in it. However the // program (under DJGPP at least) // seems to crash elsewhere if // the first parameter is "". if ( (*p->ifilcod != sstrcod) || (*p->STRARG == 0) ) { sprintf(errmsg, "printks parm 1 was not a \"quoted string\"\n"); initerror(errmsg); return; } else { sarg = p->STRARG; sdest = p->txtstring; // Copy the string to the storage // place in PRINTKS. // // We will look out for certain // special codes and write special // bytes directly to the string. // // There is probably a more elegant // way of doing this, then using // the look flag. I could use // goto - but I would rather not. do { look = 1; temp =//sarg++; tempn =//sarg--; // Look for a single caret and insert // an escape char. if ( (temp == '^') && (tempn != '^') ) { *sdest++ = 0x1B; look = 0; } // Look for a double caret and insert // a single caret - stepping forward // one. if ( (look) && (temp == '^') && (tempn == '^') ) { *sdest++ = '^'; sarg++; look = 0; } // Look for a single tilde and insert // an escape followed by a '['. // ESC[ is the leadin for all the // ANSI.SYS screen control codes for // MSDOS. if ( (look) && (temp == '~') && (tempn != '~') ) { *sdest++ = 0x1B; *sdest++ = '['; look = 0; } // Look for a double tilde and insert // a tilde caret - stepping forward // one. if ( (look) && (temp == '~') && (tempn == '~') ) { *sdest++ = '~'; sarg++; look = 0; } // Look for \n, \N and convert to a // newline character. // Step forwards. if ( (look) && (temp == '\\') && ( (tempn == 'n') || (tempn == 'N') ) ) { *sdest++ = 0x0A; sarg++; look = 0; } // Similarly look for \t and \T for // tab. if ( (look) && (temp == '\\') && ( (tempn == 't') || (tempn == 'T') ) ) { *sdest++ = 0x09; sarg++; look = 0; } if (look) { // If none of these match, then // copy the character directly // and try again. *sdest++ = temp; } // Increment pointer and process // next character until we get to // end of string. } while (*++sarg != 0); } } //************************************* // printks is called on every k cycle // It must decide when to do a // print operation. void printks(PRINTKS *p) { // Float local variables: // // timel Time in seconds // elapsed since the // instrument was // initialised. float timel; // Integer local variables: // // cycles What print cycle // we are in at this // time. long cycles; /txtptr Pointer to where in // text string to start // printing. char *txtptr; //------------------------------------- // Initialise variables. timel = ( (float) kcounter / (float) ekr ) - p->initime; // Divide the current elapsed time by // the cycle time and round down to // an integer. Thus ctime = 5, // timel = 14, cycles = 2. cycles = timel / p->ctime; // Now test if the cycle number we are // in is higher than the one in which // we last printed. // If so, update cysofar and print. if (p->cysofar < cycles) { p->cysofar = cycles; // Do the print cycle. txtptr = p->txtstring; // Special code to allow us to print // the direct byte value of the // first parameter - only if the // first character of the string is // a #. Then go on and // do a printf() with the three other // input variables as floats. // // This enables us to output any // value to the output if we really // want to. if (*txtptr == '#') { // print the 0 to 255 value of the // float - this gives wrap-around // for out of range values. putchar( 255 & (int)floor(*p->kval1) ); // printf the rest of the string, // if any. printf( ++txtptr, *p->kval2, *p->kval3, *p->kval4), 0, 0, 0; } else { // Otherwise do the normal print // cycle of printf()ing the string // with four float input variables. // // Put a few dummy variables on the // end for Justin - just in case we // put too many % format specifiers // in the string. printf(txtptr, *p->kval1, *p->kval2, *p->kval3, *p->kval4, 0, 0); } } } //***************************************************************************** // peakk and peak ugens /// // peakk() // // Write the absolute value of the // input argument to the output if // the former is higher. void peakk(PEAK *p) { if (*p->kpeakout < abs(*p->xsigin) ) { *p->kpeakout = abs(*p->xsigin); } } // peaka() // // Similar to peakk, but looks at an // a rate input variable. void peaka(PEAK *p) { int loop; int change = 0; float peak; float *asigin; // Work with a local peak variable // inside the loop, and update the // output variable only if it is // exceeded. loop = ksmps; asigin = p->xsigin; peak = *p->kpeakout; do { if (peak < abs(*asigin++) ) { peak = abs(*--asigin); asigin++; change = 1; } } while (--loop); if (change) { *p->kpeakout = peak; } } //***************************************************************************** //*************************************************************************** // // What to add to other files to get this going?? // ---------------------------------------------- /// // Add two sets of entries to those at the end of opcodlst in entry.c. // // These are sets of lines in curly braces and are at the start of this // file and are marked//>>> // // In addition, add the following function prototypes to into entry.c: // // #include "ugrw1.h" // // void tblsetw(void*), ktablew(void*), tablew(void*), itablew(void*); // void tblsetwkt(void*), ktablewkt(void*), tablewkt(void*); // // void tableng(void*), itableng(void*); // void tablegpw(void*), itablegpw(void*); // // void tablemixset(void*), tablemix(void*), itablemix(void*); // void tablecopyset(void*), tablecopy(void*), itablecopy(void*); // // void tableraset(void*), tablera(void*); // void tablewaset(void*), tablewa(void*); // // void zakinit(); // void zkset(void*), zir(void*), zkr(void*), ziw(void*), zkw(void*); // void ziwm(void*), zkwm(void*), zkmod(void*), zkcl(void*); // // void zaset(void*), zar(void*), zarg(void*), zaw(void*); // void zawm(void*), zamod(void*), zacl(void*); // // void timek(void*), times(void*); // void instimset(void*), instimek(void*), instimes(void*); // // void printkset(void*), printk(void*); // void printksset(void*), printks(void*); // // void peakk(void*), peaka(void*); /// // Add some stuff to the makefile to make this module UGRW1.C and .H part of // the compilation process. // // Add ugrw1.o to variable OBJS. // // Add ugrw1.h as a dependency for entry.o. // // Create a new dependency, ugrw1.o: ugrw1.h // // . . . . well that is what Appendix 8 of the CSound manual told me to do. // // Now here is what I actually did: // // Add ugrw1.o to the ends of the lists called COBJS and LCOBJS. // (I have not looked into the details of makefiles, but I guess these // are for the compiler and linker.) // // In the list of things starting with "entry.o:", add ugrw1.h. The order // looks like it is significant, so I put it after soundio.h (the last one // that looked like an ordinary piece of code.) // // Create a new dependency to fire up the compiler to make a new ugrw1.o // object file. This is copied from similar lines for other modules. // // I put it after the cscormai.o: dependency. // // ugrw1.o: ugrw1.h sysdep.h prototypes.h cs.h ugrw1.c // $(CC) $(CFLAGS) -c $*.c /// // // (Some notes specific to DJGPP compiler for MSDOS) // // The second line above creates the command line for compiling this module. // "$(CC) is converted to CPP which is the DJGPP compiler. "$(CFLAGS) is // converted to other things. "-c" goes straight onto the command line. // "$*.c" is trasformed into ugrw1.c. // // To get a file of the problems that the compiler reports: // // 1: Add 2r1 to the GO32 environment variable. // // 2: Add "> bugs" to the end of the second line so it becomes: // // $(CC) $(CFLAGS) -c $*.c > bugs // ///