Markov Chains in Ruby
December 17th, 2007
So the plan for a piece i’m writing is this: a set of very simple melodies, each with some set of probabilities of progressing to one of the other melodies; in other words - markov chains. Each of the melodies will have a set of possible doublings/harmonizations, and as the piece progresses the chance of those doublings/harmonizations occurring will increase - the piece will ‘thicken’ over time. This gradual process will repeat a few times, each repetition forming a major unit of the piece, probably three in all.
One of my goals is to see if the piece (and the process of writing it) can ‘evolve’ over time; by generating many versions of each major unit of the piece and choosing the one I like best - perhaps one per day - I hope to gradually improve the melodies and the probabilities to make better versions; constrained randomness generating a kind of feed-back into the compositional process. In the end i’m hoping this will be something better (or a least different) from what I would have created just sitting down and deliberately selecting each note. At the same time, though, I don’t want to dive into something totally aleatoric - I still want some control. I’m not hoping for something radically different to come about because of all this - just for the way that I write music to be altered enough so that what I create is a variant of what I would have created.
At any rate, I needed some code to do this and this post shows the beginnings of that effort. So here’s the ruby code to generate a sequence of melodies - using a markov chain of course. It only spits out the array index number of each melody in the sequence (5 in all) - it’s designed to tell me what order the melodies should be played in, always starts on 0, and will append melody numbers to the sequence until it reaches the maximum value passed into the initialization method. A lot of the inspiration for this code plus some other (probably superior) ways of accomplishing this can be found at the rubyquiz.com
class RiffSelector
def initialize(riffCount)
# define the markov chain for the riffs...
@chain = [[0, 1, 0, 0, 1], [0, 0, 1, 2, 0], [0, 1, 0, 1, 0], [0, 0, 2, 0, 1], [1, 0, 1, 0, 0]]
# the starting state...
@current = 0
# the eventual list of riffs (with doublings)...
@result = "0"
# loop through, appending the next riff in the chain...
(1..riffCount).each do
nextState = getNext()
@result += ', ' + nextState.to_s
end
end
def getNext()
probs = @chain[@current]# the array of probabilities of what state will be chosen next given the current state
total_prob = 0# the sum of all the probabilites
probs.each { |prob| total_prob += prob }
random = rand(total_prob.to_i) + 1# pick a random number within the range of 1..[sum of probabilities]
likelihood = 0
for state in (0..(probs.length - 1))# loop through each of the states
likelihood += probs.at(state)# sum probabilities as we go so that we'll eventually have a high enough number to equal the total_prob
for iter in (0..likelihood)
if random < = iter# iterate through until we reach or exceed the random number chosen earlier or until we run out of 'likelihood'
@current = state
return state
end
end
end
end
def getChain()
return @result
end
end
foo = RiffSelector.new(19)# pass in the number of riffs to put into the chain
puts foo.getChain()# output the result
