Examples
Examples
This file shows practical plugin patterns.
Copy the smallest example that matches your device, then extend it.
Which template should I start from?
- If the device is configured only through CC messages, start from
../templates/plugin.cc.template.json. - If the device is edited through bank dumps and full-bank writes, start from
../templates/plugin.sysex.template.json. - If the device mixes CC, NRPN, SysEx, or Program Change, start from
../templates/plugin.template.json.
Example 1: minimal CC-only plugin
{
"slug": "simple_cc_device",
"name": "Simple CC Device",
"version": "1.0.0",
"enabled": true,
"triggers": ["SimpleCC"],
"requiresLandscape": false,
"protocol": {
"type": "cc",
"channel": 0,
"onConnect": []
},
"parameters": [
{ "id": "volume", "cc": 7, "min": 0, "max": 127, "default": 100 },
{ "id": "expression", "cc": 11, "min": 0, "max": 127, "default": 127 },
{ "id": "reverb", "cc": 91, "min": 0, "max": 127, "default": 0 }
],
"ui": {
"tabs": [
{
"label": "Main",
"sections": [
{
"title": "Mix",
"controls": [
{ "type": "slider", "param": "volume", "label": "Volume" },
{ "type": "slider", "param": "expression", "label": "Expression" },
{ "type": "slider", "param": "reverb", "label": "Reverb" }
]
}
]
}
],
"actions": [
{ "label": "Send All", "action": "writeAll" }
]
}
}
Example 2: bulk SysEx bank plugin
{
"slug": "bulk_sysex_device",
"name": "Bulk SysEx Device",
"version": "1.0.0",
"enabled": true,
"triggers": ["BulkSysEx"],
"requiresLandscape": false,
"protocol": {
"type": "sysex",
"channel": 0,
"autoWrite": false,
"onConnect": [
{ "bytes": "F0 47 7F 6D 40 00 00 F7" }
],
"responses": [
{
"id": "bank0",
"match": "F0 47 00 6D 00 00 06",
"writePrefix": "F0 47 7F 6D 00 00 06"
}
]
},
"parameters": [
{ "id": "gain", "source": "bank0", "byteIndex": 7, "min": 0, "max": 127, "default": 64 },
{ "id": "curve", "source": "bank0", "byteIndex": 8, "min": 0, "max": 5, "default": 2 }
],
"ui": {
"tabs": [
{
"label": "Main",
"sections": [
{
"title": "Response",
"controls": [
{ "type": "knob", "param": "gain", "label": "Gain" },
{
"type": "tile_picker",
"param": "curve",
"label": "Curve",
"options": [0, 1, 2, 3, 4, 5],
"optionLabels": ["1", "2", "3", "4", "5", "Auto"]
}
]
}
]
}
],
"actions": [
{ "label": "Read", "action": "request" },
{ "label": "Write", "action": "writeAll" }
]
}
}
Example 3: mixed plugin with transforms
{
"slug": "mixed_device",
"name": "Mixed Device",
"version": "1.0.0",
"enabled": true,
"triggers": ["Mixed Device"],
"requiresLandscape": false,
"protocol": {
"type": "mixed",
"channel": 0,
"onConnect": []
},
"parameters": [
{
"id": "volume",
"min": 0,
"max": 10,
"default": 5,
"sendCommand": {
"type": "cc",
"cc": 7,
"transform": { "inputMin": 0, "inputMax": 10, "outputMin": 0, "outputMax": 127 }
}
},
{
"id": "program",
"min": 0,
"max": 127,
"default": 0,
"sendCommand": {
"type": "program_change"
}
},
{
"id": "vibratoRate",
"min": 0,
"max": 127,
"default": 64,
"sendCommand": {
"type": "nrpn",
"nrpnMsb": 1,
"nrpnLsb": 8
}
}
],
"ui": {
"tabs": [
{
"label": "Main",
"sections": [
{
"controls": [
{ "type": "knob", "param": "volume", "label": "Volume", "min": 0, "max": 10 },
{ "type": "knob", "param": "program", "label": "Program" },
{ "type": "knob", "param": "vibratoRate", "label": "Vibrato Rate" }
]
}
]
}
],
"actions": [
{ "label": "Send All", "action": "writeAll" }
]
}
}
Example 4: patch-list driven tile picker
Response:
{
"id": "patches",
"role": "patch_list",
"match": "F0 00 21 59 00 01 00 02 02 01",
"pcByteIndex": 10,
"internalIdByteIndex": 11,
"versionByteIndex": 20,
"typeMap": { "16": "Builtin", "17": "Value", "18": "Free" },
"nameMap": {
"16": { "2": "Pop Alto Saxophone" },
"18": { "24": "Clarinet" }
}
}
Control:
{
"type": "tile_picker",
"param": "instrument",
"label": "Instrument",
"patchListSource": "patches"
}
Example 5: pressed-keys from one SysEx bitfield
{
"type": "pressed_keys",
"param": "",
"label": "Pressed Keys",
"options": [72, 71, 69, 67],
"optionLabels": ["Key A", "Key B", "Key C", "Key D"],
"source": "liveKeys",
"bitByteIndex": 10,
"bits": [0, 1, 2, 3],
"bitOrder": "lsb"
}
Example 6: keyboard definitions to avoid duplication
"ui": {
"keyboardDefinitions": {
"mainFingering": {
"options": [72, 63, 61],
"optionLabels": ["Ok", "Oct Up", "Oct Down"],
"noteParams": ["padNoteOk", "padNoteUp", "padNoteDown"],
"groups": [
{ "label": "Buttons", "indices": [0, 1, 2] }
],
"sources": [
{
"source": "liveKeys",
"bitByteIndex": 10,
"bitOrder": "lsb",
"bits": [12, 11, 10]
}
]
}
}
}
Example 7: custom fingering manager
Clarii-like:
{
"type": "custom_fingering_manager",
"param": "",
"label": "Custom Fingerings",
"keyboardRef": "mainFingering",
"customProfile": "clarii",
"slotCount": 256,
"includeDisabledSlot": false,
"slotDataSource": "fingeringCustom",
"slotDataOffset": 10,
"listenStreamParam": "liveKeysStream",
"padModeParam": "padMode",
"checksum": "robkoo_xor"
}
AE-01-like:
{
"type": "custom_fingering_manager",
"param": "",
"label": "Custom Fingerings",
"keyboardRef": "mainFingering",
"slotCount": 10,
"includeDisabledSlot": true,
"slotDataSource": "fingeringCustom",
"disabledDataSource": "disabledKeys",
"checksum": "ae01",
"addressPrefix": [0, 53, 0],
"slotStride": 8,
"enabledOffset": 1,
"highOffset": 2,
"lowOffset": 5,
"noteOffset": 8,
"disabledHighAddress": [0, 39, 52, 8],
"disabledLowAddress": [0, 39, 52, 11]
}
Example 8: embedded help
"help": [
{
"title": "My Device – Overview",
"icon": "air",
"entries": [
{ "type": "para", "text": "This plugin edits the device configuration." },
{ "type": "note", "text": "Use Read before editing if the hardware state may have changed." }
]
}
]
Example 9: recommended incremental build plan
- identify device and register plugin
- add one tab and two parameters
- verify outbound messages
- add readback support
- add actions
- add presets and help
Example 10: string parameter, text\_input control, and sequence action
Shows how to let the user name a preset patch and store it with a single button using the "sequence" action.
{
"slug": "store_sequence_demo",
"name": "Store Sequence Demo",
"version": "1.0.0",
"enabled": true,
"triggers": ["StoreDemo"],
"protocol": {
"type": "cc",
"channel": 0
},
"parameters": [
{
"id": "patchSlot",
"cc": 0,
"min": 0,
"max": 127,
"default": 0
},
{
"id": "patchName",
"valueType": "string",
"stringRules": {
"maxLength": 13,
"ascii": true,
"uppercase": true,
"rightPadChar": " "
},
"initialString": ""
}
],
"ui": {
"tabs": [
{
"label": "Store",
"sections": [
{
"title": "Patch",
"controls": [
{ "type": "dropdown", "param": "patchSlot", "label": "Slot" },
{ "type": "text_input", "param": "patchName", "label": "Name" }
]
}
]
}
],
"actions": [
{
"label": "Store",
"action": "sequence",
"tabs": ["Store"],
"confirm": {
"title": "Confirm Store",
"text": "Store patch name and settings to the selected slot?",
"yesLabel": "Store",
"noLabel": "Cancel"
},
"steps": [
{
"type": "sysex_template",
"template": "F0 04 26 {{patchName:ascii13}} 00 F7"
},
{
"type": "sysex",
"bytes": "F0 04 26 44 45 4C 41 59 20 4E 41 4D 45 20 20 20 00 F7"
},
{ "type": "send_param", "param": "patchSlot" },
{ "type": "program_change", "param": "patchSlot" },
{ "type": "cc", "cc": 119, "value": 127 }
]
}
]
}
}
Key points:
patchNameuses"valueType": "string"— no MIDI is sent on input; the text is stored in state only.- The
"text_input"control renders a labeled text field.maxLengthanduppercaseare taken fromstringRules. confirmis optional on actions; when present, the action runs only after explicit user confirmation.tabsis optional on actions; here"tabs": ["Store"]keeps the action visible only in the Store tab.send_paramemits one parameter's current value using that parameter'ssendCommand.program_changecan read its value dynamically from a parameter via"param"(here:patchSlot).- The
"sequence"action fires all steps in order, or aborts entirely if any step cannot be rendered. - In the
sysex_templatestep,{{patchName:ascii13}}encodes the stored string as 13 ASCII bytes, right-padded with spaces.
- only then add advanced controls such as patch lists or pressed-keys
