Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 35 additions & 0 deletions .github/ISSUE_TEMPLATE/bug_report.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
---
name: Bug report
about: Something parses, plays, or renders wrong
title: ''
labels: bug
assignees: ''
---

**Which package**

- [ ] `@spacedevin/deck` (parser / language)
- [ ] `@spacedevin/deck-synths` (a voice sounds wrong)
- [ ] `@spacedevin/deck-player` (transport, element, offline render)
- [ ] `deckfile` crate
- [ ] docs site / WAV CLI

**The smallest `.deck` that shows it**

```deck
deck 1
bpm 120

track Lead id lead gen gameBoyDmg
note 60 0 1 v 100
```

**What you expected**

**What happened instead**

Parse output, `song.errors`, console output, or a description of what you heard.

**Environment**

Package versions, browser or Node version, OS.
8 changes: 8 additions & 0 deletions .github/ISSUE_TEMPLATE/config.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
blank_issues_enabled: true
contact_links:
- name: Docs
url: https://spacedevin.github.io/deck/
about: The grammar, playable examples, rendering, and host integration.
- name: Discussions
url: https://github.com/spacedevin/deck/discussions
about: Questions, songs you made, ideas that aren't a bug or a feature yet.
26 changes: 26 additions & 0 deletions .github/ISSUE_TEMPLATE/feature_request.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
---
name: Feature request
about: Something the language, a voice, or the player should do
title: ''
labels: enhancement
assignees: ''
---

**What you want to write**

Show the `.deck` you wish worked, or the API call you wish existed.

```deck
```

**What it should do**

**Which package it belongs in**

Grammar changes go in `@spacedevin/deck` and need a conformance case. Sound goes in
`@spacedevin/deck-synths`. Sequencing, defaults, and the element go in `@spacedevin/deck-player`.
Not sure is a fine answer.

**Anything else**

Prior art, a host that already does it, a workaround you're using.
24 changes: 24 additions & 0 deletions .github/ISSUE_TEMPLATE/new_voice.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
---
name: New voice
about: Propose an instrument for the deck-synths catalog
title: 'voice: '
labels: enhancement, synths
assignees: ''
---

**The voice**

Name, generator id you'd propose, and what it sounds like — a chip, an instrument, a model.

**Reference**

What it should be compared against: real hardware, a recording, another synth, a paper.

**Params**

The `gen` keys a song would set, and sensible defaults.

**Are you up for building it?**

The recipe is in [CONTRIBUTING.md](../../CONTRIBUTING.md#add-a-voice): one pure function, one
registry entry, one example song. Happy to help either way.
20 changes: 20 additions & 0 deletions .github/PULL_REQUEST_TEMPLATE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
## What

<!-- One or two sentences. The PR title becomes the squash commit, so write it as a Conventional
Commit: feat(synths): …, fix(player): …, docs: … -->

## Why

## Which package

- [ ] `@spacedevin/deck` (language)
- [ ] `@spacedevin/deck-synths` (voices)
- [ ] `@spacedevin/deck-player` (host)
- [ ] docs / site / CI only

## Checklist

- [ ] `npm test` passes (and `npm test -w @spacedevin/deck-player` if the player changed)
- [ ] If the parser changed: `npm run conformance:update` and I reviewed the diff
- [ ] If a voice was added: it's in `Registry.tish`, `Dispatch.tish`, the synths README table, and has a song in `docs/EXAMPLES.md`
- [ ] Docs say what the code now does
19 changes: 13 additions & 6 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,18 @@ Language-only package for the **`.deck`** patch language.

## Repo layout

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

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

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

## In scope

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

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

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

Expand Down
125 changes: 125 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
# Contributing

Thanks for looking. This repo is small on purpose, and most useful contributions are small too: a
song, a voice, a grammar case, a doc fix. This page is the setup and the recipes.

## Setup

You need **Node 22+** and the [Tish](https://github.com/tishlang/tish) compiler, which comes in as a
dev dependency. Chrome or Chromium is only needed for the WAV renderer.

```bash
git clone https://github.com/spacedevin/deck
cd deck
npm install
npm test
```

`npm test` builds the language package, runs the API and grammar suite with a 100% line-coverage
gate, the conformance corpus, every song in `docs/EXAMPLES.md`, and the Tish and JS smoke tests.

The workspaces have their own tests:

```bash
npm test -w @spacedevin/deck-player
npm run build -w @spacedevin/deck-synths
```

And the docs site, which is how you preview any markdown change:

```bash
npm run site:serve # http://localhost:4321
```

## How the repo is laid out

| Path | Package | Owns |
|---|---|---|
| `/` | `@spacedevin/deck` | the language: tokenize, parse, registries, highlight. **No audio.** |
| `packages/synths/` | `@spacedevin/deck-synths` | the 33 voices and the dispatch that picks one |
| `packages/player/` | `@spacedevin/deck-player` | Song IR, defaults and clamps, transport, offline render, `<deck-player>` |
| `conformance/` | — | the parse contract every implementation is checked against |
| `crate/` | `deckfile` | **generated** from `src/` by `npm run build:rust`; never edit by hand |
| `site/` | — | the docs-site generator; markdown is read in place from the paths above |

Dependencies point one way: player → synths → deck. Each package has an `AGENTS.md` saying what
belongs in it and what does not. Read the one for the package you're touching; the boundaries are
the thing this repo cares most about.

Tish is the source language and it has gotchas: there is **no `class` syntax** (the build emits JS
that doesn't parse), and `undefined` is not a value under `tish run`. `packages/player/AGENTS.md`
explains why the custom element is plain JS for that reason.

## Commit messages

Releases are cut by [sem](https://github.com/tishlang/sem) from Conventional Commits, so the type
you pick decides whether a version ships:

| Type | Effect |
|---|---|
| `feat:` | minor release |
| `fix:`, `perf:` | patch release |
| `feat!:` or a `BREAKING CHANGE:` footer | major release |
| `docs:`, `chore:`, `ci:`, `test:`, `refactor:` | no release |

Scope with the package when it helps: `feat(synths): …`, `fix(player): …`, `docs(examples): …`.
A green `main` cuts a prerelease with all three tarballs; promoting it publishes to npm and crates.io.
The PR title becomes the squash commit, so write it as the commit.

## Recipes

### Add a song to the examples

1. Add a `deck` fenced block to `docs/EXAMPLES.md` under the right heading, with a sentence saying
what it demonstrates.
2. `npm run test:examples` — every block must parse without errors and produce at least one
sounding channel. That is also the rule the site uses to decide whether to show a play button.
3. `npm run site:serve` and press play on it.

Grammar-reference snippets with `<placeholders>` belong in `docs/DECK_GRAMMAR.md`; complete songs
belong in `docs/EXAMPLES.md`.

### Add a voice

1. Create `packages/synths/src/<Name>.tish` exporting `play<Name>(ctx, bus, t, midi, vel, durSec, ch, bendSemis)`.
Build a short-lived Web Audio subgraph, connect its last node to `bus.input`, and disconnect the
nodes once the tail has passed (the existing voices schedule that themselves; look at
`GameBoyDmg.tish` for the shape). A voice may instead return `{ stopTime, disconnects }` and let
the player prune it per step.
2. Register it: an entry in `src/Registry.tish` (id, label, description, default `generatorParams`)
and a case in `src/Dispatch.tish`. Param aliases or a `gen_block` dialect go in `src/DeckIds.tish`.
3. Seed anything random. Two renders of one song must be identical.
4. Add a song for it to `docs/EXAMPLES.md` (recipe above) and a line to the voices table in
`packages/synths/README.md`.
5. `npm test` and `npm test -w @spacedevin/deck-player`.

### Change the grammar

1. Change `src/deckfile/*.tish`. The parser is parse-only: no defaults, no clamping, no range checks.
Those are host policy and belong in the player.
2. Update `docs/DECK_GRAMMAR.md` — it is the canonical reference — and `docs/AST.md` if the shape changed.
3. Regenerate the corpus with `npm run conformance:update` and **review the diff**. A new case means
every profile in `conformance/profiles.json` must say where it stands.
4. `npm run test:rust` to confirm the Rust emit still agrees.
5. Keywords for highlighting live in `src/deckfile/Highlight.tish`; the site picks them up from there.

### Add or fix a doc page

The site is a view over the markdown already in the repo. Drop a `.md` under `docs/`,
`packages/player/` or `packages/synths/` and it appears in the nav, in `llms.txt` and in
`llms-full.txt` on the next build. There is no route to register. Don't add YAML frontmatter to a
file that ships in an npm tarball (`README.md`, `AGENTS.md`); npm renders it as a stray heading.
Use the per-section title override in `site/build.mjs` instead.

## Pull requests

- Keep a PR to one package where you can; the template asks which.
- Tests pass, the conformance diff is reviewed if you touched the parser, and the docs say what the
code now does.
- No CHANGELOG edits: the release notes are generated from the commits.

## Reporting a bug

Open an [issue](https://github.com/spacedevin/deck/issues/new/choose). The most useful bug report
is the smallest `.deck` that shows it, plus what you expected to hear or parse. If the parsers
disagree with each other, that is a conformance case waiting to be written.
Loading
Loading