# Toy source format 0.1.0

> This specification governs the portable .toy.json document: what it contains, which references must resolve, which bounds every host enforces, and which parts are still draft. It i

Source: https://docs.tune.toys/contributors/toy-format/

This specification governs the portable `*.toy.json` document: what it contains, which references must resolve, which bounds every host enforces, and which parts are still draft. It is the write model shared by the browser editor, the agent authoring path, the native Player and independent exports.

**Status:** draft local authoring subset; schema and validator implemented on browser, native and WASM, including the v0.0.5 cable types, parameter sockets, node `data`, buses and node-face widgets. Video and sensing nodes validate everywhere but execute only in the browser. The authoritative structure is [toy.schema.json](https://github.com/joshstovall/TuneToys/blob/main/packages/toy-format/schemas/toy.schema.json). TypeScript types are generated from it, never maintained by hand. The schema `$id` is a proposed stable identifier; validation loads the bundled schema and never fetches that URL.

## Document shape

A Toy is UTF-8 JSON with `format: "tune-toys"` and an exact `schemaVersion`. It is source, not an installed binary or a published release manifest.

| Top-level field            | Holds                                                                                                             |
| -------------------------- | ----------------------------------------------------------------------------------------------------------------- |
| `id`, `revision`, `title`  | Durable Toy identity, authoring revision and presentation title                                                   |
| `entrypoint`, `entryFrame` | The definition that runs and the Frame that opens; entry Frame ownership must match                               |
| `definitions`              | Keyed reusable implementations: nodes, connections, public parameters, inputs, outputs and optional loose widgets |
| `frames`                   | Keyed explicit playable surfaces, each owned by one definition                                                    |
| `assets`                   | Declared media with relative `assets/` path, SHA-256, size, media type and license                                |
| `styles`, `theme`          | Optional portable appearance (see [Appearance](#appearance))                                                      |
| `tuning`                   | Optional Toy-wide note tuning for instruments and Tuners (see [Tuning](#tuning))                                  |
| Namespaced extensions      | Optional non-executable metadata, preserved within JSON bounds and reserved-key rules                             |

```mermaid
%%{init: {'theme': 'base', 'themeVariables': {'primaryColor': '#FAFAF7', 'primaryTextColor': '#1C1C1B', 'primaryBorderColor': '#1C1C1B', 'lineColor': '#64645F', 'fontFamily': 'DM Sans, Inter, system-ui, sans-serif'}}}%%
flowchart LR
  TOY[("Toy")] --> DEF["Definition<br/>inputs · outputs · parameters"]
  DEF --> NODE["Node<br/>primitive or group instance"]
  DEF --> CONN["Connection<br/>{node, port} → {node, port}"]
  DEF --> PARAM["Public parameter<br/>targets node parameters"]
  TOY --> FRAME["Frame<br/>owned by one definition"]
  FRAME --> WIDGET["Widget<br/>binds a public parameter"]
  FRAME --> FOLDER["Layer folder"]
  FRAME -.->|"nested Frame names a group node"| NODE
  TOY --> ASSET["Asset declaration"]

  classDef source fill:#F0442E,stroke:#1C1C1B,color:#FAFAF7
  classDef shared fill:#DCEBDD,stroke:#1C1C1B,color:#1C1C1B
  classDef host fill:#DED8F0,stroke:#1C1C1B,color:#1C1C1B
  class TOY source
  class DEF,NODE,CONN,PARAM shared
  class FRAME,WIDGET,FOLDER,ASSET host
```

A definition may own up to 512 loose interface components in `widgets`, using the same Widget union and local bindings as `Frame.widgets`. Their `bounds.x/y` are absolute Patch coordinates within ±1,000,000; dimensions retain the shared widget bounds. Frame members use local coordinates inside the Frame. Folder membership and responsive Frame constraints apply to explicit Frame members. Loose components are authoring views and do not join a published Frame by proximity.

## Identity and composition

**Keys are stable lowercase IDs; names and labels are presentation.** A node is either a pinned draft primitive or a reference to a local group definition. A group node's key identifies its instance; the definition ID identifies reusable source. Runtime addresses derive from instance paths and are never substituted for source IDs.

Connections and ports:

- Endpoints are `{node, port}`. `$in` is the enclosing definition's input source and `$out` its output destination; neither can be used in reverse.
- Every port is a bundle of lanes of one signal type: `audio` (1–32 `channels`), `video` (1–8 `streams`), `control` (1–32 `lanes`) or `midi` (one stream of sixteen MIDI channels). Widths are fixed or `"auto"`, with optional ordered labels. The shared engine executes audio, control and MIDI on every host; video runs only where a host has a video engine (the browser), and the host-only `data` type is described with the [data cable](https://github.com/joshstovall/TuneToys/blob/main/docs/architecture/data-nodes.md). Manifest ports may also be `signal` (Math and Compare, resolved to audio or control per cable; never a definition boundary, `PORT_TYPE`) or `any` (`bus.send`/`bus.receive` only). See [signal types and domains](https://github.com/joshstovall/TuneToys/blob/main/docs/architecture/signal-types-and-domains.md).
- A connection needs matching types, compatible resolved lane counts and exactly one driver per destination. Mixers and conversions are explicit.
- Feedback is rejected entirely in this draft. Delayed feedback needs a later compiler/profile contract.
- A connection may end at a **parameter socket**, `{ node, port: "@name" }`: exactly one one-lane `control` cable drives that parameter, its 0–1 value maps across the parameter's linear or logarithmic domain, and a public parameter aliasing the slot becomes read-only (no LFO or MIDI mapping may share it).
- A primitive may carry bounded `data` the manifest admits per key: float tables (`std.wavetable` `table`, 1–8 frames of 2048 samples in −1…1; `ctl.curve` `points`, 2–32 `(x, y, bend)` groups with x and y in 0…1 and bend in −1…1, from the manifest's `minItems` and per-position `fields`, `NODE_DATA` otherwise) and short text (for example the logical `label` of `midi.in` or `video.camera`, the `asset` or `source` of `media.player`, the `template` of `video.text`). The whole object stays within 64 KiB. Data is source, hashed into releases and edited through authoring; large media stays in `assets`. A `video.image` or `media.player` `asset` must name a declared asset of the image, or video/audio, family (`ASSET_REFERENCE`).
- An `items` data key contains an ordered list of records with unique durable `id`s (`[a-z][a-z0-9_-]{0,31}`), and optional manifest-declared bounded name, colour or icon fields. A manifest `repeat` block adds each item's ports and numeric parameters as `<id>.<member>`; parameter sockets use `@<id>.<member>`. `std.mixer` uses `data.channels` (0–32 items); `std.eq@0.3.0` uses `data.bands` (1–32). Numeric DSP values remain in `values`, never in presentation fields. A node admits at most 288 values; a definition admits 512 public parameters. Reordering or renaming preserves all bindings and the prepared program. Removal atomically cleans up scoped values, cables, public targets and widgets; Undo restores them. See [repeatable node items](https://github.com/joshstovall/TuneToys/blob/main/docs/architecture/node-items.md).
- `bus.send` and `bus.receive` nodes carry a `bus` name and connect without a cable inside one definition: one Send per name, any number of Receives, type and width inherited from the Send's driver; an unnamed node is inert. The compiler lowers them to direct connections; a Receive feeding its own Send is a cycle.

Public parameters declare label, range, default, unit, linear or log scale, smoothing time and direct node-parameter targets. A group parameter targets the child's public parameter. The draft uses identity conversion and requires the public range to fit each target; nonlinear or multi-range mappings need explicit future operations. One public owner may target a node parameter. Node `values` are instance defaults, and enclosing public parameter defaults override their targets during preparation.

## Frames

A Frame belongs to a definition and contains explicit widgets. Geometry lays out only explicit members. A nested Frame names a group node plus a Frame of that group's definition and binds relatively to that instance.

| Widget kind                                | Binding and rules                                                                                                                                                                                                                                                                                                      |
| ------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `knob`, `slider`                           | One public parameter                                                                                                                                                                                                                                                                                                   |
| `toggle`, `momentary`, `trigger`           | One public parameter with optional in-domain distinct `onValue`/`offValue`; only `trigger` admits `pulseMs` (10–2000, default 80)                                                                                                                                                                                      |
| `xy`                                       | Two distinct public parameter IDs                                                                                                                                                                                                                                                                                      |
| `filter`, `envelope`                       | Graphical roles must resolve to matching parameters of one real processor, including nested aliases                                                                                                                                                                                                                    |
| `face`                                     | `face` names a node face; `parameters` binds every role in `FACE_BINDINGS` to a distinct public control reaching that role's parameter of one node of the face's type, nested aliases included; `node` (that node) is required for data faces (`FRAME_PARAMETER`, `WIDGET_SOURCE_KIND`)                                |
| `text`                                     | Inert                                                                                                                                                                                                                                                                                                                  |
| `icon`                                     | Schema-admitted bundled `icon` name, accessible `label` (empty for decorative art), bounds; optional `appearance` with `none`/`currentColor`/six-digit hex fill and stroke and a 0–4 stroke width                                                                                                                      |
| `piano`                                    | `node`: a durable path to a `midi.keyboard` node, and a 2–37 key range; otherwise `FRAME_PERFORMANCE`                                                                                                                                                                                                                  |
| `screen`                                   | `mode` `image` or `video` with a declared asset (16 MiB), `scene` with a built-in scene, or `live` with `source: {node, port}` naming a video output of the Frame's definition and an optional 0-based `stream` for bundles and live-only `tracking` boolean for preview landmarks (`FRAME_SOURCE`, `ASSET_REFERENCE`) |
| `paint`                                    | `source`: the `video` output of a `video.paint` node of the definition; optional `color`, `width`, `opacity` (`WIDGET_SOURCE_KIND`)                                                                                                                                                                                    |
| `curve`                                    | `node`: a `std.wavetable` node of the definition; `tableFrame` 0–7 (`WIDGET_SOURCE_KIND`)                                                                                                                                                                                                                              |
| `switcher`                                 | `node`: a `video.switch`; `parameters` binds `program`, `preview` and `transition` roles to distinct public controls targeting that node (`WIDGET_SOURCE_KIND`, `FRAME_PARAMETER`)                                                                                                                                     |
| `readout`                                  | Exactly one of a public `parameter` or a `source` control output with a `lane`; `format` number, percent, integer or boolean, `precision`, `unit` (`FRAME_PARAMETER`, `WIDGET_SOURCE_KIND`)                                                                                                                            |
| Nested Frame                               | Group node plus a Frame of that group's definition                                                                                                                                                                                                                                                                     |
| Meters, presets, MIDI and transport fields | Admitted by the draft schema; executable semantics in [creation workflows](https://github.com/joshstovall/TuneToys/blob/main/docs/architecture/creation-workflows.md)                                                                                                                                                                                                      |

Buttons, XY pads and graphical editors are numeric controls, not executable actions or a MIDI-note graph; see [interaction semantics](https://github.com/joshstovall/TuneToys/blob/main/docs/architecture/playable-inspection.md). Unknown icon names, external SVG and event handlers reject; see [icons](https://github.com/joshstovall/TuneToys/blob/main/docs/architecture/icons.md). `face`, `paint`, `curve`, `switcher`, `readout` and a live `screen` are faces of one node in the Frame's own definition; the validator, the editor palette and Design fields share their admission in [`faces.ts`](https://github.com/joshstovall/TuneToys/blob/main/packages/toy-format/src/faces.ts). A host without the service behind a face shows an unavailable state rather than a frozen picture ([video](https://github.com/joshstovall/TuneToys/blob/main/docs/architecture/video.md#status-and-unavailable-states)).

Layers and folders:

- Every widget may carry a layer `name` (1–128 characters), `hidden` and `locked`. Missing flags mean visible and unlocked. Names are independent of widget IDs, text content and control labels.
- Hidden members are omitted from presentation at every nesting level without altering graph execution or bindings. Locks guard browser design edits; they are not an authorization boundary and do not disable live controls.
- A Frame may hold up to 512 explicitly bound widgets, matching the 512 public-control budget so grouping a full Mixer or EQ can retain every control. Frames remain bounded to 8192 × 8192 authored units; the source-size and prepared resource limits still apply.

A Frame may hold up to 64 keyed `layerFolders`, each with `name`, sibling `zIndex` (0–1023) and optional `parent`, `hidden`, `locked`. A widget's optional `folder` names its parent. Folder and widget IDs are distinct within a Frame. Missing parents, cycles and depth beyond 16 fail admission on every host. Folders form contiguous sibling stacking contexts, hidden state propagates to descendants, and locks inherit without disabling playback. See [layer behavior](https://github.com/joshstovall/TuneToys/blob/main/docs/architecture/appearance.md#multi-selection-and-layer-folders).

Responsive layout: optional Frame `layout` gives bounded viewport minimum and maximum sizes; widget `constraints` select start/end/center/stretch/proportional anchors and element limits. Authored bounds remain the reference and the shared renderer projects them. Omitted layout keeps fixed-canvas scaling. Reversed limits and unknown anchors reject. See [responsive Frames](https://github.com/joshstovall/TuneToys/blob/main/docs/architecture/responsive-frames.md).

## Tuning

An optional Toy-level `tuning` says how instruments map MIDI notes to pitch, so every instrument and every Tuner left on **Toy tuning** agree. It lives on the Toy, beside `transport`, rather than on a definition: a reusable instrument definition plays in whatever tuning the Toy that uses it declares, and nested groups never disagree.

| Field       | Holds                                                                                                                                                      |
| ----------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `preset`    | One of `12-tet`, `19-edo`, `22-edo`, `24-edo`, `31-edo`, `53-edo`, `just`, `pythagorean`, `bohlen-pierce` (tables in `packages/node-catalog/tunings.json`) |
| `cents`     | Instead of a preset: 1–128 custom degrees in cents, `0` first, strictly ascending, each below `period` and at most 9600                                    |
| `period`    | With `cents`: the repeat interval in cents, 1–9600 (1200 for an octave)                                                                                    |
| `root`      | Required. Tonic pitch class 0–11 (C = 0) of root-anchored tunings (just, Pythagorean, Bohlen–Pierce and custom); equal divisions of the octave ignore it   |
| `reference` | Required. A4 in Hz, 400–480                                                                                                                                |

Exactly one of `preset` or `cents` + `period` is present, and the custom degree rules are semantic (`TUNING`); the schema bounds each field (`SCHEMA`). Absent means 12-TET at A4 = 440 Hz. The compiler copies the tuning into the Program and the engine resolves it once at preparation: MIDI note 60 is degree 0 (60 + `root` for root-anchored tunings), and degree `note − 60` folds across periods. An equal division of the octave keeps A4 on the degree three of its own fifths above C; a root-anchored tuning puts its root at its 12-TET pitch from `reference`. 12-TET keeps `reference · 2^((note − 69) / 12)` in single precision, bit for bit. See [`std.poly`](https://docs.tune.toys/nodes/std.poly/) and [`std.tuner`](https://docs.tune.toys/nodes/std.tuner/).

## Appearance

Optional Toy and Frame `theme` values and element `style` overrides are portable presentation. A theme selects a bounded preset and/or overrides color, typography and radius tokens, plus the optional instrument-screen tokens `screen`, `screenTrace` and `screenGlow` (0–1); an explicit preset resets the base before its overrides. Element styles add bounded, geometry-independent visual properties. Colors are six-digit hex, fonts come from a local allowlist, and no arbitrary CSS or remote resource is admitted.

Toy-wide `styles` holds up to 64 named text or control definitions by durable ID, each with `name`, `kind` and bounded `values`. A widget `styleRef` must reference an existing compatible definition: text styles address text, control styles address knobs, sliders and meters. Resolved appearance merges theme, named values, then local element values. Shared values exclude layer depth, layout, content and bindings. Rename preserves identity; authoring deletion materializes effective appearance before detaching. See [appearance resolution](https://github.com/joshstovall/TuneToys/blob/main/docs/architecture/appearance.md).

## Bounds and diagnostics

| Bound              | Limit                                                                                                                                |
| ------------------ | ------------------------------------------------------------------------------------------------------------------------------------ |
| Source bytes       | 1 MiB UTF-8 (`readToy`), then JSON parse, then `validateToy`                                                                         |
| JSON traversal     | Depth 64, 50,000 values; well-formed Unicode without NUL; lone surrogates reject                                                     |
| Structure          | 64 definitions, 64 nodes and 256 connections and 256 ports per direction per definition, 64 Frames, 512 widgets per Frame, 64 assets |
| Semantic expansion | 256 expanded node instances per definition and 16 definition levels; repeated empty groups count                                     |

These are validator limits, not measured audio budgets. All definitions and Frames are checked, including unused ones. Native and WASM compilation derive structural checks from the same schema at Cargo build time and repeat semantic admission before lowering; the build generator supports the schema's closed JSON Schema 2020-12 keyword and ASCII-pattern subset and fails on anything else. Native JSON parsing runs a streaming Rust preflight first. A shared corpus compares browser, native and WASM acceptance without claiming general JSON Schema certification.

Diagnostics return stable category codes and source paths for schema, references, domains, ownership, cycles, expansion and Frame/asset declarations. Validation never mutates input or fills defaults. Unknown core fields and versions fail visibly. Unknown namespaced extension values are preserved but are never executable authority.

The maintained catalog keeps `std.gain`, `std.lowpass`, `std.delay` and `std.oscillator` at 0.1.0 fixed-stereo and 0.2.0 bundle contracts. `std.crossfade@0.2.0` blends two sources; `std.mixer@0.2.0` sums durable channel items. `std.eq@0.3.0` uses durable band items. Other admitted types and versions come from the [node catalog](https://github.com/joshstovall/TuneToys/blob/main/packages/node-catalog/README.md#primitives); engine admission remains a separate bounded profile.

Host requirements are never authored. The compiler derives `requires` on the prepared program (and `toyRequirements()` on source) from the node manifest and the root buses, so a Toy cannot claim or omit a service its graph does not use. Assets: the validator checks declaration shape, path uniqueness and traversal only. The development media/publication service verifies library-backed bytes, pinned versions, declared rights and closure bounds; full decoder and archive admission is a managed-ingestion gate.

## Evolution beyond the subset

Definition presets, parameter MIDI CC and LFO settings, Toy transport defaults, preview-asset references and Frame meters are admitted by the draft schema; their executable semantics are in [creation workflows](https://github.com/joshstovall/TuneToys/blob/main/docs/architecture/creation-workflows.md) and [media library](https://github.com/joshstovall/TuneToys/blob/main/docs/specs/media-library.md). External dependencies and release locks, sized port families, MIDI 2.0, nonlinear target mappings, explicit side-effect roots and broader target profiles are future versioned contracts. **Do not hide essential behavior in extensions to bypass validation.** Proposed release fields live in [releases](https://github.com/joshstovall/TuneToys/blob/main/docs/specs/releases.md).

The source version, DSP semantic version, engine build, target profile, state version and package release version are distinct. Before format v1: add migration fixtures, exact canonicalization and hash vectors, supported reader/writer rules and compatibility tests. Until then, 0.x draft revisions may change with explicit example and tool updates. Legacy `.bitdsp` is not automatically compatible.

## Implementation

- [packages/toy-format](https://github.com/joshstovall/TuneToys/blob/main/packages/toy-format/README.md) — schema, generated types, validator and fixtures.
- [crates/compiler](https://github.com/joshstovall/TuneToys/blob/main/crates/compiler/README.md) — generated structural checks and semantic admission for native and WASM.
- [examples](https://github.com/joshstovall/TuneToys/blob/main/examples/README.md) — [gain](https://github.com/joshstovall/TuneToys/blob/main/examples/gain/gain.toy.json), [nested gain](https://github.com/joshstovall/TuneToys/blob/main/examples/nested-gain/nested-gain.toy.json), Warm Echo and Little Sine all run through the shared Rust engine.
- [tests/conformance](https://github.com/joshstovall/TuneToys/blob/main/tests/conformance/README.md) — cross-host acceptance corpus.

## Related

- [Frames and UI composition](https://docs.tune.toys/contributors/frames/) and [appearance](https://github.com/joshstovall/TuneToys/blob/main/docs/architecture/appearance.md).
- [Revisioned authoring operations](https://github.com/joshstovall/TuneToys/blob/main/docs/specs/operations.md) are the only write path to this document.
- [Decision 0001: one portable model](https://github.com/joshstovall/TuneToys/blob/main/docs/decisions/0001-portable-model.md).
