Tuesday 18 November 2014

Adventures in Sonic Pi

Well what a fun few weeks this has been getting to this point. I had been aware of Sonic Pi for quite a while, but not having a Mac or easy access to my Raspberry Pi I hadn't been able to play much. This all changed with the release of the PC edition. With easy access to desktop PC's and laptops around the house I could play to my hearts content.

Like everyone else I started by looking at examples and what others had already done, there was so much creativity out there. One of the first things I came across was Robin Newman's blog and his posts about using a limited set of samples of a Grand Piano to create an instrument that could play a much wider range of notes (here). I liked the technical aspect of this but wondered if the concept could be made more accessible.

I set about attempting to create a Ruby class that would provide the same functionality. After plenty of reading up on Ruby (its not a language I usually develop in) and a few coding dead ends, I ended up with a couple of Ruby classes that did the job. Of course just releasing this code would have been no fun at all, people want to hear Sonic Pi playing music. So I set about putting what I had just created to use and coded a version of "The Entertainer".

If you want to jump direct to the code then I've dropped it on a Gist here and the required Grand Piano and Clarinet samples taken from the fantastic Sonatina Symphony Orchestra resource are in a zip file here (Google Drive has a download link at the top of the page). Once downloaded remember to change the use_sample_pack (line 143) to point at the location you have extracted the samples to.

If you just want to enjoy this rendition of  "The Entertainer" then the mp3 file can be found here (again Google Drive has a download link at the top of the page if required).


So you want a bit more of a breakdown of what I created?

First I created a class that all it does is play a specific note from a given sample (line 5). Each instance of the class contains the name of the sample to be played and amp, attack, rate, release and sustain settings. When the play method is called a duration is passed and is combined with the attack, release and sustain settings. These three settings are more reflecting how much of the duration should be spent doing each and in total should add up to 1. If they don't this will shorten or lengthen the requested duration. This feature can be turned to our advantage by allowing the note to carry on longer than the requested duration. This is done by the three settings totalling up to a number greater than 1. This would allowing, for example, piano strings to ring for slightly longer.

Next I created a class that contained all the samples for an instrument (line 55). Each of the 128 midi notes is mapped to their nearest sample and appropriate playback rate calculated. Every time a new sample is added it first inserts itself at the correct position before making sure that neighbouring notes use the nearest sample. For each of the notes an instance of the note playing class is created and held in an array.

Now how are these two classes put to use. Well everything is done through the second, instrument bank class. First a new instance of the class is created (line 146) passing in a reference to Sonic Pi itself. Next all the samples are loaded (line 147 - 153) each defining which base note the sample is for (in either midi number or note symbol) and what sample to use (either as file name in the sample pack folder or by sample symbol). Finally you can amend any required settings (line 154) from the internal defaults provided. At this point your new instrument bank is ready to go. All you have to do to play a note is call the play method passing in a note (again in either midi number of note symbol) and a duration. Also if you want to play a chord just pass in an array of notes.

The Entertainer (line 140) is played by creating three arrays of notes and durations (one for clarinet, one for grand piano right hand and one for grand piano left hand). A synchronisation thread is started whose only task is to provide the cues for the threads playing each of the arrays. As these threads receive the cues they count them and play the appropriate note for the required duration.

This is my first Sonic Pi project and I'm not even sure I've finished it yet. But as I thought that it was at a good point I thought it was worth sharing. It would be great to hear what you think of it or if you have any questions then please ask away.