File: part6.html

package info (click to toggle)
nyquist 3.20%2Bds-2
  • links: PTS, VCS
  • area: main
  • in suites: bookworm
  • size: 58,008 kB
  • sloc: ansic: 74,743; lisp: 17,929; java: 10,723; cpp: 6,690; sh: 171; xml: 58; makefile: 40; python: 15
file content (647 lines) | stat: -rw-r--r-- 28,118 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
<!DOCTYPE html>
<html><head><title>More Examples</title>
<link rel="stylesheet" type="text/css" href="nyquiststyle.css">
<link rel="icon" href="nyquist-icon.png" />
<link rel="shortcut icon" href="nyquist-icon.png" />
</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 = "43"><h2>More Examples</h2></a>
<p>This chapter explores Nyquist through additional examples.  The reader may
wish to browse through these and move on to Chapter <a href = "part8.html#82">Nyquist Functions</a>, which
is a reference section describing Nyquist functions.</p>
<a name = "44"><h3>Stretching Sampled Sounds</h3></a><a name="index161"></a>
<p>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 &ldquo;normal&rdquo; sample rate that is playable
on ordinary sound cards.</p>

<p></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")
</pre>

<p>
</p>
<pre>
<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)

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()
</pre>

<p>
</p>
<pre>
<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))
</pre>

<p>
</p>
<pre>
<i>; and write a sequence
;</i>
play seq(down(), up(), down())
</pre>

<p></p>

<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#90">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 &ldquo;stretch&rdquo; 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>

<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 = "45"><h3>Saving Sound Files</h3></a><a name="index162"></a>
<p>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:
</p>
<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")
</pre>

<p>
</p>
<pre>
<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"
</pre>

<p>
</p>
<pre>
<i>; delete the file (do this with care!)
; only works under Unix (not Windows)</i>
exec system("rm ./a-snd-file.snd")
</pre>

<p>
</p>
<pre>
<i>; now let's do it using a variable as the file name
;</i>
set my-sound-file = "./a-snd-file.snd"
exec s-save(a-snd, 1000000000, my-sound-file)
</pre>

<p>
</p>
<pre>
<i>; play-file is a function to open and play a sound file</i>
exec play-file(my-sound-file)
exec system(strcat("rm ", my-sound-file))
</pre>

<p>
This example shows how <code>s-save</code> can be used to save a sound to a file.</p>

<p>The last line of this example 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 = "46"><h3>Memory Space and Normalization</h3></a><a name="index163"></a><a name="index164"></a><a name="index165"></a><a name="index166"></a><a name="index167"></a><a name="index168"></a><a name="index169"></a><a name="index170"></a>
<p>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>

<p>The standard way to compute a sound and write it to disk is to pass an expression to the <code>play</code> command:
</p>
<pre>
play my-composition()
</pre>

<p></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:
</p>
<pre>
exec autonorm-off()<a name="index171"></a>
</pre>

<p>
and turn it back on by typing:
</p>
<pre>
exec autonorm-on()<a name="index172"></a>
</pre>

<p>
This normalization technique is in effect when <code>*autonorm-type*</code> is 
<code>quote(lookahead</code>), which is the default.</p>

<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>

<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:
</p>
<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)
</pre>

<p>
</p>
<pre>
<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></p>

<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 = "47"><h3>Frequency Modulation</h3></a><a name="index173"></a>
<p>The next example uses the Nyquist frequency modulation behavior <code>fmosc</code>
to generate various sounds.  The parameters to <code>fmosc</code> are:
</p>
<pre>
fmosc(<i>pitch</i>, <i>modulator</i>, <i>table</i>, <i>phase</i>)
</pre>

<p>
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:
</p>
<pre>
<i>; make a short sine tone with no frequency modulation
;</i>
play fmosc(c4, pwl(0.1))

<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>

<p>
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>

<p>The next example uses a more interesting modulation function, a ramp from
zero to C<sub>4</sub>, 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 C<sub>4</sub>, so we cheat a bit by having the ramp return to zero
&ldquo;almost&rdquo; instantaneously between times <code>0.5</code> and <code>0.501</code>.</p>

<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:
</p>
<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></p>

<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):
</p>
<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></p>

<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:
</p>
<pre>
<i>; make a function to give a frequency sweep, starting
; after &lt;delay&gt; seconds, then sweeping from &lt;pitch-1&gt;
; to &lt;pitch-2&gt; in &lt;sweep-time&gt; seconds and then
; holding at &lt;pitch-2&gt; for &lt;hold-time&gt; 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


<i>; now try it out
;</i>
play fmosc(cs2, sweep(0.1, cs2, 0.6, gs2, 0.5),
           *fm-voice*, 0.0)
</pre>

<p></p>

<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:
</p>
<pre>
play fmosc(cs2, 10.0 * lfo(6.0), *fm-voice*, 0.0)
</pre>

<p></p>

<p>What kind of manual would this be without the obligatory FM sound?  Here, a
sinusoidal modulator (frequency C<sub>4</sub>) is multiplied by a slowly increasing
ramp from zero to <code>1000.0</code>.
</p>
<pre>
set modulator = pwl(1.0, 1000.0, 1.0005) * 
                osc(c4)
<i>; make the sound</i>
play fmosc(c4, modulator)
</pre>

<p></p>

<p>For more simple examples of FM in Nyquist, see 
<a name="index174"></a><a name="index175"></a><a name="index176"></a><a name="index177"></a>
<code>nyquist/lib/warble/warble_tutorial.htm</code>.</p>
<a name = "48"><h3>Building a Wavetable</h3></a>
<p>In Section <a href = "part2.html#11">Non-Sinusoidal 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>

<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>.
</p>
<pre>
if not(boundp(quote(a-snd))) then
  set a-snd = s-read("demo-snd.aiff")

set *fm-voice* = list(extract(0.110204, 0.13932, cue(a-snd)),
                      24.848422,
                      #T)
</pre>

<p></p>

<p>The file
<i>nyquist/lib/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 = "49"><h3>Filter Examples</h3></a>
<p>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#98">Transformations</a>) to specify
the default control sample rate, or use <code>force-srate</code> (Section
<a href = "part8.html#90">Sound Synthesis</a>) to resample a signal before passing it to a filter.  </p>

<p>Before presenting examples, let's generate some unfiltered white noise:
</p>
<pre>
play noise()
</pre>

<p>
Now low-pass filter the noise with a 1000Hz cutoff:
</p>
<pre>
play lp(noise(), 1000.0)
</pre>

<p>
The high-pass filter is the inverse of the low-pass:
</p>
<pre>
play hp(noise(), 1000.0)
</pre>

<p></p>

<p>Here is a low-pass filter sweep from 100Hz to 2000Hz:
</p>
<pre>
play lp(noise(), pwl(0.0, 100.0, 1.0, 2000.0, 1.0))
</pre>

<p>
And a high-pass sweep from 50Hz to 4000Hz:
</p>
<pre>
play hp(noise(), pwl(0.0, 50.0, 1.0, 4000.0, 1.0))
</pre>

<p></p>

<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:
</p>
<pre>  
play reson(10.0 * noise(), 500.0, 20.0, 1)
</pre>

<p>
In the next example, the center frequency is swept from 100 to 1000Hz, using a constant 20Hz bandwidth:
</p>
<pre>
play reson(0.04 * noise(),
           pwl(0.0, 200.0, 1.0, 1000.0, 1.0),
           20.0)
</pre>

<p></p>

<p>For another example with explanations, see 
<a name="index178"></a><a name="index179"></a><a name="index180"></a>
<a name="index181"></a>
<code>nyquist/lib/wind/wind_tutorial.htm</code>.</p>
<a name = "50"><h3>DSP in Lisp</h3></a><a name="index182"></a><a name="index183"></a>
<p>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>

<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 = "part17.html#206">Appendix 1: Extending Nyquist</a>. This section
describes the process of writing a new signal processing function in Lisp.</p>

<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>

<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>

<p>Having designed the new &ldquo;kernel&rdquo; 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 = "part17.html#206">Appendix 1: 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>

<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>

<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>
<li>Within the object, the <code>snd-fetch</code> routine is used to fetch samples
from the two input sounds as needed;</li>
<li>The result must be of type <code>SOUND</code>, so <code>snd-fromobject</code> is used
to create the result sound.
</li></ol></p>

<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 &ldquo;fetch&rdquo; 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>

<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>

<p>The XLISP manual (see Appendix <a href = "part19.html#223">Appendix 3: 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.
</p>
<pre>
(setf product-class (send class :new '(s1 s2)))
</pre>

<p>
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>

<p>Next, we will define the <code>:next</code> method for <code>product-class</code>:
</p>
<pre>
(send product-class :answer :next '()
  '((let ((f1 (snd-fetch s1))
          (f2 (snd-fetch s2)))
      (cond ((and f1 f2)
             (* f1 f2))
            (t nil)))))
</pre>

<p>
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>

<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>:
</p>
<pre>
(send product-class :answer :isnew '(p1 p2) 
  '((setf s1 (snd-copy p1))
    (setf s2 (snd-copy p2))))
</pre>

<p>
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>

<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>

<p>Now we are ready to write <code>snd-product</code>, an approximate replacement for
<code>snd-prod</code>:
</p>
<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>

<p>
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>

<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>