Project 6: Physical Models and Patterns
Project 6 due: Monday, April 7, 2025 by 11:59PM Eastern
Peer grading due: Monday, April 14, 2025 by 11:59 Eastern
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.
Background
It might be helpful to understand the following music terms:
- Vibrato: a musical effect, which consists of regular, pulsating, slight changes in pitch.
- Crescendo: gradually increasing in loudness.
1. Warm-Up Exercises
Using clarinet-freq
(accept no substitutes!), create a sequence of clarinet tones separated by 1-second of silence as follows:
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.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 fulllfo
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.)
- 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
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.)
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 toclarinet-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: 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. Look for “logical-stop” andset-logical-stop
in the Nyquist Reference Manual.
- Hint: to get from F4 to G4, the frequency envelope should step up by “
Executing
F6()
should run one instance ofscore-gen
with pattern generators to create a score namedp6score
. The score should have 4 contiguous sections. In each section, 20 pitches are generated by randomly selecting a pitch (score event attribute namepitch:
) from the scale C4, D4, E4, F4, G4, A4, B4. These 20 pitches are all transposed by the same value, which is randomly selected from -12, 0, or +12. E.g. the first 20 notes may be transposed down an octave, the second group of 20 transposed up an octave, then 20 notes are not transposed, etc. All pitches should end up either in 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 inmake-random
to prevent a repeated selection. See below for an example.)F6()
should not playp6score
: You can useexec score-play(p6score)
to play your score, and if you like, defineF7()
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
.
When p6warmup.wav
is played, resulting sound should consist of the sequence:
sound from F2()
, pause, sound from F3()
, pause, sound from F4()
, pause, sound from F5()
, pause, sound from F6()
.
Tips for Exercises
In general, scaling the breath envelopes down might produce better results
(
F3()
) if your vibrato sounds “choppy”, have you added the breath envelope to thelfo
?(
F4()
) To get the correctrms
shape, a general hint is to increase the envelope fast at the beginning(
F5()
) Read the instructions carefully to make sure that you are callingclarinet-freq
the correct number of times!(
F6()
)clarinet-freq
does not take keyword parameters, a wrapper might be helpfulError: P6SCORE not defined error. Try the following and see if it prints the score as expected:
set p6score = nil
exec F6()
print p6score
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.) In this file, please add consent information for highlighting your composition in class or on the website. Choose True/False for each of the following:
- Share audio: True/False
- Share name: True/False
- Share text for intent and description: True/False
- Share code: True/False
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.
Please ensure that any relevant external sources are cited.
3. Pattern Examples
To get you started on some advanced pattern generation, here are some interesting examples to study:
make-copier(make-heap({24 36 48 60}, for: 1), repeat: 7)
This example repeats each value in the heap 7 times, expanding the cycle to a longer time scale. Without the for: 1
parameter, the whole heap (one full period) would repeat 7 times. With the for: 1
, the generated period is only 1 so a period is completed after each number is output. Each number is repeated 7 times.
- Let’s try this with
make-cycle
instead ofmake-heap
:make-copier(make-cycle({24 36 48 60}, for: 1), repeat: 7)
You might expect the sequence 24, 24, 24, 24, 24, 24, 24, 36, 36, 36, 36, 36, 36, 36, 48, 48, … (i.e. 7 copies of each number in the cycle), but you actually get just 24, 24, 24, … repeating forever. This is because at the end of every period (even a period of 1), the cycle is restarted. This could be useful if you provide a pattern to compute the elements of the cycle. In that case, the next period of the pattern becomes the next cycle. To cycle through our list and repeat each element 7 times, you can use make-length
to re-group the periods of make-cycle
into periods of length 1 to be copied 7 times: make-copier(make-length(make-cycle({24 36 48 60}), 1), repeat: 7)
make-sum(long-term-pattern-generator, short-term-pattern-generator)
This example adds two streams. This is one way to get behavior at different time scales:
make-accumulate(make-line({5 2}))
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, …
When all else fails and you really want a specific computation, you can use make-eval({function-name})
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, definesF2()
,F3()
,F4()
,F5()
,F6()
p6warmup.wav
— 5 sounds from Warm-Up Exercises with a bit of silence separating themp6comp.sal
— code used for your compositionp6comp.wav
— composition, between 45 and 60 seconds durationp6comp.txt
— a short statement of your intention for the composition
Discussion
Here is some material of interest related to this project.
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 lip 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 gives 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.
The Nyquist rms
function takes three parameters: (1) the signal to analyze, and (2) the analysis rate, (3) the window size. By default, the analysis rate is 100 hz, meaning that rms
computes an amplitude value for every 10ms (1/100 s) of the signal, and the window size default is 10 ms, meaning that each amplitude value corresponds to the average power in that 10 ms 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.