Idea for drums management

Hello, I’m new here and I want to share with you this implementation I made to handle drums inside a Sonic Pi track, in order to understand if this can be a good solution :slight_smile:

I liked the idea I found around the web (mostly here and here) to map the beats inside an array, to emulate somehow the common grids used by real drum machines and simplify the creation of beats.
I also wanted to have something that gives the possibility to manage in easy way the evolution of the beats during the track, so I used sync messages to register the measures number in order to create conditions inside the loop based on it.
Here an example of a beat that has a fill every forth measure:

use_bpm 120

# pattern: is an array of 1s and 0s. 1 = beat played, 0 = no beat
# drum_sample: the sample you want to play
define :play_drum_pattern do |pattern, drum_sample|
  # Every time this function is called a new thread is created.
  # The name of the thread is the tick number, in order to be always new
  # (I come up with this solution after some issues with timing)
  in_thread(name: tick.to_s) do
    pattern.each do |p|
      if p == 1 then
        sample drum_sample
      end
      sleep 0.25
    end
    # at the end, the thread is stopped to free the resources (don't know if necessary anyway)
    stop
  end
end

live_loop :drums do
  # loop synced with :measure
  # :measures carries the current measure of the loop, so we can use it to control the behaviour
  measure = (sync :measure)[0]
  puts measure
  if (measure % 4 != 0) then
    # pattern played for every measure but the last
    kick   = [1,0,0,0, 0,0,0,0, 1,0,0,0, 0,0,0,0]
    snare  = [0,0,0,0, 1,0,0,0, 0,0,0,0, 1,0,0,0]
    play_drum_pattern(kick, :drum_heavy_kick)
    play_drum_pattern(snare, :drum_snare_soft)
  else
    # pattern played for the last measure (the fill)
    kick     = [1,0,0,1, 0,0,0,1, 1,0,1,0, 0,0,0,0]
    snare    = [0,0,0,0, 1,0,0,0, 0,0,0,0, 0,0,0,0]
    open_hat = [0,0,0,0, 0,0,0,0, 0,0,0,0, 1,0,0,0]
    play_drum_pattern(kick, :drum_heavy_kick)
    play_drum_pattern(snare, :drum_snare_soft)
    play_drum_pattern(open_hat, :drum_cymbal_open)
  end
end

measure = 1;
live_loop :conductor do
  cue :beat
  # every 4 bars, a :measure message is sent (notifying the beginning of a new measure)
  if (tick % 4 == 0) then
    cue :measure, measure
    measure += 1
  end
  sleep 1
end

What do you think?

5 Likes

Yes this looks good. I’ve been working on similar ideas - layouts that look like a grid sequence. It’s working well for me.

There’s lots of ways of applying variation once you’ve got the grid system in place, fills, random variation, accents…all sorts.

I don’t think you need to name the thread, or do stop at the end, because it’s not a loop. I believe the thread just runs out. If I’m wrong about that I need to know as I use it all the time.

1 Like

Hi @soxsa,

I think you’re right about your observations: the thread doesn’t need a name (I thought that assigning one was mandatory) and probably also the stop at the end is useless (is legit to suppose that once the thread ends is wiped out automatically).
Apparently I overthought a bit :slight_smile:

Thank you for taking your time and gave me a feedback!

You’re welcome, this is what it’s all about. As well as the hard tech tips, one great thing I’ve picked up from people here is how they layout their code and how it makes it feel more like performance instrument. I’ll share a couple of idioms around the drums I’ve taken on board.

The bools() function like this below. My function p(i) plays each sample complete with modulations and effects to taste. Using the one_in() function optionally adds some variation, and the with_random_seed block makes it repeatable.

in_thread do
  with_random_seed 2 do
        20.times do
          tick
          p(0) if (bools 1,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0).look or one_in(24)
          p(1) if (bools 0,0,0,0, 1,0,0,0, 0,0,0,0, 1,0,0,0, 1,0,0,0).look or one_in(16)
          p(2) if (bools 1,0,1,0, 1,0,1,0, 1,0,1,0, 1,0,1,0, 1,0,1,0).look ^  one_in(4)
          p(3) if (bools 1,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0).look or one_in(3)
          p(4) if (bools 0,0,0,0, 0,0,0,0, 0,0,0,0, 1,0,0,0, 1,0,0,0).look or one_in(5)
          sleep 1.0/4
        end
  end
end

And this is one of my favourties, same kind of thing but using pattern strings which are even more grid-sequencer like…

  with_fx :echo, mix: 0.0, phase: 0.75, decay: 6 do
    with_random_seed (ring 1,1,1,4).tick(:a) do
      in_thread do
        27.times do
          tick
          p(9) if ("1--2--3--4--5--6--7--8--9--")
          p(0) if ("x--------x--------x--------"[look]=="x") #or one_in(27)
          p(1) if ("---x--x-----x-----x--x--x--"[look]=="x") ^ one_in(36)
          p(2) if ("--x--x--x--x--x--x--x--x--x"[look]=="x") ^ one_in(12)
          p(3) if ("-----------------------xxxx"[look]=="x") ^ one_in(12)
          p(4) if ("-xx-xx-xx-xx-xx-xx-xx-xx-xx"[look]=="x") ^ one_in(24)
          p(5) if ("---x-x---x-x---x-x---x-x---"[look]=="x") ^ one_in(24)
          p(6) if ("---x--x-----x--x-----x--x--"[look]=="x") or one_in(27)
          p(7) if ("---x--------x--------x-----"[look]=="x") or one_in(27)
          sleep 1.0/3
        end
      end
    end
  end
1 Like

And how about this one, which uses pattern ‘masks’ to apply fills with variations, all of which can be edited live.

#Drumkit with mask variations

use_bpm 120

live_loop :drumtest do
  cue :bar
  sleep 4
end

define :r0 do
  "1---2x--3---4-x-".ring.tick(:r0)=="x" and one_in(2)
end

define :r1 do
  "1---2---3---4xxx".ring.tick(:r1)=="x" and one_in(2)
end

live_loop :drums do
  sync :bar
  #stop
  a = 1.0
  n = tick(:bar)
  s = [:drum_bass_hard,
       :drum_snare_hard,
       :drum_cymbal_closed,
       :ambi_swoosh]
  
  
  define :p do |i|
    case i
    when 0
      sample s[i], beat_stretch: 1.5, amp: a*0.3
    when 1
      sample s[i], amp: a*0.2
    when 2
      sample s[i], beat_stretch: 1, amp: a*0.1*[2,1,1,1].ring.tick(:hat)
    when 3
      sample s[i], beat_stretch: 0.5, amp: a*0.1
    end
  end
  
  
  with_fx :echo, mix: 0.1, phase: 0.75, decay: 6 do
    with_fx :lpf, cutoff: 130 do
      in_thread do
        16.times do
          tick
          p(0) if ("x-----x-x-------"[look]=="x") ^ r0 #^ one_in(24)
          p(1) if ("----x-------x---"[look]=="x") ^ r1 #^ one_in(24)
          p(2) if ("xxxxxxxxxxxxxxxx"[look]=="x") #^ one_in(0)
          p(3) if ("-x--x--x--x-----"[look]=="x") ^ one_in(16)
          sleep 1.0/4
        end
      end
    end
  end
end
1 Like

I didn’t play so much with randomness yet because for now I want all under control :slight_smile:
Anyway this is interesting! If I understood well, in these examples the patterns you define act as a guide, where randomically some beats are included to apply some variations. Was this the purpose?
Another thing: how do you chose the probabilities you defined for the various one_in() functions?

Yes that is the purpose

With these techniques, you can have anything in the spectrum between the code making all the decisions to having everything prescribed! It can all lead to lovely music.

The one_in() function is built-in, and you can choose to comment it out or set a value to give ‘no variation’ all the way to chaos… With my second ‘mask’ examples, I can prescribe a fill or variation exactly, or choose to vary it a bit. one_in(0) is always false, one_in(1) is always true for instance.

A key thing with Sonic Pi is that the randomness is repeatable - you get the same results every run. Which is a very good thing for various reasons. But you can influence that with use_random_seed and with_random_seed. A technique is to create random patterns, but you can make them repeat. The musicians job is then less to create the patterns, but to choose the ones they like.

4 Likes

Cool, this use of randomness is definitely a thing worth exploring.
Thank you very much for the advices!

3 Likes

Loving this thread. I’m also using the array method, it gives me the amount of control I want, but for less ‘important’ parts, like chh I started using a rrand_I to change the length.
I’m still looking for a way to add better variations, I like one_in but I’d like a more Elektron sequencer way, more like a 2:4 do ( if second repeat out of four do) I can do that with a variable but it’s verbose…

I’m thinking about more subtle methods than the ```one_in()`` too - although that is good. I think that using the basic structure, there’s lots of scope. For instance, did you see this idea (link below)? This one is ‘fully determinisitc’ i.e. I can toggle beats on/off live and still use the same drumkit code.

This thread is excellent. I have been using a version of my own. It’s based on the part about nested lists here. The guts are really the grid player function. It finds the length of the outer list (#of beats) and plays each item of each list using the time set with a var. It’s very scalable and can take any parameter that you can put in an array. Throw in lists that have fx/synth parameters and use cues to time the calls, or generate random lists of whatever. You just have to modify for the function that makes the call(instead of sample or sleep). The list of lists has an outer list that is each beat and an inner list that is the contents of that beat. Used nil to fill in the gaps where nothing is to happen because it was a little more readable to me with the color change in the editor. I would love to hear any feedback on how to make it better. I am going to play around with the methods shown in this thread and see what I can find.

#bossa16wPlayer.rb
use_bpm 80
folderKick = "C:/Alpha/music/sonic/Analog Kicks/" #external sample location
folderHats = "C:/Alpha/music/sonic/Analog Hi Hats/"
folderFoley = "C:/Alpha/music/sonic/Black Octopus/Black Octopus Sound - Foley Essentials by AK"
#declarations for sample names
hihat = folderHats, "6BD AnalogHiHats 17.wav" #16/17 closed 12 is closing 6/5 are open 1/2 are tiny open/closed#:drum_cymbal_closed
rimshot = folderFoley, "FEAK_Snares & Rims_04.wav"#:tabla_ke1#:elec_pop
kick = folderKick, "6BD Analog Kicks 03.wav" #:bd_ada #:drum_bass_hard #:drum_heavy_kick
cymbal = folderHats, "6BD AnalogHiHats 06.wav"#:drum_cymbal_pedal
#declarations for other things
t = 0.5 # timing for sleep
#function that reads and plays the given list of lists
define :list_player do |the_list| #names func and defines parameter for list of lists
  tick_reset(:list_tick) ##reset the local tick
  list_length = the_list.length #use .length to make variable with index length
  
  list_length.times do #use length var to determine how many iteration(beats) to play
    tick_counter = tick(:list_tick) #var to increment through index
    look_counter = look(:list_tick) #optional var to use whithout advanving count
    sample the_list.ring[tick_counter][1],amp: 0.5 #fetch the [n] element of list
    sample the_list.ring[tick_counter][2] #first: what to do sec: parameter.ring
    sample the_list.ring[tick_counter][3],amp: 0.5 #[var for index][list item]
    sample the_list.ring[tick_counter][4]
    sample the_list.ring[tick_counter][5]
    sample the_list.ring[tick_counter][6]
    sleep  the_list.ring[tick_counter][0]
  end
end

##| a list of lists to contain the instructions for above player
##| is the desired #of beats long and contains the desired instruction name
##| made to be 7 spaces wide w/ nils to make referencing new items easier
bossa16 = [[t,hihat,kick,rimshot,nil,nil,nil], #one :measure one
           [t,hihat,nil,nil,nil,nil,nil], #and
           [t,hihat,nil,nil,cymbal,nil,nil], #two
           [t,hihat,kick,rimshot,nil,nil,nil], #and
           [t,hihat,kick,nil,nil,nil,nil], #three
           [t,hihat,nil,nil,nil,nil,nil], #and
           [t,hihat,nil,rimshot,cymbal,nil,nil], #four
           [t,hihat,kick,nil,nil,nil,nil], #and
           [t,hihat,kick,nil,nil,nil,nil], #one :measure two
           [t,hihat,nil,nil,nil,nil,nil], #and
           [t,hihat,nil,rimshot,cymbal,nil,nil], #two
           [t,hihat,kick,nil,nil,nil,nil], #and
           [t,hihat,kick,nil,nil,nil,nil], #three
           [t,hihat,nil,rimshot,nil,nil,nil], #and
           [t,hihat,nil,nil,cymbal,nil,nil], #four
           [t,hihat,kick,nil,nil,nil,nil]] #and
##| main loop to repeat list
loop do
  list_player bossa16 #call function for player and include desired list as parameter
end

I am really interested in you way of doing this. I am new to coding and I do not understand part of it though.

What is the p(n) part doing? It’s like it should only be running “if” the other stuff happens. Also the part after the if is hard for me to make tail of.

Thank you for any help you can provide.

1 Like

Hi @longpork, just breaking down one line:

p(0) if ("x-----x-x-------"[look]=="x") ^ r0

Here p(i) is a function, defined in the code above, that plays a sample with index i.

In SonicPi (which is Ruby-like) putting if after a statement means it’s only run when the condition after the ‘if’ is true.

The bit "x---x---x---x---"[look] treats the string as an array, and each ‘look’ returns one character. That’s another Ruby-like thing. So it steps through 16 notes of the bar, returning either an ‘x’ or a ‘-’. If it hits a ‘x’ then it runs p(0) i.e. plays a sample. Otherwise it doesn’t - so that’s a rest. The tick/look runs from 0 to 15 so dividing the bar into 16 notes - but you can choose other values.

Finally the ^ r0 refers to another pattern, defined above. This adds some variation. ^ is the ‘Exclusive Or’ operator. You can add all kinds of interesting functions here to give variety to the patterns. E.g. one_in(6) returns True pseudo-randomly, 1/6 of the time.

Is that any help?

1 Like

That was an excellent explanation. Thank you for the reply.

Im still having trouble understanding how p(i) plays the sample using the pattern. When I copy/past the code it does nothing.

Do you have the p function properly defined somewhere? One of Soxsa’s examples above does this, but also depends on a variable called s for the list of samples to play from (and a for the amp to play at):

To be absolutely sure about what your issue is, it’s probably helpful to see all of your code :slightly_smiling_face:

I simply copy and pasted his code. ```
with_fx :echo, mix: 0.0, phase: 0.75, decay: 6 do
with_random_seed (ring 1,1,1,4).tick(:a) do
in_thread do
27.times do
tick
p(9) if (“1–2–3–4–5–6–7–8–9–”)
p(0) if (“x--------x--------x--------”[look]==“x”) #or one_in(27)
p(1) if ("—x–x-----x-----x–x–x–"[look]==“x”) ^ one_in(36)
p(2) if ("–x–x–x–x–x–x–x–x–x"[look]==“x”) ^ one_in(12)
p(3) if ("-----------------------xxxx"[look]==“x”) ^ one_in(12)
p(4) if ("-xx-xx-xx-xx-xx-xx-xx-xx-xx"[look]==“x”) ^ one_in(24)
p(5) if ("—x-x—x-x—x-x—x-x—"[look]==“x”) ^ one_in(24)
p(6) if ("—x–x-----x–x-----x–x–"[look]==“x”) or one_in(27)
p(7) if ("—x--------x--------x-----"[look]==“x”) or one_in(27)
sleep 1.0/3
end
end
end
end

Do i need to combine the two windows of code in his post?

Hi, i read very interesting things here about drums, i do, from a couple of tracks, a mix technique that fit wit my needs, here is the example of an electronic ALT-DrumKit Patterns (only using synths with basic params and fx as it is not the main topic here). this help me to set quickly some base during live coding.

i am using a type of “beat variant” to randomize the 3 instruments along the sequence.

use_bpm 140 #
#-----------------------------------------------------------------------------------------
pattern_grid = "----------------".ring # 4/4 Beat Measure Structure
define :silence do 4/pattern_grid.length.to_f end # 1/4 Beat Tempo
#----------------<|   |   |   |   >-------<|   |   |   |   >-------<|   |   |   |   >-----
p_myKick_seq =  ["9---7---8---7---".ring, "9---------------".ring, "9-4-----973-----".ring]
p_mySnare_seq = ["--1-------1-----".ring, "--1---1-2-4-----".ring, "321-----321-1---".ring]
p_myHihat_seq = ["1-1-1-1-1-1-1-1-".ring, "1---1-1-1-21--1-".ring, "--------132--1--".ring]
#-----------------------------------------------------------------------------------------
v = 0 # Init Beat Variants
#-----------------------------------------------------------------------------------------
define :myKick do  synth :sine, note: :C2, attack: 0.01, release: 0.3, cutoff: 120,
    amp: ((p_myKick_seq[v])[look].to_f / 9) if ((p_myKick_seq[v])[tick] != "-") end
define :mySnare do synth :bnoise, note: :C3, attack: 0.01, release: 0.8, cutoff: 90,
    amp: ((p_mySnare_seq[v])[look].to_f / 9) if ((p_mySnare_seq[v])[tick] != "-") end
define :myHihat do synth :noise, note: :C4, attack: 0.01, release: 0.05, cutoff: 130,
    amp: ((p_myHihat_seq[v])[look].to_f / 9) if ((p_myHihat_seq[v])[tick] != "-") end
#-----------------------------------------------------------------------------------------
with_fx :compressor, clamp_time: 0.05, threshold: 0.5, relax_time: 0.3, mix: 0.2 do
  with_fx :reverb, damp: 1, room: 1, mix: 0.2 do
    with_fx :eq, low: 1.2, mid: 0.4, high: 0.8, mix: 0.8 do
      #------------------------------------------------------------------------------------
      live_loop :BEAT_KICK do
        v = [0, 1, 2, 0/2, 1/2, 2/2].choose
        myKick
        sleep silence
      end
      #-----------------------------------------------------------------------------------
      live_loop :BEAT_SNARE do
        v = [0, 1, 2, 0/2, 1/2, 2/2, 0/4, 1/4, 2/4].choose
        mySnare
        sleep silence
      end
      #---------------------------------------------------------------------------------
      live_loop :BEAT_HIHAT do
        v = [0, 1, 2].choose
        myHihat
        sleep silence
      end
    end
  end
end

i am sure there is shorter way to do exactly the same… i am on it.
Guys, keep going, this is awesome and there are plenty of options :slight_smile:
bye
uriel

Do i need to combine the two windows of code in his post?

Well, yes, as far as: any functions or variables that exist in the code you copy, must also have been defined/created before they are used, or Sonic Pi will not understand them :slightly_smiling_face:

Thank you for the help. I’ll give it a try.