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
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
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.