Blog AI 8 min read

Claude-Generated MIDI Macros for the DualSense

Skip the GUI. Describe a DualSense macro set in plain English, paste the bridge schema, get back valid JSON — Claude does the tedious mapping work.

By Aidxn Design

Claude midi macro generation is the boring, useful side of LLM tooling — the part that doesn't trend on Twitter but quietly saves a producer an afternoon. Describe a DualSense macro set in a few sentences, paste the Universal Controller MIDI schema, and Claude returns a mapping file you can drop straight into the bridge. No GUI, no clicking, no naming faders by hand.

TL;DR
  • What it is: using an LLM to author bridge mapping JSON.
  • Why: mapping editors don't scale past 30 entries; words do.
  • How: paste the schema + a clear spec, ask for valid JSON.
  • Model: Claude Sonnet 4.6 or 4.7 is plenty.
  • Not magic: validate the output, don't trust blindly.

What this actually solves

Universal Controller MIDI mappings live as JSON files. The bridge GUI is fine for the first ten entries — beyond that, you're scrolling, hunting, mis-clicking. A full DualSense macro set for a live show might have 60+ assignments: layered profiles, chord conditionals, CC range scaling per stick zone, separate behaviours for hold vs tap on the same button. Building that in a list editor is a chore. Describing it in English is fast.

The Anthropic structured-outputs documentation covers the mechanics of constraining model output to a schema. We're using the same trick informally: paste the schema, ask for conformance, validate downstream.

The schema (abbreviated)

Universal Controller MIDI mappings follow this shape. The full schema lives in schemas/mapping.schema.json inside the install bundle — open it once, copy it into a scratchpad, paste it into Claude as part of the system prompt.

{
  "$schema": "https://controller-midi.aidxn.com/schemas/mapping.v1.json",
  "name": "Live set — DualSense",
  "controller": "dualsense",
  "channels": [
    {
      "input": "cross",
      "type": "note",
      "note": 60,
      "channel": 1,
      "behaviour": "momentary"
    },
    {
      "input": "left_stick_x",
      "type": "cc",
      "cc": 16,
      "channel": 1,
      "range": [0, 127],
      "curve": "linear",
      "deadzone": 0.06
    }
  ]
}

The prompt that works

Don't waste tokens on chat. Give Claude four things: the role, the schema, the spec, and the output constraint. Here's the prompt template we use:

You are generating a Universal Controller MIDI mapping file
for a DualSense controller.

SCHEMA:
<paste the full mapping.schema.json here>

SPEC (what the user wants):
- Cross = note 60, momentary, kick.
- Square = note 61, momentary, snare.
- Triangle = note 62, momentary, hat.
- Circle = note 63, latched, clap.
- Left stick X = CC 16, channel 1, deadzone 0.08.
- Left stick Y = CC 17, channel 1, deadzone 0.08, inverted.
- Right stick = MPE pitch + timbre (channel 2, MPE expression).
- L2 = CC 21, curve = exp, full 7-bit.
- R2 = CC 22, curve = exp, full 7-bit.
- Touchpad XY = CC 18 / CC 19, 14-bit.
- Touchpad click = note 70, momentary.
- Adaptive trigger feedback on L2 when channel 1 CC 71 > 100.

OUTPUT:
Return only valid JSON conforming to the schema.
No prose. No markdown code fences.

What Claude returns

Sonnet 4.6 hands back a 60-line JSON file in two seconds. The first three entries look like this:

{
  "$schema": "https://controller-midi.aidxn.com/schemas/mapping.v1.json",
  "name": "DualSense live set",
  "controller": "dualsense",
  "channels": [
    { "input": "cross",    "type": "note", "note": 60, "channel": 1, "behaviour": "momentary" },
    { "input": "square",   "type": "note", "note": 61, "channel": 1, "behaviour": "momentary" },
    { "input": "triangle", "type": "note", "note": 62, "channel": 1, "behaviour": "momentary" }
  ]
}

Save it to ~/Library/Application Support/UniversalControllerMIDI/mappings/live-set.json on macOS or %APPDATA%/UniversalControllerMIDI/mappings/live-set.json on Windows, reload the bridge, and the profile appears in the dropdown.

Where the model is good, and where it isn't

TaskLLM qualityNotes
Simple button → note mappingExcellentRepetitive structure, exact schema. Trivial.
Stick CC with curves + deadzonesVery goodOccasional curve-name typos. Validate.
MPE assignmentsGoodNeeds the MPE section of the schema spelled out clearly.
Conditional layers (hold-L1 modifier)MixedWill invent field names if the schema is unclear. Always paste the conditional sub-schema.
Adaptive trigger feedback rulesMixedModel often forgets the bidirectional flag. Eyeball every row.
Musical reasoning (chord shapes, scales)Surprisingly goodAsk for a Mixolydian b6 pad layout and it'll get the notes right.

The validation step nobody skips

Pipe the output through ajv or any JSON Schema validator before loading. The bridge will refuse to load malformed JSON, but it won't always catch a structurally-valid mapping that uses a deprecated field name. Here's the one-liner we run in CI:

npm exec ajv validate \
  -s schemas/mapping.schema.json \
  -d mappings/live-set.json \
  --strict=true

A realistic workflow

We use this every time we set up a new live set. Rough order:

  1. Sketch the mapping on paper or in a notes doc.
  2. Open Claude, paste schema + spec, get JSON.
  3. Validate with ajv. Patch any errors via follow-up turn.
  4. Load in the bridge, test every input, log any wrong behaviour.
  5. Paste the bridge log back into Claude. Ask for corrections.
  6. Done in 15 minutes, not 90.

What it's not

Claude is not going to invent a new performance concept for you. Ask it for "a creative mapping" and it'll give you something generic — that's a content-generation task and the model is mediocre at it. The win is the boring part: turning a clear spec into clean JSON, fast. For musical decisions, see our mapping templates library for inspiration; for the schema reference, the bridge install ships it. The SysEx deep-dive covers the bidirectional side.

Universal Controller MIDI ships the schema file in every install. Open Claude, paste, ship.

Keep reading

More setup walkthroughs