Skip to content
Documentation

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) reports idle, buffering, playing, underrun, or stopped, 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, or undefined before 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:

ts
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:

ts
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.