Norns: code review

While the Norns: scripting thread is about how to write scripts and work with the norns environment, this thread is intended to be a place to request review of working code. How can we make our scripts more readable / shareable / extensible.

I encourage you to post links to code, preferably using gist.github.com. Reviewers can then place their thoughts in the comments of the gist, and this thread can be a repository of links with a short blurb explaining the nature of the review.

//

Let me start:
I was reading through the SPACETIME study and had some thoughts about how to make the actions more easily extensible (and less error-prone). My solution was a table of tables (called COMMANDS) which wraps the labels & functions inside. I’m using the # operator in Lua which perhaps was beyond the scope of that tutorial.

The functions could be anonymous (ie. remove the ā€˜inc’, ā€˜dec’, ā€˜bottom’ etc) but I opted to keep them as some self-documentation.

I’d be interested to hear if this feels more or less readable. The big benefit is that in order to add a new COMMAND, only a single line of code needs to be added.

20 Likes

I’ve already posted that elsewere but i could really use some advices, shortcuts, syntax standards etc… (I am just begining coding something else than my TT).

So I am working on a Markov’s chains sequencer (ultimately second order). This is just the core of the script but i intend to add other functions later like looping, rhytm manipulations etc… So if you feel like cleaning my mess a bit, you are welcome.

1 Like

Yeah it’s readable

Still want review it in comparison with the study script to understand how different this is

cranes: improvisational varispeed looper / delay for norns

please review code for any errors or rookie mistakes. application suggestions, usability feedback, documentation critiques – all also welcome!

preview video: Latest tracks + videos
audio artifact: Latest tracks + videos
gist: https://gist.github.com/dndrks/5b1d160e28a482d5b2f2042167adc89b

**getting started guide**

loops:
ez start:
plug audio into the first input jack (I think there’s a GitHub issue open for stereo not working with the engine).

hit key 2 to enable recording and start playing something. maybe a short melody line or bring in a single note swell. you’ll get visual confirmation that recording is happening with a white crane.

tap key 2 again and what you just played will start looping. you’ll see E go down from 60.0 (seconds) to whatever duration you set.

params:
if you head to params, you’ll see that the loop playing is actually ā€œbuffer 2ā€ (e.g.: the volume is up for buffer 2 playback, but not buffer 1).

try this:
adjust the speed of buffer 1 to anything besides 1.0
slowly raise the volume of buffer 1
head back home…

definitions:
buffer 2 will always play the same section of buffer 1, as defined by your original loop. this is a ā€œdesign choiceā€, but was really a happy accident. I think it lends itself to flexible improvisation, but if you don’t want it then just lower the volume of buffer 2 in the params.

in fact, do that now for me? while you’re at it, reset buffer 1’s speed to 1.0? it’ll help make this next section clearer…

cool, tyty! you should now only hear buffer 1 at its original speed.

S(tart) + E(nd):
S is mapped to encoder 2, E is mapped to encoder 3. change S + E a little bit. you can make really small windows for glitchier sounds, or you can navigate past your original loop. buffer 1 has a total of 60 seconds to play with, which you can freely splice however you want.

OVER:
try navigating to a section of silence with S + E. set a ~three second loop window with the encoders. tap key 2 to engage overdub/overwrite. grey crane!
on screen, OVER determines the overdub/overwrite behavior. 0 = overdub, where all new audio is added to the existing. 1 = overwrite, where new audio replaces the existing.
leaving OVER at 0, plunk a few notes into the new loop and you should hear them return and layer. build a chord or weird arpeggiation!
when you’re sick of this, use S+E to navigate forward again to silence and start anew.

clear:
to clear the buffers and reset your S+E, hold key 1 for a sec. you should see E reset to 60. do that now for me?

delayzzz:
for this next section, try moving from one experiment to the next rather than clearing the buffers in between. it should show some neat tricks for organic improvisations. if things get weird, though, just hold key 1!

simple delay:
in params, set both speeds to 1.0, buffer 1’s playback volume to 0 and buffer 2’s playback volume to 1.
return home.
tap key 2, let a second or two pass, then tap key 2 again. this engages OVERwrite/dub
set OVER to 0.25.
play some notes and they should bounce back as decaying echoes.

warble delay:
while playing, tap key 3 to introduce small pitch bumps in the delay line. these little warbles are really noticeable with sustained or long-decay notes.
key 3 minimally affects buffer 1’s speed, like pushing your finger into moving tape. since these changes record onto buffer 1, buffer 2 plays them back as performed!

pitched delay:
while playing, head to params and muck with buffer 2’s speed.
since we’re writing into buffer 1 and reading with buffer 2, you get a pitched delay that never requires adjustment. ooooo.

digital weirdness:
while playing, head to params and muck with buffer 1’s speed.
speed changes while recording makes for really weird and digital artifacts.

digital weirdness part 2:
in params, reset both buffers’ speed to 1.0 and change key 3’s behavior to ā€˜1’, which will halve the speed of buffer 1 for as long as you’re holding key 3. ā€˜2’ will double it!
while playing, hold key 3 to pitch down, which should make for some neat effects.
to get really weird, set buffer 1’s speed to 2.0 in params and try key 3 (either in ā€˜1’ or ā€˜0’ mode…both can get great results).
[fun tip: halving and doubling are relative to buffer 1’s param speed, so you can get 4x speed by setting buffer 1’s param speed to 2x and setting key 3 to double mode. or get 0.125x speed by setting buffer 1’s param speed to 0.25x and setting key 3 to half mode!]

now that we’ve explored key 3, you can also use it as a performative tool when doing standard loops!

15 Likes

Awesome! I saw the video posted last night. I’ll give this a look. On a cursory glance, the first thing to jump out:

    if speedlist[params:get("speed buffer 1")] > 1.99 then
      ray = speedlist[params:get("speed buffer 1")] + (math.random(-15,15)/1000)
    elseif speedlist[params:get("speed buffer 1")] < 2 and speedlist[params:get("speed buffer 1")] >= 1.0 then
      ray = speedlist[params:get("speed buffer 1")] + (math.random(-10,10)/1000)
    elseif speedlist[params:get("speed buffer 1")] <= 0.99 and speedlist[params:get("speed buffer 1")] >= 0.50 then
      ray = speedlist[params:get("speed buffer 1")] + (math.random(-4,5)/1000)
    elseif speedlist[params:get("speed buffer 1")] < 0.50 then
      ray = speedlist[params:get("speed buffer 1")] + (math.random(-2,2)/1000)
    end

First, I would probably set a local variable to speedlist[params:get("speed buffer 1")] so that I wouldn’t have to keep typing that monster. Something like bufSpeed1 = speedlist[params:get("speed buffer 1")]

Next, you’re doing too many checks. Look at the first if, elseif pair.

if bufSpeed1 > 1.99, do x
elseif bufSpeed1 >= 1.0 do y

would accomplish the same thing. You don’t need to additionally check if the value is less than 2, since we know that it’s not greater than 1.99. It’s also safer. For instance, if the speed is 0.999, it won’t satisfy >= 1.0 or <= 0.99, which come next. Additionally, the last statement can be else instead of elseif. So just clean it up as something like:

local bufSpeed1 = speedlist[params:get("speed buffer 1")]
if bufSpeed1 > 1.99 do x
elseif bufSpeed1 >= 1.0 do y
elseif bufSpeed1 >= 0.50 do z
else do the last thing
end
2 Likes

Next thing to jump out:

  if crane_redraw == 1 and crane2_redraw == 0 then
    crane()
    screen.update()
  elseif crane_redraw == 0 and crane2_redraw == 0 then
    screen.update()
  elseif crane_redraw == 1 and crane2_redraw == 1 then
    crane2()
    screen.update()
  elseif crane_redraw == 0 and crane2_redraw == 0 then
    screen.update()
  end

screen.update() is called in all four paths, so no need to call it in each separate one. Simply put it after the if statement. Something like:

  if crane_redraw == 1 and crane2_redraw == 0 then
    crane()
    
  elseif crane_redraw == 0 and crane2_redraw == 0 then

  elseif crane_redraw == 1 and crane2_redraw == 1 then
    crane2()
  elseif crane_redraw == 0 and crane2_redraw == 0 then

  end
  screen.update()

Now, you should notice that two of those are empty! We could cut that down to:

  if crane_redraw == 1 and crane2_redraw == 0 then
    crane()
  elseif crane_redraw == 1 and crane2_redraw == 1 then
    crane2()
  end
  screen.update()

Notice that both statements check if crane_redraw == 1. We could do a nested if instead:

  if crane_redraw == 1 then
     if crane2_redraw == 0 
         crane()
     else 
         crane2()
     end
  end
  screen.update()
6 Likes

played around with cranes tonight - really fun once I got the hang of it (not all the way there yet, admittedly). I should have started with simpler loops/lines, but to clarify, buffer 2 is what is initially recorded and buffer 1 is anything recorded over (0) that?

looking forward to seeing this in an official norns update at some point!

(I forgot about this thread and should have posted channel changer here before sending a pr to dust…)

3 Likes

you’re a brave soul indeed.

updated the gist script + link to reflect new features, some better code (thank you @trickyflemming + @Galapagoose ) , some behavior fixes.

cranes should now function more straightforwardly at first:
start by hitting key 2, playing something, hitting key 2 again to loop.
what you’re hearing is buffer 1.
S1 + E1 loop points are for buffer 1. encoders 2 + 3.
head to params and set buffer 2’s speed and volume. maybe key 3’s behavior.
go back to main interface.
hold key 1 to toggle S2 + E2 loop points for buffer 2. encoders 2+ 3.
key 3 is momentary varispeed control of buffer 1.

hitting key 2 again will turn on overdub/overwrite.
this writes into buffer 1. buffer 2 remains a playhead.
I like setting buffer 2 playback to 0.5 or 2 while recording into buffer 1 for pitched delays.

loop points can extend anywhere along the 60 second digital tape for quick smears of sound.
I like setting buffer 1 to new territory while buffer 2 loops and making a new loop in buffer 1 that buffer 2 can find later.

clear tape by holding key 3 and then key 1 (@glia, this one was for u)

12 Likes

I added a lot of things since last time so, again, general coding advice / suggestions are welcome.

@okyeron

3 Likes

I played around with Cranes last night and wow, it’s really awesome! Fantastic work! Thank you : )

2 Likes
function blinker()
  if blink == 0 then blink = 4
  elseif blink == 4 then blink = 15
  elseif blink == 15 then blink = 0 end
  return blink
end

Instead of this if tree, you could make a blinkBrightness array and use a modulo increment variable to cycle through the values. It would be cleaner and easier to expand or change values.


if n == 1 and z == 1 then altControls = 1 
  elseif n == 1 and z == 0 then altControls = 0
end

Here, you could just check if n == 1 and set altControls to z.


function noteOnRoll()
  local Test = math.random(100)
  if Test >= noteOnProb then return 0
  else return 1
  end
end

I’m not sure if this would work in Lua, but I would just rewrite this as:

function noteOnRoll()
  return math.random(100) < noteOnProb
end

    local Test = math.random(100) 
    if Test >= Prob[newNote][currentNote] then
      currentNote = currentNote+1
      if currentNote > 7 then 
        currentNote = 1 
      end
      x = false
    else x = true 
    end

Here, you could probably elimnate Test. Instead, set x from the comparison and use only one if statement instead of if-else.


Just a quick lookover. Let me know if there’s a specific section that you want looked at.

please share any results! either dm or in the latest tacks thread! would love to hear

1 Like

Thank you I will try to correct those parts. Is there any naming standard (function, variables) that I am not following? What about the global code’s organisation?

Here’s something I’ve been working on:

break_step
Sample based step sequencer with preset patterns from well known breakbeats
based-on/extended-from jah/step.lua
requires grid

Plays with reading/writing from files and params

8 Likes

GENMDM sequencer
could be general 10-track MIDI sequencer

I’ve been working on a MIDI sequencer on norns to control a GENMDM interface (Sega Genesis with USB MIDI interface by little-scale). This particular script is customized to control the Genesis sound chip but it could easily be altered to control any MIDI synth/multi-synth setup. I’m curious if people could make suggestions on how to make the script more robust/flexible. Also, it seems like there are going to be a bunch of sequencers for norns and I wonder how all the different versions can learn from each other.

2 Likes

This thread doesn’t look super active anymore, but if anyone has feedback for this polyrhythmic sampler, I will gladly take it. Trying to get this cleaned up, optimized, and totally functioning before I push it to dust.

https://github.com/Gittifer/LUA/blob/master/polygrid.lua

3 Likes

Getting some lua errors with any selected ā€œonā€ row , all the other grid keys on that row (except 16) will throw the error

lua:
/home/we/dust/scripts/gittifer/polygrid.lua:146: attempt to index a nil value (field '?')
stack traceback:
/home/we/dust/scripts/gittifer/polygrid.lua:146: in function 'gridkey'

@okyeron I think this happens because norns is trying to turn a note on that doesn’t exist. As far as I know it doesn’t effect functionality, but I haven’t found a solution yet.

I can’t be the first person to have written an app where a ball bounces around the grid. I’m sure there’s more elegant ways to code this and I’d love suggestions on improving the code; and on the to-dos - adding sound and multiple balls.

vector drone machine i’ve been working on

edit:
sorry this post was moved. wasn’t aware of the code review thread. i can update later with a gist link.

feedback i’m looking for:

  • correct usage of the PolySub engine here in regards to number of voices
  • any advice on the object oriented approach here would be good
  • general feedback is appreciated. sorry if the code is a little messy i haven’t done a full cleanup yet


https://gist.github.com/alpha-cactus/b132cb13776d931160bfadcefd6068e9

10 Likes