Combining basic drum rhythms
The content of this page is also available on YouTube: https://youtu.be/Oog_aunpVms |
Let’s say we have several basic drum rhythms that we want to freely combine randomly. For example:
where the note mi means the right hand, and la means the left. These rhythms can be easily combined to fill a beat, for example, 5b -> 5b -> 3 -> 3
or 5a -> 4 -> 4 -> 3
and so on . They can also be combined to fill two bars, etc. Note that some sequences, such as 5a -> 4 -> 4 -> 3
, They lead to a hand change: each time such a sequence is played, the "leading" hand changes. This will be important in the future.
The goal is to be able to play arbitrary sequences for arbitrary periods of time. How can I put this into practice? We use random_notes_sequence
for faster creation of long 8-clock sequences using Julia.
Definition of Notes
First, you need to define instances of Notes
that will correspond to these four basic rhythms.
using MusicManipulations, MusicVisualizations
tpq = 960 # количество импульсов на четвертную ноту
sixt = 240 # длительность шестнадцатой ноты
left = name_to_pitch("A5")
right = name_to_pitch("E6")
88
Reminder: Note(pitch, intensity, start, duration)
motif1 = [ # мотив 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 = [ # мотив 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 = [ # мотив 3
Note(right, 100, 0, sixt),
Note(left, 50, sixt, sixt),
Note(left, 50, 2sixt, sixt),
]
motif4 = [ # мотив 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 set of note sequences from which random fragments can be taken. Let’s create sequences of 8 bars (that is, 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 = 100 | pos = 29040, dur = 240
Note A5 | vel = 50 | pos = 29280, dur = 240
Note A5 | vel = 50 | pos = 29520, dur = 240
Note E6 | vel = 100 | pos = 29760, dur = 240
Note A5 | vel = 50 | pos = 30000, dur = 240
Note E6 | vel = 50 | pos = 30240, dur = 240
Note E6 | vel = 50 | pos = 30480, dur = 240
Now, if necessary, they can be written to a MIDI file by simply calling save("drums_patterns.mid", notes)
. In addition, using https://musescore.org [MuseScore] The result can be visualized and printed. This interface provides a function musescore
.
musescore("drums_patterns.png", notes)

this is a pre-prepared version — your random sequence is likely to be different
It turned out well, but there is a problem: The sequence does not take into account the fact that when playing some rhythms (5b
and 4
), the leading hand changes. This problem is solved in the next section.
Adding alternating hands and lyrics
Note that random_note_sequence
also returns the indexes of the motifs that were used to create the sequence.:
seq
30-element Vector{Int64}:
1
3
1
4
4
2
2
3
3
1
⋮
2
3
2
1
1
1
3
3
4
With this information, we can add the correct "labels". To alternate hands, it is enough to replace the corresponding notes of mi with la and vice versa. Let’s define a number of structures like metadata.
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 element of each tuple is just the name of the rhythm, which will also be displayed in the score as the "lyrics". The second element of the tuple indicates whether the leading hand is changing for the rhythm.
The function that "reverses" the note label looks like this:
inverse!(n::Note) = (n.pitch = (n.pitch == left ? right : left));
The function that "counts" the duration of each beat to place the lyrics in the correct positions in the score looks like this:
note_length(s::String) = parse(Int, s[1]);
(Remember that sixth
is the duration of the sixteenth note.) Now we initialize an empty object. MIDITrack
and add all the 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 # Обращаем ноты
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 = 100 | pos = 29040, dur = 240
Note E6 | vel = 50 | pos = 29280, dur = 240
Note E6 | vel = 50 | pos = 29520, dur = 240
Note A5 | vel = 100 | pos = 29760, dur = 240
Note E6 | vel = 50 | pos = 30000, dur = 240
Note A5 | vel = 50 | pos = 30240, dur = 240
Note A5 | vel = 50 | pos = 30480, dur = 240
Finally, for the purpose of visualization, we use the function again musescore
by passing a MIDI file as input:
musescore("drums_patterns_with_names.png", MIDIFile(1, 960, [track]))
