Web MIDI control surface
Version 1 · Operator's manual
Control Forge
Build a MIDI control surface in the browser, wire every element to real hardware, then play it. This manual covers the whole interface, panel by panel, and the workflows that hold it together.
Requires
Chrome, Edge or Opera. Web MIDI is not available in Safari or Firefox.
Serve it
python3 -m http.server 8000 — SysEx needs a served page, not a file URL.
On first load
Allow the MIDI and SysEx prompt. Refuse it and every message type still works except SysEx.
Section one
The window at a glance
Five zones, fixed in place. The centre is your surface; everything around it either builds that surface or plays it.
CONTROL FORGECB SOUNDDESIGN · WEB MIDI
OUTIAC Bus 1
IN— none —
Edit
Perform
Grid
LFO CLOCK90
Run
Panic
1
ADD CONTROL
Knob
Fader
Pad
XY
SNAPSHOTS
MOTION
Rec
Play
2
CONTROL
NAMECutoff
COLOUR
MESSAGECC (7-bit)
CC NO.74
RANGE0127
EXTRA TARGETS
MODULATION
LFO onSine
4
LOG
21:04:11 Web MIDI ready (SysEx enabled). 21:04:22 Snapshot 2 stored.
CUTOFF = 79
5
Figure 1 — The five zones of the window
1Header — ports, mode, grid size, pages, LFO clock, panic.
2Left rail — build and perform tools: palette, snapshots, motion, files.
3Surface — the grid your controls live on.
4Inspector — every setting of the one selected control.
5Log & readout — timestamped events on the left, last value sent on the right.
Section two
How to use it — first surface in ten steps
- Open the page in Chrome and allow the MIDI prompt. The log should read Web MIDI ready.
- Choose your destination in Out — a hardware port, or a virtual bus into your DAW. Choose an In port too if you want MIDI learn or motorised feedback.
- Stay in Edit mode. Optionally press Load demo at the bottom left to see a finished surface before building your own.
- In Add control, click a type — say Knob. The header of that panel shows place Knob; the button stays lit until you click it again.
- Click an empty grid cell. The control appears at its default size, snapped to the grid, and is selected. New knobs are auto-assigned the next free CC number.
- Drag the body to move it, drag the red corner triangle to resize it. Right-click, or press Delete, to remove it. Ctrl/Cmd-Z undoes.
- In the Control inspector on the right, give it a name, a colour, a channel and a CC number. Or press Listen under MIDI learn and move the matching control on your hardware.
- Repeat until the surface is built. Use Page + for a second bank; use Size and Cell to change the grid and the physical size of everything on it.
- Switch to Perform. Dragging now sends MIDI instead of moving controls. Send the whole surface once with Send every value so the receiving instrument matches what you see.
- Press Export .json to keep the surface. Import brings it back on any machine.
The one rule to remember
Edit mode moves controls and never sends MIDI. Perform mode sends MIDI and never moves controls. Almost every "nothing is happening" moment is the wrong mode.
Section three
Header — what each item does
Out
The global MIDI destination. Individual controls can override it in the inspector's Port field.
In
The source for MIDI learn and for Follow input. All connected inputs are listened to; the selector is a record of which one you meant.
Edit / Perform
Build or play. In Edit the cursor is a move cursor and resize handles appear; in Perform the surface is live.
Grid
Shows or hides the cell lines. Snapping stays on either way.
Cell
Pixel size of one grid cell, 24 to 96. This is your zoom: everything scales with it. 44 is the default; 64 and up is comfortable on a touchscreen.
Size
Columns × rows, 4 to 32 each. Shrinking the grid does not delete controls that fall outside it, but they can no longer be dragged back in until you grow it again.
Page
Banks of controls sharing one grid. Click to switch, double-click a page button to rename it, + adds, − removes the current page. One page always remains.
LFO clock
Tempo in BPM, 20 to 300. Drives every synced LFO and the motion recorder's bar lengths.
Run
Master start/stop for all LFOs on the current page. Individual LFOs also have their own On switch.
Panic
Sends All Sound Off and All Notes Off on all sixteen channels of every port in use, and releases held pads. Bound to the Escape key.
Section four
The eleven control types
Each type has a default footprint in cells and a default message. Both are free to change afterwards — a Pad wired to CC and a Knob wired to Note are perfectly legal.
Figure 2 — Control gallery, with default footprint and default message
Playing them in Perform mode
Knob, encoder,
macro, fader ↕
Drag up and down. Hold Shift for a quarter-speed fine drag. The mouse wheel also works — Shift makes it ten times finer.
Fader ↔
Drag left and right.
Button, pad
Click. In Toggle behaviour it latches; in Momentary it holds only while pressed. A key trigger does the same thing from the keyboard.
XY pad
Absolute — the dot jumps to the pointer. X sends the CC number, Y sends the Second CC, both 7-bit.
Switch
Click a segment. The position count follows the Detents field; the value is spread evenly across the range.
Meter, label
Not touchable. A meter displays incoming CC values on its channel; a label is decoration and sends nothing.
Section five
Inspector — every field
The right rail always shows the single selected control. Fields appear and disappear with the message type, so you only ever see what applies.
Name
Shown above the control, and used as the text of a Label.
Colour
Six swatches — cyan, violet, red, amber, green, white. Colour is grouping, nothing more; use one per section of the surface.
Port
Per-control output override. Leave on Follow global out unless this control belongs to a different instrument.
Message
CC 7-bit, CC 14-bit pair, NRPN, RPN, Note, Program change, Pitch bend, SysEx template, or None. None makes the control silent on its own — the right choice for a pure macro.
CC number
0–127. For a 14-bit pair this is the MSB; the LSB is automatically CC number + 32.
Second CC
XY pads only — the vertical axis.
Param
NRPN/RPN parameter number, 0–16383.
Note / velocity
Note number with its name shown alongside, and a fixed velocity 1–127.
SysEx hex
A byte template in hex, starting F0 and ending F7. Write vv where the 7-bit value goes, or VV for a 14-bit MSB/LSB pair. Bad templates are reported in the log and nothing is sent.
Checksum from
Byte index where a Roland-style checksum starts. −1 disables it.
Range
Value at minimum and value at maximum. Setting min above max inverts the control — a legitimate move.
Curve
Linear, Exponential (slow at the bottom), Logarithmic (fast at the bottom), or S-curve (soft at both ends).
Detents
Number of discrete steps; 0 is smooth. On a Switch this also sets how many segments it has.
Behaviour
Absolute sends the value. Relative sends increments for endless encoders. Toggle latches; Momentary holds.
Encoder format
How increments are encoded: two's complement, binary offset around 64, or signed bit. Match your instrument's manual.
Key trigger
Press Grab, then any key. That key now fires the control from anywhere in the page.
Feedback
On by default: matching incoming CCs and notes move this control so the surface tracks your instrument.
Lock
Protects the value from snapshot recall, morph, randomize and motion playback. Manual moves still work.
MIDI learn
Press Listen, then move a control on your hardware. The first CC or note received sets this control's message, number and channel.
Duplicate / Delete
At the foot of the rail. Duplicate places a copy one width to the right, keeping every setting. Ctrl/Cmd-D does the same.
Section six
Signal path — value to bytes
Knowing the order of operations explains most surprises: why an LFO seems shallow, why detents and curve interact, why a macro target ignores the parent's range.
Gesture
drag · wheel · key · motion · snapshot
→
+ LFO
depth 100% = ±50% of travel
→
Detents
quantise to N steps
→
→
Range
min…max, 7 or 14 bit
↓
Primary message
one message on this control's port and channel · repeats suppressed unless forced
Extra targets
each re-maps the same gesture with its own number, channel, range and curve
Figure 3 — Order of operations from gesture to MIDI bytes
Extra targets and macros
Any control can drive several destinations. Press Add target and set message type, number, channel, minimum, maximum and curve. Targets take the gesture after quantising but map it themselves, so one knob can open a filter while closing a drive, each on its own curve.
For a macro that only drives targets, set Message to None, or place the Macro control, which starts that way. Its readout shows the number of targets and the percentage of travel.
Section seven
Modulation, snapshots, motion
LFO — per control
Every control has its own LFO. Switch it On, pick a shape — sine, triangle, ramp, square, sample & hold, random walk — then either Sync to the header BPM at a division from 8/1 down to 1/32, or leave Sync off and set a free rate in hertz. Depth is how far it swings around the control's current position; Phase offsets it so several LFOs can run in formation. A modulated control is outlined in violet. Nothing moves until the header's Run is lit.
Snapshots — eight per page
Shift-click a numbered slot to store every value on the page; click it to recall. Stored slots are lit. Recall sends immediately, skipping locked controls. Snapshots are matched to controls by identity, so they survive renaming and rearranging but not a re-import.
Morph A→B glides from one snapshot to another over the time you set, in an eased ramp, streaming every intermediate value. Use long times for slow transitions and short ones for a snap with a tail. Randomize nudges every unlocked continuous control by up to the percentage set; note controls are left alone. Send every value forces the whole page out, which is how you re-sync an instrument that was switched on late.
Motion recorder
Press Record and move controls: every gesture is captured with its time. Recording implies play, so the loop starts immediately. Press Record again while it loops to overdub on top. Loop length is one to sixteen bars at the header tempo, or Length of take to let the first pass define the loop. The progress bar shows the playhead; Erase moves of selected control removes one control's automation and leaves the rest.
Layering the three
Motion playback writes values, the LFO adds to them, and locked controls ignore both. A pad on a key trigger, a filter on a four-bar motion loop and a slow synced LFO on the reverb send is a complete performance patch with no sequencer involved.
Section eight
Files, pages, undo
Export .json
Writes the whole surface — all pages, controls, targets, LFOs, grid and tempo — to a file named after the surface. Values themselves are not stored; snapshots are not stored.
Import
Replaces the current surface. Export first if you care about it. A file that is not a Control Forge surface is refused with a message in the log.
Load demo
Builds a full example page — filter knobs, an ADSR fader bank, an XY space control, drum pads, a mode switch, a relative bank encoder, an input meter and a three-target macro. The fastest way to see the whole feature set at once.
Clear page
Empties the current page only. Undoable.
Undo
Forty steps of structural history: adding, moving, resizing, deleting, duplicating, clearing, page changes. Values, LFO settings and inspector edits are not on the undo stack.
Surface name
Used as the export filename.
Section nine
Keyboard and mouse
Ctrl/Cmd-D
Duplicate selection
Delete
Remove selection (Edit mode)
Shift-drag
Fine adjust, quarter speed
Shift-wheel
Fine adjust, one tenth of a step
Right-click
Delete a control (Edit mode)
Shift-click slot
Store a snapshot
Double-click page
Rename a page
Any bound key
Fires its control, with proper release
Section ten
When something goes wrong
No ports in the lists
The browser has no Web MIDI, the prompt was refused, or nothing is connected. The log says which. Safari and Firefox will never work.
Dragging moves controls instead of playing
You are in Edit mode. Press Perform.
Values move on screen but nothing arrives
No Out port chosen, or the control's own Port points at something else. Check the readout at the bottom right — if it changes, the surface is working and the routing is not.
SysEx sends nothing
Either SysEx was refused on load — reload and allow it — or the template does not parse. It must begin F0 and end F7.
A control will not respond to anything
It may be locked, or it is a Meter or Label, which are not playable.
A note is stuck
Escape, or the Panic button.
Controls drift on their own
An LFO is running, or motion playback is looping. The header Run button and the Motion Play button are your two suspects.
Appendix
What is actually sent
Bytes per message type, with n as the zero-based channel and v as the mapped value.
CC 14-bit
Bn cc msb then Bn cc+32 lsb
NRPN
Bn 99 pmsb · Bn 98 plsb · Bn 06 vmsb · Bn 26 vlsb
RPN
As NRPN, with controllers 101 and 100
Note
9n note vel on press, 8n note 00 on release
Pitch bend
En lsb msb, full 14-bit range
Relative
A single CC per detent of movement, encoded per the Encoder format field
Panic
Bn 120 00 and Bn 123 00 on all sixteen channels of every port in use
CB Sounddesign · Control ForgeOperator's manual · v1