29 March 2024

Project 6

Due Nov 18 11:59PM, Peer Grading Due Nov 25 11:59PM

Physical Models and Patterns

In this project, you will explore physical models and pattern generators. Your main composition task is to generate music using physical model functions built into Nyquist. One problem you will encounter immediately is that just as a violin does not play itself, physical models require careful control to create musical sounds. The warm-up exercises are designed to explore physical models and their control.

1. Warm-Up Exercises

Using clarinet-freq (accept no substitutes!), create a sequence of clarinet tones separated by 1s of silence as follows:

  1. Executing F2() should play a 1-second tone at pitch C4 with no vibrato and a breath (roughly the amplitude control) envelope that sounds natural to you.
  2. Executing F3() should play a 2-second tone at pitch D4 with breath envelope vibrato. (See a discussion below on vibrato.) Design a vibrato that you think sounds natural. You will find the clarinet model has a fairly sudden threshold below which there is no oscillation and above which the tone is fairly strong. If breath vibrato causes the breath envelope to cross this threshold, the clarinet tone may pulse on and off. Control vibrato so that this does not happen. (We hope it’s obvious by this time in the course that if you need the breath envelope to oscillate in a vibrato-like manner, you should add to your breath envelope a low-frequency sine, e.g. with lfo, scaled appropriately, and perhaps multiplied by another envelope if you do not want the full lfo amplitude from the beginning. Wikipedia has a fine discussion and sound examples if you need to learn about vibrato, but note that here we are “vibrating” the breath and not using frequency vibrato.)
  3. Executing F4() should play a 5-second tone at pitch E4 that has a slow crescendo. The sound should start with a noisy breathy sounding attack that barely has any pitch. Within 1 second, the pitch should be clear. The crescendo should obviously continue until at least 4 seconds. You will find the clarinet model is very sensitive to the breath envelope and there is a very narrow range of envelope values over which a crescendo takes place. You will find that only a small amount of crescendo is possible with this model. You will need to determine good envelope values experimentally. (See the section below on RMS for more tools.)
  4. Executing F5() should play the 8-second sequence (F4, G4, A4, rest, F4, G4, A4, rest) where elements have IOI’s of 1 second. The first 3 “notes” should be made with one call to clarinet-freq(), using the frequency control to change pitch. (Hint: to get from F4 to G4, the frequency envelope should step up by step-to-hz(G4) - step-to-hz(F4). Hint 2: For the best sound, the frequency control transitions should take around 30ms rather than jumping instantaneously from one pitch to the next.) The second 3 notes should be separate calls to clarinet-freq(). Try to get a continuous sound similar to the first 3 notes by slightly overlapping F4 to G4 to A4. (Hint: Overlapping is easy if you use a score or write expressions where you can explicitly control start times and durations as opposed to using the seq() construct. If you want to use seq(), you can still get overlap if you set the logical stop time of sounds to be 1s but make the actual duration longer. See set-logical-stop in the Nyquist Reference Manual.)
  5. Executing F6() should run one instance of score-gen with pattern generators to create a score named p6score. The score should have 4 contiguous sections. In each section, 20 pitches are generated by randomly selecting a pitch (score event attribute name pitch:) from the scale C4, D4, E4, F4, G4, A4, B4. These 20 pitches are transposed by -12, 0, or 12, the amount being chosen at random. In other words, in each section there will be 20 pitches from the 3rd, 4th or 5th octave. Each note should have a duration and ioi of 0.2 seconds, for a total duration of 0.2 * 20 * 4 = 16 seconds. Finally, the piece should never generate 2 consecutive sections in the same octave. (The :max 1 attribute can be used for each item in make-random to prevent a repeated selection. See below for an example.) F6() should not play p6score: You can use exec score-play(p6score) to play your score, and if you like, define F7() to do this with one mouse click in the IDE. The sound should be incorporated into p6warmup.wav as described in the next paragraph.

Put your code (for all of these tones and sequences) in p6warmup.sal. Concatenate all the sounds with some silence separating them in p6warmup.wav.

2. Composition

Use score-gen with pattern generators to algorithmically compose a piece for physical models. You can use any of the physical models in Nyquist: clarinet, sax, flute, bowed, mandolin, wg-uniform-bar, wg-tuned-bar, wg-glass-harm, wg-tibetan-bowl, modal-bar, and sitar. (Acknowledgment: these unit generators are ported from Synthesis Tool Kit by Perry Cook and Gary Scavone.)

Your piece should have multiple pattern generators, including nested pattern generators to get variation at more than one time scale. For example, if you have a cycle of pitches, you could add an offset to the cycle on each repetition so that you hear the melodic cycle transposed each period. Alternatively, you might generate small random pitch intervals at the small time scale and have the average pitch slowly drift up or down at a larger time scale. You can use make-window and make-repeat to further manipulate streams of data.

You should also have at least two “voices” as opposed to a single melodic line. As always consider panning, reverberation, other effects, and careful mixing to make your piece musical and interesting.

Write a statement about the intention of your composition in p6comp.txt. In p6comp.txt, also describe how you used pattern generators to achieve your results and how you used nested patterns. (Your p6comp.sal should also be commented to make your algorithms understandable.)

Duration should be between 45 and 60 seconds. Hand in the following files:

p6comp.sal – the code.
p6comp.wav – the sound file.
p6comp.txt – a short statement of your intention in the composition.

3. Pattern Examples

To get you started on some advanced pattern generation, here are some interesting examples to study:

  1. This example repeats each value in the cycle 7 times, expanding the cycle to a longer time scale. Without the ” for: 1″ parameter, the whole cycle (one full period) would repeat 7 times (but cycle would repeat the data anyway, so what’s the point?) With the ” for: 1 “, the generated period is only 1 so a period is completed after each number is output, so each number is repeated 7 times.

make-copier(make-cycle({24 36 48 60}, for: 1), repeat: 7)

  1. This example adds two streams. This is one way to get behavior at different time scales:

make-sum(long-term-pattern-generator, short-term-pattern-generator)

  1. This example makes a series starting at 5 and increasing by 2 each time. The make-line({5 2}) pattern returns 5, 2, 2, 2, …, and make-accumulate sums the series to get 5, 7, 9, 11, …

make-accumulate(make-line({5 2}))

When all else fails and you really want a specific computation, you can use make-eval() to invoke a function to compute a stream of numbers. In this example, a custom function, myfunc, does some computation and returns a value to incorporate into a pattern stream. mypat uses make-eval to call myfunc.

define myfunc() return real-random()
set mypat = make-eval({myfunc})

Of course, myfunc() could also access and modify data from another pattern. The simple way to do this is using a global variable since make-eval does not have any way to accept parameters and SAL cannot construct closures.

Hand-In Summary/Check-list

(.wav and .aif files are acceptable)

  • p6warmup.sal — code for Warm-Up Exercises, defines F2(), F3(), F4(), F5(), F6()
  • p6warmup.wav — 5 sounds from Warm-Up Exercises with a bit of silence separating them
  • p6comp.sal — code used for your composition
  • p6comp.wav — composition, between 45 and 60 seconds duration
  • p6comp.txt — a short statement of your intention for the composition

Clarinets and Vibrato

Some might say clarinets should not use vibrato. Eddie Daniels has a nice discussion and demonstration of clarinet vibrato, including breath vibrato created mainly by variations in air pressure, and frequency or lib vibrato, which varies the fundamental frequency while keeping the amplitude and breath pressure more or less constant. See Vibrato On The Clarinet?! with Eddie Daniels.

Using RMS

The RMS function in Nyquist computes the “root mean square” or average power in a signal. Technically, RMS computes the answer to the following question: If instead of an irregular, oscillating signal, I wanted to substitute a single, constant amplitude value and obtain the same overall power, what value would I use? RMS is a good way to estimate an amplitude envelope from a signal.

RMS takes three parameters: (1) the signal to analyze, and (2) the analysis rate, (3) the window size. By default, the analysis rate is 100Hz, meaning that RMS computes an amplitude value for every 10ms (1/100 s) of the signal, and the window size default is 10ms, meaning that each amplitude value corresponds to the average power in that 10ms region of the input signal.

In “warmup” exercise 3 (function F4), you are asked to make a crescendo. You can plot the resulting envelope of the computed signal using something like: plot rms(my-computed-signal). The clarinet-freq model has a narrow range of amplitude variation (unlike a real clarinet), so you might expect to see an RMS envelope like the following:

Random selection without repetition

A very musical variation of random selection is random selection where you never repeat anything from a list of selections. With make-random, you can give weights, control repetitions and even force repetitions. See the manual for details. Here is an example of selecting A, B, or C where there are no immediate repetitions of A or B, but C is allowed to repeat:
set abd-pat = make-random({{A :max 1} {B :max 1} C})
loop repeat 20 exec prin1(next(abc-pat)) end
OUTPUT: BACABACBABABCCACACBC

Notice in the output that AA and BB never occur.