Finally, the last bit on that matter, concluding the trilogy on Markov chains (Markov chains for beginners, Markov chains for beginners - Part 2). This one is for our friends with an interest in rhythms rather than in melodies and harmonies. First the code, then the explanation:
use_debug false
use_bpm 110
use_random_seed 9
# base drum
live_loop :bd do
4.times do
sample :bd_sone
sleep 1
end
end
# drum patterns for snare
patterns = [
#1 2 3 4
"--------------xx", # 0
"--x---x---x---x-", # 1
"--x---x---x---xx", # 2
"--x---x---xx--xx", # 3
"--x---xx--xx--xx", # 4
"--xx--xx--xx--xx", # 5
"--x---x---x----x", # 6
"--x---x----x---x", # 7
"--x----x---x---x", # 8
"---x---x---x---x", # 9
"---x---x---x--xx", # 10
"---x---x--xx--x-", # 11
"---x--xx--x---x-", # 12
"--xx--x---x---x-", # 13
"-xxx-xxx-xxx-xxx", # 14
"xxxxxxxxxxxxxxxx", # 15
"--x---x---xx--x-", # 16
"--x---xx--x---x-", # 17
"--xx--x---x---x-", # 18
"--x---x----x--x-", # 19
"--x----x--x---x-", # 20
"---x--x---x---x-", # 21
]
# markov matrix as 2-dimensional hash map
mm = Hash.new
patterns.length.times do |i|
mm[i] = Hash.new
end
# mm[n][m] = p: if playing pattern n, then with probability p play as next pattern m
mm[0][0] = 0.2
mm[0][1] = 0.6
mm[0][14] = 0.2
mm[1][1] = 0.4
mm[1][2] = 0.2
mm[1][6] = 0.2
mm[1][14] = 0.2
mm[2][1] = 0.2
mm[2][3] = 0.4
mm[2][16] = 0.4
mm[3][4] = 1.0
mm[4][5] = 1.0
mm[5][1] = 0.2
mm[5][14] = 0.6
mm[5][15] = 0.2
mm[6][1] = 0.2
mm[6][7] = 0.4
mm[6][19] = 0.4
mm[7][8] = 1.0
mm[8][9] = 1.0
mm[9][5] = 0.3
mm[9][10] = 0.5
mm[9][14] = 0.2
mm[10][9] = 0.2
mm[10][11] = 0.8
mm[11][12] = 1.0
mm[12][13] = 1.0
mm[13][1] = 0.2
mm[13][5] = 0.2
mm[13][2] = 0.2
mm[13][14] = 0.2
mm[13][15] = 0.2
mm[14][1] = 0.2
mm[14][5] = 0.2
mm[14][15] = 0.6
mm[15][0] = 0.3
mm[15][1] = 0.5
mm[15][14] = 0.2
mm[16][17] = 1.0
mm[17][18] = 1.0
mm[18][1] = 0.3
mm[18][2] = 0.3
mm[18][5] = 0.4
mm[19][20] = 1.0
mm[20][21] = 1.0
mm[21][1] = 0.2
mm[21][18] = 0.4
mm[21][14] = 0.4
# make sure each row sum is 1.0
mm.each do |i, row|
pp = 0
row.each do |c, p|
pp += p
end
row.each do |c, p|
row[c] /= pp
end
end
# function to get next index of pattern according to markov matrix
define :next_idx do |current_idx|
next_idx = 0
r = rand
pp = 0
mm[current_idx].each do |c, p|
next_idx = c
pp += p
break if pp > r
end
return next_idx
end
n = 0
live_loop :snare do
with_fx :gverb, room: 20, dry: 2, mix: 0.1 do
puts patterns[n]
tick_reset(:p)
16.times do
#sample :elec_hi_snare, rate: 1.8, amp: 0.8 if (patterns[n][tick(:p)]=="x")
#sample :drum_snare_soft if (patterns[n][tick(:p)]=="x")
sample :sn_dolf, sustain: 0.05, release: 0.03, hpf: 70 if (patterns[n][tick(:p)]=="x")
sleep 4.0/16
end
n = next_idx(n)
end
end
There are 22 different patterns for the snare drum. If we would use a 2-dimensional array for the markov matrix, we would need a 22x22 matrix with 484 entries. But I wanted to use only about 44 transitions between the 22 patterns. So, my 22x22 matrix would contain 44 entries > 0 and 440 zeros.
Therefore, I decided to use a different representation of the markov matrix: a 2-dimensional hash map. Using a hash map it is not necessary to write down all the entries with 0, just those with probabilities >0. The hash map is initialized as an empty map using this piece of code:
# markov matrix as 2-dimensional hash map
mm = Hash.new
patterns.length.times do |i|
mm[i] = Hash.new
end
Then, the probabilities for the pattern transitions can be defined like
mm[0][1] = 0.6
The interpretation is simple: If pattern 0
is played, then play as next pattern number 1
with a probability of 0.6
.
As a convenience function there is the normalization of the rows to a total probability of 1.0
. Therefore, you do not have to do the math very precicely in your head, the program will fix it.
Please also note that the function to select the next pattern is slightly different from the one in the previous posts. Using a hash map requires an adaption in one of the lines.
The main idea behind all those probabilities is to define paths where the sequence of patterns exhibits some development. For example, if there is some irregularity on beat 4, then it is a good idea in the next pattern to shift this irregularity to beat 3, then 2 and then 1. Also, a crescendo is followed by a more calm pattern.