TABREAD.DOC =========== Documentation on the new version of ugens2.c/h - table read. 1 - New feature in the table read code - k rate control of the table number. 2 - A clear definition of how table read should behave under all circumstances - to go with my (hopefully) bug free version of the code. 3 - A report on the bugs in table reading ugens which I fixed in July 95. Robin Whittle firstpr@ozemail.com.au rwhittle@ozonline.com.au http://www.ozemail.com.au/~firstpr For Csound users and C programmers. 8 September 1995 1 - Table number controllable at k rate ======================================= The ugens table and tablei, when producing a k or a rate result, can only use an init time variable to select the table number. The valid combinations of parameter types are shown below: ir table indx, ifn [, ixmode] [,ixoff] [,iwrap] ir tablei indx, ifn [, ixmode] [,ixoff] [,iwrap] kr table kndx, ifn [, ixmode] [,ixoff] [,iwrap] ar tablei andx, ifn [, ixmode] [,ixoff] [,iwrap] In the new version of ugens2.c/h, there is code for two new ugens which accept k rate control as well as i rate. In all other respects they are similar to the original ugens. kr tablekt kndx, kfn [, ixmode] [,ixoff] [,iwrap] ar tableikt andx, kfn [, ixmode] [,ixoff] [,iwrap] kr tablekt kndx, kfn [, ixmode] [,ixoff] [,iwrap] ar tableikt andx, kfn [, ixmode] [,ixoff] [,iwrap] In all these, the type of the input variables are indicated by the first letter of their name. 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 iwrap Default 0 ==0 Limit mode - when total index is below 0, then final index is 0. Total index above table length results in a final index of the table length - high out of range total indexes stick at the upper limit of the table. See discussion below for exact detailed differences between interpolated and non interpolated modes. !=0 Wrap mode - total index is wrapped modulo the table length so that all total indexes map into the table. For instance in a table of length 8, xndx = 5 and ixoff = 6 gives a total index of 11, which wraps to a final index of 3. See discussion below for finer points in interpolated mode. 2 - Detailed desciption of what the table read ugens should do ============================================================== This description will use an example table with 5 entries: Location 0 1 2 3 4 Value 10 11 12 13 10 The main body of the table is locations 0 to 3. Location 4 is the guard point. Not all tables have one, but if you are doing interpolated reading with total indexes above 3, then you will be reading both locations 3 and 4, and taking a fraction of each for your output. "Total index" means the index generated from the ndx and xoff parameters in the table or tablei command line, after being scaled by the "Normalisation factor". The "Normalisatin factor" is internal to the ugen. Usually it is 1 - so for this table, the range of the ndx and xoff would be 0 to 3 or 4. If parameter ixmode is set to 1, then the normalisation factor is set to the table length so that an input ranges of 0 to 1 can be used for both ndx and xoff. "Table length" is a tricky thing. In this discussion, I will use it as meaning the power of two number which is the table size, not including the guardpoint. There is something of a mystery about how this guardpoint survives, because the code which allocates memory seems to do it on the basis of the table length, whereas tables can be specified to have a guardpoint - one more than this length. The FUNC data structure which describes the table gives no indication of whether the table has a guard point. The table length there is always a power of 2. So in our example, the length is 4, but there are 5 entries including the guard point. Sorry about this terminology. The guardpoint seems intended to allow interpolated reading to produce a smooth waveform. In this case, its value is set to the same as that of location 0. However it could be set to any other value if the aim was not to produce a smooth waveform with an interpolated oscillator or cyclic table read. If location 4 contained 14, then an interpolated read would produce a sawtooth with a sharp edge at the end of the waveform - falling to 10 at index 0. So the basic function of ndx, ixmode and ixoff are now clear, as is the function of interpolated read. What does ixwrap do? Without ixwrap, or if it is set to 0, the ugen is in what I call "Limit mode". Its behavior differs depending on whether it is interpolated or not - but the concept is the same. Terminology: "Total index" is the sum of xndx and ixoff, after being scaled according to ixmode. "Final index" is the total index, after it has been limited to something within the length of the table, either as a result of "limit" or "wrap" mode. The final index determines where the table is read from and so determines the final result. In Limit mode, final indexes below 0 are limited to 0 and so the value of location 0 is read for all 0 and negative total indexes. This is the same for table and tablei. In non interpolated table read, total indexes higher than 3 are limited to a final index of 3, and the value at location 3 is read out. In interpolated table read, if the total index is above 4.0, it is limited to a final index of 4.0 and the value of the guardpoint is read out. This means that a set of 5 interpolated segments bridges the 0 to 4 total index range. Wrap mode - when ixwrap = 1 - is when the total index is truncated modulo the table length to give continuous scanning of the table no matter what the total index is. So in interpolate mode, a steadily increasing total index would produce a final index rising as a sawtooth from 0 to 3.9999 repeatedly. With interpolation this would produce a saw tooth wave rising from 10 to 13 in 3 segments, and then down to 10 in the fourth. Without interpolation, the output would have been a sequence of four square steps - 10, 11, 12 and 13 repeated. That is the total description of how I believe the table read ugens should behave. Some of this not spelt out in so much detail in the manual, but I believe that my description is compatible with the _intent_ of the manual. Please let me know if you disagree with any of this. 3 - Bugs ======== Short description of bugs ========================= There were bugs in the table and tablei ugens when the usual index was exceeded, or when with tablei, the offset did not point exactly to a location in the table. These have now been fixed in a new version of ugens2.h and ugens2.c. The original versions of these files are available on my WWW site. These versions are fully commented, and as far as I am able to test them, bug free. See below for a full bug report. MSDOS CSOUND.EXE with bugs fixed -------------------------------- Also available there is an MSDOS CSOUND.EXE compiled with DJGPP with these fixes. All this work is based on the 3.28.12 version of the source from John Fitch at Bath. Various other things, including table write, a patching system, some time reading ugens and k rate numeric printing ugens are also in this version. These features are described in UGENRW1.DOC. UGRW1.C and UGRW1.H are there too, and contain instructions for installing them in Csound source for any system - all these features should be able to run in Csound compiled for any computer. Also there is a new version of gens.c is required to support the finding of tables at k rate, and a new entry.c to accomodate the new ugens. Other things at my WWW site --------------------------- TABLTEST.ORC and TABLTEST.SCO are a torture test put the table read ugens through their paces. This is how I tested my compiled version - I cannot think of much more to do. This may be useful for testing your compiled version. Let me know if there are any suspect results. This is not particularly well commented - you should look at the output on a dual trace CRO and figure out what all the 64 tests should look like - and spot the difference. The more formal definition of the behaviour of table read below will help. Detailed bug report ------------------- The original code _may_ have varied its behaviour amongst compilers, due to the way negative floats are rounded to integers. This is machine dependant - so the actual behavior may have been different on different machines. What follows is a description of how the code behaved under DJGPP (DJ Delorie's free and wonderful GNU C++ compiler - see my separate doco on bringing it up) on a MSDOS 486 system. This is contrasted with the above description of how I think it should behave. Bug 1 Interpolated with out of range total indexes in limit mode ---------------------------------------------------------------- In the example given, if the total index increased from -4 to 0, the output should have remained at 0. Instead it traversed four little sawtooth waves - each a facsimile of the proper response from 0 to 1. The same trouble occurred for indexes above 4, when it should have stuck at the value of the guardpoint. Bug 2 High total indexes in limit mode, non interpolated -------------------------------------------------------- Total indexes beween 3.0 and 3.999 (3.999 means just less then 4.0) were working fine. What should have happened was that anything above 3 should have resulted in a final index of 3 - to read that value for all higher total indexes. Instead, when it was 4, or higher than 4, it was limited to 4 and then subjected to a binary AND with 3 - reducing the final index to 0! Bug 3 Interpolate read with offset not at an exact table location ----------------------------------------------------------------- Wierd things happened when xoff was not pointing exactly at a table location. xoff = 0 is fine. xoff = 3 or 2 is fine. xoff = 2.1 is not. 0.1 of each segment (or was it 0.9) would be interpolated from the next pair of values rather than this pair - a very messy squarewave error added to the sawtooth result we should normally get from this table. Bug 4 Negative total indexes in non interpolated, wrap mode ----------------------------------------------------------- Due to a problem rounding negative floats to integers (-2.3 was becoming 2 when we want it to go to the next most negative integer - - 3) the whole negative range of the output was shifted by one to the negative. Using the ANSI function floor() fixed it. Statistics . . . ================ Looking only at the table read code, and ignoring the other ugens in ugens2.c/h, the original code (.h and .c) comprised 3469 bytes. My modified and fully commented code comprises 21069 bytes - six times larger. The total source code of csound.exe is (I think) 922k. Therefore a properly commented version of the source might occupy 5 or 6 megabytes. This seems daunting - the Bible is 4 megabytes. The table read code is 0.376% of the Csound source. It took me about two days to comment it, finding bugs as I went, writing .orc and .sco code to test the bugs, rewriting the code and testing the resulting .exe file. So fixing the rest of Csound like this might take 531 days and remove 1063 bugs. A likely story! Conclusion ========== By looking at my new code, and the old code, and running the torture test on the results, you should be able to verify that there were bugs, and that there are no bugs in the new version. If you think of something else I should consider - or if I have made a mistake, please contact me. - Robin Whittle