Norns: scripting

Kinda stumped - i think there’s some syntax I don’t understand yet.

The new version of playfair is using this new Beatclock library. This works great as is for MIDI out, but I can’t figure out how to access the MIDI IN data - since that’s being handled in the beatclock library (instead of my script)

The basic aspects are pasted below (as copied from playfair). What am I missing to get the incoming midi data?

local BeatClock = require 'beatclock'
local clk = BeatClock.new()

midi.add = function(dev)
  dev.event = clk.process_midi
  print("midi device added", dev.id, dev.name)
end

please see lib/beatclock.lua for more on what it’s doing

EDIT: Is this what I’m missing? If so - why do I need to copy this into my script from what already exists in the beatclock lib?

clk.process_midi = function (data)
 -- do stuff
end

I just finished a minimal MPE example. I was surprised how easy it was to get working and how well it works (tried it with my Linnstrument without any issues). It’s not really in a state where I’d want to push it to dust (a more interesting synth engine would be in order), but I’m going to be on the road for two weeks and I’m not sure when I’m going to do more work on it, so I thought I’d already share it here in case anyone is interested: https://gist.github.com/x2mirko/8e35b6825d04e3d925817f288aab16c4

7 Likes

i’m sorry to say this but i’d suggest holding off until i finish the MIDI study. there’s a bunch to explain.

@x2mirko fantastic!!

4 Likes

Understood. Thanks.

Another aspect that had me confused was calculating incoming clock beats per minute (bpm) - which I couldn’t figure out at all.

@okyeron
midi is fairly straightforward…

midi.add = function (dev) … blah
is defining a function you wish to be called when a midi devices is added (or reconnected to a script)

in this particular case, all that is happening is its setting the ‘event callback function’ on the midi device (dev.event = )

so dev.event is a function that is called for each midi event.

(if you look in norns/lua/midi , you can see this happening)

so in playfair what is happening is…

when we get a new midi device (added/reconnected) it is telling it to call used beatclocks.process_midi as the event callback , which in turn will process the midi (which might be a midi clock tick)

i read this as , how do you get your script to be able to process midi

simple… define your own event callback handler

so to give you the idea you want (untested/compiled :wink: ) its something like


my_midi_hander = function(data)
 -- do your midi handling here

 -- then call the beat clock to update the beat clock 
  clk.process_midi(data)
end

midi.add = function(dev)
  dev.event = my_midi_hander
end

so what this does it… you define midi.add, so you can add your own handler
then in your handler you then call the process_miidi of clk as well

again, this is a bit like the last study, it comes down to being able to define function pointers/callbacks. (sorry dont know what lua calls them :slight_smile: )

Ive not had a close look, but given tick() is being called by both the midi handler and the internal metro,
Id assume the internal metro is being set at 24ppqn (just like [midi clock]) (https://en.wikipedia.org/wiki/MIDI_beat_clock) , so this is why the ‘ticks per steps’ is defaulted to 6, (6*4 = 24)

anyway, hopefully this will give you a few pointers till is all explained better :slight_smile:

(fwiw , I suspect many are going to struggle with extensive use of callback functions… they are not the easiest of things to trace through if your not familiar with them)

1 Like

main reason i suggested holding off is that we’re refining the technique for interacting with midi. so heads up that things will change a bit (for the better)

7 Likes

That makes me very happy :slight_smile: looking forward to the study!

I would like to use my MIDI Fighter Twister as a controller for norns. After the readings from norns repo from GitHub MIDI management #454 and feature request: midi CC for toplevel system audio #465, knowing that the MIDI implementation is working in progress. I wanna experiment it a bit on this weekends, maybe try to make Awake script able to receive CC messages.

Where to start? Any reference and documentation (Lua) I should read before? Or I better keep away until the next official update? Thanks and please share your thoughts.

the last release add CC mapping - no?

1 Like

Oh thanks @TheTechnobear! :sweat_smile:

When you get working (not if) please share a video! The MIDI Fighter Twister is a compelling little bit of kit that I’m dying to see well-integrated with Norns in performance settings. Thanks!

1 Like

If the MIDI fighter twister is class compliant, it should just work. I just got a faderfox pc4 a couple days ago and found the cc mapping/ setup to be super easy.

4 Likes

It is class compliant :slight_smile: The default settings (and the multiple pages) should work out of the box.

1 Like

Is there a doc or github issue somewhere that explains everything that goes into a well-behaving engine. I’m familiar enough with supercollider, but am I have trouble getting my scripts to cooperate with norns. An example of something I’m working on: The engine runs, but the commands are unresponsive, do lua engine commands only work if on arg and not var?

// CroneEngine_Klank
// Stolen from example 1.18 in The SuperCollider Book
Engine_Klank : CroneEngine {
	var <synth;

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

	alloc {
		synth = {
			var scale, specs, freqs, amps, rings,
			    numRes = 5, bells = 4, pan, dur = 1/6;
				scale = [57, 60, 62, 64, 67, 69].midicps;
				Mix.fill(bells, {
					freqs = Array.fill(numRes, {rrand(1, 15)*(scale.choose)});
					amps = Array.fill(numRes, {rrand(0.3, 0.9)});
					rings = Array.fill(numRes, {rrand(1.0, 4.0)});
					specs = [freqs, amps, rings].round(0.01);
						//specs.postln;
					pan = (LFNoise1.kr(rrand(3,6))*2).softclip;
					Pan2.ar(Klank.ar(`specs,
						Dust.ar(dur, 0.03)), // creates impulse 0-(+1) act as trigger
						pan)
				})
}.play;

		this.addCommand("numRes", "r", { arg msg;
			synth.set(\numRes, msg[1]);
		});

		this.addCommand("bells", "b", { arg msg;
			synth.set(\bells, msg[1]);
		});

		this.addCommand("density", "d", { arg msg;
			synth.set(\dur, msg[1]);
		});
	}

	free {
		synth.free;
	}
}
1 Like

In order to parameterize SynthDefs you need to specify these parameters (in SC lingo, controls) as synthdef args. Also, format spec (second argument of addCommand) should be ”f” for float in your case.

2 Likes

I took a closer look at your SynthDef. It should be noted that numRes and bells cannot actually be modulated, due to the SynthDef being compiled. SClang simply works differently when it’s executed in a SynthDef UgenGraphFunc. (And now we’re getting into SuperCollider peculiarity territories…)

2 Likes

There’s unfortunately not a lot of docs on the engine parts. It will come eventually. :slight_smile:

Look at TestSine as a reference of a simple, well behaving engine in the meantime, and review the core Crone SC classes. They’re quite simple if you know SC.

3 Likes

@jah Thank you, this was very helpful! I actually based this off of TestSine, but had to make a lot of assumptions based on what I saw. Are there any solutions I could explore to adapt this script, or should I consider making something original with the klank UGEN? For now I’ll be looking through the Crone SC classes.

1 Like

I’m sure you can get a simple, persistent klank engine running with just a ”density” command (remove ”numRes” and ”bells” commands, they’re better of as regular/static vars since you cannot modulate them). You might need to set the second argument to addCommand to ”f” for it to work.

FWIW I have an engine class in the works (dust/lib/sc/abstractions/CroneGenEngine.sc) that introspects a SynthDef or UgenGraphFunc and propagates arguments as commands removing some of the boilerplate code. But I think it might be better to base simple stuff of TestSine to get the hang of it.

2 Likes

Here’s some troubleshooting tips that may help people working on engines.

I was getting error: AUDIO ENGINE for one of the engines I was working on. To troubleshoot, I found out I can ssh into norns and run:

$ sclang [enter]
sc3> Engine_Example.new(Crone.context)

That starts up the named engine, and in my case ran some initialization code that helped me determine what was going on.

While developing and ssh'd in, it was helpful to restart specific processes and view their logs directly:

~/norns/stop.sh
cd ~/norns
./crone.sh # for SuperCollider logs
./maiden.sh # for lua logs
6 Likes