The MP4-SA Book: Part II/4: SAOL Wavetables
Description: Using wavetables in SAOL: the table and tablemap variable types, wavetable generators, core opcodes for tables, global parameters for tables, and sfront table support.
Keywords: MP4-SA, wavetables, table, tablemap, oscil, loscil, koscil, doscil, tableread, tablewrite, ftlen, ftloop, ftloopend, ftsr, ftbasecps, ftsetloop, ftsetend, ftsetbase, ftsetsr, samples, sample playback, sampler, SAOL, MP4, buzz, concat, cubicseg, data, empty, expseg, harm, harm_phase, lineseg, periodic, polynomial, random, sample, spline, step, window

### From The MPEG-4 Structured Audio Book by John Lazzaro and John Wawrzynek.

# Part II/4: SAOL Wavetables

## Sections Introduction. An Example. Rewriting a tutorial example with wavetables. Declarations. Declaring tables and tablemaps. Periodic Waveforms. Tables for additive and subtractive synthesis. Envelope Waveforms. Piece-wise wavetable generators. Sound Files. Reading audio files into wavetables. Table Playback. Core opcodes for table readout. Low-Level Core Opcodes. Accessing table structures. Low-Level Generators. Useful wavetable initializations. | ## Core Opcodes: doscil ftbasecps ftlen ftloop ftloopend ftsetsr ftsetbase ftsetloop ftsetend ftsr koscil loscil oscil speedt tableread tablewrite ## Wavetable Generators: buzz concat cubicseg data empty expseg harm harm_phase lineseg periodic polynomial random sample spline step window

## Introduction Sound generation methods often involve the playback of audio samples from large data buffers. These buffers may contain computed waveforms, digital recordings of natural sounds, or may be a scratch pad memory for dynamic synthesis. The SAOL language includes the table data type for implementing wavetable buffers. A wavetable stores a sample array and a header of related information (including table length, sample rate, and loop points) in one structure. A set of core opcodes support wavetable playback. Other core opcodes read and write parts of the wavetable data structure. Wavetable generators may be used to initialize wavetable values at declaration. We begin this chapter with an example that introduces wavetable operation. We then detail the table declaration syntax, and describe wavetable generators and core opcodes for the most common table applications. We conclude with descriptions of the wavetable core opcodes and generators that are useful for programming at a low level of abstraction. | ##

## An Example The second example in the tutorial in Part I uses a shaped sine wave instrument model. In the tutorial example, the sine wave is computed used an iterative algorithm and the envelope is computed using a core opcode. The right panel shows another implementation of this instrument, that uses wavetables to generate the sine wave and the envelope. We use this example to introduce the basic usage of the table data type. The wavetable used to generate the sinusoid, `cyc`, is declared as a global variable, and imported into the instrument `vtone`. A table declaration includes instructions for setting the initial values of the table. In the declaration for `cyc`, the wavetable generator harm initializes the 128-sample wavetable with one cycle of a sine wave. The wavetable `shape` holds the envelope for the instrument, and is declared in `vtone`. The lineseg generator, specialized for creating piece-wise linear envelopes, initializes `shape`. The `shape` declaration shows that the parameters of a generator may be expressions. In this case, we use the standard name dur to customize the envelope to match the length of the note. The code block for `vtone` is very simple. One i-pass statement converts the MIDI note number parameter into a frequency. One k-pass statement uses the core opcode ftsetsr to set the sample rate of the envelope wavetable. The real work is done in the a-pass. Two core opcodes, doscil and oscil, play back the envelope and sine wave tables. The return values of these opcodes are multiplied to create the final waveform, which is sent to the output_bus. The a-rate core opcode doscil plays a table back once. The opcode does sample rate conversion between the sample rate of the table and the global sample rate. The a-rate core opcode oscil plays a table back in a loop, treating the table as a single cycle of a periodic waveform. By default, the table playback core opcodes uses linear interpolation. We used the SASL file from the second tutorial example to drive the `vtone` instrument. If you are connected to the Internet, and if your web browser supports WAV file playback, you can click here to listen to audio output created using `sfront`. We also used sfront to create a binary encoding of the SAOL and SASL file for this performance. This MP4 file is 399 bytes. Note that this file is small, even though the tables we used for the envelopes are large, because only the wavetable generator parameters are stored in the file. The initial values for the tables themselves are computed as part of the file decoding process. This example shows how table data types are used in SAOL. In the rest of this chapter, we describe table declarations, applications-oriented wavetable generators, playback core opcodes, and low-level core opcodes and generators in detail. | ## tsine.saol
    global {

      table cyc(harm, // sine series
    	    128,  // 128 samples long
    	    1     // f1 weight
    	    );    // no partials

      srate 44100;

    }

    //
    // instr vtone
    // table-driven version
    // of tutorial example 2
    //

    instr vtone (num) {

      // declarations

      // sinewave global table

      imports exports table cyc;

      // envelope table
      //
      // piecewise linear
      // shape
      //
      // 0.3 second attack
      // 0.2 second release

      table shape(lineseg,

      // fixed table length: 128 elements

      128,

      // (x0, y0) is (0,0)

      0, 0,

      // end of attack segment: (x1, y1)

      127*(((dur < 0.5) ?
           dur/2 : 0.3)/dur), 1,

      // end of sustain segment: (x2, y2)

      127*(((dur < 0.5) ?
           dur/2 : (dur - 0.2))/dur), 1,

      // end of release segment: (x3, y3)

      127, 0);

      ivar freq; // frequency of sine

      asig y;    // voice output

      // **********************
      // computed during i-pass
      // **********************

      // turns MIDI number into
      // wavetable frequency

      freq = cpsmidi(num);

      // **********************
      // computed during k-pass
      // **********************

      if (itime == 0) // first k-pass only
        {
          ftsetsr(shape, 128/dur);
        }

      // **********************
      // computed during a-pass
      // **********************

      y = doscil(shape)*oscil(cyc, freq);

      output(y);

    }
    output.wav [464 Kbytes, on the Web]
    tsine.mp4 [399 bytes]

## Declarations The right panel shows the table declaration syntax. The table name must adhere to the naming rules described in Part II/1. A set of parameters, enclosed in parenthesis, complete the declaration. The first parameter must be a valid wavetable generator name. The right panel shows the 16 SAOL wavetable generators. The second parameter is an expression that sets the number of samples in the table. The remaining parameters have a different meaning for each wavetable generator. For most generators, these fields are signal expressions. As shown in the example in last section, table declarations may happen in the global block or in an instr block. For global wavetables, the expressions may use i-rate global variables. These variables may be initialized by the startup instr which runs before global wavetables are created. For instr wavetables, the expressions may use instr parameters, imported i-rate global variables, and i-rate standard names. #### Importing Tables An instr accesses a global wavetable by declaring an instr wavetable with the same name as the global wavetable. The example on the right panel shows the two forms of declaration. The imports exports declaration accesses the global wavetable directly using a pointer. Changes made to the wavetable structure by an instr affect the global copy. The imports declaration makes a copy of the global wavetable at instr instantiation. Changes to the instr table do not change the global data. The imports declaration may be used even if no global wavetable with the same name exists. In this case, the table is created by a SASL table command, which we explain in Part III/1 of the book. #### Tablemaps Some wavetable applications create a set of related sample buffers, and play back one buffer from the set. For example, a sampled piano instrument may use a set of 88 wavetables, one for each note on the piano keyboard. To support these applications, SAOL has a special data type, the tablemap, that is an array of wavetables. The right panel shows the tablemap declaration syntax and an example. In this example, we declare the tables `cyc0`, `cyc1`, `cyc2`. We also declare the tablemap `cyc`, that is an array of wavetables. In the code block, we index `cyc` to select a wavetable argument for the core opcode oscil. Unindexed tablemap variables may not be passed as opcode arguments. The index for a tablemap is an expression whose value is rounded to the nearest integer to select a wavetable. The first wavetable in a tablemap has the array index 0. | `## Declaration Syntax `
    table name(gen, size [, p1, p2 ...]);
` ## Wavetable Generators `
    buzz
    concat
    cubicseg
    data
    empty
    expseg
    harm
    harm_phase
    lineseg
    periodic
    polynomial
    random
    sample
    spline
    step
    window
` ## Imports Example `
    globals {
    table cyc0(harm, 128, 1);
    table cyc1(harm, 128, 0.5, 0.25);
    table cyc2(harm, 128, 0.5, 0.1);
    }

    instr test {

    imports exports table cyc0;// form 1
    imports table cyc1, cyc2;  // form 2

    }
` ## Tablemap Declaration `
    tablemap name(t1 [, t2, t3 ...]);
` ## Tablemap Example `
    instr tmap (freq, bright) {

    table cyc0(harm, 128, 1);
    table cyc1(harm, 128, 0.5, 0.25);
    table cyc2(harm, 128, 0.5, 0.2, 0.1);
    tablemap cyc(cyc0, cyc1, cyc2);

    output(oscil(cyc[bright],freq));

    }
``

## Periodic Waveforms Three wavetable generators (harm, phase_harm, and buzz) produce a single cycle of a periodic waveform. Looped playback of wavetables produced with these generators is click-free. The definitions of these generators are shown on the right panel, along with the related generator periodic. #### Harm The simplest generator, harm, specifies a waveform as a harmonic series of N zero-phase sine waves. Generator parameters include the size of the table, which must be greater than zero, and the N sinusoid amplitudes. We use this generator in the example at the start of the chapter to produce a pure tone. | `
    x is table index
    y(x) is table value
## harm
    declaration:

    table t(harm, size, a1 [, a2, a3 ...]);

    algorithm:

    for x in [0, size-1]:

    y(x) =
    a1*sin(2*pi*x/size) +
    a2*sin(4*pi*x/size) +
    a3*sin(6*pi*x/size) +
    ...
`

#### Harm_phase and Periodic The harm_phase generator specifies an amplitude parameter and a phase parameter (specified in radians) to each sinusoid, and the periodic generator also adds a frequency parameter. Note that if the frequency parameters to the periodic generator are non-integral, the looped wavetable produces a discontinuous waveform. | ## harm_phase
    declaration:

    table t(harm_phase, size, a1, ph1
    	[, a2, ph2, a3, ph3 ...]);

    algorithm:

    for x in [0, size-1]:

    y(x) =
    a1*sin(ph1 + 2*pi*x/size) +
    a2*sin(ph2 + 4*pi*x/size) +
    a3*sin(ph3 + 6*pi*x/size) +
    ...
## periodic
    declaration:

    table t(periodic, size, f1, a1, ph1
       [, f2, a2, ph2, f3, a3, ph3 ...]);

    algorithm:

    for x in [0, size-1]:

    y(x) =
    a1*sin(ph1 + 2*pi*f1*x/size) +
    a2*sin(ph2 + 2*pi*f2*x/size) +
    a3*sin(ph3 + 2*pi*f3*x/size) +
    ...

#### Buzz Newcomers to computer music often try to duplicate the pulse waveforms used in analog subtractive synthesis directly, by writing a program to generate a pulse waveform. This approach can lead to unsatisfactory results, due to aliasing problems. A better way is to generate pulse-like waveforms additively into a table, stopping near the Nyquist limit (half the sampling frequency) to prevent aliasing. The harm generator could be used to create a pulse waveform in this way, by specifying a series of sinusoids with decreasing amplitudes. The buzz generator simplifies this task. It specifies an algorithm for generating a family of alias-free pulse-like waveforms using a few parameters. The right panel shows the algorithm for buzz. The generator sums a series of harmonic cosine functions starting at the partial specified by parameter `low`, where partial 0 is the fundamental. The first cosine waveform has unity scaling, while subsequent cosines are scaled by increasing powers of the parameter `r`. Since `r` has no restrictions on its value, amplitude series that are decreasing (`abs(r) < 1)` and increasing (`abs(r) > 1`) may be generated. The phase of successive partials may also be flipped by specifying a negative `r`. The two remaining parameters are the number of partials `num` and the size of the wavetable `size`. If both are specified as positive values, the equation on the right panel is computed. Note that a bad choice of parameters will result in aliasing. Alternatively, either `size` or `num` may be set to -1. In this case, the generator chooses the smallest wavetable size or the largest number of harmonics, respectively, that does not alias. Finally, the buzz generator scales the final waveform to have a peak amplitude independent of the parameters. | ## buzz
    declaration:

    table t(buzz, size, num, low, r);

    algorithm:

    for x in [0, size-1]:

    y(x) =
    cos(2*pi*(low+1)*x/size) +
    r*cos(2*pi*(low+2)*x/size) +
    r*r*cos(2*pi*(low+3)*x/size) +
    ... +
    r^(num)*cos(2*pi*(low+num+1)*x/size)

    scale table by:

    (1 - abs(r))/(1 - abs(r^num))

    nyquist scaling if:

    size = -1: table size scaled to
               largest harmonic low+num

    num = -1: largest num that fits
    	  the specified table size

    size and num may not both be -1

## Envelope Waveforms Five generators (step, lineseg, expseg, spline, and cubicseg) produce wavetables that are useful for signal envelopes. For each description, see the right panel for parameter syntax and algorithms. #### Shared properties For each generator, the envelope is specified as a piece-wise waveform. Table break points are listed as pairs of `y` table values and `x` table indices. The generators interpolate between the break points to create a waveform. The first table break point must have an index parameter of zero, and the sequence of table index values must not be decreasing. Setting two consecutive `x` indexes to the same value is permitted, and produces a discontinuous waveform. In this case, the second `y` value becomes the actual wavetable value for the `x` index, while the first `y` value fixes the trajectory of the previous piece-wise segment. The wavetable size may be specified directly by setting the `size` parameter to a value greater than zero. If the `size` parameter is set to `-1`, the table size is inferred from the last break point specified. | `
`

#### Interpolation methods The step generator does no interpolation. The waveform it produces looks like a stair step. For two adjacent break points `(x1, y1)` and `(x2, y2)`, the table takes on the value `y1` for `x1 <= x < x2`. | `## step
    declaration:

    table t(step, size, x1, y1,
            x2 [, y2,  x3 ...]);

    algorithm:

    for x in [0, size-1]:

    x1 <= x < x2  --> y(x) = y1
    x2 <= x < x3  --> y(x) = y2
    ...

    error if:

    -- x1 != 0
    -- series of xk values decreases
    -- series ends with a yk
`

The lineseg generator does linear interpolation between break points. We use the lineseg generator in the example at the start of the chapter. | `## lineseg
    declaration:

    table t(lineseg, size, x1, y1,
            x2, y2 [, x3, y3 ...]);

    algorithm:

    for x in [0, size-1]:

    if x1 <= x < x2
      y(x) linearly interpolates
      between y1 and y2

    if x2 <= x < x3
      y(x) linearly interpolates
      between y2 and y3

    ...

    error if:

    -- x1 != 0
    -- series of xk values decreases
    -- series ends with a xk
`

The expseg generator does exponential interpolation between break points. Note that the generator algorithm restricts the `y` values to be nonzero and of uniform sign. | `## expseg
    declaration:

    table t(expseg, size, x1, y1,
            x2, y2 [, x3, y3 ...]);

    algorithm:

    for x in [0, size-1]:

    if x1 <= x < x2
      y(x) exponentially interpolates
      between y1 and y2
      i.e.
       y(x) = y1*(y2/y1)^((x-x1)/(x2-x1))

    if x2 <= x < x3
      y(x) exponentially interpolates
      between y2 and y3

    ...

    error if:

    -- x1 != 0
    -- series of xk values decreases
    -- all yk's aren't the same sign
    -- any yk is zero
    -- series ends with a xk
`

The spline generator interpolates between break points using cubic splines. Generating the spline interpolation requires solving a third-order algebraic equation. For some break points, this equation may not have a solution. To avoid this situation, the expression `x1*x1 + x2*x2 - 2*x1*x2` should not be equal to zero. | `## spline
    declaration:

    table t(spline, size,
                x1, y1,
           [k2, x2, y2,
            k3, x3, y3,]
            kn, xn, yn);

    algorithm:

    for x in [0, size-1]:

    if x1 <= x < x2
      y(x) fits a cubic spline
      between y1 and y2
      i.e.
       y(x) = a*x^3 + b*x^2 + cx + d
       where this function passes
       through (x1,y1) and (x2,y2)
       and has derivative 0 at x1
       and k2 and x3

    if x2 <= x < x3
      y(x) fits a cubic spline
      between y2 and y3
      i.e.
       y(x) = a*x^3 + b*x^2 + cx + d
       where this function passes
       through (x2,y2) and (x3,y3)
       and has derivative k2 at x2
       and k3 at x3

    ...

    derivative of final spline
    ending is 0 (kn is ignored).

    error if:

    -- x1 != 0
    -- series of xk values
       is decreasing
    -- series does not end
       with a yk
    -- a spline doesn't exist (see text)
`

The cubicseg generator also interpolates between break points using a cubic formula. The parameter specification for cubicseg is different than the other generators in this family. The `x` values are specified via the series `infl1, x1, infl2, x2 ...`. The first `x` value `infl1` must be zero. While the spline generator creates gently changing waveshapes, the cubicseg generator can be used to generate a spiked waveshapes as well as smooth waveshapes. Like the spline generator, care must be taken to ensure that the cubic equation by the parameters specified is solvable. These conditions are too complex to calculate manually. The cubicseg generator is best used with a computer program for cubic curve design. | `## cubicseg
    declaration:

    table t(cubicseg, size, infl1, y1,
             x1, y2, infl2, y3,
             x2, y4, infl3, y5
             [,x3, y6, infl4, y7]);

    algorithm:

    for x in [0, size-1]:

    if infl1 <= x < infl2
      y(x) fits a cubic polynomial
      between y1 and y3
      i.e.
       y(x) = a*x^3 + b*x^2 + cx + d
       where this function passes
       through (infl1,y1), (x1,y2),
       and (infl2,y3) and has
       derivative 0 at x1.

    if infl2 <= x < infl3
      y(x) fits a cubic polynomial
      between y3 and y5
      i.e.
       y(x) = a*x^3 + b*x^2 + cx + d
       where this function passes
       through (infl2,y3), (x2,y4),
       and (infl3,y5) and has
       derivative 0 at x2.
    ...

    error if:

    -- infl1 != 0
    -- infl, x, infl, ... series decreases
    -- series doesn't ends with infl, y
    -- any polynomial doesn't exist
`

## Sound Files The SAOL generator sample fills a wavetable with sample data. The right panel shows the declaration syntax for this generator. When used in ASCII SAOL programs, the sample generator reads in data from an audio file. Because file access is system dependent, the MP4-SA standard leaves exact implementation details to the decoder. In this section, we describe how `sfront` implements the sample generator. The second parameter of the generator specifies the name of the audio file to read into the table. The filename is specified in double quotes, and is expected to be in the same directory as the SAOL file. Microsoft WAV files (indicated by .wav and .WAV extensions) and Electronic Arts AIFF files (indicated by .aif and .AIF extensions) are supported by `sfront`. The optional `skip` parameter (rounded to an integer value) specifies the number of samples to skip at the start of the audio file. If `skip` is specified, sample number `skip+1` in the audio file becomes the first sample of the wavetable. The first parameter of the generator specifies the wavetable size. If the `size` parameter is set to `-1`, the sample generator sets the wavetable size to be the number of samples in the audio file (minus the number of skip samples). If the `size` parameter is set to a positive value, the wavetable takes on that size, and the audio file is truncated or zero-padded as needed. Sfront supports 8-bit, 16-bit, and 24-bit samples in AIFF and WAV files. The integer audio samples are converted to floating point numbers that lie between `-1.0` and `+1.0`. The sampling rate of the file becomes the sampling rate of the wavetable. In sfront, the left and right channels in a stereo WAV or AIFF file are summed to mono by default. The right panel shows the @n syntax for selecting a specific channel (left or right) of a stereo file to place in the table. AIFF and WAV files may also hold information about looped playback the sample. Sfront converts this information into the loop start, loop end, and base frequency of the wavetable. #### Bitstream issues In the previous subsection, we described the way `sfront` implements the second parameter of the sample generator for ASCII SAOL files. For binary MP4 files, however, the second parameter of this generator has a normative interpretation. This parameter points to another part of the MP4 file, that holds large blocks of sample data. When `sfront` generates an MP4 binary encoding of a ASCII SAOL file, it loads the audio data from files specified in sample generators into the part of the MP4 file that holds sample data blocks. It encodes the second parameter of each sample generator to point to the appropriate sample block. When inserting WAV or AIFF file data into an MP4 file, `sfront` includes any looped playback data stored in the file. Note that the size of an MP4 binary file expands significantly if large audio sample files are included in the file. Content creators working on low-bitrate applications should consider synthesis-based sound generation techniques as an alternative to the sample generator. | `## sample
    declaration:

    table t(sample, size,
    	"name.wav" [,skip]);

    table t(sample, size,
    	"name.aif" [,skip]);

    algorithm:

    monaural AIFF or WAV file is read into
    table. skip is an optional parameter,
    that specifies how many samples to skip
    over at the beginning of the file.

    file sample rate is table sample rate.

    if size is set to -1, table size is
    the number of samples in the file,
    minus skip (if specified). if size is
    specified, audio file is truncated
    or zero-padded as needed.

    table values lie between -1.0 and
    +1.0, by scaling 16-bit signed
    integers from the file by 1/32768.
    24-bit samples scale by 1/8388608.
    8-bit samples scale by 1/128 (after
    re-zeroing, if needed).

    if name.wav or name.aif is a stereo
    file, sfront averages the left and
    right channel to produce a mono
    signal. to select a single channel
    of a stereo file to place in the
    table, sfront supports the following
    syntax:

    table t(sample, size,
    	"name.wav@0" [,skip]);

    table t(sample, size,
    	"name.wav@1" [,skip]);

    table t(sample, size,
    	"name.aif@0" [,skip]);

    table t(sample, size,
    	"name.aif@1" [,skip]);
`

#### Warping Tables The i-rate core opcode speedt is a useful tool for processing wavetables initialized with the sample generator. It shrinks or expands the length of a wavetable, without changing the pitch of the sample. The right panel shows the speedt header syntax. The `in` variable holds the original wavetable. The `factor` parameter specifies the amount of expansion or compression. The `out` wavetable must be large enough to hold the processed table. For example, a `factor` value of 2 requires an `out` table twice as large as the `in` table. Typically, `out` is initialized at the correct size using the empty wavetable generator. The algorithm for table warping is non-normative. Different decoders will produce different sample values for a given `factor` value. In addition, the range of acceptable `factor` values is also decoder-dependent. The speedt implementation in `sfront` expands wavetables up to a `factor` value of `2`, and compresses wavetables down to a `factor` value of `0.001`. To achieve higher expansion ratios, implement a cascade of several speedt calls. | `## Speedt `
    iopcode speedt(table in, table out,
    	       ivar factor)
``

## Table Playback The core opcodes oscil, koscil, doscil, and loscil play back wavetables. Each opcode is customized for a particular sound generation method. See the right panel next to each opcode description for the declaration syntax. #### Shared properties The playback opcodes work in a similar way. Each opcode has an internal pointer to the wavetable, that indicates the current sample. The internal pointer always starts at the first sample in the wave table. When the opcode is called, it returns the current sample, and updates the internal pointer for the next call. The opcodes differ primarily in the method for incrementing the pointer. The semantics of the playback opcodes assume that the opcode is called once per a-pass (for oscil, doscil, and loscil) or once per k-pass (for koscil). If an opcode is placed inside a while loop and called multiple times per pass, the effective frequency increases by that multiple. In general, the internal pointer does not increment in an integral fashion. The playback opcodes interpolate the return value from nearby wavetable values. By default, the playback opcodes do linear interpolation. The interpolation level can be set by the global parameter interp. Setting interp to 0 yields the default linear interpolation. If interp is set to 1, the MP4-SA decoder uses a more sophisticated method of interpolation. The exact method of interpolation, however, is unspecified by the SAOL language standard, and different decoders may use different methods. For most opcodes that use interpolation, sfront uses band-limited interpolation if interp is set to 1 (sfront's grain operator is an exception, and uses linear interpolation). Sfront has command-line options to set the accuracy of the band-limited interpolation. Later in this chapter, we discuss special issues when using band-limited interpolation with low-level core opcodes such as tableread. If the goal is to achieve normative (i.e. decoder-independent) high-quality table playback, the content creator has two options. One option is to use the playback core opcodes with linear interpolation, and use larger table sizes to improve audio quality. A second option is to write SAOL code to play back tables directly, using the low-level core opcodes. | ## Interp Global Parameter `
    global {
    interp 0;  // linear
               // interpolation
    }

    global {
    interp 1;  // non-normative
               // higher-quality
    	   // interpolation
    }
`

#### Waveform Playback Two of the core opcodes, oscil and koscil, are specialized for creating signals by repeating a single cycle of a waveform stored in a table. These opcodes have two required parameters, a table specifier `t` and the `freq` parameter that specifies how many times per second the entire table should be played through. The `freq` parameter may be negative, in which case the internal pointer is decremented instead of incremented. If the `loops` parameter is provided, the opcode loops around the table `loops` times, and then returns 0 with every call. The opcodes differ only in their rate. The a-rate oscil opcode produces signal values as described above if it is called once a-pass, while the k-rate koscil has the described semantics if it is called once per k-pass. | ## Waveform Playback `
    aopcode oscil(table t,
    	      asig freq
    	      [,ivar loops])

    kopcode koscil(table t,
    	      ksig freq
    	      [,ivar loops])
`

#### One-Shot Playback The a-rate doscil core opcode plays back the wavetable `t` once, and then returns 0. The opcode considers the wavetable to be a recording of a pitched sound. In playing the wavetable, it generates a sound whose pitch is identical to the original sound. If the "recording" sample rate is identical to the SAOL program audio sample rate, doscil simple advances the internal pointer by 1 at each call. If the two sample rates differ, doscil increments the internal pointer by the ratio of the wavetable sample rate and the audio sample rate. The wavetable passed to doscil must have its sample rate value set. The ftsetsr core opcode may be used to set the sample rate of wavetable, as we did in the example earlier in the chapter. By default, a wavetable initialized with the sample generator has its sample rate set to the audio file sample rate. | ## One-Shot Playback `
    aopcode doscil(table t)
`

#### Loop Playback The a-rate loscil core opcode expands the semantics of doscil, by playing back the wavetable recording at an arbitrary pitch (specified by the parameter `freq`). To play the table back at the correct pitch, loscil needs to know the original pitch of the sound recorded in the wavetable. The optional parameter `basefreq` supplies this information. If `basefreq` is not supplied, the base frequency of the wavetable is used, as set by the core opcode ftbasecps, or as set by the sample wavetable generator when reading in a WAV file with looped playback information. Given a base frequency value and the sampling rate of the table, the loscil opcode increments its internal pointer to achieve the signal pitch requested by `freq`. If `freq` is negative, the internal pointer is decremented. The loscil opcode returns sample values indefinitely, by looping a portion of the wavetable. The optional parameters `loopstart` and `loopend` supply the loop points, expressed as fractional array indices into the wavetable. The internal pointer for loscil starts at index 0, and advances to the `loopend` position. It then cycles from the `loopstart` position to the `loopend` position indefinitely. If the internal pointer is decremented in this regime, cycling in the reverse direction happens. If the `loopstart` and `loopend` parameters are not provided, loscil uses the loop start and loop end values for the wavetable, as set by the core opcodes ftloop and ftloopend, or as set by the sample wavetable generator when reading in a WAV file with looped playback information. If the wavetable loop values are not set, loscil loops through the entire wavetable indefinitely. | ## Loop Playback `
    aopcode loscil(table t,
    	      asig freq
    	      [,ivar basefreq,
    	        ivar loopstart,
    	        ivar loopend])
`

## Low-Level Core Opcodes Eleven core opcodes access wavetables at the lowest level of abstraction. These opcodes let programmers write replacements for the playback opcodes, and create other wavetable utility routines. The k-rate core opcodes ftsetsr and ftsetbase change the sampling rate and base frequency of the wavetable `t`. The parameter value `x` holds the new value, which must be greater than zero. The opcode returns the value `x`. The k-rate core opcodes ftsetloop and ftsetend change the loop start point and loop end point of the wavetable `t`. The parameter value `x` holds the new value, where `x` must round to a valid wavetable index. The opcode returns the value `x`. The polymorphic opcodes ftlen, ftsr, ftbasecps, ftloop and ftloopend return the values of the length, sampling rate, base frequency, loop start point, and loop end point of the wavetable `t`, respectively. Finally, the polymorphic opcodes tableread and tablewrite read and write values into wavetables. The `index` parameter for these opcodes specify the table position to read or write. For tableread, if `index` is an integer value, the value of the wavetable at that index is returned. If `index` is not an integer, the return value is interpolated from nearby values, using the same interpolation methods as the playback opcodes. For tablewrite, `index` is rounded to the nearest integer, and the `val` parameter value is inserted at that table position. The tablewrite opcode returns the value of `val`. For both tableread and tablewrite, the index value must be in a valid range for the wavetable. In many cases, SAOL programs use tableread to implement customized versions of table playback opcodes (such as doscil). Recall that to pitch-shift samples with doscil, the sample rate of the table is shifted up or down (using ftsetsr). When using tableread in this way, sfront users should note that the band-limited interpolation algorithm expects tableread tables to have their sample rate set to the value doscil would require to create the desired pitch-shift. Sfront relies on this sample rate to ensure that band-limited interpolation is alias-free. | ## To Write Tables `
    kopcode ftsetsr(table t, ksig x)
    kopcode ftsetbase(table t, ksig x)
    kopcode ftsetloop(table t, ksig x)
    kopcode ftsetend(table t, ksig x)

    opcode  tablewrite(table t,
    		   xsig index,
    		   xsig val)
` ## To Read Tables `
    opcode ftlen(table t)
    opcode ftsr(table t)
    opcode ftbasecps(table t)
    opcode ftloop(table t)
    opcode ftloopend(table t)

    opcode  tableread(table t, xsig index)
`

## Low-Level Generators In the previous sections, we describe the SAOL generators that initialize wavetables for use in specific sound generation algorithms. In this section, we describe the remaining SAOL generators, that are oriented for use in more general contexts. See the right panel for declaration syntax and algorithms for these generators. #### Simple Generators Several generators perform simple initialization, to prepare wavetables for access by the tableread and tablewrite opcodes. The empty generator fills a wavetable with zero values. The `size` parameter must be greater than zero. The data generator fills a wavetable with parameter values. If the `size` parameter has a value of `-1`, the wavetable size matches the number of parameters. If the `size` parameter is set to a value greater than zero, the wavetable takes on that size, and the parameter list is truncated or zero-padded as needed. | `## empty
    declaration:

    table t(empty, size);

    algorithm:

    for x in [0, size-1]:

    y(x) = 0
## data
    declaration:

    table t(data, size, p0 [, p1 ...]);

    algorithm:

    for x with a px parameter:

    y(x) = px
`

#### Continuous Curves The polynomial generator fills a wavetable with the values of an Nth order polynomial, whose coefficients are supplied as generator parameters. The `size` parameter must have a value greater than zero. See the right panel for the exact polynomial description. | `## polynomial
    declaration:

    table t(polynomial, size, xmin, xmax,
                      a0 [,a1, a2, a3);

    algorithm:

    for x in [0, size-1]:

    y(x) = a0 + a1*y + a2*y*y + ...

    where

    y = xmin +
        (xmax - xmin)*((size - x)/size)

    error if xmin = xmax
`

#### Table Concatenation The concat generator takes a list of wavetables as parameters. The generator concatenates the data in these tables, and uses the result to initialize its own table. If the `size` parameter has a value of `-1`, the wavetable size is the size of the concatenated data array. If the `size` parameter is set to a value greater than zero, the wavetable takes on that size, and the concatenated data array is is truncated or zero-padded as needed. | `## concat
    declaration:

    table t(concat, size, t1 [, t2 ...])

    algorithm:

    t1, t2, ... are tables. all declared
    tables must appear on lines that
    precede the concat generator line.

    t1 is the concatenation of the
    table arrays
`

#### Pseudorandom Tables The random generator fills a wavetable with values generated by a pseudorandom number generator, whose distribution is specified by the parameters. The `size` parameter sets the size of table, and must have a value greater than zero. The `dist` parameter specifies the distribution type, as an integer between `1` and `5`. The parameters `p1` and `p2` have different meanings for each distribution type. A `dist` value of `1` indicates a uniform distribution. All floating point values between `p1` and `p2` have an equal probability of being chosen for each table value. A `dist` value of `2` indicates a linear ramp distribution. Table values may lie between `p1` and `p2`, obeying the distribution shown on the right panel. Note that `p1` may not equal `p2`. A `dist` value of `3` indicates an exponential (Poisson) distribution. The `p1` value parameterizes the distribution, as shown on the right panel. A `dist` value of `4` indicates a Gaussian distribution. The `p1` value is the mean of the distribution. The `p2` value is the variance, and must be greater than zero. A `dist` value of `5` indicates the table is filled with a random binary sequence, that obeys a Poisson distribution. The `p1` value parameterizes the Poisson distribution. To fill the table `t` of length `len` we use the following algorithm. First, we initialize all table values to 0. Then, we draw a number the Poisson distribution. We round the number to an integer value `i`, and set `t(i) = 1`. Next, we draw another number from the Poisson distribution. We round this number to an integer value `j`, and set `t(i + 1 + j) = 1`. We continue drawing numbers and setting `t` in this fashion, until the algorithm requires us to set a table index that is greater or equal to `len`. #### Window Tables The generator window initializes a table to one of a set of windowing functions useful for digital signal processing applications. We describe this generator along with its associated opcodes in Part IV of the book. Next: Part II/5: SAOL Buses and Execution Order | `## random
    declaration:

    table t(random, size, dist, p1, p2)

    algorithm:

    dist takes on integer values that
    indicate a probability distribution

    for x in [0, size-1]:

    dist = 1: uniform distribution

    each table value filled with number
    in the range p1 to p2, with uniform
    probability.

    dist = 2: linear distribution

    each table value filled with number
    p(y) from p1 to p2, with probability

    p(y) = abs(d*(y-p1)), if y in [p1, p2]
    p(y) = 0,             otherwise

    where d = 2/((p2 - p1)*(p2 - p1))

    dist = 3: exponential distribution

    each table value filled with number
    p(y), with probability

    p(y) = (1/p1)*exp(-y/p1), if y > 0
    p(y) = 0,                 otherwise

    dist = 4: Gaussian distribution

    each table value filled with number
    p(y), with probability

    p(y) = (1/sqrt(2*pi*p2))*
           exp(-(p1-y)*(p1-y)/(2*p2))

    dist = 5: Poisson Binary Sequence

    see main text for algorithm, which
    uses distribution:

    p(y) = (1/p1)*exp(-y/p1), if y > 0
    p(y) = 0,                 otherwise

    to fill table with a binary sequence.

    Slib defines the constants
    RANDOM_UNIFORM, RANDOM_LINEAR,
    RANDOM_EXPON, RANDOM_GAUSSIAN,
    and RANDOM_PROCESS to use
    as the dist parameter in
    the random wavetable generator.
`

Copyright 1999 John Lazzaro and John Wawrzynek.