Norns: studies



Yup - i’ve been banging my head against those docs for awhile and trying to make sense of it.

Still trying to wrap my head around lua


I’ve got a draft musicutil.lua on the go here that has functions for converting notes to freqs and back, converting notes to friendly names, generating scales and snapping notes to a table (scale). Pull request coming soon!

Norns: scripting

Oooh… this turned out to be very helpful in getting my virtual grid buttons to light up in touchosc.

:+1: Thanks!


It’s Wednesday :clap:


somewhere in the world - like here! :slight_smile: my Wednesday nornsday is actually a Thursday nornsday for me




“pause. really consider the possibilities, and i hope your mind explodes a tiny bit. this is why programming in a musical context is so incredibly powerful and interesting.”

Yep, my mind exploded just watching that demo video. First I was like “oh great a sequencer [yawn]. Then I was like “wait what is happening?!? [head explodes]


You know these series of studies really give me the same feeling as the Micro Adventure books of the 80s where you’d type BASIC programs into a computer as part of the story. These are great ways to learn about programming. Norns is really turning into an excellent platform for learning about creative coding and music. Thank You!


@tehn I love the creative possibilities in adding additional functions to the sequencer. Here is the edited list that is working for me right now.

function on() engine.amp(0.2) end
function inc() on() note = util.clamp(note + 5, 40, 120) end
function dec() on() note = util.clamp(note - 5, 40, 120) end
function bottom() on() note = 40 end
function top() on() note = 120 end
function rand() on() note = math.random(80) + 40 end
function metrofast() counter.time = 0.125 end
function metroslow() counter.time = 0.25 end
function positionrand() position = math.random(STEPS) end
function rest() engine.amp(0) end
function filt() res() engine.cutoff(math.random(5000)+50) end
function res() engine.gain(math.random(4.0)) end
function randsynth() width() release() end
function width() end
function release() engine.release(math.random(3.0)) end

act = {inc, dec, bottom, top, rand, metrofast, metroslow, positionrand, rest, filt, randsynth}
label = {"+", "-", "<", ">", "*", "M", "m", "#", "r", "f", "s"} 


This is a really nice study.

Reading it, the things that leap out at me are a slight glossing over scope, which you use as a term a few times without fully clarifying. My gutfeel is to not slow down the study with endless explanation, though; I almost want footnotes, or something. (My notes on scope would be about “local scope” inside a function, and basically where variable names are relevant and not - eg how argument names are chosen, how local variables in a function are freed, etc. Also “class function” isn’t quite fully explained).

Sorry for nitpicking - just know that these tutorials are largely aimed at users whose exposure to programming, or at least programming Lua, might be limited. Mainly, it makes me appreciate how effective the narrative of the studies is - I’m only ever thinking about polish/reference/clarity.


are the studies planning to dive into the SC side at all (e.g. adding engines etc) ? (perhaps after matron/lua?)
(I know, lots to cover so all takes time, so might be ‘a while yet’, just wondering what is ‘in scope’)

btw… I like your examples @tehn, they are a great balance of ‘simple enough’ to understand, and extend, yet interesting enough to want to dive into - its tricky balance to achieve.

Norns: crone/supercollider

metro class is in (norns/docs)[norns.local/docs] (must be connected to norns)

I suppose this should be norns.local/doc ?


just checked. It is /doc… but honestly, it seems like it should be /docs lol



Wondering if this has happened to anyone else here or if someone can point out what I may have done wrong here but last night my first day with Norns I wanted to start the first study “many tomorrows”.

I was able to get into maiden and create a new script, name it, then add the first block of code from the study. However once I ran it in Matron I got no sound and my Norns pretty much became unresponsive except for encoder 2. When I turned encoder 2 it allowed me to scroll though the page I was on but that was about it.

Now what I’m wondering is could this have happened because I went into maiden while a script was locally loaded on Norns? Forgive my lack of knowledge here as Norns will be my first foray into programming.

For about two hours I thought I had bricked or ruined Norns. After rebooting my computer I was able to have control over it again.


@prnts this sounds like a wifi bug that we’re working on. if it happens again and still isn’t responsive after a minute, shut down the norns via the emergency switch on the bottom and reboot. (use this bottom switch only if there’s a crash-- use SLEEP from the menu normally, which does a clean shutdown.

EDIT: also see @Olivier’s post below


This has happened to me periodically when going through the studies or creating my own scripts. When Norns becomes unresponsive while I’m working in Maiden (and without any clear error reported), simply switching to and running any other stable script directly from Maiden gets things back up and running immediately.


all studies now have syntax highlighting! extra readable.


I loved this study so much that I decided to write something similiar using processing (hope you don’t mind).
But I expanded it to use 4 tracks (for four midi channels) and added few custom operators like for example:
‘C’ - change random operator in next track
‘R’ - rotate right next track
here is recording from such session

After all this experimentation now I know that I will definetly need to start saving money for Norns :stuck_out_tongue:


I combined Spacetime with @burn’s KarplusRings engine. I also made it so that button 2 resets all steps to default.

-- spacetime with rings
-- norns study 3
-- ENC 1 - change brightness
-- ENC 2 - select edit position
-- ENC 3 - choose command
-- KEY 2 - return commands to default
-- KEY 3 - randomize command set
-- spacetime is a weird function sequencer.
-- it plays a note on each step
-- each step is a symbol for the action.
-- + = increase note
-- - = decrease note
-- < = go to bottom note
-- > = go to top note
-- * = random note
-- M = fast metro
-- m = slow metro
-- # = jump random position
-- augment/change this script 
-- with new functions!
-- this mod adds burn's 
-- excellent KarplusRings 
-- engine = "KarplusRings"
local cs = require 'controlspec'

note = 40
position = 1
step = {1,1,1,1, 1,1,1,1, 1,1,1,1, 1,1,1,1}
STEPS = 16
edit = 1

function inc() note = util.clamp(note + 5, 40, 120) end
function dec() note = util.clamp(note - 5, 40, 120) end
function bottom() note = 40 end
function top() note = 120 end
function rand() note = math.random(80) + 40 end
function metrofast() counter.time = 0.125 end
function metroslow() counter.time = 0.25 end
function positionrand() position = math.random(STEPS) end

act = {inc, dec, bottom, top, rand, metrofast, metroslow, positionrand}
label = {"+", "-", "<", ">", "*", "M", "m", "#"}

function init()
  cs.AMP =,1,'lin',0,0.75,'')
  function(x) engine.amp(x) end)

  cs.DECAY =,15,'lin',0,3.6,'s')
  function(x) engine.decay(x) end)

  cs.COEF =,1,'lin',0,0.11,'')
  function(x) engine.coef(x) end)

  cs.LPF_FREQ =,10000,'lin',0,3600,'')
  function(x) engine.lpf_freq(x) end)

  cs.LPF_GAIN =,3.2,'lin',0,0.5,'')
  function(x) engine.lpf_gain(x) end)

  cs.BPF_FREQ =,10000,'lin',0,1200,'')
  function(x) engine.bpf_freq(x) end)

  cs.BPF_RES =,4,'lin',0,0.5,'')
  function(x) engine.bpf_res(x) end)

  counter = metro.alloc(count, 0.125, -1)

function count()
  position = (position % STEPS) + 1

function redraw()
  for i = 1,16 do
    screen.level((i == edit) and 15 or 2)
    if i == position then
      screen.move(i*8-8, 45)

function enc(n,d)
  if n == 1 then
    params:delta("brightness", d)
  elseif n == 2 then
    edit = util.clamp(edit + d, 1, STEPS)
  elseif n ==3 then
    step[edit] = util.clamp(step[edit]+d, 1, COMMANDS)

function key(n,z)
  if n == 2 and z == 1 then
  if n == 3 and z == 1 then

function midi_to_hz(note)
  return (440/32) * (2 ^ ((note - 9) / 12))

function randomize_steps()
  for i= 1,16 do
    step[i] = math.random(COMMANDS)

function init_steps()
  for i= 1,16 do
    step[i] = 1