Skip to content

Instantly share code, notes, and snippets.

@omardelarosa
Last active November 6, 2021 03:45
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save omardelarosa/e962e595de9bd1b7f94afde803997831 to your computer and use it in GitHub Desktop.
Save omardelarosa/e962e595de9bd1b7f94afde803997831 to your computer and use it in GitHub Desktop.
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":

Using markov chaining to create instrumental music for The Weeknd's "High For This"

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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment