Norns: studies



You’re welcome! Let me know if anything isn’t clear.


polls. OSC. files.

vid: metallophone to mic, mic to inputs. pitch detect on inputs with key press, add oscillator with detected pitch. add up to 16 pitches, reset with other key. touchOSC running on phone, controlling filter and noise in x-y configuration.


this is stunning. wow. norns is growing.


Thanks much for the file operations, this is what I have been waiting for! One question regarding this example:

function print_file(filepath)
  if f==nil then
    print("file not found: "..filepath)
    for line in io.lines(filepath) do
      -- this is where you would do something useful!
      -- but for now we'll just print each line

It is confusing to me why f:close() is called before the for…in…do operation using io.lines(filepath). I would have expected that the file must be open in order to execute the for…in…do operation using io.lines(filepath), and that f:close() is called after that.

Please, can you help me understand? Still a Lua (and programming) novice…thanks much in advance!



I believe io.lines only checks the amount of lines in a file, regardless of whether or not it is open. If this is the case, it wouldn’t matter if f:close() came before or after, but it’s good practice to close files as early as possible.


It seems strange that a file handle doesn’t need to be open to count the lines in a file. But yeah, it seems like the io class has some methods that don’t require a file handle, only a path to a file.


Thanks, but this is even more confusing to me, as the for…in…do does not count the lines, but prints each line.

Why does the file need to be opened in the first place if its content can be printed even after it is closed?


Looking to the docs for io.lines, it seems like the iterator that is returned manages the file handle internally, closing it once the iteration has ended. I believe the print_file function is calling just to verify if the file exists beforehand, as lines will throw an error if the file doesn’t exist. Checking for f==nil seems a bit easier than handling the error (I’m new to Lua but from what I can tell error handling looks pretty weird, correct me if I’m wrong).


I have the same issue; external midi sync doesnt seem to work (tried Arturia Keystep and Squarp Pyramid)


using the mallet to stop the feedback bloom was unexpectedly (and inordinately) amusing

excellent performance @tehn !


Fun hack. I found a Kindle Fire (gen 5) tablet that I forgot about. It’s some weird fake Android without Google Play app store but…TouchOSC is in the Amazon App Store (is that even the same thing?).

The tablet cost $40 new. Kinda rad as a wireless general purpose controller.


Have you checked if it works? If it does it would be AWESOME.


try it, you’ll like it


Update for future readers: I didn’t note it below, but I was on the 181002 firmware. The relevant syntax was changed with that firmware. @tehn explains in detail here:



Looks like there’s a code example typo in study part 3, in the parameters section:



Should be?



thanks for the mention. i’m mostly done updating the study… will get it posted in a bit!


So I finally updated to update 181002 to learn the new way to access grid keys, and stumbled over this:

According to study 4 “physical” this is how to make use of grid key presses:
g.event = function(x,y,z)

But according to the new version of “awake” in update 181002, this is how to make use of grid key presses:
function g.event(x, y, z)

Both seem to work. Is there any difference between the 2, or can both be used interchangably? From my real bloody novice user experience, this format
function g.event(x, y, z)
blends in better with how functions have been introduced & used in previous studies.

On a slightly related note, there seems to be a bug in study 4 “physical” in the code for the “complete step sequencer”. When I enter the code as described… = 'PolyPerc'
steps = {}
position = 1
counter = metro.alloc() 
counter.time = 0.1 
counter.count = -1
counter.callback = count

function init()
  for i=1,16 do 

g = grid.connect()

g.event = function(x,y,z)
  if z == 1 then
    steps[x] = y

function grid_redraw() 
  for i=1,16 do
    g.led(i,steps[i],i==position and 15 or 4)

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

…nothing happens, i.e. the sequencer does not run.

After experimenting I found that
counter.callback = count
needs to be inside
function init()
in order to make the sequencer run.

Is there a way to explain to a novice user why only this counter.callback needs to be in the function init(), but not the other expressions?

Thanks much in advance!


these statements mean the same thing in lua. (lua functions are first order objects, but the second form is provided as syntactic sugar - it looks more natural if you’re coming from c-like languages.)

just a guess, but i think the problem is simply that you say counter.callback = count before you define count. so you are really saying counter.callback = nil.

whereas init() is executed later by a callback (when engine is done loading), so the count function has already been defined there.

my guess is it would also work if you define count and then assign it to counter.callback, even without doing the assignment in init.


i’ll fix the study, thanks for the heads up


Strange error with renaming @tehn s awake script in 181002


In maiden…

  • open tehn > awake and copy the lua script to clipboard
  • create a new empty script ("untitled.lua" is created)
  • paste clipboard content in “untitled.lua
  • press play (awake runs flawlessly)
  • rename “untitled.lua” to “new.lua
  • press play


  • awake plays
  • REPL shows this error message over and over:

stack traceback:
/home/we/dust/scripts/jhh/untitled.lua:123: in field 'on_step'
/home/we/dust/lib/lua/beatclock.lua:66: in function 'beatclock.advance_step'
/home/we/dust/lib/lua/beatclock.lua:72: in function 'beatclock.tick'
/home/we/dust/lib/lua/beatclock.lua:22: in field 'callback'
/home/we/norns/lua/metro.lua:155: in function </home/we/norns/lua/metro.lua:152>
/home/we/dust/scripts/jhh/untitled.lua:123: attempt to call a nil value (global 'gridredraw')

Moving function step() after function gridredraw() doesn’t help. And the odd thing is, this behavior only occurs after renaming.


  • although the file “new.lua” is shown in maiden’s script list, Cyberduck (after a refresh) still shows “untitled.lua”, and after a refresh in maiden, maiden also shows “untitled.lua” (and “new.lua” is gone)


  • The new awake script still uses g:refresh() instead of g.refresh(), but the behavior mentioned above can also occur with g.refresh()

Is this a bug or am I missing something? Thanks in advance!


g:refresh() is incorrect. it should be g.refresh(). i’ll fix this in the repo.

the renaming issue sounds like a maiden bug. i’ll try to reproduce it.

the repeated error is confusing, however. try to load a different script, then come back to your work in progress.