|
Operator SDK
Build MIDI processing and UI modes for the Operator device
|
When a param page is not enough, a per-step visualization, a keyboard, a transport button row, a mode can draw its own screen and handle input directly. Make a custom UI only when declarative parameters (see Parameters) cannot completely express what you need.
| Stay declarative when | Go custom when |
|---|---|
| All your state fits Numeric / Bool / Enum / Range / NoteRange / FilePicker params | You need per-note or per-step visual feedback |
| The user only tunes values | The button set changes with your mode's state |
| No per-step visual feedback is needed | You render a bar, graph, keyboard, or grid |
| A standard gesture map is fine | You need to override the default rotate-edits-selected behavior |
A custom UI adds two functions, registered with OP_MODE_REGISTER_UI. Both run on Core 0. Their signatures are in Mode Lifecycle.
Every pixel on the 128x64 OLED is drawn through one of the OperatorApi functions below. Instead of having direct access to the display framebuffer, you use the API and the firmware renders into the display itself.
| Function | Purpose |
|---|---|
| draw_rect(x, y, w, h) | 1-pixel outline rectangle |
| fill_rect(x, y, w, h, on) | Fill with ON (white) or OFF (black) pixels |
| draw_text(x, y, text) | 5x7 standard font, 6px advance, 8px line height |
| draw_text_large(x, y, text) | 8x12 large font, 9px advance, 13px line height |
| draw_bitmap(x, y, data, w, h) | Blit a packed 1-bit bitmap |
| set_pixel(x, y, on) | Turn a single pixel ON or OFF |
Auxiliary helpers (header-only, opt-in), in <operator_sdk/text.h>: text_width and large_text_width (pixel width of a string), align_center_x and large_align_center_x (x-offset to center text in a box), truncate_with_ellipsis (safe truncate with ...), and word_wrap (greedy word-wrapping).
Two bitmap fonts ship in firmware. There's currently no API-supported way to add or change the system-provided fonts.
| Font | Glyph | Advance | Line Height | Typical Use |
|---|---|---|---|---|
| Standard | 5x7 px | 6 px | 8 px | Labels, hint rows, values, menu items |
| Large | 8x12 px | 9 px | 13 px | Titles, primary readout, large numbers |
On the 128 px wide display that is 21 standard characters or 14 large characters per line before overflow. Respect those ceilings when writing UI copy.
draw_bitmap blits a packed 1-bit image w pixels wide and h pixels tall, with its top-left corner at (x, y). A set bit draws an ON pixel.
The data is page-addressed. The image is split into horizontal pages of 8 rows each (ceil(h / 8) pages). Each byte holds the 8 vertical pixels of one column within a page, least-significant bit at the top. Bytes run column by column within a page, then page by page: the byte at index p * w + col holds column col of page p, and its bit r is the pixel at row p * 8 + r. The buffer is exactly w * ceil(h / 8) bytes, and any unused high bits in the final partial page are zero.
A tiny example is a 3x3 diagonal from top-left to bottom-right. It fits in a single page (ceil(3 / 8) is 1), so there is one byte per column and bit 0 is the top row:
For a larger image, the User Scale sample (samples/user-scale-common/keyboard_sprites.h) is a worked example.
Your UI gets one of two layouts, chosen with FLAGS FULLSCREEN_UI in op_add_mode() (see Build Reference):
| Zone | Without the flag | With the flag |
|---|---|---|
| y = 0..8 | Firmware status bar | Yours |
| y = 9..63 | Your content (128x55) | Yours |
| Total | 128 x 55 content area | 128 x 64 full screen |
Your ui_gesture runs once per input event:
The recommended way is to react to type. Your UI then follows whatever input map the user has set, without you hard-coding which control does what:
All sample modes handle input exactly like this, editing their parameters with set_param_value (see Parameters).
For something like an X/Y sketch pad, ignore type and read control and gesture directly: left or right control, Rotate or ShortPress, and the signed value. Use this only when your interaction genuinely does not fit the standard intents.
Four shipped samples cover the space:
Copy the closest one when starting a custom-UI mode. They share the conventions of template-custom-ui/ and add the domain-specific rendering.