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

