Ch. 6 – Randomness and Choices

Topics:   Randomness and creativity, Mozart, indeterminism, serialism, Python random functions, stochastic music, Iannis Xenakis, probabilities, wind chimes, melody generator, selection, Python if statement, flipping a coin, Russian roulette, throwing dice, realistic drums, relational and logical operators, generative music.

Computers offer us a source of untamed possibilities in the form of a random number generator. This chapter focuses on ways to tame this source of possibilities to serve our aesthetic purposes.  More information is provided in the reference textbook.

Here is code from this chapter:


Creating Mozart’s “Musikalisches Würfelspiel”

In 1787, Wolfgang Amadeus Mozart wrote “Musikalisches Würfelspiel”, a musical process for generating a 16-measure waltz through randomness – he rolled dice.  In this process, each measure is selected from a set of 11 precomposed chunks of music.

This code sample (Ch. 6, p. 157demonstrates how to implement a simplified version of Mozart’s musical game.

# Mozart.MusikalischesWurfelspiel.py
#
# This program generates an excerpt of Mozart's "Musikalisches
# Wurfelspiel" (aka Mozart's Dice Game). It demonstrates how
# randomness may be sieved (harnessed) to produce aesthetic results.
#
# See Schwanauer, S, and D Levitt. 1993. Appendix, in Machine Models
# of Music. Cambridge, MA: MIT Press, pp. 533-538.
#
# The original has 16 measures with 11 choices per measure.
# This excerpt is a simplified form. In this excerpt,
# musical material is selected from this matrix:
#
# I II III IV
# 96 6 141 30
# 32 17 158 5
# 40
#
# Columns represent alternatives for a measure. The composer throws
# dice to select an alternative (choice) from first column.
# Then, connects it with the choice from second column, and so on.
#

from music import *
from random import *

# musical data structure
walzerteil = Part() # contains a four-measure motif generated
# randomly from the matrix above

# measure 1 - create alternatives
# choice 96
pitches96 = [[C3, E5], C5, G4]
durations96 = [EN, EN, EN]
choice96 = Phrase()
choice96.addNoteList(pitches96, durations96)

# choice 32
pitches32 = [[C3, E3, G4], C5, E5]
durations32 = [EN, EN, EN]
choice32 = Phrase()
choice32.addNoteList(pitches32, durations32)

# choice 40
pitches40 = [[C3, E3, C5], B4, C5, E5, G4, C5]
durations40 = [SN, SN, SN, SN, SN, SN]
choice40 = Phrase()
choice40.addNoteList(pitches40, durations40)

# measure 2 - create alternatives
# choice 6 (same as choice 32)
choice6 = Phrase()
choice6.addNoteList(pitches32, durations32)

# choice 17
pitches17 = [[E3, G3, C5], G4, C5, E5, G4, C5]
durations17 = [SN, SN, SN, SN, SN, SN]
choice17 = Phrase()
choice17.addNoteList(pitches17, durations17)

# measure 3 - create alternatives
# choice 141
pitches141 = [[B2, G3, D5], E5, F5, D5, [G2, C5], B4]
durations141 = [SN, SN, SN, SN, SN, SN]
choice141 = Phrase()
choice141.addNoteList(pitches141, durations141)

# choice 158
pitches158 = [[G2, B4], D5, B4, A4, G4]
durations158 = [EN, SN, SN, SN, SN]
choice158 = Phrase()
choice158.addNoteList(pitches158, durations158)

# measure 4 - create alternatives
# choice 30
pitches30 = [[C5, G4, E4, C4, C2]]
durations30 = [DQN]
choice30 = Phrase()
choice30.addNoteList(pitches30, durations30)

# choice 5
pitches5 = [[C2, C5, G4, E4, C4], [G2, B4], [C2, E4, C5]]
durations5 = [SN, SN, QN]
choice5 = Phrase()
choice5.addNoteList(pitches5, durations5)

# roll the dice!!!
measure1 = choice([choice96, choice32, choice40])
measure2 = choice([choice6, choice17])
measure3 = choice([choice141, choice158])
measure4 = choice([choice30, choice5])

# connect the random measures into a waltz excerpt
walzerteil.addPhrase(measure1)
walzerteil.addPhrase(measure2)
walzerteil.addPhrase(measure3)
walzerteil.addPhrase(measure4)

# view and play randomly generated waltz excerpt
View.sketch(walzerteil)
Play.midi(walzerteil)

Since randomness is involved, every time it runs, it will generate different outputs. Here are three examples:




 


Creating Pierre Cage’s “Structures pour deux Chances”

An interesting way of applying randomness in music is in a style referred to as chance music. Chance music, also known as aleatoric music, is a compositional technique that introduces elements of randomness into the compositional process. John Cage, among other composers, is well known for his aleatoric compositions.  On the other hand, serialism involves using deterministic rules to control choices within the compositional process. Pierre Boulez is well known for his serial compositions.

Aleatoric and serial techniques are compositional opposites of each other. Surprisingly, though, the musical outcome can appear to be very similar. This can be observed in these two pieces — the first aleatoric, the second serialist:

This code sample (Ch. 6, p. 161) capitalizes on this similarity to create a program where is impossible to determine, simply by listening to it, if the compositional approach was aleatoric or a serialist. This piece is attributed to Pierre Cage.  Pierre Cage is a fictitious composer (a remix of the names, Pierre Boulez and John Cage).

# PierreCage.StructuresPourDeuxChances.py
#
# This program (re)creates pieces similar to:
#
# Pierre Boulez, "Structures I for two pianos", and
# John Cage, "Music of Changes, Book I".
#
# The piece generated consists of two parallel phrases containing
# notes with random pitch and duration.
#

from music import *
from random import *   # import random number generator

numberOfNotes = 100    # how many notes in each parallel phrase

##### define the data structure
part = Part()          # create an empty part
melody1 = Phrase(0.0)  # create phrase (at beginning of piece)
melody2 = Phrase(0.0)  # create phrase (at beginning of piece)

##### create musical data
# create random notes for first melody
for i in range(numberOfNotes):
   pitch = randint(C1, C7)      # get random pitch between C1 and C6
   duration = random() * 1.0    # get random duration (0.0 to 2.0)
   dynamic = randint(PP, FFF)   # get random dynamic between P and FF
   note = Note(pitch, duration, dynamic) # create note
   melody1.addNote(note)        # and add it to the phrase
# now, melody1 has been created

# create random notes for second melody
for i in range(numberOfNotes):
   pitch = randint(C1, C7)      # get random pitch between C1 and C6
   duration = random() * 1.0    # get random duration (0.0 to 2.0)
   dynamic = randint(PP, FFF)   # get random dynamic between P and FF
   note = Note(pitch, duration, dynamic) # create note
   melody2.addNote(note)        # and add it to the phrase
# now, melody2 has been created

##### combine musical material
part.addPhrase(melody1)
part.addPhrase(melody2)

##### play and write part to a MIDI file
Play.midi(part)
Write.midi(part, "Pierre Cage.Structures pour deux chances.mid")

Since randomness is involved, it will generate output similar (but not identical) to this:


 


Creating Iannis Xenakis’ stochastic piece,“Concret PH”

Stochastic music is a compositional method employed by Iannis Xenakis, as a reaction to the abstractness and complexity of music from the Serialist movement.  Xenakis proposed that the mathematics of probability could be the basis of a more general and manageable compositional technique (Xenakis 1971).

“Concret PH” is a very influential piece of stochastic music. It was created by Xenakis to be played inside the Philips Pavilion in the 1958 World’s Fair in Brussels. This building was designed by architect Le Corbusier, who employed Xenakis as an architect and mathematician at the time.

The following program (Ch. 6, p. 167) demonstrates how to generate a stochastic piece of music.  In the original piece, Xenakis used spliced tape of sounds made by burning charcoal. Here, we mimic the sound using the MIDI instrument BREATHNOISE, which at short “bursts” (notes with short duration) sounds much like Xenakis’ original sound elements.

# ConcretPH_Xenakis.py
#
# A short example which generates a random cloud texture
# inspired by Iannis Xenakis's 'Concret PH' composition
#
# see http://en.wikipedia.org/wiki/Concret_PH

from music import *
from random import *

# constants for controlling musical parameters
cloudWidth = 64          # length of piece (in quarter notes)
cloudDensity = 23.44     # how dense the cloud may be
particleDuration = 0.2   # how long each sound particle may be
numParticles = int(cloudDensity * cloudWidth)   # how many particles

part = Part(BREATHNOISE)

# make particles (notes) and add them to cloud (part)
for i in range(numParticles):

   # create note with random attributes
   pitch = randint(0, 127)     # pick from 0 to 127
   duration = random() * particleDuration # 0 to particleDuration
   dynamic = randint(0, 127)   # pick from silent to loud
   panning = random()          # pick from left to right
   note = Note(pitch, duration, dynamic, panning)   # create note

   # now, place it somewhere in the cloud (time continuum)
   startTime = random() * cloudWidth    # pick from 0 to end of piece
   phrase = Phrase(startTime)           # create phrase with this start time
   phrase.addNote(note)                 # add the above note
   part.addPhrase(phrase)               # and add both to the part
# now, all notes have been created

# add some elegance to the end
Mod.fadeOut(part, 20)

View.show(part)
Play.midi(part)
Write.midi(part, "ConcretPh.mid")

Since randomness is involved, it will generate output similar (but not identical) to this:


 


Harnessing (or sieving) randomness – wind chimes

As mentioned above, a way to generate artifacts that are aesthetically pleasing, starting with pure randomness, is to filter a random process through a sieve. For example, wind chimes capture random movements of air and force them onto a narrow set of aesthetic possibilities.  The following program (Ch. 6, p. 169) demonstrates how to create wind chimes out of randomness.

# windChimes.py
#
# Simulates a 4-tube wind chime.
#
# Demonstrates how we may sieve (harness) randomness to generate
# aesthetically pleasing musical outcomes.

from music import *
from random import *

# program parameters
cycles = 24       # how many times striker hits all four tubes
duration = 8.0    # tubes sounds last from 0 to this time units
minVol = 80       # low and high limit for random volume
maxVol = 100

# tube tuning (D7 chord)
tube1 = C5
tube2 = F5
tube3 = G4
tube4 = D6

# wind chime part
windChimePart = Part(BELLS)

# wind chime consists of four tubes
tube1Phrase = Phrase(0.0) # first tube starts at 0.0 time
tube2Phrase = Phrase(1.0) # second tube starts at 1.0 time, ...
tube3Phrase = Phrase(3.0) # ... and so on.
tube4Phrase = Phrase(5.0)

# generate wind chime notes and add them to these phrases
for i in range(cycles):

   # create random tube strikes (notes)
   note1 = Note(tube1, random() * duration, randint(minVol, maxVol) )
   note2 = Note(tube2, random() * duration, randint(minVol, maxVol) )
   note3 = Note(tube3, random() * duration, randint(minVol, maxVol) )
   note4 = Note(tube4, random() * duration, randint(minVol, maxVol) )

   # accumulate notes in parallel sequences
   tube1Phrase.addNote( note1 )
   tube2Phrase.addNote( note2 )
   tube3Phrase.addNote( note3 )
   tube4Phrase.addNote( note4 )
# now, all notes have been created

# add note sequences to wind chime part
windChimePart.addPhrase( tube1Phrase )
windChimePart.addPhrase( tube2Phrase )
windChimePart.addPhrase( tube3Phrase )
windChimePart.addPhrase( tube4Phrase )

# view and play wind chimes
View.sketch(windChimePart)
Play.midi(windChimePart)

Since randomness is involved, it will generate output similar (but not identical) to this:


 


Creating a pentatonic melody

The following program (Ch. 6, p. 170) demonstrates how to harness randomness to create a melodic line within a particular scale.

# pentatonicMelody.py
# Generate a random pentatonic melody. It begins and ends
# with the root note.

from music import *    # import music library
from random import *   # import random library

pentatonicScale = [C4, D4, E4, G4, A4]   # which notes to use
durations       = [QN, DEN, EN, SN]      # which durations to use

# pick a random number of notes to create (between 12 and 18)
numNotes = randint(12, 18)   # number of notes to create

phrase = Phrase()   # create an empty phrase

# first note should be root
note = Note(C4, QN)    # create root note
phrase.addNote(note)   # add note to phrase

# generate enough random notes (minus starting and ending note)
for i in range(numNotes - 2):
   pitch = choice(pentatonicScale)   # select next pitch
   duration = choice(durations)      # select next duration
   dynamic = randint(80, 120)        # randomly vary the volume
   panning = random()                # and place in stereo field
   note = Note(pitch, duration, dynamic, panning) # create  note
   phrase.addNote(note)              # add it to phrase

# last note should be root also (a half note, to signify end)
note = Note(C4, HN)    # create root note
phrase.addNote(note)   # add note to phrase

Play.midi(phrase)      # play the melody

Since randomness is involved, it will generate output similar (but not identical) to this:


 


Music from Brownian motion

Brownian motion is a very correlated, yet unpredictable (random) process observed commonly in nature. The following Python program demonstrates how we can we harness randomness to generate music that is more correlated, “natural” sounding.

# brownianMelody.py
#
# Demonstrates how to create more correlated music from chaos
# (i.e., randomness). This process simulates the random "walks" of
# particles within water, etc., i.e., unpredictable, but not chaotic.
# It models the flip of a coin - if heads, next note in the melody
# goes up one scale degree; if tails, next note is down one scale
# degree.

from music import *
from random import *

numberOfNotes = 29

##### define the data structure
brownianMelodyScore = Score("Brownian melody", 130)
brownianMelodyPart = Part("Brownian melody", TUBULAR_BELLS, 0)
brownianMelodyPhrase = Phrase()

##### create musical data
note = Note(C4, EN) # create first note
brownianMelodyPhrase.addNote(note) # add note to phrase

for i in range(numberOfNotes): # create enough notes

# now, let's get next note according to brownian motion
note = note.copy() # create a new copy

# flip a coin
heads = random() < 0.5     # a 50-50 chance to be True

if heads: # if we got heads,
Mod.transpose(note, 1, MAJOR_SCALE, C4) # up a scale degree
else: # otherwise
Mod.transpose(note, -1, MAJOR_SCALE, C4) # down a scale degree

brownianMelodyPhrase.addNote(note) # add note to phrase
# now, all notes have been generated

##### combine musical material
brownianMelodyPart.addPhrase(brownianMelodyPhrase)
brownianMelodyScore.addPart(brownianMelodyPart)

##### view score and play it
View.sketch(brownianMelodyScore)
Play.midi(brownianMelodyScore)

Since randomness is involved, it will generate output similar (but not identical) to this:


 


Throwing dice

The following program (Ch. 6, p. 177) demonstrates how to simulate the throwing of dice – how to divide randomness across many alternatives (in this case, 6).

# throwingDice.py
#
# Demonstrates the division of randomness to several choices.

from music import *
from random import *

numNotes = 14        # how many random notes to play

phrase = Phrase()    # create an empty phrase

for i in range(numNotes):

   dice = randint(1, 6)  # throw dice (1 and 6 inclusive)

   # determine which dice face came up
   if dice == 1:
      note = Note(C4, QN)   # C4 note
   elif dice == 2:
      note = Note(D4, QN)   # D4 note
   elif dice == 3:
      note = Note(E4, QN)   # E4 note
   elif dice == 4:
      note = Note(F4, QN)   # F4 note
   elif dice == 5:
      note = Note(G4, QN)   # G4 note
   elif dice == 6:
      note = Note(A4, QN)   # A4 note
   else:
      print "Something unexpected happened... dice =", dice

   phrase.addNote(note)  # add this random note to phrase

# now, all random notes have been created

# so, play them
Play.midi(phrase)

Since randomness is involved, it will generate output similar (but not identical) to this:


 


Let the drums come alive

In the following program (Ch. 6, p. 179), every now and then (randomly) we interject an open hi-hat sound to the sequence of closed hi-hat sounds. It also randomly varies the loudness (dynamic level) of the notes.

# drumsComeAlive.py
#
# Demonstrates how to uses randomness to make a drum pattern come
# "alive", i.e., to sound more natural, more human-like.
# In this example, every now and then (randomly, 35% of the time),
# we play an open hi-hat sound (as opposed to a closed one).
#

from music import *
from random import *

##### musical parameters
# 35% of the time we try something different
comeAlive = 0.35

# how many measures to play
measures = 8     

##### define the data structure
score = Score("Drums Come Alive", 125.0) # tempo is 125 bpm

drumsPart = Part("Drums", 0, 9)  # using MIDI channel 9 (percussion)

bassDrumPhrase = Phrase(0.0)     # create phrase for each drum sound
snareDrumPhrase = Phrase(0.0)
hiHatPhrase = Phrase(0.0)

##### create musical data
# kick
# bass drum pattern (one bass 1/2 note) x 2 = 1 measure
# (we repeat this x 'measures')
for i in range(2 * measures):

   dynamics = randint(80, 110)   # add some variation in dynamics
   n = Note(ACOUSTIC_BASS_DRUM, HN, dynamics)
   bassDrumPhrase.addNote(n)

# snare
# snare drum pattern (one rest + one snare 1/4 notes) x 2 = 1 measure
# (we repeat this x 'measures')
for i in range(2 * measures):

   r = Note(REST, QN)
   snareDrumPhrase.addNote(r)

   dynamics = randint(80, 110)    # add some variation in dynamics
   n = Note(SNARE, QN, dynamics)
   snareDrumPhrase.addNote(n)

# hats
# a hi-hat pattern (one hi-hat + one rest 1/16 note) x 8 = 1 measure
# (we repeat this x 'measures')
for i in range(8 * measures):

   # if the modulo of i divided by 2 is 1, we are at an odd hit
   # (if it is 0, we are at an even hit)
   oddHit = i%2 == 1 

   # time to come alive?
   doItNow = random() < comeAlive

   # let's give some life to the hi-hats
   if oddHit and doItNow:    # on odd hits, if it's time to do it,
      pitch = OPEN_HI_HAT       # let's open the hit-hat
   else:                     # otherwise,
      pitch = CLOSED_HI_HAT     # keep it closed   

   # also add some variation in dynamics
   dynamics = randint(80, 110)

   # create hi-hat note
   n = Note(pitch, SN, dynamics)
   hiHatPhrase.addNote(n)

   # now, create rest
   r = Note(REST, SN)
   hiHatPhrase.addNote(r)

##### combine musical material
drumsPart.addPhrase(bassDrumPhrase)
drumsPart.addPhrase(snareDrumPhrase)
drumsPart.addPhrase(hiHatPhrase)
score.addPart(drumsPart)

##### view and play
View.sketch(score)
Play.midi(score)

Since randomness is involved, it will generate output similar (but not identical) to this:


 


Creating generative music

Thee following program (Ch. 6, p. 187) demonstrates how to develop more intricate algorithmic processes for setting up probabilities of musical events (e.g., pitches and durations) and mapping them into interesting musical artifacts.

# generativeMusic.py
#
# Demonstrates how to create music with weighted probabilities.
#

from music import *
from random import *

numNotes = 32        # how many random notes to play

# pitches and their chances to appear in output (the higher the
# chance, the more likely the pitch is to appear)
pitches   = [C4, D4, E4, F4, G4, A4, B4, C5]
durations = [QN, EN, QN, EN, QN, EN, SN, QN]
chances   = [5,  1,  3,  2,  4,  3,  1,  5]

####
# Create weighted lists of pitches and durations, where the number of
# times a pitch appears depends on the corresponding chance value.
# For example, if pitches[0] is C4, and chances[0] is 5, the weighted
# pitches list will get 5 instances of C4 added.
weightedPitches = []
weightedDurs    = []
for i in range( len(chances) ):
   weightedPitches = weightedPitches + [ pitches[i] ] * chances[i]
   weightedDurs    = weightedDurs + [ durations[i] ] * chances[i]
# now, len(weightedPitches) equals sum(chances)
# same applies to weightedDurs

# debug lines:
print "weightedPitches = ", weightedPitches
print "weightedDurations = ", weightedDurs

phrase = Phrase()    # create an empty phrase

# now create all the notes
for i in range(numNotes):

   event = randint(0, len(weightedPitches)-1)
   note  = Note(weightedPitches[event], weightedDurs[event])

   # the note has been found; now add this note
   phrase.addNote(note)
# now, all notes have been generated

# so, play them
Play.midi(phrase)

Since randomness is involved, it will generate output similar (but not identical) to this: