Sonic Pi : “live sounding” drums

Phil showcases “live drum” samples & structures in Sonic Pi.

So, Phil is always playing around with the amazing Sonic Pi (By Sam Aaron)… if you’ve not got it, you really should download it… it’s an amazing & FUN tool to make music with AND learn / push your Ruby programming…

So! Phil has been working on making “live sounding” drums in Sonic Pi for a while, as well as an ability to create different patters & the ability to create structures (“songs”) – he’s come up with this:

One of the helpful aspects to this set up is, the beautifully recorded free drum samples from Judd Madden ( http://juddmadden.com/drum-samples.html ). The drum sounds in Sonic Pi are great, but Judd’s samples are absolutely superb.

Phil has commented the code as best he can, if you do try to reproduce this, you’ll need to save the drum samples into the right folders…

use_debug false
use_cue_logging false
use_bpm 120

d_root = "C:/Drums/twi"
drums_bass = "Kick/"
drums_snare = "Snares/"
drums_hat = "Hats/"
drums_cymbal = "Cymbals/"
drums_tom = "Toms/"

#......[1,2,3,4,5,6,7,8,1,2,3,4,5,6,7,8,1,2,3,4,5,6,7,8,1,2,3,4,5,6,7,8]
# some patterns for bass / snare
bds0 = [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]
bds1 = [1,0,0,0,0,0,0,0,0,0,0,0]
bds2 = bds1 + [2,0,0,0,0,0,0,0,0,0,0,0,0,1,0,2,0,0,0,0,2,0]
bds3 = [1,0,0,0,0,0,0,0,1,0,1,0,0,0,0,0]

sns0 = bds0
sns1 = [0,0,1,0,0,0,0,0]
sns2 = sns1 + [2,0,2,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,3]
sns3 = [0,0,1,0,0,0,0,0,0,0,2,0,0,0,3,0]

#some constants (rr = random range to draw from)
@rr = 12
@mc = 0
@sec = "intro"

# sections is an Array that holds arrays with "section" and a range as to when they should play.
# we later use the values to calculate how long the "main loop" sleeps for before moving to the next position/section
sections = [
  ["intro", (0..0).to_a],
  ["space", (1..1).to_a],
  ["build", (2..5).to_a],
  ["filler", (6..7).to_a],
  ["main", (8..15).to_a],
  ["filler", (16..17).to_a],
  ["main", (18..25).to_a],
  ["end", (26..26).to_a]
]

# change to true to introduce the various drums to the mix
# "parts" is a HASH that holds info on what drums to play for each section
parts = {
  "intro" => {'bds' => false,
              'sns' => false,
              'hts' => false,
              'cym' => true,
              'spl' => false,
              'tms' => true,
              'rnd' => false},
  "build" => {'bds' => true,
              'sns' => false,
              'hts' => true,
              'cym' => true,
              'spl' => false,
              'tms' => false,
              'rnd' => false},
  "main" =>  {'bds' => true,
              'sns' => true,
              'hts' => true,
              'cym' => true,
              'spl' => true,
              'tms' => false,
              'rnd' => false},
  "filler" =>  {'bds' => true,
                'sns' => true,
                'hts' => true,
                'cym' => true,
                'spl' => false,
                'tms' => true,
                'rnd' => true},
  "space" =>  {'bds' => false,
               'sns' => true,
               'hts' => true,
               'cym' => true,
               'spl' => true,
               'tms' => false,
               'rnd' => false},
  "end" => {'bds' => false,
            'sns' => false,
            'hts' => false,
            'cym' => false,
            'spl' => false,
            'tms' => false,
            'rnd' => false}
}
# a HAS that holds info on what patterns to play for what section
drumsForSections = {
  "intro" => {"bds" => bds0, "sns"=> sns0},
  "build" => {"bds" => bds1, "sns"=> sns0},
  "main" => {"bds" => bds2, "sns"=> sns2},
  "filler" => {"bds" => bds2, "sns"=> sns3},
  "space" => {"bds" => bds3, "sns"=> sns3},
  "end" => {"bds" => bds0, "sns"=> sns0},
}

# main counter keeps track of what section we should use
# we "sleep" for bar * (end - start) & pt in "8 beats" if the value of e-s = 0
live_loop :mainCount do
  bar = 8
  tick
  # get the name of the section from the sections array via "look" (the value of tick on this loop)
  @sec = sections[look][0]
  puts @sec #  show the current section name
  if @sec == "end"
    stop
  else
    if @sec == "main"
      # each loop if "main" starts with a Crash Cymbal
      sample d_root+drums_cymbal, "Crash C #{rand_i(4)+1}"
    end
    # calculate how long we should sleep for from this sections start / end numbers * 8 beats
    s = sections[look][1][0]
    e = sections[look][1][-1]
    zzz = (e-s)*bar
    if zzz == 0
      zzz = 8
    end
    
    sleep zzz
  end
end

# ========== drum tracks in separate live_loops

live_loop :ride do
  # simple ride cymbal playing each beat, if "true"
  if parts[@sec]["cym"]then
    # randomise the ride sample used & vary the amp value
    sample d_root+drums_cymbal, "Ride Cymbal #{rand_i(2) + 1}", amp: rand(0.4) + 0.8
  end
  sleep 1
end

live_loop :bd do
  tick
  # bass drum plays, if "true"
  if parts[@sec]["bds"] then
    # we look in the "drumsForSections" in the current section (e.g. "intro") then the "bds" for that section
    # if the beat ISN'T a 0, we play the BD sample.
    if drumsForSections[@sec]["bds"].look != 0 then
      sample d_root+drums_bass, rand_i(3) + 1, amp: rand(0.4) + 0.8
      # if it's the start of a bar (8 beats) we add in a bd_boom
      if look % 8 == 0 then
        sample :bd_boom
      end
    end
  end
  sleep 0.25
end

# an array for different types of snares
snlist = [0, "Off", "On", "Flam"]

live_loop :sn do
  tick
  with_fx :pan, pan: -0.25 do
    # we look in the "drumsForSections" in the current section (e.g. "intro") then the "sns" for that section
    if parts[@sec]["sns"] then
      # if the beat ISN'T a 0, we play the Snare sample.
      # and also use the number to pull in the specific snare style (Off, On, Flam)
      if drumsForSections[@sec]["sns"].look != 0 then
        sample d_root+drums_snare, snlist[drumsForSections[@sec]["sns"].look], pick, amp: rand(0.4) + 0.6
      end
    end
    sleep 0.25
  end
end

live_loop :splash do
  tick
  p = 0
  # if splashes are to play ( in parts : 'spl' => true )
  if parts[@sec]["spl"] then
    sample d_root+drums_cymbal, "Crash", "A", pick, amp: rand(0.4) + 0.8, pan: 0.3
    # add a random china bell hit for variance
    if (1..3).include? rand_i(@rr) then
      sleep 1.5
      p = 1.5
      sample d_root+drums_cymbal, "China", "Bell", pick, amp: rand(0.4) + 0.8, pan: 0.3
    end
  end
  sleep 8 - p
end

# set up some arrays for the Tom  Filler
# tom spread will sequentially play the specific tm type (2xhigh 2xmid, 2xFloor here)
tomspread = ["high", "high", "mid", "mid", "floor", "floor"].ring
# we can also pan the tom hits from left to right
tompan = [-0.8, -0.6, -0.4, -0.2, 0, 0.2, 0.4, 0.6].ring
# a counter to keep track of what tom to play in tomspread
tomtick = 0

live_loop :tm do
  # if "tms" is true for this section
  if parts[@sec]["tms"] then
    # check for "rnd" for potential tom hit randomisation
    if parts[@sec]["rnd"]
      if rand_i(@rr) == 0 then
        sample d_root+drums_tom, tomspread[tomtick], pick, amp: rand(0.4) + 0.4, pan: tompan[tomtick]
        tomtick = tomtick+1
      end
    else
      sample d_root+drums_tom, tomspread[tomtick], pick, amp: rand(0.4) + 0.4, pan: tompan[tomtick]
      tomtick = tomtick+1
    end
  end
  sleep 0.25
end

If you have any questions or comments, do get in touch!