lfo lib

discussion of the lfo library on norns (requires norns: update 220802 or beyond)

LFOs for general-purpose scripting
inspired by contributions from @markeats (changes), @justmat (hnds, first introduced with Otis), and @sixolet (toolkit). added by @dndrks + @sixolet, with improvements by @Dewb.


not sure if this is the right place to ask (mods, feel free to move as necessary if not), but I didn’t see a dedicated thread for the LFO lib. I’m running a script (schicksalslied) with several LFOs created with the library, and I’ve noticed that if I’ve got, say, 3 running and then introduce a fourth, the first three will sometimes just forget what they’re doing. They stop running, and the parameter they’re set to modulate just acts as though there wasn’t an LFO on it (i.e., returns to the value it was set to prior to introducing the LFO and stays there). If I nudge a parameter on one of the stopped LFOs (e.g., depth), it comes back to life. Any idea why this might be?


i have this same exact behavior in a script of mine(i wasn’t sure if it was intended behavior or not, nor did i find time to trace exactly what was going on), but i got 6 lfos, not necessarily all on at once, but in order to reliably change the shape of one, i had to keep resetting the ‘depth’ parameter, and this was the only way to get reliable behavior every single time i wanted to stop/start/change/etc.:

if hsel==2 then 
   poslfoz[vsel]:set('max', util.round(lasknwnpos[vsel]/58.0,0.0001))
   plf[vsel]=util.wrap(plf[vsel]+1,0,3) poslfoz[vsel]:set('depth', 1.0)
   if plf[vsel] == 0 then poslfoz[vsel]:stop()
    elseif plf[vsel] == 1 then 
        poslfoz[vsel]:set('shape', 'random') lfprmult=0.25 poslfoz[vsel]:start()  
    elseif plf[vsel] == 2 then 
        poslfoz[vsel]:set('shape', 'saw') lfprmult=0.75 poslfoz[vsel]:start()
    elseif plf[vsel] == 3 then 
        poslfoz[vsel]:set('shape', 'sine') lfprmult=0.5 poslfoz[vsel]:start() 

also, if i could piggy-back one minor issue about lfos, too, i get the following error from my app:
“ERROR: paramset cannot nest GROUPs”
and i’m pretty sure it’s because i nested LFOs within a group(because LFOs work like a group), however, my app still works as expected: when i go into params, the LFOs are properly nested within the groups i wanted(looks pretty cool too :smile: → when i turn an lfo ‘off’ all the related params disappear, when on, they reappear all within that nested group :+1:) so i feel like this error is bogus… maybe norns-devs could consider silencing it or taking a further look?


@WilliamHazard + @rajaTheResidentAlien , hihi! hope all’s well with you both :slight_smile:

thanks so much for engaging the library in your scripts + for reporting back on weirdness!

i think it might be helpful to build a few test cases, so we can narrow things down to library-specific issues – here are two scripts which test well on my side!

lfo_test1.lua instantiates and controls 60 LFOs via the direct-address API, whereas lfo_test2.lua instantiates and controls 60 LFOs via the params API. they each use K3 to start/stop and K2 to randomize rate of each LFO.

@WilliamHazard, not seeing any LFO stalling with either script, but lmk if things test differently for you!

@rajaTheResidentAlien, i did find some shape trouble, tho not the same as you’re describing – basically, the square + random shapes don’t use the correct scaling. i can get that corrected, but happy to hear out any additional shape change issues you can repro!

LFOs can be nested as a group via my_lfo:add_params(id, separator_name, group_name). if a group_name is provided, then norns will attempt to group them with a > UI formatter.

lfo_test2.lua demonstrates one occurrence of a collision, where an all the lfos group is created to house the 60 LFOs, but then i also specify that LFO #60 should build a special nest for LFO 60 group. since the all the lfos group is still iterating when we hit the instruction to also build special nest for LFO 60, norns sends out the warning that it cannot nest groups – this is a heads up so you know that you won’t see special nest for LFO 60 > inside of the all the lfos group menu.

norns will build the LFO parameters you requested, though, with the fancy disappearing act. so, in essence: the parameter menu build process simply ignores the secondary group, since we’re already inside of a group, and builds the rest of the parameters requested.

hope this helps! please lmk where y’all end up :slight_smile:


Thanks so much for putting this together, Dan! Both tests scripts are working as expected on my end. I just checked, though, and even in schicksalslied, the listed values for the LFOs are changing, even when they’re actually stalled (i.e., I can see the values changing in params, but I can hear that they’ve stopped changing). When I just tested it, I initially turned up LFOs for FM index and a couple other FM parameters on a couple voices, and they were drifting in and out of overtones and noisiness nicely until I turned on a pan LFO for one of the voices, at which point all that pleasant timbral drifting ceased entirely. I think maybe a next step I’ll try is adding an engine to one of the LFO test scripts and seeing if the actual values are stalling, even though the displayed values are not (I don’t know if that’s even what’s really happening, but that’s the best way I can think of to describe it)

edit: on closer inspection, it seems like the LFOs aren’t actually stalling per se — they’re constraining their values without my telling them to. I took a little video that demonstrates this: LFO min is 1, max is 100, depth is 100%, offset is 0%, so the scaled value should be sweeping the full range from 1-100. But it’s only sweeping between 1 and about 2.5!

edit #2: I also just noticed that I think I’m doing the groups-within-groups thing in schicksalslied, as demonstrated in the second test script. I should fix that, but I also don’t see why that would elicit this constraining behavior. Very odd.


ayyy this is super helpful, thank you!!
any chance you can provide step-by-step repro instructions and/or a PSET file? i’m testing with schicksalslied, but only seeing full range of scaled motion. happy to dig in with some waypoints! :black_heart:


i think you’re actually good! your invocation doesn’t include any groups and i’m only seeing them if i have a lot going on in schicksalslied + use maiden’s ‘run script’ shortcut to push thru a quick rerun. so i think it’s just cruft which doesn’t seem to happen if i load from the SELECT menu on norns directly.

1 Like

Yes! The way I just elicited the range issue in schicksalslied was (starting from the script ui):

  1. type in a word (I used “mid”)
  2. hit enter
  3. hit K3 to turn on engine sequencing
  4. head over to params
  5. in the LiedMotor params group, turn up sinsin_amp a bit, so it’s audible (I turned it up to 0.07)
  6. head over to LFOs
  7. turn on sinsin index LFO, depth to 50%, baseline from center, lfo rate 1
  8. turn on sinsin modnum LFO, depth to 100%, baseline from center, lfo rate 1.5
  9. turn on sinsin modeno LFO, depth to 100%, baseline from center, lfo rate 2
  10. at this point, the range issue should be apparent in the index LFO
  11. nudging any of the values of the index LFO should cause it to “wake up,” remember what it’s supposed to be doing, and correct the range issue that appeared
  12. but now the range issue will appear in the modnum and modeno LFOs
  13. nudging their values will fix it
  14. but then the range issue will appear in the index LFO again
1 Like

raaaaad, this was an excellent test case! thank u thank u thank u!!!
totally repro’d on the current build, but is working with these changes: lfo fixes + improvements by dndrks · Pull Request #1630 · monome/norns · GitHub

if you’re feeling adventurous + are on 221214, it’d be rad to grab confirmation, if you’re willing to replace your existing norns/lua/lib/lfo.lua file with this one: lfo.lua (16.9 KB)


confirmed! Thanks so much, Dan. The new lfo.lua file has things working perfectly, without any scaling issues.

1 Like

siiick, thanks so much for the quick confirmation!!

if you want, you should be able to just rename that new lfo.lua file to something like lied_lfo.lua and put it into schicksalslied/lib, then swap this line out for _lfos = include 'lib/lied_lfo' and get those changes folded into your script sooner than the next release :slight_smile:

1 Like

done! Thanks again, Dan. This is lovely

1 Like

Hi, I had a look at the lfo docs. When I change the lfo shape to random or square in the first lfo script example (“invoking an LFO”) I get the error listed below. Sine and saw shape work without problems. Am I doing something wrong?

/home/we/norns/lua/core/clock.lua:64: /home/we/norns/lua/lib/lfo.lua:259: attempt to compare nil with number
stack traceback:
[C]: in function ‘error’
/home/we/norns/lua/core/clock.lua:64: in function ‘core/clock.resume’

hihi! no you’re good, there was a bug which has been fixed and will be included in the next update: lfo fixes + improvements by dndrks · Pull Request #1630 · monome/norns · GitHub