Algorithmic Composition: A Gentle Introduction to Music Composition Using Common LISP and Common Music
Skip other details (including permanent urls, DOI, citation information) :This work is protected by copyright and may be linked to without seeking permission. Permission must be received for subsequent distribution in print or electronically. Please contact : [email protected] for more information.
For more information, read Michigan Publishing's access and usage policy.
Chapter 12: Applicative Programming
12.1 Introduction to Applicative Programming
Applicative programming is a technique that allows a function to be applied to each element of a list. A simple example of applicative programming is to add 2 to every element of a list. For example, the list (0 1 2 4) becomes (2 3 4 6). Applicative programming is a very simple yet powerful way to transform lists.
12.2 Mapping a Function onto a List
The Common LISP primitive MAPCAR allows you to apply a function to each element of a list. The applied function may be another Common LISP primitive or a user-defined function. Essentially, MAPCAR allows you to pass a function to a function. In Common LISP, when you pass a function to a function, the passed function must be proceeded by a #' (sharp-quote). The template for MAPCAR is:
(MAPCAR #'FUNCTION '(LIST))
Recall that the Common LISP primitive SQRT returns the square root of its argument.
Since MAPCAR allows you to map a function onto a list, we can take the square root of each element of a list by passing SQRT to MAPCAR.
Notice the syntax of MAPCAR. The function passed to MAPCAR is preceded by a #'. The argument to the passed function is a quoted list.
Recall in Chapter 9, Example 9.2.1, we wrote a user-defined function that used COND to determine if a midi-note is within the range of the MIDI specification.
Using MAPCAR, we can determine if an entire list of MIDI notes is in range.
Since returning a list that describes the range status of list of MIDI notes may not be helpful in composing music, we alter our function in Example 12.2.3. Any MIDI note outside the range of the MIDI Specification, is recalculated to fall in range. The new function, called RANGIFY, is defined in Example 12.2.5.
The function RANGIFY uses an algorithm that returns 0 for all MIDI notes less than 0 and 127 for all MIDI notes greater than 127.RANGIFY is one of many algorithms that may be used to correct for values that fall outside of the range of the MIDI Specification.
In Example 12.2.6, we map the function RANGIFY onto a list of values. Notice all values less than 0 return 0 and all values greater than 127 return 127.
So far, the functions that we've passed to MAPCAR only have one argument. It is possible to pass MAPCAR functions that have more than one argument. Consider the function definition and function call in Example 12.2.7.
12.3 Lambda Expressions
Sometimes, you may have a procedure that you'd like to call on-the-fly. Such a procedure may be one that is only used once so you don't want to bother giving it a name. Lambda expressions are unnamed or anonymous functions.
The template for a Common LISP lambda expression looks very much like that of DEFUN. Table 12.3.1 compares and contrasts DEFUN and LAMBDA expressions.
Table 12.3.1
Named Function (DEFUN ) | Anonymous Function (LAMBDA ) |
(DEFUN FUNCTION-NAME(OPTIONAL-ARGUMENT-LIST)
FUNCTION DEFINITION))
|
(LAMBDA(OPTIONAL-ARGUMENT-LIST)
FUNCTION DEFINITION))
|
Example 12.3.1
(DEFUN MY-TRANSPOSE(NOTE INTERVAL)
(+ NOTE INTERVAL))
|
Example 12.3.1
(LAMBDA(NOTE INTERVAL)
(+ NOTE INTERVAL))
|
When a LAMBDA expression is encountered, the computer identifies it as an anonymous function as seen in Example 12.3.2.
Example 12.3.2
Recall in Example 12.2.8 we used MAPCAR to transpose a list of MIDI notes by a list of intervals. In Example 12.3.3, we use MAPCAR with a LAMBDA expression to perform the same task.
Example 12.3.4 demonstrates the use of MAPCAR and LAMBDA expressions in Common Music.
(5-4-list '(0 1 2 3 6)))
12.4 List Filtering
Notice that the predicates passed to the primitive are proceeded by #'.
We begin by defining a predicate function that determines whether or not its input is a c-major triad.The predicate function C-MAJORP takes the SET-DIFFERENCE (or set subtraction) between the function input chord and the list '(C E G). If all of the elements of the chord are found in '(C E G),SET-DIFFERENCE returns NIL. We take the NOT of NIL and the predicate function returns T
If some of the elements in the input chord are remaining after set subtraction, SET-DIFFERENCE returns those elements as a list. Recall that any non-NIL value evaluates to T. We take the NOT of a non-NIL value and the predicate function returns NIL.
The Common LISP primitive COUNT-IF returns the number of occurrences a predicate function evaluates to T.
Notice that in Example 12.4.2 the predicate C-MAJORP finds one occurrence of the list '(C E G).
Another Common LISP list-filtering primitive is FIND-IF. As we know from Example 12.4.2, COUNT-IF counts the number of occurrences a predicate evaluates to T.FIND-IF actually returns the elements that return a T evaluation.
The Common LISP primitives FIND-IF and REMOVE-IF-NOT have similar functionality. The result returned by REMOVE-IF-NOT includes another level of parentheses.
The logical complement of FIND-IF is the Common LISP primitive REMOVE-IF.REMOVE-IF returns every object that the predicate evaluates as NIL.
The Common LISP primitive EVERY also expects a predicate and list as input.EVERY returns T if every element mapped to the predicate evaluates to T.
In Example 12.4.7, we use a LAMBDA expression to see if every note in a list is within the range of the MIDI specification.
Example 12.4.7
12.5 Using Applicative Forms in Common Music
In Example 12.5.1 (applicative.lisp), we apply what we've learned about applicative programming in Common LISP to Common Music. We use pc sets 4-4 (0 1 2 5), 4-5 (0 1 2 6), and 4-6 (0 1 2 7) as a means of unifying the pitch content. These three sets are assigned using vars. The three sets are combined to create a nested list. We create a list of all of the elements found in 4-4, 4-5 and 4-6 using APPEND. An amplitude-set is assigned by applying FIND-IF to the nested sets to search for set 4-4 using the user-defined predicate 4-4P. A rhythm set is assigned by applying REMOVE-IF-NOT to the nested sets searching for a set that is not 4-5. We have to take the FIRST of what is returned by REMOVE-IF-NOT because REMOVE-IF-NOT returns a nested list. We assign a duration set from the appended sets. All sets are converted into item streams. The note slot is conditionally assigned depending on how many events have been generated. If the rhythm or duration slot generates a zero value, the rhythm and duration slots are assigned a value of .3.
Example 12.5.1: applicative.lisp