Sonic Pi: Electric Hoedown Remix

Copyright 2019 Brian Davis - CC-BY-NC-SA

Sonic Pi is a live coding, electronic music generator. The language is Ruby and the tutorials are quite good for jumping in and having fun right away. I introduced it to my kids and we've had some fun times hacking on it together. The older ones were able to follow the tutorials. My six-year old was the youngest to have a go (quite eagerly) and she needed some help to get started, but enjoyed herself for quite a while with just the use_synth, sample, play, and sleep commands. I recommend headphones for everyone though. ;-)

The tutorials seem to focus on live coding. And I developed the below loops with live_loops. But I wanted to be able to sequence things in time, to generate something that people would recognize as more of a song like structure. An intro, a verse, a chorus, another verse, etc. It took a bit of experimentation but below is the result. Listen here. Hmmmm, might need some post production. Sounds like it would benefit from normalization. By the way, I know almost nothing about music. This exercise is sort of like throwing stuff at the wall and seeing what sticks. That's what cool about Sonic Pi, it's so fun to experiment with!

To sequence things in a linear fashion I converted my live_loops to separate threads using in_thread and sync:ed them to cue: commands. Here is the final sequence that cues the threads.

cue :bassdrum
sleep 2
cue :drumloop
sleep 2
cue :bass2
sleep 4
cue :riff
sleep 8
cue :bassdrum
cue :drumloop
cue :bass2
cue :riff
sleep 8
cue :basswood
cue :bassdrum
cue :bass2
4.times do
  sleep 1
  cue :basswood
end
cue :drumloop
4.times do
  sleep 1
  cue :basswood
end
cue :bassdrum
4.times do
  sleep 1
  cue :basswood
end
cue :riff
cue :drumloop
4.times do
  sleep 1
  cue :basswood
end
cue :bassdrum
4.times do
  sleep 1
  cue :basswood
end

To make this work you have to pay careful attention to the lengths of threads so that you know when they finish and need to be restarted. There is probably a more intuitive was to do this, but it works.

Here are the individual threads. I like having two drum loops that I can play against each and vary their respective timings. In live coding I normally do that by given them different lengths and adding a pause to one or the other. But with the technique I'm trying here the threads have very simple (and similar) timing and I use the main sequence code to offset them.

in_thread do
  loop do
    sync :bassdrum
    16.times do
      sample :bd_haus, amp: 2
      sleep 0.5
    end
    sample :bass_woodsy_c
  end
end

in_thread do
  loop do
    sync :drumloop
    16.times do
      sample :bd_tek
      sleep 0.25
      sample :bd_zome
      sleep 0.25
    end
  end
end

Sonic Pi is loaded with cool samples. I spent a whole day just trying different ones. The developer tool has autocompletion, so you just type sample : and look through the list. I use bass_woodsy_c sparingly to add some flavor.

in_thread do
  loop do
    sync :basswood
    #sleep 6
    sample :bass_woodsy_c
  end
end

One way to create melody lines is to let the computer do it. Set the random seed and shuffle a bunch of notes. In this case I'm pulling 6 notes from the c-major scale at octave two (I think?) and playing them through twice. Setting the random seed means I got the same six random notes every time. I tried different random seeds until I liked what I heard.

in_thread do
  loop do
    sync :bass2
    use_synth :blade
    use_random_seed 444
    notes = scale(:c2, :major).shuffle.take(6)
    with_fx :distortion do
      12.times do
        play notes.tick, attack: 0.25, release: 1, amp: 0.1
        sleep 1
      end
    end
  end
end

Here's another computer generated riff. It happened to use the same random seed as above. But it doesn't have to. I just noticed that I used slightly different syntax to create notes compared with above. Above I did notes = scale([args]) while below its notes = (scale [args]). Huh. This is my only exposure to Ruby. I suppose it must support more than one function call syntax?

in_thread do
  loop do
    sync :riff
    use_synth :tb303
    8.times do
      use_random_seed 444
      notes = (scale :a2, :minor_pentatonic, num_octaves: 2).shuffle.take(8)
      8.times do
        play notes.tick, release: rand(0.5), amp: 0.2, cutoff: rrand(60, 130) if one_in(2)
        sleep 0.125
      end
    end
  end
end

The first riff has a total length of 12 seconds, the second is 8 seconds. This difference allows for some interesting interaction. If I start both riffs at the same time the 8 second will finish first. In my song sequence I play with this behavior to overlap the riffs in different ways for the two verses creating surprisinly different sounds.

When developing a song in Sonic Pi I will use the different buffers to work on the pieces individually and then slowly combine them together. I started with the above above drum loops in one buffer, the riff above in another buffer and the riff below in yet another buffer. Each piece goes in it's own live_loop without the sync command. Every time I hit play the buffer I'm looking at will play it's loop and I can work on modifying it without being distracted or confused by the other pieces. When I have all the pieces how I want, I combine them into a single buffer. This was the first piece where I converted from live_loops that all play together to individual threads that are cued up by a sequencing program. I like how it creates a song with clear beginning and ending. I'm excited to try the same technique on some of my other compositions.