Skip to content

Preset format description

This document describes the format of the Electra One preset file. The preset file holds a complete definition of an Electra One preset: the pages, the devices, the controls and the MIDI messages they send. No other data needs to be transferred to Electra to run a preset.

Two files may sit beside a preset in the same slot but are not part of it: the preset's Lua script (main.lua) and the preset's router script (router.lua). Snapshots, captures and the device overrides are separate documents too.

This page describes the format exactly as firmware 5.0 reads and writes it. Where the controller and the Preset Editor differ, the controller is what counts: it is the program that loads the file.

Preset JSON format

Minification

We strongly advise minifying the JSON data prior to transfer to Electra One over the USB MIDI protocol. Minification greatly affects the amount of data that must be transferred, therefore it speeds up the time of uploading and downloading presets.

Size limits

LimitValue
Preset file1,228,800 bytes
JSON parser pool1,000,000 bytes
Devices read32
Controls432, which is what the Lua controls.create function enforces; the reader itself does not count them

A preset larger than the file limit is cut short when it is copied into RAM, and the cut document does not parse. A preset that fits but needs more than the parser pool does not parse either. Minify the JSON and both limits are far away.

How the controller reads a document

The reader takes the arrays in this order:

  1. version - the whole document is rejected unless it is 2 or 3
  2. pages
  3. devices
  4. overlays
  5. groups
  6. controls

The order matters in two places:

  • a value's overlayId resolves against the overlays already read, and overlays always come first, so any overlay may be named by any value
  • a control and a group share one id space, and the first object carrying an id keeps it (see Duplicate ids)

Unknown keys are ignored everywhere, at every level. A preset may therefore carry attributes the controller does not know about - the editor's own bookkeeping, for instance - without any effect on the load.

Two attributes are also read by a fast path that does not parse the whole file, used when the controller lists the presets in a bank:

  • name has to appear within the first 200 bytes of the file. Further in, the controller falls back to a filtered parse of the whole file, which is slower but gives the same answer.
  • projectId has to appear within the first 300 bytes. Further in, the slot reads as having no project id, which detaches it from the editor's metadata, from its snapshots and from its device overrides.

Write version, name and projectId first, as the editor does, and neither limit is ever reached.

Out-of-range numbers

Most numeric attributes are read into a narrow integer. A number that does not fit converts to 0 first, and only then is the range applied, so "controlSetId": 300 is read as 0 and constrained to control set 1 - not to control set 3.

The exceptions are the attributes the reader takes wide on purpose, to be able to refuse them rather than fold them: pages[].id, a control's or group's pageId, a message's deviceId and a message's bitWidth.

Model differences

The same document loads on every model, but a few limits depend on the model it is loaded on:

Electra One mk2Electra One mini
Highest page id1216
Highest potId1212 (1-8 knobs, 9-12 the four pads)
Control sets33
Screen1024 x 600800 x 480

Nothing else in the reader depends on the model, with one exception: a list control placed on the mini's pad row is read in cycle mode whatever its document says. See mode.

Top level objects

The preset has a number of top-level objects. These are either simple elements providing info about the preset itself or complex objects that define the structure and data of the preset.

json
{
  "version": 3,
  "name": "ADSR Test",
  "projectId": "d8WjdwYrP3lRyyx8nEMF",
  "pages": [
  ],
  "devices": [
  ],
  "overlays": [
  ],
  "groups": [
  ],
  "controls": [
  ]
}

version

The version of the preset file format.

Only 2 and 3 load. Any other number - including a missing version, which reads as 0 - makes the controller refuse the whole preset. The two versions describe the same document; version 3 is what the Preset Editor writes now, version 2 is every preset written before it. A preset is written back with the version it was read with, so nothing is silently renumbered.

  • mandatory
  • numeric
  • enum: 2, 3

name

A name of the preset. The name will be shown to the user on the screen in the status bar.

  • optional
  • string
  • default = "No name"
  • maxLength = 20, longer names are truncated

projectId

An external identifier. This id can be used to refer to any external data within your Electra applications. For example, the projectId is used to associate a preset with its metadata - the preset description, author and so on - in the Electra One Preset Editor, and it is what ties the controller's snapshots and device overrides to the preset.

  • optional
  • string
  • default = ""
  • maxLength = 20, longer ids are truncated

pages

An array of pages. A page is a collection of controls and groups displayed to the user at once.

  • optional
  • array
example:
json
"pages": [
   {
      "id": 1,
      "name": "OSCILLATORS"
   },
   {
      "id": 2,
      "name": "FILTER"
   }
]

devices

An array of devices. A device is a hardware MIDI device or software device (application, VST/AU plugin) connected to the Electra One.

  • optional
  • array
  • at most 32 entries are read; the rest are ignored
example:
json
"devices": [
   {
      "id": 1,
      "name": "My MKS-50",
      "port": 1,
      "channel": 1,
      "rate": 20
   },
   {
      "id": 2,
      "name": "BeatFX plugin",
      "port": 2,
      "channel": 1
   }
]

overlays

An array of overlays. An overlay is a list of text labels or graphical symbols that can be assigned to a control.

  • optional
  • array
example:
json
"overlays": [
   {
      "id": 1,
      "items": [
         { "value": 0, "label": "SAW" },
         { "value": 1, "label": "SQUARE" },
         { "value": 2, "label": "WHITE NOISE" },
         { "value": 4, "label": "PINK NOISE" }
      ]
   }
]

groups

An array of groups. A group is a graphical separator that improves the layout of a preset, and may also display a MIDI parameter.

  • optional
  • array
example:
json
"groups": [
   {
      "id": 500,
      "pageId": 1,
      "name": "ATTRIBUTES",
      "bounds": [ 170, 16, 485, 16 ],
      "color": "FFFFFF"
   }
]

controls

An array of controls. A control is a representation of one or more MIDI parameters or messages that can be controlled by the user.

  • optional
  • array
example:
json
"controls": [
   {
      "id": 1,
      "type": "fader",
      "name": "WHITE",
      "color": "FFFFFF",
      "bounds": [ 0, 40, 146, 56 ],
      "pageId": 1,
      "controlSetId": 1,
      "inputs": [
         { "potId": 1, "valueId": "value" }
      ],
      "values": [
         {
            "id": "value",
            "min": 0,
            "max": 127,
            "message": {
               "deviceId": 1,
               "type": "cc7",
               "parameterNumber": 1,
               "min": 0,
               "max": 127
            }
         }
      ]
   }
]

Page

A page is a collection of controls and graphic objects shown on the screen at once. Each page has a name to make it easier to switch between pages. The page name is shown in the status bar.

A page appears in the pages menu and in the control picker only when some control or group refers to it, and only when it is not hidden.

Page 0 is the performance page. The controller makes it itself; a preset document never declares it.

example:
json
{
   "id": 1,
   "name": "OSCILLATORS",
   "defaultControlSetId": 0,
   "hidden": false
}

id

A unique identifier of the page. The identifier is used by other objects to refer to a particular page.

A page whose id is not in range is ignored, and so is a page with no id at all: the whole entry is left out and an error is logged. The page is not folded onto page 1 or onto the last page, which is what firmware before 5.0 did.

  • mandatory
  • numeric
  • min = 1
  • max = 12 on an mk2, 16 on a mini

name

A name of the page. The name makes it easier for users to get oriented in Electra presets.

  • optional
  • string
  • default = "No name"
  • maxLength = 20

defaultControlSetId

The control set that is selected when the page is opened. It is read only when the controller's active control set setting is Custom; with the other settings the control set is global or remembered per page.

This number is counted from zero: 0 is control set 1, 1 is control set 2, 2 is control set 3. It is the one place in the preset format where a control set is zero based - a control's own controlSetId is counted from one. The value is not range checked.

  • optional
  • numeric
  • default = 0
  • min = 0
  • max = 2

hidden

A hidden page is left out of the pages menu and out of the control picker. It can still be opened by its id - by a switchPage command, by the MIDI control interface or from Lua.

  • optional
  • boolean
  • default = false

Device

A device refers to a MIDI hardware or software instrument connected to one of Electra's MIDI ports. This could be a hardware synthesizer connected via a port on the MIDI IO interface, a hardware sequencer connected to Electra's USB host interface, or a software plugin communicating through Electra's USB device port. Electra supports up to 32 simultaneously connected devices.

When working with Electra, you must define your connected devices explicitly - you do not send or receive MIDI messages directly to a port and channel, unless you are using low-level Lua API calls. This abstraction ensures consistent device handling and simplifies preset and control configuration.

example:
json
{
   "id": 1,
   "name": "Generic MIDI",
   "port": 1,
   "channel": 1
}

id

A unique identifier of the device. The identifier is used in other objects to refer to a particular device.

A value outside 1 .. 32 is constrained into it, so 0 and a missing id both become device 1. A device that repeats an id replaces the earlier one, and still counts towards the limit of 32.

  • mandatory
  • numeric
  • min = 1
  • max = 32

name

A user-defined name of the device. The name makes it easier for users to remember and identify devices.

  • optional
  • string
  • default = ""
  • maxLength = 20

port

A port number represents a MIDI bus inside the Electra One. Port 1 interconnects MIDI IO Port 1, USB Host Port 1, and USB Device Port 1. Similarly, Port 2 interconnects MIDI I/O Port 2, USB Host Port 2, and USB Device Port 2. That is true of a device wired to all three interfaces, which is the default; a device whose interfaces name fewer uses the port on those interfaces only.

The Electra One Mini has a single pair of MIDI DIN sockets, so on a Mini port 2 is reached over USB only.

The attribute may be a number or a string. A number outside 1 .. 2 is constrained into it, so 0 and a missing port both mean port 1. As a string, "port1" and "port2" mean what they say; "ctrl" - the name the device overrides file uses for the MIDI control port - reads as port 1 here, and so does any other string.

  • optional
  • numeric, or the string port1, port2 or ctrl
  • default = 1
  • min = 1
  • max = 2

channel

The MIDI channel of the device. Electra sends the device's messages on this channel, and an incoming channel message is taken to be from the device when it arrives on the device's port and channel, through one of its interfaces.

SysEx carries no channel. An incoming SysEx message is matched against every device wired to the interface it arrived on.

A value outside 1 .. 16 is constrained into it, so 0 and a missing channel both mean channel 1.

  • optional
  • numeric
  • default = 1
  • min = 1
  • max = 16

interfaces

The MIDI interfaces the device is wired to. It applies in both directions: the device's messages are sent only to these interfaces, and messages, including SysEx, are accepted from the device only when they arrive through one of them.

The value is an array of interface names, or a single name as a string:

  • midiIo - the MIDI DIN sockets
  • midiUsbDev - the USB device port, the connection to a computer
  • midiUsbHost - the USB host port
  • midiAll - all three

When the attribute is omitted, the device is wired to all three interfaces. An empty array wires the device to nothing: it sends and receives no MIDI. Names that are not recognised are ignored. A value that is neither a string nor an array - a number, say - wires the device to all three. Electra leaves the attribute out when it saves a device wired to all three.

json
"interfaces": [ "midiIo", "midiUsbHost" ]
  • optional
  • array of strings, or a string
  • default = all three interfaces

runningStatus

When true, Electra uses MIDI running status for the device: a status byte that repeats the previous one is left out. Only the literal true turns it on; an omitted attribute, false, or any other value leaves it off.

It applies to the messages of the preset's controls sent to the MIDI IO (DIN) sockets. USB is not affected, and messages sent from Lua always carry full status bytes.

  • optional
  • boolean
  • default = false

rate

Reserved. It was meant as a minimum wait time between messages sent to the device, in milliseconds. The value is read, constrained to 10 .. 1000, and saved with the preset, but it is currently not in effect: it does not slow down the messages sent to the device.

A device with no rate reads as 10, and Electra always writes the attribute back, so a device saved by the controller comes back carrying "rate": 10 even when the document it was read from had none.

  • optional
  • numeric
  • default = 10
  • min = 10
  • max = 1000

patch

Instructions for requesting and parsing patch dumps of the device. Each entry has a request, a SysEx message sent when patches are requested, and an array of responses, each with a header that identifies an incoming SysEx message and rules that say which bits of it go to which parameters.

request

The request is compiled as a SysEx template and framed: a leading F0 and a trailing F7 are added if the template does not carry them. Every entry in patch pushes one request, even an entry whose request is missing or empty - that pushes an empty request, which sends nothing.

Requests are sent by the requestPatch command, by the Request patch item in the preset menu, and from Lua.

response
KeyTypeDefaultMeaning
idnumeric0Identifies the response to Lua. patch.onResponse receives it, and getResponseIndex finds a response by it.
headerarray-A SysEx template that identifies an incoming message. A leading F0 is added; no F7 is added, because a header is a prefix rather than a whole message. At most 64 bytes. A longer header is emptied and the response never matches.
rulesarray[]Rules that take values out of the message.

The responses of all the patch entries of one device are flattened into a single list. The pairing between a request and its responses is not kept: any response of the device is tried against every incoming SysEx message from it, whichever request was sent.

rule

A rule takes a run of bits out of one byte of an incoming message and writes them into one parameter.

KeyLegacy spellingTypeDefaultMeaning
type-stringsysexThe message type the parameter belongs to. A missing type means sysex; an unrecognised name means none.
parameterNumberidnumeric0The parameter to write.
byte-numeric0Which byte of the message to read, counted from the start of the message including the F0.
parameterBitPositionpPosnumeric0Where in the parameter value the bits go.
byteBitPositionbPosnumeric0Where in the byte the bits are.
bitWidthsizenumeric7How many bits.

Both spellings of each key are accepted; the canonical one wins where a rule carries both. Electra writes the canonical spelling only.

byteBitPosition + bitWidth greater than 7, or parameterBitPosition + bitWidth greater than 14, logs an error. The rule is kept and applied as written.

example:
json
"patch": [
   {
      "request": [ "F0", "41", "10", "6A", "11", "00", "00", "00", "00", "00", "00", "00", "40", "2F", "F7" ],
      "responses": [
         {
            "id": 1,
            "header": [ "41", "10", "6A", "12" ],
            "rules": [
               { "type": "sysex", "parameterNumber": 10, "byte": 8,  "byteBitPosition": 0, "bitWidth": 7 },
               { "type": "sysex", "parameterNumber": 11, "byte": 9,  "byteBitPosition": 0, "bitWidth": 7 },
               { "type": "sysex", "parameterNumber": 12, "byte": 10, "byteBitPosition": 0, "bitWidth": 4 }
            ]
         }
      ]
   }
]

messages

A library of SysEx templates for the device. A control's message uses one by naming its id in data, so a template shared by many controls is written once. Each entry is an object with an id, the template in data, and an optional direction.

A template that carries a parameter placeholder can describe the whole parameter change format of an instrument: controls name it with their own parameterNumber, and an incoming message says which parameter it is for.

The template is compiled and framed - a leading F0 and a trailing F7 are added when the data does not carry them. An entry that repeats an id replaces the earlier template, and its direction replaces the earlier one too, falling back to out when the new entry gives none.

json
"messages": [
  {
    "id": 1,
    "direction": "both",
    "data": [ "43", "10", "04", { "type": "parameter" }, { "type": "value" } ]
  },
  {
    "id": 2,
    "direction": "in",
    "data": [ "41", { "type": "any" }, "12", { "type": "parameter" }, { "type": "value" } ]
  }
]
  • optional
  • array of objects
    • id: numeric, 0 .. 65535, unique within the device
    • data: array, the SysEx template
    • direction: see direction, default out

Electra leaves an entry out when it saves a device if its template is empty, and writes direction only when it is not out.

Overlay

Overlays are lists of text labels linked to particular MIDI values. Typically they are assigned to selection list controls or faders. Overlays are referred to by their identifier. Overlay items can be both text labels and graphical symbols represented with bitmap images. An item may also carry a color, which overrides the color of the control displaying it.

examples:

an overlay with text labels only

json
{
   "id": 1,
   "items": [
      { "value": 0, "label": "SAW" },
      { "value": 1, "label": "SQUARE" },
      { "value": 2, "label": "WHITE NOISE" },
      { "value": 3, "label": "PINK NOISE" }
   ]
}

an overlay with bitmap data

json
{
   "id": 3,
   "items": [
      {
         "value": 0,
         "label": "Square",
         "bitmap": "AAAAAAAA/P//AQAA/P//AQAwDACAAQAwDACAAQAwDACAAQAwDACAAQAwDACAAQAwDACAAQAwDACAAQAwDACAAQAwDACAAQAwDACAAQAwDACAAQAwDACA//8/DACA//8/AAAAAAAAAAAAAAAA"
      },
      {
         "value": 1,
         "label": "Triangle",
         "bitmap": "AAAAAAAAAADgAAAAAADwAQAAAAAYAwAAAAAMBgAAAAAGDAAAAAADGAAAAIABMAAgAMAAYAAwAGAAwAAYBDAAgAEMDBgAAAMGGAwAAAYDMAYAAIwB4AMAAPgAwAEAAHAAAAAAAAAAAAAAAAAA"
      }
   ]
}

id

A unique identifier of the overlay. Values use the identifier to refer to a particular overlay.

The id is held in one byte, so it is 0 .. 255. It is not range checked and it is not constrained: a number that does not fit a byte reads as 0. An overlay that repeats an id replaces the earlier one.

Overlay 0 cannot be referred to: overlayId 0 on a value means "no overlay".

  • mandatory
  • numeric
  • min = 1
  • max = 255

items

An array of value - label pairs. There is no limit on the number of items.

  • mandatory
  • array

value

A MIDI value assigned to the label.

The value is a signed 16-bit number, so negative values work - which is what a list of signed parameter settings needs.

  • mandatory
  • numeric
  • min = -32768
  • max = 32767

label

A text label assigned to the MIDI value. The control displays the text labels to the users.

  • optional
  • string
  • default = ""
  • maxLength = 20

bitmap

A base64 encoded bitmap image. The bitmap image is in XBM format with a size of 48 x 18 pixels, which is 108 bytes of image data.

Electra cannot write a bitmap back: the decoded image is kept in the bitmap pool and the original base64 text is not. Saving a preset that has bitmaps loses them, and the Lua preset.save function refuses to save such a preset unless it is called with { dropBitmaps = true }.

  • optional
  • base64 encoded XBM image

color

A 24-bit RGB code applied to whatever draws the item. When an item carries a color, the control showing that item is drawn in it instead of the control's own color. Items without a color are drawn in the control's color, as all overlay items were before this attribute existed.

This is what lets a single overlay carry a color scheme: a list whose entries are color coded, or a group that takes both its label and its color from the value it displays.

Note that 000000 is black rather than "no color". Omit the attribute to leave the item without one.

  • optional
  • string, six hexadecimal digits, no leading #
  • minLength = 6
  • maxLength = 6

Group

Graphical separators used to organize controls into groups by meaning. For example a group "Envelope 1" can be created for controls "Attack", "Decay", "Sustain", and "Release".

A group may also follow a MIDI parameter. A group with a values array is registered on that parameter exactly as a control's value is, and it displays the overlay item the current value selects - taking both the item's label and, where the item has one, its color. A group never sends anything: it shows a parameter and is not driven by a pot or by touch.

example:
json
{
   "id": 500,
   "pageId": 1,
   "name": "ENVELOPE",
   "bounds": [ 0, 16, 486, 16 ],
   "color": "FFFFFF"
}

id

A unique identifier of the group. Electra uses the id to uniquely identify each group. The identifiers are shared with controls, so an id names either a control or a group and never both. See Duplicate ids.

The id is held in two bytes and the reader does not range check it. The Preset Editor mints ids in 1 .. 864 and the Lua API refuses an id outside 1 .. 864, so stay inside that range if the preset is to be edited or scripted.

  • mandatory
  • numeric
  • min = 1
  • max = 864 in practice

pageId

A reference to a page identifier. Each group belongs to exactly one page.

A group that names a page id out of range is ignored, whole. A group with no pageId at all lands on page 1.

The page does not have to be declared in pages, but it should be. A page that only a control names exists and appears in the pages menu, with an empty name.

  • optional
  • numeric
  • default = 1
  • min = 1
  • max = 12 on an mk2, 16 on a mini

name

A name of the group. The name is shown to the user inside the group graphics. The name is trimmed to fit the size of the group.

  • optional
  • string
  • default = ""
  • maxLength = 40

bounds

A bounding box of the group, ie. the definition of the group's position on the screen and its size. The bounding box is represented as an array of [x, y, width, height].

Each number is read into two bytes and is not checked against the size of the screen. A missing or short array reads as zeros.

  • optional
  • array of four numbers
  • default = [0, 0, 0, 0]

color

A 24-bit RGB code defining the group's color. Electra One internally uses 16-bit RGB565 color format, so the final displayed color may differ slightly due to conversion.

A group with no color is white. The string is read as hexadecimal, so it must be six hex digits with no leading #: "#FFFFFF" reads as 000000, black.

  • optional
  • string
  • default = FFFFFF
  • minLength = 6
  • maxLength = 6

variant

The variant of the group, which determines its visual style.

buttonlike draws the group as a pad-shaped button: a rounded rectangle half the group's width, centred in it. When the group carries a values array, the colour the selected overlay item gives it decides how the button is drawn - black draws its outline only, the way an unlit pad looks, and any other colour fills it.

The reader accepts every control variant here as well, but only these three change how a group is drawn.

  • optional
  • enum
    • "default"
    • "highlighted"
    • "buttonlike"
  • default = "default"

font

The face the group draws its name in. Same object as a control's font, with a different default: a group draws in small bold proportional, and a member left out of the object keeps that fallback rather than the control default.

  • optional
  • object

values

An array holding the value that names the MIDI parameter the group follows. A group draws one label, so one value is what it uses. The group displays the overlay item that the parameter's current value selects: the item's label replaces the group's name, and where the item carries a color it replaces the group's color too. An item without a label leaves the group's own name showing, so an overlay may recolor a group without renaming it.

The value needs a message and an overlayId; the rest of the value attributes have no meaning for a group, which neither transmits nor is turned by a pot. A group without a values array behaves exactly as groups always have.

A group has no controlSetId, no inputs, no visible and no mode, and its events are not read.

  • optional
  • array
example:

A group reading ACTIVE or INACTIVE from CC 2, in a different color for each.

json
"overlays": [
   {
      "id": 1,
      "items": [
         { "value": 0,   "label": "INACTIVE", "color": "3D3D3D" },
         { "value": 127, "label": "ACTIVE",   "color": "03A598" }
      ]
   }
],
"groups": [
   {
      "id": 500,
      "pageId": 1,
      "name": "STATE",
      "bounds": [ 12, 20, 182, 16 ],
      "color": "FFFFFF",
      "values": [
         {
            "id": "value",
            "overlayId": 1,
            "message": {
               "deviceId": 1,
               "type": "cc7",
               "parameterNumber": 2
            }
         }
      ]
   }
]

Control

A control is a representation of a MIDI parameter or a MIDI message. Controls visualize and change values of MIDI parameters. A control is for example a fader, knob, pad, or ADSR envelope. A control consists of information about values that are mapped to particular MIDI messages.

examples:

A simple control with one value assigned.

json
{
   "id": 1,
   "type": "fader",
   "name": "WHITE",
   "color": "FFFFFF",
   "variant": "thin",
   "mode": "unipolar",
   "bounds": [ 0, 40, 146, 56 ],
   "pageId": 1,
   "controlSetId": 1,
   "visible": true,
   "inputs": [
      { "potId": 1, "valueId": "value" }
   ],
   "values": [
      {
         "id": "value",
         "min": 0,
         "max": 127,
         "message": {
            "deviceId": 1,
            "type": "cc7",
            "parameterNumber": 1,
            "min": 0,
            "max": 127
         }
      }
   ]
}

a text box, showing a patch name rather than a number

json
{
   "id": 7,
   "type": "textBox",
   "name": "PATCH",
   "color": "529DEC",
   "bounds": [ 12, 40, 380, 60 ],
   "pageId": 1,
   "controlSetId": 1,
   "visible": true,
   "font": {
      "size": "large",
      "spacing": "monospaced"
   },
   "values": [
      {
         "id": "value",
         "placeholder": "no patch",
         "message": {
            "deviceId": 1,
            "type": "virtual",
            "parameterNumber": 400
         }
      }
   ]
}

A text box shows the text set on its parameter - by a Lua script, by the SysEx API, or restored from a snapshot - and its placeholder when there is none. Its value is a parameter like any other, so two controls pointed at the same message show the same text. The virtual message type is the one to use for a box that holds text and sends nothing; a box that does map to a real synth parameter names that instead.

an ADSR control with multiple values assigned

json
{
   "id": 2,
   "type": "adsr",
   "name": "ADSR",
   "color": "F49500",
   "pageId": 1,
   "controlSetId": 1,
   "bounds": [ 10, 40, 158, 73 ],
   "inputs": [
      { "potId": 1, "valueId": "attack" },
      { "potId": 2, "valueId": "decay" },
      { "potId": 3, "valueId": "sustain" },
      { "potId": 4, "valueId": "release" }
   ],
   "values": [
      {
         "id": "attack",
         "min": 0, "max": 127,
         "message": { "deviceId": 1, "type": "cc7", "parameterNumber": 1, "min": 0, "max": 127 }
      },
      {
         "id": "decay",
         "min": 0, "max": 127,
         "message": { "deviceId": 1, "type": "cc7", "parameterNumber": 2, "min": 0, "max": 127 }
      },
      {
         "id": "sustain",
         "min": 0, "max": 127,
         "message": { "deviceId": 1, "type": "cc7", "parameterNumber": 3, "min": 0, "max": 127 }
      },
      {
         "id": "release",
         "min": 0, "max": 127,
         "message": { "deviceId": 1, "type": "cc7", "parameterNumber": 4, "min": 0, "max": 127 }
      }
   ]
}

id

A unique identifier of the control. Electra uses the id to uniquely identify each control. Controls and groups share one id space, so an id names either a control or a group and never both - see Duplicate ids.

The id is held in two bytes and the reader does not range check it. The Preset Editor mints ids in 1 .. 864 and the Lua API refuses an id outside 1 .. 864, so stay inside that range if the preset is to be edited or scripted.

  • mandatory
  • numeric
  • min = 1
  • max = 864 in practice

type

A type of functional and visual representation of the control.

An unknown type, or no type at all, makes a control of type none: it is created, it takes part in the id space, and it draws nothing.

  • mandatory
  • enum
    • fader
    • vfader (vertical fader, four bars)
    • knob
    • list
    • pad
    • adsr
    • ahdsr
    • adssr
    • adr
    • ar
    • dx7envelope
    • xypad
    • relative
    • textBox
    • custom
    • macro

group is not accepted here. A group is declared by being in the groups array.

A relative control has no position. Turning its knob sends steps - an increment or a decrement - encoded as its message's relativeMode says, and the control shows the last step taken. See relativeMode and accelerated in the Message object.

A custom control is drawn entirely by Lua. Its pots are told apart by the valueId string of each input, which is kept verbatim for that purpose.

A macro control drives other controls' values through its valueRefs array.

name

A name of the control. The name is usually shown underneath the control. When the control receives a touch event via the physical knob, the name is highlighted. If the name is an empty string or the attribute is omitted, the name is not shown and touch indication is disabled.

  • optional
  • string
  • default = ""
  • maxLength = 40

color

A 24-bit RGB code of the control's color. Electra uses 16-bit RGB565 colors internally. The conversion might affect the final displayed color.

A control with no color is white. The string is read as hexadecimal, so it must be six hex digits with no leading #: "#F45C51" reads as 000000, black.

Firmware before 5.0 read a missing color as aqua (00FFFF).

  • optional
  • string
  • default = FFFFFF
  • minLength = 6
  • maxLength = 6

variant

A visual variant of the control. Variants provide more subtle control of the visual appearance. Which variants a control accepts depends on its type:

  • optional
  • string
  • default = default
  • enum
    • default
    • thin (fader) - a low track under the value
    • dial (fader) - a knob instead of a track
    • valueOnly (list, textBox, pad) - no name, the value across the control
    • checkbox (pad) - a toggle switch
    • highlighted (group)
    • buttonlike (group)

highlighted and buttonlike are the group variants. They parse on a control too, and no control component draws them differently.

A pad with the valueOnly variant shows a word for its state instead of its name: an outline in the control's colour over a very dim fill while it is off, a solid fill while it is on. The word comes from the value's overlay - see overlayId - which holds two items whose values are the message's offValue and onValue. A pad without such an item falls back on its name.

mode

A predefined behaviour of the control. Mode does not affect how the control looks, instead it affects how it works.

  • optional
  • string
  • default = default
  • enum
    • default
    • unipolar
    • bipolar
    • momentary (pad)
    • toggle (pad)
    • cycle (list) - stepping past the last item wraps around to the first, and back

The reader changes the mode in three cases:

ControlGivenRead as
faderdefault, or no modebipolar
list with variant: valueOnlyanythingcycle
list on a mini whose bounds y is over 358 - the pad rowanythingcycle

So a fader never actually runs in default mode, and a valueOnly list always cycles. Electra writes the mode it read, not the mode the document gave.

font

The face the control draws its text in. Only control types that draw text of their own read it; a textBox is the one that does.

Size, weight and spacing are chosen independently, and each is optional on its own. A control that declares no font, or leaves a member out of one, draws in the default for that member - which is what every control drew in before the attribute existed. A member that is not a string keeps the default too.

A group's default font is different: small bold proportional.

  • optional
  • object
json
"font": {
   "size": "large",
   "weight": "bold",
   "spacing": "monospaced"
}
font.size
  • optional
  • string
  • default = medium (small for a group)
  • enum
    • small
    • medium
    • large
    • huge

Any other string reads as medium.

font.weight
  • optional
  • string
  • default = regular (bold for a group)
  • enum
    • regular
    • bold

Any other string reads as regular.

font.spacing

Monospaced advances every character by the same width, which is what makes a column of names line up under one another.

  • optional
  • string
  • default = proportional
  • enum
    • proportional
    • monospaced

Any other string reads as proportional.

bounds

A bounding box of the control, ie. the definition of the control's position on the screen and its size. The bounding box is represented as an array of [x, y, width, height].

Each number is read into two bytes and is not checked against the size of the screen. A missing or short array reads as zeros, which puts a zero-sized control in the top left corner.

  • optional
  • array of four numbers
  • default = [0, 0, 0, 0]

pageId

A reference to a page identifier. Each control belongs to exactly one page.

A control that names a page id out of range is ignored, whole. A control with no pageId at all lands on page 1.

The page does not have to be declared in pages, but it should be. A page that only a control names exists and appears in the pages menu, with an empty name.

  • optional
  • numeric
  • default = 1
  • min = 1
  • max = 12 on an mk2, 16 on a mini

controlSetId

Controls placed on one page can be further divided into control sets. The control sets are used to assign controls to pots (knobs). Users may switch between control sets by pressing the hardware buttons, using screen touch, or by sending MIDI messages to Electra. Only one control set can be active at any time. The controls of the active control set are highlighted with the indicators on the sides of the screen.

Counted from one here. A value outside 1 .. 3 is constrained into it, so 0 and a missing controlSetId both mean control set 1.

The mini uses one control set.

  • optional
  • numeric
  • default = 1
  • min = 1
  • max = 3

visible

A boolean flag to manage the visibility of a control. When set to false, the control is invisible.

  • optional
  • boolean
  • default = true

inputs

An array of inputs: which physical pots turn which of the control's values.

A control with no inputs cannot be turned, but can still be changed by incoming MIDI, by Lua, by an event and by a snapshot.

  • optional
  • array
  • default = []

values

An array of values associated with the control. A value represents an instance of the value of a certain MIDI parameter. Actions made with the control (turning the assigned pot, touch events) effectively change associated values and trigger transmission of MIDI messages.

The array does not have to be in handle order, and it does not have to be complete: an adsr with only attack and decay is legal. The reader sorts the values by handle index after reading them.

  • optional
  • array
  • default = []
example:
json
"values": [
   {
      "id": "value",
      "min": -64,
      "max": 63,
      "defaultValue": 0,
      "message": {
         "deviceId": 1,
         "type": "cc7",
         "parameterNumber": 1,
         "min": 0,
         "max": 127
      }
   }
]

events

An array of events, saying what the control's knob switch and knob touch do. A control without this attribute behaves exactly as controls always have.

  • optional
  • array
example:
json
"events": [
   {
      "source": "switch",
      "event": "press",
      "actions": [
         {
            "type": "message",
            "message": {
               "type": "cc7",
               "deviceId": 1,
               "parameterNumber": 2,
               "value": 127
            }
         }
      ]
   },
   {
      "source": "switch",
      "event": "release",
      "actions": [
         {
            "type": "message",
            "message": {
               "type": "cc7",
               "deviceId": 1,
               "parameterNumber": 2,
               "value": 0
            }
         }
      ]
   }
]

valueRefs

The values a macro control drives. Read only for type: "macro" and ignored on every other control type.

A macro has one value of its own - the position of its knob - and each reference says what that position does to another control's value. The same array shape describes the references of a performance macro; see Performance format.

KeyTypeDefaultMeaning
controlIdnumeric0The control to drive. It has to be a control of this preset; a reference that names no control is dropped without a word.
valueIdstring""Which value of that control - the handle name, as in valueId. An unknown name means the first handle.
modestringsetValuesetValue writes the macro's position into the value. modulate adds to whatever the value already holds. dataPipe feeds the value from a data pipe instead of from the knob.
depthnumeric100How much of the macro's travel reaches the value, in percent. Signed, one byte: -128 .. 127, so a negative depth inverts.
pipeobject-For dataPipe: { "name": string, "bankNumber": numeric, "slot": numeric }. Naming a pipe forces mode to dataPipe whatever mode says.

bankNumber, slot and name are also accepted flat on the reference, which is the spelling older firmware wrote. They are read only when there is no pipe object.

  • optional
  • array
example:
json
{
   "id": 20,
   "type": "macro",
   "name": "BRIGHTNESS",
   "color": "F45C51",
   "pageId": 1,
   "controlSetId": 1,
   "bounds": [ 20, 40, 146, 56 ],
   "inputs": [ { "potId": 1, "valueId": "value" } ],
   "values": [
      {
         "id": "value",
         "min": 0,
         "max": 127,
         "message": { "deviceId": 1, "type": "virtual", "parameterNumber": 500, "min": 0, "max": 127 }
      }
   ],
   "valueRefs": [
      { "controlId": 1, "valueId": "value", "mode": "setValue", "depth": 100 },
      { "controlId": 2, "valueId": "value", "mode": "setValue", "depth": -60 },
      { "controlId": 3, "valueId": "attack", "mode": "modulate", "depth": 25 }
   ]
}

Input

An input provides information about the assignment of a physical control or gesture to a value. An example is assigning a knob to a value of the control.

  • optional
  • object
example:
json
{
   "potId": 1,
   "valueId": "attack"
}

potId

An identifier of the physical pot (knob).

On an mk2 there are 12 pots, 1 (top left) to 12 (bottom right). On a mini pots 1 to 8 are the knobs and pots 9 to 12 are the four pads below them.

A pot only reaches the control while the control's control set is active.

A value outside the range is constrained into it, so 0 and a missing potId both mean pot 1.

  • optional
  • numeric
  • default = 1
  • min = 1
  • max = 12

valueId

Which value of the control this pot turns - the handle name, from the table under Value id.

An unknown name, an empty string and a missing attribute all mean handle 0, which is the first handle of the type: value for a fader, attack for an ADSR, x for an xypad, f1 for a vfader.

The string itself is kept as written, up to 20 characters. For a custom control that string is the only thing that tells its pots apart, so use it.

  • optional
  • string
  • default = ""
  • maxLength = 20

Value

A value represents a parameter value of a given control. A value is mapped to a value of a MIDI parameter or a MIDI message. The value object allows translation of MIDI values to user-friendly display values.

examples:

a continuous value

json
{
   "id": "value",
   "min": -64,
   "max": 63,
   "defaultValue": 0,
   "formatter": "formatFractions",
   "function": "disableOscillator",
   "message": {
      "deviceId": 1,
      "type": "cc7",
      "parameterNumber": 1,
      "min": 0,
      "max": 127
   }
}

a value with a list of discrete values (an overlay)

json
{
   "id": "value",
   "overlayId": 2,
   "function": "displaySections",
   "message": {
      "deviceId": 1,
      "type": "cc7",
      "parameterNumber": 2
   }
}

id

An identifier of the value. This identifier is a text string. This is to make it easier for programmers to get oriented. The identifier expresses the meaning of the value, eg. attack, rate, or value.

An unknown name, an empty string and a missing attribute all mean handle 0, the first handle of the control's type.

  • optional
  • string
  • default = "", which is handle 0
  • maxLength = 20
Value id

The names a control type accepts, in handle order:

Control typevalueId
fader, knob, list, pad, textBox, relative, custom, macrovalue
vfaderf1, f2, f3, f4
adsrattack, decay, sustain, release
ahdsrattack, hold, decay, sustain, release
adssrattack, decay, break, slope, sustain, release
adrattack, decay, release
arattack, release
dx7envelopel1, r1, l2, r2, l3, r3, l4, r4
xypadx, y

The same names are used by inputs[].valueId, by a macro's valueRefs[].valueId and by the Lua API.

name

A display name for the handle, overriding the name the control would show for it. An ADSR whose first handle should read "ATK" rather than "ATTACK" sets it here.

  • optional
  • string
  • maxLength = 20

min

A minimum value that the control can display. Note this is not the MIDI value, it is the minimum value that can be displayed by the control.

A value with no min takes the message's min, not 0. A list is forced to 0 and a pad is forced to 0, whatever the document says.

  • optional
  • numeric, signed 16-bit
  • default = the message's min
  • min = -32768
  • max = 32767

max

A maximum value that the control can display. Note this is not the MIDI value, it is the maximum value that can be displayed by the control.

A value with no max takes the message's max, not 0. A list is forced to the number of items of its overlay minus one, or to 1 when it has no overlay; a pad is forced to 1.

  • optional
  • numeric, signed 16-bit
  • default = the message's max
  • min = -32768
  • max = 32767

defaultValue

A value to be set when the preset is loaded. The default value is also recalled when the user double-taps the control on the touch screen, and by the setDefaultValue and setDefaultValues commands.

For most control types it is a number, clamped into min .. max.

For a pad it is a state, and both spellings are read:

  • the strings "on" and "off" - "on" is on, any other string is off. This is what the Preset Editor writes, and what Electra writes back.
  • a number - on when it equals the message's onValue, or when it is 1 and the message's offValue is not 1. Otherwise off.

A list is not clamped.

  • optional
  • numeric, or "on" / "off" for a pad
  • default = none; a value without the attribute has no default

placeholder

The text a control draws when the value holds none of its own. It is the last thing tried: an override set on the parameter comes first, then the formatter's output, and the placeholder shows only when there is neither.

Read by control types whose value is text - a textBox is the one that is. A value that declares no placeholder falls back to whatever its control would otherwise have drawn.

  • optional
  • string
  • minLength = 1
  • maxLength = 20

sensitivity

How far a knob has to turn to move the value, as a factor on its usual rate.

A knob normally sweeps the whole range of a proportional value in one turn of about 300 degrees, so a wide range is a fast knob. sensitivity slows it down, or speeds it up, without changing the range: 0.1 makes a sweep cover a tenth of the range, and 2 twice as much. A list or a pad steps one item per detent of the knob; 0.5 makes it two detents per item.

Fine adjustment (low sensitivity mode) multiplies on top, so a value with 0.5 moves at a twentieth of its usual rate while fine adjustment is on. The same factor applies to remote knobs.

The number is clamped into 0.001 .. 100. Zero, a negative number and a non-number all read as 1.

  • optional
  • number
  • default = 1
  • minimum = 0.001
  • maximum = 100
json
{
   "id": "value",
   "min": 0,
   "max": 1024,
   "sensitivity": 0.03,
   "message": {
      "deviceId": 1,
      "type": "virtual",
      "parameterNumber": 217,
      "min": 0,
      "max": 1024
   }
}

formatter

The name of a Lua function in the preset's script that formats the value for display. See Preset Lua extension.

The function takes one argument, the display value, and returns a string. A formatter that raises an error leaves the unformatted value on the screen and logs the error.

There is no default: a value that names no formatter shows its number.

  • optional
  • string
  • maxLength = 20

function

The name of a Lua function in the preset's script that is called whenever the display value changes. See Preset Lua extension.

The function takes two arguments, the control id and the value.

There is no default: a value that names no function calls nothing.

  • optional
  • string
  • maxLength = 20
The function name table

formatter, function, the name of a function element inside a SysEx template, an event's lua action function and an event message's value when it is a string all name Lua functions, and they all go into one table per preset. The index into that table is one byte, and index 0 means "no function", so a preset may name at most 255 distinct function names.

The 256th distinct name is not registered; an error is logged and:

  • a formatter or function on a value reads as none
  • a lua action, and a message action whose value names a function, is dropped from its event
  • a function element in a SysEx template sends the byte 0
  • parameterMap.setFunction raises an error in Lua

The same name used in several places counts once.

overlayId

A reference to the overlay identifier defined in the array of overlays. The list control will use the overlay items as the list items. A fader control will show overlay labels for matching values. A pad with the valueOnly variant shows the label of the item whose value matches the message's offValue or onValue, whichever state it is in; its overlay therefore holds two items, keyed by those two values.

0, and a missing attribute, mean no overlay.

An id that names an overlay the preset does not declare leaves the value without an overlay - and, on a list, with the range 0 .. 1.

  • optional
  • numeric
  • default = 0, no overlay
  • min = 1
  • max = 255

message

An object that describes a MIDI message assigned to the value. A value with no message gets a message with every default: type sysex, device 0, parameter 0.

  • optional
  • object

Message

An object that describes a MIDI message that will be sent when the value of the control is changed by touch or by turning the knob. The message is also used to parse incoming MIDI messages. When incoming MIDI data matches the message object, the value of the control is adjusted accordingly.

  • optional
  • object
examples:

A simple CC7 message

json
"message": {
   "deviceId": 1,
   "type": "cc7",
   "parameterNumber": 1,
   "min": 0,
   "max": 127
}

A message with a SysEx template

json
"message": {
   "deviceId": 1,
   "type": "sysex",
   "parameterNumber": 6,
   "min": 0,
   "max": 127,
   "data": [
      "43", "10", "01", "0F",
      {
         "type": "value",
         "rules": [
            {
               "parameterNumber": 40,
               "parameterBitPosition": 0,
               "byteBitPosition": 0,
               "bitWidth": 3
            }
         ]
      }
   ]
}

A simple CC7 message with event midi values assigned

json
"message": {
   "deviceId": 1,
   "type": "cc7",
   "parameterNumber": 1,
   "offValue": 0,
   "onValue": 127
}

A NRPN message handling negative values

json
"message": {
   "deviceId": 1,
   "type": "nrpn",
   "parameterNumber": 1,
   "lsbFirst": false,
   "signMode": "twosComplement",
   "bitWidth": 7
}

deviceId

A reference to the device identifier defined in the array of devices. The message will be sent to the referenced device. Also, messages received from this device that match the message definition will modify the value accordingly.

0, and a missing deviceId, mean no device: the message has no port and no channel and nothing is transmitted. It is still a parameter in the map, so a virtual value works exactly this way.

A deviceId that is neither 0 nor 1 .. 32 drops the whole value - the control keeps its other values and an error is logged. Firmware before 5.0 put such a value on device 0, or, past 63, on whatever device the field wrapped onto.

  • optional
  • numeric
  • default = 0, no device
  • min = 1
  • max = 32

type

A type of the MIDI message. The type is not limited to basic MIDI messages but supports their higher level implementation, such as NRPN, etc.

A message with no type is a sysex message. An unrecognised name reads as none.

  • optional
  • enum
    • cc7
    • cc14
    • nrpn
    • rpn
    • sysex
    • note
    • program
    • start
    • stop
    • tune
    • atpoly
    • atchannel
    • pitchbend
    • spp
    • relcc - a relative CC; see relative
    • macro - the parameter a performance macro holds
    • virtual - the value is kept in the preset and nothing is sent
    • none - nothing is sent
  • default = sysex

id

Read, logged and then ignored. It is not written back.

  • optional
  • numeric

parameterNumber

A parameter number of the message. The parameterNumber is used to specify the parameter number, note number, program number, pressure, pitchbend value or song position. To fully support NRPN and SysEx, a parameterNumber is a 14-bit number.

Some types carry no parameter number, and the reader replaces it with the omni parameter - one shared entry in the parameter map:

ControlTypes replaced with omni
any control that is not a padprogram, atchannel, pitchbend, spp
padprogram, atchannel, start, stop, tune

A pad with a program message keeps the program number in the message's max.

  • optional
  • numeric
  • default = 0
  • min = 0
  • max = 16383

min

A MIDI minimum value to be transferred. This minimum MIDI value is mapped to the display value minimum defined in the value object. It is also the default for the value's own min.

A negative min is read as 0.

  • optional
  • numeric
  • default = 0
  • min = 0
  • max = 16383

max

A MIDI maximum value to be transferred. This maximum MIDI value is mapped to the display value maximum defined in the value object. It is also the default for the value's own max.

A max over 16383 is read as 16383.

On a pad, min and max are taken from offValue and onValue instead - see below.

  • optional
  • numeric
  • default = 0
  • min = 0
  • max = 16383

onValue

A MIDI value to be transferred when the parent control goes to the active state. On the receiving side, it is the value that switches the parent control to the active state - a pad is highlighted, for example. When onValue is not defined, MIDI transmission is ignored.

On a pad, offValue and onValue become the message's MIDI range, with these exceptions:

Message type on a padRange used
note0 .. 127 - note off and note on
programthe program number, kept in max
start, stop, tunenothing to off, 127 to on
everything else, including atchanneloffValue .. onValue
  • optional
  • numeric
  • default = undefined (ignore)
  • min = 0
  • max = 16383

offValue

A MIDI value to be transferred when the parent control goes to the inactive state. When offValue is not defined, MIDI transmission is ignored.

  • optional
  • numeric
  • default = undefined (ignore)
  • min = 0
  • max = 16383

value

Not read from a value's message: a value carries a range, not a single number. A message action of an event does carry a value, and that one is read.

lsbFirst

The lsbFirst flag forces Electra to swap the LSB and MSB value bytes of 14-bit MIDI parameters. It can be used in combination with cc14 and nrpn MIDI messages.

  • optional
  • boolean
  • default = false

resetRpn

When true, Electra sends the RPN reset sequence (RPN 127/127) after an rpn message, which tells the instrument that the RPN selection is finished.

  • optional
  • boolean
  • default = false

signMode

The signMode sets how negative values are represented in the MIDI value.

  • optional
  • enum
    • noSign
    • twosComplement
    • signBit
    • signBit2
    • binOffset
  • default = noSign

With twosComplement or signBit, the value shown on the display and the MIDI value are two notations of the same number, and bitWidth says how many bits it has. Seven bits hold -64 .. 63 in twosComplement and -63 .. 63 in signBit; a display value outside that is sent as the nearest one it can hold. A modulated value is encoded the same way.

signBit2 and binOffset are accepted and are currently encoded like noSign: the value is mapped from the display range onto the MIDI range.

helpers.midiToSigned() and helpers.signedToMidi() convert between the two in Lua exactly as the controller does.

bitWidth

The bitWidth specifies the number of bits used to calculate the value. It tells the controller where the sign bit should be placed for negative values.

  • optional
  • numeric
  • default = 7, or 14 for cc14 and nrpn
  • min = 1
  • max = 14

A bitWidth outside 1 .. 14 is replaced with the default for the message type, and the controller logs a warning. Before firmware 5.0.0 it was cut to its low four bits instead, so 16 became 0 and 20 became 4.

relative

Makes an ordinary message send relative steps instead of absolute values. A control of type relative has it forced on whatever the document says; on another control type it is what turns, for instance, a cc7 message into a relative CC.

  • optional
  • boolean
  • default = false

relativeMode

How a step of a relative control is encoded in the MIDI value it sends.

  • optional
  • enum
    • signBit - increments 65 .. 127, decrements 1 .. 63
    • signBit2 - increments 1 .. 63, decrements 65 .. 127
    • binOffset - increments 65 .. 127, decrements 63 .. 0
    • twosComplement - increments 1 .. 64, decrements 127 .. 65
    • noSign - the step is sent as it is
  • default = signBit

A step is sent only when the knob has moved by a whole step. Since firmware 5.0.0 a knob turned by less sends nothing; earlier firmware sent a step of zero for every report from the knob - 0 in signBit - which a device reads as a movement. A value written to the parameter by a script, an event or a snapshot is never sent for a relative control either: it has no position, only steps.

Each step sent runs the value's function and formatter once, with the step as the value, and parameterMap.onChange once, with the MIDI value that was sent.

accelerated

Lets a relative control send bigger steps when its knob turns quickly. Without it, every step is 1 or -1.

  • optional
  • boolean
  • default = false

The size of a step depends on how soon it follows the previous one in the same direction. A knob steps a relative control eight times a revolution:

Time since the previous stepStep sent
120 ms or more1
100 ms2
50 ms4
30 ms7
20 ms or less10

A pause, or turning the other way, starts again from 1. Steps never exceed 10. Before firmware 5.0.0 accelerated had no effect on a relative control.

When the knob is let go, the control shows 0 again, and a formatter's text from the last step is cleared.

data

The SysEx template of a sysex message.

data can be given in two ways:

  • an array - the template is written inline. It is compiled, framed and registered on the message's device under the next free message id, exactly as if it had been declared in the device's messages.
  • a number - the id of a template in the device's messages. A number that names no declared template gives the message an empty template, which sends nothing.

A message on device 0 - one with no deviceId - can still carry an inline template, but there is no device to send it to.

  • optional
  • array or numeric

direction

Which way the SysEx template of a sysex message works.

  • out - the template is sent when the control changes. This is the default, and what every preset written before direction existed does.
  • both - the template is sent, and an incoming SysEx message that matches it updates the parameter.
  • in - the template is only matched against incoming SysEx messages. It is never sent.

An unrecognised name reads as out.

Direction is a property of the template, not of the message: a direction given on a control's message is applied to the template the message uses - its own inline data, or the device message it names by id. A control that names a device message and gives no direction leaves that message's own direction as it is. Two controls naming one device message with different directions therefore fight over it; the last one read wins.

Matching an incoming message:

  • a constant byte has to be equal
  • value and parameter placeholders read the value and the parameter number
  • any, checksum and function elements match any byte - a checksum is not verified, and a Lua function cannot be run backwards
  • the message has to be exactly as long as the template, and at most 64 bytes long

The parameter a match updates is the one named in the value placeholder's rules, otherwise the one carried by the parameter placeholder, otherwise the parameterNumber of the control that uses the template. A template none of those settle - one shared by controls with different parameter numbers and without a parameter placeholder - is not matched.

A template with an any element cannot be sent, so it should be in.

json
"message": {
  "type": "sysex",
  "deviceId": 1,
  "parameterNumber": 12,
  "direction": "both",
  "data": [ "43", "10", "04", "0C", { "type": "value" } ]
}
  • optional
  • enum
    • out
    • both
    • in
  • default = out

SysEx template

A SysEx template is an array describing a System Exclusive message: the bytes that never change, and placeholders for the bytes that do. The same array shape is used by a message's data, by a device's messages, by a patch request and by a response header.

Each element of the array is one of:

ElementMeaning
a numberone byte, in decimal
a stringone byte, read as hexadecimal - "43" is 0x43, and "10" is 0x10, not ten
{ "type": "value" }one byte built from parameter values
{ "type": "parameter" }one byte built from the parameter number
{ "type": "checksum" }one byte, a checksum over bytes already written
{ "type": "function" }one byte answered by a Lua function
{ "type": "any" }one byte of any value; only meaningful when receiving. In Lua it is also midi.ANY
an object with an unknown or missing typeskipped without a word

Every placeholder renders exactly one byte, however many rules it carries: the rules are combined into that one byte with a bitwise OR. Two bytes need two placeholders.

F0 and F7, written either as numbers or as strings, are dropped wherever they appear in the array. The framing is added where it belongs: a message template and a patch request get a leading F0 and a trailing F7, and a response header gets a leading F0 and no F7.

value

The control's value. Rules say which parameter, which bits of it, and where in the byte they go.

KeyLegacy spellingTypeDefaultMeaning
type-stringsysexWhich message type the parameter belongs to.
parameterNumberidnumeric0Which parameter to read. 0 means the parameter number of the control using the template, which is what makes one template serve many controls.
parameterBitPositionpPosnumeric0The lowest bit of the parameter value to take.
byteBitPositionbPosnumeric0Where in the byte to put them.
bitWidthsizenumeric7How many bits to take.

{ "type": "value" } with no rules is the same as one rule with every default: the control's own parameter, bits 0 to 6, at bit 0 of the byte.

Splitting a 14-bit value over two bytes, LSB first:

json
[
   "43", "10",
   { "type": "value", "rules": [ { "parameterBitPosition": 0, "byteBitPosition": 0, "bitWidth": 7 } ] },
   { "type": "value", "rules": [ { "parameterBitPosition": 7, "byteBitPosition": 0, "bitWidth": 7 } ] }
]

Packing two parameters into one byte:

json
{
   "type": "value",
   "rules": [
      { "parameterNumber": 10, "parameterBitPosition": 0, "byteBitPosition": 0, "bitWidth": 4 },
      { "parameterNumber": 11, "parameterBitPosition": 0, "byteBitPosition": 4, "bitWidth": 3 }
   ]
}

parameter

The parameter number of the control using the template. Rules have the same meaning, without a type and without a parameterNumber.

KeyLegacy spellingTypeDefault
parameterBitPositionpPosnumeric0
byteBitPositionbPosnumeric0
bitWidthsizenumeric7

{ "type": "parameter" } with no rules is the same as one rule with every default.

checksum

A checksum over bytes that have already been rendered.

KeyTypeDefaultMeaning
algorithmstringrolandroland, waldorf or fractal.
startnumeric0Index of the first byte to sum, counted from the start of the rendered message - so byte 0 is the F0, and a checksum that should skip it starts at 1.
lengthnumeric0How many bytes to sum.
AlgorithmCalculation
roland(128 - (sum of bytes mod 128)) & 0x7F
waldorfsum of bytes & 0x7F
fractalall bytes XORed together, & 0x7F

A name that is not one of the three logs an error and renders as 0. A missing algorithm means roland, which is what the Preset Editor shows for a checksum whose algorithm was never picked.

A start past the bytes rendered so far, or a length that runs past them, is cut down to what has been rendered.

function

A byte answered by a Lua function in the preset's script.

KeyTypeMeaning
namestringThe function to call. It counts against the function name table.
json
{ "type": "function", "name": "checksumForPage" }

any

A wildcard byte. It matches any byte when a message is received, and it makes the template unsendable - a template holding one renders nothing at all, so give it "direction": "in".

Event

An event says what a control does when its knob switch is pressed or its knob is touched, in place of - or alongside - what the controller would otherwise do with that gesture.

Which hardware produces a switch event depends on the model. An Electra One mini has a switch in every knob and uses it. An mk2 has none, and a touch of the control on the LCD takes its place.

A control that declares any switch event takes over the whole of its switch and LCD touch handling. A pad with a switch event no longer toggles its value and a fader no longer drags: the point of the events is to replace what the controller would otherwise do, and leaving half of it in place would be harder to predict than losing all of it. Touch events are additive - the highlighting and the pot touch reporting happen around them as usual.

Events are read on controls only. A groups[] entry may carry an events array and it is ignored.

example:
json
"events": [
   {
      "source": "switch",
      "event": "press",
      "mode": "toggle",
      "actions": [
         {
            "type": "message",
            "message": {
               "type": "cc7",
               "deviceId": 1,
               "parameterNumber": 30,
               "value": 127
            }
         },
         {
            "type": "lua",
            "function": "onPress"
         }
      ]
   },
   {
      "source": "touch",
      "event": "begin",
      "actions": [
         {
            "type": "command",
            "command": "openDetail"
         }
      ]
   }
]

source

The gesture the event belongs to.

switch is the switch in the knob on a mini, and a touch of the control on the LCD on an mk2, which has no knob switch. touch is the knob being touched, which both models detect.

  • mandatory
  • enum
    • "switch"
    • "touch"

event

Which edge of the gesture runs the actions. A switch reports press and release; a touch reports begin and end. The two vocabularies are not interchangeable - a switch event named begin is not an event and is discarded.

A source and edge that appear twice keep the first entry; the later one is dropped with an error.

  • mandatory
  • enum
    • "press" and "release", for switch
    • "begin" and "end", for touch

mode

How the source latches, meaning the same as it does on a pad.

With momentary, the gesture is followed as the hardware reports it: pressing runs the press actions and letting go runs the release ones.

With toggle, the source latches. The first press runs the press actions, letting go runs nothing at all, and the next press runs the release actions.

The mode belongs to the source rather than to one entry, so naming it on a single entry is enough to put the whole source into that mode.

  • optional
  • enum
    • "momentary"
    • "toggle"
  • default = "momentary"

actions

An array of actions to run, in the order given. An empty array is a way of saying that the gesture should be taken over and do nothing.

An action the reader cannot make sense of is dropped on its own; the rest of the event still runs.

  • mandatory
  • array

Action

An action is one thing an event does. There are three kinds, told apart by the type attribute.

Note that a command action is scheduled rather than run on the spot, so it takes effect after any message and lua actions listed beside it, whatever order the array gives.

examples:

a message with a fixed value

json
{
   "type": "message",
   "message": {
      "type": "cc7",
      "deviceId": 1,
      "parameterNumber": 30,
      "value": 127
   }
}

a message whose value a Lua function answers with

json
{
   "type": "message",
   "message": {
      "type": "cc7",
      "deviceId": 1,
      "parameterNumber": 30,
      "value": "pickValue"
   }
}

a Lua call

json
{
   "type": "lua",
   "function": "onPress"
}

a command, with the bank and the slot it acts on

json
{
   "type": "command",
   "command": "loadSnapshot",
   "value": [ 1, 4 ]
}

type

Which kind of action this is. An unrecognised type drops the action.

  • mandatory
  • enum
    • "message"
    • "lua"
    • "command"

message

For a message action, the MIDI message to send. It is a Message carrying its own value rather than a range, and it is sent when the event runs.

Where a control's value is registered on the same parameter, the message travels through the parameter map: the controls and groups following that parameter all see the change, and the controller transmits it. Where nothing is registered on it, the message is simply sent - and where only groups follow it, they are told and the message is still sent, because a group shows a parameter and never speaks for it.

An action message is read differently from a value's message, because an action has no range and no control type to fold against. It reads these attributes only:

KeyTypeDefaultBehaviour
typestring-Required. A missing or empty type drops the action - unlike a value's message, which falls back to sysex.
deviceIdnumeric1Constrained into 1 .. 32, so 0 and a missing deviceId both mean device 1.
parameterNumbernumeric0
valuenumeric or string0See value.
lsbFirstbooleanfalse
resetRpnbooleanfalse
bitWidthnumeric7, or 14 for cc14 and nrpnOutside 1 .. 14 it falls back to the default. It also sets the range the value is clamped into: 0 .. 127, or 0 .. 16383 when the width is over 7.
dataarray or numeric-Read only for type: "sysex".

min, max, signMode, relative, offValue and onValue have no meaning here and are not read.

  • mandatory for a message action
  • object

value

For a message action, spelled inside the message object: the value to send. A number sends that value, clamped into the range its bitWidth allows. A string names a Lua function that is called to answer with the value, and nothing is sent if it does not answer with a number. See Control event callbacks.

For a command action, spelled on the action itself: the number the command acts on, or an array of two numbers for the commands that take a bank and a slot. At most two numbers are read; further entries are dropped with an error. Banks, slots and control sets are counted from one here, as they are everywhere the user sees them; page ids are counted from one everywhere, including inside the firmware, so they are passed through as they are.

  • optional
  • numeric, string, or array of numerics

function

For a lua action, the name of the Lua function to call. It is required and must not be empty, and it counts against the function name table. See Control event callbacks.

  • mandatory for a lua action
  • string

command

For a command action, the name of the command to run. An unrecognised name drops the action.

A command that acts on a control and is given no value acts on the control the event belongs to, so openDetail with no value opens that control's detail. Those commands are openDetail, setDefaultValue, switchHandleNext and switchHandlePrev.

These are the names a preset may use. They are the same names accepted by the device overrides file, by the configuration file and by the Lua API.

CommandValue
openSnapshots-
openCaptures-
openPresetsMenu-
openUsbHost-
openDevicesEditor-
openRouterEditor-
openSettings-
openPagesMenu-
openPresetMenu-
openMenu-
togglePerformancePage-
closePopupWindow-
requestPatch-
switchPreset[ bank, slot ], both from 1
switchPresetNext-
switchPresetPrev-
switchPagepage id, from 1
switchPageNext-
switchPagePrev-
switchControlSetcontrol set, from 1
switchControlSet1, switchControlSet2, switchControlSet3-
switchControlSetNext-
switchControlSetPrev-
togglePotSensitivity-
switchSnapshotBankbank, from 1
toggleAlt-
enableCustomLayout-
disableCustomLayout-
loadSnapshot[ bank, slot ], both from 1
openDetailcontrol id; the control itself when omitted
closeDetail-
setDefaultValuecontrol id; the control itself when omitted
setDefaultValues-
setAllDefaultValues-
randomizeParameters-
switchHandleNextcontrol id; the control itself when omitted
switchHandlePrevcontrol id; the control itself when omitted
triggerCapture[ bank, slot ], both from 1
togglePageSet-
saveState-
runUserFunction1 .. runUserFunction12-
  • mandatory for a command action
  • string

Cross-cutting behaviour

Duplicate ids

Controls and groups share one id space. The first object carrying an id keeps it, and any later object with the same id is ignored whole - its name, bounds, values, inputs, events, font and value references are all left out, and an error is logged.

Firmware before 5.0 merged the later object's values, inputs, events, font and value references onto the first one, which produced a control that was part one document object and part another.

Colors

Colors are written as six hexadecimal digits, RRGGBB, with no leading #. They are stored as 24-bit RGB and converted to RGB565 when they are painted, so the color on the screen may differ slightly from the one in the document.

The string is read with a plain hexadecimal conversion, so anything that is not a hex number reads as 0 - black. "#FFFFFF" is black, "FFFFFF" is white.

The default when the attribute is absent:

ObjectDefault
controlwhite, FFFFFF
groupwhite, FFFFFF
overlay itemno color; the control's own color is used

Objects that are dropped

These are the cases where the reader leaves something out rather than folding it into range. Everything else is constrained.

WhatWhen
the whole presetversion is not 2 or 3
a pageits id is not 1 .. the model's maximum, or it has none
a controlits pageId is not 1 .. the model's maximum
a control or groupits id is already used
a groupits pageId is not 1 .. the model's maximum
a valueits message's deviceId is neither 0 nor 1 .. 32
a macro value referenceits controlId names no control of the preset
a control eventthe source and event pair is not a real one, or the pair is declared twice
an event actionits type is unknown, a message action has no type, a lua action has no function, or a command action names no known command
a patch responseits header is longer than 64 bytes (the response stays but matches nothing)
a device past the 32nd-

What the controller writes back

A preset is written back when it is saved to a slot, and by the Lua functions preset.toJson, preset.toTable and preset.save.

The writer is not a copier. It publishes the model the reader built, so a document that goes in and comes out again is equivalent, not identical:

  • the key order is version, name, projectId, pages, devices, overlays, groups, controls
  • page 0, the performance page, is never written, and the performance macros that live on it are skipped
  • a value's min and max are always written, even when the document left them out
  • a device's rate is always written
  • a message's deviceId is written only when it is not 0; a message with no device comes back with no deviceId, which is what it was read as
  • a control's color and visible are always written, and a group's color is too
  • a fader read in default mode is written as bipolar, and a valueOnly list as cycle, because that is the mode the model holds
  • a pad's defaultValue is written as "on" or "off"
  • font is written only when it is not the object's default; events only when there are any; runningStatus only when true; interfaces only when they are not all three; a device message's direction only when it is not out
  • an inline SysEx template may come back as an entry in the device's messages array, named by id from the control's data
  • a patch's request and response pairing is lost: every response is written under the first request, and further requests get an empty responses array
  • overlay bitmaps are lost - they cannot be written back at all
  • a message's id, and every unknown key, are lost

Complete examples

A minimal preset

The smallest document that loads and does something. Everything except version is optional; the control has no pageId, so it lands on page 1, which is not declared in pages and therefore has no name. A real preset should declare its pages.

json
{
  "version": 3,
  "name": "MINIMAL",
  "controls": [
    {
      "id": 1,
      "type": "fader",
      "name": "VOLUME",
      "bounds": [ 20, 40, 146, 56 ],
      "inputs": [ { "potId": 1 } ],
      "values": [
        {
          "id": "value",
          "min": 0,
          "max": 127,
          "message": {
            "deviceId": 1,
            "type": "cc7",
            "parameterNumber": 7,
            "min": 0,
            "max": 127
          }
        }
      ]
    }
  ],
  "devices": [
    { "id": 1, "name": "SYNTH", "port": 1, "channel": 1 }
  ]
}

A Mini preset with a button row

The Mini's four pads are pot ids 9 to 12. On the Mini grid they sit at y 363, 117 x 51, at x 277, 407, 537 and 667, with a group row above them at y 342. The knobs above are pots 1 to 8, in two rows of four, 175 x 122 at x 20, 216, 412 and 608.

A Mini can hold 16 pages. Everything is in control set 1.

json
{
  "version": 3,
  "name": "MINI PADS",
  "projectId": "",
  "pages": [
    { "id": 1, "name": "MAIN" }
  ],
  "devices": [
    { "id": 1, "name": "DRUM MACHINE", "port": 1, "channel": 10 }
  ],
  "overlays": [
    {
      "id": 1,
      "items": [
        { "value": 0,   "label": "OFF", "color": "3D3D3D" },
        { "value": 127, "label": "ON",  "color": "F45C51" }
      ]
    }
  ],
  "groups": [
    {
      "id": 433,
      "pageId": 1,
      "name": "LEVELS",
      "bounds": [ 16, 10, 183, 16 ],
      "color": "FFFFFF"
    },
    {
      "id": 434,
      "pageId": 1,
      "name": "MUTES",
      "bounds": [ 277, 342, 116, 16 ],
      "color": "FFFFFF"
    }
  ],
  "controls": [
    {
      "id": 1,
      "type": "fader",
      "mode": "unipolar",
      "name": "KICK",
      "color": "F49500",
      "pageId": 1,
      "controlSetId": 1,
      "bounds": [ 20, 36, 175, 122 ],
      "inputs": [ { "potId": 1, "valueId": "value" } ],
      "values": [
        {
          "id": "value",
          "min": 0,
          "max": 127,
          "defaultValue": 100,
          "message": { "deviceId": 1, "type": "cc7", "parameterNumber": 20, "min": 0, "max": 127 }
        }
      ]
    },
    {
      "id": 2,
      "type": "fader",
      "mode": "unipolar",
      "name": "SNARE",
      "color": "F49500",
      "pageId": 1,
      "controlSetId": 1,
      "bounds": [ 216, 36, 175, 122 ],
      "inputs": [ { "potId": 2, "valueId": "value" } ],
      "values": [
        {
          "id": "value",
          "min": 0,
          "max": 127,
          "defaultValue": 100,
          "message": { "deviceId": 1, "type": "cc7", "parameterNumber": 21, "min": 0, "max": 127 }
        }
      ]
    },
    {
      "id": 11,
      "type": "pad",
      "mode": "toggle",
      "variant": "valueOnly",
      "name": "KICK",
      "color": "F45C51",
      "pageId": 1,
      "controlSetId": 1,
      "bounds": [ 277, 363, 117, 51 ],
      "inputs": [ { "potId": 9, "valueId": "value" } ],
      "values": [
        {
          "id": "value",
          "overlayId": 1,
          "defaultValue": "off",
          "message": { "deviceId": 1, "type": "cc7", "parameterNumber": 30, "offValue": 0, "onValue": 127 }
        }
      ]
    },
    {
      "id": 12,
      "type": "pad",
      "mode": "toggle",
      "variant": "valueOnly",
      "name": "SNARE",
      "color": "F45C51",
      "pageId": 1,
      "controlSetId": 1,
      "bounds": [ 407, 363, 117, 51 ],
      "inputs": [ { "potId": 10, "valueId": "value" } ],
      "values": [
        {
          "id": "value",
          "overlayId": 1,
          "defaultValue": "off",
          "message": { "deviceId": 1, "type": "cc7", "parameterNumber": 31, "offValue": 0, "onValue": 127 }
        }
      ]
    },
    {
      "id": 13,
      "type": "pad",
      "mode": "momentary",
      "name": "FILL",
      "color": "529DEC",
      "pageId": 1,
      "controlSetId": 1,
      "bounds": [ 537, 363, 117, 51 ],
      "inputs": [ { "potId": 11, "valueId": "value" } ],
      "values": [
        {
          "id": "value",
          "message": { "deviceId": 1, "type": "note", "parameterNumber": 60, "offValue": 0, "onValue": 127 }
        }
      ]
    },
    {
      "id": 14,
      "type": "pad",
      "mode": "momentary",
      "name": "PAGE 2",
      "color": "03A598",
      "pageId": 1,
      "controlSetId": 1,
      "bounds": [ 667, 363, 117, 51 ],
      "inputs": [ { "potId": 12, "valueId": "value" } ],
      "values": [
        { "id": "value", "message": { "type": "none" } }
      ],
      "events": [
        {
          "source": "switch",
          "event": "press",
          "actions": [
            { "type": "command", "command": "switchPage", "value": 2 }
          ]
        }
      ]
    }
  ]
}

A SysEx template with rules and a checksum

A Roland-style parameter change: F0 41 10 6A 12 [addr MSB] [addr LSB] [value][checksum] F7, where the address is the control's own parameter number split over two bytes, and the checksum covers the address and the value.

The rendered message is indexed from the F0, so the checksum starts at byte 5 and is 3 bytes long.

json
{
  "id": 30,
  "type": "fader",
  "mode": "unipolar",
  "name": "CUTOFF",
  "color": "529DEC",
  "pageId": 1,
  "controlSetId": 1,
  "bounds": [ 20, 40, 146, 56 ],
  "inputs": [ { "potId": 1, "valueId": "value" } ],
  "values": [
    {
      "id": "value",
      "min": 0,
      "max": 127,
      "message": {
        "deviceId": 1,
        "type": "sysex",
        "parameterNumber": 1035,
        "min": 0,
        "max": 127,
        "direction": "both",
        "data": [
          "41", "10", "6A", "12",
          { "type": "parameter", "rules": [ { "parameterBitPosition": 7, "byteBitPosition": 0, "bitWidth": 7 } ] },
          { "type": "parameter", "rules": [ { "parameterBitPosition": 0, "byteBitPosition": 0, "bitWidth": 7 } ] },
          { "type": "value" },
          { "type": "checksum", "algorithm": "roland", "start": 5, "length": 3 }
        ]
      }
    }
  ]
}

The same template written once for the whole device, so every control can name it by id:

json
"devices": [
  {
    "id": 1,
    "name": "JV-1080",
    "port": 1,
    "channel": 1,
    "messages": [
      {
        "id": 1,
        "direction": "both",
        "data": [
          "41", "10", "6A", "12",
          { "type": "parameter", "rules": [ { "parameterBitPosition": 7, "byteBitPosition": 0, "bitWidth": 7 } ] },
          { "type": "parameter", "rules": [ { "parameterBitPosition": 0, "byteBitPosition": 0, "bitWidth": 7 } ] },
          { "type": "value" },
          { "type": "checksum", "algorithm": "roland", "start": 5, "length": 3 }
        ]
      }
    ]
  }
]

A control then uses it with "data": 1:

json
"message": {
  "deviceId": 1,
  "type": "sysex",
  "parameterNumber": 1035,
  "min": 0,
  "max": 127,
  "data": 1
}

A device with a patch request and responses

The device asks for a patch dump with a SysEx request, and two responses take the dump apart: one for the voice parameters, one for the patch name.

json
"devices": [
  {
    "id": 1,
    "name": "DX7",
    "port": 1,
    "channel": 1,
    "interfaces": [ "midiIo", "midiUsbHost" ],
    "rate": 20,
    "patch": [
      {
        "request": [ "43", "20", "00" ],
        "responses": [
          {
            "id": 1,
            "header": [ "43", "00", "00", "01", "1B" ],
            "rules": [
              { "type": "sysex", "parameterNumber": 0, "byte": 6,  "byteBitPosition": 0, "bitWidth": 7 },
              { "type": "sysex", "parameterNumber": 1, "byte": 7,  "byteBitPosition": 0, "bitWidth": 7 },
              { "type": "sysex", "parameterNumber": 2, "byte": 8,  "byteBitPosition": 0, "bitWidth": 7 },
              { "type": "sysex", "parameterNumber": 3, "byte": 9,  "byteBitPosition": 0, "bitWidth": 7 }
            ]
          },
          {
            "id": 2,
            "header": [ "43", "00", "00", "01", "1B" ],
            "rules": [
              { "type": "sysex", "parameterNumber": 145, "byte": 151, "byteBitPosition": 0, "bitWidth": 7 },
              { "type": "sysex", "parameterNumber": 146, "byte": 152, "byteBitPosition": 0, "bitWidth": 7 }
            ]
          }
        ]
      }
    ]
  }
]

The request is sent by the requestPatch command, by the Request patch item of the preset menu, or from Lua. Responses are tried against every incoming SysEx message from the device: the header has to match byte for byte, and then every rule writes its bits into its parameter.

Control events

A knob whose switch mutes and unmutes a channel, latching, and whose touch opens the control's detail window. The message value of the second action is answered by a Lua function.

json
{
  "id": 40,
  "type": "fader",
  "mode": "unipolar",
  "name": "CHANNEL 1",
  "color": "F45C51",
  "pageId": 1,
  "controlSetId": 1,
  "bounds": [ 20, 40, 146, 56 ],
  "inputs": [ { "potId": 1, "valueId": "value" } ],
  "values": [
    {
      "id": "value",
      "min": 0,
      "max": 127,
      "message": { "deviceId": 1, "type": "cc7", "parameterNumber": 7, "min": 0, "max": 127 }
    }
  ],
  "events": [
    {
      "source": "switch",
      "event": "press",
      "mode": "toggle",
      "actions": [
        {
          "type": "message",
          "message": { "type": "cc7", "deviceId": 1, "parameterNumber": 120, "value": 127 }
        },
        { "type": "lua", "function": "onMute" }
      ]
    },
    {
      "source": "switch",
      "event": "release",
      "actions": [
        {
          "type": "message",
          "message": { "type": "cc7", "deviceId": 1, "parameterNumber": 120, "value": "lastLevel" }
        },
        { "type": "lua", "function": "onUnmute" }
      ]
    },
    {
      "source": "touch",
      "event": "begin",
      "actions": [
        { "type": "command", "command": "openDetail" }
      ]
    }
  ]
}

The matching script:

lua
function onMute(control, source, event, potId, valueId, value)
    control:setColor(BLUE)
end

function onUnmute(control, source, event, potId, valueId, value)
    control:setColor(RED)
end

-- The value the release action sends: whatever the fader reads now.
function lastLevel(control, source, event, potId, valueId, value)
    return (value)
end

The three functions take the callback signature described in Control event callbacks. lastLevel is the one that answers a message action's value, so it has to return a number; the other two may return nothing.

A macro

One knob opening four filters at once, the fourth one inverted.

json
{
  "version": 3,
  "name": "MACRO",
  "pages": [ { "id": 1, "name": "MAIN" } ],
  "devices": [ { "id": 1, "name": "SYNTH", "port": 1, "channel": 1 } ],
  "controls": [
    {
      "id": 1,
      "type": "fader",
      "mode": "unipolar",
      "name": "CUTOFF 1",
      "color": "529DEC",
      "pageId": 1,
      "controlSetId": 1,
      "bounds": [ 20, 40, 146, 56 ],
      "inputs": [ { "potId": 2, "valueId": "value" } ],
      "values": [
        { "id": "value", "min": 0, "max": 127,
          "message": { "deviceId": 1, "type": "cc7", "parameterNumber": 74, "min": 0, "max": 127 } }
      ]
    },
    {
      "id": 2,
      "type": "fader",
      "mode": "unipolar",
      "name": "CUTOFF 2",
      "color": "529DEC",
      "pageId": 1,
      "controlSetId": 1,
      "bounds": [ 187, 40, 146, 56 ],
      "inputs": [ { "potId": 3, "valueId": "value" } ],
      "values": [
        { "id": "value", "min": 0, "max": 127,
          "message": { "deviceId": 1, "type": "cc7", "parameterNumber": 75, "min": 0, "max": 127 } }
      ]
    },
    {
      "id": 3,
      "type": "adsr",
      "name": "ENV",
      "color": "F49500",
      "pageId": 1,
      "controlSetId": 1,
      "bounds": [ 354, 40, 146, 56 ],
      "inputs": [ { "potId": 4, "valueId": "attack" } ],
      "values": [
        { "id": "attack", "min": 0, "max": 127,
          "message": { "deviceId": 1, "type": "cc7", "parameterNumber": 73, "min": 0, "max": 127 } },
        { "id": "release", "min": 0, "max": 127,
          "message": { "deviceId": 1, "type": "cc7", "parameterNumber": 72, "min": 0, "max": 127 } }
      ]
    },
    {
      "id": 4,
      "type": "macro",
      "name": "BRIGHTNESS",
      "color": "F45C51",
      "pageId": 1,
      "controlSetId": 1,
      "bounds": [ 20, 140, 146, 56 ],
      "inputs": [ { "potId": 1, "valueId": "value" } ],
      "values": [
        { "id": "value", "min": 0, "max": 127, "defaultValue": 64,
          "message": { "deviceId": 1, "type": "virtual", "parameterNumber": 500, "min": 0, "max": 127 } }
      ],
      "valueRefs": [
        { "controlId": 1, "valueId": "value",  "mode": "setValue", "depth": 100 },
        { "controlId": 2, "valueId": "value",  "mode": "setValue", "depth": 80 },
        { "controlId": 3, "valueId": "attack", "mode": "modulate", "depth": 30 },
        { "controlId": 3, "valueId": "release","mode": "modulate", "depth": -30 }
      ]
    }
  ]
}

A macro may name a control that appears later in the controls array: the references are resolved once every control has been read. Firmware before 5.0 dropped a forward reference without a word.

Electra One proudly uses Lua and ArduinoJson.
For support contact info@electra.one · © 2019-2026 Electra One