API 1.4 (Companion 5.1+)
API 1.4 adds two new capabilities for surfaces — rotation events that can report more than one step at a time, and addressable LEDs driven by a button's gauge.
Rotation events can report a delta
Previously every rotation event reported exactly one step, so a surface reading a batch of encoder
movements had to emit one call per step. rotateLeftById and rotateRightById now take an optional
amount, and there is a new rotateById that takes a signed delta:
// Existing calls are unchanged — amount defaults to 1
this.context.rotateLeftById('0/2')
this.context.rotateRightById('0/2')
// Report several steps in one event
this.context.rotateLeftById('0/2', 3)
// Or use a signed delta: negative is leftward, positive is rightward
this.context.rotateById('0/2', -3)
For rotateLeftById and rotateRightById the magnitude of amount is used and the direction is
fixed by the method name, so passing a negative number will not reverse the direction. For
rotateById, delta must be a non-zero finite number — a delta of 0 is ignored with a warning.
This is useful for hardware that reports accumulated movement, or where polling can coalesce several detents into one report.
Gauge-driven LEDs
Surfaces with an addressable strip or ring of LEDs attached to a control — the ring around a Stream Deck Studio encoder, LEDs alongside a T-bar — can now ask Companion for the colour of each LED segment. A gauge on the button drives the value, and Companion samples it down to one colour per segment.
Request them from the control's style preset:
stylePresets: {
encoder: {
bitmap: { w: 72, h: 72, format: 'rgb' },
leds: { segments: 24, mode: 'full-ring' },
},
}
segments— how many individually addressable LEDs the control has.mode— how Companion maps the gauge onto them.'full-ring'means the LEDs form a complete circle, so the gauge is rendered faithfully (its angles, deadzone and colours respected 1:1), with segment 0 at 6 o'clock and indices increasing clockwise.'simple'is for any other shape, such as a straight strip, where the value is swept across all segments with segment 0 as the 0% end.
Both describe a logical ordering. If your hardware is wired differently, re-map the segments locally.
Check supportsLeds on the host capabilities before declaring leds. It is a new field on
HostCapabilities, so on an older host it will be undefined — when it isn't true, omit leds
from your style presets and fall back to whatever you would otherwise draw, such as a plain
backlight colour.
The colours then arrive on the new leds property of SurfaceDrawProps, as a packed RGB
Uint8Array of segments * 3 bytes — always raw RGB, regardless of the pixel format you requested
for any bitmap:
import { readLedColor, colorToIntensity } from '@companion-surface/base'
async draw(signal, drawProps) {
if (drawProps.leds) {
for (let i = 0; i < 24; i++) {
const color = readLedColor(drawProps.leds, i)
// send color to the hardware
}
}
}
Three helpers are exported from @companion-surface/base for working with these buffers:
readLedColor(buffer, index)— read one segment's colour.writeLedColor(buffer, index, color)— write one segment's colour, for tests and tooling.colorToIntensity(color)— flatten a colour to a single 0-255 value, using the Rec.709 luma coefficients, for surfaces whose LEDs are monochrome.
Check how the values look on your hardware before settling on a mapping. The colours Companion sends are perceptually encoded (sRGB-ish), the same as any button graphic, while most LEDs are driven linearly — so passing them through unchanged makes the top of the range look flat, with 70% and 100% nearly indistinguishable. Applying a gamma curve fixes this; see Indicator LEDs for a worked example.
See Rendering, Brightness and LEDs for the full picture.
Surface layouts are validated
The layout you return from openSurface() is now validated against the schema before Companion
accepts it, so a malformed layout fails immediately with an error naming the problem, rather than
causing confusing behaviour later on. validateSurfaceLayout is exported from
@companion-surface/base if you want to run the same check yourself, such as in a unit test.
The schema is also stricter than before: row, column and bitmap w/h must now be
integers, where it previously allowed any number. If your module generates layouts dynamically,
make sure any computed coordinates or sizes are rounded.