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:
/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 "/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 "/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?
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:
sample "/path/to/my/samples/", 0
We can even make a shortcut to our directory path using a variable:
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:
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.
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:
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:
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:
samps = "/path/to/my/samples/"
sample samps, "120", "A#"
Finally, we’re still free to add our usual opts to the call to sample
:
samps = "/path/to/my/samples/"
sample samps, "120", "Bb", 1, lpf: 70, amp: 2
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 sampleThe 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
files contained within the directories.
For example, take a look at the following code:
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:
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.
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:
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.
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:
sample "/path/to/dir", "100", "C#"
sample ["/path/to/dir", "100", "C#"]
sample "/path/to/dir", ["100", "C#"]
sample ["/path/to/dir", ["100", ["C#"]]]
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.