summaryrefslogtreecommitdiff
path: root/doc/part5.html
diff options
context:
space:
mode:
Diffstat (limited to 'doc/part5.html')
-rw-r--r--doc/part5.html313
1 files changed, 313 insertions, 0 deletions
diff --git a/doc/part5.html b/doc/part5.html
new file mode 100644
index 0000000..5216541
--- /dev/null
+++ b/doc/part5.html
@@ -0,0 +1,313 @@
+<html><head><title>Continuous Transformations and Time Warps</title></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 = "35"><h2>Continuous Transformations and Time Warps</h2></a>
+
+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>
+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>
+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 = "36"><h3>Simple Transformations</h3></a>
+The "simple" 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>
+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>
+"function" 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.
+<pre>
+define function tone-seq()
+ return seqrep(i, 16,
+ osc-note(c4) ~ 0.25)
+</pre>
+
+Now define a linearly increasing ramp to serve as a transposition function:
+<code>
+define function pitch-rise()
+ return sustain-abs(1.0, 16 * ramp() ~ 4)
+</code>
+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>:
+<code>
+define function chromatic-scale()
+ return transpose(pitch-rise(), tone-seq())
+</code>
+<p>
+Similar transformations can be constructed to change the sustain or "duty
+factor" 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:
+<code>
+play sustain((0.2 + ramp()) ~ 4,
+ chromatic-scale())
+</code>
+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>
+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>:
+<code>
+define function chromatic-scale()
+ return transpose(sustain-abs(1, pitch-rise()), tone-seq())
+</code>
+<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#24">"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>
+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>
+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 = "37"><h3>Time Warps</h3></a>
+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>
+To demonstrate <code>warp</code>, we will define a time warp function using
+<code>pwl</code>:
+<pre>
+define function warper()
+ return pwl(0.25, .4, .75, .6, 1.0, 1.0, 2.0, 2.0, 2.0)
+</pre>
+
+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:
+<pre>
+define function warp4()
+ return 4 * warper() ~ 4
+</pre>
+
+<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.
+
+<hr>
+<p>
+Figure <a href = "#37">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>:
+<pre>
+play warp(warp4(), tone-seq())
+</pre>
+
+Figure <a href = "#37">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.
+
+<hr>
+<p>
+<a name = "38"><h3>Abstract Time Warps</h3></a>
+We have seen a number of examples where the default behavior did the
+"right thing," 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>
+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>
+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:
+<pre>
+define function tone-seq-2 ()
+ return seqrep(i, 16,
+ osc-note(c4) ~~ 0.25)
+<p>
+play warp(warp4(), tone-seq-2())
+</pre>
+
+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>
+If you play the example, you will hear steady
+sixteenths and no tempo changes. What is wrong? In a sense, the "fix"
+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>
+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:
+<pre>
+define function tone-seq-3()
+ return seqrep(i, 16,
+ set-logical-stop(osc-note(c4) ~~ 0.25, 0.25))
+<p>
+play warp(warp4(), tone-seq-3())
+</pre>
+
+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 = "#38">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.
+
+<hr>
+<p>
+<a name = "39"><h3>Nested Transformations</h3></a>
+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>
+The simplest approach to a nested transformation is to simply combine them
+and hope for the best:
+<pre>
+play warp(warp4(),
+ transpose(pitch-rise(), tone-seq()))
+</pre>
+
+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 "warped" <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>
+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:
+<pre>
+ play warp(warp4(),
+ transpose(
+ control-warp(get-warp(),
+ warp-abs(nil, pitch-rise())),
+ tone-seq()))
+</pre>
+
+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 "do the right thing." 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.
+<p>
+<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>