Mixing basic drum patterns

Note

This page is also available as a YouTube video here: https://youtu.be/Oog_aunpVms


Let's say that we have some basic drum patterns that we want to be able to combine freely at random combinations. For example,

Basic drum patterns

where the note E means right hand, while A means left hand. These patterns can be easily combined to fill a bar, for example 5b -> 5b -> 3 -> 3 or 5a -> 4 -> 4 -> 3, etc. They can also be combined to fill two bars and so on. Notice that some sequences, like e.g. the 5a -> 4 -> 4 -> 3, result in alternating hands: each time the sequence is played the hand that "leads" is swapped. This will be important later on.

The goal is to be able to play arbitrary sequences of them for arbitrary lengths. How does one practice that? We will use random_notes_sequence to create longer 8-bar sequences faster with the help of Julia.

Defining the Notes

We first have to define the Notes instances that will correspond to those four basic patterns.

using MusicManipulations, MusicVisualizations

tpq = 960 # ticks per quarter note
sixt = 240 # duration of sixteenth note
left = name_to_pitch("A5")
right = name_to_pitch("E6")
88

Reminder: Note(pitch, intensity, start, duration)

motif1 = [ # motif 5a
Note(right, 100, 0, sixt),
Note(left, 50, sixt, sixt),
Note(right, 100, 2sixt, sixt),
Note(left, 50, 3sixt, sixt),
Note(left, 50, 4sixt, sixt)
]

motif2 = [ # motif 5b
Note(right, 100, 0, sixt),
Note(left, 50, sixt, sixt),
Note(left, 50, 2sixt, sixt),
Note(right, 50, 3sixt, sixt),
Note(right, 50, 4sixt, sixt)
]

motif3 = [ # motif 3
Note(right, 100, 0, sixt),
Note(left, 50, sixt, sixt),
Note(left, 50, 2sixt, sixt),
]

motif4 = [ # motif 4
Note(right, 100, 0, sixt),
Note(left, 50, sixt, sixt),
Note(right, 50, 2sixt, sixt),
Note(right, 50, 3sixt, sixt),
]

motifs = Notes.([motif1, motif2, motif3, motif4], tpq)
4-element Vector{Notes{Note}}:
 Notes{Note} with 5 notes
 Notes{Note} with 5 notes
 Notes{Note} with 3 notes
 Notes{Note} with 4 notes

Now motifs stands for a pool of note sequences we can draw random samples from. Let's generate sequences that are 8-bars long (i.e. 32 quarter notes)

q = tpq*32

notes, seq = random_notes_sequence(motifs, q)
notes
128 Notes with tpq=960
 Note E6  | vel = 100 | pos = 0, dur = 240
 Note A5  | vel = 50  | pos = 240, dur = 240
 Note E6  | vel = 100 | pos = 480, dur = 240
 Note A5  | vel = 50  | pos = 720, dur = 240
 Note A5  | vel = 50  | pos = 960, dur = 240
 Note E6  | vel = 100 | pos = 1200, dur = 240
 Note A5  | vel = 50  | pos = 1440, dur = 240
  ⋮
 Note E6  | vel = 50  | pos = 29040, dur = 240
 Note E6  | vel = 100 | pos = 29280, dur = 240
 Note A5  | vel = 50  | pos = 29520, dur = 240
 Note A5  | vel = 50  | pos = 29760, dur = 240
 Note E6  | vel = 100 | pos = 30000, dur = 240
 Note A5  | vel = 50  | pos = 30240, dur = 240
 Note A5  | vel = 50  | pos = 30480, dur = 240

and now we can write these to a MIDI file simply by doing save("drums_patterns.mid", notes) if we want to. We can also use MuseScore, to visualize and print the result. The function musescore provides this interface.

musescore("drums_patterns.png", notes)

32-bar pattern sequence

this is a pre-made figure - your random sequence will probably differ

This worked nicely, but there is a problem: The sequence does not respect the fact that some specific patterns (5b and 4) swap the leading hand. This is what we tackle in the next section.

Adding alternating hands and Lyrics

Notice that random_note_sequence also returns the indices of the motifs that were used to create the sequence:

seq
30-element Vector{Int64}:
 1
 1
 1
 1
 2
 1
 1
 1
 3
 4
 ⋮
 1
 1
 2
 3
 2
 4
 4
 3
 3

We can use this information to put the correct "stickings". To alternate hands we simply need to replace the necessary E notes with A and vice versa. Let's define some "meta-data" like structures

accent1 = ("5a", false)
accent2 = ("5b", true)
accent3 = ("3", false)
accent4 = ("4", true)
accents = [accent1, accent2, accent3, accent4]
4-element Vector{Tuple{String, Bool}}:
 ("5a", 0)
 ("5b", 1)
 ("3", 0)
 ("4", 1)

The first entry of each tuple is simply the name of the pattern which we will also show in our music score as "lyrics". The second entry of the tuple simply denotes whether the pattern swaps the leading hand.

The function that will "inverse" a note sticking is:

inverse!(n::Note) = (n.pitch = (n.pitch == left ? right : left));

The function that will "count" how long is each pattern, so that we put the lyrics on the correct positions in the score, will be:

note_length(s::String) = parse(Int, s[1]);

(remember: sixt is the duration of one sixteenth note). We now initialize an empty MIDITrack and add all events to it!

track = MIDITrack()
ℓ = 0
right_leads = true

for i in 1:length(seq)

    s = accents[seq[i]][1]
    le = MIDI.LyricEvent(0, MIDI.LYRICEV, s)
    addevent!(track, ℓ*sixt, le)

    if !right_leads # Invert notes
        for j in ℓ+1:ℓ+note_length(s)
            inverse!(notes[j])
        end
    end

    global ℓ += note_length(s)

    change = accents[seq[i]][2]
    global right_leads = xor(right_leads, change)
end

addnotes!(track, notes)
notes
128 Notes with tpq=960
 Note E6  | vel = 100 | pos = 0, dur = 240
 Note A5  | vel = 50  | pos = 240, dur = 240
 Note E6  | vel = 100 | pos = 480, dur = 240
 Note A5  | vel = 50  | pos = 720, dur = 240
 Note A5  | vel = 50  | pos = 960, dur = 240
 Note E6  | vel = 100 | pos = 1200, dur = 240
 Note A5  | vel = 50  | pos = 1440, dur = 240
  ⋮
 Note A5  | vel = 50  | pos = 29040, dur = 240
 Note E6  | vel = 100 | pos = 29280, dur = 240
 Note A5  | vel = 50  | pos = 29520, dur = 240
 Note A5  | vel = 50  | pos = 29760, dur = 240
 Note E6  | vel = 100 | pos = 30000, dur = 240
 Note A5  | vel = 50  | pos = 30240, dur = 240
 Note A5  | vel = 50  | pos = 30480, dur = 240

Finally, to visualize, we use musescore again, using a midi file as an input

musescore("drums_patterns_with_names.png", MIDIFile(1, 960, [track]))

Correct 32-bar pattern sequence

Isn't it cool that even the lyrics text was displayed so seamlessly?