Skip to main content

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.

tip

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.
tip

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.