deck

Player scope

What belongs in the playback package, and what has to stay upstream.

The host side of .deck — Web Audio playback for programs parsed by @spacedevin/deck.

Entry: src/index.tish

This package exists so that playback never enters the language package. The root AGENTS.md lists "Audio / Web Audio engines" as out of scope for @spacedevin/deck, and that stands: ../../src/ stays audio-free. Everything that rule excludes lives here.

In scope

Out of scope — do not add here

Generators

The catalog is @spacedevin/deck-synths (packages/synths/), which ships from this repo in lockstep with the language and this package. All 33 voices live there, including patch and matrixFm; a voice is a pure function (play*(ctx, bus, t, midi, vel, durSec, ch, bendSemis)) that connects its last node to bus.input. Add a voice there, not here.

Voices clean up after themselves: each schedules its own disconnects once its tail has passed. A voice may instead return { stopTime, disconnects } and let this package prune it per step (pruneVoices in src/index.tish); that path exists for voices that must not lean on a wall-clock timer, since an OfflineAudioContext has none. src/generators/ here holds only Registry.tish; there are no local voice copies left.

The catalog falls back to basicOsc for a generator id it has no voice for, so a song still plays. This package surfaces that in song.substitutions.

ttsVocal and meSpeakVocal need the Web Speech API and a mespeak worker respectively, so they stay out of scope here regardless.

Why element/ is not Tish

element/deck-player-element.js is hand-written JavaScript, shipped as authored. A custom element must be class X extends HTMLElement, and Tish has no class syntaxtish build parses the declaration as an identifier expression and emits JS that doesn't parse. Everything with behaviour stays in src/*.tish; that file is only the DOM shell around it. Don't try to move it back.

Notes for editors