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.
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. 157) demonstrates 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.#frommusicimport*fromrandomimport*# musical data structurewalzerteil=Part()# contains a four-measure motif generated# randomly from the matrix above# measure 1 - create alternatives# choice 96pitches96=[[C3,E5],C5,G4]durations96=[EN,EN,EN]choice96=Phrase()choice96.addNoteList(pitches96,durations96)# choice 32pitches32=[[C3,E3,G4],C5,E5]durations32=[EN,EN,EN]choice32=Phrase()choice32.addNoteList(pitches32,durations32)# choice 40pitches40=[[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 17pitches17=[[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 141pitches141=[[B2,G3,D5],E5,F5,D5,[G2,C5],B4]durations141=[SN,SN,SN,SN,SN,SN]choice141=Phrase()choice141.addNoteList(pitches141,durations141)# choice 158pitches158=[[G2,B4],D5,B4,A4,G4]durations158=[EN,SN,SN,SN,SN]choice158=Phrase()choice158.addNoteList(pitches158,durations158)# measure 4 - create alternatives# choice 30pitches30=[[C5,G4,E4,C4,C2]]durations30=[DQN]choice30=Phrase()choice30.addNoteList(pitches30,durations30)# choice 5pitches5=[[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 excerptwalzerteil.addPhrase(measure1)walzerteil.addPhrase(measure2)walzerteil.addPhrase(measure3)walzerteil.addPhrase(measure4)# view and play randomly generated waltz excerptView.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.#frommusicimport*fromrandomimport*# import random number generatornumberOfNotes=100# how many notes in each parallel phrase##### define the data structurepart=Part()# create an empty partmelody1=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 melodyforiinrange(numberOfNotes):pitch=randint(C1,C7)# get random pitch between C1 and C6duration=random()*1.0# get random duration (0.0 to 2.0)dynamic=randint(PP,FFF)# get random dynamic between P and FFnote=Note(pitch,duration,dynamic)# create notemelody1.addNote(note)# and add it to the phrase# now, melody1 has been created# create random notes for second melodyforiinrange(numberOfNotes):pitch=randint(C1,C7)# get random pitch between C1 and C6duration=random()*1.0# get random duration (0.0 to 2.0)dynamic=randint(PP,FFF)# get random dynamic between P and FFnote=Note(pitch,duration,dynamic)# create notemelody2.addNote(note)# and add it to the phrase# now, melody2 has been created##### combine musical materialpart.addPhrase(melody1)part.addPhrase(melody2)##### play and write part to a MIDI filePlay.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:
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_PHfrommusicimport*fromrandomimport*# constants for controlling musical parameterscloudWidth=64# length of piece (in quarter notes)cloudDensity=23.44# how dense the cloud may beparticleDuration=0.2# how long each sound particle may benumParticles=int(cloudDensity*cloudWidth)# how many particlespart=Part(BREATHNOISE)# make particles (notes) and add them to cloud (part)foriinrange(numParticles):# create note with random attributespitch=randint(0,127)# pick from 0 to 127duration=random()*particleDuration# 0 to particleDurationdynamic=randint(0,127)# pick from silent to loudpanning=random()# pick from left to rightnote=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 piecephrase=Phrase(startTime)# create phrase with this start timephrase.addNote(note)# add the above notepart.addPhrase(phrase)# and add both to the part# now, all notes have been created# add some elegance to the endMod.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.frommusicimport*fromrandomimport*# program parameterscycles=24# how many times striker hits all four tubesduration=8.0# tubes sounds last from 0 to this time unitsminVol=80# low and high limit for random volumemaxVol=100# tube tuning (D7 chord)tube1=C5tube2=F5tube3=G4tube4=D6# wind chime partwindChimePart=Part(BELLS)# wind chime consists of four tubestube1Phrase=Phrase(0.0)# first tube starts at 0.0 timetube2Phrase=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 phrasesforiinrange(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 sequencestube1Phrase.addNote(note1)tube2Phrase.addNote(note2)tube3Phrase.addNote(note3)tube4Phrase.addNote(note4)# now, all notes have been created# add note sequences to wind chime partwindChimePart.addPhrase(tube1Phrase)windChimePart.addPhrase(tube2Phrase)windChimePart.addPhrase(tube3Phrase)windChimePart.addPhrase(tube4Phrase)# view and play wind chimesView.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.frommusicimport*# import music libraryfromrandomimport*# import random librarypentatonicScale=[C4,D4,E4,G4,A4]# which notes to usedurations=[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 createphrase=Phrase()# create an empty phrase# first note should be rootnote=Note(C4,QN)# create root notephrase.addNote(note)# add note to phrase# generate enough random notes (minus starting and ending note)foriinrange(numNotes-2):pitch=choice(pentatonicScale)# select next pitchduration=choice(durations)# select next durationdynamic=randint(80,120)# randomly vary the volumepanning=random()# and place in stereo fieldnote=Note(pitch,duration,dynamic,panning)# create notephrase.addNote(note)# add it to phrase# last note should be root also (a half note, to signify end)note=Note(C4,HN)# create root notephrase.addNote(note)# add note to phrasePlay.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.frommusicimport*fromrandomimport*numberOfNotes=29##### define the data structurebrownianMelodyScore=Score("Brownian melody",130)brownianMelodyPart=Part("Brownian melody",TUBULAR_BELLS,0)brownianMelodyPhrase=Phrase()##### create musical datanote=Note(C4,EN)# create first notebrownianMelodyPhrase.addNote(note)# add note to phraseforiinrange(numberOfNotes):# create enough notes# now, let's get next note according to brownian motionnote=note.copy()# create a new copy# flip a coinheads=random()<0.5# a 50-50 chance to be Trueifheads:# if we got heads,Mod.transpose(note,1,MAJOR_SCALE,C4)# up a scale degreeelse:# otherwiseMod.transpose(note,-1,MAJOR_SCALE,C4)# down a scale degreebrownianMelodyPhrase.addNote(note)# add note to phrase# now, all notes have been generated##### combine musical materialbrownianMelodyPart.addPhrase(brownianMelodyPhrase)brownianMelodyScore.addPart(brownianMelodyPart)##### view score and play itView.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.frommusicimport*fromrandomimport*numNotes=14# how many random notes to playphrase=Phrase()# create an empty phraseforiinrange(numNotes):dice=randint(1,6)# throw dice (1 and 6 inclusive)# determine which dice face came upifdice==1:note=Note(C4,QN)# C4 noteelifdice==2:note=Note(D4,QN)# D4 noteelifdice==3:note=Note(E4,QN)# E4 noteelifdice==4:note=Note(F4,QN)# F4 noteelifdice==5:note=Note(G4,QN)# G4 noteelifdice==6:note=Note(A4,QN)# A4 noteelse:print("Something unexpected happened... dice =",dice)phrase.addNote(note)# add this random note to phrase# now, all random notes have been created# so, play themPlay.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).#frommusicimport*fromrandomimport*##### musical parameters# 35% of the time we try something differentcomeAlive=0.35# how many measures to playmeasures=8##### define the data structurescore=Score("Drums Come Alive",125.0)# tempo is 125 bpmdrumsPart=Part("Drums",0,9)# using MIDI channel 9 (percussion)bassDrumPhrase=Phrase(0.0)# create phrase for each drum soundsnareDrumPhrase=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')foriinrange(2*measures):dynamics=randint(80,110)# add some variation in dynamicsn=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')foriinrange(2*measures):r=Note(REST,QN)snareDrumPhrase.addNote(r)dynamics=randint(80,110)# add some variation in dynamicsn=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')foriinrange(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-hatsifoddHitanddoItNow:# on odd hits, if it's time to do it,pitch=OPEN_HI_HAT# let's open the hit-hatelse:# otherwise,pitch=CLOSED_HI_HAT# keep it closed# also add some variation in dynamicsdynamics=randint(80,110)# create hi-hat noten=Note(pitch,SN,dynamics)hiHatPhrase.addNote(n)# now, create restr=Note(REST,SN)hiHatPhrase.addNote(r)##### combine musical materialdrumsPart.addPhrase(bassDrumPhrase)drumsPart.addPhrase(snareDrumPhrase)drumsPart.addPhrase(hiHatPhrase)score.addPart(drumsPart)##### view and playView.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.#frommusicimport*fromrandomimport*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=[]foriinrange(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 notesforiinrange(numNotes):event=randint(0,len(weightedPitches)-1)note=Note(weightedPitches[event],weightedDurs[event])# the note has been found; now add this notephrase.addNote(note)# now, all notes have been generated# so, play themPlay.midi(phrase)
Since randomness is involved, it will generate output similar (but not identical) to this: