File: part5.html

package info (click to toggle)
nyquist 3.24%2Bds-2
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 58,156 kB
  • sloc: ansic: 74,757; lisp: 18,169; java: 10,942; cpp: 6,688; sh: 175; xml: 58; makefile: 40; python: 15
file content (335 lines) | stat: -rw-r--r-- 16,278 bytes parent folder | download | duplicates (3)
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
<!DOCTYPE html>
<html><head><title>Continuous Transformations and Time Warps</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 = "part4.html">Previous Section</a> | <a href = "part6.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 = "38"><h2>Continuous Transformations and Time Warps</h2></a>
<p>Nyquist transformations were discussed in the previous chapter, but all of
the examples used scalar values.  For example, we saw the <code>loud</code>
transformation used to change loudness by a fixed amount.  What if we want
to specify a crescendo, where the loudness changes gradually over time?</p>

<p>It turns out that all transformations can accept signals as well as numbers,
so transformations can be continuous over time.  This raises some
interesting questions about how to interpret continuous transformations.
Should a loudness transformation apply to the internal details of a note or
only affect the initial loudness?  It might seem unnatural for a decaying 
piano note to perform a crescendo.  On the other hand, a sustained
trumpet sound should probably crescendo continuously.  In the case of time
warping (tempo changes), it might be best for a drum roll to maintain a
steady rate, a trill may or may not change rates with tempo, and a run of
sixteenth notes will surely change its rate.</p>

<p>These issues are complex, and Nyquist cannot hope to automatically do the
right thing in all cases.  However, the concept of behavioral abstraction
provides an elegant solution.  Since transformations merely modify the
environment, behaviors are not forced to implement any particular style of
transformation.  Nyquist is designed so that the default transformation is
usually the right one, but it is always possible to override the default
transformation to achieve a particular effect.</p>
<a name = "39"><h3>Simple Transformations</h3></a>
<p>The &ldquo;simple&rdquo; transformations affect some parameter, but have no effect on time itself.  The simple transformations that support continuously changing parameters are: <code>sustain</code>, <code>loud</code>, and <code>transpose</code>.</p>

<p>As a first example, Let us use <code>transpose</code> to create a chromatic scale.
First define a sequence of tones at a steady pitch. The <code>seqrep</code> 
&ldquo;function&rdquo; works like <code>seq</code> except that it creates copies of a sound
by evaluating an expression multiple times. Here, <code>i</code> takes on 16 values
from 0 to 15, and the expression for the sound could potentially use <code>i</code>.
Technically, <code>seqrep</code> is not really a function but an abbreviation for
a special kind of loop construct.
</p>
<pre>
define function tone-seq()
  return seqrep(i, 16,
                osc-note(c4) ~ 0.25)
</pre>

<p>
Now define a linearly increasing ramp to serve as a transposition function:
</p>
<pre>
define function pitch-rise()
  return sustain-abs(1.0, 16 * ramp() ~ 4)
</pre>

<p>
This ramp has a duration of 4 seconds, and over that interval it rises from
0 to 16 (corresponding to the 16 semitones we want to transpose). The ramp
is inside a <code>sustain-abs</code> transformation, which prevents a <code>sustain</code>
transformation from having any effect on the ramp. (One of the drawbacks of
behavioral abstraction is that built-in behaviors sometimes do the wrong
thing implicitly, requiring some explicit correction to turn off the 
unwanted transformation.) Now,
<code>pitch-rise</code> is used to transpose <code>tone-seq</code>:
</p>
<pre>
define function chromatic-scale()
  return transpose(pitch-rise(), tone-seq())
</pre>

<p></p>

<p>Similar transformations can be constructed to change the sustain or &ldquo;duty
factor&rdquo; of notes and their loudness.  The following expression plays the
<code>chromatic-scale</code> behavior with increasing note durations.  The
rhythm is unchanged, but the note length changes from staccato to legato:
</p>
<pre>
play sustain((0.2 + ramp()) ~ 4,
             chromatic-scale())
</pre>

<p>
The resulting sustain function will ramp from 0.2 to 1.2.  A sustain of 1.2
denotes a 20 percent overlap between notes.  The <code>sum</code> has a stretch
factor of 4, so it will extend over the 4 second duration of
<code>chromatic-scale</code>.</p>

<p>If you try this, you will discover that the <code>chromatic-scale</code> no longer
plays a chromatic scale. You will hear the first 4 notes going up in intervals
of 5 semitones (perfect fourths) followed by repeated pitches. What 
is happening is that the <code>sustain</code> operation applies to 
<code>pitch-rise</code> in addition to <code>tone-seq</code>, so now the 4s
ramp from 0 to 16 becomes a 0.8s ramp. To fix this problem, we need to 
shield <code>pitch-rise</code> from the effect of <code>sustain</code> using the
<code>sustain-abs</code> transformation. Here is a corrected version of 
<code>chromatic-scale</code>:
</p>
<pre>
define function chromatic-scale()
  return transpose(sustain-abs(1, pitch-rise()), tone-seq())
</pre>

<p></p>

<p>What do these transformations mean?  How did the system know to produce a
pitch rise rather than a continuous glissando?  This all relates to the idea
of behavioral abstraction.  It is possible to design sounds that <i>do</i>
glissando under the transpose transform, and you can even make sounds that
<i>ignore</i> transpose altogether.  As explained in Chapter 
<a href = "part4.html#27">Behavioral Abstraction</a>, the transformations modify the
environment, and behaviors can reference the environment to determine what
signals to generate.  All built-in functions, such as <code>osc</code>, have a
default behavior.</p>

<p>The default behavior for sound primitives under <code>transpose</code>, 
<code>sustain</code>, and <code>loud</code> transformations is
to sample the environment at the beginning of the
note.  Transposition is not quantized to semitones or any other scale,
but in our example, we arranged for the transposition to work out to integer
numbers of semitones, so we obtained a chromatic scale anyway.</p>

<p>Transposition only applies to the oscillator and sampling primitives
<code>osc</code>, <code>partial</code>, <code>sampler</code>, <code>sine</code>, <code>fmosc</code>,
and <code>amosc</code>.  Sustain applies to <code>osc</code>, <code>env</code>, <code>ramp</code>,
and <code>pwl</code>. (Note that <code>partial</code>, <code>amosc</code>, and 
<code>fmosc</code> get their durations
from the modulation signal, so they may indirectly depend upon the sustain.)
Loud applies to <code>osc</code>, <code>sampler</code>, <code>cue</code>, <code>sound</code>,
<code>fmosc</code>, and <code>amosc</code>. (But not <code>pwl</code> or <code>env</code>.)</p>
<a name = "40"><h3>Time Warps</h3></a>
<p>The most interesting transformations have to do with transforming time
itself.  The <code>warp</code> transformation provides a mapping function from
logical (score) time to real time.  The slope of this function tells us how
many units of real time are covered by one unit of score time.  This is
proportional to 1/tempo.  A higher slope corresponds to a slower tempo.</p>

<p>To demonstrate <code>warp</code>, we will define a time warp function using
<code>pwl</code>:
</p>
<pre>
define function warper()
  return pwl(0.25, .4, .75, .6, 1.0, 1.0, 2.0, 2.0, 2.0)
</pre>

<p>
This function has an initial slope of .4/.25 = 1.6.  It may be easier to
think in reciprocal terms: the initial tempo is .25/.4 = .625.  Between 0.25
and 0.75, the tempo is .5/.2 = 2.5, and from 0.75 to 1.0, the tempo is again
.625.  It is important for warp functions to completely span the interval of
interest (in our case it will be 0 to 1), and it is safest to extend a bit
beyond the interval, so we extend the function on to 2.0 with a
tempo of 1.0.  Next, we stretch and scale the <code>warper</code> function to
cover 4 seconds of score time and 4 seconds of real time:
</p>
<pre>
define function warp4()
  return 4 * warper() ~ 4
</pre>

<p></p>
<hr>
<blockquote></blockquote>)

<img src="fig2.gif"><br><br>

<p><b>Figure 2: </b>The result of <code>(warp4)</code>, intended to map 4 seconds of
score time into 4 seconds of real time.  The function extends beyond 4
seconds (the dashed lines) to make sure the function is well-defined at
location (4, 4).  Nyquist sounds are ordinarily open on the right.

</p>
<hr>
<p>Figure <a href = "#40">2</a> shows a plot of this warp function.  Now, we can
warp the tempo of the <code>tone-seq</code> defined above using <code>warp4</code>:
</p>
<pre>
play warp(warp4(), tone-seq())
</pre>

<p>
Figure <a href = "#40">3</a> shows the result graphically.  Notice that the
durations of the tones are warped as well as their onsets.  Envelopes are 
not shown in detail in the figure.  Because of the way <code>env</code> is
defined, the tones will have constant attack and decay times, and the
sustain will be adjusted to fit the available time.</p>
<hr>
<blockquote>
</blockquote>
<img src="fig3.gif"><br><br>

<p><b>Figure 3: </b>When <code>(warp4)</code> is applied to <code>(tone-seq-2)</code>, the note onsets and durations are warped.

</p>
<hr><a name = "41"><h3>Abstract Time Warps</h3></a>
<p>We have seen a number of examples where the default behavior did the
&ldquo;right thing,&rdquo; making the code straightforward.  This is not always the
case.  Suppose we want to warp the note onsets but not the durations.  We
will first look at an incorrect solution and discuss the error.  Then we
will look at a slightly more complex (but correct) solution.  </p>

<p>The default behavior for most Nyquist built-in functions is to sample the
time warp function at the nominal starting and ending score times of the
primitive.  For many built-in functions, including <code>osc</code>, the starting
logical time is 0 and the ending logical time is 1, so the time warp
function is evaluated at these points to yield real starting and stopping
times, say 15.23 and 16.79.  The difference (e.g. 1.56) becomes the signal
duration, and there is no internal time warping.  The <code>pwl</code> function
behaves a little differently.  Here, each breakpoint is warped individually,
but the resulting function is linear between the breakpoints.</p>

<p>A consequence of the default behavior is that notes stretch when the tempo
slows down.  Returning to our example, recall that we want to warp only the
note onset times and not the duration.  One would think that the following
would work:
</p>
<pre>
define function tone-seq-2 ()
  return seqrep(i, 16,
                osc-note(c4) ~~ 0.25)

play warp(warp4(), tone-seq-2())
</pre>

<p>
Here, we have redefined <code>tone-seq</code>, renaming it to <code>tone-seq-2</code>
and changing the stretch (<code>~</code>) to absolute stretch (<code>~~</code>).  The
absolute stretch should override the warp function and produce a fixed
duration.</p>

<p>If you play the example, you will hear steady
sixteenths and no tempo changes.  What is wrong?  In a sense, the &ldquo;fix&rdquo;
works too well.  Recall that sequences (including <code>seqrep</code>) determine
the starting time of the next note from the logical stop time of the
previous sound in the sequence.  When we forced the stretch to 0.25, we also
forced the logical stop time to 0.25 real seconds from the beginning, so
every note starts 0.25 seconds after the previous one, resulting in a
constant tempo.</p>

<p>Now let us design a proper solution.  The trick is to use absolute
stretch (<code>~~</code>)
as before to control the duration, but to restore the logical stop time to a
value that results in the proper inter-onset time interval:
</p>
<pre>
define function tone-seq-3()
  return seqrep(i, 16,
                set-logical-stop(osc-note(c4) ~~ 0.25, 0.25))

play warp(warp4(), tone-seq-3())
</pre>

<p>
Notice the addition of <code>set-logical-stop</code> enclosing the
absolute stretch (<code>~~</code>) expression to set the logical
 stop time.  A possible
point of confusion here is that the logical stop time is set to 0.25, the
same number given to <code>~~</code>!  How does setting the logical stop
time to 0.25 result in a tempo change?  When used within a <code>warp</code>
transformation, the second argument to <code>set-logical-stop</code> refers to
<i>score</i> time rather than <i>real</i> time.  Therefore, the score duration of
0.25 is warped into real time, producing tempo changes according to the
enviroment.  Figure <a href = "#41">4</a> illustrates the result graphically.</p>
<hr>
<blockquote>
</blockquote>
<img src="fig4.gif"><br><br>

<p><b>Figure 4: </b>When <code>(warp4)</code> is applied
to <code>(tone-seq-3)</code>, the note onsets are warped, but not the duration,
which remains a constant 0.25 seconds.  In the fast middle section, this
causes notes to overlap.  Nyquist will sum (mix) them.

</p>
<hr><a name = "42"><h3>Nested Transformations</h3></a>
<p>Transformations can be nested.  In particular, a simple transformation such
as transpose can be nested within a time warp transformation.  Suppose we
want to warp our chromatic scale example with the <code>warp4</code> time warp
function.  As in the previous section, we will show an erroneous simple
solution followed by a correct one.</p>

<p>The simplest approach to a nested transformation is to simply combine them
and hope for the best:
</p>
<pre>
play warp(warp4(),
          transpose(pitch-rise(), tone-seq()))
</pre>

<p>
This example will not work the way you might expect.  Here is why: the warp
transformation applies to the <code>(pitch-rise)</code> expression, which is
implemented using the <code>ramp</code> function.  The default
behavior of <code>ramp</code> is to interpolate linearly (in real time) between two points.
Thus, the &ldquo;warped&rdquo; <code>ramp</code> function will not truly reflect the internal
details of the intended time warp.  When the notes are moving faster, they
will be closer together in pitch, and the result is not chromatic.
What we need is a way to properly
compose the warp and ramp functions.  If we continuously warp the ramp function
in the same way as the note sequence, a chromatic scale should be obtained.
This will lead to a correct solution.</p>

<p>Here is the modified code to properly warp a transposed sequence.  Note that
the original sequence is used without modification.  The only complication
is producing a properly warped transposition function:
</p>
<pre>
  play warp(warp4(),
            transpose(
              control-warp(get-warp(),
                           warp-abs(nil, pitch-rise())),
              tone-seq()))
</pre>

<p>
To properly warp the <code>pitch-rise</code> transposition function, we use
<code>control-warp</code>, which applies a warp function to a function of score time,
yielding a function of real time.  We need to pass the desired function
to <code>control-warp</code>, so we fetch it  from the environment with
<code>get-warp()</code>.  Finally, since the warping is done here, we want to
shield the <code>pitch-rise</code> expression from further warping, so we enclose
it in <code>warp-abs(nil, ...)</code>.</p>
<i>An aside:</i> This last example illustrates a difficulty in the design of
Nyquist.  To support behavioral abstraction universally, we must rely upon
behaviors to &ldquo;do the right thing.&rdquo;  In this case, we would like the
<code>ramp</code> function to warp continuously according to the environment.  But
this is inefficient and unnecessary in many other cases where <code>ramp</code>
and especially <code>pwl</code> are used.  (<code>pwl</code> warps its breakpoints, but still interpolates linearly between them.)  Also, if the default behavior of
primitives is to warp in a continuous manner, this makes it difficult to
build custom abstract behaviors.  The final vote is not in.<hr>
<a href = "part4.html">Previous Section</a> | <a href = "part6.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>