> Track B. Read after 04-sequence-engine.md.
The pad surface is separate from the node graph. A node on the canvas does not represent "play sequence X." Pads live in their own panel; they appear in the input bus so any source can fire them and any sink can react to their state changes.
Where it lives
A new top-level view alongside the canvas. Two options for the shell:
Option A (recommended). A Pads button in the topbar that swaps the main view between Graph and Pads. The inspector stays on the right and adapts (pad-aware when a pad is selected, node-aware when a node is selected).
Option B. A drawer that slides up from the bottom (like the debug log). Cramped — pads need real estate.
Go with A. Implementation: src/features/sequences/PadView.tsx is the new top-level component; App.tsx toggles between <GraphCanvas /> and <PadView /> based on ui.activeView.
The grid
Default 8 pads (TagTable ships 8). Display as a 4×2 or 2×4 grid; let the user pick. Each pad shows:
- The sequence's pad color as the swatch.
- Sequence name.
- A row of cue swatches (clipped to ~32) so the user can preview the pattern at a glance.
- Hot-key indicator (Q W E R T Y U I by default — TagTable's defaults; rebindable).
- Live playhead progress as an arc/bar around the pad when active.
- A small "live" highlight on the pad whose layer is currently the top in the mixer for its slot.
Pad interactions
| Action | Trigger |
|---|---|
| Start sequence | Click pad / press hotkey |
| Stop sequence | Re-click pad / re-press hotkey (toggle) |
| Stop ALL active | Spacebar (matches TagTable) |
| Restart sequence | Shift-click |
| Open editor | Edit (✎) button on hover |
| Set color | Right-click → palette |
Hotkey routing: pads register with the existing keyboard input system (src/features/inputs/keyboard.ts) under the signature pad:<padId>:hotkey. This means a pad can also be triggered via:
- USB MIDI (a button is rebound to fire that signature)
- A Resolume parameter going above a threshold (via transform
threshold) - An OSC inbound packet
- Any other XOSC source
Pad editor modal
Reuse the existing Modal component. Fields:
- Identity. Name, pad color, hotkey assignment.
- Target fixture. Universe + fixture profile + start channel. Same picker as
dmx-out(extract a shared<FixtureTargetPicker>component). - Cue list. Editable table:
t, role values,fade,emit?. Add / remove / drag-reorder. Cues sorted byton save. - Playback. Duration (s), loop toggle.
- Mixer hints. Slot (free-text + recent suggestions), base priority (number 0..100), mix mode (
Selectofreplace | max | add). - Effect generator. Optional "fill cues from a preset" — pulse, chase, color cycle, fade-rainbow. TagTable has this in
app.jsaround line 2976. Port the four most useful ones.
Built-in presets
Ship 8 starter sequences. Inspired by TagTable's generatePresetTemplates but generalized to fixture profiles. All target fixture rgb, universe 1, channel 1 — the user re-targets each one to their actual rig once.
| Name | Color | Duration | Loop | Pattern |
|---|---|---|---|---|
| Warm Wash | #FF8C1E | 60 s | yes | 6 cues, 3s fades, oranges |
| Color Cycle | #FF3366 | 24 s | yes | 8 cues, R→Y→G→C→B→M→W→off |
| Slow Fade Blue | #3B82F6 | 8 s | yes | 2 cues, 4s fade up + down |
| Strobe Test | #FFFFFF | 4 s | yes | 8 cues alternating white/off |
| Police | #EF4444 | 1 s | yes | 4 cues red/blue alternating, no fade |
| Gentle Lounge | #8B5CF6 | 30 s | yes | 6 cues, slow purples + magentas |
| Bass Hit | #FBBF24 | 0.4 s | no | 1 cue at t=0 yellow, snaps to off at t=0.4 |
| Blackout | #000000 | 1 s | no | 1 cue at t=0 all-zero |
Loaded on first run via a "Load defaults" button (skipped if a sequence with the same name already exists, like TagTable does).
Inputs from pads → other sinks
Each pad fires its hotkey signature and a per-cue signature when a cue with emit lands. The router routes those exactly like keyboard / OSC / MIDI events. So:
- Wire a pad's
pad:warm-wash:hotkeysignature to a Resolume out node → starting the pad also fires Resolume. - Wire a cue's
emit.signature: "drop"to an OSC packet → the bass-drop cue fires an OSC packet at exactly that beat.
Outputs to pads (via inputs)
The reverse direction works the same way. An input source wired to a pad's signature starts/stops the pad. Examples:
- A keyboard key bound to
pad:warm-wash:hotkeytriggers it without the user clicking the pad. - A
resolume-inwatching/composition/dashboard/playPausewired through atransform: edge(rise) into the pad signature plays/pauses pads in sync with Resolume.
What you'll write
src/features/sequences/
├── PadView.tsx # main grid view
├── Pad.tsx # one pad cell
├── PadEditorModal.tsx # the modal
├── EffectGenerator.tsx # optional preset fillers (4 effects)
├── padTypes.ts # the persisted pad list shape
├── usePads.ts # store hook returning pad list + active layers
├── padHotkeys.ts # registers pad hotkeys with the keyboard input layer
├── pad-view.css
├── pad.css
└── pad-editor-modal.css
src/components/
└── FixtureTargetPicker.tsx # shared with DmxOutInspector — extracted into ui/
src/store/store.ts # add settings.dmx.sequences + ui.activeView
src/components/Topbar.tsx # Pads / Graph toggle
src/App.tsx # render PadView vs GraphCanvas based on activeView
Don't do
- Don't put pads on the React Flow canvas. The router treats them as virtual sources/sinks via signatures; the canvas would only get more crowded.
- Don't sync pad state across windows (multi-window is declined for v0.6).
- Don't ship a "pattern recorder" that captures live channel writes. Cool but a v0.7 idea.