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
devicewidthto 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": 1on 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: uselive.colorsto read Live's theme colors and feed them intopanel,lcd, etc. - Device background: leave
bgcolor/locked_bgcoloron 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)
y + h > 169— bottom clipped. The single most common LLM mistake.x + w > devicewidth— right edge clipped; or in dynamic-width mode, device silently grows wider than intended.- Negative coordinates — object drawn off-screen.
- Forgetting
"presentation": 1on a box — invisible in Live. - Forgetting
openinpresentation: 1at patcher level — editor and Live diverge. - Float coordinates — blurry rendering on non-Retina.
- Decimals from drag-align — Inspector tools +
Arrange → Apply 1x1 Pixel Gridfix this. Set Device Widthat zoom ≠ 100% — saved pixel width is wrong; well-known community footgun.- Custom colors without testing all themes — text becomes invisible in Dark/Light.
- Dynamic-width fold-outs where saved width ≠ initial state — visible flash on load.
presentation_rect: [0,0,0,0]unintentionally — object collapses to invisible.- Marking infrastructure objects (
live.thisdevice,live.observer,live.path,live.object,live.remote~) aspresentation: 1— they have no visual purpose; only the visiblelive.*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