{{ message }}

Instantly share code, notes, and snippets.

Last active Nov 6, 2021
Generative Hip Hop using Markov Chaining

# Generative Hip Hop using Markov Chaining

This represents one way of creating generative music by doing a randomized walk on a graph where each node represents a note state in a scale and each edge represents a transition. Edges are chosen randomly, but repetition of edges allows the simulation of weighted choices at each transition.

For example, given the following ruby hash representation:

```H = {
8 => [0],
7 => [8],
6 => [2],
5 => [2, 2, 0, 0, 7, 7],
4 => [1, 1, 2, 2, 6, 6],
3 => [5],
2 => [4, 4, 0],
1 => [3, 3, 3, 0, 0, 0],
0 => [-2, 2, 4, 4, 0],
-1 => [-3, -3, 0, 0],
-2 => [-4, -4, 0],
-3 => [-5],
-4 => [-1, -1, -2, -2, -6, -6],
-5 => [-2, -2, 0, 0, -7, -7],
-6 => [2],
-7 => [8],
-8 => [0],
}```

The graph below would be produced:

For example, if you are on the `0` note of the scale and there are 3 edges to note `3` and 1 to note `5`. This would represent a `0.75` probability of going to note `3` and `0.25` of going to note `5` using a random "choice" function during each transition.

You can see an example of how this works in this YouTube video I made applying this principle to generate a generative backing track for The Weeknd's song "High For This":

This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
 use_bpm 60 # global bpm M = 4.0 # measure size R = 59 # root note S = scale(R, :minor) # working scale set :root, 0 # initialize root note (relative) SAMPLE = "path/to/vocal/sample/vocals.wav" # A hash to control loop levels in a single place LEVELS = { hats: 1.75, snares: 1.75, kicks: 3.0, right: 0.15, left: 0.5, subbass: 0.5, vox: 2.75 } # This hash simulates a markov chain. # Each key is the state and the array # value represents the next state from # which to choose at random. H = { 8 => [0], 7 => [8], 6 => [2], 5 => [2, 2, 0, 0, 7, 7], 4 => [1, 1, 2, 2, 6, 6], 3 => [5], 2 => [4, 4, 0], 1 => [3, 3, 3, 0, 0, 0], 0 => [-2, 2, 4, 4, 0], -1 => [-3, -3, 0, 0], -2 => [-4, -4, 0], -3 => [-5], -4 => [-1, -1, -2, -2, -6, -6], -5 => [-2, -2, 0, 0, -7, -7], -6 => [2], -7 => [8], -8 => [0], } # Rhythm Patterns p1 = (bools 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 0, 0, 1, 0) # kick drums p2 = (bools 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0) # snare drums p3 = (knit, M/16, 4, M/24, 6, M/16, 4, M/24, 6, M/16, 4, M/64, 8) # hihats sb = (50..100).ring.mirror # simulate fade-up -> fade-down ds = (0..9).map {|n| n * 0.1}.ring # Random durations d1 = (knit M/8, 8, M/16, 4, M/-8, 2, M/-16, 2) # State machine utility functions define :markov do |a, h| h[a].sample; end # Chooses the next state at random from hash define :gg do get[:root]; end # simplified root note in scale getter define :g do S[R + get[:root]]; end # simplified raw root note getter define :s do |n| set :root, n; end # simplified root note setter # play function with duration baked in and the ability to play rests using negative (-) time (i.e. M/-4 would be a quarter rest). define :pplay do |n, d| play n if d >= 0 sleep d.abs end # Rhythm Components live_loop :kicks do # kick drum loop l = LEVELS[:kicks] || 0 # keeping this outside of the do...end block ensures a consistent level across the measure 16.times do # using 16 times here makes the grid loops better synced to measures with_fx :level, amp: l do sample :bd_tek if p1.tick sleep M / 16 end end end live_loop :snares do # snares loop l = LEVELS[:snares] || 0 # keeping this outside of the do...end block ensures a consistent level across the measure 16.times do # using 16 times here makes the grid loops better synced to measures with_fx :level, amp: l do sample :sn_dolf if p2.tick sleep M / 16 end end end live_loop :hats do # hi-hats loop l = LEVELS[:hats] || 0 # keeping this outside of the do...end block ensures a consistent level across the measure 16.times do with_fx :level, amp: l do with_fx :eq, high: -0.5, mid: -0.9 do with_fx :distortion, mix: 0.9 do sample :elec_tick, rate: 1, amp: 3.8 + rrand(-0.5, 0.5) if p3.tick >= 0 sleep p3.look.abs end end end end end # Vox samples live_loop :vox do with_fx :level, amp: LEVELS[:vox] || 0 do with_fx :reverb, mix: ds.tick, room: 0.4 do with_fx :echo, mix: ds.look * 0.2, decay: 0.25 do sample SAMPLE, rate: 1.0, amp: 2.5 # play your vocal sample end end end sleep M * 16 # I chose a 16-measure vocal sample end # Melodic Components live_loop :right do with_fx :level, amp: LEVELS[:right] || 0 do with_fx :reverb, mix: 0.9, room: 0.8 do with_fx :echo, mix: 0.5 do use_synth :pretty_bell ##| use_synth :fm n1 = S[markov(gg, H)] + 12 # choose 1st random note in scale n2 = S[markov(gg, H)] + 12 # choose 2nd random note in scale d = d1.choose # choose random duration play n1, release: M/2, sustain: M/2 play n2 if (bools 1, 0, 0, 0).tick # plays a chord on leading edge of measures sleep M/4 sleep d.abs 2.times do pplay S[markov(gg, H)] + 12, d sleep (M/4 - d.abs) end end end end end live_loop :left do use_synth :sine with_fx :level, amp: LEVELS[:left] || 0 do with_fx :reverb, mix: 0.8, room: 0.8 do with_fx :echo, mix: 0.5 do s markov(gg, H) # update the state using markov chaining pplay (S[gg] - 12), M/8 # down one octave from root sleep (M / 4) + (M / 8) pplay (S[gg - 5] - 12), M/8 # down a fifth and an octave from the root sleep (M / 4) - (M / 8) end end end end live_loop :subbass do n = g - 24 # down two octaves from current root d = (sb.tick * 0.01) - 0.01 with_fx :level, amp: LEVELS[:subbass] || 0 do with_fx :reverb, mix: 0.8 do with_fx :distortion, distort: 0.2 do use_synth :pretty_bell play n, release: 4, sustain: 4, amp: d sleep M / 1 end end end end