Link search Menu Expand Document

S2E3 Long Distance

maps: S2E3

nb. descriptive hyperlinks connect to timestamped moments in the video above

within the episode, Trent executes crow commands via druid on the left side of the screen while taking longform notes in a text editor on the right side of the screen.

where things get “musical”

this episode begins with a function-centric adaptation of ASL, a core feature of crow which was demonstrated in the previous episode. if this doesn’t sound familiar please feel free to hop back in time – the second half of the writeup is heavily focused on ASL (making shapes and onward).

review: clocks

crow 3.0 repackaged the norns clock system for tempo-synced execution of events. it’s super powerful + flexible, but there can be a bit of a rough onboarding if you’re unfamiliar with it.

essentially, there is a wall clock from which we can either sync events to occur (at different rates), or which we can sleep to delay the processing of an event. when we spawn a function that’s aware of the clock, we call it a “coroutine”. we can use both methods (sync and sleep) simultaneously, though the general approach is:

  • for musical-time purposes use clock.sync, which accepts a beat division to which it should sync
  • for timed delays use clock.sleep, which accepts a seconds-based duration to hold before execution

creating a musical-time clock

we can create a clock-synced coroutine on a single line of code. we’ll begin by aliasing the required as cr, then we’ll build a synced function (output 1 = v/8 from dorian scale):

> cr =
> dorian = sequins{0,2,3,5,7,9,10}:step(3)
> cr(function() while true do clock.sync(1) output[1].volts = dorian()/12 end end)

basic anatomy of the cr function:

  • while true do: our events will loop continuously until the canceled coroutine is canceled
  • clock.sync(1): sync to the next-nearest “1” beat division, then execute the code that follows

clock.sync is an important component of the coroutine – without it, crow will try to run an infinite number of events instantaneously, which will time out the CPU.


  • clock.cleanup() will clear your currently-running clock coroutines.
  • clock.tempo will allow you to set the global tempo

funnel (fnl): making changes without touching knobs

to start the educational part of the episode, Trent introduces fnl, a custom function which borrows the concept of ASL and applies it to function generation.

in the first example, Trent uses fnl to to increase the clock tempo from 120 to 480 over a period of 31.4 seconds:

> fnl( function(tempo) clock.tempo = tempo end, 120, { to(480, 31.4) } )

basic anatomy of the fnl function

  • a function which we want to feed slewed values
  • our starting value
  • a table which contains tables (nested tables!!) of destination values and the duration we want the journey to take (in seconds)
    • in the example, Trent uses ASL’s to construct to represent these values, because of how conceptually similar fnl is to ASL – you can just as easily swap to(480, 31.4) with {"TO", 480, 31.4}
  • frame rate (optional, default is 15fps)

curious about the guts? here’s Trent’s overview and here’s the source code.

over i2c


next, Trent walks us through addressing w/synth parameters using fnl and i2c:

> fnl( ii.wsyn.ramp, 0, { to(5,5), to(0,0.5) }, 20 )

this will affect the shape of the w/synth by adding 5V to the ramp parameter incrementally over 5 seconds, then back down to 0 in a half-second. Trent specifies 20fps, to help round out the voltage changes.

let’s assign it to a function which can be called more easily:

> function rr() fnl( ii.wsyn.ramp, 0, { to(5,5), to(0,0.5) }, 20 ) end

now we can just execute rr() in the repl to get our ‘rising ramp’ on command.

(the definition of mapcore expands)


let’s target another w/synth parameter: the FM ratio.

> fnl( ii.wsyn.fm_ratio, 1, { to(11,7), to(3/2,2) } )

this will affect the FM ratio by sliding it up to 11/1 over 11 seconds, then back down to 3/2 in 2 seconds. but there’s also some fun to be had in changing the frame rate of this fnl, for example:

> fnl( ii.wsyn.fm_ratio, 1, { to(11,7), to(3/2,2) }, 7 )

…produces quite a different effect than the default 15fps!

to make it easier to pass the frame rate into the fnl, let’s assign it to a function which can accept frame rate as an argument:

> function rf(fps) fnl( ii.wsyn.fm_ratio, 1, { to(11,7), to(3/2,2) }, fps ) end

now, we can execute rf(6) to generate our FM modulator at 6fps!

running in tandem

a performance gesture emerges:

> rf(6)
> rr()
> rr()
> rf(3)
> rr()
> rr()
> rr()
> rr()
> rf(4)
> rr()


Trent navigates around the buffer.

fnl can be used to modulate functions which require more than one argument by nesting the target function inside of another function with the single argument we wish to funnel.

eg. if we wanted to advance a tiny playback window through a frozen delay line:

  { to(1,32) },

this is a bit of a mouthful, but essentially:

  • we want to modulate the numerator of ii.wdel.cut(num,denum) to advance the playback window start point
  • we need to re-define the length of the delay line after making this change, so our end point moves along with our start point
  • so, we wrap both into a function!

formatted as a single line for easy execution in druid:

> fnl( function(pos) ii.wdel.cut(pos*255,255); ii.wdel.length(1,8) end, 0, { to(1,32) }, 2 )

after Trent uses this mechanism to modulate the loop window while recording, they open up the loop length to reveal the collage and execute a gorgeous performance.

focusing on the destination

Trent’s holy grail of performance.

fnl / funnels allow the performer to define an end-point and describe the journey without needing to manually execute the journey themselves. this is why funnels are described as ‘a slope language for functions’ – not just LFOs which automate a looping gesture, but a realtime automation brewed up in the moment to arrive at a desired future.

rather than “holding a knob and turning it down very slowly”, try using a funnel!