How can we use random numbers, comparison operators, logical operators, the if
statement, and the while
loop to implement a "generate and test" composition program like Illiac Suite?
Import the random
module.
import random
Let's start with generating random pitches. As in Tutorial 1. Hello Python for Music, let's assume the musical representation in which a melody is expressed as a list of numbers.
First, let's write a while
loop to generate a list of 12 random numbers. We'll need that while
loop and random number generator from the previous section 2.1.
# start with an empty list
my_music = [60]
# loop until we have 12 notes
while len(my_music) < 12:
# generate a random note
new_note = random.randint(0, 127)
# append it to the list
my_music += [new_note]
# print the final list
print(my_music)
[60, 55, 127, 99, 44, 30, 103, 82, 21, 11, 105, 124]
Caveat: for now let's start with my_music = [60]
rather than a completely empty list. You'll see why later...
How would you express a musical rule such as "no melodic line may span for an an octave" or "a melodic skip of a major or minor seventh is forbidden" or "no more than one successive repeat of a given note?" The answer: comparison and logical operators.
Let's use comparison operators to express the rule "no melodic skip larger than a perfect fourth"? It helps to break it down into smaller tasks:
First let's generate a new note.
# our list of notes thus far
my_music = [60, 62, 63]
# choose a random note
new_note = random.randint(0, 127)
# print the random note
print(new_note)
55
Now test it.
# 1. get the previous note
prev_note = my_music[-1]
# 2. measure the interval btwn new and prev note
interval = abs(new_note - prev_note)
# 3. test if the interval is larger than a P4
print(interval <= 5)
False
Important: as we saw in class, we need the absolute value (abs()
) of the interval because we don't care about the sign, just the magnitude. That is, we don't care whether or the intervals is ascending or descending, just the size of the jump between notes. Other rules however may care about the direction!
Let's write the test all on one line.
# is new_note larger than a perfect fourth?
abs(new_note - my_music[-1]) <= 5
False
Those are the two major piece of the puzzle. We know whether or not the new note passes the rule, but how do we use that boolean value to modify the program behavior?
An if
statement is used when you want to perform different actions depending on whether or not a condition is True
or False
. Let's write an if
statement to add or reject the new note depending on the rule.
# start with an empty list
my_music = [60]
# loop until we have 12 notes
while len(my_music) < 12:
# generate a random note
new_note = random.randint(0, 127)
# is new_note larger than a perfect fourth?
if abs(new_note - my_music[-1]) <= 5:
# if yes, append it to the list
my_music += [new_note]
# print the final list
print(my_music)
[60, 65, 63, 67, 70, 70, 67, 65, 63, 59, 60, 62]
Things are never quite as simple as you'd like. Try initializing to an empty list with my_music = []
. What's the problem?
# start with an empty list
my_music = []
# loop until we have 12 notes
while len(my_music) < 12:
# generate a random note
new_note = random.randint(0, 127)
# is new_note larger than a perfect fourth?
if abs(new_note - my_music[-1]) <= 5:
# append it to the list
my_music += [new_note]
# print the final list
print(my_music)
--------------------------------------------------------------------------- IndexError Traceback (most recent call last) <ipython-input-7-365b3791f9cd> in <module>() 8 9 # is new_note larger than a perfect fourth? ---> 10 if abs(new_note - my_music[-1]) <= 5: 11 12 # append it to the list IndexError: list index out of range
We get an IndexError: list index out of range
. What happened? We tried to access the previous note my_music-[1]
before there was anything there! Ideally, we'd like to start from an empty list, so how can we fix this?
We want the rule to be True
when either (1) the interval is not greater than a perfect fourth *or* (2) when my_music
is an empty list (meaning we are choosing the first note). Can you think of a way to implement this? We'll use the logical operator or.
# our list of notes thus far
my_music = []
# choose a random note
new_note = random.randint(0, 127)
# is new_note larger than a perfect fourth?
len(my_music) < 1 or abs(new_note - my_music[-1]) <= 5
True
We're actually doing something quite sophisticated here. Remember, if Python tries to access the list when it's empty, we'll get an error, so how does the or
statement avioid that, aren't we still accessing the list? The or
statement does a nifty trick called "short circuiting." If the first condition is True
, it doesn't bother to evaluate the second condition, because it knows the entire statement will be True
regardless.
Replace the test with your new line of code that is protected against errors and you are ready to make some music.
# start with an empty list
my_music = []
# loop until we have 12 notes
while len(my_music) < 12:
# generate a random note
new_note = random.randint(0, 127)
# is new_note larger than a perfect fourth?
if len(my_music) < 1 or abs(new_note - my_music[-1]) <= 5:
# append it to the list
my_music += [new_note]
# print the final list
print(my_music)
[17, 15, 17, 14, 14, 14, 13, 15, 14, 15, 11, 13]