Feature request: Grid-wide constants or macro's

Good day,

As I understand, it is not possible to share variables between modules. The next best thing would be macro’s or constants that can be defined in the grid editor that can be used in all code. Use case now is to define led intensities grid wide, but there are more possibilities once you have it. Especially if this would be extended to real macro’s that can contain code snip-its.

Cheers, Bart.

It is now possible to do something similar to this.

For example it is possible to have the same function/variable declared on all modules in a multi-module Grid configuration, and have those variables/functions modified and called from another module with the immediate send feature.

I’ve been meaning to finish a short article that will introduce the wider user base to this feature, please stay tuned.

2 Likes

That sounds very cool!

@narayb is this update live or pending?

Possibly similar/related; would this feature allow for functions to be called from midi_rx events (as opposed to just updating values)? I believe it’s currently only possible to update values with midi_rx where it would be great to call a function on event receive.

What do you mean with functions called? Isn’t that already possible? I switch on/off leds upon reception of midi_rx …

All that you can do, to the best of my knowledge, is update a value. MIDI receive just sets/updates the value of a local variable with the value of the incoming MIDI message. It’s like a translation table. You can’t trigger a function. I don’t see a way to say (in plain English, not LUA):

IF midi_rx() value is 'this" THEN

function_example();

So, AFAIK, you can only update values but you cannot trigger events. I’ve admittedly not explored midi_rx() to much depth. But, the above is my understanding. There’s no way to evaluate that incoming data and trigger events (function calls).

Probably over-reacting, if yes, my apologies, if not, also my apologies … :innocent:

I’m not sure what events are in this context, looked it up, but it is not a Lua concept.
In general, I would assume an event is the (external) trigger, like pushing a button or the reception of a midi message. The code in the Midi rx tab is executed upon every midi message reception ‘event’ that reaches the module.

If you refer to ‘this’ or ‘self’ (?), I would understand that as a placeholder of some local instance with local methods. E.g. to get the index and value of the button that is currently pressed. The led, that happens to have the same number and is placed near the button, is not part of this ‘self’. You can only manipulate the led with a global function by passing its number. This can also be done after reception of a midi in message, by applying some algorithm or table to map the message parameters to a number of a led. Once you have this led number, you can use the global led functions, also in the Midi Rx tab. Maybe the Midi Rx has a ‘self’ but maybe you could see the midi.xxx as a ‘self’.xxx.

How the OS is structured under the hood and if certain event triggers can be processed in parallel, I don’t know. I don’t think you can forward events to be handled in some specific thread as an internal event, at least not as a user. I also think, after pressing a button or receiving a midi message, you can’t start calculating prime numbers because you will block the entire module. Just send a midi message or switch a led, done, and up to the next.

(?) also checking my knowledge and assumptions through this …

1 Like

An event would be a button press, encoder turn, etc… So, an event would need to happen in order to trigger code. Press a button, do a thing. ‘midi_rx()’ just listens for incoming MIDI and translates those value updates to local variables (insofar as I can tell).

midi_rx(), to my understanding, is just a translation table; when you receive this CC/value, update the value of ‘x’. It doesn’t trigger or generate an event. It’s just updates the value of a variable.

I know of no way to execute code/call a function if no event is generated. The act of updating a variable through midi_rx() doesn’t, to the best of knowledge, generate an event.

Keep in mind that my coding/scripting knowledge is quite limited. :slight_smile:

Edit: To add to this, element events can trigger other events on other elements but you first need to generate that first event. I could, for example, press a button then execute code that emulates a button press on any other element on the current controller. Again, you need the initial event to happen in order to do any of that.

I think we have the same understanding. However, you can call a function from the Midi Rx tab. I’ve done it. This is a simplified example. Use case is a button to start a track in a DJ app.

  • Button press: create and send midi message with parameters that are based on the button number to make a unique CC (or note if you like). Number to CC.
  • The DJ app, after it received this message that is mapped to ‘start track’, will return a midi message, containing the same CC and a status value.
  • In the Midi Rx tab, get the message parameters, calculate the number to switch the led (of the previously pressed button) based on the status value. CC to number, the inverse of the above.

The button code.

The Midi Rx code.

Ah, I think I now get what you mean. For Midi Rx, the reception of the message is the event. :slight_smile:

That’s the part I had not got the opportunity to test. As it’s outlined, it did not sound like you could apply logic/evaluate conditions from an incoming midi_rx() event. I was able to update element values without issue but thinking through some more complex implementations, it didn’t seem doable. I had not had the time to really rabbit hole into midi_rx().

Cheers! :smiley:

1 Like

I had not anticipated this amount of replies, I’ll publish the article today so you can check for yourselves.

It’s a new method, completely different from using MIDI RX. Seemingly more stable too, but it’s an early phase function.

1 Like

Here’s a preview version in the meanwhile:

immediate_send

immediate_send('lua code')

This function does what it says on the tin. It immediately forces all modules in a Grid setup to run a lua code snippet given between the two apostrophes of the function argument.


Early feature
This function currently requires you to be running a nightly version of the Grid firmware. You might experience bugs or performance related issues, but the nightly version should not be any less stable than the release version of the firmware.

You can find the nightly firmware version here:
for NEW Grid modules and for Grid modules sold before May of 2023.


What’s this for?

This new function allows you to share information between modules in a way that wasn’t really possible before.

Now, creating new functions that all modules understand equally, or setting variables that are always in sync no matter which module you change them on (as long as you tell the others it changed) should be a lot easier to implement, thanks to immediate_send().

How it works

The function will send to all modules (yes, to itself as well) a text string which they will immediately execute.

That string will have to be a lua code snippet like so:

immediate_send('print("Hello world!")') 
-- we should see 'Hello world!' appear in the debug monitor the same amount of times as we have modules connected

immediate_send('print(module_position_x())') 
-- here we should see the position of our connected modules in the debug monitor on the x axis

immediate_send('print("I am the " .. module_position_x() .. ". module on the X axis.")') 
-- and this one should combine the two functions above

We can and will have to use the .. concatenation operator where we want to string (heh) together multiple elements into a string which we send out in the end.

Remember that the modules on the receiving end, will interpret everything as ONE string. Meaning when wanting to use variables in arguments of functions you will have to use the .. operator AND add commas in between as well like this:

immediate_send('led_value('.. 0 .. ',' .. 2 .. ',' .. val ..')')
-- this function controls the LED brightness on all connected modules, based on the state of the control element sending out the above function

Try using the above function in a Code Block on a potentiometer and watch the first LED get brighter on each module in your setup!

1 Like

I think I (we?) got carried away a bit …

Hi Narayb,

This looks great!

I understand the concatenate operator, when you use variables so that you pass their values from the source module. Would this example, if you do not use it for the constants 0 and 2, work just as well?

Yes, that’s correct!
I just reused some code here and forgot to turn the numbers back into strings.

1 Like

The article is now published here:

2 Likes

A question about what is good practice for functions that are called by immediate_send. I created a loop to change all leds of a module in this function, but some weird things happened, on the source and destination of the immediate send all leds started flashing in a pinkish colour. Could be my programming error, but I thought it would probably be better to not to do a lot of work in this function. So what I do now is set a state variable and for the real work, looping all leds, I start a timer with < 100ms. The timer expiration will then do the real work. Since then, no weirdness happened.

What are good programming pratices for the functions called by immediate_send?

In my experience the best practice is to create functions without parameters on the target modules so that the immediate_send() messages are short and sweet.

The pinkish blinking shows that the serial communication was overloaded. Therefore using short messages is preferable in cases like this. But this behavior also shows why the immediate_send() is still an experimental feature.

1 Like