Functions and Patterns
Functions are a bit of a heavy topic to start the day with, sorry.
Itâs a fundamental topic though - not covered too well in docs. Hopefully this will help clear things up. See how you go!
Letâs talk about functions!
In strudel, almost everything is either a pattern, or a function.
Whatâs the difference between a pattern and a function?
- A pattern is a kind of behaviour - values that change over time.
- A function is something that takes patterns (as âinputâ), and creates a new pattern (as âoutputâ)
So, you can think of a function as an incomplete pattern - you need to give it one or more patterns before it becomes a new pattern
These are examples of functions:
sound fast rev iter jux
This is a pattern that demonstrates all the above functions being used together:
sound("bd hh sd hh").iter(4) .fast("<2 3>").jux(rev)
In the above, sound
is a function, and it becomes a pattern when we give it
"bd hh sd hh"
. We then turn it into a different pattern by feeding it into the
iter(4)
function. Then it gets turned into a different pattern again when we
feed it into the fast("<2 3>")
function, and finds its final form that we
hear, after being fed it into the jux(rev)
function.
Different functions require different numbers of inputs.
For example, the fast
function needs two patterns as inputs - the pattern that is âfed intoâ it, and another pattern that says how much to speed it up by.
The sound
function just needs one input - a pattern of words (e.g. âbd hh sd hhâ), to be used as a pattern of sounds.
Functions like jux
are extra weird because they take another function as an input!
For example:
.jux(rev)
.jux(iter(4))
.jux(press)
.jux(hurry(2))
So in this example
sound("bd hh sd hh").fast(2)
We can take that fast(2)
function, and give it to a jux
function:
sound("bd hh sd hh").jux(fast(2))
Now the sound("bd hh sd hh")
pattern is being fed into jux
, which in turn
feeds it into fast(2)
(but only on one speaker or earphone - thatâs what âjuxâ
does).
It might be useful to know that there are two ways of giving inputs to functions. One is like this:
sound("bd hh sd hh").fast("<2 3>")
and the other is like this:
fast("<2 3>", sound("bd hh sd hh"))
Generally in Strudel, itâs easier to write it the first way - writing the pattern you want to operate on (sound("bd hh sd hh")
), and then writing .
followed by the function you want to apply to it. (this is called âmethod chainingâ).
So we generally write things like this:
sound("bd hh sd hh").iter(4) .fast("<2 3>").jux(rev)
⊠but can also write things like this:
jux(rev, fast("<2 3>", iter(4, sound("bd hh sd hh"))))
One reason we generally donât do it the second way is that it gets difficult to keep track of all the brackets !
Control patterns
Letâs talk about a particular kind of pattern - control patterns. They are used to pattern different aspects of sound.
Here is the complete list of functions for making control patterns:
accelerate activeLabel amp analyze anchor att attack bandf bandq bank bbexpr
bbst begin binshift bp bpa bpattack bpd bpdecay bpe bpenv bpf bpq bpr bprelease
bps bpsustain byteBeatExpression byteBeatStartTime ccn ccv ch channel channels
chord clip coarse color comb compressor compressorAttack compressorKnee
compressorRatio compressorRelease cps crush ctf ctlNum ctranspose curve cut
cutoff dec decay degree delay delayfb delayfeedback delaysync delayt delaytime
deltaSlide density det detune dfb dict dictionary dist distort djf drive dry dt
duck duckatt duckattack duckdepth duckorbit dur duration end enhance expression
fadeInTime fadeOutTime fadeTime fanchor fft fm fmattack fmdecay fmenv fmh fmi
fmrelease fmsustain fmvelocity fmwave frameRate frames freeze freq fshift
fshiftnote fshiftphase ftype gain gat gate harmonic hbrick hcutoff hold hours hp
hpa hpattack hpd hpdecay hpe hpenv hpf hpq hpr hprelease hps hpsustain
hresonance imag ir iresponse kcutoff krush label lbrick legato leslie lfo lock
loop loopBegin loopEnd loopb loope lp lpa lpattack lpd lpdecay lpe lpenv lpf lpq
lpr lprelease lps lpsustain lrate lsize markcss midibend midichan midicmd
midimap midiport miditouch minutes mode mtranspose n noise note nrpnn nrpv nudge
octave octaveR octaves octer octersub octersubsub offset orbit overgain
overshape pan panchor panorient panspan pansplay panwidth partials patt pattack
pcurve pdec pdecay penv ph phasdp phaser phasercenter phaserdepth phaserrate
phasersweep phc phd phs pitchJump pitchJumpTime polyTouch postgain prel prelease
progNum psus psustain pw pwrate pwsweep rate rdim real rel release repeatTime
resonance rfade ring ringdf ringf rlp room roomdim roomfade roomlp roomsize
rsize s scram seconds semitone shape size slide smear songPtr sound source speed
spread squiz src stepsPerOctave stretch sus sustain sustainpedal sysexdata
sysexid sz trem tremdepth tremolo tremolodepth tremolophase tremoloshape
tremoloskew tremolosync tremphase tremshape tremskew tremsync triode tsdelay uid
unison unit v val velocity vib vibmod vibrato vmod voice vowel waveloss xsdelay
zcrush zdelay zmod znoise zrand zzfx
So, sound("bd sd hh cp")
is a control pattern, and so is distort("<0.8 1.1>")
.
One super useful aspect of strudel is that you can feed one control pattern into another one.
For example, you can feed a âsoundâ contrl pattern into a âdistortionâ control pattern like this
sound("bd sd hh cp") .distort("<0 1.1>")
This looks like youâre feeding one pattern into another a function, but really youâre combining two patterns together.
sound("bd sd hh cp") .distort("<0 1.1>") .speed("<1 1.5 2>")
When doing this, the rule of thumb is that the âstructureâ of the outcome is given by the leftmost pattern (weâll talk about how to change this later).
Using =>
arrows to turn patterns into functions, and to combine functions
Although the above works, annoyingly in other cases you canât treat control patterns as functions. For example, this doesnât work:
sound("bd sd hh cp")
.lastOf(3, distort(1.1))
This is because lastOf
(and other functions that take other functions as input
like jux
, every
, sometimes
, etc), will not accept control patterns.
You can fix this by turning the pattern into a function, using =>:
sound("bd sd hh cp") .lastOf(3, x => x.distort(1.1))
=>
arrow works by giving a name to the pattern to be transformed. In the above, we are calling it x
.This is also useful when you want to combine two functions together!
For example, say you want to apply both iter(4)
and fast(2)
to a pattern
using jux()
, so it only happens in one speaker/earphone.
Unfortunately the following wonât work:
sound("bd sd hh cp")
.jux(iter(4).fast(2))
However if we use the magic =>
, it will!
sound("bd sd hh cp") .jux(x => x.iter(4).fast(2))
We can use control patterns in this way too:
sound("bd sd hh cp") .jux(x => x.iter(4).fast(2).speed(2))
It takes a while to get used to =>
, but it is super useful when you get the hang of it.