# Tune.Toys documentation (full) Source: https://docs.tune.toys. Each page below starts with its URL. --- URL: https://docs.tune.toys/start/ Title: Get started This path uses a template, so you hear sound in the first minute and change it in the second. 1. Open [tune.toys](https://tune.toys/) and choose **New Toy**. The chooser offers Effect, Instrument, Template and Blank starting points, and AI recipes if generation is enabled. Pick the **Warm Echo** template. 2. Choose **Preview** to hear the template with the included demo input. Browsers only start audio after you click, so nothing plays by surprise. 3. Move the **Time**, **Feedback** and **Mix** controls in the Frame. These are the playable controls the template author placed. 4. Choose **Create**. You now own an independent copy with its own identity. Your earlier work on this device is kept before the new Toy opens. 5. Open **Patch** to see the nodes and cables behind the sound. Select a node and press `F1` or `?` to open its documentation beside the canvas. 6. Open **Frame** to move and restyle the controls. Changing a Frame never changes the sound; changing the Patch never moves a control. 7. Press `Cmd/Ctrl+S` to save. You choose between a copy on this device and an account draft. Saving never publishes. 8. When you want to share it, choose **Publish**, review access (public, unlisted or private), and confirm. Only the server's confirmation shows the success screen and the playable link. ## What to read next - [Requirements](https://docs.tune.toys/start/requirements/) for browsers, audio devices and sign-in. - [Download](https://docs.tune.toys/start/download/) for the desktop app and what is available on each platform. - [Workspace](https://docs.tune.toys/manual/workspace/) to arrange Patch, Frame, Preview and Source JSON the way you work. - [Guides](https://docs.tune.toys/guides/) for three lessons that each start from a template. - [Nodes](https://docs.tune.toys/nodes/) when you want to know exactly what a node does. --- URL: https://docs.tune.toys/start/download/ Title: Download Tune.Toys runs in your browser today, with nothing to install. Desktop downloads come from the same signed release feed the Installer uses; this page lists each one only once it is published. | Platform | What you get | Get it | Status | | ---------------------------------------------- | ----------------------------------------------------------------------------------------- | -------------------------------------------- | ------------------------------------------------------------------------------------------------------------ | | **Web** (current browsers) | The editor, Player and your Library | [Open tune.toys](https://tune.toys/) | Available | | **macOS** (Apple silicon) | The desktop app: the editor in its own window, plus the Installer | Not published yet | Runs in development builds; the signed, notarized release pipeline is ready and the first release is pending | | **DAW plugin** (VST3) and standalone Toys | A Toy in your DAW or as its own app | Through the Installer, from a product's page | In qualification | | **Windows** (x64) | The desktop app and Installer, installed for your account without an administrator prompt | Not published yet | Packaging and signing are set up; the first build and release are pending | | **Linux** (x64) | The desktop app as an AppImage, or a `.deb` package | Not published yet | Packaging is set up; the first build and release are pending | | **iOS**, **Android** and the browser extension | Playing Toys on the go | — | Planned | See [Requirements](https://docs.tune.toys/start/requirements/) for browsers and audio, and [Desktop app, Player and Installer](https://docs.tune.toys/manual/player-installer/) for what the desktop app does. ## How downloads are verified Every desktop download is listed in a product's signed release feed with its size and SHA-256 digest. The Installer and the desktop app's own updates check the feed's signature, then the file's digest, then the app's code signature before anything is installed. A file that fails any check is discarded. Releases [announce](https://docs.tune.toys/releases/) each new version. ## Building the desktop app yourself Developers can run the macOS desktop app from the source repository before it is published: ```sh npm ci npm run build # the web editor the desktop app bundles npm run build -w @tunetoys/desktop # the Electron main process, preloads and bundled editor npm start -w @tunetoys/desktop # opens the Installer; File → New Editor Window opens the editor ``` A development build is signed only for your own Mac and talks to the platform set in the Installer's settings. See the [desktop shell](https://github.com/joshstovall/TuneToys/blob/main/apps/desktop/README.md) for details. --- URL: https://docs.tune.toys/start/requirements/ Title: Requirements ## Browser Tune.Toys runs its audio engine as WebAssembly in an AudioWorklet, so it needs a current desktop or mobile browser with those features. Audio starts only after a click or tap. If a browser blocks or suspends audio, the app shows a message instead of failing silently. ## Audio - Use headphones or speakers at a comfortable level. Previews play at a reduced monitor level, and the monitor level is yours to set under **Settings → Audio**. - The audio device and sample-rate options are in [Account and settings](https://docs.tune.toys/manual/account-settings/). - MIDI keyboards need a browser that supports Web MIDI. The app asks for access only when you open the MIDI monitor or map a control. ## Account You can open, play and edit Toys without an account. An account is needed to keep drafts on the server, publish, build a library across devices, buy, sell, review, comment and use the developer platform. ## Desktop The macOS desktop app, the Installer and the DAW Player are described in [Desktop app, Player and Installer](https://docs.tune.toys/manual/player-installer/); [Download](https://docs.tune.toys/start/download/) lists what you can install today. None of them are needed to use Tune.Toys in a browser. --- URL: https://docs.tune.toys/manual/ Title: User Manual The manual follows the order of a task, not the order of the buttons. Read it front to back once, then use search. Throughout the manual, a **Toy** is the whole creation, a **Patch** is the sound graph, a **Frame** is the playable interface and a **release** is an immutable published version. The [glossary](https://docs.tune.toys/glossary/) defines every term. --- URL: https://docs.tune.toys/manual/account-settings/ Title: Account, settings, notifications and language ## Profile and security Choose a username (it appears in `@username` URLs), a display name and an avatar. Add a recovery email. Sign-in providers can be linked explicitly. **Security** lists your sessions and lets you sign out other devices. ## Appearance and language **Settings → Appearance** sets theme, density and the interface **language**. Each language shows its own name and an _unverified translation_ badge while its text is machine-drafted. Untranslated text falls back to English. ## Audio **Settings → Audio** chooses the output device and monitor level. ## Notifications The bell in the navigation rail opens your notification inbox with filters (All, Unread, Mentions, Updates, Purchases). **Settings → Notifications** has a toggle for each event type per channel: in-app, email and push. Required events such as receipts, payment failures and security sign-ins cannot be turned off. Every email has a one-click unsubscribe that confirms in the browser and never changes anything on a plain page load. ## Billing **Settings → Billing** shows orders and entitlements and opens the provider portal. ## Developer **Settings → Developer** creates apps and API keys. See [Developers](https://docs.tune.toys/developers/). --- URL: https://docs.tune.toys/manual/frame-design/ Title: Frame design A Frame is the part of a Toy that people play. It is separate from the Patch: you can redesign it without touching the sound. ## Components Add components from the palette: knobs, sliders, toggles and momentary buttons, segmented selectors, XY pads, a piano, meters and scopes, filter and envelope editors, screens and text. See the [Components reference](https://docs.tune.toys/components/). ## Binding A control binds to one public parameter of the Toy (or two for an XY pad). The Frame designer only offers parameters that exist. Dragging a control in the Frame changes the live sound; saving a different default is a separate step. ## Layout and style Frames have an authored size and scale to fit their container. Use layers to order and group elements, shared styles to reuse appearance, and the Frame preview to check small screens. See [Frames](https://docs.tune.toys/concepts/frames/). ## Nested Frames A group can have its own Frame that appears inside the parent. Each nested Frame is bound to its own instance, so editing one does not change another. --- URL: https://docs.tune.toys/manual/library/ Title: Library The Library is your personal shelf. Bookmark Toys from Explore or from a Toy page, add private tags and notes, and pick which release of a product you use. Bookmarks sync across your devices through your account. - **Bookmarks and tags** are private to you and never searched publicly. - **Selected release** keeps a product on the version you chose until you move it; app updates never move it for you. - **Installed** products are those cached or installed on a device. Installing is a Player/Installer feature; see [Desktop app, Player and Installer](https://docs.tune.toys/manual/player-installer/). Library changes sync as an ordered stream; the client saves its position only after committing a page, and bookmarking never downloads or activates audio. --- URL: https://docs.tune.toys/manual/marketplace-buying/ Title: Marketplace: buying **Status:** Checkout runs against a payment **sandbox** until live activation. Sandbox purchases use test cards and do not move real money. ## Finding products Use **Explore** to browse by instrument or effect, search by title, creator or tag, and open a product page for the preview, versions, reviews and documents. ## Buying Choose an offer on the product page. Checkout is hosted by the payment provider; Tune.Toys never sees card numbers. On success you receive an **entitlement** for that product and a receipt notification. Paid products need a live entitlement (or publisher membership) to download release bytes. The product's declared demo release and its listing stay readable without buying. ## After you buy Find the product in your Library, pick a release, and download from the product page. Refund handling follows a policy that is not yet published. --- URL: https://docs.tune.toys/manual/marketplace-selling/ Title: Marketplace: selling **Status:** Selling uses Stripe Connect Express in sandbox. Real payouts require live activation. ## Publisher profile A publisher owns products and a public storefront at `tune.toys/@handle`. Roles: **owner**, **release manager** and **editor** can edit; owners and release managers change visibility and slugs and publish storefronts; **finance** and **support** read. ## Payouts Connect onboarding is hosted by Stripe. Until it completes you can publish free products but cannot create paid offers. ## Offers and pricing Create offers per product. Prices and grants are chosen by the server, not the browser. A paid product needs an active offer and entitled buyers. ## Storefront Design your storefront from validated theme tokens and ordered sections. Drafts have revisions and a preview before you publish. ## Reviews and discussion Reviews, comments and replies are one post system. You can pin, reply to and report posts; moderation decisions are visible to you as the product owner. Blocked accounts are hidden in both directions. --- URL: https://docs.tune.toys/manual/patch/ Title: Patch A Patch is a graph of **nodes** joined by **cables**. Sound flows from outputs to inputs; the Patch ends at the Toy's output. ## Adding nodes Open Spotlight (`Cmd/Ctrl+K`), search by name or tag, and press Enter. The palette beside the canvas lists the same nodes by category. Every node has a page in the [Nodes reference](https://docs.tune.toys/nodes/) with its ports, parameters and playable help patches. ## Connecting Drag from an output port to an input port. A cable is accepted only if the whole graph stays valid: channel counts must match or be inferable, an input takes one cable unless the node mixes, and feedback loops need a delay. If a cable is rejected, the editor says why and leaves the graph unchanged. See [Cables and channels](https://docs.tune.toys/concepts/cables/) and [Multichannel audio](https://docs.tune.toys/concepts/multichannel/). ## Parameters Select a node to edit its parameters in **Properties**. A parameter can be exposed as a public control so a Frame can bind to it. Choice parameters (such as a filter type) are discrete and cannot be modulated. ## Groups, subpatches and instances Select nodes and group them to make a reusable definition. Each use of a definition is an **instance** with its own parameter values and audio state, so two copies of the same gain stage stay independent. See [Groups and reusable patches](https://docs.tune.toys/concepts/groups/). ## Undo Every accepted gesture is one undoable step. Previews while you drag are inert: Escape or releasing outside cancels without changing anything. --- URL: https://docs.tune.toys/manual/player-installer/ Title: Desktop app, Player and Installer **Status:** The desktop app runs the full editor and the Installer in macOS development builds; Windows and Linux builds are set up and not yet built. Public downloads start with the first signed release; [Download](https://docs.tune.toys/start/download/) always shows what is available. The DAW Player core builds locally; DAW, system browser and WebView qualification are still open. ## The desktop app The desktop app is the same editor you use at tune.toys, bundled so it opens without a browser tab, plus the Installer. - **Editor window.** Choose **File → New Editor Window** (`Cmd+N` on a Mac, `Ctrl+N` elsewhere). Everything works as in the browser: Patch, Frame, Preview, Source, audio and MIDI. - **Native menus.** The editor's **File, Edit, View, Patch, Window** and **Help** menus become the app's own menus: in the macOS menu bar (with **Settings** in the application menu) and in each window's menu bar on Windows and Linux. Undo, Redo and Select All edit a text field when one has focus. - **Your account.** The desktop app talks to the platform you choose in the Installer's settings. If the platform cannot be reached, account features say so; editing on this computer keeps working. ## Where it installs and how it updates | System | Installs to | Updates | | ------- | --------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- | | macOS | Wherever you put `TuneToys.app`, usually **Applications** | **Restart to update** verifies the new version's signature and swaps it in | | Windows | Your account's programs folder; no administrator prompt | **Restart to update** runs the verified installer silently and reopens the app | | Linux | The AppImage wherever you keep it, or `/opt/TuneToys` from the `.deb` | The AppImage replaces itself on **Restart to update**; the `.deb` shows a notice with a download link, so your package manager stays in charge | Every update is checked against the signed release feed, its file digest and, on macOS and Windows, the publisher's code signature before anything changes. Copies from an app store update through that store instead. ## The Installer The **Installer** keeps purchased and free products up to date and manages licenses offline. - Downloads are verified by digest and signature before use. - Licenses are certificates tied to your account and device; they are refreshed online and keep working offline for a stated period. - **Show Installer** in the **File** menu brings it back from any editor window. ## The Player The **Player** loads Toys inside a DAW or as a standalone app, rendering the same Frame and running the same engine as the browser. Pages for targets that are not qualified say so; features are never described as working before they are. Follow progress in [Releases](https://docs.tune.toys/releases/). --- URL: https://docs.tune.toys/manual/playing/ Title: Playing ## Starting audio Choose **Play** (or interact with a Frame). The first gesture unlocks audio; later Toys start immediately. **Stop** releases any held notes. ## Controls - Drag knobs and sliders, or focus them and use the arrow keys. - Hold a momentary button to play; tap a trigger pad for one event. - The piano responds to pointer, touch and keyboard. - With a MIDI keyboard connected, the **MIDI monitor** (Sound controls) shows what arrives. See [MIDI](https://docs.tune.toys/concepts/midi/) for mapping. ## Monitor level and device Open **Settings → Audio** to choose an output device, check the sample rate and set the monitor level. Previews always use a reduced level. ## Performance tips - Close extra tabs that use audio. - Prefer fewer simultaneous voices on slower devices. - If you hear crackling, raise the browser's audio buffer or lower the voice count. See [Troubleshooting](https://docs.tune.toys/manual/troubleshooting/). --- URL: https://docs.tune.toys/manual/saving-publishing/ Title: Saving and publishing ## Four states | State | Where | Notes | | ---------------- | ------------ | --------------------------------------------------------- | | Browser recovery | This tab | Automatic. Not a save. | | Device save | This browser | `Cmd/Ctrl+S`. Survives restarts on this device only. | | Account draft | Your account | Editable, private. **Saving a draft never publishes it.** | | Release | The server | Immutable. Public, unlisted or private. | ## Publishing **Publish** has four steps: choose access (public, unlisted or private, with license and fork permissions visible), review, let the server validate and create the release, and then see the playable and pinned links. Only a successful server response shows success. Changing the access of an existing release has its own review step. It never publishes your current edits and never deletes copies people already downloaded. ## Versions and changelogs A product has versions with [semantic version numbers](https://semver.org/). Each new version must sort above the highest published one, and a changelog is required. Aliases such as stable and beta point at versions. A yanked version keeps its number reserved. ## Listings A product page is generated from the release: title, description, preview and Frame image. You can override individual fields, add a gallery, manuals and FAQs as documents, and choose which fields come from the release. Creator documents appear at `docs.tune.toys/@handle//docs/`, rendered by the Tune.Toys server so changes and takedowns apply immediately. See [Marketplace: selling](https://docs.tune.toys/manual/marketplace-selling/). --- URL: https://docs.tune.toys/manual/sharing-embedding/ Title: Sharing and embedding Every public Toy has a short link and a pinned link to its exact release. The **Share** dialog shows both, a QR code, a `tunetoys://` deep link that opens the desktop app when it is installed, and embed code. ## Embedding Embed a playable Frame in another site with an iframe pointing at the embed renderer: - `https://tune.toys/embed/t/` follows the current public release of a Toy. - `https://tune.toys/embed/r/` pins one exact release. Parameters set the theme, hide controls, choose a preset or set initial values. Audio never autoplays. The full contract, including the `postMessage` API, is in [Embed API](https://docs.tune.toys/developers/embed/). ## Permissions A release chooses where it may be embedded (anywhere, an allowlist of origins, or nowhere). The Share dialog hides embed code when the policy is none. --- URL: https://docs.tune.toys/manual/troubleshooting/ Title: Troubleshooting ## No sound 1. Click Play. Browsers keep audio suspended until a gesture. 2. Check **Settings → Audio** for the right device and a monitor level above zero. 3. Check the tab is not muted and the system volume is up. 4. Reload. Your recovery copy restores the Toy. ## Crackling Lower the voice count, close other audio tabs, and try a larger buffer if your device allows it. ## Lost work Open **Library** for device saves and account drafts. The editor's recovery copy covers the Toy you had open. Source JSON can be exported at any time. ## Help links The Docs panel inside the app and this site show the same pages. Opening help for a node in the app maps to a stable URL here: | In the app | On this site | | ----------------------------------- | ------------------------------------ | | Node help (`F1`, `?`, context menu) | `/nodes//` | | A concept such as cables | `/concepts//` | | A numbered help patch | `/nodes//#patch-` | | A specific parameter | the parameter table on the node page | From this site, **Open in TuneToys** on a node or help patch uses `https://tune.toys/workspace?help=[/]`. ## Reporting a problem Report content from its page with **Report**. For bugs, include the app version shown in the footer of this site and your browser. --- URL: https://docs.tune.toys/manual/workspace/ Title: Workspace ## Starting a Toy Every **New Toy** action opens one chooser: **Effect** (a stereo effect with one level control), **Instrument** (an eight-voice instrument with a piano), **Template** (Warm Echo, Little Sine or Two Gain Stages), **Blank** (connected input and output, no nodes), and, when generation is enabled, AI recipes. Creating a Toy gives it a fresh identity and keeps what you were working on. ## Layout The workspace is a set of docked panels: **Patch**, **Frame Design**, **Preview**, **Source JSON**, and the tool panels **Properties**, **Layers**, **History** and **Docs**. Drag a tab to group it with another, to an edge to split, or outside the dock to float it. You can float up to six panels, save named layouts, and undo layout changes separately from Toy edits. Layout is stored in your browser. It is never part of a Toy, a release or its history. ## Spotlight and shortcuts - Double-click empty canvas, or press `Cmd/Ctrl+K`, to search nodes and place one where you clicked. - `Cmd/Ctrl+Shift+P` opens the command palette. - `F1` or `?` on a selected node opens its documentation in the Docs panel. See [How help links work](https://docs.tune.toys/manual/troubleshooting/#help-links). - `Cmd/Ctrl+S` saves; `Cmd/Ctrl+Z` and `Shift+Cmd/Ctrl+Z` undo and redo. History keeps the last 100 edits of the session. ## Recovery The browser keeps an automatic recovery copy of the Toy you have open. It is separate from a device save and from an account draft. See [Saving and publishing](https://docs.tune.toys/manual/saving-publishing/) for the four states. --- URL: https://docs.tune.toys/nodes/ Title: Nodes reference Every node in the catalog has a page generated from `packages/node-docs` and the node catalog, so the ports and parameter tables always match the app. Each page has the node's ports, parameters with ranges and defaults, an explanation, and ordered **help patches**: small Toys that each teach one idea, shown as a static diagram and Frame preview with an **Open in TuneToys** link. Nodes are grouped by category below. --- URL: https://docs.tune.toys/nodes/addon.dictionary.define/ Title: Dictionary definition Dictionary definition looks up an English word in the free [dictionaryapi.dev](https://dictionaryapi.dev/) service. It needs no key. ## What it does The node is generated from the `dictionary` addon manifest. It requests `GET https://api.dictionaryapi.dev/api/v2/entries/en/` and runs it like a [Web request](https://docs.tune.toys/nodes/web.request/). `response` is a JSON list of entries; each has `word`, `phonetics` (text and, often, an audio URL), and `meanings` with a part of speech and `definitions`. A word the dictionary does not know answers HTTP 404 on `status` and `error`. The word comes from the `word` port when cabled, otherwise from the node's `word` field. With `trigger` cabled each trigger asks; otherwise a new word asks; with no cable at all it asks once at start. A Toy using this node requires the network and the `dictionary` addon, enabled in Settings → Addons. The service is free, without a key or service level; its definitions come from Wiktionary under CC BY-SA. ## When to use it Use it for word games and text pieces: look up a word a player types, then speak, show or sonify its definition. ## Ports | Port | Direction | Signal | Lanes | | ---------- | --------- | -------------- | ----- | | `trigger` | Input | Data (trigger) | 1 | | `word` | Input | Data (string) | 1 | | `response` | Output | Data (json) | 1 | | `status` | Output | Data (integer) | 1 | | `error` | Output | Data (string) | 1 | | `done` | Output | Data (trigger) | 1 | ## Parameters This node has no numeric parameters. - `word`: text. The only setting is the `word` text field (up to 100 characters), used while the `word` port has no cable. ## Tips - The first definition is at `/0/meanings/0/definitions/0/definition`; pick it with [Convert](https://docs.tune.toys/nodes/data.convert/). - Keep lookups occasional; the service is run for free by its author. ## See also [Convert](https://docs.tune.toys/nodes/data.convert/), [Value](https://docs.tune.toys/nodes/data.value/), [Console log](https://docs.tune.toys/nodes/log.console/), [Data cables](https://docs.tune.toys/concepts/data-cables/). --- URL: https://docs.tune.toys/nodes/addon.open-meteo.forecast/ Title: Open-Meteo forecast Open-Meteo forecast asks [Open-Meteo](https://open-meteo.com/) for the current weather and an hourly forecast at a latitude and longitude. It needs no key. ## What it does The node is generated from the `open-meteo` addon manifest. It builds a `GET https://api.open-meteo.com/v1/forecast` request from its parameters and runs it exactly like a [Web request](https://docs.tune.toys/nodes/web.request/): `response` is the parsed JSON, with `current` and `hourly` blocks holding the variables you asked for, plus `status`, `error` and `done`. With `trigger` cabled, each trigger asks once. Without it, a change on any cabled parameter port asks; with no cable at all, the node asks once when the Toy starts. Every parameter uses its cable when one is connected, otherwise the node's own setting. A Toy using this node requires the network and the `open-meteo` addon. Enable the addon in Settings → Addons: requests then go straight from your browser, which Open-Meteo allows, and fall back to the proxy when you are signed in. Open-Meteo is free for non-commercial use under 10 000 requests per day; credit "Weather data by Open-Meteo.com" (CC BY 4.0) where you show the data. During [replay](https://docs.tune.toys/concepts/takes/) the recorded responses are played back instead. ## When to use it Use it to make sound follow the weather: temperature into a delay time, wind speed into a filter, rain into density. Pick values with [Convert](https://docs.tune.toys/nodes/data.convert/) and bridge them with [Data to control](https://docs.tune.toys/nodes/data.to-control/). ## Ports | Port | Direction | Signal | Lanes | | --------------- | --------- | -------------- | ----- | | `trigger` | Input | Data (trigger) | 1 | | `latitude` | Input | Data (number) | 1 | | `longitude` | Input | Data (number) | 1 | | `current` | Input | Data (string) | 1 | | `hourly` | Input | Data (string) | 1 | | `forecast_days` | Input | Data (integer) | 1 | | `response` | Output | Data (json) | 1 | | `status` | Output | Data (integer) | 1 | | `error` | Output | Data (string) | 1 | | `done` | Output | Data (trigger) | 1 | ## Parameters | Parameter | Label | Range | Default | Unit | Scale | Notes | | --------------- | ------------- | ----------- | ------- | ---- | ------ | ------------------------------------------------------------------- | | `latitude` | Latitude | -90 to 90 | 52.52 | ° | linear | Degrees north; negative is south. Used while the port has no cable. | | `longitude` | Longitude | -180 to 180 | 13.41 | ° | linear | Degrees east; negative is west. Used while the port has no cable. | | `forecast_days` | Forecast days | 1 to 16 | 1 | | linear | Days of hourly forecast, 1 to 16. Used while the port has no cable. | - `current`: text. - `hourly`: text. ### Latitude (`latitude`) and Longitude (`longitude`) The place, in degrees: north and east are positive. The defaults are Berlin (52.52, 13.41). ### Forecast days (`forecast_days`) How many days of hourly forecast to return, 1 to 16. ### Current and hourly The `current` and `hourly` text fields list variables, comma separated, such as `temperature_2m,wind_speed_10m,precipitation`. `current` defaults to temperature and wind speed; an empty `hourly` asks for no hourly forecast. The [Open-Meteo documentation](https://open-meteo.com/en/docs) lists every variable. ## Tips - Weather changes slowly. Refresh with a [Cron](https://docs.tune.toys/nodes/time.cron/) every 10 minutes or more, not every second. - Temperatures come in °C by default; the response's `current_units` says so. ## See also [Web request](https://docs.tune.toys/nodes/web.request/), [Convert](https://docs.tune.toys/nodes/data.convert/), [Data to control](https://docs.tune.toys/nodes/data.to-control/), [Data cables](https://docs.tune.toys/concepts/data-cables/). --- URL: https://docs.tune.toys/nodes/addon.randomuser.get/ Title: Random user Random user fetches made-up people from [randomuser.me](https://randomuser.me/): names, places, e-mail addresses and portrait pictures. It needs no key. The people are generated; none of them is real. ## What it does The node is generated from the `randomuser` addon manifest. It requests `GET https://randomuser.me/api/` with its parameters and runs it like a [Web request](https://docs.tune.toys/nodes/web.request/). `response` is JSON with a `results` list of people and an `info` block with the seed used. With `trigger` cabled each trigger asks; otherwise a change on a cabled parameter port does; with no cable at all it asks once at start. A Toy using this node requires the network and the `randomuser` addon, enabled in Settings → Addons. ## When to use it Use it for playful data: name the voices of a choir, place a sound on a map, or test a data chain with realistic-looking values. ## Ports | Port | Direction | Signal | Lanes | | ---------- | --------- | -------------- | ----- | | `trigger` | Input | Data (trigger) | 1 | | `results` | Input | Data (integer) | 1 | | `nat` | Input | Data (string) | 1 | | `seed` | Input | Data (string) | 1 | | `inc` | Input | Data (string) | 1 | | `response` | Output | Data (json) | 1 | | `status` | Output | Data (integer) | 1 | | `error` | Output | Data (string) | 1 | | `done` | Output | Data (trigger) | 1 | ## Parameters | Parameter | Label | Range | Default | Unit | Scale | Notes | | --------- | ------- | -------- | ------- | ---- | ------ | --------------------------------------------------------------- | | `results` | Results | 1 to 100 | 1 | | linear | People per request, 1 to 100. Used while the port has no cable. | - `nat`: text. - `seed`: text. - `inc`: text. ### Results (`results`) How many people one request returns, 1 to 100. ### Nationalities, seed and fields The `nat` field limits nationalities (comma-separated codes such as `us,gb,fr`); the `seed` field makes the same request return the same people; the `inc` field lists the fields to include, such as `name,location,picture`. Each is used while its port has no cable and left out when empty. ## Tips - A fixed `seed` makes a piece repeatable, like a seeded [Random](https://docs.tune.toys/nodes/data.random/). - Pick a first name with [Convert](https://docs.tune.toys/nodes/data.convert/) and path `/results/0/name/first`. ## See also [Random](https://docs.tune.toys/nodes/data.random/), [Convert](https://docs.tune.toys/nodes/data.convert/), [Console log](https://docs.tune.toys/nodes/log.console/), [Data cables](https://docs.tune.toys/concepts/data-cables/). --- URL: https://docs.tune.toys/nodes/addon.wikipedia.search/ Title: Wikipedia search Wikipedia search looks up pages on English Wikipedia by title and content. It needs no key. ## What it does The node is generated from the `wikipedia` addon manifest. It requests `GET https://en.wikipedia.org/w/rest.php/v1/search/page?q=…&limit=…` and runs it like a [Web request](https://docs.tune.toys/nodes/web.request/). `response` is JSON with a `pages` list; each page has `id`, `key`, `title`, `excerpt`, `description` and, when there is one, a `thumbnail`. The search terms come from the `q` port when cabled, otherwise from the node's `q` field. With `trigger` cabled each trigger searches; otherwise a change on a cabled port does; with no cable at all it searches once at start. A Toy using this node requires the network and the `wikipedia` addon, enabled in Settings → Addons. Text is CC BY-SA 4.0. ## When to use it Use it to find articles, then feed a page's `key` to [Wikipedia summary](https://docs.tune.toys/nodes/addon.wikipedia.summary/) or step through the results with [List](https://docs.tune.toys/nodes/data.list/). ## Ports | Port | Direction | Signal | Lanes | | ---------- | --------- | -------------- | ----- | | `trigger` | Input | Data (trigger) | 1 | | `q` | Input | Data (string) | 1 | | `limit` | Input | Data (integer) | 1 | | `response` | Output | Data (json) | 1 | | `status` | Output | Data (integer) | 1 | | `error` | Output | Data (string) | 1 | | `done` | Output | Data (trigger) | 1 | ## Parameters | Parameter | Label | Range | Default | Unit | Scale | Notes | | --------- | ----- | -------- | ------- | ---- | ------ | ---------------------------------------------------------------- | | `limit` | Limit | 1 to 100 | 10 | | linear | Most pages returned, 1 to 100. Used while the port has no cable. | - `q`: text. ### Limit (`limit`) The most pages returned, 1 to 100 (10 by default). ### Query The `q` text field holds the search terms (up to 300 characters), used while the `q` port has no cable. ## Tips - `excerpt` contains HTML highlight markup; use `title` or `description` for plain text. - Pick the first title with [Convert](https://docs.tune.toys/nodes/data.convert/) and path `/pages/0/title`. ## See also [Wikipedia summary](https://docs.tune.toys/nodes/addon.wikipedia.summary/), [Convert](https://docs.tune.toys/nodes/data.convert/), [List](https://docs.tune.toys/nodes/data.list/), [Data cables](https://docs.tune.toys/concepts/data-cables/). --- URL: https://docs.tune.toys/nodes/addon.wikipedia.summary/ Title: Wikipedia summary Wikipedia summary fetches the title, short description, lead extract and thumbnail of one English Wikipedia article. It needs no key. ## What it does The node is generated from the `wikipedia` addon manifest. It requests `GET https://en.wikipedia.org/api/rest_v1/page/summary/` and runs it like a [Web request](https://docs.tune.toys/nodes/web.request/). `response` is JSON with fields such as `title`, `description`, `extract` (plain text) and `thumbnail`. The article title comes from the `title` port when cabled, otherwise from the node's `title` field (spaces may be underscores). With `trigger` cabled each trigger asks; otherwise a new title asks; with no cable at all it asks once at start. An unknown article answers HTTP 404 on `status` and `error`. A Toy using this node requires the network and the `wikipedia` addon, enabled in Settings → Addons. Wikipedia text is CC BY-SA 4.0: credit Wikipedia and share alike when you reuse it, and follow the Wikimedia API usage guidelines. ## When to use it Use it to bring words into a piece: an extract read by a text widget, a description that sets the mood of an [LLM chat](https://docs.tune.toys/nodes/ai.chat/) prompt. ## Ports <!-- ports:start --> | Port | Direction | Signal | Lanes | | ---------- | --------- | -------------- | ----- | | `trigger` | Input | Data (trigger) | 1 | | `title` | Input | Data (string) | 1 | | `response` | Output | Data (json) | 1 | | `status` | Output | Data (integer) | 1 | | `error` | Output | Data (string) | 1 | | `done` | Output | Data (trigger) | 1 | <!-- ports:end --> ## Parameters <!-- parameters:start --> This node has no numeric parameters. <!-- parameters:end --> <!-- data:start --> - `title`: text. <!-- data:end --> The only setting is the `title` text field (up to 255 characters), used while the `title` port has no cable. ## Tips - Pick the text with [Convert](https://docs.tune.toys/nodes/data.convert/) and path `/extract`. - Use [Wikipedia search](https://docs.tune.toys/nodes/addon.wikipedia.search/) first when you do not know the exact title. ## See also [Wikipedia search](https://docs.tune.toys/nodes/addon.wikipedia.search/), [Convert](https://docs.tune.toys/nodes/data.convert/), [Console log](https://docs.tune.toys/nodes/log.console/), [Data cables](https://docs.tune.toys/concepts/data-cables/). --- URL: https://docs.tune.toys/nodes/ai.chat/ Title: LLM chat LLM chat sends a prompt to a language model with your own API key and sends the answer into the Patch as text or JSON. ## What it does With `trigger` cabled, each trigger asks once; otherwise a new `prompt` does. The request goes to the Tune.Toys completion endpoint (`POST /v1/ai/complete`), which reads your key from the vault slot named after the provider (`anthropic`, `openai` or `gemini`) and calls the provider on the server. The key never reaches the browser or the Toy, and the platform's own key is never used for this node. Usage is billed to your key and shown on `usage` as tokens, model and provider. The node's `system` field (up to 8 KiB) sets the instructions. `context` adds either a list of `{ role, content }` messages as conversation history or any JSON as extra context. With Format JSON the model is asked for one JSON value, which arrives parsed on `json`; `text` always carries the raw answer, and `bytes` carries a `data:` base64 payload if the answer contains one. A Toy with LLM chat requires the network and the provider's vault slot; the Play surface shows a chip until both are ready. During [replay](https://docs.tune.toys/concepts/takes/) the model is not asked again: the recorded answers are played back, so a take plays without a key. ## When to use it Use it to generate material: a melody as JSON for [Data to MIDI](https://docs.tune.toys/nodes/midi.from-data/), words for a display, settings from a description. Collect inputs with [Merge](https://docs.tune.toys/nodes/data.merge/) to give it context. ## Ports <!-- ports:start --> | Port | Direction | Signal | Lanes | | --------- | --------- | -------------- | ----- | | `prompt` | Input | Data (json) | 1 | | `trigger` | Input | Data (trigger) | 1 | | `context` | Input | Data (json) | 1 | | `text` | Output | Data (string) | 1 | | `json` | Output | Data (json) | 1 | | `bytes` | Output | Data (bytes) | 1 | | `usage` | Output | Data (json) | 1 | | `error` | Output | Data (string) | 1 | <!-- ports:end --> ## Parameters <!-- parameters:start --> | Parameter | Label | Range | Default | Unit | Scale | Notes | | ------------- | ----------- | --------- | ------- | ---- | ------ | ------------------------------------------------------------------------------------------------------------- | | `provider` | Provider | 0 to 2 | 0 | | linear | Model provider; its key comes from the vault slot of the same name. Choices: 0 Anthropic, 1 OpenAI, 2 Gemini. | | `format` | Format | 0 to 1 | 0 | | linear | Ask for plain text or a JSON value. Choices: 0 Text, 1 JSON. | | `max_tokens` | Max tokens | 1 to 8192 | 1024 | | linear | Longest response in tokens. | | `temperature` | Temperature | 0 to 2 | 1 | | linear | Higher values make answers more varied. | <!-- parameters:end --> <!-- data:start --> - `model`: text. - `system`: text. <!-- data:end --> ### Provider (`provider`) Anthropic, OpenAI or Gemini. Its key comes from the vault slot of the same name. The node's `model` field picks a model; it defaults to the first model the addon manifest pins and accepts any other model id. ### Format (`format`) Text, or JSON to ask for a single JSON value on `json`. ### Max tokens (`max_tokens`) The longest answer, 1 to 8 192 tokens. Longer answers cost more. ### Temperature (`temperature`) 0 to 2. Low values give steadier answers; higher values more varied ones. ## Tips - Ask for exactly the JSON shape you want, and test it with a [Console log](https://docs.tune.toys/nodes/log.console/) on `text` and `error`. - Record a [take](https://docs.tune.toys/concepts/takes/) of a good answer: it replays anywhere without a key. ## See also [Data to MIDI](https://docs.tune.toys/nodes/midi.from-data/), [Merge](https://docs.tune.toys/nodes/data.merge/), [Secrets and the vault](https://docs.tune.toys/concepts/secrets/), [Data cables](https://docs.tune.toys/concepts/data-cables/). --- URL: https://docs.tune.toys/nodes/bus.receive/ Title: Receive Receive is the other end of a wireless cable. Name the same bus as a Send in this definition and its signal appears at the Receive's output. ## What it does A Receive has no parameters. Its single output takes the type and lane count of the signal connected to the matching Send, so it can carry a mono tone, a stereo bundle or, once those domains exist, video, control and MIDI. The compiler lowers each Receive to a direct connection from the Send's driver, so nothing runs at playback. Several Receives may listen to one bus; each is an independent pick-up point and none of them sum. ## When to use it Use it wherever you would otherwise drag a long cable: feeding the same source into several effect chains, bringing a shared modulation or bed into a distant corner of the patch, or picking up a bus to meter it. ## Ports <!-- ports:start --> | Port | Direction | Signal | Lanes | | -------- | --------- | --------------------- | ----------------------- | | `signal` | Output | Any (follows the bus) | Inferred from the cable | <!-- ports:end --> ## Parameters <!-- parameters:start --> This node has no numeric parameters. <!-- parameters:end --> ## Tips - A named Receive whose bus has no Send in this definition is rejected; an unnamed Receive, or one whose Send is unconnected, stays silent. - To mix two Receives, feed them into a [Mixer](https://docs.tune.toys/nodes/std.mixer/); the mix is explicit so the Mix drawer can show it. - Use [Select channels](https://docs.tune.toys/nodes/std.select/) after a Receive to take one lane of a wide bus. ## See also [Send](https://docs.tune.toys/nodes/bus.send/), [Mixer](https://docs.tune.toys/nodes/std.mixer/), [Select channels](https://docs.tune.toys/nodes/std.select/), [Cables](https://docs.tune.toys/concepts/cables/). --- URL: https://docs.tune.toys/nodes/bus.send/ Title: Send Send is the start of a wireless cable. Give it a bus name and connect a signal; any Receive with the same name picks that signal up without a cable crossing the patch. ## What it does The compiler replaces every Send and Receive pair with a direct connection before anything plays, so a bus costs nothing at playback. A bus belongs to the definition it is drawn in: one Send per bus name, any number of Receives. The Send has no parameters and no output; its type and width are whatever you connect to it, and every Receive inherits them. ## When to use it Use it when a cable would cross half the patch, when several distant places need the same signal, or to feed the Mix drawer's buses by name. A group that wants to publish a bus exposes it through an output port instead; buses never leak out of their definition, so a group used twice keeps two independent buses. ## Ports <!-- ports:start --> | Port | Direction | Signal | Lanes | | -------- | --------- | --------------------- | ----------------------- | | `signal` | Input | Any (follows the bus) | Inferred from the cable | <!-- ports:end --> ## Parameters <!-- parameters:start --> This node has no numeric parameters. <!-- parameters:end --> ## Tips - Two Sends with one name are rejected. To sum signals, run them into a [Mixer](https://docs.tune.toys/nodes/std.mixer/) first and Send the mix. - A Send with nothing connected, or no bus name yet, leaves its Receives silent; the editor draws them as unresolved. - Buses do not create feedback: a Receive that feeds its own Send is a cycle and is rejected like any other. ## See also [Receive](https://docs.tune.toys/nodes/bus.receive/), [Mixer](https://docs.tune.toys/nodes/std.mixer/), [Combine channels](https://docs.tune.toys/nodes/std.combine/), [Cables](https://docs.tune.toys/concepts/cables/). --- URL: https://docs.tune.toys/nodes/category/ai/ Title: AI nodes - LLM chat (ai.chat): Ask Anthropic, OpenAI or Gemini with your own vault key through the completion endpoint; text or JSON out. --- URL: https://docs.tune.toys/nodes/category/bridge/ Title: Bridge nodes - Data to control (data.to-control): Turn numbers into one control lane for a parameter or control input, mapped from a declared range. - Data to MIDI (midi.from-data): Play a JSON list of notes in beats as MIDI on each trigger, scheduled on the transport. --- URL: https://docs.tune.toys/nodes/category/control/ Title: Control nodes - Band energies (ctl.bands): Split audio into log-spaced bands and follow each one's energy as a control lane: the audio-to-visual bridge. - Clock (ctl.clock): Bar, beat, eighth, sixteenth and triplet pulses plus the phase within the beat on six lanes, locked to the transport or at its own tempo. - Combine lanes (ctl.combine): Concatenate two control bundles, preserving every lane without mixing. - Constant (ctl.const): A fixed control vector of up to eight lanes, set in the inspector. The number box of the control domain. - Control envelope (ctl.envelope): A control-rate ADSR, AD or AR envelope on a gate lane, 0 to 1, with velocity scaling and a done event. - Control LFO (ctl.lfo): A free-running or tempo-synced low-frequency oscillator on a control lane, 0 to 1, with depth, offset, phase, skew, random shapes and a reset input. - Counter (ctl.counter): Count rising edges of a trigger lane, with step, limit, wrap and reset. Lane 1 is the normalised count, lane 2 the raw count. - Curve (ctl.curve): A drawn breakpoint curve on a control lane: shape the input or Position through it, or loop it at a rate, mapped onto Min to Max. - Envelope follower (ctl.follow): Track the level of an audio bundle as one control lane per channel, with attack and release. - Random (ctl.random): A seeded random control lane: steps, glides or walks to a new value on each trigger, or free-running at a rate or tempo division. - Sample and hold (ctl.hold): Capture the input lanes whenever the trigger lane rises and hold them until the next trigger. - Scale (ctl.scale): Map a control range onto another with a curve, clamp and inversion. Depth and offset for any modulation. - Select lanes (ctl.select): Extract a contiguous lane range from a control bundle. Use several to split a sensor's lanes. - Smooth (ctl.smooth): Slow a control lane down with separate rise and fall times so jumpy sensors and buttons stop clicking. --- URL: https://docs.tune.toys/nodes/category/data/ Title: Data nodes - Compare (data.compare): Compare two data values as numbers, text or lists and send true or false: ordering, equality, contains, starts or ends with, pattern match, empty, in list. - Convert (data.convert): Convert a value to another datatype, optionally picking a field or formatting a string; failures go to error. - Dataset (data.set): Read CSV, TSV, JSON or NDJSON from an asset, a host-chosen file or a connection as rows, JSON and text. - List (data.list): List operations on data values: get, push, insert, remove, slice, sort, unique, flatten, join, range, sum, minimum, maximum, mean, shuffle and more. - Math (data.math): Arithmetic on data values, with the same operations as signal Math. Numbers in, numbers out; lists are worked element by element. - Merge (data.merge): Combine up to eight inputs into one JSON object keyed by port, when any or all have arrived. - Random (data.random): Make a random number, integer, boolean, string, colour or choice on each trigger; seeded when a seed is wired. - String (data.string): Text operations on data values: length, case, trim, split, replace, slice, pad, repeat, search, regular expressions, format and JSON. - Value (data.value): A literal source, the data domain's number box and message box. --- URL: https://docs.tune.toys/nodes/category/debug/ Title: Debug nodes - Console log (log.console): Write each incoming value to the Console panel with a level and label, rate-limited. --- URL: https://docs.tune.toys/nodes/category/dynamics/ Title: Dynamics nodes - Compressor (std.compressor): Peak/RMS compression with a sidechain key input, detector filtering, hold, adaptive release and channel linking. - Envelope (std.envelope): Curved AD, AR and ADSR gain envelopes with looping and explicit retrigger behavior. - Gate (std.gate): Noise gate and downward expander with a sidechain key input, range, hold, lookahead, detector filtering and channel linking. - Limiter (std.limiter): Linked or independent sample-peak limiting with hold and adaptive release. - Multiband compressor (std.multiband): Three-band compressor on a flat-summing crossover: per-band threshold, ratio, attack, release, makeup and solo, then an output trim. - Noise reduction (std.denoise): Spectral noise gate: learn a noise profile or follow the floor adaptively, then turn down each frequency bin that stays near it. - Resonance suppressor (std.resonance): Spectral resonance suppressor: finds narrow peaks that stand above the local spectral average and pulls them down dynamically, bin by bin. - Transient shaper (std.transient): Boosts or softens the attack and sustain of each hit, independent of level, with a floor, mix and output trim. --- URL: https://docs.tune.toys/nodes/category/effect/ Title: Effect nodes - Bitcrusher (std.crush): Reduces bit depth and sample rate: 2^bits levels, sample-and-hold with jitter, optional triangular dither. - Chorus (std.chorus): Prepared multi-voice chorus, ensemble and vibrato over arbitrary channel bundles. - Convolver (std.convolver): Convolution with an attached WAV impulse response: real rooms, speaker cabinets and creative textures, with predelay, trims and gain. - Delay (std.delay): Tempo-aware, smoothly retimed echoes with colored, ducked and explicit multichannel feedback. - Drive (std.drive): Four nonlinear shapers with DC removal, tone and latency-aligned filtered oversampling. - Granulator (std.granulator): Replays windowed grains of the live input: up to 32 at once, scheduled from the node path, with freeze, reverse and pitch. - Harmonizer (std.harmonizer): Adds up to three pitch-shifted voices to the input, each with its own interval, level and pan. - Neural amp (std.nam): Play a bounded NAM A1/A2 WaveNet capture with shared DSP, verified model assets and explicit dry fallback when a model is missing. - Phaser (std.phaser): Swept allpass notches: 2 to 12 stages, a free or transport-locked sweep, signed feedback and channel spread. - Pitch correction (std.autotune): Detects the pitch of a monophonic input and pulls it to the nearest note of the Toy tuning, a scale or held MIDI notes. - Pitch shifter (std.pitch): Shifts audio up or down by up to two octaves with a splicing delay line, with fine cents, feedback and a dry/wet mix. - Reverb (std.reverb): An eight-line modulated feedback-delay network with room, hall and plate-like tunings. - Vocoder (std.vocoder): Channel vocoder: a modulator on Key imposes its band envelopes on the carrier through 8 to 32 band-pass filters, with formant shift and sibilance. --- URL: https://docs.tune.toys/nodes/category/filter/ Title: Filter nodes - Crossover (std.crossover): Splits audio into 2–4 Linkwitz-Riley bands, output as one band-major bundle whose bands sum back flat. - Feedback suppressor (std.suppressor): Feedback suppressor: tracks howling tones and places up to eight narrow time-domain notches on them, with no added latency. - Filter (std.svf): Low-pass, high-pass, band-pass, notch, all-pass or morphing filter with resonance, 12/24 dB slopes, curve editing and output RTA. - Low-pass filter (std.lowpass): A gentle one-pole filter for warmer edges. - Parametric EQ (std.eq): Parametric EQ with up to 32 reorderable RBJ bands: cuts, shelves, bells and notches, per-band bypass and output trim. --- URL: https://docs.tune.toys/nodes/category/generator/ Title: Generator nodes - Polyphonic instrument (std.poly): Eight owned, velocity-sensitive oscillator voices with ADSR, sustain and MPE, played from a MIDI cable in the Toy's tuning. --- URL: https://docs.tune.toys/nodes/category/media/ Title: Media nodes - Media player (media.player): Plays a video asset or an open stream URL (MP4, WebM; HLS where the browser plays it natively) as one video stream with play, loop and rate controls. - Recorder (media.recorder): Record a video stream to a file while Record is on. Audio joins when external audio buses land. - Text to Speech (media.speech): Generate speech with ElevenLabs using your own vault key, and play the audio through the patch. --- URL: https://docs.tune.toys/nodes/category/midi/ Title: MIDI nodes - Arpeggiator (midi.arp): Play held notes one at a time, locked to the transport: up, down, up-down, down-up, random, as played or as chords, over 1–4 octaves, with latch. - Control to MIDI (midi.from-control): Send control lanes as MIDI controllers: lane 1 to the first CC number, lane 2 to the next, one message per change. - Control to notes (midi.notes): Turn control lanes (pitch, gate and velocity) into MIDI notes, the inverse of MIDI to control, so control sources and sensors can play instruments. - Euclidean rhythm (midi.euclid): Spread pulses as evenly as possible over a cycle of steps, locked to the transport, and play each as a note, with rotation, swing and accent. - Keyboard (midi.keyboard): Notes from the on-screen piano and computer typing become MIDI. The face of the piano widget. - MIDI channel (midi.channel): Pass every channel, keep one channel, or move everything onto one channel. - MIDI in (midi.in): A logical MIDI input port. The host binds it to a device or the DAW's MIDI bus; the Toy never stores a device. - MIDI merge (midi.merge): Interleave up to four MIDI streams by time, in input order for equal offsets. - MIDI out (midi.out): A logical MIDI output port. The host binds it to a device or the DAW; messages leave at their sample offsets. - MIDI to control (midi.to-control): Turn MIDI into six control lanes: pitch, gate, velocity, bend, pressure and one CC, with last-note priority. - Step sequencer (midi.step): Up to 64 steps of notes, rests and ties locked to the transport, with per-step pitch, velocity and probability, swing and four directions. - Transpose (midi.transpose): Shift note numbers by semitones and octaves, clamped to the MIDI range. - Velocity (midi.velocity): Scale, offset, floor or fix note-on velocities. --- URL: https://docs.tune.toys/nodes/category/sensing/ Title: Sensing nodes - Body (sense.body): Track one body's pose into control lanes: torso position, hand positions, arm spread, lean, crouch and presence. - Face (sense.face): Track one face into control lanes: position, head pose, gaze, mouth, smile, blinks, brows and presence. - Gestures (sense.gesture): Recognise seven hand gestures into one gate lane each, with confidence and presence. - Hands (sense.hands): Track up to two hands from a video stream into control lanes: position, depth, pinch, spread, openness and presence per hand. - Motion (sense.motion): The device's accelerometer and gyroscope as control lanes: acceleration, rotation rate, tilt, heading and a shake gate. --- URL: https://docs.tune.toys/nodes/category/source/ Title: Source nodes - Audio player (std.player): Plays an attached WAV asset as one stream for loops, beds and backing tracks, following the transport or running free. - Noise (std.noise): Thirteen streaming noise colors and textures with independent channel and instance state. - Oscillator (std.oscillator): Selected waveform kernels, continuous morphing, harmonic shapes and phase/gate control; an optional MIDI input plays notes in the Toy's tuning. - Sampler (std.sampler): Plays an attached WAV asset as an instrument of up to eight voices in the Toy's tuning, with a playback region, a sustain loop and ADSR. - Wavetable (std.wavetable): Plays a drawn or preset table of up to eight 2048-sample frames with position morphing, unison and MIDI pitch. --- URL: https://docs.tune.toys/nodes/category/time/ Title: Time nodes - Cron (time.cron): Fire a trigger on an interval, a cron expression or musical beats, with the time as JSON. --- URL: https://docs.tune.toys/nodes/category/utility/ Title: Utility nodes - Channel matrix (std.matrix): Route and mix input channels into a declared output bundle with explicit gains. - Combine channels (std.combine): Concatenate two audio buses, preserving every channel without mixing. - Compare (std.compare): Turn any signal into a gate or trigger by comparing it to a threshold or to another signal, lane by lane, with hysteresis and tolerance. - Crossfade (std.crossfade): Blend two signals with one balance control: 0 plays A, 1 plays B. - Gain (std.gain): A clean multichannel gain stage. - Math (std.math): Arithmetic on any signal, lane by lane: add, multiply, divide, power and more on audio or control operands. The output runs at the rate of its fastest input. - Mixer (std.mixer): Sum up to 32 channels, each with gain, pan, mute and solo, into one mix with a master level. Plug a cable in to add a channel. - Receive (bus.receive): Pick up a named bus without a cable. Its type and width follow whatever drives the Send. - Select channels (std.select): Extract a contiguous channel range. Use several selectors to split a bundle. - Send (bus.send): Name a bus and send a signal to it. Every Receive with the same name in this definition hears it. - Tuner (std.tuner): Passes audio through unchanged and shows the nearest degree of a microtonal tuning on its face. Mute silences it like a tuner pedal. --- URL: https://docs.tune.toys/nodes/category/video/ Title: Video nodes - Blur (video.blur): Gaussian blur with a bounded radius at the source resolution. - Camera (video.camera): A camera feed as one video stream. The Toy names a logical camera; the host binds it to a device after the person allows it. - Chroma key (video.chroma-key): Turn one colour transparent with tolerance, softness and spill suppression: the green screen. - Color (video.color): Adjust brightness, contrast, saturation, hue, gamma and warmth, or invert the picture. - Combine streams (video.combine): Concatenate two video bundles into one, preserving every stream. - Composite (video.composite): Stack up to four layers over a base at the base's size with blend mode, opacity, position and scale per layer. - Image (video.image): A declared image asset as a still video stream, for logos, backgrounds and keys. - Multiview (video.multiview): Tile a bundle of streams into one picture with labels, for monitoring. - Paint surface (video.paint): A transparent surface the paint widget draws on, as one video stream to composite over anything. - Pixelate (video.pixelate): Mosaic cells and posterised levels for a chunky look. - Screen capture (video.display): A window, tab or whole display as one video stream, chosen by the person each time it starts. - Select streams (video.select): Extract a contiguous range of streams from a video bundle. - Snapshot (video.snapshot): Capture the newest frame as a still when the shutter fires; the host saves it. - Switcher (video.switch): Cut or transition between up to four sources; outputs the program. The face of the switcher widget. - Text layer (video.text): Renders a text template as a transparent video stream; {1} to {8} in the template show control lanes. Scoreboards and lower thirds. - Transform (video.transform): Move, scale, rotate, crop and mirror a stream, and set the output size. Also the resize node. - Video crossfade (video.crossfade): Blend two streams with one mix control. --- URL: https://docs.tune.toys/nodes/category/web/ Title: Web nodes - Dictionary definition (addon.dictionary.define): Meanings and phonetics of an English word. - Open-Meteo forecast (addon.open-meteo.forecast): Current weather and forecasts for a place, no key. - Random user (addon.randomuser.get): Generated placeholder people with names and pictures. - Web frame (web.frame): A sandboxed web page shown by a web widget, exchanging messages only with its declared origin. - Web request (web.request): Fetch a URL on each trigger under the network allowlist; secrets come from a vault slot, never the Toy. - Web scrape (web.scrape): Fetch a page through the proxy and extract text, rows or links with a CSS selector; scripts never run. - WebSocket (web.socket): Keep a WebSocket open to an allowlisted host, sending and receiving messages with optional reconnect. - Wikipedia search (addon.wikipedia.search): Search English Wikipedia for matching pages. - Wikipedia summary (addon.wikipedia.summary): Lead extract of an English Wikipedia article. --- URL: https://docs.tune.toys/nodes/ctl.bands/ Title: Band energies Band energies splits audio into log-spaced bands and follows each band as a control lane. It is the audio-to-visual bridge. ## What it does Each output lane is the level of one frequency band. Bands are spaced evenly on a logarithmic scale between Lowest band and Highest band, which are the centres of the first and last band. Attack and Release shape how fast each band rises and falls, and Gain boosts the audio before the followers so quiet sources show clearly. ## When to use it Use it to make controls dance to music: one lane per band, ready to cable into a scale or smooth node. For a single overall level use [Envelope follower](https://docs.tune.toys/nodes/ctl.follow/). ## Ports <!-- ports:start --> | Port | Direction | Signal | Lanes | | --------- | --------- | ------- | ----------------------- | | `audio` | Input | Audio | Inferred from the cable | | `control` | Output | Control | Inferred from the cable | <!-- ports:end --> ## Parameters <!-- parameters:start --> | Parameter | Label | Range | Default | Unit | Scale | Notes | | ------------ | ------------ | ------------- | ------- | ---- | ------ | ----------------------------------- | | `attack_ms` | Attack | 0 to 2000 | 15 | ms | linear | How fast each band's level rises. | | `release_ms` | Release | 0 to 5000 | 250 | ms | linear | How fast each band's level falls. | | `low_hz` | Lowest band | 20 to 2000 | 60 | Hz | log | Centre of the first band. | | `high_hz` | Highest band | 1000 to 20000 | 12000 | Hz | log | Centre of the last band. | | `gain_db` | Gain | -24 to 48 | 12 | dB | linear | Boost applied before the followers. | <!-- parameters:end --> ### Attack (`attack_ms`) and Release (`release_ms`) How fast each band's level rises (0 to 2000 ms) and falls (0 to 5000 ms). ### Lowest band (`low_hz`) and Highest band (`high_hz`) The centres of the first and last bands, from 20 to 2000 Hz and from 1000 to 20000 Hz, both on logarithmic controls. ### Gain (`gain_db`) Boost from -24 to 48 dB, applied before the followers. ## Tips - Narrow the span between Lowest band and Highest band to look closely at part of the spectrum. - Use [Smooth](https://docs.tune.toys/nodes/ctl.smooth/) afterwards if lanes flicker too much. ## See also [Envelope follower](https://docs.tune.toys/nodes/ctl.follow/), [Scale](https://docs.tune.toys/nodes/ctl.scale/), [Select lanes](https://docs.tune.toys/nodes/ctl.select/), [Cables](https://docs.tune.toys/concepts/cables/). --- URL: https://docs.tune.toys/nodes/ctl.clock/ Title: Clock Clock turns the shared transport into pulses you can cable: one lane for each bar, beat, eighth, sixteenth and eighth-note triplet, plus a sixth lane with the position inside the current beat. Use it to restart LFOs on the bar, count beats, or step anything that takes a trigger. ## What it does The six output lanes are `[bar, beat, eighth, sixteenth, triplet, phase]`. Each of the first five is 1 for exactly one block when its boundary falls inside that block, and 0 otherwise. A bar is four clock beats. The `phase` lane is the position within the clock beat, 0 to 1, at the start of the block. With Source on Transport, the clock reads the transport's beat, so it pulses only while the transport rolls and lands on the same blocks on every replay and offline render. With Source on Free, it counts its own Tempo instead. Multiply sets how many clock beats fit in one transport beat (or one Tempo beat in Free mode). Control runs once per audio block, so a pulse can arrive up to one block after its exact time: about 2.7 ms at 128 frames and 48 kHz. Sequencers that need sample-exact notes, such as [Step sequencer](https://docs.tune.toys/nodes/midi.step/), read the transport directly rather than this clock. ### Inputs `run` gates the clock: unconnected, it runs while the transport rolls; cabled, it runs while the lane is at least 0.5. A rising edge on `reset` restarts the bar at that block, so every lane pulses there and the phase starts again from 0. Pick a single lane of another node's bundle with [Select lanes](https://docs.tune.toys/nodes/ctl.select/) to cable it into either input. ## When to use it Use Clock when something should happen in time with the music but is not a note: reset a [Control LFO](https://docs.tune.toys/nodes/ctl.lfo/) on every bar, advance a [Counter](https://docs.tune.toys/nodes/ctl.counter/) on each beat, or retrigger an envelope on the sixteenths. To play notes, use a sequencer instead. ## Ports <!-- ports:start --> | Port | Direction | Signal | Lanes | | --------- | --------- | ------- | ----- | | `run` | Input | Control | 1 | | `reset` | Input | Control | 1 | | `control` | Output | Control | 6 | <!-- ports:end --> ## Parameters <!-- parameters:start --> | Parameter | Label | Range | Default | Unit | Scale | Notes | | ---------- | -------- | --------- | ------- | ---- | ------ | ---------------------------------------------------------------------------------------------------------------------------------- | | `source` | Source | 0 to 1 | 0 | | linear | Transport follows the shared transport's beat and runs while it rolls; Free counts at its own Tempo. Choices: 0 Transport, 1 Free. | | `bpm` | Tempo | 20 to 300 | 120 | | linear | Beats per minute in Free mode. Shown when `source` is 1. | | `swing` | Swing | 0 to 0.75 | 0 | | linear | Delay of every second eighth and sixteenth pulse, as a fraction of its spacing: about 0.33 is a triplet feel. | | `multiply` | Multiply | 0 to 4 | 2 | | linear | Clock beats per transport beat: ×2 doubles every pulse rate, ×½ halves it. Choices: 0 ×¼, 1 ×½, 2 ×1, 3 ×2, 4 ×4. | <!-- parameters:end --> ### Source (`source`) Transport or Free. Transport follows the shared transport's beat and tempo; Free counts its own Tempo from where it is, and its `reset` input restarts it from zero. ### Tempo (`bpm`) Beats per minute in Free mode, 20 to 300. Hidden while Source is Transport, which uses the transport's tempo. ### Swing (`swing`) Delays every second eighth and every second sixteenth by a fraction of its spacing, 0 to 0.75. About 0.33 gives a triplet feel and 0.5 a dotted one. Bars, beats and triplets are never swung. ### Multiply (`multiply`) Clock beats per transport beat: ×¼, ×½, ×1, ×2 or ×4. At ×2 every lane runs twice as fast and a bar lasts two transport beats. ## Tips - Cable the bar lane (lane 1, through Select lanes) into a Control LFO's `reset` so the LFO starts every bar at its Phase. - The `phase` lane is a beat-long ramp: scale it to sweep something once per beat. - In Free mode with nothing cabled to `run`, the clock still waits for the transport to roll; cable a constant 1 into `run` to keep it running. ## See also [Counter](https://docs.tune.toys/nodes/ctl.counter/), [Select lanes](https://docs.tune.toys/nodes/ctl.select/), [Control LFO](https://docs.tune.toys/nodes/ctl.lfo/), [Step sequencer](https://docs.tune.toys/nodes/midi.step/), [Control lanes](https://docs.tune.toys/concepts/control-lanes/). --- URL: https://docs.tune.toys/nodes/ctl.combine/ Title: Combine lanes Combine lanes concatenates two control bundles into one, preserving every lane without mixing. ## What it does Input A's lanes come first, followed by input B's lanes, so a two-lane A and a three-lane B give five lanes. Nothing is added or averaged. The node has no parameters. It is the control-domain counterpart of [Combine channels](https://docs.tune.toys/nodes/std.combine/). ## When to use it Use it to gather several single-lane sources into one bundle that travels on one cable, or to join groups of lanes. To add or multiply two signals lane by lane use [Math](https://docs.tune.toys/nodes/std.math/). ## Ports <!-- ports:start --> | Port | Direction | Signal | Lanes | | --------- | --------- | ------- | ----------------------- | | `a` | Input | Control | Inferred from the cable | | `b` | Input | Control | Inferred from the cable | | `control` | Output | Control | Inferred from the cable | <!-- ports:end --> ## Parameters <!-- parameters:start --> This node has no numeric parameters. <!-- parameters:end --> ## Tips - Chain two to join three or more bundles. - The output lane count is the sum of the two inputs. ## See also [Select lanes](https://docs.tune.toys/nodes/ctl.select/), [Math](https://docs.tune.toys/nodes/std.math/), [Combine channels](https://docs.tune.toys/nodes/std.combine/), [Cables](https://docs.tune.toys/concepts/cables/). --- URL: https://docs.tune.toys/nodes/ctl.const/ Title: Constant Constant outputs a fixed control vector of up to eight lanes. It is the number box of the control domain. ## What it does Each lane has its own value from 0 to 1, set in the inspector or from a Frame control. Lane 1 takes Value 1, lane 2 takes Value 2 and so on, up to eight. A lane only exists when the node has at least that many lanes, so values beyond the node's lane count do nothing. The output never changes unless a control is moved. ## When to use it Use it as a fixed operand for [Math](https://docs.tune.toys/nodes/std.math/), as a base value to scale, or as a source that you can drive from a Frame control and cable into the control graph. ## Ports <!-- ports:start --> | Port | Direction | Signal | Lanes | | --------- | --------- | ------- | ----------------------- | | `control` | Output | Control | Inferred from the cable | <!-- ports:end --> ## Parameters <!-- parameters:start --> | Parameter | Label | Range | Default | Unit | Scale | Notes | | --------- | ------- | ------ | ------- | ---- | ------ | -------------------------------------------------------- | | `value1` | Value 1 | 0 to 1 | 0 | | linear | Lane 1 of the output when the node has at least 1 lanes. | | `value2` | Value 2 | 0 to 1 | 0 | | linear | Lane 2 of the output when the node has at least 2 lanes. | | `value3` | Value 3 | 0 to 1 | 0 | | linear | Lane 3 of the output when the node has at least 3 lanes. | | `value4` | Value 4 | 0 to 1 | 0 | | linear | Lane 4 of the output when the node has at least 4 lanes. | | `value5` | Value 5 | 0 to 1 | 0 | | linear | Lane 5 of the output when the node has at least 5 lanes. | | `value6` | Value 6 | 0 to 1 | 0 | | linear | Lane 6 of the output when the node has at least 6 lanes. | | `value7` | Value 7 | 0 to 1 | 0 | | linear | Lane 7 of the output when the node has at least 7 lanes. | | `value8` | Value 8 | 0 to 1 | 0 | | linear | Lane 8 of the output when the node has at least 8 lanes. | <!-- parameters:end --> ### Values (`value1` to `value8`) Lane 1 to lane 8 of the output: `value1`, `value2`, `value3`, `value4`, `value5`, `value6`, `value7` and `value8`. Each runs from 0 to 1 and defaults to 0. Only the lanes the node actually has are output. ## Tips - Use several Constants, or one with more lanes, to hold a set of related values, then pull lanes apart with [Select lanes](https://docs.tune.toys/nodes/ctl.select/). - For values outside 0 to 1, follow the Constant with [Scale](https://docs.tune.toys/nodes/ctl.scale/). ## See also [Scale](https://docs.tune.toys/nodes/ctl.scale/), [Math](https://docs.tune.toys/nodes/std.math/), [Combine lanes](https://docs.tune.toys/nodes/ctl.combine/), [Cables](https://docs.tune.toys/concepts/cables/). --- URL: https://docs.tune.toys/nodes/ctl.counter/ Title: Counter Counter counts the rising edges of a trigger lane, with a step, a limit, wrapping and a reset input. ## What it does Each rising edge on trigger adds Step to the count; a negative Step counts down. The count is kept between 0 and Limit: with Clamp it stops at the limit, with Wrap it returns to 0 once it passes it. A rising edge on reset returns the count to its start. The output has two lanes: lane 1 is the count divided by Limit, from 0 to 1, and lane 2 is the raw count. ### Events The `wrap` outlet sends an event: it is 1 for one block whenever a step carries the count past Limit or below 0, whether Wrap brings it back to 0 or Clamp holds it at the edge, and 0 otherwise. A reset never counts as a wrap. Cable it into a trigger input, such as another Counter or [Sample and hold](https://docs.tune.toys/nodes/ctl.hold/), to do something once per lap. ## When to use it Use it for scoreboards, step counters and anything that advances on events. Feed it from [Compare](https://docs.tune.toys/nodes/std.compare/) in Trigger mode, or straight from a square [Control LFO](https://docs.tune.toys/nodes/ctl.lfo/) as a clock. Use lane 1 for a normalised position and lane 2 for the whole number. ## Ports <!-- ports:start --> | Port | Direction | Signal | Lanes | | --------- | --------- | ------- | ----- | | `trigger` | Input | Control | 1 | | `reset` | Input | Control | 1 | | `control` | Output | Control | 2 | | `wrap` | Output | Control | 1 | <!-- ports:end --> ## Parameters <!-- parameters:start --> | Parameter | Label | Range | Default | Unit | Scale | Notes | | --------- | ----- | --------- | ------- | ---- | ------ | ----------------------------------------------------------------------------------------- | | `limit` | Limit | 1 to 1000 | 10 | | linear | Count value that maps to 1 on the normalised lane and where wrapping or clamping happens. | | `step` | Step | -10 to 10 | 1 | | linear | Amount added on each trigger; negative counts down. | | `wrap` | Wrap | 0 to 1 | 0 | | linear | Wrap past the limit back to 0 instead of clamping. Choices: 0 Clamp, 1 Wrap. | <!-- parameters:end --> ### Limit (`limit`) The count that maps to 1 on the normalised lane, and where wrapping or clamping happens. 1 to 1000, whole numbers. ### Step (`step`) The amount added on each trigger, -10 to 10. Negative counts down. ### Wrap (`wrap`) Clamp holds the count at the limit; Wrap returns it to 0 instead. A discrete choice. ## Tips - Use [Select lanes](https://docs.tune.toys/nodes/ctl.select/) to pick the normalised or raw lane. - Both inputs are single lanes, so cable one lane into each. - `wrap` is a one-block pulse, like [Compare](https://docs.tune.toys/nodes/std.compare/) in Trigger mode: cable it into a trigger, not into a parameter that should glide. ## See also [Compare](https://docs.tune.toys/nodes/std.compare/), [Sample and hold](https://docs.tune.toys/nodes/ctl.hold/), [Control LFO](https://docs.tune.toys/nodes/ctl.lfo/), [Select lanes](https://docs.tune.toys/nodes/ctl.select/), [Cables](https://docs.tune.toys/concepts/cables/). --- URL: https://docs.tune.toys/nodes/ctl.curve/ Title: Curve Curve is a shape you draw: points joined by straight or bent segments, read as a control lane. It can bend another control signal through the shape, or play the shape on its own as a repeating sweep. ## What it does The curve lives in the node's `points` data: 2 to 32 points, each an x and a y between 0 and 1, plus a curvature between −1 and 1 for the segment to its right. Before the first point and after the last the curve holds flat; two points at the same x make a step. A curvature of 0 is a straight line, positive values start slow and finish fast, and negative values start fast and finish slow, using the same bend as [Envelope](https://docs.tune.toys/nodes/std.envelope/). A node without points plays a straight line from (0, 0) to (1, 1). In Shaper mode the curve is read at the control input, clamped to 0 to 1, or at Position when nothing is cabled. In Cycle mode it sweeps from 0 to 1 at Rate and starts again. Either way the curve's 0 to 1 is mapped onto Min to Max. Control runs once per audio block, so the curve is read once per block. ### Drawing the curve On the node card, drag a point to move it; it stays between its neighbours. Double-click empty space to add a point on the curve without changing its shape. With a point focused, the arrow keys move it (Shift for fine steps), Tab moves to the next point, Backspace or Delete removes it (the first and last points stay), and the mouse wheel or Page Up and Page Down bend the segment to its right. Escape during a drag restores the curve. The change plays while you draw, and each drag or key press is one edit, so one Undo reverts it. ### Events The `cycle` outlet is 1 for one block whenever a Cycle sweep starts again, and 0 otherwise, including in Shaper mode. The first block does not count. A sweep shorter than a block still reports one pulse per block, never more. Cable it into a trigger input, such as [Counter](https://docs.tune.toys/nodes/ctl.counter/), to count repeats. ## When to use it Use it when a straight range is not enough: a filter that opens slowly and then quickly, a volume that holds and drops, a stepped pattern. Put it after a [Control LFO](https://docs.tune.toys/nodes/ctl.lfo/) or a sensor to reshape its motion, or use Cycle mode as a drawn LFO. For a plain range change with one bend, [Scale](https://docs.tune.toys/nodes/ctl.scale/) is simpler. ## Ports <!-- ports:start --> | Port | Direction | Signal | Lanes | | --------- | --------- | ------- | ----- | | `control` | Input | Control | 1 | | `control` | Output | Control | 1 | | `cycle` | Output | Control | 1 | <!-- ports:end --> ## Parameters <!-- parameters:start --> | Parameter | Label | Range | Default | Unit | Scale | Notes | | ---------- | -------- | ---------- | ------- | ---- | ------ | ------------------------------------------------------------------------------------------------------------------------------------------------------- | | `mode` | Mode | 0 to 1 | 0 | | linear | Shaper reads the curve at the input, or at Position when nothing is cabled; Cycle sweeps it from 0 to 1 at Rate, repeating. Choices: 0 Shaper, 1 Cycle. | | `position` | Position | 0 to 1 | 0 | | linear | Where Shaper mode reads the curve while the control input is not cabled. Shown when `mode` is 0. | | `rate_hz` | Rate | 0.01 to 20 | 1 | Hz | log | Sweeps across the curve per second in Cycle mode. Shown when `mode` is 1. | | `min` | Min | -8 to 8 | 0 | | linear | Output where the curve is at the bottom of its box. | | `max` | Max | -8 to 8 | 1 | | linear | Output where the curve is at the top of its box. Below Min inverts the curve. | <!-- parameters:end --> <!-- data:start --> - `points`: floats. <!-- data:end --> ### Mode (`mode`) Shaper or Cycle. A discrete choice. Shaper reads the curve at its input; Cycle plays it as a repeating sweep and ignores the input. ### Position (`position`) Where Shaper mode reads the curve, 0 to 1, while the control input is not cabled. Turn it by hand to scan the shape. Hidden in Cycle mode. ### Rate (`rate_hz`) Sweeps per second in Cycle mode, 0.01 to 20 Hz on a logarithmic control. The sweep pauses where it is while the mode is Shaper. Hidden in Shaper mode. ### Min (`min`) The output where the curve is at the bottom of its box, −8 to 8. ### Max (`max`) The output where the curve is at the top of its box, −8 to 8. Set it below Min to turn the curve upside down. ## Tips - Leave Min at 0 and Max at 1 when the curve drives a parameter cable: the cable maps 0 to 1 across the parameter's whole range. - A falling curve in Cycle mode, from 1 down to 0, makes a drawn decay that repeats at Rate. - Add a point in the middle of a bent segment before reshaping it: the curve does not move until you drag. ## See also [Control LFO](https://docs.tune.toys/nodes/ctl.lfo/), [Scale](https://docs.tune.toys/nodes/ctl.scale/), [Envelope](https://docs.tune.toys/nodes/std.envelope/), [Cables](https://docs.tune.toys/concepts/cables/). --- URL: https://docs.tune.toys/nodes/ctl.envelope/ Title: Control envelope Control envelope turns a gate lane into a 0 to 1 control lane that rises, holds and falls like an envelope. It is the control-rate sibling of [Envelope](https://docs.tune.toys/nodes/std.envelope/): the same stages and curves, but the result is a lane you cable into any parameter instead of a gain applied to audio. ## What it does While the gate lane is at 0.5 or more it is held. A rising edge starts the attack; in ADSR the level then decays to Sustain and stays there until the gate falls, then Release returns it to zero. AD ignores the falling gate and runs attack then decay on its own; AR rises to full and holds until the gate falls. Curve bends every segment, Loop repeats whole cycles while the gate is held, and Retrigger chooses whether a new edge starts from the current level or from zero. The stage logic is the audio envelope's own, so the two agree. Velocity scales the peak from the gate lane's value at the rising edge: at 0 the gate value is ignored, at 1 a gate of 0.75 peaks at 0.75. Use the velocity lane of [MIDI to control](https://docs.tune.toys/nodes/midi.to-control/), or a gate multiplied by a level, to get velocity-sensitive envelopes. The gate must still reach 0.5 to open. ### Resolution Control runs once per audio block, and the output is the level at the block's last frame. Stage times are exact, but the lane steps about 2.7 ms at 128 frames and 48 kHz, so very short attacks look like a step. Cable the output into a parameter's cable input to let that parameter smooth it. ### Events The `done` outlet is 1 for one block when a cycle ends: a release finishes, or an AD decay completes. A retrigger during a release is not a completed cycle. Cable it into [Counter](https://docs.tune.toys/nodes/ctl.counter/) or another trigger input. ## When to use it Use it to shape a filter, a level or any parameter per note, or to build a one-shot modulator from a trigger. Prefer [Envelope](https://docs.tune.toys/nodes/std.envelope/) when the envelope shapes audio directly. ## Ports <!-- ports:start --> | Port | Direction | Signal | Lanes | | --------- | --------- | ------- | ----- | | `gate` | Input | Control | 1 | | `control` | Output | Control | 1 | | `done` | Output | Control | 1 | <!-- ports:end --> ## Parameters <!-- parameters:start --> | Parameter | Label | Range | Default | Unit | Scale | Notes | | ------------ | --------- | ---------- | ------- | ---- | ------ | -------------------------------------------------------------------------------------- | | `type` | Type | 0 to 2 | 0 | | linear | Selects the implemented algorithm. Choices are discrete. Choices: 0 ADSR, 1 AD, 2 AR. | | `attack_ms` | Attack | 0 to 10000 | 10 | ms | linear | Time to rise to full level. | | `decay_ms` | Decay | 0 to 10000 | 120 | ms | linear | Time to fall to sustain in ADSR, or zero in AD. | | `sustain` | Sustain | 0 to 1 | 0.7 | % | linear | Held ADSR level. Stored as a fraction; shown ×100 as a percentage. | | `release_ms` | Release | 0 to 10000 | 200 | ms | linear | Time to return to zero after the gate falls. | | `hold_ms` | Loop hold | 0 to 10000 | 100 | ms | linear | Duration of the held part in repeating ADSR/AR cycles. Shown when `loop` is 1. | | `curve` | Curve | -1 to 1 | 0 | | linear | Continuous concave/linear/convex segment curvature. | | `loop` | Loop | 0 to 1 | 0 | | linear | Repeats complete envelope cycles while the gate is held. Choices: 0 Off, 1 While held. | | `retrigger` | Retrigger | 0 to 1 | 0 | | linear | Where a new gate edge starts the attack. Choices: 0 Current level, 1 From zero. | | `velocity` | Velocity | 0 to 1 | 0 | | linear | How much the gate lane value at the rising edge scales the peak. 0 ignores it. | <!-- parameters:end --> ### Type (`type`) ADSR, AD or AR. A discrete choice. ### Attack (`attack_ms`), Decay (`decay_ms`), Sustain (`sustain`), Release (`release_ms`) The stage times and the held level. Decay applies to ADSR and AD, Sustain to ADSR, Release to ADSR and AR. ### Loop hold (`hold_ms`) How long a looping cycle rests at its held level before releasing. Visible when Loop is on. ### Curve (`curve`) Concave below 0, linear at 0, convex above 0, applied to every segment. ### Loop (`loop`) and Retrigger (`retrigger`) Loop repeats cycles while the gate is held. Retrigger picks the start of a new attack: the current level, or zero. ### Velocity (`velocity`) How much the gate value at the rising edge scales the peak, 0 to 1. ## Tips - A square [Control LFO](https://docs.tune.toys/nodes/ctl.lfo/) is a quick gate for trying an envelope. - Keep Velocity at 0 for a plain gate; at 1 the gate value is the peak. - Release only has something to do while the gate falls from above zero; `done` waits for it to finish. ## See also [Envelope](https://docs.tune.toys/nodes/std.envelope/), [Control LFO](https://docs.tune.toys/nodes/ctl.lfo/), [Counter](https://docs.tune.toys/nodes/ctl.counter/), [MIDI to control](https://docs.tune.toys/nodes/midi.to-control/), [Cables](https://docs.tune.toys/concepts/cables/). --- URL: https://docs.tune.toys/nodes/ctl.follow/ Title: Envelope follower Envelope follower tracks the level of an audio bundle and outputs it as one control lane per channel. ## What it does Audio comes in and a slowly moving level comes out. Detector chooses RMS (a smoothed average of the signal's energy) or Peak (the largest swing). Attack sets how fast the level rises and Release how fast it falls, in milliseconds. Gain boosts the audio before it is measured so quiet sources can reach 1; the audio itself passes on unchanged elsewhere because the follower only produces a control output. Stereo audio gives a two-lane output. ## When to use it Use it to make something react to loudness: scale the follower's output and cable it to a control. For per-frequency reaction use [Band energies](https://docs.tune.toys/nodes/ctl.bands/). ## Ports <!-- ports:start --> | Port | Direction | Signal | Lanes | | --------- | --------- | ------- | ----------------------- | | `audio` | Input | Audio | Inferred from the cable | | `control` | Output | Control | Inferred from the cable | <!-- ports:end --> ## Parameters <!-- parameters:start --> | Parameter | Label | Range | Default | Unit | Scale | Notes | | ------------ | -------- | --------- | ------- | ---- | ------ | ----------------------------------------------------------- | | `attack_ms` | Attack | 0 to 2000 | 10 | ms | linear | How fast the follower rises. | | `release_ms` | Release | 0 to 5000 | 200 | ms | linear | How fast the follower falls. | | `mode` | Detector | 0 to 1 | 0 | | linear | Measure RMS or peak level. Choices: 0 RMS, 1 Peak. | | `gain_db` | Gain | -24 to 48 | 0 | dB | linear | Boost applied before the follower so quiet sources reach 1. | <!-- parameters:end --> ### Attack (`attack_ms`) How fast the follower rises, 0 to 2000 ms. ### Release (`release_ms`) How fast the follower falls, 0 to 5000 ms. ### Detector (`mode`) RMS or Peak. RMS follows perceived level; Peak reacts to short transients. A discrete choice. ### Gain (`gain_db`) Boost, from -24 to 48 dB, applied before the follower so quiet sources reach 1. ## Tips - Use a fast Attack and a slower Release for a natural-looking level. - If the output barely moves, raise Gain. ## See also [Band energies](https://docs.tune.toys/nodes/ctl.bands/), [Smooth](https://docs.tune.toys/nodes/ctl.smooth/), [Scale](https://docs.tune.toys/nodes/ctl.scale/), [Cables](https://docs.tune.toys/concepts/cables/). --- URL: https://docs.tune.toys/nodes/ctl.hold/ Title: Sample and hold Sample and hold captures the input lanes whenever the trigger lane rises and holds them until the next trigger. ## What it does Between triggers the output is frozen at the last captured value. Each rising edge on the trigger lane takes a fresh snapshot of every lane of the control input. The trigger is a single lane; a narrower control input is broadcast. The node has no parameters. ## When to use it Use it to turn a continuous source into stepped values, for example a sweeping LFO sampled by a clock, or to freeze a sensor reading on an event. A trigger can come from [Compare](https://docs.tune.toys/nodes/std.compare/) or a square [Control LFO](https://docs.tune.toys/nodes/ctl.lfo/). ## Ports <!-- ports:start --> | Port | Direction | Signal | Lanes | | --------- | --------- | ------- | ----------------------- | | `control` | Input | Control | Inferred from the cable | | `trigger` | Input | Control | 1 | | `control` | Output | Control | Inferred from the cable | <!-- ports:end --> ## Parameters <!-- parameters:start --> This node has no numeric parameters. <!-- parameters:end --> ## Tips - Sample a fast saw LFO with a slower clock for stepped, pseudo-random values. - Hold keeps the value indefinitely; a missing trigger means the output never updates. ## See also [Compare](https://docs.tune.toys/nodes/std.compare/), [Counter](https://docs.tune.toys/nodes/ctl.counter/), [Control LFO](https://docs.tune.toys/nodes/ctl.lfo/), [Cables](https://docs.tune.toys/concepts/cables/). --- URL: https://docs.tune.toys/nodes/ctl.lfo/ Title: Control LFO Control LFO is a low-frequency oscillator whose output is a control lane, a number between 0 and 1 that moves in a repeating cycle. It runs on its own, and an optional reset input restarts it. ## What it does Shape picks the waveform: sine, triangle, saw, square, or one of two random shapes. Rate sets cycles per second from 0.01 to 40 Hz on a logarithmic control. Setting Beats above 0 locks the cycle to the shared transport instead, so one cycle lasts that many beats (up to 64) and Rate is ignored; at 0 the oscillator is free-running. Depth is the peak-to-peak size of the swing and Offset is its centre, so the defaults (Depth 1, Offset 0.5) sweep the whole 0 to 1 range. Phase sets where in the cycle the oscillator starts. The output is a single control lane. ### Reset A rising edge (0.5 or more) on the `reset` input restarts the cycle at the Phase parameter, free-running or locked to Beats, and does not report a `cycle`. Cable a clock or an [Envelope](https://docs.tune.toys/nodes/ctl.envelope/) `done` into it to restart a sweep on every note. The random shapes restart their sequence too. ### Random shapes Random step holds one value per cycle; Random smooth glides from each cycle's value to the next with an eased curve. Values are drawn from a generator seeded by the node's path, so every host, block size and replay agrees, and a transport seek lands on the same values. Depth and Offset scale them like any other shape. ### Events The `cycle` outlet sends an event: it is 1 for one block whenever the phase wraps from the end of a cycle back to the start, free-running or locked to Beats, and 0 otherwise. The first block does not count; a transport seek that moves the beat across a cycle boundary does. Cable it into a trigger input, such as [Counter](https://docs.tune.toys/nodes/ctl.counter/), to make a clock that ticks once per cycle whatever the Shape. Because control runs once per audio block, a cycle shorter than a block still reports one pulse per block, never more. ## When to use it Use it to produce a moving control signal as an explicit node that can be scaled, smoothed, combined or compared downstream. To turn the 0 to 1 swing into a different range, follow it with [Scale](https://docs.tune.toys/nodes/ctl.scale/); to slow its edges use [Smooth](https://docs.tune.toys/nodes/ctl.smooth/). ## Ports <!-- ports:start --> | Port | Direction | Signal | Lanes | | --------- | --------- | ------- | ----- | | `reset` | Input | Control | 1 | | `control` | Output | Control | 1 | | `cycle` | Output | Control | 1 | <!-- ports:end --> ## Parameters <!-- parameters:start --> | Parameter | Label | Range | Default | Unit | Scale | Notes | | --------- | ------ | ---------- | ------- | ----- | ------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `shape` | Shape | 0 to 5 | 0 | | linear | Waveform of the oscillator. The random shapes draw a new value each cycle from a generator seeded by the node path, so every host and replay agrees. Choices: 0 Sine, 1 Triangle, 2 Saw, 3 Square, 4 Random step, 5 Random smooth. | | `rate_hz` | Rate | 0.01 to 40 | 1 | Hz | log | Cycles per second when Beats is 0. | | `beats` | Beats | 0 to 64 | 0 | beats | linear | Beats per cycle, synced to the shared transport. 0 uses Rate instead. | | `depth` | Depth | 0 to 1 | 1 | | linear | Peak-to-peak amount of the output swing. | | `offset` | Offset | 0 to 1 | 0.5 | | linear | Centre of the swing. | | `phase` | Phase | 0 to 1 | 0 | | linear | Starting phase of the cycle, and where a Reset edge restarts it. | | `skew` | Skew | 0 to 1 | 0.5 | | linear | Where the Triangle peaks, or how the Saw bends. 0.5 is the plain shape. | <!-- parameters:end --> ### Shape (`shape`) Sine, Triangle, Saw, Square, Random step or Random smooth. A discrete choice. Square jumps between two levels, which makes it a handy clock for [Counter](https://docs.tune.toys/nodes/ctl.counter/) or [Sample and hold](https://docs.tune.toys/nodes/ctl.hold/). ### Rate (`rate_hz`) Cycles per second, 0.01 to 40 Hz, used when Beats is 0. ### Beats (`beats`) Beats per cycle, 0 to 64, locked to the shared transport. 0 hands control back to Rate. ### Depth (`depth`) The peak-to-peak size of the swing, 0 to 1. At 0 the output is a steady line at Offset. ### Offset (`offset`) The centre of the swing, 0 to 1. Depth and Offset together decide how much of the 0 to 1 range is used. ### Phase (`phase`) The starting phase of the cycle, 0 to 1, and where a Reset edge restarts it. Two LFOs with the same Rate or Beats and different Phase move in step but offset in time. ### Skew (`skew`) Moves the Triangle's peak earlier or later in the cycle, or bends the Saw so its midpoint arrives at that point. 0.5 is the plain shape and leaves the output exactly as it was; the other shapes ignore it. ## Tips - Keep Depth and Offset consistent: a Depth of 1 with an Offset away from 0.5 pushes part of the swing past the 0 to 1 range. - Use Beats for anything that should stay in time with the music, and Rate for slow drift. - A control lane has no sound of its own. It acts when it is connected to a node that takes control input. - Prefer `cycle` over a square wave when you need one tick per cycle: it fires on the wrap whatever the Shape, Depth or Offset, and stays a one-block pulse. ## See also [Scale](https://docs.tune.toys/nodes/ctl.scale/), [Smooth](https://docs.tune.toys/nodes/ctl.smooth/), [Math](https://docs.tune.toys/nodes/std.math/), [Cables](https://docs.tune.toys/concepts/cables/). --- URL: https://docs.tune.toys/nodes/ctl.random/ Title: Random Random is a control lane that changes to a new random value now and then. It either follows a trigger you cable in, or runs on its own clock. ## What it does Each time a new value is due, Random decides with Probability whether to change at all, then picks the value. In Step mode the lane jumps to a new value between 0 and 1; in Smooth mode it glides there over Slew; in Walk mode it moves from where it is by at most Walk either way and stays inside 0 to 1. Min and Max then map that 0 to 1 value onto the range the output uses, and may be swapped to invert it. When a cable reaches Trigger, each rising edge is a new value. When nothing does, Random runs at Rate, or at one value per Beats when Beats is above 0, locked to the shared transport. ### Seeded The sequence is seeded from the node's path, so the same Toy produces the same values on every host, every play and every replay. Two Random nodes in one Toy differ because their paths differ. Changing Probability does not shift the sequence that follows. ## When to use it Use it for sample-and-hold style stepped values, a drifting pitch, humanised timing or levels, and generative patches. For random shapes inside a cycle, see the Random step and Random smooth shapes of [Control LFO](https://docs.tune.toys/nodes/ctl.lfo/); to freeze any lane on a clock use [Sample and hold](https://docs.tune.toys/nodes/ctl.hold/). ## Ports <!-- ports:start --> | Port | Direction | Signal | Lanes | | --------- | --------- | ------- | ----- | | `trigger` | Input | Control | 1 | | `control` | Output | Control | 1 | <!-- ports:end --> ## Parameters <!-- parameters:start --> | Parameter | Label | Range | Default | Unit | Scale | Notes | | ------------- | ----------- | ---------- | ------- | ----- | ------ | ------------------------------------------------------------------------------------------------------------------------------------ | | `mode` | Mode | 0 to 2 | 0 | | linear | Step jumps to a new value, Smooth glides to it, Walk moves a bounded step from the current value. Choices: 0 Step, 1 Smooth, 2 Walk. | | `rate_hz` | Rate | 0.01 to 40 | 2 | Hz | log | New values per second when nothing is cabled to Trigger and Beats is 0. | | `beats` | Beats | 0 to 64 | 0 | beats | linear | Beats per new value, synced to the shared transport, when nothing is cabled to Trigger. 0 uses Rate instead. | | `probability` | Probability | 0 to 1 | 1 | | linear | Chance that a trigger changes the value. | | `walk` | Walk | 0 to 1 | 0.2 | | linear | Largest step either way in Walk mode. | | `slew_ms` | Slew | 0 to 10000 | 100 | ms | linear | Glide time in Smooth mode. | | `min` | Min | 0 to 1 | 0 | | linear | Output when the internal value is 0. | | `max` | Max | 0 to 1 | 1 | | linear | Output when the internal value is 1. | <!-- parameters:end --> ### Mode (`mode`) Step, Smooth or Walk. A discrete choice. ### Rate (`rate_hz`) and Beats (`beats`) How often a new value is due when Trigger is not cabled. Beats above 0 follows the transport and Rate is ignored. Control runs once per audio block, so at most one new value arrives per block. ### Probability (`probability`) The chance a trigger changes the value, 0 to 1. ### Walk (`walk`) The largest step either way in Walk mode. ### Slew (`slew_ms`) The glide time in Smooth mode. ### Min (`min`) and Max (`max`) The output range the 0 to 1 value is mapped onto. ## Tips - Cable a [Control LFO](https://docs.tune.toys/nodes/ctl.lfo/) square wave into Trigger to line new values up with a clock. - Set Min and Max narrower for a gentle drift around a centre. - Probability 0.5 on a steady clock gives uneven, human-sounding changes. ## See also [Sample and hold](https://docs.tune.toys/nodes/ctl.hold/), [Control LFO](https://docs.tune.toys/nodes/ctl.lfo/), [Counter](https://docs.tune.toys/nodes/ctl.counter/), [Scale](https://docs.tune.toys/nodes/ctl.scale/), [Cables](https://docs.tune.toys/concepts/cables/). --- URL: https://docs.tune.toys/nodes/ctl.scale/ Title: Scale Scale maps a control range onto another range, with a curve, a clamp and inversion. It is how you set depth and offset for any modulation. ## What it does The input range (Input min to Input max) is mapped linearly onto the output range (Output min to Output max), then bent by Curve. Curve at 0 is a straight line; negative values ease in (slow start, fast finish) and positive values ease out. Clamp holds the result inside the output range; with Extrapolate, input beyond the input range continues past the output range. All four range values run from -10000 to 10000. To invert a signal, set Output min higher than Output max. The same mapping applies to every lane of the input. ## When to use it Use it between any source and destination whose ranges differ: shrink an LFO to a small depth around a value, flip a sensor, or reshape a linear ramp. Pair it with [Control LFO](https://docs.tune.toys/nodes/ctl.lfo/) for modulation. ## Ports <!-- ports:start --> | Port | Direction | Signal | Lanes | | --------- | --------- | ------- | ----------------------- | | `control` | Input | Control | Inferred from the cable | | `control` | Output | Control | Inferred from the cable | <!-- ports:end --> ## Parameters <!-- parameters:start --> | Parameter | Label | Range | Default | Unit | Scale | Notes | | --------- | ---------- | --------------- | ------- | ---- | ------ | ------------------------------------------------------------------------------------------- | | `in_min` | Input min | -10000 to 10000 | 0 | | linear | Input value that maps to Output min. | | `in_max` | Input max | -10000 to 10000 | 1 | | linear | Input value that maps to Output max. | | `out_min` | Output min | -10000 to 10000 | 0 | | linear | Output at Input min. | | `out_max` | Output max | -10000 to 10000 | 1 | | linear | Output at Input max. | | `curve` | Curve | -1 to 1 | 0 | | linear | Bend of the mapping: negative eases in, positive eases out, 0 is linear. | | `clamp` | Clamp | 0 to 1 | 1 | | linear | Hold the output inside its range instead of extrapolating. Choices: 0 Extrapolate, 1 Clamp. | <!-- parameters:end --> ### Input min (`in_min`) and Input max (`in_max`) The input values that map to Output min and Output max. Narrowing this range makes the mapping more sensitive. ### Output min (`out_min`) and Output max (`out_max`) The output at Input min and at Input max. Swap them to invert. ### Curve (`curve`) From -1 to 1. 0 is linear, negative values ease in and positive values ease out. ### Clamp (`clamp`) Clamp holds the output inside Output min and Output max; Extrapolate lets it run past them. ## Tips - The defaults map 0 to 1 onto 0 to 1 and change nothing. - To use 1 - x, set Output min to 1 and Output max to 0. - Scale one control lane or a whole bundle; every lane is treated the same. ## See also [Control LFO](https://docs.tune.toys/nodes/ctl.lfo/), [Smooth](https://docs.tune.toys/nodes/ctl.smooth/), [Math](https://docs.tune.toys/nodes/std.math/), [Cables](https://docs.tune.toys/concepts/cables/). --- URL: https://docs.tune.toys/nodes/ctl.select/ Title: Select lanes Select lanes extracts a contiguous range of lanes from a control bundle. Use several to split a sensor's lanes apart. ## What it does Selection is set in the node's configuration, not by a knob, in the same way as [Select channels](https://docs.tune.toys/nodes/std.select/) for audio: the first lane to keep (counting from 1) and the number of lanes to keep. The kept lanes become the output, in order, and a range that falls outside the incoming bundle is rejected. The node has no parameters. ## When to use it Use it to pull one lane out of a multi-lane source, such as one band of [Band energies](https://docs.tune.toys/nodes/ctl.bands/) or the gate of a MIDI-to-control bundle, or place several side by side to split a bundle into separate paths. To rejoin lanes use [Combine lanes](https://docs.tune.toys/nodes/ctl.combine/). ## Ports <!-- ports:start --> | Port | Direction | Signal | Lanes | | --------- | --------- | ------- | ----------------------- | | `control` | Input | Control | Inferred from the cable | | `control` | Output | Control | Inferred from the cable | <!-- ports:end --> ## Parameters <!-- parameters:start --> This node has no numeric parameters. <!-- parameters:end --> ## Tips - To split a bundle completely, use one Select lanes node per lane. - A destination that expects one lane needs a single-lane selection. ## See also [Combine lanes](https://docs.tune.toys/nodes/ctl.combine/), [Constant](https://docs.tune.toys/nodes/ctl.const/), [Select channels](https://docs.tune.toys/nodes/std.select/), [Cables](https://docs.tune.toys/concepts/cables/). --- URL: https://docs.tune.toys/nodes/ctl.smooth/ Title: Smooth Smooth slows a control lane down with separate rise and fall times, so jumpy sensors and buttons stop clicking. ## What it does The output chases the input. Rise is the time constant while the value is increasing and Fall is the time constant while it is decreasing, each from 0 to 10000 ms. At 0 ms that direction follows the input immediately. Setting Fall longer than Rise gives a quick attack with a slow release. The same smoothing applies to every lane of the input. ## When to use it Use it after sources that jump, such as a counter, a square LFO or a button, and ahead of anything that would click if the value stepped. For deliberate envelope-like motion on audio level, see [Envelope follower](https://docs.tune.toys/nodes/ctl.follow/). ## Ports <!-- ports:start --> | Port | Direction | Signal | Lanes | | --------- | --------- | ------- | ----------------------- | | `control` | Input | Control | Inferred from the cable | | `control` | Output | Control | Inferred from the cable | <!-- ports:end --> ## Parameters <!-- parameters:start --> | Parameter | Label | Range | Default | Unit | Scale | Notes | | --------- | ----- | ---------- | ------- | ---- | ------ | -------------------------------------------- | | `rise_ms` | Rise | 0 to 10000 | 50 | ms | linear | Time constant while the value is increasing. | | `fall_ms` | Fall | 0 to 10000 | 200 | ms | linear | Time constant while the value is decreasing. | <!-- parameters:end --> ### Rise (`rise_ms`) The time constant, in milliseconds, while the value is going up. 0 to 10000. ### Fall (`fall_ms`) The time constant, in milliseconds, while the value is going down. 0 to 10000. ## Tips - Use a short Rise and a long Fall for a lingering release. - Set both to 0 to pass the signal straight through. ## See also [Control LFO](https://docs.tune.toys/nodes/ctl.lfo/), [Scale](https://docs.tune.toys/nodes/ctl.scale/), [Envelope follower](https://docs.tune.toys/nodes/ctl.follow/), [Cables](https://docs.tune.toys/concepts/cables/). --- URL: https://docs.tune.toys/nodes/data.compare/ Title: Compare Data Compare tests two data values and sends `true` or `false`. ## What it does Operation chooses the test. The first six, Greater, Less, Greater or equal, Less or equal, Equal and Not equal, match signal [Compare](https://docs.tune.toys/nodes/std.compare/). They compare as numbers when both values read as numbers, and otherwise as text, character by character. Equal on two lists is true when every element is equal, in order. The rest work on text and lists. Contains is true when text A contains text B, or when list A contains B. Starts with and Ends with test the start and end of text A. Matches reads B as a regular expression and tests text A. Is empty is true for empty text, text that is only spaces, an empty list and no value; numbers and booleans are never empty. In list is true when list B contains A. Case decides whether text tests care about upper and lower case. Compare runs whenever an input receives a value. An invalid regular expression sends no value and the inspector shows the error. ## When to use it Use it to make decisions on data: check a setting against a limit, test whether a name contains a word, or see whether a note is in a list. For audio and control signals use signal [Compare](https://docs.tune.toys/nodes/std.compare/). ## Ports <!-- ports:start --> | Port | Direction | Signal | Lanes | | -------- | --------- | -------------- | ----- | | `a` | Input | Data (any) | 1 | | `b` | Input | Data (any) | 1 | | `result` | Output | Data (boolean) | 1 | <!-- ports:end --> ## Parameters <!-- parameters:start --> | Parameter | Label | Range | Default | Unit | Scale | Notes | | --------- | --------- | ------- | ------- | ---- | ------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `op` | Operation | 0 to 11 | 0 | | linear | Comparison of a against b. Choices: 0 Greater, 1 Less, 2 Greater or equal, 3 Less or equal, 4 Equal, 5 Not equal, 6 Contains, 7 Starts with, 8 Ends with, 9 Matches, 10 Is empty, 11 In list. | | `epsilon` | Tolerance | 0 to 1 | 0 | | linear | Numeric difference still counted as equal. Shown when `op` is 4 or 5. | | `case` | Case | 0 to 1 | 0 | | linear | Whether text comparisons respect letter case. Choices: 0 Sensitive, 1 Insensitive. Shown when `op` is 0 or 1 or 2 or 3 or 4 or 5 or 6 or 7 or 8 or 9. | <!-- parameters:end --> ### Operation (`op`) The test, a discrete choice. Values 0 to 5 have the same names and order as signal Compare. ### Tolerance (`epsilon`) For Equal and Not equal on numbers, how far apart A and B may be and still count as equal, from 0 to 1. 0 asks for exact equality. Useful when a number comes from arithmetic, where 0.1 + 0.2 is not exactly 0.3. ### Case (`case`) Sensitive or Insensitive, a discrete choice. Insensitive folds case for text ordering, equality, Contains, Starts with and Ends with, and adds the `i` flag to Matches. ## Tips - To check a threshold, compare a number with Greater; the result is a boolean you can send on to other data nodes. - Matches uses JavaScript-style regular expressions: `^k` means starts with k, `[0-9]+` means one or more digits. - B has no text field of its own; connect a [Value](https://docs.tune.toys/nodes/data.value/) to B to compare with a fixed value. ## See also [Value](https://docs.tune.toys/nodes/data.value/), [String](https://docs.tune.toys/nodes/data.string/), [List](https://docs.tune.toys/nodes/data.list/), [Compare (signal)](https://docs.tune.toys/nodes/std.compare/). --- URL: https://docs.tune.toys/nodes/data.convert/ Title: Convert Convert changes a value's datatype. It can first pick one field out of JSON with a pointer, and it can write values into a text template. ## What it does A value arrives on `value`. If the node's `path` field holds a JSON pointer such as `/current/temperature_2m` or `/0/name`, Convert picks that field first. It then converts to the To datatype and sends the result on `value`. The text `"21.5"` becomes the number 21.5, `1` becomes `true`, a list of objects becomes a table, and so on. With To String and a `format` template, `{value}` is replaced by the value and `{/pointer}` by a field, so `It is {value} °C` reads `It is 21.5 °C`. A value that cannot be converted, or a pointer that finds nothing, is never thrown away silently: the reason goes to `error` and nothing is sent on `value`. ## When to use it Data cables only connect when their datatypes fit; widening such as integer to number happens on its own, but narrowing does not. Use Convert between a [Web request](https://docs.tune.toys/nodes/web.request/) response and a number input, before [Data to control](https://docs.tune.toys/nodes/data.to-control/), or to turn a [Value](https://docs.tune.toys/nodes/data.value/) into a URL or prompt string. ## Ports <!-- ports:start --> | Port | Direction | Signal | Lanes | | ------- | --------- | ------------- | ----- | | `value` | Input | Data (any) | 1 | | `value` | Output | Data (any) | 1 | | `error` | Output | Data (string) | 1 | <!-- ports:end --> ## Parameters <!-- parameters:start --> | Parameter | Label | Range | Default | Unit | Scale | Notes | | --------- | ----- | ------ | ------- | ---- | ------ | -------------------------------------------------------------------------------------------------------------- | | `to` | To | 0 to 6 | 0 | | linear | Datatype of the converted value. Choices: 0 Number, 1 Integer, 2 Boolean, 3 String, 4 Colour, 5 JSON, 6 Table. | <!-- parameters:end --> <!-- data:start --> - `path`: pointer. - `format`: text. <!-- data:end --> ### To (`to`) Number, Integer, Boolean, String, Colour, JSON or Table. The `value` output takes this datatype. ### Path and format The `path` field is a JSON pointer (up to 256 characters); leave it empty to convert the whole value. The `format` field is the String template (up to 1 024 characters). ## Tips - Cable `error` into a [Console log](https://docs.tune.toys/nodes/log.console/) at Error level while you find the right pointer. - List indexes in pointers start at 0: `/results/0/name/first`. ## See also [Data to control](https://docs.tune.toys/nodes/data.to-control/), [Dataset](https://docs.tune.toys/nodes/data.set/), [Web request](https://docs.tune.toys/nodes/web.request/), [Data cables](https://docs.tune.toys/concepts/data-cables/). --- URL: https://docs.tune.toys/nodes/data.list/ Title: List List reads, edits and summarises lists of values. ## What it does The `list` input is read as a list (a single value becomes a one-item list). Operation chooses what to do with it. Some operations need an item: it comes from the `b` input or, when `b` is not connected, from the node's B text field, read as JSON when it parses (`72`, `[1, 2]`) and as text otherwise. Index and Count come from the parameters; a connected `index` input overrides Index. | Operation | Result | | ---------------------------------------- | ---------------------------------------------------------------------- | | Length, First, Last, Get | a number or one element (Get uses Index; no value out of range) | | Push, Prepend | the list with B added at the end or the start | | Pop, Shift | the list without its last or first element | | Insert, Set | B inserted at Index, or the element at Index replaced by B | | Remove | Count elements removed from Index | | Slice, Take, Drop | Count elements from Index, the first Count, or all but the first Count | | Concatenate | the list followed by B | | Reverse, Sort ascending, Sort descending | the reordered list | | Unique, Compact | duplicates removed, or empty values removed | | Flatten | nested lists opened Count levels deep (0 opens all) | | Join | text with B (a comma by default) between elements | | Index of | position of B, or -1 | | Range | Count numbers starting at Index | | Sum, Minimum, Maximum, Mean | a number from the numeric elements | | Shuffle | the list in a random order set by Seed | Negative Index counts from the end. Sort puts numbers before text, numbers in numeric order and text by character, and keeps equal elements in their original order. Lists are limited to 4 096 items. ## When to use it Use it to keep a list of notes or settings, to pick one element, to tidy imported data (Unique, Compact, Sort) or to reduce a list to one number (Sum, Mean, Maximum). Use [String](https://docs.tune.toys/nodes/data.string/) Split to turn text into a list and Join here to turn it back. ## Ports <!-- ports:start --> | Port | Direction | Signal | Lanes | | ------- | --------- | ---------- | ----- | | `list` | Input | Data (any) | 1 | | `b` | Input | Data (any) | 1 | | `index` | Input | Data (any) | 1 | | `value` | Output | Data (any) | 1 | <!-- ports:end --> ## Parameters <!-- parameters:start --> | Parameter | Label | Range | Default | Unit | Scale | Notes | | --------- | --------- | ------------- | ------- | ---- | ------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `op` | Operation | 0 to 28 | 0 | | linear | List operation; b and index are its arguments. Choices: 0 Length, 1 Get, 2 First, 3 Last, 4 Push, 5 Prepend, 6 Pop, 7 Shift, 8 Insert, 9 Remove, 10 Set, 11 Slice, 12 Concatenate, 13 Reverse, 14 Sort ascending, 15 Sort descending, 16 Unique, 17 Flatten, 18 Compact, 19 Join, 20 Index of, 21 Range, 22 Sum, 23 Minimum, 24 Maximum, 25 Mean, 26 Shuffle, 27 Take, 28 Drop. | | `index` | Index | -4096 to 4096 | 0 | | linear | Position while the index port is unconnected; negative counts from the end. Shown when `op` is 1 or 8 or 9 or 10 or 11 or 21. | | `count` | Count | 0 to 4096 | 1 | | linear | Items removed, sliced, flattened, generated, taken or dropped. Shown when `op` is 9 or 11 or 17 or 21 or 27 or 28. | | `seed` | Seed | 0 to 65535 | 0 | | linear | Seed for Shuffle, so the order is reproducible. Shown when `op` is 26. | <!-- parameters:end --> <!-- data:start --> - `b`: text. <!-- data:end --> ### Operation (`op`) The list operation, a discrete choice. See the table above for what each one does and which fields it uses. ### Index (`index`) An element position from -4 096 to 4 096. 0 is the first element and -1 the last. Used by Get, Insert, Remove, Set and Slice, and as the first number for Range. A value on the `index` input replaces it. ### Count (`count`) A number of elements from 0 to 4 096: how many Remove takes out, Slice keeps (0 keeps the rest), Take keeps, Drop skips and Range makes, or how many levels Flatten opens (0 opens all). ### Seed (`seed`) From 0 to 65 535. Shuffle mixes the list in an order set by Seed and by how many times it has run, so a recording replays the same way. ## Tips - To sort a list, use Sort ascending or Sort descending; chain Unique first to drop repeats. - Sum, Minimum, Maximum and Mean ignore elements that are not numbers, so a mixed list still gives a number. - Splice is Remove followed by Insert: remove Count elements at Index, then insert B at the same Index. - Join with B set to `/` turns `["kick", "snare"]` into `kick / snare`. ## See also [Value](https://docs.tune.toys/nodes/data.value/), [String](https://docs.tune.toys/nodes/data.string/), [Math](https://docs.tune.toys/nodes/data.math/), [Compare](https://docs.tune.toys/nodes/data.compare/). --- URL: https://docs.tune.toys/nodes/data.math/ Title: Math Data Math does arithmetic on data values: single numbers, or every element of a list. ## What it does Operation chooses the arithmetic, in the same order and with the same names as signal [Math](https://docs.tune.toys/nodes/std.math/): Add, Subtract, Multiply, Divide, Modulo, Power, Minimum, Maximum, Average, Absolute difference, then the one-operand operations Negate, Absolute, Floor, Ceiling, Round, Square root and Sign. Both inputs are read as numbers: `true` is 1, `false` is 0 and text such as `" 42 "` becomes 42. When B is not connected it is B when unconnected. If either input is a list, the operation runs on every element: a list and a number applies the number to each element, and two lists pair their elements up to the shorter length. Results that have no number send no value (null) instead of infinity: dividing by 0, Modulo by 0, the square root of a negative number and text that does not read as a number. The inspector says which input caused it. Math runs whenever an input receives a value and sends one result. ## When to use it Use it for arithmetic on values that do not need to run at audio or control rate: scale a setting, convert units, offset every note in a list or average two readings. For audio and control signals use signal [Math](https://docs.tune.toys/nodes/std.math/). ## Ports <!-- ports:start --> | Port | Direction | Signal | Lanes | | ------- | --------- | ---------- | ----- | | `a` | Input | Data (any) | 1 | | `b` | Input | Data (any) | 1 | | `value` | Output | Data (any) | 1 | <!-- ports:end --> ## Parameters <!-- parameters:start --> | Parameter | Label | Range | Default | Unit | Scale | Notes | | ----------- | ------------------ | ------------------------- | ------- | ---- | ------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `op` | Operation | 0 to 16 | 0 | | linear | Operation applied to a and b; operations after Absolute difference use a only. Choices: 0 Add, 1 Subtract, 2 Multiply, 3 Divide, 4 Modulo, 5 Power, 6 Minimum, 7 Maximum, 8 Average, 9 Absolute difference, 10 Negate, 11 Absolute, 12 Floor, 13 Ceiling, 14 Round, 15 Square root, 16 Sign. | | `b_default` | B when unconnected | -1000000000 to 1000000000 | 0 | | linear | Value of b while its port is unconnected. Shown when `op` is 0 or 1 or 2 or 3 or 4 or 5 or 6 or 7 or 8 or 9. | <!-- parameters:end --> ### Operation (`op`) Which arithmetic to apply, a discrete choice shared with signal Math. Divide and Modulo by 0, and Square root of a negative number, send no value rather than 0. ### B when unconnected (`b_default`) The number used for B when nothing is connected to B, from -1 000 000 000 to 1 000 000 000. Hidden for the one-operand operations. ## Tips - Feed a JSON list from [Value](https://docs.tune.toys/nodes/data.value/) and Add 12 to transpose every note by an octave. - Two lists of different lengths give a list as long as the shorter one. - Chain two Math nodes to offset and then scale, as in the help patch. ## See also [Value](https://docs.tune.toys/nodes/data.value/), [Compare](https://docs.tune.toys/nodes/data.compare/), [List](https://docs.tune.toys/nodes/data.list/), [Math (signal)](https://docs.tune.toys/nodes/std.math/). --- URL: https://docs.tune.toys/nodes/data.merge/ Title: Merge Merge gathers up to eight data values into one JSON object, `{ "a": …, "b": … }`, keyed by the input each value arrived on. ## What it does Each input `a` to `h` keeps its newest value. When Merge emits, `value` carries an object with one key per cabled input that has a value, and `trigger` fires once. A data input takes one cable, so Merge is also how several sources meet in one place. ## When to use it Use it to build a JSON context for an [LLM chat](https://docs.tune.toys/nodes/ai.chat/), to log several values on one line, or to collect readings before a [Convert](https://docs.tune.toys/nodes/data.convert/) template turns them into a sentence. ## Ports <!-- ports:start --> | Port | Direction | Signal | Lanes | | --------- | --------- | -------------- | ----- | | `a` | Input | Data (any) | 1 | | `b` | Input | Data (any) | 1 | | `c` | Input | Data (any) | 1 | | `d` | Input | Data (any) | 1 | | `e` | Input | Data (any) | 1 | | `f` | Input | Data (any) | 1 | | `g` | Input | Data (any) | 1 | | `h` | Input | Data (any) | 1 | | `value` | Output | Data (json) | 1 | | `trigger` | Output | Data (trigger) | 1 | <!-- ports:end --> ## Parameters <!-- parameters:start --> | Parameter | Label | Range | Default | Unit | Scale | Notes | | --------- | ----- | ------ | ------- | ---- | ------ | ----------------------------------------------------------------------------------------------- | | `when` | When | 0 to 1 | 0 | | linear | Emit on every new input, or only once every connected input has a value. Choices: 0 Any, 1 All. | <!-- parameters:end --> ### When (`when`) Any emits whenever any input changes, with whatever values have arrived so far. All waits until every cabled input has a value, then emits on each change after that. ## Tips - Only cabled ports appear in the object; leave unused letters free. - Use All when the downstream node needs every field, for example a template with several pointers. ## See also [Convert](https://docs.tune.toys/nodes/data.convert/), [LLM chat](https://docs.tune.toys/nodes/ai.chat/), [Console log](https://docs.tune.toys/nodes/log.console/), [Data cables](https://docs.tune.toys/concepts/data-cables/). --- URL: https://docs.tune.toys/nodes/data.random/ Title: Random Random makes a new value each time its `trigger` input fires: a number, a whole number, true or false, a string, a colour or one of your own choices. ## What it does Type chooses the datatype on `value`. Number and Integer fall between Minimum and Maximum, Boolean is true or false, String is Length characters from the node's alphabet, Colour is `#rrggbb` text and Choice picks one of the node's listed strings. Without a `seed` cable each draw is unpredictable. With a seed cabled in, the sequence is a function of the seed and the draw count, so the same seed gives the same values every time the Toy starts. During [replay](https://docs.tune.toys/concepts/takes/) Random does not draw again: the values it sent while the take was recorded are played back. ## When to use it Use it with [Cron](https://docs.tune.toys/nodes/time.cron/) for generative patterns, to colour an `led` lamp, or, through [Data to control](https://docs.tune.toys/nodes/data.to-control/), to move a parameter by chance. ## Ports <!-- ports:start --> | Port | Direction | Signal | Lanes | | --------- | --------- | -------------- | ----- | | `trigger` | Input | Data (trigger) | 1 | | `seed` | Input | Data (integer) | 1 | | `value` | Output | Data (any) | 1 | <!-- ports:end --> ## Parameters <!-- parameters:start --> | Parameter | Label | Range | Default | Unit | Scale | Notes | | -------------- | ------------ | ------------------- | ------- | ---- | ------ | ------------------------------------------------------------------------------------------------------------------------------------- | | `type` | Type | 0 to 5 | 0 | | linear | Datatype of the value; Choice picks one of the listed strings. Choices: 0 Number, 1 Integer, 2 Boolean, 3 String, 4 Colour, 5 Choice. | | `min` | Minimum | -1000000 to 1000000 | 0 | | linear | Smallest number or integer. | | `max` | Maximum | -1000000 to 1000000 | 1 | | linear | Largest number or integer. | | `length` | Length | 1 to 64 | 8 | | linear | Characters in a random string. | | `distribution` | Distribution | 0 to 1 | 0 | | linear | Uniform spreads values evenly; Normal clusters them around the middle of the range. Choices: 0 Uniform, 1 Normal. | <!-- parameters:end --> <!-- data:start --> - `alphabet`: text. - `choices`: strings. <!-- data:end --> ### Type (`type`) Number, Integer, Boolean, String, Colour or Choice. The `value` port takes this datatype, so a cable from it must fit: a colour feeds a lamp, a number feeds Data to control. ### Minimum (`min`) and Maximum (`max`) The range for Number and Integer, from -1 000 000 to 1 000 000. ### Length (`length`) Characters in a String, 1 to 64, drawn from the node's `alphabet` text field (letters and digits when empty). ### Distribution (`distribution`) Uniform spreads values evenly across the range. Normal clusters them around the middle, which makes a random walk feel calmer. ### Choices and alphabet The `choices` field lists up to 64 strings for Choice; the `alphabet` field sets the characters for String. Both are edited in the inspector. ## Tips - Set Minimum and Maximum equal to send a constant number on every trigger. - Wire a [Value](https://docs.tune.toys/nodes/data.value/) through [Convert](https://docs.tune.toys/nodes/data.convert/) into `seed` for repeatable patterns. ## See also [Cron](https://docs.tune.toys/nodes/time.cron/), [Data to control](https://docs.tune.toys/nodes/data.to-control/), [Merge](https://docs.tune.toys/nodes/data.merge/), [Data cables](https://docs.tune.toys/concepts/data-cables/). --- URL: https://docs.tune.toys/nodes/data.set/ Title: Dataset Dataset reads tabular or JSON data and sends it into the Patch three ways: as a table of `rows`, as JSON `value` and as `text`. ## What it does The source is a Toy asset (the data travels with the Toy), a file you pick on this device, a file from a connected service such as Google Drive, or whatever is cabled into `source`. Dataset parses it and emits `rows` (a table with typed columns), `value` (for CSV and TSV, one JSON object per row keyed by column), `text` (when it fits in a message) and `changed`. Large files are parsed in a worker; tables are bounded to 100 000 cells and sources to 16 MiB. Device files and connection files are host-local: the Toy stores which kind of source to use, never the file or your account. Someone else opening the Toy chooses their own file. A Dataset with Source File requires file access; Source Connection requires that connection. Select a Dataset and open the **Data panel** to see its rows as a spreadsheet. Editing a cell of an asset dataset writes a new version of the asset (with Undo) and the chain downstream recomputes; a device file is written back after a permission check; a connection file is uploaded as a new revision. ## When to use it Use it for sequences, lookup tables, lyrics or any data you want to play with: step through rows with [List](https://docs.tune.toys/nodes/data.list/) and turn columns into sound with [Convert](https://docs.tune.toys/nodes/data.convert/) and [Data to control](https://docs.tune.toys/nodes/data.to-control/). ## Ports <!-- ports:start --> | Port | Direction | Signal | Lanes | | --------- | --------- | -------------- | ----- | | `source` | Input | Data (any) | 1 | | `rows` | Output | Data (table) | 1 | | `value` | Output | Data (json) | 1 | | `text` | Output | Data (string) | 1 | | `changed` | Output | Data (trigger) | 1 | <!-- ports:end --> ## Parameters <!-- parameters:start --> | Parameter | Label | Range | Default | Unit | Scale | Notes | | ---------- | ---------- | ------ | ------- | ---- | ------ | ------------------------------------------------------------------------------------------------------------------------------------ | | `source` | Source | 0 to 2 | 0 | | linear | An asset travels with the Toy; a file or connection is chosen on each host and never stored. Choices: 0 Asset, 1 File, 2 Connection. | | `format` | Format | 0 to 4 | 0 | | linear | Parser; Auto follows the media type and extension. Choices: 0 Auto, 1 CSV, 2 TSV, 3 JSON, 4 NDJSON. | | `header` | Header row | 0 to 1 | 1 | | linear | Treat the first CSV or TSV row as column names. Choices: 0 No, 1 Yes. | | `encoding` | Encoding | 0 to 2 | 0 | | linear | Text encoding of the source bytes. Choices: 0 UTF-8, 1 UTF-16, 2 Windows-1252. | <!-- parameters:end --> <!-- data:start --> - `asset`: asset. - `connection`: requirement. - `delimiter`: text. <!-- data:end --> ### Source (`source`) Asset, File or Connection. An asset is named by the node's `asset` field and stored in the Toy. File and Connection are chosen on each host. A cabled `source` input replaces whichever is set. ### Format (`format`) Auto (by media type and extension), CSV, TSV, JSON or NDJSON. ### Header row (`header`) Yes treats the first CSV or TSV row as column names; No numbers the columns and makes `value` a list of lists. ### Encoding (`encoding`) UTF-8, UTF-16 or Windows-1252, for the source bytes. ### Delimiter The `delimiter` field overrides the CSV separator, for example `;`. ## Tips - Keep sequence data in an asset so the Toy plays the same anywhere. - During [replay](https://docs.tune.toys/concepts/takes/) an asset dataset reads its asset again; file and connection datasets play back what they read while recording. ## See also [Convert](https://docs.tune.toys/nodes/data.convert/), [List](https://docs.tune.toys/nodes/data.list/), [Data to control](https://docs.tune.toys/nodes/data.to-control/), [Data cables](https://docs.tune.toys/concepts/data-cables/). --- URL: https://docs.tune.toys/nodes/data.string/ Title: String String transforms text: change case, cut it into a list, replace parts of it, search it or turn it into numbers and JSON. ## What it does The `text` input is read as text (a number becomes its digits, a list becomes its JSON). Operation chooses what happens to it. Some operations need more: `b` is the search text, separator or padding, and `c` is the replacement. When the `b` or `c` input is not connected, String uses the node's B or C text field instead. | Operation | Result | | ------------------------------------- | ------------------------------------------------------------------------ | | Length | number of characters | | Concatenate | text followed by B | | Upper case, Lower case, Trim, Reverse | the changed text | | Split | a list of the pieces between each B; empty B splits into characters | | Replace, Replace all | the first, or every, B replaced by C | | Slice | Count characters from Start; Count 0 means to the end | | Pad start, Pad end | text padded with B (a space by default) to Count characters | | Repeat | text repeated Count times | | Character at | the character at Start, or empty text | | Index of | position of B at or after Start, or -1 | | Match | a list of the match and its groups, or no value when nothing matches | | Format | each `{}` in the text replaced by the next element of B; `{0}` picks one | | To number, Parse JSON, To JSON | conversions; text that does not convert sends no value | Negative Start counts from the end. With Regular expression off, B is searched for literally; turn it on to read B as a pattern for Split, Replace, Replace all, Index of and Match. An invalid pattern sends no value and the inspector shows the error. ## When to use it Use it to clean up typed or imported text, to split a list of names or numbers written on one line, to build labels from values with Format, or to pull numbers out of text with Match and To number. To join a list back into text, use [List](https://docs.tune.toys/nodes/data.list/) Join. ## Ports <!-- ports:start --> | Port | Direction | Signal | Lanes | | ------- | --------- | ---------- | ----- | | `text` | Input | Data (any) | 1 | | `b` | Input | Data (any) | 1 | | `c` | Input | Data (any) | 1 | | `value` | Output | Data (any) | 1 | <!-- ports:end --> ## Parameters <!-- parameters:start --> | Parameter | Label | Range | Default | Unit | Scale | Notes | | --------- | ------------------ | ------------- | ------- | ---- | ------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `op` | Operation | 0 to 19 | 0 | | linear | Text operation; b and c are its arguments. Choices: 0 Length, 1 Concatenate, 2 Upper case, 3 Lower case, 4 Trim, 5 Split, 6 Replace, 7 Replace all, 8 Slice, 9 Pad start, 10 Pad end, 11 Repeat, 12 Reverse, 13 Character at, 14 Index of, 15 Match, 16 Format, 17 To number, 18 Parse JSON, 19 To JSON. | | `start` | Start | -4096 to 4096 | 0 | | linear | Start index; negative counts from the end. Shown when `op` is 8 or 13 or 14. | | `count` | Count | 0 to 4096 | 1 | | linear | Length, pad width or repeat count. Shown when `op` is 8 or 9 or 10 or 11. | | `regex` | Regular expression | 0 to 1 | 0 | | linear | Treat b as a regular expression. Choices: 0 Off, 1 On. Shown when `op` is 5 or 6 or 7 or 14 or 15. | | `case` | Case | 0 to 1 | 0 | | linear | Whether matching respects letter case. Choices: 0 Sensitive, 1 Insensitive. Shown when `op` is 5 or 6 or 7 or 14 or 15. | <!-- parameters:end --> <!-- data:start --> - `b`: text. - `c`: text. <!-- data:end --> ### Operation (`op`) The text operation, a discrete choice. See the table above for what each one does and which fields it uses. ### Start (`start`) A character position from -4 096 to 4 096. 0 is the first character and -1 the last. Used by Slice, Character at and Index of. ### Count (`count`) A number of characters or repeats, from 0 to 4 096. Slice takes Count characters (0 takes the rest), Pad start and Pad end pad to Count characters, Repeat repeats Count times. ### Regular expression (`regex`) Off or On, a discrete choice. Off reads B as plain text, so `.` is a full stop. On reads B as a pattern, so `[0-9]+` means any run of digits and Match returns capture groups. ### Case (`case`) Sensitive or Insensitive, a discrete choice. Insensitive makes Index of ignore case and adds the `i` flag to regular expressions. ## Tips - To split on commas with or without spaces, turn Regular expression on and set B to `\s*,\s*`. - To replace every number in a line, use Replace all with Regular expression on, B set to `[0-9]+` and C to the replacement. - Format with B connected to a list builds labels: `"{} of {}"` with `[3, 8]` gives `"3 of 8"`. - Text fields hold up to 1 024 characters; longer results are cut at 65 536 characters and flagged. ## See also [Value](https://docs.tune.toys/nodes/data.value/), [List](https://docs.tune.toys/nodes/data.list/), [Compare](https://docs.tune.toys/nodes/data.compare/). --- URL: https://docs.tune.toys/nodes/data.to-control/ Title: Data to control Data to control is the bridge from the data domain into sound. Numbers arrive as messages; it writes them onto a control lane that a parameter socket or control input reads at the next audio block. ## What it does Each number on `value` is mapped from the Input minimum–Input maximum range onto 0–1: `(value − min) / (max − min)`. Booleans count as 0 and 1. The compiler turns the node's `control` output into a bridge lane, and the audio engine samples it at the start of each block, exactly like a sensing node. A parameter socket such as a filter's `@cutoff` then moves across its own range, logarithmically when the parameter is logarithmic, and the parameter's smoothing applies. Until the first number arrives the parameter keeps its base value. On hosts without the data domain (the Player, exports) the lane is never written, so the parameter stays at its base value there too. ## When to use it Use it at the end of any data chain that should change the sound: weather to delay time, dataset rows to a filter, random numbers to a pitch. Put a [Convert](https://docs.tune.toys/nodes/data.convert/) before it when the number is inside JSON. ## Ports <!-- ports:start --> | Port | Direction | Signal | Lanes | | --------- | --------- | ------------- | ----- | | `value` | Input | Data (number) | 1 | | `control` | Output | Control | 1 | <!-- ports:end --> ## Parameters <!-- parameters:start --> | Parameter | Label | Range | Default | Unit | Scale | Notes | | --------- | ------------- | ------------------- | ------- | ---- | ------ | ------------------------------------------------------------------------------- | | `min` | Input minimum | -1000000 to 1000000 | 0 | | linear | Value mapped to 0 on the control lane. | | `max` | Input maximum | -1000000 to 1000000 | 1 | | linear | Value mapped to 1 on the control lane. | | `clamp` | Clamp | 0 to 1 | 1 | | linear | Keep the lane inside 0–1 when the value leaves the range. Choices: 0 Off, 1 On. | <!-- parameters:end --> ### Input minimum (`min`) and Input maximum (`max`) The range of incoming numbers that maps to the bottom and the top of the lane. For temperatures you might use -10 and 35; for a 0–1 value, 0 and 1. They must differ. ### Clamp (`clamp`) On keeps the lane inside 0–1 when a value leaves the range. Off lets it run past the ends; the receiving parameter still clamps to its own domain. ## Tips - Cable `control` into a parameter socket (shown with `@` on the node) rather than a public parameter; data nodes never move public parameters directly. - Lane jumps are smoothed by the target parameter, so choose a parameter with smoothing for audible sweeps. ## See also [Convert](https://docs.tune.toys/nodes/data.convert/), [Random](https://docs.tune.toys/nodes/data.random/), [Filter](https://docs.tune.toys/nodes/std.svf/), [Data cables](https://docs.tune.toys/concepts/data-cables/), [Control lanes](https://docs.tune.toys/concepts/control-lanes/). --- URL: https://docs.tune.toys/nodes/data.value/ Title: Value Value sends one fixed data value: a number, a piece of text, true or false, or a JSON value such as a list. ## What it does Kind chooses what Value sends. Number sends the Number parameter. Text sends the node's text field as a string. Boolean sends true when Number is not 0 and false when it is 0. JSON parses the text field, so `[1, 2, 3]` becomes a list of three numbers; text that is not valid JSON sends no value (null) and the inspector shows the error. Value sends when the Toy starts and again whenever you change Kind, Number or the text. Any message on the `send` input makes it send its current value again, so a trigger or a rising boolean can "bang" it. ## When to use it Use it as the start of a data chain: a number for [Math](https://docs.tune.toys/nodes/data.math/), a sentence for [String](https://docs.tune.toys/nodes/data.string/), a list for [List](https://docs.tune.toys/nodes/data.list/), or the second operand of [Compare](https://docs.tune.toys/nodes/data.compare/). Use JSON to write a list literal. ## Ports <!-- ports:start --> | Port | Direction | Signal | Lanes | | ------- | --------- | ---------- | ----- | | `send` | Input | Data (any) | 1 | | `value` | Output | Data (any) | 1 | <!-- ports:end --> ## Parameters <!-- parameters:start --> | Parameter | Label | Range | Default | Unit | Scale | Notes | | --------- | ------ | ------------------------- | ------- | ---- | ------ | ----------------------------------------------------------------------------------------------------------------- | | `kind` | Kind | 0 to 3 | 0 | | linear | Datatype of the stored value; Text and JSON read the text data key. Choices: 0 Number, 1 Text, 2 Boolean, 3 JSON. | | `number` | Number | -1000000000 to 1000000000 | 0 | | linear | The value when Kind is Number; non-zero is true for Boolean. | <!-- parameters:end --> <!-- data:start --> - `text`: text. <!-- data:end --> ### Kind (`kind`) Number, Text, Boolean or JSON, a discrete choice. Text and JSON read the node's text field (up to 1 024 characters), which you edit in the inspector. ### Number (`number`) The number to send, from -1 000 000 000 to 1 000 000 000, with no fixed step. Used by Number and Boolean; ignored by Text and JSON. ## Tips - Write a list as JSON, for example `[60, 64, 67]`, and feed it to [List](https://docs.tune.toys/nodes/data.list/) or [Math](https://docs.tune.toys/nodes/data.math/). - Text is sent exactly as typed, spaces included. Use [String](https://docs.tune.toys/nodes/data.string/) Trim to clean it. - Connect a trigger to `send` to resend the value on an event. ## See also [Math](https://docs.tune.toys/nodes/data.math/), [String](https://docs.tune.toys/nodes/data.string/), [List](https://docs.tune.toys/nodes/data.list/), [Compare](https://docs.tune.toys/nodes/data.compare/), [Cables](https://docs.tune.toys/concepts/cables/). --- URL: https://docs.tune.toys/nodes/log.console/ Title: Console log Console log prints every value it receives to the Console panel, with a level and a short label, so you can see what flows through a data chain. ## What it does Each message on `value` becomes one Console line: the time, the level, the node's label and the value rendered as text (long values are cut at 4 KiB). With `trigger` cabled, the node logs the newest `value` only when the trigger fires. Lines go to a bounded console ring in the host (5 000 entries); nothing is logged from the audio thread. The Console panel filters by level and node, searches, pauses and copies. Its sinks can download a `.log` file, and in Chromium write to a folder you choose. ## When to use it Use it while building: put one after a [Web request](https://docs.tune.toys/nodes/web.request/) to see the response, after [Convert](https://docs.tune.toys/nodes/data.convert/) to check a JSON pointer, or on a request's `error` output to catch refusals. ## Ports <!-- ports:start --> | Port | Direction | Signal | Lanes | | --------- | --------- | -------------- | ----- | | `value` | Input | Data (any) | 1 | | `trigger` | Input | Data (trigger) | 1 | <!-- ports:end --> ## Parameters <!-- parameters:start --> | Parameter | Label | Range | Default | Unit | Scale | Notes | | --------- | ----- | --------- | ------- | ---- | ------ | ------------------------------------------------------------------------------------------------ | | `level` | Level | 0 to 3 | 1 | | linear | Severity shown in the Console and used by file sinks. Choices: 0 Debug, 1 Info, 2 Warn, 3 Error. | | `rate` | Rate | 1 to 1000 | 20 | /s | linear | Most lines written per second; extra values are counted, not logged. | <!-- parameters:end --> <!-- data:start --> - `label`: text. <!-- data:end --> ### Level (`level`) Debug, Info, Warn or Error. It colours the line, lets the Console filter it, and decides which file a split-by-severity sink writes it to. ### Rate (`rate`) The most lines written per second, 1 to 1 000 (20 by default). Extra values are counted and reported in one notice instead of flooding the Console. ### Label The `label` text field (up to 32 characters) prefixes each line. Clicking the prefix in the Console selects the node in the Patch. ## Tips - Use the Error level on `error` outputs so problems stand out. - A Console log changes nothing else; remove it when you are done or keep it as documentation. ## See also [Web request](https://docs.tune.toys/nodes/web.request/), [Convert](https://docs.tune.toys/nodes/data.convert/), [Cron](https://docs.tune.toys/nodes/time.cron/), [Data cables](https://docs.tune.toys/concepts/data-cables/). --- URL: https://docs.tune.toys/nodes/media.player/ Title: Media player Media player plays a video file or an open stream as one video stream, with play, loop, rate and seek controls. ## What it does Set `data.asset` to a declared video asset id, or `data.source` to an open URL. Neither is required: without either the player waits for the host to choose media. HLS plays only where the browser plays it natively; DASH is not supported. Playback is paced by the media and the newest frame wins. ## When to use it Use it for clips, backgrounds and prerecorded sources that you want to filter or composite with other streams. ## Ports <!-- ports:start --> | Port | Direction | Signal | Lanes | | ------- | --------- | ------ | ----- | | `video` | Output | Video | 1 | <!-- ports:end --> ## Parameters <!-- parameters:start --> | Parameter | Label | Range | Default | Unit | Scale | Notes | | --------- | ------- | --------- | ------- | ---- | ------ | ----------------------------------------------------------------- | | `playing` | Playing | 0 to 1 | 1 | | linear | Play or pause the media. Choices: 0 Paused, 1 Playing. | | `loop` | Loop | 0 to 1 | 1 | | linear | Start again at the end. Choices: 0 Once, 1 Loop. | | `rate` | Rate | 0.25 to 4 | 1 | × | log | Playback speed. | | `seek` | Seek | 0 to 1 | 0 | | linear | Jump to this fraction of the duration whenever the value changes. | <!-- parameters:end --> <!-- data:start --> - `asset`: text. - `source`: text. <!-- data:end --> ### Playing (`playing`) Plays when on and pauses when off. ### Loop (`loop`) Starts again from the beginning at the end when on. ### Rate (`rate`) Playback speed from 0.25 to 4 times; 1 is normal. It is logarithmic. ### Seek (`seek`) Position in the media from 0 (start) to 1 (end). ## Tips - Prefer a declared asset for Toys you share; an open URL needs network access. - Video needs the browser's WebGPU video engine. The Player and exports do not run video yet and report it unavailable. To see a video output, bind a live Screen in the Frame to it. ## See also [Image](https://docs.tune.toys/nodes/video.image/), [Camera](https://docs.tune.toys/nodes/video.camera/), [Screen capture](https://docs.tune.toys/nodes/video.display/), [Video](https://docs.tune.toys/concepts/video/), [Control lanes](https://docs.tune.toys/concepts/control-lanes/). --- URL: https://docs.tune.toys/nodes/media.recorder/ Title: Recorder Recorder records a video stream to a file while Record is on. ## What it does FPS and Bitrate set the recording quality. The file is saved to the device when recording stops; the Toy never uploads it. Audio in recordings comes later. ## When to use it Use it to keep the result of a filter chain or a switcher. ## Ports <!-- ports:start --> | Port | Direction | Signal | Lanes | | ------- | --------- | ------ | ----- | | `video` | Input | Video | 1 | <!-- ports:end --> ## Parameters <!-- parameters:start --> | Parameter | Label | Range | Default | Unit | Scale | Notes | | -------------- | ---------- | -------- | ------- | ------ | ------ | ------------------------------------------------------------------------------------- | | `record` | Record | 0 to 1 | 0 | | linear | Start or stop recording; stopping finishes the file. Choices: 0 Stopped, 1 Recording. | | `fps` | Frame rate | 10 to 60 | 30 | fps | linear | Frames per second of the recording. | | `bitrate_mbps` | Bitrate | 1 to 40 | 8 | Mbit/s | log | Target video bitrate. | <!-- parameters:end --> ### Record (`record`) Records while on. ### Frame rate (`fps`) Frames per second of the recording, from 10 to 60. ### Bitrate (`bitrate_mbps`) Video bitrate in megabits per second. It is logarithmic. ## Tips - Stop recording to finish the file. - Video needs the browser's WebGPU video engine. The Player and exports do not run video yet and report it unavailable. To see a video output, bind a live Screen in the Frame to it. ## See also [Snapshot](https://docs.tune.toys/nodes/video.snapshot/), [Camera](https://docs.tune.toys/nodes/video.camera/), [Media player](https://docs.tune.toys/nodes/media.player/), [Video](https://docs.tune.toys/concepts/video/), [Control lanes](https://docs.tune.toys/concepts/control-lanes/). --- URL: https://docs.tune.toys/nodes/media.speech/ Title: Text to Speech Text to Speech turns words into ElevenLabs audio that can feed effects, the mixer and live recordings. ## What it does Enable ElevenLabs in Settings → Addons, add your API key to the `elevenlabs` vault slot in Settings → Vault, and enter a provider `voice` ID in the node's Properties. The key is injected by the account request proxy; it never enters the Toy or reaches the browser. Generation uses your ElevenLabs quota. The node does not retry paid requests automatically. Connect a text cable or enter `text` in Properties (1–4096 characters). With `trigger` cabled, a trigger generates the newest text. Without that cable, a new text message generates speech; uncabled text generates when the node starts. A new request replaces this node's current request and audio. `stop` cancels pending work and playback, and wins if both triggers arrive together. Stopping playback or the transport cancels speech. Structural engine changes stop buffered audio. Already submitted provider work may still be billed after cancellation. The mono voice is duplicated across the resolved `audio` bundle (stereo by default), following ordinary patch routing, processing, mixer levels and live output capture. `bytes` carries the generated mono 24 kHz WAV. The browser decodes and resamples off the audio thread, then transfers a bounded clip to the worklet; the shared engine reads preallocated blocks. Responses are bounded by the proxy's 5 MiB limit; decoded playback is bounded to 4,194,304 samples. Failures appear on `error` and the node status. `busy` covers generation and preparation. `done` fires when the generated clip is ready for playback, rather than when the last sample finishes. Set `model` to an ElevenLabs model ID, or leave it empty for `eleven_multilingual_v2`. Optional `language` is an ISO language code such as `en` or `fr`; an empty field lets the model detect it. Language enforcement is model-dependent and is unsupported by multilingual v2. Voice and model availability follow your provider account. See the [ElevenLabs speech API](https://elevenlabs.io/docs/api-reference/text-to-speech/convert). Replay injects recorded status outputs without calling the provider again. Generated bytes are not recorded as take data, so offline take rendering does not recreate this speech. Live output recording includes the routed speech. The native Player and exports do not run host data nodes. Real provider calls require credentials and are not qualified by the fixture suite. ## When to use it Speak generated words, announce data results, or send spoken material through effects. Connect [LLM chat](https://docs.tune.toys/nodes/ai.chat/) text to read an answer aloud. ## Ports <!-- ports:start --> | Port | Direction | Signal | Lanes | | --------- | --------- | -------------- | ----------------------- | | `text` | Input | Data (string) | 1 | | `trigger` | Input | Data (trigger) | 1 | | `stop` | Input | Data (trigger) | 1 | | `audio` | Output | Audio | Inferred from the cable | | `bytes` | Output | Data (bytes) | 1 | | `busy` | Output | Data (boolean) | 1 | | `done` | Output | Data (trigger) | 1 | | `error` | Output | Data (string) | 1 | <!-- ports:end --> ## Parameters <!-- parameters:start --> | Parameter | Label | Range | Default | Unit | Scale | Notes | | ------------ | ---------- | ---------- | ------- | ---- | ------ | ------------------------------------------------------------------------------------------ | | `speed` | Speed | 0.7 to 1.2 | 1 | | linear | Speaking speed; 1 is the normal speed for this voice. | | `stability` | Stability | 0 to 1 | 0.5 | | linear | Higher values keep the voice more consistent; lower values allow more expression. | | `similarity` | Similarity | 0 to 1 | 0.75 | | linear | How closely generated speech follows the selected voice. | | `style` | Style | 0 to 1 | 0 | | linear | Style exaggeration for models that support it. | | `volume` | Volume | 0 to 1 | 1 | % | linear | Playback volume of the generated speech. Stored as a fraction; shown ×100 as a percentage. | <!-- parameters:end --> <!-- data:start --> - `text`: text. - `voice`: text. - `model`: text. - `language`: text. <!-- data:end --> `speed` sets speaking speed from 0.7 to 1.2, with 1 normal. `stability` controls consistency; lower values allow more expression. `similarity` controls fidelity to the chosen voice. `style` exaggerates the voice's style where the model supports it. `volume` sets playback level. Settings apply to the next generated clip. ## Tips - Open the example, set your voice ID, enable ElevenLabs and configure your vault key, then press Play. - Use a trigger cable to prepare changing text without immediately submitting another paid generation. - Cable `busy` to an LED and `error` to [Console log](https://docs.tune.toys/nodes/log.console/). ## See also [Value](https://docs.tune.toys/nodes/data.value/), [LLM chat](https://docs.tune.toys/nodes/ai.chat/), [Gain](https://docs.tune.toys/nodes/std.gain/), [Data cables](https://docs.tune.toys/concepts/data-cables/). --- URL: https://docs.tune.toys/nodes/midi.arp/ Title: Arpeggiator Arpeggiator plays the notes you hold one at a time, in time with the transport. Hold a chord and it climbs, falls, bounces or wanders through it, across up to four octaves, or replays the whole chord on every step. ## What it does The arpeggiator keeps the notes held at its input, up to 32, tracked per source and channel so two keyboards never release each other's notes. On every step it plays the next note of the chosen order. Up and Down sort by pitch; Up-down and Down-up bounce without repeating the top and bottom; Random draws from a seed made from the node's path; As played keeps the order you pressed them in; Chord plays every held note together. Octaves repeats the pattern that many octaves up before it starts again. Steps are locked to the transport's beat: a new chord starts on the next step, and live playing, take replay and offline renders agree note for note. Releasing a key only changes which notes are held. Every note the arpeggiator starts, it releases itself after Gate length of its step, or at once when the transport stops or jumps or an all-notes-off arrives, so a note never hangs. Other messages, such as pitch bend, sustain and controllers, pass through. ## When to use it Use it to turn a held chord into a moving part, or with latch to keep a pattern running while your hands do something else. For a fixed melody, use [Step sequencer](https://docs.tune.toys/nodes/midi.step/). ## Ports <!-- ports:start --> | Port | Direction | Signal | Lanes | | ------ | --------- | ------ | ---------------- | | `midi` | Input | MIDI | 16 MIDI channels | | `midi` | Output | MIDI | 16 MIDI channels | <!-- ports:end --> ## Parameters <!-- parameters:start --> | Parameter | Label | Range | Default | Unit | Scale | Notes | | ---------------- | -------------- | --------- | ------- | ---- | ------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `mode` | Mode | 0 to 6 | 0 | | linear | Order of the held notes. Random draws from a seed of the node's path; Chord plays every held note on each step. Choices: 0 Up, 1 Down, 2 Up-down, 3 Down-up, 4 Random, 5 As played, 6 Chord. | | `octaves` | Octaves | 1 to 4 | 1 | | linear | Octaves the pattern climbs before it repeats. | | `division` | Division | 0 to 11 | 2 | | linear | Length of one step, locked to the transport's beat. Choices: 0 1/32, 1 1/16 triplet, 2 1/16, 3 1/16 dotted, 4 1/8 triplet, 5 1/8, 6 1/8 dotted, 7 1/4 triplet, 8 1/4, 9 1/4 dotted, 10 1/2, 11 1/1. | | `swing` | Swing | 0 to 0.75 | 0 | | linear | Delay of every second step, as a fraction of a step: 0 is straight, about 0.33 a triplet feel, 0.5 dotted. | | `gate_length` | Gate length | 0.05 to 1 | 0.5 | | linear | How long each note sounds, as a fraction of its step. | | `latch` | Latch | 0 to 1 | 0 | | linear | On keeps playing released notes until a new note starts a new chord. Choices: 0 Off, 1 On. | | `velocity` | Velocity | 0 to 1 | 0 | | linear | Use each held note's own velocity, or Fixed velocity for every step. Choices: 0 As played, 1 Fixed. | | `fixed_velocity` | Fixed velocity | 0 to 1 | 0.8 | | linear | Velocity of every step when Velocity is Fixed. Shown when `velocity` is 1. | | `channel` | Channel | 0 to 16 | 0 | | linear | 0 keeps each note's own channel; 1–16 sends every step on one channel. | <!-- parameters:end --> ### Mode (`mode`) The order of the held notes: Up, Down, Up-down, Down-up, Random, As played or Chord. ### Octaves (`octaves`) How many octaves the pattern climbs before it repeats, 1 to 4. Notes above 127 are skipped. In Chord mode each step moves the chord up an octave. ### Division (`division`) The length of one step, from 1/32 to a whole note, with triplet and dotted choices. ### Swing (`swing`) Delays every second step by a fraction of a step, 0 to 0.75. ### Gate length (`gate_length`) How much of its step a note holds, 0.05 to 1. ### Latch (`latch`) Off or On. On keeps playing the notes after you let go; the first note you press after letting go of every key starts a new chord. ### Velocity (`velocity`) As played uses each held note's own velocity; Fixed plays every step at Fixed velocity. ### Fixed velocity (`fixed_velocity`) The velocity of every step when Velocity is Fixed, 0 to 1. Hidden otherwise. ### Channel (`channel`) 0 keeps each note's own channel; 1 to 16 sends every step on that channel. ## Tips - Latch with Up-down over two octaves gives a classic synth arpeggio from a single held chord. - Put a [Transpose](https://docs.tune.toys/nodes/midi.transpose/) before the arpeggiator to shift the held chord, or after it to shift the pattern. - Random is the same on every replay: it is seeded, not truly random. ## See also [Keyboard](https://docs.tune.toys/nodes/midi.keyboard/), [Step sequencer](https://docs.tune.toys/nodes/midi.step/), [Transpose](https://docs.tune.toys/nodes/midi.transpose/), [Polyphonic instrument](https://docs.tune.toys/nodes/std.poly/). --- URL: https://docs.tune.toys/nodes/midi.channel/ Title: MIDI channel MIDI channel passes every channel, keeps one channel, or moves everything onto one channel. ## What it does Mode chooses what happens to channel information. Pass all leaves messages untouched. Only channel drops every message that is not on Channel. Remap to channel rewrites every channel message onto Channel. Channel runs from 1 to 16 and has no effect in Pass all. ## When to use it Use it to isolate one part of a multi-channel controller, or to label a source with its own channel before merging, or to line a stream up with a [MIDI to control](https://docs.tune.toys/nodes/midi.to-control/) node that listens on one channel. ## Ports <!-- ports:start --> | Port | Direction | Signal | Lanes | | ------ | --------- | ------ | ---------------- | | `midi` | Input | MIDI | 16 MIDI channels | | `midi` | Output | MIDI | 16 MIDI channels | <!-- ports:end --> ## Parameters <!-- parameters:start --> | Parameter | Label | Range | Default | Unit | Scale | Notes | | --------- | ------- | ------- | ------- | ---- | ------ | --------------------------------------------------------------------------------------------- | | `mode` | Mode | 0 to 2 | 0 | | linear | What happens to channel information. Choices: 0 Pass all, 1 Only channel, 2 Remap to channel. | | `channel` | Channel | 1 to 16 | 1 | | linear | The channel kept or assigned. | <!-- parameters:end --> ### Mode (`mode`) Pass all, Only channel or Remap to channel. A discrete choice. ### Channel (`channel`) The channel kept or assigned, 1 to 16. Unused in Pass all. ## Tips - Use Only channel to split a controller's zones into separate instruments. - Remap a single-channel source to a different channel so it can be told apart after a merge. ## See also [MIDI merge](https://docs.tune.toys/nodes/midi.merge/), [Transpose](https://docs.tune.toys/nodes/midi.transpose/), [MIDI to control](https://docs.tune.toys/nodes/midi.to-control/), [MIDI](https://docs.tune.toys/concepts/midi/). --- URL: https://docs.tune.toys/nodes/midi.euclid/ Title: Euclidean rhythm Euclidean rhythm spreads a number of pulses as evenly as possible over a cycle of steps and plays each pulse as a note. Three pulses over eight steps is the tresillo; five over eight the cinquillo. A few of these on different notes make a drum pattern. ## What it does Pulses are placed with Bjorklund's construction, the method Godfried Toussaint showed generates many traditional rhythms: E(3,8) is `x..x..x.`, E(5,8) is `x.xx.xx.` and E(7,16) is `x..x.x.x..x.x.x.`. Rotation starts the same pattern later in its cycle. More pulses than steps plays every step; no pulses plays nothing. Steps are locked to the transport's beat, so every replay and offline render plays the same notes on the same samples, and the cycle starts on beat 0. A step sounds only while the transport rolls; stopping or jumping the transport releases the sounding note. A rising edge on `reset` releases it too and starts a new cycle on the next step. Every pulse plays Note at Velocity; the first pulse of each cycle, as played after rotation, adds Accent. The `hit` outlet is 1 for one block whenever a pulse plays. The `position` outlet holds the current step index divided by Steps, including rests. The rhythm wheel shows one cycle clockwise from the top: filled dots play notes, small dots are rests, and the polygon joins the pulses. Steps, Pulses and Rotation redraw it immediately; its bottom handle rotates the pattern. During playback, the playhead follows the engine, including swing and resets. The same wheel is available as a Frame face. ## When to use it Use it for kick, snare and hat patterns, or for an off-kilter pulse on a synth. Merge several with [MIDI merge](https://docs.tune.toys/nodes/midi.merge/) into one drum voice. For melodies, use [Step sequencer](https://docs.tune.toys/nodes/midi.step/). ## Ports <!-- ports:start --> | Port | Direction | Signal | Lanes | | ---------- | --------- | ------- | ---------------- | | `reset` | Input | Control | 1 | | `midi` | Output | MIDI | 16 MIDI channels | | `hit` | Output | Control | 1 | | `position` | Output | Control | 1 | <!-- ports:end --> ## Parameters <!-- parameters:start --> | Parameter | Label | Range | Default | Unit | Scale | Notes | | ------------- | ----------- | --------- | ------- | ---- | ------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `steps` | Steps | 1 to 32 | 8 | | linear | Steps in one cycle. | | `pulses` | Pulses | 0 to 32 | 3 | | linear | Notes in one cycle, spread as evenly as the steps allow; more pulses than steps plays every step. | | `rotation` | Rotation | 0 to 31 | 0 | | linear | Shifts the pattern this many steps later in the cycle. | | `division` | Division | 0 to 11 | 2 | | linear | Length of one step, locked to the transport's beat. Choices: 0 1/32, 1 1/16 triplet, 2 1/16, 3 1/16 dotted, 4 1/8 triplet, 5 1/8, 6 1/8 dotted, 7 1/4 triplet, 8 1/4, 9 1/4 dotted, 10 1/2, 11 1/1. | | `swing` | Swing | 0 to 0.75 | 0 | | linear | Delay of every second step, as a fraction of a step: 0 is straight, about 0.33 a triplet feel, 0.5 dotted. | | `note` | Note | 0 to 127 | 60 | | linear | MIDI note every pulse plays. | | `velocity` | Velocity | 0 to 1 | 0.8 | | linear | Velocity of each pulse. | | `accent` | Accent | 0 to 1 | 0.2 | | linear | Velocity added to the first pulse of each cycle. | | `gate_length` | Gate length | 0.05 to 1 | 0.5 | | linear | How long each note sounds, as a fraction of its step. | | `channel` | Channel | 1 to 16 | 1 | | linear | MIDI channel of the notes. | <!-- parameters:end --> ### Steps (`steps`) Steps in one cycle, 1 to 32. ### Pulses (`pulses`) Notes in one cycle, 0 to 32, spread as evenly as the steps allow. ### Rotation (`rotation`) Shifts the pattern this many steps later in its cycle, 0 to 31; values past Steps wrap around. ### Division (`division`) The length of one step, from 1/32 to a whole note, with triplet and dotted choices. ### Swing (`swing`) Delays every second step by a fraction of a step, 0 to 0.75. ### Note (`note`) The MIDI note every pulse plays, 0 to 127. ### Velocity (`velocity`) The velocity of each pulse, 0 to 1. ### Accent (`accent`) Velocity added to the first pulse of each cycle, 0 to 1, so the cycle has a downbeat. ### Gate length (`gate_length`) How much of its step a note holds, 0.05 to 1. ### Channel (`channel`) The MIDI channel the notes are sent on, 1 to 16. ## Tips - Try E(3,8), E(5,8), E(7,12) and E(9,16), then rotate them against each other. - Two Euclidean rhythms with steps 3 and 4 make a polyrhythm that lines up every twelve steps. - Short gates (0.1 to 0.2) sound percussive on a sustained voice. ## See also [Step sequencer](https://docs.tune.toys/nodes/midi.step/), [Clock](https://docs.tune.toys/nodes/ctl.clock/), [MIDI merge](https://docs.tune.toys/nodes/midi.merge/), [Polyphonic instrument](https://docs.tune.toys/nodes/std.poly/). --- URL: https://docs.tune.toys/nodes/midi.from-control/ Title: Control to MIDI Control to MIDI sends control lanes as MIDI controllers: lane 1 to the first CC number, lane 2 to the next, with one message per change. ## What it does Each input lane becomes a MIDI controller. Lane 1 is sent on First CC, lane 2 on the next number, and so on. A message goes out only when a lane's value changes, on the MIDI channel set by Channel. Lane values from 0 to 1 cover the full 0 to 127 controller range. ## When to use it Use it to move outside hardware or other software with a Toy's control signals, for example an LFO from [Control LFO](https://docs.tune.toys/nodes/ctl.lfo/) sent to [MIDI out](https://docs.tune.toys/nodes/midi.out/). ## Ports <!-- ports:start --> | Port | Direction | Signal | Lanes | | --------- | --------- | ------- | ----------------------- | | `control` | Input | Control | Inferred from the cable | | `midi` | Output | MIDI | 16 MIDI channels | <!-- ports:end --> ## Parameters <!-- parameters:start --> | Parameter | Label | Range | Default | Unit | Scale | Notes | | --------- | -------- | -------- | ------- | ---- | ------ | ------------------------------------------------------------------------ | | `cc` | First CC | 0 to 127 | 1 | | linear | Controller number for lane 1; following lanes use the following numbers. | | `channel` | Channel | 1 to 16 | 1 | | linear | MIDI channel of the messages. | <!-- parameters:end --> ### First CC (`cc`) The controller number for lane 1, 0 to 127. Following lanes use the following numbers. ### Channel (`channel`) The MIDI channel of the messages, 1 to 16. ## Tips - Smooth steppy sources first with [Smooth](https://docs.tune.toys/nodes/ctl.smooth/) to avoid sending large jumps. - A bundle running past controller 127 has no controller to carry its extra lanes. ## See also [MIDI to control](https://docs.tune.toys/nodes/midi.to-control/), [MIDI out](https://docs.tune.toys/nodes/midi.out/), [Control LFO](https://docs.tune.toys/nodes/ctl.lfo/), [MIDI](https://docs.tune.toys/concepts/midi/). --- URL: https://docs.tune.toys/nodes/midi.from-data/ Title: Data to MIDI Data to MIDI plays a list of notes written as JSON through any MIDI input, such as the [Polyphonic instrument](https://docs.tune.toys/nodes/std.poly/). ## What it does `notes` takes a JSON list such as `[{ "note": 60, "velocity": 0.8, "start": 0, "duration": 0.5 }]`: MIDI note numbers, velocity 0–1 (0.8 when missing), and start and duration in beats. Up to 1 024 notes per list. With `trigger` cabled, each trigger plays the whole list from the next grid point; without it, every new list plays as it arrives. Playing again cancels notes still scheduled from the previous list. The notes become note-on and note-off messages on the node's `midi` output, stamped with audio frames from the transport's tempo, and enter the engine's MIDI input through the bridge. In the browser the scheduling runs on the main thread, so timing is as tight as its timers allow, not sample-exact. Hosts without the data domain (the Player, exports) play nothing from this node. ## When to use it Use it to play generated melodies, from an [LLM chat](https://docs.tune.toys/nodes/ai.chat/) answer, a [Dataset](https://docs.tune.toys/nodes/data.set/) or a [Value](https://docs.tune.toys/nodes/data.value/), every bar of a [Cron](https://docs.tune.toys/nodes/time.cron/) in Beat mode. ## Ports <!-- ports:start --> | Port | Direction | Signal | Lanes | | --------- | --------- | -------------- | ---------------- | | `notes` | Input | Data (json) | 1 | | `trigger` | Input | Data (trigger) | 1 | | `midi` | Output | MIDI | 16 MIDI channels | <!-- ports:end --> ## Parameters <!-- parameters:start --> | Parameter | Label | Range | Default | Unit | Scale | Notes | | ----------- | --------- | --------- | ------- | ---- | ------ | -------------------------------------------------------------------------------------------------- | | `channel` | Channel | 1 to 16 | 1 | | linear | MIDI channel of the generated notes. | | `transpose` | Transpose | -48 to 48 | 0 | st | linear | Semitones added to every note. | | `quantize` | Quantize | 0 to 3 | 0 | | linear | Snap note starts to this grid before scheduling. Choices: 0 Off, 1 Quarter, 2 Eighth, 3 Sixteenth. | <!-- parameters:end --> ### Channel (`channel`) The MIDI channel, 1 to 16, of the generated notes. ### Transpose (`transpose`) Semitones added to every note, -48 to 48. Notes pushed outside 0–127 are skipped. ### Quantize (`quantize`) Off starts the list at the current beat. Quarter, Eighth or Sixteenth waits for the next grid point and snaps each note's start to that grid. ## Tips - An invalid list (a missing `start`, a negative duration) is reported in the inspector and nothing plays. - Keep durations shorter than the gap to the next note for clean retriggers. ## See also [Polyphonic instrument](https://docs.tune.toys/nodes/std.poly/), [LLM chat](https://docs.tune.toys/nodes/ai.chat/), [Cron](https://docs.tune.toys/nodes/time.cron/), [MIDI cables](https://docs.tune.toys/concepts/midi-cables/), [Data cables](https://docs.tune.toys/concepts/data-cables/). --- URL: https://docs.tune.toys/nodes/midi.in/ Title: MIDI in MIDI in is a logical MIDI input port. The host binds it to a device or the DAW's MIDI bus; the Toy never stores a device. ## What it does The node has a short text label (up to 64 characters) that names the port inside the Toy. The person running the Toy chooses which device or host bus feeds each labelled port in their MIDI settings, so the same Toy works with any controller. The Toy keeps the label and nothing about the hardware. The node has no parameters and no input. ## When to use it Use it when a Toy should accept notes and controllers from an external keyboard or from a DAW. Keep one MIDI in per distinct source and combine several with [MIDI merge](https://docs.tune.toys/nodes/midi.merge/). ## Ports <!-- ports:start --> | Port | Direction | Signal | Lanes | | ------ | --------- | ------ | ---------------- | | `midi` | Output | MIDI | 16 MIDI channels | <!-- ports:end --> ## Parameters <!-- parameters:start --> This node has no numeric parameters. <!-- parameters:end --> <!-- data:start --> - `label`: text. <!-- data:end --> ## Tips - If nothing plays, check that MIDI access was allowed and that the port is bound to a device. - Device choices are local settings and never travel with the Toy. ## See also [Keyboard](https://docs.tune.toys/nodes/midi.keyboard/), [MIDI merge](https://docs.tune.toys/nodes/midi.merge/), [MIDI out](https://docs.tune.toys/nodes/midi.out/), [MIDI](https://docs.tune.toys/concepts/midi/). --- URL: https://docs.tune.toys/nodes/midi.keyboard/ Title: Keyboard Keyboard turns the on-screen piano and computer typing into MIDI notes. It is the face of the piano widget. ## What it does Notes played on a piano widget bound to this node, or typed on the computer keyboard, leave as a MIDI cable. It has no input and no parameters. Because it belongs to the host, it is driven by the person playing the Toy rather than by the signal graph. ## When to use it Use it as the note source for [Polyphonic instrument](https://docs.tune.toys/nodes/std.poly/), [Wavetable](https://docs.tune.toys/nodes/std.wavetable/) or any chain of MIDI processors. Pair it with [MIDI in](https://docs.tune.toys/nodes/midi.in/) through [MIDI merge](https://docs.tune.toys/nodes/midi.merge/) to accept both playing styles. ## Ports <!-- ports:start --> | Port | Direction | Signal | Lanes | | ------ | --------- | ------ | ---------------- | | `midi` | Output | MIDI | 16 MIDI channels | <!-- ports:end --> ## Parameters <!-- parameters:start --> This node has no numeric parameters. <!-- parameters:end --> ## Tips - Typing and the on-screen piano play the same notes; octave and range are set by the Frame's piano widget. - Put processors such as [Transpose](https://docs.tune.toys/nodes/midi.transpose/) between the keyboard and the instrument. ## See also [MIDI in](https://docs.tune.toys/nodes/midi.in/), [MIDI merge](https://docs.tune.toys/nodes/midi.merge/), [Polyphonic instrument](https://docs.tune.toys/nodes/std.poly/), [MIDI](https://docs.tune.toys/concepts/midi/). --- URL: https://docs.tune.toys/nodes/midi.merge/ Title: MIDI merge MIDI merge interleaves up to four MIDI streams by time into one. ## What it does Messages from in1 to in4 are ordered by their sample offset within the block. When two messages share an offset, the one from the lower-numbered input comes first, so the result is deterministic. Unused inputs are ignored. The node has no parameters. ## When to use it Use it to let several sources play one instrument, for example an external controller through [MIDI in](https://docs.tune.toys/nodes/midi.in/) alongside the on-screen [Keyboard](https://docs.tune.toys/nodes/midi.keyboard/). A MIDI input takes only one cable, so merge first. ## Ports <!-- ports:start --> | Port | Direction | Signal | Lanes | | ------ | --------- | ------ | ---------------- | | `in1` | Input | MIDI | 16 MIDI channels | | `in2` | Input | MIDI | 16 MIDI channels | | `in3` | Input | MIDI | 16 MIDI channels | | `in4` | Input | MIDI | 16 MIDI channels | | `midi` | Output | MIDI | 16 MIDI channels | <!-- ports:end --> ## Parameters <!-- parameters:start --> This node has no numeric parameters. <!-- parameters:end --> ## Tips - Merge is not a mixer: it keeps every message, including notes that overlap. - To tell sources apart downstream, remap each to its own channel with [MIDI channel](https://docs.tune.toys/nodes/midi.channel/) before merging. ## See also [Keyboard](https://docs.tune.toys/nodes/midi.keyboard/), [MIDI in](https://docs.tune.toys/nodes/midi.in/), [MIDI channel](https://docs.tune.toys/nodes/midi.channel/), [MIDI](https://docs.tune.toys/concepts/midi/). --- URL: https://docs.tune.toys/nodes/midi.notes/ Title: Control to notes Control to notes plays MIDI notes from control lanes: a pitch lane picks the note and a gate lane starts and stops it. It is the inverse of [MIDI to control](https://docs.tune.toys/nodes/midi.to-control/), so LFOs, curves, sensors and data can play any instrument. ## What it does The input is a control bundle read in MIDI to control's order: lane 1 is pitch, lane 2 gate and lane 3 velocity. Pitch 0 plays Low note and 1 plays High note, rounded to the nearest note. A one-lane input is a gate that plays Low note; a two-lane input is pitch and gate, at velocity 0.8. When the gate rises to 0.5 or more, a note starts; when it falls below, the note ends. With Legato on, a change of pitch while the gate is high moves the note: the new note starts and then the old one ends, which a glide or mono voice plays as one phrase. Control runs once per audio block, so notes start and end at the beginning of the block whose lanes changed, not on an exact sample: about 2.7 ms resolution at 128 frames and 48 kHz. Removing the input releases the sounding note. ## When to use it Use it to play an instrument from something that is not MIDI: a sensor, a drawn [Curve](https://docs.tune.toys/nodes/ctl.curve/), a [Control LFO](https://docs.tune.toys/nodes/ctl.lfo/) or data. Build the bundle with [Combine lanes](https://docs.tune.toys/nodes/ctl.combine/). For notes locked exactly to the beat, use a sequencer such as [Step sequencer](https://docs.tune.toys/nodes/midi.step/). ## Ports <!-- ports:start --> | Port | Direction | Signal | Lanes | | --------- | --------- | ------- | ----------------------- | | `control` | Input | Control | Inferred from the cable | | `midi` | Output | MIDI | 16 MIDI channels | <!-- ports:end --> ## Parameters <!-- parameters:start --> | Parameter | Label | Range | Default | Unit | Scale | Notes | | ----------- | --------- | -------- | ------- | ---- | ------ | ---------------------------------------------------------------------------------------------------------- | | `low_note` | Low note | 0 to 127 | 36 | | linear | Note played when the pitch lane is 0. | | `high_note` | High note | 0 to 127 | 96 | | linear | Note played when the pitch lane is 1. | | `channel` | Channel | 1 to 16 | 1 | | linear | MIDI channel of the notes. | | `legato` | Legato | 0 to 1 | 0 | | linear | On moves a held note when the pitch lane changes; Off keeps it until the gate falls. Choices: 0 Off, 1 On. | <!-- parameters:end --> ### Low note (`low_note`) The note played when the pitch lane is 0, 0 to 127. ### High note (`high_note`) The note played when the pitch lane is 1, 0 to 127. Below Low note turns the pitch lane upside down. ### Channel (`channel`) The MIDI channel the notes are sent on, 1 to 16. ### Legato (`legato`) Off keeps a note at its starting pitch until the gate falls. On moves it whenever the pitch lane names another note while the gate is high. ## Tips - Set Low note and High note to the same values as a MIDI to control node to get the notes it read back exactly. - A stepped pitch source, such as a Random step LFO or a curve with flat segments, plays clean notes; a smooth one with Legato on slides through every note it passes. - The instrument plays each note as a degree of the Toy's tuning. ## See also [MIDI to control](https://docs.tune.toys/nodes/midi.to-control/), [Combine lanes](https://docs.tune.toys/nodes/ctl.combine/), [Control LFO](https://docs.tune.toys/nodes/ctl.lfo/), [Polyphonic instrument](https://docs.tune.toys/nodes/std.poly/), [Control lanes](https://docs.tune.toys/concepts/control-lanes/). --- URL: https://docs.tune.toys/nodes/midi.out/ Title: MIDI out MIDI out is a logical MIDI output port. The host binds it to a device or the DAW; messages leave at their sample offsets. ## What it does Whatever MIDI arrives at the input is sent out of the Toy to the host. Messages keep their sample offsets within the audio block. Like MIDI in, the node carries a short text label (up to 64 characters), and the person running the Toy chooses the destination device or DAW bus in their MIDI settings. The node has no parameters and no output. ## When to use it Use it to play an outside synth from a Toy's keyboard or sequencing logic, or to send generated controllers with [Control to MIDI](https://docs.tune.toys/nodes/midi.from-control/). The same MIDI cable can also feed an instrument inside the Toy. ## Ports <!-- ports:start --> | Port | Direction | Signal | Lanes | | ------ | --------- | ------ | ---------------- | | `midi` | Input | MIDI | 16 MIDI channels | <!-- ports:end --> ## Parameters <!-- parameters:start --> This node has no numeric parameters. <!-- parameters:end --> <!-- data:start --> - `label`: text. <!-- data:end --> ## Tips - A MIDI cable can fan out, so one keyboard can drive an internal instrument and an output at the same time. - Destination devices are local settings and never saved in the Toy. ## See also [MIDI in](https://docs.tune.toys/nodes/midi.in/), [Control to MIDI](https://docs.tune.toys/nodes/midi.from-control/), [Keyboard](https://docs.tune.toys/nodes/midi.keyboard/), [MIDI](https://docs.tune.toys/concepts/midi/). --- URL: https://docs.tune.toys/nodes/midi.step/ Title: Step sequencer Step sequencer plays a pattern of up to 64 steps as MIDI notes, in time with the transport. Each step is a note, a rest or a tie, with its own pitch, velocity and chance of playing. Draw the pattern on the step grid and cable the output into any instrument. ## What it does The pattern lives in the node's `steps` data: four numbers per step, up to 64 steps. Offset is the number of notes above Root (−48 to 48), Gate is 0 rest, 1 note or 2 tie, Velocity is 0 to 1, and Probability is the chance, 0 to 1, that a note step plays. A tie holds the note before it through that step instead of starting a new one. A node without steps plays sixteen steps with a note on every fourth. Steps past the drawn ones, up to Length, rest. Steps are locked to the transport's beat: each one starts on the first sample at or after its position, wherever an audio block happens to begin, so playing live, replaying a take and rendering offline give the same notes on the same samples. Steps sound only while the transport rolls. Stopping the transport, jumping it to another position, or a `reset` releases the sounding note at once, so nothing is left hanging. Probability and the Random direction draw from a seed made from the node's path, so the same pattern plays the same way on every replay and host. Notes are MIDI note numbers. The instrument you cable into plays each as a degree of the Toy's tuning, so a pattern follows non-12-TET tunings without a separate quantizer. ### The step grid The face on the node card, in the inspector and in a Frame `face` widget shows one column per step. The top row holds gates: click to add or remove a note, Shift-click for a tie, and drag across steps to paint. The middle row holds each step's pitch: drag up or down (Shift is finer). The bottom row holds velocity: draw it with the pointer. A chance note fills its gate cell to its probability. The grid is one keyboard control with a single Tab stop. Arrow keys move between cells, Home and End jump to the first and last step, Space or Enter toggles a note, T toggles a tie, Delete rests a step, + and − change a pitch by a note or a velocity by 5%, and Page Up and Page Down by an octave or a quarter. Escape during a drag restores the pattern. The change plays while you edit, and each click, drag or held key is one edit, so one Undo reverts it. While the engine runs, a marker above the grid shows the step that is playing. ### Clock, reset and outlets With the `clock` input cabled, each rising edge plays the next step at the start of that block instead of following the transport; a note lasts Gate length of the time between the last two edges. A rising edge on `reset` releases what sounds and makes the next step the first. The `step` outlet is 1 for one block whenever a step starts, and `position` reports the step playing as its index divided by Length. ## When to use it Use it for basslines, melodies and drum patterns that repeat with the transport. For an evenly spread rhythm on one note, [Euclidean rhythm](https://docs.tune.toys/nodes/midi.euclid/) is quicker; to play held notes in turn, use [Arpeggiator](https://docs.tune.toys/nodes/midi.arp/). ## Ports <!-- ports:start --> | Port | Direction | Signal | Lanes | | ---------- | --------- | ------- | ---------------- | | `clock` | Input | Control | 1 | | `reset` | Input | Control | 1 | | `midi` | Output | MIDI | 16 MIDI channels | | `step` | Output | Control | 1 | | `position` | Output | Control | 1 | <!-- ports:end --> ## Parameters <!-- parameters:start --> | Parameter | Label | Range | Default | Unit | Scale | Notes | | ---------------- | -------------- | --------- | ------- | ---- | ------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `length` | Length | 1 to 64 | 16 | | linear | Steps in the pattern; steps past the drawn ones rest. | | `division` | Division | 0 to 11 | 2 | | linear | Length of one step, locked to the transport's beat. Choices: 0 1/32, 1 1/16 triplet, 2 1/16, 3 1/16 dotted, 4 1/8 triplet, 5 1/8, 6 1/8 dotted, 7 1/4 triplet, 8 1/4, 9 1/4 dotted, 10 1/2, 11 1/1. | | `swing` | Swing | 0 to 0.75 | 0 | | linear | Delay of every second step, as a fraction of a step: 0 is straight, about 0.33 a triplet feel, 0.5 dotted. | | `gate_length` | Gate length | 0.05 to 1 | 0.5 | | linear | How long each note sounds, as a fraction of its step. | | `direction` | Direction | 0 to 3 | 0 | | linear | Order the steps play in. Random picks a step from a seed of the node's path, so every replay is the same. Choices: 0 Forward, 1 Backward, 2 Ping-pong, 3 Random. | | `root` | Root | 0 to 127 | 60 | | linear | MIDI note of a step with offset 0. | | `transpose` | Transpose | -48 to 48 | 0 | st | linear | Notes added to every step. | | `channel` | Channel | 1 to 16 | 1 | | linear | MIDI channel of the notes. | | `velocity_scale` | Velocity scale | 0 to 2 | 1 | × | linear | Multiplies every step's velocity. | <!-- parameters:end --> <!-- data:start --> - `steps`: floats. <!-- data:end --> ### Length (`length`) How many steps the pattern has, 1 to 64. Steps past the drawn ones rest, and the grid shows exactly this many columns. ### Division (`division`) The length of one step, from 1/32 to a whole note, with triplet and dotted choices. 1/16 plays four steps per beat. ### Swing (`swing`) Delays every second step by a fraction of a step, 0 to 0.75. About 0.33 gives a triplet feel and 0.5 a dotted one. The swung step is shorter and the one before it longer. ### Gate length (`gate_length`) How much of its step a note holds, 0.05 to 1. At 1 a note lasts until the next step starts; ties extend it through their steps. ### Direction (`direction`) Forward, Backward, Ping-pong (forward then back without repeating the ends) or Random (a seeded random step each time). ### Root (`root`) The MIDI note a step with offset 0 plays, 0 to 127. 60 is middle C in 12-TET. ### Transpose (`transpose`) Notes added to every step, −48 to 48. Turn it to move the whole pattern. ### Channel (`channel`) The MIDI channel the notes are sent on, 1 to 16. ### Velocity scale (`velocity_scale`) Multiplies every step's velocity, 0 to 2. Velocities are kept between 1 and 127. ## Tips - Draw a few notes with ties for a legato bassline; ties sound as one long note on any instrument. - A probability below 1 on a few steps keeps a pattern moving without changing it on every replay. - Cable Clock's bar lane (through Select lanes) into `reset` to restart a 12-step pattern every bar. ## See also [Euclidean rhythm](https://docs.tune.toys/nodes/midi.euclid/), [Arpeggiator](https://docs.tune.toys/nodes/midi.arp/), [Clock](https://docs.tune.toys/nodes/ctl.clock/), [Polyphonic instrument](https://docs.tune.toys/nodes/std.poly/), [MIDI](https://docs.tune.toys/concepts/midi/). --- URL: https://docs.tune.toys/nodes/midi.to-control/ Title: MIDI to control MIDI to control turns MIDI into six control lanes: pitch, gate, velocity, bend, pressure and one controller, with last-note priority. ## What it does The output has six labelled lanes: pitch, gate, velocity, bend, pressure and cc. Pitch maps Low note to 0 and High note to 1. Gate is 1 while a note is held. Velocity, bend, pressure and the lane for one controller number follow the incoming messages. The node is monophonic with last-note priority: the most recently pressed note that is still held wins. Channel selects which channel to listen to (0 means every channel) and Glide adds portamento to the pitch lane. ## When to use it Use it to make a keyboard or controller drive control logic: scale the pitch lane into a filter cutoff range, use the gate with [Sample and hold](https://docs.tune.toys/nodes/ctl.hold/), or pull single lanes out with [Select lanes](https://docs.tune.toys/nodes/ctl.select/). ## Ports <!-- ports:start --> | Port | Direction | Signal | Lanes | | --------- | --------- | ------- | ---------------- | | `midi` | Input | MIDI | 16 MIDI channels | | `control` | Output | Control | 6 | <!-- ports:end --> ## Parameters <!-- parameters:start --> | Parameter | Label | Range | Default | Unit | Scale | Notes | | ----------- | --------- | --------- | ------- | ---- | ------ | ------------------------------------------------ | | `low_note` | Low note | 0 to 127 | 36 | | linear | Note that maps to 0 on the pitch lane. | | `high_note` | High note | 0 to 127 | 96 | | linear | Note that maps to 1 on the pitch lane. | | `cc` | CC | 0 to 127 | 1 | | linear | Controller number followed by the cc lane. | | `channel` | Channel | 0 to 16 | 0 | | linear | 0 listens to every channel; 1–16 listens to one. | | `glide_ms` | Glide | 0 to 2000 | 0 | ms | linear | Portamento on the pitch lane between notes. | <!-- parameters:end --> ### Low note (`low_note`) and High note (`high_note`) The notes, 0 to 127, that map to 0 and 1 on the pitch lane. Notes outside the range go beyond 0 to 1. ### CC (`cc`) The controller number, 0 to 127, followed by the cc lane. The default, 1, is the mod wheel. ### Channel (`channel`) 0 listens to every channel; 1 to 16 listens to one. ### Glide (`glide_ms`) Portamento time on the pitch lane between notes, 0 to 2000 ms. ## Tips - The lanes are labelled, so the editor can name them when you split the bundle. - Because only one note is tracked, use [Polyphonic instrument](https://docs.tune.toys/nodes/std.poly/) for chords. ## See also [Control to MIDI](https://docs.tune.toys/nodes/midi.from-control/), [Select lanes](https://docs.tune.toys/nodes/ctl.select/), [Scale](https://docs.tune.toys/nodes/ctl.scale/), [MIDI](https://docs.tune.toys/concepts/midi/). --- URL: https://docs.tune.toys/nodes/midi.transpose/ Title: Transpose Transpose shifts note numbers by semitones and octaves, clamped to the MIDI range. ## What it does Every note is moved by Semitones plus twelve times Octaves. Results are clamped to the MIDI range of 0 to 127 instead of wrapping. Notes are shifted on both note-on and note-off so they release correctly. Other messages are unchanged. ## When to use it Use it to move a keyboard into a comfortable range, to transpose a live controller without changing its settings, or to stack with a second copy for fifths and octaves. ## Ports <!-- ports:start --> | Port | Direction | Signal | Lanes | | ------ | --------- | ------ | ---------------- | | `midi` | Input | MIDI | 16 MIDI channels | | `midi` | Output | MIDI | 16 MIDI channels | <!-- ports:end --> ## Parameters <!-- parameters:start --> | Parameter | Label | Range | Default | Unit | Scale | Notes | | ----------- | --------- | --------- | ------- | ---- | ------ | ------------------------------------- | | `semitones` | Semitones | -48 to 48 | 0 | st | linear | Semitone shift applied to every note. | | `octaves` | Octaves | -4 to 4 | 0 | oct | linear | Octave shift added to the semitones. | <!-- parameters:end --> ### Semitones (`semitones`) The shift in semitones, -48 to 48. ### Octaves (`octaves`) An extra shift in octaves, -4 to 4, added to the semitones. ## Tips - Twelve semitones equals one octave; use whichever is clearer. - Heavy downward shifts can push notes past the bottom of the range, where they clamp at 0. ## See also [Velocity](https://docs.tune.toys/nodes/midi.velocity/), [MIDI channel](https://docs.tune.toys/nodes/midi.channel/), [Polyphonic instrument](https://docs.tune.toys/nodes/std.poly/), [MIDI](https://docs.tune.toys/concepts/midi/). --- URL: https://docs.tune.toys/nodes/midi.velocity/ Title: Velocity Velocity scales, offsets, floors or fixes the velocity of note-on messages. ## What it does Incoming velocity is multiplied by Scale (0 to 2), then Offset (a fraction of full velocity, -1 to 1) is added, and the result is raised to at least Floor. Turn Fixed on to replace every velocity with Fixed value instead. Only note-on velocity is changed; other messages pass through. ## When to use it Use it to make a controller play louder or softer, to even out dynamics with a floor, or to get a constant velocity from an instrument that responds to it. ## Ports <!-- ports:start --> | Port | Direction | Signal | Lanes | | ------ | --------- | ------ | ---------------- | | `midi` | Input | MIDI | 16 MIDI channels | | `midi` | Output | MIDI | 16 MIDI channels | <!-- ports:end --> ## Parameters <!-- parameters:start --> | Parameter | Label | Range | Default | Unit | Scale | Notes | | ------------- | ----------- | ------- | ------- | ---- | ------ | -------------------------------------------------------------------------- | | `scale` | Scale | 0 to 2 | 1 | | linear | Multiplier on incoming velocity. | | `offset` | Offset | -1 to 1 | 0 | | linear | Added after scaling, as a fraction of full velocity. | | `floor` | Floor | 0 to 1 | 0 | | linear | Minimum velocity of any note-on. | | `fixed` | Fixed | 0 to 1 | 0 | | linear | Replace every velocity with Fixed value. Choices: 0 Follow input, 1 Fixed. | | `fixed_value` | Fixed value | 0 to 1 | 0.8 | | linear | Velocity used when Fixed is on. Shown when `fixed` is 1. | <!-- parameters:end --> ### Scale (`scale`) Multiplier on incoming velocity, 0 to 2. ### Offset (`offset`) Added after scaling, as a fraction of full velocity, -1 to 1. ### Floor (`floor`) The minimum velocity of any note-on, 0 to 1. ### Fixed (`fixed`) Follow input leaves velocities to the controls above; Fixed replaces every velocity with Fixed value. A discrete choice. ### Fixed value (`fixed_value`) The velocity used when Fixed is on, 0 to 1. ## Tips - Floor keeps soft notes audible without changing loud ones. - The piano widget's velocity depends on where a key is pressed, so Fixed makes it even. ## See also [Transpose](https://docs.tune.toys/nodes/midi.transpose/), [MIDI channel](https://docs.tune.toys/nodes/midi.channel/), [Polyphonic instrument](https://docs.tune.toys/nodes/std.poly/), [MIDI](https://docs.tune.toys/concepts/midi/). --- URL: https://docs.tune.toys/nodes/sense.body/ Title: Body Body tracks one person's pose and turns it into control lanes. ## What it does Connect a video source to `video`. The `control` output has 11 lanes from 0 to 1, in order: x, y, z, hand.l.x, hand.l.y, hand.r.x, hand.r.y, spread, lean, crouch, present. It runs a MediaPipe model in the browser. The model downloads after the person consents, with its size shown first, and is cached afterwards. Nothing is uploaded. When tracking is lost the lanes hold their last values and `present` falls to 0 after 220 ms. ## When to use it Use it for whole-body control: arm spread, lean and crouch. See [Sensing](https://docs.tune.toys/concepts/sensing/). ## Ports <!-- ports:start --> | Port | Direction | Signal | Lanes | | --------- | --------- | ------- | ----- | | `video` | Input | Video | 1 | | `control` | Output | Control | 11 | <!-- ports:end --> ## Parameters <!-- parameters:start --> | Parameter | Label | Range | Default | Unit | Scale | Notes | | ---------- | ------- | --------- | ------- | ---- | ------ | ------------------------------------------------------------------------ | | `model` | Model | 0 to 2 | 0 | | linear | Smaller is faster; larger is steadier. Choices: 0 Lite, 1 Full, 2 Heavy. | | `mirror` | Mirror | 0 to 1 | 1 | | linear | Flip x for a mirrored picture. Choices: 0 As captured, 1 Mirrored. | | `smooth` | Smooth | 0 to 1000 | 80 | ms | linear | One-pole smoothing of every lane. | | `delegate` | Compute | 0 to 1 | 0 | | linear | Run the model on the GPU or the CPU. Choices: 0 GPU, 1 CPU. | <!-- parameters:end --> ### Model (`model`) Chooses the pose model; larger values trade speed for accuracy. ### Mirror (`mirror`) Flips x so movement matches a mirror when on. ### Smooth (`smooth`) Smoothing time in milliseconds for the lanes. ### Compute (`delegate`) Chooses where the model runs: 0 for the default, 1 for the alternative. ## Tips - Use [Select lanes](https://docs.tune.toys/nodes/ctl.select/) with `"audio": {"channels": 1, "firstChannel": N}` to pick one lane; lanes count from 1. - A one-lane control cable can drive any parameter through its `{ "node": "x", "port": "@param" }` socket. ## See also [Select lanes](https://docs.tune.toys/nodes/ctl.select/), [Scale](https://docs.tune.toys/nodes/ctl.scale/), [Smooth](https://docs.tune.toys/nodes/ctl.smooth/), [Sensing](https://docs.tune.toys/concepts/sensing/), [Control lanes](https://docs.tune.toys/concepts/control-lanes/). --- URL: https://docs.tune.toys/nodes/sense.face/ Title: Face Face tracks one face and turns it into control lanes. ## What it does Connect a video source to `video`. The `control` output has 15 lanes from 0 to 1, in order: x, y, z, yaw, pitch, roll, gaze.x, gaze.y, mouth, smile, blink.l, blink.r, brow, jaw, present. Mouth is lane 9. It runs a MediaPipe model in the browser. The model downloads after the person consents, with its size shown first, and is cached afterwards. Nothing is uploaded. When tracking is lost the lanes hold their last values and `present` falls to 0 after 220 ms. ## When to use it Use it to play with your face: open your mouth to open a filter. See [Sensing](https://docs.tune.toys/concepts/sensing/). ## Ports <!-- ports:start --> | Port | Direction | Signal | Lanes | | --------- | --------- | ------- | ----- | | `video` | Input | Video | 1 | | `control` | Output | Control | 15 | <!-- ports:end --> ## Parameters <!-- parameters:start --> | Parameter | Label | Range | Default | Unit | Scale | Notes | | ---------- | ------- | --------- | ------- | ---- | ------ | -------------------------------------------------------------------------- | | `mirror` | Mirror | 0 to 1 | 1 | | linear | Flip x and yaw for a mirrored picture. Choices: 0 As captured, 1 Mirrored. | | `smooth` | Smooth | 0 to 1000 | 60 | ms | linear | One-pole smoothing of every lane. | | `delegate` | Compute | 0 to 1 | 0 | | linear | Run the model on the GPU or the CPU. Choices: 0 GPU, 1 CPU. | <!-- parameters:end --> ### Mirror (`mirror`) Flips x so movement matches a mirror when on. ### Smooth (`smooth`) Smoothing time in milliseconds for the lanes. ### Compute (`delegate`) Chooses where the model runs: 0 for the default, 1 for the alternative. ## Tips - The 15-lane output cannot plug straight into a parameter; pick a lane first. - Use [Select lanes](https://docs.tune.toys/nodes/ctl.select/) with `"audio": {"channels": 1, "firstChannel": N}` to pick one lane; lanes count from 1. - A one-lane control cable can drive any parameter through its `{ "node": "x", "port": "@param" }` socket. ## See also [Select lanes](https://docs.tune.toys/nodes/ctl.select/), [Scale](https://docs.tune.toys/nodes/ctl.scale/), [Smooth](https://docs.tune.toys/nodes/ctl.smooth/), [Sensing](https://docs.tune.toys/concepts/sensing/), [Control lanes](https://docs.tune.toys/concepts/control-lanes/). --- URL: https://docs.tune.toys/nodes/sense.gesture/ Title: Gestures Gestures recognises seven hand gestures and gives each one a gate lane. ## What it does Connect a video source to `video`. The `control` output has 9 lanes from 0 to 1, in order: closed_fist, open_palm, pointing_up, thumb_down, thumb_up, victory, love, confidence, present. Each gesture lane is a gate. It runs a MediaPipe model in the browser. The model downloads after the person consents, with its size shown first, and is cached afterwards. Nothing is uploaded. When tracking is lost the lanes hold their last values and `present` falls to 0 after 220 ms. ## When to use it Use it for switches and triggers made with a hand sign. See [Sensing](https://docs.tune.toys/concepts/sensing/). ## Ports <!-- ports:start --> | Port | Direction | Signal | Lanes | | --------- | --------- | ------- | ----- | | `video` | Input | Video | 1 | | `control` | Output | Control | 9 | <!-- ports:end --> ## Parameters <!-- parameters:start --> | Parameter | Label | Range | Default | Unit | Scale | Notes | | --------- | ---------- | --------- | ------- | ---- | ------ | ---------------------------------------------------- | | `sure` | Confidence | 0.3 to 1 | 0.75 | | linear | Minimum confidence before a gesture lane goes high. | | `hold_ms` | Hold | 0 to 2000 | 120 | ms | linear | How long a gesture must persist before it registers. | | `smooth` | Smooth | 0 to 1000 | 0 | ms | linear | One-pole smoothing of the confidence lane. | <!-- parameters:end --> ### Confidence (`sure`) Minimum confidence, from 0.3 to 1, before a gesture opens its gate. ### Hold (`hold_ms`) Keeps a gate open this many milliseconds after the gesture ends. ### Smooth (`smooth`) Smoothing time in milliseconds for the lanes. ## Tips - Use [Select lanes](https://docs.tune.toys/nodes/ctl.select/) with `"audio": {"channels": 1, "firstChannel": N}` to pick one lane; lanes count from 1. - A one-lane control cable can drive any parameter through its `{ "node": "x", "port": "@param" }` socket. ## See also [Select lanes](https://docs.tune.toys/nodes/ctl.select/), [Scale](https://docs.tune.toys/nodes/ctl.scale/), [Smooth](https://docs.tune.toys/nodes/ctl.smooth/), [Sensing](https://docs.tune.toys/concepts/sensing/), [Control lanes](https://docs.tune.toys/concepts/control-lanes/). --- URL: https://docs.tune.toys/nodes/sense.hands/ Title: Hands Hands tracks up to two hands from a video stream and turns them into control lanes. ## What it does Connect a video source to `video`. The `control` output has 14 lanes from 0 to 1, in order: l.x, l.y, l.z, l.pinch, l.spread, l.open, l.present, r.x, r.y, r.z, r.pinch, r.spread, r.open, r.present. It runs a MediaPipe model in the browser. The model downloads after the person consents, with its size shown first, and is cached afterwards. Nothing is uploaded. When tracking is lost the lanes hold their last values and `present` falls to 0 after 220 ms. ## When to use it Use it for hands-in-the-air control of a filter, volume or pitch. See [Sensing](https://docs.tune.toys/concepts/sensing/). ## Ports <!-- ports:start --> | Port | Direction | Signal | Lanes | | --------- | --------- | ------- | ----- | | `video` | Input | Video | 1 | | `control` | Output | Control | 14 | <!-- ports:end --> ## Parameters <!-- parameters:start --> | Parameter | Label | Range | Default | Unit | Scale | Notes | | ---------- | ------- | --------- | ------- | ---- | ------ | ------------------------------------------------------------------------------------------------------------ | | `mirror` | Mirror | 0 to 1 | 1 | | linear | Flip x so the person's right hand is on the right of a mirrored picture. Choices: 0 As captured, 1 Mirrored. | | `smooth` | Smooth | 0 to 1000 | 40 | ms | linear | One-pole smoothing of every lane. | | `delegate` | Compute | 0 to 1 | 0 | | linear | Run the model on the GPU or the CPU. Choices: 0 GPU, 1 CPU. | <!-- parameters:end --> ### Mirror (`mirror`) Flips x so the left hand on screen reads as your left when on. ### Smooth (`smooth`) Smoothing time in milliseconds for the lanes. ### Compute (`delegate`) Chooses where the model runs: 0 for the default, 1 for the alternative. ## Tips - Use [Select lanes](https://docs.tune.toys/nodes/ctl.select/) with `"audio": {"channels": 1, "firstChannel": N}` to pick one lane; lanes count from 1. - A one-lane control cable can drive any parameter through its `{ "node": "x", "port": "@param" }` socket. ## See also [Select lanes](https://docs.tune.toys/nodes/ctl.select/), [Scale](https://docs.tune.toys/nodes/ctl.scale/), [Smooth](https://docs.tune.toys/nodes/ctl.smooth/), [Sensing](https://docs.tune.toys/concepts/sensing/), [Control lanes](https://docs.tune.toys/concepts/control-lanes/). --- URL: https://docs.tune.toys/nodes/sense.motion/ Title: Motion Motion turns the device's accelerometer and gyroscope into control lanes. ## What it does It has no inputs. The `control` output has 10 lanes from 0 to 1, in order: accel.x, accel.y, accel.z, rot.alpha, rot.beta, rot.gamma, tilt, heading, shake, present. It needs a device with motion sensors; the browser may ask permission. Last values hold when the sensor stops and `present` falls after 220 ms. Nothing is uploaded. ## When to use it Use it to tilt or shake a phone to play. See [Sensing](https://docs.tune.toys/concepts/sensing/). ## Ports <!-- ports:start --> | Port | Direction | Signal | Lanes | | --------- | --------- | ------- | ----- | | `control` | Output | Control | 10 | <!-- ports:end --> ## Parameters <!-- parameters:start --> | Parameter | Label | Range | Default | Unit | Scale | Notes | | --------- | ------ | --------- | ------- | ---- | ------ | ------------------------------------------------ | | `smooth` | Smooth | 0 to 1000 | 30 | ms | linear | One-pole smoothing of every lane. | | `range` | Range | 0.5 to 8 | 2 | g | linear | Acceleration that maps to the ends of the lanes. | <!-- parameters:end --> ### Smooth (`smooth`) Smoothing time in milliseconds for the lanes. ### Range (`range`) Scales how much motion maps to the full 0 to 1 range. ## Tips - Use [Select lanes](https://docs.tune.toys/nodes/ctl.select/) with `"audio": {"channels": 1, "firstChannel": N}` to pick one lane; lanes count from 1. - A one-lane control cable can drive any parameter through its `{ "node": "x", "port": "@param" }` socket. ## See also [Select lanes](https://docs.tune.toys/nodes/ctl.select/), [Scale](https://docs.tune.toys/nodes/ctl.scale/), [Smooth](https://docs.tune.toys/nodes/ctl.smooth/), [Sensing](https://docs.tune.toys/concepts/sensing/), [Control lanes](https://docs.tune.toys/concepts/control-lanes/). --- URL: https://docs.tune.toys/nodes/std.autotune/ Title: Pitch correction Pitch correction listens to a single voice or instrument, finds the note it is closest to and moves it onto that note. ## What it does The node measures the input's pitch every 5–6 ms with the YIN method, searching between Lowest note and Highest note. Steady tones are measured to well inside a cent. Silence, noise and other sounds without a clear pitch keep the last correction. The target is the nearest note of the Toy's tuning, the same tuning its instruments play, so it follows 19-EDO, just intonation or a custom scale. In a 12-degree tuning, Scale and Key limit the targets to the notes of a scale. When notes are held on the `midi` input, the nearest held note is the target instead. A target changes only when another note is clearly nearer, so a voice sitting between two notes does not flutter. Retune sets how quickly the correction reaches a new target, and Amount how much of the distance it corrects. The correction drives the same splicing shifter as the [Pitch shifter](https://docs.tune.toys/nodes/std.pitch/), with a fixed 40 ms window. Every channel of the cable is corrected together from the pitch of all channels mixed. The node delays everything by 20 ms, and the Patch aligns parallel paths around it. At Amount 0 it is an exact delay. ## When to use it Use it to tighten a sung or played line, for the hard snapped effect at Retune 0, to force a melody into a scale, or with held MIDI notes to play a voice's pitch from a keyboard. It expects one note at a time; chords and drums have no single pitch to correct. ## Ports <!-- ports:start --> | Port | Direction | Signal | Lanes | | ------- | --------- | ------ | ----------------------- | | `audio` | Input | Audio | Inferred from the cable | | `midi` | Input | MIDI | 16 MIDI channels | | `audio` | Output | Audio | Inferred from the cable | <!-- ports:end --> ## Parameters <!-- parameters:start --> | Parameter | Label | Range | Default | Unit | Scale | Notes | | ----------- | ------------ | ----------- | ------- | ---- | ------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `retune_ms` | Retune | 0 to 500 | 40 | ms | linear | Time the correction takes to reach a new target; 0 snaps at once. | | `amount` | Amount | 0 to 1 | 1 | | linear | Fraction of the distance to the target that is corrected. | | `scale` | Scale | 0 to 6 | 0 | | linear | Notes the correction may target. Tuning allows every note of the Toy tuning; the scales apply to 12-degree tunings and allow every note otherwise. Held MIDI notes override it. Choices: 0 Tuning, 1 Major, 2 Minor, 3 Pentatonic major, 4 Pentatonic minor, 5 Blues, 6 Chromatic. | | `key` | Key | 0 to 11 | 0 | | linear | Tonic of the scale. Choices: 0 C, 1 C♯, 2 D, 3 D♯, 4 E, 5 F, 6 F♯, 7 G, 8 G♯, 9 A, 10 A♯, 11 B. Shown when `scale` is 1 or 2 or 3 or 4 or 5. | | `low_hz` | Lowest note | 50 to 1000 | 70 | Hz | log | Lowest pitch the detector looks for. | | `high_hz` | Highest note | 100 to 2000 | 1000 | Hz | log | Highest pitch the detector looks for. | | `mix` | Mix | 0 to 1 | 1 | | linear | Dry/wet blend. The dry signal is delayed by the same latency before blending. | <!-- parameters:end --> ### Retune (`retune_ms`) The time the correction takes to cover 99% of a change in target, 0 to 500 ms. 0 snaps at once for the robotic effect; 20–60 ms is tight but smooth; 100 ms and up lets vibrato and slides through. ### Amount (`amount`) How much of the distance to the target is corrected, from 0 (none) to 1 (all of it). ### Scale (`scale`) The notes the correction may target. Tuning allows every note of the Toy tuning. Major, Minor, Pentatonic major, Pentatonic minor, Blues and Chromatic apply in 12-degree tunings such as 12-TET, just or Pythagorean; in other tunings they allow every note. Held MIDI notes override the scale. ### Key (`key`) The tonic of the scale, C to B. ### Lowest note (`low_hz`) The lowest pitch the detector looks for, 50 to 1000 Hz. ### Highest note (`high_hz`) The highest pitch the detector looks for, 100 to 2000 Hz. If it is set below Lowest note, the two swap. ### Mix (`mix`) The blend of the dry input and the corrected sound. The dry input is delayed by the same 20 ms so the two line up. ## Tips - Set Lowest note and Highest note just around the voice: a tighter range avoids octave mistakes and saves work. - Retune 0 with Scale set to Pentatonic minor gives the hard stepping effect. - Connect a [Keyboard](https://docs.tune.toys/nodes/midi.keyboard/) to `midi` and hold a chord: the voice jumps to whichever held note is nearest. - Change the Toy's tuning to 19-EDO and the same voice snaps to 19 notes per octave. ## See also [Pitch shifter](https://docs.tune.toys/nodes/std.pitch/), [Harmonizer](https://docs.tune.toys/nodes/std.harmonizer/), [Tuner](https://docs.tune.toys/nodes/std.tuner/), [Keyboard](https://docs.tune.toys/nodes/midi.keyboard/), [MIDI](https://docs.tune.toys/concepts/midi/). --- URL: https://docs.tune.toys/nodes/std.chorus/ Title: Chorus Chorus thickens a sound by mixing it with slightly delayed, slowly moving copies of itself. ## What it does Each channel passes through short delay lines whose delay time is modulated by a slow sine at Rate. The modulated copies are mixed with the dry signal at Mix. Chorus uses several taps per channel, Ensemble is a wider variant, and Vibrato outputs only the modulated signal so the pitch wavers without thickening. Spread offsets the modulation phase across every channel, including mono and odd-width bundles. Feedback sends some output back into the delay. ## When to use it Use it on pads, guitars and synth leads that sound thin, or use Vibrato for a pitch wobble. For distinct echoes use [Delay](https://docs.tune.toys/nodes/std.delay/). ## Ports <!-- ports:start --> | Port | Direction | Signal | Lanes | | ------- | --------- | ------ | ----------------------- | | `audio` | Input | Audio | Inferred from the cable | | `audio` | Output | Audio | Inferred from the cable | <!-- ports:end --> ## Parameters <!-- parameters:start --> | Parameter | Label | Range | Default | Unit | Scale | Notes | | ---------- | -------------- | ------------- | ------- | ---- | ------ | -------------------------------------------------------------------------------------------------------------------------- | | `rate` | Rate | 0.05 to 5 | 0.8 | Hz | log | Modulation frequency. | | `depth` | Depth | 0 to 10 | 3 | ms | linear | Modulated delay excursion. | | `delay` | Delay | 1 to 30 | 15 | ms | linear | Center delay; reads stay at least one sample in the past. | | `mix` | Mix | 0 to 1 | 0.4 | | linear | Dry/wet blend. Processing latency is aligned before blending. Shown when `type` is 0 or 1. | | `type` | Type | 0 to 2 | 0 | | linear | Selects the implemented algorithm. Choices are discrete and are not LFO targets. Choices: 0 Chorus, 1 Ensemble, 2 Vibrato. | | `voices` | Voices | 1 to 8 | 3 | | linear | Internal delay taps per channel, independent of the cable channel count. Shown when `type` is 0 or 1. | | `spread` | Channel spread | 0 to 1 | 1 | | linear | Phase distribution across every channel, including mono and odd-width bundles. | | `feedback` | Feedback | -0.85 to 0.85 | 0 | | linear | Signed feedback gain. Matrix rows must have an absolute gain sum of at most one. | <!-- parameters:end --> ### Rate (`rate`) The modulation frequency from 0.05 to 5 Hz. ### Depth (`depth`) How far the delay time swings, from 0 to 10 ms. More depth gives more pitch movement. ### Delay (`delay`) The center delay time, from 1 to 30 ms. ### Mix (`mix`) Dry and wet blend. It is used by the Chorus and Ensemble types. ### Type (`type`) Chorus, Ensemble or Vibrato. A discrete choice. ### Voices (`voices`) The number of internal delay taps per channel (1 to 8), independent of the cable's channel count. Used by Chorus and Ensemble. ### Channel spread (`spread`) How much the modulation phase is distributed across channels, from 0 to 1. ### Feedback (`feedback`) A signed feedback gain, from -0.85 to 0.85. ## Tips - Slow Rate and low Depth sound subtle; faster and deeper sounds more obviously wobbly. - Use Spread at 1 on stereo sources for a wide image. - Small amounts of Feedback add a flanger-like resonance. ## See also [Delay](https://docs.tune.toys/nodes/std.delay/), [Reverb](https://docs.tune.toys/nodes/std.reverb/), [Oscillator](https://docs.tune.toys/nodes/std.oscillator/). --- URL: https://docs.tune.toys/nodes/std.combine/ Title: Combine channels Combine channels joins two audio buses end to end into one wider bundle. Nothing is summed or changed. ## What it does The output channels are those of input A followed by those of input B. Joining a mono signal with a mono signal gives a two-channel bundle, and a stereo bundle with a mono signal gives three channels. Every channel is preserved exactly. The node has no parameters. ## When to use it Use it to build a multichannel bundle from separate sources, or to pair a stereo bed with extra lanes. Use [Select channels](https://docs.tune.toys/nodes/std.select/) to take bundles apart again, [Channel matrix](https://docs.tune.toys/nodes/std.matrix/) to reroute or mix lanes, and [Mixer](https://docs.tune.toys/nodes/std.mixer/) to add signals together. ## Ports <!-- ports:start --> | Port | Direction | Signal | Lanes | | ------- | --------- | ------ | ----------------------- | | `a` | Input | Audio | Inferred from the cable | | `b` | Input | Audio | Inferred from the cable | | `audio` | Output | Audio | Inferred from the cable | <!-- ports:end --> ## Parameters <!-- parameters:start --> This node has no numeric parameters. <!-- parameters:end --> This node has no parameters. Its behavior comes from the width of the signals you connect. The channel count of the combined result is the sum of the channels on A and B, up to the 32-channel limit. ## Tips - Mono to two channels: combine two mono sources, then feed the result into a stereo output. - The output width is a sum; if it does not match the next node's input, use a Channel matrix or Select channels. - See [Multichannel audio](https://docs.tune.toys/concepts/multichannel/) for width rules. ## See also [Select channels](https://docs.tune.toys/nodes/std.select/), [Channel matrix](https://docs.tune.toys/nodes/std.matrix/), [Mixer](https://docs.tune.toys/nodes/std.mixer/), [Cables](https://docs.tune.toys/concepts/cables/). --- URL: https://docs.tune.toys/nodes/std.compare/ Title: Compare Compare turns a signal into 1 or 0 by comparing A with B, lane by lane. It works on audio and on control lanes. ## What it does Operation chooses the test: Greater, Less, Greater or equal, Less or equal, Equal or Not equal. B is the threshold: either the signal on the B input or, when B is not connected, B when unconnected on every lane. The ordering tests use hysteresis so a signal hovering at the threshold does not flicker: Greater switches on when A rises above B and off only when A falls below B minus Hysteresis. Equal treats A and B as equal when they are within Tolerance of each other. Mode chooses the output. Gate stays at 1 while the test is true. Trigger is 1 only on each rising edge: for one sample when the output is audio, for one block when it is control. Each lane keeps its own state, so every channel or lane switches independently. Rate and width follow the cables the same way as in [Math](https://docs.tune.toys/nodes/std.math/): audio on A or B makes an audio output, otherwise the output is control. ## When to use it Use it at control rate to turn a smooth control into an event: open an envelope when an LFO passes a level, step a [Counter](https://docs.tune.toys/nodes/ctl.counter/) or fire a [Sample and hold](https://docs.tune.toys/nodes/ctl.hold/). At audio rate it is a comparator: a crude square shaper, or a gate that shows when a signal goes over a level. ## Ports <!-- ports:start --> | Port | Direction | Signal | Lanes | | ----- | --------- | ------------------------------------ | ----------------------- | | `a` | Input | Audio or control (follows the cable) | Inferred from the cable | | `b` | Input | Audio or control (follows the cable) | Inferred from the cable | | `out` | Output | Audio or control (follows the cable) | Inferred from the cable | <!-- ports:end --> ## Parameters <!-- parameters:start --> | Parameter | Label | Range | Default | Unit | Scale | Notes | | ------------ | ------------------ | ------- | ------- | ---- | ------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `op` | Operation | 0 to 5 | 0 | | linear | How A is tested against B. The output is 1 while the test holds and 0 otherwise. Choices: 0 Greater, 1 Less, 2 Greater or equal, 3 Less or equal, 4 Equal, 5 Not equal. | | `b_default` | B when unconnected | -8 to 8 | 0.5 | | linear | Threshold used for every lane of B when nothing is connected to it. | | `hysteresis` | Hysteresis | 0 to 1 | 0.05 | | linear | How far A must cross back past B before an ordering test switches off again, so noisy signals do not chatter. Shown when `op` is 0 or 1 or 2 or 3. | | `epsilon` | Tolerance | 0 to 1 | 0.001 | | linear | Largest difference between A and B that still counts as equal. Shown when `op` is 4 or 5. | | `mode` | Mode | 0 to 1 | 0 | | linear | Gate stays at 1 while the test holds; Trigger is 1 for one block (control) or one sample (audio) on each rising edge. Choices: 0 Gate, 1 Trigger. | <!-- parameters:end --> ### Operation (`op`) The test, a discrete choice: Greater, Less, Greater or equal, Less or equal, Equal or Not equal. ### B when unconnected (`b_default`) The threshold used on every lane when nothing is connected to B, from -8 to 8. Audio swings between -1 and 1 and control lanes usually between 0 and 1, so useful thresholds sit in those ranges. Changes are smoothed over 5 ms. ### Hysteresis (`hysteresis`) For the ordering tests, how far A must move back past the threshold before the output switches off, from 0 to 1. Larger values ignore more noise but react later. Shown for Greater, Less, Greater or equal and Less or equal. ### Tolerance (`epsilon`) For Equal and Not equal, how close A and B must be, from 0 to 1. A tolerance of 0 asks for exact equality, which continuous signals almost never reach. ### Mode (`mode`) Gate or Trigger, a discrete choice. A trigger is short: one sample at audio rate, one block at control rate, so it needs a destination that reacts to rising edges. ## Tips - To turn a threshold into a gate for an envelope, compare a control lane with Greater and connect the output to the envelope's Gate socket. - Start with a Hysteresis of about 0.05 and raise it if the output chatters. - A gate at audio rate is a 0 to 1 signal at full scale. Put a [Gain](https://docs.tune.toys/nodes/std.gain/) after it before you listen. - Compare an audio signal with a threshold near 1 to make a gate that is on while the signal clips. ## See also [Math](https://docs.tune.toys/nodes/std.math/), [Counter](https://docs.tune.toys/nodes/ctl.counter/), [Sample and hold](https://docs.tune.toys/nodes/ctl.hold/), [Envelope](https://docs.tune.toys/nodes/std.envelope/), [Control lanes](https://docs.tune.toys/concepts/control-lanes/). --- URL: https://docs.tune.toys/nodes/std.compressor/ Title: Compressor A compressor turns loud parts of a signal down, making the level more even. ## What it does A detector measures the signal level, either by peak or by smoothed RMS. When the level goes above Threshold, the compressor reduces the gain according to Ratio, with a soft Knee around the threshold. Attack and Release set how quickly the gain reduction arrives and recovers, and Hold keeps the reduction in place for a moment before recovery. Makeup raises the output afterwards. The detector can be high-passed so low frequencies do not trigger compression, and the detectors of a multichannel bundle can be linked so every channel gets the same reduction. The `key` input is a sidechain. While something is connected to it, the detector measures the key instead of the main input, and the gain it computes is applied to the main input: a kick on the key pushes a pad down on every hit (ducking), and a high-passed copy of a voice on the key turns the voice down only when it hisses (de-essing). The key carries one channel, which then drives every channel of the main input, or exactly as many channels as the main input. Left unconnected, the main input drives the detector exactly as before. A key from a node with processing latency is delayed with the rest of the graph, so it stays aligned with the audio it controls. ## When to use it Use it to even out dynamics, add punch or control a peaky source, or with a key to duck one sound under another and to de-ess. For a hard ceiling that nothing passes, use the [Limiter](https://docs.tune.toys/nodes/std.limiter/); to silence a signal below a level, use the [Gate](https://docs.tune.toys/nodes/std.gate/); to compress bands separately, use the [Multiband compressor](https://docs.tune.toys/nodes/std.multiband/). ## Ports <!-- ports:start --> | Port | Direction | Signal | Lanes | | ------- | --------- | ------ | ----------------------- | | `audio` | Input | Audio | Inferred from the cable | | `key` | Input | Audio | Inferred from the cable | | `audio` | Output | Audio | Inferred from the cable | <!-- ports:end --> ## Parameters <!-- parameters:start --> | Parameter | Label | Range | Default | Unit | Scale | Notes | | -------------- | ------------------ | ---------- | ------- | ---- | ------ | ------------------------------------------------------------------------------------------------------------------------------ | | `threshold` | Threshold | -60 to 0 | -18 | dBFS | linear | Compression starts above this detector level. | | `ratio` | Ratio | 1 to 20 | 4 | :1 | linear | Input/output slope above threshold. | | `attack` | Attack | 0.1 to 100 | 10 | ms | log | Gain-reduction attack time constant. | | `release` | Release | 10 to 2000 | 120 | ms | log | Gain-reduction recovery time constant. | | `knee` | Knee | 0 to 24 | 6 | dB | linear | Width of the quadratic soft knee. | | `makeup` | Makeup | 0 to 24 | 0 | dB | linear | Gain after compression. | | `link` | Channel link | 0 to 1 | 1 | | linear | Blends independent detectors with the strongest detector across the entire bundle. | | `detector` | Detector | 0 to 1 | 0 | | linear | Peak amplitude or smoothed mean-square detection. Choices: 0 Peak, 1 RMS. | | `detector_hp` | Detector high-pass | 0 to 1000 | 0 | Hz | linear | High-pass only in the detector (on the key when it is connected); zero bypasses it. | | `hold` | Hold | 0 to 250 | 0 | ms | linear | Holds attenuation before recovery. | | `auto_release` | Adaptive release | 0 to 1 | 0 | | linear | Blends the chosen release with a slower recovery after sustained reduction. | | `listen` | Listen | 0 to 1 | 0 | | linear | Outputs the filtered detector signal instead of the compressed audio, to tune the key and its high-pass. Choices: 0 Off, 1 On. | <!-- parameters:end --> ### Threshold (`threshold`) The level, from -60 to 0 dBFS, above which compression starts. ### Ratio (`ratio`) The input-to-output slope above the threshold, from 1:1 (no compression) to 20:1 (near limiting). ### Attack (`attack`) How quickly gain reduction arrives, from 0.1 to 100 ms. Longer attacks let more of the initial transient through. ### Release (`release`) How quickly the gain recovers, from 10 to 2000 ms. ### Knee (`knee`) The width, from 0 to 24 dB, of a smooth transition around the threshold. ### Makeup (`makeup`) Gain applied after compression, from 0 to 24 dB. ### Channel link (`link`) From independent detectors (0) to a single detector that follows the strongest channel across the whole bundle (1). ### Detector (`detector`) Peak reacts to the highest sample; RMS follows a smoothed mean-square level and responds more like loudness. ### Detector high-pass (`detector_hp`) A high-pass, 0 to 1000 Hz, applied only to the detector. Zero bypasses it. ### Hold (`hold`) Keeps the gain reduction for up to 250 ms before recovery starts. ### Adaptive release (`auto_release`) Blends the chosen Release with a slower recovery after sustained reduction. ### Listen (`listen`) On replaces the output with the signal the detector measures: the key when it is connected, otherwise the main input, after the detector high-pass. Use it to hear what triggers compression while you set a key filter or Detector high-pass; the gain reduction keeps running underneath, so switching back is seamless. ## Tips - Set Threshold first, then Ratio, then adjust Attack and Release by ear. - Use Makeup to match loudness before and after, so you judge the sound and not the volume. - A detector high-pass keeps heavy bass from pumping the whole mix. - For ducking, use a short Attack, a Release that ends before the next hit, and a high Ratio. - For de-essing, feed the key through a [Filter](https://docs.tune.toys/nodes/std.svf/) set to high-pass, turn Listen on, and raise the cutoff until only the hiss remains. ## See also [Limiter](https://docs.tune.toys/nodes/std.limiter/), [Gate](https://docs.tune.toys/nodes/std.gate/), [Multiband compressor](https://docs.tune.toys/nodes/std.multiband/), [Envelope](https://docs.tune.toys/nodes/std.envelope/), [Gain](https://docs.tune.toys/nodes/std.gain/). --- URL: https://docs.tune.toys/nodes/std.convolver/ Title: Convolver Convolver filters a sound through an attached WAV impulse response, for sampled rooms, speaker cabinets and creative textures. ## What it does The impulse response is loaded and prepared away from the audio callback. The dry input is delayed to align with the processed signal. There is no automatic impulse normalization; adjust the wet gain for the response you attach. The bundled help patches use an original room impulse. ## When to use it Use it to reproduce a measured space or cabinet. Use [Reverb](https://docs.tune.toys/nodes/std.reverb/) for a synthetic room whose decay and damping you can shape continuously. ## Ports <!-- ports:start --> | Port | Direction | Signal | Lanes | | ------- | --------- | ------ | ----------------------- | | `audio` | Input | Audio | Inferred from the cable | | `audio` | Output | Audio | Inferred from the cable | <!-- ports:end --> ## Parameters <!-- parameters:start --> | Parameter | Label | Range | Default | Unit | Scale | Notes | | ------------- | --------- | --------- | ------- | ---- | ------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `mix` | Mix | 0 to 1 | 1 | % | linear | Blend from the dry input, delayed to stay aligned, to the convolved signal. Stored as a fraction; shown ×100 as a percentage. | | `predelay_ms` | Predelay | 0 to 250 | 0 | ms | linear | Delay before the convolved signal, to separate a room's reflections from the dry sound. | | `gain` | Gain | -48 to 24 | 0 | dB | linear | Level of the convolved signal. Impulse responses vary widely in level; there is no automatic normalisation. | | `ir_start` | IR start | 0 to 1 | 0 | % | linear | Where the impulse response starts, as a share of its length. A change rebuilds the response in the background and crossfades to it. Stored as a fraction; shown ×100 as a percentage. | | `ir_length` | IR length | 0 to 1 | 1 | % | linear | How much of the impulse response is used after IR start, as a share of its whole length; shorter shortens a reverb's tail. Stored as a fraction; shown ×100 as a percentage. | | `partition` | Partition | 0 to 4 | 2 | | linear | Block size in samples, which is also the latency. Smaller is quicker but costs more processing. Fixed when the Toy is prepared, at the largest value it admits. Choices: 0 64, 1 128, 2 256, 3 512, 4 1024. | <!-- parameters:end --> <!-- data:start --> - `asset`: asset. <!-- data:end --> ### Mix (`mix`) Blend from the dry input, delayed to stay aligned, to the convolved signal. ### Predelay (`predelay_ms`) Delay before the convolved signal, to separate a room's reflections from the dry sound. ### Gain (`gain`) Level of the convolved signal. Impulse responses vary widely in level; there is no automatic normalisation. ### IR start (`ir_start`) Where the impulse response starts, as a share of its length. A change rebuilds the response in the background and crossfades to it. ### IR length (`ir_length`) How much of the impulse response is used after IR start, as a share of its whole length; shorter shortens a reverb's tail. ### Partition (`partition`) Block size in samples, which is also the latency. Smaller is quicker but costs more processing. Fixed when the Toy is prepared, at the largest value it admits. ## Tips - Attach a licensed WAV impulse through `data.asset` and the Toy's asset table. - Reduce Gain for loud impulses and leave output headroom. - Trimming the response can shorten its tail; Partition trades latency for processing cost. ## See also [Reverb](https://docs.tune.toys/nodes/std.reverb/), [Delay](https://docs.tune.toys/nodes/std.delay/), [Sampler](https://docs.tune.toys/nodes/std.sampler/). --- URL: https://docs.tune.toys/nodes/std.crossfade/ Title: Crossfade Crossfade blends two audio signals with a single balance control. ## What it does The node has two inputs, A and B, and one output. Balance 0 passes input A, balance 1 passes input B, and values between blend them. It is useful for dry/wet-style blends and for switching between two sources smoothly. An input that is not connected is silent. ## When to use it Use it to pick between two sources or two processing chains with one knob. To add several signals together at independent levels, use [Mixer](https://docs.tune.toys/nodes/std.mixer/). ## Ports <!-- ports:start --> | Port | Direction | Signal | Lanes | | ------- | --------- | ------ | ----------------------- | | `a` | Input | Audio | Inferred from the cable | | `b` | Input | Audio | Inferred from the cable | | `audio` | Output | Audio | Inferred from the cable | <!-- ports:end --> ## Parameters <!-- parameters:start --> | Parameter | Label | Range | Default | Unit | Scale | Notes | | --------- | ------- | ------ | ------- | ---- | ------ | ------------------------------------------------------- | | `balance` | Balance | 0 to 1 | 0.5 | | linear | Blend between inputs A and B. 0 selects A; 1 selects B. | <!-- parameters:end --> ### Balance (`balance`) Blend between input A and input B, from 0 to 1. 0 selects A and 1 selects B. The center position blends both. ## Tips - Feed the same source into A and a processed copy into B to build your own dry/wet control. - Both inputs must have matching channel counts unless an adaptation policy is set. See [Multichannel audio](https://docs.tune.toys/concepts/multichannel/). ## See also [Mixer](https://docs.tune.toys/nodes/std.mixer/) (the channel-strip summing node), [Gain](https://docs.tune.toys/nodes/std.gain/). --- URL: https://docs.tune.toys/nodes/std.crossover/ Title: Crossover A crossover splits a sound into frequency bands, so each can be processed on its own and added back together. ## What it does Each split divides the signal into a low and a high band with Linkwitz-Riley filters, the kind used in loudspeaker crossovers: at the split frequency both bands are 6 dB down, and added together they give back the original level at every frequency. Lower bands also pass the allpass response of every split above them, so the phase of all bands lines up and the sum of every band is flat. Two bands split once, at Low split; three bands split at Low split and High split; four bands add Mid split between them. Split frequencies are used lowest first, so the bands always run from low to high. The output is one bundle holding every band, band by band: with a stereo input and three bands it carries six channels, 1–2 the low band, 3–4 the middle and 5–6 the high band. Pick a band with [Select channels](https://docs.tune.toys/nodes/std.select/), process it, and add the bands back with a [Mixer](https://docs.tune.toys/nodes/std.mixer/). Because the number of bands sets how many channels the output carries, Bands is chosen on the node; a control or cable cannot drive it. Slope sets how steeply each band falls past its split: LR12 is 12 dB per octave, LR24 24 dB and LR48 48 dB. Every slope sums flat. At LR12 each upper band is polarity-inverted, which is what makes that slope sum flat. The node adds no latency. ## When to use it Use it to compress, distort, widen or delay one frequency range of a sound and leave the rest alone, or to send lows and highs to different places. For the common case of a three-band compressor in one node, use the [Multiband compressor](https://docs.tune.toys/nodes/std.multiband/); to shape tone without splitting, use the [Parametric EQ](https://docs.tune.toys/nodes/std.eq/). ## Ports <!-- ports:start --> | Port | Direction | Signal | Lanes | | ------- | --------- | ------ | ----------------------- | | `audio` | Input | Audio | Inferred from the cable | | `audio` | Output | Audio | Inferred from the cable | <!-- ports:end --> ## Parameters <!-- parameters:start --> | Parameter | Label | Range | Default | Unit | Scale | Notes | | --------- | ---------- | ------------ | ------- | ---- | ------ | ---------------------------------------------------------------------------------------------------------------------------------------- | | `bands` | Bands | 2 to 4 | 3 | | linear | Sets the output width to bands × input channels, so it is set on the node, never by a control or cable. Choices: 2 Two, 3 Three, 4 Four. | | `low_hz` | Low split | 20 to 2000 | 200 | Hz | log | Top of the lowest band; the only split at two bands. | | `mid_hz` | Mid split | 100 to 8000 | 1000 | Hz | log | Divides the middle into two bands at four bands. Shown when `bands` is 4. | | `high_hz` | High split | 500 to 16000 | 4000 | Hz | log | Bottom of the highest band at three and four bands. Shown when `bands` is 3 or 4. | | `slope` | Slope | 0 to 2 | 1 | | linear | Linkwitz-Riley order: 12, 24 or 48 dB per octave. Every slope sums flat; LR12 inverts each upper band. Choices: 0 LR12, 1 LR24, 2 LR48. | <!-- parameters:end --> ### Bands (`bands`) Two, three or four bands. The output carries this many copies of the input's channels, so it is set on the node, never by a control or cable. ### Low split (`low_hz`) The top of the lowest band, from 20 to 2000 Hz. At two bands it is the only split. ### Mid split (`mid_hz`) Divides the middle into two bands, from 100 to 8000 Hz. Used only at four bands. ### High split (`high_hz`) The bottom of the highest band, from 500 to 16000 Hz, at three and four bands. ### Slope (`slope`) The Linkwitz-Riley order: LR12, LR24 or LR48, for 12, 24 or 48 dB per octave. ## Tips - Keep splits at least an octave apart so each band has room of its own. - LR24 is the usual choice; LR48 isolates bands more sharply, LR12 overlaps them more gently. - Leave a band's path empty to hear the other bands without it. ## See also [Multiband compressor](https://docs.tune.toys/nodes/std.multiband/), [Select channels](https://docs.tune.toys/nodes/std.select/), [Parametric EQ](https://docs.tune.toys/nodes/std.eq/), [Filter](https://docs.tune.toys/nodes/std.svf/). --- URL: https://docs.tune.toys/nodes/std.crush/ Title: Bitcrusher Bitcrusher makes a sound lo-fi by quantizing it to fewer levels and sampling it less often, the grit of early samplers and game consoles. ## What it does A sample-and-hold clock running at Rate captures every channel at once, like the converter of an old sampler. Each capture is rounded to a two's-complement grid of 2^Bits levels from -1 up to one step below full scale, and the held value plays until the next capture. Silence stays silent at any bit depth. Jitter varies each hold period randomly, from a generator seeded by the node's path so every host and replay agrees. Dither adds triangular noise of one step before rounding, which trades the harsh, signal-dependent distortion of a coarse grid for a steady hiss. ### Graphical preview The screen compares a quiet 1 kHz sine with its crushed output over 4 ms, including Mix. Drag the inner right rail for Bits and the outer rail for Rate, or focus either handle and use the arrow keys (Shift for fine adjustment). This is an example of the nominal clock and quantizer; Jitter and Dither are not drawn. The same face is available in node cards, Properties and Frames. ## When to use it Use it for lo-fi drums, chiptune leads and aliased, metallic textures. For smooth saturation use [Drive](https://docs.tune.toys/nodes/std.drive/); follow the crusher with a [State-variable filter](https://docs.tune.toys/nodes/std.svf/) low-pass to tame the aliasing. ## Ports <!-- ports:start --> | Port | Direction | Signal | Lanes | | ------- | --------- | ------ | ----------------------- | | `audio` | Input | Audio | Inferred from the cable | | `audio` | Output | Audio | Inferred from the cable | <!-- ports:end --> ## Parameters <!-- parameters:start --> | Parameter | Label | Range | Default | Unit | Scale | Notes | | --------- | ------ | ----------- | ------- | ---- | ------ | ----------------------------------------------------------------------------------------------------------------------------------- | | `bits` | Bits | 1 to 24 | 8 | | linear | Quantizer resolution: 2^bits levels across full scale, silence stays silent. Fractional values give intermediate steps. | | `rate_hz` | Rate | 50 to 96000 | 12000 | Hz | log | Sample-and-hold rate. At or above the engine rate every sample passes. | | `jitter` | Jitter | 0 to 1 | 0 | | linear | Random variation of each hold period, up to half a period either way at 1. | | `dither` | Dither | 0 to 1 | 0 | | linear | Adds triangular dither of one step before quantizing. It sounds as noise on silence, so its tail is infinite. Choices: 0 Off, 1 On. | | `mix` | Mix | 0 to 1 | 1 | | linear | Dry/wet blend. | <!-- parameters:end --> ### Bits (`bits`) The quantizer resolution, from 1 to 24. Whole values give exactly 2^bits levels: 1 bit is two levels, 8 bits is 256. Fractional values give intermediate step sizes, so the depth can be swept smoothly. ### Rate (`rate_hz`) The sample-and-hold rate, from 50 Hz to 96 kHz. Each held value lasts the engine rate divided by Rate samples; at or above the engine rate every sample passes. ### Jitter (`jitter`) Random variation of each hold period, up to half a period either way at 1. It blurs the pitched whine that a steady low Rate adds. ### Dither (`dither`) Off or On. On adds triangular dither of one step before rounding. Quiet details below one step survive as a noisy version of themselves instead of vanishing, and the dither keeps sounding on silence, so the node then reports an infinite tail. ### Mix (`mix`) Dry and crushed blend. ## Tips - Lower Rate first for the aliasing character, then lower Bits for grit. - Very low bit depths are loud and harsh; turn Mix down or follow with a filter. ## See also [Drive](https://docs.tune.toys/nodes/std.drive/), [State-variable filter](https://docs.tune.toys/nodes/std.svf/), [Noise](https://docs.tune.toys/nodes/std.noise/). --- URL: https://docs.tune.toys/nodes/std.delay/ Title: Delay Delay plays a copy of its input back a little later, and can feed that copy back into itself to make repeating echoes. ## What it does The input is stored and read back after Time, or after a musical Division in Beats mode. Feedback sends the delayed signal back into the delay so each repeat is a quieter copy of the last. Mix blends the echoes with the dry signal, and the processing latency is aligned before blending. Time changes are smoothed, so sweeping Time bends the pitch of the repeats rather than clicking. Four types change what happens to the repeats: Clean leaves them untouched, Filtered and Tape darken them with the feedback tone, and Ducked lowers the echoes while new input is playing so they stay out of the way. ## When to use it Use it for slapback, rhythmic echoes, dub-style repeats and as the building block of other effects. For a sense of space rather than distinct repeats, use [Reverb](https://docs.tune.toys/nodes/std.reverb/). ## Ports <!-- ports:start --> | Port | Direction | Signal | Lanes | | ------- | --------- | ------ | ----------------------- | | `audio` | Input | Audio | Inferred from the cable | | `audio` | Output | Audio | Inferred from the cable | <!-- ports:end --> ## Parameters <!-- parameters:start --> | Parameter | Label | Range | Default | Unit | Scale | Notes | | ---------- | ---------------- | ------------- | ------- | ---- | ------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `time` | Time | 1 to 2000 | 280 | ms | log | Free delay time. Beat divisions follow engine tempo and are capped at the prepared eight-second capacity. Shown when `sync` is 0. | | `feedback` | Feedback | -0.95 to 0.95 | 0.35 | | linear | Signed feedback gain. Matrix rows must have an absolute gain sum of at most one. | | `mix` | Mix | 0 to 1 | 0.3 | | linear | Dry/wet blend. Processing latency is aligned before blending. | | `type` | Type | 0 to 3 | 0 | | linear | Selects the implemented algorithm. Choices are discrete and are not LFO targets. Choices: 0 Clean, 1 Filtered, 2 Tape, 3 Ducked. | | `sync` | Time mode | 0 to 1 | 0 | | linear | Free milliseconds or tempo-synchronized musical divisions. Choices: 0 Milliseconds, 1 Beats. | | `division` | Division | 0 to 8 | 4 | | linear | Musical note duration. 1/1 is four quarter notes, independent of time signature. Choices: 0 1/32, 1 1/16, 2 1/8 triplet, 3 1/8, 4 1/4, 5 1/4 dotted, 6 1/2, 7 1/1, 8 2/1. Shown when `sync` is 1. | | `tone` | Feedback tone | 200 to 20000 | 8000 | Hz | log | Low-pass cutoff; maximum is Open. Shown when `type` is 1 or 2 or 3. | | `duck` | Ducking | 0 to 1 | 0.7 | | linear | Wet attenuation while input is present, with a smoothed detector. Shown when `type` is 3. | | `routing` | Feedback routing | 0 to 2 | 0 | | linear | Independent per-channel feedback, a ring over all channels, or explicit sparse routes set in the Channels section of Properties. Choices: 0 Independent, 1 Ring, 2 Matrix. | <!-- parameters:end --> ### Time (`time`) Free delay time, 1 to 2000 ms. It is used when Time mode is Milliseconds. ### Feedback (`feedback`) A signed gain from -0.95 to 0.95 applied to the signal fed back in. Higher values give more repeats. Negative values invert the polarity of each repeat. The limits keep the loop from running away. ### Mix (`mix`) Dry and wet balance. 0 is only the input, 1 is only the echoes. ### Type (`type`) Selects Clean, Filtered, Tape or Ducked. It is a discrete choice and cannot be modulated smoothly. ### Time mode (`sync`) Milliseconds uses Time. Beats uses Division, which follows the engine tempo. ### Division (`division`) The musical length of one repeat in Beats mode, from 1/32 to 2/1 including triplet and dotted values. 1/1 is four quarter notes regardless of time signature. Delays longer than the eight-second capacity are capped. ### Feedback tone (`tone`) The low-pass cutoff in the feedback path for the Filtered, Tape and Ducked types. At its maximum the setting is Open and does not darken the repeats. ### Ducking (`duck`) For the Ducked type: how much the echoes are lowered while input is present. The detector is smoothed so the echoes return gently. ### Feedback routing (`routing`) Independent keeps each channel in its own loop. Ring feeds each channel into the next. Matrix uses explicit sparse routes set in the node's audio channel settings. Matrix rows must have an absolute gain sum of at most one. ## Tips - Short times (under 80 ms) with low feedback sound like thickening or slapback; long times sound like distinct echoes. - Combine a high Feedback with a low Feedback tone for repeats that fade into darkness. - In Beats mode, change the tempo in the transport to keep echoes in time. - Sweeping Time while sound is repeating gives pitch-shifting effects. ## See also [Reverb](https://docs.tune.toys/nodes/std.reverb/), [Chorus](https://docs.tune.toys/nodes/std.chorus/), [Low-pass filter](https://docs.tune.toys/nodes/std.lowpass/), [Multichannel audio](https://docs.tune.toys/concepts/multichannel/). --- URL: https://docs.tune.toys/nodes/std.denoise/ Title: Noise reduction Turn down steady hiss, hum and room noise while the sound you want passes through. ## What it does Noise reduction splits the sound into about a thousand narrow frequency bins (2048-point frames up to 48 kHz, 4096 above, about 23 Hz apart) and compares each bin with a noise floor. A bin that rises more than Threshold above the floor passes unchanged. A bin that stays near the floor is turned down by Reduction; between the two, the cut eases in over 3 dB. Each bin opens with Attack and closes with Release, then the bins are put back together. The floor comes from one of two places. With Learn, you play only the noise and the node memorises its average level in every bin. With Adaptive on, it follows the quietest recent level of each bin over about two seconds, so it tracks a floor that changes without a Learn pass. The node delays its output by one frame, 2048 samples (about 43 ms at 48 kHz, 4096 above), and reports that latency, so the rest of the Toy stays aligned. A learned profile lives only in the running engine. It is not saved with the Toy and is gone when the Toy is prepared again, for example after a reload or an export, so learn it again then. ## When to use it Use it on recordings, microphones or samples with a steady noise underneath: tape or preamp hiss, a fan, air conditioning, mains hum, a noisy synth. It is not a gate for silences between notes, and it does not remove noises that come and go, like clicks or traffic. ## Ports <!-- ports:start --> | Port | Direction | Signal | Lanes | | ------- | --------- | ------ | ----------------------- | | `audio` | Input | Audio | Inferred from the cable | | `audio` | Output | Audio | Inferred from the cable | <!-- ports:end --> ## Parameters <!-- parameters:start --> | Parameter | Label | Range | Default | Unit | Scale | Notes | | ------------ | --------- | ---------- | ------- | ---- | ------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `learn` | Learn | 0 to 1 | 0 | | linear | While Learning, the input passes unchanged and its average spectrum becomes the noise profile. Play only the noise. Each new Learning starts afresh. Choices: 0 Off, 1 Learning. | | `reduction` | Reduction | 0 to 60 | 18 | dB | linear | How far a bin at the noise floor is turned down. | | `threshold` | Threshold | -12 to 24 | 9 | dB | linear | How far above the noise profile a bin must rise to pass untouched. Bins 3 dB or more below it get the full Reduction. | | `attack_ms` | Attack | 1 to 200 | 10 | ms | log | How fast a bin opens when a sound rises above the threshold. | | `release_ms` | Release | 10 to 2000 | 150 | ms | log | How fast a bin closes again after the sound falls back to the noise. | | `smoothing` | Smoothing | 0 to 1 | 0.3 | % | linear | Averages each bin's level over up to 100 ms before deciding, which steadies the gate and avoids watery, chirping residue. Stored as a fraction; shown ×100 as a percentage. | | `adaptive` | Adaptive | 0 to 1 | 0 | | linear | On follows the noise floor continuously from the quietest recent level of each bin, without Learn. Sounds held steady for over two seconds then count as noise. Choices: 0 Off, 1 On. | | `mix` | Mix | 0 to 1 | 1 | % | linear | Blend from the dry input, delayed to stay aligned, to the reduced signal. Stored as a fraction; shown ×100 as a percentage. | <!-- parameters:end --> ### Learn (`learn`) Switch to Learning while only the noise plays, for a second or more, then back to Off. While Learning, the input passes unchanged. Each new Learning pass replaces the previous profile. The first frame length after you switch it on (about 43 ms), which still holds older sound, is left out. ### Reduction (`reduction`) How far a bin at the noise floor is turned down, from 0 to 60 dB. 12 to 24 dB sounds natural; more removes the noise entirely but can leave the sound thin. ### Threshold (`threshold`) How far above the floor, in dB, a bin must rise to pass untouched. The default of 9 dB keeps random peaks in the noise from opening bins. Lower it to keep quiet detail; raise it if noise still bubbles through. ### Attack and Release (`attack_ms`, `release_ms`) How fast a bin opens when a sound arrives and how fast it closes after. Short attacks keep transients; longer releases let reverb tails and decays fade naturally. ### Smoothing (`smoothing`) Averages each bin's level over up to 100 ms before the decision. More smoothing steadies the gate and avoids a watery, chirping residue; less reacts faster. ### Adaptive (`adaptive`) On, the floor is the quietest recent level of each bin, lifted to match the average of steady noise and smoothed across neighbouring bins. It needs about two seconds of sound to settle, and a sound held perfectly steady for longer than that starts to count as noise. Off uses the learned profile; with no profile, nothing is reduced. ### Mix (`mix`) Blends the reduced signal with the dry input, which is delayed by the same frame so the two stay aligned. ## Tips - Learn on a stretch of pure noise: the start of a recording, or the room before anyone plays. - Start with Reduction around 18 dB and raise it slowly. If the sound turns watery, raise Smoothing or lower Reduction. - Use Adaptive for noise that drifts, such as a fan that speeds up. Use Learn for sustained tones that must never be treated as noise. - Put an EQ before it to cut rumble the node does not need to judge. ## See also [Resonance suppressor](https://docs.tune.toys/nodes/std.resonance/), [Compressor](https://docs.tune.toys/nodes/std.compressor/), [Parametric EQ](https://docs.tune.toys/nodes/std.eq/), [Noise](https://docs.tune.toys/nodes/std.noise/). --- URL: https://docs.tune.toys/nodes/std.drive/ Title: Drive Drive adds harmonics by pushing a signal into a nonlinear shaper. A little adds warmth; a lot adds grit. ## What it does The input is multiplied by Drive (in dB), passed through the chosen shaper, filtered by Tone, blended with the dry signal by Mix and trimmed by Output. The shapers are Soft clip, Hard clip, Asymmetric and Wavefold. A DC blocker removes any constant offset that Bias or the Asymmetric shaper creates. Quality sets filtered oversampling to reduce aliasing; every setting has the same 12-sample processing latency, aligned before the dry and wet signals are blended. ## When to use it Use it to warm a clean tone, thicken bass, or add edge to a synth. Follow it with a [Low-pass filter](https://docs.tune.toys/nodes/std.lowpass/) or its own Tone control to tame brightness, and with a [Limiter](https://docs.tune.toys/nodes/std.limiter/) if the level gets hot. ## Ports <!-- ports:start --> | Port | Direction | Signal | Lanes | | ------- | --------- | ------ | ----------------------- | | `audio` | Input | Audio | Inferred from the cable | | `audio` | Output | Audio | Inferred from the cable | <!-- ports:end --> ## Parameters <!-- parameters:start --> | Parameter | Label | Range | Default | Unit | Scale | Notes | | --------- | ------- | ------------ | ------- | ---- | ------ | --------------------------------------------------------------------------------------------------------------------------------------------- | | `drive` | Drive | 0 to 36 | 9 | dB | linear | Gain into the selected nonlinear shaper. | | `tone` | Tone | 200 to 20000 | 20000 | Hz | log | Low-pass cutoff; maximum is Open. | | `mix` | Mix | 0 to 1 | 1 | | linear | Dry/wet blend. Processing latency is aligned before blending. | | `output` | Output | -24 to 0 | 0 | dB | linear | Post-blend output gain. | | `type` | Type | 0 to 3 | 0 | | linear | Selects the implemented algorithm. Choices are discrete and are not LFO targets. Choices: 0 Soft clip, 1 Hard clip, 2 Asymmetric, 3 Wavefold. | | `bias` | Bias | -0.9 to 0.9 | 0 | | linear | Offset into the shaper. A DC blocker removes persistent offset. | | `quality` | Quality | 0 to 2 | 1 | | linear | Filtered oversampling factor. Every setting has the same 12-sample processing latency. Choices: 0 1×, 1 2× filtered, 2 4× filtered. | <!-- parameters:end --> ### Drive (`drive`) The gain into the shaper, 0 to 36 dB. More drive means more distortion and more loudness. ### Tone (`tone`) A low-pass cutoff on the wet signal, 200 to 20000 Hz. At the maximum it is Open. ### Mix (`mix`) Dry and wet blend. At 1 only the shaped signal is heard; lower values give parallel distortion. ### Output (`output`) A post-blend gain from -24 to 0 dB. Use it to compensate for the loudness added by Drive. ### Type (`type`) Soft clip, Hard clip, Asymmetric or Wavefold. A discrete choice. ### Bias (`bias`) An offset into the shaper, from -0.9 to 0.9. It changes which half of the wave is shaped harder, adding even harmonics. A DC blocker removes the offset afterwards. ### Quality (`quality`) The filtered oversampling factor: 1x, 2x filtered or 4x filtered. Higher settings reduce aliasing at more CPU. ## Tips - Lower Output as you raise Drive, or the result will be much louder. - Soft clip is gentle and sounds warm; Wavefold is more extreme and bright. - Use Mix around 0.3 to 0.5 to keep the original attack and add grit underneath. ## See also [Low-pass filter](https://docs.tune.toys/nodes/std.lowpass/), [Filter](https://docs.tune.toys/nodes/std.svf/), [Limiter](https://docs.tune.toys/nodes/std.limiter/). --- URL: https://docs.tune.toys/nodes/std.envelope/ Title: Envelope An envelope shapes how loud a sound is over time. It listens to a gate: when the gate turns on the sound rises, and when it turns off the sound dies away. ## What it does The envelope multiplies the audio passing through it by a gain that follows an attack, decay, sustain and release shape. A rising edge on Gate starts the envelope. Depending on Type it can be a full ADSR (attack, decay, sustain, release), an AD (attack then decay to zero) or an AR (attack, hold while the gate stays on, release). Curve bends every segment between concave, linear and convex. It can loop for as long as the gate is held. It is a gain stage for audio, not a control signal: put it on the sound path. ### Events The `done` outlet sends an event: it is 1 for one block when the envelope finishes and returns to silence, that is when the release ends after Gate turned off or an AD cycle completes, and 0 otherwise. A looping envelope never finishes, and a Gate that rises during the release restarts it without a `done`. Cable it into a trigger input, such as [Counter](https://docs.tune.toys/nodes/ctl.counter/) or [Sample and hold](https://docs.tune.toys/nodes/ctl.hold/), to advance something each time a note has fully died away. ## When to use it Use it to turn a continuous source such as [Oscillator](https://docs.tune.toys/nodes/std.oscillator/) into notes, to make plucks and swells, or to build rhythmic gating. The [Polyphonic instrument](https://docs.tune.toys/nodes/std.poly/) already has an envelope for each voice. ## Ports <!-- ports:start --> | Port | Direction | Signal | Lanes | | ------- | --------- | ------- | ----------------------- | | `audio` | Input | Audio | Inferred from the cable | | `audio` | Output | Audio | Inferred from the cable | | `done` | Output | Control | 1 | <!-- ports:end --> ## Parameters <!-- parameters:start --> | Parameter | Label | Range | Default | Unit | Scale | Notes | | ------------ | --------- | ---------- | ------- | ---- | ------ | ----------------------------------------------------------------------------------------------------------------- | | `attack_ms` | Attack | 0 to 10000 | 10 | ms | linear | Time to rise to full gain. | | `decay_ms` | Decay | 0 to 10000 | 120 | ms | linear | Time to fall to sustain in ADSR, or zero in AD. Shown when `type` is 0 or 1. | | `sustain` | Sustain | 0 to 1 | 0.7 | % | linear | Held ADSR gain. Shown when `type` is 0. Stored as a fraction; shown ×100 as a percentage. | | `release_ms` | Release | 0 to 10000 | 200 | ms | linear | Time to return to zero. Shown when `type` is 0 or 2. | | `gate` | Gate | 0 to 1 | 0 | | linear | On or off. A rising edge retriggers; held on runs the oscillator or envelope. A control cable switches it at 0.5. | | `type` | Type | 0 to 2 | 0 | | linear | Selects the implemented algorithm. Choices are discrete and are not LFO targets. Choices: 0 ADSR, 1 AD, 2 AR. | | `curve` | Curve | -1 to 1 | 0 | | linear | Continuous concave/linear/convex segment curvature. | | `loop` | Loop | 0 to 1 | 0 | | linear | Repeats complete envelope cycles while Gate is held. Choices: 0 Off, 1 While held. | | `retrigger` | Retrigger | 0 to 1 | 0 | | linear | Restart from the current gain or reset to zero on a rising Gate. Choices: 0 Current level, 1 From zero. | | `hold_ms` | Loop hold | 0 to 10000 | 100 | ms | linear | Duration of the held part in repeating ADSR/AR cycles. Shown when `loop` is 1. | <!-- parameters:end --> ### Attack (`attack_ms`) The time, from 0 to 10000 ms, taken to rise to full gain after the gate turns on. ### Decay (`decay_ms`) The time to fall to the sustain level in ADSR, or to zero in AD. It applies to the ADSR and AD types. ### Sustain (`sustain`) The held gain while the gate stays on in an ADSR envelope, shown from 0 to 100 % of full gain (stored as a fraction from 0 to 1). ### Release (`release_ms`) The time to return to zero after the gate turns off. It applies to the ADSR and AR types. ### Gate (`gate`) The trigger, on (1) or off (0). Turning it on starts the envelope and holding it on keeps it running. Bind it to a momentary button or toggle in a Frame, or cable a control signal in: it counts as on from 0.5. ### Type (`type`) Chooses ADSR, AD or AR. It is a discrete choice. ### Curve (`curve`) Bends the segments, from -1 to 1. Zero is linear; values on either side give concave or convex curvature. ### Loop (`loop`) When On, complete envelope cycles repeat while Gate is held. ### Retrigger (`retrigger`) On a rising Gate edge, either restart from the current gain (smooth) or reset to zero first. ### Loop hold (`hold_ms`) The time the held part lasts in each repeat of a looping ADSR or AR cycle. It is shown only while Loop is on. ## Tips - A short Attack with a medium Decay and zero Sustain makes a pluck. - Looping an AD envelope is a quick way to make a repeating sound for testing effects. - The envelope works on any channel count. - `done` is a one-block pulse, like [Compare](https://docs.tune.toys/nodes/std.compare/) in Trigger mode. Cable it into a trigger input; to sequence notes, let it clock a [Counter](https://docs.tune.toys/nodes/ctl.counter/) that picks the next pitch. ## See also [Oscillator](https://docs.tune.toys/nodes/std.oscillator/), [Polyphonic instrument](https://docs.tune.toys/nodes/std.poly/), [Compressor](https://docs.tune.toys/nodes/std.compressor/). --- URL: https://docs.tune.toys/nodes/std.eq/ Title: Parametric EQ An editable list of up to 32 bands for tone shaping in one node: cut the rumble, tilt the ends, boost or carve the middle, and remove a single ringing frequency. ## What it does Each band is one biquad filter from the RBJ Audio EQ Cookbook, run in canonical ID order, then the Output trim. A band has a switch, a type, a frequency, a gain and a Q. Low cut and High cut remove everything below or above the frequency at 12 dB per octave, with Q as the resonance at the corner. Low shelf and High shelf raise or lower everything below or above the frequency by the gain. Bell boosts or cuts a region around the frequency, and Notch removes a narrow band entirely. A band that is off is bypassed exactly, and every channel of the cable gets its own filter state. Frequencies are capped at 0.49 times the sample rate. The node card draws the summed response over each band's own curve, with one point per band in its own colour. Drag a point across for frequency and up or down for gain; on a cut, up raises Q. The wheel over a focused point sets Q, and double-click resets it. A clicked point keeps its band's region lit until the pointer leaves that region. Double-click empty space on the curve to add a Bell band at that frequency and gain as one Undo step. Bands can also be added, folded, reordered and removed on the card and in the inspector. Drag a grip beyond the card to remove a band; at least one band remains. Reordering preserves each band’s controls, cables and colour and keeps audio playing. While a Toy plays, the face you are working on draws the output spectrum under the curves. Hold Alt (Option on a Mac) with the pointer over a band's point, or inside the region of the band you clicked, to solo that band: you hear only its region, and the screen says "Solo band N". A bell or notch solos as a band-pass at its frequency and Q; a low cut or low shelf as a low-pass at its corner, and a high shelf or high cut as a high-pass. The solo plays at the input's level, without the Output trim, and fades in and out over 5 ms. Release Alt, move off the band or press Escape to stop. Soloing is for listening only: it never changes the Toy, its undo history or anything saved. ## When to use it Use it to fit a sound into a mix: take low end out of a pad, add air to a lead, dip a boxy middle, or notch out a hum. For a resonant sweep or a morphing filter use the [Filter](https://docs.tune.toys/nodes/std.svf/); for one gentle roll-off use the [Low-pass filter](https://docs.tune.toys/nodes/std.lowpass/). ## Ports <!-- ports:start --> | Port | Direction | Signal | Lanes | | ------- | --------- | ------ | ----------------------- | | `audio` | Input | Audio | Inferred from the cable | | `audio` | Output | Audio | Inferred from the cable | <!-- ports:end --> ## Parameters <!-- parameters:start --> | Parameter | Label | Range | Default | Unit | Scale | Notes | | -------------- | -------------- | ----------- | ------- | ---- | ------ | --------------------------------------------------------------------------------------------------------------------------------------- | | `output` | Output | -24 to 24 | 0 | dB | linear | Output trim after every band. | | `band.enabled` | band Enabled | 0 to 1 | 1 | | linear | Turns this band on. An off band is bypassed exactly. Choices: 0 Off, 1 On. | | `band.type` | band Type | 0 to 5 | 2 | | linear | this band shape. Cuts are 12 dB/oct and use Q as resonance. Choices: 0 Low cut, 1 Low shelf, 2 Bell, 3 Notch, 4 High shelf, 5 High cut. | | `band.freq` | band Frequency | 20 to 20000 | 1000 | Hz | log | this band centre or corner frequency, capped at 0.49 times sample rate. | | `band.gain` | band Gain | -24 to 24 | 0 | dB | linear | this band boost or cut for shelves and bells. Shown when `band.type` is 1 or 2 or 4. | | `band.q` | band Q | 0.1 to 18 | 0.707 | Q | log | this band width for bells and notches, resonance for cuts, slope for shelves. | <!-- parameters:end --> <!-- data:start --> - `bands`: 1–32 items, each with a durable `id` and optional `name`, `color`, `icon`. Item order affects presentation only; ports and controls use the item ID. <!-- data:end --> ### Band switches (`band.enabled`) Turn a band on or off. Bands 1 and 6 start off, so the default EQ is flat with every band ready to use. An off band leaves the signal untouched, and when it comes back on it starts from rest. ### Band types (`band.type`) Low cut, Low shelf, Bell, Notch, High shelf or High cut. Any band can take any type; the defaults run from a low cut on band 1 to a high cut on band 6. ### Frequencies (`band.freq`) The centre of a bell or notch, or the corner of a shelf or cut, from 20 Hz to 20 kHz on a logarithmic control. ### Gains (`band.gain`) Boost or cut in dB, from −24 to +24, for shelves and bells. Cuts and notches have no gain, so it is hidden for them. ### Q (`band.q`) From 0.1 to 18. On a bell or notch, higher Q is narrower. On a cut, Q is the resonance at the corner: 0.707 is flat, higher values peak. On a shelf, 0.707 is the steepest slope without overshoot; higher values add a bump at the corner. ### Output (`output`) A trim in dB after every band, so a boost does not push the next node into clipping. ## Tips - Cut before you boost: a narrow cut is usually cleaner than a wide boost. - Sweep a narrow bell with a high gain to find a ringing frequency, then flip the gain to cut it. Holding Alt while you drag solos the band, so you hear only the region you are sweeping. - A low cut around 30 to 80 Hz removes rumble you cannot hear but a speaker still has to play. - Use Output to match the level with the EQ on and off, so you judge the tone and not the loudness. ## See also [Filter](https://docs.tune.toys/nodes/std.svf/), [Low-pass filter](https://docs.tune.toys/nodes/std.lowpass/), [Compressor](https://docs.tune.toys/nodes/std.compressor/), [Oscillator](https://docs.tune.toys/nodes/std.oscillator/). --- URL: https://docs.tune.toys/nodes/std.gain/ Title: Gain Gain multiplies a signal by a number. It is the simplest way to turn something up or down. ## What it does Every channel of the incoming audio is multiplied by Level. A level of 0 is silence, 1 leaves the signal unchanged and 2 doubles it. Gain changes only the level: it adds no tone, delay or filtering. The change is smoothed so moving the control does not click. ## When to use it Use it to trim a loud source before an effect, to set the final level of a Toy, or as the node you expose as a master volume. To combine several signals use [Mixer](https://docs.tune.toys/nodes/std.mixer/); to blend two use [Crossfade](https://docs.tune.toys/nodes/std.crossfade/). ## Ports <!-- ports:start --> | Port | Direction | Signal | Lanes | | ------- | --------- | ------ | ----------------------- | | `audio` | Input | Audio | Inferred from the cable | | `audio` | Output | Audio | Inferred from the cable | <!-- ports:end --> ## Parameters <!-- parameters:start --> | Parameter | Label | Range | Default | Unit | Scale | Notes | | --------- | ----- | ------ | ------- | ------ | ------ | ------------------------------------------------------------------------------------ | | `level` | Level | 0 to 2 | 1 | linear | linear | Multiplies every audio channel. 0 silences the signal; 1 leaves its level unchanged. | <!-- parameters:end --> ### Level (`level`) The multiplier applied to every channel, from 0 to 2. Values above 1 boost the signal and can push the output past full scale. Put a [Limiter](https://docs.tune.toys/nodes/std.limiter/) after a boost you cannot avoid. ## Tips - Gain is linear, not decibels. Halving the value is roughly a 6 dB cut. - A gain of 1 is a good default for a pass-through node you plan to expose later. - Gain works the same on mono, stereo and larger bundles. ## See also [Mixer](https://docs.tune.toys/nodes/std.mixer/), [Crossfade](https://docs.tune.toys/nodes/std.crossfade/), [Limiter](https://docs.tune.toys/nodes/std.limiter/). --- URL: https://docs.tune.toys/nodes/std.gate/ Title: Gate A gate turns a signal down while it is quiet: hiss between notes, spill between drum hits, or anything below a level you choose. ## What it does A detector follows each channel's peak level. While it is above Threshold the gate is open and the audio passes unchanged. When it falls below, the gate waits for Hold, then closes over Release; when the level returns it opens over Attack. Closed means turned down by Range: −80 dB is near silence, −10 dB only softens the gaps. With Ratio below its top value the node is a downward expander: below threshold, every decibel the level falls takes Ratio − 1 more decibels off the output, never more than Range. At the top of the Ratio range, 20, it is a gate and closes fully. The `key` input is a sidechain. While something is connected to it, the key's level opens and closes the gate and the main input is what is heard: a beat on the key chops a sustained pad into a rhythm. The key carries one channel, which then drives every channel of the main input, or exactly as many channels as the main input. Left unconnected, the main input drives the detector. The detector high-pass filters only what the detector measures, and Listen plays that filtered signal so you can hear what opens the gate. Lookahead delays the audio by up to 10 ms while the detector reads it undelayed, so the gate is already open when an onset arrives. The node reports a fixed processing latency equal to the largest Lookahead its control can reach, and the rest of the graph is delayed to match; a smaller Lookahead inside that range moves only the detector, never the latency. ## When to use it Use it to clean up noise between phrases, tighten drums, or, with a key, to gate one sound with the rhythm of another. To turn loud parts down rather than quiet parts, use the [Compressor](https://docs.tune.toys/nodes/std.compressor/); to change the attack and decay of hits without a threshold, use the [Transient shaper](https://docs.tune.toys/nodes/std.transient/). ## Ports <!-- ports:start --> | Port | Direction | Signal | Lanes | | ------- | --------- | ------ | ----------------------- | | `audio` | Input | Audio | Inferred from the cable | | `key` | Input | Audio | Inferred from the cable | | `audio` | Output | Audio | Inferred from the cable | <!-- ports:end --> ## Parameters <!-- parameters:start --> | Parameter | Label | Range | Default | Unit | Scale | Notes | | ------------- | ------------------ | ----------- | ------- | ---- | ------ | ----------------------------------------------------------------------------------------------------- | | `threshold` | Threshold | -80 to 0 | -40 | dBFS | linear | The gate is open while the detector level is above this. | | `range` | Range | -80 to 0 | -80 | dB | linear | Attenuation when fully closed; 0 dB leaves the audio untouched. | | `ratio` | Ratio | 1 to 20 | 20 | :1 | linear | Downward expansion below threshold; 20 closes fully, a gate. | | `attack` | Attack | 0.01 to 100 | 0.5 | ms | log | How quickly the gate opens. | | `hold` | Hold | 0 to 500 | 20 | ms | linear | How long the gate stays open after the level falls below threshold. | | `release` | Release | 1 to 2000 | 80 | ms | log | How quickly the gate closes after the hold. | | `lookahead` | Lookahead | 0 to 10 | 0 | ms | linear | Delays the audio so the gate opens before an onset; adds latency equal to its largest admitted value. | | `detector_hp` | Detector high-pass | 0 to 2000 | 0 | Hz | linear | High-pass only in the detector (on the key when it is connected); zero bypasses it. | | `listen` | Listen | 0 to 1 | 0 | | linear | Outputs the filtered detector signal instead of the gated audio. Choices: 0 Off, 1 On. | | `link` | Channel link | 0 to 1 | 1 | | linear | Blends independent detectors with the strongest detector across the bundle. | <!-- parameters:end --> ### Threshold (`threshold`) The detector level, from −80 to 0 dBFS, above which the gate is open. ### Range (`range`) How far the gate turns the audio down when fully closed, from 0 dB (no change) to −80 dB. A closed gate attenuates by exactly this amount. ### Ratio (`ratio`) Downward expansion below the threshold, from 1:1 (none) to 20, which closes fully like a classic gate. In between, the further the level falls below the threshold, the more it is turned down, until Range stops it. ### Attack (`attack`) How quickly the gate opens, from 0.01 to 100 ms. Very short attacks can click on low notes; Lookahead lets you keep them short without losing the onset. ### Hold (`hold`) How long, up to 500 ms, the gate stays open after the level falls below the threshold, so it does not chatter on short dips. ### Release (`release`) How quickly the gate closes after the hold, from 1 to 2000 ms. ### Lookahead (`lookahead`) Delays the audio by up to 10 ms so the gate opens before each onset. It adds latency equal to its largest reachable value. ### Detector high-pass (`detector_hp`) A high-pass, 0 to 2000 Hz, applied only to the detector (on the key when it is connected). Zero bypasses it. Raising it keeps bass from opening the gate. ### Listen (`listen`) On replaces the output with the signal the detector measures, after its high-pass. The gate keeps working underneath. ### Channel link (`link`) From independent detectors per channel (0) to one detector that follows the loudest channel across the bundle (1), so every channel opens and closes together. ## Tips - Set Threshold just above the noise you want gone, then lower Range until the gaps are as quiet as you like. - Raise Hold before raising Release when a gate flutters on decaying notes. - Use a Ratio of 2 to 4 for a natural expander that softens noise instead of cutting it. - A keyed gate on a pad, driven by a kick or a hi-hat, is a quick trance gate. ## See also [Compressor](https://docs.tune.toys/nodes/std.compressor/), [Transient shaper](https://docs.tune.toys/nodes/std.transient/), [Envelope](https://docs.tune.toys/nodes/std.envelope/), [Filter](https://docs.tune.toys/nodes/std.svf/). --- URL: https://docs.tune.toys/nodes/std.granulator/ Title: Granulator Granulator chops the live input into short windowed grains and replays them from the recent past, turning any sound into clouds, stutters, shimmering textures or a frozen drone. ## What it does The input is written into a circular buffer prepared for the largest admitted Position plus Spray plus the longest grain at its fastest read. New grains start at Density per second, or on a grid of Beats locked to the shared transport. Each grain reads Grain size of audio from Position behind the live input (plus a random Spray), transposed by Pitch and a random Pitch spray, backwards with the Reverse probability, and balanced across the channels by a random amount up to Width. A Hann window fades every grain in and out. Up to 32 grains sound at once; a grain that finds every voice busy is skipped. The sum is normalized by the energy of the windows that are sounding, so dense clouds keep a steady level. Every random choice comes from a generator seeded by the node's path, so the same Toy sounds identical on every host and in offline renders. Freeze stops writing the buffer: grains keep replaying what it holds, even after the input falls silent. ## When to use it Use it for ambient textures from any source, glitchy stutters on drums, octave shimmer, or a held freeze drone under a performance. For clean repeats use [Delay](https://docs.tune.toys/nodes/std.delay/); for a smooth wash use [Reverb](https://docs.tune.toys/nodes/std.reverb/). ## Ports <!-- ports:start --> | Port | Direction | Signal | Lanes | | ------- | --------- | ------ | ----------------------- | | `audio` | Input | Audio | Inferred from the cable | | `audio` | Output | Audio | Inferred from the cable | <!-- ports:end --> ## Parameters <!-- parameters:start --> | Parameter | Label | Range | Default | Unit | Scale | Notes | | ------------- | ----------- | ---------- | ------- | ----- | ------ | ------------------------------------------------------------------------------------------------------------------------------------- | | `size_ms` | Grain size | 10 to 1000 | 90 | ms | log | Length of each grain's Hann window, fixed when the grain starts. | | `density_hz` | Density | 0.5 to 200 | 16 | Hz | log | New grains per second when Beats is 0. A grain that finds all 32 voices busy is skipped. | | `position_ms` | Position | 0 to 2000 | 120 | ms | linear | How far behind the live input each grain starts reading. The buffer is sized for the largest admitted value. | | `spray_ms` | Spray | 0 to 1000 | 40 | ms | linear | Random extra distance back for each grain. | | `pitch` | Pitch | -24 to 24 | 0 | st | linear | Grain transposition in semitones. | | `pitch_spray` | Pitch spray | 0 to 24 | 0 | st | linear | Random transposition of each grain up to this many semitones either way; the total stays within ±24. | | `reverse` | Reverse | 0 to 1 | 0 | | linear | Probability that a grain plays backwards. | | `width` | Width | 0 to 1 | 0.5 | | linear | Random balance of each grain across the channels. | | `freeze` | Freeze | 0 to 1 | 0 | | linear | Stops writing the live buffer so grains keep replaying what it holds. A frozen buffer reports an infinite tail. Choices: 0 Off, 1 On. | | `beats` | Beats | 0 to 16 | 0 | beats | linear | Beats between grains, phase-locked to the shared transport. 0 uses Density instead. | | `mix` | Mix | 0 to 1 | 0.5 | | linear | Dry/wet blend. | <!-- parameters:end --> ### Grain size (`size_ms`) The length of each grain's window, from 10 to 1000 ms. It is fixed when the grain starts. Short grains sound buzzy and rhythmic, long grains smooth and smeared. ### Density (`density_hz`) New grains per second, from 0.5 to 200, when `beats` is 0. Density times Grain size is roughly how many grains overlap. ### Position (`position_ms`) How far behind the live input each grain starts reading, from 0 to 2000 ms. Grains that play faster than the input start far enough back that they never catch up with it. ### Spray (`spray_ms`) A random extra distance back for each grain, up to this many milliseconds, which scatters the cloud in time. ### Pitch (`pitch`) Grain transposition in semitones, from -24 to 24. ### Pitch spray (`pitch_spray`) A random transposition of each grain up to this many semitones either way. The total stays within ±24. ### Reverse (`reverse`) The probability that a grain plays backwards, from 0 (never) to 1 (always). ### Width (`width`) How far each grain is randomly balanced across the channels. At 0 every grain sits in the middle. ### Freeze (`freeze`) Off or On. On stops writing the live buffer, so the grains keep replaying what it holds. A Toy that can freeze reports an infinite tail. ### Beats (`beats`) Beats between grains, phase-locked to the shared transport: 0.25 starts a grain every sixteenth note. At 0 grains follow `density_hz`. While the transport is stopped, synced grains do not start. ### Mix (`mix`) Dry and grain blend. ## Tips - Raise Spray and Width with a long Grain size for a wide ambient cloud. - Pitch 12 with a little Pitch spray gives an octave shimmer; add Reverse for a backwards bloom. - Freeze a chord, then play over the top of the drone it leaves. - Large Position, Spray and Grain size values make the prepared buffer larger: up to 8 seconds per channel. ## See also [Delay](https://docs.tune.toys/nodes/std.delay/), [Reverb](https://docs.tune.toys/nodes/std.reverb/), [Chorus](https://docs.tune.toys/nodes/std.chorus/). --- URL: https://docs.tune.toys/nodes/std.harmonizer/ Title: Harmonizer Harmonizer turns one note into a chord: it adds up to three copies of the input, each shifted by its own interval. ## What it does Each voice is a [Pitch shifter](https://docs.tune.toys/nodes/std.pitch/) on one shared delay line. Its Interval sets the shift in semitones, its Level its gain and its Pan its place in the stereo field. Dry sets the input level and Wet the level of all voices together. The input is delayed by the same amount as the voices, so everything lines up. A voice at Level 0 is switched off and costs nothing. Pan works on a two-lane (stereo) cable with the equal-power law of the [Bus](https://docs.tune.toys/nodes/std.mixer/): −1 is left, 1 is right, and the centre sends each lane the level times about 0.71. On a mono or wider cable, Pan has no effect and every lane gets the voice at its Level. The node delays everything by half its largest Window (20 ms at the default 40 ms), and the Patch aligns parallel paths around it. ## When to use it Use it for instant thirds and fifths on a lead line, an octave below a melody, or stacked, panned backing voices. The intervals are fixed, so they suit a melody that stays in one key or chord; set them to a scale's intervals or change them with the music. For a single shifted copy with feedback, use the [Pitch shifter](https://docs.tune.toys/nodes/std.pitch/). ## Ports <!-- ports:start --> | Port | Direction | Signal | Lanes | | ------- | --------- | ------ | ----------------------- | | `audio` | Input | Audio | Inferred from the cable | | `audio` | Output | Audio | Inferred from the cable | <!-- ports:end --> ## Parameters <!-- parameters:start --> | Parameter | Label | Range | Default | Unit | Scale | Notes | | ----------- | ---------- | --------- | ------- | ---- | ------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `interval1` | Interval 1 | -24 to 24 | 4 | st | linear | Pitch of voice 1 relative to the input, in semitones. | | `level1` | Level 1 | 0 to 1 | 0.7 | | linear | Gain of voice 1; 0 switches the voice off. | | `pan1` | Pan 1 | -1 to 1 | -0.5 | | linear | Stereo position of voice 1 on a two-lane bundle: −1 left, 0 centre, 1 right, equal power. Ignored on other widths. | | `interval2` | Interval 2 | -24 to 24 | 7 | st | linear | Pitch of voice 2 relative to the input, in semitones. | | `level2` | Level 2 | 0 to 1 | 0.7 | | linear | Gain of voice 2; 0 switches the voice off. | | `pan2` | Pan 2 | -1 to 1 | 0.5 | | linear | Stereo position of voice 2 on a two-lane bundle: −1 left, 0 centre, 1 right, equal power. Ignored on other widths. | | `interval3` | Interval 3 | -24 to 24 | 12 | st | linear | Pitch of voice 3 relative to the input, in semitones. | | `level3` | Level 3 | 0 to 1 | 0 | | linear | Gain of voice 3; 0 switches the voice off. | | `pan3` | Pan 3 | -1 to 1 | 0 | | linear | Stereo position of voice 3 on a two-lane bundle: −1 left, 0 centre, 1 right, equal power. Ignored on other widths. | | `dry` | Dry | 0 to 1 | 1 | | linear | Level of the input, delayed by the same latency as the voices. | | `wet` | Wet | 0 to 1 | 0.7 | | linear | Level of the summed voices. | | `window_ms` | Window | 10 to 100 | 40 | ms | linear | Splice window. Longer windows smear transients less often and splice low notes cleanly; shorter ones respond faster. Latency is half the largest admitted window. | <!-- parameters:end --> ### Interval 1, 2 and 3 (`interval1`, `interval2`, `interval3`) Each voice's shift from −24 to 24 semitones: 3 is a minor third, 4 a major third, 7 a fifth, 12 an octave. Fractional values detune. ### Level 1, 2 and 3 (`level1`, `level2`, `level3`) Each voice's gain from 0 to 1. At 0 the voice is off. ### Pan 1, 2 and 3 (`pan1`, `pan2`, `pan3`) Each voice's place on a stereo cable, from −1 (left) to 1 (right), equal power. Ignored on other widths. ### Dry (`dry`) The level of the input, delayed to line up with the voices. ### Wet (`wet`) The level of all voices together. ### Window (`window_ms`) How much audio each splice spans, 10 to 100 ms, for every voice. Longer windows suit low notes and big intervals; shorter ones keep fast notes tight. The node's latency is half the largest value the Window can reach. ## Tips - Intervals 4 and 7 make a major triad, 3 and 7 a minor one. - Pan the third and fifth apart and keep an octave below in the centre for width without losing the root. - A voice at interval 0 with Level 0.5 and a small fractional detune, such as 0.08, thickens like a double. ## See also [Pitch shifter](https://docs.tune.toys/nodes/std.pitch/), [Pitch correction](https://docs.tune.toys/nodes/std.autotune/), [Chorus](https://docs.tune.toys/nodes/std.chorus/), [Bus](https://docs.tune.toys/nodes/std.mixer/), [Multichannel audio](https://docs.tune.toys/concepts/multichannel/). --- URL: https://docs.tune.toys/nodes/std.limiter/ Title: Limiter A limiter keeps a signal under a ceiling. Put one at the end of a chain to protect against sudden loud peaks. ## What it does The limiter looks at each sample and turns the gain down just enough that the output does not exceed Ceiling. It adds no latency. Release sets how quickly the gain recovers and Hold keeps the reduction for a moment first. Channel link ties all channels of a bundle to the strongest one so the image does not shift. It limits sample peaks only. It is not a true-peak limiter, so peaks between samples can still exceed the ceiling after digital-to-analog conversion. ## When to use it Use it as a final safety stage, after gain boosts or [Drive](https://docs.tune.toys/nodes/std.drive/), or on a sum of several sources. For smoother level control use the [Compressor](https://docs.tune.toys/nodes/std.compressor/). ## Ports <!-- ports:start --> | Port | Direction | Signal | Lanes | | ------- | --------- | ------ | ----------------------- | | `audio` | Input | Audio | Inferred from the cable | | `audio` | Output | Audio | Inferred from the cable | <!-- ports:end --> ## Parameters <!-- parameters:start --> | Parameter | Label | Range | Default | Unit | Scale | Notes | | -------------- | ---------------- | --------- | ------- | ---- | ------ | ---------------------------------------------------------------------------------- | | `ceiling` | Ceiling | -24 to 0 | -1 | dBFS | linear | Sample-peak ceiling. This is not an intersample true-peak guarantee. | | `release` | Release | 1 to 1000 | 80 | ms | log | Gain-reduction recovery time constant. | | `hold` | Hold | 0 to 250 | 0 | ms | linear | Holds attenuation before recovery. | | `auto_release` | Adaptive release | 0 to 1 | 0 | | linear | Blends the chosen release with a slower recovery after sustained reduction. | | `link` | Channel link | 0 to 1 | 1 | | linear | Blends independent detectors with the strongest detector across the entire bundle. | <!-- parameters:end --> ### Ceiling (`ceiling`) The highest sample level allowed, from -24 to 0 dBFS. The default is -1 dBFS. ### Release (`release`) The gain recovery time, from 1 to 1000 ms. ### Hold (`hold`) Holds gain reduction for up to 250 ms before recovery begins. ### Adaptive release (`auto_release`) Blends the chosen Release with a slower recovery after sustained reduction. ### Channel link (`link`) From independent detectors (0) to one detector following the strongest channel across the bundle (1). ## Tips - Heavy limiting makes sound flatter and louder. Stop when it stops sounding natural. - Leave a little headroom with a ceiling around -1 dBFS. - Use Adaptive release to avoid pumping on sustained loud material. ## See also [Compressor](https://docs.tune.toys/nodes/std.compressor/), [Gain](https://docs.tune.toys/nodes/std.gain/), [Drive](https://docs.tune.toys/nodes/std.drive/). --- URL: https://docs.tune.toys/nodes/std.lowpass/ Title: Low-pass filter A simple filter that lets low frequencies through and softens high ones. It is the quickest way to make a sound warmer or darker. ## What it does A one-pole low-pass filter reduces everything above its cutoff gently, at about 6 dB per octave. It has no resonance, so it never rings or whistles. It is applied to every channel of the incoming audio. ## When to use it Use it to take the edge off a bright source, to tame a harsh distortion or to darken feedback inside an echo. When you need resonance, a steeper slope or other filter types, use the [Filter](https://docs.tune.toys/nodes/std.svf/). ## Ports <!-- ports:start --> | Port | Direction | Signal | Lanes | | ------- | --------- | ------ | ----------------------- | | `audio` | Input | Audio | Inferred from the cable | | `audio` | Output | Audio | Inferred from the cable | <!-- ports:end --> ## Parameters <!-- parameters:start --> | Parameter | Label | Range | Default | Unit | Scale | Notes | | --------- | ------ | ----------- | ------- | ---- | ----- | ------------------------------------------------------------------------- | | `cutoff` | Cutoff | 40 to 18000 | 3200 | Hz | log | Sets the low-pass cutoff frequency. Lower values soften high frequencies. | <!-- parameters:end --> ### Cutoff (`cutoff`) The frequency, from 40 to 18000 Hz, above which the sound is gradually reduced. The control is logarithmic, so each step covers a similar musical distance. ## Tips - Cutoff is a gradual slope, not a wall: sounds well above it are quieter, not gone. - Place it before an effect to darken what feeds the effect, or after to darken the result. - For a sweep with a peak, use the [Filter](https://docs.tune.toys/nodes/std.svf/) instead. ## See also [Filter](https://docs.tune.toys/nodes/std.svf/), [Drive](https://docs.tune.toys/nodes/std.drive/), [Delay](https://docs.tune.toys/nodes/std.delay/). --- URL: https://docs.tune.toys/nodes/std.math/ Title: Math Math does arithmetic on signals, lane by lane. Feed it audio and it works on every sample; feed it control lanes and it works once per block. ## What it does Operation chooses how lane n of A and lane n of B become lane n of the output. The two-operand operations are Add, Subtract, Multiply, Divide, Modulo, Power, Minimum, Maximum, Average and Absolute difference; the one-operand operations (Negate, Absolute, Floor, Ceiling, Round, Square root, Sign) use A and ignore B. The inputs follow their cables. If A or B carries audio, the output is audio; if both carry control, or nothing at all, the output is control. A control operand against an audio operand is read lane by lane, so a one-lane control B applies to every audio channel and a two-lane control B applies one value per channel. When B is not connected, every lane of B is B when unconnected. When A is not connected it is 0. Every operation is defined for every input: dividing by 0 gives 0, the square root and power of a negative number keep its sign, and any result that would not be a finite number becomes 0. Math never outputs NaN or infinity. ## When to use it Use it to scale a signal (Multiply), to offset a control (Add), to ring-modulate two oscillators (Multiply on two audio inputs), to keep a level inside a limit (Minimum, Maximum) or to quantise a control (Round, Floor). For a plain level control with a public knob, [Gain](https://docs.tune.toys/nodes/std.gain/) is simpler. To turn a signal into a gate, use [Compare](https://docs.tune.toys/nodes/std.compare/). ## Ports <!-- ports:start --> | Port | Direction | Signal | Lanes | | ----- | --------- | ------------------------------------ | ----------------------- | | `a` | Input | Audio or control (follows the cable) | Inferred from the cable | | `b` | Input | Audio or control (follows the cable) | Inferred from the cable | | `out` | Output | Audio or control (follows the cable) | Inferred from the cable | <!-- ports:end --> ## Parameters <!-- parameters:start --> | Parameter | Label | Range | Default | Unit | Scale | Notes | | ----------- | ------------------ | ------- | ------- | ---- | ------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `op` | Operation | 0 to 16 | 0 | | linear | The lane-wise operation. Add through Absolute difference combine A and B; Negate through Sign transform A alone and ignore B. Choices: 0 Add, 1 Subtract, 2 Multiply, 3 Divide, 4 Modulo, 5 Power, 6 Minimum, 7 Maximum, 8 Average, 9 Absolute difference, 10 Negate, 11 Absolute, 12 Floor, 13 Ceiling, 14 Round, 15 Square root, 16 Sign. | | `b_default` | B when unconnected | -8 to 8 | 0 | | linear | Value used for every lane of B when nothing is connected to it. Multiply with 0.5 halves a signal. Shown when `op` is 0 or 1 or 2 or 3 or 4 or 5 or 6 or 7 or 8 or 9. | <!-- parameters:end --> ### Operation (`op`) Which arithmetic to apply, a discrete choice. Divide and Modulo give 0 when B is 0; Modulo takes the sign of B; Power and Square root keep the sign of A; Round rounds halves away from zero. Changing it takes effect at the next block. ### B when unconnected (`b_default`) The value used for every lane of B when nothing is connected to B, from -8 to 8. Changes are smoothed over 5 ms so moving it does not click. It is hidden for the one-operand operations, which ignore B. ## Tips - To halve audio, set Operation to Multiply and B when unconnected to 0.5. Every channel is halved. - For per-channel gain from a control, connect a control bundle with one lane per channel to B and Multiply: lane 1 sets the first channel, lane 2 the second. - Multiply two audio oscillators for ring modulation: you hear the sum and difference of their frequencies. - Add B when unconnected to an LFO to move its centre before it reaches a parameter socket. - Math does not clamp. Follow a boost with a [Limiter](https://docs.tune.toys/nodes/std.limiter/), or shape a control with [Scale](https://docs.tune.toys/nodes/ctl.scale/). ## See also [Gain](https://docs.tune.toys/nodes/std.gain/), [Compare](https://docs.tune.toys/nodes/std.compare/), [Scale](https://docs.tune.toys/nodes/ctl.scale/), [Control lanes](https://docs.tune.toys/concepts/control-lanes/), [Multichannel audio](https://docs.tune.toys/concepts/multichannel/). --- URL: https://docs.tune.toys/nodes/std.matrix/ Title: Channel matrix The Channel matrix routes input channels to output channels using a list of explicit routes, each with its own gain. ## What it does The output width is declared on the node. Each route names a one-based input channel, a one-based output channel and a gain from -8 to 8. A route copies that input lane into that output lane at the gain. Several routes to one output add together, without normalization. Output channels with no route are silent, and an empty list of routes outputs silence. A route that points outside the declared bundles is rejected. Up to 256 routes are allowed. The node has no parameters; its width and routes are edited in the Channels section of its Properties. ## When to use it Use it to downmix, swap or duplicate channels, or to mix a bundle down to a smaller one. For simple extraction use [Select channels](https://docs.tune.toys/nodes/std.select/); for simple joining use [Combine channels](https://docs.tune.toys/nodes/std.combine/). ## Ports <!-- ports:start --> | Port | Direction | Signal | Lanes | | ------- | --------- | ------ | ----------------------- | | `audio` | Input | Audio | Inferred from the cable | | `audio` | Output | Audio | Inferred from the cable | <!-- ports:end --> ## Parameters <!-- parameters:start --> This node has no numeric parameters. <!-- parameters:end --> This node has no parameters. Its routes are configuration on the node. ## Tips - Negative gains invert polarity. - A mono to stereo spread is a matrix with two routes from channel 1, one to each output. - Routes are not normalized, so downmixing several loud channels can raise the level; use a gain below 1 on each route. ## See also [Select channels](https://docs.tune.toys/nodes/std.select/), [Combine channels](https://docs.tune.toys/nodes/std.combine/), [Mixer](https://docs.tune.toys/nodes/std.mixer/), [Multichannel audio](https://docs.tune.toys/concepts/multichannel/). --- URL: https://docs.tune.toys/nodes/std.mixer/ Title: Mixer Blend an editable list of up to 32 audio channels. Plug a cable into + Channel or onto the card to create a channel, or add an empty one and wire it later. ## What it does Each channel has its own input, gain, pan, mute and solo. A durable channel ID names its input and controls: `channel.in`, `channel.gain`, `channel.pan`, `channel.mute` and `channel.solo`. The `master` level follows the sum. Mute beats solo; when any channel is soloed, only soloed channels are heard. Solo is saved and automatable. Drag a channel's grip to reorder it, or drag it off the card to remove it. Reordering, names, colours and icons affect presentation only; cables and Frame controls continue following the same channel. Undoing removal restores its controls and cables. ## When to use it Combine sources into one mix, make a subgroup, or build a desk with mute and solo. For a single balance between two sources use [Crossfade](https://docs.tune.toys/nodes/std.crossfade/). ## Ports <!-- ports:start --> | Port | Direction | Signal | Lanes | | ------------ | --------- | ------ | ----------------------- | | `channel.in` | Input | Audio | Inferred from the cable | | `audio` | Output | Audio | Inferred from the cable | <!-- ports:end --> ## Parameters <!-- parameters:start --> | Parameter | Label | Range | Default | Unit | Scale | Notes | | -------------- | ------------ | --------- | ------- | ---- | ------ | --------------------------------------------------------------------------------------------------------------------------------------------------- | | `master` | Master | -60 to 12 | 0 | dB | linear | Mix output level in decibels, after every channel. −60 dB is off; 0 dB leaves the sum unchanged. | | `channel.gain` | channel Gain | -60 to 12 | 0 | dB | linear | Level of this channel in decibels. −60 dB is off; 0 dB leaves it unchanged. | | `channel.pan` | channel Pan | -1 to 1 | 0 | | linear | Position on a two-lane mix: −1 left, 0 centre, 1 right. Constant-power for a one-lane channel, balance for a two-lane one; ignored on other widths. | | `channel.mute` | channel Mute | 0 to 1 | 0 | | linear | Silences this channel. Mute beats solo. Choices: 0 On, 1 Muted. | | `channel.solo` | channel Solo | 0 to 1 | 0 | | linear | While any channel of this Mixer is soloed, only soloed channels are heard. Choices: 0 Off, 1 Solo. | <!-- parameters:end --> <!-- data:start --> - `channels`: 0–32 items, each with a durable `id` and optional `name`, `color`, `icon`. Item order affects presentation only; ports and controls use the item ID. <!-- data:end --> ## Tips - −60 dB is off. Start each source quietly and leave headroom at `master`. - Mono channels use constant-power pan into a stereo mix; stereo channels use balance with unity gain at centre. Pan is ignored on other widths. - Channels may have an optional name, colour and icon. Their IDs stay stable when renamed or reordered. - Use Alt (Option) Solo in the Mix drawer for exclusive solo. ## See also [Crossfade](https://docs.tune.toys/nodes/std.crossfade/), [Gain](https://docs.tune.toys/nodes/std.gain/), [Buses](https://docs.tune.toys/concepts/buses/), [Multichannel audio](https://docs.tune.toys/concepts/multichannel/). --- URL: https://docs.tune.toys/nodes/std.multiband/ Title: Multiband compressor A multiband compressor compresses the lows, mids and highs of a sound separately, so a loud bass note never turns the vocals down. ## What it does A 24 dB-per-octave Linkwitz-Riley [Crossover](https://docs.tune.toys/nodes/std.crossover/) splits each channel into a low band below Low split, a middle band and a high band above High split. Each band has its own compressor, with the same gain law as the [Compressor](https://docs.tune.toys/nodes/std.compressor/) and a fixed 6 dB soft knee: it follows the band's peak across every channel, so all channels of a band are turned down together, and Threshold, Ratio, Attack and Release work as they do there. Each band's Makeup raises it afterwards. The bands are added back together and Output trims the sum. With every ratio at 1 and no makeup, the bands sum flat, so the node changes nothing until a band compresses. Solo a band to hear only that band; while any band is soloed, only soloed bands are heard. The node adds no latency. ## When to use it Use it to control a boomy low end without dulling the top, to de-ess a voice with the high band, or to even out a full mix. To process bands with other effects, split them with a [Crossover](https://docs.tune.toys/nodes/std.crossover/) instead. ## Ports <!-- ports:start --> | Port | Direction | Signal | Lanes | | ------- | --------- | ------ | ----------------------- | | `audio` | Input | Audio | Inferred from the cable | | `audio` | Output | Audio | Inferred from the cable | <!-- ports:end --> ## Parameters <!-- parameters:start --> | Parameter | Label | Range | Default | Unit | Scale | Notes | | ---------------- | -------------- | ------------ | ------- | ---- | ------ | ------------------------------------------------------------------------------ | | `low_hz` | Low split | 20 to 2000 | 200 | Hz | log | Top of the low band. | | `high_hz` | High split | 500 to 16000 | 3000 | Hz | log | Bottom of the high band. | | `low_threshold` | Low threshold | -60 to 0 | -24 | dBFS | linear | The low band compresses above this peak level. | | `low_ratio` | Low ratio | 1 to 20 | 3 | :1 | linear | Low band input/output slope above threshold. | | `low_attack` | Low attack | 0.1 to 100 | 20 | ms | log | Low band gain-reduction attack. | | `low_release` | Low release | 10 to 2000 | 200 | ms | log | Low band gain-reduction recovery. | | `low_makeup` | Low makeup | 0 to 24 | 0 | dB | linear | Low band gain after compression. | | `low_solo` | Low solo | 0 to 1 | 0 | | linear | While any band is soloed, only soloed bands are heard. Choices: 0 Off, 1 Solo. | | `mid_threshold` | Mid threshold | -60 to 0 | -24 | dBFS | linear | The mid band compresses above this peak level. | | `mid_ratio` | Mid ratio | 1 to 20 | 2 | :1 | linear | Mid band input/output slope above threshold. | | `mid_attack` | Mid attack | 0.1 to 100 | 10 | ms | log | Mid band gain-reduction attack. | | `mid_release` | Mid release | 10 to 2000 | 150 | ms | log | Mid band gain-reduction recovery. | | `mid_makeup` | Mid makeup | 0 to 24 | 0 | dB | linear | Mid band gain after compression. | | `mid_solo` | Mid solo | 0 to 1 | 0 | | linear | While any band is soloed, only soloed bands are heard. Choices: 0 Off, 1 Solo. | | `high_threshold` | High threshold | -60 to 0 | -24 | dBFS | linear | The high band compresses above this peak level. | | `high_ratio` | High ratio | 1 to 20 | 2 | :1 | linear | High band input/output slope above threshold. | | `high_attack` | High attack | 0.1 to 100 | 5 | ms | log | High band gain-reduction attack. | | `high_release` | High release | 10 to 2000 | 100 | ms | log | High band gain-reduction recovery. | | `high_makeup` | High makeup | 0 to 24 | 0 | dB | linear | High band gain after compression. | | `high_solo` | High solo | 0 to 1 | 0 | | linear | While any band is soloed, only soloed bands are heard. Choices: 0 Off, 1 Solo. | | `output` | Output | -24 to 24 | 0 | dB | linear | Gain after the bands are summed. | <!-- parameters:end --> ### Splits (`low_hz`, `high_hz`) Low split, from 20 to 2000 Hz, is the top of the low band; High split, from 500 to 16000 Hz, is the bottom of the high band. They are used lowest first. ### Thresholds (`low_threshold`, `mid_threshold`, `high_threshold`) The peak level, from −60 to 0 dBFS, above which each band compresses. ### Ratios (`low_ratio`, `mid_ratio`, `high_ratio`) Each band's input-to-output slope above its threshold, from 1:1 (no compression) to 20:1. ### Attacks (`low_attack`, `mid_attack`, `high_attack`) How quickly each band's gain reduction arrives, from 0.1 to 100 ms. ### Releases (`low_release`, `mid_release`, `high_release`) How quickly each band's gain recovers, from 10 to 2000 ms. ### Makeups (`low_makeup`, `mid_makeup`, `high_makeup`) Gain added to each band after compression, from 0 to 24 dB. ### Solos (`low_solo`, `mid_solo`, `high_solo`) Solo plays only the soloed bands, to hear what each one catches. ### Output (`output`) Gain after the bands are summed, from −24 to 24 dB. ## Tips - Lows usually want slower attacks and releases than highs. - To de-ess, put High split near 5 kHz, set a fast high attack and leave the other ratios at 1. - A [Compressor](https://docs.tune.toys/nodes/std.compressor/) with a high-passed key is the other way to de-ess, turning the whole voice down only on its esses. ## See also [Compressor](https://docs.tune.toys/nodes/std.compressor/), [Crossover](https://docs.tune.toys/nodes/std.crossover/), [Limiter](https://docs.tune.toys/nodes/std.limiter/), [Parametric EQ](https://docs.tune.toys/nodes/std.eq/). --- URL: https://docs.tune.toys/nodes/std.nam/ Title: Neural amp Neural amp plays an attached `.nam` capture through the shared audio engine. ## What it does The compiler admits WaveNet A1/A2 and containers of up to four WaveNets. Parsing and preparation happen before playback. Model files are limited to 8 MiB, finite weights and bounded topology. Unsupported architectures and features are refused with a `NAM_*` diagnostic. A missing model passes dry audio and reports `NAM_MODEL_UNAVAILABLE`. ## When to use it Load a capture you have permission to use for an amp or pedal tone. Add Convolver after a capture that needs a cabinet impulse response. Local file uploads are private by default. TONE3000 catalog selection remains disabled pending the commercial agreement. ## Ports <!-- ports:start --> | Port | Direction | Signal | Lanes | | ------- | --------- | ------ | ----------------------- | | `audio` | Input | Audio | Inferred from the cable | | `audio` | Output | Audio | Inferred from the cable | <!-- ports:end --> ## Parameters <!-- parameters:start --> | Parameter | Label | Range | Default | Unit | Scale | Notes | | ----------- | --------- | --------- | ------- | ---- | ------ | ------------------------------------------------------------------------------- | | `input` | Input | -24 to 24 | 0 | dB | linear | Input for the loaded neural amp model. | | `output` | Output | -36 to 12 | 0 | dB | linear | Output for the loaded neural amp model. | | `normalize` | Normalize | 0 to 1 | 1 | | linear | Normalize for the loaded neural amp model. Choices: 0 Off, 1 On. | | `size` | Size | 0 to 1 | 0 | | linear | Size for the loaded neural amp model. Choices: 0 Full, 1 Lite. | | `channels` | Channels | 0 to 1 | 0 | | linear | Channels for the loaded neural amp model. Choices: 0 Mono (sum), 1 Per channel. | | `mix` | Mix | 0 to 1 | 1 | | linear | Mix for the loaded neural amp model. | <!-- parameters:end --> <!-- data:start --> - `model`: asset. <!-- data:end --> ### Input (`input`) Input trim changes how hard the model is driven, from −24 to +24 dB. ### Output (`output`) Output trim adjusts the processed level, from −36 to +12 dB. ### Normalize (`normalize`) On uses available model loudness metadata to target −18 dB. Without that metadata the gain is unity. ### Size (`size`) Full chooses the largest submodel; Lite chooses the smallest. A new size warms for its receptive field and crossfades over 10 ms. A single model has the same tone for either choice. ### Channels (`channels`) Mono averages input lanes and shares that result. Per channel gives each lane its own independent model state. ### Mix (`mix`) Blend dry and processed audio. When sample rates differ, a prepared windowed-sinc resampler runs the model at its own rate; the dry path and graph compensation use the reported whole-sample latency. ## Tips - Load a `.nam` file in the inspector or choose a model already attached to the Toy. - Leave headroom at the output; captures differ in level. - Preserve the creator's license and attribution when sharing. - TONE3000 models cannot be bundled in releases or independent plugin exports. ## See also [Drive](https://docs.tune.toys/nodes/std.drive/), [Convolver](https://docs.tune.toys/nodes/std.convolver/), [Gain](https://docs.tune.toys/nodes/std.gain/). --- URL: https://docs.tune.toys/nodes/std.noise/ Title: Noise A source of random-sounding signals: eight colors of noise and five sparse or chaotic textures. It has no audio input. ## What it does Noise generates a stream and sends it to every output channel. Each channel and each nested instance has its own repeatable stream, so channels are decorrelated, and starting the Toy again repeats the same noise. The color types (White, Pink, Brown, Blue, Violet, Grey, Green, Black) differ in their spectrum; the texture types (Velvet, Dust, Shift register, Chaos, Fractal) are sparse impulses or unusual generators. Tone is a low-pass on the output. ## When to use it Use it for wind, rain, hiss, snare noise, breath and as an excitation for filters. Run it through the [Filter](https://docs.tune.toys/nodes/std.svf/) for pitched or swept sounds. ## Ports <!-- ports:start --> | Port | Direction | Signal | Lanes | | ------- | --------- | ------ | ----------------------- | | `audio` | Output | Audio | Inferred from the cable | <!-- ports:end --> ## Parameters <!-- parameters:start --> | Parameter | Label | Range | Default | Unit | Scale | Notes | | ---------------- | ------- | ------------- | ------- | ------ | ------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `amplitude` | Level | 0 to 0.5 | 0.12 | linear | linear | Output level. Prepared per-mode gains balance continuous modes with headroom; sparse impulses retain their natural crest factor. | | `type` | Type | 0 to 12 | 0 | | linear | Eight colors and five textures. Discrete selection with a prepared, bounded transition; no numeric morphing between types. Choices: 0 White, 1 Pink, 2 Brown, 3 Blue, 4 Violet, 5 Grey, 6 Green, 7 Black, 8 Velvet, 9 Dust, 10 Shift register, 11 Chaos, 12 Fractal. | | `tone` | Tone | 200 to 20000 | 20000 | Hz | log | Output low-pass cutoff. The maximum setting is Open and bypasses the tone filter. | | `density` | Density | 0.1 to 12000 | 120 | Hz | log | Mean dust events per second. Event arrivals are independent, rather than periodic. Shown when `type` is 9. | | `clock` | Clock | 20 to 20000 | 7000 | Hz | log | Shift-register clock frequency. A fractional clock preserves the requested rate across sample rates. Shown when `type` is 10. | | `rate` | Rate | 0.1 to 1000 | 30 | Hz | log | Fundamental rate of six smooth random layers, each twice as fast and half as strong. Shown when `type` is 12. | | `seed` | Seed | 0 to 16777215 | 0 | | linear | 0 preserves path-derived deterministic streams. A positive integer selects another repeatable stream; each channel and nested instance stays independent. | | `velvet_density` | Density | 1 to 20000 | 2000 | Hz | log | Mean velvet impulses per second; one random signed impulse in each cell. Shown when `type` is 8. | <!-- parameters:end --> ### Level (`amplitude`) Output level from 0 to 0.5. Per-mode gains balance the continuous colors; sparse impulses keep their natural peaks. ### Type (`type`) Chooses one of the thirteen colors and textures. It is a discrete choice with a short, bounded transition between types. ### Tone (`tone`) An output low-pass from 200 to 20000 Hz. At the maximum it is Open and bypassed. ### Density (`density`) For Dust: the mean number of events per second. Arrivals are random, not periodic. ### Density, velvet (`velvet_density`) For Velvet: the mean number of signed impulses per second, one random impulse per cell. ### Clock (`clock`) For Shift register: the shift-register clock frequency, preserved across sample rates. ### Rate (`rate`) For Fractal: the fundamental rate of six smooth random layers, each twice as fast and half as strong. ### Seed (`seed`) 0 keeps the path-derived deterministic streams. A positive integer selects another repeatable stream; channels and nested instances stay independent. ## Tips - Pink and Brown noise sound more natural than White for wind and rumble. - Sparse textures sound best through reverb or a resonant filter. - Because noise repeats on every start, use Seed to get a different take. ## See also [Filter](https://docs.tune.toys/nodes/std.svf/), [Low-pass filter](https://docs.tune.toys/nodes/std.lowpass/), [Reverb](https://docs.tune.toys/nodes/std.reverb/). --- URL: https://docs.tune.toys/nodes/std.oscillator/ Title: Oscillator A full-featured sound source with eight waveforms, a morph control, detune and phase. It has no audio input; an optional MIDI input plays it from notes. ## What it does The oscillator generates a tone at Frequency, shaped by Type. The waveforms are Morph, Sine, Triangle, Saw, Pulse, Double saw, Organ and Harmonic bell. Morph blends continuously between sine, triangle, saw and pulse, evaluating only the two neighboring waves. Saw and pulse edges are band-limited to reduce aliasing. Detune offsets the pitch in cents, Phase sets the starting position of the cycle, and a rising Gate edge restarts the wave from that position. With a cable into its `midi` input, the oscillator plays the newest held note instead of Frequency: the note's pitch comes from the Toy's tuning, Detune still offsets it, pitch bend moves it by up to Bend range semitones, and Glide slides between notes. Releasing the newest note falls back to the newest still-held one; after the last release the pitch stays where it was, so a downstream envelope can fade it out. Before the first note, Frequency sets the pitch. The MIDI input does not gate the sound: pair it with an [Envelope](https://docs.tune.toys/nodes/std.envelope/) whose Gate comes from [MIDI to control](https://docs.tune.toys/nodes/midi.to-control/). Inside a [polyphonic group](https://docs.tune.toys/concepts/voices/) each voice's oscillator sees only that voice's note. ## When to use it Use it as the main source of a synth patch, as a test signal, or for several detuned voices. Pair it with an [Envelope](https://docs.tune.toys/nodes/std.envelope/) for notes and a [Filter](https://docs.tune.toys/nodes/std.svf/) to shape the tone. To play chords with your own oscillator, filter and envelope, put them in a group and give it [voices](https://docs.tune.toys/concepts/voices/). ## Ports <!-- ports:start --> | Port | Direction | Signal | Lanes | | ------- | --------- | ------ | ----------------------- | | `midi` | Input | MIDI | 16 MIDI channels | | `audio` | Output | Audio | Inferred from the cable | <!-- ports:end --> ## Parameters <!-- parameters:start --> | Parameter | Label | Range | Default | Unit | Scale | Notes | | ------------- | ----------- | ------------- | ------- | ------ | ------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `frequency` | Frequency | 20 to 20000 | 220 | Hz | log | Fundamental pitch, capped below Nyquist; detune uses cents. | | `amplitude` | Level | 0 to 0.5 | 0.12 | linear | linear | Output gain. | | `shape` | Morph | 0 to 3 | 0 | | linear | Continuous sine/triangle/saw/pulse morph; only its two neighboring waveforms are evaluated. Shown when `type` is 0. | | `pulse_width` | Pulse width | 0.05 to 0.95 | 0.5 | | linear | Pulse duty cycle with a frequency-dependent safe edge width. Shown when `type` is 0 or 4. | | `type` | Type | 0 to 7 | 0 | | linear | Selects the implemented algorithm. Choices are discrete and are not LFO targets. Choices: 0 Morph, 1 Sine, 2 Triangle, 3 Saw, 4 Pulse, 5 Double saw, 6 Organ, 7 Harmonic bell. | | `phase` | Phase | 0 to 1 | 0 | cycles | linear | Cycle offset; Gate rising edges restart phase from this position. | | `detune` | Detune | -1200 to 1200 | 0 | cents | linear | Pitch offset in cents. | | `gate` | Gate | 0 to 1 | 1 | | linear | On or off. A rising edge retriggers; held on runs the oscillator or envelope. A control cable switches it at 0.5. | | `bend_range` | Bend range | 0 to 48 | 2 | st | linear | Semitones a full pitch bend on the MIDI input moves the note, up or down. | | `glide_ms` | Glide | 0 to 2000 | 0 | ms | linear | Time constant of the slide from one MIDI note's pitch to the next; 0 jumps. | <!-- parameters:end --> ### Frequency (`frequency`) The fundamental pitch, from 20 to 20000 Hz, capped below Nyquist. ### Level (`amplitude`) Output gain from 0 to 0.5. ### Morph (`shape`) Used when Type is Morph. 0 is sine, 1 is triangle, 2 is saw, 3 is pulse. ### Pulse width (`pulse_width`) The pulse duty cycle, 0.05 to 0.95. It applies to Morph and Pulse. Edges are kept safe at high frequencies. ### Type (`type`) Chooses the waveform. A discrete choice. ### Phase (`phase`) A cycle offset from 0 to 1. A rising Gate restarts the wave from this position. ### Detune (`detune`) A pitch offset from -1200 to 1200 cents (one octave each way). ### Gate (`gate`) Defaults to on, so the oscillator runs. A rising edge retriggers the phase. ### Bend range (`bend_range`) How far a full pitch bend on the `midi` input moves the note, 0 to 48 semitones each way. 2 is the usual keyboard range; set it to the MPE bend of a polyphonic group (48 by default) when you play it from an MPE controller. ### Glide (`glide_ms`) The time constant, in milliseconds, of the slide from one MIDI note's pitch to the next. 0 jumps straight to each note; the first note always starts at its own pitch. ## Tips - Detune two oscillators by a few cents and sum them for a thick tone. - Use Pulse with a low Pulse width for a nasal, reedy sound. - Keep Level low, then raise the monitor level. - With a MIDI cable, a Frequency knob does nothing until you disconnect it: notes own the pitch. ## See also [Envelope](https://docs.tune.toys/nodes/std.envelope/), [Filter](https://docs.tune.toys/nodes/std.svf/), [MIDI to control](https://docs.tune.toys/nodes/midi.to-control/), [Voices](https://docs.tune.toys/concepts/voices/). --- URL: https://docs.tune.toys/nodes/std.phaser/ Title: Phaser Phaser sweeps a set of notches up and down the spectrum, the classic whooshing, swirling sound of a chain of allpass filters mixed back with the dry signal. ## What it does Each channel runs through 2 to 12 first-order allpass stages that share one break frequency. An allpass passes every frequency at full level but delays its phase; mixed with the dry sound, the two cancel wherever the chain has shifted the phase by an odd multiple of half a cycle. That gives one notch for every two stages. A sine sweep moves the break frequency between Low and High, either at Rate or locked to the shared transport when Beats is above zero. Feedback sends the last stage back into the first, deepening the notches and raising peaks between them. Channel spread offsets the sweep for each channel, so a stereo signal swirls between the speakers. The stages are normalized lattice allpasses: a sweeping coefficient never adds energy, so even fast sweeps with high feedback stay clean, and the engine reports a proven tail for every setting. ## When to use it Use it on pads, guitars, electric pianos, drums and noise for movement without the pitch wobble of [Chorus](https://docs.tune.toys/nodes/std.chorus/). For a single static notch or band use [State-variable filter](https://docs.tune.toys/nodes/std.svf/); for echoes use [Delay](https://docs.tune.toys/nodes/std.delay/). ## Ports <!-- ports:start --> | Port | Direction | Signal | Lanes | | ------- | --------- | ------ | ----------------------- | | `audio` | Input | Audio | Inferred from the cable | | `audio` | Output | Audio | Inferred from the cable | <!-- ports:end --> ## Parameters <!-- parameters:start --> | Parameter | Label | Range | Default | Unit | Scale | Notes | | ---------- | -------------- | ------------- | ------- | ----- | ------ | ----------------------------------------------------------------------------------------------------------- | | `stages` | Stages | 0 to 4 | 1 | | linear | First-order allpass stages in the chain. Every two stages add one notch. Choices: 0 2, 1 4, 2 6, 3 8, 4 12. | | `rate_hz` | Rate | 0.01 to 20 | 0.4 | Hz | log | Sweep cycles per second when Beats is 0. | | `beats` | Beats | 0 to 64 | 0 | beats | linear | Beats per sweep cycle, phase-locked to the shared transport. 0 uses Rate instead. | | `depth` | Depth | 0 to 1 | 0.8 | | linear | Share of the Low to High range the sweep covers, around its geometric centre. | | `low_hz` | Low | 20 to 20000 | 200 | Hz | log | Allpass break frequency at the bottom of the sweep. | | `high_hz` | High | 20 to 20000 | 2400 | Hz | log | Allpass break frequency at the top of the sweep, kept below 45 % of the sample rate. | | `feedback` | Feedback | -0.95 to 0.95 | 0.3 | | linear | Signed gain from the last stage back into the first; it sharpens the notches and peaks. | | `spread` | Channel spread | 0 to 1 | 0.5 | | linear | Sweep phase distribution across every channel: 0 moves them together, 1 spaces them around the cycle. | | `mix` | Mix | 0 to 1 | 0.5 | | linear | Dry/wet blend. 0.5 gives the deepest notches. | <!-- parameters:end --> ### Stages (`stages`) How many allpass stages are in the chain: 2, 4, 6, 8 or 12. Every two stages add one notch, so 4 stages give the familiar two-notch phaser and 12 give six notches. ### Rate (`rate_hz`) Sweep cycles per second, from 0.01 to 20 Hz, when `beats` is 0. ### Beats (`beats`) Beats per sweep cycle, phase-locked to the shared transport: 4 sweeps once a bar in 4/4 and always lands on the same point of the bar. At 0 the sweep runs freely at `rate_hz`. While the transport is stopped a synced sweep holds still. ### Depth (`depth`) How much of the range from `low_hz` to `high_hz` the sweep covers, around its geometric centre. At 0 the notches stay put at the centre. ### Low (`low_hz`) The break frequency at the bottom of the sweep. ### High (`high_hz`) The break frequency at the top of the sweep. It is kept below 45 % of the sample rate. ### Feedback (`feedback`) Signed gain from the last stage back into the first, from -0.95 to 0.95. Positive values deepen the notches and add resonant peaks between them; negative values move the peaks and make the notches shallower. ### Channel spread (`spread`) How the sweep phase is distributed across channels. At 0 every channel sweeps together; at 0.5 a stereo pair is a quarter cycle apart; at 1 the channels are spaced evenly around the cycle. ### Mix (`mix`) Dry and wet blend. 0.5 cancels fully at the notches; 1 is the allpassed signal alone, which sounds like a subtle phase shift on its own. ## Tips - Slow rates (0.1 to 0.5 Hz) with 4 stages are the classic phaser; 12 stages with high feedback sound more like a jet. - Keep `low_hz` above a few hundred hertz on bass-heavy sounds to keep the low end steady. - Long tails at high feedback and very low break frequencies are real: the notches ring as they decay. ## See also [Chorus](https://docs.tune.toys/nodes/std.chorus/), [State-variable filter](https://docs.tune.toys/nodes/std.svf/), [Delay](https://docs.tune.toys/nodes/std.delay/). --- URL: https://docs.tune.toys/nodes/std.pitch/ Title: Pitch shifter Pitch shifter plays its input higher or lower without changing its speed, from two octaves down to two octaves up. ## What it does The input runs through a short delay line read faster or slower than it is written, so the pitch moves by Semitones plus Fine. Before the read position drifts out of the Window, the shifter splices to a new position near the centre. It picks the one whose waveform best matches what is playing, then crossfades. On a held note the splice jumps by whole cycles, so a shifted tone stays steady and in tune. A sine lands within a fraction of a cent across the whole range. Feedback sends the shifted sound back into the line, so each pass shifts again: an octave up with feedback climbs octave after octave as it fades. Mix blends the shifted sound with the dry input, which is delayed by the same amount so the two line up. Every channel of the cable is shifted with the same splices. The node delays everything by half its largest Window (20 ms at the default 40 ms), and the Patch aligns parallel paths around it. At 0 semitones and 0 cents it is an exact delay. ## When to use it Use it for octave doublers, fifths under a lead, detuned thickening at a few cents, and shimmering feedback climbs. To add several shifted voices with their own levels and pans, use the [Harmonizer](https://docs.tune.toys/nodes/std.harmonizer/). To pull a voice onto the nearest note, use [Pitch correction](https://docs.tune.toys/nodes/std.autotune/). ## Ports <!-- ports:start --> | Port | Direction | Signal | Lanes | | ------- | --------- | ------ | ----------------------- | | `audio` | Input | Audio | Inferred from the cable | | `audio` | Output | Audio | Inferred from the cable | <!-- ports:end --> ## Parameters <!-- parameters:start --> | Parameter | Label | Range | Default | Unit | Scale | Notes | | ----------- | --------- | ------------- | ------- | ----- | ------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `semitones` | Semitones | -24 to 24 | 12 | st | linear | Shift in semitones; fractional values and sweeps are allowed. | | `cents` | Fine | -100 to 100 | 0 | cents | linear | Fine shift added to Semitones, in cents. | | `window_ms` | Window | 10 to 100 | 40 | ms | linear | Splice window. Longer windows smear transients less often and splice low notes cleanly; shorter ones respond faster. Latency is half the largest admitted window. | | `mix` | Mix | 0 to 1 | 0.5 | | linear | Dry/wet blend. The dry signal is delayed by the same latency before blending. | | `feedback` | Feedback | -0.75 to 0.75 | 0 | | linear | Signed gain feeding the shifted output back into the delay line, so each pass shifts again. | <!-- parameters:end --> ### Semitones (`semitones`) The shift from −24 to 24 semitones. 12 is an octave up, 7 a fifth, −12 an octave down. Fractional values and sweeps glide smoothly. ### Fine (`cents`) Up to 100 cents either way, added to Semitones. A few cents with Mix at 0.5 sounds like a doubled take. ### Window (`window_ms`) How much audio each splice spans, 10 to 100 ms. Long windows suit low notes, pads and big shifts; short windows keep fast, percussive material tight. A splice lines up with notes whose period is shorter than half the window: 40 ms reaches down to 50 Hz, 10 ms to 200 Hz. The node's latency is half the largest value the Window can reach. ### Mix (`mix`) The blend of the dry input and the shifted sound. 0 is only the input, 1 only the shifted sound. ### Feedback (`feedback`) A signed gain from −0.75 to 0.75 that feeds the shifted output back into the line. Each pass shifts again and is quieter than the last. Negative values flip each pass's polarity. ## Tips - Semitones 12 with Mix 0.5 is a classic octave doubler; −12 adds weight under a lead. - Semitones 12 and Feedback 0.5 into a [Reverb](https://docs.tune.toys/nodes/std.reverb/) gives a shimmer. - For a chorus-like widening, set Semitones 0, Fine 8 and Mix 0.5. - Shifting works best on one note at a time; chords and drums smear more at large shifts. ## See also [Harmonizer](https://docs.tune.toys/nodes/std.harmonizer/), [Pitch correction](https://docs.tune.toys/nodes/std.autotune/), [Delay](https://docs.tune.toys/nodes/std.delay/), [Chorus](https://docs.tune.toys/nodes/std.chorus/). --- URL: https://docs.tune.toys/nodes/std.player/ Title: Audio player Plays one recording into the patch: a drum loop, an ambient bed or a backing track. Attach a WAV file to the node and it plays when the Toy plays. ## What it does The player reads the attached file between Start and End at the chosen Rate. In Loop it repeats that region, crossfading at the seam; in Once it plays the region and stops. With Transport sync it plays only while the transport rolls and starts again from the top whenever the transport starts, so a take and its offline render hear exactly the same audio. Starting, stopping and restarting fade over Fade so nothing clicks. Stopping returns to the start. The engine decodes and resamples the file once, when the Toy is prepared; until a file is attached the node is silent, and the inspector says why when the file is missing or cannot be played. It plays WAV files only (16, 24 or 32-bit integer or 32 and 64-bit float, one to eight channels, 8 to 192 kHz). Lanes map as on the [Sampler](https://docs.tune.toys/nodes/std.sampler/). ## When to use it Use it for any recording that plays as it is, not from the keys: loops, beds, backing tracks, sound effects started by a clock or a button. To play a recording across a keyboard, use the [Sampler](https://docs.tune.toys/nodes/std.sampler/). Video stays on the [Media player](https://docs.tune.toys/nodes/media.player/). ## Ports <!-- ports:start --> | Port | Direction | Signal | Lanes | | ------- | --------- | ------ | ----------------------- | | `audio` | Output | Audio | Inferred from the cable | <!-- ports:end --> ## Parameters <!-- parameters:start --> | Parameter | Label | Range | Default | Unit | Scale | Notes | | --------- | ------- | --------- | ------- | ---- | ------ | ---------------------------------------------------------------------------------------------------------------------------------------- | | `playing` | Playing | 0 to 1 | 1 | | linear | Plays or stops. Stopping fades out and returns to the start; a cable into @playing starts and stops it. Choices: 0 Stopped, 1 Playing. | | `restart` | Restart | 0 to 1 | 0 | | linear | A rise through 0.5, such as a clock pulse, jumps back to the start with a short crossfade. | | `loop` | Loop | 0 to 1 | 1 | | linear | Once stops at the region's end; Loop repeats the region with a crossfade at the seam. Choices: 0 Once, 1 Loop. | | `start` | Start | 0 to 1 | 0 | | linear | Start of the region, as a fraction of the file. | | `end` | End | 0 to 1 | 1 | | linear | End of the region, as a fraction of the file. At or before Start the player is silent. | | `rate` | Rate | -4 to 4 | 1 | × | linear | Speed and pitch together. 1 plays as recorded; negative values play backwards from the end. | | `gain` | Gain | -60 to 12 | 0 | dB | linear | Output level. -60 dB is silent. | | `sync` | Sync | 0 to 1 | 1 | | linear | Free plays whenever Playing is on. Transport also needs the transport rolling and restarts when it starts. Choices: 0 Free, 1 Transport. | | `fade_ms` | Fade | 0 to 100 | 5 | ms | linear | De-click time for starts, stops, restarts and loop seams. | <!-- parameters:end --> <!-- data:start --> - `asset`: asset. <!-- data:end --> ### Playing (`playing`) Plays or stops. Stopping fades out and returns to the start. A control cable into `@playing` starts and stops it from the patch. ### Restart (`restart`) A rise through 0.5 jumps back to the start with a crossfade. Cable a clock pulse (`ctl.clock`) into `@restart` to re-sync a loop every bar, or bind it to a Frame trigger. ### Loop (`loop`), Start (`start`) and End (`end`) The region, as fractions of the file, and whether it repeats. In Loop the seam crossfades with the material just before Start; where the file has none (Start at 0) the seam fades out and back in instead. In Once the region plays to its end and the player stays silent until the next restart or start. ### Rate (`rate`) Speed and pitch together: 1 plays as recorded, 2 an octave up at double speed, and negative values play backwards from End. ### Gain (`gain`) Output level, from −60 dB (silent) to +12 dB. ### Sync (`sync`) Free plays whenever Playing is on. Transport also needs the transport rolling and restarts each time it starts, which keeps loops aligned with the bar and replays deterministic. ### Fade (`fade_ms`) The de-click time, up to 100 ms, for starts, stops, restarts and loop seams. Set it to 0 for a loop that is already seamless. ## Tips - Trim a loop to whole bars so it stays on the beat at Rate 1. - A loop recorded at another tempo drifts against the transport; Rate changes its pitch too, since the player does not time-stretch. - Put a [Gain](https://docs.tune.toys/nodes/std.gain/) or a compressor after a backing track to sit it under live parts. ## See also [Sampler](https://docs.tune.toys/nodes/std.sampler/), [Media player](https://docs.tune.toys/nodes/media.player/), [Gain](https://docs.tune.toys/nodes/std.gain/), [Cables](https://docs.tune.toys/concepts/cables/). --- URL: https://docs.tune.toys/nodes/std.poly/ Title: Polyphonic instrument A ready-made eight-voice instrument. It has no audio input: it makes sound when it receives notes from a Frame piano, the computer keyboard or MIDI, and sends a stereo signal out. ## What it does Each note takes one of eight voices. A voice is an oscillator with its own attack, decay, sustain and release, so overlapping notes keep their own envelopes. The instrument responds to note velocity and to the sustain pedal. Level is shared across the voices: each voice receives one eighth of it, so playing eight notes together has fixed headroom. It does not take an audio input or modulation cables. Effects go after it on the output cable. ### Pitch and tuning The keys play the Toy's `tuning`: a preset such as 31-EDO or just intonation, or a custom table of cents. Without one the Toy plays 12-TET with A4 at 440 Hz, and note 69 is A4. - **Equal divisions of the octave** (12-TET, 19-, 22-, 24-, 31- and 53-EDO): middle C (MIDI 60) is degree 0, each key up is one step, and A4 stays at the reference on the degree three of the division's own fifths above C. In 31-EDO, C4 to C5 spans 31 keys. - **Just, Pythagorean, Bohlen–Pierce and custom tables**: MIDI 60 plus Root plays the root at its 12-TET pitch from the reference, and each key up is the next degree, repeating at the period (an octave, or a tritave for Bohlen–Pierce). The engine maps all 128 notes once when the Toy is prepared, so a note-on only reads a table. Pitch bend is ±2 semitones, except on MPE member channels (below). The tuning is set in the Toy's source, for example `"tuning": { "preset": "31-edo", "root": 0, "reference": 440 }`; the Thirty-One Keys example Toy plays that one. A [Tuner](https://docs.tune.toys/nodes/std.tuner/) left on Toy tuning measures against the same degrees. ### Voices and stealing A note takes the voice already playing that key from the same source and channel, else a silent voice, else the oldest released voice, else the oldest held one. A stolen voice crossfades to its new note over 32 samples. Each MIDI source (a Frame piano, the computer keyboard, each MIDI device) owns its own notes and sustain pedal per channel, so one source releasing a key never silences another's. ### MPE With MPE set to Lower zone (master channel 1, members 2–16) or Upper zone (master channel 16, members 1–15), each note arrives on its own member channel. That channel's pitch bend then moves only the voice holding its note, by up to MPE bend semitones; master-channel bend still moves every voice by ±2 semitones, and the two add. Pressure and CC74 have no target in this instrument; build a [polyphonic group](https://docs.tune.toys/concepts/voices/) to route them per voice. MPE configuration messages (MCM) and MIDI-CI are not handled: set the zone here to match the controller. ## When to use it Use it when you want something playable with a few controls and no wiring: a starting point for keyboards, pads and plucks. Reach for [Oscillator](https://docs.tune.toys/nodes/std.oscillator/) with an [Envelope](https://docs.tune.toys/nodes/std.envelope/) when you need a single voice you can wire and modulate yourself, and give its group [voices](https://docs.tune.toys/concepts/voices/) to play chords with it. ## Ports <!-- ports:start --> | Port | Direction | Signal | Lanes | | ------- | --------- | ------ | ----------------------- | | `midi` | Input | MIDI | 16 MIDI channels | | `audio` | Output | Audio | Inferred from the cable | <!-- ports:end --> ## Parameters <!-- parameters:start --> | Parameter | Label | Range | Default | Unit | Scale | Notes | | ------------- | ----------- | ------------ | ------- | ----- | ------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `amplitude` | Level | 0 to 0.5 | 0.3 | | linear | Master level. Each of the eight voices receives one eighth of this level for fixed headroom. | | `shape` | Shape | 0 to 3 | 0 | | linear | Continuous sine, triangle, saw and pulse morph using the shared wave oscillator. | | `attack_ms` | Attack | 0 to 10000 | 5 | ms | linear | Voice attack time. | | `decay_ms` | Decay | 0 to 10000 | 120 | ms | linear | Voice decay time. | | `sustain` | Sustain | 0 to 1 | 0.7 | % | linear | Held voice envelope level. Stored as a fraction; shown ×100 as a percentage. | | `release_ms` | Release | 0 to 10000 | 200 | ms | linear | Voice release after note-off or sustain release. | | `detune` | Fine tune | -100 to 100 | 0 | cents | linear | Moves every note by this many cents from the Toy's tuning, for this instrument only. 0 plays the tuning as written. | | `pulse_width` | Pulse width | 0.05 to 0.95 | 0.5 | | linear | Pulse duty cycle. | | `mpe` | MPE | 0 to 2 | 0 | | linear | In a zone, a member channel's pitch bend moves only the voice holding that channel's note; master-channel bend moves every voice. Choices: 0 Off, 1 Lower zone, 2 Upper zone. | | `mpe_bend` | MPE bend | 0 to 96 | 48 | st | linear | Semitones a full member-channel pitch bend moves its note in an MPE zone. | <!-- parameters:end --> ### Level (`amplitude`) Master level of the instrument. Because each voice gets one eighth, raising it never makes a full chord clip on its own. ### Shape (`shape`) A continuous morph: 0 is sine, 1 is triangle, 2 is saw and 3 is pulse. Values between blend neighboring waves. ### Attack (`attack_ms`), Decay (`decay_ms`), Sustain (`sustain`), Release (`release_ms`) The per-voice envelope. Attack, Decay and Release are times in milliseconds. Sustain is the level, shown from 0 to 100 %, that a held key stays at after the decay. Release starts at note-off, or when the sustain pedal is let go. ### Fine tune (`detune`) Moves every note of this instrument by up to 100 cents either way from the Toy's tuning; 0 plays the tuning as written. Two instruments a few cents apart thicken a sound. The tuning itself, including the A4 reference, belongs to the Toy (see [Pitch and tuning](#pitch-and-tuning)). ### Pulse width (`pulse_width`) The duty cycle of the pulse wave, from 0.05 to 0.95. It affects the sound near the pulse end of Shape. ### MPE (`mpe`) Off treats every channel alike. Lower zone and Upper zone make channel 1 or channel 16 the master and the other fifteen member channels, one per sounding note (see [MPE](#mpe)). ### MPE bend (`mpe_bend`) How far a full pitch bend on a member channel moves its note, 0 to 96 semitones each way; 48 is the MPE default most controllers send. It applies only while MPE is on. ## Tips - Short Attack and Release sound plucked; long ones sound like a pad. - Sustain of 0 makes a note die away after the decay even while the key is held. - Add [Reverb](https://docs.tune.toys/nodes/std.reverb/) after the instrument for space. - Frame pianos and MIDI both reach the instrument. See [MIDI](https://docs.tune.toys/concepts/midi/). - To hear a microtonal scale, give the Toy a `tuning` and play neighbouring keys: each is one degree. ## See also [Oscillator](https://docs.tune.toys/nodes/std.oscillator/), [Envelope](https://docs.tune.toys/nodes/std.envelope/), [Reverb](https://docs.tune.toys/nodes/std.reverb/), [Tuner](https://docs.tune.toys/nodes/std.tuner/), [Frames](https://docs.tune.toys/concepts/frames/), [Voices](https://docs.tune.toys/concepts/voices/). --- URL: https://docs.tune.toys/nodes/std.resonance/ Title: Resonance suppressor Smooth out harsh, ringing frequencies only when and where they appear, without a fixed EQ cut. ## What it does The suppressor splits the sound into narrow frequency bins (2048-point frames up to 48 kHz, 4096 above) and compares each bin with the average of its neighbours, over a window that is wide at low Sharpness and narrow at high Sharpness. Where a bin between Low and High stands more than Threshold above that average, it is pulled back down to Threshold, but never by more than Depth. The comparison leaves out the bin itself and its closest neighbours, so a peak cannot raise its own reference. Cuts grow with Attack and recover with Release, so a resonance is only cut while it rings. The node delays its output by one frame, 2048 samples (about 43 ms at 48 kHz, 4096 above), and reports that latency, so the rest of the Toy stays aligned. ## When to use it Use it on a boxy or whistling microphone, a harsh synth, a guitar amp that rings at one note, or a mix bus that turns shrill when it gets loud. A fixed narrow EQ cut works when the frequency never moves; this follows peaks that come and go. For feedback in a live system, use the [Feedback suppressor](https://docs.tune.toys/nodes/std.suppressor/), which adds no latency. ## Ports <!-- ports:start --> | Port | Direction | Signal | Lanes | | ------- | --------- | ------ | ----------------------- | | `audio` | Input | Audio | Inferred from the cable | | `audio` | Output | Audio | Inferred from the cable | <!-- ports:end --> ## Parameters <!-- parameters:start --> | Parameter | Label | Range | Default | Unit | Scale | Notes | | ------------ | --------- | ----------- | ------- | ---- | ------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `depth` | Depth | 0 to 30 | 9 | dB | linear | The most any bin is turned down. | | `sharpness` | Sharpness | 0 to 1 | 0.5 | % | linear | Narrows the average a peak is compared with, from one octave around it at 0 to a sixth of an octave at 100 %, so only sharper resonances are caught. Stored as a fraction; shown ×100 as a percentage. | | `threshold` | Threshold | 0 to 24 | 6 | dB | linear | How far a bin may rise above its local average before it is pulled down. A peak is pulled back to this height, by at most Depth. | | `attack_ms` | Attack | 1 to 200 | 10 | ms | log | How fast a cut grows on a new resonance. | | `release_ms` | Release | 10 to 2000 | 150 | ms | log | How fast a cut recovers once the resonance fades. | | `low_hz` | Low | 20 to 20000 | 200 | Hz | log | Lowest frequency the suppressor works on; below it the signal passes untouched. | | `high_hz` | High | 20 to 20000 | 12000 | Hz | log | Highest frequency the suppressor works on; above it the signal passes untouched. | | `mix` | Mix | 0 to 1 | 1 | % | linear | Blend from the dry input, delayed to stay aligned, to the suppressed signal. Stored as a fraction; shown ×100 as a percentage. | | `listen` | Listen | 0 to 1 | 0 | | linear | Removed plays only what the suppressor takes out, to hear which resonances it catches. Choices: 0 Off, 1 Removed. | <!-- parameters:end --> ### Depth (`depth`) The deepest cut, in dB, from 0 to 30. 6 to 12 dB tames most resonances without dulling the sound. ### Sharpness (`sharpness`) The width of the neighbourhood a peak is compared with: one octave around it at 0 %, a sixth of an octave at 100 %. Higher Sharpness catches only narrow rings and leaves broad tonal character alone; lower Sharpness also evens out wider bumps. ### Threshold (`threshold`) How far, in dB, a bin may stand above its neighbourhood before it is pulled back. Lower values smooth the spectrum more. ### Attack and Release (`attack_ms`, `release_ms`) How fast a cut grows on a new resonance and how fast it recovers once the resonance fades. ### Low and High (`low_hz`, `high_hz`) The frequency range the suppressor works in. Outside it, the signal passes untouched. ### Mix (`mix`) Blends the suppressed signal with the dry input, delayed by the same frame so the two stay aligned. ### Listen (`listen`) Removed plays only what the suppressor takes out (the dry sound minus the result, scaled by Mix), to hear which resonances it catches. ## Tips - Use Listen first: switch to Removed and adjust Threshold and Sharpness until you hear only the ringing, then switch back. - Keep Low above the fundamental of a voice or instrument so its body is never cut. - Long Release values sound smoother; short ones follow fast-changing material more closely. ## See also [Parametric EQ](https://docs.tune.toys/nodes/std.eq/), [Noise reduction](https://docs.tune.toys/nodes/std.denoise/), [Feedback suppressor](https://docs.tune.toys/nodes/std.suppressor/), [Compressor](https://docs.tune.toys/nodes/std.compressor/). --- URL: https://docs.tune.toys/nodes/std.reverb/ Title: Reverb Reverb adds the sense of a space around a sound by blending in a dense, fading tail. ## What it does The input is delayed by Pre-delay and fed into an eight-line feedback-delay network. A mixing matrix at Diffusion spreads energy between the lines, Damping darkens the tail as it decays, and a small Modulation avoids a metallic sound. Space selects Room, Hall or Plate-like tunings, and Size scales the whole network. Mix blends the tail with the dry signal. Each channel keeps its own history. Freeze removes new input and holds the energy of the network so the tail does not decay; a frozen network reports an infinite tail. ## When to use it Use it to place a sound in a room, to smooth a mix or to make pads and drones. Use [Delay](https://docs.tune.toys/nodes/std.delay/) when you want distinct repeats. ## Ports <!-- ports:start --> | Port | Direction | Signal | Lanes | | ------- | --------- | ------ | ----------------------- | | `audio` | Input | Audio | Inferred from the cable | | `audio` | Output | Audio | Inferred from the cable | <!-- ports:end --> ## Parameters <!-- parameters:start --> | Parameter | Label | Range | Default | Unit | Scale | Notes | | ------------ | ---------- | ------------ | ------- | ---- | ------ | ----------------------------------------------------------------------------------------------------------------------- | | `decay` | Decay | 0.1 to 12 | 1.8 | s | log | Nominal RT60 at the reference damping setting; Freeze suspends decay. | | `damping` | Damping | 200 to 20000 | 6500 | Hz | log | Low-pass damping within the feedback network. | | `predelay` | Pre-delay | 0 to 100 | 10 | ms | linear | Delay before the diffuse wet field. | | `mix` | Mix | 0 to 1 | 0.3 | | linear | Dry/wet blend. Processing latency is aligned before blending. | | `type` | Space | 0 to 2 | 0 | | linear | Selects the implemented algorithm. Choices are discrete and are not LFO targets. Choices: 0 Room, 1 Hall, 2 Plate-like. | | `size` | Size | 0.5 to 2 | 1 | | linear | Scales the prepared feedback-delay network. | | `diffusion` | Diffusion | 0 to 1 | 0.85 | | linear | Mixing depth of the contractive Householder feedback matrix. | | `modulation` | Modulation | 0 to 2 | 0.4 | ms | linear | Small slow delay modulation for a less metallic tail. | | `freeze` | Freeze | 0 to 1 | 0 | | linear | Removes excitation and holds network energy. A frozen network reports an infinite tail. Choices: 0 Off, 1 On. | <!-- parameters:end --> ### Decay (`decay`) The nominal time, 0.1 to 12 s, for the tail to fade (RT60) at the reference damping. Freeze suspends it. ### Damping (`damping`) A low-pass in the feedback network, 200 to 20000 Hz. Lower values give a darker, warmer tail. ### Pre-delay (`predelay`) The delay before the wet field, 0 to 100 ms. ### Mix (`mix`) Dry and wet blend. Processing latency is aligned before blending. ### Space (`type`) Room, Hall or Plate-like. A discrete choice. ### Size (`size`) Scales the prepared network from 0.5 to 2. ### Diffusion (`diffusion`) The mixing depth of the feedback matrix, from 0 to 1. Higher values sound smoother and denser. ### Modulation (`modulation`) A small, slow delay modulation from 0 to 2 ms that makes the tail less metallic. ### Freeze (`freeze`) Off or On. When on, new input stops entering the network and the tail holds. ## Tips - A little Pre-delay keeps the start of a sound clear of its own tail. - Lower Damping for dark, warm spaces. - Freeze with a long Decay makes an instant drone from a short sound. ## See also [Delay](https://docs.tune.toys/nodes/std.delay/), [Chorus](https://docs.tune.toys/nodes/std.chorus/), [Noise](https://docs.tune.toys/nodes/std.noise/). --- URL: https://docs.tune.toys/nodes/std.sampler/ Title: Sampler An instrument made from a recording. Attach a WAV file to the node, play it from a Frame piano, the computer keyboard or MIDI, and every key plays the recording at its own pitch. ## What it does Each note takes one of up to eight voices. A voice reads the attached file from its start, faster for higher keys and slower for lower ones, and shapes it with its own attack, decay, sustain and release. Owners and MIDI channels are kept apart, the sustain pedal holds notes, pitch bend moves them ±2 semitones, and a ninth note steals the oldest released voice first, as in the [Polyphonic instrument](https://docs.tune.toys/nodes/std.poly/). The engine decodes and resamples the file once, when the Toy is prepared, so a note never waits for it. Until a file is attached the node is silent; the inspector says why when the file is missing or cannot be played. ### Files it plays WAV only: 16, 24 or 32-bit integer or 32 and 64-bit float, one to eight channels, 8 to 192 kHz. A mono file plays on every output lane, a stereo file plays left and right on a stereo cable, and a multichannel file folds to a mono cable by its average. MP3 and FLAC are not decoded; convert them to WAV first. ### Pitch and tuning Keys play the Toy's `tuning`. The root note plays the file as recorded and every other key plays it at the tuning's pitch ratio to the root: an octave up plays twice as fast, and in 19-EDO each key up is one nineteenth of an octave. ## When to use it Use it for instruments built from a recorded sound: a piano note, a vocal syllable, a drum hit played across the keys. For one long recording that should simply play, such as a backing track, use the [Audio player](https://docs.tune.toys/nodes/std.player/). ## Ports <!-- ports:start --> | Port | Direction | Signal | Lanes | | ------- | --------- | ------ | ----------------------- | | `midi` | Input | MIDI | 16 MIDI channels | | `audio` | Output | Audio | Inferred from the cable | <!-- ports:end --> ## Parameters <!-- parameters:start --> | Parameter | Label | Range | Default | Unit | Scale | Notes | | ------------ | ---------- | ----------- | ------- | ----- | ------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `gain` | Gain | -60 to 12 | 0 | dB | linear | Level of the summed voices. -60 dB is silent. | | `root` | Root note | 0 to 127 | 60 | | linear | The note that plays the file at its recorded pitch. Every other note plays at the tuning's ratio to it. | | `tune` | Fine tune | -100 to 100 | 0 | cents | linear | Moves every note by this many cents, for this sampler only. | | `start` | Start | 0 to 1 | 0 | | linear | Where a note starts playing, as a fraction of the file. Read when a note starts. | | `end` | End | 0 to 1 | 1 | | linear | Where a note stops playing, as a fraction of the file. At or before Start the region is empty and notes are silent. | | `loop` | Loop | 0 to 2 | 0 | | linear | Sustain loop while a note is held: off, forward, or forward and back. A released note plays on to End. Choices: 0 Off, 1 Forward, 2 Ping-pong. | | `loop_start` | Loop start | 0 to 1 | 0 | | linear | Start of the sustain loop, as a fraction of the file, kept inside the region. | | `loop_end` | Loop end | 0 to 1 | 1 | | linear | End of the sustain loop, as a fraction of the file, kept inside the region. | | `attack_ms` | Attack | 0 to 10000 | 1 | ms | linear | Voice attack time. | | `decay_ms` | Decay | 0 to 10000 | 100 | ms | linear | Voice decay time. | | `sustain` | Sustain | 0 to 1 | 1 | % | linear | Held voice envelope level. Stored as a fraction; shown ×100 as a percentage. | | `release_ms` | Release | 0 to 10000 | 150 | ms | linear | Voice release after note-off or sustain release. | | `velocity` | Velocity | 0 to 1 | 1 | % | linear | How much note velocity sets a voice's level. 0 plays every note at full level. Stored as a fraction; shown ×100 as a percentage. | | `voices` | Voices | 1 to 8 | 8 | | linear | Voices a note can take. 1 is mono with last-note priority and glide. | | `glide_ms` | Glide | 0 to 2000 | 0 | ms | linear | In mono, the time a held voice takes to slide to the next note without restarting. | | `mode` | Mode | 0 to 1 | 0 | | linear | Gate plays while the note is held. One-shot plays the region to its end and ignores note-off. Choices: 0 Gate, 1 One-shot. | | `mpe` | MPE | 0 to 2 | 0 | | linear | In a zone, a member channel's pitch bend moves only the voice holding that channel's note; master-channel bend moves every voice. Choices: 0 Off, 1 Lower zone, 2 Upper zone. | | `mpe_bend` | MPE bend | 0 to 96 | 48 | st | linear | Semitones a full member-channel pitch bend moves its note in an MPE zone. | <!-- parameters:end --> <!-- data:start --> - `asset`: asset. <!-- data:end --> ### Gain (`gain`) The level of all voices together, from −60 dB (silent) to +12 dB. Voices add up, so a loud recording played as a chord can need a few decibels less. ### Root note (`root`) and Fine tune (`tune`) `root` is the MIDI note at which the file plays at its recorded pitch; set it to the note the recording is of. `tune` moves every key by up to 100 cents for this sampler only. ### Start (`start`) and End (`end`) The part of the file a note plays, as fractions of its length. A note starts at Start and stops at End, fading out over the last 2 ms so the cut does not click. An End at or before Start leaves nothing to play. ### Loop (`loop`), Loop start (`loop_start`) and Loop end (`loop_end`) A sustain loop. While a key or the pedal holds a note, Forward jumps from Loop end back to Loop start and Ping-pong turns around at each end. Release the note and it plays on to End during its release. The loop points stay inside the region; a loop shorter than one sample is ignored. ### Attack (`attack_ms`), Decay (`decay_ms`), Sustain (`sustain`) and Release (`release_ms`) The per-voice envelope, as on the Polyphonic instrument. A short Attack keeps a percussive recording's transient; a long Release lets a note ring after the key. ### Velocity (`velocity`) How much note velocity sets a voice's level. At 1 a soft key plays quietly; at 0 every note plays at full level. ### Voices (`voices`) and Glide (`glide_ms`) How many of the eight voices notes may take. At 1 the sampler is mono: the newest key wins, and releasing it returns to a key still held. With Glide above 0 a held voice slides to the next key without restarting the recording, and a new note glides from the last one. ### Mode (`mode`) Gate plays while the note is held. One-shot plays the region to End for every note, ignoring note-off, the pedal and the loop: right for drum hits. ### MPE (`mpe`) and MPE bend (`mpe_bend`) As on the [Polyphonic instrument](https://docs.tune.toys/nodes/std.poly/#mpe): Lower zone and Upper zone make channel 1 or channel 16 the master and the other fifteen member channels, one per sounding note. A member channel's pitch bend moves only its own note, by up to `mpe_bend` semitones (48 by default); the master channel's bend moves every note and its pedal holds them. ## Tips - Set Root note to the recording's own note before anything else, or every key is transposed. - Choose loop points where the waveform is quiet or steady; a loop at a loud transient clicks. - One-shot with Voices 1 makes a monophonic drum pad that cuts off its own tail. - Effects go after it on the output cable; [Reverb](https://docs.tune.toys/nodes/std.reverb/) suits plucked samples. ## See also [Audio player](https://docs.tune.toys/nodes/std.player/), [Polyphonic instrument](https://docs.tune.toys/nodes/std.poly/), [Keyboard](https://docs.tune.toys/nodes/midi.keyboard/), [MIDI](https://docs.tune.toys/concepts/midi/), [Voices](https://docs.tune.toys/concepts/voices/). --- URL: https://docs.tune.toys/nodes/std.select/ Title: Select channels Select channels keeps a contiguous range of channels from an audio bundle and drops the rest. ## What it does Selection is set in the node's audio configuration, not by a knob. The first selected channel (counting from 1) and the number of channels to keep are set in the Channels section of the node's Properties. The selected lanes become the output, in order. A range that falls outside the incoming bundle is rejected. The node has no parameters. ## When to use it Use it to pick one lane from a multichannel source, or place several selectors side by side to split a bundle into separate paths. To rebuild a bundle use [Combine channels](https://docs.tune.toys/nodes/std.combine/). To reorder or mix lanes use [Channel matrix](https://docs.tune.toys/nodes/std.matrix/). ## Ports <!-- ports:start --> | Port | Direction | Signal | Lanes | | ------- | --------- | ------ | ----------------------- | | `audio` | Input | Audio | Inferred from the cable | | `audio` | Output | Audio | Inferred from the cable | <!-- ports:end --> ## Parameters <!-- parameters:start --> This node has no numeric parameters. <!-- parameters:end --> This node has no parameters. Choose its range in the Channels section of the node's Properties. ## Tips - To split a stereo bundle, use two selectors: one keeping channel 1, one keeping channel 2. - A mono result needs a mono or adapting destination. See [Multichannel audio](https://docs.tune.toys/concepts/multichannel/). ## See also [Combine channels](https://docs.tune.toys/nodes/std.combine/), [Channel matrix](https://docs.tune.toys/nodes/std.matrix/), [Cables](https://docs.tune.toys/concepts/cables/). --- URL: https://docs.tune.toys/nodes/std.suppressor/ Title: Feedback suppressor Catch the howl of a microphone feeding back through speakers and notch it out before it takes over. ## What it does The suppressor listens to its input in narrow frequency bins (4096-point analysis up to 48 kHz, 8192 above, about 12 Hz apart), a quarter frame at a time. It looks for the signs of feedback: a single peak between 60 Hz and 18 kHz, louder than −50 dBFS, that stands far above the average level, is much louder than the bins a few steps either side, and has no partner at twice or half its frequency. Notes have harmonics; feedback is a pure tone. A peak that keeps those signs for several frames places a 6 dB notch on its exact frequency, and each time it persists the notch deepens by 3 dB, up to Max depth. The notches are narrow bell cuts that run on the audio itself, so the suppressor adds no latency. Depth changes glide at 3 dB per 10 ms. Once a howl stops, its notch recovers over Release and is freed. Every lane has its own detector and notches. ## When to use it Use it in a live chain after a microphone: a talk-back mic, a vocal, a kit mic in a small room, or a Toy that routes its own output back into an input. It is not a substitute for good mic placement or a lower monitor level. To tame resonances in a mix rather than feedback, use the [Resonance suppressor](https://docs.tune.toys/nodes/std.resonance/). ## Ports <!-- ports:start --> | Port | Direction | Signal | Lanes | | ------- | --------- | ------ | ----------------------- | | `audio` | Input | Audio | Inferred from the cable | | `audio` | Output | Audio | Inferred from the cable | <!-- ports:end --> ## Parameters <!-- parameters:start --> | Parameter | Label | Range | Default | Unit | Scale | Notes | | ------------- | ----------- | -------- | ------- | ---- | ------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------- | | `sensitivity` | Sensitivity | 0 to 1 | 0.5 | % | linear | How readily a steady, pure tone counts as feedback. Higher catches howls sooner but may notch held notes. Stored as a fraction; shown ×100 as a percentage. | | `max_depth` | Max depth | 3 to 40 | 18 | dB | linear | The deepest any notch may go. A notch deepens in steps while its howl persists. | | `q` | Q | 5 to 100 | 30 | Q | log | Notch width: 30 is about a twentieth of an octave. Higher is narrower and colours the sound less. | | `notches` | Notches | 1 to 8 | 6 | | linear | How many notches may be active. When all are in use, a new howl replaces the shallowest notch. | | `release_s` | Release | 1 to 120 | 20 | s | log | Time for a notch at Max depth to recover fully once its howl is gone. Notches that recover to nothing are freed. | | `freeze` | Freeze | 0 to 1 | 0 | | linear | On holds every notch as it is: no new notches and no release. Choices: 0 Off, 1 On. | | `reset` | Reset | 0 to 1 | 0 | | linear | A change from Idle to Clear fades every notch out over 10 ms and starts tracking afresh. Choices: 0 Idle, 1 Clear. | <!-- parameters:end --> ### Sensitivity (`sensitivity`) How readily a steady, pure tone counts as feedback. Higher values react sooner and to quieter howls; lower values wait longer and may let a howl build. At 50 % a clear howl gets its first notch within about 0.2 seconds and 15 dB of cut within half a second. ### Max depth (`max_depth`) The deepest any notch may go, from 3 to 40 dB. ### Q (`q`) The width of every notch. 30 is about a twentieth of an octave. Higher values are narrower and take less of the music with them. ### Notches (`notches`) How many notches may be active at once, from 1 to 8. When all are in use, a new howl takes over the shallowest notch. ### Release (`release_s`) The time, in seconds, for a notch at Max depth to recover once its howl is gone. Long releases keep a room rung out; short ones give the sound back quickly. ### Freeze (`freeze`) On holds every notch exactly as it is: no new notches and no release. Ring out the room, then freeze. ### Reset (`reset`) A change from Idle to Clear fades every notch out over 10 ms and starts listening afresh. Bind it to a Trigger control. ## Tips - Ring out before the show: raise the monitor level slowly until a howl starts, let the suppressor catch it, repeat, then switch Freeze on. - Lower Sensitivity if held notes from a flute or synth get notched. - The notches are kept only while the Toy runs. Run the ring-out again after reloading. ## See also [Parametric EQ](https://docs.tune.toys/nodes/std.eq/), [Resonance suppressor](https://docs.tune.toys/nodes/std.resonance/), [Limiter](https://docs.tune.toys/nodes/std.limiter/), [Compressor](https://docs.tune.toys/nodes/std.compressor/). --- URL: https://docs.tune.toys/nodes/std.svf/ Title: Filter A flexible filter that can cut highs, cut lows, isolate a band or remove one, with adjustable resonance. ## What it does The state-variable filter has a Cutoff and a Resonance (Q). Type chooses low-pass, band-pass, high-pass, notch or all-pass, or Morph, which moves continuously from low-pass through band-pass to high-pass. Slope chooses one stage (12 dB per octave) or two cascaded stages (24 dB per octave). Higher resonance emphasizes frequencies around the cutoff, up to a ringing peak. Cutoff is capped at 0.45 times the sample rate. ## When to use it Use it for filter sweeps, wah-style effects and subtractive tone shaping. Select Low-pass for a roll-off, High-pass to remove low frequencies, Band-pass to isolate a band, Notch to cut one, or All-pass to change phase. New filters start in Low-pass mode. The graph shows the response for the selected type and slope. Drag across it to set Cutoff and vertically to set Resonance; the wheel adjusts resonance too. Hover or focus the graph while playing to see the real-time output spectrum (RTA) behind the response. Patch, Properties and Frame faces use the same editor. ## Ports <!-- ports:start --> | Port | Direction | Signal | Lanes | | ------- | --------- | ------ | ----------------------- | | `audio` | Input | Audio | Inferred from the cable | | `audio` | Output | Audio | Inferred from the cable | <!-- ports:end --> ## Parameters <!-- parameters:start --> | Parameter | Label | Range | Default | Unit | Scale | Notes | | -------------- | ---------------------- | ----------- | ------- | ---- | ------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `cutoff` | Cutoff | 20 to 20000 | 1200 | Hz | log | Filter cutoff, capped at 0.45 times sample rate. | | `resonance` | Resonance | 0.5 to 12 | 0.707 | Q | linear | State-variable filter Q. | | `mode` | Morph | 0 to 2 | 0 | | linear | Continuous low-pass, band-pass and high-pass morph. Shown when `type` is 0. | | `type` | Type | 0 to 5 | 1 | | linear | Selects the implemented algorithm. Choices are discrete and are not LFO targets. Choices: 0 Morph, 1 Low-pass, 2 Band-pass, 3 High-pass, 4 Notch, 5 All-pass. | | `slope` | Slope | 0 to 1 | 0 | | linear | One or two cascaded state-variable stages; notch/all-pass retain their response semantics. Choices: 0 12 dB/oct, 1 24 dB/oct. | | `compensation` | Resonance compensation | 0 to 1 | 0 | | linear | Reduces resonant gain as Q increases. Shown when `type` is 0 or 1 or 3. | <!-- parameters:end --> ### Cutoff (`cutoff`) The filter frequency, from 20 to 20000 Hz on a logarithmic control. ### Resonance (`resonance`) The filter Q from 0.5 to 12. The default, 0.707, is a flat response. Higher values peak at the cutoff. ### Morph (`mode`) Used when Type is Morph: 0 is low-pass, 1 is band-pass, 2 is high-pass, with smooth values between. ### Type (`type`) Morph, Low-pass, Band-pass, High-pass, Notch or All-pass. A discrete choice. ### Slope (`slope`) 12 dB/oct uses one stage; 24 dB/oct cascades two. The notch and all-pass types keep their response meaning. ### Resonance compensation (`compensation`) When on, reduces the gain boost that comes with a high Q. It applies to Morph, Low-pass and High-pass. ## Tips - Start with a bright source such as a saw wave; filters need harmonics to remove. - Sweep Cutoff with Resonance around 4 to 8 for classic filter sweeps. - Very high resonance can be loud. Lower the level or turn Compensation on. ## See also [Low-pass filter](https://docs.tune.toys/nodes/std.lowpass/), [Drive](https://docs.tune.toys/nodes/std.drive/), [Oscillator](https://docs.tune.toys/nodes/std.oscillator/). --- URL: https://docs.tune.toys/nodes/std.transient/ Title: Transient shaper A transient shaper changes the shape of each hit rather than its level: more snap or less, a longer ring or a tighter one. ## What it does Each channel keeps a level that holds every peak for 10 ms, so tones above 50 Hz read as steady, and two followers of it. One rises slowly and falls instantly, so it lags only while a hit grows: that gap is the hit's attack. The other rises instantly and falls slowly, so it stays above only while a hit decays: that gap is its sustain. Attack and Sustain set the gain, in decibels, applied in proportion to each gap, reaching their full value when a gap is 12 dB. The gain follows the shape of the sound, not its loudness, so soft and loud hits change alike, and a held tone with no onset or decay passes unchanged. The fast follower attacks in 1 ms and releases in 20 ms; Speed multiplies those times for the slow side. Below Floor the shaping fades out over 6 dB, so quiet noise between hits is not boosted. Mix blends the shaped signal with the dry one, and Output trims the result. With Attack and Sustain at zero the node passes audio bit for bit. ## When to use it Use it to add punch to drums or plucks, to soften harsh attacks, to bring out a room's ring or to dry it up, all without setting a threshold. To control level instead of shape, use the [Compressor](https://docs.tune.toys/nodes/std.compressor/); to silence the gaps, use the [Gate](https://docs.tune.toys/nodes/std.gate/). ## Ports <!-- ports:start --> | Port | Direction | Signal | Lanes | | ------- | --------- | ------ | ----------------------- | | `audio` | Input | Audio | Inferred from the cable | | `audio` | Output | Audio | Inferred from the cable | <!-- ports:end --> ## Parameters <!-- parameters:start --> | Parameter | Label | Range | Default | Unit | Scale | Notes | | --------- | ------- | --------- | ------- | ---- | ------ | ----------------------------------------------------------------------------------------------- | | `attack` | Attack | -24 to 24 | 0 | dB | linear | Gain on onsets: positive adds punch, negative softens hits. | | `sustain` | Sustain | -24 to 24 | 0 | dB | linear | Gain on decays: positive brings out tails and room, negative tightens them. | | `speed` | Speed | 2 to 20 | 8 | × | log | Ratio of the slow to the fast follower times; higher treats longer spans as attack and sustain. | | `floor` | Floor | -80 to 0 | -60 | dBFS | linear | Below this level shaping fades out, so quiet noise is not boosted. | | `mix` | Mix | 0 to 1 | 1 | | linear | Dry/shaped blend. | | `output` | Output | -24 to 24 | 0 | dB | linear | Gain after shaping. | <!-- parameters:end --> ### Attack (`attack`) Gain on onsets, from −24 to 24 dB. Positive values add snap; negative values soften hits. ### Sustain (`sustain`) Gain on decays, from −24 to 24 dB. Positive values bring out tails and room; negative values tighten them. ### Speed (`speed`) The ratio, from 2 to 20, between the slow and fast follower times. Higher values treat a longer stretch of each onset as attack and of each decay as sustain. ### Floor (`floor`) The level, from −80 to 0 dBFS, below which shaping fades out. ### Mix (`mix`) Blend of the dry signal (0) and the shaped signal (1). ### Output (`output`) Gain after shaping, from −24 to 24 dB. ## Tips - Boosting Attack raises peaks: lower Output or follow with a [Limiter](https://docs.tune.toys/nodes/std.limiter/). - A negative Sustain is a quick way to dry up a boomy drum. - Raise Floor when a Sustain boost lifts the noise between hits. ## See also [Compressor](https://docs.tune.toys/nodes/std.compressor/), [Gate](https://docs.tune.toys/nodes/std.gate/), [Envelope](https://docs.tune.toys/nodes/std.envelope/). --- URL: https://docs.tune.toys/nodes/std.tuner/ Title: Tuner A Tuner sits in an audio chain and shows how far the sound is from the nearest note, or degree, of a tuning. By default it measures against the **Toy's tuning**, the one the Toy's instruments play. It can also use its own: ordinary 12-tone equal temperament, five finer equal divisions, just intonation, Pythagorean tuning, Bohlen–Pierce or any scale you import from a Scala `.scl` file. ## What it does Audio passes through bit for bit; the Tuner never changes the sound. Mute fades the output to silence over 5 ms and back, so you can tune on stage without the audience hearing it. The Tuner estimates a single pitch from its **input** on the UI thread, away from the audio callback. It keeps reading while Mute silences the output. Choose a **View**: - **Meter** shows a large note and octave (or scale degree), measured hertz, signed cents, a needle and a strip of every degree across one period. Right of centre is sharp; left is flat. The ruler spans ±50 cents, or half the smallest step for tunings finer than 24-EDO, such as ±19.4 cents in 31-EDO. - **Wheel** places one tuning period around a circle, with degrees at their actual cents positions. Unequal scales have unequal gaps; Bohlen–Pierce fills a tritave rather than an octave. The needle shows measured pitch, the highlighted dot marks its nearest degree, and an arc shows the error. The outer strobe moves with measured frequency error, reverses for flat versus sharp, and stops at exact tuning. Green means the measured pitch is within **In tune ±**; amber means flat or sharp. The cents readout always shows the measured error. **Needle damping** steadies the needle without changing that readout. **Level gate** rejects quiet input by RMS dBFS. **Hold** briefly preserves the last reading, dimmed and labelled Held, after pitch disappears; then the needle, arc and strobe clear. Uncertain estimates dim and show Unclear. These are monophonic displays: chords and noise may not give a reliable pitch. Both views use the same tuning, controls and input measurement in Patch cards, the inspector and Frame widgets. Display settings never retune the audio. ## When to use it Use it after a source to check its tuning: an [Oscillator](https://docs.tune.toys/nodes/std.oscillator/) or a [Polyphonic instrument](https://docs.tune.toys/nodes/std.poly/) voice. To hear or check a microtonal scale, choose its tuning and play notes near its degrees. One Tuner measures one pitch at a time; it does not transcribe chords. ## Ports <!-- ports:start --> | Port | Direction | Signal | Lanes | | ------- | --------- | ------ | ----------------------- | | `audio` | Input | Audio | Inferred from the cable | | `audio` | Output | Audio | Inferred from the cable | <!-- ports:end --> ## Parameters <!-- parameters:start --> | Parameter | Label | Range | Default | Unit | Scale | Notes | | ----------- | -------------- | ----------- | ------- | ---- | ------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `reference` | Reference | 400 to 480 | 440 | Hz | linear | Frequency of A4 for the Tuner's own tunings. Equal divisions keep A4 on a degree; just, Pythagorean, Bohlen–Pierce and custom scales start at Root. Toy tuning uses the Toy's reference instead. Shown when `tuning` is 0 or 1 or 2 or 3 or 4 or 5 or 6 or 7 or 8 or 9. | | `mute` | Mute | 0 to 1 | 0 | | linear | On fades the output to silence over 5 ms, like a tuner pedal; Off fades back to an exact pass-through. Choices: 0 Off, 1 On. | | `tuning` | Tuning | 0 to 10 | 10 | | linear | The degrees the face measures against. Toy tuning follows the Toy's own tuning, the one its instruments play; Custom uses the node's cents table and period, such as an imported Scala scale. Choices: 0 12-TET, 1 19-EDO, 2 22-EDO, 3 24-EDO, 4 31-EDO, 5 53-EDO, 6 Just intonation, 7 Pythagorean, 8 Bohlen–Pierce, 9 Custom, 10 Toy tuning. | | `root` | Root | 0 to 11 | 0 | | linear | Tonic pitch class of the Tuner's own just, Pythagorean, Bohlen–Pierce and custom scales, at its 12-TET pitch from Reference. Choices: 0 C, 1 C♯, 2 D, 3 D♯, 4 E, 5 F, 6 F♯, 7 G, 8 G♯, 9 A, 10 A♯, 11 B. Shown when `tuning` is 0 or 1 or 2 or 3 or 4 or 5 or 6 or 7 or 8 or 9. | | `view` | View | 0 to 1 | 0 | | linear | Meter shows signed cents; Wheel places every degree at its true cents position and adds a measured-error strobe. Choices: 0 Meter, 1 Wheel. | | `tolerance` | In tune ± | 1 to 25 | 5 | ¢ | linear | Cents from a degree that count as in tune, capped at half the smallest scale step. | | `damping` | Needle damping | 0 to 0.95 | 0.75 | | linear | Needle smoothing at a 60 Hz reference rate. Zero follows immediately; higher values settle more slowly. Raw note, Hz and cents stay measured. | | `hold_ms` | Hold | 100 to 3000 | 600 | ms | linear | Retains a dimmed reading for this long after pitch is lost or falls below Level gate. | | `gate_db` | Level gate | -70 to -20 | -46 | dBFS | linear | Minimum RMS level for a new pitch reading. Below this, the last reading is held, then cleared. | <!-- parameters:end --> <!-- data:start --> - `cents`: floats. - `period`: floats. <!-- data:end --> ### Reference (`reference`) The frequency of A4 for the Tuner's own tunings, from 400 to 480 Hz; 440 by default. With Tuning on Toy tuning it is hidden: the Toy's tuning sets the reference. In the equal divisions of the octave (12-TET to 53-EDO) A4 is always a degree, so the reference stays in tune. Drag the A4 point on the face sideways, or focus it and use the arrow keys or the wheel; Shift moves in tenths of a hertz. ### Mute (`mute`) On fades the output to silence over 5 ms; Off fades back to an exact pass-through. A Toy that starts muted is silent from its first sample. ### Tuning (`tuning`) The degrees the face measures against. **Toy tuning**, the default, follows the Toy's own `tuning`: the preset or custom table, Root and Reference its [Polyphonic instrument](https://docs.tune.toys/nodes/std.poly/) voices play, or 12-TET at 440 Hz when the Toy declares none. The face's caption then starts with "Toy", such as `Toy · 31-EDO · A = 440`, and it has no Reference point to drag. Any other choice uses the Tuner's own Reference and Root: | Tuning | Degrees per period | Names | | --------------- | ------------------------- | --------------------------------------- | | 12-TET | 12 per octave | C, C♯, D … B with an octave number, A4 | | 19-EDO … 53-EDO | 19, 22, 24, 31 or 53 | `n\N` steps from C, such as `14\19` (A) | | Just intonation | 12, five-limit ratios | Note names from Root | | Pythagorean | 12, a chain of pure 3/2 | Note names from Root | | Bohlen–Pierce | 13 equal steps of 3:1 | `n\13` from Root | | Custom | Up to 128, from node data | Degree numbers from Root, 0 is the root | Just intonation uses 1/1, 16/15, 9/8, 6/5, 5/4, 4/3, 45/32, 3/2, 8/5, 5/3, 9/5 and 15/8. Pythagorean uses 1/1, 256/243, 9/8, 32/27, 81/64, 4/3, 729/512, 3/2, 128/81, 27/16, 16/9 and 243/128. Custom reads the node's `cents` data (degrees in cents above the root, ascending, from 0, each up to 9600) and `period` data (one value in cents, 1200 for an octave and about 1901.955 for a tritave). The **Import .scl…** button under the face fills both from a Scala file and selects Custom, as one edit that a single Undo reverts. Files are at most 64 KiB with at most 128 pitches; a malformed line, or pitches that are not positive and ascending, is refused with the line number. ### Root (`root`) The tonic pitch class, C to B, of the Tuner's own just, Pythagorean, Bohlen–Pierce and custom scales; hidden on Toy tuning, which brings its own root. The root sits at its 12-TET pitch from Reference, and the other degrees are measured from it. Equal divisions of the octave ignore it. ### View (`view`), tolerance (`tolerance`), damping (`damping`), Hold (`hold_ms`) and Level gate (`gate_db`) View selects Meter or Wheel. In tune ± defaults to 5 cents and is capped at half the smallest scale step, so the tolerance cannot span neighbouring degrees. Needle damping defaults to 0.75 and uses elapsed time, so display refresh rate does not change its response. Hold defaults to 600 ms; Level gate defaults to −46 dBFS. All five controls belong to the Tuner and can be bound on a Frame like its other parameters. ## Tips - Keep Mute on while you tune a live instrument, then switch it off to play. - A just major third sounds 13.7 cents flatter than the 12-TET third: set Tuning to Just intonation with Root on the key and watch the needle centre on the pure interval. - 24-EDO adds quarter tones between the 12-TET notes; 31-EDO is close to quarter-comma meantone. - Leave Tuning on Toy tuning after an instrument, and the needle centres on every key the instrument plays, whatever the Toy's tuning. ## See also [Oscillator](https://docs.tune.toys/nodes/std.oscillator/), [Polyphonic instrument](https://docs.tune.toys/nodes/std.poly/), [Gain](https://docs.tune.toys/nodes/std.gain/). --- URL: https://docs.tune.toys/nodes/std.vocoder/ Title: Vocoder Make any sound speak: a voice, drum or noise on Key shapes the spectrum of a synth or chord on Audio, band by band. ## What it does The vocoder splits both signals into the same set of bands, spaced evenly in pitch from Low band to High band. For each band it follows the level of the modulator (the `key` input) and sets the level of that band of the carrier (the `audio` input) to match. The carrier supplies the pitch and the tone; the modulator supplies the moving shape of vowels and consonants. Each band is a pair of band-pass filters, and the envelopes rise with Attack and fall with Release. There is no added latency. With nothing on Key, the vocoder uses its own input as the modulator, which turns it into a smooth band-envelope filter. A one-lane modulator drives every lane of a wider carrier. The level is set so a broadband modulator at about −12 dBFS RMS leaves a broadband carrier near its own level, whatever the band count and width. ## When to use it Use it for robot voices, talking synths and pads, drums that "speak" through a chord, or rhythmic gating of a sustained sound by another part. Put the sound with the pitch on Audio and the sound with the rhythm or words on Key. For one moving resonance rather than a whole bank, use the [Filter](https://docs.tune.toys/nodes/std.svf/). ## Ports <!-- ports:start --> | Port | Direction | Signal | Lanes | | ------- | --------- | ------ | ----------------------- | | `audio` | Input | Audio | Inferred from the cable | | `key` | Input | Audio | Inferred from the cable | | `audio` | Output | Audio | Inferred from the cable | <!-- ports:end --> ## Parameters <!-- parameters:start --> | Parameter | Label | Range | Default | Unit | Scale | Notes | | ------------ | ---------- | ------------ | ------- | ---- | ------ | -------------------------------------------------------------------------------------------------------------------------------------------- | | `bands` | Bands | 0 to 4 | 2 | | linear | Number of bands, spaced evenly in pitch from Low band to High band. More bands make speech clearer. Choices: 0 8, 1 12, 2 16, 3 24, 4 32. | | `low_hz` | Low band | 20 to 2000 | 100 | Hz | log | Centre of the lowest band. | | `high_hz` | High band | 500 to 16000 | 8000 | Hz | log | Centre of the highest band, kept at least an octave above Low band and below 0.45 times the sample rate. | | `width` | Band width | 0.5 to 2 | 1 | × | log | Band width relative to the band spacing: 1 makes neighbouring bands cross at −3 dB, lower is narrower and more resonant. | | `attack_ms` | Attack | 0.5 to 100 | 4 | ms | log | How fast each band envelope follows a rise in the modulator. | | `release_ms` | Release | 5 to 1000 | 60 | ms | log | How fast each band envelope falls after the modulator drops. | | `sibilance` | Sibilance | 0 to 1 | 0.2 | % | linear | Level of the modulator itself above High band, so consonants such as s and t stay audible. Stored as a fraction; shown ×100 as a percentage. | | `formant` | Formant | -12 to 12 | 0 | st | linear | Shifts the carrier bands up or down against the modulator bands, in semitones, for a smaller or larger voice. | | `mix` | Mix | 0 to 1 | 1 | % | linear | Blend from the dry carrier to the vocoded signal. Stored as a fraction; shown ×100 as a percentage. | | `output` | Output | -24 to 24 | 0 | dB | linear | Output trim after the mix. | <!-- parameters:end --> ### Bands (`bands`) 8, 12, 16, 24 or 32 bands. Few bands sound vintage and blurry; many bands make words clearer. Changing the count crossfades over 5 ms. ### Low band and High band (`low_hz`, `high_hz`) The centres of the lowest and highest bands. The bank always spans at least an octave and stays under 0.45 times the sample rate. Most of a voice sits between 100 Hz and 8 kHz. ### Band width (`width`) How wide each band is relative to the spacing between bands. At 1, neighbours cross at −3 dB. Lower values make narrow, resonant, metallic bands; higher values overlap them for a smoother sound. ### Attack and Release (`attack_ms`, `release_ms`) How fast each band follows the modulator when it gets louder and when it gets quieter. Short attacks keep consonants crisp; longer releases smear words into pads. ### Sibilance (`sibilance`) Mixes in the modulator itself above the top band, through a 12 dB/oct high-pass. Consonants like s, t and f have little to vocode in most carriers; this keeps them audible. ### Formant (`formant`) Moves the carrier bands up or down against the modulator bands, from −12 to +12 semitones. Down sounds like a larger voice, up like a smaller one. The pitch of the carrier does not change. ### Mix and Output (`mix`, `output`) Mix blends from the dry carrier to the vocoded signal. Output trims the level after the mix. ## Tips - A bright, harmonically rich carrier works best: a saw, a pulse, a chord or noise. A sine has nothing to vocode. - Keep the modulator dry and up front. A voice with reverb on it smears every band. - Raise Sibilance when words lose their edges, and lower it if the hiss is distracting. - For a whisper, put noise on Audio. ## See also [Oscillator](https://docs.tune.toys/nodes/std.oscillator/), [Noise](https://docs.tune.toys/nodes/std.noise/), [Filter](https://docs.tune.toys/nodes/std.svf/), [Parametric EQ](https://docs.tune.toys/nodes/std.eq/). --- URL: https://docs.tune.toys/nodes/std.wavetable/ Title: Wavetable Wavetable plays a table of up to eight 2048-sample frames, morphing between them, with unison and MIDI pitch. ## What it does The table is node data: one to eight frames of 2048 samples each, as values between -1 and 1. Position moves across the frames, 0 at the first and 1 at the last, blending neighbouring frames. Unison stacks one to four detuned copies of each note, spread by Detune in cents. Frequency sets the pitch when no MIDI note is held; connecting a MIDI cable lets notes play it directly, one note at a time, and a held note overrides Frequency. Glide adds portamento between notes. Amplitude is the peak output level. The output is audio and follows the width of its configuration. ## When to use it Use it for evolving timbres that a fixed waveform cannot make: sweep Position across drawn frames, thicken with Unison, or play it from a [Keyboard](https://docs.tune.toys/nodes/midi.keyboard/). For a plain waveform or [Polyphonic instrument](https://docs.tune.toys/nodes/std.poly/) chords use those instead; this oscillator plays one note at a time. ## Ports <!-- ports:start --> | Port | Direction | Signal | Lanes | | ------- | --------- | ------ | ----------------------- | | `midi` | Input | MIDI | 16 MIDI channels | | `audio` | Output | Audio | Inferred from the cable | <!-- ports:end --> ## Parameters <!-- parameters:start --> | Parameter | Label | Range | Default | Unit | Scale | Notes | | ----------- | --------- | ----------- | ------- | ----- | ------ | -------------------------------------------------------------------------------------------------- | | `frequency` | Frequency | 20 to 20000 | 220 | Hz | log | Pitch when no MIDI note is held. A held note overrides it and glides between notes. | | `position` | Position | 0 to 1 | 0 | | linear | Morph position across the table's frames; 0 is the first frame and 1 the last. | | `amplitude` | Amplitude | 0 to 1 | 0.25 | | linear | Peak output level. | | `unison` | Unison | 1 to 4 | 1 | | linear | Number of stacked detuned voices per note. Choices: 1 1 voice, 2 2 voices, 3 3 voices, 4 4 voices. | | `detune` | Detune | 0 to 50 | 8 | cents | linear | Spread of the unison voices in cents. | | `glide_ms` | Glide | 0 to 2000 | 0 | ms | linear | Portamento time between MIDI notes. | <!-- parameters:end --> <!-- data:start --> - `table`: floats. <!-- data:end --> ### Frequency (`frequency`) The pitch, 20 to 20000 Hz on a logarithmic control, used when no MIDI note is held. ### Position (`position`) Morph position across the table's frames, 0 to 1. With a single frame there is nothing to morph to. ### Amplitude (`amplitude`) Peak output level, 0 to 1. ### Unison (`unison`) One to four stacked detuned voices per note. A discrete choice. ### Detune (`detune`) The spread of the unison voices, 0 to 50 cents. No effect with one voice. ### Glide (`glide_ms`) Portamento time between MIDI notes, 0 to 2000 ms. ## Tips - Edit waveforms directly on the node card or in Properties. **Waveform** selects the frame to edit; it does not move Position. **+ Add** appends a copy of that waveform and selects it (up to eight); **Remove** deletes the selected waveform (keeping at least one). - Draw with **Pencil**, **Line** or **Smooth**, or apply **Sine**, **Saw**, **Square**, **Triangle** or **Noise** to the selected waveform. Other waveforms stay unchanged. Each stroke, held arrow-key edit, preset, addition or removal is one Undo step, with live sound previews. Escape or a cancelled gesture restores the previous waveform. - On the drawing surface, Left/Right moves the sample cursor and Up/Down changes the waveform there; Shift makes finer edits. The Frame's **Wavetable curve** widget uses the same drawing tools for a fixed table frame. - Keep Amplitude modest when raising Unison; more voices add up. - Position is a control, so a [Control LFO](https://docs.tune.toys/nodes/ctl.lfo/) is the natural way to sweep it. ## See also [Oscillator](https://docs.tune.toys/nodes/std.oscillator/), [Polyphonic instrument](https://docs.tune.toys/nodes/std.poly/), [Keyboard](https://docs.tune.toys/nodes/midi.keyboard/), [MIDI](https://docs.tune.toys/concepts/midi/). --- URL: https://docs.tune.toys/nodes/time.cron/ Title: Cron Cron is the data domain's clock. It sends a trigger every few seconds, at the times a cron expression names, or on musical beats, and it sends the time with each tick as JSON. ## What it does Each tick sends `true` on `tick` and an object on `time`: `{ iso, epochMs, beat, bar, count }`, where `count` starts at 1. Ticks are stamped with the audio block they belong to, so they line up with the sound rather than with a UI timer. Cron only ticks while the transport rolls. Pressing Play starts it; stopping, seeking or changing tempo re-arms it. A cabled `enable` input pauses it while `false`. Cron runs in the browser's data runtime. The Player and exports report the data domain unavailable at load, so it does not tick there. ## When to use it Use it to drive anything that should happen again and again: pick a new [Random](https://docs.tune.toys/nodes/data.random/) value, refresh a [Web request](https://docs.tune.toys/nodes/web.request/), play a [JSON melody](https://docs.tune.toys/nodes/midi.from-data/) every bar, or log the time to the [Console](https://docs.tune.toys/nodes/log.console/). ## Ports <!-- ports:start --> | Port | Direction | Signal | Lanes | | -------- | --------- | -------------- | ----- | | `enable` | Input | Data (boolean) | 1 | | `tick` | Output | Data (trigger) | 1 | | `time` | Output | Data (json) | 1 | <!-- ports:end --> ## Parameters <!-- parameters:start --> | Parameter | Label | Range | Default | Unit | Scale | Notes | | ---------- | -------- | ------------ | ------- | ---- | ------ | ----------------------------------------------------------------------------------------------------------------------------------------------------- | | `mode` | Mode | 0 to 2 | 0 | | linear | Interval fires every few seconds, Cron follows the expression in the host time zone, Beat follows the transport. Choices: 0 Interval, 1 Cron, 2 Beat. | | `interval` | Interval | 0.05 to 3600 | 1 | s | log | Seconds between ticks in Interval mode. | | `division` | Division | 0 to 5 | 2 | | linear | Note value between ticks in Beat mode. Choices: 0 Whole, 1 Half, 2 Quarter, 3 Eighth, 4 Sixteenth, 5 Bar. | | `limit` | Limit | 0 to 100000 | 0 | | linear | Stop after this many ticks; 0 runs until stopped. | <!-- parameters:end --> <!-- data:start --> - `expression`: cron. <!-- data:end --> ### Mode (`mode`) Interval ticks every Interval seconds, counted from when the transport starts; ticks are targeted at exact frames, so they never drift. Cron follows the node's cron expression in the host's time zone. Beat ticks on the transport's beats at the chosen Division. ### Interval (`interval`) Seconds between ticks in Interval mode, from 0.05 to 3600. The first tick arrives one interval after Play. ### Division (`division`) The note value between ticks in Beat mode: whole, half, quarter, eighth or sixteenth notes, or one bar of the current time signature. ### Limit (`limit`) Stop after this many ticks. 0 runs until the transport stops. ### Cron expression The node's `expression` field takes five fields (`minute hour day month weekday`) or six with seconds first, such as `*/5 * * * * *` for every five seconds. The inspector previews the next five runs. It is used only in Cron mode. ## Tips - Nothing happens until you press Play: Cron follows the transport. - Use Beat mode for anything musical. It follows tempo changes, while Interval keeps clock time. - Wire `tick` to triggers and `time` to [Console log](https://docs.tune.toys/nodes/log.console/) to see when each tick happened. ## See also [Random](https://docs.tune.toys/nodes/data.random/), [Console log](https://docs.tune.toys/nodes/log.console/), [Data to MIDI](https://docs.tune.toys/nodes/midi.from-data/), [Data cables](https://docs.tune.toys/concepts/data-cables/), [Takes](https://docs.tune.toys/concepts/takes/). --- URL: https://docs.tune.toys/nodes/video.blur/ Title: Blur Blur applies a Gaussian blur with a bounded radius at the source resolution. ## What it does The radius is in pixels at the source size, from 0 to 64; 0 passes the picture through. It processes every stream of the input at that stream's size and keeps the stream count. ## When to use it Use it to soften a background, hide detail or smooth a key edge. ## Ports <!-- ports:start --> | Port | Direction | Signal | Lanes | | ------- | --------- | ------ | ----------------------- | | `video` | Input | Video | Inferred from the cable | | `video` | Output | Video | Inferred from the cable | <!-- ports:end --> ## Parameters <!-- parameters:start --> | Parameter | Label | Range | Default | Unit | Scale | Notes | | --------- | ------ | ------- | ------- | ---- | ------ | ----------------------------------------------------------- | | `radius` | Radius | 0 to 64 | 8 | px | linear | Blur radius in source pixels; 0 passes the picture through. | <!-- parameters:end --> ### Radius (`radius`) Blur radius in pixels, from 0 to 64. ## Tips - Radius is bounded so the cost stays predictable. - Video needs the browser's WebGPU video engine. The Player and exports do not run video yet and report it unavailable. To see a video output, bind a live Screen in the Frame to it. ## See also [Pixelate](https://docs.tune.toys/nodes/video.pixelate/), [Color](https://docs.tune.toys/nodes/video.color/), [Chroma key](https://docs.tune.toys/nodes/video.chroma-key/), [Video](https://docs.tune.toys/concepts/video/), [Control lanes](https://docs.tune.toys/concepts/control-lanes/). --- URL: https://docs.tune.toys/nodes/video.camera/ Title: Camera Camera is the Toy's webcam source. It outputs one video stream and is the usual starting point for filters, keying and sensing. ## What it does The Toy names a logical camera in `data.label` (for example Camera A); the host binds that label to a real device after the person allows it, and the camera prompts when the Toy is played. Only the label is stored in the Toy, never a device id. The output is one stream of frames, paced by the camera: the newest frame wins and slow consumers drop frames rather than queue them. The Toy never records or uploads the camera. ## When to use it Use it as the picture source for [Color](https://docs.tune.toys/nodes/video.color/), [Chroma key](https://docs.tune.toys/nodes/video.chroma-key/) and the [sensing nodes](https://docs.tune.toys/concepts/sensing/). Use [Screen capture](https://docs.tune.toys/nodes/video.display/) for a window or tab instead. ## Ports <!-- ports:start --> | Port | Direction | Signal | Lanes | | ------- | --------- | ------ | ----- | | `video` | Output | Video | 1 | <!-- ports:end --> ## Parameters <!-- parameters:start --> | Parameter | Label | Range | Default | Unit | Scale | Notes | | --------- | ------ | ------ | ------- | ---- | ------ | ------------------------------------------------------------------------------------------------------------- | | `mirror` | Mirror | 0 to 1 | 1 | | linear | Flip the image horizontally so the person sees themselves as in a mirror. Choices: 0 As captured, 1 Mirrored. | <!-- parameters:end --> <!-- data:start --> - `label`: text. <!-- data:end --> ### Mirror (`mirror`) Flips the picture left to right, like a mirror. On by default so moving your hand right moves it right on screen. ## Tips - Press Play to start the camera; the browser asks permission first. - Declare no device ids: the label is the only thing the Toy keeps. - Video needs the browser's WebGPU video engine. The Player and exports do not run video yet and report it unavailable. To see a video output, bind a live Screen in the Frame to it. ## See also [Screen capture](https://docs.tune.toys/nodes/video.display/), [Color](https://docs.tune.toys/nodes/video.color/), [Face](https://docs.tune.toys/nodes/sense.face/), [Video](https://docs.tune.toys/concepts/video/), [Control lanes](https://docs.tune.toys/concepts/control-lanes/). --- URL: https://docs.tune.toys/nodes/video.chroma-key/ Title: Chroma key Chroma key turns one colour transparent: the green screen. ## What it does Hue picks the colour to remove, 120 for green and 240 for blue. Saturation sets how colourful a pixel must be to count, Tolerance how far from the hue still counts, Softness how gently the edge fades and Spill how much key colour is removed from what remains. The output keeps an alpha channel; place it as a layer of a [Composite](https://docs.tune.toys/nodes/video.composite/). It processes every stream of the input at that stream's size and keeps the stream count. ## When to use it Use it to put a person in front of a backdrop image or video. ## Ports <!-- ports:start --> | Port | Direction | Signal | Lanes | | ------- | --------- | ------ | ----------------------- | | `video` | Input | Video | Inferred from the cable | | `video` | Output | Video | Inferred from the cable | <!-- ports:end --> ## Parameters <!-- parameters:start --> | Parameter | Label | Range | Default | Unit | Scale | Notes | | ------------ | ---------------- | -------- | ------- | ---- | ------ | -------------------------------------------------------- | | `hue` | Key hue | 0 to 360 | 120 | ° | linear | Hue of the colour to remove; 120 is green. | | `saturation` | Saturation floor | 0 to 1 | 0.3 | | linear | Pixels less saturated than this are never keyed. | | `tolerance` | Tolerance | 0 to 180 | 25 | ° | linear | Hue distance that is fully transparent. | | `softness` | Softness | 0 to 90 | 15 | ° | linear | Extra hue distance over which transparency fades in. | | `spill` | Spill | 0 to 1 | 0.5 | | linear | How strongly the key colour is desaturated at the edges. | <!-- parameters:end --> ### Key hue (`hue`) Colour to remove, in degrees around the colour wheel. ### Saturation floor (`saturation`) Minimum colour strength a pixel needs to be keyed. ### Tolerance (`tolerance`) How far from the hue, in degrees, still counts as the key colour. ### Softness (`softness`) Width of the soft edge, in degrees. ### Spill (`spill`) How much of the key colour is neutralised on what remains. ## Tips - Light the screen evenly; a clean key needs little Tolerance. - Raise Spill if the subject has a green fringe. - Video needs the browser's WebGPU video engine. The Player and exports do not run video yet and report it unavailable. To see a video output, bind a live Screen in the Frame to it. ## See also [Composite](https://docs.tune.toys/nodes/video.composite/), [Color](https://docs.tune.toys/nodes/video.color/), [Image](https://docs.tune.toys/nodes/video.image/), [Video](https://docs.tune.toys/concepts/video/), [Control lanes](https://docs.tune.toys/concepts/control-lanes/). --- URL: https://docs.tune.toys/nodes/video.color/ Title: Color Color adjusts brightness, contrast, saturation, hue, gamma and warmth, or inverts the picture. ## What it does Each control changes one aspect of the picture and the defaults change nothing. It processes every stream of the input at that stream's size and keeps the stream count. ## When to use it Use it to match sources, stylise a camera, or build a photobooth with [Pixelate](https://docs.tune.toys/nodes/video.pixelate/). ## Ports <!-- ports:start --> | Port | Direction | Signal | Lanes | | ------- | --------- | ------ | ----------------------- | | `video` | Input | Video | Inferred from the cable | | `video` | Output | Video | Inferred from the cable | <!-- ports:end --> ## Parameters <!-- parameters:start --> | Parameter | Label | Range | Default | Unit | Scale | Notes | | ------------- | ---------- | ----------- | ------- | ---- | ------ | --------------------------------------------------- | | `brightness` | Brightness | -1 to 1 | 0 | | linear | Offset added to every channel. | | `contrast` | Contrast | 0 to 3 | 1 | | linear | Scale around mid grey. | | `saturation` | Saturation | 0 to 3 | 1 | | linear | 0 is monochrome, 1 unchanged. | | `hue` | Hue | -180 to 180 | 0 | ° | linear | Hue rotation in degrees. | | `gamma` | Gamma | 0.2 to 5 | 1 | | log | Gamma curve applied to each channel. | | `temperature` | Warmth | -1 to 1 | 0 | | linear | Shift towards orange (positive) or blue (negative). | | `invert` | Invert | 0 to 1 | 0 | | linear | Invert the colours. Choices: 0 Normal, 1 Inverted. | <!-- parameters:end --> ### Brightness (`brightness`) Adds or removes light, from -1 to 1. ### Contrast (`contrast`) Spreads or squeezes tones around grey; 1 is unchanged. ### Saturation (`saturation`) Colour strength; 0 is greyscale and 1 is unchanged. ### Hue (`hue`) Rotates all colours around the colour wheel, in degrees. ### Gamma (`gamma`) Bends the midtones; below 1 brightens and above 1 darkens. Logarithmic. ### Warmth (`temperature`) Shifts the picture cooler (negative) or warmer (positive). ### Invert (`invert`) Turns every colour into its opposite when on. ## Tips - Chain it before [Chroma key](https://docs.tune.toys/nodes/video.chroma-key/) to help a difficult key. - Video needs the browser's WebGPU video engine. The Player and exports do not run video yet and report it unavailable. To see a video output, bind a live Screen in the Frame to it. ## See also [Pixelate](https://docs.tune.toys/nodes/video.pixelate/), [Blur](https://docs.tune.toys/nodes/video.blur/), [Chroma key](https://docs.tune.toys/nodes/video.chroma-key/), [Video](https://docs.tune.toys/concepts/video/), [Control lanes](https://docs.tune.toys/concepts/control-lanes/). --- URL: https://docs.tune.toys/nodes/video.combine/ Title: Combine streams Combine streams concatenates two video bundles into one, preserving every stream. ## What it does It has no parameters. All streams of `a` come first, then all streams of `b`, up to 8 in total. Nest Combine nodes to join more. ## When to use it Use it to gather sources for [Multiview](https://docs.tune.toys/nodes/video.multiview/) or to pass several pictures down one cable. ## Ports <!-- ports:start --> | Port | Direction | Signal | Lanes | | ------- | --------- | ------ | ----------------------- | | `a` | Input | Video | Inferred from the cable | | `b` | Input | Video | Inferred from the cable | | `video` | Output | Video | Inferred from the cable | <!-- ports:end --> ## Parameters <!-- parameters:start --> This node has no numeric parameters. <!-- parameters:end --> This node has no parameters. ## Tips - Order matters: A first, then B. - Video needs the browser's WebGPU video engine. The Player and exports do not run video yet and report it unavailable. To see a video output, bind a live Screen in the Frame to it. ## See also [Select streams](https://docs.tune.toys/nodes/video.select/), [Multiview](https://docs.tune.toys/nodes/video.multiview/), [Switcher](https://docs.tune.toys/nodes/video.switch/), [Video](https://docs.tune.toys/concepts/video/), [Control lanes](https://docs.tune.toys/concepts/control-lanes/). --- URL: https://docs.tune.toys/nodes/video.composite/ Title: Composite Composite stacks up to four layers over a base, with blend mode, opacity, position and scale for each layer. ## What it does The output is the size of the base. Each layer, `layer1` to `layer4`, is placed over the base in order. Per layer, Blend picks how it combines (0 to 3), Opacity fades it, X and Y move it and Scale resizes it. Each input takes exactly one stream. ## When to use it Use it to put a keyed camera over a backdrop, overlay text or paint, or build picture-in-picture. ## Ports <!-- ports:start --> | Port | Direction | Signal | Lanes | | -------- | --------- | ------ | ----- | | `base` | Input | Video | 1 | | `layer1` | Input | Video | 1 | | `layer2` | Input | Video | 1 | | `layer3` | Input | Video | 1 | | `layer4` | Input | Video | 1 | | `video` | Output | Video | 1 | <!-- ports:end --> ## Parameters <!-- parameters:start --> | Parameter | Label | Range | Default | Unit | Scale | Notes | | ---------- | --------- | --------- | ------- | ---- | ------ | --------------------------------------------------------------------------------------- | | `blend1` | Blend 1 | 0 to 3 | 0 | | linear | Blend mode of layer 1 over what is below. Choices: 0 Over, 1 Add, 2 Multiply, 3 Screen. | | `opacity1` | Opacity 1 | 0 to 1 | 1 | | linear | Opacity of layer 1. | | `x1` | X 1 | -1 to 1 | 0 | | linear | Horizontal offset of layer 1 as a fraction of the base width. | | `y1` | Y 1 | -1 to 1 | 0 | | linear | Vertical offset of layer 1 as a fraction of the base height. | | `scale1` | Scale 1 | 0.05 to 8 | 1 | × | log | Scale of layer 1 about its centre. | | `blend2` | Blend 2 | 0 to 3 | 0 | | linear | Blend mode of layer 2 over what is below. Choices: 0 Over, 1 Add, 2 Multiply, 3 Screen. | | `opacity2` | Opacity 2 | 0 to 1 | 1 | | linear | Opacity of layer 2. | | `x2` | X 2 | -1 to 1 | 0 | | linear | Horizontal offset of layer 2 as a fraction of the base width. | | `y2` | Y 2 | -1 to 1 | 0 | | linear | Vertical offset of layer 2 as a fraction of the base height. | | `scale2` | Scale 2 | 0.05 to 8 | 1 | × | log | Scale of layer 2 about its centre. | | `blend3` | Blend 3 | 0 to 3 | 0 | | linear | Blend mode of layer 3 over what is below. Choices: 0 Over, 1 Add, 2 Multiply, 3 Screen. | | `opacity3` | Opacity 3 | 0 to 1 | 1 | | linear | Opacity of layer 3. | | `x3` | X 3 | -1 to 1 | 0 | | linear | Horizontal offset of layer 3 as a fraction of the base width. | | `y3` | Y 3 | -1 to 1 | 0 | | linear | Vertical offset of layer 3 as a fraction of the base height. | | `scale3` | Scale 3 | 0.05 to 8 | 1 | × | log | Scale of layer 3 about its centre. | | `blend4` | Blend 4 | 0 to 3 | 0 | | linear | Blend mode of layer 4 over what is below. Choices: 0 Over, 1 Add, 2 Multiply, 3 Screen. | | `opacity4` | Opacity 4 | 0 to 1 | 1 | | linear | Opacity of layer 4. | | `x4` | X 4 | -1 to 1 | 0 | | linear | Horizontal offset of layer 4 as a fraction of the base width. | | `y4` | Y 4 | -1 to 1 | 0 | | linear | Vertical offset of layer 4 as a fraction of the base height. | | `scale4` | Scale 4 | 0.05 to 8 | 1 | × | log | Scale of layer 4 about its centre. | <!-- parameters:end --> ### Blend 1 (`blend1`) Blend mode of layer 1, 0 to 3. ### Opacity 1 (`opacity1`) Opacity of layer 1. ### X 1 (`x1`) Horizontal position of layer 1, -1 to 1. ### Y 1 (`y1`) Vertical position of layer 1, -1 to 1. ### Scale 1 (`scale1`) Size of layer 1, logarithmic. ### Blend 2 (`blend2`) Blend mode of layer 2. ### Opacity 2 (`opacity2`) Opacity of layer 2. ### X 2 (`x2`) Horizontal position of layer 2. ### Y 2 (`y2`) Vertical position of layer 2. ### Scale 2 (`scale2`) Size of layer 2. ### Blend 3 (`blend3`) Blend mode of layer 3. ### Opacity 3 (`opacity3`) Opacity of layer 3. ### X 3 (`x3`) Horizontal position of layer 3. ### Y 3 (`y3`) Vertical position of layer 3. ### Scale 3 (`scale3`) Size of layer 3. ### Blend 4 (`blend4`) Blend mode of layer 4. ### Opacity 4 (`opacity4`) Opacity of layer 4. ### X 4 (`x4`) Horizontal position of layer 4. ### Y 4 (`y4`) Vertical position of layer 4. ### Scale 4 (`scale4`) Size of layer 4. ## Tips - Unconnected layers are ignored. - Use [Transform](https://docs.tune.toys/nodes/video.transform/) to prepare a layer's size first. - Video needs the browser's WebGPU video engine. The Player and exports do not run video yet and report it unavailable. To see a video output, bind a live Screen in the Frame to it. ## See also [Chroma key](https://docs.tune.toys/nodes/video.chroma-key/), [Video crossfade](https://docs.tune.toys/nodes/video.crossfade/), [Image](https://docs.tune.toys/nodes/video.image/), [Video](https://docs.tune.toys/concepts/video/), [Control lanes](https://docs.tune.toys/concepts/control-lanes/). --- URL: https://docs.tune.toys/nodes/video.crossfade/ Title: Video crossfade Video crossfade blends two streams with one mix control. ## What it does Mix 0 shows only `a` and 1 shows only `b`. Each input takes one stream. ## When to use it Use it to dissolve between two sources by hand or with a control cable; use [Switcher](https://docs.tune.toys/nodes/video.switch/) for cuts and timed transitions. ## Ports <!-- ports:start --> | Port | Direction | Signal | Lanes | | ------- | --------- | ------ | ----- | | `a` | Input | Video | 1 | | `b` | Input | Video | 1 | | `video` | Output | Video | 1 | <!-- ports:end --> ## Parameters <!-- parameters:start --> | Parameter | Label | Range | Default | Unit | Scale | Notes | | --------- | ----- | ------ | ------- | ---- | ------ | --------------------- | | `mix` | Mix | 0 to 1 | 0 | | linear | 0 shows A, 1 shows B. | <!-- parameters:end --> ### Mix (`mix`) Balance between A (0) and B (1). ## Tips - Drive Mix from a control lane through its `@mix` socket. - Video needs the browser's WebGPU video engine. The Player and exports do not run video yet and report it unavailable. To see a video output, bind a live Screen in the Frame to it. ## See also [Switcher](https://docs.tune.toys/nodes/video.switch/), [Composite](https://docs.tune.toys/nodes/video.composite/), [Color](https://docs.tune.toys/nodes/video.color/), [Video](https://docs.tune.toys/concepts/video/), [Control lanes](https://docs.tune.toys/concepts/control-lanes/). --- URL: https://docs.tune.toys/nodes/video.display/ Title: Screen capture Screen capture shares a window, a browser tab or the whole display as one video stream. ## What it does Each time the Toy starts, the browser asks the person what to share. The output is one stream of frames, newest frame wins. It has no parameters. The Toy stores nothing about what was shared and never records or uploads it. ## When to use it Use it to bring a presentation, game or another app into a Toy for filtering, compositing or recording. ## Ports <!-- ports:start --> | Port | Direction | Signal | Lanes | | ------- | --------- | ------ | ----- | | `video` | Output | Video | 1 | <!-- ports:end --> ## Parameters <!-- parameters:start --> This node has no numeric parameters. <!-- parameters:end --> <!-- data:start --> - `label`: text. <!-- data:end --> This node has no parameters. ## Tips - The choice is made by the person every time; it cannot be pre-selected by the Toy. - Video needs the browser's WebGPU video engine. The Player and exports do not run video yet and report it unavailable. To see a video output, bind a live Screen in the Frame to it. ## See also [Camera](https://docs.tune.toys/nodes/video.camera/), [Media player](https://docs.tune.toys/nodes/media.player/), [Recorder](https://docs.tune.toys/nodes/media.recorder/), [Video](https://docs.tune.toys/concepts/video/), [Control lanes](https://docs.tune.toys/concepts/control-lanes/). --- URL: https://docs.tune.toys/nodes/video.image/ Title: Image Image turns a declared image asset into a still video stream for logos, backgrounds and keys. ## What it does Set `data.asset` to the id of an image asset declared in the Toy. The stream shows that image at its own size and never changes. It has no parameters. ## When to use it Use it as the base of a [Composite](https://docs.tune.toys/nodes/video.composite/), as the backdrop behind a keyed camera, or as a logo layer. ## Ports <!-- ports:start --> | Port | Direction | Signal | Lanes | | ------- | --------- | ------ | ----- | | `video` | Output | Video | 1 | <!-- ports:end --> ## Parameters <!-- parameters:start --> This node has no numeric parameters. <!-- parameters:end --> <!-- data:start --> - `asset`: text. <!-- data:end --> This node has no parameters. ## Tips - An asset id that is not declared in the Toy is rejected when it loads. - Video needs the browser's WebGPU video engine. The Player and exports do not run video yet and report it unavailable. To see a video output, bind a live Screen in the Frame to it. ## See also [Composite](https://docs.tune.toys/nodes/video.composite/), [Chroma key](https://docs.tune.toys/nodes/video.chroma-key/), [Media player](https://docs.tune.toys/nodes/media.player/), [Video](https://docs.tune.toys/concepts/video/), [Control lanes](https://docs.tune.toys/concepts/control-lanes/). --- URL: https://docs.tune.toys/nodes/video.multiview/ Title: Multiview Multiview tiles a bundle of streams into one picture, with labels, for monitoring. ## What it does The input carries 1 to 8 streams, joined with [Combine streams](https://docs.tune.toys/nodes/video.combine/). Columns sets the grid, Gap the space between tiles and Labels adds the stream numbers. ## When to use it Use it to watch several sources at once, for example before a [Switcher](https://docs.tune.toys/nodes/video.switch/). ## Ports <!-- ports:start --> | Port | Direction | Signal | Lanes | | ------- | --------- | ------ | ----------------------- | | `video` | Input | Video | Inferred from the cable | | `video` | Output | Video | 1 | <!-- ports:end --> ## Parameters <!-- parameters:start --> | Parameter | Label | Range | Default | Unit | Scale | Notes | | --------- | ------- | ------- | ------- | ---- | ------ | ------------------------------------------------------------- | | `columns` | Columns | 1 to 4 | 2 | | linear | Tiles per row. | | `gap` | Gap | 0 to 64 | 4 | px | linear | Space between tiles in pixels. | | `labels` | Labels | 0 to 1 | 1 | | linear | Show stream numbers on the tiles. Choices: 0 Hidden, 1 Shown. | <!-- parameters:end --> ### Columns (`columns`) Number of columns in the grid, 1 to 4. ### Gap (`gap`) Space between tiles in pixels. ### Labels (`labels`) Shows a label on each tile when on. ## Tips - The output is one stream and can be filtered or recorded like any other. - Video needs the browser's WebGPU video engine. The Player and exports do not run video yet and report it unavailable. To see a video output, bind a live Screen in the Frame to it. ## See also [Combine streams](https://docs.tune.toys/nodes/video.combine/), [Switcher](https://docs.tune.toys/nodes/video.switch/), [Select streams](https://docs.tune.toys/nodes/video.select/), [Video](https://docs.tune.toys/concepts/video/), [Control lanes](https://docs.tune.toys/concepts/control-lanes/). --- URL: https://docs.tune.toys/nodes/video.paint/ Title: Paint surface Paint surface is a transparent video surface that the paint widget (coming later) draws on. ## What it does The output is one transparent stream, so it composites over anything. Strokes belong to the surface; the paint widget that draws them is planned and is not available yet, so today the surface stays empty. ## When to use it Use it as an overlay layer in a [Composite](https://docs.tune.toys/nodes/video.composite/) for annotations and drawing once the widget lands. ## Ports <!-- ports:start --> | Port | Direction | Signal | Lanes | | ------- | --------- | ------ | ----- | | `video` | Output | Video | 1 | <!-- ports:end --> ## Parameters <!-- parameters:start --> | Parameter | Label | Range | Default | Unit | Scale | Notes | | --------- | ----- | ------ | ------- | ---- | ------ | -------------------------------------------------------- | | `clear` | Clear | 0 to 1 | 0 | | linear | Rising to 1 wipes the surface. Choices: 0 Keep, 1 Clear. | <!-- parameters:end --> ### Clear (`clear`) Wipes everything drawn on the surface when it goes high. ## Tips - Place it as a top layer so the picture beneath stays visible. - Video needs the browser's WebGPU video engine. The Player and exports do not run video yet and report it unavailable. To see a video output, bind a live Screen in the Frame to it. ## See also [Composite](https://docs.tune.toys/nodes/video.composite/), [Camera](https://docs.tune.toys/nodes/video.camera/), [Text layer](https://docs.tune.toys/nodes/video.text/), [Video](https://docs.tune.toys/concepts/video/), [Control lanes](https://docs.tune.toys/concepts/control-lanes/). --- URL: https://docs.tune.toys/nodes/video.pixelate/ Title: Pixelate Pixelate makes the picture chunky with mosaic cells and fewer colour levels. ## What it does Cell sets how big the mosaic squares are and Levels how many shades each colour keeps. It processes every stream of the input at that stream's size and keeps the stream count. ## When to use it Use it for retro looks, to anonymise a picture, or at the end of a photobooth chain. ## Ports <!-- ports:start --> | Port | Direction | Signal | Lanes | | ------- | --------- | ------ | ----------------------- | | `video` | Input | Video | Inferred from the cable | | `video` | Output | Video | Inferred from the cable | <!-- ports:end --> ## Parameters <!-- parameters:start --> | Parameter | Label | Range | Default | Unit | Scale | Notes | | --------- | --------- | -------- | ------- | ---- | ----- | ------------------------------------------------------- | | `cell` | Cell size | 1 to 128 | 12 | px | log | Size of each mosaic cell in source pixels. | | `levels` | Levels | 2 to 256 | 256 | | log | Colour levels per channel; 256 leaves colour untouched. | <!-- parameters:end --> ### Cell size (`cell`) Mosaic cell size in pixels, from 1 to 128. It is logarithmic; 1 is unchanged. ### Levels (`levels`) Number of shades per colour channel, from 2 to 256. 256 is unchanged. ## Tips - Combine a small Levels with a small Cell for a posterised look. - Video needs the browser's WebGPU video engine. The Player and exports do not run video yet and report it unavailable. To see a video output, bind a live Screen in the Frame to it. ## See also [Color](https://docs.tune.toys/nodes/video.color/), [Blur](https://docs.tune.toys/nodes/video.blur/), [Transform](https://docs.tune.toys/nodes/video.transform/), [Video](https://docs.tune.toys/concepts/video/), [Control lanes](https://docs.tune.toys/concepts/control-lanes/). --- URL: https://docs.tune.toys/nodes/video.select/ Title: Select streams Select streams extracts a contiguous range of streams from a video bundle. ## What it does It has no parameters. It takes a bundle of 1 to 8 streams on `video` and outputs the chosen range. The range is set the same way as on the audio [Select channels](https://docs.tune.toys/nodes/std.select/): the node's channel settings name the first stream and how many streams to keep, and the range must fit inside the incoming bundle. ## When to use it Use it to split one stream out of a bundle made with [Combine streams](https://docs.tune.toys/nodes/video.combine/). ## Ports <!-- ports:start --> | Port | Direction | Signal | Lanes | | ------- | --------- | ------ | ----------------------- | | `video` | Input | Video | Inferred from the cable | | `video` | Output | Video | Inferred from the cable | <!-- ports:end --> ## Parameters <!-- parameters:start --> This node has no numeric parameters. <!-- parameters:end --> This node has no parameters. ## Tips - Streams count from 1. - Video needs the browser's WebGPU video engine. The Player and exports do not run video yet and report it unavailable. To see a video output, bind a live Screen in the Frame to it. ## See also [Combine streams](https://docs.tune.toys/nodes/video.combine/), [Multiview](https://docs.tune.toys/nodes/video.multiview/), [Switcher](https://docs.tune.toys/nodes/video.switch/), [Video](https://docs.tune.toys/concepts/video/), [Control lanes](https://docs.tune.toys/concepts/control-lanes/). --- URL: https://docs.tune.toys/nodes/video.snapshot/ Title: Snapshot Snapshot captures the newest frame as a still when the shutter fires. ## What it does The host saves each capture as the shutter fires (the editor downloads it). Shutter takes the photo and Format chooses the image file type. Saving writes to the device; the Toy never uploads the picture. ## When to use it Use it for a photobooth, after filters such as [Color](https://docs.tune.toys/nodes/video.color/). ## Ports <!-- ports:start --> | Port | Direction | Signal | Lanes | | ------- | --------- | ------ | ----- | | `video` | Input | Video | 1 | <!-- ports:end --> ## Parameters <!-- parameters:start --> | Parameter | Label | Range | Default | Unit | Scale | Notes | | --------- | ------- | ------ | ------- | ---- | ------ | ----------------------------------------------------- | | `shutter` | Shutter | 0 to 1 | 0 | | linear | Rising to 1 takes a picture. Choices: 0 Idle, 1 Fire. | | `format` | Format | 0 to 1 | 0 | | linear | Image format of the capture. Choices: 0 PNG, 1 WebP. | <!-- parameters:end --> ### Shutter (`shutter`) Takes a photo when it goes high. ### Format (`format`) Image file type for saved photos; 0 is the default format and 1 the alternative. ## Tips - It has no output; connect the same source to a Screen to see what you capture. - Video needs the browser's WebGPU video engine. The Player and exports do not run video yet and report it unavailable. To see a video output, bind a live Screen in the Frame to it. ## See also [Recorder](https://docs.tune.toys/nodes/media.recorder/), [Camera](https://docs.tune.toys/nodes/video.camera/), [Color](https://docs.tune.toys/nodes/video.color/), [Video](https://docs.tune.toys/concepts/video/), [Control lanes](https://docs.tune.toys/concepts/control-lanes/). --- URL: https://docs.tune.toys/nodes/video.switch/ Title: Switcher Switcher cuts or transitions between up to four sources and outputs the program. ## What it does Program chooses the source on the output and Preview the next one. Changing Program starts the Transition, which lasts Duration milliseconds. Inputs are `in1` to `in4`, one stream each. ## When to use it Use it as a live vision mixer. The switcher widget is planned as its Frame face; the parameters stay the authority. ## Ports <!-- ports:start --> | Port | Direction | Signal | Lanes | | ------- | --------- | ------ | ----- | | `in1` | Input | Video | 1 | | `in2` | Input | Video | 1 | | `in3` | Input | Video | 1 | | `in4` | Input | Video | 1 | | `video` | Output | Video | 1 | <!-- ports:end --> ## Parameters <!-- parameters:start --> | Parameter | Label | Range | Default | Unit | Scale | Notes | | ------------- | ---------- | --------- | ------- | ---- | ------ | --------------------------------------------------------------------------------------------------------------- | | `program` | Program | 1 to 4 | 1 | | linear | The source on air; changing it starts the transition. | | `preview` | Preview | 1 to 4 | 2 | | linear | The source cued next; Take swaps it with the program. | | `transition` | Transition | 0 to 4 | 1 | | linear | How a program change moves between sources. Choices: 0 Cut, 1 Dissolve, 2 Wipe left, 3 Wipe right, 4 Wipe down. | | `duration_ms` | Duration | 0 to 5000 | 400 | ms | linear | Length of a dissolve or wipe. | <!-- parameters:end --> ### Program (`program`) The source shown on the output, 1 to 4. ### Preview (`preview`) The source ready for the next take, 1 to 4. ### Transition (`transition`) How a change of Program happens; 0 is a cut. ### Duration (`duration_ms`) Length of a transition in milliseconds. ## Tips - Make Program a public parameter so a Frame or MIDI control can take it. - Video needs the browser's WebGPU video engine. The Player and exports do not run video yet and report it unavailable. To see a video output, bind a live Screen in the Frame to it. ## See also [Video crossfade](https://docs.tune.toys/nodes/video.crossfade/), [Multiview](https://docs.tune.toys/nodes/video.multiview/), [Composite](https://docs.tune.toys/nodes/video.composite/), [Video](https://docs.tune.toys/concepts/video/), [Control lanes](https://docs.tune.toys/concepts/control-lanes/). --- URL: https://docs.tune.toys/nodes/video.text/ Title: Text layer Text layer renders a text template as a transparent video stream, with control lanes inserted into the text. ## What it does The template is `data.template`. In it `{1}` to `{8}` show lane 1 to 8 of the `fields` control input, with the number of decimals set by Decimals. The output is transparent except for the text, so it composites over any picture. Size, position, alignment, colour, opacity and shadow style the text; Width and Height set the surface size. ## When to use it Use it for scoreboards, labels and lower thirds. Feed `fields` from [Counter](https://docs.tune.toys/nodes/ctl.counter/) for a score or from [Select lanes](https://docs.tune.toys/nodes/ctl.select/) for any single value. ## Ports <!-- ports:start --> | Port | Direction | Signal | Lanes | | -------- | --------- | ------- | ----------------------- | | `fields` | Input | Control | Inferred from the cable | | `video` | Output | Video | 1 | <!-- ports:end --> ## Parameters <!-- parameters:start --> | Parameter | Label | Range | Default | Unit | Scale | Notes | | ---------- | -------- | ---------- | ------- | ---- | ------ | ----------------------------------------------------------------- | | `size` | Size | 8 to 400 | 64 | px | log | Font size in pixels of the rendered frame. | | `x` | X | 0 to 1 | 0.5 | | linear | Horizontal anchor position across the frame. | | `y` | Y | 0 to 1 | 0.5 | | linear | Vertical anchor position down the frame. | | `align` | Align | 0 to 2 | 1 | | linear | Text alignment at the anchor. Choices: 0 Left, 1 Centre, 2 Right. | | `red` | Red | 0 to 1 | 1 | | linear | Red component of the text colour. | | `green` | Green | 0 to 1 | 1 | | linear | Green component of the text colour. | | `blue` | Blue | 0 to 1 | 1 | | linear | Blue component of the text colour. | | `opacity` | Opacity | 0 to 1 | 1 | | linear | Opacity of the text. | | `shadow` | Shadow | 0 to 1 | 0.5 | | linear | Strength of the drop shadow behind the text. | | `decimals` | Decimals | 0 to 4 | 0 | | linear | Decimal places shown for lane values. | | `width` | Width | 64 to 3840 | 1280 | px | linear | Width of the rendered frame. | | `height` | Height | 64 to 2160 | 720 | px | linear | Height of the rendered frame. | <!-- parameters:end --> <!-- data:start --> - `template`: text. - `font`: text. <!-- data:end --> ### Size (`size`) Font size in pixels, from 8 to 400. It is logarithmic. ### X (`x`) Horizontal position of the text, 0 left to 1 right. ### Y (`y`) Vertical position of the text, 0 top to 1 bottom. ### Align (`align`) Text alignment: 0 left, 1 centre, 2 right. ### Red (`red`) Red amount of the text colour. ### Green (`green`) Green amount of the text colour. ### Blue (`blue`) Blue amount of the text colour. ### Opacity (`opacity`) Overall opacity of the text. ### Shadow (`shadow`) Strength of the drop shadow behind the text. ### Decimals (`decimals`) How many decimal places a control lane shows, from 0 to 4. ### Width (`width`) Width of the text surface in pixels. ### Height (`height`) Height of the text surface in pixels. ## Tips - Leave `fields` unconnected for fixed text. - A lane shows its 0 to 1 value; scale it first with [Scale](https://docs.tune.toys/nodes/ctl.scale/) for a score. - Video needs the browser's WebGPU video engine. The Player and exports do not run video yet and report it unavailable. To see a video output, bind a live Screen in the Frame to it. ## See also [Counter](https://docs.tune.toys/nodes/ctl.counter/), [Composite](https://docs.tune.toys/nodes/video.composite/), [Select lanes](https://docs.tune.toys/nodes/ctl.select/), [Video](https://docs.tune.toys/concepts/video/), [Control lanes](https://docs.tune.toys/concepts/control-lanes/). --- URL: https://docs.tune.toys/nodes/video.transform/ Title: Transform Transform moves, scales, rotates, crops and mirrors a stream, and sets its output size. ## What it does Position, scale, rotation and crop change how the picture sits inside the frame. Width and Height set the output size; 0 keeps the input size, so it is also the resize node. It processes every stream of the input at that stream's size and keeps the stream count. ## When to use it Use it to fit a source to a layout, flip a camera, or shrink a stream before a [Composite](https://docs.tune.toys/nodes/video.composite/). ## Ports <!-- ports:start --> | Port | Direction | Signal | Lanes | | ------- | --------- | ------ | ----------------------- | | `video` | Input | Video | Inferred from the cable | | `video` | Output | Video | Inferred from the cable | <!-- ports:end --> ## Parameters <!-- parameters:start --> | Parameter | Label | Range | Default | Unit | Scale | Notes | | ---------- | -------- | ----------- | ------- | ---- | ------ | ---------------------------------------------------- | | `x` | X | -1 to 1 | 0 | | linear | Horizontal offset as a fraction of the output width. | | `y` | Y | -1 to 1 | 0 | | linear | Vertical offset as a fraction of the output height. | | `scale` | Scale | 0.05 to 8 | 1 | × | log | Uniform scale about the centre. | | `rotation` | Rotation | -180 to 180 | 0 | ° | linear | Rotation about the centre. | | `crop` | Crop | 0 to 0.49 | 0 | | linear | Fraction trimmed from every edge before scaling. | | `mirror_h` | Mirror | 0 to 1 | 0 | | linear | Flip horizontally. Choices: 0 Off, 1 On. | | `mirror_v` | Flip | 0 to 1 | 0 | | linear | Flip vertically. Choices: 0 Off, 1 On. | | `width` | Width | 0 to 3840 | 0 | px | linear | Output width in pixels; 0 keeps the source width. | | `height` | Height | 0 to 2160 | 0 | px | linear | Output height in pixels; 0 keeps the source height. | <!-- parameters:end --> ### X (`x`) Horizontal offset from -1 to 1. ### Y (`y`) Vertical offset from -1 to 1. ### Scale (`scale`) Zoom from 0.05 to 8 times; 1 is unchanged. Logarithmic. ### Rotation (`rotation`) Rotation in degrees. ### Crop (`crop`) Trims each edge, from 0 to 0.49 of the picture. ### Mirror (`mirror_h`) Flips the picture left to right when on. ### Flip (`mirror_v`) Flips the picture top to bottom when on. ### Width (`width`) Output width in pixels; 0 keeps the input width. ### Height (`height`) Output height in pixels; 0 keeps the input height. ## Tips - Set Width and Height once to fix a Toy's output size. - Video needs the browser's WebGPU video engine. The Player and exports do not run video yet and report it unavailable. To see a video output, bind a live Screen in the Frame to it. ## See also [Composite](https://docs.tune.toys/nodes/video.composite/), [Pixelate](https://docs.tune.toys/nodes/video.pixelate/), [Camera](https://docs.tune.toys/nodes/video.camera/), [Video](https://docs.tune.toys/concepts/video/), [Control lanes](https://docs.tune.toys/concepts/control-lanes/). --- URL: https://docs.tune.toys/nodes/web.frame/ Title: Web frame Web frame puts a third-party web page inside your Frame through a `web` widget, and lets the Patch and the page exchange messages. ## What it does The node's `url` field names the page and its `origin` field the one exact origin (such as `https://example.org`) it may talk to. A `web` widget bound to the node shows the page in a sandboxed iframe: scripts and forms may run, but the page gets no access to Tune.Toys, its cookies or the Toy. Messages cabled into `message` are posted to the page with the declared origin as target; messages from the page arrive on `message` only when they come from that origin. A string on `navigate` moves the frame to another URL on the same origin. `state` reports `attached`, `send-only`, `navigating`, `detached` or `refused-message`. A Toy with a Web frame requires embedding, shown as a chip on the Play surface. Sites that refuse framing show that state rather than a blank box. Screenshots and recordings capture an embed as the browser shows it; nothing bypasses its sandbox. ## When to use it Use it to place a map, a score viewer, a visualizer you host yourself or another web tool next to your controls, and to steer it from data. ## Ports <!-- ports:start --> | Port | Direction | Signal | Lanes | | ---------- | --------- | ------------- | ----- | | `message` | Input | Data (any) | 1 | | `navigate` | Input | Data (string) | 1 | | `message` | Output | Data (json) | 1 | | `state` | Output | Data (string) | 1 | <!-- ports:end --> ## Parameters <!-- parameters:start --> | Parameter | Label | Range | Default | Unit | Scale | Notes | | --------------- | ------------- | ------ | ------- | ---- | ------ | --------------------------------------------------------------------------- | | `load_on_click` | Load on click | 0 to 1 | 1 | | linear | Wait for a click before loading the third-party page. Choices: 0 Off, 1 On. | <!-- parameters:end --> <!-- data:start --> - `url`: url. - `origin`: origin. - `allow`: strings. <!-- data:end --> ### Load on click (`load_on_click`) On waits for a click before contacting the third-party site, so opening a Toy never loads someone else's page by surprise. Off loads it with the Frame. ### URL, origin and allow `url` is the page (`https:`), `origin` the exact origin for messages and navigation, and `allow` grants up to seven browser features from a closed list (autoplay, camera, clipboard-write, fullscreen, geolocation, microphone, midi). Grant only what the page needs. ## Tips - The widget and the node are one pair: place a `web` widget in the Frame and bind it to the node's port. - Messages are JSON; agree on a small message shape with the page you embed. ## See also [Web request](https://docs.tune.toys/nodes/web.request/), [Console log](https://docs.tune.toys/nodes/log.console/), [Frames](https://docs.tune.toys/concepts/frames/), [Data cables](https://docs.tune.toys/concepts/data-cables/). --- URL: https://docs.tune.toys/nodes/web.request/ Title: Web request Web request fetches a URL and sends the response into the Patch: parsed JSON, text or bytes, with the HTTP status, an error message and a done trigger. ## What it does With `trigger` cabled, each trigger sends one request; without it, a new `url` on the input sends one. One request is in flight at a time; triggers that arrive meanwhile collapse into one more request. The URL comes from the `url` input or the node's `url` field, `https:` only (`http:` only for localhost). Header rows and a `headers` JSON object are added; credential headers such as `Authorization` or `Cookie` are refused. Where the request goes is decided by policy, never by the Toy: - **Direct** from the browser (no cookies, CORS mode) only when the host is in your network allowlist (Settings → Network) or belongs to an enabled `direct` addon. - **Proxy** through Tune.Toys (`POST /v1/proxy/request`) for every other host, which needs sign-in. The proxy refuses private, loopback and metadata addresses after DNS, re-checks redirects and caps responses at 5 MiB and 20 seconds. - **Keys** never reach the browser. The `auth` field names a vault slot such as `secret:openai`; the proxy injects the value on the server, and only to that addon's hosts. A Toy that uses a slot requires it, and the Play surface shows a chip until you add your key ([secrets](https://docs.tune.toys/concepts/secrets/)). Web request runs in the browser's data runtime; the Player and exports report the data domain unavailable. During [replay](https://docs.tune.toys/concepts/takes/) it does not fetch: the recorded responses are played back. ## When to use it Use it for any HTTP API that has no addon node yet. For the services in Settings → Addons prefer their typed nodes, such as [Open-Meteo forecast](https://docs.tune.toys/nodes/addon.open-meteo.forecast/), which build the URL for you. Follow it with [Convert](https://docs.tune.toys/nodes/data.convert/) to pick fields. ## Ports <!-- ports:start --> | Port | Direction | Signal | Lanes | | ---------- | --------- | -------------- | ----- | | `trigger` | Input | Data (trigger) | 1 | | `url` | Input | Data (string) | 1 | | `body` | Input | Data (any) | 1 | | `headers` | Input | Data (json) | 1 | | `response` | Output | Data (any) | 1 | | `status` | Output | Data (integer) | 1 | | `error` | Output | Data (string) | 1 | | `done` | Output | Data (trigger) | 1 | <!-- ports:end --> ## Parameters <!-- parameters:start --> | Parameter | Label | Range | Default | Unit | Scale | Notes | | --------- | ------- | ------- | ------- | ---- | ------ | --------------------------------------------------------------------------------------------------------------------- | | `method` | Method | 0 to 4 | 0 | | linear | HTTP method. Choices: 0 GET, 1 POST, 2 PUT, 3 PATCH, 4 DELETE. | | `parse` | Parse | 0 to 3 | 0 | | linear | How the response body is read; Auto follows the content type. Choices: 0 JSON, 1 Text, 2 Bytes, 3 Auto. | | `auth_in` | Auth in | 0 to 1 | 0 | | linear | Where the vault secret named by auth is placed by the proxy. Choices: 0 Header, 1 Query. | | `timeout` | Timeout | 1 to 20 | 10 | s | linear | Seconds before the request fails with an error. | | `route` | Route | 0 to 2 | 2 | | linear | Direct needs an allowlisted host that answers CORS; Auto falls back to the proxy. Choices: 0 Direct, 1 Proxy, 2 Auto. | | `retries` | Retries | 0 to 3 | 0 | | linear | Extra attempts after a network failure or a 5xx response. | <!-- parameters:end --> <!-- data:start --> - `url`: url. - `headers`: headers. - `auth`: requirement. <!-- data:end --> ### Method (`method`) GET, POST, PUT, PATCH or DELETE. A cabled `body` is sent with every method except GET; objects are sent as JSON. ### Parse (`parse`) JSON, Text, Bytes or Auto (by the response's content type). It sets the datatype of `response`. Bytes stay in memory and are never written into a take. ### Auth in (`auth_in`) Where the proxy places the vault secret named by `auth`: as an `Authorization: Bearer` header, or as a `key` query parameter. ### Timeout (`timeout`) Seconds, 1 to 20, before the request fails with an error. ### Route (`route`) Direct, Proxy or Auto. Auto tries the browser first for an allowlisted host and falls back to the proxy after a CORS or network failure. A request with `auth` always uses the proxy. ### Retries (`retries`) Extra attempts, 0 to 3, after a network failure, a 429 or a 5xx status, with growing pauses between them. ## Tips - Cable `error` into a [Console log](https://docs.tune.toys/nodes/log.console/): refusals explain exactly what to allow or sign in for. - An HTTP error status arrives on `status` and `error`; `response` only carries successful bodies. - Respect the service's terms and rate limits. Use [Cron](https://docs.tune.toys/nodes/time.cron/) with a long interval rather than a fast loop. ## See also [Web scrape](https://docs.tune.toys/nodes/web.scrape/), [WebSocket](https://docs.tune.toys/nodes/web.socket/), [Convert](https://docs.tune.toys/nodes/data.convert/), [Secrets and the vault](https://docs.tune.toys/concepts/secrets/), [Data cables](https://docs.tune.toys/concepts/data-cables/). --- URL: https://docs.tune.toys/nodes/web.scrape/ Title: Web scrape Web scrape fetches a web page through the Tune.Toys proxy and pulls out what a CSS selector matches: text, HTML, an attribute or a table, plus the page's links. ## What it does With `trigger` cabled, each trigger scrapes once; otherwise a new `url` does. The page is always fetched by the proxy, so you must be signed in, and the host must be in your network allowlist. The proxy honours the site's `robots.txt` (a disallowed page fails with an error) and returns HTML as text; nothing on the page runs. The browser then parses it with a bounded HTML parser and sends: - `text`: the text of every matched element, joined; - `items`: one table row per matched element; - `links`: a table of the page's links, with `javascript:` and other non-web schemes dropped; - `error`: why the scrape failed. ## When to use it Use it for public pages that have no API. When a site offers an API, a [Web request](https://docs.tune.toys/nodes/web.request/) or an addon node is more reliable and kinder to the site. ## Ports <!-- ports:start --> | Port | Direction | Signal | Lanes | | --------- | --------- | -------------- | ----- | | `trigger` | Input | Data (trigger) | 1 | | `url` | Input | Data (string) | 1 | | `text` | Output | Data (string) | 1 | | `items` | Output | Data (table) | 1 | | `links` | Output | Data (table) | 1 | | `error` | Output | Data (string) | 1 | <!-- ports:end --> ## Parameters <!-- parameters:start --> | Parameter | Label | Range | Default | Unit | Scale | Notes | | --------- | ------- | --------- | ------- | ---- | ------ | -------------------------------------------------------------------------------------- | | `extract` | Extract | 0 to 3 | 0 | | linear | What each selected element contributes. Choices: 0 Text, 1 HTML, 2 Attribute, 3 Table. | | `limit` | Limit | 1 to 1000 | 100 | | linear | Most rows extracted per page. | <!-- parameters:end --> <!-- data:start --> - `url`: url. - `selector`: text. - `attribute`: text. <!-- data:end --> ### Extract (`extract`) What each matched element contributes: its Text, its HTML, the value of the node's `attribute` field (such as `href`), or, for `table` elements, its cells as rows. ### Limit (`limit`) The most rows taken from one page, 1 to 1 000. ### Selector and attribute The `selector` field is a CSS selector such as `h1`, `.headline a` or `table.prices`; the `attribute` field names the attribute for Extract Attribute. ## Tips - Check the site's terms before scraping it, and scrape rarely. - Pages that build their content with scripts look empty to a scraper; look for the site's API instead. ## See also [Web request](https://docs.tune.toys/nodes/web.request/), [Convert](https://docs.tune.toys/nodes/data.convert/), [Console log](https://docs.tune.toys/nodes/log.console/), [Data cables](https://docs.tune.toys/concepts/data-cables/). --- URL: https://docs.tune.toys/nodes/web.socket/ Title: WebSocket WebSocket keeps a live two-way connection to a server: anything cabled into `send` goes out, every message that comes back arrives on `message`. ## What it does The node connects when the Toy starts, or on each `connect` trigger when that input is cabled; `close` closes it. The URL is the node's `url` field, `wss:` only (`ws:` only for localhost), and its host must be in your network allowlist. The browser connects directly; WebSockets do not pass through the proxy and never carry your keys. `state` reports `connecting`, `open`, `reconnecting` and `closed`; `error` explains failures. Messages you send are written as text (objects as JSON). Incoming messages are parsed per Parse. ## When to use it Use it for live feeds and for talking to your own server or tools on your computer, such as a local bridge at `ws://localhost`. ## Ports <!-- ports:start --> | Port | Direction | Signal | Lanes | | --------- | --------- | -------------- | ----- | | `send` | Input | Data (any) | 1 | | `connect` | Input | Data (trigger) | 1 | | `close` | Input | Data (trigger) | 1 | | `message` | Output | Data (any) | 1 | | `state` | Output | Data (string) | 1 | | `error` | Output | Data (string) | 1 | <!-- ports:end --> ## Parameters <!-- parameters:start --> | Parameter | Label | Range | Default | Unit | Scale | Notes | | ----------- | --------- | -------- | ------- | ---- | ------ | --------------------------------------------------------------------------- | | `parse` | Parse | 0 to 3 | 0 | | linear | How incoming messages are read. Choices: 0 JSON, 1 Text, 2 Bytes, 3 Auto. | | `reconnect` | Reconnect | 0 to 1 | 1 | | linear | Reconnect with exponential backoff after a drop. Choices: 0 Off, 1 Backoff. | | `heartbeat` | Heartbeat | 0 to 300 | 0 | s | linear | Seconds between keep-alive pings; 0 sends none. | <!-- parameters:end --> <!-- data:start --> - `url`: url. - `protocols`: strings. <!-- data:end --> ### Parse (`parse`) JSON, Text, Bytes or Auto. It sets the datatype of `message`. ### Reconnect (`reconnect`) Off, or Backoff: after a drop, reconnect after 0.5 seconds, doubling the wait each time up to 30 seconds, and reset after a successful open. ### Heartbeat (`heartbeat`) A liveness timeout in seconds; 0 turns it off. If no message arrives for that long the socket is closed (and reopened with Backoff). The node sends no ping frames of its own. ### URL and protocols The `url` field is the socket address; the `protocols` field lists up to eight subprotocols to offer. ## Tips - Many services need a subscription message after connecting; send it from a [Value](https://docs.tune.toys/nodes/data.value/) through [Convert](https://docs.tune.toys/nodes/data.convert/). - During [replay](https://docs.tune.toys/concepts/takes/) the socket stays closed and the recorded messages are played back. ## See also [Web request](https://docs.tune.toys/nodes/web.request/), [Console log](https://docs.tune.toys/nodes/log.console/), [Convert](https://docs.tune.toys/nodes/data.convert/), [Data cables](https://docs.tune.toys/concepts/data-cables/). --- URL: https://docs.tune.toys/concepts/ Title: Concepts Short pages that explain ideas shared by many nodes. They are also available inside the app's Docs panel. --- URL: https://docs.tune.toys/concepts/buses/ Title: Buses A bus is a cable you do not have to draw. Connect a signal to a [Send](https://docs.tune.toys/nodes/bus.send/), give it a name, and any [Receive](https://docs.tune.toys/nodes/bus.receive/) with the same name in the same definition carries that signal. ## What it does A bus belongs to the definition it is drawn in: one Send per bus name, any number of Receives. The Send's input and the Receive's output are typed `any` in the palette; once the Send is driven, every Receive takes the driver's type and lane count for connection checks and width solving. The compiler replaces each pair with a direct connection before anything plays, so buses cost nothing at playback. Receives never sum: feed several into a [Mixer](https://docs.tune.toys/nodes/std.mixer/) to combine them, which is also where the Mix drawer shows the mix. ## Tips - Groups keep their buses private. A reused group has one independent bus per instance; publish a signal through an output port instead. - An unnamed Send or Receive, or a Receive whose Send is unconnected, stays silent and valid; a named Receive without any Send is rejected. - A Receive that feeds its own Send is a cycle and is rejected like any other feedback. ## See also [Send](https://docs.tune.toys/nodes/bus.send/), [Receive](https://docs.tune.toys/nodes/bus.receive/), [Cables and channels](https://docs.tune.toys/concepts/cables/), [Multichannel audio](https://docs.tune.toys/concepts/multichannel/). --- URL: https://docs.tune.toys/concepts/cables/ Title: Cables and channels A cable connects an output port of one node to an input port of another. It is how sound moves through a patch. ## What it does Each cable has one driver: an input port accepts exactly one cable, and the source can feed many. Audio cables carry one or more channels. Connecting two ports checks that their channel counts agree, that the input is not already driven, and that the connection does not create a cycle. Invalid drops are blocked with an explanation. ## Tips - To add several signals into one input, use the [Mixer](https://docs.tune.toys/nodes/std.mixer/). - Cables can have visual waypoints that never change the sound. - See [Multichannel audio](https://docs.tune.toys/concepts/multichannel/) for how widths are decided. ## See also [Groups](https://docs.tune.toys/concepts/groups/), [Gain](https://docs.tune.toys/nodes/std.gain/). --- URL: https://docs.tune.toys/concepts/control-lanes/ Title: Control lanes A control cable carries lanes: numbers, normally between 0 and 1, that change from one audio block to the next. They are the cheap, slow wire of a Toy, used for modulation, sensors and anything that does not need an audio sample rate. ## What it does A Toy cable is one of four types: audio (channels), control (lanes), MIDI (one stream carrying all 16 MIDI channels) or any (an unresolved [bus](https://docs.tune.toys/concepts/buses/) end). Control nodes (`ctl.*`) run once per audio block inside the shared engine, so a control value moves in block-sized steps. Use [Smooth](https://docs.tune.toys/nodes/ctl.smooth/) when a stepped value would click. A one-lane control cable can plug into any node parameter through its parameter socket. In the connection the destination port is `@` plus the parameter id, for example `{ "node": "svf", "port": "@cutoff" }`. The lane value from 0 to 1 maps across the parameter's range, logarithmically for log parameters such as cutoff, and the knob then shows the cable as its owner. A parameter with a cable cannot also have an LFO or a MIDI mapping. A parameter socket needs a one-lane source; use [Select lanes](https://docs.tune.toys/nodes/ctl.select/) to pick one lane out of a wider bundle. Some nodes also send events: a labelled outlet beside the main output, such as `done` on [Envelope](https://docs.tune.toys/nodes/std.envelope/), `wrap` on [Counter](https://docs.tune.toys/nodes/ctl.counter/) and `cycle` on [Control LFO](https://docs.tune.toys/nodes/ctl.lfo/), that is 1 for one block when the event happens and 0 otherwise, like [Compare](https://docs.tune.toys/nodes/std.compare/) in Trigger mode. It is an ordinary one-lane control output, so cable it into a trigger input or a parameter socket; because control runs once per block, an event never lasts less than a block and two events inside one block count as one. ## Tips - Shape a lane with [Scale](https://docs.tune.toys/nodes/ctl.scale/) for depth, offset and curve before it reaches a socket. - [Envelope follower](https://docs.tune.toys/nodes/ctl.follow/) turns audio level into lanes; [MIDI to control](https://docs.tune.toys/nodes/midi.to-control/) turns notes into six lanes. - Choice parameters (such as filter type) are discrete and are not good cable targets. ## See also [Control LFO](https://docs.tune.toys/nodes/ctl.lfo/), [Constant](https://docs.tune.toys/nodes/ctl.const/), [Select lanes](https://docs.tune.toys/nodes/ctl.select/), [Cables and channels](https://docs.tune.toys/concepts/cables/). --- URL: https://docs.tune.toys/concepts/data-cables/ Title: Data cables Tune.Toys has five cable types. **Audio**, **video**, **control** and **MIDI** run continuously or in time with the engine. **Data** cables, drawn dotted in violet, carry one message at a time: a number, some text, a JSON object, a table. Data nodes such as [Cron](https://docs.tune.toys/nodes/time.cron/), [Web request](https://docs.tune.toys/nodes/web.request/) and [Dataset](https://docs.tune.toys/nodes/data.set/) live in the host's data runtime, never on the audio thread. ## Datatypes Every data port declares a datatype: `trigger`, `boolean`, `integer`, `number`, `string`, `color` (`#rrggbb`), `json`, `table`, `bytes` or `any`. Some outputs take their datatype from a parameter, such as a Web request's Parse or a Convert's To. A cable connects when the datatypes match, when the input accepts `any`, or when the value widens without loss: - `integer` → `number`, and `boolean` → `integer`; - `number`, `integer`, `boolean`, `string` or `color` → `json`; - anything → `string` (as JSON text). Everything else is narrowing (`json` to `number`, `any` to `trigger`) and needs an explicit [Convert](https://docs.tune.toys/nodes/data.convert/), which also picks fields out of JSON with a pointer. The validator names the mismatch and suggests the Convert. ## Delivery A data input takes one cable; outputs fan out freely, and [Merge](https://docs.tune.toys/nodes/data.merge/) is how several sources meet. Triggers queue (64 per cable); every other datatype keeps only its newest message. Messages are bounded (1 MiB, tables 100 000 cells, bytes 16 MiB), and cycles are refused, like audio feedback. Each message is stamped with the audio block it belongs to, so a cron tick and the sound it changes line up. ## Bridges Data reaches sound only through explicit bridge nodes: - [Data to control](https://docs.tune.toys/nodes/data.to-control/) maps numbers onto a control lane that a parameter socket reads at the next block. - [Data to MIDI](https://docs.tune.toys/nodes/midi.from-data/) schedules a JSON note list as MIDI into an instrument. The data runtime runs in the browser. The Player and exports report the data domain unavailable at load: bridges never write there, so parameters keep their base values. ## Seeing values Select a data node and open the **Data panel**: tables as a spreadsheet, JSON as a tree, text, numbers with a history, colours as swatches. [Console log](https://docs.tune.toys/nodes/log.console/) prints values to the Console panel. An `led` widget in the Frame lights up from a colour, boolean, number or trigger port. ## See also [Cables and channels](https://docs.tune.toys/concepts/cables/), [Control lanes](https://docs.tune.toys/concepts/control-lanes/), [MIDI cables](https://docs.tune.toys/concepts/midi-cables/), [Secrets and the vault](https://docs.tune.toys/concepts/secrets/), [Takes](https://docs.tune.toys/concepts/takes/). --- URL: https://docs.tune.toys/concepts/frames/ Title: Frames A Frame is a layout of controls such as knobs, sliders, buttons and a piano that sits on top of a patch. ## What it does Each control is bound to a public parameter of the definition the Frame shows, and that parameter targets one or more node parameters. Moving a knob changes those parameters in the running Toy. A Frame does not process audio. One shared renderer draws every Frame, in the editor, in published Toys and in previews. ## Tips - Keep a Frame small and focused on the controls a player needs. - Controls can be hidden, locked and organized in layers without changing the sound. - A piano control sends notes to a node such as the [Polyphonic instrument](https://docs.tune.toys/nodes/std.poly/). ## See also [Presets](https://docs.tune.toys/concepts/presets/), [MIDI](https://docs.tune.toys/concepts/midi/). --- URL: https://docs.tune.toys/concepts/groups/ Title: Groups A group packages part of a patch as a named definition you can place more than once. ## What it does A group node refers to another definition in the same Toy. Each instance owns independent mutable state, so two instances of a delay group have separate echoes. Values set on the group node apply to that instance only. Groups can nest, within the limits of the format. ## Tips - Expose only the controls you want as public parameters of the group. - Frame membership is explicit: placing a group does not add its controls to a Frame automatically. ## See also [Frames](https://docs.tune.toys/concepts/frames/), [Cables](https://docs.tune.toys/concepts/cables/). --- URL: https://docs.tune.toys/concepts/midi-cables/ Title: MIDI cables A MIDI cable carries notes and controller messages between MIDI nodes. One cable is one stream, and that stream holds all 16 MIDI channels, so you do not need a cable per channel. ## What it does MIDI nodes run inside the engine and handle each message at its sample offset within the audio block. Three nodes touch the outside world: [MIDI in](https://docs.tune.toys/nodes/midi.in/) and [MIDI out](https://docs.tune.toys/nodes/midi.out/) are logical ports whose labels the host binds to a device or the DAW (the Toy never stores a device), and [Keyboard](https://docs.tune.toys/nodes/midi.keyboard/) is the node behind the piano widget and computer typing. [Polyphonic instrument](https://docs.tune.toys/nodes/std.poly/) and [Wavetable](https://docs.tune.toys/nodes/std.wavetable/) take a MIDI cable on their `midi` input. Between those ends, [MIDI merge](https://docs.tune.toys/nodes/midi.merge/) interleaves streams, [MIDI channel](https://docs.tune.toys/nodes/midi.channel/) filters or remaps channels, [Transpose](https://docs.tune.toys/nodes/midi.transpose/) and [Velocity](https://docs.tune.toys/nodes/midi.velocity/) edit notes. [MIDI to control](https://docs.tune.toys/nodes/midi.to-control/) and [Control to MIDI](https://docs.tune.toys/nodes/midi.from-control/) cross to and from [control lanes](https://docs.tune.toys/concepts/control-lanes/). ## Tips - A MIDI output only connects to a MIDI input; convert with the bridge nodes to reach audio or control. - Per-control MIDI learn bindings (see [MIDI](https://docs.tune.toys/concepts/midi/)) are separate from MIDI cables. ## See also [MIDI](https://docs.tune.toys/concepts/midi/), [Cables and channels](https://docs.tune.toys/concepts/cables/), [Buses](https://docs.tune.toys/concepts/buses/). --- URL: https://docs.tune.toys/concepts/midi/ Title: MIDI MIDI input lets hardware keyboards and controllers play a Toy. ## What it does Notes reach note-receiving nodes such as the [Polyphonic instrument](https://docs.tune.toys/nodes/std.poly/). A control can also store a MIDI binding of channel and CC number so a controller knob moves it over the control's range. The Toy stores only the channel and CC number; the device you use is a local setting and is never saved in the Toy. The browser asks for MIDI access when you start using it. ## Tips - Use the MIDI learn option on a control, then move a controller knob. - If nothing responds, check that MIDI access was allowed. ## See also [Frames](https://docs.tune.toys/concepts/frames/), [Presets](https://docs.tune.toys/concepts/presets/). --- URL: https://docs.tune.toys/concepts/multichannel/ Title: Multichannel audio An audio cable carries a bundle of between 1 and 32 channels. Stereo is a bundle of two. ## What it does Ports declare a fixed channel count or an automatic one. Automatic ports adopt the width of what is connected, and sources default to mono unless a count is declared. Strict is the default input policy: widths must match. The Channels section of a node's Properties can adapt narrower inputs with broadcast, pad or repeat; none of these policies reduces a wider input. Reduction and routing are done explicitly: [Select channels](https://docs.tune.toys/nodes/std.select/) keeps a range, [Channel matrix](https://docs.tune.toys/nodes/std.matrix/) routes and mixes lanes, [Combine channels](https://docs.tune.toys/nodes/std.combine/) joins bundles and [Mixer](https://docs.tune.toys/nodes/std.mixer/) adds them. Two inputs follow their own rule. A sidechain input named `key`, on the [Compressor](https://docs.tune.toys/nodes/std.compressor/) and [Gate](https://docs.tune.toys/nodes/std.gate/), never changes the node's width: it carries one channel, which then drives every channel, or exactly as many as the node. A [Crossover](https://docs.tune.toys/nodes/std.crossover/) outputs one copy of its input per band, band by band, so three bands of stereo is a six-channel bundle; its band count is set on the node because it decides that width. ## Tips - A mono output is sent to both monitor channels. - Monitoring choices are host settings and are not saved in the Toy. ## See also [Cables](https://docs.tune.toys/concepts/cables/). --- URL: https://docs.tune.toys/concepts/patch-lock/ Title: Patch lock Patch lock turns off structural editing in the patch view so you can inspect and play without moving things by accident. ## What it does While locked, the patch cannot be changed from the canvas or inspector. Playback, Frame controls and navigation still work. It is a view preference for the editor, not part of the sound. ## See also [Frames](https://docs.tune.toys/concepts/frames/), [Cables](https://docs.tune.toys/concepts/cables/). --- URL: https://docs.tune.toys/concepts/presets/ Title: Presets A preset stores values for public parameters under a name so a sound can be recalled in one step. ## What it does Presets belong to a definition. Selecting one applies all of its values together after checking each against its control range. A preset captures base values, not momentary modulation. Applying a preset is a playing action; saving or changing source defaults is a separate editing action. ## Tips - Save a preset before experimenting. - Presets store only parameter values, not the patch structure. ## See also [Frames](https://docs.tune.toys/concepts/frames/), [MIDI](https://docs.tune.toys/concepts/midi/). --- URL: https://docs.tune.toys/concepts/secrets/ Title: Secrets and the vault Some nodes need a key: an [LLM chat](https://docs.tune.toys/nodes/ai.chat/) uses your Anthropic, OpenAI or Gemini key, and a [Web request](https://docs.tune.toys/nodes/web.request/) may call a service with yours. Tune.Toys keeps those keys out of Toys entirely. ## Slots, not values A Toy stores only a **slot name**, such as `secret:openai`. The compiler turns it into a requirement, so the Play surface shows a "needs a key" chip before anything runs. Toys, takes, exports and logs never contain a key, and sharing or remixing a Toy never shares one: whoever opens it fills the slot with their own. Slot names come from the addon catalog (`anthropic`, `openai`, `gemini` …) or are any other short name you choose, such as `my-weather`. A catalog slot is only ever sent to its addon's hosts; any other slot only to hosts in your own network allowlist. ## The vault Settings → Vault stores keys in your account. You paste a key once; afterwards the app shows only its slot, label, first four and last four characters, and when it was last used. You can rotate or remove it at any time, and each change is noted in your inbox. Keys are encrypted on the server with a key per account and bound to their slot. Only the API decrypts one, in memory, for the single upstream call that needs it, inside the request proxy or the LLM endpoint. Upstream errors are scrubbed of the key before they reach you, and upstream calls are logged by host, status, size and duration only. ## The device vault Signed out, you can keep a key in this browser's **device vault** for addons whose service accepts direct browser calls. A key on the device can be read by anything that runs in the page, so the vault page says so plainly and recommends signing in. The proxy and the LLM endpoint always use the account vault. ## Connections Connected services such as Google Drive are not keys. They are separate grants made through the service's own consent screen, stored sealed on the server and used through the connection routes; a [Dataset](https://docs.tune.toys/nodes/data.set/) with Source Connection requires one, and a Toy never contains the connection. ## See also [Data cables](https://docs.tune.toys/concepts/data-cables/), [Takes](https://docs.tune.toys/concepts/takes/). --- URL: https://docs.tune.toys/concepts/sensing/ Title: Sensing Sensing nodes turn what a camera sees, or how a device moves, into control lanes you can plug into any parameter. ## What it does [Hands](https://docs.tune.toys/nodes/sense.hands/), [Face](https://docs.tune.toys/nodes/sense.face/), [Body](https://docs.tune.toys/nodes/sense.body/) and [Gestures](https://docs.tune.toys/nodes/sense.gesture/) take a `video` input from a source and emit a fixed, labelled control output with values from 0 to 1. [Motion](https://docs.tune.toys/nodes/sense.motion/) reads the device's accelerometer and gyroscope and has no input. Each lane has a fixed label and position, listed on the node's page. The tracking nodes run MediaPipe models in the browser. A model downloads only after the person consents, with its size shown, and is cached afterwards. Video and sensor data stay on the device; the Toy never records or uploads them. When tracking is lost the lanes hold their last values and the `present` lane falls after 220 ms. A sensor's output is wide, so it cannot go straight into a parameter. Use [Select lanes](https://docs.tune.toys/nodes/ctl.select/) with `"audio": {"channels": 1, "firstChannel": N}` to pick one lane (lanes count from 1; for example the Face `mouth` lane is lane 9), then plug that one-lane cable into a parameter socket such as `{ "node": "filter", "port": "@cutoff" }`. Shape it first with [Scale](https://docs.tune.toys/nodes/ctl.scale/) or [Smooth](https://docs.tune.toys/nodes/ctl.smooth/). Video needs the browser's WebGPU engine for pictures; sensing runs where the browser supports it. The Player and exports do not run video or sensing yet. ## Tips - Connecting all lanes straight to a one-lane input is a width conflict and is rejected. - Use `present` to mute or fade a sound when nobody is in view. ## See also [Video](https://docs.tune.toys/concepts/video/), [Control lanes](https://docs.tune.toys/concepts/control-lanes/), [Select lanes](https://docs.tune.toys/nodes/ctl.select/), [Smooth](https://docs.tune.toys/nodes/ctl.smooth/). --- URL: https://docs.tune.toys/concepts/takes/ Title: Takes A **take** is the record of one performance of one Toy: every gesture, note, preset change, transport change and data message, stamped with the audio frame it happened at. It is small, portable JSON (`application/vnd.tunetoys.take+json`, at most 8 MiB and 200 000 events), and it can be replayed later to produce exactly what you heard. ## What is recorded - The Toy itself at the starting revision, or the immutable release it was played from, plus sample rate, block size and tempo. - Events: control gestures and values (one value per control per block), notes, presets, transport start, stop and seek, raw MIDI, authoring edits made mid-take, clock re-anchors, markers, and **data messages** from host nodes, each bounded to 64 KiB. - Never: device bindings, the bytes of an audio input file, vault keys or account connections. A take of an effect replays silence at its input when the original input is not available, and says so. ## Replay Online replay loads the take's Toy and feeds its events to the running engine at their frames; recorded values move controls as automation, and your own gestures override them until the next recorded value. Data nodes with side effects do not run during replay: [Web request](https://docs.tune.toys/nodes/web.request/), [WebSocket](https://docs.tune.toys/nodes/web.socket/), [LLM chat](https://docs.tune.toys/nodes/ai.chat/), [Cron](https://docs.tune.toys/nodes/time.cron/), addon nodes and unseeded [Random](https://docs.tune.toys/nodes/data.random/) are silenced and their recorded outputs are played back. Pure nodes downstream recompute. So a take of a weather piece or an LLM melody replays offline, with no network and no key. Offline rendering drives the same events through the engine block by block: the same take at the same sample rate gives identical audio. ## Exports From a take: WAV (rendered in the browser for takes up to 60 seconds, otherwise by the worker service), MP3, OGG and FLAC (worker), a Standard MIDI File of its notes, the take JSON itself, and platform presets such as YouTube or vertical shorts (worker). Exports are downloads; nothing is uploaded to a platform for you. Recording a take works in the browser today; recording and replay inside the Player plugin are planned. ## See also [Data cables](https://docs.tune.toys/concepts/data-cables/), [Presets](https://docs.tune.toys/concepts/presets/), [Secrets and the vault](https://docs.tune.toys/concepts/secrets/). --- URL: https://docs.tune.toys/concepts/video/ Title: Video A video cable carries one to eight streams of frames, the way an audio cable carries channels. Sources, effects and mixers wire together exactly like audio nodes. ## What it does Video nodes run in the browser's WebGPU video engine. It is frame-paced: each node renders when something downstream asks for a frame, and the newest frame wins, so a slow source drops frames instead of building a queue. The Player and exports do not run video yet and report it unavailable; the audio of the Toy still plays. The only way to see a video output is a Screen widget in the Frame with `"mode": "live"` and `"source": { "node": "<node id>", "port": "video" }`. Sources come from the host: [Camera](https://docs.tune.toys/nodes/video.camera/), [Screen capture](https://docs.tune.toys/nodes/video.display/) and [Media player](https://docs.tune.toys/nodes/media.player/) need the person's permission or choice, and a Toy stores only a logical label, never a device. [Image](https://docs.tune.toys/nodes/video.image/), [Paint surface](https://docs.tune.toys/nodes/video.paint/) and [Text layer](https://docs.tune.toys/nodes/video.text/) make pictures without a device. Processors such as [Color](https://docs.tune.toys/nodes/video.color/), [Blur](https://docs.tune.toys/nodes/video.blur/) and [Chroma key](https://docs.tune.toys/nodes/video.chroma-key/) take a `video` input and give a `video` output. [Composite](https://docs.tune.toys/nodes/video.composite/), [Crossfade](https://docs.tune.toys/nodes/video.crossfade/) and [Switcher](https://docs.tune.toys/nodes/video.switch/) mix streams; [Combine streams](https://docs.tune.toys/nodes/video.combine/) and [Select streams](https://docs.tune.toys/nodes/video.select/) route bundles. [Snapshot](https://docs.tune.toys/nodes/video.snapshot/) and [Recorder](https://docs.tune.toys/nodes/media.recorder/) save to the device. ## Tips - Video cannot connect to audio or control inputs; the editor rejects mismatched cables. - Every video parameter can be driven by a one-lane control cable through its `@param` socket. - Camera and microphone are never recorded or uploaded by the Toy. ## See also [Camera](https://docs.tune.toys/nodes/video.camera/), [Composite](https://docs.tune.toys/nodes/video.composite/), [Sensing](https://docs.tune.toys/concepts/sensing/), [Control lanes](https://docs.tune.toys/concepts/control-lanes/), [Cables and channels](https://docs.tune.toys/concepts/cables/). --- URL: https://docs.tune.toys/concepts/voices/ Title: Voices A group with voices is polyphonic: build one voice from ordinary nodes, such as an oscillator, a filter and an envelope, and the group plays up to eight notes at once. ## What it does Set Voices on a group instance (2 to 8). When the Toy is prepared, that instance becomes one copy of its definition per voice, a note allocator in front of them and a sum behind them. Each note goes to one copy, so its oscillator, filter and envelope run on their own, and the copies' audio outputs add up into the group's output. Values, public controls, Frame controls, presets, LFOs and MIDI mappings that reach inside the group move every copy together; meters and traces inside stay per voice. The group's definition needs exactly one MIDI input, which the allocator shares out to the voices, and audio outputs only. Audio and control inputs reach every voice unchanged, so one LFO can sweep every voice's filter. A polyphonic definition cannot contain another polyphonic group. Inside a voice, notes arrive one at a time on an ordinary MIDI cable. Wire it to a [Oscillator](https://docs.tune.toys/nodes/std.oscillator/)'s `midi` input for pitch and to [MIDI to control](https://docs.tune.toys/nodes/midi.to-control/) for the gate, velocity, bend, pressure and CC lanes, and cable the gate lane into an [Envelope](https://docs.tune.toys/nodes/std.envelope/)'s Gate. ### Notes, sustain and stealing The allocator follows the [Polyphonic instrument](https://docs.tune.toys/nodes/std.poly/): a repeated key from the same source and channel restarts its own voice, a new key takes the voice released longest ago, and when every voice is held it takes the oldest note. A stolen or restarted voice receives a note-off and, one sample later, its new note-on. The sustain pedal keeps released notes held until it lifts, per source and channel. Each source (a Frame piano, the computer keyboard, each MIDI device) owns its notes, and a panic, transport stop or device disconnect releases them, so no voice is left hanging. Bend, pressure and other controller messages reach every voice. ### MPE Set the group's MPE zone in the Toy source (`"mpe": { "zone": "lower" }` or `"upper"`, with an optional `bend` range, 48 semitones by default). In a zone, each note plays on its own member channel, and that channel's pressure, CC74 and other controllers reach only the voice holding its note, so the voice's MIDI to control lanes become per-note expression. Pitch bend is combined per voice: the allocator adds the master channel's bend (±2 semitones) to the member channel's (± the zone's bend) and sends one bend scaled to the zone's range, so set the voice oscillator's Bend range to the same number. MPE configuration messages and MIDI-CI are not handled; choose the zone to match the controller. ## Cost Each voice is a whole copy, so a group's cost grows with its voices: the inspector shows the expanded operator count beside the Voices field. A Toy holds at most 256 operators; voices that would pass it are refused with the reason. ## Tips - Start from the **Poly Voice** template in New Toy, or select a group and choose **Make polyphonic**. - Keep the voice small. A two-node voice at eight voices costs less than one heavy effect chain. - Put shared effects such as reverb after the group, not inside it, so they run once. - For a fixed oscillator-and-envelope instrument with no wiring, [Polyphonic instrument](https://docs.tune.toys/nodes/std.poly/) is lighter. ## See also [Groups](https://docs.tune.toys/concepts/groups/), [MIDI cables](https://docs.tune.toys/concepts/midi-cables/), [MIDI](https://docs.tune.toys/concepts/midi/). --- URL: https://docs.tune.toys/components/ Title: Components reference Frame components are the playable parts of a Toy. Every component is drawn by the one shared Frame renderer, so it looks and behaves the same in the browser, on a published page, in an embed and in the native Player. Most controls bind to **one** public parameter. The XY pad binds two. Displays such as the scope read a real signal and show "unavailable" rather than inventing a waveform. | Status | Meaning | | ------- | ------------------------------------------------------------------------------- | | Stable | Implemented in the browser and the shared renderer | | Planned | Described in architecture documents but not yet implemented; these pages say so | --- URL: https://docs.tune.toys/components/buttons/ Title: Toggle, momentary and trigger **Binds:** one public parameter each. | Kind | Behavior | | ----------- | ----------------------------------------------------------------------------------------- | | `toggle` | Switches between an off and an on value. Appearances include switch and checkbox. | | `momentary` | On while held, off on release or cancellation (pointer cancel, Escape, window blur). | | `trigger` | A short pulse of 10 to 2000 ms (default 80 ms). Presses during a pulse do not add pulses. | Endpoints default to the parameter's minimum and maximum, may be authored, and must differ and stay in range. Trigger timing follows the UI clock and is not sample-accurate event scheduling. --- URL: https://docs.tune.toys/components/envelope/ Title: Envelope editor **Binds:** attack, decay, sustain and release controls of one real envelope. **Kind:** `envelope`. Handles adjust attack, decay and sustain, and release. Solid curves show base values; dashed curves show the effective values the engine reports. --- URL: https://docs.tune.toys/components/filter/ Title: Filter editor **Binds:** distinct public controls on one real filter processor (cutoff, resonance where available). **Kind:** `filter`. Dragging adjusts cutoff horizontally and Q vertically. The curve follows the real one-pole or state-variable response at the active sample rate; if the rate is unknown it is labeled as a 48 kHz preview. --- URL: https://docs.tune.toys/components/knob/ Title: Knob **Binds:** one public parameter. **Kind:** `knob`. Drag vertically or use the arrow keys when focused. The knob honors the parameter's range, unit and scale (linear or logarithmic) and shows the current value. A label is shown beneath it. Editor preferences control the drag feel. Double-click resets to the default value. Appearance (colors, size, label) is set per component or through a shared style. --- URL: https://docs.tune.toys/components/meter/ Title: Meter **Kind:** `meter`. Reads a real signal level from the host. Shows unavailable when telemetry is missing. --- URL: https://docs.tune.toys/components/piano/ Title: Piano **Binds:** a voice node (not a numeric parameter). **Kind:** `piano`. The first and last notes choose the range. It responds to pointer, touch, computer keyboard typing and MIDI. Held notes release on stop, blur or cancellation. --- URL: https://docs.tune.toys/components/scope/ Title: Scope and spectrum **Binds:** one actual input, output or node signal. **Kind:** `scope`. Scopes observe the engine through a bounded capture ring and never touch the audio thread's output path. Frames update on the UI thread. If a host cannot provide telemetry, the scope shows that it is unavailable instead of drawing a stand-in. --- URL: https://docs.tune.toys/components/screen/ Title: Screen **Binds:** a verified attached asset or a built-in scene. **Kind:** `screen`. Assets are verified (length, SHA-256, declared type and decoded dimensions) before display. Images up to 16 MiB; muted MP4 or WebM video up to 16 MiB with play, pause and seek; decoded size limits apply. --- URL: https://docs.tune.toys/components/segmented/ Title: Segmented selector **Binds:** one public parameter. **Kind:** `segmented`. Each option has a label and a numeric value on the parameter's step grid. Use it for waveform, filter type or any choice parameter. The values stay numeric bindings, so presets and automation see numbers, not strings. --- URL: https://docs.tune.toys/components/slider/ Title: Slider **Binds:** one public parameter. **Kind:** `slider`. The orientation is automatic from the component's shape, or set to horizontal or vertical. The fill shows the normalized value; logarithmic parameters use a logarithmic travel. --- URL: https://docs.tune.toys/components/text/ Title: Text **Kind:** `text`. Text elements share the text style family (font family, size and weight). Imported or creator text is always escaped; it is never executed. --- URL: https://docs.tune.toys/components/xy/ Title: XY pad and joystick **Binds:** two distinct public parameters. **Kind:** `xy`. Pointer movement writes both values as one atomic change. Arrow keys and accessible axis sliders support keyboard editing. Logarithmic domains are respected. A joystick can spring back to the normalized center. --- URL: https://docs.tune.toys/guides/ Title: Guides Each guide starts from an official template, so you can hear the result immediately and then change it. They replace the earlier lessons on tune.toys. --- URL: https://docs.tune.toys/guides/build-a-delay/ Title: Build a browser delay effect **Template:** Warm Echo. **You will learn:** filter tone, delay time, feedback and mix. 1. Open Warm Echo and choose Preview to hear it with the included demo input. 2. Lower Tone to soften the sound before it enters the delay. Compare a bright cutoff with a darker one. 3. Change Time to hear shorter and longer repeats. Raise Feedback gradually to retain more repeats. 4. Adjust Mix to blend the effect with the original audio. 5. Choose Create from template, then Create to open an independent Toy. Patch shows the signal path; Frame shows the playable controls. 6. Save your work. Publish when you want a shorter playable link with a preview image. For the nodes involved, see [Delay](https://docs.tune.toys/nodes/std.delay/) and [Low-pass filter](https://docs.tune.toys/nodes/std.lowpass/). --- URL: https://docs.tune.toys/guides/first-synth/ Title: Make your first browser synthesizer **Template:** Little Sine. **You will learn:** oscillator frequency and level, and a low-pass tone control. 1. Open Little Sine and preview its continuous oscillator at a comfortable listening level. 2. Change Frequency to hear how oscillator rate controls pitch. 3. Change Level to compare amplitude without changing frequency. 4. Lower Tone to explore the low-pass filter after the oscillator. 5. Create your own copy and inspect the oscillator and filter in Patch. Exposed parameters connect them to the Frame controls. 6. Save the Toy, then publish a playable page when you are ready to share it. This example is a continuous sound source, not a note-triggered keyboard instrument. For the nodes involved, see [Oscillator](https://docs.tune.toys/nodes/std.oscillator/) and [Low-pass filter](https://docs.tune.toys/nodes/std.lowpass/). --- URL: https://docs.tune.toys/guides/reusable-groups/ Title: Reuse a patch with independent controls **Template:** Two Gain Stages. **You will learn:** definitions, instances and nested Frames. 1. Open Two Gain Stages and inspect the two Level controls in its Frame. 2. Preview the demo input. Change one Level while leaving the other at its default. 3. Create a copy and open Patch. The first and second nodes both instantiate the same gain definition. 4. Open a nested instance to inspect its gain node and public Level parameter. 5. Return to the main Frame. Each nested Frame binds to its own instance, so editing one control leaves the other independent. 6. Save or publish the composition to share its portable graph and Frames together. Read [Groups and reusable patches](https://docs.tune.toys/concepts/groups/) and [Frames](https://docs.tune.toys/concepts/frames/) for the ideas behind this. --- URL: https://docs.tune.toys/developers/ Title: Developers **Status:** The developer platform is implemented locally. Third-party OAuth, a hosted MCP endpoint, paid usage packs and CORS for browser-side keys are planned. Rate-limit numbers are proposed defaults pending an owner decision. The API is described by an OpenAPI 3.1 document generated from the same route declarations the server runs. Use the [route index](https://docs.tune.toys/developers/api/) here, or the interactive [Swagger UI on tune.toys](https://tune.toys/developers), which can send requests with your key. Download [`openapi.json`](https://docs.tune.toys/developers/openapi.json) for code generation. | I want to | Go to | | ----------------------------- | ------------------------------------------------------------------------------- | | Make my first call | [Quickstart](https://docs.tune.toys/developers/quickstart/) | | Understand keys and scopes | [Authentication](https://docs.tune.toys/developers/authentication/) | | Know how fast I can call | [Rate limits](https://docs.tune.toys/developers/rate-limits/) | | Handle failures | [Errors](https://docs.tune.toys/developers/errors/) | | Page through lists | [Pagination](https://docs.tune.toys/developers/pagination/) | | Retry writes safely | [Idempotency](https://docs.tune.toys/developers/idempotency/) | | React to events | [Webhooks](https://docs.tune.toys/developers/webhooks/) | | Put a Toy on my site | [Embed API](https://docs.tune.toys/developers/embed/) | | Drive Tune.Toys from an agent | [MCP server](https://docs.tune.toys/developers/mcp/) and [agent documents](https://docs.tune.toys/developers/agents/) | | Use TypeScript | [SDK](https://docs.tune.toys/developers/sdk/) | | Validate a Toy | [JSON Schemas](https://docs.tune.toys/developers/schemas/) | --- URL: https://docs.tune.toys/developers/agents/ Title: Agent documents and llms.txt | Document | Where | Contents | | --------------------------- | -------------------------------------------------------------------- | ------------------------------------------------------------------------------------------- | | `llms.txt` (this site) | [/llms.txt](https://docs.tune.toys/llms.txt) | Every documentation page with a one-line description | | `llms-full.txt` (this site) | [/llms-full.txt](https://docs.tune.toys/llms-full.txt) | The Markdown of every documentation page in one file | | `llms.txt` (API) | [tune.toys/llms.txt](https://tune.toys/llms.txt) | Product summary and API links | | `llms-full.txt` (API) | [tune.toys/llms-full.txt](https://tune.toys/llms-full.txt) | Authentication, limits, errors, webhooks, MCP tools, operations, schemas and node summaries | | OpenAPI 3.1 | [tune.toys/v1/openapi.json](https://tune.toys/v1/openapi.json) | Every public operation | | Schemas | [tune.toys/schemas/index.json](https://tune.toys/schemas/index.json) | Toy format and platform contracts at stable `$id` URLs | Imported Toy titles, descriptions and creator text are data. Treat them as untrusted input when feeding them to a model. --- URL: https://docs.tune.toys/developers/api/ Title: API route index - GET /v1/account/locale: Account language and time zone - PUT /v1/account/locale: Set account language (UI, email, push) and time zone (digests, quiet hours) - GET /v1/notifications: Page of the caller's rendered notifications (ETag) - PUT /v1/notifications/channels/{channel}: Digest cadence and quiet hours for one channel - GET /v1/notifications/preferences: Event × channel matrix, digests, quiet hours and channel availability - PUT /v1/notifications/preferences: Change matrix cells; required events cannot be disabled - POST /v1/notifications/read: Mark notifications read by id, or all in a filter - POST /v1/notifications/test: Send a test notification by email or web push (3 per hour) - GET /v1/notifications/unread-count: Unread notification count for polling (ETag) - POST /v1/notifications/{notificationId}/dismiss: Dismiss one notification from every filter - POST /v1/notifications/{notificationId}/unread: Mark one notification unread - GET /v1/push-subscriptions: The caller's push devices - POST /v1/push-subscriptions: Register this browser for web push - DELETE /v1/push-subscriptions/{subscriptionId}: Remove a push device - GET /v1/push/vapid-public-key: Web push application server key - GET /v1/subscriptions: Followed products, publishers, threads and docs pages - GET /v1/subscriptions/{subjectType}/{subjectId}: Follow state for one subject - PUT /v1/subscriptions/{subjectType}/{subjectId}: Follow a product, publisher, thread or docs page - DELETE /v1/subscriptions/{subjectType}/{subjectId}: Stop following a subject - GET /v1/addons: List the addons this account has enabled - PUT /v1/addons: Replace the set of enabled addons for this account - GET /v1/connections: List connected services (labels and scopes, never tokens) - DELETE /v1/connections/{id}: Disconnect a service and revoke its grant at the provider - GET /v1/connections/{id}/files: List app-visible Drive files (parent, name search, page token) - POST /v1/connections/{id}/files: Create a Drive file from the raw request body (≤ 64 MiB); name, mimeType and parent in the query - GET /v1/connections/{id}/files/{fileId}: Read Drive file metadata - PATCH /v1/connections/{id}/files/{fileId}: Rename (JSON { name }) or replace the bytes (raw body ≤ 64 MiB) of a Drive file - DELETE /v1/connections/{id}/files/{fileId}: Delete a Drive file the app can see - GET /v1/connections/{id}/files/{fileId}/content: Stream Drive file bytes (≤ 64 MiB) - GET /v1/connections/{id}/picker: Short-lived drive.file access token and Picker settings for the Google Drive picker - POST /v1/connections/{provider}/start: Start consent for a service connection (Google Drive drive.file, Spotify, TONE3000) - GET /v1/network/allowlist: Read the extra exact hosts this account allows for web nodes - PUT /v1/network/allowlist: Replace the account network allowlist (exact hosts, at most 32; private ranges stay refused) - POST /v1/agent/proposals: Generate a revision-bound Toy edit proposal from a recipe brief - POST /v1/ai/complete: Complete a chat with Anthropic, OpenAI or Gemini using the caller’s own vault key - GET /v1/ai/models: Pinned model suggestions per provider and the caller’s daily allowance - POST /v1/proxy/request: Make one allowlisted HTTPS request, optionally injecting a vault secret server-side - GET /v1/artifacts/{id}/bytes: Download a free or browser artifact with digest verification - GET /v1/platform/targets: The 13 target profiles with buildable formats and builder status - POST /v1/products/{id}/aliases/{alias}: Move stable or beta with an expected-previous check - GET /v1/products/{id}/versions: List versions with matrix, artifacts and aliases (drafts for publisher members only) - POST /v1/products/{id}/versions: Create a draft version pinned to project releases - GET /v1/products/{id}/versions/suggest: Suggest the next semver from the public-control diff of the latest release - GET /v1/projects/{projectId}/product: The product that wraps a published project, for version and download UI - GET /v1/versions/{id}: Read one version with its matrix and artifacts - PATCH /v1/versions/{id}: Edit a draft, or the matrix claims of a published version - GET /v1/versions/{id}/artifacts: Registered artifacts of a version (public fields) - POST /v1/versions/{id}/artifacts: Upload and register an artifact (application/octet-stream body, metadata in the query) - POST /v1/versions/{id}/publish: Freeze and publish a version; creates the browser artifact - GET /v1/versions/{id}/targets: Platform matrix for a version: all 13 profiles - POST /v1/versions/{id}/yank: Withdraw a published version with a public reason - POST /v1/artifacts/{id}/tickets: Issue a one-time, 15-minute download ticket (anonymous for free public artifacts) - GET /v1/certificates: The caller's certificates with offline states - POST /v1/certificates/{id}/refresh: Refresh a certificate, or learn that it was revoked - GET /v1/distribution/keys: Public Ed25519 keys by purpose (diagnostics; clients pin keys) - GET /v1/downloads/{ticketId}: Artifact bytes for a ticket token (`?t=`): single Range, ETag digest, Repr-Digest - GET /v1/install/{product}/{version}: Install deep-link fallback page (HTML) - GET /v1/installations: Installation records for the caller, optionally per device or product - POST /v1/installations: Record a verified install on a Player or Installer device - PUT /v1/installations/{id}: Change state or version, report last seen, or roll back - POST /v1/products/{id}/certificate: Issue a signed 30-day offline license certificate for an owned product - GET /v1/products/{id}/feed/{channel}: Signed update manifest (tunetoys.update-manifest/1) with its changelog text - GET /v1/products/{id}/latest/{target}: Latest installable desktop app artifact for macos, windows or linux (`?channel=`, Linux `?format=appimage|deb`) - POST /v1/auth/email: Add or resend verification for a recovery address - POST /v1/auth/login: Create a handle/password session - POST /v1/auth/logout: Revoke the current session and expire its cookie - GET /v1/auth/me: Read the current account and browser CSRF token - POST /v1/auth/oauth/{provider}/start: Start provider sign-in or explicit identity linking - GET /v1/auth/options: List sign-in providers and email delivery availability - POST /v1/auth/password: Set or replace the caller's password and end their other browser sessions - GET /v1/auth/profile: Read owner-only email and connected-method metadata - PATCH /v1/auth/profile: Change the caller's display name - POST /v1/auth/recover: Request password recovery email - POST /v1/auth/register: Create a handle/password account and session - POST /v1/auth/reset-password: Consume a reset token and replace the password - POST /v1/auth/verify-email: Consume an email verification token - POST /v1/billing/checkout: Start a hosted checkout for an offer - GET /v1/billing/entitlements: List the caller's entitlements - GET /v1/billing/options: List configured offers - GET /v1/billing/orders: List the caller's orders - POST /v1/billing/orders/{orderId}/portal: Open the provider billing portal for an order - POST /v1/billing/orders/{orderId}/sync: Reconcile an order with the provider - GET /v1/catalog: Search the public catalog - GET /v1/releases/{digest}: Read an immutable release - GET /v1/releases/{digest}/assets/{assetId}: Download a release asset as a sandboxed attachment - GET /v1/releases/{digest}/engine: Download the release engine WebAssembly - GET /v1/releases/{digest}/package: Download the canonical release package - GET /v1/toys/{toyId}: Read a public or authorized Toy listing - GET /v1/community/activity: List activity from followed accounts - PUT /v1/community/avatar: Set the caller's avatar from owned media - GET /v1/community/avatars/{avatarId}: Read avatar image bytes - GET /v1/community/blocks: List accounts the caller blocked - POST /v1/community/collections: Create a collection - GET /v1/community/collections/{collectionId}: Read a collection page - PUT /v1/community/collections/{collectionId}: Update a collection - DELETE /v1/community/collections/{collectionId}: Delete a collection at an expected version - PUT /v1/community/collections/{collectionId}/items: Add, move or remove a Toy or sound in a collection by subject (subjectType project|media) - PUT /v1/community/collections/{collectionId}/toys/{toyId}: Add, move or remove a Toy in a collection - GET /v1/community/invitations: List the caller's pending project invitations - PUT /v1/community/profile: Update the caller's community profile - GET /v1/community/profiles/{handle}: Read a public profile - PUT /v1/community/profiles/{handle}/block: Block or unblock a profile - GET /v1/community/profiles/{handle}/collections: List a profile's collections - PUT /v1/community/profiles/{handle}/follow: Follow or unfollow a profile - GET /v1/community/profiles/{handle}/followers: List a profile's followers - GET /v1/community/profiles/{handle}/following: List a profile's followed accounts - GET /v1/community/profiles/{handle}/likes: List Toys a profile liked - GET /v1/community/profiles/{handle}/toys: List a profile's public Toys - GET /v1/community/projects/{projectId}/assets/{assetId}: Download a shared project media asset - POST /v1/community/projects/{projectId}/edits: Apply a collaborative project edit - POST /v1/community/projects/{projectId}/invitation: Accept or decline a project invitation - POST /v1/community/projects/{projectId}/members: Invite a project member - DELETE /v1/community/projects/{projectId}/members/{accountId}: Remove a project member - PUT /v1/community/projects/{projectId}/presence: Publish collaboration presence - DELETE /v1/community/projects/{projectId}/presence/{sessionId}: End a collaboration presence session - GET /v1/community/projects/{projectId}/state: Read collaboration state since a source version - POST /v1/community/reports: Report content or an account - GET /v1/community/reports: List reports for moderators - PATCH /v1/community/reports/{reportId}: Review a report as a moderator - GET /v1/community/shared: List projects shared with the caller - GET /v1/community/toys/{toyId}/likes: Read like count and caller state for a Toy - PUT /v1/community/toys/{toyId}/likes: Like or unlike a Toy - GET /v1/creators: Creators with public Toys, sounds or chains: counts, filter by what they publish, sort by followers or newest - GET /v1/config: Whether server text proposals are configured - GET /v1/health: Readiness and engine ABI - GET /v1/developer/apps: List the caller's developer apps - POST /v1/developer/apps: Create a developer app (at most 10 per account) - GET /v1/developer/apps/{appId}: Read one app with its effective tier - PATCH /v1/developer/apps/{appId}: Update app fields at an expected version - DELETE /v1/developer/apps/{appId}: Delete an app; revokes its keys and disables its webhooks - GET /v1/developer/apps/{appId}/audit: Recent audit events of one app, newest first - GET /v1/developer/apps/{appId}/keys: Key metadata for one app; never secrets - POST /v1/developer/apps/{appId}/keys: Create a key; the secret is returned once - GET /v1/developer/apps/{appId}/tier: Effective tier, its limits and whether an upgrade can be purchased - GET /v1/developer/apps/{appId}/usage: Hourly or daily usage series with per-key totals - GET /v1/developer/apps/{appId}/usage.csv: Hourly usage as CSV (UTC, at most 90 days) - GET /v1/developer/apps/{appId}/webhooks: Webhooks of one app - POST /v1/developer/apps/{appId}/webhooks: Create a webhook (pro); sends a verification challenge - POST /v1/developer/deliveries/{deliveryId}/redeliver: Queue another attempt of a delivered or failed event - GET /v1/developer/key: Introspect the presenting key: app, scopes, tier and expiry - POST /v1/developer/key/revoke: Self-revocation for leak response - PATCH /v1/developer/keys/{keyId}: Rename a key, change its scopes or IP allowlist - POST /v1/developer/keys/{keyId}/revoke: Revoke a key immediately - POST /v1/developer/keys/{keyId}/rotate: Issue a replacement key; the old key keeps working for the overlap - GET /v1/developer/limits: Current bucket and quota state for a key, or the tier policies - GET /v1/developer/scopes: Scopes a key may hold, with descriptions - PATCH /v1/developer/webhooks/{webhookId}: Change events, URL or state; URL changes and re-enabling re-verify - GET /v1/developer/webhooks/{webhookId}/deliveries: Delivery log: one row per attempt, newest first - POST /v1/developer/webhooks/{webhookId}/test: Queue a signed webhook.test delivery - POST /v1/developer/webhooks/{webhookId}/verify: Resend the verification challenge - GET /v1/openapi.json: This OpenAPI 3.1 document - GET /v1/email/unsubscribe: Browser page that confirms an email unsubscribe; never mutates - POST /v1/email/unsubscribe: RFC 8058 one-click and page unsubscribe for one token scope - POST /v1/embed/events: Count one in-frame play (bounded daily counter, no viewer data) - GET /v1/products/{id}/embed: Embed policy, origins, effective frame-ancestors and the Patch view opt-in - PATCH /v1/products/{id}/embed: Replace the embed policy and origin allowlist; optionally set the Patch view opt-in - GET /v1/products/{id}/embed/analytics: Loads and plays per embedding origin for the last N days - POST /v1/exports: Queue a take render, Frame video or screen-recording transcode - GET /v1/exports: The caller's 50 newest export and convert jobs - GET /v1/exports/{exportId}: Export state, progress, warnings, error class and output - DELETE /v1/exports/{exportId}: Cancel a queued or running export; finished jobs are unchanged - GET /v1/exports/{exportId}/output: Download a finished export as a sandboxed attachment - GET /v1/library: List the caller's library - GET /v1/library/bookmarks: Read quick-bookmark flags for listed Toys - GET /v1/library/sync: Read library events after a cursor - PUT /v1/library/{itemId}: Write library bookmark, tags and pinned release - DELETE /v1/library/{itemId}: Remove a library item at an expected version - PUT /v1/library/{itemId}/bookmark: Set the quick-bookmark flag - GET /v1/listings/publishers: Publishers where the caller is an active member, with roles - GET /v1/products/{productId}/documents: Manuals, docs and FAQ documents - POST /v1/products/{productId}/documents: Create a Markdown or PDF document - PUT /v1/products/{productId}/documents/{documentId}: Update a document (title, slug, body, PDF or position) - DELETE /v1/products/{productId}/documents/{documentId}: Delete a document - GET /v1/products/{productId}/listing: Listing, draft and published revisions, effective fields and checks - PATCH /v1/products/{productId}/listing: Change listing visibility, default locale or product slug - POST /v1/products/{productId}/listing/assets: Attach an uploaded image as the thumbnail, social or marketing image - GET /v1/products/{productId}/listing/assets: Generated listing asset slots with status, overrides, jobs and the daily budget - POST /v1/products/{productId}/listing/assets/jobs/{jobId}/cancel: Cancel a queued or running asset job; the previous ready asset stays - DELETE /v1/products/{productId}/listing/assets/{assetId}: Remove an uploaded asset override (reset to generated) - GET /v1/products/{productId}/listing/assets/{assetId}/bytes: Member preview of a ready generated asset, whatever the listing visibility - PUT /v1/products/{productId}/listing/draft: Save the draft revision (named top-level keys only) - DELETE /v1/products/{productId}/listing/draft: Discard the unpublished draft revision - DELETE /v1/products/{productId}/listing/draft/overrides/{field}: Reset one field (or all) to the generated value - POST /v1/products/{productId}/listing/gallery/video-links: Parse a YouTube or Vimeo URL to a canonical video id - POST /v1/products/{productId}/listing/publish: Validate and publish the draft revision - POST /v1/products/{productId}/listing/regenerate: Regenerate all generated assets, some kinds, or one variant (never touches uploaded overrides) - GET /v1/products/{productId}/listing/revisions: Listing revision history - POST /v1/products/{productId}/listing/revisions/{revisionId}/restore: Copy a revision into the draft - GET /v1/products/{productId}/listing/translations: Translations of the draft (or published) revision - PUT /v1/products/{productId}/listing/translations/{locale}: Save translated fields and their state for the draft revision - GET /v1/projects/{projectId}/listing: Listing editor read model for a published Toy's product - GET /v1/publishers/{handle}/products: A publisher's products with listing state, for members - GET /v1/media: Search visible media (alias of GET /v1/sounds in the pre-0.0.8 item shape; kind= filters by family) - GET /v1/media/upload-access: Read the caller's upload permission and policy mode - POST /v1/media/uploads: Reserve an upload with its transfer facts, rights and catalog metadata in one request - POST /v1/media/uploads/{uploadId}/complete: Verify and publish an uploaded media version - GET /v1/media/{mediaId}: Read media metadata - GET /v1/media/{mediaId}/bytes: Download media bytes as a sandboxed attachment - POST /v1/media/{mediaId}/convert: Queue a conversion of a library item into a new private library item - GET /v1/native/authorize: Validate a Player authorization request and loopback callback - POST /v1/native/authorize: Consent to a Player request and issue a short-lived code - GET /v1/native/devices: List the caller's authorized Player devices - POST /v1/native/devices/{deviceId}/revoke: Revoke an owned Player device - PATCH /v1/offers/{offerId}: Activate or deactivate a creator offer - GET /v1/products/{productId}/access: The caller's access to a product's paid content - GET /v1/products/{productId}/offers: List active offers (managers also see inactive offers) - POST /v1/products/{productId}/offers: Create a free or one-time creator offer (owner/finance) - GET /v1/publishers/me: Read (creating on first use) the caller's personal publisher - PATCH /v1/publishers/{publisherId}: Update display name, support email or seller terms acceptance - GET /v1/publishers/{publisherId}/connect: Read Stripe Connect capability state (owner/finance) - POST /v1/publishers/{publisherId}/connect/onboard: Create or resume Stripe Express onboarding (owner only) - POST /v1/publishers/{publisherId}/connect/sync: Reconcile Connect state with Stripe (owner/finance) - GET /v1/publishers/{publisherId}/members: List publisher members and roles - POST /v1/publishers/{publisherId}/members: Add a member by handle with a role (owner only) - DELETE /v1/publishers/{publisherId}/members/{accountId}: Remove a member (owner only) - GET /v1/pages/product: Public product page model - GET /v1/pages/sounds: Public sound landing page model - GET /v1/pages/storefront: Public storefront page model - GET /v1/posts: List root posts of a subject with inline replies - POST /v1/posts: Create a comment or reply - GET /v1/posts/{postId}: Read a post with its visible replies - PATCH /v1/posts/{postId}: Edit a post or resolve a project note - DELETE /v1/posts/{postId}: Remove a post, leaving a tombstone - POST /v1/posts/{postId}/moderation: Remove, restore or clear the verified flag of a post (moderator) - PUT /v1/posts/{postId}/pin: Pin or unpin a root post (subject owner, at most three) - PUT /v1/posts/{postId}/reactions/{kind}: Set or clear a like or helpful vote - GET /v1/posts/{postId}/replies: List every visible reply below a post - PUT /v1/posts/{postId}/response: Create or edit the publisher response to a review - GET /v1/posts/{postId}/revisions: List prior versions of a post (author or moderator) - GET /v1/products/{productId}/rating-summary: Read the review rating summary of a product - GET /v1/products/{productId}/review: Read the caller's review of a product with eligibility - PUT /v1/products/{productId}/review: Create or edit the caller's review of a product - GET /v1/projects: List the caller's drafts, newest first - POST /v1/projects: Create an owned project from a validated document - GET /v1/projects/{projectId}: Read an owned project - PUT /v1/projects/{projectId}: Save a draft at an expected revision - PUT /v1/projects/{projectId}/catalog: Update public discovery tags and category - POST /v1/projects/{projectId}/operations: Apply an authoring operation batch at an expected revision - POST /v1/projects/{projectId}/publish: Publish an immutable release - PUT /v1/projects/{projectId}/visibility: Change listing visibility - POST /v1/releases/{digest}/fork: Fork a release into a new owned project - GET /v1/publishers/{handle}/storefront: Published storefront, or the draft and revisions for members with ?draft=1 - PUT /v1/publishers/{handle}/storefront/draft: Save theme tokens and sections to the draft revision - POST /v1/publishers/{handle}/storefront/publish: Validate references and publish the draft revision - POST /v1/publishers/{handle}/storefront/revisions/{revisionId}/restore: Copy an older revision into a new draft - GET /v1/sounds: Search and filter public sounds (or the caller’s own with mine=1); page of 40 - GET /v1/sounds/facets: Facet counts for a sounds filter set - GET /v1/sounds/saved: List the caller's saved sounds - GET /v1/sounds/saved/sync: Read saved-sound events after a cursor (the library sync protocol) - GET /v1/sounds/similar/{mediaId}: Nearest sounds by embedding, or unsupported with the reason - GET /v1/sounds/{mediaId}: Item, current version, resolved fields with sources, renditions, counts and viewer state - PATCH /v1/sounds/{mediaId}: Edit the catalog entry at an expected version (owner) - DELETE /v1/sounds/{mediaId}: Remove a sound from the catalog; pinned release versions stay - GET /v1/sounds/{mediaId}/analysis: Every analysis run of the current version with the full report - GET /v1/sounds/{mediaId}/bytes: Original bytes as a sandboxed attachment: the owner, or anyone when reusable; counted - POST /v1/sounds/{mediaId}/events: Play or preview beacon, bounded per address per day - PUT /v1/sounds/{mediaId}/like: Like or unlike a sound - GET /v1/sounds/{mediaId}/relations: Relations in both directions the caller may read - PUT /v1/sounds/{mediaId}/relations: Relate this sound to another (owner of either side) - DELETE /v1/sounds/{mediaId}/relations: Remove a relation (owner of either side) - GET /v1/sounds/{mediaId}/renditions/{kind}: Rendition bytes (peaks, preview, thumbnail, score page); 404 RENDITION_NOT_READY until produced - PUT /v1/sounds/{mediaId}/save: Save or unsave a sound (synced to hosts like library bookmarks) - GET /v1/tone3000/library/{kind}: Read a bounded TONE3000 favorites or downloads list - POST /v1/tone3000/models/{id}/import: Import one private TONE3000 model on request - GET /v1/tone3000/search: Search TONE3000 NAM captures on explicit request - GET /v1/tone3000/tones/{id}: Read TONE3000 tone metadata - GET /v1/tone3000/tones/{id}/models: List admitted TONE3000 model architectures - GET /v1/vault/secrets: List vault secret metadata (never values) - POST /v1/vault/secrets: Store a secret in a slot; returns metadata only - PUT /v1/vault/secrets/{id}: Replace a secret value atomically (new prefix and last4) - DELETE /v1/vault/secrets/{id}: Remove a secret --- URL: https://docs.tune.toys/developers/authentication/ Title: Authentication and keys ## Presenting a key Send `Authorization: Bearer tt_...` or `X-TuneToys-Key: tt_...`. A key combined with a cookie, a second key header or any other Authorization value is rejected with `400 MIXED_AUTH`. | Property | Behavior | | ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | Format | `tt_<prefix>_<secret>`: 8 base32 characters, then 43 base64url characters of random bytes and a 6-character CRC32 checksum. The checksum rejects typos without a lookup. | | Storage | Only an HMAC of the key is stored. A lost key cannot be recovered; create a new one. | | Lifetime | 30, 90 (default) or 365 days. There are no non-expiring keys. | | Rotation | Issues a replacement with the same scopes. The old key keeps working for an overlap (default 24 hours, at most 7 days). | | Revocation | Immediate on this instance; other instances drop cached keys within 30 seconds. `POST /v1/developer/key/revoke` lets a leaked key revoke itself. | | Network | Optional allowlist of up to 16 CIDR ranges (`403 KEY_NETWORK`). | | Acting user | The key's creator. Normal owner and visibility checks still apply; scopes never widen them. | ## Scopes | Scope | Allows | | -------------------- | ------------------------------------------------------------------------------------------------------------------------------------- | | `read:public` | Catalog, listings, releases and release bytes | | `read:library` | Library, bookmarks and sync | | `write:library` | Bookmark, annotate and remove library items | | `read:projects` | Your draft projects | | `write:projects` | Create drafts, save, apply revision-checked operation batches and fork. Never publishes. | | `read:products` | Your publishers' products and listings; needed for product webhooks | | `write:listings` | Listing draft fields, assets, documents and translations. Never publishes or changes visibility | | `read:usage` | Your app's own usage | | `read:notifications` | Reserved for notification inbox reads | | `publish:releases` | Create versions, upload artifacts and publish releases for your publisher; the only way to register `signed` or `notarized` artifacts | ## What keys cannot do Keys never reach billing, native device authorization, account authentication, developer app and key management, moderation, publication, visibility changes, storefront publication or server agent proposals. These routes do not list `key` in their auth modes, so the refusal is structural: `403 AUTH_MODE`. Publication, billing and moderation always remain signed-in human actions. Key requests skip browser Origin, CSRF and `X-TuneToys-Request` checks because they carry no ambient credential. Bodies must still be `application/json`, and routes that require idempotency need an `Idempotency-Key`. The API sends no CORS headers for `/v1`; call it from a server, not from a web page. --- URL: https://docs.tune.toys/developers/embed/ Title: Embed API **Status:** Release, help-patch and node-card embeds, every view below, the `tunetoys-embed/v1` protocol and `@tunetoys/embed` are implemented; check the [release notes](https://docs.tune.toys/releases/) for the version that ships them on tune.toys. These docs embed node cards and help patches on every node page. ## Subjects and views Every embed shows one **subject** in one **view**. The server decides which views a subject offers; a view it does not offer falls back to the subject's default with a console warning. | Route | Subject | Views (default first) | | --------------------------------------------- | -------------------------------------------------- | -------------------------------------------------------------- | | `https://tune.toys/embed/t/<id>` | The current public release of a Toy | `frame`; `patch` and `split` when the creator allows the Patch | | `https://tune.toys/embed/r/<digest>` | One exact release (stable for blog posts and docs) | as above | | `https://tune.toys/embed/help/<type>/<order>` | A help patch from these docs, by node type | `split`, `patch`, `frame`, `card` (with `node=<id>`) | | `https://tune.toys/embed/node/<type>` | One node's card at its default values | `card` | | View | Shows | | ------- | ------------------------------------------------------------------------------------------------------------------ | | `frame` | The playable Frame with a Play button | | `patch` | The read-only Patch, drawn by the editor: values change, cards fold, ports show hints; nothing is saved or rewired | | `split` | The Patch above the Frame and Play | | `card` | One node card, interactive like the editor's but silent; on a help patch, name the node with `node=<id>` | ```html <iframe src="https://tune.toys/embed/help/std.delay/1?view=split&theme=auto" title="Your first echo" width="560" height="480" loading="lazy" allow="autoplay; midi" ></iframe> ``` Or let the script build size and upgrade a placeholder: `data-tunetoys-embed` takes `toy:<id>`, `release:<digest>`, `help:<type>/<order>` or `node:<type>`. ```html <div data-tunetoys-embed="node:std.lowpass" data-theme="dark"></div> <script async src="https://tune.toys/embed/v1/embed.js"></script> ``` Release embeds follow the creator's embed policy and origin allowlist. Help-patch and node embeds hold no account data and may be framed by any site. A paid release without a demo never shows its Patch. ## Query parameters All are optional; unknown keys are ignored. | Parameter | Values | Behavior | | --------------- | --------------------------------- | -------------------------------------------------------- | | `view` | `frame`, `patch`, `split`, `card` | One of the views the subject offers | | `node` | a node id in the help patch | The card to show with `view=card` | | `theme` | `light`, `dark`, `auto` | Embed chrome and editor theme; not the Toy's Frame theme | | `controls` | `show`, `hide` | `hide` renders the Frame for display plus a Play button | | `preset` | a preset id of the release | Applied after preparation | | `p.<controlId>` | number | Initial value of a public control, clamped; at most 32 | | `title` | `show`, `hide` | Title line | | `locale` | a supported locale | Embed chrome language | | `ref` | opaque label | Counted in daily counters only | There is deliberately no `autoplay`, no URL loading and no parameter that names an account or entitlement. ## Messages Messages are plain objects `{ protocol, channel, id?, type, ... }`, at most 16 KiB. Host to embed: `init`, `setParameter`, `setParameters`, `play`, `stop`, `loadPreset`, `setTheme`, `requestState`. Embed to host: `ready` (with `subject`, `view` and, for releases, `digest`), `state`, `parameterChanged`, `playbackChanged`, `error`, `resize`. `play` works only after an in-frame user gesture has unlocked audio; a node card answers `not_embeddable`. Size the iframe from `resize`, which follows the handshake as soon as the embed has measured itself. Always check `event.origin` and `event.source` before trusting a message; the `@tunetoys/embed` package does this for you. Only public control values can be read or written. No message can read source, assets or account data, install, purchase or navigate the top window. --- URL: https://docs.tune.toys/developers/errors/ Title: Errors Every failing response uses one envelope. `requestId` echoes the `X-Request-Id` response header; include it in support requests. ```json { "error": { "code": "SCOPE", "message": "This key is missing a required scope.", "details": { "requiredScopes": ["write:projects"], "missingScopes": ["write:projects"] }, "requestId": "req_..." } } ``` Match on `code`, never on `message`. `details` is bounded, machine-readable context. | Code | Status | Meaning | | --------------------------------------- | ------ | ------------------------------------------------------------- | | `MIXED_AUTH` | 400 | A key was sent with a cookie or another credential | | `IDEMPOTENCY_KEY` | 400 | A required `Idempotency-Key` is missing or malformed | | `VALIDATION` | 400 | The body failed schema validation; `details` carries paths | | `KEY_INVALID` | 401 | Unknown, malformed or expired key | | `KEY_NETWORK` | 403 | The caller address is outside the key's allowlist | | `AUTH_MODE` | 403 | The route does not accept keys | | `SCOPE` | 403 | A required scope is missing | | `TIER_REQUIRED` | 403 | The feature needs a pro app | | `PUBLISHER_ROLE` | 403 | The acting user lacks the publisher role | | `VERSION_CONFLICT`, `REVISION_CONFLICT` | 409 | Your expected version or revision is stale; re-read and retry | | `IDEMPOTENCY_CONFLICT` | 409 | The same `Idempotency-Key` was used with different input | | `RATE_LIMIT` | 429 | A bucket or the daily quota is exhausted | The authoritative per-route list is in the [route index](https://docs.tune.toys/developers/api/) and the OpenAPI document. --- URL: https://docs.tune.toys/developers/idempotency/ Title: Idempotency Project creation, saves and operations, publication, visibility, forks, library mutations and listing writes require an `Idempotency-Key` of 1 to 128 characters. - A receipt is scoped to the account and the canonical request, including the action and the project or release identity. - An exact retry returns the original result. Reusing a key with different input returns `409 IDEMPOTENCY_CONFLICT`. - Owner and access checks still run before a result is replayed. - **Keep the original key when a connection fails before you know whether the write succeeded.** Generate a new key only for a new intent. Writes that change a versioned resource also take an `expectedVersion` (or revision). Idempotency protects retries; versions protect against overwriting someone else's change. --- URL: https://docs.tune.toys/developers/mcp/ Title: MCP server **Status:** Implemented as a private stdio package, not yet published to npm. A hosted remote endpoint needs OAuth for users and is planned. `@tunetoys/mcp` is a Model Context Protocol server. It is an ordinary API client: every call goes through the SDK with your developer key, so it gets exactly that key's scopes, limits and owner checks. ## Configuration | Variable | Default | Meaning | | ------------------- | ---------------------- | ----------------------------------------------------------- | | `TUNETOYS_API_KEY` | none | Developer key. Without it only anonymous read tools appear. | | `TUNETOYS_API_BASE` | `https://tune.toys/v1` | API base, for example a local API | ```sh claude mcp add tunetoys --env TUNETOYS_API_KEY=tt_... -- node /path/to/TuneToys/packages/mcp/src/main.ts ``` Keep the key in the client's secret configuration, never in a repository. ## Tools | Tool | Needs | Behavior | | ---------------------------------------------- | ----------------------- | ---------------------------------------------------- | | `search_catalog`, `get_listing`, `get_product` | none | Public reads | | `get_node_docs` | none | Node contract and docs, local | | `validate_toy_json` | none | Validates against the Toy schema, local | | `get_toy_source` | none or `read:projects` | A release by digest, or your draft | | `list_my_projects` | `read:projects` | Your drafts | | `propose_operations` | `read:projects` | Applies a batch locally and reports; writes nothing | | `create_draft_project`, `apply_operations` | `write:projects` | Creates a draft; applies revision-checked operations | | `update_listing_fields` | `write:listings` | Saves listing overrides; does not publish | ## Safety Write tools carry `destructiveHint`, accept at most 64 operations per batch and share a budget of 20 write calls per process. Conflicts surface as tool errors; the agent must re-read and propose again. No tool publishes, changes visibility, uploads media or touches billing. --- URL: https://docs.tune.toys/developers/pagination/ Title: Pagination Three styles exist; each route's OpenAPI entry says which it uses. | Style | Used by | How | | ------------- | ---------------------------------------- | ------------------------------------------------------------------------------------------------------- | | Offset | `GET /v1/catalog` | `offset=0`, `q`, `category`; 24 per page. All query terms match before paging. | | Keyset cursor | `GET /v1/posts`, `GET /v1/notifications` | Pass the opaque `nextCursor` as `cursor` until it is absent. Do not parse or build cursors. | | Sync cursor | `GET /v1/library/sync?after=0` | Up to 100 ordered changes, a `cursor` and `hasMore`. Persist the cursor only after you commit the page. | Stop when there is no `nextCursor` or `hasMore` is false. Page sizes are bounded by the server and may change before 1.0. --- URL: https://docs.tune.toys/developers/quickstart/ Title: Quickstart 1. Sign in, open **Settings → Developer**, create an app and a key. New keys get `read:public` only; tick each extra scope. The full key (`tt_<prefix>_<secret>`) is shown once, so store it in a secret manager. 2. Call the API with the key as a bearer token. Never send cookies with it. ```sh export TUNETOYS_API_KEY=tt_... curl -H "Authorization: Bearer $TUNETOYS_API_KEY" "https://tune.toys/v1/catalog?q=delay" curl -H "Authorization: Bearer $TUNETOYS_API_KEY" https://tune.toys/v1/developer/key ``` 3. From TypeScript, `new PlatformClient('https://tune.toys/v1', fetch, { apiKey })` sends the bearer, omits credentials and skips CSRF. It refuses to run in a browser page unless you pass `allowBrowserKey`. 4. For agents, run the [MCP server](https://docs.tune.toys/developers/mcp/) with `TUNETOYS_API_KEY`. `GET /v1/developer/key` introspects the key: its app, scopes, tier and expiry. It is free of rate-limit cost, so use it to check your setup. --- URL: https://docs.tune.toys/developers/rate-limits/ Title: Rate limits and quotas Limits use shared token buckets, so they hold across API instances. The numbers below are **proposed defaults** until an owner decision publishes advertised limits. | Bucket (per minute, burst) | Anonymous (per address) | Free key | Pro key | | -------------------------- | ----------------------- | -------- | --------- | | read | 120, 60 | 300, 100 | 3000, 600 | | write | not limited here | 30, 10 | 300, 60 | | expensive | 10, 5 | 20, 10 | 240, 60 | | Rolling 24 hour requests | none | 10 000 | 500 000 | Each route has a `cost`: list and sync pages weigh 2, release package, engine and asset bytes 5, webhook test sends 25 in `expensive`, and `0` for `/v1/developer/key`, `/v1/developer/limits`, `/v1/developer/scopes` and `/v1/openapi.json`. ## Headers Every limited response carries `RateLimit-Limit`, `RateLimit-Remaining` and `RateLimit-Reset` (seconds) for the most constrained bucket. ## When you are limited A rejection is `429 RATE_LIMIT` with `Retry-After` and `details: { bucket, limit, remaining, resetSeconds, retryAfterSeconds, quota }`. `bucket` is `daily` for the quota. Back off for `Retry-After` seconds, then retry. The first quota rejection per key per day notifies the app owner. ## Tiers and usage An app is **pro** while its owner holds an active Developer API grant, and **free** otherwise. Usage is recorded per key and hour and readable at `GET /v1/developer/apps/{appId}/usage` (also as CSV). Usage is informational and kept 90 days. --- URL: https://docs.tune.toys/developers/schemas/ Title: JSON Schemas - ApiError: Error envelope returned by every failing API response. `requestId` echoes the X-Request-Id response header. - ApiKeyScopes: Scopes granted to one developer API key. - ApiKey: Metadata of one developer API key. The secret is returned once at creation or rotation and never stored in plain text. - ApiUsage: Metered usage of one developer app in UTC hourly or daily windows. Counters are informational and may lag by up to 30 seconds. - ArtifactDescriptor: One registered build output of a product version. digest, size, inventory, targetProfile, format and arch never change; a correction is a new artifact. - PlatformCommon: Shared scalar formats referenced by the other platform contracts. - DeveloperApp: A developer app groups API keys, usage, webhooks and a tier. `tier` is the last observed effective tier, computed from the owner's feature:api.pro grant. - PlatformEnums: Closed platform enums. Generated TypeScript exports each as a type and an as-const array; this file is their single source. - LicenseCertificate: Offline license certificate payload (tunetoys.license-certificate/1), signed in a SignedEnvelope with the license key. Contains no device ID, email or handle. allowedMajor or until is absent for the all update policy. - ListingRevisionContent: Content of one product listing revision. The effective value of a field is overrides[field] ?? generated[field]. - SoundAnalysis: One engine run over a media version: its state, the facet summary the index reads and the full report. A newer engine version appends a row; the resolved view takes the newest ready one. - SoundItem: The mutable catalog entry of one upload: what people see and edit (title, artwork, tags, declared musical fields, visibility). Bytes and rights live on its versions. Server-owned counters and the resolved view are separate shapes here. - SoundRelation: A directional relation between two media items, created by either owner. Symmetric in the UI; a relation to an item the viewer cannot read is omitted. - SoundRendition: One derived artifact of a media version (peaks, preview, thumbnail, score page, piano roll, beats). Immutable; unique per version, kind, recipe, toolchain and profile. - SoundVersion: One immutable upload of a media item: bytes, digest, rights, what the probe found and the tags the file carried. Never edited after creation; releases pin versions. - MediaVocabulary: The closed vocabularies of the Sounds catalog as one document (served to editors and used as the zero-shot tag prompts). The long lists (`Instrument`, `Genre`, `Mood`) are defined here; the short ones reference enums.schema.json. Generated TypeScript exports each titled list as a type and an as-const array. - NotificationEvent: Input of the single notify helper. params holds ICU arguments (ids, counts, handles), never rendered strings or private content. - PlatformMatrix: Per-version platform matrix: one entry per target profile. included means an artifact exists, supported is a creator claim, tested requires evidence. - PostContent: Author-supplied content of a review, comment or reply. Server-owned fields (author, verified owner, root, depth, pins, state) are not part of it. Replies inherit subject and team scope from their root. - ProductPage: Visibility-filtered public product listing, documents, versions, offers and head metadata shared by HTML and React. - SignedEnvelope: Ed25519 envelope for update manifests and license certificates. The signature covers a purpose-specific domain prefix followed by the exact payload bytes; verifiers check it before parsing and never re-canonicalize. - SoundLandingPage: Validated sound facet landing content, filters, pagination metadata and related categories. - SoundSearchParams: Query of GET /v1/sounds and /v1/sounds/facets: every filter optional, bounded, and owned by the page URL. Facet counts answer for the same filter set. - StorefrontPage: Published or member-preview storefront sections, theme and public product cards. - StorefrontRevisionContent: Content of one publisher storefront revision: validated theme tokens and ordered typed sections. Storefronts are data, never code. - UpdateManifest: Signed update feed payload for one product and channel (tunetoys.update-manifest/1). Lists artifact digests, never URLs or tickets. Signed in a SignedEnvelope with the update_feed key. - Webhook: A pro-tier webhook endpoint. Deliveries are signed with TuneToys-Signature: t=<unix seconds>,v1=<hex HMAC-SHA256(secret, t + "." + body)>. --- URL: https://docs.tune.toys/developers/sdk/ Title: SDK `@tunetoys/sdk` is the typed client used by the app, the MCP server and the Player. With a key: ```ts import { PlatformClient } from '@tunetoys/sdk'; const client = new PlatformClient('https://tune.toys/v1', fetch, { apiKey: process.env.TUNETOYS_API_KEY, }); ``` It sends the bearer, omits credentials, skips CSRF and refuses to run in a browser page unless you pass `allowBrowserKey`. It exports `verifyWebhook` for webhook receivers. The package is private to the repository today and is not published to npm. Until it is, generate a client from [`openapi.json`](https://docs.tune.toys/developers/openapi.json) or call the HTTP API directly. --- URL: https://docs.tune.toys/developers/webhooks/ Title: Webhooks Pro apps with a live key that has `read:products` can receive signed webhooks for products of the app's publisher. | Topic | Behavior | | ------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | Events | `version_published`, `listing_updated`, `review_created`, `purchase_completed`, `update_feed_changed`. Not every event is wired in every build; see the status note in the platform guide. | | Payload | `{ id, type, createdAt, apiVersion, data }`. `data` holds ids and minimal fields (at most 1 KiB). Fetch details through the API. | | Signature | `TuneToys-Signature: t=<unix seconds>,v1=<hex HMAC-SHA256(secret, t + "." + body)>`, plus `TuneToys-Webhook-Id` and `TuneToys-Event-Id`. | | Secret | `whsec_...`, shown once. | | Endpoint | Public HTTPS only. Private, loopback and link-local addresses are refused at connect time. No redirects. 10 second timeout. | | Verification | Creating a webhook sends `webhook.verify` with `data.challenge`. Respond 2xx with JSON echoing `{"challenge": ...}`. | | Retries | Eight attempts: immediately, then 1 min, 5 min, 30 min, 2 h, 6 h, 12 h and 24 h. A webhook with no success in three days is disabled. | ## Verifying a delivery 1. Read the raw body. 2. Reject a timestamp `t` older than five minutes. 3. Compute `HMAC-SHA256(secret, t + "." + rawBody)` and compare in constant time with each `v1` value. 4. Deduplicate on the event id. The SDK exports `verifyWebhook` which does steps 2 and 3. --- URL: https://docs.tune.toys/contributors/ Title: Contributors These pages are rendered from the repository's `docs/` folder. They describe how the system is built, including work that is planned, and are not the product experience. Each page links to its source file. The complete set, including decisions and delivery records, is in the [repository documentation index](https://github.com/joshstovall/TuneToys/blob/main/docs/README.md). --- URL: https://docs.tune.toys/contributors/architecture-overview/ Title: System architecture This document answers three questions for anyone changing the repository: which layer a piece of code belongs to, which folder owns it, and what it is allowed to depend on. Tune.Toys has three durable layers: **portable authored data**, **execution and presentation**, and **creation/distribution services**. Product shells compose them. Cloud services support discovery and delivery; local audio never depends on their availability. **Status:** the local browser, authoring, shared Frame runtime, Rust compiler/engine/ABI, and source/catalog packages are implemented. The development API, releases, agent adapter and native Player core are implemented. Managed hosting and full journey qualification remain open; see [implementation status](https://github.com/joshstovall/TuneToys/blob/main/docs/delivery/implementation-status.md). ```mermaid %%{init: {'theme': 'base', 'themeVariables': {'primaryColor': '#FAFAF7', 'primaryTextColor': '#1C1C1B', 'primaryBorderColor': '#1C1C1B', 'lineColor': '#64645F', 'fontFamily': 'DM Sans, Inter, system-ui, sans-serif'}}}%% flowchart TB WEB["Web editor and community"] --> AUTHOR["Authoring API<br/>revisions and proposals"] AGENT["In-app agent<br/>later CLI and MCP"] --> AUTHOR AUTHOR --> SOURCE[("Toy source<br/>definitions · ports · controls · Frames")] SOURCE --> COMPILER["Compiler<br/>resolve · validate · admit · prepare"] COMPILER --> ENGINE["Shared DSP core"] SOURCE --> FRAMES["Shared Frame renderer"] FRAMES <--> LIVE["Running-instance protocol"] LIVE <--> ENGINE ENGINE --> BROWSER["WASM + AudioWorklet host"] ENGINE --> NATIVE["Native ABI + JUCE shell"] NATIVE --> PLAYER["TuneToys Player plugin"] NATIVE --> EXPORT["Independent plugin export"] SOURCE --> RELEASE["Immutable release pipeline"] RELEASE --> CDN["Managed or self-hosted web publication"] RELEASE --> LIBRARY["Catalog · account library · install cache"] LIBRARY --> PLAYER 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 SOURCE source class AUTHOR,COMPILER,ENGINE,FRAMES,LIVE,RELEASE shared class WEB,AGENT,BROWSER,NATIVE,PLAYER,EXPORT,CDN,LIBRARY host ``` Arrows describe the complete intended boundaries, not a claim that every edge is qualified end to end. ## Ownership | Layer | Location | Owns | | ----------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------- | | Product shells | [`apps/web`](https://github.com/joshstovall/TuneToys/blob/main/apps/web/README.md), [`apps/player-plugin`](https://github.com/joshstovall/TuneToys/blob/main/apps/player-plugin/README.md), [`apps/desktop`](https://github.com/joshstovall/TuneToys/blob/main/apps/desktop/README.md) (Editor and Installer; release qualification open) | Routes/windows, user flows, host lifecycle | | Authoring | [`packages/authoring`](https://github.com/joshstovall/TuneToys/blob/main/packages/authoring/README.md), [`packages/editor`](https://github.com/joshstovall/TuneToys/blob/main/packages/editor/README.md) (extraction pending; editor UI lives in `apps/web`) | Atomic edits, history, candidates; graph editing UI | | Portable contracts | [`packages/toy-format`](https://github.com/joshstovall/TuneToys/blob/main/packages/toy-format/README.md), [`packages/runtime-protocol`](https://github.com/joshstovall/TuneToys/blob/main/packages/runtime-protocol/README.md) | Source grammar; instance control/event protocol | | Presentation | [`packages/frame-runtime`](https://github.com/joshstovall/TuneToys/blob/main/packages/frame-runtime/README.md) | Shared React renderer, widgets, bindings, accessibility | | Browser execution | [`packages/runtime-web`](https://github.com/joshstovall/TuneToys/blob/main/packages/runtime-web/README.md) | Worklet, WASM loading, browser I/O, generation handoff | | Processing | [`crates/compiler`](https://github.com/joshstovall/TuneToys/blob/main/crates/compiler/README.md), [`crates/engine`](https://github.com/joshstovall/TuneToys/blob/main/crates/engine/README.md), [`crates/engine-ffi`](https://github.com/joshstovall/TuneToys/blob/main/crates/engine-ffi/README.md) | Admission/programs, DSP/state, native ABI | | Native host integration | [`native/plugin-shell`](https://github.com/joshstovall/TuneToys/blob/main/native/plugin-shell/README.md) | JUCE buses, transport, gestures, state, embedded web bridge | | Distribution | [`services/api`](https://github.com/joshstovall/TuneToys/blob/main/services/api/README.md), [`services/worker`](https://github.com/joshstovall/TuneToys/blob/main/services/worker/README.md) (listing assets; builds planned), [`exporters/*`](https://github.com/joshstovall/TuneToys/blob/main/exporters/README.md) | Auth/catalog/library; isolated jobs; target packaging | | Extension interface | [`packages/sdk`](https://github.com/joshstovall/TuneToys/blob/main/packages/sdk/README.md), [`packages/node-catalog`](https://github.com/joshstovall/TuneToys/blob/main/packages/node-catalog/README.md) | Public developer APIs and node/component discovery | ## Dependency rules Pure data contracts sit at the bottom and import nothing above them. | Layer | May depend on | Must not depend on | | ---------------- | ----------------------------------------- | --------------------------------------------------- | | Contracts | Nothing above | React, browser/OS APIs, databases, account services | | Authoring | Contracts | React, account databases | | Editor UI | Authoring, contracts | Mutating the graph behind authoring's back | | Frames | Running-instance interface, public params | The editor | | Runtime adapters | Compiler outputs, contracts | The account service | | Apps | Any shared package | Other apps | | Packages | Other packages | Apps | | Rust DSP | Its own crates | The website, JUCE, Electron, any model vendor | **The compiler may allocate off the audio thread; the prepared engine may not allocate during render.** The FFI adapter presents a small C ABI. JUCE remains C++ host integration, and the same native shell serves both Player and independent exports. The backend begins as a modular monolith: identity, projects, releases, library, discovery and later commerce modules in one API deployment and one relational database. Long builds and renders belong in a separately isolated worker backed by durable jobs and object storage. Add services only when security, scaling or ownership demonstrates a need. AWS is selected: the initial hosting plan is one Lightsail/PostgreSQL server, private S3 for media and backups, and SES. The existing S3/SES adapters and PostgreSQL cutover support the next steps; further hosting services are deferred until needed. See [AWS architecture](https://github.com/joshstovall/TuneToys/blob/main/docs/architecture/aws.md) and the [media library](https://github.com/joshstovall/TuneToys/blob/main/docs/specs/media-library.md). ## Data authority | Data | Authority | Mutability | | ------------------------------------------------- | ------------------------------------------ | ------------------------- | | Source | Project/revision store | Mutable through revisions | | Releases and assets | Content digest | Immutable | | Catalog entries, visibility, library annotations | Relational database | Mutable | | Search indexes, thumbnails | Derived from the above | Rebuildable | | Installed releases | Local verified cache of pinned releases | Replaceable, never edited | | DAW sessions | Self-contained recall state in the project | Owned by the host project | | Device selection, secrets, grants, remote pairing | Host-local only | Never portable | A running instance has its own generation and live values. Saving a project default, changing a runtime parameter, publishing a release, bookmarking a Toy and restoring a DAW session are separate operations. **A catalog edit or a canvas move can never change an active sound.** ## Implementation - Contracts: [`packages/toy-format`](https://github.com/joshstovall/TuneToys/blob/main/packages/toy-format/README.md), [`packages/runtime-protocol`](https://github.com/joshstovall/TuneToys/blob/main/packages/runtime-protocol/README.md), [`packages/node-catalog`](https://github.com/joshstovall/TuneToys/blob/main/packages/node-catalog/README.md) - Execution: [`crates/`](https://github.com/joshstovall/TuneToys/blob/main/crates/README.md), [`packages/runtime-web`](https://github.com/joshstovall/TuneToys/blob/main/packages/runtime-web/README.md), [`native/plugin-shell`](https://github.com/joshstovall/TuneToys/blob/main/native/plugin-shell/README.md) - Presentation and authoring: [`packages/frame-runtime`](https://github.com/joshstovall/TuneToys/blob/main/packages/frame-runtime/README.md), [`packages/authoring`](https://github.com/joshstovall/TuneToys/blob/main/packages/authoring/README.md) - Shells and services: [`apps/`](https://github.com/joshstovall/TuneToys/blob/main/apps/README.md), [`services/`](https://github.com/joshstovall/TuneToys/blob/main/services/README.md), [`exporters/`](https://github.com/joshstovall/TuneToys/blob/main/exporters/README.md) ## Related - [Execution and optimization](https://docs.tune.toys/contributors/execution/) - [Frames and interface composition](https://docs.tune.toys/contributors/frames/) - [Publishing, Player and remote control](https://docs.tune.toys/contributors/distribution/) - [Trust and service boundaries](https://docs.tune.toys/contributors/security/) - [Architecture decisions](https://docs.tune.toys/contributors/decisions/) - [MVP scope](https://docs.tune.toys/contributors/mvp/) and [implementation status](https://github.com/joshstovall/TuneToys/blob/main/docs/delivery/implementation-status.md) --- URL: https://docs.tune.toys/contributors/authoring-agents/ Title: Authoring for people and agents How do the graph editor, the Frame designer, the in-app prompt panel and later CLI/MCP adapters change a Toy without bypassing one another? Every author, human or model, goes through `packages/authoring`: the same node catalog, the same structured operations, the same revision checks. No model-specific JSON becomes the stored Toy representation. **Status:** implemented locally — typed operations and history, manual candidate JSON, optional server-side Claude text generation, compilation, audition, acceptance and Undo. Richer domain-level explanations, durable server history, serialized inverse operations and an MCP adapter remain planned. Evidence: [recorded live Claude proposal journey](https://github.com/joshstovall/TuneToys/blob/main/docs/delivery/browser-journeys.md#live-claude-proposal) and [implementation status](https://github.com/joshstovall/TuneToys/blob/main/docs/delivery/implementation-status.md). ## Proposal lifecycle A user asks for a complete Toy, a node group, or a Frame that controls existing parameters. The agent inspects a bounded selection, the current revision, public interfaces, available nodes, target limits and relevant examples. It returns an ordinary candidate operation batch, a concise explanation, requested capabilities and validation diagnostics. Acceptance and publication stay separate actions. ```mermaid %%{init: {'theme': 'base', 'themeVariables': {'primaryColor': '#FAFAF7', 'primaryTextColor': '#1C1C1B', 'primaryBorderColor': '#1C1C1B', 'lineColor': '#64645F', 'actorBkg': '#DCEBDD', 'actorBorder': '#1C1C1B', 'signalColor': '#1C1C1B', 'noteBkgColor': '#DED8F0', 'noteBorderColor': '#1C1C1B', 'fontFamily': 'DM Sans, Inter, system-ui, sans-serif'}}}%% sequenceDiagram participant User participant App as Editor / prompt panel participant Agent as Agent adapter (API) participant Authoring as packages/authoring participant Host as Runtime host User->>App: prompt or manual candidate JSON App->>Agent: bounded selection, revision, catalog, limits Agent-->>App: candidate operation batch + explanation + diagnostics App->>Authoring: validate against base revision Note over App: before/after source diff, separate from the model's explanation App->>Host: optional bounded audition in a separate generation User->>App: accept App->>Authoring: atomic revision with inverse operations Authoring-->>App: new revision, Undo entry App->>Host: prepare new runtime generation Note over Host: failure leaves the current sound running User->>App: publish (separate authorized action, release checks) ``` | Rule | Consequence | | -------------------------------------------------------------------------- | -------------------------------------------------------------------- | | A stale expected revision is a conflict | The candidate is rejected and must be regenerated against the latest | | Acceptance writes one atomic revision with inverse operations | One Undo removes the whole proposal | | Activation prepares a new runtime generation separately from acceptance | Accepted source and the running sound are distinct states | | Failed compilation or cancelled inference never overwrites accepted source | The current sound is never interrupted by a bad proposal | | A proposal can be schema-valid and musically bad | Technical checks and listening judgment stay separate | | Imported notes, asset text and documentation are untrusted context | They cannot override application or user authorization | ## Data design for models The portable model is designed so an agent can edit it without ingesting the whole project. | Principle | In the Toy format | | ----------------------------------------------------------- | -------------------------------------------------------------------------------------------------- | | Descriptive stable IDs and keyed maps, not index references | `nodes`, `connections`, `parameters`, `frames` are keyed objects | | Explicit endpoints | Connections carry `from` and `to` objects with node and port | | Self-describing controls | Public parameters carry units, ranges, defaults, smoothing and named targets | | Presentation binds to identity | Frames bind parameter IDs, never pixel positions or runtime-generated names | | Small files, inspectable parts | A definition or selection can be discovered and inspected on its own | | Catalog as documentation | Descriptions, typed ports, parameter domains, examples, capability needs, versions, target support | | Diagnostics with addresses | Error codes and authored paths | | Extension data preserved, not executed | Namespaced extension data survives round trips but confers no execution authority | ## Authoring API responsibilities | Method group | Purpose | | ---------------------------------- | ----------------------------------------------------------- | | discover, inspect | Catalog and selection context for people and models | | create candidate, validate, diff | Build and check a proposal without touching accepted source | | prepare / render audition, compare | Hear or see a candidate in an isolated generation | | accept, undo | Commit one atomic revision; reverse it | | save, request publication | Persist a draft; start the separate release path | Operations carry actor and provenance, base revision, idempotency key, affected object IDs and the declared target profile. Store concise intent and observable artifacts; private model reasoning is not required. Revisions are server-ordered and sequential, with isolated candidate branches. Offline/local editing keeps a local history and reconciles explicitly; concurrent multi-user CRDT editing is deferred (the current collaboration subset is in [community](https://docs.tune.toys/contributors/community/#shared-editing)). The full envelope is in the [operation contract](https://github.com/joshstovall/TuneToys/blob/main/docs/specs/operations.md). ## Service boundaries The AI integration lives in the API/worker layer behind authoring permissions and per-job budgets. Provider credentials stay server-side. Users choose whether private source and assets may leave their device and which service receives them. Cancellation propagates through model and render jobs; budgets bound tokens, duration, asset uploads and retry count. **Provider choice never affects local playback or recall.** The app works without the agent service. ## New Toy recipes The New Toy chooser's AI-first flow ([v0.0.4 plan](https://github.com/joshstovall/TuneToys/blob/main/docs/delivery/sprints/v0.0.4/README.md)) uses the same proposal path. A recipe from [`@tunetoys/recipes`](https://github.com/joshstovall/TuneToys/blob/main/packages/recipes/README.md) fixes the form, the starter base, the effort level and a guidance paragraph; the form compiles to a small brief, and the browser applies the returned batch to its own copy of the shared base from `@tunetoys/discovery` before the person presses Create. The request is one cached system block (rules plus the compact catalog), one base-Toy block cached for official bases, and the brief, so a generation costs the brief plus output; one corrective turn reuses the prefix. Usage is metered per run and generation is bounded by per-address brakes, a rolling account allowance and a deployment ceiling ([HTTP contract](https://github.com/joshstovall/TuneToys/blob/main/docs/specs/http-api.md#agent-candidates)). The recipe forms and result step in the browser are planned. ## Current review experience Before acceptance the browser shows actual source-property before/after values, separately from the model's explanation. The diff is bounded to 500 changes with explicit truncation and expandable full values. It is a structural source diff, not a semantic explanation. High-level agent text, control and nested-Frame actions preserve unstated appearance, layer metadata and responsive constraints when updating an existing widget of the same kind. New widgets and kind changes use replacement semantics. The emitted canonical `widget.put` is still a complete replacement, so every write passes the same source validator and revision boundary. ## Implementation - [packages/authoring](https://github.com/joshstovall/TuneToys/blob/main/packages/authoring/README.md) — operations, candidates, history, `agent.ts` proposal helpers - [services/api](https://github.com/joshstovall/TuneToys/blob/main/services/api/README.md) — `agent.ts` Claude Messages adapter and candidate routes ([HTTP contract](https://github.com/joshstovall/TuneToys/blob/main/docs/specs/http-api.md#agent-candidates)) - [apps/web](https://github.com/joshstovall/TuneToys/blob/main/apps/web/README.md) — `PromptComposer.tsx`, `ProposalChanges.tsx` - [packages/node-catalog](https://github.com/joshstovall/TuneToys/blob/main/packages/node-catalog/README.md) — the catalog both people and agents discover; `compactCatalog()` is its model-facing rendering - [packages/recipes](https://github.com/joshstovall/TuneToys/blob/main/packages/recipes/README.md) — New Toy recipes, briefs and the brief compiler ## Related - [Revisioned authoring operations](https://github.com/joshstovall/TuneToys/blob/main/docs/specs/operations.md) - [Frames and UI composition](https://docs.tune.toys/contributors/frames/) - [Trust and service boundaries](https://docs.tune.toys/contributors/security/) - [Claude API credential setup](https://github.com/joshstovall/TuneToys/blob/main/docs/delivery/local-testing.md#claude-api-credentials) - [Decision 0003: Frames and authoring](https://github.com/joshstovall/TuneToys/blob/main/docs/decisions/0003-frames-and-authoring.md) --- URL: https://docs.tune.toys/contributors/community/ Title: Collaborative community What does the full collaborative social community platform require, what exists, and what is still missing? This document is the requirements-and-evidence record for that active objective. It extends the earlier MVP scope. **Passing the HTTP contracts does not complete the objective**; editor synchronization, cursor overlays, conflict recovery and complete community journeys are still required. No hosted promotion or real social email campaign has been performed for this work. **Status:** active work. Persistent social, notification, membership, presence and edit APIs pass SQLite and PostgreSQL fixtures; profile, discussion and collection UI is being qualified. See the table below and [implementation status](https://github.com/joshstovall/TuneToys/blob/main/docs/delivery/implementation-status.md). ## Entities ```mermaid %%{init: {'theme': 'base', 'themeVariables': {'primaryColor': '#FAFAF7', 'primaryTextColor': '#1C1C1B', 'primaryBorderColor': '#1C1C1B', 'lineColor': '#64645F', 'fontFamily': 'DM Sans, Inter, system-ui, sans-serif'}}}%% flowchart LR ACCOUNT["Account<br/>profile · picture · links · achievements"] PROJECT["Project (draft)<br/>members · roles · edits · sessions"] RELEASE[("Release / catalog entry<br/>immutable, public or not")] COLLECTION["Collection<br/>private · unlisted · public"] THREAD["Discussion thread<br/>anchored · public or team-only"] INBOX["Notifications<br/>center · preferences · deliveries"] ACCOUNT -->|follows| ACCOUNT ACCOUNT -->|likes · bookmarks| RELEASE ACCOUNT -->|owns| COLLECTION --> RELEASE ACCOUNT -->|invited viewer / commenter / editor| PROJECT PROJECT -->|publishes| RELEASE THREAD -.anchored to.-> RELEASE THREAD -.anchored to.-> PROJECT ACCOUNT -->|receives| INBOX ACCOUNT -.blocks · reports.-> ACCOUNT 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 RELEASE source class ACCOUNT,PROJECT,COLLECTION,THREAD shared class INBOX host ``` **Mutable social data never enters a portable Toy or an immutable release package.** ## Requirements and evidence | Requirement | Implemented | Remaining work and qualification | | -------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- | | Profiles | Public pages for all accounts, display name, bio, eight HTTPS links, normalized picture upload/removal, public work, real earned achievements | Consistent account/header avatars; real multi-account profile/avatar browser journey passed | | Collections and liked work | Private/unlisted/public named collections, explicit ordered membership, public collection pages, optional public Likes tab; bookmarks remain private | Full collection pagination/reordering UI and browser journeys | | Engagement | Follows/unfollows, likes/unlikes, following feed, and posts: product reviews, threaded comments and replies with mentions, likes, helpful votes, pins, publisher responses, edit history, removal/resolution and durable anchors | Competing UI writes and full moderation/block/report browser journeys; review → response → reply → like → pin browser journey | | Shared drafts | Viewer/commenter/editor invitations, acceptance, roles/removal, shared project listing and enforced access | Workspace invitation/membership controls and live draft entry | | Concurrent editing | Validated historical authoring batches, independent-field merging, atomic revisions, conflict rejection and author-specific Undo | Connect editor pending edits, conflict review, reconnect, Redo and two-browser journeys | | Cursors and sessions | Account-bound tab identities, bounded cursor/selections, expiry and authorized reads | Workspace cursor/selection overlays and cleanup/reconnect journeys | | Collaborative notes | Public versus team-only anchored threads with read/comment permissions | Editor selection/reveal and team discussion UI | | Notifications | Unified notification center with read/dismiss state, an event × channel preference matrix with locked required events, deduplication, collapsing and deep links | Bell with unread count and ETag polling; filters, grouped rows, optimistic actions; see [notifications and localization](https://github.com/joshstovall/TuneToys/blob/main/docs/architecture/notifications-localization.md) | | Email and push channels | Leased delivery for every channel, verified recipients, preference/visibility checks at delivery, bounded retries, digests, SES/Resend, web push, bounce suppression and scoped one-click unsubscribe | Injected-transport tests; operator delivery/bounce monitoring and hosted qualification remain. Native push is planned only | | Community controls | Blocking (read-time post filter), reports with `kind: post`, configured moderator report queue, post removal, restore and verified-badge clearing | Moderator queue view and complete blocking/reporting browser journeys | ## Identity and access SQLite migration 9 and PostgreSQL migration 10 add community, membership, session and edit records. Server-owned `projects.parent_release` records actual fork provenance separately from editable source for the remixer achievement. Achievements derive from real public work and contributions; no accounts or awards are invented. | Rule | | ---------------------------------------------------------------------------------------------------- | | Bookmarks stay separate from explicit public likes | | Private collections and liked work are not exposed unless the owner chooses | | Public collections show visitors only public Toys; later visibility changes withdraw them | | Blocks prevent interaction; they cannot make anonymously readable public URLs secret | | Invitations reveal inviter, project identity and role before acceptance, never draft source | | Every draft, team, session and edit operation checks accepted membership | | Only owners manage other members; members may leave; publishing and visibility remain owner actions | | Revocation and blocking deny subsequent reads, edits, notes and presence, including old edit retries | ## Shared editing The API accepts a durable request key, an existing base project version and a shared authoring batch. It applies the batch to that admitted historical source, merges independent fields against the latest source, validates the complete result and commits through the existing `applyBatch` project writer. Scalar and array collisions reject with `COLLABORATION_CONFLICT`; invalid graph dependencies reject atomically. Toy identity stays stable. The authenticated account supplies the audit actor. Undo reverses only the author's recorded change against the latest source, preserving unrelated peer work. A conflicting reversal rejects; an edit cannot be undone twice; Undo itself is recorded. Edit audit rows reference existing revision snapshots instead of duplicating whole source documents. | Presence limit | Value | | ------------------------------- | -------------------------------------------- | | Expiring sessions per account | 8 | | Invited members per project | 32 | | Selected identities per message | 100, with view/scope and bounded coordinates | | Unchanged-state polls | Omit source | | Rate buckets | Presence and edits are separate | Presence stays outside Toys and audio callbacks. ## Posts: reviews, comments and replies One `posts` table (SQLite migration 11, PostgreSQL 12) carries reviews, comments and replies, replacing the foundation comment table in place. `services/api/src/posts.ts` owns the rules; `posts-store.ts` owns storage and database triggers keep thread shape, depth, identity and the three-pin limit. | Kind | Subjects | Rules | | --------- | ---------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `review` | `product` | Rating 1–5, optional 120-character title and 8 KiB Markdown; one per account and product (a second save edits it); not by publisher members or the project owner; verified email or one day of account age required | | `comment` | `product`, `project`, `version`, `storefront`, `docs_page` | 1 byte to 8 KiB Markdown; anchors and team-only notes stay on project subjects with the rules below | | `reply` | Inherited from the root | Depth ≤ 4; a reply to a depth-4 post continues on its parent with a quoted `@handle`. A publisher response is the one flagged reply per review | | Rule | Behavior | | ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Verified owner | Frozen at creation from an active `product:<id>` billing grant only; free claims and plays do not verify; refunds never rewrite it; moderators can clear it | | Reviewed version | Chosen by the author or suggested from the latest installation, else the selected library release; an edit after a newer version is published shows "Updated after version X" with the prior rating | | Rating summary | `rating_summary` is recomputed in the transaction of every review write, removal, restore or moderation and exposed on listings as `ratingSummary`; library items no longer carry ratings | | Edits and history | Every content edit stores the previous body, rating and title in the immutable `post_revisions`, readable by the author and moderators | | Removal | Author removal clears the text; subject owners (project owner, editing publisher member) remove comments and replies but not reviews; moderators remove anything. Tombstones remain while visible replies need them | | Blocks | Read-time filter in both directions; replies, reactions, mentions and notifications between blocked accounts are refused or dropped | | Pins | Up to three roots per subject by the subject manager or a moderator, rendered first | | Notifications | `reply`, `mention`, `publisher_response`, `review_on_your_product`, `comment_on_your_work` and `pinned` commit with the post; deliveries are planned by the notification service | The web `PostFeed` renders Reviews (rating summary histogram filters, `ReviewComposer`, helpful sort) and Discussion tabs, threads with collapse below depth 2, reactions, pins, publisher responses, edit/remove/report and moderator actions. It replaces the Toy page discussion and editor team notes, and renders directly on the public React profile, collection and product pages inside the shared shell. ## Notifications and operation Community events (follow, like, comment, reply, mention, invitation, collaboration edit) are written by `notify()` in the same transaction as the event. It drops self-notifications and blocked actors, deduplicates per account and collapses unread repeats with the same group key (for example likes on one Toy) into one row with a count. One `NotificationRunner` delivers every channel: it acquires a database lease, re-checks the verified address, current preferences, blocks and resource access, then calls the provider outside transactions. Retries keep provider identity; two runners cannot claim the same live lease. SES delivery is at-least-once across a crash after acceptance; exactly-once email is not claimed. Six attempts bound retries and rows retain delivery and failure evidence. Email and push link to the authenticated app rather than embedding private source or comment text, and push bodies never name people or projects. Unsubscribe tokens are random, stored hashed, scoped to one category or all optional email, and can never disable required account, security or purchase notices. The unsubscribe page never mutates on GET; RFC 8058 one-click uses POST. The design and routes are in [notifications and localization](https://github.com/joshstovall/TuneToys/blob/main/docs/architecture/notifications-localization.md). `TUNETOYS_MODERATOR_ACCOUNT_IDS` explicitly configures durable account IDs for report review. No administrator credential is seeded. PostgreSQL runtime privileges include the new tables. Offline import includes durable social data and orders threaded posts by insertion to preserve parent foreign keys. Cursor sessions are transient. ## Verification | Check | Covers | | --------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `services/api/test/community.test.mjs` | Real HTTP/SDK profiles, interactions, project discussion and team notes through posts, reports, visibility, roles, concurrent edits, author Undo, revocation and immutable packages | | `services/api/test/posts.test.mjs` | Review eligibility and uniqueness, verified-owner freeze, legacy reviews, summaries, helpful/rating sorts, publisher responses, edit history, depth, pins, blocks, moderation and notifications | | `services/api/test/community-mail.test.mjs`, `notifications.test.mjs` | Injected delivery: leases, retries, digests, suppression, web push encryption, preferences, center routes and scoped unsubscribe without sending real mail or push | | Both on SQLite and disposable PostgreSQL | The first database checkpoint passed 58 PostgreSQL tests and the complete root `npm run check` | | `npm run test:community-ui -w @tunetoys/web` (after a browser build) | Profile/avatar editing, follows, likes/replies, private-to-public collections, notifications/read state and anonymous 390px discovery, without page errors or horizontal overflow | The goal additionally requires every remaining workspace, cursor, conflict, reconnect and moderation item in the requirements table to be implemented and verified before completion. ## Implementation - [services/api](https://github.com/joshstovall/TuneToys/blob/main/services/api/README.md) — `community.ts`, `community-routes.ts`, `community-access.ts`, `community-pages.ts`, `notifications.ts`, `notification-runner.ts`, `posts.ts`, `posts-routes.ts`, `posts-store.ts` - [services/api/migrations](https://github.com/joshstovall/TuneToys/blob/main/services/api/migrations/README.md) — community and collaboration migrations - [packages/authoring](https://github.com/joshstovall/TuneToys/blob/main/packages/authoring/README.md) — `collaboration.ts` merge and author Undo - [apps/web](https://github.com/joshstovall/TuneToys/blob/main/apps/web/README.md) — `CommunityApp.tsx`, `CollaborationPanel.tsx`, `PostFeed.tsx`, `ReviewComposer.tsx`, `RatingSummary.tsx`, `CreatorProfilePage.tsx`, `CollectionPage.tsx` ## Related - [Discovery and public pages](https://docs.tune.toys/contributors/discovery/) — creator pages and public routes - [Platform entities and API responsibilities](https://github.com/joshstovall/TuneToys/blob/main/docs/specs/platform-data.md) - [Executable HTTP contract](https://github.com/joshstovall/TuneToys/blob/main/docs/specs/http-api.md) - [Authoring for people and agents](https://docs.tune.toys/contributors/authoring-agents/) — the single-author revision model this extends - [MVP scope](https://docs.tune.toys/contributors/mvp/) — the authorization that added this objective --- URL: https://docs.tune.toys/contributors/contributing/ Title: Contributing Start with [MVP scope](https://docs.tune.toys/contributors/mvp/), the [architecture overview](https://docs.tune.toys/contributors/architecture-overview/) and the README of the folder you intend to change. [AGENTS.md](https://github.com/joshstovall/TuneToys/blob/main/AGENTS.md) is the working agreement for humans and agents and takes precedence where it is stricter. ## Local workflow 1. Use the Node and npm versions pinned in the root manifest, install Rustup stable with the `wasm32-unknown-unknown` target, then `npm ci --ignore-scripts` and `npm run check`. npm and Cargo lockfiles freeze resolution. If your global npm cache is unavailable, use `npm ci --ignore-scripts --cache /tmp/tunetoys-npm-cache`; do not change ownership of unrelated local files. 2. For a schema change: bump its version if required, update examples and semantic validation, run `npm run generate`, and describe compatibility. Never edit generated types by hand. 3. `npm run format` formats source and docs. `npm run docs:check` requires a README in every source folder and verifies local Markdown links; both run inside `npm run check`. | Command | Required when | | --------------------------- | ------------------------------------------------------------------------------------------------------------------------ | | `npm run check` | Any foundation code change; needs PostgreSQL 17 for `npm test` (see [database checks](https://github.com/joshstovall/TuneToys/blob/main/docs/delivery/database.md#checks)) | | `npm run build:player` | Native shell or Player changes, in addition to root checks | | `npm run validate:examples` | Example or schema changes | ## Boundaries - Most folders are planned boundaries. Add a package manifest or build target only when there is real implementation and a meaningful check; documentation-only reservations need no dummy tests or no-op builds. - Keep application-specific flows in `apps/` and domain rules in shared `packages/`. Use `@tunetoys/*` for internal TypeScript packages; the workspace pattern already includes future packages under `packages/`, and services join workspace configuration with their first implementation. - Avoid a service per domain: one modular API plus a separate worker process. - Pure contracts may not import React, browser/OS APIs, databases or account services. Shared Frame code depends on a narrow runtime interface. Audio processing may not depend on editor state or cloud availability. - Add dependencies at the owning package and update the lockfile. Never import runtime files from the sibling bit-audio checkout. ## Testing Test at the behavioural boundary. Contract changes need malformed-input and compatibility cases; DSP changes need numeric, event and state fixtures on each admitted target. The [validation matrix](https://github.com/joshstovall/TuneToys/blob/main/docs/delivery/validation.md) lists current checks and gate suites. ## Decisions and review - An architectural change records its reason, alternatives, consequences and compatibility impact in [docs/decisions](https://docs.tune.toys/contributors/decisions/). Label implemented behaviour, adopted direction and unproven proposals separately. A proposed performance budget is not a measured claim. - Pull requests explain the concrete behaviour, the checks performed and remaining target limitations. - Keep release changes reviewable: no build or check command may publish, install plugins, enable paid services or provision production resources as a side effect. - Ports of legacy algorithms retain provenance and their original terms. Public licenses, publisher identity and release credentials need an explicit owner decision before distribution; see [license status](https://github.com/joshstovall/TuneToys/blob/main/LICENSE.md) and [security boundaries](https://github.com/joshstovall/TuneToys/blob/main/SECURITY.md). --- URL: https://docs.tune.toys/contributors/decisions/ Title: Architecture decisions Dated records of direction-setting choices: the problem, the decision, the alternatives rejected, the consequences accepted, and what evidence would cause a revisit. **A decision's status describes direction, not implementation.** Supersede a record with evidence instead of accumulating contradictory guidance. | ADR | Date | Status | | --------------------------------------------------------------------------------------------------------------------------------------------- | ---------- | ---------------------------------------------------------------------------------------------------------------------- | | [0001 One portable model in a boundary-based monorepo](https://github.com/joshstovall/TuneToys/blob/main/docs/decisions/0001-portable-model.md) | 2026-10-31 | Adopted; draft source subset implemented | | [0002 Shared processing semantics with target adapters](https://github.com/joshstovall/TuneToys/blob/main/docs/decisions/0002-shared-engine.md) | 2026-10-31 | Adopted; Rust compiler/engine and C ABI implemented; JUCE/state/legacy parity pending | | [0003 One Frame renderer and one authoring API](https://github.com/joshstovall/TuneToys/blob/main/docs/decisions/0003-frames-and-authoring.md) | 2026-10-31 | Adopted; draft Frame bindings implemented as data | | [0004 Immutable releases and local recall before marketplace breadth](https://github.com/joshstovall/TuneToys/blob/main/docs/decisions/0004-distribution.md) | 2026-10-31 | Adopted; development releases and local recall exist, managed services and exporters open | | [0005 Executable local platform before managed deployment](https://github.com/joshstovall/TuneToys/blob/main/docs/decisions/0005-development-platform.md) | 2026-10-31 | Development adapter adopted; SQLite removed, superseded by 0009 | | [0006 AWS hosting and reusable immutable media](https://github.com/joshstovall/TuneToys/blob/main/docs/decisions/0006-aws-media.md) | 2026-10-31 | Adopted at the user's request; lean pilot deployed, launch qualification pending | | [0007 Typed media signals, execution domains and a WebGPU video engine](https://github.com/joshstovall/TuneToys/blob/main/docs/decisions/0007-media-signals-and-domains.md) | 2026-11-01 | Adopted for planning in sprint v0.0.5; nothing implemented | | [0008 Takes as the recording model, a `data` cable type, and connections separate from identities](https://github.com/joshstovall/TuneToys/blob/main/docs/decisions/0008-takes-data-cables-and-connections.md) | 2026-11-01 | Adopted; implemented in the browser and API with fixtures (sprint v0.0.6); live providers and native hosts unqualified | | [0009 PostgreSQL only, with a Drizzle schema and one baseline](https://github.com/joshstovall/TuneToys/blob/main/docs/decisions/0009-postgres-only-drizzle.md) | 2026-11-01 | Adopted; implemented except pilot adoption and WP-H in progress | | [0010 One analysis engine: Rust descriptors native and WASM, Node parsers, optional CLAP on ONNX](https://github.com/joshstovall/TuneToys/blob/main/docs/decisions/0010-media-analysis-engine.md) | 2026-11-01 | Adopted; implemented in sprint v0.0.8; the embedding lane unsupported until a CLAP export is pinned | | [0011 Durable node items and one channel Mixer](https://github.com/joshstovall/TuneToys/blob/main/docs/decisions/0011-item-ports-and-the-channel-mixer.md) | 2026-11-01 | Accepted; implementation and verification tracked in sprint v0.0.16 | ```mermaid %%{init: {'theme': 'base', 'themeVariables': {'primaryColor': '#FAFAF7', 'primaryTextColor': '#1C1C1B', 'primaryBorderColor': '#1C1C1B', 'lineColor': '#64645F', 'fontFamily': 'DM Sans, Inter, system-ui, sans-serif'}}}%% flowchart LR A1["0001<br/>one portable model"] --> A2["0002<br/>shared engine"] A1 --> A3["0003<br/>one renderer, one authoring API"] A1 --> A4["0004<br/>immutable releases first"] A4 --> A5["0005<br/>executable local platform"] A5 --> A6["0006<br/>AWS hosting and media"] A2 --> A7["0007<br/>media signals and domains"] A3 --> A7 A7 --> A8["0008<br/>takes, data cables, connections"] A6 --> A8 A5 --> A9["0009<br/>PostgreSQL only, Drizzle"] A6 --> A9 A2 --> A10["0010<br/>one analysis engine"] A6 --> A10 classDef source fill:#F0442E,stroke:#1C1C1B,color:#FAFAF7 classDef shared fill:#DCEBDD,stroke:#1C1C1B,color:#1C1C1B class A1 source class A2,A3,A4,A5,A6,A7,A8,A9,A10 shared ``` ## Writing a new decision Use the next number and the shape of the existing records: **Date and status**, **Problem**, **Decision**, **Alternatives**, **Consequences**, and the evidence needed to revisit. Keep detailed data contracts in [specs](https://github.com/joshstovall/TuneToys/blob/main/docs/specs/README.md) rather than duplicating them here, and link the architecture document that applies the decision. --- URL: https://docs.tune.toys/contributors/developer-platform/ Title: Developer platform How do scripts, publisher automation and local agents use Tune.Toys without a browser session, and how does the API describe itself? Developer apps hold scoped API keys; the dispatcher authenticates keys, rate-limits and meters them with database-backed buckets; pro apps receive signed webhooks; and an OpenAPI 3.1 document, JSON Schemas at their `$id` URLs, `llms.txt` and an MCP server are generated from the same route declarations that the server dispatches. **Status:** Implemented locally (WP-12). Numbers marked _proposed_ are defaults pending the owner decision on advertised API limits and terms ([scope decision 5](https://github.com/joshstovall/TuneToys/blob/main/docs/delivery/sprints/v0.0.2/00-scope-and-decisions.md)). Third-party OAuth, a hosted MCP endpoint, paid usage packs and CORS for browser-side keys are planned, not implemented. The plan is [12 Developer platform](https://github.com/joshstovall/TuneToys/blob/main/docs/delivery/sprints/v0.0.2/12-developer-platform.md); the wire contract is [the HTTP API](https://github.com/joshstovall/TuneToys/blob/main/docs/specs/http-api.md#developer-api-keys). ## Quickstart 1. Sign in, open **Settings → Developer**, create an app and a key. New keys get `read:public` only; tick each extra scope. The full key (`tt_<prefix>_<secret>`) is shown once. 2. Call the API with the key as a bearer token. Never send cookies with it. ```sh curl -H "Authorization: Bearer $TUNETOYS_API_KEY" https://tune.toys/v1/catalog?q=delay curl -H "Authorization: Bearer $TUNETOYS_API_KEY" https://tune.toys/v1/developer/key ``` 3. From TypeScript, `new PlatformClient('https://tune.toys/v1', fetch, { apiKey })` sends the bearer, omits credentials and skips CSRF. It refuses to run in a browser page unless `allowBrowserKey` is passed. 4. For agents, run the [MCP server](https://github.com/joshstovall/TuneToys/blob/main/packages/mcp/README.md) with `TUNETOYS_API_KEY`. The machine-readable reference is `GET /v1/openapi.json`, browsable at `/developers` (Swagger UI from the pinned `swagger-ui-dist`, no CDN; "Try it out" sends `credentials: 'omit'`). ## Authentication and keys | Property | Behavior | | ------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Wire format | `tt_<prefix>_<secret>`: 8 lowercase base32 characters, then 43 base64url characters of 32 random bytes and a 6-character CRC32 checksum (`^tt_[a-z2-7]{8}_[A-Za-z0-9_-]{49}$`). The checksum rejects typos without a lookup. | | Presentation | `Authorization: Bearer tt_…` or `X-TuneToys-Key: tt_…`. A key with a cookie, a second key header or any other Authorization value is `400 MIXED_AUTH`. | | Storage | Only `HMAC-SHA256(pepper, key)`. The pepper is `TUNETOYS_API_KEY_PEPPER` (64 hex) or, when unset, derived from the native authorization key all API tasks share. Rotating either invalidates every key and webhook secret. | | Lifetime | 30, 90 (default) or 365 days; no non-expiring keys. Rotation issues a replacement with the same scopes and keeps the old key for an overlap (default 24 h, at most 7 days). | | Revocation | Immediate on this instance; other instances drop cached key rows within 30 s. `POST /v1/developer/key/revoke` lets a leaked key revoke itself. Deleting an app revokes its keys and disables its webhooks. | | Network | Optional IP allowlist of up to 16 CIDR entries, checked against the client address (`403 KEY_NETWORK`). | | Acting user | The key's creator. Handlers run their normal owner and visibility checks; scopes never widen them. | | Last used | Written at most once per minute per key. | | Audit | App, key, webhook and tier changes with actor, key prefix and a hashed address; visible to the app owner. | | Scope | Allows | | -------------------- | ------------------------------------------------------------------------------------------------------------------------------------- | | `read:public` | Catalog, listings, releases and release bytes | | `read:library` | Library, bookmarks and sync | | `write:library` | Bookmark, annotate and remove library items | | `read:projects` | Own draft projects | | `write:projects` | Create drafts, save, apply revision-checked operation batches and fork. Never publishes. | | `read:products` | Own publishers' products and listings; needed for product webhooks | | `write:listings` | Listing draft fields, assets, documents and translations. Never publishes or changes visibility | | `read:usage` | The key's own app usage | | `read:notifications` | Reserved for notification inbox reads | | `publish:releases` | Create versions, upload artifacts and publish releases for your publisher; the only way to register `signed` or `notarized` artifacts | Keys never reach billing, native device authorization, account authentication, developer app/key/webhook management, moderation, release/listing/storefront publication, visibility changes or server agent proposals. Those route declarations omit `key` from `auth`, so the denial is structural (`403 AUTH_MODE`), and `services/api/test/openapi.test.mjs` fails if one gains it. Key requests are exempt from Origin, `X-TuneToys-Request` and CSRF checks because they carry no ambient credential. Bodies must still be `application/json` (`415` otherwise) and routes that declare `idempotency: 'required'` need an `Idempotency-Key`. No CORS headers are sent for `/v1`. ## Rate limits and quotas Keys and anonymous reads use GCRA token buckets stored in `api_rate_buckets`, so limits hold across API instances without Redis. A process-local bucket with the same parameters rejects floods before the database; after each database decision it adopts the shared arrival time. Signed-in browser sessions keep the existing 600/minute fixed window and named limits (auth, proposals, checkout, native grants, listing writes). | Bucket (per minute, burst) _proposed_ | Anonymous (per address) | Free key | Pro key | | ------------------------------------- | ----------------------- | -------- | --------- | | read | 120, 60 | 300, 100 | 3000, 600 | | write | not limited here¹ | 30, 10 | 300, 60 | | expensive | 10, 5 | 20, 10 | 240, 60 | | Rolling 24 h request quota | none | 10 000 | 500 000 | ¹ Anonymous writes (registration, login) keep the 600/minute window and their named limits. Weights come from each route's `cost`: list and sync pages 2, release package/engine/asset bytes 5, webhook test sends 25 in `expensive`, and `0` (no bucket) for `/v1/developer/key`, `/v1/developer/limits`, `/v1/developer/scopes` and `/v1/openapi.json`. Daily byte quotas are recorded in usage but not enforced yet. Every limited response carries `RateLimit-Limit`, `RateLimit-Remaining` and `RateLimit-Reset` (seconds) for the most constrained bucket. A rejection is `429 RATE_LIMIT` with `Retry-After` and `details: {bucket, limit, remaining, resetSeconds, retryAfterSeconds, quota}`; `bucket` is `daily` for the quota. The first quota rejection per key per day notifies the owner (`api_limit_reached`). Anonymous limits key on the client address from the router's trusted-proxy rule: the socket peer, or the right-most untrusted `X-Forwarded-For` hop when the peer is a configured trusted proxy (`TUNETOYS_TRUSTED_PROXIES`). Without that configuration a load balancer makes every caller share one address; configure it before relying on per-address limits. ## Tiers and usage `developer_apps.tier` records the last observed effective tier. The effective tier is `pro` while the app owner holds an active `feature:api.pro` grant (the `TuneToys Developer API` billing offer) and `free` otherwise, cached 60 s. A change writes a `tier_changed` audit event and an `api_tier_changed` notification; a downgrade pauses active webhooks without deleting them and an upgrade resumes them. Keys never purchase anything; upgrades go through Settings → Billing. Usage accumulates in process per key and UTC hour and flushes every 30 s (and before usage reads and on shutdown) as additive upserts into `api_usage`: accepted requests, `429` rejections and response bytes. A crash may lose up to 30 s of counts; usage is informational until a paid pack needs an exact ledger. Usage is kept 90 days; `GET /v1/developer/apps/{appId}/usage` returns zero-filled hourly or daily series with per-key totals and `usage.csv` exports `window_start,key_prefix,requests,rejected,bytes_out`. Keys expiring within 7 days and 1 day notify the owner (`api_key_expiring`) from the hourly sweep. ## Webhooks (pro) | Topic | Behavior | | ------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | Events | `version_published`, `listing_updated`, `review_created`, `purchase_completed`, `update_feed_changed` for products of the app's publisher (or the owner's personal publisher), when the app has a live key with `read:products` | | Payload | Thin: `{id, type, createdAt, apiVersion, data}` where `data` holds ids and minimal fields (at most 1 KiB). Fetch details through the API. | | Signature | `TuneToys-Signature: t=<unix seconds>,v1=<hex HMAC-SHA256(secret, t + "." + body)>` plus `TuneToys-Webhook-Id` and `TuneToys-Event-Id`. Receivers reject `t` older than five minutes and deduplicate on the event ID; the SDK exports `verifyWebhook`. | | Secret | `whsec_…`, 32 random bytes, shown once, stored AES-256-GCM encrypted under a key derived from the pepper | | Endpoint | Public HTTPS only. The resolved address is checked at connect time against private, loopback, link-local and other reserved ranges; no redirects; 10 s timeout; 64 KiB read limit | | Verification | Creation (and URL changes or re-enabling) sends `webhook.verify` with `data.challenge`; a 2xx JSON response echoing `{"challenge": …}` makes the webhook `active` | | Retries | Eight attempts: immediately, then after 1 min, 5 min, 30 min, 2 h, 6 h, 12 h and 24 h. Each attempt is a row; after the last failure a webhook with no success in three days is `disabled` | | Dispatcher | Leased (`lease_until`) runner in the API process every 5 s; HTTP runs outside transactions. Test sends and manual redelivery queue rows and wake it | Other packages emit events with `emitWebhookEvent(db, {type, publisherId, data})` from `services/api/src/webhooks.ts`. It only inserts delivery rows, so it is safe inside the caller's transaction, and returns the number queued. `version_published` is emitted from version publication in `versions.ts` (deterministic event ID per version). `listing_updated` (listing publish in `listings.ts`), `review_created` (review creation in `posts-store.ts`/`posts-routes.ts`), `purchase_completed` (grant creation in `billing.ts`) and `update_feed_changed` (`appendUpdateFeeds` in `distribution.ts`) are not wired yet; each owner adds one `emitWebhookEvent` call with thin ids in its existing transaction. ## Agent-facing documents | Path | Source | | ----------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `GET /v1/openapi.json` | `packages/platform-contracts/openapi.json` from `npm run generate:openapi` (strong ETag, 5-minute cache) | | `GET /developers` | Swagger UI over the document | | `GET /schemas/index.json`, `/schemas/...` | Every platform contract at `https://tune.toys/schemas/platform/<name>.schema.json` and the Toy schema at `/schemas/toy-0.1.0.schema.json` | | `GET /llms.txt`, `/llms-full.txt` | `packages/platform-contracts/llms/` from `npm run generate:llms`: summary and links; full reference with this guide, MCP tools, operations, schemas, Toy format and node summaries (≤ 1 MiB) | The OpenAPI document embeds the platform-contract and Toy schemas whole in `components.schemas` with their `$id`s, so relative references between contract files keep resolving, and adds shared error responses, `RateLimit-*` headers, security schemes (`sessionCookie`, `apiKey`, `apiKeyHeader`, `nativeBearer`), per-operation scopes and the extensions `x-tunetoys-stability`, `x-tunetoys-auth`, `x-tunetoys-scopes`, `x-tunetoys-rate-bucket` and `x-tunetoys-cost`. Internal routes (OAuth callbacks, signed webhooks, public HTML) are served but omitted. Pre-1.0 schemas still change in place, so they are cached for an hour rather than marked immutable. ## Differences from the plan | Plan item | Implemented | | --------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Session GCRA buckets (600/min read for sessions) | Not enabled: browser sessions keep the existing fixed window so signed-in behavior is unchanged until the owner sets advertised limits | | Daily bytes-out quota | Recorded in usage; not enforced | | Pepper file for SQLite (`<database>.api-key-pepper`) | Derived from the native authorization key (already a stable per-deployment secret, with its own file for SQLite) unless `TUNETOYS_API_KEY_PEPPER` is set | | Webhook payload storage | The final schema has no event table, so `api_webhook_deliveries.event_id` stores a self-describing `evt.<base64url>` token with the thin payload; every attempt signs the same body | | Webhook secret rotation with two signatures, `DELETE` webhooks | Not implemented (one secret column; deliveries reference the row). Disable a webhook with `PATCH {state: 'disabled'}`; receivers already accept several `v1=` values | | Delivery log latency, error class and response excerpt | Only status and state are stored (final schema) | | Publisher-role intersection for publisher apps | Keys act as their creator through each handler's own publisher-role checks; no separate 60 s role cache | | Keys revoked on password reset or account deletion | Not yet wired; app deletion revokes its keys | | `x-tunetoys-cost` object, `TuneToys-API-Version` header, `Deprecation`/`Sunset`, `api-changelog.md` | Cost is split into `x-tunetoys-rate-bucket` and `x-tunetoys-cost`; version headers, deprecation headers and the changelog check are planned | | Servers `https://tune.toys/v1` | Servers are origins (`https://tune.toys`, `http://127.0.0.1:8787`) and paths keep `/v1`, matching the route declarations | | Swagger UI at `/developers/api` | Served at `/developers` on the API origin | | Immutable versioned schema file names | Current `$id`s are unversioned for platform contracts; cached for an hour because pre-1.0 contracts change in place | | MCP `propose_operation_batch` / `apply_operation_batch` | Named `propose_operations` / `apply_operations`; plus `get_listing` and `validate_toy_json` | | `health` and `config` for keys | Not key-accessible yet (deny by default; they live in `server.ts`) | ## Related - [HTTP API](https://github.com/joshstovall/TuneToys/blob/main/docs/specs/http-api.md#developer-api-keys) — wire contract and error codes - [Human and agent authoring](https://docs.tune.toys/contributors/authoring-agents/) — the revision rules every agent write follows - [Security](https://docs.tune.toys/contributors/security/) - [MCP server](https://github.com/joshstovall/TuneToys/blob/main/packages/mcp/README.md) and [SDK](https://github.com/joshstovall/TuneToys/blob/main/packages/sdk/README.md) --- URL: https://docs.tune.toys/contributors/discovery/ Title: Discovery and public pages How does someone who is not signed in find, hear and save a Toy, and how do search engines and social cards see it? This document covers the public routes the API serves, card previews and image derivatives, the public catalog search, HTML metadata and indexing, and the listening/saving flow. **Status:** implemented local browser/API slice. Search-engine indexing and third-party social cache refresh are external behaviors, not qualified compatibility claims. Managed hosting remains pending; see [implementation status](https://github.com/joshstovall/TuneToys/blob/main/docs/delivery/implementation-status.md). ## Public routes | Route | Serves | Indexing | | -------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------- | | `/` | Product homepage | Indexable | | `/explore` | Paginated public catalog with GET search form | Landing indexable; search results noindex | | `/explore/instruments`, `/explore/effects` | Category landing pages | Indexable | | `/sounds`, `/explore/chains`, `/creators` | Sounds grid, published chains, creators grid ([sounds](https://github.com/joshstovall/TuneToys/blob/main/docs/architecture/sounds.md)) | First unfiltered page indexable; filters, search and later pages noindex | | `/sounds/:id` | One sound: preview, resolved fields, links to its landing pages | Public indexable; unlisted noindex | | `/sounds/{genre,instrument,mood}/:value`, `/sounds/tempo/:bpm` | One-facet landing pages over the media vocabularies and 60–200 bpm in steps of 5 | Page 1 indexable with at least 4 sounds; unknown values 404 | | `/templates`, `/templates/:slug` | Official template index and three template pages from `@tunetoys/discovery` | Indexable | | `/learn/...` | Original practical guides | Indexable | | `/creators/:handle` | Creator profile: public work, optional liked work, collections, relationships ([community](https://docs.tune.toys/contributors/community/)) | Created, Sounds and Collections tabs, page 1, indexable; other tabs noindex | | `/t/:id` | Current public Toy alias, the search target | Indexable | | `/@:handle` | Publisher storefront: published revision of theme tokens and typed sections, or the generated default (hero, product grid) | Public storefronts indexable | | `/@:handle/:slug` | Product page: header, trailer and carousels, Overview/Versions/Reviews/Discussion/Docs anchored sections | Public listings of public Toys indexable; unlisted noindex | | `/@:handle/:slug/v/:semver`, `/docs/:docSlug` | One version's changelog and platforms; one manual, guide or FAQ page | Follows the listing | | `/r/:digest` | Exact pinned release with historical metadata | noindex,follow | | `/workspace`, `/library/…`, `/settings/…` | Editor, Library, Settings (SPA shell) | noindex,nofollow | The templates and guides are original editorial metadata over existing admitted examples, not invented user accounts or catalog releases. ```mermaid %%{init: {'theme': 'base', 'themeVariables': {'primaryColor': '#FAFAF7', 'primaryTextColor': '#1C1C1B', 'primaryBorderColor': '#1C1C1B', 'lineColor': '#64645F', 'fontFamily': 'DM Sans, Inter, system-ui, sans-serif'}}}%% flowchart LR HTML["Server head + semantic fallback"] --> APP["React router + shared shell"] APP -->|Preview or Play| ENGINE["Lazy shared engine + Frame controls"] APP -->|New / Remix / Workspace| EDITOR["Lazy persistent editor session"] MODELS["Visibility-filtered page models"] --> HTML MODELS -->|Public JSON| APP classDef shared fill:#DCEBDD,stroke:#1C1C1B,color:#1C1C1B classDef host fill:#DED8F0,stroke:#1C1C1B,color:#1C1C1B class HTML,APP,MODELS shared class ENGINE,EDITOR host ``` React renders every public URL inside the same navigation, account and theme shell, including community and notifications. `main.tsx` always mounts the router. The server retains status, redirects, cache/ETag rules, CSP, `<head>` metadata, structured data, sitemaps, images and semantic fallback inside `main[data-fallback]`. Fallback has headings, text, links, images with alt text and working GET forms; the small token-based `fallback.css` is its only stylesheet. React replaces fallback with `createRoot`; this is not hydration. `pages/marketplace.ts` resolves the published listing/storefront once for HTML and generated `ProductPage`/`StorefrontPage` JSON contracts. `pages/sounds.ts` supplies validated sound landing copy and filters. Their public endpoints are `GET /v1/pages/product`, `/storefront` and `/sounds`; previews retain member checks and private caching. Creator and collection pages use the existing community endpoints. Templates and lessons share the bundled discovery content. After navigation, React updates title, description and canonical; crawler metadata remains server-owned. The shell loads without an editor document, Frame renderer or audio host. `WorkspaceApp.tsx` loads on the first editor action, then keeps the document and audio session mounted across pages. Public page chunks have a 450 KiB gzip cold-load budget, verified from the build's actual module map. Template and release Preview load the real engine on demand, last at most twelve seconds and respect lower/muted monitor preferences. Switching, cancellation, hiding and leaving dispose the audition; template controls use the shared Frame renderer during playback. A root visit with browser recovery, a source fragment or an account-project handoff routes to `/workspace`; otherwise `/` shows the app home. Source fragments never reach server metadata. Template creation opens the real [New Toy chooser](https://github.com/joshstovall/TuneToys/blob/main/docs/architecture/new-toy.md), preserving current source until acceptance. ## Card previews `ToyThumbnail.tsx` draws each Toy as an SVG node-and-cable map through `PatchThumbnail.tsx` and `patchThumbnail.ts`. This includes home, Explore, community, storefront Toy cards, Library, recently played, reusable components and the workspace Toy browser. Saved positions, instance widths/colors, catalog colors, typed cables, visual groups and durable identities are retained. Populated Frames do not replace the graph. Local Toys and bundled official templates render directly; published cards use lazy, visibility-checked source reads through the account-scoped release query cache, which is also used by playback. Remote source is admitted before drawing, and missing or mismatched releases show an unavailable state while the Toy link remains usable. Browsing loads no audio engine or media assets and makes no thumbnail-image requests. `ToyPreview.tsx` retains inert Frame collages for the separate image-capture entry, with the entry Frame first and an SVG map when no visible Frame exists. `toyThumbnail.ts` supplies independent nested-instance source values without DSP. ## Image derivatives `PreviewCapture.tsx` is a separate production build entry at `preview-render.html`. The API's bounded `PreviewImages` service opens only this trusted bundle in an isolated Chromium context, admits source through `readToy`, serves every request from local files and blocks external networking, cookies, service workers and downloads. It waits for fonts, layout, lazy icon geometry and decoded Frame images, then captures the existing renderer. Only image assets referenced by Frames are read from the authorized release inventory with verified hashes and lengths; audio assets are not loaded and no engine is initialized. Sharp encodes 320/640 px WebP cards and 1200 × 630 PNG OG artwork; it is not a second Frame renderer. | Route | Output | | -------------------------------------------------------------------------- | ------------------------------- | | `/previews/r/:digest.webp?w=320\|640` | Release card | | `/previews/template/:slug.webp?w=320\|640` | Template card | | `/social/r/:digest.png`, `/social/t/:id.png`, `/social/template/:slug.png` | OG artwork (WebP also accepted) | | `/social/site.png`, `/social/site.webp` | Site artwork | | Limit or rule | | --------------------------------------------------------------------------------------------------------------------------------------------------- | | Aggregate image budget 16 megapixels / 16 MiB; capture flattens a bounded first raster frame to PNG in a render-only source copy | | Disk key covers admitted source, wrapper metadata, output dimensions/format and the fingerprinted renderer build | | One capture at a time, at most eight queued jobs, weighted Frame-complexity cap, twenty-second deadline; identical concurrent work shares a promise | | Cache prunes to 128 MiB / 512 images, writes atomically, closes idle Chromium after thirty seconds | | Authorization is checked before cache/render access and again after asynchronous work | | Toy images use private/no-store caching; only intentionally public images permit indexing; template/site images have short public lifetimes | | Previously downloaded copies and external social caches cannot be recalled | Correct content types, lengths and HEAD responses accompany all images. Public HTML embeds real image URLs with alt text, dimensions, responsive sources and lazy loading below the primary image. Derived bytes stay separate from portable source and immutable releases. Local capture needs the pinned Chromium headless shell (`node node_modules/playwright-core/cli.js install --only-shell chromium`); `TUNETOYS_CHROMIUM_PATH` selects an existing executable, and the container installs the shell during its build. Build the web app before serving the API: a missing capture bundle produces an explicit image-build error, never invented artwork. Preview disk space lives beside the API data by default. ## Public catalog `GET /v1/catalog` accepts `q` (maximum 200 characters), `category` (`instrument` or `effect`) and `offset`. Every whitespace-separated term must match the published title/description, current creator handle/display name, public tags or category. Search is parameterized literal substring matching over all public releases before 24-item pagination; `%` and `_` have no wildcard meaning. This first search uses SQLite's lowercase behavior and has no fuzzy matching or locale-aware stemming. Category follows the published entrypoint: an audio input makes a Toy an effect, otherwise an instrument. This is an audio-discovery grouping, not a compatibility profile. Search and filter state live in the Explore URL; requests are debounced, stale responses discarded, and errors, empty results and reset actions are visible. Creators enter up to twelve public tags of at most 32 characters during publication. `PUT /projects/:id/catalog` updates tags with owner authentication, idempotency and the expected project version. Tag changes advance mutable project/catalog metadata and notify library subscribers without modifying source, release bytes or selected library releases; tag-only edits preserve the current unsaved editor document. Public tags are distinct from private personal library tags. Omitting tags on publication preserves existing metadata. `005-discovery.sql` adds the catalog columns and backfills category; no portable Toy schema or release envelope changes. ## HTML, metadata and indexing Every public landing page has a descriptive title, summary, canonical URL, Open Graph/Twitter large-image metadata, meaningful alt text and `max-image-preview:large`. URLs use the configured `TUNETOYS_PUBLIC_ORIGIN`, never the request Host. HTML is escaped and structured JSON-LD receives a CSP hash. Home describes the WebSite, organization/confirmed social profiles and browser application; collections, creator profiles, app/template pages, breadcrumbs and guides carry matching structured data. Toy pages are `SoftwareApplication` with their product's paid offers or a free `Offer`, and an `aggregateRating` only from real reviews; sound pages are `AudioObject` with a licence deed URL, millisecond duration and preview `contentUrl`. `og:type` follows the page: `profile` for creators, `music.song` for audio, `article` for guides. Collections are a `CollectionPage` listing their Toys and, when they hold public audio, also a `MusicPlaylist` of `MusicRecording` tracks; guides add `HowTo` steps (`#step-<n>` anchors) beside their `TechArticle`. Share cards are 1200×630 brand cards from `shareImage.ts`: creators get a monogram card with their counts (`/social/creator/:handle.png`), sound landing pages a waveform seeded by the facet (`/social/sounds/:facet/:value.png`); the page URL carries `?v=` from what the card prints, so share caches refresh when it changes. No ratings, prices or eligibility claims are fabricated. Each category page ends its first unfiltered page with a short explanatory section, and template and guide pages link the nodes they use to docs.tune.toys, whose node pages link back to the templates. Home and Explore self-canonicalize. Each catalog page has its own `?page=N` URL, canonical and Previous/Next links. Unlisted and private pages/images remain excluded; inaccessible resources return real 404s. Trailing slashes redirect to canonical paths. The public sitemap lists landing pages, official templates/guides, creators who publish Toys or sounds, current public Toy aliases, public sounds and collections, and the sound landing pages that pass the indexing threshold, with image references; Toy lastmod comes from its published release time (`sitemap-pages.ts` holds the non-Toy queries). Past 45 000 URLs `/sitemap.xml` becomes a sitemap index of `/sitemaps/<n>.xml` parts. `/robots.txt` advertises the sitemap. One `robotsDirective` sets both the robots meta tag and `X-Robots-Tag`, so they never disagree. Indexed pages are sent `public,max-age=60,stale-while-revalidate=60` (never with a `Set-Cookie`), which bounds how long an unpublished page can outlive its visibility change; all others stay `private,no-store`. GET and HEAD return consistent metadata; public image indexing does not weaken release visibility checks. Optional `TUNETOYS_GOOGLE_SITE_VERIFICATION` and `TUNETOYS_BING_SITE_VERIFICATION` add property-verification tokens to the homepage; they do not create, connect or submit an external account. Operators verify the domain, submit the sitemap and inspect indexing and field Core Web Vitals in the appropriate account. **Neither search indexing nor a social crawler's rendering is guaranteed by local tests.** Vite's static preview does not generate this dynamic HTML; use `npm run build` and the API static host for public-route, metadata and image qualification. ## Listening, saving and returning Explore cards audition verified pinned releases through the existing BrowserHost with the admitted engine and verified assets. Effects use their Toy preview audio or the original demo loop; instruments use their own generated audio. | Audition rule | | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Starts only on a user action, at up to 20% monitor level (respecting a lower or muted editor setting), and stops after twelve seconds | | One catalog audition at a time; switching Toys, cancelling, changing search/type, navigating away, opening editor dialogs, hiding the page or starting workspace playback disposes it | | Previewing stops existing workspace audio without changing its source or saving/publishing it | | Errors stay visible with retry; cancelled and failed starts never enter history | Library → Recently played remembers up to thirty successfully started releases from Explore auditions and published-page Play. It stores only project identity, exact release digest and playback time on this browser, in separate guest/account namespaces; it does not synchronize history or alter a saved/pinned library release. Storage denial falls back to in-memory history. Current authorization is rechecked before showing titles or replay links. Items can be removed and history cleared. Replay uses `/r/:digest`, so later publication cannot silently change the sound previously heard. Explore, published playback and Recently played have a one-click Save/Saved toggle; guests are prompted to sign in. Initial bookmark flags are fetched in bounded batches by explicit project identities, independently of the 500-item full-library view. `PUT /library/:projectId/bookmark` changes only the bookmark field inside the existing transactional mutation boundary, retaining private tags, ratings and selected releases. It is a set-to-value command with a retained idempotency key, so retries do not toggle again; a no-change request creates no annotation version/event, and unsaving a missing item creates no row. Actual changes advance durable library versions and sync events, so stale full-annotation writes reject. **Saving does not pin, download or activate a release.** Published playback gives the shared Frame a compact header and action area. Play, presets and monitor volume stay visible; input selection and explicitly requested MIDI access live in an Audio popover with nested Escape and focus return. Input changes stay disabled during playback. Navigation and secondary Library/Remix actions use menus; the description is expandable. Package verification, pinned engine playback, rights and bookmark semantics are unchanged. ## Product pages and storefronts Rendered by `StorefrontPage.tsx` and `ProductPage.tsx` over the shared `services/api/src/pages/marketplace.ts` models (`storefrontPages.ts` re-exports the HTTP routes), with domain rules in `listing-model.ts` and the shared [`@tunetoys/markdown`](https://github.com/joshstovall/TuneToys/blob/main/packages/markdown/README.md) renderer. - **Content source.** A product page renders the listing's published revision (`overrides[field] ?? generated[field]`); without one it renders the generated baseline (`generateListingBaseline`: title, first-sentence tagline, description, category, tags, entry-Frame controls, requirements) from the stable version, else the newest published version, else the latest release. Publisher members may open `?preview=<revisionId>` for drafts and older revisions (`private,no-store`, noindex, preview banner). `?lang=<locale>` applies a non-stale translation per field and the page lists `hreflang` alternates. - **Visibility.** Listing visibility is separate from release visibility and product state. `private` listings, `draft` products and suspended publishers return 404 to non-members; `unlisted` sends `noindex,follow`; delisted or removed products render a `410` tombstone. Lists (storefront grids, "More by", sitemap) include only published products whose listing is public and whose wrapped Toy is public. A private release never contributes its preview image, Play link, social card or library button. - **Gallery.** React renders the trailer and carousels; fallback retains their media links and alt text. Carousels are scroll-snap lists with a labelled region and a focusable slide list (arrow keys scroll natively); `PublicGallery.tsx` adds previous/next buttons, a position announcement and Home/End. Uploaded media renders only when the media item is public. YouTube and Vimeo items are poster `<button>`s with a fallback link; the iframe (`youtube-nocookie.com` or `player.vimeo.com?dnt=1`, `sandbox="allow-scripts allow-same-origin allow-presentation"`, `allow="autoplay; picture-in-picture"`) is created only after activation, so no third-party request happens before. Only pages that contain such a poster add `frame-src https://www.youtube-nocookie.com https://player.vimeo.com` to their CSP. GIFs under `prefers-reduced-motion` stay hidden until Play; smooth scrolling is disabled. - **Sections.** Overview (Markdown body or description, controls with notes, requirements and download platforms from the stable version's included targets, tags, links, publisher card, bundle "Included", "More by"), Versions (published and withdrawn versions with rendered changelogs and alias badges), Reviews and Discussion (direct React `PostFeed` components), Docs (document links; FAQ inline). `/@h/:slug/versions|reviews|discussion|docs` redirect (308) to the anchored section. - **Metadata.** Canonical `/@handle/:slug`, OG/Twitter image from an uploaded `og` asset, else the generated OG card, else the release social card, JSON-LD `SoftwareApplication` (plus `Product` with `offers` only when an active paid offer exists, `aggregateRating` only with ratings, `operatingSystem` from included targets) and a breadcrumb. Storefronts emit `ProfilePage`. - **Redirects.** Retired handles (`publisher_handle_history`) and slugs (`product_slug_history`) redirect with 308. `/creators/:handle` redirects to `/@handle` once the publisher has published a storefront; until then it stays the community profile. - **Caching.** Public and unlisted pages send `public, max-age=60, stale-while-revalidate=600` with a weak ETag over the rendered HTML (`304` on match); previews, private pages and tombstones send `private,no-store`. - **Storefront theme.** Validated tokens become scoped CSS custom properties on the storefront page container, using the same preview renderer as Settings → Publisher. The shared navigation and focus rings stay outside the theme scope. - **Cards and sitemap.** The web Explore/Library card image is `/previews/listing/:projectId.webp`: the uploaded (else generated) listing thumbnail when the listing is public, otherwise a `307` to the release preview. The sitemap adds every public product page and its storefront, with image and video entries from [generated listing assets](#generated-listing-assets). ## Generated listing assets Implemented by WP-04: `services/api/src/listing-assets.ts` (triggers, routes, page helpers) and [`services/worker`](https://github.com/joshstovall/TuneToys/blob/main/services/worker/README.md) (generation). Publishing a product version that the listing shows (the stable alias, else the newest published version), moving the stable alias, or publishing a listing revision only **enqueues** `asset_jobs`; nothing renders in a request. Captures reuse `PreviewImages` and the `PreviewCapture` bundle above (no second Frame renderer); cards reuse the `shareImage.ts` glyph-path typography; the audio preview runs the shared WASM engine offline. Kinds whose tool is missing (Chromium, the built capture bundle, `TUNETOYS_FFMPEG_PATH`) are recorded as skipped with a reason and never replaced by a placeholder. | Route (GET and HEAD) | Content | Cache-Control | | ------------------------------------------ | ------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `/assets/l/:listingId/:kind/:variant.:ext` | Image, motion, audio, PDF, caption bytes | `public, max-age=31536000, immutable` with `?v=<digest>`; otherwise `public, max-age=300, stale-while-revalidate=3600` (`og`/`social`: `max-age=3600, stale-while-revalidate=86400`) | | `/@handle/:slug/llms.txt` | `text/plain` agent summary | `public, max-age=300, stale-while-revalidate=3600` | | `/@handle/:slug/README.md` | `text/markdown` product README | Same | | `/@handle/:slug/manifest.json` | Version, artifact inventory and asset index | `public, max-age=60, stale-while-revalidate=600` | | `/@handle/:slug/controls.schema.json` | JSON Schema 2020-12 of the public controls | `public, max-age=3600` | | `/@handle/:slug/structured.jsonld` | schema.org graph | `public, max-age=300` | - **Visibility.** Rechecked on every request: a `private` listing, a draft/delisted/removed product, a suspended publisher or a private wrapped Toy serves `404`; unlisted listings and non-public Toys serve by URL with `X-Robots-Tag: noindex`. Members preview any ready asset through `GET /api/v1/products/:productId/listing/assets/:assetId/bytes`. - **Selection.** An uploaded `thumbnail` or `og` override wins (the binary route answers `307` to the public media bytes); otherwise the ready generated row; otherwise the page falls back to the release preview and social card. A failed or skipped regeneration never replaces a ready asset. - **Headers.** Strong `ETag` (the SHA-256 digest) with `304` on `If-None-Match`; `Content-Length` on HEAD; text and JSON allow any origin; SVG carries `default-src 'none'; sandbox`. - **Product page.** Uses the generated OG card and `thumb-640`, adds `<link rel="alternate">` for `llms.txt`, README, manifest and control schema, `og:video` (MP4) and `og:audio` when ready, and `VideoObject`/`AudioObject` JSON-LD on indexable pages. - **Sitemap.** Each public product `<url>` lists `<image:image>` entries (thumb-1280, OG, desktop/phone screenshots, captioned with alt text) and a `<video:video>` entry when the MP4 and poster are ready. - **Machine drafts.** Alt text and captions are template-derived and stored with `metadata.unverified = true`; the editor labels them "Draft, unverified". - Not claimed: social crawler rendering, search indexing of video entries, or external cache refresh. ## Evidence | Check | Covers | | ----------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | API fixtures (`platform`, `seo`, `preview-images`) | Cross-page search, public vs personal tags, category filtering, literal queries, owner/version/bounds checks, unchanged release bytes, HTML escaping/CSP, canonicals, PNG dimensions, sitemap exclusion, private artwork denial, historical-release metadata, GET/HEAD, visibility changes during image work | | `services/api/test/listings.test.mjs` | Generated page with no creator work, overrides and reset, member preview, publish, click-to-load posters with page-scoped `frame-src`, tab redirects, documents, slug 308, unlisted noindex and sitemap exclusion, private 404, storefront contrast/sections/preview/publish/restore, creator redirect | | `apps/web/test/listening.test.mjs` | Bounded/scoped persistence, preview cancellation, late-load suppression, failure/retry and timeout | | `npm run test:seo-ui --workspace @tunetoys/web` | Real shared-renderer WebP/PNG exports, byte caches, font/icon readiness, image access after revocation, public pages without editor/WASM startup, layout shift, search, template playback with live controls and editor handoff | | `npm run test:listening-ui --workspace @tunetoys/web` | Against a disposable real API/database after a browser build: real shared-engine signal, single-preview switching, cancellation, retry, timeout, exact replay/history scope, revoked access, saving without pinning, desktop/mobile layout | | Browser checks | New/recovered/snapshot entry, search/reset/filtering, real Remix navigation, desktop/mobile layout | Accounts, databases, caches and browser profiles in these suites are disposable; `UI_SCREENSHOT_DIR` retains visual evidence. References: [Google JavaScript SEO](https://developers.google.com/search/docs/crawling-indexing/javascript/javascript-seo-basics), [site names](https://developers.google.com/search/docs/appearance/site-names), [Open Graph](https://ogp.me/), [Sharp](https://sharp.pixelplumbing.com/) and [OpenType.js](https://github.com/opentypejs/opentype.js). ## Implementation - [services/api](https://github.com/joshstovall/TuneToys/blob/main/services/api/README.md) — `publicSite.ts`, `discovery.ts`, `previewImages.ts`, `shareImage.ts`, `pages/`, `storefrontPages.ts`, `listing-model.ts`, `listing-assets.ts` - [services/worker](https://github.com/joshstovall/TuneToys/blob/main/services/worker/README.md) — generated listing asset jobs - [packages/markdown](https://github.com/joshstovall/TuneToys/blob/main/packages/markdown/README.md) — shared Markdown parser and allowlisting renderer - [packages/discovery](https://github.com/joshstovall/TuneToys/blob/main/packages/discovery/README.md) — official templates and guides - [apps/web](https://github.com/joshstovall/TuneToys/blob/main/apps/web/README.md) — `main.tsx`, `Catalog.tsx`, `ToyPreview.tsx`, `PreviewCapture.tsx`, `RecentlyPlayed.tsx`, `PublishedToy.tsx` - [packages/frame-runtime](https://github.com/joshstovall/TuneToys/blob/main/packages/frame-runtime/README.md) — the one renderer behind cards, captures and playback ## Related - [Publishing, libraries and Player](https://docs.tune.toys/contributors/distribution/) - [Collaborative community](https://docs.tune.toys/contributors/community/) - [App shell, Settings and Library](https://github.com/joshstovall/TuneToys/blob/main/docs/architecture/settings-library.md) - [Executable HTTP contract](https://github.com/joshstovall/TuneToys/blob/main/docs/specs/http-api.md#public-discovery-metadata) - [Embedding, oEmbed and deep links](https://github.com/joshstovall/TuneToys/blob/main/docs/architecture/embedding.md) - [Trust and service boundaries](https://docs.tune.toys/contributors/security/) --- URL: https://docs.tune.toys/contributors/distribution/ Title: Publishing, libraries and Player What happens between "I accepted an edit" and "someone else hears it in their DAW"? This document describes the delivery paths that start from one immutable release: web links, the account library, TuneToys Player, and independent branded plugins. It also records the remote-control design that follows later. **Status:** development implementation. Entitlement-aware download tickets, signed update feeds, offline license certificates, installation records, Player update offers and the first Installer (macOS arm64, development signed) exist; see [tickets, feeds and licenses](#tickets-signed-feeds-and-licenses). The API freezes self-contained source/Frame bundles and exact engine bytes with content digests, aliases, visibility and library sync; browser publication and verified playback are connected; the Player installs and recalls verified release media; a pinned app/API image runs on the [AWS pilot](https://github.com/joshstovall/TuneToys/blob/main/docs/delivery/aws-pilot.md). Public commercial signing, specialization and full host/DAW qualification remain staged. Later sections describe the broader target alongside what exists. ## One release, several delivery paths An editable project is mutable. A release is an immutable, verified closure of a Toy, pinned dependencies, required assets, Frame presentation, runtime requirements, distribution terms and conformance reports. A catalog entry points to releases and owns discovery and visibility. **Updating a title, rating or bookmark cannot mutate a release or a running instance.** ```mermaid %%{init: {'theme': 'base', 'themeVariables': {'primaryColor': '#FAFAF7', 'primaryTextColor': '#1C1C1B', 'primaryBorderColor': '#1C1C1B', 'lineColor': '#64645F', 'fontFamily': 'DM Sans, Inter, system-ui, sans-serif'}}}%% flowchart LR PROJECT["Accepted project revision"] --> JOB["Resolve · validate · build · qualify"] JOB --> RELEASE[("Immutable release<br/>digest-addressed")] RELEASE --> WEB["Managed link /t/ · pinned /r/<br/>or self-hosted static site"] RELEASE --> CACHE["Verified local install cache"] CACHE --> PLAYER["TuneToys Player in a DAW"] RELEASE --> BINARY["Independent branded plugin"] ACCOUNT["Account library<br/>bookmarks · tags · ratings"] --> SYNC["Library sync"] SYNC --> CACHE CATALOG["Catalog entry<br/>title · visibility · tags"] -.points at.-> RELEASE 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 RELEASE source class PROJECT,JOB,SYNC,CACHE,CATALOG,ACCOUNT shared class WEB,PLAYER,BINARY host ``` Library sync makes releases discoverable locally; it does not install every bookmarked Toy or activate it on a track. Installation is an explicit download-and-verify operation. Loading prepares a new local instance. Updates are offered, never substituted into an existing performance or DAW project. ## Web publication | Path | Meaning | | -------------------------------------- | -------------------------------------------------------------------------------------------- | | `https://tune.toys/t/<slug-or-id>` | Share alias resolved once at page load; the page keeps its release until a deliberate reload | | `https://tune.toys/r/<release-digest>` | Pinned release; immutable bytes on content-addressed paths | Alias and catalog responses use appropriate revalidation. Assets needed by active or retained releases are kept. The [HTTP contract](https://github.com/joshstovall/TuneToys/blob/main/docs/specs/http-api.md#publication-and-authorization) describes the implemented subset. A static web export contains its Frame renderer, browser adapter, specialized WASM, manifest and complete permitted asset closure. It runs audio on the visitor's machine after a user gesture and the necessary input permissions. Managed hosting adds links, account discovery, quotas and optional branding; self-hosting receives a buildable static artifact with the same processing semantics. Editor code and private source/history are excluded from playback bundles. **Client-delivered processing is inspectable and must not be advertised as secret.** Private release delivery authorizes manifests, dependencies, builds, previews and every asset; a private listing with public CDN assets is not private. Public or unlisted demos can intentionally expose a separate approved release. Unlisted means absent from discovery, not access-controlled. Signed asset URLs expire and cannot serve as durable package identities. ## TuneToys Player [apps/player-plugin](https://github.com/joshstovall/TuneToys/blob/main/apps/player-plugin/README.md) is the VST3 host shell for Tune.Toys-native Toys. It shares JUCE integration with independent exports. A later desktop Player can reuse the same library, cache and runtime clients. Hosting arbitrary third-party VST/AU binaries is a separate native feature outside the MVP. | Concern | Design | | --------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Sign-in | System browser, desktop OAuth with PKCE and a local callback; provider/redirect policy finalized during implementation | | Tokens | Native credential storage only; never in the WebView, Toy graph, logs or DAW state | | Library sync | Bounded pages with a resumable cursor; cached metadata browsable offline; failed sync preserves the installed library and current audio | | Sign-out | Removes credentials without unloading the saved session | | Automation | Fixed stable host slots (provisional budget 64, frozen after the native spike); each DAW instance persists slot → public parameter ID with range/normalization | | Replacing a Toy | A deliberate action with a mapping preview and documented automation consequence; bookmarks and updates never cause it | | Extra controls | Usable in the Frame with explicit automation limits | Renaming or reordering parameters cannot reassign a slot. Independent plugins use their own frozen public-parameter mappings rather than Player macro slots. Current native evidence is in the [native Player spec](https://github.com/joshstovall/TuneToys/blob/main/docs/specs/native-player.md) and [Player authorization](https://github.com/joshstovall/TuneToys/blob/main/docs/specs/player-authorization.md). ## Offline recall and updates DAW state stores the selected release identity, the admitted Toy/Frame/dependency/asset closure, stable parameter values, slot mappings, bypass, relevant host configuration and a state format version. It stores no credentials or local grants. The first MVP proposes an 8 MiB total package budget so small releases embed fully in project state; the budget is frozen at P0. Larger asset libraries need a later durable backup/packaging contract rather than an undocumented cache dependency; see [native media closure](https://github.com/joshstovall/TuneToys/blob/main/docs/specs/native-assets.md). Restore validates all state before applying any values. The installed engine must support the saved semantic/profile version; unsupported state yields a clear recovery path and leaves existing audio intact where applicable. No arbitrary native code executes from session blobs. Supported engine releases and migrations are retained and upgrade recall is tested; a hash alone cannot preserve future OS compatibility. Transient DSP history may reset under an explicit policy while parameter and preset identities stay stable. Independent plugin binaries embed their approved graph, engine, Frame and assets. Compatible updates restore old parameter values into the receiving binary's compatible implementation with an upgrade report. A graph found in untrusted old session state is never silently executed. Player graphs are data admitted against the installed trusted engine's bounded primitive catalog. ## Build and publish transaction A worker freezes a source revision, resolves and verifies dependencies, assets and rights, performs target admission, creates reproducible artifacts, runs conformance and records hashes, toolchain and evidence. Signing runs under narrow release authority after qualification. An API transaction creates the immutable release record and advances a catalog alias with an expected previous version. Failed or cancelled builds leave the published pointer unchanged. Retrying with the same idempotency key must not double-publish or double-charge. The isolated worker is planned; today the API performs the development subset in-process. ## Versions and artifacts A product version (semver, frozen changelog, pinned release digests) layers over immutable releases; artifacts are verified builds of a version, and `stable`/`beta` are the only mutable pointers. Publishing a version creates the browser artifact from the pinned release; local VST3 and Standalone ZIPs, and the Chrome extension ZIP built from a release, are added with `npm run publish:artifacts` from the `artifacts.json` written by `build:plugin` or `build:extension`. The server re-hashes every upload, checks the declared inventory against the pinned release and engine, and accepts only buildable profiles, so the platform matrix never claims a target nothing builds. `supported` is a creator claim; `tested` needs an included artifact plus an evidence report for its digest. Yanking withdraws a version from downloads and feeds without touching installed bytes. `GET /v1/artifacts/:id/bytes` serves only free products and the browser artifact; every other artifact byte goes through a download ticket. Details: [versions and platforms](https://github.com/joshstovall/TuneToys/blob/main/docs/delivery/sprints/v0.0.2/05-versions-and-platforms.md) and the [HTTP contract](https://github.com/joshstovall/TuneToys/blob/main/docs/specs/http-api.md#product-versions-and-artifacts). ## Tickets, signed feeds and licenses Implemented in `services/api/src/distribution.ts` and `signing.ts` (WP-06); the contract is in the [HTTP API](https://github.com/joshstovall/TuneToys/blob/main/docs/specs/http-api.md#distribution-tickets-feeds-and-licenses). | Mechanism | Contract | | -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Download tickets | `POST /v1/artifacts/:id/tickets` checks visibility, yank state, the paid-content guard and the update policy frozen in the grant's order snapshot (`major`, `all`, `window:<months>`). One artifact, at most 15 minutes, optional device scope, 256-bit token stored only as SHA-256. Anonymous callers get free public artifacts under per-IP limits. | | Ticket bytes | `GET /v1/downloads/:ticketId?t=…`: single Range with `If-Range`, `ETag` = SHA-256, `Repr-Digest`, attachment name, `private, no-store` for paid. A byte budget of 3× the size bounds retries; delivering the last byte completes the ticket. Logs carry only a hash prefix. | | Signed update feeds | `GET /v1/products/:id/feed/:channel` returns a `tunetoys.update-manifest/1` Ed25519 envelope over exact canonical bytes plus changelog text checked against `changelogSha256`. Entries are an append-only ledger with a monotonic `sequence`; publish, yank and alias moves append, and reads re-sign content changes, key rotation or feeds older than a day (7-day expiry). | | License certificates | `POST /v1/products/:id/certificate` and `POST /v1/certificates/:id/refresh` issue `tunetoys.license-certificate/1` envelopes with a separate key: 30-day expiry, 14-day grace, `allowedMajor` or `until`, no device ID. Refund, suspension or dispute revokes certificates and open tickets in the billing transaction and posts an account notice. | | Installations | `POST /v1/installations` and `PUT /v1/installations/:id` keep one record per device and product: `installed`, `update_available` (set when a new feed version is covered by the grant, with an `update_available` notification), `failed`, `rolled_back` with `previous_version_id`, `removed`. | | Keys | Ed25519 per purpose (`update_feed`, `license`) from `TUNETOYS_UPDATE_FEED_KEY` / `TUNETOYS_LICENSE_KEY` (PEM or `_FILE`), or generated 0600 in the data directory for loopback development. `signing_keys` records public keys only; other keys of a purpose are retired (still verifying). `GET /v1/distribution/keys` lists them for diagnostics; clients pin keys and never add trust from it. | **Stable download links.** `GET /download/macos|windows|linux` on the site (`distributionSiteRoutes`) answers `302` to a fresh anonymous ticket for the latest published installer of the product named by `TUNETOYS_DESKTOP_PRODUCT_ID`; `GET /v1/products/:id/latest/:target` returns the same version and artifact facts as JSON. Both resolve the `stable` channel (or `?channel=beta`) with the feed's channel rules, skip yanked versions and hidden artifacts, and use the ordinary ticket checks and anonymous limits, so a download link is never a bypass. Until the app is published the route shows a small page pointing at the docs Download page. Native and desktop clients verify before reading: [`crates/distribution-verify`](https://github.com/joshstovall/TuneToys/blob/main/crates/distribution-verify/README.md) in the Player worker and Node Ed25519 in the [Installer](https://github.com/joshstovall/TuneToys/blob/main/apps/desktop/README.md), both against the shared [signed vectors](https://github.com/joshstovall/TuneToys/blob/main/tests/fixtures/distribution/README.md). The Player keeps installing release closures through the release routes, pinned by the verified feed's release digests; ticketed artifacts stream to disk under the feed's size (hard cap 512 MiB), verify, and move atomically into a retained `versions/` folder so rollback never needs the network. Certificates live in application support (0700/0600), enforce expiry and grace offline, and never touch audio, Toys or DAW state. Installed bytes keep working in every license state. ## Remote control, later A Frame can run locally or control an independently running host. Pairing establishes host identity, allowed endpoints, session lifetime and revocable grants. A relay carries authenticated, encrypted control and observation traffic where direct access is unavailable. Raw unauthenticated runtime sockets are never exposed, and possession of a Toy link is not control authority. The remote host owns audio and device execution. A lost connection expires control leases, releases source-owned momentary events and reports stale observations; reconnect requires a fresh authorized session and cannot silently reacquire privileged outputs. Remote audio/video streaming is a separately measured service with its own latency and cost profile. **The first sharing MVP is browser-local execution, not cloud DSP hosting.** ## Desktop app releases The TuneToys desktop app (the Editor window over the bundled web editor, plus the Installer) ships through the same products, versions, artifacts, tickets and signed feeds as creator releases, without coupling to a Toy: ```mermaid %%{init: {'theme': 'base', 'themeVariables': {'primaryColor': '#FAFAF7', 'primaryTextColor': '#1C1C1B', 'primaryBorderColor': '#1C1C1B', 'lineColor': '#64645F', 'fontFamily': 'DM Sans, Inter, system-ui, sans-serif'}}}%% flowchart LR TAG["desktop-v* tag"]:::source --> BUILD["Build and package<br/>macOS · Windows · Linux"]:::shared BUILD --> SIGN["Developer ID + notarize<br/>Azure Trusted Signing"]:::host SIGN --> PUB["publish:releases key<br/>create · register · publish"]:::shared PUB --> FEED["Signed feed<br/>stable · beta"]:::shared FEED --> DL["/download/:target"]:::host FEED --> SELF["Installer and self-update<br/>verify, then swap"]:::host classDef source fill:#F0442E,stroke:#1C1C1B,color:#FAFAF7 classDef shared fill:#DCEBDD,stroke:#1C1C1B,color:#1C1C1B classDef host fill:#DED8F0,stroke:#1C1C1B,color:#1C1C1B ``` | Piece | Rule | | ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | Product | Kind `app`, owned by the official publisher (`bit.audio`), free, public; `tools/seed-desktop-product.mjs` creates `tunetoys` and prints the id for `TUNETOYS_DESKTOP_PRODUCT_ID`. App versions pin no Toy release, get no browser artifact, and publish with at least one included artifact; their feed versions say `kind: 'app'` with no release digests | | Artifacts | `desktop-app-macos` `installer/arm64` (zip of one `TuneToys.app`), `desktop-app-windows` `installer/x64` (per-user NSIS), `desktop-app-linux` `installer/x64` (AppImage) and `standalone/x64` (`.deb`); inventory `{ kind: 'app-build', appVersion, gitCommit }`. Up to 1 GiB per artifact and 4 GiB per version, streamed in and out of the blob store with Range and the stored SHA-256 as `ETag` | | Signature states | `signed` and `notarized` are accepted only from an API key with `publish:releases`; people uploading by hand stay at `unsigned`/`dev`. Clients still check the OS signature themselves | | Links | `/download/:target` serves the latest; `/download/:target/:semver` serves exactly one published version (the stable URL winget uses); both mint a fresh anonymous ticket | | Clients | One Installer adapter per OS (`platform.ts`): install paths, a bounded JavaScript zip reader with the hostile-archive rules, and the OS signature check (`codesign` with the pinned Team ID on macOS, Authenticode with the pinned subject on Windows, none on Linux, where the signed feed and digest are the check). Self-update swaps the `.app`, runs the verified installer silently, or replaces the AppImage, only on **Restart to update**; `.deb`, store and Flatpak builds never update themselves | [`release-desktop.yml`](https://github.com/joshstovall/TuneToys/blob/main/.github/workflows/README.md) builds, signs and publishes once the owner provides Developer ID and notarization credentials, Azure Trusted Signing, a `publish:releases` key and the production feed key (pinned with `tools/pin-feed-keys.mjs`); see the [v0.0.14](https://github.com/joshstovall/TuneToys/blob/main/docs/delivery/sprints/v0.0.14/README.md#owner-prerequisites) and [v0.0.15](https://github.com/joshstovall/TuneToys/blob/main/docs/delivery/sprints/v0.0.15/02-combined-execution.md) sprints. No desktop release has been published yet. ## Commerce and app releases Commercial storefronts, paid and free product families, preset/sample/chain packs, bundles, Pro and Stripe/Connect settlement follow the [marketplace plan](https://github.com/joshstovall/TuneToys/blob/main/docs/product/marketplace.md). The billing foundation does not yet replace release/media authorization with paid entitlements. App deployments follow the separate [release runbook](https://docs.tune.toys/contributors/releases/); they never move creator release pins. ## Implementation - [services/api](https://github.com/joshstovall/TuneToys/blob/main/services/api/README.md) — releases, aliases, catalog, library sync, `publicSite.ts` - [packages/sdk](https://github.com/joshstovall/TuneToys/blob/main/packages/sdk/README.md) — release and library clients - [apps/player-plugin](https://github.com/joshstovall/TuneToys/blob/main/apps/player-plugin/README.md) and [native/plugin-shell](https://github.com/joshstovall/TuneToys/blob/main/native/plugin-shell/README.md) — Player shell, install cache, state - [exporters/plugin](https://github.com/joshstovall/TuneToys/blob/main/exporters/plugin/README.md) — independent VST3/Standalone export - [exporters/browser-extension](https://github.com/joshstovall/TuneToys/blob/main/exporters/browser-extension/README.md) — Chrome MV3 extension export - [services/worker](https://github.com/joshstovall/TuneToys/blob/main/services/worker/README.md) — planned isolated build worker ## Related - [Release, state and compatibility](https://github.com/joshstovall/TuneToys/blob/main/docs/specs/releases.md) - [Platform entities and API responsibilities](https://github.com/joshstovall/TuneToys/blob/main/docs/specs/platform-data.md) - [Independent plugin export and recall](https://github.com/joshstovall/TuneToys/blob/main/docs/specs/plugin-export.md) - [Discovery and public pages](https://docs.tune.toys/contributors/discovery/) - [Decision 0004: distribution](https://github.com/joshstovall/TuneToys/blob/main/docs/decisions/0004-distribution.md) --- URL: https://docs.tune.toys/contributors/dsp-primitives/ Title: Shared DSP primitives This document answers which processors the shared engine provides, where their numeric contracts come from, and what bounds, latency and evidence apply. The same Rust kernels serve browser/WASM, the native Player and independent exports. **Status:** every processor below has an executable shared Rust kernel with native/WASM parity fixtures. Pre-1.0 contracts change in place under the policy in [AGENTS.md](https://github.com/joshstovall/TuneToys/blob/main/AGENTS.md); this is not a compatibility promise for earlier development builds. Benchmarks are local software evidence, not device or DAW certification. ## Processor catalog | Node | Title | Role | Documented in | | ---------------- | --------------------- | ----------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------- | | `std.nam` | Neural amp | Bounded WaveNet A1/A2 inference, prepared conversion and size switching | [NAM architecture](https://github.com/joshstovall/TuneToys/blob/main/docs/architecture/neural-amp-models.md) | | `std.gain` | Gain | Foundation | this page | | `std.lowpass` | Low-pass filter | Foundation, one-pole | this page | | `std.delay` | Delay | Foundation; Clean/Filtered/Tape/Ducked modes | [below](#implemented-processing) | | `std.crossfade` | Crossfade | Foundation | this page | | `std.oscillator` | Oscillator | Multiwave source | [below](#implemented-processing) | | `std.svf` | Resonant filter | State-variable filter | [below](#implemented-processing) | | `std.eq` | Parametric EQ | 1–32 durable RBJ biquad bands with per-band bypass | [below](#implemented-processing), [Parametric EQ](https://docs.tune.toys/nodes/std.eq/) | | `std.noise` | Noise | Thirteen streaming colors/textures | [noise](https://github.com/joshstovall/TuneToys/blob/main/docs/architecture/noise.md) | | `std.drive` | Drive | Saturation with oversampling | [below](#implemented-processing) | | `std.compressor` | Compressor | Dynamics | [below](#implemented-processing) | | `std.limiter` | Limiter | Dynamics | [below](#implemented-processing) | | `std.chorus` | Chorus | Modulation | [below](#implemented-processing) | | `std.reverb` | Reverb | Feedback-delay network | [below](#implemented-processing) | | `std.envelope` | Envelope | Gate-controlled gain stage | [below](#implemented-processing), [playable inspection](https://github.com/joshstovall/TuneToys/blob/main/docs/architecture/playable-inspection.md#graphical-editing) | | `std.poly` | Polyphonic instrument | Eight-voice note receiver, played from a `midi` cable | [performance and reuse](https://github.com/joshstovall/TuneToys/blob/main/docs/architecture/performance-reuse.md#owned-polyphonic-performance) | | `std.wavetable` | Wavetable | 1–8 frame table source with MIDI pitch | [MIDI, control and mixing](https://github.com/joshstovall/TuneToys/blob/main/docs/architecture/midi-and-control.md#wavetable-and-the-curve-editor) | | `std.math` | Math | Utility; 17 ops on audio or control lanes | [Math](https://docs.tune.toys/nodes/std.math/), [below](#signal-ports) | | `std.compare` | Compare | Utility; gate or trigger with hysteresis | [Compare](https://docs.tune.toys/nodes/std.compare/), [below](#signal-ports) | | `ctl.*` | Control nodes | Block-rate lanes and parameter sockets | [control lanes](https://docs.tune.toys/concepts/control-lanes/) | | `midi.*` | MIDI nodes | Sample-offset event processors and host ports | [MIDI cables](https://docs.tune.toys/concepts/midi-cables/) | | `std.mixer` | Mixer | Up to 32 durable channel strips with gain, pan, mute, solo and master | [MIDI, control and mixing](https://github.com/joshstovall/TuneToys/blob/main/docs/architecture/midi-and-control.md#stdmixer-and-the-mix-drawer) | | `std.combine` | Combine channels | Lane concatenation | [multichannel audio](https://docs.tune.toys/contributors/multichannel-audio/#mixing-and-routing) | | `std.select` | Select channels | Lane extraction | [multichannel audio](https://docs.tune.toys/contributors/multichannel-audio/#mixing-and-routing) | | `std.matrix` | Channel matrix | Explicit routes | [multichannel audio](https://docs.tune.toys/contributors/multichannel-audio/#mixing-and-routing) | Additional shared processors delivered in v0.0.12 and v0.0.17: | Nodes | Role | Contract and evidence | | --------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `std.tuner` | Pass-through audio with click-free mute; the shared face detects pitch and reads Toy or node tuning | [Tuner](https://docs.tune.toys/nodes/std.tuner/); runtime-web tuner and tuning fixtures | | `std.sampler`, `std.player` | Tuned MIDI sample voices and transport-aware audio-file playback | [Sampler](https://docs.tune.toys/nodes/std.sampler/), [Player](https://docs.tune.toys/nodes/std.player/); sample-asset conformance and runtime-web PCM fixtures | | `std.gate`, `std.transient`, `std.crossover`, `std.multiband` | Keyed dynamics, transient shaping, band splitting and multiband compression; Compressor also accepts a sidechain `key` | [Gate](https://docs.tune.toys/nodes/std.gate/); runtime-web dynamics parity/latency/partition fixtures | | `std.pitch`, `std.harmonizer`, `std.autotune` | Pitch shifting, three-part harmony and pitch correction using Toy tuning or MIDI targets | [Pitch correction](https://docs.tune.toys/nodes/std.autotune/); runtime-web pitch fixtures | | `std.vocoder`, `std.denoise`, `std.resonance`, `std.suppressor` | Carrier/key vocoding, noise reduction, spectral resonance reduction and feedback notches | [Vocoder](https://docs.tune.toys/nodes/std.vocoder/); shared FFT and runtime-web spectral fixtures | | `std.convolver` | Asset-backed partitioned FFT convolution, trims, predelay, gain and aligned dry/wet mix | [Convolver](https://docs.tune.toys/nodes/std.convolver/); allocation/tail/latency tests and native/WASM parity at every partition size | | `std.phaser`, `std.crush`, `std.granulator` | Allpass modulation, bit/sample-rate reduction and live-buffer granulation | [Granulator](https://docs.tune.toys/nodes/std.granulator/); runtime-web character fixtures | All processors operate on [1–32-channel bundles](https://docs.tune.toys/contributors/multichannel-audio/), subject to node-specific lane rules such as Crossover's band-major output. The foundation nodes keep version `0.1.0` fixed-stereo contracts and gained automatic widths in `0.2.0`. `std.oscillator` is the single Oscillator node: sine tones, other waveforms, morphing and optional MIDI pitch share one prepared kernel and one waveform face. Asset-backed nodes share ABI 3 `tt_attach_asset`: WAV parsing, resampling and storage admission happen before activation, away from rendering. Portable Toys carry declarations and hashes; hosts supply verified bytes. Convolution uses the shared FFT and a prepared partition of 64–1024 samples, which also determines its declared latency. Graph compensation aligns parallel paths, and the attached IR determines the convolver's tail. Lossy/FLAC source decoding, time stretching, formant shifting and asset-backed granulation remain outside this slice. The [cross-sprint verification record](https://github.com/joshstovall/TuneToys/blob/main/docs/delivery/sprints/verification-v0.0.12-v0.0.17.md) records host evidence and remaining listening/DAW/CPU qualification. ### Signal ports Math and Compare declare `signal` ports: audio or control, resolved per instance before lane solving. A connected `signal` input takes its source's type, an unconnected one is control, and the output is audio when any input is audio, else control. A MIDI, video or data source is a `PORT_TYPE` rejection, and definition boundaries cannot declare `signal`. `resolveSignalPorts` in `packages/node-catalog/src/channels.ts` and `resolve_signals` in `crates/compiler/src/channels.rs` apply the same rule, and the result lands in `Operator.output`. The engine builds a sample kernel (`primitives/math.rs`, `primitives/compare.rs`) for audio output and a block kernel (`control/`) for control output. Both call the op tables in `crates/engine/src/ops.rs`. Operands of the other rate are read lane by lane with broadcast. See [value nodes](https://github.com/joshstovall/TuneToys/blob/main/docs/architecture/value-nodes.md) for the shared op vocabulary and the data counterparts, and [sprint v0.0.7 §01](https://github.com/joshstovall/TuneToys/blob/main/docs/delivery/sprints/v0.0.7/01-value-model-and-port-types.md). ## Contracts and presentation [The node manifest](https://github.com/joshstovall/TuneToys/blob/main/packages/node-catalog/nodes.json) is the single numeric/presentation source for every admitted primitive: ports with their signal type and width, execution domain, derived host requirements, parameter domains, smoothing, discrete steps, choices and visibility. `tools/generate-node-manifest.mjs` generates the TypeScript catalog and the Rust contracts (domains, port lists, smoothing, discrete admission and named slot indices); root `npm run generate:check` rejects stale output. The version 0.3.0 noise contract remains hand-maintained in `noise.ts` and the compiler. | Bound | Value | | ------------------------------ | ----------------------------------------------------------------------------------------------------------------------- | | Operator control vector | Exactly the admitted domain length; per-operator prepared size, at most 288 slots | | Public controls per definition | 512 | | Controls per expanded program | 4,096 | | Discrete choices/counts | Integer linear ranges, no LFO, zero DSP smoothing, identical rounding at browser, MIDI and native automation boundaries | Public and nested aliases inherit these semantics. Patch, Properties and insertion previews use shared choices and conditional controls; authored Frame controls stay explicit. The shared graphical filter/envelope widgets bind every response-affecting control to the same processor, including Type, slope/compensation or curve/loop/retrigger/hold. Their analytic pictures are verified against actual PCM; they are not a second audio engine. ## Implemented processing | Node | Behavior | | --------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | Oscillator | Morph, Sine, Triangle, Saw, Pulse, Double saw, Organ and Harmonic bell. Morph evaluates only its two required neighbors; a named waveform evaluates its selected kernel. Saw/pulse use polynomial step correction; triangle uses its integrated corner correction, with no leaky integrator history. Harmonic shapes taper upper partials near Nyquist while retaining the fundamental. Phase offset, cents detune and a held/retriggering Gate share one node oscillator across identically controlled output lanes. This is not implicit unison or voice allocation. | | Resonant filter | A trapezoidal state-variable core supplies continuous LP/BP/HP Morph, named low-pass/band-pass/high-pass/notch/all-pass, and one or two cascaded stages. Q 0.5–12, cutoff capped at 0.45 × rate. Optional slope-aware resonance compensation applies to resonant LP/HP/Morph responses. All-pass changes phase with flat magnitude. Each audio lane owns its integrators. | | Parametric EQ | 1–32 item bands in canonical ID order, then an Output trim. Each band is an RBJ Audio-EQ-Cookbook biquad in transposed direct form II: 12 dB/oct low/high cut with Q as corner resonance, low/high shelf with Q as slope, bell, or notch, at 20 Hz–20 kHz capped at 0.49 × rate. Coefficients recompute only when a band's values change, on the shared 16-sample cadence. An off band is skipped, so a fully off EQ is bit-exact; a band switched on starts from rest, and band on/type changes pass through the bounded continuity correction. Each audio lane owns its band states. The node face draws the same coefficients (`face/eqMath.ts`), parity-tested against rendered PCM. | | Drive | Cubic soft clip, hard clip, asymmetric saturation and wavefolding. Drive, Bias, Tone, Mix and Output; a DC blocker removes persistent offsets and the shaper's zero-input offset is subtracted. The soft curve is monotonic with a continuous first derivative at its saturation endpoints; it avoids audio-rate transcendental calls. Quality selects 1×, 2× or 4× processing through prepared 17-tap half-band FIR stages. Polyphase/symmetric evaluation skips exact zero taps, zero-stuffed multiplies and discarded decimator outputs. Every quality has 12 samples of processing latency, with dry and wet paths aligned. Filter/DC transients can exceed the static shaper peak; there is no alias-producing post-filter hard clamp. Oversampling reduces residual aliasing rather than guaranteeing its absence. | | Delay | Clean, Filtered, Tape and Ducked echoes; signed feedback, wet ducking, musical divisions or milliseconds, and 10 ms read-head transitions. Tempo comes from the shared transport. Time is bounded by an eight-second prepared ceiling; longer beat divisions cap there. Independent feedback, a ring over all N channels, or explicit sparse routes in the node's Channels inspector section. Empty custom routes produce no feedback. Matrix row absolute gain sums must be at most one, and feedback is bounded to ±0.95. | | Chorus | Chorus, Ensemble and wet-only Vibrato. One to eight internal taps per audio channel, continuous channel spread, depth/rate/center delay, and feedback bounded to ±0.85. Internal tap count and cable width are separate dimensions. Prepared voice angles and cached spread weights share one modulation phase across the bundle; audio histories stay independent. | | Reverb | An eight-line feedback-delay network with Room, Hall and Plate-like delay tunings, Size, Diffusion, Modulation, damping, pre-delay and mix. A contractive Householder blend mixes the internal lines; channels retain separate networks, with no assumed stereo pairing or spatial layout. Shared delay-read positions and cached decay/damping coefficients avoid repeating that work per channel. Freeze removes excitation, suspends modulation/size movement and uses unity feedback; it reports an infinite tail while active. This is an algorithmic reverb, with no convolution assets or physical-plate claim. | | Compressor | Peak or 10 ms mean-square/RMS detector, optional detector-only high-pass, threshold/ratio/quadratic knee, attack/release, hold, makeup and adaptive release. Linking interpolates independent lane detectors toward the strongest detector across the whole bundle. Fully linked operation shares gain-reduction calculations and applies one common gain, preserving channel balance. | | Limiter | Immediate sample-peak attenuation, ceiling, hold, adaptive release and independent/full-bundle linking. Fully linked operation shares one attenuation state. No lookahead or intersample true-peak claim. | | Envelope | Real AD, AR and ADSR audio gain stages, concave/linear/convex polynomial segment curves, current-level/from-zero retrigger, held loops and loop hold time. Gate changes are sample-boundary controls. One-shot AD completes after Gate release; AR holds its peak; ADSR holds Sustain. Looping repeats complete cycles while Gate remains high. Voice processing reuses the same envelope core with its own state. | Exact ranges, defaults and help text live in the manifest. Mode changes use bounded continuity correction without rebuilding hosts or duplicating delay networks. Delay retiming has separate dual read heads. Noise keeps its own prepared equal-power transition. ## Preparation, latency and resource bounds `crates/engine/src/primitives/` separates the kernels from common bounded buffer, cache and transition helpers; the engine facade owns dispatch and accounting. Fixed FIR taps, voice angles, arrays and buffers are prepared before rendering. Coefficients for smoothed controls refresh only when changed, at a fixed 16-sample cadence shared across lanes and independent of callback partitioning. Detector, gate and envelope histories still advance sample by sample. **No callback allocates, locks, performs I/O, constructs a graph or calls UI APIs.** | Bound | Value | | ------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Delay capacity | Derived from literal values, public target ranges and modulation bounds; the eight-second ceiling covers tempo sync. A short literal echo does not reserve an eight-second loop | | Prepared storage profile | **128 MiB** of signal/DSP storage per engine, including signal history, network buffers, control planes and graph alignment (replaces the earlier 16 MiB development profile so a 32-channel, 96 kHz long delay fits) | | Oversized graphs | Rejected before allocation/activation; the last valid host generation stays active | | Drive latency | 12 samples at every quality | | Tail bound | Conservative −120 dB amplitude bounds following output ancestors, including feedback, damping, DC removal and serial processing; frozen reachable reverbs report infinity | The engine propagates intrinsic processing latency through the graph and prepares per-input compensation where paths rejoin. Musical echo, chorus and reverb delays are effect behavior, not compensation latency. Owner-thread ABI queries expose `tt_latency_samples` and `tt_prepared_storage_bytes`; the native Session/Processor reports prepared latency to JUCE on prepare, load and restore. No latency-dependent allocation or host call occurs inside Rust rendering. Native rendering publishes the current tail after controls/process; actual DAW tail and latency handling still needs host qualification. ## Verification and profiling `tests/conformance/dsp-upgrade-cases.mjs` is the owned shared mode corpus. Runtime fixtures verify every mode over multichannel bundles, integer source/alias boundaries, waveform phase/spectra, analytic filter response, drive DC and measured alias reduction, fixed dry latency, graph branch alignment, tempo echoes, odd-width feedback rings and custom matrices, envelope timing/curves/loops, RMS/hold dynamics and freeze tail semantics. Default/edge/native/WASM PCM and block-partition fixtures remain active. Allocation counting includes live selector changes. The linked C++ ABI consumes the same mode corpus; native Session tests cover control recall and host-facing latency. Browser regression exercises real playback and shared Frame controls. | Command | Covers | | ------------------------------------------- | ------------------------------------------------------------------------------------- | | `npm run check` | Required repository gate, including Rust and native ABI fixtures | | `npm run build:web`, `npm run build:player` | Browser and native builds with the shared engine | | `npm run test:dsp-ui -w @tunetoys/web` | Browser playback and shared Frame controls | | `npm run bench:dsp` | Every mode at 1/2/3/8/16/32 channels and 44.1/48/96 kHz, native release and Node WASM | The benchmark records a median of three runs after 128 warm-up blocks, preparation time, callback budget percentage, conservative storage and latency, plus hardware identity and the tested engine digest in `.cache/benchmarks/dsp.json`. Run it after building contracts and WASM. Local Apple M2 Pro evidence (2,052 native/WASM measurements): the largest median was the eight-voice Ensemble at 32 channels/96 kHz in Node WASM at 193 µs per 128 frames, or 14.5% of one callback budget. Four-times Drive measured about 133 µs (10%) after polyphase FIR and cubic-shaper refinement. The largest accounted single-node profile was about 12.4 MiB for the 32-channel FDN; long public-control delay ranges can reserve more, up to the admitted engine ceiling. These are software measurements on that host. ## Provenance Kernels, coefficients and fixtures are original TuneToys work. Sibling projects are behavior references, not build dependencies. Mathematical references: Andrew Simper's [trapezoidal SVF derivation](https://cytomic.com/files/dsp/SvfLinearTrapOptimised2.pdf), [Faust oscillator documentation](https://faustlibraries.grame.fr/libs/oscillators/) for polynomial step corrections, Esqueda/Välimäki/Bilbao's [BLAMP work](https://research.spa.aalto.fi/publications/papers/dafx16-blamp/) for corner correction, [JUCE's oversampling documentation](https://docs.juce.com/master/classjuce_1_1dsp_1_1Oversampling.html) for filtering/latency tradeoffs, and Julius O. Smith's [feedback-delay-network treatment](https://www.dsprelated.com/freebooks/pasp/Feedback_Delay_Networks_FDN.html). No implementation or compatibility claim for another product is imported. ## Implementation - [`crates/engine`](https://github.com/joshstovall/TuneToys/blob/main/crates/engine/README.md) — kernels in [`src/primitives`](https://github.com/joshstovall/TuneToys/blob/main/crates/engine/src/primitives/README.md), facade, tail and alignment - [`crates/compiler`](https://github.com/joshstovall/TuneToys/blob/main/crates/compiler/README.md) — admission and program building - [`packages/node-catalog`](https://github.com/joshstovall/TuneToys/blob/main/packages/node-catalog/README.md) — manifest, generated contracts and control metadata - [`packages/runtime-web/test`](https://github.com/joshstovall/TuneToys/blob/main/packages/runtime-web/test/README.md) and [`tests/conformance`](https://github.com/joshstovall/TuneToys/blob/main/tests/conformance/README.md) — fixtures ## Related - [Execution and optimization](https://docs.tune.toys/contributors/execution/) - [Streaming noise](https://github.com/joshstovall/TuneToys/blob/main/docs/architecture/noise.md), [multichannel audio](https://docs.tune.toys/contributors/multichannel-audio/), [polyphonic performance](https://github.com/joshstovall/TuneToys/blob/main/docs/architecture/performance-reuse.md) - [Signal types and domains](https://github.com/joshstovall/TuneToys/blob/main/docs/architecture/signal-types-and-domains.md), [MIDI, control and mixing](https://github.com/joshstovall/TuneToys/blob/main/docs/architecture/midi-and-control.md) - [Graphical editors and playable controls](https://github.com/joshstovall/TuneToys/blob/main/docs/architecture/playable-inspection.md) - [Implementation status](https://github.com/joshstovall/TuneToys/blob/main/docs/delivery/implementation-status.md) --- URL: https://docs.tune.toys/contributors/execution/ Title: Execution and optimization This document answers how one Rust processing core runs in a browser tab, a DAW plugin and an exported product, what the audio thread may and may not do, and how performance work is sequenced. **Status:** the Rust compiler/engine compile natively and to `wasm32`, the AudioWorklet host and the JUCE shell over the versioned C ABI are implemented, and native/WASM parity fixtures run in `npm run check`. Measured profiles exist for the [DSP processors](https://docs.tune.toys/contributors/dsp-primitives/#verification-and-profiling) and [noise](https://github.com/joshstovall/TuneToys/blob/main/docs/architecture/noise.md#cost-and-resource-bounds). Real-device, DAW underrun and cross-host equivalence qualification remain open; see [validation](https://github.com/joshstovall/TuneToys/blob/main/docs/delivery/validation.md). ## Rust, JUCE, and WASM One Rust processing core is compiled separately for native and `wasm32`, with target adapters around it. JUCE does not compile Rust source directly and a WASM binary does not become a VST. A native Rust static library exposes a versioned C ABI; a C++ JUCE processor owns it and supplies buses, parameters, events, transport, latency and DAW state. The browser loads a WASM build of the same processing semantics behind an AudioWorklet adapter. ```mermaid %%{init: {'theme': 'base', 'themeVariables': {'primaryColor': '#FAFAF7', 'primaryTextColor': '#1C1C1B', 'primaryBorderColor': '#1C1C1B', 'lineColor': '#64645F', 'fontFamily': 'DM Sans, Inter, system-ui, sans-serif'}}}%% flowchart LR SRC[("Toy source")] --> COMPILER["crates/compiler<br/>resolve · check · admit · program"] COMPILER --> ENGINE["crates/engine<br/>prepare · render"] ENGINE --> FFI["crates/engine-ffi<br/>versioned C ABI"] ENGINE --> WASM["wasm32 build"] FFI --> JUCE["native/plugin-shell<br/>buses · params · transport · state"] WASM --> WORKLET["packages/runtime-web<br/>AudioWorklet adapter"] JUCE --> PLAYER["Player · independent exports"] WORKLET --> TAB["Browser tab"] 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 SRC source class COMPILER,ENGINE,FFI,WASM shared class JUCE,WORKLET,PLAYER,TAB host ``` Rust documents [FFI and static-library integration](https://doc.rust-lang.org/nomicon/ffi.html), JUCE's [CMake API](https://github.com/juce-framework/JUCE/blob/master/docs/CMake%20API.md) describes plugin wrapper targets, and the [Web Audio specification](https://www.w3.org/TR/webaudio/) defines worklet execution. These enable the architecture; they do not prove Tune.Toys real-time performance or cross-target equivalence. Toolchains and the JUCE revision are pinned (`native/juce.lock.json`, `Cargo.lock`). The legacy tree used one C DSP/compiler implementation for native and WASM and specialized it for exported applets. Its algorithms, fixtures, graph-admission lessons and export evidence remain migration inputs, compared against the same fixtures as the Rust core. **Never maintain two independently evolving production engines.** If a measured profile ever requires a different backend, record a decision and keep it behind the same boundary. ## Prepare and render `source → resolve dependency closure → type/semantic checks → target admission → immutable program → prepare resources → activate generation → render` ```mermaid %%{init: {'theme': 'base', 'themeVariables': {'primaryColor': '#FAFAF7', 'primaryTextColor': '#1C1C1B', 'primaryBorderColor': '#1C1C1B', 'lineColor': '#64645F', 'actorBkg': '#DCEBDD', 'actorBorder': '#1C1C1B', 'signalColor': '#1C1C1B', 'noteBkgColor': '#DED8F0', 'noteBorderColor': '#1C1C1B', 'fontFamily': 'DM Sans, Inter, system-ui, sans-serif'}}}%% sequenceDiagram participant Main as Main / owner thread participant Audio as Audio thread Main->>Main: resolve, check, admit, build immutable program Main->>Main: prepare generation N+1 (allocate state, decode assets, routing, latency) Note over Main: allocation and I/O happen here only Main->>Audio: hand over complete generation at a block boundary Audio->>Audio: render N+1 from preallocated buffers Audio-->>Main: return ownership of generation N Main->>Main: reclaim generation N off the callback Note over Audio: a failed preparation leaves N running ``` Preparation allocates graph state, decodes assets, validates resource bounds, builds routing, initializes parameters and computes latency off the audio thread. Activation exchanges a complete generation at a block boundary. The previous generation is reclaimed off the callback after ownership returns. Failure keeps the last valid generation; tails, crossfade and latency behavior must be defined per host before hot replacement is supported. **The callback handles bounded preallocated audio buffers and timestamped events. It never performs I/O, compilation, asset decoding, JSON parsing, mutex waits, UI calls or unbounded logging.** Host adapters slice arbitrary host block sizes into admitted internal blocks and preserve sample offsets. UI repaint, camera tracking, network sync and analysis run at their own cadences; their bounded outputs enter the control/event path. | Rule | Contract | | -------------- | --------------------------------------------------------------------------------------------------------------- | | Sample format | `f32`, explicit sample-offset events, target-specific denormal/non-finite policy | | Smoothing | Part of the semantic contract, not a UI animation | | Floating point | No bit-identical results assumed across native/WASM/SIMD; tolerance and event-order rules are fixed in fixtures | | Feedback | Requires explicit delay boundaries; the current draft validator rejects all signal cycles | ## Sharing and state Immutable primitive code, dependency definitions, assets, compiled programs and cached artifacts are shared where valid. Each running instance owns its mutable parameters, voices, delay lines, random seeds, note ownership and observations. Browser previews, audition candidates and DAW tracks can never share mutable compiler/engine instances by accident. The compiler and engine crates share a small program representation; source JSON interpretation stays outside rendering. `no_std` compatibility is a design goal for the processing crate, not a reason to constrain the first application to MCU budgets. Firmware is deferred and no MCU target or profile is selected. ## Optimization plan Semantic execution identity, presentation identity and full release integrity are separate. Compiled/prepared artifacts are cached by source/dependency hashes, semantic version, engine build, target profile, feature set and toolchain. Canonicalization must be specified and tested before these hashes become release authority. - **Prune** DSP by reachability from outputs, explicitly retained side effects and required state/event roots. Frame visibility never decides audio liveness. Retain a source map so diagnostics name authored IDs after inlining. - **Specialize** builds to admitted primitives/services, statically import only used widget families, hash/deduplicate packaged assets and exclude the editor from published bundles. Benchmark that specialization preserves output/state behavior. - **Measure** native/WASM fixture parity, render CPU and deadline misses, memory per instance, preparation time, download size, UI cold/warm load, and open/closed editor cost. - **Add later, by evidence:** SIMD, multithreading, shared memory or code generation. Browser shared-memory optimizations bring deployment/isolation constraints and are not prerequisites for the first worklet host. ## Implementation - [`crates/compiler`](https://github.com/joshstovall/TuneToys/blob/main/crates/compiler/README.md), [`crates/engine`](https://github.com/joshstovall/TuneToys/blob/main/crates/engine/README.md), [`crates/engine-ffi`](https://github.com/joshstovall/TuneToys/blob/main/crates/engine-ffi/README.md) - [`packages/runtime-web`](https://github.com/joshstovall/TuneToys/blob/main/packages/runtime-web/README.md) — worklet, WASM loading, generation handoff - [`native/plugin-shell`](https://github.com/joshstovall/TuneToys/blob/main/native/plugin-shell/README.md) — JUCE processor over the C ABI - [`tests/conformance`](https://github.com/joshstovall/TuneToys/blob/main/tests/conformance/README.md) — shared native/WASM/browser fixtures ## Related - [Shared DSP processors and controls](https://docs.tune.toys/contributors/dsp-primitives/), [noise](https://github.com/joshstovall/TuneToys/blob/main/docs/architecture/noise.md), [multichannel audio](https://docs.tune.toys/contributors/multichannel-audio/) - [Running-instance protocol](https://github.com/joshstovall/TuneToys/blob/main/docs/specs/runtime-protocol.md) and [native Player core](https://github.com/joshstovall/TuneToys/blob/main/docs/specs/native-player.md) - [Decision 0002: shared engine](https://github.com/joshstovall/TuneToys/blob/main/docs/decisions/0002-shared-engine.md) - [Validation and support matrix](https://github.com/joshstovall/TuneToys/blob/main/docs/delivery/validation.md) --- URL: https://docs.tune.toys/contributors/frames/ Title: Frames and interface composition This document answers what a Frame is, how its controls bind to a running instance, which widget kinds exist, and what every host must do when it mounts one. A Frame is a declarative presentation of a definition in a running-instance context. It is the authored app interface, not a screenshot of the graph. The editor shows each Frame on the Patch as a card holding its components, which move and resize in place, where most Toys are designed, with a derived binding link from each control to the node parameter it drives ([patch editor](https://docs.tune.toys/contributors/patch-editor/)); runtime presentation reads an explicit widget list. **Status:** the shared React renderer, every widget kind below, nested and inline Frames, themes/styles, responsive projection and the host interface are implemented in [`packages/frame-runtime`](https://github.com/joshstovall/TuneToys/blob/main/packages/frame-runtime/README.md) and used by the browser editor, published playback and the native Player WebView. Node faces (`face`, `paint`, `curve`, `switcher`, `readout`) and live video Screens bind through optional host hooks; the browser implements them, and hosts without a video engine or authoring (the native Player WebView, exports) show honest unavailable states ([video](https://github.com/joshstovall/TuneToys/blob/main/docs/architecture/video.md#status-and-unavailable-states), [MIDI, control and mixing](https://github.com/joshstovall/TuneToys/blob/main/docs/architecture/midi-and-control.md#wavetable-and-the-curve-editor)). General event actions remain planned. Physical touch, screen-reader, embedded WebView and DAW qualification remain open. ## One renderer, every host The renderer is a React package with no dependency on the graph editor or account shell. Browser pages, a JUCE WebView and later Electron embed that same implementation. Framework-neutral binding/interaction contracts let another shell mount it without a second renderer. Legacy Svelte/React faces supply behavior and fixtures to port deliberately; **do not ship two independent implementations of the same production widget.** ```mermaid %%{init: {'theme': 'base', 'themeVariables': {'primaryColor': '#FAFAF7', 'primaryTextColor': '#1C1C1B', 'primaryBorderColor': '#1C1C1B', 'lineColor': '#64645F', 'fontFamily': 'DM Sans, Inter, system-ui, sans-serif'}}}%% flowchart LR subgraph TOY["Toy source"] DEF["Definition<br/>public parameters · nodes"] FRAME["Frame<br/>widgets · bounds · theme"] FRAME -- "binds parameter IDs of" --> DEF end subgraph HOST["Host (browser · Player · export)"] FH["FrameHost<br/>instance path · live values · gestures"] INST["Running instance<br/>root / stage-1 / voice-2"] FH <--> INST end RENDER["Shared renderer<br/>FrameView"] FRAME --> RENDER DEF --> RENDER RENDER <--> FH 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 DEF,FRAME source class RENDER shared class FH,INST host style TOY fill:#FAFAF7,stroke:#1C1C1B,color:#1C1C1B style HOST fill:#FAFAF7,stroke:#1C1C1B,color:#1C1C1B ``` ## Bindings and nesting Controls bind to stable public parameter IDs relative to the owning definition. At runtime the host supplies an instance path such as `root / first-stage / voice-2`, and a reused Frame resolves its relative bindings in that context. A child `frame` widget names a group node instance and a Frame belonging to that node's definition; an inline `frame` widget omits the node and references a Frame of the same definition (see [performance and reuse](https://github.com/joshstovall/TuneToys/blob/main/docs/architecture/performance-reuse.md#patch-lock-and-reuse)). **Reusing one definition twice yields two controls and independent state.** Frames have stable IDs, dimensions, explicit member/widget IDs, labels, bounds and an entry point. Loose interface components may also live in `Definition.widgets` at absolute Patch coordinates; explicit `Frame.widgets` membership selects the published interface. Graph-node canvas coordinates are editor metadata; moving an unrelated graph node cannot add it to a published Frame. Publishing includes the complete processing dependency closure even when most nodes are not visible. Frames use bounded reference geometry with optional portable anchors and limits; see [responsive Frames](https://github.com/joshstovall/TuneToys/blob/main/docs/architecture/responsive-frames.md). ## Widget kinds The schema admits these widget kinds. Numeric controls and graphical roles bind public IDs; observations use the shared host interface. | Kind | Binds | Notes | Detail | | -------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------ | --------------------------------------------------------------------------------- | -------------------------------------------------------------------------- | | `knob`, `slider` | One public numeric control | Rectangular slider with automatic/horizontal/vertical orientation | [appearance](https://github.com/joshstovall/TuneToys/blob/main/docs/architecture/appearance.md), [components](https://docs.tune.toys/contributors/interface-components/) | | `toggle`, `momentary`, `trigger` | One public numeric control | Button, checkbox and switch faces; trigger pulses 10–2000 ms | [playable controls](https://github.com/joshstovall/TuneToys/blob/main/docs/architecture/playable-inspection.md#playable-controls) | | `xy` | Two distinct public controls | XY pad or spring-return joystick face | [playable controls](https://github.com/joshstovall/TuneToys/blob/main/docs/architecture/playable-inspection.md#playable-controls) | | `segmented` | One public numeric control, 2–16 named values | Values must fit the effective domain and discrete step | [performance and reuse](https://github.com/joshstovall/TuneToys/blob/main/docs/architecture/performance-reuse.md#frame-widgets-and-tuning) | | `piano` | A `midi.keyboard` node path | Pointer/touch/keyboard notes become MIDI in the graph | [performance and reuse](https://github.com/joshstovall/TuneToys/blob/main/docs/architecture/performance-reuse.md#owned-polyphonic-performance) | | `filter`, `envelope` | Role-bound public controls of one processor | Analytic response/ADSR editors verified against PCM | [graphical editing](https://github.com/joshstovall/TuneToys/blob/main/docs/architecture/playable-inspection.md#graphical-editing) | | `face` | A face key, every role bound to a distinct public control of one node of its type; `node` for data faces | Any node face (dynamics, delay, oscillators, EQ, wavetable, tuner, curve, …) | [face kit](https://github.com/joshstovall/TuneToys/blob/main/packages/frame-runtime/src/face/README.md) | | `meter` | Engine observation | Per-lane signal meters, expandable beyond stereo | [multichannel audio](https://docs.tune.toys/contributors/multichannel-audio/) | | `scope` | Engine capture of one selected source | Oscilloscope, FFT, spectrogram, XY/phase and tuner modes | [components](https://docs.tune.toys/contributors/interface-components/) | | `screen` | A verified attached image or muted video, a built-in 3D scene, or a live video output (`source`, optional `stream` and preview `tracking`) | Codec availability is host-dependent | [components](https://docs.tune.toys/contributors/interface-components/) | | `paint` | A `video.paint` node's `video` output | Live strokes (colour, width, opacity, eraser, Clear); runtime state, never source | [frame-runtime](https://github.com/joshstovall/TuneToys/blob/main/packages/frame-runtime/src/README.md#node-faces) | | `curve` | A `std.wavetable` node's table frame | Pencil, line, smooth and preset shapes; one source commit per gesture | [frame-runtime](https://github.com/joshstovall/TuneToys/blob/main/packages/frame-runtime/src/README.md#node-faces) | | `switcher` | A `video.switch` node and its public program/preview/transition | Multiview tally, cue, Take; public controls stay the authority | [frame-runtime](https://github.com/joshstovall/TuneToys/blob/main/packages/frame-runtime/src/README.md#node-faces) | | `readout` | One public parameter or one control-output lane | Number, percent, integer or on/off text with a unit | [frame-runtime](https://github.com/joshstovall/TuneToys/blob/main/packages/frame-runtime/src/README.md#node-faces) | | `image` | A declared PNG/JPEG/WebP/GIF asset | Verified by length and SHA-256 before display | [performance and reuse](https://github.com/joshstovall/TuneToys/blob/main/docs/architecture/performance-reuse.md#frame-widgets-and-tuning) | | `text`, `icon` | Nothing | Static text; bundled icon vocabulary, no SVG markup | [icons](https://github.com/joshstovall/TuneToys/blob/main/docs/architecture/icons.md) | | `frame` | A group instance's Frame, or an inline Frame | Nested presentation, max sixteen levels | this page | Arbitrary HTML, CSS injection, JavaScript, remote iframes and executable widget bundles are not admitted by the draft schema. General event actions remain planned. ## Shared node faces Node cards, inspectors and Frame `face` widgets use the same [face kit](https://github.com/joshstovall/TuneToys/blob/main/packages/frame-runtime/src/face/README.md). Pure scene models produce SVG geometry and accessible handles; the shared gesture grammar freezes axes during a drag, commits once per gesture and supports keyboard edits, reset and cancellation. `FACE_BINDINGS` defines each face's roles and compatible processor, with generated Rust admission matching TypeScript. Data faces additionally name a node; a host with authoring can persist a data edit through `commitNodeData` as one Undo step. The Wavetable face includes waveform selection, **+ Add** (a copy of the selected waveform), **Remove** (keeping one) and the shared pencil/line/smooth/preset editor. Editing selection is local to each surface and independent of playback Position. Changes carry the whole 1–8-frame `data.table` through the existing live preview and atomic authoring bridge; rejected edits restore the previous table, and Undo/Redo supersedes an unfinished stroke. Fixed-frame `curve` widgets use the same sample editor. The portable theme tokens `screen`, `screenTrace` and `screenGlow` override the shared glass, trace and glow defaults. A single scheduler updates visible live layers outside React. Dense spectrum pixels use Canvas below the SVG; zoom and reduced motion limit refresh rates. The engine exposes one signal trace, so the active inspection or face claims it by priority. Polyphonic playheads read each engine voice’s stage, elapsed time and gain; Chorus dots read its actual modulation phase. These optional host observations never enter portable source or DAW state. Unsupported engines omit the moving marks. The Bitcrusher (`std.crush`, face `crush`) draws a quiet 1 kHz sine and its quantized, held output over 4 ms, including Mix. Two right-hand rails edit Bits and Rate through the shared gesture lifecycle. It is a nominal-clock example without jitter or dither, not a live signal trace. Its portable roles are `bits`, `rate_hz` and `mix`; the signed grid and sample holds are checked against shared-engine PCM. ## Interaction contract A control sends begin-gesture, parameter changes and end-gesture through the host interface. It displays authored default, live value, automation/modulation ownership and unavailable/read-only state distinctly. Repaint is suspended when hidden; input receipt and note release never depend on animation frames. Outbound observations are coalesced. **DSP never waits for rendering.** Pointer cancellation, lost focus, editor close, disposal and disconnected sources release held notes and momentary gates according to source ownership. A widget never releases another input source's notes. Keyboard navigation, accessible names, visible focus, value readouts, reduced-motion behavior, touch sizing and resize behavior belong in widget acceptance fixtures. A plugin embeds its UI resources for offline use. A hosted page gets only the Frame, the runtime adapter, the verified release and required assets. Host-level start, input-selection and error controls may surround it; creator branding and free-host branding are separate from the Frame's authored processing. ## Non-audio applications Camera, display, media and sensing nodes run as host and video domains beside the audio engine and reach Frames through live Screens, paint surfaces and lane readouts; their requirements are derived, and control production stays outside audio rendering ([signal types and domains](https://github.com/joshstovall/TuneToys/blob/main/docs/architecture/signal-types-and-domains.md), [video](https://github.com/joshstovall/TuneToys/blob/main/docs/architecture/video.md), [sensing](https://github.com/joshstovall/TuneToys/blob/main/docs/architecture/sensing.md)). The browser runs them; the native Player WebView and exports show the faces as unavailable, and a native host does not yet report unavailable domains before activation. General widgets and service actions, remote-system control with remote mode and connection status, and IoT services remain planned. ## Implementation - [`packages/frame-runtime`](https://github.com/joshstovall/TuneToys/blob/main/packages/frame-runtime/README.md) — `FrameView`, `ParameterControl`, theme resolution, responsive projection, icons - [`packages/runtime-protocol`](https://github.com/joshstovall/TuneToys/blob/main/packages/runtime-protocol/README.md) — the host interface Frames talk to - [`apps/web`](https://github.com/joshstovall/TuneToys/blob/main/apps/web/README.md) — Frame Design editor and published playback host - [`native/plugin-shell`](https://github.com/joshstovall/TuneToys/blob/main/native/plugin-shell/README.md) — embedded WebView host ## Related - [Appearance and Frame design](https://github.com/joshstovall/TuneToys/blob/main/docs/architecture/appearance.md), [responsive Frames](https://github.com/joshstovall/TuneToys/blob/main/docs/architecture/responsive-frames.md), [interface components](https://docs.tune.toys/contributors/interface-components/) - [Toy source format](https://docs.tune.toys/contributors/toy-format/) and [running-instance protocol](https://github.com/joshstovall/TuneToys/blob/main/docs/specs/runtime-protocol.md) - [Decision 0003: Frames and authoring](https://github.com/joshstovall/TuneToys/blob/main/docs/decisions/0003-frames-and-authoring.md) --- URL: https://docs.tune.toys/contributors/hosting/ Title: Container and single-server hosting How to build the immutable API/browser image, prepare a single Linux host, and what must pass before inviting users. The step-by-step promotion itself is in [releases](https://docs.tune.toys/contributors/releases/); the current hosted state is in the [AWS pilot record](https://github.com/joshstovall/TuneToys/blob/main/docs/delivery/aws-pilot.md). **Status:** Implemented foundation — the image and disposable container checks are implemented; the `tunetoys-pilot-live` CloudFormation stack in account `982471468586`, region `us-east-2`, is running its Lightsail instance with a pinned image. Launch gates below remain open. Fargate, RDS, CloudFront, a registry and always-on staging are deferred per the [lean Lightsail plan](https://github.com/joshstovall/TuneToys/blob/main/docs/architecture/aws.md). Operator context: the local CLI profile is `tunetoys` (temporary console-session credentials); use the `josh@song.study` Chrome profile for browser sign-in. This is the Just Ship It project selected for the `hello@bit.audio` AWS account. ## 1. Build an immutable artifact locally Docker is an explicit prerequisite; root checks do not start it. Build for the architecture of the selected server, not the developer laptop: ```sh npm run build:container -- --platform=linux/amd64 npm run test:container ``` - Linux amd64 is the default; `--platform=linux/arm64` is also admitted. Cross-architecture execution requires Docker emulation. The official Node multi-architecture index, PostgreSQL fixture image and public RDS trust bundle are pinned in [the image lock](https://github.com/joshstovall/TuneToys/blob/main/infra/container/base-images.json); the RDS bundle is available for a later deployment and is not the trust anchor for a private local PostgreSQL certificate. - The build prepares the shared Rust WASM on the build machine and copies those exact bytes into the image. It freezes admitted source files into a private temporary snapshot, checks hashes inside Docker, and runs the contract generation checks whose sources are in the snapshot and the production browser build there (the Rust-formatted node manifest and the Markdown-sourced node docs and `llms` files are checked by `npm run check` instead; see [the container README](https://github.com/joshstovall/TuneToys/blob/main/infra/container/README.md#build)). It never copies `.env`, local account databases, caches, private signing material or another repository. Runtime uses the production dependencies of the API and the listing-asset worker (`services/worker`, which the API imports at start and runs in-process only with `TUNETOYS_WORKER_INLINE=1`), user 1000 and a read-only-root compatible filesystem. Native macOS plugins build separately. - `.cache/containers/<input-sha>/<run-id>/build.json` records source/platform, engine digest, immutable image ID and snapshot location. The frozen context stays beside the receipt. `.cache/containers/latest.json` advances only if the checkout still matches after the build; otherwise the command exits nonzero and retains the exact receipt/image. Pass that receipt explicitly to `node tools/test-container.mjs /absolute/path/build.json` when intentionally qualifying an older snapshot. A latest receipt is local bookkeeping, not a release or deployment authorization. - The smoke runner uses disposable containers, unique networks and synthetic accounts. It checks that a hosted image refuses to start without PostgreSQL, then a non-root/read-only TLS PostgreSQL journey (the API no longer has a SQLite mode): trusted CA and hostname, rejection of incorrect trust/hostname/password, non-superuser role bootstrap, repeat setup, account/project/publication, exact engine bytes, library, fork, initial HTML and real PNG rendering. It limits API containers to 768 MiB and PostgreSQL to 384 MiB and deletes only what it created. With `--smoke` it then loads home, Explore, a template, the published Toy and its embed renderer in Playwright's pinned Chromium headless shell, read-only. **It does not qualify a shared 2 GiB host, live S3/SES, public TLS, audio playback in a browser or a DAW.** To transfer a reviewed image without a registry: `docker image save` the receipt's exact image ID, retain a SHA-256 digest of the archive beside the receipt, transfer through the operator's authenticated channel, verify the digest, `docker image load`, and compare `docker image inspect` identity, architecture and `toys.tune.*` labels with the receipt. Never deploy a floating tag. Retain the preceding archive/receipt for rollback. ## 2. Prepare the host Provisioning and installation are separate operator steps. Confirm account, region, owned hostname, administrator access, budget and recovery contacts first. 1. Use a maintained Linux image and the platform's matching Docker engine, PostgreSQL 17 and Caddy packages. Mount the encrypted PostgreSQL data disk before initializing it; root-disk containers are not database persistence. 2. Configure PostgreSQL on loopback only with SCRAM and a server certificate with a verified loopback identity (for `127.0.0.1`, include that IP in its SAN). Keep its private key PostgreSQL-owned and mode `0600`. Store the public issuing CA at `/etc/tunetoys/database-ca.pem`; the container receives only this public CA. Do not enable fixture plaintext or disable certificate/hostname verification. Restrict web/administrator ingress in Lightsail; neither 8787 nor 5432 is public. 3. Create an empty dedicated database and separate application/migration roles through [database bootstrap](https://github.com/joshstovall/TuneToys/blob/main/docs/delivery/database.md#role-bootstrap). Apply verified migrations before starting the API. Limit PostgreSQL connections/memory for the shared host; the API defaults to eight pooled connections. Benchmark the full server and backup workload before inviting users. 4. Create root-owned mode `0600` service files outside the checkout: | File | Contents | | --------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `/etc/tunetoys/image.env` | Only `TUNETOYS_IMAGE=sha256:<the loaded image ID>` from the reviewed receipt | | `/etc/tunetoys/api.env` | Application-role database fields, `TUNETOYS_DATABASE_SCHEMA=tunetoys`, `TUNETOYS_DATABASE_CA_FILE=/run/database-ca.pem`, `TUNETOYS_PUBLIC_ORIGIN=https://<owned hostname>`, stable native authorization key, private S3 bucket/region and narrowly scoped server AWS credentials. Only configured provider settings. No administrative/migration passwords. | | Protected bootstrap/migration env | Available only during schema promotion. Back up the native authorization key through the operator's protected secret recovery process. | Docker environment files contain literal single-line `NAME=value` entries; do not add shell quoting or source them. Keep multiline provider material in a supported escaped form only after provider qualification. Root `.env` is local development configuration; never copy it to the server. 5. Media policy: the service forces `TUNETOYS_MEDIA_UPLOAD_POLICY=invited`. Add only trusted creators' real account UUIDs to `TUNETOYS_MEDIA_UPLOADER_IDS` in the protected API environment, then restart. Empty grants nobody upload permission. Users can register, create/share graphs, browse and reuse approved public media without raw-upload access. The browser's upload dialog reports this capability, and every reservation/write/completion enforces it independently. Existing signed S3 forms can write only staging bytes until expiry; finalization still requires current access. See [pilot media access](https://github.com/joshstovall/TuneToys/blob/main/docs/specs/media-library.md#hosted-pilot-access). 6. Review [the service and Caddy source](https://github.com/joshstovall/TuneToys/blob/main/infra/lightsail/README.md) before installing. The service uses Linux host networking, binds the application to loopback, and the API refuses to start without its PostgreSQL configuration. Set Caddy's `TUNETOYS_HOST` to the owned hostname, validate its configuration, point DNS at the approved static IP, and let Caddy obtain its certificate. The reverse proxy preserves API status codes, authorization cookies and dynamic publication HTML; it has no cached-account or SPA-error rewrite. The request limiter sees the loopback proxy, so its address quotas apply across visitors; shared-host load and edge policy remain qualification items. ## 3. Promote and verify - Hold the exclusive `/srv/tunetoys/operator/backup-work/deployment.lock` throughout an image/schema/key promotion or rollback. The hourly backup uses the same lock and skips a locked run without emitting success. Preserve the lock's inode; removing/recreating the lock file can allow two operators to hold different locks. Confirm a fresh backup before promotion starts. - Retain the current image and verified database/blob/secret backups before migration. Run the separate migration role, then start/restart the API service and check `/v1/health` over loopback and public HTTPS. - Verify `/v1/media/upload-access` returns invited mode and denies a signed-in account outside the configured list; its upload reservation must return `403 MEDIA_UPLOAD_RESTRICTED`. An older image that lacks or ignores this policy cannot pass promotion. - Exercise sign-in, owned drafts, private release denial, anonymous public playback, exact engine/assets, Player authorization/sync and configured email delivery. No native plugin is built on this host. Automated promotion (v0.0.3, not yet installed on the pilot): [`promote.sh`](https://github.com/joshstovall/TuneToys/blob/main/infra/lightsail/README.md#automated-promotion) performs the steps above under the same lock with a compare-and-swap on `image.env`, receipt-label verification, a fresh confirmed backup, and automatic rollback of image-only releases. It refuses an image whose migration file set lacks any name the running image has (`downgrade-refused`) and allows `rollback` only with an unchanged set. Schema releases run the candidate's `tools/bootstrap-database.mjs` with the protected `bootstrap.env` (migration role and grant reconciliation) and are always rehearsed first with `tunetoys-promote rehearse` against a clone restored from the latest backup. The owner installs it with `infra/lightsail/install-promotion.sh` and the [owner checklist](https://github.com/joshstovall/TuneToys/blob/main/infra/lightsail/README.md#owner-checklist); until then the manual flow is the only path. The exact sequence, version identity rules and rollback limits are in [releases](https://docs.tune.toys/contributors/releases/). PostgreSQL requires an exact migration ledger: even additive schema migrations need a tested recovery path because an older image cannot restart against the new ledger. No push, build or check automatically deploys production. > **Pending:** the pilot database still carries the retired 001–017 migration history. It adopts `0000_baseline.sql` on the next schema promotion (`tunetoys-promote promote --kind schema`); until then do not promote an image that cannot adopt, and expect rollback to a pre-baseline image to be refused afterward. See [adopting the baseline](https://github.com/joshstovall/TuneToys/blob/main/docs/delivery/database.md#adopting-the-baseline). ## 4. Recovery Back up PostgreSQL consistently with `pg_dump`, preserve role definitions separately, and retain versioned private S3 bytes plus the native key. An upload of an archive is not a restore qualification. Rehearse a replacement host with a restored database, media, key and pinned image; verify existing accounts, releases and Player grants before declaring recovery ready. Database rollback requires an explicitly rehearsed compatible backup/migration path; changing an image alone cannot undo a schema/data change. ## Documentation site `docs.tune.toys` is a static site built from the repository by [`apps/docs`](https://github.com/joshstovall/TuneToys/blob/main/apps/docs/README.md) (`npm run build:docs`, validated by `npm run check:docs`; neither is part of root `npm run check`). It needs no credentials and no database. **Status:** Live at [docs.tune.toys](https://docs.tune.toys/) on the existing pilot as of November 2, 2026, with 226 pages for app `0.1.1`; public DNS and verified HTTPS pass. The installer and block pass `npm run test:docs-install` on Ubuntu 24.04 with Ubuntu's Caddy 2.6.2; all 49 docs tests pass. See the [deployment record](https://github.com/joshstovall/TuneToys/blob/main/docs/delivery/aws-pilot.md). Cost boundary: docs reuse the existing Caddy process, static IPv4, disk and included Lightsail transfer; build and package locally. There are no new AWS resources or fixed hosting charges. Snapshot storage and transfer above the bundle allowance remain usage charges; retain only needed release directories on the shared data disk. Path split on one hostname ([site block](https://github.com/joshstovall/TuneToys/blob/main/infra/lightsail/docs.caddy)): - `/@*` (published creator documents and storefront pages) and the shared asset paths those pages use (`/v1/*`, `/assets/*`, `/previews/*`, `/social/*`) are proxied to the loopback API. The API renders them with the shared Markdown renderer so edits and takedowns apply immediately and are never frozen into a static build. - Everything else is served from `/srv/tunetoys/docs/current`. `file_server` redirects `/nodes/std.delay` to `/nodes/std.delay/`; unknown paths return the generated `404.html`. - Cache: HTML and directory indexes `max-age=300, must-revalidate`; shared assets, search index and feeds one hour; `/v/*` snapshots one day. Publish (the operator runs these; nothing deploys automatically): 1. **Once:** in GoDaddy DNS add `docs` → `A` → `3.20.14.74` (the pilot's static IP `tunetoys-pilot-ip`), TTL 600. Caddy obtains the certificate on its own once the name resolves to the host. 2. On the release commit, with built contracts (`npm run build:contracts`): `npm run package:docs`. It builds strictly (any docs check problem stops it) and writes `.cache/docs/tunetoys-docs-<app-version>-<content hash>.tar.gz` and its `.sha256`. `apps/docs/package.json` `version` is the app version the docs describe; bump it with the release. 3. Copy both files to the host through the operator's channel, then, in that folder: `sha256sum -c tunetoys-docs-….tar.gz.sha256 && tar -xzf tunetoys-docs-….tar.gz && sudo ./tunetoys-docs-…/install-docs.sh`. The installer places an immutable `/srv/tunetoys/docs/releases/<bundle>/`, switches `/srv/tunetoys/docs/current` atomically, installs [`docs.caddy`](https://github.com/joshstovall/TuneToys/blob/main/infra/lightsail/docs.caddy) as `/etc/caddy/docs.caddy`, adds `import /etc/caddy/docs.caddy` to `/etc/caddy/Caddyfile` once, runs `caddy validate` and reloads. If Caddy rejects the configuration it restores both files and Caddy keeps serving its running configuration. It reports whether `https://docs.tune.toys/` answers on the host. 4. Rollback: `sudo ./install-docs.sh --rollback` from any bundle repoints `current` at the release that was live before. It is independent of database rollback. Versioning: each build contains the current docs at `/` and an immutable copy at `/v/<app-version>/`. A new build keeps earlier `/v/<version>/` directories that exist in the previous `dist` (copy the previous release's `v/` folder into the new `dist` before archiving, or build in place). `/versions.json` holds the `current` pointer and the version list; snapshots show a banner when they are no longer current. `npm run package:docs` builds into a fresh folder and so carries only the current version's snapshot; before the second app version ships, seed it with the previous release's `v/` (planned). ## Launch gates Before invitations: qualify the implemented upload-access policy and hosted limits/failures; configure live identity/email; verify backup restoration; install failure/spend alerts; review rights/legal/security; run the [browser/DAW validation gate](https://github.com/joshstovall/TuneToys/blob/main/docs/delivery/validation.md). Untrusted public uploads require isolated decoder/ingestion qualification. **These gates remain open; the container is a deployable foundation, not a claim of a launched service.** ## Related - [AWS pilot record](https://github.com/joshstovall/TuneToys/blob/main/docs/delivery/aws-pilot.md) - [Database runbook](https://github.com/joshstovall/TuneToys/blob/main/docs/delivery/database.md) - [Release process](https://docs.tune.toys/contributors/releases/) - [Infrastructure definitions](https://github.com/joshstovall/TuneToys/blob/main/infra/README.md) - [Trust and service boundaries](https://docs.tune.toys/contributors/security/) --- URL: https://docs.tune.toys/contributors/interface-components/ Title: Compact nodes and shared interface components This document answers how Patch nodes are sized, how interface components are placed in Patch and Frame Design from one palette, which control faces and displays exist, and the capture/media bounds behind Scope and Screen. **Status:** implemented in the browser editor and shared renderer with shared source conformance across browser/WASM/native. Browser fixtures qualify local software interactions, not external WebViews, codecs or DAWs. Native UI signal capture is unqualified. Live video Screens and the paint, curve, switcher and readout faces run in the browser; native hosts show their unavailable states ([video](https://github.com/joshstovall/TuneToys/blob/main/docs/architecture/video.md)). Arbitrary rendering scripts and imported 3D models remain planned. Components may sit anywhere on the Patch. An empty-space palette drop creates a loose `Definition.widgets` entry at the pointer; dropping inside a Frame creates a `Frame.widgets` member. Dragging into or out of a Frame transfers ownership with converted coordinates in one Undo step. Both use the same shared renderer and Properties fields. Loose components persist through save/reload, support negative positions and do not appear in public playback until explicitly placed in a Frame. ## Node sizing Patch defaults to 190px audio nodes, with undoable 140–640px instance widths. Drag the right edge, or focus its resize handle and use arrows (10px; Shift 1px). Visual component cards resize in both axes from their corner. Pointer and held-key gestures preview locally, commit once, and cancel on Escape, blur or source replacement. Parameter rows keep exact numeric editing without embedded sliders; full parameter sliders stay in Properties. ## One palette, two surfaces Patch → Interface and Frame Design use the same component palette. **Components are explicit members of a Frame belonging to the current definition**; the first insertion creates that Frame when necessary. Patch positions use namespaced editor metadata keyed by Frame/widget identities. Existing Frame members can be placed from the palette's Existing components group. Frame-only members stay out of Patch until placed; closing a Patch card hides that view while retaining its Frame member. Changing a card's size edits its portable widget bounds. `WidgetView` projects the same member through the shared Frame board, including instance-relative public bindings, themes and asset receipts. Components do not pretend to be DSP nodes or acquire signal ports. The card's pencil opens its owning Frame for binding and appearance editing. Hidden Frame members stay hidden in Patch. ## Controls and displays | Component | Binding | Notes | | -------------------------------------------------------------- | ----------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Knob | One public numeric control | See [knob feedback](https://github.com/joshstovall/TuneToys/blob/main/docs/architecture/appearance.md#editor-preferences-and-controls) | | Slider | One public numeric control | Rectangular fill/bar; automatic, horizontal or vertical orientation | | Button, momentary button, trigger pad | One public numeric control | See [playable controls](https://github.com/joshstovall/TuneToys/blob/main/docs/architecture/playable-inspection.md#playable-controls) | | Checkbox, Switch | One public numeric control | Toggle appearances | | Switches | One public numeric control | Named segmented selector; values stay public numeric bindings | | XY pad, Joystick | Two distinct public controls | Joystick defaults to spring return at the normalized center, including logarithmic domains; Properties change its face and spring behavior | | Scope | One actual input/output/node source | Modes: oscilloscope, Hann FFT, rolling spectrogram, synchronized XY/phase and tuner, with channel choice and Freeze/Resume. The authored default mode is portable; changing a live mode does not edit the sound | | Screen | A verified attached asset or built-in scene | Verified image, verified muted video with play/pause/seek, or a built-in interactive 3D cube/pyramid on canvas (drag/arrows orbit, +/- zoom, Home resets). Media mode, asset, fit and scene are authored in Properties; video decoding depends on host codecs | | Live Screen | A video output of the definition | `mode: 'live'` with `source` and an optional `stream`; the host's video engine paints the canvas. Without one: "Video needs a host with a video engine". See [video](https://github.com/joshstovall/TuneToys/blob/main/docs/architecture/video.md#screens) | | Paint, Curve, Switcher, Readout | A `video.paint`, `std.wavetable` or `video.switch` node, or one parameter or control lane | Node faces; see [Frames](https://docs.tune.toys/contributors/frames/#widget-kinds), [video](https://github.com/joshstovall/TuneToys/blob/main/docs/architecture/video.md#screens) and [MIDI, control and mixing](https://github.com/joshstovall/TuneToys/blob/main/docs/architecture/midi-and-control.md#wavetable-and-the-curve-editor) | | Piano, filter/ADSR editors, text, icons, panels, nested Frames | As documented elsewhere | Distinct vector icons identify each face | Checkbox/Switch are toggle appearances and Joystick is an XY appearance; they reuse existing numeric transactions, ownership, cancellation and MIDI/automation behavior and introduce no event engine. Screen and Scope are portable widget kinds admitted by the generated schema with shared browser/native/WASM semantics. Clipboard and reusable-component closure, asset remapping and node/asset deletion include their references. Scope has an audio inlet on the Patch, accepting one 1–32-channel bundle. A cable writes the component's `source` and exact `port`; it does not add a DSP processor or alter audio routing. Properties offers the same audio-only endpoints. New Scopes start disconnected; Delete, the cut tool or **Disconnect audio** clears the binding, and Undo restores it. Scope mode fills the component's current width and height; XY retains equal axis scaling. Compact Scopes show no text beneath the plot. ## Capture and media bounds Engine capture remains one selected source at a time and retains two synchronized 2048-sample lanes: the selected channel and the next channel, wrapping to channel zero (mono pairs with itself). Selection resets both rings, and storage counts toward preparation admission. The additive `tt_trace_secondary_value` ABI accessor exposes the paired lane. Sampling is allocation-free; the worklet serializes bounded arrays in its observation handler outside `process()`. Older pinned engines show paired-capture unavailability. Live inspectors share the most recent lease and restore earlier leases on close; this is not an unlimited simultaneous capture engine. See [signal inspection](https://github.com/joshstovall/TuneToys/blob/main/docs/architecture/playable-inspection.md#signal-inspection). | Bound | Value | | ------------------- | ------------------------------------------------------------------------- | | Spectrogram history | 1024 × 512 UI canvas; FFT and drawing run on the UI thread | | Image | 16 MiB encoded | | Video (MP4/WebM) | 16 MiB | | Decoded dimensions | 8192 per axis and 16 megapixels | | Verification | Byte length, SHA-256, declared type and decoded dimensions before display | Freeze retains the actual snapshot. Blob URLs are reference counted and released on replacement/teardown. Videos pause on hidden pages and teardown. No credentials, local URLs or device bindings enter portable source. The Slider's rectangular fill/bar and aspect-driven orientation were behavior references from the sibling Hotwire `ControlFace.svelte`. The React implementation and fixtures are original; no source or sibling build dependency was imported. ## Verification Shared source conformance covers each Scope mode, control appearance, slider orientation and Screen asset admission across browser/WASM/native. Runtime signal fixtures check synchronized ring chronology, resets, isolation and invalid indices against actual WASM. Palette and clipboard tests exercise admitted members, independent identities, assets and atomic Undo (`apps/web/test/frame-palette.test.mjs`, `apps/web/test/interface-components.browser.mjs`). Root `npm run check` remains required. ## Implementation - [`packages/frame-runtime`](https://github.com/joshstovall/TuneToys/blob/main/packages/frame-runtime/README.md) — control faces, Scope, Screen, `WidgetView` - [`apps/web`](https://github.com/joshstovall/TuneToys/blob/main/apps/web/README.md) — component palette, Patch cards, Properties - [`crates/engine`](https://github.com/joshstovall/TuneToys/blob/main/crates/engine/README.md) and [`crates/engine-ffi`](https://github.com/joshstovall/TuneToys/blob/main/crates/engine-ffi/README.md) — paired capture rings and `tt_trace_secondary_value` ## Related - [Frames](https://docs.tune.toys/contributors/frames/) and [appearance](https://github.com/joshstovall/TuneToys/blob/main/docs/architecture/appearance.md) - [Playable inspection](https://github.com/joshstovall/TuneToys/blob/main/docs/architecture/playable-inspection.md) and [performance widgets](https://github.com/joshstovall/TuneToys/blob/main/docs/architecture/performance-reuse.md#frame-widgets-and-tuning) - [Patch editor](https://docs.tune.toys/contributors/patch-editor/) and [app shell palettes](https://github.com/joshstovall/TuneToys/blob/main/docs/architecture/settings-library.md) --- URL: https://docs.tune.toys/contributors/multichannel-audio/ Title: Multichannel audio bundles This document answers how an audio cable carries 1 to 32 lanes, how widths are inferred through nested definitions, which routers exist, and how each host maps logical lanes to real buses. **Status:** bounded 1–32-channel source inference, shared Rust processing, bundle routing, observations and host adapters are implemented; every [DSP processor](https://docs.tune.toys/contributors/dsp-primitives/) builds on them. Surround speaker layouts, ambisonic processing, hardware configurations and DAWs are not qualified. ## Portable contracts and inference An audio port has a fixed `channels` count from 1 to 32 or `"auto"`, plus optional ordered `labels`. Labels constrain the width to their length and describe lanes; they do not imply a speaker layout. Logical channels and device assignments stay separate, and no device identifier is stored in a Toy or session. Primitive version `0.1.0` retains the four fixed-stereo foundation contracts and algorithms. Version `0.2.0` supports automatic widths through the same gain, low-pass, delay and oscillator algorithms. Legacy Toys are not rewritten on load; changing a legacy node's channel fields in the inspector upgrades that node to 0.2.0, keeping its two channels, in one operation with Undo. ```mermaid %%{init: {'theme': 'base', 'themeVariables': {'primaryColor': '#FAFAF7', 'primaryTextColor': '#1C1C1B', 'primaryBorderColor': '#1C1C1B', 'lineColor': '#64645F', 'fontFamily': 'DM Sans, Inter, system-ui, sans-serif'}}}%% flowchart LR IN["$in<br/>channels: 2"] --> G["Group instance A<br/>ports: auto"] IN2["Oscillator<br/>channels: 6"] --> G2["Group instance B<br/>same definition, own variables"] G --> OUT["$out<br/>channels: auto → 2"] G2 --> OUT2["$out<br/>channels: auto → 6"] SOLVE["Solver<br/>fixed/equality · concatenation · max-width<br/>runs before preparation"] SOLVE -. "resolves" .-> G SOLVE -. "resolves" .-> G2 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 IN,IN2 source class G,G2,SOLVE shared class OUT,OUT2 host ``` `packages/node-catalog/src/channels.ts` and `crates/compiler/src/channels.rs` resolve fixed/equality, concatenation and maximum-width constraints before preparation. **Each nested definition instance receives separate variables**, so one definition can run at different widths. Fixed ports constrain inferred ports in either direction. Derived widths resolve before otherwise unconstrained lanes default to mono. Sources default to mono in version 0.2; a declared count prepares independent generator states. Public numeric controls currently apply to every lane; per-channel automation arrays remain planned. The same solver resolves `video` streams and `control` lanes, with shared `SELECTORS` and `COMBINERS` per type ([signal types and domains](https://github.com/joshstovall/TuneToys/blob/main/docs/architecture/signal-types-and-domains.md#lanes-and-the-solver)); control and MIDI cables are described in [MIDI, control and mixing](https://github.com/joshstovall/TuneToys/blob/main/docs/architecture/midi-and-control.md). | Input adaptation policy | Behavior | | ----------------------- | -------------------------------- | | `strict` (default) | Requires matching widths | | `broadcast` | Expands only mono inputs | | `pad` | Fills missing lanes with silence | | `repeat` | Cycles narrower bundles | Primitive `audio` configuration declares an optional output width and one of these policies. **No policy reduces a wider input**; reduction or selection requires an explicit router. Connections retain one driver per inlet; the Mixer exposes intentional fan-in. Inference and schema checks cover unused definitions as well as active instances. A malformed label count, incompatible fixed constraint, unsupported legacy configuration, out-of-range selector or matrix route is rejected. The existing source byte/depth/expansion budgets still apply. The 32-channel limit is an execution-profile admission bound, not a special stereo/surround format. ## Mixing and routing All four routers use version 0.2.0: | Primitive | Behavior | | -------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `std.mixer` — Mixer | Up to 32 channels, each with an ID-scoped audio inlet, dB gain, pan, mute and solo. Broadcast adaptation infers the widest input unless an output width is configured. Reorder is presentation only. | | `std.combine` — Combine channels | Concatenates inputs A and B in that order. No summing. The output count is their combined width. | | `std.select` — Select channels | Extracts a contiguous range beginning at the one-based `audio.firstChannel`, with `audio.channels` outputs (default one). Multiple selectors split a bundle. | | `std.matrix` — Channel matrix | A fixed output width and up to 256 explicit `audio.routes`, each with one-based `from`, `to` and gain −8…8. Routes add without normalization; omitted destinations are silent. An empty route list is silence. | The Channels section of a node's Properties edits output counts, adaptation policies, selection ranges, matrix and Delay feedback routes, and, on Audio input and Audio output, port widths and channel names. It appears only on nodes whose width can be configured: combiners derive theirs, and converters, MIDI, data and bus nodes have none. Each change is one Undo step; a combination the solver rejects stays as a draft beside its error until another field makes it valid or it is reverted. Ports and cables show inferred widths for the selected nested instance. Changes use the existing source validator, authoring history and renderer; no second writable graph exists. Node discovery and auditions use the same contracts, and temporary audition input adapters are real admitted matrix nodes. ## Prepared engine and ABI Compiled Program 2 carries explicit input/output counts, operator widths, input bus widths, adaptation policies and prepared routes. Program 1 remains accepted with stereo defaults for older serialized programs. Input arity and numeric vectors are sized per operator during preparation. Fixed members and canonical ID-ordered repeat members share a generated maximum of 72 input slots and 288 parameter slots. Shared generated DSP contracts provide discrete selectors and control metadata. The engine allocates channel-sized scratch, filter history, delay history, generator phases and observation storage during preparation and checks channel-weighted buffers against the 128 MiB profile. Processing uses prepared lane maps, bounded loops and fixed stack scratch; it does not infer widths, allocate, lock or perform I/O. A changed topology is a replacement prepared generation, and existing host candidate failure handling retains the active program. ABI 1 gains additive `tt_input_channels`, `tt_output_channels`, `tt_signal_channels` and `tt_meter_channel` queries. Buffer views contain interleaved `max_frames × channels` floats. Legacy meter fields keep their original meanings; the new channel queries expose every lane. Frame meters render the supplied lane arrays through the shared renderer, with an expandable, bounded list for bundles wider than stereo. Tail preparation includes mixer/matrix gain bounds and all relevant input paths. ## Host mappings | Host | Input | Output / monitoring | | --------------- | ----------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Browser worklet | Discrete source channels; Audio settings choose ordered zero-padding or repetition | The worklet preserves logical lanes. A browser-local device mixer applies explicit discrete routing, alternating-lane stereo fold, first pair or mono average through native Web Audio nodes; per-device strips add gain/mute/solo/polarity and lane overrides. Offline WAV rendering keeps the logical output count | | JUCE adapter | 0–32 bus channels; missing lanes are zero; a mono logical input averages supplied host channels | 1–32 bus channels; matching widths map directly; differing counts fold lanes cyclically, averaging contributors per destination; mono duplicates for two-channel monitoring | The browser preserves the engine's logical bundle. Legacy programs keep the former mono-input repetition default. Routing policies, device bindings and strip identities/levels are browser-local host preferences, not Toy fields. See the [device mixer](https://github.com/joshstovall/TuneToys/blob/main/docs/architecture/audio-device-mixer.md). Native snapshot metadata describes the mappings and actual host counts and is explicitly excluded from serialized DAW state. The default product bus remains stereo. Fixtures qualify software behavior; physical devices, channel-layout conventions and actual DAW configurations require separate qualification. ## Evidence and references The shared channel corpus (`tests/conformance/channel-cases.mjs`) checks browser, native and WASM admission, including repeated generic definitions at different widths. Runtime fixtures (`packages/runtime-web/test/multichannel.test.mjs`) exercise 1/2/3/5/8/16/32 lanes, per-lane telemetry, independent instances, explicit adaptation and routing, memory rejection, native/WASM PCM comparisons and block partitioning at 44.1/48/96 kHz. Rust and native host allocation fixtures include multichannel callbacks. Browser checks cover channel editing, inference, Undo, matrix configuration and monitor controls. Root `npm run check` remains required. The design uses Cycling '74's documented [MC bundles and inlet summing](https://docs.cycling74.com/userguide/mc/mc_patchcords/), [wrapper expansion](https://docs.cycling74.com/userguide/mc/mc_wrapper/), [replication policies](https://docs.cycling74.com/reference/mc.%2A~/), [combining](https://docs.cycling74.com/reference/mc.combine~/) and [mixdown](https://docs.cycling74.com/reference/mc.mixdown~/) as behavioral references. Implementation and fixtures are original TuneToys code; no Max, Hotwire or synth.ist source or sibling build dependency was added. Explicit speaker-layout matrices, multichannel spatial reverbs, per-lane automation/voice allocation, floating channel counts inside an active generation, external audio buses (side-chains, media and recorder audio) and MIDI 2.0 remain planned. ## Implementation - [`packages/node-catalog`](https://github.com/joshstovall/TuneToys/blob/main/packages/node-catalog/README.md) — `src/channels.ts` - [`crates/compiler`](https://github.com/joshstovall/TuneToys/blob/main/crates/compiler/README.md) — `src/channels.rs`; [`crates/engine`](https://github.com/joshstovall/TuneToys/blob/main/crates/engine/README.md); [`crates/engine-ffi`](https://github.com/joshstovall/TuneToys/blob/main/crates/engine-ffi/README.md) - [`packages/runtime-web`](https://github.com/joshstovall/TuneToys/blob/main/packages/runtime-web/README.md) and [`native/plugin-shell`](https://github.com/joshstovall/TuneToys/blob/main/native/plugin-shell/README.md) — host mappings ## Related - [Shared DSP primitives](https://docs.tune.toys/contributors/dsp-primitives/) and [execution](https://docs.tune.toys/contributors/execution/) - [Signal types and domains](https://github.com/joshstovall/TuneToys/blob/main/docs/architecture/signal-types-and-domains.md) - [Toy source format](https://docs.tune.toys/contributors/toy-format/) - [Native Player core](https://github.com/joshstovall/TuneToys/blob/main/docs/specs/native-player.md) --- URL: https://docs.tune.toys/contributors/mvp/ Title: MVP scope This document defines the first complete release: the journey that must work end to end, what each capability delivers at each stage, and the acceptance criteria that decide whether it is done. The public MVP is gates P1–P3 in the [roadmap](https://github.com/joshstovall/TuneToys/blob/main/docs/delivery/roadmap.md). No gate is claimed complete; see [implementation status](https://github.com/joshstovall/TuneToys/blob/main/docs/delivery/implementation-status.md). The user has additionally authorized the [full collaborative community platform](https://docs.tune.toys/contributors/community/). That active objective extends the initial release scope below; social APIs alone do not complete the requested editor, cursor, collaboration and community journeys. ## First complete release A creator opens a template in the web editor, edits typed nodes, nests a reusable group, exposes stable controls, designs a Frame, saves, publishes and shares a playable link. A second user plays and forks it, bookmarks, tags and rates it, signs into TuneToys Player, loads the cached release in a DAW, automates controls and recalls the session offline. An agent can propose a graph, group or Frame change through the same revision-checked authoring API, explain the diff, validate it and prepare an audition. Acceptance and publication are separate actions. The app works without the agent service. The initial reference is a useful stereo effect with live input and packaged test audio. Gain is the foundation's schema example; the product reference adds a musically useful filter/delay once primitive fixtures pass. A small instrument is the next reference so the API never assumes every Toy has audio input. ## Scope by delivery stage | Capability | Foundation delivered here | First product MVP | Following release | | ----------------------------------- | ------------------------------------- | ------------------------------------------------------------------- | ------------------------------------------- | | Portable source | Draft schema/types/validator/examples | Versioned authored model and migrations | Broader domain models | | Node/group editing and Frame design | Ownership/specification | Functional web editor, nested groups, explicit membership | Richer widgets and domains | | Agent authoring | Operations/API design | In-app prompt for bounded proposals and review | Broader tools and providers | | Browser playback | Adapter/engine plan | Shared DSP with live input and packaged audio | More admitted primitives and browsers | | Account and sharing | Entity/visibility design | Public/private/unlisted, fork, bookmarks, personal tags, ratings | Teams, collections, advanced discovery | | TuneToys Player VST3 | Shared native plan | Account library sync, local cache, automation, offline recall | Additional DAWs/platforms and AU | | Independent Toy plugin export | Identity and release design | Feasibility proof required; public automated export follows gate P4 | Managed builds, signing, publisher delivery | | Hosting | Static publication plan | Share links, immutable revision URLs, small managed hosting | Custom domains, commercial hosting tiers | | Commerce | Terms and optional billing foundation | Free releases initially; optional billing foundation | Paid Toys and publisher tooling | | Remote control | Protocol/grant design | No public remote-control service | Authenticated relay and native host pairing | | Desktop/mobile/extension/firmware | Reserved target boundaries | Excluded | Desktop first; others by evidence | Standalone plugin export remains a core product outcome with an early feasibility spike and a separate public release gate; it is not removed from the platform plan. ## Acceptance criteria - Another person creates, shares, forks and plays a Toy without editing JSON by hand or needing the original developer's machine. - A nested definition instantiated twice has independent control values and processing state. Its reusable Frame binds to the selected instance. - Public parameter IDs survive rename, reorder and UI changes. Agent proposals reject a stale revision and can be undone after acceptance. - A recipient can access a public or unlisted web release without signing in. Private content and its derived assets remain authorized end to end. Forking follows source and remix permissions. - Bookmarks, tags and ratings appear in the Player library after authenticated sync; a bookmark does not force a download. Explicit install or load acquires the pinned release. - Player audio continues if the UI closes or the network fails. A saved DAW project restores its pinned package and parameter mapping offline without credentials in project state. - Preparation failure leaves the last valid program running. Update notices never hot-replace an active DAW sound. - A declared browser and one DAW/OS configuration pass the [validation gate](https://github.com/joshstovall/TuneToys/blob/main/docs/delivery/validation.md). All other configurations remain unqualified. ## Explicitly later Simultaneous collaborative editing, arbitrary user code execution, broad marketplace payments, third-party VST/AU hosting, remote audio streaming, native GPU/video pipelines, lighting outputs and firmware are expansion work. A browser-only video engine and sensing, plus MIDI cables, control nodes and buses on every engine host, shipped in [v0.0.5](https://github.com/joshstovall/TuneToys/blob/main/docs/delivery/sprints/v0.0.5/README.md) as expansion beyond this loop, not as MVP requirements ([signal types and domains](https://github.com/joshstovall/TuneToys/blob/main/docs/architecture/signal-types-and-domains.md)). Their integration boundaries are planned without making them prerequisites to shipping the audio loop. ## Related - [Vision and vocabulary](https://docs.tune.toys/contributors/vision/), [roadmap](https://github.com/joshstovall/TuneToys/blob/main/docs/delivery/roadmap.md), [validation](https://github.com/joshstovall/TuneToys/blob/main/docs/delivery/validation.md). - Contracts the criteria depend on: [Toy source format](https://docs.tune.toys/contributors/toy-format/), [operations](https://github.com/joshstovall/TuneToys/blob/main/docs/specs/operations.md), [releases](https://github.com/joshstovall/TuneToys/blob/main/docs/specs/releases.md), [Player authorization](https://github.com/joshstovall/TuneToys/blob/main/docs/specs/player-authorization.md). --- URL: https://docs.tune.toys/contributors/patch-editor/ Title: Patch editor How does a creator wire a graph on the canvas, and what guarantees hold for every gesture? The patch editor is browser UI over React Flow and the shared authoring operations: every accepted edit is one validated, undoable authoring batch, and no canvas interaction changes the portable Toy model or DSP semantics. **Status:** implemented browser editing UI. Cable brush selection, arbitrary multi-port drag splicing and broad physical-device qualification remain separate work; full browser/DAW qualification is tracked in [implementation status](https://github.com/joshstovall/TuneToys/blob/main/docs/delivery/implementation-status.md). ## One rule for every gesture ```mermaid %%{init: {'theme': 'base', 'themeVariables': {'primaryColor': '#FAFAF7', 'primaryTextColor': '#1C1C1B', 'primaryBorderColor': '#1C1C1B', 'lineColor': '#64645F', 'fontFamily': 'DM Sans, Inter, system-ui, sans-serif'}}}%% flowchart LR GESTURE["Gesture or menu action<br/>drag · drop · key · context menu"] --> PREVIEW["Inert preview<br/>no source, no history, no audio"] PREVIEW -->|Escape · cancel · blur · outside| NOTHING["No change"] PREVIEW -->|release / confirm| ADMIT{"Full source admission<br/>channels · occupancy · cycles · ownership"} ADMIT -->|rejected| EXPLAIN["Visible reason<br/>graph unchanged"] ADMIT -->|accepted| BATCH["One authoring batch<br/>one Undo step"] BATCH --> HOST["Runtime prepares next generation<br/>DSP keeps running meanwhile"] classDef shared fill:#DCEBDD,stroke:#1C1C1B,color:#1C1C1B classDef source fill:#F0442E,stroke:#1C1C1B,color:#FAFAF7 classDef host fill:#DED8F0,stroke:#1C1C1B,color:#1C1C1B class GESTURE,PREVIEW,ADMIT,EXPLAIN,NOTHING shared class BATCH source class HOST host ``` The canvas fills the workspace between document tabs and the transport. Undo/redo, node discovery, selection/panning, visual grouping, subpatch creation, arrange, zoom, minimap and an optional public-control inspector are canvas overlays. The shared [app shell](https://github.com/joshstovall/TuneToys/blob/main/docs/architecture/settings-library.md) keeps a persistent component palette beside the patch on desktop; palette and Spotlight share cycle-free catalog entries, and the compact navigation drawer provides tap-to-add on narrow screens. ## Open views `EditorTabs.tsx` provides closable, keyboard-accessible document tabs: Left/Right and Home/End switch, Delete closes a non-pinned view, the main patch stays open. Each nested patch tab carries its definition ID and full instance path, so two instances of one reusable definition open independently and keep separate cameras. Tabs, the active view, cameras, node selections, Frame layer selections and inspector visibility restore after reload from browser-local `tunetoys.workspace.v1:<id>` storage; invalid paths, missing members, foreign document state and malformed cameras are discarded. Closing a tab never removes source. The tab menu opens Frame design, applet preview and portable source views. Snapshot links open the applet once; the first source edit clears the URL fragment so reload uses recovery data. Loose components are stored in `Definition.widgets`, positioned directly on the Patch, and rendered by the same `WidgetView` as Frame members. Empty-canvas drops preserve the pointer position; only dropping on a Frame gives it membership. Dragging across a Frame boundary converts bounds and transfers ownership in one undoable batch. Negative coordinates, Properties editing, resizing, selection restoration and same-Patch component duplication use this source model. `PatchCanvas.tsx` renders each explicitly defined Frame as a movable, resizable canvas card that holds its components, like a group: each component is drawn through the same shared renderer used in published playback at its Frame position, addresses the active instance, and is moved or resized right on the card, writing the Frame itself. The Patch lock chooses between arranging (unlocked: components select, drag and resize; their controls do not respond) and playing (locked: controls take input; structure and layout stay fixed). Design opens the Frame designer and Preview opens a dedicated applet tab without a second renderer or DSP copy. Selecting a component edits it in Properties, a dashed binding link joins its outlet to every parameter row its control drives, and dragging that outlet onto another row re-points the control ([web app](https://github.com/joshstovall/TuneToys/blob/main/apps/web/src/README.md#components-on-the-patch)). The [customizable workspace](https://docs.tune.toys/contributors/workspace-layout/) owns splitting, docking and floating of these views. ## Context actions Dragging a Frame's header or empty background moves the whole Frame with its contents in one undoable layout edit. Member coordinates and bindings stay unchanged; dragging a component still moves that component independently. Escape cancels the move, and Patch lock prevents it. Double-clicking empty space opens Spotlight and places the chosen node at that flow coordinate at any zoom/pan. Right-click opens a menu for the targeted canvas, node, cable, visual group or Frame card; a right-click on a selected node preserves the selection, another node becomes the sole target. Menus route through the same authoring operations and history as toolbar actions. Right-button dragging does not pan; use the pan tool, middle button or trackpad. Native context menus remain in editable node fields. Frame design has canvas insertion/settings actions and shared layer property, duplication, stacking, visibility, lock and removal actions in both the canvas and Layers panel; locked layers keep their edit protections and Play mode keeps live controls. The editor's Mantine adapter uses the semantic color roles and partial component styles without a global reset. Tooltips support hover and focus; menus delegate positioning, focus and dismissal to Mantine; the view picker uses Popover. Searchable selectors preserve durable option values and call the same admission callbacks. Authentication, profile and library forms use Mantine `useForm` over the existing validators and versioned mutations; the shared Notifications host replaces notice timers; Media Dropzone keeps whole-selection rejection and the existing file limits and cancellation. ## Nodes and cables | Element | Behavior | | ------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Node card | One `AudioNode` renderer shared with picker and drag previews; category-tinted header, compact numeric values without range sliders, 30 px desktop headers, 8 px gutters, 3 px parameter row padding; coarse pointers get taller targets; choices share the value box (`ADSR ⌄` like `80 ms`) | | Values | Public controls send live host gestures and commit source defaults at gesture end; unexposed literals use `node.value`; `parameterDefaultOperation` follows aliases to the owning public default; controls clamp to admitted domains and log controls use shared Frame normalization | | Folding | Chevron folds controls/footer to the header; H folds a mixed selection or expands a fully folded one; body stops rendering while DSP keeps running; port anchors stay mounted; header inlet/outlet buttons open connectable port lists | | Appearance | `layout.node` writes optional display name, color and width in `extensions["toys.editor.nodes"][definitionId][nodeId]` through ordinary history; display name is independent of the durable ID; invalid imported appearance is ignored | | Ports | Declared signal type and width; hollow inlets, filled outlets; audio cyan circles, control amber diamonds (parameter inlets too), MIDI red squares, video blue and data violet circles. `PORT_TYPES` (`portTypes.ts`) sets every glyph, cable and width label from the themed `--port-*` tokens; every port shows its width (`2ch`, `1 lane`, `1 stream`, `Auto`), which follows the admitted multichannel graph | | Cables | Draw/reconnect through shared source validation (channel compatibility, occupied destinations, cycles); compatible targets highlight, blocked ports explain; Escape cancels; reconnection excludes the replaced edge from admission | | Add to Frame | Each parameter has Add/Show in Frame; an unexposed value becomes a public control and an explicit Frame member in one batch via `frameControlOperations`, and from a Patch its card lands beside the node and opens in Properties; multiple Frames prompt for a destination, none creates one | | Inlets | Every parameter row carries its control inlet (`@name`); a collapsed card lists only cabled inlets. A control cable from any control outlet, or a Frame control's binding outlet, drops on the row | | Properties | Same value/ownership model as cards, real controls, declared/resolved port contracts, attached cables, nearest compatible peer search, replace/disconnect and peer reveal; display/default resets are explicit source edits | Collapse state is stored per browser view/instance path. Clipboard paste remaps appearance, grouping moves child entries and deletion cleans them. React Flow remeasures after folding, opening port lists or changing widths. Group open stays a separate action. Dropping a cable on empty canvas opens compatible node discovery at that location. A cable's Insert node action (also in the inspector) opens a reviewed port plan; contextual results inspect before Add and multiple viable socket pairs are selectable. The original cable stays until the entire node/default/wire/layout batch is admitted; stale requests fail without changing source. Delete and reconnect bridges only selected nodes with one input, one output and one connected input, and complete validation must accept every resulting wire. Node removal cleans orphan public controls, presets and Frame members. The picker itself is described in [node picker](https://github.com/joshstovall/TuneToys/blob/main/docs/architecture/node-picker.md). ## Selection clipboard, gestures and keyboard commands | Command | Behavior | | --------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Cmd/Ctrl+D, copy/cut/paste | Bounded `tune-toys-selection` payload: selected nodes, internal cables, complete public-control owners, required nested definitions/Frames, layout and visual regions; paste imports fresh identities, boundary cables do not travel; partial public controls become literals; one history edit | | Option/Alt-drag on a node header | Drags a copy of the node or selection with inert previews at the receiving view's zoom; release inside the canvas commits the paste batch with preserved relative positions and selects the copies; under four pixels creates nothing; Escape, pointer cancel, lost capture, blur, source change or release outside cancels | | Arrow keys | Preview a nudge of 4 canvas units (40 with Shift), commit once on release; Escape discards | | Shift+2 / Shift+1 / Shift+0 / Shift++ − | Fit selection / fit patch / 100% / zoom | | Shift-drag a cable, then Shift-click | Batch wiring from one origin; release Shift or Escape to finish; each cable is one undoable edit | | `?` or the keyboard button | Searchable reference of implemented shortcuts and gestures | Async clipboard work cannot mutate an unmounted patch; a same-tab fallback retains copies when clipboard APIs are unavailable. Inputs, dialogs and menus keep their local keyboard and clipboard behavior. Shared Frame knobs use vertical normalized motion, Shift fine adjustment, keyboard stepping and double-click reset with host ownership guards. Copying a Receive retains its bus name without copying the external Send or its upstream graph. Paste requires a same-named Send in the destination definition, with signal types and channel widths checked before commit. When a selection includes the Send, its copied Send and Receives use an independent bus name if the destination already uses the original name. Nested definitions keep their own bus scope and must remain complete. ## Two kinds of grouping | Kind | Storage and operations | Effect on DSP | | ------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------- | | Visual group | Named movable region; member IDs in `extensions["toys.editor.groups"][definitionId]`; `layout.group.put` / `layout.group.remove`; a node belongs to at most one region; moving a region writes member positions through `layout`; removing or nesting a member cleans membership; malformed imports ignored | None | | Subpatch | The `group` operation moves selected nodes into a reusable definition, rewires boundary ports and parent cables, keeps public IDs and explicit Frame membership; group nodes open the addressed instance in a tab; Rename subpatch uses `definition.rename`; breadcrumbs open ancestor instances; nesting repeats without a separate write model | Topology change through the same validated source | ## Notes, routes, cable tools and finding nodes | Tool | Behavior | | ------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Notes and waypoints | Editable draggable notes and cable waypoints in bounded `toys.editor.annotations` metadata keyed by definition and durable IDs; never processing nodes, never routing; drag/delete/double-click remove; Escape cancels; obsolete routes pruned; copies and grouping carry routes | | Cut cables | Explicitly armed overlay; drag previews the crossed-cable count; release removes them in one validated batch; sampling prepared once per gesture with a bounded hit set per segment, honoring zoom and routed curves | | Shift-drag splice | An unattached single-input/single-output node over a cable previews a splice; release with Shift held combines the move and both connections in one batch under full admission; Escape restores layout; connected or multi-port nodes keep ordinary moves | | Find node in Toy | Searches names, types and nested instance paths and reveals the instance | | Patch detail | Full by default, with an explicit Compact option; zoom preserves node detail and cables | | Arrange | Works on the selection with measured dimensions and preserves unrelated positions; without a selection arranges the patch and Frame cards | Reusable component capture and insertion are in [components](https://github.com/joshstovall/TuneToys/blob/main/docs/architecture/reusable-components.md). ## Canvas structure and render model `PatchCanvas.tsx` composes one Patch view from cohesive modules in [`apps/web/src/patch/`](https://github.com/joshstovall/TuneToys/blob/main/apps/web/src/patch/README.md): a pure derived graph (`graphModel.tsx`), identity reconciliation (`graphIdentity.ts`), and hooks for selection, node drags, cables, clipboard, keyboard and nudges, palette drops, plus the context menu, toolbars and dialogs. - **React Flow owns node state between revisions.** Nodes are uncontrolled: a drag or arrow-key nudge moves cards inside the Flow store, so a pointer move re-renders only the moved card and its cables, never the canvas component. Release writes one `layout` batch (with a Shift splice when armed); Escape or a revision change mid-gesture restores the starting positions and writes nothing. Each new revision's derived graph is reconciled into the store. - **Derived objects keep identity.** Authoring clones the document per revision, so the graph is rebuilt, then compared by content: unchanged nodes and cables keep their previous objects and React Flow skips them. Callbacks in node and edge data are latest-bound — a single action object dispatches by durable id against the current document — so callback props never force a rebuild and content comparison is exact. - **Card-local gestures survive unrelated edits.** Graphical editors and resize handles are keyed by their node's own values, not the document revision. - **Inspector edits stay one Undo step.** Parameter number boxes send live host values while typing or scrubbing and commit once; style and appearance controls preview through `editPreview.tsx` and commit once. ## Provenance and verification Layout references were synth.ist's `features/canvas/CanvasFlow.tsx` and `ui/nodes/GraphNode.tsx` and Hotwire console's `lib/editor/DocumentEditor.svelte`: canvas as the main surface, editable node bodies, typed edge handles, floating graph tools. This is an original implementation; no source or build dependency was imported. Authoring fixtures cover graph preservation, invalid/overlapping membership, cleanup, rename/undo, atomic Frame exposure and binding reuse, and alias/instance isolation. Browser-workspace fixtures (`patch-interactions`, `patch-graph-identity`, `cable-feedback`, `node-workflows`, `creation-tools`, `editor-workspace`) cover cable reasons and reconnection, per-instance cameras/selections, snapshot defaults, storage failures and stale-parent fallback. `npm run test:creation-ui --workspace @tunetoys/web` runs browser checks after a build. Root `npm run check` remains required. ## Implementation - [apps/web](https://github.com/joshstovall/TuneToys/blob/main/apps/web/README.md) — `PatchUi.tsx`, `PatchCanvas.tsx` and [`patch/`](https://github.com/joshstovall/TuneToys/blob/main/apps/web/src/patch/README.md), `GraphNodes.tsx`, `NodeInspector.tsx`, `PatchCable.tsx`, `PatchCutTool.tsx`, `PatchNote.tsx`, `NodeCopyDrag.tsx`, `EditorTabs.tsx`, `ContextMenu.tsx`, `ShortcutHelp.tsx` - [packages/authoring](https://github.com/joshstovall/TuneToys/blob/main/packages/authoring/README.md) — `selection.ts`, `frame-controls.ts`, `node-appearance.ts`, `patch-annotations.ts`, grouping operations - [packages/frame-runtime](https://github.com/joshstovall/TuneToys/blob/main/packages/frame-runtime/README.md) — knob gestures and numeric controls shared with the inspector ## Related - [Customizable editor workspace](https://docs.tune.toys/contributors/workspace-layout/) - [App shell, Settings and Library](https://github.com/joshstovall/TuneToys/blob/main/docs/architecture/settings-library.md) - [Node picker](https://github.com/joshstovall/TuneToys/blob/main/docs/architecture/node-picker.md) - [Compact resizable nodes and shared interface components](https://docs.tune.toys/contributors/interface-components/) - [Multichannel audio bundles](https://docs.tune.toys/contributors/multichannel-audio/) — the channel rules cables obey - [Revisioned authoring operations](https://github.com/joshstovall/TuneToys/blob/main/docs/specs/operations.md) --- URL: https://docs.tune.toys/contributors/releases/ Title: Releasing TuneToys app versions The step-by-step process for promoting a tested immutable Linux container to the AWS pilot, how artifacts are identified, and what rollback can and cannot do. This releases the website/API only; creator Toy publications and Player/plugin builds are different release streams. **Status:** Implemented as a manual process — no automatic production deployment, rolling deployment or zero-downtime path exists. Saving source, committing, pushing or running `npm run build`/`check` does not update `tune.toys`. The [v0.0.3 pipeline](https://github.com/joshstovall/TuneToys/blob/main/docs/delivery/sprints/v0.0.3/README.md) has landed but has not run on GitHub; CI has no AWS identity, so images are [published to ECR by the operator](#publish-to-ecr), and the host promotes them by digest with `promote.sh` once the owner installs it. Rules that never change: keep a retained rollback artifact and confirm a fresh private S3 backup before each promotion; app deployment must never move a customer's pinned Toy version or replace audio in a running DAW. Current production identity and checks are in the [AWS pilot record](https://github.com/joshstovall/TuneToys/blob/main/docs/delivery/aws-pilot.md); host setup is in [hosting](https://docs.tune.toys/contributors/hosting/). ## Release flow ```mermaid %%{init: {'theme': 'base', 'themeVariables': {'primaryColor': '#FAFAF7', 'primaryTextColor': '#1C1C1B', 'primaryBorderColor': '#1C1C1B', 'lineColor': '#64645F', 'fontFamily': 'DM Sans, Inter, system-ui, sans-serif'}}}%% flowchart LR DEV["Reviewed source"] --> CHECK{"Checks pass?"} CHECK -->|Yes| IMAGE[["Frozen Linux image<br/>source + engine receipt"]] IMAGE --> TEST{"Exact artifact<br/>qualified?"} TEST -->|Yes| BACKUP[("Fresh S3<br/>recovery archive")] BACKUP --> LOCK["Deployment lock"] LOCK --> LIVE(["Restart Lightsail API"]) LIVE --> HEALTH{"HTTPS and<br/>flow checks"} HEALTH -->|Pass| KEEP["Record release<br/>and back up"] HEALTH -->|"Fail and compatible"| ROLLBACK["Restore previous image"] 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 IMAGE source class CHECK,TEST,HEALTH shared class BACKUP,LOCK,LIVE,KEEP,ROLLBACK host ``` | Step | Action | Evidence to retain | | ---------- | ------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------- | | 1. Review | Choose the exact change; review source, dependencies, migrations, configuration and compatibility | Commit/tag when available, reviewed diff and release notes | | 2. Check | Run `npm run check`; run affected HTTP/browser/native checks | Logs tied to the chosen source | | 3. Build | `npm run build:container -- --platform=linux/amd64` | Exact `.cache/containers/<input-sha>/<run-id>/build.json`, frozen context and image | | 4. Qualify | `node tools/test-container.mjs /absolute/path/to/build.json`; inspect the built UI and relevant journeys | Exact receipt/image/engine digests and results | | 5. Back up | Confirm target account/region/host/current image; run a fresh host backup and inspect its new confirmed S3 result | Archive key/version/checksum, current image/config and recovery reference | | 6. Stage | [Publish to ECR](#publish-to-ecr) by digest; or (fallback) save the exact image ID, transfer it through SSH, verify and load it | Registry digest and `receipts/<inputSha256>.json`, or archive SHA-256; matching host image labels/platform | | 7. Promote | Hold the shared deployment lock, compare the expected old image, atomically replace `image.env`, restart the API | Previous `image.env`, new identity and operator/promotion record | | 8. Verify | Check loopback/public HTTPS readiness, app UI, sign-in, permissions and changed flows | Smoke checks and decision to keep or revert | | 9. Retain | Confirm a new backup includes the new image, update the deployment record, keep the prior image/archive | Release notes, rollback artifact and post-release backup | The 2 GB host is not a build machine: build on the development/build host and preserve the shared prepared engine bytes. Use a temporary test environment rather than an always-on staging bill. The current production target is Linux amd64, account `982471468586`, Ohio `us-east-2`, instance `tunetoys-pilot`, static IP `3.20.14.74`. Confirm these against AWS before acting; historical documentation is not target-discovery authority. ## Version and artifact identity - Use a human app version/changelog for communication and immutable input/image/engine digests for deployment. About reads the version from `apps/web/package.json`. - Choose a patch/minor/major app version before building; for example, `npm version patch --workspace @tunetoys/web --no-git-tag-version --ignore-scripts` updates the web version and lockfile without publishing or creating a Git tag. Review both files. - API, Player and independent product versions have separate compatibility meanings. Never bump engine ABI, source schema or plugin identity as a cosmetic app-version change. A new app version does not rewrite immutable packages, media hashes, stable parameter IDs, plugin IDs, signed exports or saved DAW state; engine ABI/semantic and portable schema changes need their own compatibility/fixture review. - The repository currently has development package versions; a package version or Git tag alone does not prove which bytes are running. Record the source commit when the checkout has been committed. For the current pilot, frozen source manifests also identify reviewed artifacts produced while other chats edit the checkout. - A receipt contains `inputSha256`, `engineSha256`, `imageId`, `platform` and its frozen `context`. Never deploy a floating tag such as `latest`. A changed checkout prevents the build tool from advancing its local latest pointer; the retained older artifact is not automatically approved. Inspect it, compare its manifest with the desired changes and qualify that exact receipt, or rebuild from a stable checkout. Check current source/tests separately from the source actually inside a retained image. - The sidebar release is an example of a narrow update built from the prior deployed snapshot while later editor/DSP work continued, preserving the deployed engine/API. Normal releases should use one isolated, reviewed checkout so the final artifact includes exactly the intended changes. ## Publish to ECR This is the current publish step. It replaces the archive transfer when the host has `promote.sh` installed; the [code-only promotion](#code-only-promotion) steps below remain the fallback. The automated path is the same tool: `release-web.yml` runs it with `--ambient-credentials` once the `tunetoys-ci` role exists, which the organization's service control policy currently prevents (see the [workflows README](https://github.com/joshstovall/TuneToys/blob/main/.github/workflows/README.md)). 1. Commit the reviewed change, then build and qualify from that clean checkout: `npm run build:container -- --platform=linux/amd64` and `node tools/test-container.mjs /absolute/path/build.json --smoke`. 2. Preview: `node tools/publish-image.mjs /absolute/path/build.json --dry-run`. It prints every command and any refusal without calling AWS. 3. Publish: `node tools/publish-image.mjs /absolute/path/build.json`. The AWS profile is `--profile`, else `AWS_PROFILE`, else `tunetoys`; the repository and bucket are `--repository`/`--bucket` or `TUNETOYS_ECR_REPOSITORY`/`TUNETOYS_AUDIT_BUCKET` (values in the [workflows README](https://github.com/joshstovall/TuneToys/blob/main/.github/workflows/README.md#configuration)). It refuses a dirty checkout, container inputs that differ from the receipt's frozen snapshot, and a local image whose platform or labels differ from the receipt. It logs in to ECR, tags the receipt's image ID `<repository>:<inputSha256>` and `<repository>:sha-<commit>`, pushes, prints `digest=sha256:…`, and writes the receipt (without its local path, plus registry, commit and caller) once to `s3://<audit bucket>/receipts/<inputSha256>.json`. Tags are immutable: an existing tag or receipt is accepted only when it names the same image. The profile needs ECR push on `tunetoys/api` and `s3:PutObject` on `receipts/`; the host's `tunetoys-host-delivery` policy grants pull only. 4. On the host, after a fresh confirmed backup is available: `sudo tunetoys-promote promote <imageId> --expect <running imageId> --digest <digest> --input <inputSha256> --engine <engineSha256> --kind image --run <digits> --commit <sha>`. It pulls by digest, verifies identity and labels, refuses a migration downgrade, takes the lock, proves the backup, swaps `image.env`, restarts, checks health and rolls back an image-only failure. A schema release runs `tunetoys-promote rehearse … --kind schema` first, then `promote … --kind schema --recovery <option>`. Then complete steps 8 and 9 of the release flow. The receipt's `imageId` is whatever the building Docker daemon reports, and `promote.sh` compares it with the pulled image's ID. A builder using the containerd image store reports the OCI index digest (the local builds on November 1, 2026 did), while a classic-store daemon reports the image config digest. Confirm that the builder and the host report the same ID for one image before the first promotion from ECR. ## Code-only promotion Use the trusted administrator SSH identity and pinned known-hosts file. Keep private operator configuration outside Git. Review commands before running them; credentials are not command arguments or release payloads. 1. Save the candidate with `docker image save` using its receipt's exact image ID, compress it and compute SHA-256. Retain the exact receipt alongside it. No `.env`, local account database or private operator directory belongs in an image. 2. Transfer through authenticated SSH to a unique temporary staging directory. Verify the archive checksum before loading. Inspect the loaded image and require Linux amd64 plus matching `toys.tune.input-sha256` and `toys.tune.engine-sha256` labels. Loading an image is staging, not promotion. 3. Start a fresh backup with `sudo systemctl start tunetoys-backup.service`. Inspect the **new** `/var/log/tunetoys/operations.log` entry confirming its versioned S3 archive. A successful service exit is insufficient: a run skipped because the deployment lock is held deliberately emits no backup success. Investigate/retry before changing production. 4. Acquire an exclusive `flock` on `/srv/tunetoys/operator/backup-work/deployment.lock`. Backups and promotions share that inode; never remove/recreate it. Compare `/etc/tunetoys/image.env` and the running container against the expected previous image. Abort if another operator changed production. 5. Preserve the previous root-only `image.env` and public release metadata under `/srv/tunetoys/operator/releases/<input-sha>/`. Write only `TUNETOYS_IMAGE=sha256:<verified image ID>` to a mode `0600` temporary file in the same config directory and atomically rename it over `image.env`. Do not source Docker env files as shell code or print the private `api.env`. 6. Restart with `sudo systemctl restart tunetoys-api`. Check `http://127.0.0.1:8787/v1/health` and `https://tune.toys/v1/health`, public HTML/asset loading and changed UI behavior. Keep the lock until the promotion/compatible rollback decision finishes. Configuration stays on the encrypted mounted data disk; Caddy/domain/mail usually need no change. 7. If readiness or smoke checks fail and the old image is compatible with the unchanged database/config/data, atomically restore the preceding `image.env` and restart. Check readiness again. Do not delete the failed artifact or its receipt; retain it for diagnosis. 8. Release the lock, run/confirm another backup, and update [the deployment record](https://github.com/joshstovall/TuneToys/blob/main/docs/delivery/aws-pilot.md). Retain old lazy-loaded web assets across a future deployment strategy or tell editing users to refresh when ready; do not force a reload that discards their work. Expected brief downtime comes from stopping/restarting the single API container. A later blue/green approach needs old asset retention, database compatibility and capacity beyond this small host. ## Schema or configuration releases **Current PostgreSQL startup requires an exact migration ledger and hashes.** Applying a new migration makes an old image's startup verification fail, even when SQL is additive. An older image continuing to run is not proof that it can restart. Never promise automatic image-only rollback after a migration. See [database delivery](https://github.com/joshstovall/TuneToys/blob/main/docs/delivery/database.md). 1. Review the forward migration, runtime table privileges, import/cutover impact and old/new data behavior. Do not edit applied migration files. Rehearse the candidate image with a restored, populated database and private media/native key. 2. Choose and test recovery explicitly: a rollback image that includes the new ledger but compatible old application behavior; fix forward; or restoring the pre-migration database under maintenance. A database restore can discard subsequent writes, so stop writes and define the recovery point first. A production restore is not a routine image flip. 3. Capture a fresh confirmed backup including roles, configuration/native key, TLS, exact image and S3 byte inventory. Hold the deployment lock through migration and promotion, using the separate protected migration environment/role. The application role must not migrate or gain owner privileges. 4. Run the candidate's explicit migration/bootstrap flow from [database delivery](https://github.com/joshstovall/TuneToys/blob/main/docs/delivery/database.md), reconcile reviewed table grants, start the candidate and verify existing accounts, releases, library/Player grants and new schema paths. A missing or altered ledger must fail before serving requests. 5. Perform the agreed recovery if qualification fails. Otherwise release maintenance/lock, verify and create a fresh post-release backup. Record both migration history and running image. The billing migrations are in source only until this process is performed. Do not deploy a newer billing image against the old pilot ledger, or run a migration as an incidental startup side effect. A future compatible-ledger policy would need version bounds and backward/forward tests; it is not current support. For configuration changes, retain the prior protected values and stable native authorization key. Validate optional provider configuration before restart. Never copy the developer `.env` wholesale. Stripe connection/live activation is its own reviewed rollout, not a side effect of deploying the code; see [Stripe setup](https://github.com/joshstovall/TuneToys/blob/main/docs/delivery/stripe.md). ## Acceptance and monitoring Minimum checks: app HTML/assets, sign-in/session continuity, an existing owned draft, public playback, private cross-account denial, invited-upload policy, exact engine/media bytes, library sync and the changed feature. Use bounded original audio; do not require user devices or live purchases to pass automated fixtures. Native changes also require Player/plugin build and declared DAW qualification; web-only changes do not claim that qualification. Record the app/source/image/engine identities, operator/time, changes, migration/configuration impact, checks, backup version, rollback choice and known limitations. Monitor health, memory, error rate, missing-backup alarms, email and relevant business-event failures after rollout. Budget alerts notify and do not stop resources. A daily snapshot or uploaded archive is not a proved replacement-host restore. Keep the remaining hosted/browser/DAW recovery gates visible in the deployment record. Restore rehearsal and periodic dependency/OS updates are operational work separate from ordinary feature releases. ## Later automation Automate this same sequence after manual releases are repeatable: CI checks, immutable reviewed artifacts/receipts, retained versioned S3 archives, target verification, fresh-backup proof, protected promotion credentials, lock/CAS, schema compatibility gates, public smoke checks and explicit recovery. Trigger production promotion deliberately; do not turn every push into a live deployment. Signing credentials and Stripe live keys never enter a browser build or untrusted pull-request runner. The [v0.0.3 continuous delivery plan](https://github.com/joshstovall/TuneToys/blob/main/docs/delivery/sprints/v0.0.3/README.md) specifies this automation, and its files have landed disabled: release workflows under `.github/workflows/`, `infra/lightsail/promote.sh`, `infra/aws/delivery.json` and the gate, smoke and publish tools. Nothing runs on push until the repository variable `TUNETOYS_RELEASE_PIPELINE` is `enabled` and the [enablement checklist](https://github.com/joshstovall/TuneToys/blob/main/.github/workflows/README.md#enablement-checklist) is complete. As it stands, an enabled push would check, build and qualify the image on CI and then stop: without the `tunetoys-ci` role it publishes nothing and reports "gate skipped: no AWS identity". The plan: the same sequence driven from a push to `main`, with image-only releases promoted automatically once supervised releases pass and schema releases always held for approval. ## Related - [Hosting runbook](https://docs.tune.toys/contributors/hosting/) - [AWS pilot record](https://github.com/joshstovall/TuneToys/blob/main/docs/delivery/aws-pilot.md) - [Database runbook](https://github.com/joshstovall/TuneToys/blob/main/docs/delivery/database.md) - [Release, state and compatibility contract](https://github.com/joshstovall/TuneToys/blob/main/docs/specs/releases.md) for creator Toy releases --- URL: https://docs.tune.toys/contributors/security/ Title: Trust and service boundaries Which data is trusted, which code is trusted, and who may do what on which host? This document states the trust boundaries the platform is designed around. It is a design, not a security certification: **no implementation or external audit is implied by anything here.** **Status:** design with a partial implementation. Source validation, bounded expansion, API authorization checks, native credential separation, hosted upload access control and (v0.0.6) the sealed connections, the envelope-encrypted vault and the SSRF-guarded request proxy exist with fixtures; worker isolation, release signing, upload scanning and incident handling are release-gate requirements, not features. See [SECURITY.md](https://github.com/joshstovall/TuneToys/blob/main/SECURITY.md) and [implementation status](https://github.com/joshstovall/TuneToys/blob/main/docs/delivery/implementation-status.md). ## Boundaries at a glance ```mermaid %%{init: {'theme': 'base', 'themeVariables': {'primaryColor': '#FAFAF7', 'primaryTextColor': '#1C1C1B', 'primaryBorderColor': '#1C1C1B', 'lineColor': '#64645F', 'fontFamily': 'DM Sans, Inter, system-ui, sans-serif'}}}%% flowchart LR subgraph UNTRUSTED["Untrusted data"] SRC["Toy source JSON"] ARCH["Archives · assets · manifests"] REQ["User and agent requests"] end subgraph VALIDATE["Validation and admission"] V["Bounded structure · references<br/>recursion · expansion · paths · digests"] C["Compiler admission<br/>trusted primitive catalog"] end subgraph TRUSTED["Trusted curated code"] API["API modules<br/>owner · visibility · recheck on read"] ENGINE["Rust engine<br/>no prompts, no waits in the callback"] BRIDGE["Native bridge<br/>exact sender · shape · bounds · generation"] end WORKER["Isolated worker (planned)<br/>bounded CPU/memory/disk/time<br/>no production credentials"] HOST["Host grants<br/>mic · camera · files · network · remote"] SRC --> V --> C --> ENGINE ARCH --> V REQ --> API API -.-> WORKER BRIDGE --> ENGINE HOST -. explicit grant per session .-> BRIDGE classDef bad fill:#F0442E,stroke:#1C1C1B,color:#FAFAF7 classDef shared fill:#DCEBDD,stroke:#1C1C1B,color:#1C1C1B classDef host fill:#DED8F0,stroke:#1C1C1B,color:#1C1C1B classDef planned fill:#FAFAF7,stroke:#64645F,color:#64645F,stroke-dasharray:4 3 class SRC,ARCH,REQ bad class V,C,API,ENGINE,BRIDGE shared class HOST host class WORKER planned ``` ## Input and execution Source JSON, archives, assets, extension manifests and user/agent requests cross an untrusted boundary. Before preparation, validate bounded structure, graph references, recursion, expanded size, asset paths and digests, decoded resource sizes and capability requirements. **Schema validation does not establish runtime safety, DSP compatibility, file integrity or permission.** | Claim | Current code | | ------------------------------------------------- | ----------------------------------------------------------------------------- | | Draft schema, references, domains and ports | Checked by the shared validator | | Bounded nesting and expansion, Frames, safe paths | Checked | | Rendering of admitted primitives | Shared engine renders admitted multichannel primitives from a bounded catalog | | Archive reading, package resolution | Not done by source validation | | Asset byte and signature verification | Release packages verify digests; source validation itself does not | | Sandboxing executable code | Not provided; there is no executable user code in the first profile | Keep these claims separate in errors and UI. Native code is trusted curated code in the first profile; arbitrary generated scripts and native libraries are deferred. Worker jobs, when they exist, run with bounded CPU, memory, disk and time, isolated workspaces, no production credentials by default, dependency and network allowlists, and explicit artifact upload authority. Uploaded archives need traversal, symlink, decompression-bomb, media-decoder and malware controls before public ingestion; today hosted uploads are limited to [invited accounts](https://github.com/joshstovall/TuneToys/blob/main/docs/specs/media-library.md#hosted-pilot-access). ## Identity and privacy Authorize every project revision, dependency, artifact and preview against the caller. Owner, workspace and visibility checks belong in API modules, not only in front-end hiding. Catalog indexes may cache public metadata but must recheck access at retrieval. Preserve a visibility audit trail; deletion/retention and dependency revocation policies must be published before launch. Account identity, publisher verification and signing identity are separate things. A digest proves content integrity, not the publisher's trustworthiness. A signature authenticates a release under a key and policy, not its musical quality. Key rotation, compromised-release handling and artifact retention are managed independently of mutable catalog aliases. ## Host authority | Action | Grants device or network access? | | -------------------------------------------------------------- | -------------------------------- | | Installing or bookmarking a Toy | No | | Accepting an agent proposal | No | | Restoring a saved DAW project | No | | An explicit grant for a specific endpoint in this host/session | Yes, until revalidation | Capabilities name required and optional services; grants authorize specific endpoints in the current host and session. Rebinding and reconnect follow explicit revalidation. **The audio callback never performs permission prompts or waits on authorization.** Embedded web UIs load declared resources and restrict navigation. Privileged bridges validate exact sender and origin, command shape, bounds, instance and generation, and the allowed operation. Displayed text is escaped and supported asset formats are sanitized. Bearer tokens live in server sessions or native credential stores and are redacted from telemetry. ## Connections, vault and the request proxy Identities, connections and vault entries are separate tables with separate keys ([connections and vault](https://github.com/joshstovall/TuneToys/blob/main/docs/architecture/connections-and-vault.md)). Implemented with HTTP fixtures; no external security review has been done, and the keys are not provisioned in any environment. | Boundary | Rule | | --------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Portable data | Toys, takes, exports, logs and node data name a vault **slot** or a requirement (`secret:<slot>`, `connection:<provider>`), never a value or token. Host-local file and connection references stay in browser storage | | Connection tokens | Sealed with AES-256-GCM under `TUNETOYS_CONNECTION_KEY`, bound to the row, refreshed and used only by the API. The **Picker route** is the one exception: it returns a short-lived `drive.file` access token to the owner, never the refresh token | | Vault values | Envelope encryption: a per-account data key sealed by `TUNETOYS_VAULT_KEY`, each secret sealed under it with the slot as associated data. `Vault.use` is the only decryption path, for one upstream call inside the proxy or the LLM route; no route returns a value | | Key binding | A catalog slot's value is sent only to its addon's hosts; a custom slot only to hosts in the person's own allowlist (`SECRET_HOST`). The platform's own model key is never used for `ai.chat` | | SSRF | The proxy admits only enabled addon hosts and the person's exact-host allowlist, resolves first, refuses private, loopback, link-local, CGNAT, metadata and other non-public ranges, connects to the checked address and re-checks every redirect (≤ 3) | | Scraping | `web.scrape` goes through the proxy, which honours `robots.txt` and never runs scripts; HTML is parsed with a bounded parser in the browser | | Logs and errors | Upstream calls are logged by host, status, size and duration; error bodies are truncated and scrubbed of the secret | | Transport and callers | Secret-bearing routes refuse plain HTTP except loopback, accept only browser sessions (never API keys or Player bearers) and pass the Origin/CSRF guard for writes | | The worker | Receives neither the connection key nor the vault key; export and convert jobs need neither | | Device vault | Signed-out keys in `tunetoys.vault.device.v1` under a non-extractable WebCrypto key. **Anything running in the page can use them**; offered only for direct-route addons, labelled as such, and refused by the proxy and the LLM route | | Embedded pages | The `web` widget's iframe is sandboxed without `allow-same-origin`, loads on click and relays `postMessage` only with the declared exact origin; the Google Picker script loads only on "Open from Drive…" | | Replay | A replayed take never executes side-effect nodes: their recorded outputs are injected, so replays and offline renders make no network calls | ## Observability Trace request, candidate, revision, build, release and runtime-generation IDs without logging raw private Toy content or audio by default. Collect crash and performance reports under an explicit privacy policy. Optional model or evaluation upload must identify the service and retention; private projects are never silently used for training. ## Release gates Dependency and license inventory, signing/notarization policy, authorized asset delivery, worker isolation, backup/restore rehearsal and basic abuse/rate limits are required before the relevant [roadmap](https://github.com/joshstovall/TuneToys/blob/main/docs/delivery/roadmap.md) gates, not implemented features. ## Implementation - [packages/toy-format](https://github.com/joshstovall/TuneToys/blob/main/packages/toy-format/README.md) — validator and bounded expansion - [crates/compiler](https://github.com/joshstovall/TuneToys/blob/main/crates/compiler/README.md) — admission against the trusted primitive catalog - [services/api](https://github.com/joshstovall/TuneToys/blob/main/services/api/README.md) — authorization, sessions, `media-policy.ts`, `native-auth.ts` - [services/api/src](https://github.com/joshstovall/TuneToys/blob/main/services/api/src/README.md#connections-vault-proxy-and-node-llm-v006-wp-c) — `connections.ts`, `vault.ts`, `proxy.ts`, `network-allowlist.ts`, `ai-routes.ts` - [native/plugin-shell](https://github.com/joshstovall/TuneToys/blob/main/native/plugin-shell/README.md) — bridge validation and credential storage boundary ## Related - [SECURITY.md](https://github.com/joshstovall/TuneToys/blob/main/SECURITY.md) — reporting and current boundaries - [Player authorization, sync and installation](https://github.com/joshstovall/TuneToys/blob/main/docs/specs/player-authorization.md) - [Release, state and compatibility](https://github.com/joshstovall/TuneToys/blob/main/docs/specs/releases.md) - [Extension design](https://github.com/joshstovall/TuneToys/blob/main/docs/architecture/extensions.md) — trust tiers for third-party code - [Lean AWS hosting](https://github.com/joshstovall/TuneToys/blob/main/docs/architecture/aws.md) — credential and backup handling on the pilot server --- URL: https://docs.tune.toys/contributors/toy-format/ Title: 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 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). --- URL: https://docs.tune.toys/contributors/vision/ Title: Product vision Tune.Toys lets developers, musicians, curious users and agents create and distribute playable software. It begins with audio instruments and effects and keeps a foundation for logic applications, sensing, education, creative tools and connected systems as their runtimes are qualified. ## The creative loop ```mermaid %%{init: {'theme': 'base', 'themeVariables': {'primaryColor': '#FAFAF7', 'primaryTextColor': '#1C1C1B', 'primaryBorderColor': '#1C1C1B', 'lineColor': '#64645F', 'fontFamily': 'DM Sans, Inter, system-ui, sans-serif'}}}%% flowchart LR START([Start from a Toy]) --> INSPECT[Inspect or prompt] --> EDIT[Edit graph and Frame] --> PLAY[Play] --> SAVE[Save] --> PUBLISH[Publish] --> SHARE[Share] SHARE --> FORK[Fork] -.-> START SHARE --> INSTALL[Install in a DAW] classDef make fill:#DCEBDD,stroke:#1C1C1B,color:#1C1C1B classDef play fill:#F0442E,stroke:#1C1C1B,color:#FAFAF7 classDef share fill:#DED8F0,stroke:#1C1C1B,color:#1C1C1B class START,INSPECT,EDIT make class PLAY play class SAVE,PUBLISH,SHARE,FORK,INSTALL share ``` A recipient reaches a playable web experience from a link. A musician bookmarks it and finds it in TuneToys Player inside a DAW. A developer ships a separate plugin with its own identity and branding. A plugin company could eventually run its catalog, licensing, updates and distribution on the platform. ## Vocabulary | Term | Meaning | | ------------------ | ---------------------------------------------------------------------------------- | | Toy | The portable authored creation; can be a component or complete application | | Patch | Familiar editor/community term for a Toy graph, not a separate file model | | Definition / group | Reusable implementation with typed ports and public parameters | | Instance | One use of a definition with independent values, DSP state, and observations | | Frame | Explicit playable UI bound to one definition in an instance context | | Project | Editable Toy source and working assets/history | | Release | Immutable resolved content plus evidence and distribution terms | | Catalog entry | Mutable title/discovery/visibility metadata pointing to releases | | Library | A user's bookmarks, tags, ratings, acquisitions, and installed/cached releases | | Player | TuneToys plugin/application that loads Toys; not a general third-party plugin host | | Independent plugin | A compiled Toy exported under its own persistent plugin identity | A Toy inside a Toy uses the same composition system as an ordinary reusable group. Reusing a definition shares immutable code and assets; each instance keeps its own runtime state. Forking creates a new project identity and preserves provenance. Remodeling an interface never silently changes the processing graph. The contracts behind these terms are the [Toy source format](https://docs.tune.toys/contributors/toy-format/) and [releases](https://github.com/joshstovall/TuneToys/blob/main/docs/specs/releases.md). ## Example journeys A maker builds a stereo effect, asks an agent to expose a useful macro and create a three-knob Frame, auditions the proposal and shares a link. A recipient plays it, bookmarks it and opens the same release in the Player. Reopening the DAW project without internet restores the same sound. Later, a developer creates a face-controlled synth. Camera inference produces bounded control data outside the audio callback. The web export requests camera access; a native plugin declares whether camera control is available and provides a fallback. A Frame can instead control a separately running host through an authenticated session. Local execution and remote control are explicit modes. ## Durable principles - One authored model connects behavior, public controls, Frames, assets, presets and provenance. - Humans and agents use the same authoring API. - The audio engine runs without an open editor, an active account connection or a live model. - Published source and remix access follow declared terms; private work stays private across dependencies, previews and builds. The long-term target set is web hosting and self-hosted static sites, Player, independent VST3/AU plugins, Electron desktop apps, browser extensions, mobile apps and eventually firmware. **Each target earns support through implementation and tests.** Being expressible as JSON does not make a Toy portable to every host. ## Related - [MVP scope](https://docs.tune.toys/contributors/mvp/) turns this into acceptance criteria; the [roadmap](https://github.com/joshstovall/TuneToys/blob/main/docs/delivery/roadmap.md) orders the gates. - [System architecture](https://docs.tune.toys/contributors/architecture-overview/) and [decision 0001](https://github.com/joshstovall/TuneToys/blob/main/docs/decisions/0001-portable-model.md) implement the one-model principle. --- URL: https://docs.tune.toys/contributors/workspace-layout/ Title: Customizable editor workspace How can a creator arrange Patch, Frame Design, Preview, Source JSON, Properties, Layers and History the way they work, and trust that none of it leaks into the Toy? The workspace is a validated browser-local dock tree rendered with Mantine Splitter and Tabs, with its own layout history, pins, named layouts and floating panels. Layout state never enters a Toy, publication, compilation or authoring history. **Status:** implemented browser-local docked areas, pinned tool contexts, named reusable layouts, separate layout Undo/Redo, closed-panel recovery and floating utility panels. Pop-out browser windows, piano rolls and additional editor types remain planned; see [implementation status](https://github.com/joshstovall/TuneToys/blob/main/docs/delivery/implementation-status.md). ## Structure ```mermaid %%{init: {'theme': 'base', 'themeVariables': {'primaryColor': '#FAFAF7', 'primaryTextColor': '#1C1C1B', 'primaryBorderColor': '#1C1C1B', 'lineColor': '#64645F', 'fontFamily': 'DM Sans, Inter, system-ui, sans-serif'}}}%% flowchart TB TREE["dockLayout.ts<br/>bounded binary tree · per Toy"] --> SPLIT["Split node<br/>id · orientation · ratio"] TREE --> AREA["Tab-group area<br/>id · ordered panels · active tab"] AREA --> EDITOR["Editor panels<br/>Patch · Frame Design · Preview · Source JSON<br/>validated EditorTab, own camera/selection"] AREA --> TOOL["Singleton tools<br/>Properties · Layers · History"] TREE -.-> FLOAT["Up to 6 floating panels<br/>bounds + dock-home anchor"] EDITOR --> TOY[("One validated Toy<br/>one authoring history<br/>one runtime host")] classDef shared fill:#DCEBDD,stroke:#1C1C1B,color:#1C1C1B classDef source fill:#F0442E,stroke:#1C1C1B,color:#FAFAF7 classDef host fill:#DED8F0,stroke:#1C1C1B,color:#1C1C1B class TREE,SPLIT,AREA shared class EDITOR,TOOL,FLOAT host class TOY source ``` A duplicated editor has a fresh local instance identity with separate selection, camera and Frame interaction mode; all views edit the same Toy. `DockWorkspace.tsx` renders the tree. Drag a tab to a strip or center guide to group it, to an edge guide to create an adjacent area, between guides or outside the dock to float it, and drag a floating header back to the same guides. A translucent preview shows the destination; Escape cancels; each accepted drag is one layout Undo step. Every tab surface shares one menu (see [Tabs](#tabs)); it is the keyboard and discovery path for the same moves. Empty branches collapse. Presets: editor with Properties/Layers, editor beside JSON, preview above editor. Compact screens show one panel group at a time through a switcher while keeping the desktop arrangement. | Bound | Value | | ------------------------- | ---------------------------------------------------------------- | | Areas / editor tabs | 12 / 32, bounded split depth | | Floating panels | 6; the last docked panel cannot float | | Named layouts | 12, profiles capped at 512 KiB (`tunetoys.workspace-layouts.v1`) | | Layout Undo history | 30 session steps | | Closed-panel cache | 16 per Toy | | Per-Toy workspace record | 2 MiB (`tunetoys.workspace.v1`) | | Editor Back/Forward trail | 64 locations | | Toy Undo history | 100 session edits | ## Tabs Right-click a tab, press Shift+F10 on it, click a group's … button, or right-click a floating header: the same menu appears, built by `workspaceTabMenu.tsx` from the current layout so every entry is enabled only when the layout model would accept it. The … button and the empty tab strip open the group form, which starts with a flat _Open panel_ list. Middle-click closes a tab; double-click maximizes or restores its group. | Section | Items | | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Close | Close, Close others, Close to the right, Close group. Closed panels enter the sixteen-entry recovery list; one layout Undo step per command | | Keep | Pin / Unpin tab (pinned editor views lead their group, hide the close control, survive group closes and persist); the main patch is permanently pinned. Duplicate view opens a second instance with its own camera and selection beside the first | | Arrange | Split right / down (a duplicate in a new adjacent area), Move to → new group on any side, an existing numbered group or a floating window; Float / Dock back; Maximize group / Restore | | Related | Editor tabs: open or focus the Patch, Design, Preview or Source view of the same instance. Properties and Layers: Follow the focused editor or pin to one editor | | Recover | Reopen closed tab | Workspace shortcuts use Alt+Shift so they never collide with editor keys: arrows focus neighbors, `1`–`9` focus a numbered group, `[` / `]` cycle tabs, `,` / `.` move the tab within its group, `W` closes, `P` pins, `\` / `−` split right / down, `Enter` maximizes, `F` floats or docks, `T` reopens, `Z` / `Y` undo / redo layout, `L` opens the layout chooser. Each shortcut routes through the same command list the command search shows, so disabled states match. Menus, the command search and tooltips draw reminders with key glyphs through `formatShortcut` (`⌥⇧W` on a Mac, `Alt+⇧W` elsewhere); the help sheet uses the same glyphs per key. Layout work lives in the menubar's **Window** menu, published by the dock as `workspace-*` commands: a check item per tool panel (Properties, Layers, History, Docs, Mix, Timeline, Console, Data, Files), one radio item per panel group (how compact layouts switch the visible group), **Panels** (focus, tabs, split, float, close), **Reopen closed**, and **Workspace layout** (choose, save, update, revert, presets and saved layouts as radio items, layout Undo/Redo, Reset). The dock has no toolbar row of its own; the save status sits beside the Toy title in the header. Old tab-only workspaces receive a default dock tree. Admission drops invalid or stale source references, unknown panel kinds, duplicate IDs and corrupt geometry while restoring surviving views. Denied or full storage leaves the current layout usable in memory and reports the failure. ## Mantine audit Mantine **9.7.4** was the npm registry and documentation version at this review on October 31, 2026, matching the installed packages; no upgrade was needed. | Component | Available behavior | Workspace decision | | --------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- | | [Splitter](https://mantine.dev/core/splitter/) | Nested horizontal/vertical layouts, controlled sizes, CSS-unit bounds, collapsible panes and keyboard resizing | Renders every split in the dock tree. Splitter panes remain direct children as required by its API. | | [Tabs](https://mantine.dev/core/tabs/) | Controlled active tabs, keyboard navigation, optional inactive-panel unmounting | Provides each area's tab navigation. The host owns tab identities, placement and editor state. | | [Menu](https://mantine.dev/core/menu/) | Submenus, keyboard interaction and anchored positioning | Provides area actions and shallow Arrange/Organize/Clipboard/Select-layer menus. | | [FloatingWindow](https://mantine.dev/core/floating-window/) | Viewport-constrained windows and pointer/keyboard resize handles | Renders floating editor/tool panels with resizing. A shared host pointer controller owns drag previews, docking, keyboard movement, identity and persistence. | | [Menubar](https://mantine.dev/core/menubar/) and [Toolbar](https://mantine.dev/core/toolbar/) | Application menu and grouped command surfaces | Menubar implemented: the [application menu](https://github.com/joshstovall/TuneToys/blob/main/packages/app-menu/README.md) holds the editor's commands and the dock's layout work; Toolbar remains unused. | | [ScrollArea](https://mantine.dev/core/scroll-area/) | Independent scroll viewports and viewport refs | Available when custom scrollbars are useful. Dock bodies use native overflow scrolling. | | [AppShell](https://mantine.dev/core/app-shell/) | Outer header/sidebar/footer layout | Used for app chrome; nested editor docking remains the workspace adapter's responsibility. | Splitter handles geometry and Tabs handles activation; neither exposes editor identity, moving tabs between areas, edge docking, source-aware restoration or layout presets, so the docking model is host-owned. No external docking dependency or second Frame renderer was added. ## Pins, named layouts and recovery Properties and Layers each have a Pin control and a Panel menu. Follow active editor uses the current editor selection; a pin binds the tool to one validated patch/Frame instance and its view state. Pins survive closing their editor and switching to JSON; selection changes update other tools pinned to the same view; source admission clears deleted targets. Canvas/property Rename uses a dialog even when Layers is pinned elsewhere; tree Rename stays inline. Save layout stores a named arrangement: panel geometry, editor descriptors and pin targets, with no Toy document, parameter values, cameras or selections. Apply, rename and remove live in Manage saved layouts. On the same Toy valid targets return; on another Toy, Patch/JSON map to its entrypoint and Frame/Preview to its entry Frame. Layout Undo/Redo is separate from Toy Undo/Redo and revision numbers. Splitter resize and floating drag/resize gestures each record one step; focus, tab activation and selection do not. Undo/Redo validates against the current Toy and keeps current cameras/selections. Reopen last closed panel and Recently closed restore editor identity, view state, dock position and floating bounds. A new Toy identity resets layout history. ## Floating panels Any panel can float through Panel actions → Float panel or by dragging its tab. Floating panels keep local identity, position, dimensions and dock-home anchor per Toy; dock-back recreates the original split where it can, then falls back to a reachable group. Viewport admission keeps controls reachable when screen size changes; on compact screens floating panels join the group switcher while desktop bounds stay saved. `FloatingWorkspacePanel.tsx` wraps Mantine FloatingWindow. `useWorkspaceDrag.ts` owns pointer capture, threshold, previews, docking and cancellation for tabs and headers; Mantine's native window drag is disabled so one controller handles the gesture. `workspaceDocking.ts` computes guide hit areas, tab insertion and directional focus. The move grip takes arrow keys (10 px, Shift 40 px); the resize grip supports pointer and Mantine keyboard resizing. Windows raise on interaction without touching Toy history. Recovery writes dimensions as well as coordinates because Mantine's resize handle writes inline dimensions. ## Layout chooser and workspace commands Window › Workspace layout marks the selected preset or saved layout, and leaves it unmarked while the arrangement is modified. The visual chooser renders actual split ratios, roles, tab groups and floating bounds for the three presets and named layouts; `workspacePresets.ts` supplies preview and application from one constructor. Update replaces the current named arrangement; Revert reapplies its stored arrangement or the selected preset. Selection and a bounded geometry fingerprint persist per Toy; focus, titles, selections, cameras, closed-panel caches and z-order never mark a layout Modified. The fingerprint is a UI change detector, never source identity or publication authority. | Alt+Shift shortcut | Action | | ------------------ | ------------------------ | | Arrows | Focus neighboring panel | | `[` / `]` | Previous / next tab | | Enter | Maximize / restore | | T | Reopen last closed panel | | Z / Y | Layout Undo / Redo | | L | Open the layout chooser | | F | Float / dock | The same actions appear in command search. Input fields, editing controls, composition, menus and dialogs keep native key behavior; Toy Undo remains Cmd/Ctrl+Z. Focus changes move DOM focus as well as the workspace focus marker, and the bindings appear in shortcut help. ## Editors inside the workspace `WorkspaceEditor.tsx` scopes each editor to its own tab, definition and instance path. Only the focused patch owns global graph shortcuts and clipboard listeners; inactive graph views disable React Flow deletion shortcuts and cancel previews; Frame gestures cancel when their view goes inactive. The component palette follows the retained editor when a tool panel takes focus, and palette drops carry the destination view identity so dragging into another split cannot author in the previously focused editor (see [component palette boundaries](https://github.com/joshstovall/TuneToys/blob/main/docs/architecture/settings-library.md)). ### Source JSON | Feature | Behavior | | -------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | View | The accepted portable document | | Edit JSON | Isolated text draft with live shared validation, clickable diagnostic locations and before/after review; Apply is one revision-bound `source.replace` edit; identity and revision cannot be edited; a changed base blocks application and keeps the draft for download/discard; closing a changed draft offers Keep editing/Discard | | Find | Cmd/Ctrl+F literal key/value search with counts, Enter/Shift+Enter navigation, Escape to close | | Reveal in JSON | From node and Frame/layer menus; opens or reuses a Source panel and focuses the exact path; nested instances reveal their definition's source | | Context bar | Breadcrumbs for the selected row; copies exact JSONPath (quoted bracket notation for unusual keys), a value or the enclosing node/widget/folder; Shift+F10 opens the row menu; Show in Patch/Frame centers the entity, with instance choice when needed | | Change markers | Short-lived added/changed/removed markers after admitted edits and history moves, capped at 256 entries, expiring after six seconds, computed in the browser UI only | | Preferences | Expanded branches, query/result and scroll per Toy/view under `tunetoys.source-view.v1`; admission prunes stale paths; blocked storage keeps a bounded session fallback | | Diagnostics | Import/snapshot and full-document validation errors keep structured paths and bounded rejected source; Review diagnostics highlights the rejected location; oversized or unsafe JSON gets diagnostics without a tree | Drafts, validation and diagnostics never affect the running audio host, and rejected documents never replace the accepted Toy. ### Layers, selection and navigation Properties and Layers scroll independently; changing selection cannot move Layers. Canvas selection reveals folder ancestors, clears a concealing filter and scrolls the row into view. Tree and canvas hover share the active Frame view identity. Inherited lock/hide explanations name the responsible folder; preview outlines do not unhide or unlock source members. Frame context menus keep rename, duplicate, copy/cut, visibility, locks and delete at the top level, with arrangement, folder, paste and overlap selection one submenu down, all through existing actions and atomic history. Editor Back/Forward keeps a bounded session trail of source selection/search/expansion/scroll, patch selection/camera and Frame selection/scroll. Header buttons and Alt+Left/Right restore locations, skip stale instance paths and leave portable source and both Undo histories untouched. ### History History is a docked tab that can move anywhere while the canvas stays interactive. Selecting a retained state inspects its before/after property diff without applying it; added/removed/changed stay distinct, revision counters are excluded, and large diffs show the first 500 changes with links to full snapshot previews. Changes link to current JSON/Patch/Frame identities with explicit instance choices. The comparison picker also compares the current Toy with its last device save or last acknowledged account draft, scoped to the Toy and account; missing references are reported, not substituted. Each saved-comparison property offers Revert to device save/account draft: it restores that value (or removes an added property) on a detached document, preserves unrelated edits and commits one undoable replacement; arrays restore together; identity/revision fields are protected; full validation rejects restores that would break references and shows the reason. Preview before/this state opens read-only JSON or the shared Frame renderer with compiler-resolved defaults on an independent idle host that starts no audio. Restore this state is a separate explicit action using the same retained-state path and monotonic revisions as Undo/Redo. Durable history, inverse-operation serialization and collaborative reconciliation remain planned. ## Rendering and persistence The workspace renders from external stores rather than App state. `toyDocumentStore.ts` holds the open Toy, its single authoring history and any gesture preview; panels subscribe to the committed document or the previewed `shown` document with `useSyncExternalStore`, so an inspector drag re-renders only the panels that draw it. `workspaceLayoutStore.ts` holds the layout (tabs, per-tab views and selection, dock tree) and its layout Undo/Redo; its snapshot is normalized against the committed Toy and cached per (Toy, layout) pair, and it writes the layout for the open Toy after each change (once per task). `EditorWorkspace` subscribes to it, as do the few chrome pieces that draw the active editor context (`WorkspaceChrome.tsx`: context navigation, Back/Forward, Frame clipboard and the Sound, icon and node pickers; `SharedSession.tsx`: the shared-session anchor and presence), each through a selector, so selection and layout changes do not re-render the App shell. `audioSession.tsx` holds the one runtime host and transport state. Edits go through `WorkspaceCommands` (`workspaceCommands.ts`), created once per App: every command reads the stores when it runs rather than closing over a render, so `EditorWorkspace` receives one stable object and modals, errors, playback and hub navigation do not re-render editors. `EditorNavigation` (`useEditorNavigation.ts`) keeps the Back/Forward trail and reveal requests the same way and records tab switches from the layout store after each change settles; `SourceNavigationProvider` subscribes for the editors that consume them. The App and these modules compile under React Compiler; control flow it does not support (`try`/`finally`, `throw` inside `try`) lives in plain helpers and classes outside components. After the first Workspace visit the App keeps `EditorWorkspace` mounted while Explore, Library or Settings is shown. Its root is `hidden`, `inert` and `display: none`, so hidden editors are not focusable, announced or hit-tested, and returning keeps the dock, React Flow canvases (store, nodes, viewport, selection) and Frame designers without rebuilding them. Effects stay alive, so global input is gated instead: the App clears the active editor while the Workspace is hidden, which turns off Patch keyboard and clipboard listeners, Source search keys and Frame gestures; `DockWorkspace` receives `shortcuts={false}`; and App-level document shortcuts (node search, Undo/Redo, editor Back/Forward) apply only while the Workspace is visible. Playback and its Shift+Space shortcut continue across pages. React `<Activity>` was evaluated and rejected: hiding tears effects down and React Flow resets its whole store in effect cleanup, so every node remounted on return. Hidden editors also stop their UI work. The App wraps the mounted `EditorWorkspace` in `WorkspaceVisibilityProvider` (`workspaceVisibility.tsx`), which bridges to the shared renderer's `FrameLiveProvider`. Inside a paused region every Frame widget, node control, graphical editor, Context control and MIDI monitor subscribes to the audio host through `useLiveSubscribe`, which listens to nothing while paused, so the 80 ms telemetry poll no longer re-renders meters, scopes, knobs and live node values behind a hub page; `SignalInspector` releases its capture lease and a playing Screen video pauses. The same gate applies while the browser tab is hidden. Audio, the engine's telemetry production and the session Transport (rendered outside the workspace, with its own meter) are untouched. On return React resubscribes and re-reads each snapshot, so the first visible frame shows current meters with dock, selection, scroll and zoom intact. Frame viewports, the preview device size and the spectrogram ignore the zero size a `display: none` region reports, so returning paints the last layout at once instead of re-measuring from zero; other resize observers re-measure on return. The page comes from the URL through `hubRoutes.ts` (`useHubPage`), backed by the `router.ts` location store, so back/forward between Workspace and a hub page shows or hides the same mounted editor rather than remounting it; the `#toy=` recovery fragment, `?starter=`/`?new=1` chooser handoff and `?collaborate=` session survive page switches. Panels and dialogs that open on demand load lazily: the History and Docs dock panels suspend to a small status line inside their dock area (the area keeps its size), and App-level overlays (node Spotlight, Media library, shortcuts, About) suspend to nothing; the node Spotlight chunk is fetched shortly after the editor first mounts so keyboard opening does not drop input. The shared-session bar suspends to an identically styled placeholder so the editor does not shift. ## Verification | Check | Covers | | ---------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `apps/web/test/dock-layout.test.mjs` | Moves/grouping/pruning, ordering, malformed restoration, source separation, duplicate-view cameras/selections, bounded nesting, storage | | `apps/web/test/workspace-control.test.mjs` | Nested pinned contexts across close/reload, stale-source admission, bounded layout history and gesture recovery, retained cameras/selections, closed/floating restoration and dock-home anchors, cross-Toy adaptation, malformed/blocked storage | | `apps/web/test/workspace-ux.test.mjs` | Guide geometry, forward tab insertion, floating bounds, spatial focus, shortcut admission, layout change detection, Update/Revert/adaptation, shared presets, editor floating roles | | `apps/web/test/workspace-tabs.test.mjs` | Tab pins (ordering, persistence, permanent main patch), group closes skipping pins and recording recovery, keyboard tab reordering, related views, tab shortcut chords | | `source-draft`, `source-navigation`, `source-workflows`, `history-review` fixtures | JSON drafts, find/reveal, context bar, history diffs and saved-copy reverts | | Browser review | Pointer docking/reordering, tab menus (right-click, Shift+F10, … button, floating headers), middle-click close, floating, keyboard focus/maximize, text-field guards, command search, visual presets, named Update/Revert/reload, split editors, independent cameras, compact switching, nonmodal History, pinned selection while JSON is active, phone layouts | Root `npm run check` remains required. ## Implementation - [apps/web](https://github.com/joshstovall/TuneToys/blob/main/apps/web/README.md) — `EditorWorkspace.tsx`, `toyDocumentStore.ts`, `workspaceLayoutStore.ts`, `workspaceCommands.ts`, `useEditorNavigation.ts`, `audioSession.tsx`, `hubRoutes.ts`, `router.ts`, `DockWorkspace.tsx`, `dockLayout.ts`, `WorkspaceEditor.tsx`, `FloatingWorkspacePanel.tsx`, `useWorkspaceDrag.ts`, `workspaceDocking.ts`, `workspacePresets.ts`, `HistoryPanel.tsx`, `SourceContextBar.tsx` - [packages/authoring](https://github.com/joshstovall/TuneToys/blob/main/packages/authoring/README.md) — history, `source.replace`, saved-copy comparison ## Related - [Patch editor](https://docs.tune.toys/contributors/patch-editor/) - [App shell, Settings and Library](https://github.com/joshstovall/TuneToys/blob/main/docs/architecture/settings-library.md) - [Appearance](https://github.com/joshstovall/TuneToys/blob/main/docs/architecture/appearance.md) — layer folders, multi-selection and bulk styling in the Frame designer - [Revisioned authoring operations](https://github.com/joshstovall/TuneToys/blob/main/docs/specs/operations.md#local-session-history) - [Hotwire / synth.ist UX audit](https://github.com/joshstovall/TuneToys/blob/main/docs/reference/ux-audit.md) --- URL: https://docs.tune.toys/releases/ Title: Releases App releases version the website, API and browser app together. They are separate from **product versions**, which creators publish for their own Toys. How a release is cut, qualified and rolled back is documented for contributors in [Releasing TuneToys app versions](https://docs.tune.toys/contributors/releases/). Subscribe with the [RSS feed](https://docs.tune.toys/releases/feed.xml). Documentation for earlier app versions is kept as immutable snapshots; see [Versions](https://docs.tune.toys/v/). --- URL: https://docs.tune.toys/releases/0-0-2/ Title: 0.0.2 (in development) **Status:** in development. This entry is a placeholder that is replaced with real release notes when the version is promoted. ## Documentation - This documentation site, with generated node pages, rendered help patches, the User Manual, guides, developer docs and a Contributors section. ## Platform - Listings and storefronts, versions and changelogs, notifications, localization hooks, sharing and embedding, and the developer platform are part of this sprint's plan. See the [sprint scope](https://github.com/joshstovall/TuneToys/blob/main/docs/delivery/sprints/v0.0.2/README.md) for status; nothing here claims a feature is live before it is qualified. --- URL: https://docs.tune.toys/glossary/ Title: Glossary The core vocabulary comes from the product vision. More terms follow, alphabetically. ### Cable A connection from one node's output port to another's input port. See [Cables and channels](https://docs.tune.toys/concepts/cables/). ### Entitlement The server's record that your account may download a paid product. ### Help patch A small Toy attached to a node page that teaches one idea. See [Nodes](https://docs.tune.toys/nodes/). ### Node One processing unit in a Patch, such as a delay or an oscillator. ### Parameter A named numeric value of a node or a Toy, with a range, default, unit and scale. A parameter exposed to a Frame is a **public control**. ### Port A typed input or output of a node. Types are audio, control and event. ### Preset A saved set of parameter values. See [Presets](https://docs.tune.toys/concepts/presets/). ### Product A publisher's listed offering, with versions, offers, reviews and documents. ### Publisher An account or team that owns products and a storefront at `@handle`. ### Storefront A publisher's public page, designed from validated theme tokens. ## Core vocabulary - **Toy**: The portable authored creation; can be a component or complete application - **Patch**: Familiar editor/community term for a Toy graph, not a separate file model - **Definition / group**: Reusable implementation with typed ports and public parameters - **Instance**: One use of a definition with independent values, DSP state, and observations - **Frame**: Explicit playable UI bound to one definition in an instance context - **Project**: Editable Toy source and working assets/history - **Release**: Immutable resolved content plus evidence and distribution terms - **Catalog entry**: Mutable title/discovery/visibility metadata pointing to releases - **Library**: A user's bookmarks, tags, ratings, acquisitions, and installed/cached releases - **Player**: TuneToys plugin/application that loads Toys; not a general third-party plugin host - **Independent plugin**: A compiled Toy exported under its own persistent plugin identity