mp4sa> the mp4sa book> SAOL>wavetables 
Sections

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 
IntroductionSound 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 ExampleThe 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 128sample 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 piecewise 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 ipass statement converts the MIDI note number parameter into a frequency. One kpass statement uses the core opcode ftsetsr to set the sample rate of the envelope wavetable. The real work is done in the apass. 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 arate 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 arate 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, applicationsoriented wavetable generators, playback core opcodes, and lowlevel core opcodes and generators in detail. 
tsine.saolglobal { table cyc(harm, // sine series 128, // 128 samples long 1 // f1 weight ); // no partials srate 44100; } // // instr vtone // tabledriven 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 ipass // ********************** // turns MIDI number into // wavetable frequency freq = cpsmidi(num); // ********************** // computed during kpass // ********************** if (itime == 0) // first kpass only { ftsetsr(shape, 128/dur); } // ********************** // computed during apass // ********************** y = doscil(shape)*oscil(cyc, freq); output(y); }
output.wav [464 Kbytes, on the Web]
tsine.mp4 [399 bytes] 
DeclarationsThe 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 irate 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 irate global variables, and irate standard names. Importing TablesAn 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. TablemapsSome 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 Syntaxtable name(gen, size [, p1, p2 ...]); Wavetable Generatorsbuzz concat cubicseg data empty expseg harm harm_phase lineseg periodic polynomial random sample spline step window Imports Exampleglobals { 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 Declarationtablemap name(t1 [, t2, t3 ...]); Tablemap Exampleinstr 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 WaveformsThree wavetable generators (harm, phase_harm, and buzz) produce a single cycle of a periodic waveform. Looped playback of wavetables produced with these generators is clickfree. The definitions of these generators are shown on the right panel, along with the related generator periodic. HarmThe simplest generator, harm, specifies a waveform as a harmonic series of N zerophase 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 harmdeclaration: table t(harm, size, a1 [, a2, a3 ...]); algorithm: for x in [0, size1]: y(x) = a1*sin(2*pi*x/size) + a2*sin(4*pi*x/size) + a3*sin(6*pi*x/size) + ... 
Harm_phase and PeriodicThe 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 nonintegral, the looped wavetable produces a discontinuous waveform. 
harm_phasedeclaration: table t(harm_phase, size, a1, ph1 [, a2, ph2, a3, ph3 ...]); algorithm: for x in [0, size1]: y(x) = a1*sin(ph1 + 2*pi*x/size) + a2*sin(ph2 + 4*pi*x/size) + a3*sin(ph3 + 6*pi*x/size) + ... periodicdeclaration: table t(periodic, size, f1, a1, ph1 [, f2, a2, ph2, f3, a3, ph3 ...]); algorithm: for x in [0, size1]: 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) + ... 
BuzzNewcomers 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 pulselike 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 aliasfree pulselike 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. 
buzzdeclaration: table t(buzz, size, num, low, r); algorithm: for x in [0, size1]: 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 WaveformsFive 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 propertiesFor each generator, the envelope is specified as a piecewise 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 piecewise 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 methodsThe 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. 
stepdeclaration: table t(step, size, x1, y1, x2 [, y2, x3 ...]); algorithm: for x in [0, size1]: 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. 
linesegdeclaration: table t(lineseg, size, x1, y1, x2, y2 [, x3, y3 ...]); algorithm: for x in [0, size1]: 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. 
expsegdeclaration: table t(expseg, size, x1, y1, x2, y2 [, x3, y3 ...]); algorithm: for x in [0, size1]: if x1 <= x < x2 y(x) exponentially interpolates between y1 and y2 i.e. y(x) = y1*(y2/y1)^((xx1)/(x2x1)) 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 thirdorder 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. 
splinedeclaration: table t(spline, size, x1, y1, [k2, x2, y2, k3, x3, y3,] kn, xn, yn); algorithm: for x in [0, size1]: 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. 
cubicsegdeclaration: table t(cubicseg, size, infl1, y1, x1, y2, infl2, y3, x2, y4, infl3, y5 [,x3, y6, infl4, y7]); algorithm: for x in [0, size1]: 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 FilesThe 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 MP4SA 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 zeropadded as needed. Sfront supports 8bit, 16bit, and 24bit 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 issuesIn 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 lowbitrate applications should consider synthesisbased sound generation techniques as an alternative to the sample generator. 
sampledeclaration: 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 zeropadded as needed. table values lie between 1.0 and +1.0, by scaling 16bit signed integers from the file by 1/32768. 24bit samples scale by 1/8388608. 8bit samples scale by 1/128 (after rezeroing, 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 TablesThe irate 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 nonnormative. Different decoders will produce different sample values for a given factor value. In addition, the range of acceptable factor values is also decoderdependent. 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. 
Speedtiopcode speedt(table in, table out, ivar factor) 
Table PlaybackThe 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 propertiesThe 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 apass (for oscil, doscil, and loscil) or once per kpass (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 MP4SA 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 bandlimited interpolation if interp is set to 1 (sfront's grain operator is an exception, and uses linear interpolation). Sfront has commandline options to set the accuracy of the bandlimited interpolation. Later in this chapter, we discuss special issues when using bandlimited interpolation with lowlevel core opcodes such as tableread. If the goal is to achieve normative (i.e. decoderindependent) highquality 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 lowlevel core opcodes. 
Interp Global Parameterglobal { interp 0; // linear // interpolation } global { interp 1; // nonnormative // higherquality // interpolation } 
Waveform PlaybackTwo 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 arate oscil opcode produces signal values as described above if it is called once apass, while the krate koscil has the described semantics if it is called once per kpass. 
Waveform Playbackaopcode oscil(table t, asig freq [,ivar loops]) kopcode koscil(table t, ksig freq [,ivar loops]) 
OneShot PlaybackThe arate 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. 
OneShot Playbackaopcode doscil(table t) 
Loop PlaybackThe arate 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 Playbackaopcode loscil(table t, asig freq [,ivar basefreq, ivar loopstart, ivar loopend]) 
LowLevel Core OpcodesEleven 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 krate 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 krate 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 pitchshift 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 bandlimited interpolation algorithm expects tableread tables to have their sample rate set to the value doscil would require to create the desired pitchshift. Sfront relies on this sample rate to ensure that bandlimited interpolation is aliasfree. 
To Write Tableskopcode 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 Tablesopcode 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) 
LowLevel GeneratorsIn 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 GeneratorsSeveral 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 zeropadded as needed. 
emptydeclaration: table t(empty, size); algorithm: for x in [0, size1]: y(x) = 0 datadeclaration: table t(data, size, p0 [, p1 ...]); algorithm: for x with a px parameter: y(x) = px 
Continuous CurvesThe 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. 
polynomialdeclaration: table t(polynomial, size, xmin, xmax, a0 [,a1, a2, a3); algorithm: for x in [0, size1]: y(x) = a0 + a1*y + a2*y*y + ... where y = xmin + (xmax  xmin)*((size  x)/size) error if xmin = xmax 
Table ConcatenationThe 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 zeropadded as needed. 
concatdeclaration: 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 TablesThe 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 TablesThe 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. 
randomdeclaration: table t(random, size, dist, p1, p2) algorithm: dist takes on integer values that indicate a probability distribution for x in [0, size1]: 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*(yp1)), 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((p1y)*(p1y)/(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. 
mp4sa> the mp4sa book> SAOL>wavetables 