Language reference
A GAS score describes musical intent: what should play, when, and how it should feel. The compiler turns the score into a timeline, and a connector passes that intent to a model. Write a score teaches the language through a complete piece; this page lists all of it.
Document structure
A score has four parts: globals, tracks, sections, and arrangement calls. Write them in that order; the compiler warns when they are out of order. Indentation groups section and bar bodies, so keep it consistent; the examples use four spaces. # starts a comment.
tempo 88key "C major"time_signature 4/4length bars 4flavor "quiet, spacious"level 0.8track piano "Soft felt piano"piano.timbre "close and dry"section intro:length bars 4piano.level 0.6bar 1:piano.playintro()
Only the global length is required. A section without its own length bars N takes the global length; with length infinite, every section needs its own.
A section body accepts length, a bare flavor, and track statements written with the track name, such as piano.level. A bare level, timbre, notes, or motif is invalid in a section. A play or stop before the first bar block happens at bar 1; after a bar block, put them inside bar blocks.
Bars inside a section count from 1 for each call of that section. The timeline converts them to absolute bars, also counted from 1. The renderer converts bars to real time during playback.
Keywords and scopes
| Keyword | Value | Where it belongs |
|---|---|---|
tempo |
Beats per minute, at least 1; fractions allowed | Document global |
key |
Quoted text, such as "C major" |
Document global |
time_signature |
Meter, such as 4/4 |
Document global |
length |
bars N, bars N loop, or infinite |
Document global; sections accept bars N |
track |
Name and quoted instrument description | Document declaration |
play, stop |
No value | Track command |
flavor |
Quoted performance or mood description | Global, section, or track |
level |
Number from 0 to 1 | Global or track |
timbre |
Quoted sound description | Track |
notes, motif |
alda(...) |
Track |
Tempo defaults to 120 and the time signature to 4/4. A missing key stays unspecified. flavor describes the performance and timbre the sound. A track’s level sets its influence on the mix rather than its volume; the document-level level sets the overall output volume.
Where a value is written decides how long it lasts:
- Document level: a track statement, such as
piano.timbre, sets the track’s default. - Section level: a value lasts only while that section plays.
- Bar block: a value takes effect at that bar and lasts until something replaces it.
play and stop always behave like bar-block events, even when written at the top of a section: play starts a track until a stop or the end of the run. The overall flavor and a track’s own flavor are separate values, which the connector combines.
Length and arrangement
| Global length | Behaviour |
|---|---|
length bars 16 |
Play sixteen bars, then complete |
length bars 16 loop |
Repeat the sixteen bars until stopped |
length infinite |
Play the arrangement once, then hold its final state until stopped |
Calls at the bottom of the score place sections in order. A section that is never called does not play, and calling one again creates a separate instance with its own span. Sections do not loop on their own; repeat the call instead.
At each loop, the score’s events replay while live overrides stay in place. Section settings still end with their section.
The compiler warns when the declared length and the arrangement’s span disagree. Make them match unless the difference is deliberate.
Live input
A live fragment changes a playing score. It can contain track declarations, track commands, and tempo, which apply in order:
track lead "bright square lead"lead.flavor "short answers"lead.playtempo 108
A declared track starts inactive, and later lines can use it. Everything else is rejected: section, bar, arrangement calls, length, key, time_signature, and a bare flavor or level. Set the overall flavor and level with the API setters instead.
Live text has no timing keyword: each fragment applies to the session as it is now. The JavaScript API describes when changes land and what happens when only part of a fragment applies.
Overrides and the cascade
A live change, or a change made through the API, is an override: it covers the score’s value without erasing it. Overrides stay in place across loops. Stopping, a finite score completing, and a renderer retry all clear them; tracks your application defined survive.
Live text cannot clear an override, but the API has a clear method for flavor, timbre, level, and tempo. Clearing reveals whatever the score says at that moment, including any change the score made since the override began, so you never need to restore an old value by hand.
When several layers set the same value, the later layer in this list wins:
- Document globals.
- Track defaults.
- Overrides set while stopped.
- The score’s events, including section settings while their section plays.
- Overrides set while playing.
So a value set before Play can be covered by an event in the score, while a value set during playback covers it. Within a layer, the latest change wins.
Notation and reserved words
notes asks for specific musical material, and motif asks for material inspired by it. Both use Alda notation:
piano.motif alda(o4d4 f a > d2.)
The compiler keeps the notation as text, and the notation package can convert its supported subset of Alda to note events and MIDI. No shipping connector plays notes or motif yet, so a score that uses them loads with warnings. There are no API setters for them.
lyrics, lyrics.theme, effect, extend, and prompt are reserved for future features. Using them is an error.
Diagnostics
The compiler reports problems with a location, severity, category and code, and message. The CLI prints them like this:
song.gas:12:9: error [semantic/unresolved-track] I cannot find a track named 'drums' for this command.| Category | Meaning |
|---|---|
syntax |
The input could not be parsed |
semantic |
The input parsed, but contains invalid names, structure, or values |
deferred |
A reserved feature was used |
informational |
A note about the input |
Errors stop compilation. Common causes are an undeclared track, a track or section declared twice, a missing length, a bar outside its section, and a value out of range. Warnings still produce a timeline. An empty arrangement, a length mismatch, parts out of order, and a repeated global (the first value is kept) are all warnings.
On errors, the CLI writes to standard error, exits with status 1, and leaves any existing output file untouched. Delete an old output file yourself if your build depends on its absence.
Warnings about what a model supports are separate: they come from the session when a score loads, because only then is a model chosen. Supported models lists what Lyria supports.