What a script can reach
Everything below is available inside the hooks, without importing anything. Nothing else is: see The fence.
log
The only way a script writes anything out. Lines land in the browser console,
prefixed with the cue that produced them ([script] 12 Intro music).
function onStart() {
log.info('starting');
log.warning('the room is loud');
log.error('that should not have happened');
}
Each takes any number of arguments, like console.log.
startTime and time
Two plain variables the engine refreshes before every hook and every timer callback:
| Variable | What it holds |
|---|---|
startTime | Epoch milliseconds when the cue started playing — after its delay, the moment onStart ran |
time | Seconds since then |
function onTick() {
if (time > 5) log.info('five seconds in');
}
Assigning to them does nothing useful: the next call overwrites what you wrote.
A script that declares its own time or startTime fails to compile
(“Identifier ‘time’ has already been declared”), which is the clearest way to
find out the name is taken.
after(duration, callback)
Run something once, duration seconds from now.
function onStart() {
after(2, function () {
this.level.set(0.2);
});
}
every(interval, callback)
Run something over and over. The first argument is either the interval in
seconds, or an object: interval is the gap, delay is how long to wait
before the first run and defaults to one whole interval. The callback is given
its run index, counting from 0.
function onStart() {
// The short form: every two seconds, first run two seconds in.
every(2, function (index) {
log.info('beat', index);
});
}
function onStart() {
// Right away, then every half second.
every({ interval: 0.5, delay: 0 }, function (index) {
this.textColor.set(index % 2 === 0 ? '#ff0000' : '#ffffff');
});
}
Both timers run on the engine’s frames, not on setTimeout:
- They belong to the cue. They cannot fire before it starts playing, and they are dropped the moment it ends — nothing keeps running after the cue is gone.
- A frame that arrives late catches a repeat up rather than letting it drift, and the indexes stay consecutive.
- A callback that throws is reported once and dropped; the hooks carry on.
- A timer set inside a callback waits for the next frame rather than joining the one in flight.
- An interval that is not a positive number, or a callback that is not a function, schedules nothing at all.
random
Numbers, points and colours picked at random. Bounds are coerced and put in
order, so random.int(10, 1) is random.int(1, 10), and a bound that is not a
number falls back to the default rather than putting NaN on stage.
| Call | Gives |
|---|---|
random.int(min, max) | A whole number, both ends included. Defaults to 0–1. |
random.float(min, max) | A number, min included and max excluded. Defaults to 0–1. |
random.vec2(min, max) | { x, y }, each drawn separately |
random.vec3(min, max) | { x, y, z }, each drawn separately |
random.color(options) | A CSS colour, #rrggbb |
random.element(array) | One item out of the array; undefined when it is empty |
random.color takes a hue, a saturation and a value. Each is either a fixed
number or a [min, max] range to pick between. Hue is in degrees (0–360) and
wraps, so [300, 420] sweeps through red; saturation and value are 0–1 and
clamp. Left out, hue is anywhere on the wheel and the other two are 1.
random.color(); // any vivid colour
random.color({ saturation: [0.5, 1], value: 1 }); // bright, never washed out
random.color({ hue: [200, 260], value: [0.4, 1] }); // blues, any brightness
random.color({ saturation: 0, value: [0, 1] }); // a grey
random.element picks one item out of an array, and gives undefined for an
empty one rather than throwing — a list that happens to be empty should not
stop a show.
const GREETINGS = ['HELLO', 'BONJOUR', 'HOLA'];
function onStart() {
every(0.5, function () {
this.text.set(random.element(GREETINGS));
this.textColor.set(random.color({ saturation: [0.6, 1] }));
this.offsetX.set(random.float(-0.1, 0.1));
});
}
this
The cue’s own properties, each an object with get() and — when the property
can be changed — set():
function onStart() {
log.info('level is', this.level.get());
this.level.set(0.5);
// A property with no setter is read-only; test for one before using it.
if (this.brightness && this.brightness.set) {
this.brightness.set(1.4);
}
}
Which properties a cue has depends on its type — see
properties.md. this works in onStart, onTick, onEnd
and inside timer callbacks, as long as the callback is a function and not an
arrow (() => {} has no this of its own).
What a script writes is live only: it changes what is playing, never the saved show. Editing the same field in the inspector while the cue plays overwrites what the script set.
The editor
- Ctrl/Cmd+S compiles.
- Ctrl+Space — or just typing — proposes the hooks, the API,
this.<name>for the selected cue, and the variables the script itself declares. Enter or Tab accepts, Esc dismisses. - Tab indents when no proposal is open; Ctrl/Cmd+Z undoes.