spobooks bbv9810.0001.001 in

    Chapter 9: Conditionals

    Evaluating data and making decisions is an important part of describing music algorithmically. In this chapter, we will learn how to make decisions using Common LISP functions that are categorized as conditionals . Conditionals choose an action based on an evaluation. Using Common LISP conditionals, we can write Common Music programs that create music based on a condition or set of conditions.

    9.1 IF

    IF is a Common LISP function that is followed by a test clause that evaluates to T or NIL. When the test-clause evaluates to T, the T-consequent clause is evaluated. When the test-clause evaluates to NIL, an optional NIL-consequent clause is evaluated. If the NIL-consequent clause is omitted, the program continues with the next Common LISP form.

    The template for IF is:
    (IF <TEST-CLAUSE> <T-CONSEQUENT> <NIL-CONSEQUENT>)

    In Example 9.1.1, we use SETF at the Common LISP ? prompt to assign a global variable *MIDI-NOTE* a value of 60. We would like to assign a global variable *VELOCITY* a value of .9 if *MIDI-NOTE* is less than 60. Otherwise, we will assign *VELOCITY* a value of .5. Example 9.1.1 illustrates the conditional assignment of the variable *VELOCITY*.

    Example 9.1.1:

    ? (setf *midi-note* 60)
    60
    ? (if (< *midi-note* 60) (setf *velocity* .9) (setf *velocity* .5))
    .5

    In Example 9.1.1, the test-clause (< *MIDI-NOTE* 60) uses the relational operator < to see if the current value of *MIDI-NOTE* is less than 60. Because *MIDI-NOTE* was assigned a value of 60, the test-clause evaluates to NIL and the NIL-consequent clause is evaluated. The evaluation of the NIL-consequent clause assigns .5 to *VELOCITY*.

    IF may be nested to make more complex decisions. The nesting of IF is accomplished when another IF takes the place of a NIL-consequent clause.

    The template for a nested IF is:
    (IF <TEST-CLAUSE> <T-CONSEQUENT>
    (IF <TEST-CLAUSE> <T-CONSEQUENT> <NIL-CONSEQUENT>

    In Example 9.1.2, we write a Common LISP function TEST-RANGE that uses a nested IF to determine if the variable A-NOTE is within the range of the MIDI Specification.

    Example 9.1.2
    (defun test-range (a-note)
    (if (< a-note 0) 'too-low
    (if (> a-note 127) 'too-high 'in-range)))

    Given a midi-note input of -5, the function TEST-RANGE evaluates the test-clause "is -5 less than 0?" The test-clause evaluates to T and the quoted object 'TOO-LOW is returned by the function.

    Given a midi-note input of 129, the function TEST-RANGE evaluates the test-clause "is 129 less than 0?" The test-clause evaluates to NIL and program control transfers to the next test clause "is 129 greater than 127?" The test-clause evaluates to T and the quoted object 'TOO-HIGH is returned by the function.

    Given an input of 60, the function TEST-RANGE evaluates the test-clause "is 60 less than 0?" The test-clause evaluates to NIL and program control transfers to the next test clause "is 60 greater than 127?" The test-clause evaluates to NIL and the quoted object 'IN-RANGE is returned by the function.

    9.2 COND

    COND is a Common LISP macro used for multiple test-consequent clauses.COND is comparable to a nested IF in that it allows the programmer to establish multiple test-consequent pairs.

    COND takes the general form:

    (COND (<TEST-1> <CONSEQUENT-1>)
    (<TEST-2> <CONSEQUENT-2>)
    .
    .
    .
    (<TEST-N> <CONSEQUENT-N>))

    Quite often, the last test clause of COND is simply T that allows the last consequent clause to be used as a default action should no other test clauses be true. Once COND finds a test clause that evaluates to T, program control transfers to the next Common LISP form.

    In Example 9.2.1, we write a Common LISP function TEST-RANGE-WITH-COND that uses COND to determine if a MIDI-NOTE as input to the function is within the range of the MIDI Specification. Example 9.2.1 is comparable to Example 9.1.2 that uses a nested IF.

    Example 9.2.1

    (defun test-range-with-cond (midi-note)
    (cond ((< midi-note 0) 'too-low)
    ((> midi-note 127) 'too-high)
    (T 'in-range)))

    Given a midi-note input of -5, the function TEST-RANGE-WITH-COND evaluates the first test clause "is -5 less than 0?" The test clause evaluates to T and the quoted object TOO-LOW is returned by the function.

    Given a midi-note input of 129, the function TEST-RANGE-WITH-COND evaluates the first test clause "is 129 less than 0?" The test clause evaluates to NIL and program control transfers to the next test clause "is 129 greater than 127?" The test clause evaluates to T and the quoted object TOO-HIGH is returned by the function.

    Given an input of 60, the function TEST-RANGE-WITH-COND evaluates the test clause "is 60 less than 0?" The test clause evaluates to NIL and program control transfers to the next test clause "is 60 greater than 127?" The test clause evaluates to NIL and program control transfers to the next test clause. T evaluates to itself and the quoted object IN-RANGE is returned by the function.

    9.3 CASE

    CASE is a Common LISP macro that implements multiple test-consequent clauses.CASE is similar to COND but CASE evaluates a key form and allows multiple consequent clauses based on the evaluation of that key form.

    The template for CASE is:
    (CASE <KEY-FORM>
    (<KEY-1> <CONSEQUENT-1A> <CONSEQUENT-1B> . . .<CONSEQUENT 1Z>)
    (<KEY-2> <CONSEQUENT-2A> <CONSEQUENT-2B> . . .<CONSEQUENT 2Z>)
    .
    .
    .
    (<KEY-N> <CONSEQUENT-NA> <CONSEQUENT-NB> . . .<CONSEQUENT-NZ>))

    In Example 9.3.1, we use CASE to write a function MIDI-RANGE-WITH-CASE that assigns a value of 1 if a midi-note number is lower than the MIDI Specification (0-127), 2 if the midi-note number is higher than the MIDI Specification, and 3 if the midi-note number is in range. We return a quoted object indicating the status of the evaluation. Compare Example 9.3.1 with Examples 9.1.2 and 9.2.1.

    Example 9.3.1

    (defun midi-range-with-case (midi-note)
    (let ((result (if (< midi-note 0) 1
    (if (> midi-note 127) 2 3))))
    (case result

    (1 'too-low)

    (2 'too-high)

    (3 'in-range))))

    First, MIDI-RANGE-WITH-CASE enters the body of a LET. The value returned by the nested IF is assigned to the variable RESULT. If the input is less than 0, the variable RESULT is assigned a value of 1. If the input is greater than 127, the variable RESULT is assigned a value of 2. If both conditions evaluate to NIL, RESULT is assigned a value of 3. The variable RESULT is used as the key-form for the CASE returning a quoted object that corresponds to value of RESULT.

    9.4 Common Music's Conditionals

    Common Music has conditionals that monitor the status of an algorithmic composition. The Common Music macros when-resting, unless-resting, when-chording, unless-chording, when-ending, and unless-ending check the status of certain aspects of an algorithmic composition. Recall the Common LISP macros WHEN and UNLESS from Chapter 6.WHEN evaluates its consequent-clause if the condition evaluates to T.UNLESS evaluates its consequent-clause if the condition evaluates to NIL. As you might guess, when-resting, when-chording, and when-ending evaluate a consequent clause when Common Music is making a rest, generating a chord, or is executing the last event of a container.unless-resting, unless-chording, and unless-ending are the logical complements of these macros.

    Example 9.4.1 is an example of the Common Music macro unless-resting. If Common Music is not generating a rest, the amplitude, rhythm, and duration slots are assigned.

    Example 9.4.1: unless-resting.lisp

    (generator unless-resting midi-note (length 6 channel 0)
    (setf note (item (notes c4 d4 fs5 r r in random)))
    (unless-resting
    (setf amplitude (item (items .4 .5 .6))))
    (setf rhythm amplitude)
    (setf duration (+ rhythm .5)))
    Stella [Unless-Resting]: list
    Unless-Resting:
    1. #<MIDI-NOTE | D4| 0.400| 0.900| 0.400| 0|>
    2. #<REST 0.4#x4BA86CE>
    3. #<REST 0.4#x4BA8716>
    4. #<MIDI-NOTE |FS5| 0.500| 1.000| 0.500| 0|>
    5. #<MIDI-NOTE |FS5| 0.600| 1.100| 0.600| 0|>

    6. #<MIDI-NOTE | C4| 0.400| 0.900| 0.400| 0|>

    Example 9.4.2 demonstrates the Common Music macro unless-chording. If Common Music is not generating a chord, the amplitude slot is assigned.Example 9.4.2: unless-chording.lisp
    (generator unless-chording midi-note (length 6 channel 0)
    (setf note (item (notes [c4 e g] [g4 b d5] r)))
    (setf rhythm .65)
    (setf duration .5)
    (unless-chording
    (setf amplitude (item (items .6 .7)))))
    The Common Music function status? operates behind the scenes of the Common Music macros we just discussed to see if a container is resting, chording, or ending. The template for status? is:
    (status? state)

    state is a keyword argument that describes the status. Some of the possible states are :resting, :chording, or :ending. Example 9.4.3 uses the Common Music function status? to FORMAT if Common Music is generating a chord.

    Example 9.4.3: status.lisp

    (algorithm status midi-note (length 6)
    (setf note (item (notes [c4 d] e r [fs gs])))
    (setf amplitude (item (items .1 .2 .3 .4)))
    (setf rhythm .5)
    (setf duration amplitude)
    (format t "~&note = ~s time = ~s status = ~s" note time (status? :chording)))
    Stella [Status]: mix
    Mix objects: (<cr>=Status)
    Start time offset:(<cr>=None)
    note = C4 time = 0 status = NIL
    note = D4 time = 0 status = T
    note = E4 time = 0.5 status = NIL
    note = R time = 1.0 status = NIL
    note = FS4 time = 1.5 status = NIL
    note = GS4 time = 1.5 status = T
    note = C4 time = 2.0 status = NIL
    note = D4 time = 2.0 status = T
    note = E4 time = 2.5 status = NIL

    9.5 Using Conditionals in Algorithmic Composition

    Conditionals offer a powerful way to delineate form and sculpt musical events in the realization of a compositional algorithm. In Example 9.5.1, we create a musical gesture of 99 note events that change their pitch content during each third of the container. To achieve continuity, we maintain the same intervallic distances between the pitches in each set. We use a pitch set that follows the intervallic succession: M2 M2 m3 M2 M2. To achieve variety, we transpose the pitch set up a M2 for the second third of the container and down a M2 for the last third of the container.

    Example 9.5.1: cond.lisp

    (generator cond midi-note (channel 0 length 99)
    (print count)
    (cond ((< count 33) (setf note (item (notes c4 d4 g4 a4 in heap))))
    ((and (>= count 33) (<= count 65)) (setf note (item (notes d4 e4 g4 a4 in heap))))
    (t (setf note (item (notes bf3 c4 ef4 f4 in heap)))))
    (if (< count 49) (setf amplitude (item (items .1 .3 .5 in heap))) (setf amplitude (item (items .3 .5 .7))))
    (setf rhythm (item (rhythms s s. e in heap tempo 160)))

    (setf duration rhythm))

    The container cond is initialized so that each note event is output on MIDI channel 0 and there are 99 note events. A COND is used to test the value of the Common Music variable count which increments from 0 to 98. The total number of events is divided into three test clauses for COND. In the first COND clause, if count is less than 33, the note slot will be assigned a value from the pitch set c4 d4 g4 a4 using the heap pattern type. In the second COND clause, the note slot will be assigned based on the same pitch set but transposed up a major second. The third COND clause implements the default case and assigns the note slot based on the same pitch set but transposed down a major second from the original set.

    An IF is used to determine the set of amplitudes for any particular note event. The value of the variable count is tested and if we are in the first half of the container, an amplitude from the set .1 .3 .5 is assigned. If we are in the second half of the container, an amplitude from the set .3 .5 .7 is assigned. This algorithm allows the musical material to grow in amplitude as the number of note events increase.

    In Example 9.5.2, we create a musical gesture for a duration of ten seconds that changes pitch content and amplitude during each second of the container.

    audio file cond.mp3

    Example 9.5.2: case.lisp

    (generator case midi-note (start 0 end 10 channel 0 duration .2 rhythm .2)
    (case (round time)
    (0 (setf note (item (notes c4 d e in heap)))
    (setf amplitude (item (amplitudes pp p mp))))
    (1 (setf note (item (notes c4 d e in heap)))
    (setf amplitude (item (amplitudes pp p mp))))
    (2 (setf note (item (notes cs4 ds f in heap)))
    (setf amplitude (item (amplitudes p mp mf))))
    (3 (setf note (item (notes d4 e fs in heap)))
    (setf amplitude (item (amplitudes p mp mf))))
    (4 (setf note (item (notes ds4 f g in heap)))
    (setf amplitude (item (amplitudes p mp mf))))
    (5 (setf note (item (notes e4 fs gs in heap)))
    (setf amplitude (item (amplitudes mp mf f))))
    (6 (setf note (item (notes f4 g a in heap)))
    (setf amplitude (item (amplitudes mp mf f))))
    (7 (setf note (item (notes fs4 gs as in heap)))
    (setf amplitude (item (amplitudes mp mf f))))
    (8 (setf note (item (notes g4 a b in heap)))
    (setf amplitude (item (amplitudes mf f ff))))
    (9 (setf note (item (notes gs4 as c5 in heap)))
    (setf amplitude (item (amplitudes mf f ff))))
    (t (setf note (item (notes a4 b cs5 in heap)))
    (setf amplitude (item (amplitudes mf f ff))))))

    audio file case.mp3

    The generator case is assigned container initializations for start and end. Additionally, channel, duration, and rhythm slots are assigned static values in the container initialization list. The key-form of the CASE is obtained by ROUND ing the value of the variable time to achieve an integer that is used as the key for CASE. Each key in the CASE has two clauses to be evaluated that result in an assignment to the note and amplitude slots. In this particular example, the resultant music creates a gradually rising chromatic figure that increases in pitch and loudness as time progresses.

    9.6 Suggesting Listening

    Matices Coincidentes (converging colors) composed by Pablo Furman was inspired by the use of perspective in art and architectural design. Pablo Furman explores the convergence and blending of tone colors using electronics. [Furman, 1998]