norns studies part 1: variables, simple maths, keys + encoders
hello. ready? remember to stay hydrated.
first, locate yourself such:
- connect to norns via hotspot or network
- navigate web browser to http://norns.local (or type in IP address if this fails)
- you’re looking at maiden, the editor
- create a new file in the
codedirectory: click on the
codedirectory and then click the + icon in the scripts toolbar
- rename the file: click the pencil icon in the scripts toolbar
it’s blank. full of possibilities. type in the text below:
-- many tomorrows -- norns study 1 engine.name = "TestSine" function init() print("the end and the beginning they are the same.") end
click save (the disk icon in the upper right) and then run (the play icon just below that).
if you typed it correctly, you’ll hear a sine tone.
the top two lines are comments. in lua a comment begins with two dashes, and with norns the very top comments are special in that they are displayed as the preview text in the script selector. try it:
- get into the norns menu
- enter the script selector
- find your file, select it
you can type as many lines at the top of a script as you want, but note that the norns screen isn’t so wide, so keep your lines short. it’s helpful to write a description about your script and list the controls. but since so far this does basically nothing we write basically nothing.
but we did do one important thing: we selected an engine.
engine.name = "TestSine"
this line selected the
TestSine engine – note that this needs to be in quotes (single or double work, but don’t mix + match). this is an imperative first step.
then, we created the
init function, which gets called at script startup (this is true of every script). right now all it does is print. but where does it print?
the COMMAND LINE (aka REPL, read-eval-print loop) is where. it’s the window below the editor. messages from matron (the lua machine) will appear here— you can scroll back to see what it had to say.
when you execute the script with
print("some words") then
some words get printed to this place. it’s incredibly helpful, despite this being possibly the most boring intro to such an astoundingly interesting music machine.
at the bottom is a
>> prompt which is where you can type in commands. let’s do that.
and you’ll see
hello. printed in response. norns has excellent manners.
but let’s do something even more useful now:
engine.list_commands() --- engine commands --- amp (f) hz (f) ------ <ok>
this is useful information! for the “TestSine” engine we have two commands:
both are float (decimal) values. given the name “TestSine” we can infer that
amp is amplitude and
hz is frequency. so let’s change some values:
now is a good time to point out the UP ARROW on your keyboard. when typing into the command line use the up arrow to see the previous things you typed in. this makes rapid-changing of the frequency much easier.
also try modulating the amplitude with
engine.amp(0.8) (amplitude is 0.0-1.0, but you can certainly give it large values and it’ll clip happily.)
so back to the script, if we want to have the engine start up with a particular frequency, we add it to the
function init() engine.hz(100) print("the end and the beginning they are the same.") end
try it. it works!
back to the command line. let’s throw around some variables:
coins = 4 spell = "heal party"
we just made two variables. the first assigned the number 4, the second a string. you can easily confirm that it worked with:
this prints just the number. you’ll find it helpful when debugging to make more informative prints by using string concatenation which just means gluing strings together (or tying them together, if you prefer a more rational metaphor). you do this with the lua operator
.. (two periods):
print("i will cast " .. spell .. " for " .. coins .. " coins.")
(lua has a huge string library, many resources are on the web.)
(also, see addendum on local vs. global variables.)
all of the normal arithmetic operators are available:
coins = coins + 1 coins = coins - 10 coins = coins * 2 coins = coins / 4 coins = coins % 2
%) is perhaps unusual. it gives the remainder after a division. so:
11 % 10 would equal 1. we can use this as a trick for confining values to a range, say:
x = x % 10
for whatever value of x, it will be wrapped to the range 0-9.
and let’s just make sure you know about this early on:
coins = math.random(100)
coins to a random value up to 100. but also:
so far all of our interaction has been through the command prompt. this is a good way to demonstrate some basic syntax, but the point of norns is interaction. here’s how we make an event happen on key presses:
function key(n,z) print("key " .. n .. " == " .. z) end
type this in at the bottom of your script. save and rerun. then push some keys, you’ll get some prints.
nis which key number
zis down (1) or up (0)
let’s modify the script to do something more engaging:
function key(n,z) if n == 3 then engine.hz(100 + 100 * z) end end
here we make an
if (condition) then (do stuff) end
above we used
n == 3 as the condition, which checked to see if the key number was equal to 3. other comparison operators include:
>=(greater or equal)
<=(less than or equal)
if statements can also be expanded with
if coins > 100 then print("this bag is way too heavy") elseif coins < 0 then print("who's calling?") else print("two cups of coffee, please.") end
to get data from the encoders type this at the end of the script:
function enc(n,d) print("encoder " .. n .. " == " .. d) end
d is delta. the encoders report incremental steps of a turn: clockwise is positive, counterclockwise is negative.
we can accumulate these steps to get an absolute position:
function enc(n,d) if n == 3 then position = position + d print("encoder 3 at position: " .. position) end end
replace your encoder function with this one, save and run. upon turning the encoder you’ll get an error!
lua: /home/we/dust/code/study/study1-manyfutures.lua:19: attempt to perform arithmetic on a nil value (global 'position') stack traceback: /home/we/dust/code/study/study1-manyfutures.lua:19: in function 'encoders.callback' /home/we/norns/lua/encoders.lua:56: in function 'encoders.process'
(your line number may be different, but the error is the same). we made a small mistake. while you don’t need to declare variables, you can’t add
nil to numbers and since:
position = position + d
position already exists, we have to first make it exist. just do that by adding a line inside of
init which gives
position a default value:
function init() position = 10 engine.hz(100) print("the end and the beginning they are the same.") end
but you may have already discovered error checking if you made some typos. for example:
print("we like to party"
<incomplete> (translation: typo. no closing parenthesis.)
be sure to keep an eye on the command line for errors
putting together the concepts above. this script is demonstrated in the video up top.
-- many tomorrows -- norns study 1 -- -- KEY 2 toggle sound on/off -- KEY 3 toggle octave -- ENC 2 randomize amplitude -- ENC 3 change frequency -- -- first turn on AUX reverb! engine.name = "TestSine" function init() sound = 1 level = 1 octave = 1 f = 100 position = 0 engine.hz(f) print("the end and the beginning they are the same.") end function key(n,z) if n == 2 then if z == 1 then -- trick below to toggle between 0 and 1 sound = 1 - sound engine.amp(sound * level) end elseif n == 3 then octave = z + 1 engine.hz(octave * f) end end function enc(n,d) if n == 2 then level = math.random(100) / 100 engine.amp(sound * level) elseif n == 3 then position = (position + d) % 11 f = 100 + position * 50 engine.hz(octave * f) end end
variables in lua are global by default. use the
local keyword to make a variable visible only to the scope within which it’s declared:
local hidden_spell = "foobarbaz"
notice that you can’t access
hidden_spell in the REPL:
nil as its output.
locals declared within functions are only visible to that function (including
function init() local hidden_spell = "foobarbaz" cast() end function cast() print(hidden_spell) end
locals declared outside of functions are available to other functions within the file:
function init() cast() end local hidden_spell = "foobarbaz" function cast() print(hidden_spell) end
when scripting, global variables make it incredibly easy to troubleshoot from the REPL. besides a few very specific phrases (see the system global variable list), you should feel safe using globals in your scripts. each time a new script runs, the previously-declared global namespace is wiped, so there’s no risk of cross-influence.
declaring locals is often a matter of taste, but also utility + legibility:
engine.name = "TestSine" function init() sound = 1 level = 1 f = 650 engine.hz(f) end function enc(n,d) if n == 2 then local last_level = level level = math.random(100) / 100 if level ~= last_level then engine.amp(sound * level) elseif level == last_level then -- level didn't change, so change note f = 650 / math.random(2,4) engine.hz(f) end end end
engine.list_commands()– list available engine commands (prints to command line)
print(x)– print value of
init()– function which executes at script load
key(n,z)– function to parse norns keypresses
enc(n,d)– function to parse norns encoder movement
math.random()– generates random values, see this tutorial for detailed usage
elseif– conditional statements, see Lua docs for detailed usage
==, etc – relational operators, see Lua docs for detailed usage
- part 1: many tomorrows
- part 2: patterning // screen drawing, for/while loops, tables
- part 3: spacetime // functions, parameters, time
- part 4: physical // grids + midi
- part 5: streams // system polls, osc, file storage
ask questions and share what you’re making at llllllll.co
edits to this study welcome, see monome/docs