# More Than a Live API Wrapper

A lot of projects that connect an AI to Ableton Live are **thin wrappers**: they
take the Live API more or less as-is and forward its calls to the model. That's
easy to build, but it pushes every rough edge of a low-level, decade-old API
straight onto the AI — and the AI then pushes that friction onto you.

Producer Pal takes the opposite approach. Since the beginning of 2025 it has
been **continuously refined** — every time the AI stumbled over an awkward
interface, a confusing value, or a missing capability, that rough edge got
smoothed over. The result is a set of tools designed to be intuitive for **both
the AI and the human**, and in some cases to offer features the Live API doesn't
provide at all.

Three examples show the range: one small and tidy, one deep and full of
arrangement trickery, and one that reaches a feature the API doesn't even admit
exists.

## A small example: colors as `#RRGGBB`

Ask the Live API for a track or clip color and you get back a number — something
like `16711680`. To set a color, you hand it a number too. That's a **packed
24-bit integer**: red, green, and blue each occupy one byte (`0x00RRGGBB`). It's
perfectly logical for a C++ engine and perfectly opaque to everyone else. No AI
(and no human) thinks _"bright red"_ and reaches for `16711680`.

Producer Pal wraps that raw number in the **`#RRGGBB` hex syntax** everyone
already knows from CSS. The AI works with `#FF0000`; Producer Pal does the bit
math on both sides of the boundary
([`live-api-extensions.ts`](https://github.com/adamjmurray/producer-pal/blob/main/src/live-api-adapter/live-api-extensions.ts)):

```js
// Live API integer → "#RRGGBB"
const r = (colorValue >> 16) & 0xff;
const g = (colorValue >> 8) & 0xff;
const b = colorValue & 0xff;

// "#RRGGBB" → Live API integer
this.set("color", (r << 16) | (g << 8) | b);
```

Tiny as it is, this is exactly the kind of refinement that adds up. Every tool
that touches color — creating tracks, recoloring clips, theming scenes — speaks
`#RRGGBB`, so the AI never has to reason about bit-shifting, and you can ask for
_"make the drums dark purple"_ and have it just work.

There's an extra bit of polish here too. Live snaps colors to a **fixed palette
of about 70 swatches**, so the color you ask for isn't always the color you get.
A thin wrapper would silently hand back a different number and leave the AI
confused. Producer Pal reads the color back after setting it, and when Live has
quantized it, it tells the model so in plain language
([`color-verification-helpers.ts`](https://github.com/adamjmurray/producer-pal/blob/main/src/tools/shared/color-verification-helpers.ts)):

> Requested clip color #FF0000 was mapped to nearest palette color #FF3636. Live
> uses a fixed color palette.

Now the AI understands _why_ the color shifted, instead of treating it as a bug.

## A big example: splitting clips in the Arrangement

The simple cases are nice. The interesting ones are the features Live's API
**doesn't have** — where Producer Pal has to build the capability itself.

Splitting an arrangement clip is the headline example. In Live's UI you just hit
**⌘E** to cut a clip in two. But the Live API exposes **no split operation at
all**. There is no `split_clip`, and the properties you'd reach for to fake it
are walled off:

- You can't shorten a clip by writing its `end_time` — for warped, looped clips
  the arrangement length is effectively **immutable** once the clip exists.
- You can't create an audio clip _in the arrangement_ with a specific length —
  `create_audio_clip` only exists in Session view.
- The one lever that does move clips around — dropping a new clip so it
  **overlaps** an existing one — only trims from the **edges**. If a new clip
  overlaps the _middle_ of an existing one, Live truncates at the overlap and
  **throws away everything after it**. It does not split into a "before" and an
  "after." (These constraints are documented in the project's
  [Arrangement-Operations](https://github.com/adamjmurray/producer-pal/blob/main/dev/Arrangement-Operations.md)
  notes, hard-won by probing real Ableton behavior.)

So Producer Pal builds split out of the one primitive that _does_ work reliably
— edge trimming via an overlapping clip — applied carefully
([`arrangement-splitting.ts`](https://github.com/adamjmurray/producer-pal/blob/main/src/tools/shared/arrangement/arrangement-splitting.ts)):

1. **Duplicate the original** off to a "holding area" far past your real
   content, where nothing can collide with it.
2. **Trim each segment** out of copies of that clip by dropping a short
   **temporary clip** at exactly the boundary, letting Live's edge-truncation do
   the cut, then deleting the temp clip.
3. **Move each finished segment** back to its final spot in the arrangement.
4. **Re-scan the track** afterward, because the Live API invalidates clip
   references the moment you start duplicating and deleting.

The net effect, for a clip split at beats 4 and 8:

```
Original:   |=================|   beats 0–16
            0    4    8      16

Result:     |===|             segment 0  (right-trimmed in place)
                 |===|        segment 1  (duplicated, trimmed both sides, moved)
                      |======| segment 2  (left-trimmed, moved)
```

### Why there's a generated silent WAV file

Here's the part that surprises people. To trim an **audio** clip's edge,
Producer Pal needs a temporary audio clip to drop on the boundary — and Live
won't create an audio clip without a real audio **file** to point at. There's no
"empty audio clip."

So Producer Pal **generates its own**: a tiny
[silent WAV file](https://github.com/adamjmurray/producer-pal/blob/main/src/shared/silent-wav-generator.ts)
— 0.1 seconds of 44.1 kHz, 16-bit mono silence, about 8.8 KB, written once to a
temp directory and reused. It's built by hand, byte by byte (RIFF header, `fmt`
chunk, and a `data` chunk that's simply all zeros):

```js
const buffer = Buffer.alloc(44 + dataSize);
buffer.write("RIFF", 0);
// ... fmt chunk: PCM, 1 channel, 44100 Hz, 16-bit ...
buffer.write("data", 36);
// remaining bytes are already zero — i.e. silence
```

Silence is the whole point: the temporary clip exists for only a fraction of a
beat before it's deleted, but if anything ever went wrong and it _did_ play, it
would make no sound. Its only job is to **be present at a position** so Live's
edge-trim behavior fires; its contents never matter.

The file's path is generated by the Node.js server when the device loads and
threaded into every tool request as part of the request context
(`silenceWavPath`), so the Live API side always knows where to find it. (For
MIDI clips none of this is needed — `create_midi_clip` happily makes an empty
clip of any length directly in the arrangement.)

## A feature the API doesn't admit to

Both examples so far started from something the Live API _tells_ you about. The
most surprising refinement came from the opposite case — a capability the API
doesn't advertise at all.

Every object in the Live API can describe itself: ask a track for its `info` and
it reports its properties and functions. That self-description, plus Ableton's
reference documentation, is the map almost every tool builds from. A track's map
lists `arm`, `mute`, `solo`, its routing — but **not** input monitoring, the
**In / Auto / Off** switch sitting on every track in Live's mixer. It's absent
from the introspection dump _and_ from the
[official documentation](https://docs.cycling74.com/apiref/lom/track/). By every
signal the API gives you, there is simply no way to set it.

While chasing exactly that feature — which appeared not to exist — Producer
Pal's development resorted to **guessing plausible property names** anyway.
`current_monitoring_state` turned out to be real: read it and you get back a
number (`0` = In, `1` = Auto, `2` = Off); write it and the track's monitoring
actually changes. It just isn't listed anywhere. That undocumented,
un-introspectable property is now the `monitoringState` control on Producer
Pal's update-track tool.

A thin wrapper could never have offered it, because a thin wrapper only knows
what the API admits to. (And it's _still_ missing from both the docs and the
reflection today — a re-probe of a running Live Set while writing this page
found the property works exactly as before while remaining invisible to
introspection.)

## A language for music, not just API calls

The three examples above are all about meeting the **Live API** where it is. The
single largest investment in Producer Pal sits a level _above_ the API entirely:
the notation the AI actually composes in.

Asked to write MIDI directly, an LLM has to emit raw note data — pitch numbers
and times in abstract beats — which is error-prone and unmusical. So Producer
Pal gives it two purpose-built notations to think in instead:

- **[`bar|beat` notation](/features#custom-music-notation)** — a text-based
  music notation where time is counted the way musicians count it (`1|1` = bar
  1, beat 1), pitches are names (`C3`, `F#4`), and durations are note values
  (`n/4`, `n/8`), mapped onto exact positions in clips and the arrangement
  across any time signature.
- **[Transforms](/features#transforms)** — a small expression language for
  _shaping_ notes and audio with math: LFO shapes, ramps, randomized ranges, and
  per-note or per-clip variation, where a single string can broadcast across
  many clips at once.

Both are full domain-specific languages with their own grammars and parsers —
the kind of thing nobody builds for a thin wrapper. They're a big enough topic
to deserve their own deep-dive here someday; for now, see the
[features page](/features#custom-music-notation) for more on both.

## Why this matters

None of this — the bit math, the holding area, the generated silent WAV, the
property Live never told us about — is visible when you use Producer Pal. You
just ask for a split, or a color, and it happens. That's the point. Every one of
these refinements exists so the AI meets a clean, predictable interface instead
of the Live API's raw edges, and so you get capabilities a thin wrapper simply
can't offer. It's the accumulated result of more than a year of smoothing rough
edges, and it's most of what separates a genuinely usable tool from a quick API
bridge.

For how the two engines underneath actually carry this work back and forth, see
[The Bridge](/how-it-works/the-bridge).
