This unit is the approach, not the syntax. For the exact markdown a build accepts, see komposition-format (V1/V2) and komposition-v3 (layered). Read this one first: it is what stops a syntactically valid komposition from being musically wrong.
In the middle of everything sits a simple description of what the user wants. A worked example, in the user’s own terms:
A music video at 130 BPM. Intro 4 bars, verse 8 bars, refrain 8 bars, outro 4 bars. It draws on two songs — one at 125 BPM, one at 138. From the first we play the segments “straw hat man” and “car chase” for intro and verse; from the second, “banging wall” and “outro”.
That is the whole input. The user describes what they want; the system does the arithmetic. The user never calculates timing — and neither should the agent driving the CLI on their behalf.
Note what the user did not say: no timestamps, no durations in seconds, no offsets in milliseconds. They named structural lengths in bars and they named segments of songs. That is the vocabulary to preserve all the way down to the document you author.
State positions the way the music is counted:
segment
outrostarts at beat 128 and plays for 16 beats
Not starts at 59076ms. Not starts at 59.08s. Seconds are not the unit either — they are
just milliseconds with fewer digits.
Exact milliseconds may be present in API responses, but they belong at the render boundary. The komposition layer, and the conversational LLM sitting above it, should not speak in milliseconds unless forced to.
The failure this unit exists to prevent looks like this:
32 bars × 4 beats × 60000 / 130 BPM = 59077ms.59077 — or, more often, a suspiciously round 59000 — into the
document.Step 2 is the error, and it is wrong even when the arithmetic is right. It bakes one assumption (constant tempo from time zero, no intro offset, this exact tempo estimate) into a number that no longer carries any of that context. If any assumption was off, nothing downstream can tell.
Round millisecond numbers in an authored komposition are the tell. A real grid-derived position is almost never a multiple of 500.
The correction is not “do the arithmetic more carefully.” It is: don’t do the arithmetic. State beats, and let the beat-grid resolution step convert them against the track’s real measured grid — see source-metadata-approach.
4B .. 4B+3. The first beat of bar 1 is beat 4.That relationship describes the counting scheme. It is not a way to find out where bar 12 actually lands in a recording.
Never derive a bar’s position by multiplying. Real tracks have pickups, silent leads, and intro offsets. A bar’s position comes from the analyzed downbeat grid, not from arithmetic on a BPM label.
Multiplication tells you what bar 12 would be on a metronome that started at t=0. The downbeat grid tells you where bar 12 is. Use the grid.
The wire formats do not accept a bars unit. V1/V2 accepts N beats
(komposition-format); V3 accepts s, ms, or beats
(komposition-v3).
So convert bar counts to beat counts when you author — in 4/4, multiply the bar count by 4 — and keep the bar structure visible in the document’s prose and segment names so the user’s intent survives. Converting a count (a length, “8 bars long” → “32 beats long”) is safe. Converting a position by the same multiplication is the anti-pattern above: positions come from the grid.
The example above becomes, structurally:
| Section | Bars | Beats | Starts at beat |
|---|---|---|---|
| intro | 4 | 16 | 0 |
| verse | 8 | 32 | 16 |
| refrain | 8 | 32 | 48 |
| outro | 4 | 16 | 80 |
Those timeline positions are exact, because the output timeline is generated at the master BPM and genuinely does start at zero. The positions that must never be computed this way are the ones inside a source track — where in “banging wall” the segment starts. Those come from that track’s grid.
A komposition has a single master BPM, and it is constant for the whole document.
A gradual master-tempo ramp — 125 sliding to 140 across the video — is an illustration of what this model could express. It is explicitly not supported at this stage. Do not author it and do not tell a user it is available.
Transitions are DJ-style. Songs are chosen so they overlap: for example one bar before and two bars after the cutoff point, with highpass and lowpass filters moving in opposite directions, so the incoming song enters on its highs and the crossover lands on the cutoff.
Multiple songs playing at once during a transition is the intent, not a mistake. Any guidance that says “there should be only one song” is superseded.
This is implemented, and it has a dedicated construct: ## Overlay Segments in V3 (see
komposition-v3). It declares a window in which two or more sources play
simultaneously, authored entirely in beats — startBeat, durationBeats, per-track
sourceBeat, and a transition with inDurationBeats / outDurationBeats.
The optional eqKill: "bass_swap" transition setting enables its EQ treatment; omit it for
volume crossfade only. The full entered format is in komposition-v3.
Use that construct for a transition rather than overlapping two ## Audio sections — and do
not cover the crossfade zone in both, or the audio double-plays.
You author to the schema, and the backend rejects what does not conform. Rejection is structural and early, not a silent mis-render.
A create call with no H1 header, for instance, comes back as (kompositions-workflow):
{
"error": "Invalid komposition structure",
"message": "Content must start with '# Komposition Name' header",
"validationErrors": ["Missing H1 header at start of content"]
}
Read validationErrors and fix the document — do not retry the same payload, and do not
work around a rejection by switching to a different format version.
Before writing a single line of komposition markdown:
| Intent | Where it is expressed |
|---|---|
| Master BPM different from a source’s native BPM, system reconciles | Supported in Overlay Segments via strategy: "C_STRETCH" + sourceBpm. The plain ## Audio track format has a single global BPM and no per-track native tempo. |
| Two or more songs playing at once | Supported. The ## Overlay Segments construct, authored in beats. |
| DJ crossfade | Supported in Overlay Segments. Set eqKill: "bass_swap" for its optional EQ treatment; omit it for a volume crossfade only. |
bars as a literal unit token in a document |
Not a unit token. Positions and lengths are written in beats. Think in bars, author beats. |
| Gradual master-tempo ramp | Not supported at this stage. Illustrative only — do not author it. |
A caution about every row above that says “not”. This repo is a client, and it is demonstrably behind the server — several capabilities in this table were documented here as impossible while being fully implemented. Absence of syntax in this client’s docs is not evidence of absence in the API. Before telling a user something cannot be done, check the tools manifest; “I cannot find it documented” is a claim you can support, “the platform does not do it” usually is not.
Where something genuinely is not available, say so as a limitation. Do not simulate it by precomputing milliseconds — that produces a document that builds, drifts, and gives nobody a way to find out why.