Welcome to Sonic Pi. Hopefully you’re as excited to get started making crazy sounds as I am to show you. It’s going to be a really fun ride where you’ll learn all about music, synthesis, programming, composition, performance and more.
But wait, how rude of me! Let me introduce myself - I’m Sam Aaron - the chap that created Sonic Pi. You can find me at @samaaron on Twitter and I’d be more than happy to say hello to you. You might also be interested in finding out more about my Live Coding Performances where I code with Sonic Pi live in front of audiences.
If you have any thoughts, or ideas for improving Sonic Pi - please pass them on - feedback is so helpful. You never know, your idea might be the next big feature!
This tutorial is divided up into sections grouped by category. Whilst I’ve written it to have an easy learning progression from start to finish, feel very free just to dip in and out of sections as you see fit. If you feel that there’s something missing, do let me know and I’ll consider it for a future version.
Finally, watching others live code is a really great way to learn. I regularly stream live on https://youtube.com/samaaron so please do drop by, say hi and ask me lots of questions :-)
OK, let’s get started…
One of the most exciting aspects of Sonic Pi is that it enables you to write and modify code live to make music, just like you might perform live with a guitar. This means that given some practice you can take Sonic Pi on stage and gig with it.
Free your mind
Before we get into the real details of how Sonic Pi works in the rest of this tutorial, I’d like to give you an experience of what it’s like to live code. Don’t worry if you don’t understand much (or any) of this. Just try to hold onto your seats and enjoy…
A live loop
Let’s get started, copy the following code into an empty buffer above:
Now, press the button and you’ll hear a nice fast bass drum beating away. If at any time you wish to stop the sound just hit the button. Although don’t hit it just yet… Instead, follow these steps:
- Make sure the bass drum sound is still running
- Change the value from to something higher like .
- Press the button again
- Notice how the drum speed has changed.
- Finally, remember this moment, this is the first time you’ve live coded with Sonic Pi and it’s unlikely to be your last…
Ok, that was simple enough. Let’s add something else into the mix. Above add the line . Your code should look like this:
Now, play around. Change the rates - what happens when you use high values, or small values or negative values? See what happens when you change the value for the sample just slightly (say to ). What happens if you choose a really small value? See if you can make it go so fast your computer will stop with an error because it can’t keep up (if that happens, just choose a bigger time and hit again).
Try commenting one of the lines out by adding a to the beginning:
Notice how it tells the computer to ignore it, so we don’t hear it. This is called a comment. In Sonic Pi we can use comments to remove and add things into the mix.
Finally, let me leave you something fun to play with. Take the code below, and copy it into a spare buffer. Now, don’t try to understand it too much other than see that there are two loops - so two things going round at the same time. Now, do what you do best - experiment and play around. Here are some suggestions:
- Try changing the blue values to hear the sample sound change.
- Try changing the times and hear that both loops can spin round at different rates.
- Try uncommenting the sample line (remove the ) and enjoy the sound of the guitar played backwards.
- Try changing any of the blue values to numbers between (not in the mix) and (fully in the mix).
Remember to press and you’ll hear the change next time the loop goes round. If you end up in a pickle, don’t worry - hit , delete the code in the buffer and paste a fresh copy in and you’re ready to jam again. Making mistakes is how you’ll learn the quickest…
Now, keep playing and experimenting until your curiosity about how this all actually works kicks in and you start wondering what else you can do with this. You’re now ready to read the rest of the tutorial.
So what are you waiting for…
Sonic Pi has a very simple interface for coding music. Let’s spend a little time exploring it.
- A - Play Controls
- B - Editor Controls
- C - Info and Help
- D - Code Editor
- E - Prefs Panel
- F - Log Viewer
- G - Help System
- H - Scope Viewer
A. Play Controls
These pink buttons are the main controls for starting and stopping sounds. There’s the Run button for running the code in the editor, Stop for stopping all running code, Save for saving the code to an external file and Record to create a recording (a WAV file) of the sound playing.
B. Editor Controls
These orange buttons allow you to manipulate the code editor. The Size + and Size - buttons allow you to make the text bigger and smaller.
C. Info and Help
These blue buttons give you access to information, help and preferences. The Info button will open up the information window which contains information about Sonic Pi itself - the core team, history, contributors and community. The Help button toggles the help system (G) and the Prefs button toggles the preferences window which allows you to control some basic system parameters.
D. Code Editor
This is the area where you’ll write your code and compose/perform music. It’s a simple text editor where you can write code, delete it, cut and paste, etc. Think of it like a very basic version of Word or Google Docs. The editor will automatically colour words based on their meaning in the code. This may seem strange at first, but you’ll soon find it very useful. For example, you’ll know something is a number because it is blue.
E. Prefs Panel
Sonic Pi supports a number of tweakable preferences which can be accessed by toggling the prefs button in the Info and Help button set. This will toggle the visibility of the Prefs Panel which includes a number of options to be changed. Examples are forcing mono mode, inverting stereo, toggling log output verbosity and also a volume slider and audio selector on the Raspberry Pi.
F. Log Viewer
When you run your code, information about what the program is doing will be displayed in the log viewer. By default, you’ll see a message for every sound you create with the exact time the sound was triggered. This is very useful for debugging your code and understanding what your code is doing.
G. Help System
One of the most important parts of the Sonic Pi interface is the help system which appears at the bottom of the window. This can be toggled on and off by clicking on the blue Help button. The help system contains help and information about all aspects of Sonic Pi including this tutorial, a list of available synths, samples, examples, FX and a full list of all the functions Sonic Pi provides for coding music.
H. Scope Viewer
The scope viewer allows you to see the sound you’re hearing. You can easily see that the saw wave looks like a saw and that the basic beep is a curvey sine wave. You can also see the difference between loud and quiet sounds by the size of the lines. There are 3 scopes to play with - the default is a combined scope for the left and right channels, there is a stereo scope which draws a separate scope for each channel. Finally there is a Lissajous curve scope which will show the phase relationship between the left and right channels and allows you to draw pretty pictures with sound (https://en.wikipedia.org/wiki/Lissajous_curve).
Sonic Pi encourages you to learn about both computing and music through play and experimentation. The most important thing is that you’re having fun, and before you know it you’ll have accidentally learned how to code, compose and perform.
There are no mistakes
Whilst we’re on this subject, let me just give you one piece of advice I’ve learned over my years of live coding with music - there are no mistakes, only opportunities. This is something I’ve often heard in relation to jazz but it works equally well with live coding. No matter how experienced you are - from a complete beginner to a seasoned live coder, you’ll run some code that has a completely unexpected outcome. It might sound insanely cool - in which case run with it. However, it might sound totally jarring and out of place. It doesn’t matter that it happened - what matters is what you do next with it. Take the sound, manipulate it and morph it into something awesome. The crowd will go wild.
When you’re learning, it’s tempting to want to do amazing things now. However, just hold that thought and see it as a distant goal to reach later. For now, instead think of the simplest thing you could write which would be fun and rewarding that’s a small step towards the amazing thing you have in your head. Once you have an idea about that simple step, then try and build it, play with it and then see what new ideas it gives you. Before long you’ll be too busy having fun and making real progress.
Just make sure to share your work with others!
OK, enough of the intros - let’s get into some sound.
In this section we’ll cover the basics of triggering and manipulating synths. Synth is short for synthesiser which is a fancy word for something which creates sounds. Typically synths are quite complicated to use - especially analog synths such as Eurorack modules connected together by a mess of wires. However, Sonic Pi gives you much of that power in a very simple and approachable manner.
Don’t be fooled by the immediate simplicity of Sonic Pi’s interface. You can get very deep into very sophisticated sound manipulation if that’s your thing. Hold on to your hats…
Take a look at the following code:
This is where it all starts. Go ahead, copy and paste it into the code window at the top of the app (the big white space under the Run button). Now, press Run…
Intense. Press it again. And again. And again…
Woah, crazy, I’m sure you could keep doing that all day. But wait, before you lose yourself in an infinite stream of beeps, try changing the number:
Can you hear the difference? Try a lower number:
So, lower numbers make lower pitched beeps and higher numbers make higher pitched beeps. Just like on a piano, the keys at the lower part of the piano (the left hand side) play lower notes and the keys on the higher part of the piano (the right hand side) play higher notes. In fact, the numbers actually relate to notes on the piano. actually means play the 47th note on the piano. Which means that is one note up (the next note to the right). It just so happens that the 4th octave C is number 60. Go ahead and play it: .
Don’t worry if this means nothing to you - it didn’t to me when I first started. All that matters right now is that you know that low numbers make lower beeps and high numbers make higher beeps.
Playing a note is quite fun, but playing many at the same time can be even better. Try it:
Jazzy! So, when you write multiple s, they all play at the same time. Try it for yourself - which numbers sound good together? Which sound terrible? Experiment, explore and find out for yourself.
So, playing notes and chords is fun - but how about a melody? What if you wanted to play one note after another and not at the same time? Well, that’s easy, you just need to between the notes:
How lovely, a little arpeggio. So what does the mean in ? Well it means the duration of the sleep. It actually means sleep for one beat, but for now we can think about it as sleeping for 1 second. So, what if we wanted to make our arpeggio a little faster? Well, we need to use shorter sleep values. What about a half i.e. :
Notice how it plays faster. Now, try for yourself, change the times - use different times and notes.
One thing to try is in-between notes such as and . There’s absolutely no need to stick to standard whole notes. Play around and have fun.
Traditional Note Names
For those of you that already know some musical notation (don’t worry if you don’t - you don’t need it to have fun) you might want to write a melody using note names such as C and F# rather than numbers. Sonic Pi has you covered. You can do the following:
Remember to put the colon in front of your note name so that it goes pink. Also, you can specify the octave by adding a number after the note name:
If you want to make a note sharp, add an after the note name such as and if you want to make a note flat, add a such as .
Now go crazy and have fun making your own tunes.
As well as allowing you to control which note to play or which sample to trigger, Sonic Pi provides a whole range of options to craft and control the sounds. We’ll be covering many of these in this tutorial and there’s extensive documentation for each in the help system. However, for now we’ll introduce two of the most useful: amplitude and pan. First, let’s look at what options actually are.
Sonic Pi supports the notion of options (or opts for short) for its synths. Opts are controls you pass to which modify and control aspects of the sound you hear. Each synth has its own set of opts for finely tuning its sound. However, there are common sets of opts shared by many sounds such as and envelope opts (covered in another section).
Opts have two major parts, their name (the name of the control) and their value (the value you want to set the control at). For example, you might have a opt called and want to set it with a value of .
Opts are passed to calls to by using a comma and then the name of the opt such as (don’t forget the colon ) and then a space and the value of the opt. For example:
(Note that isn’t a valid opt, we’re just using it as an example).
You can pass multiple opts by separating them with a comma:
The order of the opts doesn’t matter, so the following is identical:
Opts that aren’t recognised by the synth are just ignored (like and which are clearly ridiculous opt names!)
If you accidentally use the same opt twice with different values, the last one wins. For example, here will have the value 2 rather than 0.5:
Many things in Sonic Pi accept opts, so just spend a little time learning how to use them and you’ll be set! Let’s play with our first opt: .
Amplitude is a computer representation of the loudness of a sound. A high amplitude produces a loud sound and a low amplitude produces a quiet sound. Just as Sonic Pi uses numbers to represent time and notes, it uses numbers to represent amplitude. An amplitude of 0 is silent (you’ll hear nothing) whereas an amplitude of 1 is normal volume. You can even crank up the amplitude higher to 2, 10, 100. However, you should note that when the overall amplitude of all the sounds gets too high, Sonic Pi uses what’s called a compressor to squash them all to make sure things aren’t too loud for your ears. This can often make the sound muddy and strange. So try to use low amplitudes, i.e. in the range 0 to 0.5 to avoid compression.
Amp it up
To change the amplitude of a sound, you can use the opt. For example, to play at half amplitude pass 0.5:
To play at double amplitude pass 2:
The opt only modifies the call to it’s associated with. So, in this example, the first call to play is at half volume and the second is back to the default (1):
Of course, you can use different values for each call to play:
Another fun opt to use is which controls the panning of a sound in stereo. Panning a sound to the left means that you hear it out of the left speaker, and panning it to the right means you hear it out of your right speaker. For our values, we use a -1 to represent fully left, 0 to represent center and 1 to represent fully right in the stereo field. Of course, we’re free to use any value between -1 and 1 to control the exact positioning of our sound.
Let’s play a beep out of the left speaker:
Now, let’s play it out of the right speaker:
Finally let’s play it back out of the center of both (the default position):
Now, go and have fun changing the amplitude and panning of your sounds!
So far we’ve had quite a lot of fun making beeps. However, you’re probably starting to get bored of the basic beep noise. Is that all Sonic Pi has to offer? Surely there’s more to live coding than just playing beeps? Yes there is, and in this section we’ll explore some of the exciting range of sounds that Sonic Pi has to offer.
Sonic Pi has a number of different instruments it calls synths (which is short for synthesisers). Whereas samples represent pre-recorded sounds, synths are capable of generating new sounds depending on how you control them (which we’ll explore later in this tutorial). Sonic Pi’s synths are very powerful and expressive and you’ll have a lot of fun exploring and playing with them. First, let’s learn how to select the current synth to use.
Buzzy saws and prophets
A fun sound is the saw wave - let’s give it a try:
Let’s try another sound - the prophet:
How about combining two sounds. First one after another:
Now multiple sounds at the same time (by not sleeping between successive calls to ):
Notice that the command only affects the following calls to . Think of it like a big switch - new calls to will play whatever synth it’s currently pointing to. You can move the switch to a new synth with .
To see which synths Sonic Pi has for you to play with take a look at the Synths option in the menu at the bottom of this help screen (between Examples & Fx). There are over 20 to choose from. Here are a few of my favourites:
Now play around with switching synths during your music. Have fun combining synths to make new sounds as well as using different synths for different sections of your music.
In an earlier section, we looked at how we can use the command to control when to trigger our sounds. However, we haven’t yet been able to control the duration of our sounds.
In order to give us a simple, yet powerful means of controlling the duration of our sounds, Sonic Pi provides the notion of an ADSR amplitude envelope (we’ll cover what ADSR means later in this section). An amplitude envelope offers two useful aspects of control:
- control over the duration of a sound
- control over the amplitude of a sound
The duration is the length the sound lasts for. A longer duration means that you hear the sound for longer. Sonic Pi’s sounds all have a controllable amplitude envelope, and the total duration of that envelope is the duration of the sound. Therefore, by controlling the envelope you control the duration.
The ADSR envelope not only controls duration, it also gives you fine control over the amplitude of the sound. All audible sounds start and end silent and contain some non-silent part in-between. Envelopes allow you to slide and hold the amplitude of non-silent parts of the sound. It’s like giving someone instructions on how to turn up and down the volume of a guitar amplifier. For example you might ask someone to “start at silence, slowly move up to full volume, hold it for a bit, then quickly fall back to silence.” Sonic Pi allows you to program exactly this behaviour with envelopes.
Just to recap, as we have seen before, an amplitude of 0 is silence and an amplitude of 1 is normal volume.
Now, let us look at each of the parts of the envelopes in turn.
The only part of the envelope that’s used by default is the release time. This is the time it takes for the synth’s sound to fade out. All synths have a release time of 1 which means that by default they have a duration of 1 beat (which at the default BPM of 60 is 1 second):
The note will be audible for 1 second. Go ahead and time it :-) This is short hand for the longer more explicit version:
Notice how this sounds exactly the same (the sound lasts for one second). However, it’s now very easy to change the duration by modifying the value of the opt:
We can make the synth sound for a very short amount of time by using a very small release time:
The duration of the release of the sound is called the release phase and by default is a linear transition (i.e. a straight line). The following diagram illustrates this transition:
The vertical line at the far left of the diagram shows that the sound starts at 0 amplitude, but goes up to full amplitude immediately (this is the attack phase which we’ll cover next). Once at full amplitude it then moves in a straight line down to zero taking the amount of time specified by . Longer release times produce longer synth fade outs.
You can therefore change the duration of your sound by changing the release time. Have a play adding release times to your music.
By default, the attack phase is 0 for all synths which means they move from 0 amplitude to 1 immediately. This gives the synth an initial percussive sound. However, you may wish to fade your sound in. This can be achieved with the opt. Try fading in some sounds:
You may use multiple opts at the same time. For example for a short attack and a long release try:
This short attack and long release envelope is illustrated in the following diagram:
Of course, you may switch things around. Try a long attack and a short release:
Finally, you can also have both short attack and release times for shorter sounds.
In addition to specifying attack and release times, you may also specify a sustain time to control the sustain phase. This is the time for which the sound is maintained at full amplitude between the attack and release phases.
The sustain time is useful for important sounds you wish to give full presence in the mix before entering an optional release phase. Of course, it’s totally valid to set both the and opts to 0 and just use the sustain to have absolutely no fade in or fade out to the sound. However, be warned, a release of 0 can produce clicks in the audio and it’s often better to use a very small value such as 0.2.
For an extra level of control, you can also specify a decay time. This is a phase of the envelope that fits between the attack and sustain phases and specifies a time where the amplitude will drop from the to the (which unless you explicitly set it will be set to the ). By default, the opt is 0 and both the attack and sustain levels are 1 so you’ll need to specify them for the decay time to have any effect:
One last trick is that although the opt defaults to be the same value as you can explicitly set them to different values for full control over the envelope. This allows you to to create envelopes such as the following:
It’s also possible to set the to be higher than :
So to summarise, Sonic Pi’s ADSR envelopes have the following phases:
- attack - time from 0 amplitude to the ,
- decay - time to move amplitude from to ,
- sustain - time to move the amplitude from to ,
- release - time to move amplitude from to 0
It’s important to note that the duration of a sound is the summation of the times of each of these phases. Therefore the following sound will have a duration of 0.5 + 1 + 2 + 0.5 = 4 beats:
Now go and have a play adding envelopes to your sounds…
Another great way to develop your music is to use pre-recorded sounds. In great hip-hop tradition, we call these pre-recorded sounds samples. So, if you take a microphone outside, go and record the gentle sound of rain hitting canvas, you’ve just created a sample.
Sonic Pi lets you do lots of fun things with samples. Not only does it ship with 130 public domain samples ready for you to jam with, it lets you play and manipulate your own. Let’s get to it…
Playing beeps is only the beginning. Something that’s a lot of fun is triggering pre-recorded samples. Try it:
Sonic Pi includes many samples for you to play with. You can use them just like you use the command. To play multiple samples and notes just write them one after another:
If you want to space them out in time, use the command:
Notice how Sonic Pi doesn’t wait for a sound to finish before starting the next sound. The command only describes the separation of the triggering of the sounds. This allows you to easily layer sounds together creating interesting overlap effects. Later in this tutorial we’ll take a look at controlling the duration of sounds with envelopes.
There are two ways to discover the range of samples provided in Sonic Pi. First, you can use this help system. Click on Samples in the menu at the bottom of this help screen, choose your category and then you’ll see a list of available sounds.
Alternatively you can use the auto-completion system. Simply type the start of a sample group such as: and you’ll see a drop-down of sample names appear for you to select. Try the following category prefixes:
Now start mixing samples into your compositions!
As we saw with synths, we can easily control our sounds with parameters. Samples support exactly the same parameterisation mechanism. Let’s revisit our friends and .
You can change the amplitude of samples with exactly the same approach you used for synths:
We’re also able to use the parameter on samples. For example, here’s how we’d play the amen break in the left ear and then half way through play it again through the right ear:
Note that 0.877 is half the duration of the sample in seconds.
Finally, note that if you set some synth defaults with (which we will discuss later), these will be ignored by .
Now that we can play a variety of synths and samples to create some music, it’s time to learn how to modify both the synths and samples to make the music even more unique and interesting. First, let’s explore the ability to stretch and squash samples.
Samples are pre-recorded sounds stored as numbers which represent how to move the speaker cone to reproduce the sound. The speaker cone can move in and out, and so the numbers just need to represent how far in and out the cone needs to be for each moment in time. To be able to faithfully reproduce a recorded sound the sample typically needs to store many thousands of numbers per second! Sonic Pi takes this list of numbers and feeds them at the right speed to move your computer’s speaker in and out in just the right way to reproduce the sound. However, it’s also fun to change the speed with which the numbers are fed to the speaker to change the sound.
Let’s play with one of the ambient sounds: . To play it with the default rate, you can pass a opt to :
This plays it at normal rate (1), so nothing special yet. However, we’re free to change that number to something else. How about :
Woah! What’s going on here? Well, two things. Firstly, the sample takes twice as long to play, secondly the sound is an octave lower. Let’s explore these things in a little more detail.
A sample that’s fun to stretch and compress is the Amen Break. At normal rate, we might imagine throwing it into a drum ‘n’ bass track:
However by changing the rate we can switch up genres. Try half speed for old school hip-hop:
If we speed it up, we enter jungle territory:
Now for our final party trick - let’s see what happens if we use a negative rate:
Woah! It plays it backwards! Now try playing with lots of different samples at different rates. Try very fast rates. Try crazy slow rates. See what interesting sounds you can produce.
A Simple Explanation of Sample Rate
A useful way to think of samples is as springs. Playback rate is like squashing and stretching the spring. If you play the sample at rate 2, you’re squashing the spring to half its normal length. The sample therefore takes half the amount of time to play as it’s shorter. If you play the sample at half rate, you’re stretching the spring to double its length. The sample therefore takes twice the amount of time to play as it’s longer. The more you squash (higher rate), the shorter it gets, the more you stretch (lower rate), the longer it gets.
Compressing a spring increases its density (the number of coils per cm) - this is similar to the sample sounding higher pitched. Stretching the spring decreases its density and is similar to the sound having a lower pitch.
The Maths Behind Sample Rate
(This section is provided for those that are interested in the details. Please feel free to skip it…)
As we saw above, a sample is represented by a big long list of numbers representing where the speaker should be through time. We can take this list of numbers and use it to draw a graph which would look similar to this:
You might have seen pictures like this before. It’s called the waveform of a sample. It’s just a graph of numbers. Typically a waveform like this will have 44100 points of data per second (this is due to the Nyquist-Shannon sampling theorem). So, if the sample lasts for 2 seconds, the waveform will be represented by 88200 numbers which we would feed to the speaker at a rate of 44100 points per second. Of course, we could feed it at double rate which would be 88200 points per second. This would therefore take only 1 second to play back. We could also play it back at half rate which would be 22050 points per second taking 4 seconds to play back.
The duration of the sample is affected by the playback rate:
- Doubling the playback rate halves the playback time,
- Halving the playback rate doubles the playback time,
- Using a playback rate of one fourth quadruples the playback time,
- Using a playback rate of 1/10 makes playback last 10 times longer.
We can represent this with the formula:
Changing the playback rate also affects the pitch of the sample. The frequency or pitch of a waveform is determined by how fast it moves up and down. Our brains somehow turn fast movement of speakers into high notes and slow movement of speakers into low notes. This is why you can sometimes even see a big bass speaker move as it pumps out super low bass - it’s actually moving a lot slower in and out than a speaker producing higher notes.
If you take a waveform and squash it it will move up and down more times per second. This will make it sound higher pitched. It turns out that doubling the amount of up and down movements (oscillations) doubles the frequency. So, playing your sample at double rate will double the frequency you hear it. Also, halving the rate will halve the frequency. Other rates will affect the frequency accordingly.
It is also possible to modify the duration and amplitude of a sample using an ADSR envelope. However, this works slightly differently to the ADSR envelope available on synths. Sample envelopes only allow you to reduce the amplitude and duration of a sample - and never to increase it. The sample will stop when either the sample has finished playing or the envelope has completed - whichever is first. So, if you use a very long , it won’t extend the duration of the sample.
Let’s return to our trusty friend the Amen Break:
With no opts, we hear the full sample at full amplitude. If we want to fade this in over 1 second we can use the param:
For a shorter fade in, choose a shorter attack value:
Where the ADSR envelope’s behaviour differs from the standard synth envelope is in the sustain value. In the standard synth envelope, the sustain defaulted to 0 unless you set it manually. With samples, the sustain value defaults to an automagical value - the time left to play the rest of the sample. This is why we hear the full sample when we pass no defaults. If the attack, decay, sustain and release values were all 0 we’d never hear a peep. Sonic Pi therefore calculates how long the sample is, deducts any attack, decay and release times and uses the result as your sustain time. If the attack, decay and release values add up to more than the duration of the sample, the sustain is simply set to 0.
To explore this, let’s consider our Amen break in more detail. If we ask Sonic Pi how long the sample is:
It will print out which is the length of the sample in seconds. Let’s just round that to for convenience here. Now, if we set the release to , something surprising will happen:
It will play the first second of the sample at full amplitude before then fading out over a period of 0.75 seconds. This is the auto sustain in action. By default, the release always works from the end of the sample. If our sample was 10.75 seconds long, it would play the first 10 seconds at full amplitude before fading out over 0.75s.
Remember: by default, fades out at the end of a sample.
Fade In and Out
We can use both and together with the auto sustain behaviour to fade both in and out over the duration of the sample:
As the full duration of the sample is 1.75s and our attack and release phases add up to 1.5s, the sustain is automatically set to 0.25s. This allows us to easily fade the sample in and out.
We can easily get back to our normal synth ADSR behaviour by manually setting to a value such as 0:
Now, our sample only plays for 0.75 seconds in total. With the default for and at 0, the sample jumps straight to full amplitude, sustains there for 0s then releases back down to 0 amplitude over the release period - 0.75s.
We can use this behaviour to good effect to turn longer sounding samples into shorter, more percussive versions. Consider the sample :
You can hear the cymbal sound ringing out over a period of time. However, we can use our envelope to make it more percussive:
You can then emulate hitting the cymbal and then dampening it by increasing the sustain period:
Now go and have fun putting envelopes over the samples. Try changing the rate too for really interesting results.
This section will conclude our exploration of Sonic Pi’s sample player. Let’s do a quick recap. So far we’ve looked at how we can trigger samples:
We then looked at how we can change the rate of samples such as playing them at half speed:
Next, we looked at how we could fade a sample in (let’s do it at half speed):
We also looked at how we could use the start of a sample percussively by giving an explicit value and setting both the attack and release to be short values:
However, wouldn’t it be nice if we didn’t have to always start at the beginning of the sample? Wouldn’t it also be nice if we didn’t have to always finish at the end of the sample?
Choosing a starting point
It is possible to choose an arbitrary starting point in the sample as a value between 0 and 1 where 0 is the start of the sample, 1 is the end and 0.5 is half way through the sample. Let’s try playing only the last half of the amen break:
How about the last quarter of the sample:
Choosing a finish point
Similarly, it is possible to choose an arbitrary finish point in the sample as a value between 0 and 1. Let’s finish the amen break half way through:
Specifying start and finish
Of course, we can combine these two to play arbitrary segments of the audio file. How about only a small section in the middle:
What happens if we choose a start position after the finish position?
Cool! It plays it backwards!
Combining with rate
We can combine this new ability to play arbitrary segments of audio with our friend . For example, we can play a very small section of the middle of the amen break very slowly:
Combining with envelopes
Finally, we can combine all of this with our ADSR envelopes to produce interesting results:
Now go and have a play mashing up samples with all of this fun stuff…
Whilst the built-in samples can get you up and started quickly, you might wish to experiment with other recorded sounds in your music. Sonic Pi totally supports this. First though, let’s have a quick discussion on the portability of your piece.
When you compose your piece purely with built-in synths and samples, the code is all you need to faithfully reproduce your music. Think about that for a moment - that’s amazing! A simple piece of text you can email around or stick in a Gist represents everything you need to reproduce your sounds. That makes it really easy to share with your friends as they just need to get hold of the code.
However, if you start using your own pre-recorded samples, you lose this portability. This is because to reproduce your music other people not only need your code, they need your samples too. This limits the ability for others to manipulate, mash-up and experiment with your work. Of course this shouldn’t stop you from using your own samples, it’s just something to consider.
So how do you play any arbitrary WAV, AIFF or FLAC file on your computer? All you need to do is pass the path of that file to :
Sonic Pi will automatically load and play the sample. You can also pass all the standard params you’re used to passing :
Note: this section of the tutorial covers the advanced topic of working with large directories of your own samples. This will be the case if you’ve downloaded or bought your own sample packs and wish to use them within Sonic Pi.
Feel free to skip this if you’re happy working with the built-in samples.
When working with large folders of external samples it can be cumbersome to have to type the whole path every time to trigger an individual sample.
For example, say you have the following folder on your machine:
When we look inside that folder we find the following samples:
Typically in order to play the piano sample we can use the full path:
If we want to then play the guitar sample we can use its full path too:
However, both of these calls to sample requires us to know the names of the samples within our directory. What if we just want to listen to each sample in turn quickly?
Indexing Sample Packs
If we want to play the first sample in a directory we just need to pass the directory’s name to and the index as follows:
We can even make a shortcut to our directory path using a variable:
Now, if we want to play the second sample in our directory, we just need to add 1 to our index:
Notice that we no longer need to know the names of the samples in the directory - we just need to know the directory itself (or have a shortcut to it). If we ask for an index which is larger than the number of samples, it simply wraps round just like Rings. Therefore, whatever number we use we’re guaranteed to get one of the samples in that directory.
Filtering Sample Packs
Usually indexing is enough, but sometimes we need more power to sort and organise our samples. Luckily many sample packs add useful information in the filenames. Let’s take another look at the sample file names in our directory:
Notice that in these filenames we have quite a bit of information. Firstly, we have the BPM of the sample (beats per minute) at the start. So, the piano sample is at 120 BPM and our first three melodies are at 100 BPM. Also, our sample names contain the key. So the guitar sample is in Bb and the melodies are in A#. This information is very useful for mixing in these samples with our other code. For example, we know we can only play the piano sample with code that’s in 120 BPM and in the key of Bb.
It turns out that we can use this particular naming convention of our sample sets in the code to help us filter out the ones we want. For example, if we’re working at 120 BPM, we can filter down to all the samples that contain the string with the following:
This will play us the first match. If we want the second match we just need to use the index:
We can even use multiple filters at the same time. For example, if we want a sample whose filename contains both the substrings “120” and “A#” we can find it easily with the following code:
Finally, we’re still free to add our usual opts to the call to :
The sample filter pre-arg system understands two types of information: sources and filters. Sources are information used to create the list of potential candidates. A source can take two forms:
- “/path/to/samples” - a string representing a valid path to a directory
- “/path/to/samples/foo.wav” - a string representing a valid path to a sample
The fn will first gather all sources and use them to create a large list of candidates. This list is constructed by first adding all valid paths and then by adding all the valid , , , , files contained within the directories.
For example, take a look at the following code:
Here, we’re combining the contents of the samples within two directories and adding a specific sample. If contained 3 samples and contained 12, we’d have 16 potential samples to index and filter (3 + 12 + 1).
By default, only the sample files within a directory are gathered into the candidate list. Sometimes you might have a number of nested folders of samples you wish to search and filter within. You can therefore do a recursive search for all samples within all subfolders of a particular folder by adding to the end of the path:
Take care though as searching through a very large set of folders may take a long time. However, the contents of all folder sources are cached, so the delay will only happen the first time.
Finally, note that the sources must go first. If no source is given, then the set of built-in samples will be selected as the default list of candidates to work with.
Once you have a list of candidates you may use the following filtering types to further reduce the selection:
- Strings will filter on substring occurrence within file name (minus directory path and extension).
- Regular Expressions will filter on pattern matching of file name (minus directory path and extension).
- - Keywords will filter candidates on whether the keyword is a direct match of the filename (minus directory path and extension).
- - Procs with one argument will be treated as a candidate filter or generator function. It will be passed the list of current candidates and must return a new list of candidates (a list of valid paths to sample files).
- - Numbers will select the candidate with that index (wrapping round like a ring if necessary).
For example, we can filter over all the samples in a directory containing the string and play the first matching sample at half speed:
See the help for for many detailed usage examples. Note that the ordering of the filters is honoured.
Finally, you may use lists wherever you may place a source or filter. The list will be automatically flattened and the contents will be treated as regular sources and filters. Therefore the following calls to are semantically equivalent:
This was an advanced section for people that need real power to manipulate and use sample packs. If most of this section didn’t make too much sense, don’t worry. It’s likely you don’t need any of this functionality just yet. However, you’ll know when you do need it and you can come back and re-read this when you start working with large directories of samples.
A great way to add some interest into your music is using some random numbers. Sonic Pi has some great functionality for adding randomness to your music, but before we start we need to learn a shocking truth: in Sonic Pi random is not truly random. What on earth does this mean? Well, let’s see.
A really useful random function is which will give you a random value between two numbers - a min and a max. ( is short for ranged random). Let’s try playing a random note:
Ooh, it played a random note. It played note . A nice random note between 50 and 95. Woah, wait, did I just predict the exact random note you got too? Something fishy is going on here. Try running the code again. What? It chose again? That can’t be random!
The answer is that it is not truly random, it’s pseudo-random. Sonic Pi will give you random-like numbers in a repeatable manner. This is very useful for ensuring that the music you create on your machine sounds identical on everybody else’s machine - even if you use some randomness in your composition.
Of course, in a given piece of music, if it ‘randomly’ chose every time, then it wouldn’t be very interesting. However, it doesn’t. Try the following:
Yes! It finally sounds random. Within a given run subsequent calls to random functions will return random values. However, the next run will produce exactly the same sequence of random values and sound exactly the same. It’s as if all Sonic Pi code went back in time to exactly the same point every time the Run button was pressed. It’s the Groundhog Day of music synthesis!
A lovely illustration of randomisation in action is the haunted bells example which loops the sample with a random rate and sleep time between bell sounds:
Another fun example of randomisation is to modify the cutoff of a synth randomly. A great synth to try this out on is the emulator:
So, what if you don’t like this particular sequence of random numbers Sonic Pi provides? Well it’s totally possible to choose a different starting point via . The default seed happens to be 0, so choose a different seed for a different random experience!
Consider the following:
Every time you run this code, you’ll hear the same sequence of 5 notes. To get a different sequence simply change the seed:
This will produce a different sequence of 5 notes. By changing the seed and listening to the results you can find something that you like - and when you share it with others, they will hear exactly what you heard too.
Let’s have a look at some other useful random functions.
A very common thing to do is to choose an item randomly from a list of known items. For example, I may want to play one note from the following: 60, 65 or 72. I can achieve this with which lets me choose an item from a list. First, I need to put my numbers in a list which is done by wrapping them in square brackets and separating them with commas: . Next I just need to pass them to :
Let’s hear what that sounds like:
We’ve already seen , but let’s run over it again. It returns a random number between two values exclusively. That means it will never return either the top or bottom number - always something in between the two. The number will always be a float - meaning it’s not a whole number but a fraction of a number. Examples of floats returned by :
Occasionally you’ll want a whole random number, not a float. This is where comes to the rescue. It works similarly to except it may return the min and max values as potential random values (which means it’s inclusive rather than exclusive of the range). Examples of numbers returned by are:
This will return a random float between 0 (inclusive) and the max value you specify (exclusive). By default it will return a value between 0 and one. It’s therefore useful for choosing random values:
Similar to the relationship between and , will return a random whole number between 0 and the max value you specify.
Sometimes you want to emulate a dice throw - this is a special case of where the lower value is always 1. A call to requires you to specify the number of sides on the dice. A standard dice has 6 sides, so will act very similarly - returning values of either 1, 2, 3, 4, 5, or 6. However, just like fantasy role-play games, you might find value in a 4 sided dice, or a 12 sided dice, or a 20 sided dice - perhaps even a 120 sided dice!
Finally you may wish to emulate throwing the top score of a dice such as a 6 in a standard dice. therefore returns true with a probability of one in the number of sides on the dice. Therefore will return true with a probability of 1 in 6 or false otherwise. True and false values are very useful for statements which we will cover in a subsequent section of this tutorial.
Now, go and jumble up your code with some randomness!
Now that you’ve learned the basics of creating sounds with and and creating simple melodies and rhythms by ing between sounds, you might be wondering what else the world of code can offer you…
Well, you’re in for an exciting treat! It turns out that basic programming structures such as looping, conditionals, functions and threads give you amazingly powerful tools to express your musical ideas.
Let’s get stuck in with the basics…
A structure you’ll see a lot in Sonic Pi is the block. Blocks allow us to do useful things with large chunks of code. For example, with synth and sample parameters we were able to change something that happened on a single line. However, sometimes we want to do something meaningful to a number of lines of code. For example, we may wish to loop it, to add reverb to it, to only run it 1 time out of 5, etc. Consider the following code:
To do something with a chunk of code, we need to tell Sonic Pi where the code block starts and where it ends. We use for start and for end. For example:
However, this isn’t yet complete and won’t work (try it and you’ll get an error) as we haven’t told Sonic Pi what we want to do with this do/end block. We tell Sonic Pi this by writing some special code before the . We’ll see a number of these special pieces of code later on in this tutorial. For now, it’s important to know that wrapping your code within and tells Sonic Pi you wish to do something special with that chunk of code.
So far we’ve spent a lot of time looking at the different sounds you can make with and blocks. We’ve also learned how to trigger these sounds through time using .
As you’ve probably found out, there’s a lot of fun you can have with these basic building blocks. However, a whole new dimension of fun opens up when you start using the power of code to structure your music and compositions. In the next few sections we’ll explore some of these powerful new tools. First up is iteration and loops.
Have you written some code you’d like to repeat a few times? For example, you might have something like this:
What if we wished to repeat this 3 times? Well, we could do something simple and just copy and paste it three times:
Now that’s a lot of code! What happens if you want to change the sample to ? You’re going to have to find all the places with the original and switch them over. More importantly, what if you wanted to repeat the original piece of code 50 times or 1000? Now that would be a lot of code, and a lot of lines of code to alter if you wanted to make a change.
In fact, repeating the code should be as easy as saying do this three times. Well, it pretty much is. Remember our old friend the code block? We can use it to mark the start and end of the code we’d like to repeat three times. We then use the special code . So, instead of writing do this three times, we write - that’s not too hard. Just remember to write at the end of the code you’d like to repeat:
Now isn’t that much neater than cutting and pasting! We can use this to create lots of nice repeating structures:
We can put iterations inside other iterations to create interesting patterns. For example:
If you want something to repeat a lot of times, you might find yourself using really large numbers such as . In this case, you’re probably better off asking Sonic Pi to repeat forever (at least until you press the stop button!). Let’s loop the amen break forever:
The important thing to know about loops is that they act like black holes for code. Once the code enters a loop it can never leave until you press stop - it will just go round and round the loop forever. This means if you have code after the loop you will never hear it. For example, the cymbal after this loop will never play:
Now, get structuring your code with iteration and loops!
A common thing you’ll likely find yourself wanting to do is to not only play a random note (see the previous section on randomness) but also make a random decision and based on the outcome run some code or some other code. For example, you might want to randomly play a drum or a cymbal. We can achieve this with an statement.
Flipping a Coin
So, let’s flip a coin: if it’s heads, play a drum, if it’s tails, play a cymbal. Easy. We can emulate a coin flip with our function (introduced in the section on randomness) specifying a probability of 1 in 2: . We can then use the result of this to decide between two pieces of code, the code to play the drum and the code to play the cymbal:
Notice that statements have three parts:
- The question to ask
- The first choice of code to run (if the answer to the question is yes)
- The second choice of code to run (if the answer to the question is no)
Typically in programming languages, the notion of yes is represented by the term and the notion of no is represented by the term . So we need to find a question that will give us a or answer which is exactly what does.
Notice how the first choice is wrapped between the and the and the second choice is wrapped between the and the . Just like do/end blocks you can put multiple lines of code in either place. For example:
This time we’re sleeping for a different amount of time depending on which choice we make.
Sometimes you want to optionally execute just one line of code. This is possible by placing and then the question at the end. For example:
This will play chords of different numbers with the chance of each note playing having a different probability.
So you’ve made your killer bassline and a phat beat. How do you play them at the same time? One solution is to weave them together manually - play some bass, then a bit of drums, then more bass… However, the timing soon gets hard to think about, especially when you start weaving in more elements.
What if Sonic Pi could weave things for you automatically? Well, it can, and you do it with a special thing called a thread.
To keep this example simple, you’ll have to imagine that this is a phat beat and a killer bassline:
As we’ve discussed previously, loops are like black holes for the program. Once you enter a loop you can never exit from it until you hit stop. How do we play both loops at the same time? We have to tell Sonic Pi that we want to start something at the same time as the rest of the code. This is where threads come to the rescue.
Threads to the Rescue
By wrapping the first loop in an do/end block we tell Sonic Pi to run the contents of the do/end block at exactly the same time as the next statement after the do/end block (which happens to be the second loop). Try it and you’ll hear both the drums and the bassline weaved together!
Now, what if we wanted to add a synth on top. Something like:
Now we have the same problem as before. The first loop is played at the same time as the second loop due to the . However, the third loop is never reached. We therefore need another thread:
Runs as threads
What may surprise you is that when you press the Run button, you’re actually creating a new thread for the code to run. This is why pressing it multiple times will layer sounds over each other. As the runs themselves are threads, they will automatically weave the sounds together for you.
As you learn how to master Sonic Pi, you’ll learn that threads are the most important building blocks for your music. One of the important jobs they have is to isolate the notion of current settings from other threads. What does this mean? Well, when you switch synths using you’re actually just switching the synth in the current thread - no other thread will have their synth switched. Let’s see this in action:
Notice how the middle sound was different to the others? The statement only affected the thread it was in and not the outer main run thread.
When you create a new thread with , the new thread will automatically inherit all of the current settings from the current thread. Let’s see that:
Notice how the second note is played with the synth even though it was played from a separate thread? Any of the settings modified with the various functions will behave in the same way.
When threads are created, they inherit all the settings from their parent but they don’t share any changes back.
Finally, we can give our threads names:
Look at the log pane when you run this code. See how the log reports the name of the thread with the message?
Only One Thread per Name Allowed
One last thing to know about named threads is that only one thread of a given name may be running at the same time. Let’s explore this. Consider the following code:
Go ahead and paste that into a buffer and press the Run button. Press it again a couple of times. Listen to the cacophony of multiple amen breaks looping out of time with each other. Ok, you can press Stop now.
This is the behaviour we’ve seen again and again - if you press the Run button, sound layers on top of any existing sound. Therefore if you have a loop and press the Run button three times, you’ll have three layers of loops playing simultaneously.
However, with named threads it is different:
Try pressing the Run button multiple times with this code. You’ll only ever hear one amen break loop. You’ll also see this in the log:
Sonic Pi is telling you that a thread with the name is already playing, so it’s not creating another.
This behaviour may not seem immediately useful to you now - but it will be very handy when we start to live code…
Once you start writing lots of code, you may wish to find a way to organise and structure things to make them tidier and easier to understand. Functions are a very powerful way to do this. They give us the ability to give a name to a bunch of code. Let’s take a look.
Here, we’ve defined a new function called . We do this with our old friend the do/end block and the magic word followed by the name we wish to give to our function. We didn’t have to call it , we could have called it anything we want such as , or ideally something meaningful to you like or .
Remember to prepend a colon to the name of your function when you define it.
Once we have defined our function we can call it by just writing its name:
We can even use inside iteration blocks or anywhere we may have written or . This gives us a great way to express ourselves and to create new meaningful words for use in our compositions.
Functions are remembered across runs
So far, every time you’ve pressed the Run button, Sonic Pi has started from a completely blank slate. It knows nothing except for what is in the buffer. You can’t reference code in another buffer or another thread. However, functions change that. When you define a function, Sonic Pi remembers it. Let’s try it. Delete all the code in your buffer and replace it with:
Press the Run button - and hear your function play. Where did the code go? How did Sonic Pi know what to play? Sonic Pi just remembered your function - so even after you deleted it from the buffer, it remembered what you had typed. This behaviour only works with functions created using (and ).
You might be interested in knowing that just like you can pass min and max values to , you can teach your functions to accept arguments. Let’s take a look:
This isn’t very exciting, but it illustrates the point. We’ve created our own version of called which is parameterised.
The parameters need to go after the of the do/end block, surrounded by vertical goalposts and separated by commas . You may use any words you want for the parameter names.
The magic happens inside the do/end block. You may use the parameter names as if they were real values. In this example I’m playing note . You can consider the parameters as a kind of promise that when the code runs, they will be replaced with actual values. You do this by passing a parameter to the function when you call it. I do this with to play note 80. Inside the function definition, is now replaced with 80, so turns into . When I call it again with , is now replaced with 90, so turns into .
Let’s see a more interesting example:
Here I used as if it was a number in the line . I also used as if it was a note name in my call to .
See how we’re able to write something very expressive and easy to read by moving a lot of our logic into a function!
A useful thing to do in your code is to create names for things. Sonic Pi makes this very easy: you write the name you wish to use, an equal sign (), then the thing you want to remember:
Here, we’ve ‘remembered’ the symbol in the variable . We can now use everywhere we might have used . For example:
There are three main reasons for using variables in Sonic Pi: communicating meaning, managing repetition and capturing the results of things.
When you write code it’s easy to just think you’re telling the computer how to do stuff - as long as the computer understands it’s OK. However, it’s important to remember that it’s not just the computer that reads the code. Other people may read it too and try to understand what’s going on. Also, you’re likely to read your own code in the future and try to understand what’s going on. Although it might seem obvious to you now - it might not be so obvious to others or even your future self!
One way to help others understand what your code is doing is to write comments (as we saw in a previous section). Another is to use meaningful variable names. Look at this code:
Why does it use the number ? Where did this number come from? What does it mean? However, look at this code:
Now, it’s much clearer what means: it’s the duration of the sample ! Of course, you might say why not simply write:
Which, of course, is a very nice way of communicating the intent of the code.
Often you see a lot of repetition in your code and when you want to change things, you have to change it in a lot of places. Take a look at this code:
We’re doing a lot of things with ! What if we wanted to hear what it sounded like with another loop sample such as ? We’d have to find and replace all s with . That might be fine if you have lots of time - but what if you’re performing on stage? Sometimes you don’t have the luxury of time - especially if you want to keep people dancing.
What if you’d written your code like this:
Now, that does exactly the same as above (try it). It also gives us the ability to just change one line to and we change it in many places through the magic of variables.
Finally, a good motivation for using variables is to capture the results of things. For example, you may wish to do things with the duration of a sample:
We can now use anywhere we need the duration of the sample.
Perhaps more importantly, a variable allows us to capture the result of a call to or :
Now we have caught and remembered as a variable, which allows us to control the synth as it is running:
We’ll look into controlling synths in more detail in a later section.
Warning: Variables and Threads
Whilst variables are great for giving things names and capturing the results of things, it is important to know that they should typically only be used locally within a thread. For example, don’t do this: