Rising: Crow Studies

Crow serves as a CV and ii interface for norns.

It may be helpful to first explore the norns studies to provide context for how to integrate crow’s new functionality.

Download: github.com/monome/crow-studies

(Note: be sure your norns is updated to version 190920 or later.)

Crow will automatically be detected and interfaced upon connection to norns. Presently only a single crow is supported.

1. Output

Run 1-output.lua. Connect crow output 1 to an oscillator pitch or similar.

This sets up a knob and screen interface for two very simple commands:

crow.output[1].volts = 3.33
crow.output[1].slew = 0.1

This sets output 1 to 3.33v, slewing over 0.1 seconds.

Crow’s voltage range is -5.0 to 10.0 for outputs 1 to 4.

2. Input

Run 2-input.lua.

  • Connect an LFO output to crow input 1. K1 will capture the current value. K2 will toggle stream mode on and off.
  • Connect the same cable to input 2 which is set up to trigger a change function on each transition.

Inputs have several modes:

  • stream: the input is reported at a fixed interval.
  • change: configurable low/high transitions are reported.
  • none: inputs are read only with a manual query.


First we set the function for incoming data, and then set the mode:

function process_stream(v)
  print("input stream: "..v)

crow.input[1].stream = process_stream
crow.input[1].mode("stream", 0.25)

process_stream will be called every 0.25 seconds, printing the value of crow input 1.


Again we create a function to handle the input change, and set the mode:

function process_change(v)
  print("input change: "..v)

crow.input[1].change = process_change
crow.input[1].mode("change", 2.0, 0.25, "both")

process_change will be called whenever input 1 crosses 2.0 volts with a hysteresis of 0.25.

If the input is rising, the value reported will be 1. If falling, it will be 0.

The last parameter when setting the mode can have three values: "rising", "falling", or “both".


We can still manually query the input with mode set to "none".

function process_stream(v)
  print("input stream: "..v)

crow.input[1].stream = process_stream


process_stream will be called each time crow.input[1].query() is called, returning the value of crow input 1.

3. ii

Run 3-ii.lua.

Attach a Just Friends via ii. Be sure to align the GND pins. K2 will play an ascending note, K3 plays a random note.

The ii bus requires pullup resistance, which can be toggled by crow:


If your ii bus is already pulled up (by Teletype or a powered bus board, for example), you can erase this line (as pullup is off by default), or explicitly turn off pullups like this:


To change JF’s mode and play a note:


Crow can also query values from the ii bus. If you have an Ansible connected running Kria, you can query the current preset like this:

crow.ii.kria.event = function(i,v)
  print("kria event:",i,v)


See the reference section for a full table of supported ii devices and commands.

4. shapes

Run 4-shapes.lua. Crow output 1 is an LFO, output 2 is an envelope. K2 will randomize the LFO speed. K3 will trigger the envelope. Voltage output is displayed as meters on the left.

Crow can generate and loop multipoint envelopes:

-- start at 0, rise to 5V over 0.1 seconds, fall to 1V over 2 seconds
crow.output[1].action = "{ to(0,0), to(5,0.1), to(1,2) }"

To start (and restart) this action:


Shapes can be repeated:

crow.output[1].action = "times( 4, { to(0,0), to(5,0.1), to(1,2) } )"

And also looped:

crow.output[1].action = "loop( { to(0,0), to(5,0.1), to(1,2) } )"

Actions can be interrupted at any time by setting a fixed voltage, for example:

crow.output[1].volts = 0

There are a few predefined shapes, such as LFO:

-- LFO rate of 1, amplitude of 5V
crow.output[1].action = "lfo(1,5)"


It is possible to read the current value of an output using a query:

function out(v)
  print("crow output: "..v)

crow.output[1].receive = out


Each time query is called, crow will send a value to the function receive. The script uses this technique to create a realtime scope of the outputs on the norns screen.



crow.output[x].volts = y         -- set output x (1 to 4) to y (-5.0 to 10.0) volts
crow.output[x].slew = y          -- set output x slew time to y

crow.output[x].action =
  "{ to(volt,time), ... , to(volt,time) }"    -- series of segments
  "times( x, { ... } )"                       -- repeat segments x times
  "loop( { ... } )"                           -- loop segments indefinitely

crow.output[x].query()           -- query current output x value
crow.output[x].receive           -- function called by query x


crow.input[x].stream             -- function called by "stream" mode and query
crow.input[x].change             -- function called by "change" mode

crow.input[x].mode("none")       -- set input x to query only
crow.input[x].mode("stream", rate)      -- set input x to stream mode at specified rate
crow.input[x].mode("change", thresh, hyst, edge) -- set input x to change mode
  -- specify threshold, hysteresis, and edge ("rising", "falling", or "both")

crow.input[x].query()            -- queries current value of input x


crow.ii.pullup(state)       -- enable/disable pullups (true/false)

-- ansible
-- commands
crow.ii.ansible.trigger( channel, state )
crow.ii.ansible.trigger_toggle( channel )
crow.ii.ansible.trigger_pulse( channel )
crow.ii.ansible.trigger_time( channel, time )
crow.ii.ansible.trigger_polarity( channel, polarity )
crow.ii.ansible.cv( channel, volts )
crow.ii.ansible.cv_slew( channel, time )
crow.ii.ansible.cv_offset( channel, volts )
crow.ii.ansible.cv_set( channel, volts )

-- request params
crow.ii.ansible.get( 'trigger', channel )
crow.ii.ansible.get( 'trigger_time', channel )
crow.ii.ansible.get( 'trigger_polarity', channel )
crow.ii.ansible.get( 'cv', channel )
crow.ii.ansible.get( 'cv_slew', channel )
crow.ii.ansible.get( 'cv_offset', channel )

-- then receive
crow.ii.ansible.event = function( e, data )
	if e == 'trigger' then
		-- handle trigger param here
	elseif e == 'trigger_time' then
	elseif e == 'trigger_polarity' then
	elseif e == 'cv' then
	elseif e == 'cv_slew' then
	elseif e == 'cv_offset' then

-- ansible kria
crow.ii.kria.preset( number )
crow.ii.kria.pattern( number )
crow.ii.kria.scale( number )
crow.ii.kria.period( time )
crow.ii.kria.position( track, param, pos )
crow.ii.kria.loop_start( track, param, pos )
crow.ii.kria.loop_length( track, param, pos )
crow.ii.kria.reset( track, param )
crow.ii.kria.mute( track, state )
crow.ii.kria.toggle_mute( track )
crow.ii.kria.clock( track )

-- request params
crow.ii.kria.get( 'preset' )
crow.ii.kria.get( 'pattern' )
crow.ii.kria.get( 'scale' )
crow.ii.kria.get( 'period' )
crow.ii.kria.get( 'position', track, param )
crow.ii.kria.get( 'loop_start', track, param )
crow.ii.kria.get( 'loop_length', track, param )
crow.ii.kria.get( 'reset', track )
crow.ii.kria.get( 'mute', track )
crow.ii.kria.get( 'cv', track )

-- then receive
crow.ii.kria.event = function( e, data )
	if e == 'preset' then
		-- handle preset param here
	elseif e == 'pattern' then
	elseif e == 'scale' then
	elseif e == 'period' then
	elseif e == 'position' then
	elseif e == 'loop_start' then
	elseif e == 'loop_length' then
	elseif e == 'reset' then
	elseif e == 'mute' then

-- ansible meadowphysics
-- commands
crow.ii.meadowphysics.preset( number )
crow.ii.meadowphysics.reset( track )
crow.ii.meadowphysics.stop( track )
crow.ii.meadowphysics.scale( number )
crow.ii.meadowphysics.period( time )

-- request params
crow.ii.meadowphysics.get( 'preset' )
crow.ii.meadowphysics.get( 'stop' )
crow.ii.meadowphysics.get( 'scale' )
crow.ii.meadowphysics.get( 'period' )
crow.ii.meadowphysics.get( 'cv', track )

-- then receive
crow.ii.meadowphysics.event = function( e, data )
	if e == 'preset' then
		-- handle preset param here
	elseif e == 'stop' then
	elseif e == 'scale' then
	elseif e == 'period' then
	elseif e == 'cv' then

-- jf
-- commands
crow.ii.jf.trigger( channel, state )
crow.ii.jf.run_mode( mode )
crow.ii.jf.run( volts )
crow.ii.jf.transpose( pitch )
crow.ii.jf.vtrigger( channel, level )
crow.ii.jf.mode( mode )
crow.ii.jf.tick( clock-or-bpm )
crow.ii.jf.play_voice( channel, pitch/divs, level/repeats )
crow.ii.jf.play_note( pitch/divs, level/repeats )
crow.ii.jf.god_mode( state )
crow.ii.jf.retune( channel, numerator, denominator )
crow.ii.jf.quantize( divisions )

-- w/
-- commands
crow.ii.wslash.record( active )
crow.ii.wslash.play( direction )
crow.ii.wslash.loop( state )
crow.ii.wslash.cue( destination )

-- request params
crow.ii.wslash.get( 'record' )
crow.ii.wslash.get( 'play' )
crow.ii.wslash.get( 'loop' )
crow.ii.wslash.get( 'cue' )

-- then receive
crow.ii.wslash.event = function( e, data )
	if e == 'record' then
		-- handle record param here
	elseif e == 'play' then
	elseif e == 'loop' then
	elseif e == 'cue' then