Markov chains for beginners - Part 3

There was a new idea from @amiika in this post: Req: a function that indexes all Markov matrices.

The idea is to build matrices not by explicitely defining probabilities but by writing down the sequences you would like to hear. I have used this idea to demonstrate how it could be used as an alternative to the above code:

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

# helper functions for matrix creation

define :to_mm do |idx, mm=8|
  # Length of the markov matrix
  length = mm.is_a?(Integer) ? mm : mm.length
  # Init with random matrix if mm=integer
  mm = Array.new(length) { Array.new(length, 0.0) } if mm.is_a?(Integer)
  # Treat integer as a markov chain: 121 = 1->2, 2->1
  degrees = idx.split("-")
  degrees.push(degrees[0]) if degrees.length==1
  degrees.each_with_index do |d,i|
    if degrees[i+1]
      # Overflow depending on mm length: 8->0, 9->1, 0->2
      row = (d.to_i==0 ? length : d.to_i-1)%length
      column = (degrees[(i+1)].to_i-1)%length
      mm[row][column] += 1.0
    end
  end
  mm
end

define :print_matrix do |mm|
  mm.each do |row|
    r = ""
    row.each do |c|
      r += "#{c.round(1)} "
    end
    puts r
  end
end


define :normalize do |mm|
  mm.length.times do |row|
    pp = 0.0
    mm[row].each do |p|
      pp += p
    end
    mm[row].length.times do |i|
      if pp == 0.0 then
        puts "warning: no transition defined for row #{row+1}!" if i == 0
        mm[row][i] = 1.0/mm[row].length
      else
        mm[row][i] /= pp
      end
    end
  end
  mm
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

# drum patterns for snare
patterns = [
  #1   2   3   4
  "--------------xx", # 1
  "--x---x---x---x-", # 2
  "--x---x---x---xx", # 3
  "--x---x---xx--xx", # 4
  "--x---xx--xx--xx", # 5
  "--xx--xx--xx--xx", # 6
  "--x---x---x----x", # 7
  "--x---x----x---x", # 8
  "--x----x---x---x", # 9
  "---x---x---x---x", # 10
  "---x---x---x--xx", # 11
  "---x---x--xx--x-", # 12
  "---x--xx--x---x-", # 13
  "--xx--x---x---x-", # 14
  "-xxx-xxx-xxx-xxx", # 15
  "xxxxxxxxxxxxxxxx", # 16
  "--x---x---xx--x-", # 17
  "--x---xx--x---x-", # 18
  "--xx--x---x---x-", # 19
  "--x---x----x--x-", # 20
  "--x----x--x---x-", # 21
  "---x--x---x---x-", # 22
]

mm = to_mm "1-2-2-2-3-4-5-6-2", patterns.length  # initialization with number of patterns = 22
mm = to_mm "2-7-8-9-10-11-12-13-14-2", mm
mm = to_mm "7-2", mm
mm = to_mm "2-11-12-13-14-2", mm
mm = to_mm "14-15-16-2", mm
mm = to_mm "16-15-16-2", mm
mm = to_mm "2-3-17-18-19-2", mm
mm = to_mm "7-20-21-22-2", mm
mm = to_mm "10-15", mm
mm = to_mm "16-1", mm
mm = to_mm "14-6", mm
mm = to_mm "10-6", mm
mm = to_mm "6-15", mm

normalize mm
#print_matrix mm

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, mm)
  end
end

The actual composition is concentrated in these lines (other code is just needed for the execution) :

patterns = [
  #1   2   3   4
  "--------------xx", # 1
  "--x---x---x---x-", # 2
  "--x---x---x---xx", # 3
  "--x---x---xx--xx", # 4
  "--x---xx--xx--xx", # 5
  "--xx--xx--xx--xx", # 6
  "--x---x---x----x", # 7
  "--x---x----x---x", # 8
  "--x----x---x---x", # 9
  "---x---x---x---x", # 10
  "---x---x---x--xx", # 11
  "---x---x--xx--x-", # 12
  "---x--xx--x---x-", # 13
  "--xx--x---x---x-", # 14
  "-xxx-xxx-xxx-xxx", # 15
  "xxxxxxxxxxxxxxxx", # 16
  "--x---x---xx--x-", # 17
  "--x---xx--x---x-", # 18
  "--xx--x---x---x-", # 19
  "--x---x----x--x-", # 20
  "--x----x--x---x-", # 21
  "---x--x---x---x-", # 22
]

mm = to_mm "1-2-2-2-3-4-5-6-2", patterns.length  # initialization with number of patterns = 22
mm = to_mm "2-7-8-9-10-11-12-13-14-2", mm
mm = to_mm "7-2", mm
mm = to_mm "2-11-12-13-14-2", mm
mm = to_mm "14-15-16-2", mm
mm = to_mm "16-15-16-2", mm
mm = to_mm "2-3-17-18-19-2", mm
mm = to_mm "7-20-21-22-2", mm
mm = to_mm "10-15", mm
mm = to_mm "16-1", mm
mm = to_mm "14-6", mm
mm = to_mm "10-6", mm
mm = to_mm "6-15", mm

First, you define the patterns that are possible and then you write down the sequences that should be used. The more often a certain transition is used (such as 2-2) the more likely it is to happen.

1 Like