(grid) Method for Fast LED Redraws

Edit: See my later post for the solution and patch.

TL;DR Are there any design patterns or strategies for updating lots of LED animations? Or, perhaps, does grid have a “FPS” limit?

Hey all. I’ve got a project I’m working on that involves lots of moving LEDs. There are “signals” that move about the grid and “cells” that are stationary, but the user can place them everywhere. There are submenus, multi-finger gestures, etc. This is an M4L device and, to my horror, when I turned on the Live playback everything started lagging horrendously. After I hit stop it took many seconds for the animations and redraws to catch up. Obviously not acceptable. I haven’t done a deep dive into finding the bottleneck but my first hypothesis is that I shouldn’t be clearing the whole grid and redrawing every LED for each beat… but the idea of intelligently tracking every LED state and toggling them atomically makes me a little queazy. What is strange to me is that even with a simple arrangement of cells and signals it lags. Also the flickering looks crappy.

Here’s a video, some screenshots of the patch, and some of the JS running it. Essentially I’ve got the state of all the objects stored in JSON and I push that out to [dict]s on each beat. Then I clear the field (see clearField) below and redraw everything. I can’t use stuff like clear messages or /monome/grid/led/all 0 because column zero is a menu. Thank you.



2 Likes

Well this fixed the lag issue! [tempo] defaults to divisions of 16.

Screen Shot 2020-06-10 at 15.43.36

Still have the flickering issue that I’d love to fix.

1 Like

Taking advantage of things like /grid/led/map, /grid/led/row, and /grid/led/col would be a good first step. Those work much faster than setting LEDs individually.

If I find myself needing to redraw all or most of the grid often, I’ll store and manipulate the grid state in a buffer, and then draw the whole buffer all at once (well, row by row). No clearing needed with /grid/led/all 0 this way, so this should get rid of the flickering.

2 Likes

Yeah I’m iterating 120 LEDs on and off every second… Do you have an example of a buffer implementation you could share?

Perhaps you ‘shouldn’t’ use those messages for some reason, but it could well be much easier to redraw the whole display (including menu) every frame.

The best reference for learning drawing styles is the Grid Studies. I borrowed the below from there.

If you want to draw line-by-line, you can use eg: /monome/grid/led/level/row 0 6 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 to draw row 6 (zero-based), with the lights fading up in level left to right. The first 0 just says ‘start at the left edge’, but you can offset by 8 if you want to split the grid horizontally.

Personally I’d just use the /grid/led/level/map command which requires you send the commands in 8x8 grids. The full spec is here. Can’t think of any grid examples, but a lot of the Arc apps use this approach so that might be a good place to search.

4 Likes

Something I’ve found to help as well, and that is if you have a way to quickly reference current LED/column/row state of your grid, is to create a wrapper function around your lower-level LED/column/row on and off functions that checks whether an LED/column/row actually needs to send a state change to the monome grid–if an LED/column/row is already what it needs to be then no-op and keep moving.

Really simple trick, but it dramatically cut down on the OSC/serial/USB traffic for me with a couple simple 2-D array checks.

2 Likes

the single LED and row/col commands are bandwidth-optimized only if you’re doing very simple operations— and in the early days we had tons of patches that did simple stuff where this was enough.

i’ve long since abandoned this approach. on norns and the eurorack modules the only serial packets sent are full 8x8 “map” arrays, if the area is dirty. granted, there’s no OSC flying around.

my suggestion: for complex applications (even in max, use js instead) just redraw the entire 8x8 grid every refresh. you can get very fast refresh rates this way, and the CPU will hardly suffer.

you can have separate functions for drawing a nav bar, different views, etc, and they all can just get called by a central grid redraw. this redraw can even be on a timer so you don’t need to worry about dirty states (less efficient, but nothing will explode).

the nodejs grid study is the right track (and largely relevant to max/msp js): https://monome.org/docs/grid/studies/nodejs/

or, also check out my work-in-progress rewrite of mlr (the grid portion) which lays out “pages” with a “nav row” and nicely divides the code for readability: https://github.com/tehn/mlr/blob/dev/lib/grid.lua

6 Likes

on phone / too lazy to link code, but my max apps anaphora & anachronism are made all in javascript on the grid side and abstract led(x,y,level) & refresh() functions to draw full quadrants the same way norns does. ran into the framerate / message traffic issue a while ago with max and switching to doing everything in javascript made life very much easier. sadly just never got around to documenting and releasing the library

2 Likes

:exploding_head:

Tangent: also did not know you could stack additional args to outlet() like this. I’ve been building output arrays by hand…

My goodness. Thanks for your input everyone. I pored over all the links you sent my way. This is gonna be one helluva refactor.

I think I may start by moving all this to use map and a buffer instead of throwing all this away.

4 Likes

For those who come in our footsteps, some hazy day in the future. Fast redraws on all editions. Can someone with a 256 confirm it works?

3 Likes

It seems to work as expected on my 256…

2 Likes

Yes!! Thank you so much.

1 Like