crow v3

A massive update of quality & quantity! Version 3 brings a great deal of new functionality, while solving some bugs & making the ecosystem more robust.

Updating your firmware is now easier than ever, but first you need to update druid
(hint: pip3 install --upgrade monome-druid).

Once druid is updated, with your crow connected simply type druid firmware and you’ll witness your crow’s evolution. WINDOWS USERS — this command doesn’t work yet so:

manual update instructions are also available (if you have trouble with the druid method).


“a slope language” now runs at audio rate, allowing for sample-accurate timings and oscillation up to 24k.

A new asllib action oscillate is available:

output[n].action = oscillate(freq, amplitude, shape) -- optional: amplitude & shape

Note: n2v, negate and clamp are removed (as they can be replaced with native math operations, or asl constructs). This may break some rare scripts (notably n2v was used frequently in non-asl code. It can be replaced with a divide-by-12).


dyn, short for Dynamic, is useful for naming ASL arguments so that they can be updated by a script, without restarting the ASL.

-- create an lfo with a dynamic height, starting at 3V
output[1]( loop{ to( dyn{height=3}, 1)
               , to( 0, 1)

output[1].dyn.height = 1 -- update height of LFO to 1V
-- note: this will take effect at the next repetition

Dynamic vars can be used anywhere in the ASL, either in a to() call, or as a predicate value (eg. times). Furthermore, asllib functions may take dyn values as variables. For example:

-- an lfo whose speed can be changed at runtime
output[2]( lfo( dyn{speed=0.1} ) )

Mutations can be attached to any dyn which are executed each time the asl accesses the value, for example:

-- ramp lfo, decelerating from 0.1 seconds to infinity
output[1].action =
  loop{ to(5, dyn{time=0.1}:step(0.1)) -- step performs addition / subtraction
      , to(0, 0)
-- the lfo will slow down by 0.1 seconds each time it repeats
output[1].dyn.time = 0.1 -- reset the variable like a normal dyn

Available mutations are step, mul, and wrap. wrap is additionally useful for being able to keep a dyn within bounds when it is modified from other functions or the REPL. See the reference


This new library connects data to “behaviors” which facilitate rapid building of sequencers and arpeggiators.

notes = sequins{0,2,4,6,7,9,11}

print( notes() )    -- each time notes() is called it returns the next value
                    -- 0 then 2 then 4 etc...

Sequins can be nested (effective for creating melodies):

s = sequins             -- save yourself some typing
seq = s{1, 2, s{3, 4}}  -- last note alternates between 3 and 4
print( seq() )          -- 1, 2, 3, 1, 2, 4 ...

Step size can be set on initialization or on the fly:

seq = s{1,2,3,4,5}:step(2) -- seq() --> 1,3,5,2,4,1,...
seq:step(1)                -- seq() --> 1,2,3,4...

Any datatype is allowed, ie functionseq = s{lfo, ar, pulse} or stringseq = s{"hello","goodbye"}.

See the reference for the full range of capabilities.


Public variables are the automatic method for exposing crow variables to a connected USB host device. Public variables are kept synchronized across both devices and may be modified by either device. This system is specifically setup for building remote user interfaces. We’ve updated bowery to use public variables for all the main performance controls.


This new norns script is a basic crow-script loader. When loading a new crow script, any public variables will have a UI element generated to control it. It comes populated with the updated bowery collection, which is open for contributions!

The script is also a demonstration of building a public-aware host. Bowering could be extended in many ways, or act as an example for building your own crow-interactive scripts. Norns support also introduces a few helper functions & conventions for uploading scripts to crow and “freezing” public variables so norns can act as a crow script configurator.

Note: norns: update 210630 / 210701 is required to work with crow 3.0



A new clock system has been implemented which parallels the norns system. This means crow now can have a ton of additional time-based functionality beyond metro using the norns-familiar sync and sleep methods. See the clock reference for the full syntax.

input and output

just intonation support

Two new global functions justvolts and just12 for converting from fractions (ie floats) to their volts, or 12TET representation.
They have the same signature justX(notex, offset). notex can be a single fraction which returns a converted value, or it can be a table of fractions which will return a new table with each element converted. offset is an optional argument which expects a single fraction acting as a just-transposition to notex. This supports a method for handling key-modulation while maintaining internal harmony.

justvolts(1/1) -> 0.0
justvolts(3/2) -> 0.5849625
justvolts(1/4) -> -2.0 -- notes can be outside the 1/1 .. 2/1 range

just12(1/1) -> 0.0
just12(3/2) -> 7.019549
just12(3/2, 9/8) -> 9.058649 is 3/2 offset by 9/8

just12{1/1, 3/2, 7/4} -> {0.0, 7.019549, 9.688259} -- table call returns a new table

Output scale is used like so:

output[1].scale({1/1,3/2,11/8}, 'ji') -- a third optional arg sets volts/octave, so you can still use 1v2/octave if you want

Note the use of ji where you normally put the temperament. This calls just12 under the hood, but is slightly better than doing it manually because it enforces some default behavior.

Input scale is similar to output, but uses the mode function call (like normal input.scale quantizers):

input[1].mode('scale', {1/1, 9/8, 5/4}, 'ji') -- last arg (v/8 scaling) is optional
                                              -- when omitted, defaults to 1V/octave

input frequency detection mode

The new input mode freq is short for frequency tracker. It functions the same as stream & volume modes, but streams values representing the frequency of an attached oscillator.

input[1].freq = function(hz)
  -- called each interval defined in the .mode call below

input[1].mode("freq", 0.1) -- stream the frequency 10x per second

Also available is a new function hztovolts which is based on the same absolute freq range as just-friends, w/syn, etc. This is especially handy if you want to track a frequency input and control a module over ii, or even to take an input frequency and send it to an output as a voltage (so it can be doubled by another oscillator).


  • output.volts query now returns shaped and scaled value, as seen at the jack
  • output scale syntax now allows output[n].scale = {note list}


The calibration system has been overhauled, giving direct access to the scale and offset values per input and output channel along with the ability to save the current configuration to flash.

While recalibration isn’t something you should need to do, if you’re interested check out the reference

assorted improvements and fixes

  • userscript storage increased to 16k (doubled!)
  • massive RAM optimization (this means more clocks, tables, etc at runtime)
  • ii follower mode on Teletype (requires TT v4.0)


Alongside this new firmware release we have some new features in druid:

  • pydfu-based crow firmware updater, auto-fetches from git release @tehn
  • websocket server at port 6666 for sending commands directly to crow (ie, line execution from editor) @csboling
  • command autocomplete + input captures @csboling
  • filename autocompletion @csboling

NOTE: windows connection is temporarily broken, so please don’t update yet if you’re running windows.

websocket commands

You can now send commands to crow via druid remotely, such as through your text editor with some additional configuration. Check out the reference, but here’s a snippet for attaching a hotkey to vim (requires you first install websocat):

map <C-\> :silent .w !websocat ws://localhost:6666 -1<CR>

Max + Max for Live

The Max objects & Max for Live devices have been updated to support the new crow 3.0 features thanks to @dan_derks. A few new changes too:

  • ^^dual now has dropdown to select pulse or ADSR for secondary output event – if ADSR is selected, device will horizontally expand to reveal ADSR stage controls
  • ^^outs no longer has an lfo option, since remote duplicates the coverage – artists can load Live’s native LFO device and map it to remote's knob to run time-synced LFO’s through Live → crow




watching all the clips and reading this documentation now

thanks to all involved in the overhaul and beta testing :slight_smile:


seriously, super love to everyone in the norns study discord who battletested, asked great questions, and celebrated this insanely rad effort. notably: @proswell , @license , @infinitedigits , @desolationjones , @fourhoarder , @andrew !!

cannot wait to hear all the fantastic music that’s about to get made with sequins :sparkles:


a pleasure to test :smiley:


Absolutely incredible. Can’t wait to dive in.

I’m running OS X Mojave

Reading the upgrade Druid info I see this:

If you on on a previous version of macOS, you will probably have to add this line to your ~/.bash_rc instead.

Can someone explain what I actually need to do? I have no idea what this is…. Thanks!

1 Like

Good thing my jam is the combination of sine waves, typing, and birdsong.


thanks so much all of you involved in the development, such a useful tool!

V3! super excited for this one! audio-rate + sequins = 2hp 4voice drone machine :exploding_head:

congrats to @Galapagoose and everyone else involved!


This is an absolutely incredible amount of updates! audio-rate ASL, dyn, public & bowering, clock, freq… just amazing. cannot wait to try it out!

Unbelievable- thank you soooo so much

ah, those crow sounds in the video are real life physical crows which appeared at my barn-studio window just in time for the recording.

i just wanted to further commend @Galapagoose on this huge amount of code and design— the various features span numerous months and while we had a lot of discussion, trent basically conquered all of this on his own. when i proposed we make a “eurorack thing for norns” it was just an i/o module— now it’s evolved into a whole universe in itself. thank you all for coming along for the journey.


I’m a bit lost on how to use clock detached from the outputs within druid:

> cid = print('test') end)                                                                                                                                   
> clock.sync(1)                                                                                                                                                                   
attempt to yield from outside a coroutine                                                                                                                                         
stack traceback:                                                                                                                                                                  
  [C]: in function 'coroutine.yield'                                                                                                                                              
  ?: in field 'sync'                                                                                                                                                              
  repl:1: in main chunk                                                                                                                                                           
runtime error.                                                                                                                                                                    

totally separately, I’m also seeing strange errors when using a norns script that worked fine before, but now seems to spam the following to matron’s logs when crow is plugged in (just these four lines, repeated over and over. no crow.reset() seems to help):

crow:	repl:1: attempt to call a nil value (field 'execute')
crow:	stack traceback:
crow:	repl:1: in main chunk
crow:	runtime error.

I figured that was what was going on. I love it.

1 Like

clock syncs in druid follow norns conventions (the output-centric clock helpers are incredible though), so the sync needs to be wrapped inside of the coroutine’s function.

for a single sync’d execution:

> cid = clock.sync(1); print('test') end)

for continuous/looping:

> cid = while true do clock.sync(1); print('test') end end)

if you have something else you’re hoping to make happen, though, we’re definitely hoping to generate more docs based on practical use cases, so please feel free to say “nah, i need this to happen” and it’ll generate good examples :slight_smile:


here’s a dumb lil jam script using sequins to sequence CV on output 4 while the three other outputs pulse at related rhythms: clock + sequins messin · GitHub

sounds like:

ah dang, testing must’ve not caught this case, apologies for the trouble – if you can point us at the script we can dig in!


very very cool. I didn’t notice this in the release docs until I saw the listening on 6666 message in druid. :tada:

1 Like

Oh my avian goodness. Crow shows us new flight routes and brings us more trinkets, and they are shinier and more precious than before.

Thank you


d’oh! This makes sense; I think I’ve just been away from clock for too long.

This was just launching Cyrene, no user actions. Happy to help dig in this weekend I think :eyes:

@Galapagoose I know it was discussed ages back, but how about ASL = A Shape Language?

If you can update and run druid from your Terminal, you do not need to do anything. The documentation is providing a possible solution for what to do if you install druid, but continue to get druid: command not found or so when you type druid in the terminal.

When you type the name of a program into Terminal (or an equivalent program for other operating systems), your computer attempts to figure out where the file for that program actually is. It does this by checking an “environment variable” called PATH. System environment variables are basically just named pieces of text that all programs can access. When you type druid into Terminal and press “enter”, Terminal will typically check:

  • is there a file named druid in the current working directory that can be executed as a program?

  • if not, ask the system what the value of the PATH variable is. In Terminal, you can view the value of the PATH variable with echo $PATH. This might look something like:

    $ echo $PATH
  • PATH will have several names of folders separated by colon : characters. Terminal will check each of these folders in sequence, looking for a file named druid that can be executed as a program. So in the above example it will check if there is a file /usr/local/sbin/druid, if not then /usr/local/bin/druid, …

When you install or update druid, the installation process may issue a warning that the druid program is "not on your PATH", and print out the folder that would need to get added to the PATH for Terminal to be able to find it. One possible way to add that folder to your PATH is by editing the configuration files that Terminal uses when it first starts up. Which configuration files those are depends on which operating system version you are running – either ~/.bashrc or ~/.zshrc, where ~ is shorthand for “your home directory”. What is your home directory? Well, that’s determined by the value of the HOME environment variable, which can be found by executing echo $HOME. It is also the directory that Terminal will put you into when first launched, so right after launching a new Terminal if you run the program pwd (which stands for Print Working Directory) then that will print it out as well.

So if the output from pip3 install --upgrade monome-druid indicates that druid was installed successfully, but typing druid tells you “command not found” or "no druid on PATH" or something like that, then the docs are saying you may be able to fix this by adding a line to your ~/.bashrc or ~/.zshrc which adds the appropriate folder to the PATH every time Terminal starts up. Which directory that is depends on your Python install, but should be indicated in that output from pip3 install if there is a problem.