- Peter Ivan Edwards
Shuffled: Part 2, in which random permutation is transposed
As mentioned at the end of Shuffled: Part 1, the first 6 bars of this piece includes many fixed elements. The question now is which ones will be activated. I've decided on register and dynamic. What I wanted to do is create a transitional passage from the lowest octave of the piano to its middle octave, that is, around 3 octave above, at middle C. This will occur over 6 iterations using random permutation.This collection of permutations will be set to a variable because I want to be able to use it for something else later. If the code to generate permutations were run twice, then there would be two different collections because the code uses randomization. So, if one wants to access the same randomly generated data, then the code needs to be run once, stored, and referenced.
(setf series (loop repeat 6 collecting (rnd-order '(0 1 2 3 4 5 6 7 8 9 10 11))))
The function setf, which stands for "set field", sets the word following it, in this case the word series, as a name for whatever data is to its right. This is a way to create a named variable in Common LISP. The code to generate the data for the variable series is the same as in the last post, where it generated the 6 random permutations used to determine pitch. Here we will get a new collection of randomly permuted lists. Once we evaluate this code, we can reference the 6 lists of randomly permuted values with the word series. For example, the code
will give us the first sublist.
Previously, the value 24 was added to each randomly permuted value to place it in the desired register. I need to do something similar here, but the task is more complicated because I want to increase the transposition by 6 each time. This could be accomplished by adding 24 to the first set, 30 to the second, 36 to the third, etc. We could use a loop within a loop like before:
(loop for t in '(24 30 36 42 48 54) for ser in series collecting (loop for s in ser collecting (+ t s)))
This is a little more complicated than the version used earlier because it has two variables, t and ser. The outer loop takes a transposition value and a list from series and for each of them, the inner loop iterates through each value in the list ser and adds the transposition values t to it. To demonstrate, imagine the first ser to be '(3 1 2 8 6 7 5 10 0 11 9 4). If we add a t value of 24 to each then, the first s value of ser is 3.
(+ 24 3) => 27
The second s value of ser is 1.
(+ 24 1) => 25
The process is continued until all s values of ser have been evaluated and the results that have been stored along the way are returned.
'(27 25 26 32 30 31 29 34 24 25 33 28)
This result is handed off to the outer loop to be stored until all t and ser values have been passed to the inner loop and their results have been returned.
This is an acceptable way to do this, but there is a small problem with it. For every other transposition, values are transposed by a tritone rather than an octave, so a C doesn't map to a C when transposed but instead to an F#. With a randomly permuted series of all semitones within an octave this isn't a real problem. The permutational process is equally likely to return '(3 1 2 8 6 7 5 10 0 11 9 4) as it is '(9 7 8 2 0 1 11 6 5 3 10), the tritone transposition of the original list. Hence, there are no ramifications on the perception of the music. However, I want to map Cs to Cs and F#s to F#s. That code is a little more complicated. The computer needs to check if s is greater or less than 6. If it's less, then the s value transpose by an octave, if it's more, then it isn't transposed.
Since this issue arises only every other transposition - 6 + 6 yields 12, which when added to the s value maps all notes to themselves up an octave - I will track the transpositions with the list '(0 1 2 3 4 5). There are two advantages to this. First, these values can determine if we are dealing with a simply transposition or the more complicated version. Even values involve simple transpositions, odd values the more complicated ones. While iterating through the values of '(0 1 2 3 4 5), a condition checks whether the value is even. If true, a certain code is run; if false, a different code is run. Secondly, we can use this value to create the transposition. The code for this process is below.
(loop for t in '(0 1 2 3 4 5) for ser in series collecting (if (evenp t) (loop for s in ser collecting (+ 24 (* t 6) s)) (loop for s in ser collecting (if (< s 6) (+ 24 (* (+ t 1) 6) s) (+ 24 (* (- t 1) 6) s))))))
The variable t will represent the transpositions. For each t value, there will also be an accompanying ser list. In the third line there is the text (if (evenp t). This is a condition. It states, "If the variable t is even, then..." The function evenp stands for "even predicate". It checks if the value it is given is even and returns true or false. If it is true, then the next line of code is run, otherwise the code after that is run.
Let's look at the code that is executed when t is even. For each s in ser - so, for each integer in the list - the following equation is run: 24 + (t * 6) + s. This is a little different from the version used in Part 1. Here I've made 24 a base value and I use the value of t to create the octave transpositions. Note that only the t values of 0, 2, and 4 will be used with this code. Hence, the resulting equations will be:
24 + (0 * 6) + s = 24 + s
24 + (2 * 6) + s = 36 + s
24 + (4 * 6) + s = 48 + s
What happens when the value of t is odd?
(loop for s in ser collecting (if (< s 6) (+ 24 (* (+ t 1) 6) s) (+ 24 (* (- t 1) 6) s)))
For each s value, the computer asks, "(if (< s 6)", or in proper English, "If the value of s is less than 6, then..." If this is true, then the following equation is run: 24 + ((t + 1) * 6) + s. Let's see this in action with the t value of 1 and an s value of 0.
24 + ((1 + 1) * 6) + 0 = 24 + 12 + 0 = 36
The s value, a C, is transposed up an octave. But what happens if it is a 6? The second equation is used. Let's run that scenario with a t value of 1 as well.
24 + ((1 - 1) * 6) + 6 = 24 + 0 + 6 = 30
This remains an F# but note that the value 30 is lower than 36 from the previous equation. In essence, all notes will map to themselves but notes from 0-5 (C-F) will be transposed so that they are above the notes 6-11 (F#-B).
The variable series will also be used to make melodies that will be played by the right hand. The melodies will be randomly created using the following code:
(setf loci (loop repeat 6 collecting (sort-asc (filter-first (rnd-pick '(3 4)) (rnd-order '(0 1 2 3 4 5 6 7 8 9 10 11))))))
A variable named loci is set. 6 sublists are created, each containing 3 or 4 values between 0-11. Let's look closer at the second and third lines of code. Within the LISP programming language, one needs to learn to read right to left (or sometimes bottom to top). LISP is a bit the Hebrew or Arabic of programming languages. The result of a process to the right is then used as data for the process to its left. In this code, the rnd-order function on line 3 is run first. As you are already familiar, it will return something like '(9 2 10 4 5 8 11 0 3 6 1 7). The rnd-pick function randomly chooses a value from the list it is given, in this case either 3 or 4. The filter-first function keeps the first n values of a list. For example:
(filter-first 3 '(9 2 10 4 5 8 11 0 3 6 1 7)) => '(9 2 10).
This data is passed to the sort-asc function, which sorts the list in an ascending order. Continuing with our example, it will return '(2 9 10). The results of loci can be incorporated in a version of the earlier code.
(loop for t in '(0 1 2 3 4 5) for ser in series for l in loci collecting (if (evenp t) (loop for n in l collecting (+ 36 (* t 6)) (nth n ser))) (loop for n in l collecting (if (< (nth n ser) 6) (+ 36 (* (+ t 1) 6)) (nth n ser)) (+ 36 (* (- t 1) 6)) (nth n ser)))))))
I won't explain the above code in detail since I covered most of it previously. It is important to note that the inner loop doesn't iterate through the values of ser but instead through the values of l. What are these though? They are index values. In a list like '(2 9 10) the value 2 is at the 0th position, 9 is at the 1st position, and 10 is at the 2nd position. These are the index values associated with elements of a list. The nth function, which you can see in line 5 of the code, returns the nth value in a list. For example:
(nth 0 '(2 9 10)) => 2 (nth 2 '(2 9 10) => 10
In essence, the program runs through the t, ser, and l values, takes the nth values, indicated by l, of ser and transposes it appropriately. Note that the base value here is 36 rather than 24. This melody will be one octave higher than the left hand.
loci is also a variable because its data are needed to create the rhythm as well. Remember that if we ran the same code that makes up loci twice, we would get different datasets because the algorithm involves randomness. The values of loci are used not only to extract the pitch values at particular indices but also to articulate where those values are in the list. This second bit of information will be important for creating the rhythm because I want the melody notes in the right hand to occur at the same time as when they occur in the left hand, as see in the example below.
The code for creating the rhythm is below.
(loop for l in loci collecting (length-legato (position-replace l '1/16 (make-list 12 :initial-element '-1/16))))
Iterating through each sublist l of loci - and now we jump to the end of the code and read right to left (or in this case bottom to top since each functions been placed on a different line):
(make-list 12 :initial-element '-1/16) => '(-1/16 -1/16 -1/16 -1/16 -1/16 -1/16 -1/16 -1/16 -1/16 -1/16 -1/16 -1/16)
Make a list of 12 elements in which all elements are initially set to -1/16. This yields a list representing 12 16th note rests.
(position-replace l '1/16 ...
For every value in the sublist l replace the -1/16 at that index with 1/16. Below is an example in which l is '(2 9 10). Note in the returned list that the positive values are at the nth values of l.
(position-replace '(2 9 10) '(-1/16 -1/16 -1/16 -1/16 -1/16 -1/16 -1/16 -1/16 -1/16 -1/16 -1/16 -1/16)) => '(-1/16 -1/16 1/16 -1/16 -1/16 -1/16 -1/16 -1/16 -1/16 1/16 1/16 -1/16)
The length-legato function converts the rests so that the passage is legato.
(length-legato '(-1/16 -1/16 1/16 -1/16 -1/16 -1/16 -1/16 -1/16 -1/16 1/16 1/16 -1/16)) => '(-1/8 7/16 1/16 1/8)
These two parts are brought together, the right hand is placed in octaves to bring it out a bit and a crescendo from piano to fortissimo is added over its length.
But now that I've arrived around middle C through this transitional passage, what comes next? That will be explored in the next post.