{"id":"02ebe42e-7040-409f-aa2c-c48f66906a92","slug":"jinglescript","kind":"mcp","name":"jinglescript","summary":"A JSON score language for jingles and sound effects that an LLM writes, rendered to audio plus a timing map for syncing animation. MCP server and GUI Chat Protocol plugin included.","owner":"receptron","repoUrl":"https://github.com/receptron/jinglescript","license":"MIT","licenseTier":"permissive","trust":"community","stars":3,"match":null,"fewStars":true,"format":"mcp","createdAt":"2026-10-08T22:01:46.000Z","addedAt":"2026-10-21T18:13:59.422Z","logoUrl":"https://avatars.githubusercontent.com/u/168242058?v=4","linkOnly":false,"collection":null,"skillPath":null,"categories":["utilities","communication","cloud-infra"],"tags":[],"runtime":"node","local":true,"cloudCapable":false,"cloudCapability":"unknown","cloudReadyOnPlatform":false,"needsApiKey":false,"reviewed":false,"syncedAt":"2026-10-26T02:13:09.328Z","sample":false,"url":"https://conduits.dev/tools/jinglescript","language":"TypeScript","transports":["stdio"],"install":{"npm":"jinglescript"},"requirements":{"env":[],"upstreamMayCharge":false},"tools":null,"readme":"# JingleScript\n\nA small JSON score language for jingles and sound effects, written by an LLM (or a person), rendered\nto audio plus a **timing map** so animation can land exactly on the music.\n\n```\nrequest ──► LLM ──► score (JSON) ──► render ──► audio (WAV)\n                                           └──► timing.json (cues, beats, note onsets, lyrics)\n```\n\n**Try it in your browser: [the playground](https://receptron.github.io/jinglescript/)** — pick an\nexample and edit it on a timeline (drag sounds and cues, double-click to add one, change pitch and\nsound in a panel) or as JSON, and hear it rendered in the page (no server).\n\n- Everything is synthesized in code — except two things that play recordings. `grandpiano` plays a\n  real grand piano from the [Versilian Community Sample Library](https://github.com/sgossner/VCSL)\n  (CC0, public domain). Those samples are not in the package: the first render that uses\n  `grandpiano` downloads the notes it plays (about 1.2 MB each) into `~/.cache/jinglescript`\n  (`JINGLESCRIPT_CACHE` overrides it). The recorded sound effects (ding, glass, metal, punch, book,\n  door, dice, cards, cloth, and the footstep surfaces) are CC0 recordings by\n  [Kenney](https://kenney.nl), and five more (applause, shutter, coin, page, droplet) were generated\n  with Stability AI's Stable Audio 3 and released as CC0; all ship in the package (`samples/sfx/`,\n  2.0 MB; sources, prompts and licences in [samples/sfx/SOURCES.md](samples/sfx/SOURCES.md)). Everything else needs no samples and no\n  network. What you render is yours to use.\n- Deterministic: the same score and seed give bit-identical audio on every machine.\n- Cues are named moments (`\"hit\": { \"seconds\": 1.5 }`); notes are placed on them, and the timing map\n  reports them, so an animation reads `cues.hit` instead of hard-coding a time.\n\n**Status: early (0.x).** Twelve instruments (marimba, xylophone, glockenspiel, vibraphone, music\nbox, piano, grandpiano (sampled), organ, ukulele, piccolo and trumpet — both synthetic-sounding —\nand clap) and twenty-five sound effects: eleven synthesized (clock, footsteps, heels, tapdance,\nknock, pistol, laser, whoosh, riser, impact, pop), nine recorded (ding, glass, metal, punch, book,\ndoor, dice, cards, cloth) and five generated with Stable Audio 3 (applause, shutter, coin, page,\ndroplet), plus recorded surfaces for footsteps.\nChords by name (`\"chord\": \"G7\"`) and tab-style strums (`\"strum\": \"D-DU-UDU\"`). An MCP server and a\nGUI Chat Protocol plugin with a player view. Custom instruments are planned.\n\n```sh\nnpm install jinglescript\n```\n\n## Try it\n\nThe package requires Node ≥ 22.12. Running from a clone, as below, runs the TypeScript sources\ndirectly and needs Node ≥ 22.18 (or use `npx jinglescript …` with the package):\n\n```sh\nyarn install\nnode src/cli.ts check examples/hatena-marumo-a.json\nnode src/cli.ts render examples/hatena-marumo-a.json -o out/opening.wav\n#   writes out/opening.wav and out/opening.timing.json\nnode src/cli.ts demo ukulele -o out/   # hear a sound across its range (or an effect's variants)\nnode src/cli.ts instruments            # every instrument and sound effect\nnode src/cli.ts guide                  # how to write a score (for LLMs)\nnode src/cli.ts schema                 # the JSON Schema (also in schema/jinglescript-1.json)\nnode src/cli.ts notation examples/learn-happy-birthday.json -o out/birthday.musicxml\n#   the score as sheet music, for MuseScore and the like\n```\n\nThe `learn-*` examples are short lessons written for sheet music: the C major scale with its\nsolfège, note values from quarters to triplets, Twinkle Twinkle and Happy Birthday on a piano\ngrand staff, and Row, Row, Row Your Boat in 6/8.\n\n`examples/piano/` holds fifteen full-length public-domain piano pieces (Debussy, Chopin, Liszt,\nBeethoven, Rachmaninoff, Schubert, Satie, Tchaikovsky, Joplin) written for `grandpiano` — what a\n2–7 minute score looks like.\nThey are in the repository only, not the npm package; see [examples/piano/README.md](examples/piano/README.md).\n\n## Use it from Claude (MCP)\n\n`jinglescript mcp` is an MCP server with a single tool, **`manageJingleScript`**. Its `action`\nsays what to do: `getGuide`, `getSchema`, `listInstruments`, `getInstrument`, `checkScore`,\n`renderScore`. Rendered files go to the folder given by `--out` (default `out/jinglescript`).\n`checkScore` and `renderScore` take the score inline (`score`) or as a file (`path`: a `.json`\nfile, relative to the directory the server runs in or absolute), so a long score need not travel\nin the tool call; renders are then named after the file.\n`renderScore` answers with a summary of the timing map (tempo, duration, cues, `audibleUntil`,\nnotes per track) and the path of the full `timing.json`; pass `includeTiming: true` to get every\nbeat, note and lyric in the answer too. A score written for sheet music (one with a `meter`,\n`key` or `pickup`, or rendered with `view: \"notation\"`) also gets a `.musicxml` file next to the\naudio.\n\nClaude Code:\n\n```sh\nclaude mcp add jinglescript -- npx -y jinglescript mcp --out /path/to/renders\n```\n\nClaude Desktop (`claude_desktop_config.json`):\n\n```json\n{\n  \"mcpServers\": {\n    \"jinglescript\": {\n      \"command\": \"npx\",\n      \"args\": [\"-y\", \"jinglescript\", \"mcp\", \"--out\", \"/path/to/renders\"]\n    }\n  }\n}\n```\n\nThen ask for a jingle in a sentence (\"a 5-second ukulele opening that lands on a hit at 1.5 s\").\n\n## Use it from Claude Code (skill)\n\n`skills/jinglescript/SKILL.md` is a Claude Code skill that needs no MCP setup: it reads the\nguide, writes the score to `jingles/<name>.json`, runs `npx -y jinglescript check` until it\npasses, and renders with `npx -y jinglescript render`. Install it for yourself:\n\n```sh\nmkdir -p ~/.claude/skills/jinglescript\ncurl -fsSL https://raw.githubusercontent.com/receptron/jinglescript/main/skills/jinglescript/SKILL.md \\\n  -o ~/.claude/skills/jinglescript/SKILL.md\n```\n\n(or copy it into a project's `.claude/skills/jinglescript/`). Then ask Claude Code for a jingle.\nThe skill runs the latest published `jinglescript` through npx, as the MCP setup above does; to stay\non one version, replace `npx -y jinglescript` in it with `npx -y jinglescript@<version>`, and install\nthe file from that release's tag instead of `main`.\n\n## Put it on a video\n\n[docs/video-sound-effects.md](docs/video-sound-effects.md) walks through adding footsteps, bonks and\na jingle to two shots of *Big Buck Bunny* (CC BY) with ffmpeg: find the moments, name them as cues,\nrender, mix, and check the frames line up; then burn in telops on cues and karaoke captions from the\nlyrics, with ffmpeg's ASS subtitles.\n\n## Hear it in a GUI chat (GUI Chat Protocol)\n\n`gui-plugin/` is a [GUI Chat Protocol](https://github.com/receptron/gui-chat-protocol) plugin with the same tool,\n`manageJingleScript`, for hosts such as MulmoTerminal and MulmoClaude. A rendered jingle opens in a\n**player view**: waveform, beats, named cues you can click to jump to, and a lane of notes per\ntrack, with downloads of the audio and of the music as MIDI. For teaching, the same view shows\n**sheet music** with a cursor following playback: `renderScore` takes `view: \"notation\"` (or\n`\"waveform\"`, the default), `autoplay: true` to start playing as the card appears, and\n`notifyWhenDone: true` to have the view tell the LLM, in one chat message, when the user has\nheard it to the end — so a lesson can go on by itself. See [gui-plugin/README.md](gui-plugin/README.md) for adding it to a host.\n\n## Library\n\n```ts\nimport { parseScore, render, loadSamples, toWav, checkScore, getSchema, getAuthoringGuide } from \"jinglescript\";\n\nconst score = parseScore(json); // zod-validated; throws with every problem, each with a path and a hint\nconst samples = await loadSamples(score); // grandpiano's recordings (cached; nothing when it is not used)\nconst { audio, sampleRate, timing } = await render(score, { samples }); // audio: [left, right] Float32Array\nawait writeFile(\"out/jingle.wav\", toWav(audio, sampleRate));\nawait writeFile(\"out/jingle.timing.json\", JSON.stringify(timing, null, 2));\n```\n\n`render` returns a Promise and never touches the network. In Node it renders on a worker thread,\nso a long piece, or a vocal sung by NEUTRINO, never blocks your event loop (a server keeps\nanswering); the audio is the same, bit for bit, as rendering in place. `loadSamples` fetches what\na score's `grandpiano` notes play (or reads it from the cache; `{ download: false }` keeps it\noffline).\n`renderToFiles(score, dir, stem)`, the CLI and the MCP tool do this for you.\n\n`scoreToMidi(score)` gives the music as a Standard MIDI File (`Uint8Array`, type 1) to edit in a\nDAW or notation program: a track per score track, at the times the audio plays them, with General\nMIDI sounds for the built-ins (claps, knocks, the clock and impacts on the drum channel; the\npistol as Gunshot). A custom instrument gets its base's sound. Effects with no General MIDI sound\n(whoosh, riser, laser, pop, footsteps, heels, tap dance) are markers named after the effect;\ncues are markers, lyrics are lyric events. Pitches are as they sound (a glockenspiel's written C5\nis C6).\n\n`scoreToMusicXml(score)` gives the music as sheet music (MusicXML 4.0, a string), and\n`scoreToNotation(score)` the bars, notes and rests it is written from, with a warning (and the\nnote's path) for every time it had to round onto the grid and every note it had to shorten. A score\nsets its `meter`, `key` and `pickup` for this; they never change the sound. Times are rounded to\n32nd notes and triplets, a note without `len` is written up to the next one, one staff holds one\nline of notes, a piano's right and left hand share a grand staff, chords written by name are\nspelled from their root and print their symbol, lyrics go under their notes, and sound effects are\nleft off.\n\nFor LLMs and agents: `getSchema(part?)`, `getAuthoringGuide()`, `listInstruments()`,\n`getInstrument(name)` and `checkScore(json, { notation? })` (never throws; returns errors with JSON\npaths and hints, plus the resolved cue times; with `notation: true`, or for a score with a `meter`,\nalso what the sheet music would round or shorten).\n\n### In the browser\n\n`jinglescript/browser` is the same library without the parts that need Node (files, ffmpeg, the\nMCP tool): parse and check, `render`, `toWav`, `scoreToMidi`, `scoreToMusicXml`, the timing map,\nand the API for LLMs.\nIt renders in the page, with no server, to the same bit-identical audio. There `render` works on\nthe page's own thread (its Promise resolves when it is done); run it in a Web Worker to keep a long\nrender from holding up the page.\n\n```ts\nimport { parseScore, render, loadSamples, toWav } from \"jinglescript/browser\";\n\nconst score = parseScore(json);\nconst samples = await loadSamples(score); // only when it plays grandpiano or a recorded effect\nconst { audio, sampleRate, timing } = await render(score, { samples });\nconst context = new AudioContext({ sampleRate });\nconst buffer = context.createBuffer(2, audio[0].length, sampleRate);\naudio.forEach((channel, i) => buffer.copyToChannel(channel, i));\n// play `buffer`, or offer toWav(audio, sampleRate) as a download\n```\n\nThe browser's `loadSamples` fetches what the score plays:\n\n- `grandpiano`: the first 4.5 s of each VCSL recording it plays, through jsDelivr's GitHub mirror\n  (GitHub's raw server refuses a browser's ranged request), checked against the same SHA-256 as in\n  Node, and kept in the page's Cache API (`cache`), so a piece is downloaded once.\n- Recorded sound effects: from `sfxBaseUrl`, by default this version of the package on jsDelivr\n  (`https://cdn.jsdelivr.net/npm/jinglescript@<version>/samples/sfx/`). Serve `samples/sfx/` yourself\n  and pass its URL to keep the page off the CDN. These have no checksum, so they are left to the\n  browser's HTTP cache.\n\nOptions: `sfxBaseUrl`, `pianoBaseUrl`, `cache` (a Cache API name, or `false`), `onDownload`,\n`seed`, `fetch`. Audio is WAV only (MP3 and OGG need ffmpeg).\n\n## How to write a jingle\n\nThis section is the authoring guide an LLM gets from `jinglescript guide`, `getAuthoringGuide()`\nor the MCP action `getGuide`; it is generated from `src/guide.ts` (`npm run readme`).\n\n<!-- guide:start -->\n\nA JingleScript score is JSON (format \"jinglescript/1\") describing a short piece of music — a\njingle, a song (played, or sung with `sing`), an exercise shown as sheet music — and sound\neffects, plus named **cues** an animation syncs to. Get the exact schema with getSchema() /\n`jinglescript schema` / the MCP action `getSchema`; check your score\nwith checkScore() / `jinglescript check` / the MCP action `checkScore`, and fix every error it\nreports before rendering. A long score is easier to keep in a `.json` file: write it there and\npass `path` to `checkScore` / `renderScore` instead of the score itself.\n\n### Work in this order (a jingle for a video)\n\nFor a song see **Songs and melodies** below; for a lesson, **Sheet music**.\n\n1. **Cues first.** Write down the moments the video needs, in seconds, as cues:\n   `\"cues\": { \"hit\": { \"seconds\": 1.5 }, \"voice\": { \"seconds\": 1.8 } }`. Never convert seconds to\n   beats yourself — give cues in seconds and place notes on them. Every time the request\n   mentions (a start, a landing, a logo, a cut) becomes a cue, even when it happens to fall on a\n   beat: the animation reads cues, not notes.\n2. **Tempo and length.** 100–140 BPM suits most jingles. `length` is the whole audio; leave\n   1–3 s after the last note for it to ring out, more if a voice-over follows.\n3. **Music on the cues.** Place the big moment exactly on its cue (`\"at\": \"hit\"`), and the lead-in\n   before it on the beat grid (`\"at\": 0`, `0.5`, `1` … in beats) or relative to the cue\n   (`\"at\": \"hit-1\"` = one beat before, `\"hit+0.5\"` = half a beat after).\n4. **Check, fix, render.**\n\n### Time\n\n- `at` as a number is **beats** from the start (one beat = a quarter note at `tempo`; 0.5 is an\n  eighth note, 0.25 a sixteenth).\n- `at` as `{ \"seconds\": 0.86 }` is clock time, for a note the animation pins to a moment.\n- `at` as a cue name is that cue's time; `\"cue+n\"` / `\"cue-n\"` offsets it by n beats.\n- `repeat` plays a note again every `every` beats, `count` times in total or `until` a time:\n  `{ \"at\": 0, \"repeat\": { \"every\": 1, \"until\": \"hit\" } }`.\n\n### Notes and chords\n\n- `pitch` is \"C4\" (middle C), \"F#3\", \"Bb5\"; a list is a chord: `[\"C5\", \"E5\", \"G5\"]`.\n- `vel` 0–1 is how hard: 0.8 normal, 1.0 for the hit, 0.3–0.4 for a soft echo.\n- Several tracks play together. Put the melody and the bass on separate tracks (even with the same\n  instrument), each with its own `gain` (dB) and `pan` (0 left … 1 right).\n\n### Chords and strums\n\n- Write a chord by name with `chord` instead of `pitch`: \"C\", \"Am\", \"G7\", \"Fmaj7\", \"Bb\", \"F#m7\",\n  \"Dsus4\". The instrument voices it — on the **ukulele** you get the real chord shape (C = 0003,\n  F = 2010, G7 = 0212), on other instruments the chord stacked from octave 4. Use `pitch` lists\n  when you want exact notes.\n- `strum` plays a chord string by string, ~15 ms apart, slightly loose like a real hand (seeded,\n  so the same score always sounds the same). `\"down\"` or `\"up\"` for one stroke, or a pattern\n  with one character per eighth note: D down, U up, d/u soft, - rest.\n  - `\"D-DU-UDU\"` — the classic island strum, one 4-beat bar.\n  - `\"D-D-D-D-\"` — plain downstrokes on every beat.\n  - `\"D-DUD-DU\"` — a busier pop strum.\n  Write one note per bar with its chord and pattern; add `\"repeat\": { \"every\": 4, \"count\": 2 }`\n  to keep the same chord for two bars.\n- End on a single stroke: `{ \"at\": \"end\", \"chord\": \"C\", \"strum\": { \"pattern\": \"down\", \"spread\": 25 } }`\n  (a slower, wider final strum). A stroke placed on a cue starts exactly on it.\n\n#### Example: island strum on ukulele\n\n```json\n{\n  \"format\": \"jinglescript/1\",\n  \"tempo\": 100,\n  \"length\": { \"seconds\": 12 },\n  \"cues\": { \"end\": { \"beats\": 16 } },\n  \"tracks\": [\n    { \"instrument\": \"ukulele\", \"notes\": [\n      { \"at\": 0, \"chord\": \"C\", \"strum\": \"D-DU-UDU\", \"vel\": 0.75 },\n      { \"at\": 4, \"chord\": \"F\", \"strum\": \"D-DU-UDU\", \"vel\": 0.75 },\n      { \"at\": 8, \"chord\": \"G7\", \"strum\": \"D-DU-UDU\", \"vel\": 0.75 },\n      { \"at\": 12, \"chord\": \"C\", \"strum\": \"D-DU-UDU\", \"vel\": 0.75 },\n      { \"at\": \"end\", \"chord\": \"C\", \"strum\": { \"pattern\": \"down\", \"spread\": 25 }, \"vel\": 0.9 }\n    ] }\n  ]\n}\n```\n\n### Instruments\n\nEvery instrument and its range is listed in the schema (`tracks[].instrument`) and by\nlistInstruments() / `jinglescript instruments`. Rules of thumb:\n\n- **Struck and plucked** (marimba, xylophone, glockenspiel, vibraphone, musicbox, ukulele, clap) ring out on their own; `len` is ignored. **Sustained** (organ, piccolo, trumpet) hold each\n  note until the track's next note unless you give `len` in beats.\n- **grandpiano** is the piano: a recorded grand (range A0–A7). Its samples download on first use\n  (about 1.2 MB per note), so its first render needs the network.\n- **grandpiano has dampers**, like a real piano. Without `len` a note rings out (about 3 s). With\n  `len` the key is held that long and the note stops when it ends — write it for staccato, short\n  notes and rests. The track's `pedal` keeps released notes ringing until it lifts:\n  `\"pedal\": [{ \"at\": 0, \"until\": 4 }, { \"at\": 4, \"until\": 8 }]`, a new stretch where the harmony\n  changes. Striking a key again stops the note still ringing on it. A piece written for two hands\n  on two tracks needs the pedal on both.\n- **musicbox** and **piccolo** sound an octave above the written pitch.\n- **piccolo** and **trumpet** sound synthetic; use them for a playful line, not for realism.\n- **clap** takes no pitch. Use `repeat` for claps on every beat. The default is a drum-machine\n  clap; `\"variant\"` can be \"snappy\", \"hands\" (one person) or \"group\" (a few people).\n- A second instrument doubling the melody quietly adds colour: glockenspiel an octave up at\n  `\"gain\": -6` over a marimba melody makes it sparkle.\n- All instruments are balanced to the same loudness at the same velocity; use track `gain` to\n  put one behind another.\n\n### Sound effects\n\nEffects go in tracks like instruments and land on cues exactly like notes; the timing map's `t`\nis the start of the sound, so an animation frame on it lines up.\n\n| Effect | Variants (first = default) | Notes |\n|---|---|---|\n| clock | tick, tock | `\"variant\": [\"tick\", \"tock\"]` with `repeat` for a ticking clock |\n| footsteps | left, right, carpet, concrete, snow, wood | `\"variant\": [\"left\", \"right\"]`, `repeat` every 0.5–1 beat to walk; the surfaces are recorded steps (`\"variant\": \"wood\"` with `repeat`) |\n| heels | left, right | high-heel steps; walk the same way |\n| knock | door, table | \"knock-knock\": two notes about half a beat apart |\n| tapdance | toe, heel, shuffle | |\n| pistol | shot, cartoon | |\n| laser | — | optional `pitch` = where the \"pew\" starts (default about D7); `len` = sweep length (default 0.25 s) |\n| whoosh | soft, fast | `len` = length (default 0.5 s); place with `end` to finish on a cut |\n| riser | noise, tone | builds into a hit: `{ \"end\": \"hit\", \"len\": { \"seconds\": 2 } }` (default 2 s) |\n| impact | soft, hard | the boom a reveal lands on |\n| pop | — | optional `pitch` = starting pitch (default about E6); things appearing, text popping in |\n| ding | — | recorded: a struck bell; a correct answer, a reveal |\n| glass | — | recorded: glasses clinking, a toast |\n| metal | — | recorded: a light metal clang |\n| punch | hit, soft | recorded: a punch or slap; \"soft\" is a thud onto a cushion |\n| book | flip, place | recorded: a page turned; a book set down |\n| door | close, creak | recorded: \"creak\" swells in over about 0.8 s; for knocking use knock |\n| dice | throw, die, shake | recorded: dice thrown, one die, dice rattled (1.5 s) |\n| cards | place, fan | recorded: \"place\" slides the card first, so it lands 0.1–0.15 s after the note |\n| cloth | — | recorded: clothes rustling |\n| applause | — | generated: a small audience clapping, fading out over about 5 s |\n| shutter | — | generated: a camera shutter click |\n| coin | — | generated: a coin dropped on a table, a clink and a short rattle |\n| page | — | generated: one page turned slowly; softer and longer than book's \"flip\" |\n| droplet | — | generated: one water drop into still water, a \"plop\" |\n\n- `end` (instead of `at`) places an effect with a length by when it **finishes** — a riser or\n  whoosh that stops exactly on the cut. `len` is beats, or `{ \"seconds\": n }` when the request\n  gives seconds.\n- checkScore lists where every note and effect resolved to (`timeline`), so you can confirm an\n  effect placed by `end` starts where you meant.\n- Close, dry effects (ticks, footsteps, taps) sound better outside the reverb: give their track\n  `\"reverb\": false`.\n- Short impulses (ticks, steps, taps, knocks) peak like an instrument note; other effects are as\n  loud as instruments at the same velocity; put them under the music with track\n  `gain` (-4 to -8 dB) unless they are the point.\n- The recorded effects play real recordings; a variant with two recordings alternates between\n  them at random (seeded), so a repeat does not sound the same every time.\n- Big impacts are very peaky; a jingle with one may come out a little quieter than the loudness\n  target (the renderer reports it).\n\n### Custom instruments\n\nWhen no built-in sounds right, define your own under top-level `\"instruments\"` and use its name\nin a track like a built-in. Names start with a lowercase letter (`\"softBell\"`) and must not be a\nbuilt-in's. Three forms, from easiest to most flexible — use the first that does the job:\n\n**1. Tweak a built-in** with `base` and any of: `params` (`decay`, `brightness`, `attack`),\n`transpose` (semitones), `detune` (cents), `gain` (dB), `variant`.\n\n```json\n\"instruments\": {\n  \"softBell\": { \"base\": \"glockenspiel\", \"params\": { \"decay\": 1.8, \"brightness\": -0.5, \"attack\": 0.01 } },\n  \"deepBoom\": { \"base\": \"impact\", \"variant\": \"hard\", \"params\": { \"brightness\": -0.6 } }\n}\n```\n\n- `decay` multiplies how long it rings: 0.5 half, 2 twice. Above 1 only for built-ins that have a\n  block definition (marimba, xylophone, glockenspiel, vibraphone, musicbox, ukulele, organ).\n- `brightness` -1 (dark, muffled) … 1 (bright, crisp). `attack` seconds of fade-in: 0.02–0.08\n  softens the strike, 0.2+ swells in.\n\n**2. Layer** 2–6 sounds that play together on every note; each layer takes the same fields as a\ntweak, plus `delay` (seconds after the onset). Give the note a chord as usual: pitched layers\nplay every pitch of it, layers without pitch (an impact, a clap) play once.\n\n```json\n\"bigHit\": { \"layers\": [\n  { \"base\": \"grandpiano\" },\n  { \"base\": \"impact\", \"variant\": \"soft\", \"gain\": -8 },\n  { \"base\": \"glockenspiel\", \"transpose\": 12, \"gain\": -12 }\n] }\n```\n\n**3. Build from blocks.** Sources are mixed: `osc` (sine, triangle, saw, square, pulse at a\n`ratio` of the note), `modes` (struck partials: bars, bells), `noise` (clicks, breath, hiss),\n`string` (a plucked string). Shapers act on the whole sound: `env`, `filter`, `pitchEnv` (a\npitch glide), `lfo` (vibrato or tremolo). Every time is in seconds; `decay` means \"seconds until\nsilent\".\n\n```json\n\"zap\": { \"kind\": \"sfx\", \"pitch\": \"C7\", \"blocks\": [\n  { \"osc\": \"square\", \"level\": 0.6 },\n  { \"pitchEnv\": { \"from\": 0, \"to\": -36, \"time\": 0.25, \"curve\": \"linear\" } },\n  { \"filter\": \"lowpass\", \"cutoff\": 5000 },\n  { \"env\": { \"attack\": 0.002, \"decay\": 0.3 } }\n] },\n\"warmPad\": { \"blocks\": [\n  { \"osc\": \"saw\", \"detune\": -7 }, { \"osc\": \"saw\", \"detune\": 7 },\n  { \"filter\": \"lowpass\", \"cutoff\": 1800 },\n  { \"env\": { \"attack\": 0.15, \"decay\": 0.5, \"sustain\": 0.7, \"release\": 0.6 } },\n  { \"lfo\": \"vibrato\", \"rate\": 5, \"depth\": 8, \"delay\": 0.3 }\n] },\n\"bell\": { \"blocks\": [\n  { \"modes\": [ { \"ratio\": 1, \"level\": 1, \"decay\": 4 }, { \"ratio\": 2.76, \"level\": 0.4, \"decay\": 1.5 }, { \"ratio\": 5.4, \"level\": 0.2, \"decay\": 0.6 } ] },\n  { \"noise\": \"white\", \"burst\": 0.003, \"level\": 0.1 }\n] }\n```\n\n- `env` with `sustain` 0 is struck (rings out over `decay`); `sustain` above 0 holds for the\n  note's `len` like the organ. A source with its own `env` ignores the whole sound's.\n- `\"kind\": \"sfx\"` makes a sound effect: `pitch` is the default when a note gives none; `length`\n  makes it last `len` (default `length` seconds) and lets notes be placed by `end`.\n- getInstrument(\"marimba\") (and the other block built-ins) shows the built-in as blocks: copy it\n  and change ratios, decays or the click to make a relative of it.\n- You do not set levels: every custom sound is balanced against the marimba, and the engine keeps\n  it clean (no aliasing, clicks, DC or runaway levels). Use track `gain` or layer `gain` to mix.\n\n### Lyrics\n\nWords can ride on the melody, so a player shows them karaoke-style: the current line, the next\none, and a wipe across each syllable as its note plays. The words are text on the notes, timed\nexactly by them; the instrument plays the melody, unless the track has `sing` (see **Songs and\nmelodies**), which sings them.\n\n- Put `lyric` on each melody note: one syllable per note. In Japanese one mora (`\"ハ\"`,\n  `\"テ\"`, `\"ナ\"`; a contracted sound such as `\"きょ\"` is one mora, one note); in English one\n  syllable, ending in `-` when the word continues on the next note (`\"hap-\"`, `\"py\"` shows as\n  \"happy\"). Words are spaced for you, except between Japanese syllables.\n- `\"lyric\": \"_\"` holds the previous syllable over this note too (one syllable sung over two\n  notes).\n- Notes without `lyric` may sit among them on the same track (an instrumental fill, an echo\n  after the hit); they show no text.\n- When a word should land on a cue, put its stressed syllable there (`\"day\"` of \"today\" on\n  `\"hit\"`, `\"to-\"` half a beat before).\n- `\"lineEnd\": true` on the last syllable of a line starts a new line after it. Keep lines short:\n  one phrase of the melody, 2–4 seconds.\n- The melody comes first, the words go onto its notes. Count the syllables and check that the\n  melody has a note for each (plus any `\"_\"`); when it has too few, split a note (a quarter into\n  two eighths) or add a pickup — don't build the melody as one note per syllable on an even beat.\n  A chord takes one lyric; percussion and effects take none; a note with `repeat` takes none\n  (write the notes out).\n- checkScore lists each line with its time (`lyrics`), so you can confirm the words land where\n  the request wants them.\n\n```json\n{ \"instrument\": \"marimba\", \"name\": \"melody\", \"notes\": [\n  { \"at\": 0, \"pitch\": \"G4\", \"lyric\": \"ハ\" },\n  { \"at\": 0.5, \"pitch\": \"C5\", \"lyric\": \"テ\" },\n  { \"at\": 1, \"pitch\": \"E5\", \"lyric\": \"ナ\" },\n  { \"at\": 1.5, \"pitch\": \"D5\", \"lyric\": \"_\" },\n  { \"at\": 2, \"pitch\": \"G5\", \"lyric\": \"good\" },\n  { \"at\": 3, \"pitch\": \"E5\", \"lyric\": \"mor-\" },\n  { \"at\": 3.5, \"pitch\": \"C5\", \"lyric\": \"ning!\", \"lineEnd\": true }\n] }\n```\n\n### Songs and melodies\n\nA song needs a melody someone would hum: a shape, a rhythm and a goal. An LLM writing notes one\nafter another drifts into the easiest next step — the neighbouring scale note, on the next beat —\nand the result runs up and down the scale in even notes. Decide the melody before you write any\nJSON.\n\n**A song the user names** (a nursery song, a folk song, a hymn): play its real melody — its\npitches, its rhythm, its meter, key and pickup — as the song goes. Never make one up and present\nit as that song. Write it only when you know it note by note, or have it as note names (ドレミ,\nsolfège, \"E E F# …\", ABC or MusicXML) from the user or from a page that spells them out. A web\nsearch for the song mostly finds lyrics and pages whose sheet music is an image or a PDF, which\ngives no notes. When you can't get the notes, say so, and ask the user for them or offer an\noriginal song with the same words.\n\n**A new melody.** Plan it in words first, then write the notes:\n\n- **Phrases.** 2 or 4 bars each; a song of 8 or 16 bars is two or four phrases. End a phrase on a\n  long note (a half note or longer) and leave a rest before the next one (a breath, when sung).\n  The first phrase ends open (on the 2nd, 5th or 7th degree, over the V chord); the last ends on\n  the key note over the I chord.\n- **A motif.** 3–5 notes with a rhythm of their own (e.g. two eighths and a dotted quarter).\n  Repeat it, move it up a step or a third (a sequence), change its ending — most of a song is its\n  motif again, changed. Phrase pairs often go A A' (same start, different ending).\n- **Steps and leaps.** Mostly steps, with a leap of a 3rd to a 6th in most bars; after a big leap,\n  step back the other way. Don't run more than 4–5 notes by step in one direction. Repeat a note\n  sometimes. Put the highest note once, about two-thirds of the way in.\n- **Rhythm.** Mix lengths: eighths for quick words, quarters, dotted rhythms, long notes at phrase\n  ends. A song in all equal notes is a scale exercise, not a song.\n- **Harmony.** Choose the chords first, one or two per bar (I, IV, V, vi …). The melody's notes on\n  the strong beats belong to the chord; notes between them pass by step. Play the chords on an\n  accompaniment track, so the melody sits on harmony.\n- **Words.** Put stressed syllables on strong beats. In Japanese, let the pitch follow the word's\n  accent where it can (はし \"chopsticks\" falls, はし \"bridge\" rises).\n- **Range.** About an octave, C4–D5 or so for singing; a melody that stays inside a fifth sounds\n  like an exercise.\n\n**Singing.** `\"sing\": { \"engine\": \"neutrino\" }` on the melody track sings its `lyric`s with the\nuser's NEUTRINO (`\"voice\"`: a voice they have installed, e.g. \"reina\" (the default), \"merrow\"; `\"level\"`: dB above the accompaniment, default 4). Only one track sings; its\nlyrics are kana, one mora per note (`\"_\"`/`\"ー\"` holds the vowel). Start the first sung note\na beat or more after the start (the consonant comes before the note), give every note its `len`\nand leave rests between phrases to breathe. The other tracks accompany it.\n\n```json\n{\n  \"format\": \"jinglescript/1\",\n  \"title\": \"あさのひかり\",\n  \"tempo\": 96, \"meter\": \"4/4\", \"key\": \"C\",\n  \"length\": { \"beats\": 17 },\n  \"tracks\": [\n    { \"instrument\": \"organ\", \"name\": \"voice\", \"sing\": { \"engine\": \"neutrino\" }, \"notes\": [\n      { \"at\": 1, \"pitch\": \"E4\", \"len\": 0.5, \"lyric\": \"あ\" }, { \"at\": 1.5, \"pitch\": \"G4\", \"len\": 0.5, \"lyric\": \"さ\" },\n      { \"at\": 2, \"pitch\": \"C5\", \"len\": 1.5, \"lyric\": \"の\" }, { \"at\": 3.5, \"pitch\": \"B4\", \"len\": 0.5, \"lyric\": \"ひ\" },\n      { \"at\": 4, \"pitch\": \"A4\", \"len\": 1, \"lyric\": \"か\" }, { \"at\": 5, \"pitch\": \"G4\", \"len\": 2, \"lyric\": \"り\", \"lineEnd\": true },\n      { \"at\": 9, \"pitch\": \"F4\", \"len\": 0.5, \"lyric\": \"そ\" }, { \"at\": 9.5, \"pitch\": \"A4\", \"len\": 0.5, \"lyric\": \"ら\" },\n      { \"at\": 10, \"pitch\": \"D5\", \"len\": 1.5, \"lyric\": \"に\" }, { \"at\": 11.5, \"pitch\": \"C5\", \"len\": 0.5, \"lyric\": \"う\" },\n      { \"at\": 12, \"pitch\": \"B4\", \"len\": 0.5, \"lyric\": \"た\" }, { \"at\": 12.5, \"pitch\": \"D5\", \"len\": 0.5, \"lyric\": \"お\" },\n      { \"at\": 13, \"pitch\": \"C5\", \"len\": 2, \"lyric\": \"う\" }\n    ] },\n    { \"instrument\": \"vibraphone\", \"name\": \"chords\", \"gain\": -4, \"notes\": [\n      { \"at\": 0, \"chord\": \"C\", \"len\": 4 }, { \"at\": 4, \"chord\": \"G\", \"len\": 4 },\n      { \"at\": 8, \"chord\": \"Dm\", \"len\": 4 }, { \"at\": 12, \"chord\": \"G7\", \"len\": 1 }, { \"at\": 13, \"chord\": \"C\", \"len\": 3 }\n    ] }\n  ]\n}\n```\n\nTwo phrases on one motif (a rising leap E–G–C, then down by step): the first ends open on G over\nG, the second takes the motif a step higher (F–A–D) and lands on C.\n\n### Sheet music (teaching and reading music)\n\nA score can also be shown as sheet music, for a lesson: a scale, a rhythm, a song to sing along to.\nThe score only needs the fields below; how the player shows it is chosen **in the renderScore call,\nnot in the score**: `{ \"action\": \"renderScore\", \"score\": { … }, \"view\": \"notation\", \"autoplay\": true,\n\"notifyWhenDone\": true }`. `\"view\": \"notation\"` shows the notes on staves with a cursor that\nfollows playback; `\"autoplay\": true` starts it by itself; `\"notifyWhenDone\": true` sends you a\nmessage when the user has heard it to the end, so you can ask about it or go on to the next\nexercise. Say what to listen for before renderScore, and say nothing while it plays: speech over\nthe music drowns it out. In a voice chat the player waits until you have answered the render and\nfallen quiet; `\"autoplayDelay\": n` makes it wait n seconds after it appears instead. The sound is the same either way: sheet music is drawn from the score you\nwrote.\n\n- Set `\"meter\"` (\"4/4\", \"3/4\", \"2/4\", \"6/8\") and `\"key\"` (\"C\", \"G\", \"F\", \"D\", \"Bb\", \"Am\", \"Em\" …).\n  They only change the page; still write every note's real pitch (\"F#4\" in G major, \"Bb4\" in F).\n- `\"pickup\": n` when the tune starts before the first full bar (\"Hap-py\" of Happy Birthday is a\n  pickup of 1 beat in 3/4; many songs start this way). Bar lines fall at pickup, pickup + a bar, …\n- For a triplet, `len` is a third of the beat it shares (0.333 for a triplet eighth). Triplets are\n  written in 2/4, 3/4, 4/4 and 2/2, filling a quarter note (don't mix 0.25 and 0.333 within one);\n  6/8 has none.\n- Time in beats (quarter notes), on the grid: whole beats, halves, quarters (0.25), eighths of a\n  beat, or triplets (12, 12.333, 12.667). Use a tempo and plain beat numbers rather than seconds\n  or cues. In 6/8 a bar is still 3 beats: six eighths are 0.5 beats each.\n- Give every note its written length with `len` in beats (1 a quarter, 2 a half, 1.5 a dotted\n  quarter, 0.5 an eighth). Without it a note is written up to the next one.\n- One line of notes per track: a melody track and an accompaniment track. Chords (`pitch` lists)\n  are fine; notes that overlap at different times are not (the later one cuts the earlier).\n- Piano music: a \"right hand\" and a \"left hand\" track, both `grandpiano`, side by side, become\n  one grand staff. A track mostly below A3 gets a bass clef (or set `\"clef\"`).\n- `lyric`s are printed under their notes. A chord written by name (`chord`) prints its symbol\n  above the staff.\n- Sound effects are never drawn; `\"notation\": false` on a track leaves it off the page too.\n- checkScore with `\"view\": \"notation\"` warns about notes off the grid or held into the next note;\n  fix them so the page shows what you meant.\n\n```json\n{\n  \"format\": \"jinglescript/1\",\n  \"title\": \"Twinkle, Twinkle (first line)\",\n  \"tempo\": 100, \"meter\": \"4/4\", \"key\": \"C\",\n  \"length\": { \"beats\": 10 },\n  \"tracks\": [\n    { \"instrument\": \"grandpiano\", \"name\": \"right hand\", \"notes\": [\n      { \"at\": 0, \"pitch\": \"C4\", \"len\": 1, \"lyric\": \"Twin-\" }, { \"at\": 1, \"pitch\": \"C4\", \"len\": 1, \"lyric\": \"kle\" },\n      { \"at\": 2, \"pitch\": \"G4\", \"len\": 1, \"lyric\": \"twin-\" }, { \"at\": 3, \"pitch\": \"G4\", \"len\": 1, \"lyric\": \"kle\" },\n      { \"at\": 4, \"pitch\": \"A4\", \"len\": 1, \"lyric\": \"lit-\" }, { \"at\": 5, \"pitch\": \"A4\", \"len\": 1, \"lyric\": \"tle\" },\n      { \"at\": 6, \"pitch\": \"G4\", \"len\": 2, \"lyric\": \"star\" }\n    ] },\n    { \"instrument\": \"grandpiano\", \"name\": \"left hand\", \"gain\": -4, \"notes\": [\n      { \"at\": 0, \"pitch\": [\"C3\", \"G3\"], \"len\": 4 },\n      { \"at\": 4, \"pitch\": [\"F2\", \"C3\"], \"len\": 2 },\n      { \"at\": 6, \"pitch\": [\"C3\", \"G3\"], \"len\": 2 }\n    ] }\n  ]\n}\n```\n\n### What makes a jingle work\n\n- A **question and an answer**: three rising notes that end open (e.g. G4 C5 E5 on eighths), then\n  a short answer that resolves.\n- **The hit**: a full chord (C5 E5 G5 C6) with a low bass (C3 + G3) on a separate track, at velocity\n  1.0, exactly on the cue the animation lands on.\n- Keep the notes short: about 2 s of notes is plenty for a 5 s sting. After the hit, one soft\n  echo note (vel 0.35, an octave up) at most; leave the tail for the voice-over.\n- Stay in one key; major keys and the pentatonic scale sound friendly. (This is for a short\n  sting; a song needs more — see **Songs and melodies**.)\n\n### Example\n\n```json\n{\n  \"format\": \"jinglescript/1\",\n  \"title\": \"Bouncy opening\",\n  \"tempo\": 130,\n  \"length\": { \"seconds\": 4.6 },\n  \"cues\": { \"hit\": { \"seconds\": 1.5 }, \"voice\": { \"seconds\": 1.8 } },\n  \"tracks\": [\n    { \"instrument\": \"marimba\", \"name\": \"melody\", \"notes\": [\n      { \"at\": 0, \"pitch\": \"G4\", \"vel\": 0.8 },\n      { \"at\": 0.5, \"pitch\": \"C5\", \"vel\": 0.85 },\n      { \"at\": 1, \"pitch\": [\"E5\", \"G5\"], \"vel\": 0.9 },\n      { \"at\": \"hit\", \"pitch\": [\"C5\", \"E5\", \"G5\", \"C6\"], \"vel\": 1.0 },\n      { \"at\": \"hit+1\", \"pitch\": \"C6\", \"vel\": 0.35 }\n    ] },\n    { \"instrument\": \"marimba\", \"name\": \"bass\", \"notes\": [\n      { \"at\": \"hit\", \"pitch\": [\"C3\", \"G3\"], \"vel\": 0.9 }\n    ] }\n  ]\n}\n```\n\n### Output\n\n`title`, `author` and `copyright` (all optional) are written into the audio file's tags\n(WAV, MP3, OGG) and the MIDI file. Fill in `author` and `copyright` only with what the user\ngave you — never invent a name, a company or a year.\n\nRendering gives audio and a timing map (`jinglescript-timing/1`): every cue, beat and note in\nseconds, `audibleUntil`, and — when the score has lyrics — every line and syllable with its\ntime (`lyrics`). The animation reads cue times from it by name.\n\n<!-- guide:end -->\n\n## Singing (NEUTRINO)\n\nA track with `\"sing\": { \"engine\": \"neutrino\", \"voice\": \"reina\", \"level\": 4 }` is sung, from its notes'\nkana `lyric`s, by [NEUTRINO](https://studio-neutrino.com/) installed by you, through the ordinary `render()`, CLI and MCP — the score alone decides; there is no\nseparate API (Node only; the browser build throws `SingerUnavailableError`). It is looked for in `/Applications/NEUTRINO`, or where `NEUTRINO_DIR` in a\n`.env` file in the project directory (or the environment) says. `level` is the vocal's loudness over\nthe other tracks in LU (default 4); the track's `gain` adds to it. A missing NEUTRINO or voice is an\nerror, never a silent fallback. Details (in Japanese): [docs/singing-neutrino.md](docs/singing-neutrino.md).\n\n## Licence\n\nMIT — except the score files in `examples/piano/`, which carry their own CC BY 4.0 or\nCC BY-SA 4.0 licence ([examples/piano/README.md](examples/piano/README.md)).\n"}