Wednesday, 17 February 2016

Sail with me into the Pi (part 3)

We're talking about Sonic Pi, we're talking about Sail by AWOLNation. In part 2 we put together a second part, a pattern for playing separate parts together using the `in_thread` method, and we looked at reducing repetition by defining methods for code that's used more than once.

Part three is going to be a lot like part two, we'll take another step in building the music up and do a bit more abstraction.

First things first, we're going to add one more to our collection of synthesisers. It looks like this:

Actually, there's a bit of repetition here. A collection of variables which all end in '_synth', you could even call it a collection of synths. There's a data structure in Ruby which is a collection of values which are labelled, it's called a hash. For our synths, it looks a bit like this:

We can get one of these values out of our hash with squared brackets, it looks like this:

This is another example of a refactor, which, as you may remember from part 1, is a change to our code which doesn't change what it does, but changes something about how it works. But this is only the start of this refactor, we've changed how we structure our data, we need to change the uses of that data. So wherever we see `@vamp_synth` we need to change it to `@synths[:vamp]`. Fortunately, we've reduced enough repetition in our code that there's only two places we need to do that:

How do we know that our refactor was successful? Simple, we run the code and check that it does the same thing. Fortunately Sonic Pi makes it quite enjoyable to do this, we just tap alt + r and listen. It sounds the same, so now we can move on.

The new part we're going to introduce is a riff, if you listen to the track it's played on a plucked cello. It's played immediately after those bass hits are finished, so there'll be a sleep of 2 beats to coincide with that. You'll also notice that it's often repeating the same note three times in a row, three times in one beat.

We can decide from this that we need two methods before we start worrying about what the actual notes are. We need one method to play one of these plucking notes, and one to pluck the same note three times in a single beat. Here they are:

Let's see; I'm defining the plucking method on line 35. I'm grabbing the :riff synth from our synths hash and changing to that synth (line 36), then I'm playing the note it's been given on line 37. You'll notice that I've set attack, decay, and sustain to 0. Skipping the first 3 parts of the ADSR curve, and going right to the release. The note will be short, one triplet. Then we'll wait for the length that's been passed in before proceeding (line 38).

Line 41 defines our triplet method. We know what it should be doing is playing a note 3 times, and you can see that the code says `3.times`, that's pretty self-explanatory, but what about the next part.

Next on line 42 we see some curly brackets. This is another way to define a block in Ruby. Where we saw do and end earlier on, this is doing the same thing. So the code between { and } will be run 3 times. Then we're calling riff_pluck, which we've just defined, passing in the note that's been given to riff_triplet and saying to wait 1 triplet for the next pluck.

So now we can start to write it. The riffs to go along with the two parts we've already written, in full, looks like this.

There's a lot of that, I can't even fit it all on one screen (thus, I can't fit it all in one screen shot). You can probably see that I've already used all of the techniques we've discussed so far to shorten the code. Each use of the pluck_triplet method saves us two more lines, and I've used the `times` method again to repeat those two bars that don't change.

Oh, and it sounds like this:

I can't cut this down any more, we need each line we see, but I can tidy it up a bit, and make it easier to navigate. Well, the obvious place to split this sequence up is when we see that sleep for 2 beats. We're going to make each of these parts a method of its own, named after the first note in it. Each part of this is what's known as a riff, a pattern based on the notes of a chord. So, splitting that sequence up at each sleep, it looks like this:

And now that we've put each riff away in it's own method, the sequence looks like this (I've put it away in a _loop method like the other two parts):

This looks a lot tidier. We haven't got rid of that code, but we have put it away in methods which describe what they are, and the sound is, of course, exactly the same.

Now, we learned in part 2 what we need to get more than one part to play together and at the right time. We call the methods on successive lines, the parts sleep through the part where they're not playing, and each part is wrapped in an in_thread loop. We've met all of these requirements, so we can add it to our existing code like so:

And it sounds like this:

We're starting to get the feel of the song now. You can grab the full code at this stage here.

Next time we're going to look into a bit more of Sonic Pi, how to play samples, sound files, play them differently, and play some drums.

Thanks for reading, as ever, feel free to say hi on Twitter at @MarmiteJunction.

1 comment: