• No results found

Nyquist Scores

In document Algorithmic Composition (Page 63-67)

Philosophy of Algorithmic Composition

Chapter 4 Programming and Nyquist

4.3 Nyquist Scores

The examples above define a couple of functions, note and plucked-string, that produce sounds. How are these combined to make mu-sic? Nyquist uses lists to represent scores or note lists. A score is a list of notes or sound events, and a note is a list of three elements: a time, a duration, and a sound expression. Example 4.3.1 contains a score with 3 notes of the C-major scale.

Example 4.3.1: A score

{{0.0 1.0 {note pitch: 60 vel: 100}}

{1.0 1.0 {note pitch: 62 vel: 110}}

{2.0 1.0 {note pitch: 64 vel: 120}}}

Notice the overall form of this example. The score is a list of lists.

The top-level list is a list of notes, or more generally, a list of sound events. The term note is often frowned upon in computer music cir-cles because it implies many traditional assumptions about music. A note implies pitch, rhythm, and instrumentation, whereas a computer music score could just as well contain a recording of footsteps, a conversation, and a four-part chorale as three sound events. Since most of our examples in fact deal with notes, we will generally use that term when it is applicable. In this example, each line contains one note, expressed as a list.

Each list expressing a note starts with a starting time followed by a duration (or stretch factor). These notes start at times 0.0, 1.0, and 2.0. All have a duration of 1.0. Technically, the duration is really a

4.3 Nyquist Scores 49

time-scaling factor that “stretches” the normal duration of the note.

Most notes have a nominal duration of 1, so the time-scaling factor, or stretch factor, becomes the duration, but it is possible for sound events to have any nominal duration.

The third element of each line is the sound expression, which tells how to create a sound. Notice that the sound expression does not follow the SAL syntax for function calls because the expression must be represented within a list structure, not as a textual program. To accomplish this, sound expressions within scores use Lisp syntax, which is convenient because SAL is implemented in Lisp and be-cause Lisp programs are represented by lists. In Lisp notation, the first item in a list is a function name, and the remaining items are pa-rameters to pass to the function. Thus, {note pitch: 60 vel: 100} is Lisp notation for note(pitch: 60, vel: 100). To complete the story, it should be mentioned that while lists in SAL are written with braces {}, lists in Lisp are written with parentheses (), and while keywords in SAL are written with a trailing colon (pitch:), keywords in Lisp are preceded with a colon (:pitch). If you stick to writing programs in SAL, these details should only rarely be visible.

Pitches are given by numbers consistent with MIDI: 60 is middle-C, 61 is C-sharp, and so on. In the example, pitches are middle-middle-C, D, and E. Input values here are indicated by alternating keywords (pitch: and vel:) with values (e.g. 60 and 100).

As a slight extension to the score representation, scores can have an explicit starting time and ending time, indicated by a “pseudo-sound-expression” using the name score-begin-end.

Example 4.3.2: A score with begin and end times {{0.0 0.0 {score-begin-end 0.0 4.0}}

{0.0 1.0 {note pitch: 60 vel: 100}}

{1.0 1.0 {note pitch: 62 vel: 110}}

{2.0 1.0 {note pitch: 64 vel: 120}}}

In this case, the score begins at time 0.0 and ends at 4.0. If this score is spliced onto another one, the second score will start at time 4.0, even though the last note of the first score ends at time 3.

To convert a score into sound, use the score-play function. No-tice that the play command requires an expression that results in a sound, so it cannot be used with a score. The score-play function converts a score to a sound, and since it also plays the sound, we do not want to invoke it with the play command. Instead, we simply evaluate (call) the score-play function using the exec command.

Example 4.3.3: Using score-play exec score-play(

{{0.0 0.0 {score-begin-end 0.0 4.0}}

{0.0 1.0 {note pitch: 60 vel: 100}}

{1.0 1.0 {note pitch: 62 vel: 110}}

{2.0 1.0 {note pitch: 64 vel: 120}}})

When score-play interprets a score, it uses the score to determine the start time and duration of each note. For each note, at the appro-priate time, Nyquist then evaluates the (Lisp syntax) expression to compute a sound. All resulting sounds are added together and played using the play command described earlier.

Nyquist has a special way to express chords. If the pitch:

parameter is a list, the overall event expression is expanded into a set of expressions, one for each element of the pitch: list. Thus {0.0 1.0 {note pitch: {60 67}}} is equivalent to the two notes {0.0 1.0 {note pitch: 60}} and {0.0 1.0 {note pitch:

67}}.

It follows that if the pitch parameter is nil or the empty list, then this is a chord with zero notes and represents nothing (in musical terms, a rest).

Example 4.3.4: score-sort.sal

exec score-play(score-sort(

{{0.0 0.5 {plucked-string pitch: 67 vel: 90 cutoff: 4000}}

{0.5 0.5 {plucked-string pitch: 69 vel: 95 cutoff: 5000}}

{1.0 0.5 {plucked-string pitch: 71 vel: 100 cutoff: 6000}}

{1.5 0.5 {plucked-string pitch: 72 vel: 105 cutoff: 7000}}

{2.0 0.5 {plucked-string pitch: 71 vel: 100 cutoff: 6000}}

{2.5 0.5 {plucked-string pitch: 69 vel: 95 cutoff: 5000}}

{3.0 1.0 {plucked-string pitch: 67 vel: 90 cutoff: 4000}}

{0.0 1.0 {note pitch: 59 vel: 100}}

{1.0 1.0 {note pitch: 55 vel: 100}}

{2.0 1.0 {note pitch: 55 vel: 100}}

{3.0 1.0 {note pitch: 59 vel: 100}}}))

Figure 4.3.1: Score from Example 4.3.4

4.4 Variables 51

Example 4.3.4 contains a short score with two instruments.

score-play requires that scores be sorted, but in this case it is con-venient to group the score notes by instrument. The notes are not sorted in time, but the score-sort function sorts the score into time order. Listen for the changes in the pluck sound due to the changing cutoff frequencies. The score in common music notation is shown in Figure 4.3.1.

4.4 Variables

Lisp symbols can be used to represent values. We have already seen in function definitions how symbols called parameters represent in-put values. It is also possible to associate any value with any symbol.

The symbol is then called a variable.

Example 4.4.1: Variables

SAL> set a = 23 ; set variable A to value 23 SAL> print a ; print the value of A

23

SAL> print a + 5 ; use A in an expression 28

SAL> set a = 7 ; variables can be changed SAL> print a

7

SAL> print a + 5 12

One use of variables is to represent scores. It is easier to type a variable name than to retype an entire score. Notice in Example 4.4.2 that the variable my-score is set once but used several times. (In this example, only user input is shown to save space.)

Variables and functions are different even though both are denoted by symbols. In fact the same symbol can represent both a variable and a function as shown in Example 4.4.3. To access the variable, just write the symbol’s name. A symbol is an expression that evaluates to the current value of the variable denoted by the symbol. To denote the value returned by a call to a function, put a list of input expressions in parentheses after the symbol that names the function. This function call expression is evaluated as follows:

First, the function associated with the symbol is found. Second, the input expressions are evaluated from left to right. Third, the resulting

input values are passed to the function, the function body is evaluated, and a value is returned.

Example 4.4.2: Saving scores in variables in score-variables.sal set my-score =

{{0.0 0.5 {plucked-string pitch: 67 vel: 90 cutoff: 4000}}

{0.5 0.5 {plucked-string pitch: 69 vel: 95 cutoff: 5000}}

{1.0 0.5 {plucked-string pitch: 71 vel: 100 cutoff: 6000}}

{1.5 0.5 {plucked-string pitch: 72 vel: 105 cutoff: 7000}}

{2.0 0.5 {plucked-string pitch: 71 vel: 100 cutoff: 6000}}

{2.5 0.5 {plucked-string pitch: 69 vel: 95 cutoff: 5000}}

{3.0 1.0 {plucked-string pitch: 67 vel: 90 cutoff: 4000}}}

exec score-play(my-score) ; play the score exec score-print(my-score) ; neatly print score

; play at half speed

exec score-play(score-stretch(my-score, 2)) Example 4.4.3: Functions and Variables

SAL> define function foo()

begin ; define foo as a function print "hi there"

return "hi there"

end

SAL> ; define foo as a variable:

set foo = " goodbye "

SAL> exec foo() ; call foo as a function hi there

SAL> print foo ; evaluate foo as a variable goodbye

SAL> ; concatenate strings:

print strcat(foo(), foo, foo()) hi there

hi there

hi there goodbye hi there

In document Algorithmic Composition (Page 63-67)