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
| Limit | Value |
|---|---|
| Preset file | 1,228,800 bytes |
| JSON parser pool | 1,000,000 bytes |
| Devices read | 32 |
| Controls | 432, 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:
version- the whole document is rejected unless it is 2 or 3pagesdevicesoverlaysgroupscontrols
The order matters in two places:
- a value's
overlayIdresolves 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:
namehas 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.projectIdhas 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 mk2 | Electra One mini | |
|---|---|---|
| Highest page id | 12 | 16 |
Highest potId | 12 | 12 (1-8 knobs, 9-12 the four pads) |
| Control sets | 3 | 3 |
| Screen | 1024 x 600 | 800 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.
{
"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:
"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:
"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:
"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:
"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:
"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:
{
"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:
{
"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,port2orctrl - 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 socketsmidiUsbDev- the USB device port, the connection to a computermidiUsbHost- the USB host portmidiAll- 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.
"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.
- optional
- array of objects
request: a SysEx template, the request to sendresponses: array of responses
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
| Key | Type | Default | Meaning |
|---|---|---|---|
id | numeric | 0 | Identifies the response to Lua. patch.onResponse receives it, and getResponseIndex finds a response by it. |
header | array | - | 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. |
rules | array | [] | 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.
| Key | Legacy spelling | Type | Default | Meaning |
|---|---|---|---|---|
type | - | string | sysex | The message type the parameter belongs to. A missing type means sysex; an unrecognised name means none. |
parameterNumber | id | numeric | 0 | The parameter to write. |
byte | - | numeric | 0 | Which byte of the message to read, counted from the start of the message including the F0. |
parameterBitPosition | pPos | numeric | 0 | Where in the parameter value the bits go. |
byteBitPosition | bPos | numeric | 0 | Where in the byte the bits are. |
bitWidth | size | numeric | 7 | How 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:
"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.
"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 devicedata: array, the SysEx templatedirection: see direction, defaultout
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
{
"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
{
"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:
{
"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.
"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.
{
"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
{
"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
{
"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:
| Control | Given | Read as |
|---|---|---|
fader | default, or no mode | bipolar |
list with variant: valueOnly | anything | cycle |
list on a mini whose bounds y is over 358 - the pad row | anything | cycle |
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
"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:
"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:
"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.
| Key | Type | Default | Meaning |
|---|---|---|---|
controlId | numeric | 0 | The control to drive. It has to be a control of this preset; a reference that names no control is dropped without a word. |
valueId | string | "" | Which value of that control - the handle name, as in valueId. An unknown name means the first handle. |
mode | string | setValue | setValue 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. |
depth | numeric | 100 | How much of the macro's travel reaches the value, in percent. Signed, one byte: -128 .. 127, so a negative depth inverts. |
pipe | object | - | 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:
{
"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:
{
"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
{
"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)
{
"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 type | valueId |
|---|---|
| fader, knob, list, pad, textBox, relative, custom, macro | value |
| vfader | f1, f2, f3, f4 |
| adsr | attack, decay, sustain, release |
| ahdsr | attack, hold, decay, sustain, release |
| adssr | attack, decay, break, slope, sustain, release |
| adr | attack, decay, release |
| ar | attack, release |
| dx7envelope | l1, r1, l2, r2, l3, r3, l4, r4 |
| xypad | x, 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'soffValueis 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
{
"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
formatterorfunctionon a value reads as none - a
luaaction, and a message action whosevaluenames a function, is dropped from its event - a
functionelement in a SysEx template sends the byte 0 parameterMap.setFunctionraises 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
"message": {
"deviceId": 1,
"type": "cc7",
"parameterNumber": 1,
"min": 0,
"max": 127
}A message with a SysEx template
"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
"message": {
"deviceId": 1,
"type": "cc7",
"parameterNumber": 1,
"offValue": 0,
"onValue": 127
}A NRPN message handling negative values
"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:
| Control | Types replaced with omni |
|---|---|
| any control that is not a pad | program, atchannel, pitchbend, spp |
pad | program, 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 pad | Range used |
|---|---|
note | 0 .. 127 - note off and note on |
program | the program number, kept in max |
start, stop, tune | nothing to off, 127 to on |
everything else, including atchannel | offValue .. 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
cc14andnrpn - 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 step | Step sent |
|---|---|
| 120 ms or more | 1 |
| 100 ms | 2 |
| 50 ms | 4 |
| 30 ms | 7 |
| 20 ms or less | 10 |
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
idof 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 beforedirectionexisted 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
valueandparameterplaceholders read the value and the parameter numberany,checksumandfunctionelements 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.
"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:
| Element | Meaning |
|---|---|
| a number | one byte, in decimal |
| a string | one 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 type | skipped 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.
| Key | Legacy spelling | Type | Default | Meaning |
|---|---|---|---|---|
type | - | string | sysex | Which message type the parameter belongs to. |
parameterNumber | id | numeric | 0 | Which parameter to read. 0 means the parameter number of the control using the template, which is what makes one template serve many controls. |
parameterBitPosition | pPos | numeric | 0 | The lowest bit of the parameter value to take. |
byteBitPosition | bPos | numeric | 0 | Where in the byte to put them. |
bitWidth | size | numeric | 7 | How 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:
[
"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:
{
"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.
| Key | Legacy spelling | Type | Default |
|---|---|---|---|
parameterBitPosition | pPos | numeric | 0 |
byteBitPosition | bPos | numeric | 0 |
bitWidth | size | numeric | 7 |
{ "type": "parameter" } with no rules is the same as one rule with every default.
checksum
A checksum over bytes that have already been rendered.
| Key | Type | Default | Meaning |
|---|---|---|---|
algorithm | string | roland | roland, waldorf or fractal. |
start | numeric | 0 | Index 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. |
length | numeric | 0 | How many bytes to sum. |
| Algorithm | Calculation |
|---|---|
roland | (128 - (sum of bytes mod 128)) & 0x7F |
waldorf | sum of bytes & 0x7F |
fractal | all 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.
| Key | Type | Meaning |
|---|---|---|
name | string | The function to call. It counts against the function name table. |
{ "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:
"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
- "press" and "release", for
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
{
"type": "message",
"message": {
"type": "cc7",
"deviceId": 1,
"parameterNumber": 30,
"value": 127
}
}a message whose value a Lua function answers with
{
"type": "message",
"message": {
"type": "cc7",
"deviceId": 1,
"parameterNumber": 30,
"value": "pickValue"
}
}a Lua call
{
"type": "lua",
"function": "onPress"
}a command, with the bank and the slot it acts on
{
"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:
| Key | Type | Default | Behaviour |
|---|---|---|---|
type | string | - | Required. A missing or empty type drops the action - unlike a value's message, which falls back to sysex. |
deviceId | numeric | 1 | Constrained into 1 .. 32, so 0 and a missing deviceId both mean device 1. |
parameterNumber | numeric | 0 | |
value | numeric or string | 0 | See value. |
lsbFirst | boolean | false | |
resetRpn | boolean | false | |
bitWidth | numeric | 7, or 14 for cc14 and nrpn | Outside 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. |
data | array 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
messageaction - 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
luaaction - 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.
| Command | Value |
|---|---|
openSnapshots | - |
openCaptures | - |
openPresetsMenu | - |
openUsbHost | - |
openDevicesEditor | - |
openRouterEditor | - |
openSettings | - |
openPagesMenu | - |
openPresetMenu | - |
openMenu | - |
togglePerformancePage | - |
closePopupWindow | - |
requestPatch | - |
switchPreset | [ bank, slot ], both from 1 |
switchPresetNext | - |
switchPresetPrev | - |
switchPage | page id, from 1 |
switchPageNext | - |
switchPagePrev | - |
switchControlSet | control set, from 1 |
switchControlSet1, switchControlSet2, switchControlSet3 | - |
switchControlSetNext | - |
switchControlSetPrev | - |
togglePotSensitivity | - |
switchSnapshotBank | bank, from 1 |
toggleAlt | - |
enableCustomLayout | - |
disableCustomLayout | - |
loadSnapshot | [ bank, slot ], both from 1 |
openDetail | control id; the control itself when omitted |
closeDetail | - |
setDefaultValue | control id; the control itself when omitted |
setDefaultValues | - |
setAllDefaultValues | - |
randomizeParameters | - |
switchHandleNext | control id; the control itself when omitted |
switchHandlePrev | control id; the control itself when omitted |
triggerCapture | [ bank, slot ], both from 1 |
togglePageSet | - |
saveState | - |
runUserFunction1 .. runUserFunction12 | - |
- mandatory for a
commandaction - 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:
| Object | Default |
|---|---|
| control | white, FFFFFF |
| group | white, FFFFFF |
| overlay item | no 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.
| What | When |
|---|---|
| the whole preset | version is not 2 or 3 |
| a page | its id is not 1 .. the model's maximum, or it has none |
| a control | its pageId is not 1 .. the model's maximum |
| a control or group | its id is already used |
| a group | its pageId is not 1 .. the model's maximum |
| a value | its message's deviceId is neither 0 nor 1 .. 32 |
| a macro value reference | its controlId names no control of the preset |
| a control event | the source and event pair is not a real one, or the pair is declared twice |
| an event action | its 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 response | its 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
minandmaxare always written, even when the document left them out - a device's
rateis always written - a message's
deviceIdis written only when it is not 0; a message with no device comes back with nodeviceId, which is what it was read as - a control's
colorandvisibleare always written, and a group'scoloris too - a fader read in
defaultmode is written asbipolar, and avalueOnlylist ascycle, because that is the mode the model holds - a pad's
defaultValueis written as"on"or"off" fontis written only when it is not the object's default;eventsonly when there are any;runningStatusonly when true;interfacesonly when they are not all three; a device message'sdirectiononly when it is notout- an inline SysEx template may come back as an entry in the device's
messagesarray, named by id from the control'sdata - a patch's request and response pairing is lost: every response is written under the first request, and further requests get an empty
responsesarray - 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.
{
"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.
{
"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.
{
"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:
"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:
"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.
"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.
{
"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:
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)
endThe 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.
{
"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.