VSN1 display & MaxMsp | Intech Studio
✅ solution Grid

VSN1 display & MaxMsp

AK
AK · · 34 replies

Has anyone figured out how to send text from max to the display of the VSN1? A simple step by step guide on how such solution is spread across the various sections of the grid editor would be highly apreciated. Is there anything out already for someone fit in MaxMsp but totaly dummy on lua and its strcuture in the editor? Thanks in advance.

Replies (34)

AK AK ·

this, for example, could be on the max side as a simple test using cc 20-23.

AK AK ·

this, for example, could be on the max side as a simple test using cc 20-23.

CleanShot_2026-02-12_at_10.17.282x.png
kkerti kkerti ·

Working on it, making a sysex example right now, almost done

AK AK ·

sounds good. in the end i will have to figure out a way to send to the grid controller directly from within the maxforlive device without the track-midi-out … as the final device, currently, is sitting in the master track. i hope to find a way without having to write a control script - which is another rabbit hole entirely.

kkerti kkerti ·
kkerti kkerti ·
image.png
AK AK ·

many thanks. my main problem however is the grid side. is this supposed to work with your widget WIP (which here only shows a red screen with a colorchanging square bottom right and a 0 in the middle)?

kkerti kkerti ·
image.jpg
kkerti kkerti ·

grid-editor://?config-link=3aQggwzkqFAH2e00rVN5

kkerti kkerti ·

Or look up the name "MaxMSP Test Widget (wip)", and load it onto a VSN1 module.

kkerti kkerti ·

By default after loading the profile, 4 colorful rectangles should be seen with 0,1,2,3 as values.

kkerti kkerti ·

Disclaimer: This is a showcase of the widget system, which will be supported on firmware level, once we settled on the behaviour. This means, the code and example working here might be different after weeks / months, looking back to this thread. The config is in lua though, so it should work, but hopefully in some time as this moves to the firmware, we will have cleaner configs and more script length at our disposal!

kkerti kkerti ·

The backbone of this config is the Layout. This code is on the system event setup in the profile I mentioned above. It's plain lua. The goal of the Layout, is to add widgets to it, where the widgets get automatic sizing benefits as all widget is a cell within a grid layout, like they will know their x (start X coord), y (start Y coord), h (height) and w (width) to draw out things.

Layout = {
  new = function(self)
    local instance = setmetatable({}, { __index = self })
    return instance
  end,
  addWidget = function(self, cx, cy, wg)
    wg.w = self.width / self.r // 1
    wg.h = self.height / self.c // 1
    wg.x = self.x + cx * wg.w // 1
    wg.y = self.y + cy * wg.h // 1
    wg.parent = self
    self.cells[cy * self.r + cx] = wg
    return wg
  end,
  initialize = function(self, lcd, hcc, vcc, x, y, w, h)
    self.lcd = lcd
    self.r = hcc
    self.c = vcc
    self.width = w
    self.height = h
    self.x = x
    self.y = y
    self.cells = {}
    for i = 0, hcc * vcc - 1 do
      self.cells[i] = {}
    end
  end,
  render = function(self)
    for i = 0, self.r * self.c - 1 do
      local cell = self.cells[i]
      if type(cell.render) == "function" then
        cell:render()
      end
    end
    self.lcd:draw_swap()
  end,
  sysexrx_cb = function(self, hdr, ev)
    for i = 0, self.r * self.c - 1 do
      local cell = self.cells[i]
      if type(cell.sysexrx_cb) == "function" then
        cell:sysexrx_cb(hdr, ev)
      end
    end
  end,
}

This code is for the brave, some parts even I have to spend an hour to figure out, but fortunately LLM tools, specially Claude can help with it. Plan is to move this to firmwre level.

AK AK ·

great. works, thanks. what would be a simple mod to get a different layout ?

AK AK ·

oh. i think you just answerd my question with this Layout code. thanks.

kkerti kkerti ·

I try to explain bits in this thread, hang on

AK AK ·

My needed layout is a simple text list however with options for size and style per line. i could solve that with the background color possibly but need a bigger font.

kkerti kkerti ·

The widget, which we render looks like this and resides on the second small button's setup event under the screen:

MAX_TEXT = {
  new = function(self, ci, wclr)
    local instance = setmetatable({}, { __index = self })
    instance.ci = ci
    instance.text = ci
    instance.tclr = { 255, 255, 255 }
    instance.wclr = wclr ~= nil and wclr or { 40, 40, 40 }
    instance.tsize = 32
    return instance
  end,
  render = function(self)
    self.parent.lcd:draw_area_filled(self.x, self.y, self.x + self.w, self.y + self.h, self.wclr)
    self.parent.lcd:draw_text(self.text, self.x + 4, self.y + 4, self.tsize, self.tclr)
  end,
  sysexrx_cb = function(self, hrd, ev)
    local ci = tonumber(ev:match("^%x%x%x%x(%x%x)"), 16)
    if ev ~= nil and self.ci == ci then
      self.text = extractSysexString(ev)
    end
  end,
}

To show sysex data received on the widget, the sysexrx_cb, is passed to the Layout with coding wizardry. The received sysex data is parsed with a helper function written in lua, found under the first button.

In sysexrx_cb, we use the ci value which stands for cell index. This helps determine, which cell should be written.

In the new part, the widget is initialised with sensible defaults. Right now just ci - cell index, wclr widget color are passed in as parameters during initialization, but this can be extended to have tsize and other params too.

kkerti kkerti ·

Two more key details are:

kkerti kkerti ·

1.) LCD Setup
On the LCD, under setup event we glue this together like this:

lcd_set_backlight(255)
canvas = Layout:new()
canvas:initialize(self, 2, 2, 0, 0, 320, 240)
canvas:addWidget(0, 0, MAX_TEXT:new(0, { 150, 190, 0 }))
canvas:addWidget(0, 1, MAX_TEXT:new(1, { 0, 120, 120 }))
canvas:addWidget(1, 0, MAX_TEXT:new(2, { 170, 50, 40 }))
canvas:addWidget(1, 1, MAX_TEXT:new(3, { 70, 120, 255 }))
canvas:render()
self.sysexrx_cb = function(self, hdr, ev)
  canvas:sysexrx_cb(hdr, ev)
end

This is the place, where the layout is initialized for the full screen, with 2 row and 2 cell -> canvas:initialize(self, 2, 2 ...).

Then the widget is registered in the 4 cell, with their cell index and default colors.

After registration, we call canvas:render() to render after initialization.

The sysexrx_cb is also passed here for the canvas (instance of Layout).

kkerti kkerti ·

2.) LCD Draw
The changes passed to the widgets are continously rendered with the canvas:render() function on the LCD draw event.

image.png
kkerti kkerti ·

wait a minute, I'll try to adjust what we have now

kkerti kkerti ·
image.jpg
kkerti kkerti ·

1.) To get differen text size upon widget creation:

Add tsize as a paremeter for the widget code:

new = function(self, ci, tsize, wclr)
    local instance = setmetatable({}, { __index = self })
    instance.ci = ci
    instance.text = ci
    instance.tclr = { 255, 255, 255 }
    instance.wclr = wclr ~= nil and wclr or { 40, 40, 40 }
    instance.tsize = tsize ~= nil and tsize or 32
    return instance
  end,

I do a check for nil value, could be omitted as well. Default is 32. The widget uses draw_text, which can receive arbitrary text size. If you use draw_text_fast, it can get text size in the increment of 8, so 8, 16, 24 and so on (but might be 8,16,32? I'm not sure on this one).

2.) Update the LCD's setup event

We pass the text size as the second parameter for widget creation.

I have also changed the layout, creation a 1 col, 4 row canvas.

The widgets are rendered then under one and the other.

The last parameter in the new right now is the widget background color, could be omitted or set to something else.

lcd_set_backlight(255)
canvas = Layout:new()
canvas:initialize(self, 1, 4, 0, 0, 320, 240)
canvas:addWidget(0, 0, MAX_TEXT:new(0, 20, { 150, 190, 0 }))
canvas:addWidget(0, 1, MAX_TEXT:new(1, 30, { 0, 120, 120 }))
canvas:addWidget(0, 2, MAX_TEXT:new(2, 40, { 170, 50, 40 }))
canvas:addWidget(0, 3, MAX_TEXT:new(3, 50, { 70, 120, 255 }))
canvas:render()
self.sysexrx_cb = function(self, hdr, ev)
  canvas:sysexrx_cb(hdr, ev)
end
kkerti kkerti ·

If you want to change the text color and size from sysex, you can do that too.

In the widget code (MAX_TEXT on the second small button), you have to somehow extract the other data and pass it to the widget..

if ev ~= nil and self.ci == ci then
  self.text = extractSysexString(ev)
  -- here you could add / call other helper functions to parse text color (tclr variable in the widget) or text size (tsize variable)
  --self.tclr = MY_TEXT_COLOR_SYSEX_EXTRACTOR(ev)
  --self.tclr = MY_TEXT_SIZE_SYSEX_EXTRACTOR(ev)
end
AK AK ·

hey kristof, your help is highly apreciated, really. thanks so much. i have to admit, that still, i dont get where the various code sippets go in the editor and why (like why with the smal buttons? this a workaround?) but hey… here is a first working approach thanks to your guide. awsome. thanks.

kkerti kkerti ·

Yes, to split the code to functions and put them at various places is a workaround. We lifted the character limit from 400 characters to 900, but it is still a limiting factor.

kkerti kkerti ·

I have shared the altered profile as "MaxMSP Test Widget (wip) - Variant 2", so you can use it right away. If you need just more horizontal lines, you would have to:

1.) extend the canvas size, like changing it to 5:
canvas:initialize(self, 1, 5, 0, 0, 320, 240)

2.) add a new widget at row 5:
canvas:addWidget(0, 4, MAX_TEXT:new(3, 50, { 70, 120, 100 }))

AK AK ·

Thanks again kristof. I now have a working version of what I tried to do (getting the scene list of "ya_Next" device onto the VSN1 screen) thanks to you.

kkerti kkerti ·

Great, happy exploring. Later on if you wish new additons or have other features on your mind, let us know, it might inspire new ways for configurations.

AK AK ·

a quick follow up: how would I tackle the fact that the solution currently does not display umlaut (ä ü ö) and and some more special characters and quirks ("…" becomes "&" while "&" remains "&")?

AK AK ·

… trying to figure out how to decode special characters (like umlauts ä, ö, ü) from the SysEx data. Do you know if the default font used by draw_text actually contains the glyphs for these characters? If it doesn't, is there a way to specify or load a different font that supports UTF-8 or extended character sets?

kkerti kkerti ·

I think it does not contain those characters. I will have to ask @user on this. Right now it's not possible to load different character set.

AK AK ·

This probably needs to be something high up on the list to get VSN1 display speak multiple language fonts, right? If you get any feedback from @user please let me know. Highly apreciated, thanks.

Discord

View on Discord

This post is from the Intech Studio Discord community.

Open thread →