Custom Modes

JS API Reference

Write plain JavaScript modes for Shimmer's 12 by 12 LED grid and MIDI engine. Scripts run on-device in Duktape and can be tested live in the browser simulator.

Lifecycle activate, update, optional deactivate
Display 12 columns by 12 rows, row 0 at the top
Timing m.dt is clamped to 1..96 ms
Limit 12,288 bytes per uploaded script

Script Template

/**
 * @name My Mode
 * @author Your Name
 * @hue 32
 * @sat 220
 * @param_label Motion
 * @description One-line summary shown in the web UI.
 * @tags Tilt, Generative
 * @sound Warm pluck
 */

var state = 0;

function activate(m) {
  state = 0;
  m.clear();
  m.show();
}

function update(m) {
  // Called every frame. Draw pixels and trigger notes here.
  m.show();
}

// Optional:
// function deactivate(m) {}

Metadata Tags

TagPurpose
@nameMode name shown in slot labels and controls.
@authorShown in the Scripts tab.
@param_labelLabel for the per-slot Density slider in Controls.
@descriptionShown in UI descriptions.
@tagsComma-separated script tags for browsing: Tilt, MIDI in, Clocked, Generative, Ambient, Rhythm, Chords, Utility, Physics, Game, Humidity, Temperature.
@soundOptional sound hint; still searchable and used as fallback context for older scripts.
@hueDefault hue for m.px(col, row, brightness), 0..255.
@satDefault saturation for m.px(col, row, brightness), 0..255.

Tag Guide

Use up to four tags that describe how the script behaves. Tags are shown in the script library and drive the tag filter.

TiltResponds to tilt, shake, knock, steering, rolling, or other motion.
MIDI inUses incoming MIDI notes or messages as part of the script.
ClockedLocks events to tempo, beats, loops, pulses, or regular steps.
GenerativeCreates self-running patterns, melodies, or behaviour without needing live input.
AmbientSlow, textural, pad-like, droney, sparse, or background-friendly.
RhythmBeat-first scripts: Euclidean patterns, pulses, bursts, polyrhythms, or grids.
ChordsHarmony, drones, chord memory, voicing, stacked notes, or sustained groups.
UtilityPractical tool modes such as monitors, quantisers, tests, and setup helpers.
PhysicsSimulates movement, particles, falling, bouncing, fluid, flocking, growth, or pressure.
GameGame-like rules, objectives, scoring, growth, collision, or cellular automata.
HumidityReads m.humidity and changes behaviour from the humidity sensor.
TemperatureReads m.temp and changes behaviour from the temperature sensor.

Example: @tags Tilt, MIDI in, Physics, Humidity

Runtime Lifecycle

activate(m) is called once when the mode becomes active. Reset state here.

update(m) is called every frame, roughly 60 fps. Draw pixels and trigger notes here.

deactivate(m) is optional. Firmware always sends note-off and clears the display on mode exit.

m Properties

PropertyTypeMeaning
m.dtmsFrame delta time, clamped to 1..96ms.
m.beatMsmsMilliseconds per beat at current tempo or external MIDI clock, clamped to 40..4000ms.
m.density0..255Per-slot density knob value.
m.brightness0..255Per-slot brightness value.
m.rootNote0..127Current root MIDI note used by scale-degree output.
m.scale0..90 Major, 1 Minor, 2 Dorian, 3 Pentatonic, 4 Chromatic, 5 Mixolydian, 6 Lydian, 7 Phrygian, 8 Harmonic Minor, 9 Whole Tone.
m.COLS12Grid width.
m.ROWS12Grid height.
m.accelX-128..127Tilt forward/back. Positive means the top edge is tilted down.
m.accelY-128..127Tilt left/right. Positive means tilted right.
m.accelZ-128..127Roughly +64 when flat, decreasing as the device tilts onto its side.
m.motion0..255Motion magnitude. Spikes transiently on knock or shake.
m.tempnumberTemperature in Celsius on supported hardware.
m.humiditynumberRelative humidity percentage on supported hardware.

Grid Reference

m.px(col, row, ...) uses col 0 on the left and row 0 at the top.

row 0, top edge
112132144
row 11, bottom edge

Accelerometer Patterns

ActionResult
Tilt right, LED 12/144 side downaccelY becomes positive.
Tilt top-down, LED 1-12 edge downaccelX becomes positive.
Lay flat, display upaccelZ is about +64.
Stand on edgeaccelZ moves toward 0.
// Slow tilt smoothing
var smooth = 0;
smooth += (m.accelY - smooth) * (m.dt / 8000.0);

// Motion knock detection
if (m.motion > 150 && lastMotion <= 150) {
  // fires once per knock
}
lastMotion = m.motion;

Pixel Functions

m.px(col, row, brightness)Set a pixel using @hue and @sat.
m.px(col, row, hue, sat, val)Set a pixel with explicit HSV values, all 0..255.
m.fade(amount)Subtract amount from every pixel. Default is 3.
m.clear()Set all pixels to black.
m.show()Push the pixel buffer to LEDs. Call once per update.

MIDI Functions

m.note(degree, velocity, durationMs)Play a scale degree. Velocity defaults to 80, duration to 1 beat.
m.noteMidi(note, velocity, durationMs)Play an absolute MIDI note, 0..127.
m.noteOn(note, velocity)Raw note-on, held until m.noteOff() or m.allOff().
m.noteOff(note)Raw note-off.
m.cc(cc, value)Send CC on the current output channel.
m.pitchBend(value)Signed pitch bend, clamped to -8192..8191.
m.allOff()Cancel held notes on the current output channel.

degree maps through the active scale and root. Degree 0 is the selected root MIDI note exactly; degrees 0-6 span one octave diatonically and 7-13 continue into the next.

MIDI In

Incoming USB and DIN MIDI messages are available each frame via read-once properties, filtered by the mode's selected MIDI In channel.

PropertyTypeMeaning
m.midiType0..40 none, 1 noteOn, 2 noteOff, 3 CC, 4 pitchBend.
m.midiChannel0..16MIDI input channel, 1..16 when an event arrived.
m.midiNote0..127 or 255Note number, 255 when no note event arrived.
m.midiVel0..127Velocity, 0 for noteOff.
m.midiCC0..127 or 255CC number, 255 when no CC arrived.
m.midiCCVal0..127CC value.
m.midiBend-8192..8191Pitch bend amount, 0 centered.
function update(m) {
  if (m.midiType === 1) {
    var col = Math.floor(m.midiNote * m.COLS / 128);
    // spawn something at col
  }
  m.show();
}

Clock Mode

AutoFollow external clock when present, otherwise run internal clock and send clock out.
LeaderIgnore external clock and send your own clock out.
FollowerFollow external clock and do not send your own clock out.
InternalIgnore external clock and do not send clock out.

Timing And Helpers

m.tick(timerId, intervalMs)Returns true once per interval. Timer id is 0..7.
m.rnd()Random integer from 0..255.
m.rnd(max)Random integer from 0..max-1.
m.degreeToCol(degree)Map scale degree 0..6 to display column 0..11.
m.colToDegree(col)Map display column 0..11 to scale degree 0..6.
m.map(v, inLo, inHi, outLo, outHi)Linear float mapping.

Practical Rules

  • Call m.show() exactly once per update(), at the end.
  • Script globals persist while the mode is active. Reset them in activate().
  • Use m.dt for movement so speed is frame-rate independent.
  • Use m.tick() for rhythmically stable events.
  • Clamp unusually large m.dt or m.beatMs before using them in physics or divisions.
  • Avoid startup note storms and cap note output in physics-heavy modes.
  • Use current API names only: m.dt, m.beatMs, and m.px().
  • Avoid browser-only APIs like setTimeout(), document, window, and fetch().