Skip to content
Documentation

Timeline format

Compiling a score produces a timeline: plain JSON that places the score’s intent on numbered bars. It contains nothing specific to a model, and no real-time values; the renderer converts bars to seconds during playback. Produce one with the CLI, or with compileSource in the JavaScript API:

bash
npx @luna-estelar/gas@0.2.0 compile song.gas --out song.timeline.json

Top level

Field Meaning
formatVersion { major, minor } version of this JSON format
timelineId Identifier for the timeline; "timeline" unless the caller supplies one
languageVersion Version of the GAS language, currently "1.0"
compilerVersion Version of the compiler package, currently "0.1.3"
source { mediaType, byteLength, name? }
playback Finite, loop, or infinite playback
arrangedBars Total span of the section calls
musicalContext Optional tempo, time signature, and key
globals Document-level flavor and level
tracks Declared instruments and their defaults
arrangement Section instances in order
resources Referenced resources
events Intent placed at musical positions

The three versions change independently: a compiler update need not change the language or the JSON format.

Playback and context

playback is one of these objects:

json
[
  { "mode": "finite", "declaredBars": 4 },
  { "mode": "loop", "declaredBars": 4 },
  { "mode": "infinite" }
]

Infinite playback has no bar count: the arrangement plays once, then holds its final state.

musicalContext can include:

json
{
  "tempo": 88,
  "timeSignature": { "beatsPerBar": 4, "beatUnit": 4 },
  "key": "C major"
}

Every field is optional. During playback, a missing tempo defaults to 120 and a missing time signature to 4/4; a missing key stays unspecified.

Tracks and arrangement

A track has an ID, name, description, and defaults:

json
{
  "trackId": "track.piano",
  "name": "piano",
  "description": "Soft felt piano",
  "defaults": []
}

Each default has a defaultId, an action, and a value for flavor, timbre, notes, motif, or level.

Each section call has its own arrangement entry:

json
{
  "sectionInstanceId": "section.intro.0",
  "sectionName": "intro",
  "callIndex": 0,
  "start": { "bar": 1 },
  "end": { "bar": 5 }
}

end is exclusive, so this instance covers bars 1–4. A repeated call gets another instance with its own span.

Events and values

This event starts the piano at bar 1:

json
{
  "eventId": "event.0000",
  "type": "track",
  "targetId": "track.piano",
  "action": "play",
  "position": { "bar": 1 },
  "sequence": 0,
  "scope": "timed",
  "sectionInstanceId": "section.intro.0"
}

Every event has an ID, a target, a position, a sequence, and a scope. sequence orders events on the same bar. A timed event lasts until something replaces it; a section event applies only inside its section instance.

Track play and stop events are always timed. Track events for flavor, timbre, notes, motif, or level carry a value. A section’s flavor has type: "section", action: "flavor", and scope: "section".

Value kind Shape
text { "kind": "text", "text": "..." }
level { "kind": "level", "value": 0.8 }
alda { "kind": "alda", "source": "..." }
resource { "kind": "resource", "resourceId": "..." }

Alda notation stays as written. Converting it to notes or MIDI is up to whatever uses it.

Positions and provenance

A MusicalPosition has an absolute bar, counted from 1, and an optional beat.

A node can carry provenance that maps it back to the score:

json
{
  "sourceRange": {
    "start": { "line": 12, "character": 8 },
    "end": { "line": 12, "character": 18 }
  },
  "sectionName": "intro",
  "sectionInstanceId": "section.intro.0",
  "callIndex": 0,
  "sectionPosition": { "bar": 1 }
}

Editors can use it to highlight the source behind an event. Playback ignores it.

Schema and validation

The types ship in @luna-estelar/gas/protocol. The individual gas-protocol package also includes schemas/1.0/timeline.schema.json and the session schemas. To check a timeline, use validateTimelinePayload from /protocol/validation, which runs without a schema compiler. session.loadTimeline validates before it loads.

The protocol source has the complete definitions.