Getting started
We can write and run code interactively in this worksheet.
Feel free to edit any of the code you see - you can always refresh the page to get the original worksheet back.
Links:
This is how we write a basic pattern in Strudel.
sound
is a function that tells strudel we want to play a sound- Inside the speech marks we write the names of the sounds we want to play (more on this later)
sound("bd hh sd oh")
- You play the sound by pressing the play button or hitting
ctrl
andenter
- You can stop the sound by hitting
ctrl
and.
- The sounds have to load so you might not hear them first time round
What happens if we put more sounds in the pattern?
sound("bd hh sd oh hh bd oh sd")
Even more?
sound("bd hh sd oh hh bd oh sd hh sn oh bd")
The pattern becomes much faster and denser - this is because Strudel has a consistent ‘cycle’ of time running in the background. Any sounds in the pattern will run within that cycle, and Strudel will try to space them evenly in time. (Unless we tell it not to… we’ll learn how to do that later).
This means that the pulse of the pattern is dictated by the number of elements
sound("hh bd sd")
vs.
sound("hh bd hh cp hh bd hh")
Later on when we learn how to play multiple patterns at once this gives us some opportunities to play with rhythm.
Now try writing your own pattern with some of these other sounds:
insect wind jazz metal east crow casio space numbers
Mininotation
The green writing inside the speech marks has its own set of rules. We call this the ‘mininotation’ and it’s the core of how Strudel generates patterns.
We can speed things up with *
sound("hh")
sound("hh*4")
sound("hh*32")
Or slow them down with /
- the snare only sounds every other cycle:
sound("bd sd/2")
We can choose different sounds from a set using :
. The computer starts counting at 0
, so casio:0
is the same as casio
.
sound("casio casio casio casio")
sound("casio casio:1 casio casio:5")
We can add a rest using ~
sound("casio casio:1 ~ casio:5")
We can create more variation with sub-sequences.
We do this by breaking our steps down into mini patterns using []
sound("bd sd bd hh")
sound("bd sd [bd cp] [hh oh]")
Let’s have a look at how that looks visually:
sound("bd sd [bd cp] [hh oh]")
You can nest sequences within sequences for dense patterns
sound("[[bd [bd bd bd bd]] bd sd] [bd cp]")
We can also schedule events across multiple cycles using <>
sound("casio <[hh cp] casio:1> ~ casio:5")
Let’s add some easy randomness using ?
.
Putting ?
after a sound means there is a 50% probability that the sound will play.
sound("hh*8?")
Chaining functions (and making it sound more interesting)
Let’s learn about chaining functions together. This is how we start to make more complex patterns.
There’s another way of selecting samples, using a function called n
- this gives us a bit more flexibility in writing patterns.
sound("casio casio:1 casio casio:5")
We connect the functions together using .
. The n
function also uses the mininotation to create a pattern of numbers
n("0 1 0 5").sound("casio")
This saves us a bit of typing, and makes things easier to edit as we go.
(This next part is a bit confusing, don’t worry too much if it doesn’t make sense right now)
n("0 1 0 5").sound("casio")
We can chain functions together in any order, however, because Strudel always takes the rhythm from the first pattern, we need to be careful how we do this:
sound("casio").n("0 1 0 5")
So far we’ve been looking at choosing different sounds from the folder with n
.
We can also choose different pitches with note
.
This works in the same way as n
:
note("0 1 0 5").sound("casio")
Or we can combine the two:
note("0 1 0 5").sound("casio").n("<0 2>")
And we can make use of all the mininotation we learned above in the note
or n
pattern
n("0 1*3 <0 [~ 7]> 5").sound("casio <space jazz>")
Strudel does its best to map the two patterns together, based on when each event in the pattern starts.
Effects
Ok, let’s try adding some effects.
sound("casio <[hh cp] casio:1> ~ casio:5")
The vowel
effect is a kind of filter.
Notice again that we chain functions together using .
sound("casio <[hh cp] casio:1> ~ casio:5").vowel("a")
We can use mini-notation to create a pattern in the effect:
sound("casio <[hh cp] casio:1> ~ casio:5").vowel("<a e>")
sound("casio <[hh cp] casio:1> ~ casio:5").vowel("a e i")
Most effects take a pattern of numbers. pan
moves the signal left and right:
sound("casio <[hh cp] casio:1> ~ casio:5").pan("0 1")
room
adds reverb:
sound("casio <[hh cp] casio:1> ~ casio:5").room("<0 0 1 2>")
We can chain effects together:
sound("casio <[hh cp] casio:1> ~ casio:5").room("<0 0 1 2>").pan("0 1").vowel("<a o p>")
Stacking patterns
We can play multiple patterns at the same time if we start each new line with $:
Let’s start with our drum pattern:
sound("bd hh*2 bd <~ sd>")
And our synth line from above:
sound("casio <[hh cp] casio:1> ~ casio:5").room("<0 0 1 2>").pan("0 1").vowel("<a o p>")
And play them both at the same time:
$: sound("bd hh*2 bd <~ sd>") $: sound("casio <[hh cp] casio:1> ~ casio:5").room("<0 0 1 2>").pan("0 1").vowel("<a o p>")
If we separate out the lines we can add comments in using //
- the computer will ignore comments.
This can help us keep track of what’s going on.
If our code is getting a bit messy we can break it out onto separate lines within the stack too.
//drums: $: sound("bd hh*2 bd <~ sd>") //synth: $: sound("casio <[hh cp] casio:1> ~ casio:5") .room("<0 0 1 2>") .pan("0 1") .vowel("<a o p>")
Your samples
We need to load your samples in from the internet using the samples
function.
Then we can call them in the same way as usual.
The samples are in four banks:
per
(23 sounds) - these are percussive samplestone
(28 sounds) - these are tonal samplestext
(33 sounds) - these are textural sampleslong
(19 sounds) - these are longer samples (we handle these slightly differently - we’ll come to that later)
Remember if you re-load the worksheet you may have to run this chunk again to load the samples back in.
samples('github:heavy-lifting/soc-ex') n("1 5 8 23").sound("tone")
Try writing some patterns using the other sound banks
//per
//text
//long
Pattern transformation
Ok, so things are starting to sound a bit better. Let’s explore some ways of creating more complex patterns.
Let’s start with a simple drum beat:
n("9 10*2 9 [~ 2]").sound("per")
We can slow that down with slow()
:
n("9 10*2 9 [~ 2]").sound("per").slow(2)
Or speed it up with fast()
:
n("9 10*2 9 [~ 2]").sound("per").fast(2)
You can change the number in brackets after fast
or slow
to change how much we speed up or slow down by.
You can even pattern that number using the mininotation!
n("9 10*2 9 [~ 2]").sound("per").slow("1 2")
It might seem like something a bit weird is happening here - basically Strudel is switching between two versions of the pattern, the normal version and the slow version. It can be a bit hard to get your head round exactly what’s happening sometimes, but as long as you’re happy with the sound I don’t think it matters too much.
We can incorporate fast and slow into our stack:
//drums: $: n("9 10*2 9 [~ 2]").sound("per").fast("1 1 2") //synth: $: n("0 <[1 2] 3> ~ 4") .sound("tone") .room("<0 0 1 2>") .pan("0 1") .vowel("<a o p>") .slow("1 1 2 4")
Let’s think about some other kinds of patterns - rev()
lets us play a pattern backwards:
n("0 <[1 2] 3> ~ 4").sound("tone")
n("0 <[1 2] 3> ~ 4").sound("tone").rev()
Or we can use every()
to apply a transformation every so many cycles:
Notice here we are combining the functions in a different way. The every()
function takes two arguments, which are separated by a comma. The first one is the number of cycles to trigger the transformation, the second one is the transformation.
n("0 <[1 2] 3> ~ 4").sound("tone").every(4, rev())
We can pass any function as the second argument to the every()
function:
n("0 <[6 7] 8> ~ 10").sound("tone").every(4, slow(2))
Or we can do something similar with sometimes
which will apply a transformation with 50% likelihood:
n("9 10*2 9 [~ 2]").sound("per").sometimes(rev)
This can sound quite confusing as it’s randomly jumping between the forwards and backwards versions of the pattern. We can use someCycles()
instead to choose once per cycle.
n("9 10*2 9 [~ 2]").sound("per").someCycles(rev)
Iter
starts the pattern at a different point each cycle, giving us a rotational pattern:
n("1 2 3 4").sound("numbers").iter(4).slow(2)
This can lead to some nice variation across time:
//drums: $: n("9 10*2 9 [~ 2]").sound("per").every(3, rev) //synth: $: n("0 <[6 7] 8> ~ 10") .sound("tone") .room("<0 0 1 2>") .pan("0 1") .vowel("<a o p>") .iter(4)
Finally, we can apply a transformation in one speaker only using jux
:
(although for some reason this isn’t working as expected…)
n("9 10*2 <7 9> [~ 2]").sound("per").jux(rev)
Long samples
If we play a long sample it will be triggered every cycle and so we will end up with an overlapping effect. This can sound good, but it’s not always what we’re looking for. Here are a few functions to help you work with longer sounds.
loopAt
will loop the sample at a set number of cycles
sound("long").loopAt("1")
We can of course pattern that too:
n("3 4 5 6").sound("long").loopAt("<1 2>")
slice
cuts the sample into equal slices and then lets us pattern how they are played back:
n("3").sound("long").slice(8, "5 3 1 [~ 2*2]").iter(4)
Bonus content:
Polyrhythm/polymeter
Health warning: I may have got these mixed up…
We can use stack
or [,]
to create polyrhythmic structures - in this case patterns of three over four
$: n("10 11 14 17").sound("per") $: n("12 18 22").sound("tone")
sound("[per per per, text text]")
We can use {,}
to create polymetrical structures - the elements of the pattern after the comma will follow the same pulse as those before the comma
This can give us a sense of rotation over time.
sound("{per per:6 [~ per:14] per:27, text:17 ~ ~ ~ tone:29}")
Euclidian rhythm
We can use brackets after the name of a sound to define a Euclidian rhythm - Strudel will try to space the first number of events over the second number of steps:
sound("per:18(3,8)")
We can rotate the pattern so it starts on a different step by using a third argument:
sound("tone:17(3,8,<0 2>)")
Try experimenting with different values.