Drum computer with patterns, swing & easy live controls

I’m a big fan of the Elektron Analog Rytm drum computer. I wrote a script to model a general drum computer. You can make patterns, change the volume (or another parameter) per step (called parameter lock in the Rytm), and combine several patterns into a song.

I added live controls, like easy ways to do live coding:

  • swing
  • mixer. Instead of starting and stopping a loop, you can also set the volume to 0. In this way you can avoid sync issues.
  • changing start position of pattern, reverse, tempo

I hope you enjoy it!

# Live controls. This is an attempt to have several interesting modifications to the sound close at hand, to make
# live coding during a performance easier. However, it makes the code a bit more complex.

use_bpm 126


# These controls are to easily transpose the pitch (tune) of the melodies. 12 is the number
# musical notes in an octave. Changing whole octaves makes sure that the melody stays in harmony
# with the other melodies.

pitch_low = 0 * 12
pitch_mid = 1 * 12


# This makes sure that the sleep time is not constant, but swinging.
# To make the rhytm straight, change the swing_amount to 4. Go to 5 and it sounds laidback.
# Going close to 8 reduces the second sleep in swing to almost 0, thus almost skipping the note altogether
# Make to add 1 decimal to the swing_amount, which turns to data type into a floating number.

swing_amount = 4.8
swing = [ swing_amount / 64*4 , (8.0 - swing_amount) /64*4 ].ring


# This is an easy approach to mute or change the volume of the different tracks. It's like a audio mixer.
# The values work on the amp: function. Another option to turn instruments on/off is to start and stop
# the live_loops, but somehow I feel the result is less easy to control. In this situation, you let all
# the loops play all the time, avoiding out of sync problems altogether.
# bd = bassdrum, sn =snaredrum, cy = cymbal, pe = cymbal pedal, low = instr low, mid = instr mid, ran = instr random

#loops_mute =[bd   sn  cy pe  dr5 dr6 dr7 dr8 low mid]
loops_mute = [ 0.8, 1, 1, 1,  1,  0,   1,  0,  1,  1]


# You can play the arrays for drum patterns and melody from start till end, but why not play with time?
# The first array defines the start position in the loop (melody or rhythm). Change it to create more complex
# intertwined rhythms. The second array defines how many steps for sleep you take every cycle. The default for drums is
# 2, because the first step in the drum pattern array is the note, and the second defines the volume of the note (see
# more specific comments at the sections of the drum patterns). You can also use a negative number, which reverses the pattern.

# pos= [drums low mid]  (only use even number)
pos =   [0,    0,  4]

# time_tweak = [drums low mid]
time_tweak =   [2,     1,  -1]


#-----------------------------------------------------------------------------------------------------------------

# This section is a method to create drums like a drum computers. The rhythms in a drum computer are called patterns.
# This usually involves a row of 16 buttons and a cursor looping through this row. For each drum sound, you press the
# buttons on the positions where you want the drum sound to play. Usually the pattern plays from 1 to 16 and repeats.
# However, in Sonic Pi you can also skip steps or reverse the pattern (also see the array above called time_tweak.
# In some modern drum computers (like Elektron Analog Rytm), you can also change a parameter of a drum sound for each
# step. This is called parameter lock. In our drum computer, we can change the volume of each separate drum for each step.
# Finally, you can connect different patterns to a song.

# This defines the drum samples (drum kit). The only reason to do this, is to make the names shorter, so that they take less
# space in the arrays of the drum patterns. s0 stands for sample 0.

define :s0 do
  :bd_klub
end

define :s1 do
  :sn_dub
end

define :s2 do
  :drum_cymbal_closed
end

define :s3 do
  :drum_cymbal_pedal
end

define :s4 do
  :elec_flip
end


# These are the drum patterns. The drum looper (next part) will cycle through these arrays. These arrays consist of
# 32 steps. However, 2 steps are taken at the same time, before a sleep command. The first step of this duo is to place
# a sample (e.g. s0) or a rest (:r). The second one is used to set the volume (:amp) of the sample. However, the second one
# can also be used for other parameters of a sample. For example the length of the sample (:finish) or the pitch.

kick_1 = [s0,1, :r,1, :r,1, :r,1, s0,1, :r,1, :r,1, :r,1, s0,1, :r,1, :r,1, :r,1, s0,1, :r,1, :r,1, :r,1].ring
kick_2 = [s0,1, :r,1, :r,1, :r,1, s0,1, s0,0.5, :r,1, s0,1.2, :r,1, :r,1, :r,1, :r,1, :r,1, :r,1, :r,1, :r,1].ring
snare_1 = [:r,1, :r,1, :r,1, :r,1, s1,0.6, :r,1, :r,1, :r,1, :r,1, :r,1, :r,1, :r,1, s1,0.6, :r,1, :r,1, :r,1].ring
snare_2 = [:r,1, :r,1, :r,1, :r,1, s1,0.6, :r,1, :r,1, :r,1, :r,1, s1,0.7, :r,1, s1,0.5, s1,0.4, :r,1, :r,1, :r,1].ring
cym_1 = [s2,0.3, s2,0.5, s2,1, :r,1, s2,1, s2,0.3, s2,1, :r,1, :r,1, :r,1, s2,1, :r,1, :r,1, :r,1, :r,1, :r,1].ring
ped_1 = [:r,1, :r,1, :r,1, :r,1, :r,1, :r,1, :r,1, :r,1, :r,1, :r,1, :r,1, :r,1, s3,0.3, s3,0.6, s3,0.8, s3,1.2].ring
elec_1 = [:r,1, :r,1, :r,1, :r,1, :r,1, :r,1, :r,1, :r,1, s4,1, :r,0.3, :r,2, :r,1, :r,1, :r,1, :r,1, :r,1].ring
mute = [:r,1, :r,1, :r,1, :r,1, :r,1, :r,1, :r,1, :r,1, :r,1, :r,1, :r,1, :r,1, :r,1, :r,1, :r,1, :r,1].ring


# This function uses the drum patterns to create the movement through the patterns. A total of 4 patterns are defined here.
# Each pattern is a variable (e.g. drumname1). In the following live_loop :playdrum, you actually choose which patterns you
# want to play. This makes it very dynamic and easy to add more patterns, and play different variations of drums in the
# live_loop. Because the patterns contain 2 steps (note, volume) before a sleep command, the pattern skips 2 places each
# loop (pos[0]+=2). With a straight rhythm, this would do the job. But if you use the swing function, there is always a pair
# of steps with different duration. So if you would skip 2 steps, this would mean you always take either the shorter one or
# the longer one of the pair. That's why there is a ring which puts the swing position one place back 1 out of every 2
# loops.

define :choosedrum do |drumname1,drumname2,drumname3,drumname4,drumname5,drumname6,drumname7,drumname8|
  16.times do
    sample drumname1[pos[0]], amp: 0 + drumname1[pos[0]+1] * loops_mute[0]
    sample drumname2[pos[0]], amp: 0 + drumname2[pos[0]+1] * loops_mute[1], finish: 0.2
    sample drumname3[pos[0]], amp: drumname3[pos[0]+1] * loops_mute[2], finish: 0.1
    sample drumname4[pos[0]], amp: 0 + drumname4[pos[0]+1] * loops_mute[3], finish: 0.2
    sample drumname5[pos[0]], amp: 0 + drumname5[pos[0]+1] * loops_mute[4]
    sample drumname5[pos[0]], amp: 0 + drumname6[pos[0]+1] * loops_mute[5]
    sample drumname5[pos[0]], amp: 0 + drumname7[pos[0]+1] * loops_mute[6]
    sample drumname5[pos[0]], amp: 0 + drumname8[pos[0]+1] * loops_mute[7]
    sleep swing[pos[0] - (ring 0,1).tick]
    pos[0]+= time_tweak[0]
  end
end


# This is where you can make a song with patterns.

live_loop :playdrum do
  choosedrum(kick_1,snare_1,cym_1,mute,mute,mute,mute,mute)
  choosedrum(kick_1,snare_1,cym_1,ped_1,mute,mute,mute,mute)
  choosedrum(kick_1,snare_1,cym_1,mute,mute,mute,mute,mute)
  choosedrum(kick_2,snare_2,cym_1,ped_1,elec_1,mute,mute,mute)
end



# This is the basic array to draw the melodies from.

melody_1 = [:c4,:r,:ds4,:ds4,:g4,:c4,:r,:gs4,:r,:r,:gs3,:r,:g3,:r,:r,:r].ring


live_loop :instr_low do
  use_synth :subpulse
  use_synth_defaults cutoff: 70, release: 1.2, amp: 1 * loops_mute[8], pulse_width: rrand(0,1)
  16.times do
    play melody_1[pos[1]] + pitch_low
    sleep swing[pos[1]]
    pos[1]+= time_tweak[1]
  end
end

live_loop :instr_mid do
  use_synth :blade
  use_synth_defaults cutoff: rrand(75,120), release: 0.2, amp: rrand(1.0, 1.4) * loops_mute[9]
  with_fx :reverb, mix: 0.55, room: 0.85, damp: 0.8 do
    16.times do
      play melody_1[pos[2]] + pitch_mid
      sleep swing[pos[2]]
      pos[2]+= time_tweak[2]
    end
  end
end
3 Likes

Certainly interesting, I will enjoy twiddling with this.
Keep getting an error in the run window…
:r is normally a ‘rest’… so not quite sure whats
going on there…

Eli…

{run: 23, time: 14.7619, thread: :live_loop_playdrum}
├─ sample [:r]
│ - no match found, skipping.

Hi Eli,

You probably changed the start at # pos = [drums low mid]. Every “time” step in the pattern actually is a set of two steps. The first is which drum sample is being played, the second is the volume of the drum sample. So if you change the start to an odd number, you get an error. Try even numbers instead.

Will do… though I thought I’d just done a straight cut n paste from the web into SPi…
(EDIT: Yeah… cut n paste direct from your code, and still get the same error…).

In the meantime… had a twiddle with the instrument side of it… this will be most useful :slight_smile:

Many thanks!

Eli…

use_bpm 120

pitch_low = 0 * 12
pitch_mid = 1 * 12
pitch_hi = 2 * 12

swing_amount = 4.8
swing = [ swing_amount / 64*4 , (8.0 - swing_amount) /64*4 ].ring

# pos= [drums low mid hi]
pos =   [0,    0,  0, 0]

# time_tweak = [drums low mid hi]
time_tweak =   [2,     1,  -1, 1]

melody_1 = [:c4,:r,:ds4,:ds4,:g4,:c4,:r,:gs4,:r,:r,:gs3,:r,:g3,:r,:r,:r].ring
melody_2 = melody_1#.shuffle

live_loop :instr_low do
  use_synth :subpulse
  use_synth_defaults cutoff: 70, release: 1.2, amp: 1, pulse_width: rrand(0,1)
  16.times do
    play melody_1[pos[1]] + pitch_low
    sleep swing[pos[1]]
    pos[1]+= time_tweak[1]
  end
end

live_loop :instr_mid do
  use_synth :supersaw
  use_synth_defaults cutoff: rrand(75,120), release: 0.2, amp: rrand(1.0, 1.4)
  with_fx :reverb, mix: 0.55, room: 0.85, damp: 0.8 do
    16.times do
      play melody_1[pos[2]] + pitch_mid
      sleep swing[pos[2]]
      pos[2]+= time_tweak[2]
    end
  end
end

live_loop :instr_hi do
  use_synth :prophet
  use_synth_defaults cutoff: rrand(75,120), release: 0.2, amp: rrand(1.0, 1.4)
  with_fx :reverb, mix: 0.55, room: 0.85, damp: 0.8 do
    16.times do
      play melody_2[pos[3]] + pitch_hi
      sleep swing[pos[3]]
      pos[3]+= time_tweak[3]
    end
  end
end

Hello even four years later:

As Eli wrote any :r in drum patterns leads to
‘no match found’ for sample [:r] output.

This does not change with even nor odd
pos = settings.

There seems no ‘empty’/muted/unsound/rest sample
available in the default samples of SP.

Your song runs but it is not really clear what you exactly
want to achieve with your drumm-patterns containing ‘:r’.

Maybe you can rethink and/or correct that in your publication.

Because in general your example sounds great and is quite flexible.

Thanks

for example you could define your
choosedrum-function like the following with appended ‘rest’-conditions

.. if drumname_x[pos[0]]!=:r

so the choosedrum-function would be:

define :choosedrum do |drumname1,drumname2,drumname3,drumname4,drumname5,drumname6,drumname7,drumname8|
  16.times do
    sample drumname1[pos[0]], amp: 0 + drumname1[pos[0]+1] * loops_mute[0] if drumname1[pos[0]]!=:r
    sample drumname2[pos[0]], amp: 0 + drumname2[pos[0]+1] * loops_mute[1], finish: 0.2 if drumname2[pos[0]]!=:r
    sample drumname3[pos[0]], amp: drumname3[pos[0]+1] * loops_mute[2], finish: 0.1 if drumname3[pos[0]]!=:r
    sample drumname4[pos[0]], amp: 0 + drumname4[pos[0]+1] * loops_mute[3], finish: 0.2 if drumname4[pos[0]]!=:r
    sample drumname5[pos[0]], amp: 0 + drumname5[pos[0]+1] * loops_mute[4] if drumname5[pos[0]]!=:r
    sample drumname6[pos[0]], amp: 0 + drumname6[pos[0]+1] * loops_mute[5] if drumname6[pos[0]]!=:r
    sample drumname7[pos[0]], amp: 0 + drumname7[pos[0]+1] * loops_mute[6] if drumname7[pos[0]]!=:r
    sample drumname8[pos[0]], amp: 0 + drumname8[pos[0]+1] * loops_mute[7] if drumname8[pos[0]]!=:r
    sleep swing[pos[0] - (ring 0,1).tick]
    pos[0]+= time_tweak[0]
  end
end

Maybe some ruby-geek sees the above code.

Probably the function parameter ‘drumnameN’
and the frequent pos[0]-references would be refactoring
candidates.

Are there no mistakes in the code? I did something wrong I suppose.