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))
The => 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.