Norns: development

Thanks! It didn’t occur to me to invoke an external command line tool from Lua. I suppose I could do this with ffmpeg as well, but the sox API looks like it might be bit more friendly.

2 Likes

Sox is prolly better than ffmpeg. There’s also ecasound, which is like a whole DAW with a command line interface! That could be fun.

4 Likes

So…I have a completely new rewrite of FM7. It’s so different I’m labeling it version 2.0. It has a large number of requirements, including a grid and an arc, optionally a MIDI keyboard. I would like to make the code more modular and detect if there is an arc connected, then change how the encoders on the Norns control parameters.

I tried the pattern I saw in other scripts which is basically these two lines

a = arc.connect()
if a then special_arc_function() end

But with my script this block is always true even when an arc is not connected. How can I detect if a device is present?

2 Likes

Try looking at the tables arc.devices or grid.devices

similarly I have used this to get a list of attached hid’s

function init()
  for v in pairs(hid.devices) do
    print (v .. ": " .. hid.devices[v].name)
  end 
end
2 Likes

Oh neat. It seems like the table for a connected arc persists across disconnects but when a device is removed, the key a.device is nil.

1 Like

I found a bug in how engines are loaded while debugging the Gemini script.

Given a script with an engine is loaded successfully, loading a different script that has an error on the Lua side (in this case an initialization ordering error) causes the previous engine to be loaded.

This is reproducible by loading the FM7 library, followed by loading this commit from the Gemini library, which will throw an exception when an Arc is connected. The third step is to apply my diff to fix the initial exception via Maiden, save and load the script. This will cause SuperCollider to throw an exception expressing a missing engine from Gemini. This is followed by something (perhaps this is the root cause?) loading the last successful engine into this script, exposing all parameters and polls then initializing the “failed” script a second time. Rebooting the device “fixed” this bug. Is it worth an issue on github to hunt this down?

Yes. It sounds like something in the script.lua domain

1 Like

Good evening!

I lost about 30 minutes feeling like a crazy person because I was editing a library that is included in the top level script, then loading the script to the device through Maiden. This did not include the changes I made to the library, even though I was looking right at the code. IIRC there is some kind of caching thing when using the Lua require 'foo' syntax. Is that correct?

Is there a way to develop libraries on the device that does not require rebooting the device to see changes?

Yeah, require caches the file. You can use include('foo') instead. I asked the exact same thing very recently so seems to be a common question

1 Like

require's caching behavior is a lua thing, not a norns thing. we use it in core libs, but not for any great reason

we added include to norns, which doesn’t cache and searches muliple locations starting with the path to the current script.
https://monome.org/docs/norns/script-reference/#using-libraries

2 Likes

Encoder 2 in a FM7 broke at a seemingly random moment. I have the callback firing only when a key toggle is true, which seems to continue to work (when the toggle is false I see no exceptions) but when the toggle is true I get the following exception

stack traceback:

/home/we/norns/lua/core/encoders.lua:57: in function 'core/encoders.process'

lua: /home/we/norns/lua/core/encoders.lua:57: attempt to call a number value (field 'callback')

I noticed this about 3 minutes into a live session I began recording with the internal TAPE feature. Prior to recording, the encoder worked as expected. The encoder changes a parameter (the c:m ratio in this case) so I checked the system parameter screen and I could continue to change the value from there. I haven’t tried to reproduce this yet. Reloading the script “fixed” this state.

update: I can reproduce this. Each time the record action of the TAPE feature is called, my encoder function breaks in the same way. Details on Github.

1 Like

How does norns do GPIO? I’ve looked through gpio.c, and it looks like the knobs show up as an input device. How do they end up in /dev/input?

there’s an overlay that defines the GPIOs

OK, thanks. Looks like I’m in a bit over my head! :sweat_smile:

yeah, it’s not a two-way binding. when you load a script, the script loading module checks if engine.name is defined. if it is, it loads that engine. when the engine is done loading, low-level lua callback is fired and the script’s init is run.

when i made the low-level stuff i thought a single lua script might want to switch engines. but when the script management stuff was built up, the assumption was made that this will not happen. it is one of many undocumented assumptions that you will run into if you start to deeply interrogate the “middle” layers of the lua system. (and to be clear, i’m glad you are doing this!)

but sure, engine.load(name) method should update engine.name field, if it disagrees with the argument.

the .free method for engine resources should free all scsynth resources and stop all sound. if it doesnt, it’s an error. i just quickly tested this with TestSine and seems fine to me.

my test:

new test engine for switching:

Engine_TestNoise.sc

// CroneEngine_TestNoise
// variante of TestSine, for switching

// Inherit methods from CroneEngine
Engine_TestNoise : CroneEngine {
	var <synth;

	*new { arg context, doneCallback;
		^super.new(context, doneCallback);
	}

	alloc {
		synth = {
			arg out, hz=220, amp=0.5, amplag=0.02, hzlag=0.01;
			var amp_, hz_;
			amp_ = Lag.ar(K2A.ar(amp), amplag);
			hz_ = Lag.ar(K2A.ar(hz), hzlag);
			Out.ar(out, (LPF.ar(WhiteNoise.ar, hz_) * amp_).dup);
		}.play(args: [\out, context.out_b], target: context.xg);
		this.addCommand("hz", "f", { arg msg;
			synth.set(\hz, msg[1]);
		});

		this.addCommand("amp", "f", { arg msg;
			synth.set(\amp, msg[1]);
		});
	}
	free {
		synth.free;
	}
}

test script:
switch_engines.lua

function init()
end

function key(n,z)
   if z == 1 then
      if n == 2 then
	 engine.load('TestSine')
      end
      if n == 3 then
	 engine.load('TestNoise')
      end
   end
end

result: each engine correctly frees its single running Synth when the other engine is loaded. (the mechanism that calls .free on the last engine is on the supercollider side, in CroneEngine iirc.

(if you have softcut running with engine input, or reverb you may of course still hear sound from the old engine that has been captured in a buffer or something?)

(i don’t know about Why. it’s a very old “hello world” that shouldn’t even be availble in any repo and probably has outdated syntax. maybe it was just lying around on your norns’s filesystem.)

that’s an internal method for the PolySub engine, which is polyphonic.


aside: i’m wary of making too many standards for engines. standards are assumptions that change how you can make music. e.g. for some applications, you want “polyphony” to be implicit voice assignment with a single command. for others, you want it to mean a big array of oscillators that are manipulated independently.

i think we should try to preserve some of the freedom of SuperCollider and of digital instrument design in general when thinking about what engines are or should do. engine commands can take any number of arguments including strings, by design. youlu should be able to implement whatever you want in an engine, including toy languages, modular systems, or even things that execute arbitrary sclang code chunks.

not trying to start a big debate on this, BTW. obviously there are benefits to having “classes” of engines with common interface. just want to point out the other side of it.

there is more detailed discussion on this issue, mentioned above - i even did an example of how we could have an abstract class for MidiPolyEngine or whatever to enforce common API.

i’ve also just transferred the issue to the norns repo.

6 Likes

I totally agree with keeping this freedom in engine APIs as I’ve been doing some fairly experimental generative work on Norns where the conventions we’d see in synths and FX don’t apply.

As script and engine writers, we could create sets of conventions, that we “tag” engines with like poly-synth, mono-synth etc, that we treat sort of like a Java interface or whatever, so a script like Orca could know that every engine tagged orca uses .noteOn() and .noteOff() — but I wouldn’t want the Norns dev environment make every engine hold to a format like that.

2 Likes

Another approach I was thinking about is some lua “middleware” that introspects engines for various common commands like hz and noteOn and then dispatches commands to the engine accordingly.

So as a script author you’d just write synth.playNote(midiNoteNum) and the details would get worked out in the middleware. We’d rely on duck typing of checking for available commands rather than testing on engine names, which gets fragile anyway.

1 Like

that makes sense.

for technical reasons i would change the focus a little bit though, and actually do the introspection on the SuperCollider side:

  • SC classes can be defined all over the file system, Crone knows about all of them
  • SC has powerful class introspection methods built in (e.g. can just loop over CroneEngine.allSubClasses)
  • (hm actually there is a bit of a problem in that engine commands are added dynamically at allocation time. but SC has a better chance of coping with that, then lua does.)

problem: communication glue between SC and lua is actually kind of brittle since it’s all hardcoded in C.

potential answer: Crone should perform introspection and just write metadata to disk as a lua file on SC startup.


in fact, it has been suggested in Slack conversations (and i agree) that a potentially complex gordian-knot of metaprogramming tasks can be cut just by accompanying each engine class with an (optional?) handmade lua file containing metadata for that engine.

3 Likes

Should I throw a PR up for this?

So I think there’s a bug somewhere but I’ve not been able to reproduce it.

At some point in testing @neauoire’s engine switching script I ended up with Why continuing to play after switching. Then in additional testing, maiden output for engine loading was being printed twice in REPL. A full reboot got me back to normal again.

I’ll need to do some more testing and see if this pops up again.

ok well, i can indeed get TestSine to be “stuck” in the above example, eventually, by mashing both buttons as fast as possible.

this isn’t super surprising: even though engine.load has always been available in or outside of scripts, it has never been used in this fashion and the focus has been on using 1 script -> 1 engine assumption. @tehn may even be annoyed that i’m encouraging this other usage at all.

bear in mind that these things are asynchronous. for this to be robust i think you would (at least) not want to allow an engine load request to go out before the callback from the previous request

Should I throw a PR up for this?

sure, but i imagine that there might be a few changes needed to make things robust if this is going to be a common usage. this might mean changes to the interactions between Crone and the CroneEngine superclass when loading / freeing engines, which are non-trivial and involve some weird multithreading:

[ https://github.com/monome/norns/blob/master/sc/core/Crone.sc#L104 ]
[ https://github.com/monome/norns/blob/master/sc/core/CroneEngine.sc#L20 ]
[ https://github.com/monome/norns/blob/master/sc/core/CroneEngine.sc#L42 ]

(btw, we are way into “Development” territory so i am likely gonna move these last few posts)

2 Likes