In case you have read the post Markov chains for beginners and got a bit curious about what you could do with it, here is a second little lesson for you. I would like to give an example on how to create a melody that is perfectly in harmony with the underlying chord progressions but still rich and random. Actually, my intention here is to overdo it and create melodies that are as harmonic as possible, because this demonstrates the possibilities we have got using Markov chains even better. Of course, the opposite would be possible as well: create beats or sequences that are as nasty and aggressive as possible.
We start with a simple and standard chord progression that has been used in thousands of songs. Maybe you feel familiar with it when playing:
use_debug false
use_bpm 80
st = 4.0 # sleep time for progressions
progression = [
[(chord :C3, :major, num_octaves: 2), :ionian],
[(chord :A3, :minor, num_octaves: 2), :aeolian],
[(chord :D3, :minor, num_octaves: 2), :dorian],
[(chord :G3, 'dom7', num_octaves: 2), :mixolydian]
]
# organ playing chord progression
with_fx :reverb,room: 0.6, mix: 0.3 do
with_fx :flanger, feedback: 0.5 do
with_fx :lpf, mix: 0.5, cutoff: 70 do
live_loop :organ, auto_cue: false do
p = progression.tick
synth :tri, note: p[0], attack: 0.01, sustain: st, release: 0.1, amp: 0.8
sleep st
end
end
end
end
There are 4 chords in key C that can be played in an endless loop. The array progression
contains the chords, but also the modes of scales that are associated with these chords: :ionian, :aeolian, :dorian, :mixolydian
. This chord-mode association is pretty standard and can be found in Wikipedia.
For our application it is an important fact, that all notes that make up the chord are contained in the corresponding scales as well. And moreover, they are found at always the same position. In each of those scales s
, the major/minor chord is built using the notes s[0]-s[2]-s[4]
, i.e., the indices needed to get the chord notes are always 0-2-4
, independent of the chord being played. That’s very helpful when building the Markov matrix:
# Markov matrix for 8 notes of the scales
markov_matrix = [
# 0* 1 2* 3 4* 5 6* 7*
[0.05, 0.2, 0.75, 0.0, 0.0, 0.0, 0.0, 0.0], # 0* base tone
[0.1, 0.05, 0.75, 0.1, 0.0, 0.0, 0.0, 0.0], # 1
[0.0, 0.1, 0.1, 0.2, 0.6, 0.0, 0.0, 0.0], # 2* third
[0.0, 0.0, 0.2, 0.0, 0.7, 0.0, 0.0, 0.1], # 3
[0.0, 0.0, 0.2, 0.1, 0.2, 0.2, 0.3, 0.0], # 4* fifth
[0.0, 0.0, 0.0, 0.1, 0.35, 0.05, 0.4, 0.1], # 5
[0.0, 0.0, 0.0, 0.0, 0.4, 0.2, 0.1, 0.3], # 6* seventh
[0.0, 0.0, 0.0, 0.0, 0.3, 0.2, 0.5, 0.0], # 7* octave
]
The comments above and behind each row tell us the indices in the scales. Notes belonging to the chord are marked with a *
. I have marked 6
and 7
as well, because the seventh and the octave also nicely harmonize with the chord notes.
Now, the main point is to understand how the entries in the matrix have been chosen. The first row defines what notes should come next while the base note of the chord is being played. As you can see there is a 0.05
probability to play the same note again, 0.2
to move up a single step, but 0.75
to directly go to the next note that belongs to the chord. And this pattern more or less is repeated in every row: The highest probabilities are always to use the notes of the chords, and only with lower probabilities allow for the notes between. And this is what makes the created melodies strongly related to the notes of the chords and thus very harmonic.
Here’s the complete program. The function next_note_idx
is exactly the same as in the previous post on that topic.
# harmonic melody using Markov chain
# written by Nechoj
use_debug false
use_bpm 80
st = 4.0 # sleep time for progressions
progression = [
[(chord :C3, :major, num_octaves: 2), :ionian],
[(chord :A3, :minor, num_octaves: 2), :aeolian],
[(chord :D3, :minor, num_octaves: 2), :dorian],
[(chord :G3, 'dom7', num_octaves: 2), :mixolydian]
]
# organ playing chord progression
with_fx :reverb,room: 0.6, mix: 0.3 do
with_fx :flanger, feedback: 0.5 do
with_fx :lpf, mix: 0.5, cutoff: 70 do
live_loop :organ, auto_cue: false do
p = progression.tick
synth :tri, note: p[0], attack: 0.01, sustain: st, release: 0.1, amp: 0.8
sleep st
end
end
end
end
# Markov matrix for 8 notes of the scales
markov_matrix = [
# 0* 1 2* 3 4* 5 6* 7*
[0.05, 0.2, 0.75, 0.0, 0.0, 0.0, 0.0, 0.0], # 0* base tone
[0.1, 0.05, 0.75, 0.1, 0.0, 0.0, 0.0, 0.0], # 1
[0.0, 0.1, 0.1, 0.2, 0.6, 0.0, 0.0, 0.0], # 2* third
[0.0, 0.0, 0.2, 0.0, 0.7, 0.0, 0.0, 0.1], # 3
[0.0, 0.0, 0.2, 0.1, 0.2, 0.2, 0.3, 0.0], # 4* fifth
[0.0, 0.0, 0.0, 0.1, 0.35, 0.05, 0.4, 0.1], # 5
[0.0, 0.0, 0.0, 0.0, 0.4, 0.2, 0.1, 0.3], # 6* seventh
[0.0, 0.0, 0.0, 0.0, 0.3, 0.2, 0.5, 0.0], # 7* octave
]
# function to get next index of note in scale according to markov matrix
define :next_note_idx do |current_note_idx|
n = 0
r = rand
pp = 0
markov_matrix[current_note_idx].each do |p|
pp += p
break if pp > r
n += 1
end
return n
end
with_fx :reverb,room: 0.8, mix: 0.4 do
live_loop :melody, auto_cue: false do
# build scale to play
p = progression.tick
base_note = p[0][0] # first note of current chord
base_note += 12 if base_note < :D4.to_i # lift up 1 octave if too low
base_note += 12 if base_note < :D4.to_i # lift up 1 octave if too low (again)
mode = p[1] # mode of scale defined in progressions
sca = scale base_note, mode
n = [0, 2, 4, 7].choose # index of first note in scale to play
with_synth :pulse do
with_synth_defaults amp: 0.8, cutoff: 90 do
ng = 4 # number of chunks of notes to play with same density
ng.times do |i|
d = [2, 2, 4, 4, 4].choose
density d do |dd|
play sca[n], attack: 0.02*d, release: 0.05, sustain: st/ng-0.12
n = next_note_idx(n)
sleep st/ng
end
end
end
end
end
end
There are two additional principles coded into the matrix. First is to create an upward direction of the melody when starting at low notes, downwards when starting with the high indices. Second, there are almost no probabilities for larger jumps. All probabilities in a row are placed on neighboring positions, and the 0.0
probabilities are grouped as well, marking forbidden areas. This gives a dense progression of the melody notes without larger jumps. These principles contribute to the impression of great harmony (as said above, I tried to overdo it …).
The entry point defining the note to start with when a new chord is played, is defined at random in the line
n = [0, 2, 4, 7].choose
There is a certain rhythm in the melody, because the length of the notes being played depends on the density of the time scale which is chosen at random in the line
d = [2, 2, 4, 4, 4].choose
In oder to make the melody less harmonic, lower the probabilities in the Markov matrix for the chord notes and increase e.g. the probabilities for indices 1, 3, 5
. Make sure that the sum of probabilities in each row always is 1.0
. Implement larger jumps in each row. And finally, use density
values such as 3
or 5
to introduce uneven rhythmic patterns for the melody.