diff options
Diffstat (limited to 'doc/part6.html')
-rw-r--r-- | doc/part6.html | 573 |
1 files changed, 573 insertions, 0 deletions
diff --git a/doc/part6.html b/doc/part6.html new file mode 100644 index 0000000..a903acc --- /dev/null +++ b/doc/part6.html @@ -0,0 +1,573 @@ +<html><head><title>More Examples</title></head> +<body bgcolor="ffffff"> +<a href = "part5.html">Previous Section</a> | <a href = "part7.html">Next Section</a> | <a href = "title.html#toc">Table of Contents</a> | <a href = "indx.html">Index</a> | <a href = "title.html">Title Page</a> +<hr> +<a name = "40"><h2>More Examples</h2></a>This chapter explores Nyquist through additional examples. The reader may +wish to browse through these and move on to Chapter <a href = "part8.html#77">"Nyquist Functions"</a>, which +is a reference section describing Nyquist functions. +<p> +<a name = "41"><h3>Stretching Sampled Sounds</h3></a><a name="index148">This example illustrates how to stretch a sound, resampling it in the process. +Because sounds in Nyquist are <i>values</i> that contain the sample rate, start +time, etc., use <code>sound</code> to convert a sound into a behavior that can be +stretched, e.g. <code>sound(a-snd)</code>. This behavior stretches a sound according +to the stretch factor in the environment, set using <code>stretch</code>. For +accuracy and efficiency, Nyquist does not resample a stretched sound until +absolutely necessary. The <code>force-srate</code> function is used to resample +the result so that we end up with a "normal" sample rate that is playable +on ordinary sound cards. +<p> +<pre> +<i>; if a-snd is not loaded, load sound sample: +;</i> +if not(boundp(quote(a-snd))) then + set a-snd = s-read("demo-snd.aiff") +<p> +<i>; the SOUND operator shifts, stretches, clips and scales +; a sound according to the current environment +;</i> +define function ex23() + play force-srate(*default-sound-srate*, sound(a-snd) ~ 3.0) +<p> +define function down() + return force-srate(*default-sound-srate*, + seq(sound(a-snd) ~ 0.2, + sound(a-snd) ~ 0.3, + sound(a-snd) ~ 0.4, + sound(a-snd) ~ 0.6)) +play down() +<p> +<i>; that was so much fun, let's go back up: +;</i> +define function up() + return force-srate(*default-sound-srate*, + seq(sound(a-snd) ~ 0.5, + sound(a-snd) ~ 0.4, + sound(a-snd) ~ 0.3, + sound(a-snd) ~ 0.2)) +<p> +<i>; and write a sequence +;</i> +play seq(down(), up(), down()) +</pre> + +<p> +Notice the use of the <code>sound</code> behavior as opposed to <code>cue</code>. The +<code>cue</code> behavior shifts and scales its sound according to <code>*warp*</code> +and <code>*loud*</code>, but it does not change the duration or resample the +sound. In contrast, <code>sound</code> not only shifts and scales its sound, but +it also stretches it by resampling or changing the effective sample rate + according to <code>*warp*</code>. If +<code>*warp*</code> is a continuous warping function, then the sound will be +stretched by time-varying amounts. +(The <code>*transpose*</code> element of the environment is +ignored by both <code>cue</code> and <code>sound</code>.) +<p> +<b><i>Note:</i></b> <code>sound</code> may use linear interpolation rather than a high-quality resampling algorithm. In some cases, this may introduce errors audible as noise. Use <code>resample</code> (see Section <a href = "part8.html#85">"Sound Synthesis"</a>) for high-quality interpolation. +<p> +In the functions <code>up</code> and <code>down</code>, the <code>*warp*</code> is set by +<i>stretch</i> (<code>~</code>), which simply scales time by a constant scale factor. In this case, +<code>sound</code> can "stretch" the signal simply by changing the sample rate without +any further computation. When <code>seq</code> tries to add the signals together, it +discovers the sample rates do not match and uses linear interpolation to adjust +all sample rates to match that of the first sound in the sequence. The result of +<code>seq</code> is then converted using <code>force-srate</code> to convert the sample rate, +again using linear interpolation. +It would be slightly better, from a computational +standpoint, to apply <code>force-srate</code> individually +to each stretched sound rather +than applying <code>force-srate</code> after <code>seq</code>. +<p> +Notice that the overall duration of <code>sound(a-snd) ~ 0.5</code> will +be half the duration of <code>a-snd</code>. +<p> +<a name = "42"><h3>Saving Sound Files</h3></a><a name="index149">So far, we have used the <code>play</code> command to play a sound. The +<code>play</code> command works by writing a sound to a file while +simultaneously playing it. +This can be done one step at a time, and +it is often convenient to save a sound to a particular file for later use: +<pre> +<i>; write the sample to a file, +; the file name can be any Unix filename. Prepending a "./" tells +; s-save to not prepend *default-sf-dir* +;</i> +exec s-save(a-snd, 1000000000, "./a-snd-file.snd") +<p> +<i>; play a file +; play command normally expects an expression for a sound +; but if you pass it a string, it will open and play a +; sound file</i> +play "./a-snd-file.snd" +<p> +<i>; delete the file (do this with care!) +; only works under Unix (not Windows)</i> +exec system("rm ./a-snd-file.snd") +<p> +<i>; now let's do it using a variable as the file name +;</i> +set my-sound-file = "./a-snd-file.snd" +<p> +exec s-save(a-snd, 1000000000, my-sound-file) +<p> +<i>; play-file is a function to open and play a sound file</i> +exec play-file(my-sound-file) +<p> +exec system(strcat("rm ", my-sound-file)) +</pre> + +This example shows how <code>s-save</code> can be used to save a sound to a file. +<p> +This example also shows how the <code>system</code> function can be used to invoke +Unix shell commands, such as a command to play a file or remove it. +Finally, notice that <code>strcat</code> can be used to concatenate a command name +to a file name to create a complete command that is then passed to +<code>system</code>. (This is convenient if the sound file name is stored in a +parameter or variable.) +<p> +<a name = "43"><h3>Memory Space and Normalization</h3></a> + +<a name="index150"><a name="index151"><a name="index152"><a name="index153"><a name="index154"><a name="index155"><a name="index156"><a name="index157"> +Sound samples take up lots of memory, and often, there is not enough primary (RAM) memory to hold a complete composition. For this reason, Nyquist can compute sounds incrementally, saving the final result on disk. <i>However,</i> Nyquist can also save sounds in memory so that they can be reused efficiently. In general, if a sound is saved in a global variable, memory will be allocated as needed to save and reuse it. +<p> +The standard way to compute a sound and write it to disk is to pass an expression to the <code>play</code> command: +<pre> +play my-composition() +</pre> + +<p> + +Often it is nice to <i>normalize</i> sounds so that they use the full available +dynamic range of 16 bits. Nyquist has an automated facility to help with +normalization. By default, Nyquist computes up to 1 million samples (using +about 4MB of memory) looking for the peak. The entire sound is normalized so +that this peak will not cause clipping. If the sound has less than 1 million +samples, or if the first million samples are a good indication of the overall +peak, then the signal will not clip. +<p> +With this automated normalization technique, you can choose the desired +peak value by setting <code>*autonorm-target*</code>, which is initialized to 0.9. +The number of samples examined is <code>*autonorm-max-samples*</code>, initially +1 million. You can turn this feature off by executing: +<pre> +exec autonorm-off()<a name="index158"> +</pre> + +and turn it back on by typing: +<pre> +exec autonorm-on()<a name="index159"> +</pre> + +This normalization technique is in effect when <code>*autonorm-type*</code> is +<code>quote(lookahead</code>), which is the default. +<p> +An alternative normalization method uses the peak value from the previous +call to <code>play</code>. After playing a file, Nyquist can adjust an internal +scale factor so that if you play the same file again, the peak amplitude +will be <code>*autonorm-target*</code>, which is initialized to 0.9. This can +be useful if you want to carefully normalize a big sound that does not +have its peak near the beginning. To select this style of normalization, +set <code>*autonorm-type*</code> to the (quoted) atom <code>quote(previous</code>). +<p> +You can also create your own normalization method in Nyquist. +The <code>peak</code> function computes the maximum value of a sound. +The peak value is also returned from the <code>play</code> macro. You can +normalize in memory if you have enough memory; otherwise you can compute +the sound twice. The two techniques are illustrated here: +<pre> +<i>; normalize in memory. First, assign the sound to a variable so +; it will be retained:</i> +set mysound = sim(osc(c4), osc(c5)) +<i>; now compute the maximum value (ny:all is 1 giga-samples, you may want a +; smaller constant if you have less than 4GB of memory:</i> +set mymax = snd-max(mysound, NY:ALL) +display "Computed max", mymax +<i>; now write out and play the sound from memory with a scale factor:</i> +play mysound * (0.9 / mymax) +<p> +<i>; if you don't have space in memory, here's how to do it:</i> +define function myscore() + return sim(osc(c4), osc(c5)) +<i>; compute the maximum:</i> +set mymax = snd-max(list(quote(myscore)), NY:ALL) +display "Computed max", mymax +<i>; now we know the max, but we don't have a the sound (it was garbage +; collected and never existed all at once in memory). Compute the sound +; again, this time with a scale factor:</i> +play myscore() * (0.9 / mymax) +</pre> + +<p> +You can also write a sound as a floating point file. This +file can then be converted to 16-bit integer with the proper scaling +applied. If a long computation was involved, it should be much faster +to scale the saved sound file than to recompute the sound from scratch. +Although not implemented yet in Nyquist, some header formats can +store maximum amplitudes, and some soundfile player programs can +rescale floating point files on the fly, allowing normalized +soundfile playback without an extra normalization pass (but at a cost +of twice the disk space of 16-bit samples). +You can use Nyquist to rescale a floating point file and +convert it to 16-bit samples for playback. +<p> +<a name = "44"><h3>Frequency Modulation</h3></a><a name="index160"> +The next example uses the Nyquist frequency modulation behavior <code>fmosc</code> +to generate various sounds. The parameters to <code>fmosc</code> are: +<pre> +fmosc(<i>pitch</i> <i>modulator</i> <i>table</i> <i>phase</i>) +</pre> + +Note that pitch is the number of half-steps, e.g. <code>c4</code> has the value of 60 which is middle-C, and phase is in degrees. Only the first two parameters are required: +<pre> +<i>; make a short sine tone with no frequency modulation +;</i> +play fmosc(c4, pwl(0.1)) +<p> +<i>; make a longer sine tone -- note that the duration of +; the modulator determines the duration of the tone +;</i> +play fmosc(c4, pwl(0.5)) +</pre> + +In the example above, <code>pwl</code> (for Piece-Wise Linear) is used to generate +sounds that are zero for the durations of <code>0.1</code> and <code>0.5</code> seconds, +respectively. In effect, we are using an FM oscillator with no modulation +input, and the result is a sine tone. The duration of the modulation +determines the duration of the generated tone (when the modulation signal +ends, the oscillator stops). +<p> +The next example uses a more interesting modulation function, a ramp from +zero to C4, expressed in hz. More explanation of <code>pwl</code> is in +order. This operation constructs a piece-wise linear function sampled at +the <code>*control-srate*</code>. The first breakpoint is always at <code>(0, +0)</code>, so the first two parameters give the time and value of the second +breakpoint, the second two parameters give the time and value of the third +breakpoint, and so on. The last breakpoint has a value of <code>0</code>, so only +the time of the last breakpoint is given. In this case, we want the ramp to +end at C4, so we cheat a bit by having the ramp return to zero +"almost" instantaneously between times <code>0.5</code> and <code>0.501</code>. +<p> +The <code>pwl</code> behavior always expects an odd number of parameters. The +resulting function is shifted and stretched linearly according to +<code>*warp*</code> in the environment. Now, here is the example: +<pre> +<i>; make a frequency sweep of one octave; the piece-wise linear function +; sweeps from 0 to (step-to-hz c4) because, when added to the c4 +; fundamental, this will double the frequency and cause an octave sweep. +;</i> +play fmosc(c4, pwl(0.5, step-to-hz(c4), 0.501)) +</pre> + +<p> +The same idea can be applied to a non-sinusoidal carrier. Here, we assume that <code>*fm-voice*</code> is predefined (the next section shows how to define it): +<pre> +<i>; do the same thing with a non-sine table +;</i> +play fmosc(cs2, pwl(0.5, step-to-hz(cs2), 0.501), + *fm-voice*, 0.0) +</pre> + +<p> +The next example shows how a function can be used to make a special +frequency modulation contour. In this case the contour generates a sweep +from a starting pitch to a destination pitch: +<pre> +<i>; make a function to give a frequency sweep, starting +; after <delay> seconds, then sweeping from <pitch-1> +; to <pitch-2> in <sweep-time> seconds and then +; holding at <pitch-2> for <hold-time> seconds. +;</i> +define function sweep(delay, pitch-1, sweep-time, + pitch-2, hold-time) + begin + with interval = step-to-hz(pitch-2) - step-to-hz(pitch-1) + return pwl(delay, 0.0, + <i>; sweep from pitch 1 to pitch 2</i> + delay + sweep-time, interval, + <i>; hold until about 1 sample from the end</i> + delay + sweep-time + hold-time - 0.0005, + interval, + <i>; quickly ramp to zero (pwl always does this,</i> + <i>; so make it short)</i> + delay + sweep-time + hold-time) + end +<p> +<i>; now try it out +;</i> +play fmosc(cs2, sweep(0.1, cs2, 0.6, gs2, 0.5), + *fm-voice*, 0.0) +</pre> + +<p> +FM can be used for vibrato as well as frequency sweeps. Here, we use the +<code>lfo</code> function to generate vibrato. The <code>lfo</code> operation is +similar to <code>osc</code>, except it generates sounds at the +<code>*control-srate*</code>, and the parameter is hz rather than a pitch: +<pre> +play fmosc(cs2, 10.0 * lfo(6.0), *fm-voice*, 0.0) +</pre> + +<p> +What kind of manual would this be without the obligatory FM sound? Here, a +sinusoidal modulator (frequency C4) is multiplied by a slowly increasing +ramp from zero to <code>1000.0</code>. +<pre> +set modulator = pwl(1.0, 1000.0, 1.0005) * + osc(c4) +<i>; make the sound</i> +play fmosc(c4, modulator) +</pre> + +<p> +For more simple examples of FM in Nyquist, see +<a name="index161"><a name="index162"><a name="index163"><a name="index164"> +<code>demos/warble_tutorial.htm</code>. Another interesting FM sound +reminiscent of "scratching" can be found with a detailed explanation +in <code>demos/scratch_tutorial.htm</code>.<a name="index165"> +<a name="index166"><a name="index167">. +<p> +<a name = "45"><h3>Building a Wavetable</h3></a> +In Section <a href = "part2.html#9">"Waveforms"</a>, we saw how to synthesize a wavetable. A +wavetable for <code>osc</code> also can be extracted from any sound. This is +especially interesting if the sound is digitized from some external sound +source and loaded using the <code>s-read</code> function. Recall that a table +is a list consisting of a sound, the pitch of that sound, and T (meaning the +sound is periodic). +<p> +In the following, a sound is first read from the file <code>demo-snd.nh</code>. +Then, the <code>extract</code> function is used +to extract the portion of the sound between 0.110204 and 0.13932 seconds. +(These numbers might be obtained by first plotting the sound and estimating +the beginning and end of a period, or by using some software to look for +good zero crossings.) The result of <code>extract</code> becomes the first +element of a list. The next element is the pitch (24.848422), and the last +element is <code>T</code>. The list is assigned to <code>*fm-voice*</code>. +<pre> +if not(boundp(quote(a-snd))) then + set a-snd = s-read("demo-snd.aiff") +<p> +set *fm-voice* = list(extract(0.110204, 0.13932, cue(a-snd)), + 24.848422, + #T) +</pre> + +<p> +The file +<i>demos/examples.sal</i> contains an extensive example of how to locate +zero-crossings, extract a period, build a waveform, and generate a tone from it. (See <code>ex37</code> through <code>ex40</code> in the file.) +<p> +<a name = "46"><h3>Filter Examples</h3></a>Nyquist provides a variety of filters. All of these filters take either +real numbers or signals as parameters. If you pass a signal as a filter +parameter, the filter coefficients are recomputed at the sample rate of the +<i>control</i> signal. Since filter coefficients are generally expensive to +compute, you may want to select filter control rates carefully. Use +<code>control-srate-abs</code> (Section <a href = "part8.html#92">"Transformations"</a>) to specify +the default control sample rate, or use <code>force-srate</code> (Section +<a href = "part8.html#85">"Sound Synthesis"</a>) to resample a signal before passing it to a filter. +<p> +Before presenting examples, let's generate some unfiltered white noise: +<pre> +play noise() +</pre> + +Now low-pass filter the noise with a 1000Hz cutoff: +<pre> +play lp(noise(), 1000.0) +</pre> + +The high-pass filter is the inverse of the low-pass: +<pre> +play hp(noise(), 1000.0) +</pre> + +<p> +Here is a low-pass filter sweep from 100Hz to 2000Hz: +<pre> +play lp(noise(), pwl(0.0, 100.0, 1.0, 2000.0, 1.0)) +</pre> + +And a high-pass sweep from 50Hz to 4000Hz: +<pre> +play hp(noise(), pwl(0.0, 50.0, 1.0, 4000.0, 1.0)) +</pre> + +<p> +The band-pass filter takes a center frequency and a bandwidth parameter. +This example has a 500Hz center frequency with a 20Hz bandwidth. The scale +factor is necessary because, due to the resonant peak of the filter, the +signal amplitude exceeds 1.0: +<pre> +play reson(10.0 * noise(), 500.0, 20.0, 1) +</pre> + +In the next example, the center frequency is swept from 100 to 1000Hz, using a constant 20Hz bandwidth: +<pre> +play reson(0.04 * noise(), + pwl(0.0, 200.0, 1.0, 1000.0, 1.0), + 20.0) +</pre> + +<p> +For another example with explanations, see +<a name="index168"><a name="index169"><a name="index170"> +<a name="index171"> +<code>demos/wind_tutorial.htm</code>. +<p> +<a name = "47"><h3>DSP in Lisp</h3></a> +<a name="index172"><a name="index173">In almost any +signal processing system, the vast majority of computation +takes place in the inner loops of DSP algorithms, and Nyquist is designed so +that these time-consuming inner loops are in highly-optimized +machine code rather than relatively slow interpreted lisp code. As a result, +Nyquist typically spends 95% of its time in these inner loops; the overhead +of using a Lisp interpreter is negligible. +<p> +The drawback is that Nyquist must provide the DSP operations you need, or +you are out of luck. When Nyquist is found lacking, you can either write a +new primitive signal operation, or you can perform DSP in Lisp code. Neither +option is recommended for inexperienced programmers. Instructions for +extending Nyquist are given in Appendix <a href = "part16.html#183">"Extending Nyquist"</a>. This section +describes the process of writing a new signal processing function in Lisp. +<p> +Before implementing a new DSP function, you should decide which approach is +best. First, figure out how much of the new function can be implemented +using existing Nyquist functions. For example, you might think that a +tapped-delay line would require a new function, but in fact, it can be +implemented by composing sound transformations to accomplish delays, scale +factors for attenuation, and additions to combine the intermediate results. +This can all be packaged into a new Lisp function, making it easy to use. +If the function relies on built-in DSP primitives, it will execute very +efficiently. +<p> +Assuming that built-in functions cannot be used, try to define a new +operation that will be both simple and general. Usually, it makes sense to +implement only the kernel of what you need, combining it with existing +functions to build a complete instrument or operation. For example, if you +want to implement a physical model that requires a varying breath pressure +with noise and vibrato, plan to use Nyquist functions to add a basic +pressure envelope to noise and vibrato signals to come up with a composite +pressure signal. Pass that signal into the physical model rather than +synthesizing the envelope, noise, and vibrato within the model. This not +only simplifies the model, but gives you the flexibility to use all of +Nyquist's operations to synthesize a suitable breath pressure signal. +<p> +Having designed the new "kernel" DSP operation that must be implemented, +decide whether to use C or Lisp. (At present, SAL is not a good option +because it has no support for object-oriented programming.) +To use C, you must have a C compiler, the +full source code for Nyquist, and you must learn about extending Nyquist by +reading Appendix <a href = "part16.html#183">"Extending Nyquist"</a>. This is the more complex approach, but +the result will be very efficient. A C implementation will deal properly +with sounds that are not time-aligned or matched in sample rates. +To use Lisp, you must learn something +about the XLISP object system, and the result will be about 50 times slower +than C. Also, it is more difficult to deal with time alignment and +differences in sample rates. +The remainder of this section gives an example of a Lisp version of +<code>snd-prod</code> to illustrate how to write DSP functions for Nyquist in Lisp. +<p> +The <code>snd-prod</code> function is the low-level multiply routine. It has two +sound parameters and returns a sound which is the product of the two. To +keep things simple, we will assume that two sounds to be multiplied have a +matched sample rate and matching start times. The DSP algorithm for each +output sample is simply to fetch a sample from each sound, multiply them, +and return the product. +<p> +To implement <code>snd-prod</code> in Lisp, three components are required: +<ol> +<li> +An object is used to store the two parameter sounds. This object will be +called upon to yield samples of the result sound; +<li>Within the object, the <code>snd-fetch</code> routine is used to fetch samples +from the two input sounds as needed; +<li>The result must be of type <code>SOUND</code>, so <code>snd-fromobject</code> is used +to create the result sound. +</ol> +<p> +The combined solution will work as follows: The result is a value of type +<code>sound</code> that retains a reference to the object. When Nyquist needs +samples from the sound, it invokes the sound's "fetch" function, which in +turn sends an XLISP message to the object. The object will use +<code>snd-fetch</code> to get a sample from each stored sound, multiply the +samples, and return a result. +<p> +Thus the goal is to design an XLISP object that, in response to a +<code>:next</code> message will return a proper sequence of samples. When the +sound reaches the termination time, simply return <code>NIL</code>. +<p> +The XLISP manual (see Appendix <a href = "part19.html#202">"XLISP: An Object-oriented Lisp"</a> describes the object system, +but in a very terse style, so this example will include some explanation of +how the object system is used. First, we need to define a class for the +objects that will compute sound products. Every class is a subclass of class +<code>class</code>, and you create a subclass by sending <code>:new</code> to a class. +<pre> +(setf product-class (send class :new '(s1 s2))) +</pre> + +The parameter <code>'(s1 s2)</code> says that the new class will have two instance +variables, <code>s1</code> and <code>s2</code>. In other words, every object which is an +instance of class <code>product-class</code> will have its own copy of +these two variables. +<p> +Next, we will define the <code>:next</code> method for <code>product-class</code>: +<pre> +(send product-class :answer :next '() + '((let ((f1 (snd-fetch s1)) + (f2 (snd-fetch s2))) + (cond ((and f1 f2) + (* f1 f2)) + (t nil))))) +</pre> + +The <code>:answer</code> message is used to insert a new method into our new +<code>product-class</code>. The method is described in three parts: the name +(<code>:next</code>), a parameter list (empty in this case), and a list of +expressions to be evaluated. In this case, we fetch samples from <code>s1</code> +and <code>s2</code>. If both are numbers, we return their product. If either is +<code>NIL</code>, we terminate the sound by returning <code>nil</code>. +<p> +The <code>:next</code> method assumes that <code>s1</code> and <code>s2</code> hold the sounds +to be multiplied. These must be installed when the object is created. +Objects are created by sending <code>:new</code> to a class. A new object is +created, and any parameters passed to <code>:new</code> are then sent in a +<code>:isnew</code> message to the new object. Here is the <code>:isnew</code> +definition for <code>product-class</code>: +<pre> +(send product-class :answer :isnew '(p1 p2) + '((setf s1 (snd-copy p1)) + (setf s2 (snd-copy p2)))) +</pre> + +Take careful note of the use of <code>snd-copy</code> in this initialization. The +sounds <code>s1</code> and <code>s2</code> are modified when accessed by +<code>snd-fetch</code> in the <code>:next</code> method defined above, but this destroys +the illusion that sounds are immutable values. The solution is to copy the +sounds before accessing them; the original sounds are therefore unchanged. +(This copy also takes place implicitly in most Nyquist sound functions.) +<p> +To make this code safer for general use, we should add checks that <code>s1</code> +and <code>s2</code> are sounds with identical starting times and sample rates; +otherwise, an incorrect result might be computed. +<p> +Now we are ready to write <code>snd-product</code>, an approximate replacement for +<code>snd-prod</code>: +<pre> +(defun snd-product (s1 s2) + (let (obj) + (setf obj (send product-class :new s1 s2)) + (snd-fromobject (snd-t0 s1) (snd-srate s1) obj))) +</pre> + +This code first creates <code>obj</code>, an instance of <code>product-class</code>, to +hold <code>s1</code> and <code>s2</code>. Then, it uses <code>obj</code> to create a sound +using <code>snd-fromobject</code>. This sound is returned from +<code>snd-product</code>. Note that in <code>snd-fromobject</code>, you must also +specify the starting time and sample rate as the first two parameters. These +are copied from <code>s1</code>, again assuming that <code>s1</code> and <code>s2</code> have +matching starting times and sample rates. +<p> +Note that in more elaborate DSP algorithms we could expect the object to +have a number of instance variables to hold things such as previous samples, +waveform tables, and other parameters. +<p> +<hr> +<a href = "part5.html">Previous Section</a> | <a href = "part7.html">Next Section</a> | <a href = "title.html#toc">Table of Contents</a> | <a href = "indx.html">Index</a> | <a href = "title.html">Title Page</a> +</body></html> |