Press Run to start the engine: its live metrics show here.
3 Samples
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…
3.1 Triggering Samples
Playing beeps is only the beginning. Something that’s a lot of fun is
triggering pre-recorded samples. Try it:
Triggering Samples
sample :ambi_lunar_land
Sonic Pi includes many samples for you to play with. You can use them
just like you use the play command. To play multiple samples and notes
just write them one after another:
Triggering Samples · 2
play 36
play 48
sample :ambi_lunar_land
sample :ambi_drone
If you want to space them out in time, use the sleep command:
Triggering Samples · 3
sample :ambi_lunar_land
sleep 1
play 48
sleep 0.5
play 36
sample :ambi_drone
sleep 1
play 36
Notice how Sonic Pi doesn’t wait for a sound to finish before starting
the next sound. The sleep command only describes the separation of the
triggering of the sounds. This allows you to easily layer sounds
together creating interesting overlap effects.
Discovering Samples
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: sample :ambi_ and you’ll see a
drop-down of sample names appear for you to select. Try the following
category prefixes:
:ambi_
:bass_
:elec_
:perc_
:guit_
:drum_
:misc_
:bd_
Now start mixing samples into your compositions!
3.2 Sample Parameters
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 amp: and pan:.
Amping samples
You can change the amplitude of samples with exactly the same
approach you used for synths:
Amping samples
sample :ambi_lunar_land, amp:0.5
Panning samples
We’re also able to use the pan: 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:
Panning samples
sample :loop_amen, pan: -1
sleep 0.877
sample :loop_amen, pan:1
Note that 0.877 is half the duration of the :loop_amen sample in
seconds.
Finally, note that if you set some synth defaults with
use_synth_defaults (which we will discuss later), these will be
ignored by sample.
3.3 Stretching Samples
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.
Sample Representation
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.
Changing Rate
Let’s play with one of the ambient sounds: :ambi_choir. To play it
with the default rate, you can pass a rate: opt to sample:
Changing Rate
sample :ambi_choir, rate:1
This plays it at normal rate (1), so nothing special yet. However, we’re
free to change that number to something else. How about 0.5:
Changing Rate · 2
sample :ambi_choir, rate:0.5
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.
Let's stretch
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:
Let's stretch
sample :loop_amen
However by changing the rate we can switch up genres. Try half speed for
old school hip-hop:
Let's stretch · 2
sample :loop_amen, rate:0.5
If we speed it up, we enter jungle territory:
Let's stretch · 3
sample :loop_amen, rate:1.5
Now for our final party trick - let’s see what happens if we use a
negative rate:
Let's stretch · 4
sample :loop_amen, rate: -1
Woah! It plays it backwards! Now try playing with lots of different
samples at different rates. Try very fast rates. Try really 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.
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.
3.4 Enveloped Samples
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 release:, it won’t extend the duration of the sample.
Amen Envelopes
Let’s return to our trusty friend the Amen Break:
Amen Envelopes
sample :loop_amen
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 attack: param:
Amen Envelopes · 2
sample :loop_amen, attack:1
For a shorter fade in, choose a shorter attack value:
Amen Envelopes · 3
sample :loop_amen, attack:0.3
Auto Sustain
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.
Fade Outs
To explore this, let’s consider our Amen break in more detail. If we ask
Sonic Pi how long the sample is:
Fade Outs
print sample_duration :loop_amen
It will print out 1.753310657596372 which is the length of the sample
in seconds. Let’s just round that to 1.75 for convenience here. Now,
if we set the release to 0.75, something surprising will happen:
Fade Outs · 2
sample :loop_amen, release:0.75
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, release: fades out at the end of a sample.
Fade In and Out
We can use both attack: and release: together with the auto sustain
behaviour to fade both in and out over the duration of the sample:
Fade In and Out
sample :loop_amen, attack:0.75, release:0.75
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.
Explicit sustain
We can easily get back to our normal synth ADSR behaviour by manually
setting sustain: to a value such as 0:
Explicit sustain
sample :loop_amen, sustain:0, release:0.75
Now, our sample only plays for 0.75 seconds in total. With the default
for attack: and decay: 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.
Percussive cymbals
We can use this behaviour to good effect to turn longer sounding samples
into shorter, more percussive versions. Consider the sample
:drum_cymbal_open:
Percussive cymbals
sample :drum_cymbal_open
You can hear the cymbal sound ringing out over a period of
time. However, we can use our envelope to make it more percussive:
Now go and have fun putting envelopes over the samples. Try changing the
rate too for really interesting results.
3.5 Partial Samples
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:
Partial Samples
sample :loop_amen
We then looked at how we can change the rate of samples such as
playing them at half speed:
Partial Samples · 2
sample :loop_amen, rate:0.5
Next, we looked at how we could fade a sample in (let’s do it at half
speed):
Partial Samples · 3
sample :loop_amen, rate:0.5, attack:1
We also looked at how we could use the start of a sample percussively
by giving sustain: 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:
Choosing a starting point
sample :loop_amen, start:0.5
How about the last quarter of the sample:
Choosing a starting point · 2
sample :loop_amen, start:0.75
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:
Choosing a finish point
sample :loop_amen, finish:0.5
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:
Specifying start and finish
sample :loop_amen, start:0.4, finish:0.6
What happens if we choose a start position after the finish position?
Specifying start and finish · 2
sample :loop_amen, start:0.6, finish:0.4
Cool! It plays it backwards!
Combining with rate
We can combine this new ability to play arbitrary segments of audio with
our friend rate:. For example, we can play a very small section of the
middle of the amen break very slowly:
Now go and have a play mashing up samples with all of this fun stuff…
3.6 External Samples
On the web: your own sample files don't play here yet, only the built-in samples. Everything in this section works in the Sonic Pi app for Windows, macOS, Linux and Raspberry Pi.
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.
Portability
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.
Local Samples
So how do you play any arbitrary WAV, AIFF, OGG, OGA, FLAC or MP3 file on your computer?
All you need to do is pass the path of that file to sample:
Local Samples
# Raspberry Pi, Mac, Linux
sample "/Users/sam/Desktop/my-sound.wav"
# Windows
sample "C:/Users/sam/Desktop/my-sound.wav"
Sonic Pi will automatically load and play the sample. You can also pass
all the standard params you’re used to passing sample:
On the web: your own sample files don't play here yet, only the built-in samples. Everything in this section works in the Sonic Pi app for Windows, macOS, Linux and Raspberry Pi.
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:
Sample Packs
/path/to/my/samples/
When we look inside that folder we find the following samples:
100_A#_melody1.wav
100_A#_melody2.wav
100_A#_melody3.wav
120_A#_melody4.wav
120_Bb_guit1.wav
120_Bb_piano1.wav
Typically in order to play the piano sample we can use the full path:
Sample Packs · 2
sample "/path/to/my/samples/120_Bb_piano1.wav"
If we want to then play the guitar sample we can use its full path too:
Sample Packs · 3
sample "/path/to/my/samples/120_Bb_guit.wav"
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 sample and the index 0 as follows:
Indexing Sample Packs
sample "/path/to/my/samples/", 0
We can even make a shortcut to our directory path using a variable:
Indexing Sample Packs · 2
samps = "/path/to/my/samples/"
sample samps, 0
Now, if we want to play the second sample in our directory, we just need
to add 1 to our index:
Indexing Sample Packs · 3
samps = "/path/to/my/samples/"
sample samps, 1
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:
100_A#_melody1.wav
100_A#_melody2.wav
100_A#_melody3.wav
120_A#_melody4.wav
120_Bb_guit1.wav
120_Bb_piano1.wav
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 "120" with the following:
Filtering Sample Packs
samps = "/path/to/my/samples/"
sample samps, "120"
This will play us the first match. If we want the second match we just
need to use the index:
Filtering Sample Packs · 2
samps = "/path/to/my/samples/"
sample samps, "120", 1
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:
Filtering Sample Packs · 3
samps = "/path/to/my/samples/"
sample samps, "120", "A#"
Finally, we’re still free to add our usual opts to the call to sample:
Filtering Sample Packs · 4
samps = "/path/to/my/samples/"
sample samps, "120", "Bb", 1, lpf:70, amp:2
Sources
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 sample 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 .flac, .aif, .aiff,
.wav, .wave, .mp3 files contained within the directories.
For example, take a look at the following code:
Sources
samps = "/path/to/my/samples/"
samps2 = "/path/to/my/samples2/"
path = "/path/to/my/samples3/foo.wav"
sample samps, samps2, path, 0
Here, we’re combining the contents of the samples within two directories
and adding a specific sample. If "/path/to/my/samples/" contained 3
samples and "/path/to/my/samples2/" 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:
Sources · 2
samps = "/path/to/nested/samples/**"
sample samps, 0
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.
Filters
Once you have a list of candidates you may use the following filtering
types to further reduce the selection:
"foo" Strings will filter on substring occurrence within file name (minus directory path and extension).
/fo[oO]/ Regular Expressions will filter on pattern matching of file name (minus directory path and extension).
:foo - Keywords will filter candidates on whether the keyword is a direct match of the filename (minus directory path and extension).
lambda{|a| ... } - 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).
1 - 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 "foo" and play the first matching sample at half
speed:
Filters
sample "/path/to/samples", "foo", rate:0.5
See the help for sample for many detailed usage examples. Note that
the ordering of the filters is honoured.
Composites
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 sample are semantically equivalent:
Composites
sample "/path/to/dir", "100", "C#"
sample ["/path/to/dir", "100", "C#"]
sample "/path/to/dir", ["100", "C#"]
sample ["/path/to/dir", ["100", ["C#"]]]
Wrapping Up
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.
Sonic Pi
Lowering the many barriers of entry for creative experiences with code.