Norns: scripting



quick thought (and untested) but maybe try something like:

local _enc = function(num, delta)
  -- do actual handling here

function enc(num, delta)

i think you should be able to reassign _enc on the fly and get what you’re after?


this is the correct way to do this @ppqq


I realized I wanted to dynamically call a function name based on some global state, not override the definition of enc(). StackOverflow to the rescue.

Tables of functions are pretty cool and easy to understand.


loving all the new scripts in the update! especially molly the polly! sounds insane.

made a quick hack of @Justmat’s step sequencer to distinguish the black keys from the white keys on the grid keybed:

4 changes to the script:

1: add new variable with the others (not sure if this is needed or just good hygiene?)

local black_keys = {}

2: add new function to get table of coordinates whose notes are sharp/flat

local function keybed_lights()
  for y = 3, 8 do
    for x = 1, 16 do
      local note = music_util.note_num_to_name(note_from_grid(x, y), 1)
      if string.find(note, "%#") then
        table.insert(black_keys, {x, y})
  return black_keys

3: add this to init() to store the table on run

black_keys = keybed_lights()

4: add this to grid_redraw to display the keys

  for index, t in pairs(black_keys) do 
      g.led(t[1], t[2], 2)

i still don’t have a very good grasp on how to use github or what the etiquette is for posting minimal modifications of others’ work there, but let me know if this kind of stuff shouldn’t live on!


Nice! This seems really useful. I have been preparing a small update for Paths, so I’ll be sure to include it.


Is there a way to change the step of a ParamSet or to use floats instead of integers? So if I want to use a parameter to change the value of something from 0.0 1.0 with the steps being 0.01, how would I do that?

Also, in regards to Polls, is there a way to pass Symbols or Strings between a CroneEngine and LUA?


I’m going deeper into the params/preset system. I noticed an artifact called fm7.pmap. The contents are keys enumerating all params I exposed through supercollider and values which all read “-1”.

What do these values mean?


no. you can currently send a single float, or a blob of bytes (which is passed to lua as an array; afaik only the VU meters use this - see AudioContext.)

we could add format strings to polls, as we have for commands. i didn’t feel this was necessary enough to introduce the attendant complexity immediately. specifically i wonder why you would ever need to generate a string from SC - personally i would just have a little engine-specific lua code to convert a float value. (the main reason i would want poll formats is to send multiple float values in a single packet.)

in any case:

technically yes. paramset / params accept arbitrary deltas:

and param subclasses can customize how they handle deltas.

but in the menu, delta is fixed (or rather is a function of encoder sensitivity, which is fixed in the menu… or something like that.)

i agree that finer param control in the menu would be great. (how? dunno. on aleph we had two param value knobs - coarse with acceleration, fine without. here would need a mod key or something. up to y’all.) but you can also roll your own custom param UI in a script, affecting the exact same param data.


pmap is param map = midi map

-1 means unassigned

re: param delta ranges, yes we should figure out a way to refine the design for granularity


if I may, (might be too soon to make a ticket on github) what do you think about renaming the extension to “.midimap”?


i’d prefer not to, since “mapping” may end up having information that isn’t midi eventually.

.pmap matches .pset which are related. but i get what you’re saying. what’s more important is to make sure docs keep up with edge development


pmap files have the same problem that we had with psets earlier: adding/removing params will shift the parameter indexes that are currently saved in pmap, so it makes sense to save the map with param identifiers as keys.


@artfwo good find!


I’ve got some error messages when trying to run that script. I don’t think that it is a big deal but i am stuck:


Question reg. changes in AWAKE script

I have to admit that I still love the AWAKE script so much that I have not yet touched any of the other scripts (sorry, I feel kind of bad for it, as so many folks did so much work on these), instead I have been modifying it for my purposes (= learning programming and adding some feature to it). With the last updates @tehn made some changes to his AWAKE, and today I merged my changes into this new AWAKE version. In doing this, I stumbled over these changes:

  1. The old version had this
    local cs = require 'controlspec'
    which is now gone. Why?

  2. The old version had this MIDI and cleanup functionality at the very end. Have these MIDI functionality been removed or replaced? And is a cleanup no longer required?
    midi.add = function(dev)
    print('awake: midi device added',,
    midi_device = dev
    midi.remove = function(dev)
    print('awake: midi device removed',,
    midi_device = nil
    function cleanup()
    midi.add = nil
    midi.remove = nil

Thanks in advance for clearing up my confusion!


that’s happened to me a bunch too. i sit down to make something and just get lost in awake. it’s the best!

as for your questions, @tehn is the authority but i’ll take.a few stabs.

at first blush, that does look like a bug but maybe it’s just inconsistent. i’m guessing that controlspec is just sure to have been loaded by the time user scripts are loaded so it’s essentially a global. (it’s a little surprising i agree…)

iirc all midi tidying has been neatly inlined so it’s no longer needed in user code.

definitely an improvement!

local cs = require 'controlspec'

is gone because i opted to use the global controlspec which is initialized due to the use of controlspec’s in the param system.

so you see:

cs_AMP =,1,'lin',0,0.5,'')

there’s no need for redundancy making my own local copy of cs

my scripts in general need a heavy revision to illustrate best practices, since the script infrastructure has improved since launch


That message appears when i rerun my script. I’ve got no such thing on the first run:


/home/we/norns/lua/paramset.lua:151: attempt to index a nil value (local ‘param’)

stack traceback:

/home/we/norns/lua/paramset.lua:151: in function ‘paramset.get’

/home/we/dust/scripts/martinmestres/ANDR.lua:83: in field ‘event’

/home/we/norns/lua/midi.lua:120: in local ‘event’

/home/we/norns/lua/midi.lua:301: in function </home/we/norns/lua/midi.lua:289>

It seems linked to the fact that:

params:add_option(“CLOCK”,“clock”, {“INT”,“EXT”})

is located in the function init(). Getting it out fix everything. Why’s that?


How difficult would it be to modify the looping behavior of tracks in glut (e.g., change to one-shot)?

I was originally thinking of a distinct script, but it could be cool as an option per track in glut…


Haven’t looked into it in depth, but I’m pretty sure this would just take an additional few lines in and added parameters in the glut.lua script. I’m going just going off the fact that buffers in SuperCollider have built-in loop arguments.