Generating discrete random values using an expression like (random 88) is called
sampling. Sampling can be performed two different ways. Sampling with replacement means that once a value has been sampled it is still available to be sampled again. For example, if key number 60 is generated by (random 88) then
60 can be still be selected as a possible next value. Sampling without replacement describes a process in which generated values are not returned to the population once they have been selected. This is similar to how cards are dealt from a shuffled deck. By using sampling without replacement, a composer randomly reorders a set of specific values whose relationships are otherwise fixed. This reordering can be perceived as a kind of “freshness” in the data. The function
shuffle can be used to implement sampling without replacement. Shuffle
randomly permutes a list of values, which can then be sampled one at a time to produce a random ordering without repetition. Since shuffle operates on list positions rather than values, the list can hold any type of data.
Example 18-5. Sampling without replacement.
(define (sampling reps rate notes ) (let ((len (length notes)))
(process for i below reps for j = (mod i len)
when (= j 0) ; need to reshuffle?
set notes = (shuffle notes) output (new midi :time (now)
:keynum (list-ref notes j) :duration (* rate 1.5)
:amplitude (interp j 0 .4 (- len 1) .8)) wait rate)))
Our sampling process also produces a crescendo that starts over again each time
the values are shuffled so that each “period” in the population can be easily heard. Note that while shuffling avoids direct repetition within the period, there may still be a direct repetition between the last note of a period and the first note of the next.
Interaction 18-8. Calling shuffle.
cm> (events (sampling 80 .1 '(c d ef f g a bf c5)) "shuffle.mid")
"shuffle-1.mid" cm>
→ shuffle-1.mid
Direct reselection can be an issue anytime discrete random selection is used. When the range of random values is sufficiently large direct repetition may be so infrequent that it does not have a noticeable effect on the results. But as the number of events in a discrete process diminishes the likelihood of direct reselection increases. At some point this may cause an unwanted regularity in musical results. One way to deal with this is to use the optional omit argument to the between function to avoid direct reselection. An omit value will not be returned
by between. By specifying the last output value as the omit value for the next
random number, direct reselection will not occur. The following process can be used experiment with the effects of allowing or inhibiting direct reselection.
Example 18-6. Avoiding direct repetition in discrete distributions.
(define (reps len lb ub skip?) (process repeat len
for l = (if skip? k false) for k = (between lb ub l) output (new midi time (now) :keynum k
:duration .2) wait .125))
If skip? is true then the variable l is set to k, the last value returned by between. In
this case l is not false, so when it passed as the omit value to between direct
repetition of l cannot occur. If skip? is false then l will be false no matter what the value of kis and direct selection is then possible.
Interaction 18-9. Random selection with and without direct repetition.
cm> (events (reps 30 60 63 false) "reps.mid") "reps-1.mid"
cm> (events (reps 30 60 63 true) "reps.mid") "reps-2.mid"
cm>
→ reps-1.mid
→ reps-2.mid
Variance
Continuous selection is commonly used to provide variance in scaling and offsetting procedures. For example, by scaling a regular tempo or pulse value by a small random percentage the actual rate of events will fluctuate in time and produce a more natural effect than a fixed rate would. If the percentage variance is small enough the fluctuations will not affect the perception of an underlying structural pulse. The process definition in Example 18-7 can be used to experiment with random percentage variance applied to the pulse, amplitude and duration of events.
Example 18-7. The tapping process.
(define (tapping reps pct rate key amp) (let ((half (/ reps 2)))
(process for i below reps
for v = (if (< i half) 0 pct) output (new midi :time (now)
:duration (vary .1 v ) :keynum key
:amplitude (vary amp v :below)) wait (vary rate v))))
The tapping process plays a single key number reps times at a specified rate and
amplitude. The events in the first half of the process are played with fixed values and in the second half the values are allowed to vary by up to pct amount of their values. By default the function vary distributes variance equally on either side of
the fixed value. To produce variance less than the value specify :below as the third
argument to vary. Similarly, the keyword :above will offset the variance above the
Interaction 18-10. Comparing 10% variance with 50% variance.
cm> (events (tapping 50 .1 .2 72 .6) "tap.mid") "tap-1.mid"
cm> (events (tapping 50 .5 .2 72 .6) "tap.mid") "tap-2.mid"
cm>
→ tap-1.mid
→ tap-2.mid
Even something as simple as random variation of a beat can be made musically interesting if it is controlled in some manner. In the next example the amount of variation applied to the beat is controlled by an envelope. When the envelope is at 0 then no variation occurs, when the envelope is at 1 then full variance is expressed. By using an envelope to control random variance the process can interpolate over the full continuum between completely random time increments and a strictly measured pulse. The same envelope that controls the beat will also be used to provide a value for the a and b beta distribution parameters that generates amplitude values for the process. When the control envelope is at zero (no random variance) then the beta distribution will be a=b=.4 and produce a large percentage of loud and soft values (Figure 18-7). When the envelope is at 1 then the beta distribution will be a=b=1 and produce values in the uniform distribution. When the control envelope is at zero (no random variance) then the beta distribution will be at (beta a=b=1) and produce values in the uniform distribution. When the envelope is at 1 (full random variance) then a=b=.4) and a large percentage of loud and soft values are produced. The shape of the envelope defined in Example 18-8 causes the process to begin and end with random time values and express the strict pulse in the middle.
Example 18-8. The rain process.
(define (rain len rate env key1 key2 amp) (process for i below len
for e = (interpl (/ i len) env) for v = (vary rate e :above)
;; rescale e to control shape of beta distribution
;; when e=0 z=.4, and when e=1 z=1.
for z = (rescale e 0 1 1 .4)
for b = (ran :type:beta:a z :b z) output (new midi :time (+ (now) v) :duration v
:keynum (between key1 key2)
:amplitude (rescale b 0 1 .1 amp)) wait rate))
(definerain-env '(0 1 .4 0 .6 0 1 1))
We listen to three simultaneous versions of the rain process, each with different
length beats.
cm> (events (list (rain 80 .5 rain-env 70 90 .8) (rain 40 1 rain-env 40 60 .8) (rain 20 2 rain-env 20 40 .9)) "rain.mid") "rain-1.mid" cm> → rain-1.mid
Shape-based composition
By applying a random variance to envelope values we can generate random variations on the basic shape of the envelope:
Example 18-9. Adding a random variance to envelope values.
(define (shaper len rate env key1 key2) (process for i below len
for e = (interpl (/ i len) env ) for k = (rescale e 0 1 key1 key2) for r = (between -5 5)
output (new midi :time (now) :keynum (+ k r) :duration rate) wait (* rate (pick .5 .25 1)))) (defineshape-env
'(0 0 .2 1 .4 .25 .6 .5 .8 0 1 .5))
The process in Example 18-9 scales and offsets values from an envelope to generate a range of key numbers specified to the process. A random amount is then added to the the key number to shift it upwards or downwards.
Interaction 18-12. Listening to two variations of the same envelope.
cm> (events (list (shaper 40 1 shape-env 70 90) (shaper 40 1 shape-env 60 80) (shaper 20 2 shape-env 60 48)) "shape.mid")
"shape-1.mid"
cm> (events (list (shaper 40 1 shape-env 70 90) (shaper 40 1 shape-env 60 80) (shaper 20 2 shape-env 60 48)) "shape.mid") "shape-2.mid" cm> → shape-1.mid → shape-2.mid
Tendency Masks
A tendency mask is a technique related to the shape-based approach first described by the composer Gottfried Michael Koenig. A tendency mask is created using two envelopes to specify the lower and upper boundaries for a random selection. By describing each boundary as an envelope the range of the random variable can vary as a function of some dynamic process. Common Music's
tendency function returns a random value given an x lookup value, and a lower and
upper envelope that define the boundaries.
Example 18-10. Tendency masking.
(definelow-mask '(0 .4 .75 .4 1 0)) (definetop-mask '(0 1 .25 .6 1 .6))
(define (masker len rate lower upper key1 key2) (let ((last (- len 1)))
(process for i to last
for e = (tendency (/ i last) lower upper) for k = (rescale e 0 1 key1 key2)
output (new midi :time (now) :duration rate :keynum k) wait rate)))
In this example two envelopes lower and upper control the range of random selection. The variable e is set to a random value between an interpolated lower and upper bounds in the two envelopes specified to tendency This random value,
which lies between zero and one, is then rescaled to lie between the key numbers key1 and key2 passed as arguments to the process.
Interaction 18-13. Listening to masker.
cm> (events (masker 80 .1 low-mask top-mask 20 110) "masker.mid")
"masker-1.mid" cm>
→ masker-1.mid