Function for euclidean notation sample player

Hi! I’d like to share a function I have used to translate euclidean notations into loops. As a newbie both in Sonic Pi and in music theory I find this very easy to use and play around with.
I am not sure this works properly neither in general nor in particolar use cases so please feel free to review it.

# euclidean pattern player! I did this because I have zero experience with that notation.
# If there is a function for this, I am unaware!

# Arguments
# hits, steps: The values of the euclidean notation, so how many hits for that many steps.
#              Sonic Pi "spread" function creates the correct euclidean distribution in array form.
#
# samples:     A hash table of samples and relative function arguments to be played inside.
#
# sleep_time:  how many beats to wait for each next step.
#
# swap:        An array of positional values to alternate the samples. For example, if we have
#              three samples and we want to achieve A-B-C-C-B-A alternation, we provide an array:
#              [0,1,2,2,1,0] where 0 is the A sample's position in the samples array, 1 is the B's, etc.
#              I implemented this to use two hi-hats in the same loop. Also being able to play with the array
#              during live might be handy!
define :eucloop do |hits, steps, samples, sleep_time=0.25, swap=nil|
  pattern = spread(hits, steps)
  # create the euclidean distribution in a true\false array
  hit_index = 0
  # this is to keep track of how many times we played an instrument (which is not every step).
  pattern.each do |hit|
    #for each element in the pattern, if it's True (so a hit to play)
    if hit
      if swap
        # if we have the swap argument, else its nil
        sample_index = swap[hit_index % swap.length]
        # we choose the index of the sample to be played
      else
        sample_index = hit_index % samples.length
        # else we keep the same we had
      end
      s = samples[sample_index]
      # we choose our sample, args couple from the sample hash
      sample s[:sample], **s[:args]
      # and we play our sample with its own args!
      hit_index += 1
      # since we played a hit we update the index
    end
    sleep sleep_time # here we sleep. Goodnight Euclides xoxo smooch kiss
  end
end

# Usage with default samples
kicks = [{sample: :bd_tek, args: {amp: 0.5}},
         {sample: :bd_haus, args: {amp: 0.7}}]
swap = [0, 0, 1, 0, 1] # A AB AB instrument play order where A = kicks[0], B = kicks[1]
live_loop :kicks do
  eucloop(5, 8, kicks, 0.25, swap)
end

# Usage with your own samples
samplesdir = "your samples dir path goes here!"
hh1 = File.join(samplesdir, "WavePoint-ClosedHat1.wav")
hh2 = File.join(samplesdir, "WavePoint-ClosedHat2.wav")
hats =[{sample: hh1, args: {amp: 0.5}},
       {sample: hh2, args: {amp: 0.5}}]
swap = [0,1,0,1,0] # This is the A-BA-BA
live_loop :hi_hats, sync: :tempo do
  eucloop(5,8,hats,0.25,swap)
end
1 Like

Hey!
Really cool that you created that as a learning exercise — it’s a great way to deepen your understanding. I noticed your comment about not being sure if there’s a built-in Euclidean function. There is! It’s called spread — super handy for generating Euclidean rhythms. (forget about this you did use spread), I am curious though, why did you decide to create the function instead of using only spread?

1 Like

Yes! I am using spread in the function. I was looking for a way to play the result of a spread function while also choosing the sample to be played, all wrapped up in a single function call for easier live editing.

How would you do it in “vanilla” sonic pi? I have ideas but they got so complicated I opted for a function. Probably the solution is just out of my intuition haha :slight_smile:

I would have done something like this:

live_loop :eucl do
  eucl_rythm = spread(5,8)
  sample :bd_tek if eucl_rythm.tick
  sleep 0.25
end

To explain a bit further:

  • I create the spread, which is essentially a ring of true and false values. This means I can “ask” it whether to play or not.
  • I play the sample only if the current element of the ring is true. That’s what .tick does — it iterates through the ring. It’s important to note that .tick is scoped per loop, so if you call .tick multiple times within the same loop, it will affect the internal counter. (I recommend checking out the Sonic Pi tutorials — they do a great job explaining this in more detail.)

That’s more of my “vanilla” Sonic Pi approach. That said, I want to be clear: what you did isn’t wrong or some kind of “bad practice” — none of those coding shenanigans apply here. The most important thing is to learn and have fun. Sure, some approaches might be more readable or easier to understand, but there’s no single “right” way to do things — only the fun way that works for you. So keep doing your thing!

1 Like