Samples

Samples are the most common way to make sound with tidal and strudel. A sample is a (commonly short) piece of audio that is used as a basis for sound generation, undergoing various transformations. Music that is based on samples can be thought of as a collage of sound. Read more about Sampling

Strudel allows loading samples in the form of audio files of various formats (wav, mp3, ogg) from any publicly available URL.

Default Samples

By default, strudel comes with a built-in “sample map”, providing a solid base to play with.

s("bd sd [~ bd] sd,hh*16, misc")

Here, we are using the s function to play back different default samples (bd, sd, hh and misc) to get a drum beat.

For drum sounds, strudel uses the comprehensive tidal-drum-machines library, with the following naming convention:

DrumAbbreviation
Bass drum, Kick drumbd
Snare drumsd
Rimshotrim
Clapcp
Closed hi-hathh
Open hi-hatoh
Crashcr
Riderd
Shakers (and maracas, cabasas, etc)sh
High tomht
Medium tommt
Low tomlt
Cowbellcb
Tambourinetb
Other percussionsperc
Miscellaneous samplesmisc
Effectsfx

Furthermore, strudel also loads instrument samples from VCSL by default.

To see which sample names are available, open the sounds tab in the REPL.

Note that only the sample maps (mapping names to URLs) are loaded initially, while the audio samples themselves are not loaded until they are actually played. This behaviour of loading things only when they are needed is also called lazy loading. While it saves resources, it can also lead to sounds not being audible the first time they are triggered, because the sound is still loading. This might be fixed in the future

Sound Banks

If we open the sounds tab and then drum machines, we can see that the drum samples are all prefixed with drum machine names: RolandTR808_bd, RolandTR808_sd, RolandTR808_hh etc..

We could use them like this:

s("RolandTR808_bd RolandTR808_sd,RolandTR808_hh*16")

… but thats obviously a bit much to write. Using the bank function, we can shorten this to:

s("bd sd bd sd,hh*16").bank("RolandTR808")

You could even pattern the bank to switch between different drum machines:

s("bd sd bd sd,hh*16").bank("RolandTR808 RolandTR909")

Behind the scenes, bank will just prepend the drum machine name to the sample name with _ to get the full name. This of course only works because the name after _ (bd, sd etc..) is standardized. Also note that some banks won’t have samples for all sounds!

Selecting Sounds

If we open the sounds tab again, followed by tab drum machines, there is also a number behind each name, indicating how many individual samples are available. For example RolandTR909_hh(4) means there are 4 samples of a TR909 hihat available. By default, s will play the first sample, but we can select the other ones using n, starting from 0:

s("hh*8").bank("RolandTR909").n("0 1 2 3")

Numbers that are too high will just wrap around to the beginning

s("hh*8").bank("RolandTR909").n("0 1 2 3 4 5 6 7")

Here, 0-3 will play the same sounds as 4-7, because RolandTR909_hh only has 4 sounds.

Selecting sounds also works inside the mini notation, using ”:” like this:

s("bd*4,hh:0 hh:1 hh:2 hh:3 hh:4 hh:5 hh:6 hh:7")
.bank("RolandTR909")

Loading Custom Samples

You can load your own sample map using the samples function. In this example we create a map using sounds from the default sample map:

samples({
  bd: 'bd/BT0AADA.wav',
  sd: 'sd/rytm-01-classic.wav',
  hh: 'hh27/000_hh27closedhh.wav',
}, 'https://raw.githubusercontent.com/tidalcycles/Dirt-Samples/master/');
s("bd sd bd sd,hh*16")

When you load your own samples, you can choose the names that you will then refer to in your pattern string inside the s function. Compare with this example which uses the same samples, but with different names.

samples({
  bassdrum: 'bd/BT0AADA.wav',
  snaredrum: 'sd/rytm-01-classic.wav',
  hihat: 'hh27/000_hh27closedhh.wav',
}, 'https://raw.githubusercontent.com/tidalcycles/Dirt-Samples/master/');
s("bassdrum snaredrum bassdrum snaredrum, hihat*16")

Here we have changed the “map” to include longer sample names.

The samples function

The samples function has two arguments:

  • A JavaScript object that maps sound names to audio file paths.
  • A base URL that comes before each path describing where the sample folder can be found online.
    • Make sure your base URL ends with a slash, while your sample paths do not begin with one!

To see how this looks in practice, compare the DirtSamples GitHub repo with the previous sample map example.

Because GitHub is a popular place for uploading open source samples, it has its own shortcut:

samples({
  bd: 'bd/BT0AADA.wav',
  sd: 'sd/rytm-01-classic.wav',
  hh: 'hh27/000_hh27closedhh.wav',
}, 'github:tidalcycles/dirt-samples');
s("bd sd bd sd,hh*16")

The format is github:user/repo/branch/.

Let’s see another example, this time based on the following GitHub repo: https://github.com/jarmitage/jarmitage.github.io. We can see there are some guitar samples inside the /samples folder, so let’s try to load them:

samples({
  g0: 'samples/guitar/guitar_0.wav',
  g1: 'samples/guitar/guitar_1.wav',
  g2: 'samples/guitar/guitar_2.wav',
  g3: 'samples/guitar/guitar_3.wav',
  g4: 'samples/guitar/guitar_4.wav'
}, 'github:jarmitage/jarmitage.github.io/master/');
s("<g0 g1 g2 g3 g4>/2")

Multiple Samples per Sound

It is also possible, to declare multiple files for one sound, using the array notation:

samples({
  bd: ['bd/BT0AADA.wav','bd/BT0AAD0.wav'],
  sd: ['sd/rytm-01-classic.wav','sd/rytm-00-hard.wav'],
  hh: ['hh27/000_hh27closedhh.wav','hh/000_hh3closedhh.wav'],
}, 'github:tidalcycles/dirt-samples');
s("bd:0 bd:1,~ <sd:0 sd:1> ~ sd:0,[hh:0 hh:1]*4")

The :0 :1 etc. are the indices of the array. The sample number can also be set using n:

samples({
  bd: ['bd/BT0AADA.wav','bd/BT0AAD0.wav'],
  sd: ['sd/rytm-01-classic.wav','sd/rytm-00-hard.wav'],
  hh: ['hh27/000_hh27closedhh.wav','hh/000_hh3closedhh.wav'],
}, 'github:tidalcycles/dirt-samples');
s("bd bd,~ sd ~ sd,hh*8").n("<0 1>")

In that case, we might load our guitar sample map a different way:

samples({
  guitar: [
    'samples/guitar/guitar_0.wav', 
    'samples/guitar/guitar_1.wav', 
    'samples/guitar/guitar_2.wav', 
    'samples/guitar/guitar_3.wav', 
    'samples/guitar/guitar_4.wav'
  ]
}, 'github:jarmitage/jarmitage.github.io/master/');
s("<guitar:0 guitar:1 guitar:2 guitar:3 guitar:4>*2")

And as above, we can choose the sample number using n for even more flexibility:

samples({
  guitar: [
    'samples/guitar/guitar_0.wav', 
    'samples/guitar/guitar_1.wav', 
    'samples/guitar/guitar_2.wav', 
    'samples/guitar/guitar_3.wav', 
    'samples/guitar/guitar_4.wav'
  ]
}, 'github:jarmitage/jarmitage.github.io/master/');
n("<0 1 2 3 4>*2").s("guitar")

Pitched Sounds

For pitched sounds, you can use note, just like with synths:

samples({
  'gtr': 'gtr/0001_cleanC.wav',
}, 'github:tidalcycles/dirt-samples');
note("g3 [bb3 c4] <g4 f4 eb4 f3>@2").s('gtr').gain(.5)

Here, the guitar samples will overlap, because they always play till the end. If we want them to behave more like a synth, we can add clip(1):

samples({
  'gtr': 'gtr/0001_cleanC.wav',
}, 'github:tidalcycles/dirt-samples');
note("g3 [bb3 c4] <g4 f4 eb4 f3>@2").s('gtr').clip(1)
  .gain(.5)

Base Pitch

If we have 2 samples with different base pitches, we can make them in tune by specifying the pitch like this:

samples({
  'gtr': 'gtr/0001_cleanC.wav',
  'moog': { 'g3': 'moog/005_Mighty%20Moog%20G3.wav' },
}, 'github:tidalcycles/dirt-samples');
note("g3 [bb3 c4] <g4 f4 eb4 f3>@2").s("gtr,moog").clip(1)
  .gain(.5)

If a sample has no pitch set, c3 is the default.

We can also declare different samples for different regions of the keyboard:

samples({
  'moog': {
    'g2': 'moog/004_Mighty%20Moog%20G2.wav',
    'g3': 'moog/005_Mighty%20Moog%20G3.wav',
    'g4': 'moog/006_Mighty%20Moog%20G4.wav',
  }}, 'github:tidalcycles/dirt-samples');
note("g2!2 <bb2 c3>!2, <c4@3 [<eb4 bb3> g4 f4]>")
  .s('moog').clip(1)
  .gain(.5).cpm(60)

The sampler will always pick the closest matching sample for the current note!

Shabda

If you don’t want to select samples by hand, there is also the wonderful tool called shabda. With it, you can enter any sample name(s) to query from freesound.org. Example:

samples('shabda:bass:4,hihat:4,rimshot:2')
stack(
  n("0 1 2 3 0 1 2 3").s('bass'),
  n("0 1*2 2 3*2").s('hihat'),
  n("~ 0 ~ 1 ~ 0 0 1").s('rimshot')
).clip(1)

You can also generate artificial voice samples with any text, in multiple languages. Note that the language code and the gender parameters are optional and default to en-GB and f

samples('shabda/speech:the_drum,forever')
samples('shabda/speech/fr-FR/m:magnifique')
stack(
  s("the_drum*2").chop(16).speed(rand.range(0.85,1.1)),
  s("forever magnifique").slow(4).late(0.125)
)

Sampler Effects

Sampler effects are functions that can be used to change the behaviour of sample playback.

begin

a pattern of numbers from 0 to 1. Skips the beginning of each sample, e.g. 0.25 to cut off the first quarter from each sample.

  • amount (number|Pattern): between 0 and 1, where 1 is the length of the sample
samples({ rave: 'rave/AREUREADY.wav' }, 'github:tidalcycles/dirt-samples')
s("rave").begin("<0 .25 .5 .75>").fast(2)

end

The same as .begin, but cuts off the end off each sample.

  • length (number|Pattern): 1 = whole sample, .5 = half sample, .25 = quarter sample etc..
s("bd*2,oh*4").end("<.1 .2 .5 1>").fast(2)

loop

Loops the sample. Note that the tempo of the loop is not synced with the cycle tempo. To change the loop region, use loopBegin / loopEnd.

  • on (number|Pattern): If 1, the sample is looped
s("casio").loop(1)

loopBegin

Synonyms: loopb

Begin to loop at a specific point in the sample (inbetween begin and end). Note that the loop point must be inbetween begin and end, and before loopEnd! Note: Samples starting with wt_ will automatically loop! (wt = wavetable)

  • time (number|Pattern): between 0 and 1, where 1 is the length of the sample
s("space").loop(1)
.loopBegin("<0 .125 .25>").scope()

loopEnd

Synonyms: loope

End the looping section at a specific point in the sample (inbetween begin and end). Note that the loop point must be inbetween begin and end, and after loopBegin!

  • time (number|Pattern): between 0 and 1, where 1 is the length of the sample
s("space").loop(1)
.loopEnd("<1 .75 .5 .25>").scope()

cut

In the style of classic drum-machines, cut will stop a playing sample as soon as another samples with in same cutgroup is to be played. An example would be an open hi-hat followed by a closed one, essentially muting the open.

  • group (number|Pattern): cut group number
s("[oh hh]*4").cut(1)

clip

Multiplies the duration with the given number. Also cuts samples off at the end if they exceed the duration. In tidal, this would be done with legato, which has a complicated history in strudel. For now, if you're coming from tidal, just think clip = legato.

  • factor (number|Pattern): = 0
note("c a f e").s("piano").clip("<.5 1 2>")

loopAt

Makes the sample fit the given number of cycles by changing the speed.

    samples({ rhodes: 'https://cdn.freesound.org/previews/132/132051_316502-lq.mp3' })
    s("rhodes").loopAt(2)

    fit

    Makes the sample fit its event duration. Good for rhythmical loops like drum breaks. Similar to loopAt.

      samples({ rhodes: 'https://cdn.freesound.org/previews/132/132051_316502-lq.mp3' })
      s("rhodes/2").fit()

      chop

      Cuts each sample into the given number of parts, allowing you to explore a technique known as 'granular synthesis'. It turns a pattern of samples into a pattern of parts of samples.

        samples({ rhodes: 'https://cdn.freesound.org/previews/132/132051_316502-lq.mp3' })
        s("rhodes")
         .chop(4)
         .rev() // reverse order of chops
         .loopAt(2) // fit sample into 2 cycles

        striate

        Cuts each sample into the given number of parts, triggering progressive portions of each sample at each loop.

          s("numbers:0 numbers:1 numbers:2").striate(6).slow(3)

          slice

          Chops samples into the given number of slices, triggering those slices with a given pattern of slice numbers. Instead of a number, it also accepts a list of numbers from 0 to 1 to slice at specific points.

            samples('github:tidalcycles/dirt-samples')
            s("breaks165").slice(8, "0 1 <2 2*2> 3 [4 0] 5 6 7".every(3, rev)).slow(0.75)
            samples('github:tidalcycles/dirt-samples')
            s("breaks125").fit().slice([0,.25,.5,.75], "0 1 1 <2 3>")

            splice

            Works the same as slice, but changes the playback speed of each slice to match the duration of its step.

              samples('github:tidalcycles/dirt-samples')
              s("breaks165")
              .splice(8,  "0 1 [2 3 0]@2 3 0@2 7")

              speed

              Changes the speed of sample playback, i.e. a cheap way of changing pitch.

              • speed (number|Pattern): inf to inf, negative numbers play the sample backwards.
              s("bd*6").speed("1 2 4 1 -2 -4")
              speed("1 1.5*2 [2 1.1]").s("piano").clip(1)

              After samples, let’s see what Synths afford us.