JavaScript API
Your application controls GAS through a session from @luna-estelar/gas. In a browser, createBrowserSession creates one already connected to the renderer and Web Audio playback. Run locally is a complete application; the snippets here assume the surrounding objects already exist.
Browser host
Import createBrowserSession from @luna-estelar/gas/browser/session, and call it before the first await in a user gesture, such as a click handler. Some browsers only let audio start inside the gesture.
| Option | Behaviour |
|---|---|
connector |
Connector or factory; pass a factory so a failed renderer can be retried |
settings |
Connector settings, such as Lyria’s { apiKey, endpoint? } |
context? |
Use an existing AudioContext; otherwise the host opens and owns one |
prebufferSeconds? |
Playback margin; defaults to one model chunk, clamped to 0.25–4 seconds |
capture? |
Record generated PCM for each run; off by default |
engine?, renderer? |
Custom playback engine or renderer options |
It returns session, audio, context, optional capture, audiblePosition(), and close().
audio.on('status', listener)reportsidle,buffering,playing,underrun, orstopped, with buffered seconds and underrun count.audio.on('warning', listener)reports rejected chunks, a format mismatch, or a suspended context.audiblePosition()returns{ position, seconds }for the audio the listener is hearing, orundefinedbefore the first audio. Read it on animation frames to draw a playhead; the session’s position events are too sparse for that.close()releases the session and playback, and closes the audio context if the host opened it. To change the provider key, close the host and create a new one.
Both on methods return an unsubscribe function.
Creating a session without the browser host
Outside a browser, or to supply your own clock and audio output, create the session yourself:
import { createSession } from '@luna-estelar/gas';
import { createRenderer } from '@luna-estelar/gas/renderer';
import { createLyriaConnector } from '@luna-estelar/gas/lyria';
import type { ConnectorSettings, MonotonicClock } from '@luna-estelar/gas/protocol';
declare const clock: MonotonicClock;
declare const settings: ConnectorSettings;
const session = await createSession({
createRenderer: async () => createRenderer({
clock, connector: createLyriaConnector(), settings
})
});createSession calls the createRenderer factory straight away, and retryRenderer() calls it again. A connector can only be opened once, so create a new one inside the factory. A custom host must also subscribe to the session’s audio events and play the chunks itself.
Loading and source helpers
| Method | Returns |
|---|---|
compileSource(input, options?) |
Promise<CompileResult> with the timeline and diagnostics |
loadSource(input, options?) |
Promise<LoadResult> with support warnings |
loadTimeline(timeline) |
Promise<LoadResult> after validating the timeline |
Load only while stopped; loading replaces the previous score. A compile or validation failure rejects with GasOperationError. CompileOptions takes name? and timelineId?. loadTimeline takes a precompiled timeline and never loads the parser.
Each source helper returns a SourceInput:
| Source helper | Input |
|---|---|
sourceText(text, name?) |
String |
sourceBytes(bytes, name?) |
Uint8Array |
sourceBlob(blob, name?) |
Blob |
sourceFile(file) |
File |
sourceUrl(url, name?) |
URL to fetch |
sourcePath(path) |
Filesystem path, in a host with filesystem access |
Playback
| Method | Behaviour |
|---|---|
play() |
Start a run; needs a loaded score and a ready renderer |
stop() |
End the run; the browser host discards queued audio |
retryRenderer() |
Replace a failed renderer, keeping the loaded score |
close() |
Close the session for good and release its renderer |
play() rejects while already playing or after the renderer has failed. When it resolves, generation has started, but the first audio may not be audible yet.
A finite score completes on its own. The lifecycle event carries completed: true, and the session returns to stopped while the browser host plays out the audio it has already received. A provider stream that ends early is not a completion.
Stopping, completing, and retrying the renderer all clear overrides, so reapply any lasting application state after the next play(). To release a browser host, call browser.close() rather than session.close(), so the audio engine and context are released too.
Commands and overrides
Commands take track IDs. A score’s track piano has the ID track.piano: track. followed by the name as reduced by slugify from /protocol. getTracks() lists every ID.
| Method | Effect |
|---|---|
defineTrack({ id, name?, description? }) |
Add a track from your application; it starts inactive |
playTrack(id) |
Start a track |
stopTrack(id) |
Stop a track |
| Set | Clear |
|---|---|
setGlobalFlavor(value) |
clearGlobalFlavor() |
setGlobalLevel(value) |
clearGlobalLevel() |
setTrackFlavor(id, value) |
clearTrackFlavor(id) |
setTrackTimbre(id, value) |
clearTrackTimbre(id) |
setTrackLevel(id, value) |
clearTrackLevel(id) |
setTempo(bpm) |
clearTempo() |
Levels range from 0 to 1. Tempo is at least 1 and can be fractional. Invalid values and unknown tracks reject rather than being clamped. There are no setters for notation, key, or time signature.
Each command returns Promise<CommandResult> with any warnings. During playback, it can also report the requested and applied positions; buffered audio can still delay when the change is heard.
A setter creates an override, and its clear method removes it to reveal the score’s value. Overrides and the cascade explains how they layer and how long they last.
Live fragments
submitLiveCommands sends live GAS text, using track names from the score rather than IDs:
import { sourceText } from '@luna-estelar/gas';
import type { GasSession } from '@luna-estelar/gas';
declare const session: GasSession;
const result = await session.submitLiveCommands(sourceText('tempo 96'));| Outcome | Meaning |
|---|---|
| Rejects | Parsing failed, or the first statement failed |
{ ok: true, applied, warnings } |
Every statement applied |
{ ok: false, applied, warnings, failure } |
The first applied statements applied; the next one failed |
The whole fragment is parsed before anything applies. Statements then apply in order, and earlier ones are not rolled back when a later one fails. Live input lists what a fragment can contain.
State, events, and configuration
getState() returns a SessionState: lifecycle, playback phase, timelineLoaded, optional runId, and tracks. getTracks() returns TrackView entries with an ID, optional name and description, and a source of authored or host.
on(channel, listener) returns an unsubscribe function:
| Channel | Payload |
|---|---|
state |
SessionState after a change |
lifecycle |
Renderer lifecycle and playback, stream status, throttling, completion |
position |
RendererPositionEvent at authored events and loop boundaries |
audio |
AudioChunk for the host’s output |
warning |
Session or playback warning |
error |
GasOperationError |
diagnostic |
Compile diagnostics |
Audio and position events from a superseded run are filtered out. In a LifecycleEvent, stream === 'throttled' means the provider is throttling, throttled: true means the renderer is pacing generation, and completed: true means a finite score finished.
session.renderer exposes getCapabilities(), getModelInfo(), getDefaults(), getConnectorConfig(), and getConfigSchema(). While stopped, change defaults with updateDefaults(patch) and connector settings with updateConnectorConfig(patch). A connector that implements validateConfig, as Lyria does, checks the new settings; otherwise they are accepted with a warning. Supported models lists Lyria’s settings.
Errors and types
GasOperationError carries:
| Field | Meaning |
|---|---|
kind |
compile, validate, command, live, playback, renderer, or lifecycle |
diagnostics, warnings |
Structured details from the operation |
failure |
The underlying command failure, when there is one |
reason |
Connector classification: auth, network, quota, provider, or internal |
retryable |
Whether retrying the renderer could help |
closeCode |
Raw WebSocket close code, when there is one |
code, cause |
Further classification or cause, when available |
Connector messages are sanitised, but build your UI text from kind and reason rather than showing provider text. A renderer failure can arrive on the error channel and as a rejected operation, so show it only once. Troubleshooting covers what to do about each failure.
Types ship with the packages. The root exports GasSession, SessionWiring, CompileOptions, CompileResult, LoadResult, CommandResult, LiveCommandResult, AppliedPosition, SourceInput, ResolvedSource, DefineTrackInput, TrackView, SessionState, SessionEventMap, SessionWarning, PlaybackWarning, PlaybackPhase, LifecycleEvent, DiagnosticEvent, and RendererControl. Protocol types come from /protocol. Most entry points also export a version for their package.