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 8: Variable Assignment and Scoping
This chapter introduces you to how to assign and reference variables in Common LISP and Common Music. You will become familiar with several more Common LISP functions and the concept of the scope of a variable.
8.1 SETF
In Chapter 4, we used the Common LISP macro SETF to assign a value to the Common Music global variable *standard-tempo*. We viewed the scope of the variable *standard-tempo* as global because its value is referenced by Common Music when calculating relative rhythms and durations in containers. The scope of a variable is the region in which a variable's value is known.
The Common LISP macro SETF template is:
(SETF VARIABLE-1 VALUE-1 VARIABLE-2 VALUE-2VARIABLE-N VALUE-N)
For example, (SETF A 1 B 2 C 3) will assign the variable A the value of 1, B the value of 2, and C the value of 3.
Example 8.1.1 shows the variable *standard-tempo* evaluates to 60.0. We define a Common LISP function DOUBLE-THE-TEMPO that doubles a tempo using the variable TEMPO in the function's argument list. The body of the function returns twice its input. When we call the function with an input of *standard-tempo*, the doubled value of *standard-tempo* 120.0 is returned. A query of the value of *standard-tempo* indicates that the global variable has not been reassigned. A query of the value of TEMPO indicates that the symbol tempo has no value. What does this mean?
Example 8.1.1 demonstrates some of the differences between local and global variables. The variable TEMPO in the function DOUBLE-THE-TEMPO is a local variable.TEMPO is considered a local variable because its value is known only within the scope of its function. We demonstrate the scope of TEMPO by calling the function DOUBLE-THE-TEMPO. We affirm that TEMPO is local to the function DOUBLE-THE-TEMPO because Common Music knows nothing of its value.
When we call the function DOUBLE-THE-TEMPO with an input of *standard-tempo*, the function returns the doubled value of the global variable *standard-tempo*. A subsequent query of the variable *standard-tempo* indicates its value is unchanged. The global variable *standard-tempo* is unchanged because it was not explicitly reassigned using SETF.
Example 8.1.2 uses SETF in the body of the function definition to reassign the value of *standard-tempo*. A function call demonstrates that SETF reassigns the value of the global variable *standard-tempo*.
DOUBLE-THE-TEMPO
In Example 8.1.2, we use SETF in the body of the function definition to reassign the global variable *standard-tempo*.
In Example 8.1.3, the second input to SETF is a function call to DOUBLE-THE-TEMPO, WHICH again doubles the tempo.
In Chapter 5, we used SETF to assign values to the slots of midi-notes.SETF is also used to write values to the slots of a class. We should be careful not to confuse assigning values to the slots of a class with assigning values to global variables. The following example demonstrates that Common Music uses SETF to assign values to slots and that those values are local to the container that holds those objects.
Notice in Example 8.1.4, the rhythm slot was calculated based on the current value of *standard-tempo* which is 240 bpm.
8.2 LET and LET*
So far, we've created local variables in the argument list of a user-defined function. You may also use the Common LISP function LET to create local variables.LET assigns local variables as variable value pairs.
Examples 8.2.1 through 8.2.3 may be evaluated in either Common LISP or Common Music.
In Example 8.2.1, the user-defined function AVERAGE-OF-THREE uses LET to calculate the average of three numbers passed as arguments to the function.
We enter the body of the function with three arguments. A LET is used to assign the local variable SUM the sum of the three arguments.LIST is used to present the evaluation as a list by combining the quoted symbols THE, AVERAGE, OF and IS with the evaluation of (/ SUM 3.0).
LET is an interesting function because the variable value assignments occur simultaneously rather than sequentially. Example 8.2.2 illustrates this point. The user-defined function MORE-AVERAGING takes the average of a list of three numbers and two random numbers generated based on an exclusive upper-bound entered at the function call. The local variables SUM-OF-NUMBERS and SUM-OF-RANDOM-NUMBERS are assigned at the same time.
Example 8.2.2: more-averaging.lisp
? (more-averaging 1 2.5 4 1.0)
(AVERAGE-OF-NUMBERS 2.5 AND AVERAGE-OF-RANDOM-NUMBERS 0.6019732842258612)
Suppose you want to calculate a running average. That is, you average two numbers, and add the third number to that average and recalculate the average. In order to complete such a calculation, the variables must be assigned sequentially. The Common LISP function LET* allows you to assign local variables sequentially. The template for LET* is :
(LET* ((VARIABLE-1 VALUE-1)
(VARIABLE-2 VALUE-2)
(VARIABLE-N VALUE-N))
(BODY-OF-LET))
Example 8.2.3 implements a running average function using LET*.
? (running-average 1 2.5 4)
(RUNNING AVERAGE IS 2.875)
8.3 Common Music's Local Variables
Before beginning assignment of local variables in Common Music, let's first learn about some of Common Music's local variables:count and time.
count is a Common Music variable that is local to a generator or algorithm. The variable count is incremented for each note event of a container and ranges in value from 0 to the length -1. For example, if a container has a length of five midi-notes, count ranges in value from 0 to 4.
time is a Common Music variable that is also local to a generator or algorithm. The variable time is incremented based on the rhythm of each note or rest event.time begins at 0 and increases by seconds expressed as a floating point value from the start of the container.
Example 8.3.1 illustrates the Common Music variables count and time by creating a generator that monitors their changing values using FORMAT.
Example 8.3.1: monitoring-count-and-time.lisp
In Example 8.3.1, we initialize the generator to have a length of 5 notes and end at 3 seconds. Notice that the length initialization has higher precedence than the end container initialization since we stop after five notes, long before three seconds have elapsed.
How can you integrate local variables into Common Music's containers? Examples 8.3.2-8.3.3 use LET and LET * to calculate and assign local variables. The slot assignments occur in the body of the LET or LET*.
In Example 8.3.2, we use the local variable count to assist in calculating the value of the amplitude slot. We calculate the amplitude by incrementing count by one and dividing that sum by 5.3. Notice that the amplitude slot assignment occurs in the body of the LET. To help us keep track of the value of the local variable count, we use the Common LISP primitive PRINT.
Example 8.3.2: let-example.lisp
Example 8.3.3 uses LET* to calculate values for the rhythm and duration slots. The duration slot is calculated by dividing the sum of count plus one and 2 raised to the power of count. The Common LISP primitive EXPT is used for exponentiation.
Example 8.3.3: let*-example.lisp
(setf rhythm (float rhy))))
Start time offset:(<cr>=None)
8.4 The Common Music vars Declaration
vars is a Common Music declaration that creates and assign variables that are local to a particular container.vars takes the general form:
Like LET*, vars assigns the variable-value pairs sequentially. Notice that vars does not require an additional pair of parentheses surrounding the variable-value pairs as LET and LET * do.vars assigns the values to the variables once when the container is scheduled to run.
Example 8.4.1 is a simple that uses vars. Two local variables, note-value and amplitude-value, are created and assigned when the container is mixed. The values of the local variables are assigned to their respective slots. Notice that the slots are assigned outside of the vars declaration in contrast to Examples 8.3.2 and 8.3.3 using LET and LET*.
Example 8.4.1: vars.lisp
Refer to Section 8.8 for further examples on the use of vars.
8.5 Assigning Local Variables Interactively using the Computer Keyboard
In Section 7.4, we learned how Common LISP accepts data from the computer keyboard using the function READ. We can use READ in conjunction with LET and LET * to interactively assign local variables.
Example 8.5.1 is a Common LISP user-defined function, SIMPLE-ADD, that accepts two numbers from the computer keyboard and assigns those numbers to local variables using the Common LISP function LET.SIMPLE-ADD returns the sum of the two numbers.
Stella [Top-Level]: (simple-add)
PLEASE ENTER A NUMBER 6
Example 8.5.2 uses LET and READ in a Common Music generator. We use LET to assign the input from READ to the local variables THE-NOTE and THE-AMPLITUDE. We assign the values of these local variables to their respective slots.
Example 8.5.2: interactive-assign.lisp
8.6 Understanding Variable Scope in Common LISP
The scope of a variable is the region in which its value is known. We have seen variables that have both local and global scope.
A Common LISP form that assigns a variable creates a lexical closure .SETF, DEFUN, and LET are Common LISP forms that create lexical closures.
A lexical closure determines the scope of a variable. Assigning a variable using SETF at the interpreter prompt creates a lexical closure for a global variable. Assigning a variable using LET creates a lexical closure for a local variable. The value of a local variable is not known outside of its lexical context.
Examples 8.6.1 through Example 8.6.11 are entered at the Common LISP ? prompt.
Example 8.6.1 demonstrates the variable A is unassigned by generating an unbound variable error.
In Example 8.6.2, we assign the variable A the value of 1 using SETF at the Common LISP ? prompt. Using SETF at the Common LISP ? prompt or Common Music's Top-Level creates a lexical closure for a global variable. Notice that the variable A is not surrounded by asterisks. The asterisks do not make a variable a global variable. The asterisks surrounding a global variable name are a programming convention so the programmer can readily see the scope of a variable. The way a variable is assigned determines its scope.
Example 8.6.2
We can query the value of the variable A by typing its name at the Common LISP ? prompt as seen in Example 8.6.3.
The Common LISP macros INCF and DECF increment and decrement a variable, respectively.
In the case of Example 8.6.4, INCF and DECF increment and decrement the global variable A.
In Example 8.6.5, LET creates a lexical closure for the variable B. B is assigned using LET and incremented using INCF. Notice how the value of B is not known by the interpreter. In the case of Example 8.6.5, INCF increments the local variable B.
Example 8.6.5 demonstrates how LET creates a lexical closure for the variable A. Recall from Example 8.6.4 that the global variable A has a value of 1.LET assigns the local variable A the value of 3. The body of the LET contains a SETF that increments the value of the local variable A by one. The SETF is contained within the body of the LET. The form returns 4. We query the value of A at the Common LISP ? prompt and see that the global variable A has retained its value of 1. Example 8.6.5 demonstrates how two variables of the same name have different lexical contexts.
Example 8.6.5
4
In Example 8.6.6, a global variable C is assigned a value of 0. We define a function INCREASE that increases the value of C by a user-specified amount signified by the variable X.
Example 8.6.6
The definition of the function INCREASE results in compiler warnings because the variable C is not known within the lexical context of the DEFUN.
In Example 8.6.7, the function call to INCREASE is not accompanied by a warning and the value of C is increased by 3.INCREASE adds 3 to the value of the global variable C.
A function definition that generates warnings but still executes properly is considered poor style. It is an indication that the programmer may not fully understand the lexical context of the variables. The programmer must carefully consider the lexical context of variables and the lexical closures that are created. Example 8.6.8 shows an improvement of the definition of the function INCREASE.
Example 8.6.8
In Example 8.6.9, we call the function INCREASE with inputs of C and 3. The value of the global variable C is used in the evaluation.
Example 8.6.9
A query to the interpreter shows that the global variable C still has a value of 3. The global variable C has not been reassigned because the SETF is confined to the lexical closure created by defun with inputs of C and X.
3
In order to reassign the global variable C, we need to assign it in lexical context in which it was created as seen in Example 8.6.11.
Example 8.6.11
8.7 Understanding Variable Scope in Common Music
Now that we have a basic understanding of lexical context in Common LISP, let's see how these concepts apply to Common Music.
Examples 8.7.1 through Example 8.7.12 are entered at the Stella command interpreter.
Example 8.7.1 creates a generator named scope that contains five midi-notes. All slots are assigned at container initialization except for the note slot. A SETF inside the generator assigns the variable X a value of 60. We assign the note slot the value of X using SETF.
Example 8.7.1
Variable X generates two undeclared free variable warnings. Both SETF and the generator create anonymous lambda forms resulting in the compiler warning "Undeclared free variable X (2 references), in an anonymous lambda form inside an anonymous lambda form."
The generator scope is mixed and the note slot is assigned a value of 60.
Example 8.7.2
We query the value of the variable X at the interpreter and a value of 60 is returned.
The SETF inside the generator creates a global lexical context. The variable X is referenced when the note slot is assigned.
In Example 8.7.4, we reassign the global variable X a value of 61.
Next, we create a generator and reference the global variable X within the body of the generator.
Example 8.7.5
The global variable X is referenced in assigning the note slot but not without generating a warning.
Example 8.7.6 is a better way to assign a variable to a slot. By using the lexical closure of a LET, no warnings are generated.
#<GENERATOR: Scope>
Example 8.7.7 further demonstrates the concept of lexical closure. A LET within the generator scope creates a lexical closure. The local variable Y is assigned a value of 61. Within the body of the LET, the variable Y is incremented using SETF. While still in the body of the LET, the note slot is assigned the value of Y. No warnings are generated when scope is evaluated. At the interpreter, the symbol Y has no value.
Example 8.7.8 demonstrates lexical closure using DEFUN. The body of the function definition uses SETF to increment the variable Z.
Example 8.7.8
In Example 8.7.9. we assign the global variable A a value of 1 using SETF.
Example 8.7.9
Stella [Scope]: (setf a 1)
In Example 8.7.10, the function ADD-ONE is called with an input of A. The variable A references global variable A and uses the value 1. The function ADD-ONE returns 2. Global variable A retains its value of 1.
Example 8.7.11 demonstrates a function call to ADD-ONE with an input of 60 to assign the note slot.
Section 8.4 discussed the Common Music declaration vars to assign variables that are local to a Common Music container. Example 8.7.12 demonstrates how vars sequentially assigns its variables by first assigning the variable h a value of 60 and then calling the user-defined function ADD-ONE with an input of h and assigning the result to the variable h. The note slot is assigned the value of h.
8.8 Creating and Reading Item Streams
The Common Music function make-item-stream converts a list to an item stream. This function is very useful because Common LISP has many primitives that operate on lists. We can use the power of Common LISP to manipulate musical data represented as lists, convert the lists to item streams, and output the item streams using Common Music.
The template for make-item-stream is:(make-item-stream type pattern items)
type refers to one of the item stream data types.pattern refers to one of the item-stream-pattern types.items refers to the list to be converted into an item stream.
Example 8.8.1 converts the list (c4 d4 e4 fs4 r) into a cyclic note stream.
You may read an item stream using the Common Music function read-items.
The template for read-items is:(read-items stream)
Example 8.8.2 converts a list to a cyclic note stream and assigns the item stream to the global variable MY-ITEM-STREAM. We use read-items to see the value of MY-ITEM-STREAM.
Example 8.8.2
Example 8.8.3 shows an attempt at creating an item stream and using it a generator. We use the item-stream-accessor item to access one item at a time from the item stream. Notice that the generator without-vars only outputs the note C4. This is because the item stream is created each time the generator loops to output an event. Consequently, the only note that plays is C4.
Example 8.8.3: without-vars.lisp
In Example 8.8.4, we use the Common Music declaration vars to create an item stream and assign it to the variable the-list. Notice that the output is the cyclic succession of notes C4, D4, E4, FS4, a rest, and C4. Here we observe another important attribute of vars. Not only does vars create variables that are local to a container, vars is evaluated only once- when the container is scheduled to run. The cyclic note stream is assigned once when the generator is mix ed and the item-stream-accessor item accesses each item in the item stream.
In summary, we have seen how Common LISP forms such as LET and DEFUN create lexical closures. Variables assigned using SETF, INCF, or DECF create a lexical closure dependent on the context in which the variable was created. The Common Music declaration vars creates and assigned variables that are local to a container.