/grid/led/level/map x_off y_off l[64] and ring/map n l[64] equivalents on Norns

Hi
I’m new to Norns. Previously i’d had a little experience using arc and grid with my Kyma system - passing osc (through Osculator) back and forth. The messages I commonly used were the [64] member arrays for addressing the intensity of each led:

/grid/led/level/map x_off y_off l[64]
and
ring/map n l[64]

These seemingly have no equivalent in Norns territory, where i’m restricted to using g:led and a:led for single leds ,the g/a:all for setting everything to the same or, for Arc only, the a:segment message for a range of segments.

Is there a reason that the /map approach isn’t available in Norns? I found it really useful.

I’m sure i can figure out a way round it but curious as to the thinking behind it.

Many thanks
Geoff

IIRC, grid:refresh() is sending the map command

grid:led is setting individual pixels, but just in the buffer - it’s not immediately set - until you send a refresh.

arc:range is missing. I can’t remember why I left that out when doing the initial arc integration.

and just to add to this question please. I think the following script (albeit convoulted) should work to map the argument coming in as osc message /grid/1/1 to gridx1y1 which itself should determine the intensity in g:led(1,1,gridx1y1) but this doesn’t seem to work. Some pointers of where i’m going wrong would be greatly appreciated to set me off…still figuring out osc in Norns a bit!

function init()
gridx1y1 = 0
end

function osc_in(path, args, from)
if path == “/grid/1/1” then
gridx1y1 = args[1]
elseif path == “/grid/2/1” then
gridx2y1 = args[1]
else
print(path)
tab.print(args)
end
print("osc from " … from[1] … " port " … from[2])

osc.event = osc_in
end

g = grid.connect()
g.key = function(gridx,gridy,gridz)
g:led(1,1,gridx1y1)
g:refresh()
dest = {“192.168.1.58”,8000}
osc.send(dest, “/gkey”, {gridx, gridy, gridz})
end

Thanks - so the grid:refresh() can take 64 (or 128) arguments?

Here is a study on using Grid with Norns.

Thank you, I have read that a few times (sorry if I’m slow to learn). It’s how I link it up with Osc that I’m really struggling with.

1 Like

grid:refresh() takes no arguments. It will update whatever is in the buffer.

The most likely reason is just that the existing code has been good enough for most applications, so there hasn’t been enough user or developer interest in this functionality for it to achieve liftoff. I’m happy to advise on how implementing this would work, but the easiest way to test and iterate on this kind of thing is to modify and rebuild the norns software (really pretty painless!) and confirm that it does what you want, then make a pull request so it’s included in the next norns update. Without compiling the norns software stack from source you’d otherwise be stuck waiting for that future update.

Roughly the procedure here would be

  • add a routine to matron/src/device/device_monome.c for updating a whole quad from a byte array
  • plumb that through to the Lua layer by adding some code in matron/src/weaver.c, like here is where the existing grid routines are registered. Implementations of these functions and how they pass data to/from Lua are here.
  • add it to the Lua grid library, following the example of the other grid routines here.

If there’s interest I can elaborate on that further, I think it’s generally worth documenting how to modify norns itself (possible an auxiliary study?), some existing notes on the build environment and such here.


It looks to me like this code assigns the osc.event callback to your osc_in function inside the osc_in function itself, which means that it will never be called – typically you would assign a handler like this inside the init() function, or just “freestanding” in the script. Note that you can preserve code formatting by wrapping your code in triple-backquotes ```, or by using the “Preformatted text (Ctrl+Shift+C)” button in the comment editor.

Back at computer now and wanted to add a few points:

grid:refresh() from norns sends the whole led buffer using the the equivalent of /grid/led/level/map - so pretty much everything from norns uses the map approach (even if just setting one led and then sending refresh, it’s sending the full map of data).

What’s missing if anything from the monome serial spec are /prefix/led/level/row and /prefix/led/level/col but those seemed to be left out as using map was efficient enough.

So… for your query something like this might work for you?

local dest_ip = "10.0.1.26"
local dest_port = "9000"

function init()
	g = grid.connect()
	g.key = grid_key
	osc.event = osc_in
end

function osc_in (path, args, from)
  local k
  local pathxy = {}
  for k in string.gmatch(path, "%S+") do
    table.insert(pathxy,k)
  end
  --print (pathxy[1], pathxy[2], pathxy[3], math.floor(args[1]))
  oscpath = pathxy[1]
  if oscpath == "/grid/key" then
    x = math.floor(pathxy[2])
    y = math.floor(pathxy[3])
    s = math.floor(args[1])

    print("x:"..x,"y:"..y,"s:"..s)

    if s > 0 then
      g:led(x, y, 15)
    else
      g:led(x, y, 0)
    end
    g:refresh()
    
    --osc.send(oscdestip, path, args) 
  end
end


function grid_key(x, y, z)
  if z > 0 then
    g:led(x, y, 15)
  else
    g:led(x, y, 0)
  end
  g:refresh()

  osc.send({dest_ip, dest_port}, "/gkey", {x, y, z})
  print("sent:", dest_ip, dest_port, "/gkey", x, y, z)
end

EDIT: FWIW - I was not seeing messages in OSCulator until I opened the “parameters” window (not super famiilar with OSCulator, so migth be user error on my part)

1 Like

ok just to clarify:

/map is/was an serialosc OSC message that closely mirrors the actual serial protocol which is transmitted via USB. it evolved from the “optimized” method of sending a full row via serial (which was optimized for a particular LED driver that was no longer used after going var-bright) and the matching “col” which is actually less efficient than sending individual LED messages. the map serial message sends a block of LED data without individually specifying x/y data, so it saves a ton of serial bandwidth.

sidenote: do not try to refresh the grid by sending tons of single LED messages via serial. you can effectively do this in max/msp/etc by using tons of serlialosc /led messages and you will bottleneck your system.

the way around this in max/msp is to use javascript (hang on, wait for it) which maintains its own array of the LED data, then uses /map to copy the array data to a big OSC message to serialosc, typically on a timer. sound redundant???

norns is different. it doesn’t use serialosc. it uses libmonome, which is basically serialosc without the osc.

all norns has is an internal LED buffer, set by grid:led(x,y,i) and then grid:refresh() which checks for “dirty” bits and updates (sends serial, via map message) to those requiring update)

this, is infinitely better than any grid interface we’ve done before. it’s more efficient and takes care of the plumbing. however, there are actually some optimizations about how to treat this buffer/refresh pair for efficiency (ie, using dirty flags for your app and a clock timer for regular refresh… but that’s another study… and really nitpicky when it comes down to it… more of best practice)


what i think you’re looking for is a simple translation layer

given an incoming /map message of format

/map x_off y_off data[1..64]

(that’s 66 bytes)

just make a thing that turns that into grid:led() calls (below is psuedocode)

--- on osc/map
x_off = args[1]
y_off = args[2]
for x=1,8 do
  for y=1,8 do
    grid:led(x+x_off,y+y_off,args[x+y*8])
  end
end
grid:refresh()

the point being, i’m not sure why/when there’d be an incoming OSC message in map format. norns is really good at “building”/drawing grid pixel interfaces… so unless you’re simply doing a translation layer (for something that previously was getting sent to serialosc) i’d forgo even thinking about “map”

let me know if i can clarify further

9 Likes

Thanks so much @tehn and everyone else. Primarily I am trying to (at this stage at least) just build a translation layer to leave my grid and arc plugged into norns but use them direct with Kyma via osc using a simple script on norns. I’ll look at alternative programming methods after this.

Kyma is a bit funny however with the way it sends and receives osc messages however and I’ve found that using the 64 array/map messages is FAR easier then getting it to work with individual led messages. The 64 messages analogy also works really well for working with frame based data in Kyma.

I’ll use the info provided above and explore further - I really appreciate the help!

1 Like

Hmm, i’m struggling to get just the basics to work actually :frowning:

Where am I going wrong here please? (I can see the osc coming in ok to maiden but nothing happening with grid). Thanks so much!

g = grid.connect()
function osc_in(path, args, from)
if path == “/grid/led” then
grid:led(arg[1],arg[2],args[3])
g:refresh()
end
end
print("osc from " … from[1] … " port " … from[2])

osc.event = osc_in

Edit: I see that arg[1] and arg[2] should be args (with an ‘s’) I think. Is that it? (I’m away from computer/Norns now)

I think you also want g:led in place of grid:led. grid is a global table/object/library for interfacing with grids in general, while g refers to the specific grid that’s connected (since you’ve called g = grid.connect()).

You should be able to copy/paste the code I replied with above as a place to start. (I tested it and it was working for me here.)

Then try to modify that as needed perhaps?

Can you share more about this? I assume the base concept is instead of spamming redraw()/refresh() everywhere in your app you set a flag to dirty then inside redraw() check that flag?

1 Like

Sorry, I can’t get this to work but am sure it’s user error. I’ve loaded in your script verbatim, the only things i changed were the IP address details at the top to match mine.

I’m not 100% sure on the osc message that i should be sending in this script example however as i’m not clear on exactly what the script is doing. I’ve tried sending an osc message with /grid/key (with three arguments) as I think that’s what the script is expecting (?) but get the following error in maiden:


lua:

/home/we/dust/code/untitled1.lua:19: bad argument #1 to ‘floor’ (number expected, got nil)

stack traceback:

[C]: in function ‘math.floor’

/home/we/dust/code/untitled1.lua:19: in function ‘core/osc.event’

/home/we/norns/lua/core/osc.lua:98: in function </home/we/norns/lua/core/osc.lua:91>


bright = 15
show_x = 1
show_y = 1

function init()
  g = grid.connect()
  clock.run(grid_redraw_clock)
end

function grid_redraw_clock()
  while true do
    clock.sleep(1/30)
    if dirty==true then
      grid_redraw()
      dirty = false
    end
  end
end

function grid_redraw()
  g:clear()
  g:led(show_x,show_y,bright)
  g:refresh()
end

function g.key(x,y,z)
  if z==1 then
    show_x = x
    show_y = y
    dirty = true
  end
end

function enc(n,d)
  if n==3 then
    bright = util.clamp(bright+d,0,15)
    dirty = true
  end
end

this code is untested, please let me know if anyone spots typos or runs this successfully.

the approach:

  • all grid drawing happens in one function grid_redraw which is called only if dirty is true, meaning the grid doesn’t get redrawn if nothing in the data has changed.
  • dirty can be set to true by any other part of the script (ie, grid key, or an encoder turn, or a metro/clock, or incoming OSC, etc) so the data and UI are separated.
5 Likes

I’m trying this method but am getting the following error.
lua:

/home/we/norns/lua/core/grid.lua:94: bad argument #4 to ‘grid_set_led’ (number expected, got nil)

stack traceback:

[C]: in field ‘grid_set_led’

/home/we/norns/lua/core/grid.lua:94: in function ‘core/grid.led’

/home/we/norns/lua/core/vport.lua:5: in method ‘led’

/home/we/dust/code/SimpleOSC2.lua:15: in function ‘core/osc.event’

/home/we/norns/lua/core/osc.lua:98: in function </home/we/norns/lua/core/osc.lua:91>

The code i’m using is as follows, which is the same as yours, except i put g:led and g:refresh rather than grid:

g = grid.connect()

function osc_in(path, args, from)
if path == “/grid/map” then
x_off = args[1]
y_off = args[2]
for x=1,8 do
for y=1,8 do
g:led(x+x_off,y+y_off,args[x+y*8])
end
end
end
end
g:refresh()

osc.event = osc_in

My osc message is coming in as /grid/map with 66 arguments. Thanks so much!

Edit:
Ok, i don’t seem able to do any arithmetic inside the square brackets using plain numbers…so, just args[x] works, args [y] works as does args[x+y] but args[x+y*15] doesn’t and throws an error. I’ve tried args[(x+y)*8] also but no dice! How can I construct something so that 64 arguments go to each cell individually? This is so easy using straight osc :frowning:

This is so cool. Thank you for sharing. Two small changes to get it to run: move grid.connect() & g:clear() -> g:all(0).

bright = 15
show_x = 1
show_y = 1
g = grid.connect()

function init()
  clock.run(grid_redraw_clock)
end

function grid_redraw_clock()
  while true do
    clock.sleep(1/30)
    if dirty==true then
      grid_redraw()
      dirty = false
    end
  end
end

function grid_redraw()
  g:all(0)
  g:led(show_x,show_y,bright)
  g:refresh()
end

function g.key(x,y,z)
  if z==1 then
    show_x = x
    show_y = y
    dirty = true
  end
end

function enc(n,d)
  if n==3 then
    bright = util.clamp(bright+d,0,15)
    dirty = true
  end
end
1 Like

The error you posted gives a “stack traceback”, which shows the file and line where the error occurred, and several layers of function calls leading up to it.

This directs us to grid.lua line 94 in the norns source and says that argument 4 (val) to _norns.grid_set_led was unexpectedly nil, which is the name Lua uses for any undefined value. This corresponds to the 3rd argument passed to g:led from your code:

g:led(x+x_off,y+y_off,args[x+y*8])

So we can conclude that args[x+y*8] is nil for some values of x and y. In this code example, these loop from 1 to 8. When both are 8, for instance, we have 8 + 8*8 = 72, which is greater than the 66 args you indicate are being passed over OSC.

I notice also that you are calling g:refresh() outside your OSC handler function, which I don’t think is what you want, since this will result in it only being called once during script evaluation rather than whenever a message is received. You may also wish to call g:clear() before the loop with all the g:led(...) calls to reset any grid LEDs that are already turned on before drawing again.

Also, using triple backquotes ``` around code blocks will preserve their formatting when pasting code on the forum, which is helpful for quickly reading and understanding code.

1 Like