Drum patterns, sleep, and bpm

Hello everyone!

I’ve been trying to build up my experience with making drum patterns with Sonic Pi. Would you please help me understand how to create consistent patterns across BPMs?

I was watching this video about drum patterns to try and replicate in SonicPi. In this case the Basic Rock rhythm seemed like a good place to start.

use_bpm 110

live_loop :buup do
  cue :boop
  sleep 1
end

live_loop :seembal do
  sync :boop
  sample :drum_cymbal_closed, amp: 0.2
end

live_loop :snahre do
  sync :boop
  sample :drum_snare_soft
  sleep 3
end

sleep 1
live_loop :keek do
  sync :boop
  sample :drum_heavy_kick
  sleep 1
  sample :drum_heavy_kick
  sleep 1
  sample :drum_heavy_kick
  sleep 1
end

So in this snippet, we have a BPM of 110 and a cue beat that sleeps for 1 (beat?).

At 110 bpm, it seems to function as expected; 1 snare hit, 3 heavy kicks, 1 snare hit, etc. The closed cymbal triggers every beat as expected too.

However, here’s my problem, when I increase the tempo to 140 (for a faster, standard rock tempo) the snippet changes in function to: 1 snare hit, 2 heavy kicks, 1 snare hit, 1 kick, 1 snare hit, etc. It just sounds all sorts of syncopated.

From what I understand from the documentation, with sleep, “…beats are converted to seconds by scaling to the current bpm setting.”

Is there a way to constrain either the scaling or bpm to keep the code uniform but faster (or slower)? I hope it isn’t the case where I have to manipulate the sleep values depending on the BPM.

Thanks!

This is an interesting one.

I am not 100% sure why there is a difference purely when changing the tempo - perhaps a rounding error in the internal code? I do know that the behaviour you’re hearing at 110 bpm is due to the :snahre loop waiting 4 beats in between triggering the snare sample, rather than every 3, due to the sync :buup making it wait an extra beat every time around.
One hacky way to make it behave the same way as this at the higher tempo is to replace the sleep 3 in the :snahre live_loop with the following:

  3.times do
    sleep 1
  end

Having said all that, using sync X inside a live_loop like you do here to act as a regular metronome between loops is often not a very workable approach, as it can lead to situations exactly like this. It is usually better to synchronise loops purely when they are first created and let them then just act independently from then on.

Here is how you could write your code using the (usually) recommended way to synchronise live_loops:

use_bpm 140

live_loop :buup do
  sleep 1
end

live_loop :seembal, sync: :buup do
  sample :drum_cymbal_closed, amp: 0.2
  sleep 1
end

live_loop :snahre, sync: :buup do
  sample :drum_snare_soft
  sleep 4
end

live_loop :keek, sync: :buup do
  sleep 1
  sample :drum_heavy_kick
  sleep 1
  sample :drum_heavy_kick
  sleep 1
  sample :drum_heavy_kick
  sleep 1
end

Notice that the snare loop clearly still triggers the snare every 4 beats, without having to rely on a confusing sync in the middle of the loop. (Incidentally, we can also do away with the unnecessary sleep before the kick drum loop, and shift it inside the loop itself. There are a bunch of other optimisations that you could also do, but the above are what I would suggest to start with).

If you are interested, threads such as this explain the various uses of sync helpfully in detail:

1 Like

Hi @gregeporter,

welcome to Sonic Pi and the forum!

To second @ethancrawford’s suggestions: This is really weird an I also do not really understand, how the BPM changes are able to affect your beat in such a way. But I would also say that the constant syncing and the one sleeping beat outside the loop might add to complicate things and debugging (though admittedly this is no clear explanation at all of what is going on here but just a shot in the dark).

(As a side note: In the video example I only see the snare on the 4, not on the 1. Or have I overlooked something?)

Having said that I think there are ways to make the code easier and secure a consistent timing. In addition to Ethan’s example I would like to propose yet another way which I quite frequently use in different scenarios:

use_bpm 120

live_loop :buup do
  sleep 1
end

live_loop :seembal, sync: :buup do # as Ethan proposed: only one sync when the loop starts
  sample :drum_cymbal_closed, amp: 1
  sleep 1
end

live_loop :snahre, sync: :buup do
  ptn = (ring 0, 0, 0, 1) # change at will and set rhythm _and_ amp here
  sample :drum_snare_soft, amp: ptn.tick
  sleep 1
end

live_loop :keek, sync: :buup do
  ptn = (ring 1, 1, 1, 0)
  sample :drum_heavy_kick, amp: ptn.tick
  sleep 1
end

As you time resolution for this rhythm is 1 beat you can condense the whole thing to one loop if you want (and even skip the syncing … but that’s not very flexible and you will soon go back to the multi-loop approach for the following examples of the video):

use_bpm 120

live_loop :seembal do
  snahre = (ring 0, 0, 0, 1)
  keek = (ring 1, 1, 1, 0)
  sample :drum_cymbal_closed, amp: 1
  sample :drum_snare_soft, amp: snahre.tick
  sample :drum_heavy_kick, amp: keek.look # look does not advance but 'look' where tick is
  sleep 1
end

Hope this helps.

2 Likes

I am not entirely sure, but I think this one is only accessible for patreons. If possible we should move this elsewhere because this is really a frequently asked question/issue, right?

I may be mistaken on the details, but It looks to me like the Patreon-only limit has been removed. That topic is still readable if you are not logged in.

1 Like

Condensing things by using rings etc was part of the “bunch of other optimisations that you could also do” that I had decided not to elaborate on :wink: but yes, it is an option :smile:

1 Like

Yeah, I did not mean to play the smartass (as your knowledge and proficiency concerning the code exceeds mine by far!) … it just seemed an obvious thing to propose and Greg is a coder too, right?

2 Likes

Ahaha. You did nothing wrong! And just in case it’s not clear @gregeporter - I’m always happy to suggest optimisations or other suggestions about Sonic Pi code if you find it useful :slight_smile:

1 Like

@Martin and @ethancrawford, thank you very much for your suggestions. Very interesting ideas.

In terms of that rogue sleep 1, based on the video, it seemed straightforward enough to get the cymbal to play every beat.

It seemed straightforward enough too to get the snare to play every 4th beat but the kicks are what made me pause.

Looking at the video, the pattern started out with a

kick, pause, kick, kick, kick
along with
pause, snare, pause, pause, pause

So if I started on the second beat, or shifted everything left by one:

pause, kick, kick, kick, pause
snare, pause, pause, pause, snare

then it would be a lot easier to write up and, I thought, the solution to that would be to sleep 1 before starting the regular loop.

All that said, thank you again for your helpful suggestions and link to that other thread. I’ll definitely check those out.

I think, looking at my previous efforts, there is definitely things that I can get a better handle on, the use of sync and rings. :stuck_out_tongue: