Custom Drum Beat Player

So I’ve created a proof of concept for a ‘beat pattern’ language for creating beats from a string of characters. For now, it’s just a way to learn programming for Sonic Pi, although I hope to develop it into something useful. Here’s a demonstration:

https://youtu.be/Dq7XDQb9PWE

…and here’s the code:

use_bpm 120

def playbeat(beatstring)
  samp = ":drum_bass_hard"
  sleep_dur = 1

  for i in (0..beatstring.length-1).step(2)
    note = beatstring[i]
    dur = beatstring[i+1]

    case note
    when "h"
      samp = :drum_cymbal_closed
    when "s"
      samp = :good_snare
    when "k"
      samp = :drum_bass_hard
end

case dur
    when "1"
      sample samp
      sleep 1
    when "2"
      2.times do
        sample samp
        sleep 0.5
      end
    when "4"
      4.times do
        sample samp
        sleep 0.25
      end
    end
  end
end

live_loop :beatz do
  playbeat("k2s1k1s2")
end

live_loop :hh do
  playbeat("h1")
end

It’s very limited and simplistic at the moment, but provides a quick and dirty way to throw together simple beats in a pretty compact form. The one thing I like is that you can play multiple notes with a single note/duration combination (e.g., h4 will play four 16th notes with a high-hat sound). On the other hand-- this notation doesn’t allow you to (for instance) play two eighth notes using a different sound for each note. So: it will eventually become less compact as I address these limitations…

Bryan

2 Likes

I think it’s interesting that people come up with their own languages to describe music in a compact way that is quick to develop during live coding. There’s the Ziffers project, for an example.

Speaking personally, not for other people, I think there’s value in developing your own languages for describing music in a way convenient for themselves.

I had a quick go at a drum pattern player.

My basic pattern is:

“!!k!//h–h--sh–!///kh–!//kh–h--sh–4o”

k=kick drum
s=snare drum
h=closed hi hat
o=open hi hat
-=sleep for one 16th

! and / indicate velocity values, with ! = ////, eight /s equal an amp of 1.0, which is enough velocity resolution for me. Numbers indicate a default duration with 4 mean 4/16 = 0.25.

There is also an array of times to sleep for the 16ths, which if changed from the default would allow a groove.

I’m not suggesting that anyone uses my format: quite the opposite. It’s just an example of how it’s possible to come up with flexible and compact music language, but when you try to include all possibilities, the language just gets more and more complex and the description strings more obscure. E.g. my code doesn’t allow changing the drum sounds, so I’d need to add an additional variable with a map allowing over-riding of the default drum sounds, and so on.

Note that I don’t use a case statement in my code, which makes my if statements strange, as I’m trying to stick to Sonic Pi only code. Not additional structures from Ruby.

use_bpm 120
use_debug false

define :playbeat do |pattern,groove=[0.25,0.25,0.25,0.25,
                                     0.25,0.25,0.25,0.25,
                                     0.25,0.25,0.25,0.25,
                                     0.25,0.25,0.25,0.25
                                     ]|
  
  in_thread do
    groover = groove.ring
    sixteenths = 0
    dur = 4 # default duration in 16ths
    dur_string = ""
    
    amp = 1.0
    amp_string = ""
    
    (pattern.length).times do |index|
      
      if pattern[index] == "!" || pattern[index] == "/"
        if pattern[index] == "!"
          amp_string = amp_string + "////"
        else
          amp_string = amp_string + "/"
        end
      else
        if amp_string != ""
          amp = amp_string.length / 8.0
          amp_string = ""
        end
      end
      
      if pattern[index] >= "0" &&
          pattern[index] <= "9"
        dur_string = dur_string + pattern[index]
      else
        if dur_string != ""
          dur = dur_string.to_i/16.0
          dur_string = ""
        end
      end
      
      
      if pattern[index] == 'k'
        sample :bd_haus, amp: amp
      end
      
      if pattern[index] == 's'
        sample :sn_dolf, amp: amp
      end
      
      
      if pattern[index] == 'h'
        sample :drum_cymbal_closed, amp: amp
      end
      
      if pattern[index] == 'o'
        sample :drum_cymbal_open, sustain: dur, amp: amp
      end
      
      if pattern[index] == '-'
        sleep groover[sixteenths]
        inc sixteenths
      end
    end
  end
end


pat1 = "!!k!//h--h--sh--!///kh--!//kh--h--sh--4o"
pat2 = "!!k!//h--h--sh--!///kh--!//kh--!/kh--!//sh--4o"
pat3 = "!!k!//h--h--sh--!///kh--!//kh--4o--sh-!s-!//s"


live_loop :drums do
  
  playbeat (ring pat1, pat2, pat1, pat3 ).tick
  
  sleep 4
end
2 Likes

That is the beauty of live coding! You’re not limited to using a rigid paradigm-- you can invent your own! And not only that-- you’re not limited to using just a single way of doing things. I can decide to add a second ‘pattern player’ function that uses a different ‘language’ and use them both in tandem. It’s a trite saying, but true: the possibilities are endless. :slight_smile:

2 Likes

Well said. I’m excited about a new feature that looks like it is slated for release - support for ixi lang. This looks like it could make writing these kinds of tools even easier, such that you don’t have to write the boilerplate pattern parsing and action delegation code each time.

YES! I’m really excited about the ixi lang feature…

1 Like