SuperAPI AI Directory

m4l-presentation-layout

Office & productivity

View repository

Updated 10-16 03:41

About this skill

M4L Presentation Mode and Device Layout

The #1 LLM failure mode for M4L is making a GUI that doesn't fit. The device strip is 169 pixels tall. There is no exception. Anything with y + h > 169 is clipped.

1. Device strip dimensions

Height: fixed at 169 px

Cycling '74's User Interfaces in Max for Live page states verbatim: "The height of all Live devices is fixed at 169 pixels." This has been stable from Max 5 through Max 9. There is no escape.

Practical cap: design content within y = 0..168. Recommend a 4–8 px top/bottom inset → usable inner box y = 8..160 (h ≈ 152 px).

Width: dynamic by default

Width is set via the patcher's devicewidth field (pixels). Two modes:

  • Dynamic (default, devicewidth: 0.0): Live grows the strip just past the rightmost visible object.
  • Fixed: set devicewidth to a positive integer pixel count. Or use Max's GUI: View → Set Device Width, after first setting zoom to 100% (zoom-affected sizing is a known footgun).

No documented hard max/min. The Ableton/Cycling '74 Production Guidelines recommend keeping width as small as possible — wide devices push neighbors off-screen in Live's Device View.

No expand / stretched mode

Live has no user-drag resize for M4L devices. "Fold-out" devices implement it themselves by sending setwidth <pixels> to live.thisdevice at runtime. If you do this, save the initial width matching the initial state to prevent a size flash on load.

2. patching_rect vs presentation_rect

Two completely independent coordinate spaces, both origin top-left, both pixels:

  • patching_rect [x,y,w,h] — position/size in the cabling/editor view. Live users never see this. Always present on every box.
  • presentation_rect [x,y,w,h] — position/size in presentation mode (= what Live shows). Only present and used when "presentation": 1 on the box.
  • "presentation": 1 — required on every box that should appear in Live.
  • Patcher-level "openinpresentation": 1 — required for the device to render presentation in Live. Without it, the editor opens in patching mode and many "presentation looks wrong" issues trace here.

A single object can have completely different positions and sizes in patching vs presentation. This is normal and idiomatic.

3. Coordinate rules

Integer pixels only

The Ableton Production Guidelines mandate whole-pixel sizing:

"For the Presentation Rectangle setting, make sure that widgets are sized in whole pixels to avoid blurry shapes on non-retina screens. Aligning objects with click-drag relative to the center of other objects can result in decimal-point numbers, so be sure to check the Inspector for object dimensions and positions in Presentation Mode."

Always emit integers. After any algorithmic placement: x = round(x); y = round(y); w = round(w); h = round(h);

Max provides Arrange → Apply 1x1 Pixel Grid (Max 8.6+) to fix existing layouts.

Safe bounding box

x in [0, devicewidth - 1]
y in [0, 168]
x + w <= devicewidth
y + h <= 169

With an 8 px symmetric inset (idiomatic):

inner x in [8, devicewidth - 8]
inner y in [8, 161]
inner height ~= 153

4. Typical sizes of live.* UI objects

Cycling '74's reference pages declare presentation_rect defaults of 0. 0. 0. 0. for every live.* object — Max stores no canonical default presentation size. When you place an object the editor draws it at an internal size driven by appearance / showname / shownumber etc.

These are typical observed footprints when freshly placed at 100% zoom — use them as starting estimates, then verify in the Inspector:

Object Typical w × h (px) Notes
live.dial (Vertical, default) 35 × 48 Label + number rows add height
live.dial (Tiny) 24 × 24 No label, no number
live.dial (Large) 40 × 85 Knob + name + value below
live.dial (Panel/horizontal) 75 × 30 Horizontal slider-style
live.numbox 50 × 15
live.text (button mode) 50 × 15
live.text (toggle/LCD mode) 30 × 15
live.toggle 15 × 15 Square
live.button 15 × 15 Square, momentary
live.tab (Live mode) 70 × 15 per tab Enable Live mode for pixel-grid snapping
live.menu 80 × 15 Width depends on longest enum item
live.slider (horizontal) 80 × 15
live.slider (vertical) 15 × 80
live.gain~ (horizontal) 95 × 25 Configurable
live.meter~ (vertical) 10 × 80 Display-only
live.banks n/a Editor-only, not visible in Live strip

appearance on live.dial is the single biggest driver of footprint. showname/shownumber toggle label/value rows, each adding ~12 px vertically.

5. Patcher attributes for a clean layout

Best defaults for an M4L device patcher:

"openinpresentation": 1,
"integercoordinates": 1,
"gridsize": [8.0, 8.0],
"gridsnaponopen": 1,
"objectsnaponopen": 1,
"gridonopen": 1,
"default_fontname": "Ableton Sans",
"default_fontsize": 12.0
  • integercoordinates: 1 — snaps all box positions/sizes to integers globally. Inspector label: "Snap to Pixel".
  • gridsize: [8, 8] — community-common multiple; nests with 16×16 patterns.
  • default_fontname: "Ableton Sans" — Ableton Sans ships with Max and gives the native Live look. Use it on comments and labels.

6. Themes and colors

Live 10+ has four themes: Light, Mid Light (default), Mid Dark, Dark. Live 12 expanded the palette.

  • live.* objects auto-theme. Don't override colors unless you must. If you do, test all four themes.
  • For non-live.* objects that should follow the theme: use live.colors to read Live's theme colors and feed them into panel, lcd, etc.
  • Device background: leave bgcolor / locked_bgcolor on default for native look.

7. Reference layouts

All coords are integer pixels. 8 px symmetric inset. Verified to fit inside 169 px height.

A. Title + 4-knob row (devicewidth = 240)

patcher:
  openinpresentation: 1
  integercoordinates: 1
  devicewidth: 240

Title comment (Ableton Sans 12)
  presentation_rect:  [   8,   8, 224,  14 ]
Knob 1 (live.dial, Large)
  presentation_rect:  [  16,  40,  40,  85 ]
Knob 2
  presentation_rect:  [  72,  40,  40,  85 ]
Knob 3
  presentation_rect:  [ 128,  40,  40,  85 ]
Knob 4
  presentation_rect:  [ 184,  40,  40,  85 ]

Vertical extent: 8 + 14 + 18(gap) + 85 = 125. Plenty of slack to 169.

B. 8-knob 2-row device (devicewidth = 360)

patcher:
  devicewidth: 360

Title
  presentation_rect:  [   8,   6, 344,  14 ]
Row 1 dials at y=26..78 (h=52), 4 cols, w=48, pitch=84
  Dial 1: [  16,  26, 48, 52 ]
  Dial 2: [ 100,  26, 48, 52 ]
  Dial 3: [ 184,  26, 48, 52 ]
  Dial 4: [ 268,  26, 48, 52 ]
Row 2 dials at y=92..144
  Dial 5: [  16,  92, 48, 52 ]
  Dial 6: [ 100,  92, 48, 52 ]
  Dial 7: [ 184,  92, 48, 52 ]
  Dial 8: [ 268,  92, 48, 52 ]
Footer (status comment)
  presentation_rect: [   8, 150, 344, 12 ]

C. Knob + menu + meter (devicewidth = 280)

Title:                          [   8,   6, 264, 14 ]
Mode menu (live.menu):          [   8,  30, 120, 18 ]
Knob A (live.dial, Large):      [ 140,  26,  40, 85 ]
Knob B (live.dial, Large):      [ 188,  26,  40, 85 ]
Output meter (live.meter~):     [ 240,  26,  16,110 ]
On/off (live.toggle):           [   8, 140,  18, 18 ]
Bypass label (comment):         [  32, 142, 100, 14 ]

D. Tab + content area (devicewidth = 360)

For multi-page UIs, top y=8..22 holds a live.tab page selector; below is a bpatcher content region whose presentation_rect covers [8, 30, 344, 130]. Switch which bpatcher is visible (or its content) based on tab state via pattr or hidden attribute.

8. Anti-patterns (causes of overflow / clipping)

  1. y + h > 169 — bottom clipped. The single most common LLM mistake.
  2. x + w > devicewidth — right edge clipped; or in dynamic-width mode, device silently grows wider than intended.
  3. Negative coordinates — object drawn off-screen.
  4. Forgetting "presentation": 1 on a box — invisible in Live.
  5. Forgetting openinpresentation: 1 at patcher level — editor and Live diverge.
  6. Float coordinates — blurry rendering on non-Retina.
  7. Decimals from drag-align — Inspector tools + Arrange → Apply 1x1 Pixel Grid fix this.
  8. Set Device Width at zoom ≠ 100% — saved pixel width is wrong; well-known community footgun.
  9. Custom colors without testing all themes — text becomes invisible in Dark/Light.
  10. Dynamic-width fold-outs where saved width ≠ initial state — visible flash on load.
  11. presentation_rect: [0,0,0,0] unintentionally — object collapses to invisible.
  12. Marking infrastructure objects (live.thisdevice, live.observer, live.path, live.object, live.remote~) as presentation: 1 — they have no visual purpose; only the visible live.* UI classes belong in presentation.

9. Layout algorithm for the LLM

Given N knobs and a fixed devicewidth:

def knob_row_layout(n_knobs, devicewidth, knob_w=40, knob_h=85,
                    title_h=14, inset=8, title_to_knob_gap=18):
    title_y = inset
    knob_y  = inset + title_h + title_to_knob_gap          # 40 for the values above
    available_w = devicewidth - 2 * inset
    if n_knobs == 1:
        pitch = available_w
    else:
        pitch = (available_w - knob_w) // (n_knobs - 1)
    knobs = []
    for i in range(n_knobs):
        x = inset + i * pitch
        knobs.append((x, knob_y, knob_w, knob_h))
    title = (inset, title_y, available_w, title_h)
    assert knob_y + knob_h <= 169, f"row overflows: y+h={knob_y+knob_h}"
    return title, knobs

Always assert y + h <= 169. Run a final sweep over every emitted presentation_rect checking the bounds:

def check_fits(boxes, devicewidth, height=169):
    for box in boxes:
        if "presentation" in box and box["presentation"] == 1:
            x, y, w, h = box["presentation_rect"]
            assert 0 <= x and x + w <= devicewidth, f"x overflow: {box['id']}"
            assert 0 <= y and y + h <= height,      f"y overflow: {box['id']}"
            assert all(isinstance(v, int) for v in (x, y, w, h)), f"non-int: {box['id']}"

10. Sources for further detail

  • Cycling '74 — User Interfaces in Max for Live (169 px claim)
  • Ableton/maxdevtools — m4l-production-guidelines.md
  • Cycling '74 reference — patcher, live.dial, live.gain~, live.meter~, live.banks