The examples I have been studying so far seem to be build on a static code base and using probabilities and random function to introduce variable behavior. During my work with Markov chains the idea come up to modify the probabilities while playing. This would change the style of the piece over time.
Does anybody have experience or examples or ideas on that?
Here is a case study to demonstrate the principle (without being a real piece of music yet):
# self-modifying algorithm
use_debug false
use_bpm 80
use_random_seed 1
st = 4.0
sca = scale :C4, :phrygian
densities = (ring 1, 2, 3, 4, 5)
# Markov matrix for 8 notes of the scale
markov_matrix1 = [
# 0* 1 2* 3 4* 5 6* 7*
[0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0], # 0* base tone
[1.0, 0.0 , 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], # 1
[0.0, 0.0 , 0.0, 0.0, 1.0, 0.0, 0.0, 0.0], # 2* third
[0.0, 1.0 , 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], # 3
[0.0, 0.0 , 0.0, 0.0, 0.0, 0.0, 1.0, 0.0], # 4* fifth
[0.0, 0.0 , 0.0, 1.0, 0.0, 0.0, 0.0, 0.0], # 5
[0.0, 0.0 , 0.0, 0.0, 0.0, 0.0, 0.0, 1.0], # 6* seventh
[0.0, 0.0 , 0.0, 0.0, 0.0, 1.0, 0.0, 0.0], # 7* octave
]
# Markov matrix for density of notes
markov_matrix2 = [
# 1 2 3 4 5
[1.0, 0.0, 0.0, 0.0, 0.0], # 1
[1.0, 0.0, 0.0, 0.0, 0.0], # 2
[1.0, 0.0, 0.0, 0.0, 0.0], # 3
[1.0, 0.0, 0.0, 0.0, 0.0], # 4
[1.0, 0.0, 0.0, 0.0, 0.0], # 5
]
# Function to modify markov matrix
define :modify do |mm|
# randomly select a cell for modification
row = rand_i mm.length
col = rand_i mm[0].length
# assign new value
mm[row][col] = 0.1
# normalize to row_sum = 1.0
pp = 0.0
mm[row].each do |p|
pp += p
end
mm[row].length.times do |i|
mm[row][i] /= pp
end
end
# function to get next index according to markov matrix mm
define :next_idx do |current_idx, mm|
n = 0
r = rand
pp = 0
mm[current_idx].each do |p|
pp += p
break if pp > r
n += 1
end
return n
end
n = 0
d = 0
with_fx :reverb,room: 0.8, mix: 0.4 do
live_loop :melody, auto_cue: false do
with_synth :pulse do
with_synth_defaults amp: 0.8, attack: 0.05, cutoff: 90 do
ng = 4 # number of chunks of notes to play with same density
ng.times do
density densities[d] do
play sca[n], release: 0.05, sustain: st/ng-0.12
n = next_idx(n, markov_matrix1)
sleep st/ng
end
d = next_idx(d, markov_matrix2)
end
end
end
modify(markov_matrix1) if tick > 2
modify(markov_matrix2) if look%2 == 0
end
end
The modify
function introduces random modifications to the Markov matrices, but the modifications could also be more intentional, following a plan. Furthermore, other parameters could be modified as well, not just probabilities in a matrix.