Skip to content
Documentation

Connect with an application

Your application already knows what is happening: a player is in danger, a scene changes, a value crosses a threshold. GAS lets the music respond. You load the score once, then change tempo, flavor, or the active tracks as that state changes.

This page extends the application from Run locally. A Danger nearby checkbox makes the piano faster and more tense, and clearing it returns the music to what the score says.

Add the danger control

Add a checkbox beside the buttons in index.html:

html
<label><input id="danger" type="checkbox" /> Danger nearby</label>

Then add this controller to src/main.ts, before the Play listener:

ts
import type { GasSession } from '@luna-estelar/gas';

function connectDanger(input: HTMLInputElement, report: (text: string) => void) {
  let session: GasSession | undefined;
  let queue = Promise.resolve();
  let revision = 0;

  function apply(): Promise<void> {
    const target = session;
    const active = input.checked;
    const ticket = revision;
    queue = queue.then(async () => {
      if (!target || target !== session || ticket !== revision) return;
      if (active) {
        await target.setTempo(108);
        if (target !== session || ticket !== revision) return;
        await target.setTrackFlavor('track.piano', 'tense, short phrases');
      } else {
        await target.clearTempo();
        if (target !== session || ticket !== revision) return;
        await target.clearTrackFlavor('track.piano');
      }
    }).catch(() => {
      if (target === session && ticket === revision) report('Could not apply the danger change.');
    });
    return queue;
  }

  input.addEventListener('change', () => { void apply(); });
  return {
    async start(next: GasSession) {
      revision++;
      session = next;
      await apply();
    },
    stop() {
      revision++;
      session = undefined;
    }
  };
}

const danger = connectDanger(
  document.querySelector<HTMLInputElement>('#danger')!,
  (text) => { document.querySelector<HTMLParagraphElement>('#status')!.textContent = text; }
);

When danger starts, the controller sets two overrides with the session’s setters: setTempo and setTrackFlavor. The JavaScript API lists every setter and its matching clear method. Setters take track IDs, so the score’s piano track is track.piano. When danger ends, clearTempo and clearTrackFlavor remove the overrides, and the music returns to whatever the score says at that point. Restoring an old value by hand instead would miss any change the score makes in the meantime. Overrides and the cascade explains how overrides layer over the score.

The controller also keeps the changes in order. It applies one reaction at a time, so turning danger on and off quickly can’t apply the two reactions out of order. A failed command is reported in the status rather than stopping later changes.

Connect it to playback

The controller replaces the Make it busier button. Make these edits:

  1. Delete the #change button from index.html, and its const change declaration and click listener from src/main.ts.

  2. In the Play listener, replace if (run === generation) change.disabled = false; with:

    if (run === generation) await danger.start(current.session);
  3. In finish, change play.disabled = stop.disabled = change.disabled = true; to play.disabled = stop.disabled = true;, and call danger.stop(); on the line after it.

Overrides only last for one run: stopping the music clears them. That is why the controller starts again after every Play, applying whatever the checkbox says at that moment. danger.stop() detaches it, so a change queued just before Stop can’t reach the next run.

Try it: press Play and toggle Danger nearby. Then press Stop, change the checkbox, and press Play again. The new run starts in the state the checkbox shows.

Use the same pattern in a game

Way Back Home loads one looping score and changes it as the player moves through the rooms. Finding a suit brings in a hopeful layer, and reaching the beach changes the texture.

There are two ways to send a change, and both reach the same session. A fixed musical change, like a room’s mood, can be a live command written in GAS:

gas live
piano.flavor "warm evening air, unhurried"
tempo 84

A value your application calculates, like distance from danger, is better sent with a setter. The choice is about where the musical decision lives: in GAS text you can read and edit, or in your application’s code.

The JavaScript API covers the rest of the session: loading, playback, events, and errors. If something doesn’t work, start with Troubleshooting.

Next: Architecture — how GAS is built, from score to audio.