Skip to content

Commit b03c9fe

Browse files
authored
feat(player): light the code as it plays, with an example for every voice (#31)
* feat(player): light the code as it plays The element animated a progress bar and nothing else. Deckard and the promo videos light the step under the playhead and the instrument that is sounding, which is most of what makes a step sequencer readable, and the site had none of it. The highlighter now emits the positions it already knew about: data-step on each step in a lane, and data-track on every line, counting track headers in source order — the order parseSong builds channels in, so a trigger's bus maps straight back to the lines that describe it. <deck-player> finds the highlighted block beside it and, on each step, marks the step under the playhead in every lane and tints the lines of each track sounding on it. Lanes are modulo their own length, so a 16-step and a 32-step lane sit at different places in their own patterns, which is what the sequencer does with them. Both classes come off on pause, so a block at rest reads exactly as before, and a <deck-player> with no code beside it finds nothing and lights nothing. The element owns the audio and the page owns the markup; they meet only at those two attributes. * docs(examples): an example for every voice Coverage was 22 of 33. The gaps were noiseBurst, fmTone, aether, formantVocal, chiptune, cymbal, obSync, laserSync, patch, and the two speech voices. Adds sections for percussion and metal, two-operator FM, drifting textures, sung vowels, the generic chip voice, the rest of the sync family, and a hand-wired patch graph — the last being the one voice with no fixed architecture, where the gen_block names its own oscillators, filters and envelopes. ttsVocal and meSpeakVocal are documented rather than pretended to be ordinary. Both reach outside the audio graph — the Web Speech API and the meSpeak engine's assets — so they need host support and render as silence offline. Saying so is more useful than a Play button that does nothing. The intro no longer claims the player only synthesizes three generators faithfully; it carries the whole catalogue now, and nothing on the page is substituted. 33/33 voices now have an example. Every block was checked for substitutions and for building a real graph; ten of them, covering the voices written from scratch here, were rendered to audio through scripts/render-wav.mjs to confirm they sound rather than merely parse. * docs: catch the docs up to three packages, and open the door to contributors (#30) The last three merges shipped @spacedevin/deck-synths, made the player play the whole 33-voice catalog, added the WAV renderer and lit the code as it plays. The docs still described the two-package world: the player README said most voices fall back to a plain oscillator, its AGENTS.md said it still carried its own generators, and the synths package was not on the site or in llms.txt at all. - site/build.mjs: Synths and Contributing sections, so llms.txt and llms-full.txt pick them up; blurb says three packages - README: community-facing rewrite with badges, a packages table, a quick start (element, code, parse, WAV), and a Contributing section - player README / AGENTS: the catalog is deck-synths, nothing is substituted, code lighting is documented, the cleanup contract is stated as the code has it - synths README gains the voices table and a new AGENTS.md - manifests: ./rendering export, homepage, bugs, keywords, sharper descriptions; versions and peer ranges untouched - new CONTRIBUTING.md, issue templates (bug, feature, new voice) and a PR template
1 parent f18ba60 commit b03c9fe

17 files changed

Lines changed: 534 additions & 92 deletions
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
---
2+
name: Bug report
3+
about: Something parses, plays, or renders wrong
4+
title: ''
5+
labels: bug
6+
assignees: ''
7+
---
8+
9+
**Which package**
10+
11+
- [ ] `@spacedevin/deck` (parser / language)
12+
- [ ] `@spacedevin/deck-synths` (a voice sounds wrong)
13+
- [ ] `@spacedevin/deck-player` (transport, element, offline render)
14+
- [ ] `deckfile` crate
15+
- [ ] docs site / WAV CLI
16+
17+
**The smallest `.deck` that shows it**
18+
19+
```deck
20+
deck 1
21+
bpm 120
22+
23+
track Lead id lead gen gameBoyDmg
24+
note 60 0 1 v 100
25+
```
26+
27+
**What you expected**
28+
29+
**What happened instead**
30+
31+
Parse output, `song.errors`, console output, or a description of what you heard.
32+
33+
**Environment**
34+
35+
Package versions, browser or Node version, OS.

‎.github/ISSUE_TEMPLATE/config.yml‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
blank_issues_enabled: true
2+
contact_links:
3+
- name: Docs
4+
url: https://spacedevin.github.io/deck/
5+
about: The grammar, playable examples, rendering, and host integration.
6+
- name: Discussions
7+
url: https://github.com/spacedevin/deck/discussions
8+
about: Questions, songs you made, ideas that aren't a bug or a feature yet.
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
---
2+
name: Feature request
3+
about: Something the language, a voice, or the player should do
4+
title: ''
5+
labels: enhancement
6+
assignees: ''
7+
---
8+
9+
**What you want to write**
10+
11+
Show the `.deck` you wish worked, or the API call you wish existed.
12+
13+
```deck
14+
```
15+
16+
**What it should do**
17+
18+
**Which package it belongs in**
19+
20+
Grammar changes go in `@spacedevin/deck` and need a conformance case. Sound goes in
21+
`@spacedevin/deck-synths`. Sequencing, defaults, and the element go in `@spacedevin/deck-player`.
22+
Not sure is a fine answer.
23+
24+
**Anything else**
25+
26+
Prior art, a host that already does it, a workaround you're using.
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
---
2+
name: New voice
3+
about: Propose an instrument for the deck-synths catalog
4+
title: 'voice: '
5+
labels: enhancement, synths
6+
assignees: ''
7+
---
8+
9+
**The voice**
10+
11+
Name, generator id you'd propose, and what it sounds like — a chip, an instrument, a model.
12+
13+
**Reference**
14+
15+
What it should be compared against: real hardware, a recording, another synth, a paper.
16+
17+
**Params**
18+
19+
The `gen` keys a song would set, and sensible defaults.
20+
21+
**Are you up for building it?**
22+
23+
The recipe is in [CONTRIBUTING.md](../../CONTRIBUTING.md#add-a-voice): one pure function, one
24+
registry entry, one example song. Happy to help either way.

‎.github/PULL_REQUEST_TEMPLATE.md‎

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
## What
2+
3+
<!-- One or two sentences. The PR title becomes the squash commit, so write it as a Conventional
4+
Commit: feat(synths): …, fix(player): …, docs: … -->
5+
6+
## Why
7+
8+
## Which package
9+
10+
- [ ] `@spacedevin/deck` (language)
11+
- [ ] `@spacedevin/deck-synths` (voices)
12+
- [ ] `@spacedevin/deck-player` (host)
13+
- [ ] docs / site / CI only
14+
15+
## Checklist
16+
17+
- [ ] `npm test` passes (and `npm test -w @spacedevin/deck-player` if the player changed)
18+
- [ ] If the parser changed: `npm run conformance:update` and I reviewed the diff
19+
- [ ] If a voice was added: it's in `Registry.tish`, `Dispatch.tish`, the synths README table, and has a song in `docs/EXAMPLES.md`
20+
- [ ] Docs say what the code now does

‎AGENTS.md‎

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,16 +6,18 @@ Language-only package for the **`.deck`** patch language.
66

77
## Repo layout
88

9-
This repo publishes **two** packages. The rules below are this one's.
9+
This repo publishes **three** packages. The rules below are this one's.
1010

1111
| Path | Package | What |
1212
|------|---------|------|
1313
| `/` (this file) | `@spacedevin/deck` | the language: tokenize, parse, registries, highlight |
14-
| `packages/player/` | `@spacedevin/deck-player` | the host: Web Audio playback, transport, `<deck-player>` |
14+
| `packages/synths/` | `@spacedevin/deck-synths` | the instrument catalog: 33 Web Audio voices + dispatch |
15+
| `packages/player/` | `@spacedevin/deck-player` | the host: Song IR, transport, `<deck-player>`, offline render |
1516

1617
The "out of scope" list below — **including audio** — is about `@spacedevin/deck`. `src/` stays
17-
audio-free; everything that list excludes lives in `packages/player/`, which has its own
18-
[AGENTS.md](packages/player/AGENTS.md). The player depends on this package and never the reverse.
18+
audio-free; everything that list excludes lives in `packages/synths/` and `packages/player/`, each
19+
with its own AGENTS.md ([synths](packages/synths/AGENTS.md), [player](packages/player/AGENTS.md)).
20+
Dependencies point one way: player → synths → deck, never the reverse.
1921

2022
## In scope
2123

@@ -52,7 +54,8 @@ audio-free; everything that list excludes lives in `packages/player/`, which has
5254
`site/build.mjs` renders the markdown **already in this repo** to
5355
[spacedevin.github.io/deck](https://spacedevin.github.io/deck/) on every push to `main`.
5456

55-
Adding a page means **adding a `.md` file** under `docs/` or `packages/player/` — there is no route,
57+
Adding a page means **adding a `.md` file** under `docs/`, `packages/player/` or `packages/synths/`
58+
— there is no route,
5659
nav entry, or registration to update. The title comes from `title:` frontmatter, else a per-section
5760
override in `site/build.mjs`, else the first `#` heading, else the filename; `description:` becomes
5861
the lede.
@@ -88,10 +91,14 @@ Sources are read **in place**. `docs/*.md` are package exports and ship in the t
8891
| [README.md](README.md) | Install + API map |
8992
| [docs/DECK_GRAMMAR.md](docs/DECK_GRAMMAR.md) | **Canonical** language reference |
9093
| [docs/AST.md](docs/AST.md) | What `parseProgram` / `parseTrackBody` return |
91-
| [docs/EXAMPLES.md](docs/EXAMPLES.md) | Complete runnable songs |
94+
| [docs/EXAMPLES.md](docs/EXAMPLES.md) | Complete runnable songs, one per voice |
95+
| [docs/RENDERING.md](docs/RENDERING.md) | The WAV CLI and `renderDeckToBuffer()` |
9296
| [docs/DECK_EXTENSION.md](docs/DECK_EXTENSION.md) | gen_block dialect registration + common dialects |
9397
| [docs/HOST.md](docs/HOST.md) | How a host boots registries |
9498
| [examples/](examples/) | Runnable parse / boot / helper demos |
99+
| [packages/synths/README.md](packages/synths/README.md) | The voice catalog and its contract |
100+
| [packages/player/README.md](packages/player/README.md) | Playback API and `<deck-player>` |
101+
| [CONTRIBUTING.md](CONTRIBUTING.md) | Dev setup, conventions, how-to recipes |
95102

96103
Host apps (e.g. Deckard) may document UI, apply clamps, ownership, and their generator id tables — not a second copy of the language.
97104

‎CONTRIBUTING.md‎

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
# Contributing
2+
3+
Thanks for looking. This repo is small on purpose, and most useful contributions are small too: a
4+
song, a voice, a grammar case, a doc fix. This page is the setup and the recipes.
5+
6+
## Setup
7+
8+
You need **Node 22+** and the [Tish](https://github.com/tishlang/tish) compiler, which comes in as a
9+
dev dependency. Chrome or Chromium is only needed for the WAV renderer.
10+
11+
```bash
12+
git clone https://github.com/spacedevin/deck
13+
cd deck
14+
npm install
15+
npm test
16+
```
17+
18+
`npm test` builds the language package, runs the API and grammar suite with a 100% line-coverage
19+
gate, the conformance corpus, every song in `docs/EXAMPLES.md`, and the Tish and JS smoke tests.
20+
21+
The workspaces have their own tests:
22+
23+
```bash
24+
npm test -w @spacedevin/deck-player
25+
npm run build -w @spacedevin/deck-synths
26+
```
27+
28+
And the docs site, which is how you preview any markdown change:
29+
30+
```bash
31+
npm run site:serve # http://localhost:4321
32+
```
33+
34+
## How the repo is laid out
35+
36+
| Path | Package | Owns |
37+
|---|---|---|
38+
| `/` | `@spacedevin/deck` | the language: tokenize, parse, registries, highlight. **No audio.** |
39+
| `packages/synths/` | `@spacedevin/deck-synths` | the 33 voices and the dispatch that picks one |
40+
| `packages/player/` | `@spacedevin/deck-player` | Song IR, defaults and clamps, transport, offline render, `<deck-player>` |
41+
| `conformance/` | — | the parse contract every implementation is checked against |
42+
| `crate/` | `deckfile` | **generated** from `src/` by `npm run build:rust`; never edit by hand |
43+
| `site/` | — | the docs-site generator; markdown is read in place from the paths above |
44+
45+
Dependencies point one way: player → synths → deck. Each package has an `AGENTS.md` saying what
46+
belongs in it and what does not. Read the one for the package you're touching; the boundaries are
47+
the thing this repo cares most about.
48+
49+
Tish is the source language and it has gotchas: there is **no `class` syntax** (the build emits JS
50+
that doesn't parse), and `undefined` is not a value under `tish run`. `packages/player/AGENTS.md`
51+
explains why the custom element is plain JS for that reason.
52+
53+
## Commit messages
54+
55+
Releases are cut by [sem](https://github.com/tishlang/sem) from Conventional Commits, so the type
56+
you pick decides whether a version ships:
57+
58+
| Type | Effect |
59+
|---|---|
60+
| `feat:` | minor release |
61+
| `fix:`, `perf:` | patch release |
62+
| `feat!:` or a `BREAKING CHANGE:` footer | major release |
63+
| `docs:`, `chore:`, `ci:`, `test:`, `refactor:` | no release |
64+
65+
Scope with the package when it helps: `feat(synths): …`, `fix(player): …`, `docs(examples): …`.
66+
A green `main` cuts a prerelease with all three tarballs; promoting it publishes to npm and crates.io.
67+
The PR title becomes the squash commit, so write it as the commit.
68+
69+
## Recipes
70+
71+
### Add a song to the examples
72+
73+
1. Add a `deck` fenced block to `docs/EXAMPLES.md` under the right heading, with a sentence saying
74+
what it demonstrates.
75+
2. `npm run test:examples` — every block must parse without errors and produce at least one
76+
sounding channel. That is also the rule the site uses to decide whether to show a play button.
77+
3. `npm run site:serve` and press play on it.
78+
79+
Grammar-reference snippets with `<placeholders>` belong in `docs/DECK_GRAMMAR.md`; complete songs
80+
belong in `docs/EXAMPLES.md`.
81+
82+
### Add a voice
83+
84+
1. Create `packages/synths/src/<Name>.tish` exporting `play<Name>(ctx, bus, t, midi, vel, durSec, ch, bendSemis)`.
85+
Build a short-lived Web Audio subgraph, connect its last node to `bus.input`, and disconnect the
86+
nodes once the tail has passed (the existing voices schedule that themselves; look at
87+
`GameBoyDmg.tish` for the shape). A voice may instead return `{ stopTime, disconnects }` and let
88+
the player prune it per step.
89+
2. Register it: an entry in `src/Registry.tish` (id, label, description, default `generatorParams`)
90+
and a case in `src/Dispatch.tish`. Param aliases or a `gen_block` dialect go in `src/DeckIds.tish`.
91+
3. Seed anything random. Two renders of one song must be identical.
92+
4. Add a song for it to `docs/EXAMPLES.md` (recipe above) and a line to the voices table in
93+
`packages/synths/README.md`.
94+
5. `npm test` and `npm test -w @spacedevin/deck-player`.
95+
96+
### Change the grammar
97+
98+
1. Change `src/deckfile/*.tish`. The parser is parse-only: no defaults, no clamping, no range checks.
99+
Those are host policy and belong in the player.
100+
2. Update `docs/DECK_GRAMMAR.md` — it is the canonical reference — and `docs/AST.md` if the shape changed.
101+
3. Regenerate the corpus with `npm run conformance:update` and **review the diff**. A new case means
102+
every profile in `conformance/profiles.json` must say where it stands.
103+
4. `npm run test:rust` to confirm the Rust emit still agrees.
104+
5. Keywords for highlighting live in `src/deckfile/Highlight.tish`; the site picks them up from there.
105+
106+
### Add or fix a doc page
107+
108+
The site is a view over the markdown already in the repo. Drop a `.md` under `docs/`,
109+
`packages/player/` or `packages/synths/` and it appears in the nav, in `llms.txt` and in
110+
`llms-full.txt` on the next build. There is no route to register. Don't add YAML frontmatter to a
111+
file that ships in an npm tarball (`README.md`, `AGENTS.md`); npm renders it as a stray heading.
112+
Use the per-section title override in `site/build.mjs` instead.
113+
114+
## Pull requests
115+
116+
- Keep a PR to one package where you can; the template asks which.
117+
- Tests pass, the conformance diff is reviewed if you touched the parser, and the docs say what the
118+
code now does.
119+
- No CHANGELOG edits: the release notes are generated from the commits.
120+
121+
## Reporting a bug
122+
123+
Open an [issue](https://github.com/spacedevin/deck/issues/new/choose). The most useful bug report
124+
is the smallest `.deck` that shows it, plus what you expected to hear or parse. If the parsers
125+
disagree with each other, that is a conformance case waiting to be written.

0 commit comments

Comments
 (0)