MIDI Notation
Ask an AI to write MIDI directly and it has to emit raw note data: pitch numbers, start times, and durations in abstract beats. That is error-prone and unmusical — nothing in it reads like music, so nothing about it plays to a language model's strengths.
So Producer Pal doesn't ask for raw MIDI. It gives the AI a text notation to compose in, and translates that notation into real notes in your Live Set. The notation is the single largest investment in the project, and it's the main reason results feel musical rather than mechanical.
There are three notations to choose from, and one expression language — transforms — that reshapes notes in any of them.
Choosing a notation
| Notation | Best for | Strengths | Tradeoffs |
|---|---|---|---|
| bar|beat | The default. Capable models. | Most expressive: velocity ranges, probability, bar copying, note deletion | Stateful syntax; bars and beats shift meaning in odd meters |
| MIDI JSON | Coding agents; exact data | Highest fidelity: exact velocities and tuplets; trivial to generate and parse | Verbose — every note spelled out in full; no delete syntax |
| Stark | Small and local models; drums; chords | Literal and round-trippable; chord symbols; event-based drum lines | Velocity is lossy (three dynamics); no probability or velocity randomness |
If you're using Claude, GPT, Gemini, or another frontier model through the chat UI or an MCP client, stay on bar|beat. It's the default, it's the most expressive, and capable models handle it well.
Reach for MIDI JSON when a coding agent is driving Producer Pal, or when you need exact velocities and tuplet ratios preserved — it's the most literal of the three. Reach for Stark when you're running a small or local model, or when you're working mostly in drums and chord progressions.
Notation and small-model mode are independent
Small model mode trims the tool schemas and skills; the notation setting changes how notes are encoded. They're separate switches, and any combination works. A common pairing for local models is Stark plus small-model mode.
Setting the notation
The notation is a global device setting, not a per-conversation or per-tool one. Whatever you pick applies to the built-in Chat UI, external MCP clients, and the REST API alike. Set it in any of these places:
- Max for Live device — the Notation control on the Setup tab
- Chat UI — Settings → Tools → Advanced → Notation
- REST API —
POST /configwith{"notation": "stark"} - Command line —
npx producer-pal --notation stark - Coding agents — the Agent Skill sets it itself with
node ppal.mjs --set-config '{"notation":"midi-json"}'
Switching takes effect in a new conversation
The AI is taught its notation once, at the start of a conversation. If you switch mid-chat, it can still read your existing notes in the new notation, but it will keep writing the old one until you start a new conversation.
The notation also rewrites the clip tools' own parameter descriptions, so the AI is told what to send in the notation that's actually active. Each section below ends with the notes parameter text the AI receives under that notation. Small model mode shortens these further and hides some parameters outright, so the text shown is the standard-mode wording.
bar|beat
The default. A compact, human-readable notation where time is counted the way musicians count it.
v100 n/4 C3 E3 G3 1|1That's a C major triad, velocity 100, quarter notes, at bar 1 beat 1. The pieces are:
- Pitches are names —
C3(middle C, MIDI 60),F#4,Bb2. - Positions are
bar|beat, both 1-indexed —1|1is the first beat,2|3is bar 2 beat 3. Sub-beat positions can be decimals (2|3.5) or note-value offsets (1|1+n/12). - Durations are note values —
n/4is a quarter,n/8an eighth,n/12an eighth triplet. - Velocity is
v100, or a range likev90-110for humanized randomness. - Probability is
p0.5— the note plays half the time.
v, n, and p are stateful prefixes: once set, they apply to every note that follows until changed. That's what keeps it compact — you state a velocity once, not on every note.
v90-110 n/4 C1 1|1,3 n/8 D1 1|2,4 // humanized kick and snare
n/12 C3 1|1x3 // three eighth-note tripletsBars can be copied rather than retyped — @2=1 copies bar 1 to bar 2, @2-8=1 fans it across bars 2 through 8, and @3-10=1-2 tiles a two-bar pattern across bars 3 through 10.
Strengths. The most expressive of the three. It's the only notation with velocity ranges, note probability, bar copying, and a delete sentinel (v0 removes a note at a position). Positions read the way a musician thinks about time, which is exactly the translation an LLM is good at.
Tradeoffs. The statefulness is a footgun — a n/8 in the wrong place retimes everything after it. And bars and beats are meter-relative: a "beat" is a quarter in 4/4 but an eighth in 6/8, while note values like n/4 mean the same duration in every meter. That distinction is precise but it's one more thing for a model to get right.
No chord symbols. bar|beat deliberately has none, and won't get them — C7 is already a valid pitch (note C, octave 7), so a chord symbol would be lexically ambiguous with a note name. Simultaneous notes at one position (1|1 C3 E3 G3) express any chord fully, with per-note velocity and duration. Stark gets chord symbols because its chords: line header declares the context up front.
Tool parameters under bar|beat (standard mode)
| Tool | Parameter | Description |
|---|---|---|
ppal-create-clip | notes | MIDI in bar|beat notation: v0-127 n<dur> [p0-1] note(s) bar|beat(s) - MIDI clips only |
ppal-update-clip | notes | MIDI notes in bar|beat notation: v0-127 n<dur> [p0-1] note(s) bar|beat(s) - MIDI clips only. MERGES into existing notes (overwrites at same pitch+start - restate a note to edit it in place). To delete/move existing notes or replace a region use preTransforms; don't rewrite the whole clip |
MIDI JSON
Notes as a compact array of objects. The most literal of the three, and the easiest for a program to generate or parse.
[{p:60,t:0,d:4,v:100},{p:62,t:1,d:1,v:90,vd:10,c:0.75}]The keys are p pitch (0-127, C3 = 60), t start and d duration in musical beats, v velocity (1-127), plus optional vd velocity deviation and c probability (chance). Defaults are omitted, so most notes are just four keys.
Times are absolute musical beats: t:0 is the clip start, t:4 is beat 5, and chords share a t. Tuplets can be written as exact fractions — d:2/3 is a triplet quarter — and read back as fractions rather than a lossy 0.3333.
The parser is deliberately tolerant, so whatever an LLM emits still parses: keys may be quoted or bare, short (p/t/d/v) or long (pitch/start/…).
Strengths. Exactness. Velocities, deviations, and tuplet ratios all survive a round trip precisely. Coding agents generate and parse JSON natively rather than composing a text notation by hand, which is why the Agent Skill selects it.
Tradeoffs. It's the most verbose per note — there's no statefulness, no bar copying, no repeats, so a busy clip costs the most tokens. And it has no delete syntax; to remove or edit existing notes, use preTransforms.
Tool parameters under MIDI JSON (standard mode)
| Tool | Parameter | Description |
|---|---|---|
ppal-create-clip | notes | MIDI notes as a JSON array string, e.g. [{p:60,t:0,d:4,v:100}] (see Skills) - MIDI clips only |
ppal-update-clip | notes | MIDI notes as a JSON array string, e.g. [{p:60,t:0,d:4,v:100}] (p pitch, t start & d duration in beats, v velocity; see Skills) - MIDI clips only. MERGES (overwrites at same pitch+start; restate to edit in place). No v0-delete; delete/move existing notes via preTransforms - don't rewrite the clip |
Stark
A literal, round-trippable notation built for small models. One line per part, written as type: content.
kick: X z X z
snare: z X z X
hihat /8: X X X X X X X XThat's a one-bar 4/4 backbeat: kick on 1 and 3, snare on 2 and 4, closed hi-hat on every eighth. X is a normal hit, x soft, ^ an accent, and z a rest.
Every section is event-based — whitespace between tokens is just a separator, with no rhythmic meaning. Each token advances time by its own duration, which defaults to /4 (a quarter note) — except chords: lines, which default to /1 (a whole note) — and can be set per line (hihat /8:) or glued to a single token (X/8). So a pattern counts by the subdivision you're already thinking in: a 4/4 bar of quarters is four tokens, of eighths is eight. *N repeats a token — hihat /16: X*16 is a one-bar 16th roll.
Pitched lines work the same way, and chords can be written as symbols:
melody: C Eb G' z/2 A/8!
chords: C Am F G7
chords: Cm7 [Eb G C']' and , shift the octave, ! accents, ? softens, and [...] stacks notes into an explicit voicing. Chord symbols (Cm7, G7/B, Fmaj9) get realized into concrete pitches. Drums use the 16 General MIDI names (kick, snare, hihat, clap, …); an absolute pitch name works as a header too (C3:) for pads outside that set.
Strengths. Literal and predictable — no key, no scale, no snapping, and accidentals spelled explicitly. Reading a clip back re-emits Stark rather than falling back to another notation, so the AI sees what it wrote. The event-based drum lines and the chord symbols are the two things small models handle noticeably more easily than they handle bar|beat.
Tradeoffs. Velocity is the lossy axis. On read-back it snaps to three dynamics — soft, normal, accent — and is re-randomized within each range, so reach for bar|beat or MIDI JSON when exact velocities matter. Stark also has no note probability and no velocity deviation at all. Chord symbols are input-only sugar: the serializer never emits a chords: line, so a clip you wrote with symbols reads back as literal notes.
Tool parameters under Stark (standard mode)
| Tool | Parameter | Description |
|---|---|---|
ppal-create-clip | notes | MIDI notes in stark notation (literal, round-trippable type: content; event-based drum hits, see Skills) - MIDI clips only |
ppal-update-clip | notes | MIDI notes in stark notation, a literal per-line type: content format with event-based drum hits (see Skills) - MIDI clips only. MERGES (overwrites at same pitch+start; restate to edit in place). Delete/move existing notes via preTransforms - don't rewrite the clip |
Transforms
Notation says what the notes are. Transforms say how to reshape them — a small expression language for changing notes and audio properties with math. Pass a transforms string to Create Clip, Update Clip, or Duplicate.
Each line is [selector:] parameter operator expression:
timing = swing(0.05) // swing the whole clip
C1-C2: velocity += 30 // accent the bass notes
1|1-4|4.75: velocity = ramp(40, 127) // crescendo over 4 bars
where(note.velocity < 40): delete // drop the quiet notes
pitch += clipseq(0, 5, 7) // per-copy transposition- Selectors narrow what a line touches — a pitch (
C3) or pitch range (C3-C5), a time range (1|1-2|4, or3|*for all of bar 3), a value test (where(note.velocity > 100)), or a combination. No selector means every note. Each selector applies only to its own line. - Parameters are
velocity,pitch,timing,duration,probability, anddeviationfor MIDI, plusgainandpitchShiftfor audio clips. - Operators are
+=,-=,*=,/=, and=. - Expressions are arithmetic over the current values, with LFO waveforms (
sin,cos,tri,saw,square),rampandcurvefor interpolation,randandchoosefor randomness,seqto cycle through values, scale functions likesnap(to the Live Set's scale), timing functions likelegato, and general math functions. - Note-count operations change how many notes exist:
ratchet(4)rolls a note into four,repeat(n/8, 3)echoes it,splitcuts at explicit positions, andmerge()fuses same-pitch notes into one sustained note. - Context variables let expressions read where they are:
note.index,note.count,next.pitch, and clip metadata likeclip.indexandclip.duration.
A transform string broadcasts across every clip or copy in a batch, so one string can vary many clips at once — clip.index arithmetic or clipseq(...) gives each one a different result. That's what makes Duplicate able to generate a set of variations in a single call, and it pairs naturally with take lanes.
preTransforms is the same language, applied to a clip's existing notes before any new notes merge in. It's the general editing path — and for MIDI JSON and Stark, which have no delete syntax of their own, it's how you remove or rewrite notes.
Transforms are the same in every notation
Transforms work identically whichever notation is active — they operate on notes after they've been parsed, so nothing about them changes. Their syntax, though, is bar|beat-flavored: selectors use bar|beat positions and pitch names regardless. Even on MIDI JSON or Stark, you still write 1|1-2|1: delete and C1: velocity += 30.
The instructions the AI actually gets
Producer Pal teaches the AI its notation at the start of every conversation, by returning a skill set from the Connect tool. The full text of those skills — including the notation guide for whichever notation is active — isn't reproduced here; see the features page for where to read it: the Chat UI's Skills tab previews the assembled document for any notation and model size, and the source is browsable on GitHub. You can customize or replace them, including swapping in your own notation guide.