@hraness/dawg 0.6.1 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (277) hide show
  1. package/CHANGELOG.md +118 -0
  2. package/DAWG.md +454 -140
  3. package/README.md +30 -26
  4. package/core/autotune.ts +1119 -0
  5. package/core/chords.ts +271 -23
  6. package/core/clips.ts +499 -0
  7. package/core/diff.ts +184 -104
  8. package/core/expression.ts +15 -0
  9. package/core/fx.ts +99 -29
  10. package/core/ids.ts +52 -0
  11. package/core/instruments.ts +19 -0
  12. package/core/keys.ts +3 -3
  13. package/core/loop.ts +8 -0
  14. package/core/lyrics.ts +297 -0
  15. package/core/master.ts +3 -3
  16. package/core/range.ts +581 -0
  17. package/core/resonators.ts +16 -2
  18. package/core/routing.ts +165 -0
  19. package/core/score.ts +955 -14
  20. package/core/sdk/eval-child.ts +7 -2
  21. package/core/sdk/eval.ts +35 -6
  22. package/core/sdk/print.ts +287 -5
  23. package/core/sdk/sync-lyrics.ts +49 -0
  24. package/core/sdk/v1.ts +2132 -46
  25. package/core/sections.ts +480 -35
  26. package/core/sing.ts +815 -0
  27. package/core/style-provenance.ts +80 -0
  28. package/core/styles/africa-mena-southasia.ts +2893 -0
  29. package/core/styles/americas.ts +3810 -0
  30. package/core/styles/art.ts +4993 -0
  31. package/core/styles/base.ts +123 -0
  32. package/core/styles/cycles.ts +106 -0
  33. package/core/styles/electronic.ts +2723 -0
  34. package/core/styles/europe-asia-pacific.ts +2838 -0
  35. package/core/styles/excerpt.ts +29 -0
  36. package/core/styles/gamelan.ts +283 -0
  37. package/core/styles/generate.ts +2199 -0
  38. package/core/styles/index.ts +515 -0
  39. package/core/styles/parts.ts +106 -0
  40. package/core/styles/pop.ts +3189 -0
  41. package/core/styles/rock.ts +2993 -0
  42. package/core/styles/roots.ts +4175 -0
  43. package/core/styles/schema.ts +429 -0
  44. package/core/styles/taxonomy.ts +940 -0
  45. package/core/styles/validate.ts +528 -0
  46. package/core/tempo.ts +32 -2
  47. package/core/tuning.ts +19 -3
  48. package/core/vocoder.ts +524 -0
  49. package/guides/agent.md +29 -0
  50. package/guides/arrange.md +31 -0
  51. package/guides/audio.md +31 -0
  52. package/guides/audition.md +20 -14
  53. package/guides/automation.md +12 -7
  54. package/guides/chords.md +15 -15
  55. package/guides/effects.md +17 -16
  56. package/guides/faders.md +20 -16
  57. package/guides/files.md +13 -9
  58. package/guides/getting-started.md +15 -11
  59. package/guides/keys.md +19 -15
  60. package/guides/media.md +17 -12
  61. package/guides/mix.md +15 -7
  62. package/guides/music.md +23 -8
  63. package/guides/notes.md +15 -9
  64. package/guides/panes.md +32 -0
  65. package/guides/performance.md +15 -13
  66. package/guides/play.md +21 -13
  67. package/guides/project.md +26 -8
  68. package/guides/providers.md +20 -14
  69. package/guides/resample.md +15 -9
  70. package/guides/rhythm.md +18 -13
  71. package/guides/sessions.md +17 -7
  72. package/guides/show-me.md +31 -0
  73. package/guides/sound.md +25 -9
  74. package/guides/sounds.md +15 -13
  75. package/guides/styles.md +31 -0
  76. package/guides/tape.md +32 -0
  77. package/guides/tempo.md +15 -10
  78. package/guides/tracks.md +15 -10
  79. package/guides/tuning.md +32 -0
  80. package/guides/voice.md +31 -0
  81. package/guides/web-search.md +18 -8
  82. package/native/prebuilt/darwin-arm64/libdawg_sink.dylib +0 -0
  83. package/native/prebuilt/darwin-x64/libdawg_sink.dylib +0 -0
  84. package/native/prebuilt/linux-arm64/libdawg_sink.so +0 -0
  85. package/native/prebuilt/linux-x64/libdawg_sink.so +0 -0
  86. package/native/prebuilt/manifest.json +21 -0
  87. package/package.json +6 -2
  88. package/src/agent/agent.ts +130 -18
  89. package/src/agent/calibration-tools.ts +53 -0
  90. package/src/agent/clip-tools.ts +453 -0
  91. package/src/agent/command-agent.ts +378 -0
  92. package/src/agent/drum-tools.ts +2 -2
  93. package/src/agent/expression-tools.ts +1 -1
  94. package/src/agent/gateway.ts +246 -60
  95. package/src/agent/models.ts +53 -12
  96. package/src/agent/ops.ts +27 -1
  97. package/src/agent/pack-tools.ts +1 -1
  98. package/src/agent/planner.ts +25 -0
  99. package/src/agent/portable-schema.ts +80 -0
  100. package/src/agent/preview-tool.ts +4 -1
  101. package/src/agent/provider.ts +22 -8
  102. package/src/agent/range-tools.ts +216 -0
  103. package/src/agent/rhythm-tools.ts +1 -1
  104. package/src/agent/section-tools.ts +1 -1
  105. package/src/agent/show-me.ts +497 -0
  106. package/src/agent/steer.ts +15 -0
  107. package/src/agent/style-tools.ts +217 -0
  108. package/src/agent/tool-error.ts +12 -0
  109. package/src/agent/tools.ts +110 -23
  110. package/src/agent/usage.ts +2 -2
  111. package/src/agent/voice-tools.ts +925 -0
  112. package/src/agent/xcb-agent.ts +13 -10
  113. package/src/argv.ts +38 -0
  114. package/src/audio/analysis.ts +253 -0
  115. package/src/audio/arrange.ts +49 -5
  116. package/src/audio/audio-command.ts +218 -0
  117. package/src/audio/autotune-engine.ts +101 -0
  118. package/src/audio/autotune.ts +640 -0
  119. package/src/audio/clips.ts +240 -0
  120. package/src/audio/devices.ts +264 -0
  121. package/src/audio/doctor.ts +157 -0
  122. package/src/audio/dsp/bandbank.ts +138 -0
  123. package/src/audio/dsp/envelope.ts +10 -0
  124. package/src/audio/dsp/follow.ts +120 -0
  125. package/src/audio/dsp/formant.ts +427 -0
  126. package/src/audio/dsp/glottal.ts +243 -0
  127. package/src/audio/dsp/interp.ts +7 -2
  128. package/src/audio/dsp/lpc.ts +50 -0
  129. package/src/audio/dsp/periodicity.ts +59 -0
  130. package/src/audio/dsp/pitch.ts +995 -0
  131. package/src/audio/dsp/psola.ts +199 -0
  132. package/src/audio/effects/chain.ts +3 -1
  133. package/src/audio/effects/common.ts +43 -0
  134. package/src/audio/effects/convolution.ts +7 -4
  135. package/src/audio/effects/filter.ts +48 -69
  136. package/src/audio/effects/formant.ts +263 -0
  137. package/src/audio/engine.ts +360 -40
  138. package/src/audio/fit.ts +35 -3
  139. package/src/audio/instrument-check.ts +59 -43
  140. package/src/audio/instruments.ts +4 -0
  141. package/src/audio/keys/calibration.ts +56 -0
  142. package/src/audio/keys/electric.ts +8 -1
  143. package/src/audio/keys/engine.ts +13 -1
  144. package/src/audio/keys/piano.ts +22 -2
  145. package/src/audio/kits.ts +135 -6
  146. package/src/audio/live.ts +114 -20
  147. package/src/audio/native.ts +615 -0
  148. package/src/audio/preview.ts +30 -2
  149. package/src/audio/render-worker.ts +2 -0
  150. package/src/audio/renderer.ts +2 -0
  151. package/src/audio/resample.ts +2 -1
  152. package/src/audio/sampler.ts +85 -4
  153. package/src/audio/samples.ts +20 -2
  154. package/src/audio/sing/analysis.ts +193 -0
  155. package/src/audio/sing/engine.ts +949 -0
  156. package/src/audio/strings/bow.ts +48 -5
  157. package/src/audio/strings/engine.ts +8 -2
  158. package/src/audio/synth/oscillators.ts +31 -21
  159. package/src/audio/synth/voice.ts +34 -1
  160. package/src/audio/vocoder/bank.ts +314 -0
  161. package/src/audio/vocoder/carrier.ts +165 -0
  162. package/src/audio/vocoder/control.ts +68 -0
  163. package/src/audio/vocoder/detect.ts +50 -0
  164. package/src/audio/vocoder/index.ts +304 -0
  165. package/src/audio/vocoder/talkbox.ts +143 -0
  166. package/src/audio/wav.ts +500 -77
  167. package/src/audio/winds/engine.ts +5 -1
  168. package/src/audio/winds/trim.ts +28 -4
  169. package/src/audio/winds/trims1.ts +297 -0
  170. package/src/audio/winds/voice.ts +15 -2
  171. package/src/auth/cli.ts +38 -36
  172. package/src/auth/credentials.ts +30 -1
  173. package/src/auth/login.ts +15 -9
  174. package/src/auth/tui.ts +19 -10
  175. package/src/commands/arrange.ts +82 -32
  176. package/src/commands/autotune.ts +421 -0
  177. package/src/commands/calibration.ts +74 -0
  178. package/src/commands/clips.ts +887 -0
  179. package/src/commands/drums.ts +3 -2
  180. package/src/commands/edit.ts +11 -4
  181. package/src/commands/expression.ts +1 -1
  182. package/src/commands/formant.ts +221 -0
  183. package/src/commands/fx.ts +101 -32
  184. package/src/commands/grammar.ts +560 -0
  185. package/src/commands/help.ts +774 -366
  186. package/src/commands/history.ts +139 -10
  187. package/src/commands/keys.ts +25 -8
  188. package/src/commands/modal.ts +1 -1
  189. package/src/commands/music.ts +1 -1
  190. package/src/commands/nearest.ts +53 -0
  191. package/src/commands/pack.ts +9 -2
  192. package/src/commands/param-range.ts +56 -0
  193. package/src/commands/parses.ts +135 -0
  194. package/src/commands/progression.ts +170 -0
  195. package/src/commands/range.ts +763 -0
  196. package/src/commands/resample.ts +13 -10
  197. package/src/commands/rhythm.ts +3 -0
  198. package/src/commands/rig.ts +3 -24
  199. package/src/commands/sample.ts +11 -1
  200. package/src/commands/sing.ts +474 -0
  201. package/src/commands/strum.ts +13 -1
  202. package/src/commands/style.ts +415 -0
  203. package/src/commands/time.ts +6 -3
  204. package/src/commands/tuning.ts +3 -3
  205. package/src/commands/vocal-pitch.ts +617 -0
  206. package/src/commands/vocal.ts +147 -0
  207. package/src/commands/vocoder.ts +627 -0
  208. package/src/commands/wind.ts +2 -2
  209. package/src/fs/durable.ts +50 -0
  210. package/src/identity/actor.ts +127 -0
  211. package/src/lang/glossary.ts +511 -0
  212. package/src/launch-args.ts +267 -0
  213. package/src/main.ts +2508 -308
  214. package/src/media/cli.ts +20 -3
  215. package/src/media/import.ts +3 -1
  216. package/src/project/check.ts +21 -2
  217. package/src/project/clip-pins.ts +72 -0
  218. package/src/project/init.ts +23 -8
  219. package/src/project/sync.ts +418 -86
  220. package/src/render.ts +20 -1
  221. package/src/session/client.ts +107 -3
  222. package/src/session/clipboard.ts +79 -0
  223. package/src/session/daemon.ts +199 -5
  224. package/src/session/live-host.ts +232 -0
  225. package/src/session/meta.ts +14 -0
  226. package/src/session/origin.ts +174 -0
  227. package/src/session/port.ts +109 -14
  228. package/src/session/presence.ts +34 -4
  229. package/src/session/protocol.ts +365 -7
  230. package/src/session/rebase.ts +63 -12
  231. package/src/session/receipt.ts +265 -0
  232. package/src/session/shared-live.ts +131 -0
  233. package/src/session/store.ts +126 -45
  234. package/src/tui/arrange-menu.ts +225 -23
  235. package/src/tui/audition.ts +1 -1
  236. package/src/tui/euclid.ts +113 -27
  237. package/src/tui/fader.ts +282 -42
  238. package/src/tui/granular-menu.ts +2 -4
  239. package/src/tui/knob-fields.ts +107 -0
  240. package/src/tui/knob-map.ts +198 -0
  241. package/src/tui/menu-clips.ts +297 -0
  242. package/src/tui/menu-time.ts +20 -13
  243. package/src/tui/menu-voice.ts +405 -0
  244. package/src/tui/menu.ts +987 -179
  245. package/src/tui/modal-menu.ts +6 -6
  246. package/src/tui/performance-menu.ts +5 -2
  247. package/src/tui/play-chords.ts +4 -2
  248. package/src/tui/play-mode.ts +15 -1
  249. package/src/tui/play-session.ts +243 -28
  250. package/src/tui/sing-menu.ts +278 -0
  251. package/src/tui/style-menu.ts +104 -0
  252. package/src/tui/tape-mode.ts +580 -0
  253. package/src/tui/tape-view.ts +263 -0
  254. package/src/tui/vocoder-menu.ts +244 -0
  255. package/src/tui/wind-menu.ts +3 -3
  256. package/src/version.ts +8 -0
  257. package/src/web/fetch.ts +115 -29
  258. package/tui/activity.ts +180 -9
  259. package/tui/app.ts +491 -84
  260. package/tui/clip-row.ts +132 -0
  261. package/tui/delight.ts +193 -0
  262. package/tui/drawer.ts +233 -27
  263. package/tui/frame-gate.ts +76 -0
  264. package/tui/grammar.ts +143 -65
  265. package/tui/guide.ts +50 -5
  266. package/tui/highway.ts +309 -30
  267. package/tui/hints.ts +197 -0
  268. package/tui/hits.ts +7 -1
  269. package/tui/input.ts +60 -9
  270. package/tui/keys.ts +1 -1
  271. package/tui/knobs.ts +268 -0
  272. package/tui/play-strip.ts +80 -14
  273. package/tui/prompt.ts +1 -1
  274. package/tui/screen.ts +151 -18
  275. package/tui/tape.ts +439 -0
  276. package/tui/text.ts +35 -2
  277. package/tui/theme.ts +46 -1
package/DAWG.md CHANGED
@@ -11,7 +11,11 @@ bun install
11
11
  bun run dawg
12
12
  ```
13
13
 
14
- Run `dawg` from any directory. It creates `.dawg/session` on first use and reuses that session in later terminal windows. Use `dawg --new` for a separate composition, `dawg --session <name|id>` to attach explicitly (an unknown value is `no session named "…" · dawg sessions`, never a new session), and `dawg --track bass` to focus a named track. `dawg --version` prints the version; unknown subcommands and options are rejected with usage before `.dawg/` exists, and the launch that creates `.dawg/` says `created .dawg/ · add it to .gitignore` in the strip. Every window connects to `dawgd`, a per-session daemon the first window starts in the background. It is the single writer: windows send operations with a base revision and an idempotency key, duplicate keys are no-ops, a stale full composition receives a typed rebase diagnostic, and a stale `operations` intent (what the agent sends) is replayed on the current score when nothing it touches changed since its base (the notes it updates or removes, the tracks it rewrites or clears, tempo and length, and ids it creates; at most 64 revisions back, with the base recovered by rewinding the event log); anything else still gets the rebase diagnostic. Every event stores a compact reverse delta (`rewind`) rather than a copy of the previous score, so a session record grows by about the size of each edit; when it nears its 4 MiB cap the oldest rewinds are compacted away and only that older history becomes unreachable. Rebased events record `rebasedFrom`, and their rewind leads back to the score they replayed on, so undo drops only that change, and on the file fallback the agent commits the full composition with the strict base check. Accepted commits are persisted through the same atomic snapshot store before being broadcast to every window. The daemon also owns the only transport and audio player, broadcasting play, pause, seek, and tempo with a timestamp so every window draws the same hit line. It keeps a presence table (`clientId`, `pid`, focused track) and can atomically claim the first unfocused track for a new window. The daemon exits 30 seconds after its last window closes, removes its socket on SIGTERM, and a crashed daemon's socket and lock are reclaimed by the next window. If `dawgd` cannot be started (or `DAWG_DAEMON=0`), windows fall back to the snapshot under the file lock, watching the session directory with `fs.watch` (so renames and edits from other windows arrive immediately) with a 1 s backstop poll, or a 200 ms poll where watching is unavailable, with presence kept in per-window heartbeat files. `dawg sessions` lists sessions in the current workspace. `dawg render <out.wav|out.mid> [--session <name|id>] [--import <file>]` reads the session record from disk (no daemon, no audio) and writes a stereo 16-bit WAV through the playback renderer (or a Standard MIDI File with tempo and time-signature meta events for `.mid`, see **Tempo and meter**); the same score always yields the same bytes, and the command prints the size and sha256. `/status` reports `status · <name> · rev <n> · <digest> · shared via dawgd` (or `saved locally · no daemon`), where the digest is the 16-hex composition digest dawgd broadcasts. In demo mode a drum track is seeded with a one-bar kick, snare and hat groove instead of melodic notes.
14
+ Run `dawg` from any directory. It creates `.dawg/session` on first use and reuses that session in later terminal windows. Use `dawg --new` for a separate composition, `dawg --session <name|id>` to attach explicitly (an unknown value is `no session named "…" · dawg sessions`, never a new session), and `dawg --track bass` to focus a named track (normalized like `/track`: lowercase, spaces become `-`, `[a-z0-9._-]{1,64}`, and an existing track matches by id or name, so `--track Bass` focuses `bass`). `dawg --version` prints the version; unknown subcommands and options, a value flag with no value (or one starting with `-`), an invalid `--track` and an unknown `--theme` are rejected with usage and exit 2 before `.dawg/` exists, and `dawg <command> --help` prints usage and writes nothing for every subcommand (`init`, `check` and `sessions` also reject extra arguments). `dawg --import a --export b` converts one loop file to another without opening a session, and a demo frame (`--demo`, or stdin not a terminal) in a directory with no `.dawg/` renders from a throwaway session and leaves nothing behind; and the launch that creates `.dawg/` says `created .dawg/ · add it to .gitignore` in the strip. Every window connects to `dawgd`, a per-session daemon the first window starts in the background. It is the single writer: windows send operations with a base revision and an idempotency key, duplicate keys are no-ops, a stale full composition receives a typed rebase diagnostic, and a stale `operations` intent (what the agent sends) is replayed on the current score when nothing it touches changed since its base (the notes it updates or removes, the tracks it rewrites or clears, tempo and length, and ids it creates; at most 64 revisions back, with the base recovered by rewinding the event log); anything else still gets the rebase diagnostic. Every event stores a compact reverse delta (`rewind`) rather than a copy of the previous score, so a session record grows by about the size of each edit; when it nears its 4 MiB cap the oldest rewinds are compacted away and only that older history becomes unreachable. Rebased events record `rebasedFrom`, and their rewind leads back to the score they replayed on, so undo drops only that change, and on the file fallback the agent commits the full composition with the strict base check. Accepted commits are persisted through the same atomic snapshot store before being broadcast to every window. The daemon also owns the only transport and audio player, broadcasting play, pause, seek, and tempo with a timestamp so every window draws the same hit line. It keeps a presence table (`clientId`, `pid`, focused track) and can atomically claim the first unfocused track for a new window. The daemon exits 30 seconds after its last window closes, removes its socket on SIGTERM, and a crashed daemon's socket and lock are reclaimed by the next window. If `dawgd` cannot be started (or `DAWG_DAEMON=0`), windows fall back to the snapshot under the file lock, watching the session directory with `fs.watch` (so renames and edits from other windows arrive immediately) with a 1 s backstop poll, or a 200 ms poll where watching is unavailable, with presence kept in per-window heartbeat files. `dawg sessions` lists sessions in the current workspace. `dawg render <out.wav|out.mid> [--session <name|id>] [--import <file>]` reads the session record from disk (no daemon, no audio) and writes a stereo 16-bit WAV through the playback renderer (or a Standard MIDI File with tempo and time-signature meta events for `.mid`, see **Tempo and meter**); the same score always yields the same bytes, and the command prints the size and sha256. `/status` reports `status · <name> · rev <n> · <digest> · shared via dawgd` (or `saved locally · no daemon`), where the digest is the 16-hex composition digest dawgd broadcasts. In demo mode a drum track is seeded with a one-bar kick, snare and hat groove instead of melodic notes.
15
+
16
+ ### One command grammar
17
+
18
+ Every prompt-bar command reads the same with or without its slash (`/fx reverb on` is `fx reverb on`). `remove`, `rm` and `delete` are one verb on every object, and `list`, `presets`, `ls` and a bare noun are one listing. Aliases run their canonical form: `rig <preset>` (`track <preset>` still works), `model key` (`login`), `chords idiom` (`chords style`), `synth filter` (`synth cutoff`), `loop a-b|off` (`loop 2-3` sets the loop range; it makes no section), `export <file>.wav [stems]` (the `dawg render` path; agent paths are workspace-relative), and a bare `pattern <groove>` or `kit <name>`. A known verb that fails answers with one usage card, `✗ tempo 900 · tempo takes 20…300 BPM · tempo 128`, with a nearest suggestion and never a raw core key; it never reaches the agent, and without a provider nothing does. Instruments are checked exactly on write (`✗ instrument sawtoth · did you mean sawtooth? · instrument list`), while stored projects still load. `remove n1` checks the note exists (`✗ no note n1 · notes lists them`). Status reads such as bare `fx` and `tracks` print `•`, and a bare scalar (`tempo`, `volume`) opens its fader (on its four knobs where it is one; see **Four knobs**). The agent tools `set_effects`, `vocode` and `add_drums` still run but are not advertised; use `set_fx`, `set_vocoder` and `set_rhythm`.
15
19
 
16
20
  ### Sessions, names and forks
17
21
 
@@ -27,17 +31,17 @@ Auto-naming (`src/session/naming.ts`) is gated on a local musical fingerprint: t
27
31
 
28
32
  The `dawgd` protocol is newline-delimited JSON over a Unix socket in the session directory, or a hashed path under `$TMPDIR` when that path would exceed the platform socket-path limit. Every frame carries `v: 1`, frames are size-bounded, and every inbound frame is parsed from `unknown`. Clients send `hello`, `apply`, `transport`, `sync`, `focus`, `claim`, `meta`, and `ping`; the daemon replies with `welcome`, `result`, `snapshot`, `claimed`, `pong`, and typed `error` frames, and pushes `commit`, `transport`, `presence`, and `meta`.
29
33
 
30
- The header (`dawg` · track · ▶/⏸ BPM · key · session · N windows, then model · rev · sync on the right; `test/tui.test.ts` freezes the order) sits above the highway. The highway sits above the activity strip and the prompt. Notes stream toward the hit line, and velocity sets glyph density (`░▒▓█`) and saturation. Beat and bar rules get stronger at each level. Sustains draw as beams with a decaying tail and a short ghost after release. Each hit runs approach glow → flash/burst at the line → fade. The hit line pulses on the beat, and a sweep marks the loop wrap. A track with no hits draws `<track> · empty · type a request · ctrl-p play · ctrl-k menu` (when narrower: `<track> · empty · add C4 at 0 to start`, or `hit kick at 0` on a kit) in place of bar numbers and lane labels; the hit line stays. Drum tracks use one lane per voice with a legend; lane projection is pluggable (`LaneProjection` in `tui/highway.ts`). By default every unmuted track is overlaid through its own projection and accent, the focused track drawn on top at full strength and the others dimmed; `/view focus` restores the single-track view. Animation is derived from transport time, so frame rate never changes timing. Frames render into a retained cell buffer, only changed rows are written, and output is capped at about 30 fps.
34
+ The header (`dawg` · track · ▶/⏸ BPM · key · session · N windows, then model · rev · sync on the right; `test/tui.test.ts` freezes the order) sits above the highway. The highway sits above the activity strip and the prompt. Notes stream toward the hit line, and velocity sets glyph density (`░▒▓█`) and saturation. Beat and bar rules get stronger at each level. Sustains draw as beams with a decaying tail and a short ghost after release. Each hit runs approach glow → flash/burst at the line → fade. The hit line pulses on the beat, and a sweep marks the loop wrap. A track with no hits draws `<track> · empty · space play · ctrl-p play mode · ctrl-k menu` on a reserved row that never covers the ruler, led by `hit kick at 0` on a kit, `/lyrics la la · sing ooh` on a vocal track and, from 100 columns, a seeded `try style <id>` (the prompt placeholder suggests the same style); while playing it reads `space stop · type a request`, or `space stop · ctrl-p play mode` with no agent. The hit line stays. Drum tracks use one lane per voice with a legend; lane projection is pluggable (`LaneProjection` in `tui/highway.ts`). By default every unmuted track is overlaid through its own projection and accent, the focused track drawn on top at full strength and the others dimmed; `/view focus` restores the single-track view. Animation is derived from transport time, so frame rate never changes timing. Frames render into a retained cell buffer, only changed cells are written with minimal color codes, and output is capped at about 30 fps (bursts of forced frames coalesce), under 60 KB/s for a style-sized song at 80x24.
31
35
 
32
- Keys: `Space` (empty prompt) toggles playback. `Enter` submits, or queues in QUEUE mode. `Shift+Enter`/`Ctrl+J` inserts a newline, `Alt+Enter` queues and `Ctrl+Q` toggles STEER/QUEUE. `Ctrl+Z`/`Ctrl+Y` undo and redo, and `Ctrl+O` or `/transcript` opens the transcript, which scrolls with ↑/↓, PgUp/PgDn and Home/End, and `/` cycles its filter (all, requests, ops, errors). `Esc` cancels an agent turn, closes the overlay or clears the draft. `Ctrl+L` redraws and `Ctrl+C` exits. Bracketed paste keeps multiline text intact.
36
+ Keys: `Space` (empty prompt) toggles playback. `Enter` sends, or waits for the agent turn when the pill reads NEXT. `Shift-Enter`/`Ctrl-J` inserts a newline, `Alt-Enter` queues and `Ctrl-Q` toggles the NOW / NEXT pill. During an agent turn a typed command that parses locally (`tempo 120`, `play`, a note edit) runs at once beside the turn instead of steering it, so commands and keyboard play never wait on the model; prose and slash commands still steer. `Ctrl-Z`/`Ctrl-Y` undo and redo, and `Ctrl-O` or `/transcript` opens the transcript, which scrolls with ↑/↓, PgUp/PgDn and Home/End, and `/` cycles its filter (all, requests, ops, errors). `Esc` cancels an agent turn, closes the overlay or clears the draft. `Ctrl-L` redraws and `Ctrl-C` exits. Bracketed paste keeps multiline text intact.
33
37
 
34
38
  Themes: `/theme default|high-contrast|mono`, `--theme`, `DAWG_THEME`. Color falls back through truecolor, 256-color, 16-color and monochrome. `NO_COLOR` and `TERM=dumb` are supported. `/motion off`, `--reduce-motion` and `DAWG_REDUCE_MOTION=1` switch to static states in the same positions. `--no-mouse` or `DAWG_MOUSE=0` keeps the terminal's own mouse selection (see **Mouse**).
35
39
 
36
- Other code reports into the activity strip through `ActivityFeed` (`tui/activity.ts`): `pushCard(text, {tone, baseRevision, resultRevision, hint})`, `pushError(text)`, `setSpinner(label | undefined)`, `setQueueDepth(n)` and `applyAgentEvent(event)`, which accepts the agent's streaming `AgentEvent`s unchanged. Command handlers return a `Receipt` (`{ok: true | false | "warn", text}`), so a failure such as `main is not a drum track` is red because it says so, not because of its wording; `receiptTone` only classifies the legacy strings that remain. The `^z undo` hint rides only on receipts that changed the score, and only the first three in a session. `/help`, `/sessions` and `/tracks` open a scrollable text overlay (`TuiApp.openText`) and leave one summary card in the strip; `/help` is generated from `src/commands/help.ts`, which also feeds `dawg --help` and the usage hints that answer an unknown `/word` or a near-miss such as `pan 3` without a model call. A known verb followed by a sentence (three or more words, no numbers: `add a walking bass in A minor`) goes to the agent instead. An unknown `/word` names the nearest command (`unknown command /clik · did you mean /click? · /help`); a bare word one edit from a command whose remaining words parse as its arguments is suggested locally (`tempoo 90 · did you mean tempo 90?`); other bare words are requests for the agent.
40
+ Other code reports into the activity strip through `ActivityFeed` (`tui/activity.ts`): `pushCard(text, {tone, baseRevision, resultRevision, hint})`, `pushError(text)`, `setSpinner(label | undefined)`, `setQueueDepth(n)` and `applyAgentEvent(event)`, which accepts the agent's streaming `AgentEvent`s unchanged. Command handlers return a `Receipt` (`{ok: true | false | "warn", text}`), so a failure such as `main is not a drum track` is red because it says so, not because of its wording; `receiptTone` only classifies the legacy strings that remain. The `ctrl-z undo` hint rides only on receipts that changed the score, and only the first three in a session. `/help`, `/sessions` and `/tracks` open a scrollable text overlay (`TuiApp.openText`) and leave one summary card in the strip; `/help` is generated from `src/commands/help.ts`, which also feeds `dawg --help` and the usage hints that answer an unknown `/word` or a near-miss such as `pan 3` without a model call. A known verb followed by a sentence (three or more words, no numbers: `add a walking bass in A minor`) goes to the agent instead. An unknown `/word` names the nearest command (`unknown command /clik · did you mean /click? · /help`); a bare word one edit from a command whose remaining words parse as its arguments is suggested locally (`tempoo 90 · did you mean tempo 90?`); other bare words are requests for the agent.
37
41
 
38
- `/help` opens a short, task-first guide: **start here** (type a request, Ctrl-P, Ctrl-K, `?`, undo), **play notes**, **make drums**, **shape the sound**, **chords**, **song structure**, and **more**. `/help all` is the full reference; `/help music`, `/help session`, `/help window` and `/help keys` show one group, and `/help arrange` lists every arranging command (`section`, `form`, `build`, `drop`, `fill`) with its full usage.
42
+ `/help` opens a short start page (type a request or a style, Ctrl-P, Ctrl-K, `?`, undo) and the ten topics: **sound**, **voice**, **effects**, **rhythm**, **chords**, **mix**, **arrange**, **project**, **keys** and **agent**. The same ids open the same subject in all three doors: `/help voice` lists its commands, `/guide voice` explains it, `/menu voice` opens its Ctrl-K section. `/help all` is the full reference and `/help <command>` (`/help autotune`) shows one command's usage. Entries are written bare (`tempo 96`, `track drums`); only the free-text window verbs keep the slash (`/rename`, `/fork`, `/resume`, `/auth`), and `/play` (play mode, Ctrl-P) keeps it because bare `play` is the transport. Groups follow the Ctrl-K root order and stay within about fifteen rows, with sub-groups such as `sound · samples`; guitar amp presets are `rig <preset>` under effects, and the agent key is `model key` (`login` is an alias, not listed). Placeholders read `<drum>`, `<sample>`, `<register>` and `<lane>`. `/help keys` is drawn from the key table in `tui/grammar.ts`, so it always matches the keys. Old topic names still open their topic as aliases (music → arrange, session → project, window → keys, tuning → chords, performance → sound, and more in `src/lang/glossary.ts`). An unknown topic answers `no topic X · did you mean Y · /help`.
39
43
 
40
- `/guide` (or F1) opens the user guides: one short page per feature (`guides/*.md`, shipped in the package and rendered unchanged on dawg.sh/docs), each showing what to ask the agent and the command, key or menu path that does the same by hand. The pane is a tree: ↑↓ (j k) move, → l or Enter expand a section then open a guide, ← h collapse or go to the parent and back from a page, `/` filters by title and text, Esc clears the filter, then steps back, then closes. `/guide chords` opens one guide directly. `guides/guides.test.ts` keeps every guide within 20 rows at 80 columns and checks every `/command` a guide names against the help reference.
44
+ `/guide` (or F1) opens the user guides (`guides/*.md`, shipped in the package and rendered unchanged on dawg.sh/docs). The tree follows the topics: Getting started, then Using dawg (`keys`: the menu, faders, play mode), Sound, Voice, Effects, Rhythm, Chords (with Tuning), Mix (with Automation), Arrange (notes, tracks, tempo, styles, sections), Project (files, sessions) and Agent (show-me, providers, web search). Every guide uses one template, **Ask**, **Type it yourself**, **Menu**, **Keys** and **Next**, so the same answer is always in the same place. The pane: ↑↓ (j k) move, → l or Enter expand a section then open a guide, ← h collapse or go to the parent and back from a page, `/` filters by title and text, Esc clears the filter, then steps back, then closes. `/guide <topic>` opens one guide directly and accepts every topic id and alias (`/guide scale` opens Chords). `guides/guides.test.ts` keeps every guide within 20 rows at 72 columns, checks the template, every `/command` and every `Ctrl-K ›` path, and lints the words against the glossary.
41
45
 
42
46
  The local command path understands requests such as:
43
47
 
@@ -92,12 +96,15 @@ duration note <id> 0.25
92
96
  /export loop.track.json
93
97
  /import loop.track.json
94
98
  /model opus-5.5
99
+ /model fast
95
100
  /help
96
101
  /help all
97
102
  ```
98
103
 
99
104
  `/track <name>` focuses a track in this window and creates it when it is new; when another live window has it focused the reply is `<name> is open in another window` and focus stays put. Music words are bare and app commands take a slash; `tracks`, `export`, `import` and `track` still work bare as aliases but are listed once.
100
105
 
106
+ `/track rm <name>` (aliases `remove` and `delete`) removes a track and its notes, and drops any `vocoder.src` or `autotune.from` on another track that named it; the last track stays (`/clear` empties it). `/track move <name> <position>` puts a track at a 1-based place in the list. Names match the id or the display name (`/track rm piano b`). Both undo with ^Z, and both sit in Ctrl-K › Arrange › tracks as **move** and **remove** on the focused track.
107
+
101
108
  Drum tracks use the `kit` instrument (a track named `drums` gets it automatically). Notes on a kit track keep the score's MIDI pitch field, using General MIDI percussion numbers (kick 36, rim 37, snare 38, clap 39, closed hat 42, tom 45, open hat 46), so drum hits round-trip through `track.loop/v1` unchanged and the highway draws them in one lane per voice. Grammar, one command per prompt, beats in score beats:
102
109
 
103
110
  ```text
@@ -124,16 +131,24 @@ undo | redo
124
131
 
125
132
  Effects live on the track as optional `filter {cutoff, resonance}`, `delay {beats, feedback, mix}`, `reverb {mix, size}`, `filterAutomation`, `resonanceAutomation`, `delayFeedbackAutomation`, `delayMixAutomation`, and `solo` fields. Effect lanes modulate an existing effect: a resonance lane needs a filter and the delay lanes need a delay. Documents written before these fields existed still parse; out-of-range or non-finite values are rejected. Undo and redo append ordinary session events, so history is shared by every window and a new edit clears the redo stack.
126
133
 
127
- Unrecognized prompts go to the agent whenever a provider is configured (`DAWG_AI=0` disables it); see **Providers and auth** below. `src/agent/models.ts` holds the model catalog: frontier (`opus-5.5`, `fable-5.1`, `sol-6.1`, `gemini-3.1-pro`), fast (`sonnet-5.5`, `haiku-4.5`, `gpt-5.4-mini`, `gemini-3.8-flash`) and open weights (`deepseek-v4-pro`, `kimi-k3`, `qwen3.8-27b`, `glm-5.3`, `llama-4-maverick`), each with its gateway and OpenRouter ID, checked against the live gateway and OpenRouter model lists and tagged for tool use. The `/model` picker joins the catalog with the live list and shows only tool-calling models; any other `vendor/model` ID the provider lists is accepted too. `DAWG_MODEL` picks the model for one run, and an unknown value fails at startup with the valid list. The default is `opus-5.5`. `DAWG_OPUS_MODEL` / `DAWG_SOL_MODEL` still remap those two aliases.
134
+ Unrecognized prompts go to the agent whenever a provider is configured (`DAWG_AI=0` disables it); see **Providers and auth** below. `src/agent/models.ts` holds the model catalog: frontier (`opus-5.5`, `fable-5.1`, `sol-6.1`, `gemini-3.1-pro`), fast (`sonnet-5.5`, `haiku-5.5`, `haiku-4.5`, `gpt-5.4-mini`, `gemini-3.8-flash`, `glm-5.3-flash`) and open weights (`deepseek-v4-pro`, `kimi-k3`, `qwen3.8-27b`, `glm-5.3`, `llama-4-maverick`), each with its gateway and OpenRouter ID, checked against the live gateway and OpenRouter model lists and tagged for tool use. The `/model` picker joins the catalog with the live list and shows only tool-calling models; any other `vendor/model` ID the provider lists is accepted too, and on OpenRouter a routing variant suffix such as `:nitro`, `:free` or `:floor` is kept (`DAWG_MODEL=google/gemini-2.5-flash:nitro`). Anthropic models get a prompt-cache breakpoint on the static system prompt, which stays byte-identical across steps. `DAWG_MODEL` picks the model for one run, and an unknown value fails at startup with the valid list. The default is `opus-5.5`. `/model fast` (also `dawg model fast`, `DAWG_MODEL=fast`, and `gatewayModel`/`openrouterModel: "fast"` resolved at save) picks `haiku-5.5`, the fastest model in `bench/agent-eval` that passes the non-style tasks about as well as the default (`FAST_MODEL_ALIAS` in `src/agent/models.ts`; numbers in `docs/model-eval.md`). A catalog row may name a `fallback` on another vendor: `haiku-5.5` falls back to `glm-5.3-flash` for one try when every attempt failed before the first byte (repeated 5xx, header timeout, or a stream that sends nothing for `GATEWAY_FIRST_BYTE_TIMEOUT_MS`, 20 s; any byte, a keep-alive comment included, counts). A reply that starts with `Done: <summary>` beside its tool calls ends the turn once they apply, saving the summary round trip, unless the step wrote notes, rhythms, chords or structure (`CONTENT_TOOLS`), which always get a review step. `DAWG_OPUS_MODEL` / `DAWG_SOL_MODEL` still remap those two aliases.
128
135
 
129
136
  ### Agent turns
130
137
 
131
138
  A turn calls `POST /v1/chat/completions` with `stream: true` and one JSON-schema tool for each operation family. The OpenAI-compatible SSE stream is parsed locally with `fetch`, so the agent adds no runtime dependency. Each request sends a compact, deterministic **composition brief** instead of raw logs. It contains revision, tempo, meter, bars, key, tracks with instrument, mix, note count and pitch range, the focused track's notes, recent accepted operations and the instrument list. It is capped at 12 KiB and never includes environment values.
132
139
 
133
- When a tool call finishes streaming, it passes three checks: the tool's own argument checks, the planner's bounded operation validator, and a dry run of the score reducer. Only then is it committed through the session as a separate revision pinned to the revision it was planned against. If the call fails a check, or another window committed first (stale revision), dawg rejects it without changing the score. The model receives the diagnostic as the tool result and can correct itself. A turn is bounded to 8 steps, 32 tool calls, 256 KiB of streamed response and a 90 s timeout.
140
+ When a tool call finishes streaming, it passes three checks: the tool's own argument checks, the planner's bounded operation validator, and a dry run of the score reducer. Only then is it committed through the session as a separate revision pinned to the revision it was planned against. If the call fails a check, or another window committed first (stale revision), dawg rejects it without changing the score. The model receives the diagnostic as the tool result and can correct itself. A turn is bounded to 8 steps, 32 tool calls, 1 MiB of streamed response and a 90 s timeout.
134
141
 
135
142
  `runAgentTurn` (`src/agent/agent.ts`) emits structured progress events for the TUI: `step`, `text-delta`, `tool-start`, `tool-applied` (with `summary`, `baseRevision`, `resultRevision` and `trackId`), `tool-rejected` (with `diagnostic`), and a final `done` or `error` (`aborted`, `timeout`, `budget`, `provider`). To add an operation family, append a tool to `AGENT_TOOLS` in `src/agent/tools.ts`. The schema, dispatch and validation all come from that one entry.
136
143
 
144
+ Times in tools and the brief are beats from 0; the system prompt maps musical counts (beats 2 and 4 of a 4/4 bar are beats 1 and 3) so models place backbeats correctly. `update_notes` takes either an absolute `pitch` or a relative `transpose` in semitones, and its summary lists each moved note as `id before→after`. Tool schemas are sent with unions rewritten as `anyOf` branches (`src/agent/portable-schema.ts`) so Gemini accepts them. `bench/agent-eval/` measures how well a model drives these tools; see `docs/model-eval.md`.
145
+
146
+ ### Show-me (command mode)
147
+
148
+ In a terminal, with `/showme on` (the default) or `quiet` and an API provider (the gateway or a direct key), a turn runs in **command mode** (`src/agent/command-agent.ts`). The model writes dawg prompt commands, one per line, as a person types them. The prompt bar shows the line being written as ghost text at the model's own speed. Each complete line that parses (`isAgentCommand`, the same `commandParses` the prompt's typo check uses) runs through the TUI's `submit()`, the path Enter runs: same parser, commit, undo step and project sync. The score changes during the turn. Lines that are not commands are prose for the transcript. Window-only commands (`/model`, `model key`, `/showme`, `/theme`, `/quit`) never run. A failed line goes back to the model with its error for a correction round, up to 4 steps. A small JSON tool set stays for what commands cannot express: workspace files, web, media, `explain`, `preview_sound` and `measure_mix`.
149
+
150
+ A caption under the prompt names the gesture (`src/agent/show-me.ts`). For a parameter value, the fader glides about 150 ms in 30 ms steps through staged audition while the loop plays, then the command commits. For a note or drum hit, the caption names the play-mode key and the octave keys, and the note sounds in time behind the stream (`NoteScheduler`): grid-spaced when the model is faster than the tempo, step entry when slower. Media tools name the `dawg media` verb. At the end of the turn the caption gives a do-it-yourself hint with the Ctrl-K path. Nothing is replayed after the turn, and typing, play mode and the next turn are never blocked. `/showme off`, non-TTY runs and subscription providers use the JSON tool loop above. Command mode sends about a ninth of the input tokens and finishes in about half the time; see `docs/show-me.md` for the measurements and the exceptions.
151
+
137
152
  ### Workspace and web tools
138
153
 
139
154
  The project directory (the directory `dawg` runs in) is the agent's workspace. Six more entries in `AGENT_TOOLS` give the model bounded file and web access on both the gateway and xcb paths; `src/agent/workspace.ts` holds the path policy and `src/web/` the network side.
@@ -142,13 +157,13 @@ The project directory (the directory `dawg` runs in) is the agent's workspace. S
142
157
  - `write_file`, `edit_file`: only `song.ts` and the focused track's `tracks/<slug>/` directory (`core/slug.ts` derives the slug from the track name). Writes are atomic (temp file and rename), capped at 1 MiB, and create parent directories. `edit_file` replaces exactly one occurrence of `old`; zero or several matches return a count and nothing changes. `tracks/<slug>/notes.md` is the model's scratchpad and is never parsed. After a write, the optional host hook `onWorkspaceWrite(path)` can append text to the tool result (the project lane uses it to report how `track.ts` applied).
143
158
  - Every path is resolved lexically and then through `realpath`; `..`, absolute paths outside the root, symlinks that leave the project and anything under `.dawg/` are rejected with a diagnostic naming the writable roots. The brief gains a `project` entry with a tree of at most 30 lines and the first 1 KiB of the focused `notes.md`; both are shed before track summaries when the 12 KiB budget is tight.
144
159
  - `web_search` returns up to 8 `{title, url, snippet}` results. Providers, first match wins: `BRAVE_SEARCH_API_KEY` (explicit override); an AI Gateway key, which makes one non-streaming `anthropic/claude-haiku-4.5` call with the gateway's server-side search tool (`DAWG_WEB_SEARCH=exa|perplexity|parallel|browserbase`, default `exa`; the gateway bills the search, about $0.007 for Exa; its reported cost already includes that fee, so dawg records it as-is and uses the fee as an estimate only when no cost is reported; a live Exa search with three results cost $0.013); an OpenRouter key (`OPENROUTER_API_KEY`), which uses the `web` plugin and its `url_citation` annotations; else DuckDuckGo's HTML endpoint. `DAWG_WEB_SEARCH` can also pin a backend (`duckduckgo`, `openrouter`, `gateway`, `brave`) when its credentials exist. A failing paid provider falls through to DuckDuckGo and the result says so. The activity card names the answering provider (`searched via gateway · exa`), and `WebHost.onSpend` reports each billed search for the spend ledger.
145
- - `fetch_url` fetches one public http(s) URL locally: hostnames are resolved first and loopback, private, link-local, CGNAT and multicast addresses (IPv4, IPv6 and mapped) are refused, redirects (at most 3) are re-checked per hop, bodies stop at 2 MiB and the text handed to the model at 32 KiB. HTML is reduced to headings, lists, links and paragraphs; scripts, styles and navigation are dropped. Fetched text is untrusted and the system prompt says so.
160
+ - `fetch_url` fetches one public http(s) URL locally: hostnames are resolved first and loopback, private, link-local, CGNAT and multicast addresses (IPv4, IPv6 and mapped) are refused, the connection is pinned to the address that was checked (the Host header and TLS server name keep the original name, so DNS rebinding cannot swap in a private address), IPv4-compatible, 6to4, site-local and discard-prefix IPv6 forms are classified by what they embed, redirects (at most 3) are re-checked per hop, bodies stop at 2 MiB and the text handed to the model at 32 KiB. HTML is reduced to headings, lists, links and paragraphs; scripts, styles and navigation are dropped. Fetched text is untrusted and the system prompt says so.
146
161
 
147
162
  All limits live in `WORKSPACE_LIMITS`, `SEARCH_LIMITS` and `FETCH_LIMITS`. Search and fetch take an injectable `fetch` (and `lookup`), so tests run on fixtures in `src/web/fixtures/` without network. The gateway search fixture is derived from the documented response shape; capture a live response once to confirm it.
148
163
 
149
164
  ### Media tools
150
165
 
151
- Seven more `AGENT_TOOLS` entries (`src/media/`) turn reference audio into material for a track. They work on files under the focused track's `tracks/<slug>/downloads/` (the same slug and write scope as `write_file`), return project-relative output paths so the model can chain them, and never install anything: a missing binary is reported with its install command. `dawg media <verb>` runs the same code from the shell, and `dawg media doctor` lists the backend, each binary, how it runs and how to install it.
166
+ Seven more `AGENT_TOOLS` entries (`src/media/`) turn reference audio into material for a track. They work on files under the focused track's `tracks/<slug>/downloads/` (the same slug and write scope as `write_file`), return project-relative output paths so the model can chain them, and never install anything: a missing binary is reported with its install command. `dawg media <verb>` runs the same code from the shell, and `dawg media doctor` lists the backend, each binary, how it runs and how to install it, plus one line for the project's pitch analysis cache (`.dawg/analysis`: files, size against its 64 MB cap, least recently used pruned, tracker version).
152
167
 
153
168
  - `download_audio {url, name?}`: YouTube only (`youtube.com`, `youtu.be`, `music.youtube.com`). Writes `<name>.wav` plus a `<name>.json` sidecar (title, duration, source URL, backend, sha256, time). The same source URL is reused instead of downloaded again. yt-dlp runs with `--no-playlist`, `--max-filesize 500m` and a 15 min budget.
154
169
  - `split_stems {file}`: six stems (vocals, drums, bass, guitar, piano, other) into `<base>.stems/`, cached once present. 20 min budget.
@@ -164,7 +179,7 @@ Seven more `AGENT_TOOLS` entries (`src/media/`) turn reference audio into materi
164
179
 
165
180
  ### Providers and auth
166
181
 
167
- `src/agent/provider.ts` picks a backend per turn: `DAWG_PROVIDER`, then the choice saved in `~/.config/dawg/config.json`, then `auto` (AI Gateway, then OpenRouter, then a ready Codex or Claude subscription, else offline with a `dawg login` hint). The config holds `provider`, the model and, for subscriptions, the xcb account. It is written atomically with 0600 permissions and never holds a key. A saved choice that stops working (revoked key, account gone) comes back as `offline` with `invalidSaved` and the reason; startup and `dawg login` say so once and open the picker rather than switching providers. Only `dawg logout`, `/logout`, `dawg login <provider>` and `/model` change it.
182
+ `src/agent/provider.ts` picks a backend per turn: `DAWG_PROVIDER`, then the choice saved in `~/.config/dawg/config.json`, then `auto` (AI Gateway, then OpenRouter, then a ready Codex or Claude subscription, else offline with a `dawg model key` hint). The config holds `provider`, the model and, for subscriptions, the xcb account. It is written atomically with 0600 permissions and never holds a key. A saved choice that stops working (revoked key, account gone) comes back as `offline` with `invalidSaved` and the reason; startup and `dawg model key` (alias `dawg login`) say so once and open the picker rather than switching providers. Only `dawg logout`, `/logout`, `dawg model key <provider>` and `/model` change it.
168
183
 
169
184
  Keys (`ai-gateway`, `openrouter`) resolve from the environment (`AI_GATEWAY_API_KEY`, `OPENROUTER_API_KEY`), then the macOS Keychain (service `dawg`), then `~/.config/dawg/credentials.json` (0600 under a 0700 directory, written atomically through a temp file and rename). Storing uses `security -i` with the command on stdin, so a key never appears on an argv. Keys must match `[A-Za-z0-9._-]{16,256}` and are shown only masked (`vck_…abcd`). Nothing auth-related goes to `.dawg/`.
170
185
 
@@ -178,9 +193,9 @@ Keys (`ai-gateway`, `openrouter`) resolve from the environment (`AI_GATEWAY_API_
178
193
 
179
194
  Every subprocess goes through the injectable `CommandRunner` in `src/auth/runner.ts`, which bounds output, writes stdin and on abort sends SIGTERM (SIGKILL after 15 s) and waits for exit. Tests script `vercel`, `security` and `xcb` through it and run OpenRouter against a local fake server.
180
195
 
181
- `/login` in the TUI calls `handoff()`: it stops the frame timer, detaches stdin, leaves raw mode, bracketed paste and the alternate screen, runs the same flow on the real terminal, then re-enters, clears and forces a full redraw.
196
+ `model key` (alias `/login`) in the TUI calls `handoff()`: it stops the frame timer, detaches stdin, leaves raw mode, bracketed paste and the alternate screen, runs the same flow on the real terminal, then re-enters, clears and forces a full redraw.
182
197
 
183
- The gateway and OpenRouter share `src/agent/gateway.ts`, an OpenAI-compatible streaming client with tool calls that requests `stream_options.include_usage`. `src/agent/usage.ts` prices each usage chunk, using the provider's own `cost` when present and otherwise tokens × the models.dev price. It keeps the session total and a daily ledger in `~/.config/dawg/usage.json` (31 days, lock file plus atomic rename) that windows share, and draws the spend line under the prompt (`$0.12 session · $0.48 today · opus-5.5 · gateway`; `subscription` for xcb; `no model · dawg login` offline; it narrows by dropping today, then session). Billed web searches add to the same meter. Prices come from `https://models.dev/api.json`, cached in `~/.config/dawg/cache/` for 24 h, fetched with a timeout and size cap, with a stale cache preferred to nothing offline; on OpenRouter its own `/models` prices win. The picker's `~$0.005/prompt` is `TYPICAL_PROMPT` (≈ 29,800 input + 600 output tokens, measured from the system prompt, tool schemas and a fixture brief over about 2 requests) × price.
198
+ The gateway and OpenRouter share `src/agent/gateway.ts`, an OpenAI-compatible streaming client with tool calls that requests `stream_options.include_usage`. `src/agent/usage.ts` prices each usage chunk, using the provider's own `cost` when present and otherwise tokens × the models.dev price. It keeps the session total and a daily ledger in `~/.config/dawg/usage.json` (31 days, lock file plus atomic rename) that windows share, and draws the spend line under the prompt (`$0.12 session · $0.48 today · opus-5.5 · gateway`; `subscription` for xcb; `commands only` offline, with no model key nag; it narrows by dropping today, then session). Billed web searches add to the same meter. Prices come from `https://models.dev/api.json`, cached in `~/.config/dawg/cache/` for 24 h, fetched with a timeout and size cap, with a stale cache preferred to nothing offline; on OpenRouter its own `/models` prices win. The picker's `~$0.005/prompt` is `TYPICAL_PROMPT` (≈ 53,000 input + 600 output tokens, measured from the system prompt, tool schemas and a fixture brief over about 2 requests at about 3 bytes per token, as the agent eval in `bench/agent-eval` observed) × price.
184
199
 
185
200
  The xcb provider (`src/agent/xcb.ts`, `src/agent/xcb-agent.ts`) calls `xcb --json generate` with one `{version:1, account, model, prompt, timeoutMs, maxOutputBytes}` request on stdin. xcb exposes zero tools and does not stream, so the prompt carries the system rules, the composition brief and the tool catalog as JSON schemas, and asks for exactly one `{ops:[{tool,args}], say?, done}` object. The reply is untrusted. dawg takes the first balanced JSON object in at most 64 KiB, allows at most 16 ops and caps `say` at 400 characters. Each op then goes through `executeCall`, the same argument checks, operation validator, reducer dry run and per-op revision commit used by the gateway loop, and emits the same `tool-applied`/`tool-rejected`/`text-delta` events. If a reply cannot be parsed, an op is rejected or `done` is false, dawg makes another call with the per-op results, up to 3 calls and within the normal turn budgets. Esc aborts the turn, which terminates the xcb child and keeps every accepted revision. The child timeout is `timeoutMs + 75 s`, because the first `generate` per binding (and after an xcb or provider update) admits the account and can take up to a minute longer. A `busy` result, when two first calls hit one account, is retried with backoff. Accounts come from `xcb --json generate --capabilities`, parsed field by field from `unknown`. dawg never runs the xcb installer.
186
201
 
@@ -188,7 +203,9 @@ The xcb provider (`src/agent/xcb.ts`, `src/agent/xcb-agent.ts`) calls `xcb --jso
188
203
 
189
204
  Playback renders the score to interleaved stereo 16-bit PCM with deterministic sine, piano, pluck, bass, saw, square, and triangle voices and a synthesized kit whose noise comes from a PRNG seeded by each note, so every render is byte-identical. Track volume and pan automation, the low-pass filter (with cutoff and resonance lanes), the delay send (with feedback and mix lanes), and the reverb send are applied per track; mute always silences a track and any solo silences unsoloed tracks. Pan uses an equal-power law (-1 left, 1 right). The delay is a stereo ping-pong (first repeat on the panned side, later repeats alternate) and the reverb is a Freeverb-style network of eight parallel damped combs and four series allpasses per channel, with the right channel's delay lines offset for width; both use only integer delay lengths and fixed coefficients, so renders stay deterministic.
190
205
 
191
- Audio engine. With `dawgd` running only the daemon plays audio; on the file-lock fallback a per-session audio lock keeps multiple TUI windows from starting duplicate voices. The engine renders one loop with every tail (release, delay, reverb) folded back onto the loop start, so the buffer repeats seamlessly, and streams it as raw s16le stereo into one long-lived player process, paced by the wall clock with about 200 ms queued. An edit renders the new loop and swaps it in at the current loop position without restarting the player; a tempo change keeps the musical beat; a seek or a drift above 30 ms re-anchors the write position to the shared transport clock, offset by the queued audio, so the transport matches what you hear. Backends, in order: `ffplay -f s16le -i -`, then SoX `play -t raw -`, then (macOS) `afplay` re-rendering a loop-folded WAV rotated to the current beat on each edit, the only backend that restarts. `DAWG_AUDIO_BACKEND=ffplay|sox|afplay|none` forces one, `DAWG_AUDIO_PLAYER="cmd {rate} {channels}"` streams into any stdin player, and `DAWG_AUDIO=0` disables sound. `dawg auth status` and `/auth` print the detected backend. The renderer is deterministic and independently testable; a native or sample-backed instrument backend can replace it behind the same player port.
206
+ Audio engine. With `dawgd` running only the daemon plays audio; on the file-lock fallback a per-session audio lock keeps multiple TUI windows from starting duplicate voices. The engine renders one loop with every tail (release, delay, reverb) folded back onto the loop start, so the buffer repeats seamlessly, and streams it as raw s16le stereo into one long-lived player process, paced by the wall clock with about 200 ms queued. An edit renders the new loop and swaps it in at the current loop position without restarting the player; a tempo change keeps the musical beat; a seek or a drift above 30 ms re-anchors the write position to the shared transport clock, offset by the queued audio, so the transport matches what you hear. Backends, in order: the native sink (`native/sink`, a Rust/cpal ring drained by the device's audio callback, loaded through bun:ffi after its sha256 matches the shipped manifest; it receives the same s16le stream, converted exactly to f32, paced by a 5 ms pump with a 15 ms play lead), then `ffplay -f s16le -i -`, then SoX `play -t raw -`, then (macOS) `afplay` re-rendering a loop-folded WAV rotated to the current beat on each edit, the only backend that restarts. `DAWG_AUDIO_BACKEND=native|ffplay|sox|afplay|none` forces one, `DAWG_AUDIO_PLAYER="cmd {rate} {channels}"` streams into any stdin player, and `DAWG_AUDIO=0` disables sound. `dawg auth status` and `/auth` print the detected backend; `dawg doctor` also prints why the native sink is or is not in use, the play lead, the audio devices, and the output and input saved from the audio menu (`chosen: output USB Audio Interface · input default (Built-in Microphone)`, `(missing, using default)` when a saved device is gone, and the file it lives in; `chosen` in `--json`). The renderer is deterministic and independently testable; a native or sample-backed instrument backend can replace it behind the same player port.
207
+ Audio devices (`src/audio/devices.ts`, `src/audio/audio-command.ts`). One output and one input, each a device name or the system default; there is no routing matrix. Ctrl-K › Project › audio shows two rows (output and input), each opening the device list (arrows, Enter picks, Esc back); `audio` shows the choice, `audio out <name|default>` and `audio in <name|default>` set it (exact, case-insensitive or a unique prefix of a listed name), and `audio test` plays a short tone on the output, then meters one second of the input (peak dBFS). Picking an output plays a soft blip on it through its own short stream. The choice is saved per machine in `<config>/audio.json` (`~/.config/dawg/audio.json`, or under `DAWG_CONFIG_DIR`), never in the project; `DAWG_AUDIO_DEVICE` still overrides the output. Every engine (the window's, the file-lock fallback's, dawgd's) follows the file: it reads it before each player starts and checks it about once a second while playing, so a pick moves playback at the frame being heard. When the chosen output is gone (unplugged mid-song, or missing at start) the engine opens the system default and says so once (`audio output "<name>" is unavailable · playing on the system default`); the saved choice is kept for the next start. Choosing needs the native sink, which lists and opens devices by name. On SoX, `audio out <name>` sets `AUDIODEV` for `play` but cannot list devices or choose an input; ffplay and afplay always play on the system default, and `audio` says why (with the native sink's unavailable reason). The input is chosen and metered only; recording takes is a later lane.
208
+
192
209
  Set `DAWG_AUDIO=0` for headless sessions.
193
210
 
194
211
  Use `DAWG_DEMO=1 bun run src/main.ts` for a deterministic non-interactive frame stream while developing the renderer.
@@ -212,16 +229,16 @@ tracks/<slug>/samples/ audio a sampler references by relative path
212
229
 
213
230
  SDK. `core/sdk/v1.ts` is one dependency-free file with JSDoc on every export, because its signatures are what an agent reads. Authors write beats; `song()` returns a `track.loop/v1` document in integer ticks (`round(beat × ticksPerBeat)`). Builders: `note(pitch, start, length = 1, velocity = 0.8)`, `seq("E2 . G2", {from, step, len, vel})` (`.`, `-`, `_` rest), `hit(voice, start, velocity, length = 0.25)` and `hits(voice, beats)` for `kit` voices (`kick`, `snare`, `hat`, …) and sampler voices, `every(step, {from, until})`, `sampler(voices, {mode})`, `slices(src, count)`, `chord(symbol, start, length, opts)`, `progression(chords, opts)` (see [Chords](#chords)), `track({...})` and `song({...})`. Note ids are content hashes, so the files never carry them and dawg keeps the session's ids for notes that did not change.
214
231
 
215
- Evaluation. `evaluateProject(dir)` (`core/sdk/eval.ts`) imports `song.ts` in a fresh `bun --no-addons --no-install` child with cwd at the project, an environment of only `PATH`, `HOME` and `TMPDIR`, a 10 s timeout and 1 MiB of output, then decodes the document through the ordinary score validator. Failures are diagnostics `file:line:col message`, never throws. Evaluation is a guard against mistakes, not a sandbox: project code runs with your user's file access, like any build script.
232
+ Evaluation. `evaluateProject(dir)` (`core/sdk/eval.ts`) imports `song.ts` in a fresh `bun --no-addons --no-install` child with cwd at the project, an environment of only `PATH`, `HOME` and `TMPDIR`, a 10 s timeout and 32 MiB of output (above the largest score `SCORE_LIMITS` allows; an overflow is reported as such, not as a timeout), then decodes the document through the ordinary score validator. Failures are diagnostics `file:line:col message`, never throws. Evaluation is an isolated process, not a sandbox: project code runs with your user's file and network access, like any build script, and the sync evaluates it on startup and on every change, so open only projects you trust.
216
233
 
217
234
  Typecheck. `typecheckProject(dir)` (`src/project/typecheck.ts`) runs the native TypeScript 7 compiler from the `typescript` dependency with `--incremental` state in `.dawg/tsbuild`. A cold check of a three-track project takes about 65 ms and a warm one about 26 ms on an M-series Mac. The header shows `types ✓` or `types ✗ N`.
218
235
 
219
236
  Two-way sync (`src/project/sync.ts`) runs in every window of a project:
220
237
 
221
238
  - Files to score: `fs.watch` on the project and `tracks/` (plus a 1.5 s poll) with a 150 ms debounce. A changed source is evaluated, its notes adopt the session's ids, and `diffScores` (`core/diff.ts`) turns the difference into the smallest list of score operations, committed as one `files.apply` revision through the session port, so dawgd rebases it like an agent intent and undo drops it as one step. The window shows `applied from files · 2 notes, 1 track`. A file that fails to evaluate leaves the score untouched and shows `files rejected · <diagnostic>` once per distinct error.
222
- - Score to files: after any accepted revision (TUI, agent, another window) the window reprints only the files whose bytes would change. A file whose evaluation already equals the score is never rewritten, so hand formatting and comments survive until the content they describe changes. A track file left behind by a rename or removal is deleted only if its hash still matches what dawg wrote; a hand-edited one is kept.
223
- - Startup: if any source differs from the hash in `.dawg/sync.json` (edited while dawg was closed, or never written by dawg), the files win; otherwise the session wins and the files are reprinted.
224
- - Echo: a window skips files whose hashes it already evaluated; another window's write costs one no-op evaluation.
239
+ - Score to files: after any accepted revision (TUI, agent, another window) the window reprints only the files whose own track (or song) slice changed since their last evaluation, so hand formatting and comments in untouched files survive. A file whose bytes no longer hash to what dawg last wrote (edited in an editor, or failing to evaluate) is never overwritten; the window shows `<file> edited · not overwritten` and the next look applies it. `.dawg/sync.json` is read, written and merged under `.dawg/sync.lock`, and every write is fsynced with its directory. A track file left behind by a rename or removal is deleted only if its hash still matches what dawg wrote; a hand-edited one is kept.
240
+ - Startup: the files belong to one session at a time (recorded in `.dawg/sync.json`). A window on another session while the owner is still open stays detached and shows `files belong to session <id>`. Otherwise, if any source differs from the hash in `.dawg/sync.json` (edited while dawg was closed, or never written by dawg), the files win; else the session wins and the files are reprinted.
241
+ - Echo: a file whose hash is one dawg wrote (in any window) or one this window already evaluated is skipped, so a window never reverts its own newer edit by applying another window's reprint. Hashes cover every project source (`.ts`/`.js`/`.json` helper modules, `.scl`/`.kbm` tunings), so editing an imported helper syncs too; a look with nothing new reports `files unchanged`.
225
242
  - The agent's `write_file`/`edit_file` on a `.ts` source applies before the tool result returns; the result carries the outcome line, `types ✓` or `types ✗ N` and up to eight diagnostics.
226
243
 
227
244
  The printer (`core/sdk/print.ts`) is deterministic and Prettier-stable (`prettier --check` passes on its output), prints only non-default fields, and satisfies `print(evaluate(print(score))) = print(score)`.
@@ -235,7 +252,7 @@ Score format. The score stays `track.loop/v1` with `version: 1`: every addition
235
252
  Every track has one fixed effects chain (`FX_CHAIN` in `core/fx.ts`, DSP in `src/audio/effects/`):
236
253
 
237
254
  ```text
238
- filter → djf → autofilter → vowel → crush → distort → stomp → head → cab → tremolo → compressor → pan → phaser → chorus → leslie → postgain → delay → reverb → [mix: orbit → duck]
255
+ filter → djf → autofilter → formant → vowel → crush → distort → stomp → head → cab → tremolo → compressor → pan → phaser → chorus → leslie → postgain → delay → reverb → [mix: orbit → duck]
239
256
  ```
240
257
 
241
258
  Stages before `pan` run on the track's mono voice sum; pan spreads it to stereo with the equal-power law; the rest run on the stereo pair. An effect that is off costs nothing. The core set — **filter, auto filter, distortion, tremolo, compressor, chorus, delay, reverb** — leads the Effects menu and the agent brief; dj filter, vowel, bitcrush, phaser, leslie, post gain, orbit and duck are under **more effects** for Strudel parity.
@@ -264,7 +281,7 @@ automate distort-drive points 0:1 8:6 every numeric fx param has a lane
264
281
 
265
282
  Aliases: `dist`, `comp`, `room`, `bitcrush`, `trem`, `auto-filter`, `bus`/`o` (orbit), `sidechain`/`duckorbit` (duck), `lpf`/`hpf`/`bpf` (filter with that type). The menu's Effects section opens each effect on its on/off toggle, presets and simple parameters; **advanced** lists every parameter with its Strudel names. The agent's `set_fx` tool takes the same names and presets.
266
283
 
267
- Presets: filter `warm dark acid thin telephone`; autofilter `slow-sweep wobble s&h hpf-rise env-follow`; distort `warm crunch fuzz fold shape`; tremolo `gentle eighth-chop pulse`; compressor `gentle punch squash`; chorus `subtle wide seasick`; delay `ping-pong dotted-eighth slapback dub`; reverb `room hall plate ambient`; djf `dark thin`; vowel `a o ee`; crush `8-bit lofi destroy`; phaser `slow fast`; leslie `fast slow`; duck `pump subtle gate`.
284
+ Presets: filter `warm dark acid thin telephone`; autofilter `slow-sweep wobble s&h hpf-rise env-follow`; distort `warm crunch fuzz fold shape`; tremolo `gentle eighth-chop pulse`; compressor `gentle punch squash`; chorus `subtle wide seasick`; delay `ping-pong dotted-eighth slapback dub`; reverb `room hall plate ambient`; djf `dark thin`; formant `deep giant bright tiny`; vowel `a o ee`; crush `8-bit lofi destroy`; phaser `slow fast`; leslie `fast slow`; duck `pump subtle gate`.
268
285
 
269
286
  The DSP is clean-room, written from public documentation of the parameters and standard literature (RBJ biquads, a Stilson/Smith-style ladder, Freeverb-style combs and allpasses, the Giannoulis–Massberg–Reiss compressor), not from Strudel or superdough source (AGPL). Renders stay deterministic: the random S&H shape hashes the cycle index, so cold, cached and worker renders are byte-identical (`src/audio/renderer.test.ts`).
270
287
 
@@ -286,8 +303,12 @@ Parameters (**bold** effect = shown in the simple menu; Lane = automation lane):
286
303
  | **autofilter** | shape | sine / tri / square / saw / ramp / random | sine | | |
287
304
  | autofilter | phase | 0..1 | 0 | | |
288
305
  | autofilter | follow | -6..6 oct | 0 | `lpenv (per note, see synth)` | `autofilter-follow` |
306
+ | **formant** | shift | -12..12 st | 0 | | `formant-shift` |
307
+ | **formant** | mix | 0..1 | 1 | | `formant-mix` |
289
308
  | **vowel** | vowel | a / e / i / o / u / ae / aa / oe / ue / y / uh / un / en / an / on | a | `vowel` | |
290
309
  | **vowel** | mix | 0..1 | 1 | | `vowel-mix` |
310
+ | vowel | to (optional) | a / e / i / o / u / ae / aa / oe / ue / y / uh / un / en / an / on | (none) | | |
311
+ | vowel | morph (optional) | 0..1 | 0 | | `vowel-morph` |
291
312
  | **crush** | bits | 1..16 | 8 | `crush` | `crush-bits` |
292
313
  | **crush** | coarse | 1..64 | 1 | `coarse` | |
293
314
  | **crush** | mix | 0..1 | 1 | | `crush-mix` |
@@ -352,16 +373,16 @@ rig show the focused track's rig
352
373
  rig reset remove all three stages
353
374
  stomp fuzz | stomp gain 7 head lead | head treble 7 gate -55 | cab 4x12 | cab mic 0.6
354
375
  fx head gain 4 the same stages through the generic fx grammar
355
- track jangle a new guitar track: a guitar voice plus the jangle rig
376
+ rig jangle the jangle rig on the focused track (alias: track jangle makes a new guitar track)
356
377
  ```
357
378
 
358
379
  - **stomp** `type` `fuzz` (Big Muff-style, with its tone stack), `face` (Fuzz Face-style), `od` (Tube Screamer-style mid hump and soft clip), `rat` (op-amp hard clip and filter), `octave` (Octavia-style full-wave rectifier, `octave` sets the blend). Each pedal is level-matched to bypass from a fixed -18 dBFS 196 Hz sine (within 1 dB at every type and gain), so kicking on a fuzz does not jump the track; `level` is a trim on top. Presets `muff face screamer rat octavia boost`.
359
380
  - **head** `type` `clean` (Fender-style blackface), `chime` (Vox AC-style top boost), `crunch` and `lead` (Marshall-style), `high` (modern high gain), `solid` (clean solid state), `bass` (bass amp). Each has its own Yeh–Smith passive tone stack (`bass mid treble`), a `presence` shelf, power-amp `sag` and a `master`. Each type's makeup gain is calibrated once from a fixed -30 dBFS 196 Hz sine, so switching heads keeps the level within 1 dB. `gate` (dB threshold, absent = off) is a noise gate before the preamp with hysteresis and a short hold. Presets `blackface ac plexi lead modern jc svt`.
360
- - **cab** `type` `1x12 2x12 4x12 1x10 open 8x10 1x15 di`: biquad speaker models (low resonance, presence peak, cone break-up roll-off); `mic` moves from the cone centre (bright) to the edge (dark); `di` is the band-limited direct box for bass.
381
+ - **cab** `type` `1x12 2x12 4x12 1x10 open 8x10 1x15 di`: biquad speaker models (low resonance, presence peak, cone break-up roll-off); `mic` moves from the cone center (bright) to the edge (dark); `di` is the band-limited direct box for bass.
361
382
 
362
- Rig presets (`RIG_PRESETS` in `core/fx.ts`): `clean crunch punk ragged lead metal fuzz octave funk wah bachata spring bassdrive reese jangle alt`. A rig writes its three stages and its companion effects (`funk` an envelope filter, `wah` an auto-wah, `bachata` a chorus, `jangle` a compressor, `spring` the track reverb); switching rigs or `rig reset` removes the previous rig's companions while they still hold the values the rig wrote, and keeps any you edited. Each stage stays editable afterwards, and the rig row then reads `—`. Track words `jangle punk funk ragged gtr-lead gtr-metal bachata` create a guitar track on every path (`track jangle`, `dawg jangle`, `instrument jangle`, the agent's create_track and set_instrument, SDK `instrument: "jangle"`): the strings `electric` voice (the 12-string `jangle` preset for jangle) plus that rig. `lead` and `bass` keep their synth meaning.
383
+ Rig presets (`RIG_PRESETS` in `core/fx.ts`): `clean crunch punk ragged lead metal fuzz octave funk wah bachata spring bassdrive reese jangle alt`. A rig writes its three stages and its companion effects (`funk` an envelope filter, `wah` an auto-wah, `bachata` a chorus, `jangle` a compressor, `spring` the track reverb); switching rigs or `rig reset` removes the previous rig's companions while they still hold the values the rig wrote, and keeps any you edited. Each stage stays editable afterwards, and the rig row then reads `—`. `rig <preset>` is the one word for the amp and pedals. The track words `jangle punk funk ragged gtr-lead gtr-metal bachata` remain as aliases that create a guitar track with that rig on every path (`track jangle`, `dawg jangle`, `instrument jangle`, the agent's create_track and set_instrument, SDK `instrument: "jangle"`): the strings `electric` voice (the 12-string `jangle` preset for jangle) plus that rig. `lead` and `bass` keep their synth meaning.
363
384
 
364
- `amp` stays Strudel's linear gain: `fx amp` and `amp crunch` answer "did you mean head (guitar amp)?". Lanes: `stomp-gain`, `stomp-tone`, `head-gain` (coefficients follow automation every 32 samples, only while automated). Menu: **Effects › Guitar rig** (rig preset row, then Stomp box, Amp head, Speaker cabinet). Agent: `set_rig`. SDK: `fx: { ...rig("crunch") }` or the stages by name. In play mode a held note through a rig sounds its first 0.75 s window at render quality at once and the rest renders on a background worker in key order, so key handling never waits on it (the stages are causal, so the window is the exact prefix of the whole note).
385
+ `amp` stays Strudel's linear gain: `fx amp` and `amp crunch` answer "did you mean head (guitar amp)?". Lanes: `stomp-gain`, `stomp-tone`, `head-gain` (coefficients follow automation every 32 samples, only while automated). Menu: **Ctrl-K › Effects › Guitar rig** (rig preset row, then Stomp box, Amp head, Speaker cabinet). Agent: `set_rig`. SDK: `fx: { ...rig("crunch") }` or the stages by name. In play mode a held note through a rig sounds its first 0.75 s window at render quality at once and the rest renders on a background worker in key order, so key handling never waits on it (the stages are causal, so the window is the exact prefix of the whole note).
365
386
 
366
387
  | Effect | Param | Range | Default | Lane |
367
388
  | --------- | --------------- | --------------------------------------------------- | ------- | ------------ |
@@ -397,7 +418,7 @@ fx swell time 0.5 volume swell: each strum fades in, no pick attack
397
418
  fx double a second, seeded take a few ms late, spread left and right
398
419
  rig shoegaze fuzz + chime head + wobble + bloom + double + a big reverb
399
420
  fx reverb ir builtin:reverse reverse-gate swell after the attack, not a pre-verb (also builtin:gate and builtin:spring)
400
- track shoegaze a new electric guitar track with the shoegaze rig
421
+ track shoegaze alias: a new electric guitar track named shoegaze, with the shoegaze rig
401
422
  ```
402
423
 
403
424
  - **wobble** (`depth` 0..100 cents, `rate` Hz, `drift` 0 periodic .. 1 wandering) is a modulated fractional delay read with a 4-point interpolator, like a held vibrato arm or tape wow. Its peak deviation equals `depth` (a sustained A3 at depth 35 swings ±35 c). `depth` is automatable.
@@ -405,7 +426,7 @@ track shoegaze a new electric guitar track with the shoegaze rig
405
426
  - **swell** (`time` s, `mix`) restarts a raised-cosine fade at each note onset: a volume-pedal swell without the pick.
406
427
  - **double** (`time` 5..60 ms, `drift` ms, `width`) is automatic double tracking: a second take through a modulated delay whose timing wanders by `drift`, panned against the dry take. Left/right correlation stays between 0.3 and 0.8.
407
428
 
408
- Rig presets gain `shoegaze glide dreampop swell ebow` (the `swell` rig is the swell effect plus a clean amp and a room; `ebow` is swell plus a bloom on the note itself). `jangle` now also writes a light `double`; a project that loaded jangle before 0.6.1 keeps its stored stages and renders unchanged until `rig jangle` is applied again. In `song.ts`, `rig("jangle")` and `instrument: "jangle"` keep the 0.6.0 stages (no double) so existing files render unchanged; add `double: {}` for the 0.6.1 sound. `instrument: "shoegaze"`, `"dreampop"` and `"ebow"` (the rig names that are also instrument words; `glide` and `swell` are reached with `rig glide`/`rig swell` or `fx: rig("glide")`) also set the rig's reverb wash, like `track shoegaze`; `rig()` returns effects only, so pair it with `reverb: rigReverb("shoegaze")`. The rigs carry a `postgain` trim so the shoegaze rigs land within 2.5 dB of `clean` on a held chord, and `shoegaze`, `glide` and `dreampop` also on a dense strum. `swell` and `ebow` restart their fade on every onset, so a fast strum through them sits several dB lower; they are made for held notes and slow chords. The built-in impulses `reverse` (energy rising to a hard stop: a reverse-gate swell that rises after each attack, not a pre-verb that swells into the note; no rig preset uses it, so reach it with `fx reverb ir builtin:reverse`), `gate` (a dense, flat 250 ms burst cut short) and `spring` (a dispersive, chirping tank) are generated from seeded noise like `room hall plate`; no recorded IR ships. Menu: **Effects › Shoegaze** and the rig preset row in **Effects › Guitar rig**. Agent: `set_fx` and `set_rig`. SDK: `fx: { ...rig("shoegaze") }` or `fx: { wobble: { depth: 30 } }`. A full shoegaze rig costs under 25 ms per track-second.
429
+ Rig presets gain `shoegaze glide dreampop swell ebow` (the `swell` rig is the swell effect plus a clean amp and a room; `ebow` is swell plus a bloom on the note itself). `jangle` now also writes a light `double`; a project that loaded jangle before 0.6.1 keeps its stored stages and renders unchanged until `rig jangle` is applied again. In `song.ts`, `rig("jangle")` and `instrument: "jangle"` keep the 0.6.0 stages (no double) so existing files render unchanged; add `double: {}` for the 0.6.1 sound. `instrument: "shoegaze"`, `"dreampop"` and `"ebow"` (the rig names that are also instrument words; `glide` and `swell` are reached with `rig glide`/`rig swell` or `fx: rig("glide")`) also set the rig's reverb wash, like `rig shoegaze`; `rig()` returns effects only, so pair it with `reverb: rigReverb("shoegaze")`. The rigs carry a `postgain` trim so the shoegaze rigs land within 2.5 dB of `clean` on a held chord, and `shoegaze`, `glide` and `dreampop` also on a dense strum. `swell` and `ebow` restart their fade on every onset, so a fast strum through them sits several dB lower; they are made for held notes and slow chords. The built-in impulses `reverse` (energy rising to a hard stop: a reverse-gate swell that rises after each attack, not a pre-verb that swells into the note; no rig preset uses it, so reach it with `fx reverb ir builtin:reverse`), `gate` (a dense, flat 250 ms burst cut short) and `spring` (a dispersive, chirping tank) are generated from seeded noise like `room hall plate`; no recorded IR ships. Menu: **Ctrl-K › Effects › Shoegaze**, and the rig preset row in **Ctrl-K › Effects › Guitar rig**. Agent: `set_fx` and `set_rig`. SDK: `fx: { ...rig("shoegaze") }` or `fx: { wobble: { depth: 30 } }`. A full shoegaze rig costs under 25 ms per track-second.
409
430
 
410
431
  | Effect | Param | Range | Default | Lane |
411
432
  | ---------- | ------ | ------------ | ------- | -------------- |
@@ -437,7 +458,7 @@ The song master is an optional chain after every track, orbit bus and duck are s
437
458
 
438
459
  Targets: `streaming` -14 LUFS / -1 dBTP, `apple` and `podcast` -16, `broadcast` -23 (EBU R 128), `classical` -20, `ambient` -18, `club` -8 / -2 dBTP (with the `loud` limiter) and `loud` -6 / -2 dBTP (with the `brick` limiter, for hyperpop, gabber and hardcore; masters louder than -14 LUFS stay under -2 dBTP because lossy encoding of loud, dense material adds inter-sample overs). With the limiter on, a target sets the limiter's input gain: a bracketing search on the measured integrated loudness that starts from the linear estimate, gives the same answer for the same mix every time, and stops early with `reached: false` once more drive no longer raises the loudness. At -6 LUFS the limiter alone may stop short; `tape preset crush` or a lower glue threshold before it helps; without it, the gain is capped so the true peak stays at or below -1 dBTP, so a quiet target is always met and a loud one may fall short (the measurement says so).
439
460
 
440
- Measurement follows ITU-R BS.1770-4 and EBU R 128: K-weighting derived for any sample rate, 400 ms momentary and 3 s short-term windows (maxima on a 10 ms grid), the -70 LUFS absolute and -10 LU relative gates for integrated loudness, the EBU Tech 3342 loudness range (-20 LU gate, 10th to 95th percentile of short-term values), and true peak from the BS.1770-4 Annex 2 4x oversampling filter. Tests check EBU Tech 3341 cases 1–5, 9–14 (11 and 14 modelled) and 15–19 and Tech 3342 cases 1–4 within their tolerances. The parameters are in `core/master.ts` (`MASTER_SPECS`, `MASTER_PRESETS`, `LOUDNESS_TARGETS`).
461
+ Measurement follows ITU-R BS.1770-4 and EBU R 128: K-weighting derived for any sample rate, 400 ms momentary and 3 s short-term windows (maxima on a 10 ms grid), the -70 LUFS absolute and -10 LU relative gates for integrated loudness, the EBU Tech 3342 loudness range (-20 LU gate, 10th to 95th percentile of short-term values), and true peak from the BS.1770-4 Annex 2 4x oversampling filter. Tests check EBU Tech 3341 cases 1–5, 9–14 (11 and 14 modeled) and 15–19 and Tech 3342 cases 1–4 within their tolerances. The parameters are in `core/master.ts` (`MASTER_SPECS`, `MASTER_PRESETS`, `LOUDNESS_TARGETS`).
441
462
 
442
463
  Parameters (**bold** unit = shown in the simple menu; the rest are under `advanced`). Glue's `auto` make-up adds half the reduction a full-scale peak gets, so glue on and off compare near level-matched. Clean EQ corners above 0.45 × the render rate are clamped there.
443
464
 
@@ -448,10 +469,10 @@ Parameters (**bold** unit = shown in the simple menu; the rest are under `advanc
448
469
  | **eq** | low | -12..12 dB | 0 | low shelf gain |
449
470
  | eq | lowfreq | 20..1000 Hz | 100 | low shelf corner |
450
471
  | **eq** | bell1 | -12..12 dB | 0 | first bell gain |
451
- | eq | bell1freq | 40..16000 Hz | 400 | first bell centre |
472
+ | eq | bell1freq | 40..16000 Hz | 400 | first bell center |
452
473
  | eq | bell1q | 0.1..10 | 1 | first bell width: higher is narrower |
453
474
  | **eq** | bell2 | -12..12 dB | 0 | second bell gain |
454
- | eq | bell2freq | 200..18000 Hz | 3000 | second bell centre |
475
+ | eq | bell2freq | 200..18000 Hz | 3000 | second bell center |
455
476
  | eq | bell2q | 0.1..10 | 1 | second bell width: higher is narrower |
456
477
  | **eq** | high | -12..12 dB | 0 | high shelf gain |
457
478
  | eq | highfreq | 1000..20000 Hz | 10000 | high shelf corner |
@@ -469,7 +490,7 @@ Parameters (**bold** unit = shown in the simple menu; the rest are under `advanc
469
490
  | tape | tone | 2000..20000 Hz | 20000 | high-frequency roll-off after the curve; 20000 is off |
470
491
  | **tape** | mix | 0..1 | 1 | dry/wet |
471
492
  | **width** | width | 0..2 | 1 | side level: 0 is mono, 1 unchanged, 2 twice as wide |
472
- | **width** | mono | 0..300 Hz | 120 | below this the mix is mono (keeps bass centred); 0 is off |
493
+ | **width** | mono | 0..300 Hz | 120 | below this the mix is mono (keeps bass centered); 0 is off |
473
494
  | **limiter** | ceiling | -12..0 dBTP | -1 | highest true peak out |
474
495
  | **limiter** | gain | 0..24 dB | 0 | drive into the limiter (a target sets it itself) |
475
496
  | **limiter** | release | 1..1000 ms | 100 | recovery time; short is louder, long is cleaner |
@@ -478,7 +499,7 @@ Parameters (**bold** unit = shown in the simple menu; the rest are under `advanc
478
499
 
479
500
  <!-- master-params:end -->
480
501
 
481
- A song with a master renders at 48 kHz; a song without one keeps the engine's 22,050 Hz, as dawg 0.4 wrote it, so older projects export byte-identically. `--rate 48000` (or 44100) picks the rate for any render. `dawg render out.wav --normalize streaming` (or a LUFS number) applies a target for that export only, without changing the song; `--measure` prints the loudness line after any render. When samples hit 16-bit full scale, `dawg render` prints a `warning · N samples clip` line on stderr with the fix (lower volumes, or a master with a limiter). While the loop plays, the header shows `-14.1 LUFS (-14) · TP -1.0` for the last rendered loop, with or without a master, so you can read a mix before mastering it. The bracket is the target, and the reading turns to a warning colour when it is more than 0.5 LU off the target or the true peak passes the limiter's ceiling (-1 dBTP without one). With a master the loop monitors at the engine's 22,050 Hz while exports and `master measure` run at 48 kHz; the header shows the 48 kHz reading once it is measured in the background (a `~` marks the monitor estimate until then). EQ above about 9.9 kHz is not audible while monitoring but is in the export. The menu has it under **Mix & automation › master** (`/menu master`), where `Space` stages master changes for A/B like other sound edits. The agent has `set_master` and `measure_mix` (integrated, short-term and momentary LUFS, loudness range, true peak, spectral balance in five bands (sub, bass, low-mid, high-mid, high) and stereo correlation, with `bypass_master` to compare); it masters only when asked and measures before and after. In the SDK: `song({ master: { glue: { ratio: 2 }, limiter: { ceiling: -1 }, target: -14 } })` (SDK 1.17.0).
502
+ A song with a master renders at 48 kHz; a song without one keeps the engine's 22,050 Hz, as dawg 0.4 wrote it, so older projects export byte-identically. `--rate 48000` (or 44100) picks the rate for any render. `dawg render out.wav --normalize streaming` (or a LUFS number) applies a target for that export only, without changing the song; `--measure` prints the loudness line after any render. When samples hit 16-bit full scale, `dawg render` prints a `warning · N samples clip` line on stderr with the fix (lower volumes, or a master with a limiter). While the loop plays, the header shows `-14.1 LUFS (-14) · TP -1.0` for the last rendered loop, with or without a master, so you can read a mix before mastering it. The bracket is the target, and the reading turns to a warning color when it is more than 0.5 LU off the target or the true peak passes the limiter's ceiling (-1 dBTP without one). With a master the loop monitors at the engine's 22,050 Hz while exports and `master measure` run at 48 kHz; the header shows the 48 kHz reading once it is measured in the background (a `~` marks the monitor estimate until then). EQ above about 9.9 kHz is not audible while monitoring but is in the export. The menu has it under **Ctrl-K › Mix › master** (`/menu master`), where `Space` stages master changes for A/B like other sound edits. The agent has `set_master` and `measure_mix` (integrated, short-term and momentary LUFS, loudness range, true peak, spectral balance in five bands (sub, bass, low-mid, high-mid, high) and stereo correlation, with `bypass_master` to compare); it masters only when asked and measures before and after. In the SDK: `song({ master: { glue: { ratio: 2 }, limiter: { ceiling: -1 }, target: -14 } })` (SDK 1.17.0).
482
503
 
483
504
  ## Synth
484
505
 
@@ -613,11 +634,11 @@ FM operators 2–8 repeat the `fm` rows with a suffix (`fm2`, `fmh2`, `fmattack2
613
634
  | sample controls `begin`, `end`, `speed`, `unit`, `loop`, `loopBegin`/`loopb`, `loopEnd`/`loope`, `clip`/`legato`, `fit`, `loopAt`, `accelerate`, `squiz`, `cut`, `gain`, `vel`, `rr` | sampler voice fields; `/sample set`, `set_sample` | done (see Samples) |
614
635
  | fitting to tempo (Ableton Repitch/Beats/Tones; Strudel `fit`) | `bpm` `fitmode` `len`; `/fitmode`, `fit_sample` | done (see Fitting samples) |
615
636
 
616
- ## Keys (modelled piano)
637
+ ## Keys (modeled piano)
617
638
 
618
- A track whose `instrument` is a piano family (`grand`, `upright`, `felt`, `honkytonk`, `prepared`) and which has a `keys` field plays dawg's modelled piano (`src/audio/keys/`): a felt hammer of the chosen hardness strikes a bank of stretched, inharmonic string modes (two or three detuned unison strings per key, with a fast first stage and a slow aftersound), a soundboard knock, dampers that stop a released key in about a second, and a small body EQ per family. The 0.5 sustain pedal (down, half, up) holds the dampers off. It is built in: nothing downloads and every render is byte-identical.
639
+ A track whose `instrument` is a piano family (`grand`, `upright`, `felt`, `honkytonk`, `prepared`) and which has a `keys` field plays dawg's modeled piano (`src/audio/keys/`): a felt hammer of the chosen hardness strikes a bank of stretched, inharmonic string modes (two or three detuned unison strings per key, with a fast first stage and a slow aftersound), a soundboard knock, dampers that stop a released key in about a second, and a small body EQ per family. The 0.5 sustain pedal (down, half, up) holds the dampers off. It is built in: nothing downloads and every render is byte-identical.
619
640
 
620
- `piano` keeps two meanings on purpose. A project already stored as `instrument: "piano"` keeps the legacy tone forever. Every new write of the word (`piano`, `instrument piano`, `set_instrument piano`, the menu) stores `instrument: "grand"` with `keys: { preset: "grand" }`. `organ` stays the synth preset (the modelled organs are `tonewheel`, `combo` and `pipe`, below). The sampled Salamander grand is still in the browser under instruments.
641
+ `piano` keeps two meanings on purpose. A project already stored as `instrument: "piano"` keeps the legacy tone forever. Every new write of the word (`piano`, `instrument piano`, `set_instrument piano`, the menu) stores `instrument: "grand"` with `keys: { preset: "grand" }`. `organ` stays the synth preset (the modeled organs are `tonewheel`, `combo` and `pipe`, below). The sampled Salamander grand is still in the browser under instruments.
621
642
 
622
643
  Tuning: each key's first partial sits on the track's tuning (12-TET or any table, 19-EDO included; an unmapped degree is silent). By default the octaves are stretched from the strings' own inharmonicity, as a piano tuner would: low octaves are tuned between the 2:1 and 4:2 beats and the treble is beatless 2:1 to the stretched note below, so the octave from A3 to A4 beats under 1 Hz. `keys stretch 0` keeps every key exactly on the tuning. Bends and glides keep each string mode under the Nyquist limit (modes that would alias are muted), and each note fades over its last 250 ms so it ends inside the 8 s loop-tail window.
623
644
 
@@ -626,7 +647,7 @@ Polyphony is 64 voices; a new key steals the oldest released voice, then the old
626
647
  Prompt grammar (one undo step per command):
627
648
 
628
649
  ```text
629
- piano the modelled grand (also: grand)
650
+ piano the modeled grand (also: grand)
630
651
  piano ballad a preset: grand ballad upright felt lofi honkytonk prepared
631
652
  upright | felt | honkytonk | prepared the preset word alone
632
653
  keys list this track's piano settings
@@ -638,9 +659,9 @@ keys reset the family's own sound (keys: {})
638
659
  automate keys-hardness points 0:0.2 8:0.8 automatable parameters have lanes
639
660
  ```
640
661
 
641
- Presets: `grand` (concert grand, bright and long, three-string unisons), `ballad` (darker grand, softer hammer, more aftersound, plus a room reverb), `upright` (boxy, more inharmonic, shorter), `felt` (felt strip down, muted and intimate, audible mechanics, a small room), `lofi` (felt piano with tape wow, a 3.5 kHz low-pass filter and a 10-bit crush), `honkytonk` (16-cent unisons, bright saloon upright), `prepared` (bolts, rubber and screws on 60% of keys, seeded per key, so the same key always carries the same preparation). A preset is stored as `keys.preset`; its values are read at render, so overrides stay small. The effects a preset brings (`ballad`, `felt`, `lofi`) are ordinary track fields (`reverb`, `filter`, `fx.crush`) and stay editable; switching presets or `keys reset` removes them while they still hold the preset's values, and the same preset word in `song.ts` (`instrument: "lofi"`) brings the same effects. A bare `lofi` stays the drum kit and crush preset word; type `piano lofi`.
662
+ Presets: `grand` (concert grand, bright and long, three-string unisons), `ballad` (darker grand, softer hammer, more aftersound, plus a room reverb), `upright` (boxy, more inharmonic, shorter), `felt` (felt strip down, muted and intimate, audible mechanics, a small room), `lofi` (felt piano with tape wow, a 3.5 kHz low-pass filter and a 10-bit crush), `honkytonk` (16-cent unisons, bright saloon upright), `prepared` (bolts, rubber and screws on 60% of keys, seeded per key, so the same key always carries the same preparation). A preset is stored as `keys.preset`; its values are read at render, so overrides stay small. The effects a preset brings (`ballad`, `felt`, `lofi`) are ordinary track fields (`reverb`, `filter`, `fx.crush`) and stay editable; switching presets or `keys reset` removes them while they still hold the preset's values, and the same preset word in `song.ts` (`instrument: "lofi"`) brings the same effects. A bare `lofi` stays the kit and crush preset word; type `piano lofi`.
642
663
 
643
- The menu has the pianos under **Sound > browse sounds > Keys**, and for a piano track a **Sound > Keys** page and the simple rows (preset, hardness, decay, release, felt) on the **Sound** page. The agent's `set_keys` tool takes the same presets and names; `set_instrument` and `create_track` take the piano words. In the SDK: `track({ instrument: "grand", keys: { hardness: 0.3 } })`.
664
+ The menu has the pianos under **Ctrl-K › Sound › instruments › Keys**, and for a piano track a **Sound › keys** page and the simple rows (preset, hardness, decay, release, felt) on the **Sound** page. The agent's `set_keys` tool takes the same presets and names; `set_instrument` and `create_track` take the piano words. In the SDK: `track({ instrument: "grand", keys: { hardness: 0.3 } })`.
644
665
 
645
666
  | Param | Range | Default | Lane | What it does |
646
667
  | ------------ | ------------------------------------- | ------------------ | --------------- | -------------------------------------------------------------------- |
@@ -669,10 +690,10 @@ Lanes are read at each note's onset. The model is dawg's own, from public litera
669
690
 
670
691
  ### Soft pedal and sostenuto (0.6.1)
671
692
 
672
- The modelled pianos have the other two pedals of a grand. Both are pedal lanes like the sustain pedal (`[{ tick, state }]`, at most 1024 events) and absent means today's sound:
693
+ The modeled pianos have the other two pedals of a grand. Both are pedal lanes like the sustain pedal (`[{ tick, state }]`, at most 1024 events) and absent means today's sound:
673
694
 
674
695
  - **Soft pedal** (`softPedal`, una corda, the left pedal): while it is down each note's hammer is shifted so it strikes fewer strings with a softer part of the felt: the unison narrows, the hammer's high partials are rolled off and the level drops about 3 dB, so the note is quieter and darker (a 10%+ lower spectral centroid and at least 3 dB less 2-4 kHz energy on middle C). `half` is half the shift. It is read at each note's onset, so a note struck before the pedal keeps its tone.
675
- - **Sostenuto** (`sostenuto`, the middle pedal, `down` and `up` only): keys already held when it goes down keep their dampers up until it lifts; notes struck afterwards damp at their own release. A held key struck again while the pedal is down keeps ringing to the lift (the rod keeps its damper up), and a key still held through a lift is caught again by the next press. It works alongside the sustain pedal. Only the modelled pianos with a `keys` object hear the soft pedal; `pedal soft` says so on any other track.
696
+ - **Sostenuto** (`sostenuto`, the middle pedal, `down` and `up` only): keys already held when it goes down keep their dampers up until it lifts; notes struck afterwards damp at their own release. A held key struck again while the pedal is down keeps ringing to the lift (the rod keeps its damper up), and a key still held through a lift is caught again by the next press. It works alongside the sustain pedal. Only the modeled pianos with a `keys` object hear the soft pedal; `pedal soft` says so on any other track.
676
697
 
677
698
  ```text
678
699
  pedal soft 0-8 una corda from beat 0 to 8 (also: down|half|up <beat>, bars, off)
@@ -680,7 +701,7 @@ pedal sost 0-4 sostenuto down at 0, up at 4 (holds the
680
701
  pedal soft list the lane; pedal sost off clears it
681
702
  ```
682
703
 
683
- Menu: **Sound › performance** has **soft pedal** and **sostenuto** rows (off, or held over every bar) on a modelled piano track. Agent: `set_piano_pedals` (`soft`, `sostenuto`: events, `"bars"` or null). SDK (1.26.0): `track({ instrument: "grand", softPedal: [[0, "down"], [8, "up"]], sostenuto: [[1, "down"], [4, "up"]] })`.
704
+ Menu: **Sound › performance** has **soft pedal** and **sostenuto** rows (off, or held over every bar) on a modeled piano track. Agent: `set_piano_pedals` (`soft`, `sostenuto`: events, `"bars"` or null). SDK (1.26.0): `track({ instrument: "grand", softPedal: [[0, "down"], [8, "up"]], sostenuto: [[1, "down"], [4, "up"]] })`.
684
705
 
685
706
  ### Electric keys (0.6.1)
686
707
 
@@ -711,7 +732,7 @@ automate keys-vibe points 0:0 8:0.8 automatable parameters have lanes
711
732
  | **pickup** | clav | neck bridge both out | both | | pickup switch |
712
733
  | **mute** | clav | 0..1 | 0 | | mute slider: damps the upper partials |
713
734
 
714
- `hardness`, `touch`, `decay`, `release`, `width`, `vib` and `vibmod` apply to the electric families too. The menu has them under **Sound › browse sounds › Keys › Electric**, with their rows on the **Sound** page; the agent's `set_instrument` and `set_keys` take the same words; the SDK takes `track({ instrument: "epiano", keys: { vibe: 0.6 } })` or `track({ instrument: "suitcase" })`. The models are dawg's own, from public descriptions of the instruments (tine and tone-bar cantilever, electromagnetic and electrostatic pickups, the Clavinet's pickup switching), with no sampled audio.
735
+ `hardness`, `touch`, `decay`, `release`, `width`, `vib` and `vibmod` apply to the electric families too. The menu has them under **Sound › instruments › Keys › Electric**, with their rows on the **Sound** page; the agent's `set_instrument` and `set_keys` take the same words; the SDK takes `track({ instrument: "epiano", keys: { vibe: 0.6 } })` or `track({ instrument: "suitcase" })`. The models are dawg's own, from public descriptions of the instruments (tine and tone-bar cantilever, electromagnetic and electrostatic pickups, the Clavinet's pickup switching), with no sampled audio.
715
736
 
716
737
  ### Organs (tonewheel, combo, pipe)
717
738
 
@@ -741,7 +762,7 @@ automate keys-rotary points 0:1 4:2 spin the rotor up at beat 4
741
762
 
742
763
  Presets: `tonewheel` (888000000, scanner C3, slow rotary), `gospel` (888800008, 3rd percussion, fast rotary, driven), `jazzorgan` (888000000 with soft 3rd percussion), `combo` (reed registers 08800 with vibrato), `vox` (bright 08880), `pipe` (plenum in a church reverb), `flutes` (gedackt 8' and flute 4' with tremulant), `cornet`, `reeds`, `celeste` (gamba and celeste beating).
743
764
 
744
- The menu has them under **Sound > browse sounds > Keys > Organs**; for an organ track the **Sound** page shows the preset, a **Drawbars** (tonewheel), **Registers** (combo) or **Stops** (pipe) sub-menu with one row per footage or stop, and the family's rows. The agent's `set_keys` takes `drawbars`, `registers`, `stops` and `rotary` next to `params`. In the SDK: `track({ instrument: "tonewheel", keys: { drawbars: "888800008", rotary: "fast" } })` or `keys: { stops: ["principal8", "octave4"] }`.
765
+ The menu has them under **Ctrl-K › Sound › instruments › Keys › Organs**; for an organ track the **Sound** page shows the preset, a **Drawbars** (tonewheel), **Registers** (combo) or **Stops** (pipe) sub-menu with one row per footage or stop, and the family's rows. The agent's `set_keys` takes `drawbars`, `registers`, `stops` and `rotary` next to `params`. In the SDK: `track({ instrument: "tonewheel", keys: { drawbars: "888800008", rotary: "fast" } })` or `keys: { stops: ["principal8", "octave4"] }`.
745
766
 
746
767
  | Param | Family | Range | Default | Lane | What it does |
747
768
  | ------------- | --------------- | --------------------------- | ------------------------------------- | ------------- | ---------------------------------------------- |
@@ -787,7 +808,7 @@ export default track({
787
808
 
788
809
  Semantics follow Strudel's sampler:
789
810
 
790
- | Strudel | dawg | Behaviour |
811
+ | Strudel | dawg | Behavior |
791
812
  | ------------------------------------------------------- | ---------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------ |
792
813
  | `samples({ kick: "kick.wav" })` | `sampler({ kick: "samples/kick.wav" })` | one voice per name |
793
814
  | `s("kick hat")` | `hits("kick", …)`, `hit("hat", …)` (oneshot mode) | voices take pitch slots 36, 37, … in name order; a hit plays the whole sample, whatever the note length |
@@ -850,7 +871,7 @@ How they combine with Strudel's controls and the 0.5 tempo map:
850
871
 
851
872
  `fade out 0.5` and `fade in 0.05` (Strudel `fadeTime` and `fadeInTime`, stored as `fadeTime` and `fadeInTime`, 0..2 s) shape a voice's start and end in place of the short default declick, and follow the fitted length when the voice is fitted. `fade off` clears both.
852
873
 
853
- Both act on the focused sampler voice (`shift 7 vox` names one). The menu's **Sound › Sample** voice rows list Shift, Formant, Fade in and Fade out, the agent's `set_sample` tool takes `shift`, `formant`, `fadeTime` and `fadeInTime`, and the SDK writes `sample("samples/vox.wav", { shift: 7, formant: 0, fadeTime: 0.5 })`.
874
+ Both act on the focused sampler voice (`shift 7 vox` names one). The menu's sample rows (**Ctrl-K › Sound**) list Shift, Formant, Fade in and Fade out, the agent's `set_sample` tool takes `shift`, `formant`, `fadeTime` and `fadeInTime`, and the SDK writes `sample("samples/vox.wav", { shift: 7, formant: 0, fadeTime: 0.5 })`.
854
875
 
855
876
  ### Resample (0.6.1)
856
877
 
@@ -861,7 +882,7 @@ Both act on the focused sampler voice (`shift 7 vox` names one). The menu's **So
861
882
  - The voice records where it came from in `from: { source: "track:lead" | "orbit:2" | "master", section?, bars?, score }`, `score` being the sha256 of the score it was rendered from. It is informational; the renderer never reads it.
862
883
  - Up to 600 s. `section` uses the song's sections; `bars 1-2` is 1-based and inclusive.
863
884
 
864
- **Project › Resample** lists each track, orbit and the mix with a `→ sampler` and `→ granular` row. The agent's `resample {source: track|orbit|master, trackId?, orbit?, section?, bars?, post?, grain?, as?}` tool does the same.
885
+ **Ctrl-K › Project › resample** lists each track, orbit and the mix with a `→ sampler` and `→ granular` row. The agent's `resample {source: track|orbit|master, trackId?, orbit?, section?, bars?, post?, grain?, as?}` tool does the same.
865
886
 
866
887
  Fitted windows are computed once and kept in a 64 MB least-recently-used cache of the played window only. Only the frames a note can reach are fitted (a held keyed or clipped note fits its length plus the release; a one-shot or looped voice fits the whole window). Renders and exports always compute them; in play mode a fit up to 8 s (of source or output, whichever is longer) is computed on the spot (under 160 ms), and a longer one stays silent with "fitting" in the status line until it is ready ("fit ready" then), never at the wrong pitch. Audition previews fit synchronously.
867
888
 
@@ -869,7 +890,7 @@ Every voice starts and stops with a 1–3 ms fade, so cuts do not click. Without
869
890
 
870
891
  Decoding: WAV (PCM 16/24/32-bit integer and 32-bit float, any channel count and rate) and AIFF/AIFF-C (8/16/24/32-bit) decode natively, mixed to mono and resampled to the engine rate on the fly with linear interpolation. MP3, FLAC, Ogg, M4A and anything else decode through `ffmpeg` when it is on `PATH` (dawg never installs it); without it the voice is skipped with `<voice> · <path> · not WAV/AIFF and ffmpeg is not on PATH · convert it to WAV, or install ffmpeg (e.g. brew install ffmpeg) and reload`. Decoded PCM is cached at `.dawg/assets/<sha256>.pcm`, least recently used first out past 512 MiB. Files over 50 MiB or 10 minutes, paths that leave the project (including through a symlink), and more than 64 voices are rejected. A `sha256` that no longer matches the file is a warning and the file still plays. Problems appear as receipts in the TUI and on stderr from `dawg render`; the track renders without the missing voices and nothing crashes.
871
892
 
872
- In the TUI, oneshot sampler tracks show one highway lane per voice, labelled by name; keyed tracks use the pitch axis. `/tracks` shows each sampler's sample count and how many failed to load. `/sample <path> [as <voice>]` adds a voice to the focused track: a file outside the track directory is copied into `tracks/<slug>/samples/`, the voice name defaults to the file name, and a focused synth track that already has notes gets a new `samples` track instead. Existing hits keep their voice when the new name shifts the slots. `/sample` alone lists the voices. The agent's `import_sample` media tool writes 48 kHz stereo WAVs to the same folder.
893
+ In the TUI, oneshot sampler tracks show one highway lane per voice, labeled by name; keyed tracks use the pitch axis. `/tracks` shows each sampler's sample count and how many failed to load. `/sample <path> [as <voice>]` adds a voice to the focused track: a file outside the track directory is copied into `tracks/<slug>/samples/`, the voice name defaults to the file name, and a focused synth track that already has notes gets a new `samples` track instead. Existing hits keep their voice when the new name shifts the slots. `/sample` alone lists the voices. The agent's `import_sample` media tool writes 48 kHz stereo WAVs to the same folder.
873
894
 
874
895
  ## Sample packs
875
896
 
@@ -896,11 +917,11 @@ instrument: sampler({
896
917
  | `/pack cache [prune [<size>] \| clear]` | disk used by pack downloads and decoded audio against their caps; `prune` evicts down to the cap (or `<size>`, e.g. `500M`), `clear` evicts everything this project does not use |
897
918
  | `/kit [<kit>]` | with no name, one picker of every kit (synth kits first, then sample kits); a synth kit name (`syn808`, `syn909`, `acoustic`, `lofi`, `electro`, `trap`, `default`) sets the offline drum synth; a bank (`909`, `808`, `707`, `606`, `linn`, `lm1`, `dmx`, `cr78`, `uzu`, `dirt`, or any bank name like `RolandTR909`) turns the focused drum track into a sampler on it; `/kit list` lists both |
898
919
 
899
- **Bank nicknames.** Strudel's REPL registers short names for the drum machines with `aliasBank("https://strudel.b-cdn.net/tidal-drum-machines-alias.json")`, a JSON map of bank → nickname (`{"RolandTR909": "TR909", "AkaiLinn": "Linn", "EmuSP12": "SP12", …}`, 66 entries). dawg ships a snapshot of that file (taken 2026-10-07) so nicknames work offline, and refreshes it whenever it fetches the `tidal-drum-machines` manifest. A nickname works wherever a bank does: `/kit TR909`, `/kit tr808`, `/pack use tidal-drum-machines/TR909`, `pack:tidal-drum-machines/TR909_bd` in `track.ts`, the agent's `use_sound`, and the menu's **Drum kits → Strudel banks** list. Resolution order: a nickname in its exact case (`Linn` → `AkaiLinn`, as in Strudel), then dawg's short names (`909`, `linn` → `LinnDrum`, unchanged from before), then a bank name in any case, then a nickname in any case (`sp12` → `EmuSP12`), then a bank suffix. Pins always store the full bank name (`pack:tidal-drum-machines/RolandTR909_bd`), so documents never depend on alias data.
920
+ **Bank nicknames.** Strudel's REPL registers short names for the drum machines with `aliasBank("https://strudel.b-cdn.net/tidal-drum-machines-alias.json")`, a JSON map of bank → nickname (`{"RolandTR909": "TR909", "AkaiLinn": "Linn", "EmuSP12": "SP12", …}`, 66 entries). dawg ships a snapshot of that file (taken 2026-10-07) so nicknames work offline, and refreshes it whenever it fetches the `tidal-drum-machines` manifest. A nickname works wherever a bank does: `/kit TR909`, `/kit tr808`, `/pack use tidal-drum-machines/TR909`, `pack:tidal-drum-machines/TR909_bd` in `track.ts`, the agent's `use_sound`, and the menu's **Rhythm › kits › Strudel banks** list. Resolution order: a nickname in its exact case (`Linn` → `AkaiLinn`, as in Strudel), then dawg's short names (`909`, `linn` → `LinnDrum`, unchanged from before), then a bank name in any case, then a nickname in any case (`sp12` → `EmuSP12`), then a bank suffix. Pins always store the full bank name (`pack:tidal-drum-machines/RolandTR909_bd`), so documents never depend on alias data.
900
921
 
901
922
  **Cache sizes.** Pack downloads (`~/.cache/dawg/packs/files/`, shared by every project) are capped at 2 GiB and decoded audio (`<project>/.dawg/assets/`) at 1 GiB per project; `DAWG_PACKS_CACHE_MAX` and `DAWG_ASSETS_CACHE_MAX` override them (`500M`, `4G`, or bytes). Both evict least recently used files first, and neither evicts a file the open project's score uses, even when that project alone is over the cap. An evicted pack file is fetched again from its pinned URL and checked against its pinned sha256 the next time it plays, so eviction only ever costs a download. Manifests are small and never evicted. Decoded samples also share a 512 MiB in-memory LRU per process.
902
923
 
903
- `/menu` (Ctrl-K) has a **Sounds** section: drum kits, instruments (the Salamander piano and `gm_*` soundfonts, Strudel naming), "use a sound", and packs. The agent has `list_packs`, `search_sounds {query}` and `use_sound {sound, track?, voice?}`. A pack sound plays from keyboard play mode like any sampler voice. `kitFromBank(bank)` in `src/audio/packs.ts` returns the voice map for a bank (kick, snare, hat, …) for other kit lists.
924
+ Ctrl-K › Sound › instruments lists kits, instruments (the Salamander piano and `gm_*` soundfonts, Strudel naming), "use a sample" and sample packs. The agent has `list_packs`, `search_sounds {query}` and `use_sound {sound, track?, voice?}`. A pack sound plays from keyboard play mode like any sampler voice. `kitFromBank(bank)` in `src/audio/packs.ts` returns the voice map for a bank (kick, snare, hat, …) for other kit lists.
904
925
 
905
926
  Every pack is fully supported, whatever its license; dawg records each sample's pack and license (or `none stated`) in the pinned voice. A render that uses pack sounds names the packs in the WAV's INFO comment and prints a `credits ·` line, and a project that uses a CC-BY or CC-BY-SA pack gets a `CREDITS.md` with the required attribution (dawg leaves a hand-written `CREDITS.md` alone).
906
927
 
@@ -961,7 +982,7 @@ The agent's `make_wavetable` tool (`src/audio/wavetable-maker.ts`, `src/media/wa
961
982
  - **Clean-up.** DC removed, fundamental rotated to start as a rising sine (so neighbouring frames line up), optional `smooth` across neighbours, and per-frame (default), whole-table or no normalisation to 0.98 peak.
962
983
  - **Report.** The result gives the path, sha256, frame count, method, region, detected pitch and a short sweep description (spectral centroid per frame span, how smooth the morph is), and the `set_wavetable` call that plays it.
963
984
 
964
- All arithmetic is float64 in a fixed order with no randomness, so the same input and options give the same bytes. Project tables live under `tracks/<slug>/wavetables/`; `/wt list` and the menu's table picker list them, `/wt vox.wav` (or a full `tracks/…` path) picks one for the focused track, and `track.ts` refers to it as `wavetable("./wavetables/vox.wav")`. The score keeps the project path and its sha256; evaluation re-hashes it like sampler files. A file that changed since it was picked plays with a warning; a missing one is a load problem naming `make_wavetable`.
985
+ All arithmetic is float64 in a fixed order with no randomness, so the same input and options give the same bytes. Project tables live under `tracks/<slug>/wavetables/`; `/wt list` and the menu's table picker list them, `/wt vox.wav` (or a full `tracks/…` path) picks one for the focused track, and `track.ts` refers to it as `wavetable("./wavetables/vox.wav")`. A track that switches to another instrument keeps its table, written `wavetable: wavetable(...)` beside `instrument`, so switching back restores it. The score keeps the project path and its sha256; evaluation re-hashes it like sampler files. A file that changed since it was picked plays with a warning; a missing one is a load problem naming `make_wavetable`.
965
986
 
966
987
  ## Plucked strings
967
988
 
@@ -1014,7 +1035,7 @@ instrument: stringed("sitar", { buzz: 0.8, sym: 0.5 }),
1014
1035
  | `string <param> off` · `string reset` | back to the preset's value · drop every override |
1015
1036
  | `string off` | back to the legacy `pluck` voice |
1016
1037
 
1017
- The menu has the presets under Sound › browse sounds › Strings and every string parameter on the Sound page of a string track (left/right adjust, `x` resets, space auditions with staged A/B). The agent's `set_string {trackId?, preset?, params?, reset?, off?}` runs the same command. SDK 1.21.0: `stringed(preset, params)` as a track's `instrument`, or `track({ instrument: "string", string: { preset: "koto", ring: 4 } })`; the printer writes `stringed(...)` back.
1038
+ The menu has the presets under Sound › instruments › Strings and every string parameter on the Sound page of a string track (left/right adjust, `x` resets, space auditions with staged A/B). The agent's `set_string {trackId?, preset?, params?, reset?, off?}` runs the same command. SDK 1.21.0: `stringed(preset, params)` as a track's `instrument`, or `track({ instrument: "string", string: { preset: "koto", ring: 4 } })`; the printer writes `stringed(...)` back.
1018
1039
 
1019
1040
  ### Bowed strings
1020
1041
 
@@ -1050,7 +1071,7 @@ Measured: tuning within 1 cent to C7 at 22.05 and 48 kHz; 0 of 1296 pressure/spe
1050
1071
  | `bowed` · `bowed <preset>` · `bowed presets` | the cello · a bowed preset · the bowed presets |
1051
1072
  | `bowed <param> <value> …` | the same as `string <param> <value> …` (`bowed sord 1`) |
1052
1073
 
1053
- The menu lists them under **Sound › browse sounds › Strings › Bowed**, and the **Sound** page on a bowed track shows `pressure speed vib sord dyn bright ring body` first (the rest under advanced). SDK 1.31.0: `stringed("violin", { pressure: 0.7 })` or `stringed({ preset: "cello", sord: 1 })`.
1074
+ The menu lists them under **Sound › instruments › Strings › Bowed**, and the **Sound** page on a bowed track shows `pressure speed vib sord dyn bright ring body` first (the rest under advanced). SDK 1.31.0: `stringed("violin", { pressure: 0.7 })` or `stringed({ preset: "cello", sord: 1 })`.
1054
1075
 
1055
1076
  ## Granular
1056
1077
 
@@ -1086,7 +1107,7 @@ Quality and cost. Grains read a shared semitone-level band-limited bank (`src/au
1086
1107
  | `track cloud` · `track hold-2` | a new granular track named after a preset |
1087
1108
  | `track pad grain swarm` | focus or create a track and grain it in one step |
1088
1109
 
1089
- In the ctrl-k menu, **Sound › granular** edits the preset, source and every parameter of a granular track (on any other pitched track it reads **granular (convert)** and offers `grain on` and the presets); **Sound › browse sounds › Granular** lists the presets. `grain` edits stage in the audition loop, so space plays them and `a` compares A/B. The agent's `set_granular {trackId, preset?, src?, voice?, params?, reset?, off?}` tool takes the same names, and the SDK writes:
1110
+ **Ctrl-K › Sound › granular**: it edits the preset, source and every parameter of a granular track (on any other pitched track the same row reads `grain this track's synth` and offers `grain on` and the presets); **Ctrl-K › Sound › instruments › Granular** lists the presets. `grain` edits stage in the audition loop, so space plays them and `a` compares A/B. The agent's `set_granular {trackId, preset?, src?, voice?, params?, reset?, off?}` tool takes the same names, and the SDK writes:
1090
1111
 
1091
1112
  ```ts
1092
1113
  instrument: granular("cloud", { scan: 0.1, seed: 7 }),
@@ -1101,17 +1122,17 @@ Four optional parameters make a granular track playable like an instrument rathe
1101
1122
  | Parameter | Values (default) | What it does |
1102
1123
  | --------- | ---------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
1103
1124
  | `sync` | `off` `1/64` `1/32` `1/16t` `1/16` `1/16d` `1/8t` `1/8` `1/8d` `1/4t` `1/4` `1/4d` `1/2` `1/1` | Grains start on a note-value grid that follows the song's tempo map (ramps included), as Ableton Granulator II and Output Portal sync their grain rate. `grain` still sets each grain's length; `overlap` is ignored. `jitter` moves onsets off the grid by up to a fraction of a step. Measured within 1 ms of 117.19 ms per 1/16 at 128 BPM. |
1104
- | `quant` | `off` `scale` `chord` | Snaps each grain's pitch offset (`pitch`, `detune`, `shimmer`) to the nearest pitch of the song key's scale, or of the pitch classes sounding on the song's other pitched tracks at that grain's onset, falling back to the scale when nothing sounds (scale quantise, as on Output Portal and Bitwig's Sampler). The played note itself is never moved. |
1125
+ | `quant` | `off` `scale` `chord` | Snaps each grain's pitch offset (`pitch`, `detune`, `shimmer`) to the nearest pitch of the song key's scale, or of the pitch classes sounding on the song's other pitched tracks at that grain's onset, falling back to the scale when nothing sounds (scale quantize, as on Output Portal and Bitwig's Sampler). The played note itself is never moved. |
1105
1126
  | `mono` | `on` `off` (off) | One voice: a note that starts before the previous one ends (legato) retargets that voice's pitch and keeps its grain stream and head, so a melody glides through one cloud; a detached note starts a new voice and cuts the old one's tail. |
1106
1127
  | `pedal` | `on` `off` (off) | The track's sustain pedal freezes the head while it is down (the cloud keeps playing the same spot), as on Mutable Clouds' freeze and the Hologram Microcosm hold. |
1107
1128
 
1108
1129
  `grain sync 1/16`, `grain quant scale`, `grain mono on` and `grain pedal on` set them (`off` unsets); **Sound › granular** lists them as rows, `set_granular` takes them in `params`, and the SDK writes `granular("cloud", { sync: "1/16", quant: "scale", mono: true })`.
1109
1130
 
1110
- Lanes: every numeric parameter that moves well over time has a `grain-<param>` automation lane: `grain-pos`, `grain-scan`, `grain-grain`, `grain-overlap`, `grain-jitter`, `grain-spray`, `grain-pitch`, `grain-detune`, `grain-shimmer`, `grain-spread`, `grain-reverse`, `grain-repeat` and `grain-drift`. A lane replaces the parameter's value while it has points (`automate grain-pos points 0:0.1 8:0.9` sweeps the head across the source), is read every 32 frames, and latches per grain at its onset, so a block render equals a whole render. They are in **Mix & automation**'s lane picker on granular tracks and stored in `track.fxAutomation` like the effect lanes.
1131
+ Lanes: every numeric parameter that moves well over time has a `grain-<param>` automation lane: `grain-pos`, `grain-scan`, `grain-grain`, `grain-overlap`, `grain-jitter`, `grain-spray`, `grain-pitch`, `grain-detune`, `grain-shimmer`, `grain-spread`, `grain-reverse`, `grain-repeat` and `grain-drift`. A lane replaces the parameter's value while it has points (`automate grain-pos points 0:0.1 8:0.9` sweeps the head across the source), is read every 32 frames, and latches per grain at its onset, so a block render equals a whole render. They are in **Mix**'s lane picker on granular tracks and stored in `track.fxAutomation` like the effect lanes.
1111
1132
 
1112
1133
  ## Mallets and bells (modal)
1113
1134
 
1114
- `instrument: "modal"` plays struck bars, tines, bells, bowls and drums on a modal resonator bank (`src/audio/dsp/modal.ts`, `src/audio/resonators.ts`): each note excites a table of measured mode ratios through a mallet pulse, each mode rings as a two-pole resonator with its own decay, and the strike point weights the modes the way it does on a real bar (the node at the centre of a marimba bar mutes the second mode). It is dawg's own engine, ported from the reviewed 0.6 prototype.
1135
+ `instrument: "modal"` plays struck bars, tines, bells, bowls and drums on a modal resonator bank (`src/audio/dsp/modal.ts`, `src/audio/resonators.ts`): each note excites a table of measured mode ratios through a mallet pulse, each mode rings as a two-pole resonator with its own decay, and the strike point weights the modes the way it does on a real bar (the node at the center of a marimba bar mutes the second mode). It is dawg's own engine, ported from the reviewed 0.6 prototype.
1115
1136
 
1116
1137
  Presets (a word picks one): `marimba` `vibes` `xylophone` `glock` `celesta` `chimes` `kalimba` `mbira` `steelpan` `bowl` `gong` `timpani`; aliases `vibraphone`, `glockenspiel`, `tubular`, `thumbpiano`, `gongageng`, `steeldrum`, `singingbowl`, `kettledrum` and `tubularbells`. `instrument vibes` (or any preset word) switches the focused track. Plain `marimba` with no `modal` field keeps the pre-0.6 marimba voice byte-identical, so old projects sound the same; use `modal marimba` for the modal one (the `instrument marimba` receipt says so). One-shot renders let a modal tail ring up to 30 s (bowls and gongs ring out instead of stopping at the 8 s loop-fold cap). `dawg check` warns about tracks still on the legacy `marimba`, `modal` or `wind` words.
1117
1138
 
@@ -1119,7 +1140,7 @@ Presets (a word picks one): `marimba` `vibes` `xylophone` `glock` `celesta` `chi
1119
1140
  | -------------------------- | ------------------ | --------------------------------------------------------------------------------- |
1120
1141
  | `mallet` | yarn … brass | `yarn` `cord` `rubber` `plastic` `brass`; sets `hardness` |
1121
1142
  | `hardness` | 0..1 | mallet hardness: soft rounds off the high modes, hard adds them; velocity adds |
1122
- | `position` | 0..1 | strike point: 0 the end or edge, 0.5 the centre |
1143
+ | `position` | 0..1 | strike point: 0 the end or edge, 0.5 the center |
1123
1144
  | `ring` | 0.05..30 s (log) | ring time (T60) at middle C |
1124
1145
  | `tilt` | 0..2 | how much faster high modes and high notes decay |
1125
1146
  | `damp` `release` | 0..1, 0.005..2 s | damping at note-off (0 rings on, 1 chokes) and the choke time; the pedal lifts it |
@@ -1146,11 +1167,11 @@ instrument: modal("marimba", { mallet: "rubber", ring: 2 }),
1146
1167
  | `modal reset` | clear overrides, keep the preset |
1147
1168
  | `modal off` | leave the engine for the legacy marimba voice |
1148
1169
 
1149
- The menu's **Sound › browse sounds › Mallets and bells** lists the presets, and the **Sound** page shows the preset, mallet, the simple parameters and an **advanced** group on a modal track. The agent's `set_modal {trackId, preset?, mallet?, params?, reset?}` tool takes the same names.
1170
+ The menu's **Sound › instruments › Mallets and bells** lists the presets, and the **Sound** page shows the preset, mallet, the simple parameters and an **advanced** group on a modal track. The agent's `set_modal {trackId, preset?, mallet?, params?, reset?}` tool takes the same names.
1150
1171
 
1151
1172
  ### Gamelan (0.6.1)
1152
1173
 
1153
- The modal engine also plays Javanese and Balinese bronzes, small bells and frame drums: `saron` `demung` `slenthem` `gangsa` `gender` `bonang` `kenong` `kethuk` `kempul`, the bells `crotales` `musicbox` `toypiano`, and the drums `daf` `bodhran` `tabla` (aliases `crotale`, `musicalbox`, `framedrum`). `modal gamelan` lists them; the menu has them under **Sound › browse sounds › Mallets and bells › Gamelan**. Gamelan is tuned in slendro or pelog, not 12-TET, so pair the presets with `tuning slendro` or `tuning pelog` (the 0.5 tuning tables).
1174
+ The modal engine also plays Javanese and Balinese bronzes, small bells and frame drums: `saron` `demung` `slenthem` `gangsa` `gender` `bonang` `kenong` `kethuk` `kempul`, the bells `crotales` `musicbox` `toypiano`, and the drums `daf` `bodhran` `tabla` (aliases `crotale`, `musicalbox`, `framedrum`). `modal gamelan` lists them; the menu has them under **Ctrl-K › Sound › instruments › Mallets and bells** (Gamelan). Gamelan is tuned in slendro or pelog, not 12-TET, so pair the presets with `tuning slendro` or `tuning pelog` (the 0.5 tuning tables).
1154
1175
 
1155
1176
  Balinese instruments come in pairs: the pengumbang is tuned low and the pengisep a few hertz high, so a unison beats (_ombak_, "wave") at that difference, typically 5 to 8 Hz. `modal pair <track>` makes the focused track the pengisep of the named partner: it sounds `ombak` Hz above it (gangsa's 7 Hz by default), the partner plays one voice at its own pitch, and the two together beat at exactly `ombak` Hz instead of each track beating on its own. `modal pair off` undoes it. `modal ombak 6` or `modal pair t2` on a track that is not modal yet starts it as a `gangsa`. In the SDK: `modal("gangsa", { ombak: 6, pair: "pengumbang" })`.
1156
1177
 
@@ -1160,7 +1181,7 @@ Live, an undamped bar (`damp 0`: gongs, kempul, bowls) keeps ringing after you r
1160
1181
 
1161
1182
  `instrument: "wind"` with a `wind` field plays blown instruments on breath-driven digital waveguides (`src/audio/winds/`): a bore delay line tuned exactly with a Thiran allpass, closed by a reed table (clarinet), a conical reed (saxophones, oboe, bassoon), an air jet (flutes) or a lip resonator (brass), with loss and bell filters, breath noise and a breath envelope. Reed and jet run at 2x with first-order antiderivative antialiasing (`src/audio/dsp/shape.ts`, `oversample.ts`); each preset carries per-semitone pitch and level trims for 22.05, 44.1 and 48 kHz, generated by `bun scripts/calibrate-winds.ts`, so every preset plays within 3 cents across its range. It is dawg's own model, after Smith's digital waveguides, Cook's STK reed and jet models and Välimäki's fractional-delay filters, ported from the reviewed 0.6 prototype; no samples.
1162
1183
 
1163
- Presets (a word picks one): flutes `flute` `recorder` `whistle` `ney` `shakuhachi` `panpipe` `suling` `bansuri`; reeds `clarinet` `bassclarinet` `oboe` `bassoon`; saxophones `sax` (tenor) `altosax` `barisax`; brass `trumpet` `harmon` `plunger` `trombone` `tuba` `horn`. Aliases: `tinwhistle` `pennywhistle` `nay` `panflute` `panpipes` `saxophone` `tenorsax` `tenor` `alto` `bari` `baritonesax` `frenchhorn` `mutedtrumpet` `wahtrumpet`. `instrument flute` (or any preset word) switches the focused track. The bare legacy word `wind` with no `wind` field keeps its pre-0.6 tone byte-identically; `wind flute` gives the engine. `presets wind` (or `wind presets`) lists them; the menu has them under **Sound › browse sounds › Winds and brass**, and the **Sound** page shows the simple rows `breath` `bright` `mute` `players` `growl` with the rest under advanced.
1184
+ Presets (a word picks one): flutes `flute` `recorder` `whistle` `ney` `shakuhachi` `panpipe` `suling` `bansuri`; reeds `clarinet` `bassclarinet` `oboe` `bassoon`; saxophones `sax` (tenor) `altosax` `barisax`; brass `trumpet` `harmon` `plunger` `trombone` `tuba` `horn`. Aliases: `tinwhistle` `pennywhistle` `nay` `panflute` `panpipes` `saxophone` `tenorsax` `tenor` `alto` `bari` `baritonesax` `frenchhorn` `mutedtrumpet` `wahtrumpet`. `instrument flute` (or any preset word) switches the focused track. The bare legacy word `wind` with no `wind` field keeps its pre-0.6 tone byte-identically; `wind flute` gives the engine. `presets wind` (or `wind presets`) lists them; the menu has them under **Sound › instruments › Winds and brass**, and the **Sound** page shows the simple rows `breath` `bright` `mute` `players` `growl` with the rest under advanced.
1164
1185
 
1165
1186
  | Parameter | Range | Meaning |
1166
1187
  | ------------------ | -------------------------------- | ----------------------------------------------------------------------------- |
@@ -1229,7 +1250,7 @@ export default track({
1229
1250
  });
1230
1251
  ```
1231
1252
 
1232
- | Field | Range (default) | T-1 parameter | Behaviour |
1253
+ | Field | Range (default) | T-1 parameter | Behavior |
1233
1254
  | -------------------- | ------------------------------------ | ---------------- | --------------------------------------------------------------------------------------------- |
1234
1255
  | `steps` | 1..64 (16) | Steps | Length of one pass; the row repeats every pass to the end of the loop. |
1235
1256
  | `pulses` | 0..steps (4) | Pulses | Hits spread over the steps by Bjorklund's algorithm. |
@@ -1241,7 +1262,7 @@ export default track({
1241
1262
  | `pace` | -1..1 (0) | Pace | > 0 slows the repeats down progressively, < 0 speeds them up. |
1242
1263
  | `ramp` | -1..1 (0) | Ramp | Velocity across the repeats: > 0 builds, < 0 fades. |
1243
1264
  | `velocity` | 0..1 (0.8) | Velocity | Base velocity. |
1244
- | `accent`, `accents` | 0..1 (0), 1..pulses (1) | Accent | Lifts `E(accents, pulses)` of the pulses (or the `X` steps) towards full velocity. |
1265
+ | `accent`, `accents` | 0..1 (0), 1..pulses (1) | Accent | Lifts `E(accents, pulses)` of the pulses (or the `X` steps) toward full velocity. |
1245
1266
  | `gate`, `legato` | 0.05..4 steps (1), boolean | Sustain | Note length in steps; `legato` holds each hit to the next one (Strudel `euclidLegato`). |
1246
1267
  | `probability`,`seed` | 0..1 (1), 0..1e6 (0) | Probability | Drops pulses (and their repeats) by a seeded hash: the same seed always drops the same hits. |
1247
1268
  | `swing` | -0.5..0.5 step (0) | Timing | Every second step later (> 0) or earlier. |
@@ -1252,19 +1273,19 @@ Rows regenerate when the loop length or meter changes. Editing a generated lane
1252
1273
 
1253
1274
  Prompt grammar: `euclid kick 4 16`, `euclid hat 7 16 rotate 2`, `euclid hat swing 0.2 prob 0.8 seed 3` (named fields merge into the existing row, `default` resets one), `euclid snare off|freeze`, `grid snare ....X.......x...`. The agent's `set_rhythm` tool takes the same rows and its prompt prefers it for drums.
1254
1275
 
1255
- **Editor.** `/euclid [voice]`, or Rhythm in `/menu`, opens a T-1-style editor on the focused kit or oneshot sampler track: one row per voice with its step grid (`x` hit, `X` accent, `·` rest) and summary (`E(4,16)`). Every change runs one `euclid …` command, so it is one receipt and one undo step, and the edited voice plays once (audition) after it lands. The hits show on the highway like any notes.
1276
+ **Editor.** `/euclid [voice]`, or Rhythm in `/menu`, opens a T-1-style editor on the focused kit or oneshot sampler track: one row per voice with its step grid (`x` hit, `X` accent, `·` rest) and summary (`E(4,16)`). Every change runs one `euclid …` command, so it is one receipt and one undo step, and the edited voice plays once (audition) after it lands. The hits show on the highway like any notes. The bottom row is the four-knob strip (see **Four knobs**; `KNOB_MAPS.euclid`): `●›kick ▲ pulses 4 ■ rotate 0 ◆ velocity 0.8`. `/euclid hat` opens on `▲` pulses; bare `/euclid` on `●`.
1256
1277
 
1257
- | Key | Action |
1258
- | --------------------------- | ------------------------------------------------- |
1259
- | `↑ ↓` / `j k` | select voice |
1260
- | `← →` / `h l` / `- +` | nudge the selected parameter |
1261
- | `Tab` / `Shift-Tab` (`] [`) | next / previous parameter |
1262
- | digits, `.`, `-`, Backspace | type a value, Enter applies |
1263
- | Enter | add a row for a voice without one; keep (looping) |
1264
- | Space | start or stop the audition loop (staging) |
1265
- | `a` / `c` | A/B / solo ↔ in context, while the loop plays |
1266
- | `x` / Delete, `f` | remove the row and its notes / freeze it to notes |
1267
- | Esc | cancel typing, revert staged changes, then close |
1278
+ | Key | Action |
1279
+ | --------------------------- | ----------------------------------------------------------- |
1280
+ | `↑ ↓` / `j k` | pick a knob: `●` drum, `▲` pulses, `■` rotate, `◆` velocity |
1281
+ | `← →` / `h l` / `- +` | turn it (`●` moves to the next or previous drum row) |
1282
+ | `Tab` / `Shift-Tab` (`] [`) | next / previous parameter, every field |
1283
+ | digits, `.`, `-`, Backspace | type a value, Enter applies |
1284
+ | Enter | add a row for a voice without one; keep (looping) |
1285
+ | Space | start or stop the audition loop (staging) |
1286
+ | `a` / `c` | A/B / solo ↔ in context, while the loop plays |
1287
+ | `x` / Delete, `f` | remove the row and its notes / freeze it to notes |
1288
+ | Esc | cancel typing, revert staged changes, then close |
1268
1289
 
1269
1290
  ## Chords
1270
1291
 
@@ -1291,6 +1312,10 @@ dawg's own design:
1291
1312
  - Perform modes `block`, `strum-up`, `strum-down` (1/32-beat gap), `arp-up`, `arp-down`, `arp-updown`, `arp-random` (seeded) with a grid-aligned `rate` and 1–4 `octaves`, `harp` (a 1/16-beat upward sweep across the octaves that rings to the end of the chord), and `slop` (Orchid's humanised block chord: each voice lands up to 1/16 beat late, chosen by the press's seed, so repeats differ but a recording replays exactly).
1292
1313
  - Progressions (dawg's "auto"): eleven presets (`axis` I–V–vi–IV, `sad-pop` vi–IV–I–V, `fifties` I–vi–IV–V, `ii-v-i`, `turnaround` I–vi–ii–V, `canon`, `aeolian` i–VI–III–VII, `andalusian` i–VII–VI–V, `minor-ii-v`, `dorian-vamp`, `mixolydian-rock` I–♭VII–IV–I) and four styles, `pop`, `jazz`, `modal` and `classical`, that walk a weighted graph of scale-degree transitions (tonic → predominant → dominant → tonic, with plagal and vi–IV moves for pop and the cycle of fifths for jazz) from I with a seeded PRNG. A progression of four or more chords ends on a dominant-function chord (V or vii°; IV or vii in modal) so the loop leads home. The same key, style, length and seed always give the same chords.
1293
1314
 
1315
+ - Chord symbols (0.7) also take stacked alterations on any listed base, bare or in parentheses: `C7#5`, `Cmaj7+5`, `C9#11`, `C13#11`, `C7b9b13`, `C7(b9,#9)`, `C9b5`, `C7alt` (b9 #9 #11 b13), and the spellings `C-maj7`, `Cmmaj7`, `Cadd2`, `C2`, `C6add9`. Roman numerals are lossless: a numeral reads back as the chord it names. When the short form would read back differently, the numeral carries the chord's own suffix in brackets (`Idom7`, `I[5]`, `I[7#9]`, `V[13#11]`), and a major-scale degree the mode alters takes `♮` (`♮II` in Phrygian). Both forms parse wherever numerals do.
1316
+
1317
+ Prompt. `progression <chords> [each <beats>] [at <beat>] [bass]` (alias `prog`) writes sustained, voice-led `block` chords on the focused track: numerals in the song key (`progression i7 IV7 i7 IV7 each 8`) or symbols (`progression Am7 D9 bass`), `each` defaulting to one bar. It adds notes beside the existing ones, grows the song to fit, and writes the same notes as SDK `progression(chords, { key, from, each })`; `bass` adds the root under each chord. It is the carrier for a vocoder or talkbox without the agent. Menu: **Ctrl-K › Chords and key › progression**.
1318
+
1294
1319
  Agent. `suggest_progression {key?, chords? | style?, length?, seed?, sevenths?, inversion?, spread?}` (read-only) returns each chord's name, roman numeral, voicing and bass. `write_chords {trackId?, chords, key?, start?, beatsPerChord?, perform?, pattern?, rate?, octaves?, strum?, velocity?, bass?, bassMode?, bassTrackId?, inversion?, spread?}` writes them as one revision. `chords` takes roman numerals in the key (`ii7`, `bVII`, `V/V`) or symbols (`Cm7`, `F/A`). The system prompt tells the agent to use these tools for chord parts, so its chords are diatonic and voice-led rather than hand-stacked.
1295
1320
 
1296
1321
  SDK. `chord("Cm7", start, length, opts)` and `progression("ii7 V7 Imaj7", { key, from, each, perform, pattern, rate, octaves, strum, seed, voicing, spread, part, bass })` expand to notes at evaluation; see [docs/project-format.md](./docs/project-format.md). The vendored SDK stays one import-free file: `core/sdk/v1.ts` carries a generated copy of the engine (`bun core/sdk/sync-chords.ts`, checked by a test).
@@ -1320,23 +1345,29 @@ Per track:
1320
1345
 
1321
1346
  Precedence with the synth: a note's settings override the track's synth. A note's `vibrato` replaces the synth's `vib`/`vibmod`, a `bend` replaces the pitch envelope (`penv`), and a gliding note ignores ZzFX `slide`. Render order: articulation, then humanize velocity, then pedal, then glide and mono voicing (on written timing), then humanize timing and length, then the velocity curve.
1322
1347
 
1323
- Menu: **Sound › performance** has glide time (ms) and mode, sustain pedal (off or every bar), velocity curve, humanize timing, velocity and length, and new take; the loop stages them for A/B like any other sound change. Agent: `set_expression` (articulation, glide, bend, vibrato and humanize over note ids or a beat range) and `set_performance` (track glide, pedal, velocity curve and humanize). SDK (1.15.0): `note("C4", 0, 1, 0.8, { art: "staccato", glide: 0.05, bend: [[0, -200], [0.25, 0]], vibrato: { rate: 5.5, depth: 30 }, humanize: { timing: 10 } })`, `expr(notes, { art: "ghost" })` for many notes, and `track({ glide: 0.08, pedal: [[0, "down"], [4, "up"]], velocityCurve: "soft", humanize: { timing: 8, seed: 7 } })`.
1348
+ Menu: **Sound › performance** has glide time (ms) and mode, sustain pedal (off or every bar), velocity curve, humanize timing, velocity and length, and new take; the loop stages them for A/B like any other sound change. Agent: `set_expression` (articulation, glide, bend, vibrato and humanize over note ids or a beat range) and `set_performance` (the track's glide, pedal, velocity curve and humanize). SDK (1.15.0): `note("C4", 0, 1, 0.8, { art: "staccato", glide: 0.05, bend: [[0, -200], [0.25, 0]], vibrato: { rate: 5.5, depth: 30 }, humanize: { timing: 10 } })`, `expr(notes, { art: "ghost" })` for many notes, and `track({ glide: 0.08, pedal: [[0, "down"], [4, "up"]], velocityCurve: "soft", humanize: { timing: 8, seed: 7 } })`.
1349
+
1350
+ ## Sound calibration
1351
+
1352
+ A song's `calibration` picks the revision of level, pitch and kit fixes the released engines render with. Absent or 0 keeps every 0.4 to 0.6.1 project byte-identical; `dawg init` writes the latest (1). `/calibration` shows it, `/calibration 1|latest|0|off` sets it; Ctrl-K › Project › calibration, the `set_calibration` agent tool and `song({ calibration: 1 })` do the same.
1353
+
1354
+ Revision 1: a closed (42) or pedal (44) hat chokes a sounding open hat (46) over 8 ms; GM toms 41 to 50 are pitched two thirds of a semitone per key around 45 (low tom); 49, 52, 55 and 57 play a crash, 51, 53 and 59 a ride and 56 a cowbell (they were a rim click), and section fills end on the crash; hat metal is band-limited; keys presets are leveled to within 3 dB of piano across notes 36 to 96; and lip brass locks its lip resonance to the sounding pitch with soft lip saturation, so held notes are steady and in tune.
1324
1355
 
1325
1356
  ## Tunings and scales
1326
1357
 
1327
- Every project plays in 12-tone equal temperament at A4 = 440 Hz until it says otherwise. A song tuning, a track tuning or a note's cents change only the frequencies; notes stay MIDI keys, so editing, chords, play mode and exports work the same. MIDI export carries a tuning with the MIDI Tuning Standard (a single-note tuning SysEx per tuned track, selected with RPN 3), and writes glides, bends, vibrato and note cents as pitch bend (range ±24 semitones) on notes that sound alone on their track; synths without MTS play 12-TET keys. A project without any of these renders byte-identically to 0.4.
1358
+ Every project plays in 12-tone equal tuning at A4 = 440 Hz until it says otherwise. A song tuning, a track tuning or a note's cents change only the frequencies; notes stay MIDI keys, so editing, chords, play mode and exports work the same. MIDI export carries a tuning with the MIDI Tuning Standard (a single-note tuning SysEx per tuned track, selected with RPN 3), and writes glides, bends, vibrato and note cents as pitch bend (range ±24 semitones) on notes that sound alone on their track; synths without MTS play 12-TET keys. A project without any of these renders byte-identically to 0.4.
1328
1359
 
1329
1360
  Tunings (`core/tuning.ts`). A tuning is one table (`edo: 19`, `ratios: ["9/8", "5/4", …, "2/1"]`, `cents: [231, 474, …, 1200]`, a Scala `scl` file, or a library `name`) plus `ref` (the 12-TET A4 in Hz, default 440, that fixes the root key's pitch), `root` (the key of degree 0) and `map` (`linear` or `nearest`). The last table entry is the period, usually 1200 cents (2/1).
1330
1361
 
1331
- - The library: `12-tet`, `19-edo`, `24-edo`, `31-edo`, `pythagorean`, `just` (5-limit), `7-limit`, `well-tuned-piano` (La Monte Young's 7-limit key map from E♭, after Kyle Gann's published ratios), `pelog` and `slendro` (Kunst's and Surjodiningrat's averages), `nyamaropa` (a Shona mbira after Berliner), `hindustani` (twelve just svaras), `shruti` (the 22 shrutis), maqam and dastgah tables (`rast`, `bayati`, `saba`, `sikah`, `huzam`, `shur`, `homayoun`, `chahargah`, `segah`, `nava`) and raga tables (`yaman`, `bhairav`, `kafi`, `bhairavi`, `todi`, `marwa`, `darbari`, `malkauns` and more, each built from the raga's own svaras over 5-limit defaults). `tuning list` shows each one with a line about it. The gamelan, mbira, maqam and raga tables are marked approximate: every gamelan and every mbira is tuned differently, and maqam and raga intonation varies by tradition and performer.
1362
+ - The library: `12-tet`, `19-edo`, `24-edo`, `31-edo`, `pythagorean`, `just` (5-limit), `7-limit`, `well-tuned-piano` (La Monte Young's 7-limit key map from E♭, after Kyle Gann's published ratios), `pelog` and `slendro` (Kunst's and Surjodiningrat's averages), `nyamaropa` (a Shona mbira after Berliner), `thai` (seven equal steps, the Thai and Khmer norm, also `7-edo`), `hindustani` (twelve just svaras), `shruti` (the 22 shrutis), maqam and dastgah tables (`rast`, `bayati`, `saba`, `sikah`, `huzam`, `shur`, `homayoun`, `chahargah`, `segah`, `nava`) and raga tables (`yaman`, `bhairav`, `kafi`, `bhairavi`, `todi`, `marwa`, `darbari`, `malkauns` and more, each built from the raga's own svaras over 5-limit defaults). `tuning list` shows each one with a line about it. The gamelan, mbira, maqam and raga tables are marked approximate: every gamelan and every mbira is tuned differently, and maqam and raga intonation varies by tradition and performer.
1332
1363
  - Anchoring follows Scala and Surge's tuning library: the root key sounds at its 12-TET frequency under `ref`, and the table counts up from it. So `ref` is the 12-TET A4 that fixes the root, and A4 itself sounds at exactly `ref` only when the root is an A (in `just` from C, A4 is 5/3 above C4, about 436 Hz at ref 440). For an exact reference key and frequency, use a `.kbm` keyboard mapping. The root defaults to the library tuning's own (E♭ for the Well-Tuned Piano), else the song key's tonic in octave 4, else C4.
1333
1364
  - Mapping. Twelve-step tables retune the twelve keys. Other sizes default to `linear`, one key per step, as Scala, Surge and Ableton's tuning system do (19-EDO puts the octave 19 keys up). `map: "nearest"` keeps the piano layout instead and plays each key at the nearest table pitch, which suits pentatonic gamelan tables on a normal keyboard.
1334
1365
  - Frequencies at or above 20 kHz count as unmapped keys and stay silent, so a coarse table (`edo: 1`) cannot alias at the top of the keyboard.
1335
1366
  - Scala. `tuning scl tunings/slendro.scl [kbm tunings/white.kbm]` copies a file from outside the project into `tunings/` and stores the path. The `.scl` parser follows the Scala specification: `!` comments, a description line, the count, then one pitch per line (a period means cents, otherwise a ratio or integer), the 1/1 implicit and the last pitch the period. A `.kbm` keyboard mapping (size, first and last key, middle key, reference key and frequency, octave degree, then the map with `x` for unmapped keys) overrides `ref` and `root`. Bad files are refused with a line number.
1336
- - Track tuning overrides the song's field by field: a track with its own table uses it, and `ref`, `root` and `map` fall back to the song's. Drum kits ignore tunings.
1367
+ - Track tuning overrides the song's field by field: a track with its own table uses it, and `ref`, `root` and `map` fall back to the song's. Kits ignore tunings.
1337
1368
  - Note cents: `note("E4-14c", 0)`, `add E4-14c at 0` or `cents n3 -14` give one note a static offset of up to ±1200 cents on top of any tuning.
1338
1369
 
1339
- Scales (`core/chords.ts`). The song key string now names any scale: `"D dorian"`, `"A harmonic-minor"`, `"E hijaz"`, `"C yaman"`, `"C messiaen-3"`. The library has the church modes, harmonic and melodic minor, phrygian dominant, major and minor pentatonic, blues and major blues, the maqamat `hijaz`, `bayati`, `rast`, `saba`, `kurd`, `nahawand` and `nikriz`, the dastgahs `shur`, `homayoun`, `chahargah`, `segah` and `nava`, the neutral-third maqamat `sikah` and `huzam`, common Hindustani ragas (`yaman`, `bhairav`, `kafi`, `bhairavi`, `asavari`, `khamaj`, `todi`, `purvi`, `marwa`, `darbari`, `malkauns`, `bhupali`, `durga`) by their thaat or aroha notes, and Messiaen's seven modes of limited transposition. Every existing key string reads as before. Quarter-tone scales (Bayati, Rast, Saba) name their half-flat degrees; setting such a scale suggests the matching library tuning so they sound.
1370
+ Scales (`core/chords.ts`). The song key string now names any scale: `"D dorian"`, `"A harmonic-minor"`, `"E hijaz"`, `"C yaman"`, `"C messiaen-3"`. The library has the church modes, harmonic and melodic minor, phrygian dominant, major and minor pentatonic, `yonanuki-minor` (1 2 b3 5 b6, the enka and trot pentatonic), blues and major blues, the maqamat `hijaz`, `bayati`, `rast`, `saba`, `kurd`, `nahawand` and `nikriz`, the dastgahs `shur`, `homayoun`, `chahargah`, `segah` and `nava`, the neutral-third maqamat `sikah` and `huzam`, common Hindustani ragas (`yaman`, `bhairav`, `kafi`, `bhairavi`, `asavari`, `khamaj`, `todi`, `purvi`, `marwa`, `darbari`, `malkauns`, `bhupali`, `durga`) by their thaat or aroha notes, Messiaen's seven modes of limited transposition, `chromatic` (all twelve pitch classes), `harmonic-series` (partials 8 to 15 over the tonic; `tuning harmonic-series` sounds them in just cents) and `quarter-tone` (each major degree beside its quarter-tone shadow, sounded as note cents). Every existing key string reads as before. Quarter-tone scales (Bayati, Rast, Saba) name their half-flat degrees; setting such a scale suggests the matching library tuning so they sound.
1340
1371
 
1341
1372
  Chords. The chord engine stays twelve-tone: a scale outside the seven diatonic modes uses the nearest diatonic mode for key-mode chords. In a twelve-key tuning chords keep their keys (so in `just` they sound pure); in a linear non-12 tuning such as 19-EDO, the chord tools and play-mode chord phrases move each written pitch to the key that sounds nearest, so a C major triad becomes steps 0, 6 and 11.
1342
1373
 
@@ -1357,7 +1388,7 @@ Commands and menu.
1357
1388
  | `scale [<tonic>] <name>` | the song key and scale (`scale D hijaz`) |
1358
1389
  | `cents <id> <±c>` | detune one note |
1359
1390
 
1360
- The menu has the same in Project › tuning & scale (song tuning, ref, root, map, equal steps, ratios, cents, Scala file, keyboard map, scale and tonic; `/menu tuning` jumps there) and Sound › tuning (the track's, showing inherited song values as `· song`). The Chords tonic row keeps a library scale (`D hijaz` stays hijaz). Agent: `set_tuning {target?, name? | edo? | ratios? | cents? | scl?, kbm?, ref?, root?, map?, off?}` and `set_scale {tonic?, scale}` run the same commands, `add_notes` and `update_notes` take an optional `cents` per note (0 clears it), and the agent brief carries the song and track tunings and a `cents` column when a focused note has one. SDK 1.16.0: `song({ tuning })`, `track({ tuning })` (a name string or an object), and pitch strings with a cents suffix (`note("E4-14c", 0)`, `seq("C4 E4-14c G4+2c")`).
1391
+ The menu has the same in Ctrl-K › Chords and key › tuning (song tuning, ref, root, map, equal steps, ratios, cents, Scala file, keyboard map, scale and tonic; `/menu tuning` jumps there) and Ctrl-K › Sound › track tuning (the track's, showing inherited song values as `· song`). The Chords tonic row keeps a library scale (`D hijaz` stays hijaz). Agent: `set_tuning {target?, name? | edo? | ratios? | cents? | scl?, kbm?, ref?, root?, map?, off?}` and `set_scale {tonic?, scale}` run the same commands, `add_notes` and `update_notes` take an optional `cents` per note (0 clears it), and the agent brief carries the song and track tunings and a `cents` column when a focused note has one. SDK 1.16.0: `song({ tuning })`, `track({ tuning })` (a name string or an object), and pitch strings with a cents suffix (`note("E4-14c", 0)`, `seq("C4 E4-14c G4+2c")`).
1361
1392
 
1362
1393
  Rendering. Synth and wavetable voices start at the tuned frequency; keyed samplers repitch by the ratio between the tuned and the 12-TET frequency, and one-shot samplers apply note cents only. Live playback, audition and export use the same table, so what you hear is what renders.
1363
1394
 
@@ -1403,11 +1434,11 @@ strum strum the block chords already on the track
1403
1434
  - **Strokes.** A grid on `step` (an eighth by default) of `D` down, `U` up, `d` `u` light strokes, `x` a muted chuck, and `-` or `.` a rest (the strings ring on), or a named pattern: `down folk pop punk funk reggae waltz jangle island`. Down strokes go low to high, up strokes high to low over three or four strings, and a new stroke cuts the strings it restrikes. Patterns are dawg's own: the classic folk/pop eighth-note patterns of beginner method books.
1404
1435
  - **Speed.** The time a full six-string down stroke takes, 22 ms by default (0..200 ms), the spread a real pick takes across the strings; it is in milliseconds so it stays the same at any tempo (`speed 1/32b` gives it in beats, converted at the song tempo). At 120 BPM the first-to-last note spread equals `speed` within 1 ms.
1405
1436
 
1406
- Track.guitar is stored only when set (`{ tune, capo, hand, ring, position }`). Menu: **Sound › guitar** (on guitar-like tracks: tune, capo, hand, ring, position, strum the chords, reset) and **Chords › strokes / speed**. Agent: `set_guitar`, `strum_chords`, and `write_chords` with `perform: "guitar"`, `strokes`, `speed`. SDK: `track({ guitar: { tune: "dadgad", capo: 2 } })` and `strum("G D Em C", { strokes: "folk", speed: 30 })`.
1437
+ Track.guitar is stored only when set (`{ tune, capo, hand, ring, position }`). Menu: the **guitar** page of Ctrl-K › Sound (on guitar-like tracks: tune, capo, hand, ring, position, strum the chords, reset) and a **play** page in Ctrl-K › Chords and key (strokes, speed). Agent: `set_guitar`, `strum_chords`, and `write_chords` with `perform: "guitar"`, `strokes`, `speed`. SDK: `track({ guitar: { tune: "dadgad", capo: 2 } })` and `strum("G D Em C", { strokes: "folk", speed: 30 })`.
1407
1438
 
1408
1439
  ### Chord mode
1409
1440
 
1410
- Play mode has a chord sub-mode modelled on the Orchid's Key mode. It is `auto` by default when the focused track can play chords (pitched synths, piano, soundfonts, keyed samplers; not tracks whose instrument, name or id says bass, kit, drum or perc) in 12-TET without a mono or legato glide, otherwise `manual`, so a track in pelog, just intonation or another non-12 tuning, or a TB-303-style legato line, records single notes. Choosing a mode by hand (`Q`, `/chords`, the menu) sticks for the session.
1441
+ Play mode has a chord sub-mode modeled on the Orchid's Key mode. It is `auto` by default when the focused track can play chords (pitched synths, piano, soundfonts, keyed samplers; not tracks whose instrument, name or id says bass, kit, drum or perc) in 12-TET without a mono or legato glide, otherwise `manual`, so a track in pelog, just intonation or another non-12 tuning, or a TB-303-style legato line, records single notes. Choosing a mode by hand (`Q`, `/chords`, the menu) sticks for the session.
1411
1442
 
1412
1443
  - `auto`: each note key plays the diatonic chord of the song key on that root (C major: `S` plays Dm, `G` plays G). Keys outside the scale borrow from the parallel major or minor. The strip labels every white and black key with its chord.
1413
1444
  - `manual`: note keys play single notes as before; latch a chord type or extension and they play that chord on the pressed root.
@@ -1443,6 +1474,8 @@ Terminals send key presses and auto-repeats, never key releases, so held notes a
1443
1474
 
1444
1475
  Recording: with record armed and the transport running, each note is quantized to the grid (`/grid 1/16` by default; `1/4 1/8 1/8T 1/16 1/16T 1/32`), wrapped into the loop, and appended to the focused track as `addNote` operations when the playhead leaves the bar, so each recorded bar is one revision: one `Ctrl-Z` undoes a bar, other windows and the project files see it like any edit. Stopping commits the rest. The same pitch on the same step twice is one note. Replace removes the bar's earlier notes in the same revision. No agent and no network are involved.
1445
1476
 
1477
+ **Loop recording.** With a loop set (`loop 5-6`, `\` on TAPE), recording commits once per pass instead of once per bar: each wrap is one revision and one `Ctrl-Z`, the card reads `pass 3 · +5 notes`, the header shows `↻ 5–6 · pass 3`, and a pass that played nothing writes nothing. `keys record` arms it (`keys record replace` replaces, `keys record off` disarms), opening PLAY on the focused track first; on TAPE `r` and `R` run those, and `Esc` goes back to TAPE. Replace erases only the bars each pass crossed, inside the loop. Without a loop, playing over a form's repeat (a `░` ghost pass on TAPE) records into the source section's bars, one revision a bar. Live notes sound through the shared daemon engine, so every pane hears them, and each carries its beat. Other panes see the recording pane as `C●`; while another pane is recording a track, replace on it is refused and names that pane (`✗ pane B is recording bass · overdub instead (r)`), and a pass already armed for replace lands as an overdub. On TAPE the reels `◐◓◑◒` turn one step a beat while the transport runs (`|/-\` in ASCII), and as each pass closes the loop brackets draw reversed with the fill `━` for a moment, a shape change rather than a color; `/motion off` keeps both still.
1478
+
1446
1479
  ## Click track
1447
1480
 
1448
1481
  `/click on|off|<volume>` (`/click 40%`, `/click 0.4`) or `M` in play mode. An accented downbeat and lighter beats at the transport tempo and the score's meter, mixed as a separate monitoring bus. It is never part of a loop render, a stem, `dawg render`, or `/export`; tests compare those byte for byte with the click on. `/count-in 0|1|2` sets how many bars of click play before recording starts (default 1); the header counts down and flashes the beat, so it also works with backend `none`.
@@ -1465,7 +1498,7 @@ meter 7/8 at bar 5 meter change on a bar line, lasting until the next
1465
1498
  meter remove bar 5 | meter clear
1466
1499
  track rate 3/2 polytempo: the focused track plays at 1.5× the song tempo (0.125..8 or a/b)
1467
1500
  track phase 0.5 start the track half a beat later
1468
- track cycle 3 polymeter: loop the track's first 3 beats against the song's bars
1501
+ track loop 3 polymeter: loop the track's first 3 beats against the song's bars
1469
1502
  track phasing 3 [over 48] continuous drift: a 3-beat cycle gains one cycle every 48 beats, then realigns (needs a loop of whole spans)
1470
1503
  track phasing 3 hold 8 stepped, as in Piano Phase: hold in step 8 cycles, move a sixteenth ahead over 2 (drift 2, shift 0.25)
1471
1504
  track time off follow the song again
@@ -1478,9 +1511,9 @@ track time off follow the song again
1478
1511
  - **Track time** (`track.time: {rate, phase, cycle, steps?}`, phase, cycle and shift in ticks in the file) places a track's notes on the song timeline: the first `cycle` ticks repeat every `cycle / rate` song ticks, shifted `phase` song ticks later, restarting with every song loop. Two identical tracks with one on `track phasing 4` drift apart a little each cycle and line up again after `over` beats (default the loop), the continuous tape phasing of Reich's _It's Gonna Rain_ and _Come Out_; `over` and the cycle must divide the loop, and the prompt names the bars it needs otherwise. `track phasing 3 hold 8` stores `steps: {shift, hold, drift}` instead of a rate: the track holds in step for `hold` cycles, then moves `shift` ahead over `drift` cycles, and repeats, the shift-and-lock process of _Piano Phase_ and _Drumming_. Automation stays in song time.
1479
1512
  - **Everywhere**: the offline renderer, the live engine and audition loop, the transport clock (beat ⇄ wall time, shared by every window through dawgd), click and count-in, recording quantization and the highway use the same map. `/export song.mid` and `dawg render song.mid` write a format-1 Standard MIDI File: track 0 carries the time-signature (FF 58) and tempo (FF 51) meta events, ramps are written as tempo steps every sixteenth whose BPM is the exact average over the step, so each step boundary lands on the same second as the WAV.
1480
1513
 
1481
- The menu has the same controls under **Project › Tempo & meter** (`/menu tempo`): the tempo map (add a change, a ramp, a rit or accel, a tempo, tempo primo, a fermata), meter changes, and the focused track's rate, phase, cycle, phasing and stepped phasing. The agent's `set_time` tool takes the same actions, and the SDK has `tempo`, `ramp`, `rit`, `accel`, `aTempo`, `tempoPrimo`, `fermata`, `meter`, `phasing` and `stepPhasing` (see **Project files and SDK**).
1514
+ The menu has the same controls under **Ctrl-K › Project › tempo and meter** (`/menu tempo`): the tempo map (add a change, a ramp, a rit or accel, a tempo, tempo primo, a fermata), meter changes, and the focused track's rate, phase, cycle, phasing and stepped phasing. The agent's `set_time` tool takes the same actions, and the SDK has `tempo`, `ramp`, `rit`, `accel`, `aTempo`, `tempoPrimo`, `fermata`, `meter`, `phasing` and `stepPhasing` (see **Project files and SDK**).
1482
1515
 
1483
- ## Drum patterns and kits
1516
+ ## Grooves and kits
1484
1517
 
1485
1518
  **Patterns.** dawg ships a library of 31 starting grooves, written for dawg from the defining placements of each style (no transcriptions). A pattern is a set of rhythm rows, one per voice, so after applying it every part is still a few Euclidean or grid parameters you can change in `/euclid`, with the prompt grammar, or in `track.ts`.
1486
1519
 
@@ -1493,7 +1526,7 @@ The menu has the same controls under **Project › Tempo & meter** (`/menu tempo
1493
1526
 
1494
1527
  Applying to a missing track creates a kit track; an empty melodic track becomes a kit track; a melodic track with notes is refused. Voices the track lacks (a sampler kit without a rim, say) are skipped and named in the receipt. Every apply is one revision and one undo step. In `track.ts`, `pattern("boom-bap")` returns the rows: `rhythm: pattern("boom-bap")`, or `[...pattern("house"), euclid("rim", 5, 16)]` to add one.
1495
1528
 
1496
- Patterns: `house`, `disco`, `techno`, `minimal`, `electro`, `breakbeat`, `amen-style`, `dnb`, `halftime`, `boom-bap`, `lofi`, `trap`, `drill`, `reggaeton`, `dancehall`, `one-drop`, `afrobeat`, `afrobeats`, `bembe`, `tresillo`, `son-clave`, `bossa-nova`, `samba`, `cumbia`, `garage`, `jersey-club`, `footwork`, `rock`, `funk`, `shuffle`, `euclid-poly`. Sounds → Drum patterns in `/menu` lists them too.
1529
+ Patterns: `house`, `disco`, `techno`, `minimal`, `electro`, `breakbeat`, `amen-style`, `dnb`, `halftime`, `boom-bap`, `lofi`, `trap`, `drill`, `reggaeton`, `dancehall`, `one-drop`, `afrobeat`, `afrobeats`, `bembe`, `tresillo`, `son-clave`, `bossa-nova`, `samba`, `cumbia`, `garage`, `jersey-club`, `footwork`, `rock`, `funk`, `shuffle` (triplet 8ths), `half-time-shuffle`, `euclid-poly`. Ctrl-K › Rhythm › grooves lists them too.
1497
1530
 
1498
1531
  **Kits.** A `kit` track plays the built-in drum synth. `kit: "<name>"` on the track (`/kit <name>`, or `set_drum_kit` for the agent) chooses one of six synthesized kits, all offline and deterministic; a track without `kit` sounds exactly as before.
1499
1532
 
@@ -1507,7 +1540,7 @@ Patterns: `house`, `disco`, `techno`, `minimal`, `electro`, `breakbeat`, `amen-s
1507
1540
  | `electro` | tight short kick, clicky rim, ticking hats (alias `minimal`) |
1508
1541
  | `trap` | distorted long 808, crisp hats, high snare |
1509
1542
 
1510
- Sample kits from packs (`/kit 909` and the rest, see **Sample packs**) sit in the same picker after the synth kits. `/kit syn909` on a sampler kit turns it back into a synth kit track, moving hits and rows to the drum voices of the same name. The agent has `list_drum_patterns`, `apply_drum_pattern {name, trackId?, tempo: auto|keep|set}` and `set_drum_kit {kit, trackId?}`, and its prompt starts genre grooves from a pattern.
1543
+ Sample kits from packs (`/kit 909` and the rest, see **Sample packs**) sit in the same picker after the synth kits. `/kit syn909` on a sampler kit turns it back into a synth kit track, moving hits and rows to the drums of the same name. The agent has `list_drum_patterns`, `apply_drum_pattern {name, trackId?, tempo: auto|keep|set}` and `set_drum_kit {kit, trackId?}`, and its prompt starts style grooves from a groove.
1511
1544
 
1512
1545
  ## Arrange (sections and form)
1513
1546
 
@@ -1535,7 +1568,7 @@ Sections are markers over the timeline, like the arranger track in Studio One or
1535
1568
  | `drop [<section> \| at <bar>] [cut <beats>] [no impact]` | a pre-drop cut (1 beat of silence by default, up to two bars) and an impact on the downbeat; bare `drop` lands on the section named `drop` or `chorus`, else on the bar after the last build (adding that bar when the build ends the song); with neither it asks for `drop chorus` or `drop at 17`. A cut that clips a build's uplifter retunes its rise to end at the cut |
1536
1569
  | `fill [<section> \| at <bar>] [toms\|roll\|kick] [<n> beats] [no crash]` | a drum fill on the beats before the section (1 by default, ½ beat up to two bars), or at every section boundary |
1537
1570
 
1538
- Playback follows the form; with a section looped it loops just that section, with its mutes and variations, and the highway, play mode and auditions stay inside it (an audition region is clipped to the looped section). Export (`dawg render`, the agent's preview) always plays the whole form and ignores the section loop; `dawg render out.wav --section chorus` renders one section. Every WAV covers the whole song (or section) plus its tail; a long song renders in windows and is capped at 15 minutes, and a held note that crosses a window seam is crossfaded so long drones stay smooth. Exports mark the form: WAV renders carry a `cue ` point and `LIST adtl` label at each section start, and MIDI exports an FF 06 marker. Sections count bars in one meter: a compound meter held from bar 1 (`meter 6/8`, `song({ meter: [12, 8] })`) works, while meter changes later in the song and sections exclude each other.
1571
+ Playback follows the form; with a section looped it loops just that section, with its mutes and variations, and the highway, play mode and auditions stay inside it (an audition region is clipped to the looped section). Export (`dawg render`, the agent's preview) always plays the whole form and ignores the section loop; `dawg render out.wav --section chorus` renders one section on its own: notes and clips are cut at its edges and nothing before it plays, so stateful effects (a vocoder's band followers, a talkbox's first LPC frame, reverb tails) start fresh and the first and last few milliseconds differ from the same span of the full render. The windows a long render is split into internally do pre-roll and match the full render. Every WAV covers the whole song (or section) plus its tail; a long song renders in windows and is capped at 15 minutes, and a held note that crosses a window seam is crossfaded so long drones stay smooth. Exports mark the form: WAV renders carry a `cue ` point and `LIST adtl` label at each section start, and MIDI exports an FF 06 marker. Sections count bars in one meter: a compound meter held from bar 1 (`meter 6/8`, `song({ meter: [12, 8] })`) works, while meter changes later in the song and sections exclude each other.
1539
1572
 
1540
1573
  Generators write ordinary notes, tracks and automation, so everything they make can be edited or undone (one step per command):
1541
1574
 
@@ -1548,35 +1581,260 @@ Generators write ordinary notes, tracks and automation, so everything they make
1548
1581
 
1549
1582
  Section mutes and variations govern every bar of their section: a note held from an earlier section stops at the bar where a section that mutes its track begins, so a song sounds the same played straight through and through the form. Generators place their cut and crash by bar order in the score; with a form that reorders sections, run them on the bars that precede the target in the form (`build 13-16`, `fill at 17`).
1550
1583
 
1551
- The arrangement strip is one row under the header that shows the sections over the timeline (`▏verse ▏chorus`), the looped section reversed, the section under the playhead bold, and the playhead as `▼`. It appears only when the song has sections. The menu's **Arrange** section (`/menu arrange`) lists every section; each opens loop, jump here, a mute toggle for every track, transpose and gain, build (into it, over it, or custom length and layers), drop (cut length and impact), fill (style, beats and crash), duplicate, duplicate as, move to, move left and right, rename, clear mutes and variations, unmark and delete. Agent tools: `list_sections`, `edit_section` (mark, add, duplicate, move, rename, delete, unmark, mute, unmute, vary, reset, loop, unloop), `set_form` and `add_transition` (build into or over a section, drop, fill). In `song.ts`: `song({ sections: [{ name: "verse", startBar: 0, bars: 8 }, …], form: "intro verse*2 chorus", loopSection: "chorus" })` (SDK 1.18.0).
1584
+ The arrangement strip is one row under the header that shows the sections over the timeline (`▏verse ▏chorus`), the looped section reversed, the section under the playhead bold, and the playhead as `▼`. It appears only when the song has sections. The menu's **Arrange** section (`/menu arrange`) lists every section; each opens loop, jump here, a mute toggle for every track, transpose and gain, build (into it, over it, or custom length and layers), drop (cut length and impact), fill (style, beats and crash), duplicate, duplicate as, move to, move left and right, rename, clear mutes and variations, unmark and delete. Agent tools: `list_sections`, `edit_section` (mark, add, duplicate, move, rename, delete, unmark, mute, unmute, vary, reset, loop, unloop), `set_form` and `add_transition` (build into or over a section, drop, fill). In `song.ts`: `song({ sections: [{ name: "verse", startBar: 0, bars: 8 }, …], form: "intro verse*2 chorus", loop: "chorus" })` (SDK 1.18.0; `loopSection: "chorus"` still reads).
1585
+
1586
+ ## Ranges (loop, copy, move, bars)
1587
+
1588
+ The **loop range** is the bars playback cycles. `loop 5-6` sets it, `loop chorus` loops a section, `loop next` and `loop prev` step it one length along, and `loop off` plays the song again. A loop range is not a section: it never shows on the arrangement strip or in the form, and it is saved as `score.loop` (`loop: "5-6"` in `song.ts`, SDK 1.34.0). Setting one clears a looped section and the other way round. Export ignores it, like the section loop. Songs saved by 0.7 with a hidden section named `loop` load with that section turned into the loop range.
1589
+
1590
+ Range commands take bars as the ruler counts them (from 1): `5-6`, `5`, or a section name. Without a range they act on the loop range, else the section under the playhead, else the bar under it, which is what the keyed gestures use. `<track>` is a track id or name, `all` is every track, and a bare command acts on the focused track. Each command is one undo step and one diff operation set, so other windows and the agent see the edit, not a new score.
1591
+
1592
+ | Command | Does |
1593
+ | ---------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
1594
+ | `loop <a>-<b>` / `loop <section>` / `loop next` / `loop prev` / `loop off` | set, step or clear the loop range |
1595
+ | `copy [<track>\|all] <a>-<b> to <bar> [x<N>] [insert\|merge]` | copy the notes, automation and clips in those bars to `<bar>`, replacing what is there; `x2` tiles it end to end, `merge` keeps what is there, `insert` shifts later music right |
1596
+ | `move [<track>\|all] <a>-<b> to <bar> [insert]` | copy, then empty the source |
1597
+ | `clear [<track>\|all] <a>-<b>` | empty the bars; the bars stay (bare `clear` still empties the focused track) |
1598
+ | `copy [<track>\|all] <a>-<b>` then `paste [at <bar>] [x<N>] [insert\|merge]` | the clipboard: `copy` without `to` fills it, `paste` lays it down at the playhead's bar or `<bar>`; it is per window and not saved |
1599
+ | `reverse [<track>\|all] <a>-<b>` | mirror the bars in time |
1600
+ | `bars insert <n> at <bar>` / `bars remove <a>-<b>` | add empty bars before `<bar>`, or cut bars out; later sections, clips, automation, tempo and meter points and the loop range move with the music |
1601
+ | `jump <bar>[.<beat>]` / `jump <section>` | move the playhead |
1602
+ | `section split <name> at <bar>` / `section join <name>` | cut a section in two (the second half is `<name> 2`), or merge it with the section after it |
1603
+ | `form print` | the form written out as plain bars (`form bake`) |
1604
+
1605
+ Copy, move, clear and reverse never touch tempo or meter points (except that `insert` opens new bars first); `bars insert` and `bars remove` move them. The menu's **Arrange › range** runs the same commands (loop bars, loop next and prev, copy, move, clear, reverse, paste at, insert and remove bars), the agent's `edit_range` tool types them, and show-me can type any of them. In `song.ts`, `bars(notes, 5, 6)` takes the notes in bars 5-6 starting at 0, `place(notes, { at: 7, times: 3 })` lays them at bar 7 three times, `reversed(notes, { bars: 2 })` mirrors them, and `insertBars(song, { at: 7, bars: 2 })` inserts bars into a whole song (SDK 1.34.0).
1606
+
1607
+ ## TAPE (Ctrl-T)
1608
+
1609
+ `Ctrl-T` or `/tape` opens TAPE in place of the highway: every track as a row across the bars, each cell a density glyph (` ▁▂▃▄▅▆▇█`, the notes starting there weighted by velocity; a held note `▁`, an audio clip `▃`, empty `·`). Above the rows sit the bar ruler with the playhead `▼` and the loop brackets `[══]` read from `score.loop`, and the sections (`▀name▀`, the looped one reversed). The focused row carries `›`, a muted row is dim, and color is never the only cue (NO_COLOR and the mono theme keep every glyph). `-` and `=` zoom between a bar, a beat and half a beat a cell, and the view scrolls to keep the playhead in sight. The highway stays home: `Esc` or `Ctrl-T` again goes back, and `dawg pane tape` opens a pane on it.
1610
+
1611
+ The footer is the range line (`range: bass · bars 5–6 (loop)`, the loop range, else the section, else the bar under the playhead) with the clipboard chip, the four knobs (● playhead, ▲ loop length, ■ tempo, ◆ volume, from `tui/knobs.ts`; `↑` `↓` pick, `←` `→` turn, `⇧` coarse, `Enter` opens the drawer) and the key hints. Every key echoes and runs a typed command, so TAPE teaches the range commands above: `c` (`C` all tracks) is `copy bass 5-6`, `x` is `copy` then `clear`, `v` pastes at the playhead's bar (`V` inserts), `delete` clears, `~` reverses, `\` loops the section here (again: off), `<` `>` slide the loop, `[` `]` `{` `}` move its end and start, `,` `.` jump between sections, `s` `S` split and join the section, `h` `H` mute and solo, and `1`-`9` or `Tab` focus a track row (number keys pick tracks on TAPE, not knobs). The first paste after a cut lands as one `move`, one revision. The clipboard is per pane and kept in `.dawg/clipboard/` next to the session, so it survives a restart. With the mouse, a click on the ruler jumps, a drag on it sets the loop, a click on a row focuses it and a drag along a row sets the loop over those bars; the wheel walks the playhead a bar at a time. Other panes on a track show in the right gutter (`B`, `C●` while that pane records). Score edits commit like any other command, so other windows and the agent see them.
1612
+
1613
+ With a form set (`form verse chorus*2`), TAPE draws the song unrolled, in the order it plays: the first pass of each section is solid and every later pass is a ghost, shaded `░` (`~` without Unicode) and dimmed, with the section row labeled `chorus ×2`. Each column still names its score bar, so a key, click or drag on a ghost acts on the source section, and the range line says so (`range: bass · bars 5–8 (chorus) · edits chorus (plays 2×)`). The playhead follows the pass that is playing; bars the form never plays sit after it. `form print` (or `form bake`) writes the form out as plain bars: `printed form to tape · 16 bars`.
1614
+
1615
+ ## Styles
1616
+
1617
+ `/style` writes a whole song in a style: drums, bass, chords, melody and form, generated from a **style card**, a set of theory patterns rather than recordings. A card names the meter (signature, additive grouping, hypermeter, cycles such as tala, iqa, gongan or clave timelines), the tempo range, the groove (subdivision, swing ratio, microtiming, per-role lay-back, seeded humanisation), the onset grids per role as abstract step weights, the pitch space (12-TET scales and modes, or a tuning such as maqam quarter tones, with scale weights), the harmony as a grammar (Roman-numeral presets, fixed forms such as the 12-bar blues, a Markov chain and cadences), melody contour, range and interval statistics, the texture (which roles are required) and the form. Cards inherit: a leaf patches its branch, which patches its family root, so `deep-house` only states what differs from `house` and `electronic`.
1618
+
1619
+ The taxonomy (`core/styles/taxonomy.ts`) has eight families and about 850 styles; every leaf is present, and a leaf without its own card generates from its nearest ancestor's card. Generation is deterministic: the same style, bars and seed always write the same song, and nothing is random beyond the seed.
1620
+
1621
+ | Command | Does |
1622
+ | -------------------------------------------- | -------------------------------------------------------------------------------- |
1623
+ | `style` | the families and their root styles |
1624
+ | `style list [<id>]` | the children of a style |
1625
+ | `style search <words>` | ranked search over ids, names, aliases and regions |
1626
+ | `style info <id>` | path, meter, tempo, groove, tuning, harmony and roles |
1627
+ | `style <id> [bars] [seed]` | replace the song with one in that style (1 to 64 bars, default 8; one undo step) |
1628
+ | `style blend <a> <b> [weight] [bars] [seed]` | mix two cards; weight 0 is `a`, 1 is `b` (default 0.5) |
1629
+ | `style again` | the song's style with the next seed |
1630
+
1631
+ Every generated song is checked by `core/styles/validate.ts` before it is written: meter and tempo in range, onsets on the card's grid within tolerance, the swing ratio measured from the notes, every pitch in the scale or tuning, the chord sequence accepted by the harmony grammar, melody range and interval statistics, and every instrument resolving. The summary line reports how many checks passed. The song records its provenance in `style` (`{ id, seed, bars, blend? }`), which `/style again` reads.
1632
+
1633
+ Menu: **Ctrl-K › Arrange › style** (`/menu style`) walks the taxonomy, with `find`, `blend` and, once the song has a style, `again`; each style offers make 4, 8, 16 or 32 bars and about. Agent tools: `list_styles` (families, children or a query), `style_info` and `apply_style` (`id`, `bars`, `seed`, `blend`, `weight`). In `song.ts`: `song({ style: style("deep-house", { seed: 3, bars: 8 }) })` records the provenance (SDK 1.33.0); the notes live in the tracks as usual.
1634
+
1635
+ ## Voice
1636
+
1637
+ Release 0.7 adds voice tools: audio clips and lyrics, pitch tracking, autotune, a formant shift, sung vowels and choirs, and a vocoder. Each arrives in its own subsection below. `/vocal` is the umbrella: bare `/vocal` lists every voice verb this build has, and `/help voice` shows them; `/help <command>` (`/help vocoder`, `/help clip`, `/help autotune`) shows one command with its full usage. Ctrl-K › Voice: every voice tool; on a track without a voice it offers "turn this track into a voice".
1638
+
1639
+ A track may store `clips`, `takes` and note `lyric`s (see docs/project-format.md). Projects without them sound exactly as before.
1640
+
1641
+ ### Clips and lyrics
1642
+
1643
+ An audio clip is a window onto a WAV file in the project, placed at a bar on a track. Clips sum into the track before its effects, so the track's filter, compressor, reverb, sends and automation all act on them. Any track can hold clips: on a synth track the notes still play beside them.
1644
+
1645
+ - **`vocal`** is the instrument for a sung part. Its notes are guides: silent in exports and renders, a soft sine (about -12 dB under a plain sine) in play mode and the audition loop so you can sing or check against them. `instrument vocal` (or `/track vocal`) also fills the vocal chain into effects the track has not set: a 90 Hz high-pass for rumble and plosives, a 3:1 compressor, and a short plate. A `vocal` track without clips keeps the plain tone older projects had.
1646
+ - **`/vocal import <file> [bar]`** copies a WAV that is already 48 kHz mono 16-bit straight in, and converts anything else through the sample importer (ffmpeg, which dawg detects and never installs), into `tracks/<slug>/samples/` as 48 kHz mono 16-bit, pins its sha256 and places a clip at the bar (bar 1 when left out). On a fresh track (default sine, no notes) it also sets the instrument to `vocal`, without the chain. `instrument vocal` adds the vocal chain (90 Hz high-pass, 3:1 compressor, short plate) to effects the track does not set, and its receipt lists what it added. The clip gain is set so the file's peak sits at -6 dBFS, leaving headroom for the chain. `/vocal stem [bar]` places the vocals stem from the last `split_stems`. `/vocal setups` lists one-step setups; `/vocal setups <name>` applies only what this build supports and names what is missing (`hyper` sets `autotune hard`, formant +3.5, slapback and light distortion; setups needing harmony, recording or `/say` wait for those tools).
1647
+ - **`/clip`** lists the focused track's clips; `/clip [id] <edit>` changes one (the clip under the cursor when `id` is left out): `gain -3` (dB, -60..12, absolute; `gain by -3` nudges), `fade .01 .2` (in and out, seconds, equal-power; `fade in .01` or `fade out default` sets or resets one side), `move 9` and `split 7` (1-based bars, `5.3` is bar 5 beat 3), `trim [offset s] [dur s|end]` (seconds into the file; `dur end` plays to the file's end), `rev`, `mute` (toggles), `repeat 2 [to 32]` (copies every 2 bars up to bar 32, or to the song end; `repeat every 2 to 32` also works), `rm`.
1648
+ - **`/lyrics [bar] <text>`** puts syllables onto the focused track's notes from the bar, one per note in time order (the top note of a chord). `hel-lo` splits a word across notes, `_` holds the last syllable over another note (a melisma), `~` skips a note. A word without hyphens is split by an English syllable guess (th, sh, ch, ng and ck stay whole, a consonant plus `le` is its own syllable: `lit-tle`, `some-thing`, `for-ev-er`), and the receipt names the words it split; type hyphens where the guess is wrong. Notes starting together (a chord or a doubled note) take one syllable on the top note, and the others hold. `/lyrics` shows them; `/lyrics clear [bar]` removes them. On a sing track each syllable sings its vowel and a `_` holds the vowel before it; on other tracks the guide and the highway show them.
1649
+
1650
+ Placement follows the tempo map: a clip starts on its tick's time and plays at the file's own speed, so a tempo ramp moves its start but never stretches the audio (Ableton calls this warp off; a clip in a take with `ppm` is stretched by that drift only). A clip whose file is missing or whose bytes no longer match its sha256 renders silent and warns; `dawg check` re-hashes every clip and take and reports mismatches like a type error. Sections act on clips: a section mute silences the clips in it, and a clip crossing a section edge is cut there with a 5 ms equal-power fade, as are clips cut by `split`.
1651
+
1652
+ Ctrl-K › Voice › **clips** (each clip with gain, fade in, fade out, start in file, length, reverse, mute, move, split, repeat, file and remove; import a file, the vocals stem and the setups) and **lyrics**, and Ctrl-K › Voice › **voice presets**. The highway draws a clip row beside the focused track: each clip as a waveform block named by its file, a `┃` seam where one clip starts at another's end, and each note's lyric beside its head. Agent tools: `place_clip`, `edit_clip` (gain, fades, trim, move, split, rev, mute, repeat, remove), `set_lyrics`; the tools and commands take gain in dB and the tools' `fadeIn`/`fadeOut` are the SDK's `fadeInTime`/`fadeTime`. SDK:
1653
+
1654
+ ```ts
1655
+ track({
1656
+ id: "vox",
1657
+ instrument: "vocal",
1658
+ clips: [
1659
+ audio("tracks/vox/samples/verse.wav", { at: 8, gain: 0.7, fadeTime: 0.2 }),
1660
+ ...repeatAudio(audio("tracks/vox/samples/hey.wav", { at: 16 }), {
1661
+ every: 4,
1662
+ until: 32,
1663
+ }),
1664
+ ],
1665
+ notes: lyrics("hel-lo _ world", [
1666
+ note("C4", 8),
1667
+ note("D4", 9),
1668
+ note("E4", 10),
1669
+ ]),
1670
+ });
1671
+ ```
1672
+
1673
+ `at`, `in` and `out` are in beats, `offset` and `dur` in seconds, and `gain` is linear (0.5 is about -6 dB; the printer writes the dB beside it as `gain: 0.5 /* -6.0 dB */`); `take(name, src, opts)` describes a take (the shape 0.7.1 recording writes). `audio()` prints its options in the order `id at offset dur gain fadeInTime fadeTime rev take mute text say sha256`, and a printed project reads back deep-equal.
1674
+
1675
+ ### Formant shift and vowel morph
1676
+
1677
+ The `formant` effect moves a sound's formants (the resonances of the throat and mouth that make a voice sound big or small, male or female) without changing its pitch; it works on any source and most clearly on voices. It sits in the chain after `autofilter` and before `vowel`.
1678
+
1679
+ | Knob | Range | Default | Lane | Does |
1680
+ | ---------------- | ----------- | ------- | --------------- | --------------------------------------------------------------------------- |
1681
+ | formant shift | -12..12 st | 0 | `formant-shift` | moves the spectral envelope; negative is deeper or bigger, positive smaller |
1682
+ | formant mix | 0..1 | 1 | `formant-mix` | blends the shifted and the dry signal |
1683
+ | vowel morph (to) | 0..1 (to v) | 0 | `vowel-morph` | glides the vowel filter's five formants from `vowel` toward `to` (log Hz) |
1684
+
1685
+ ```text
1686
+ /formant -4 deeper (pitch stays); /formant 3 0.5 is smaller at half mix
1687
+ /formant giant presets deep giant bright tiny; /formant off removes it
1688
+ /vowel a o 0.5 vowel filter halfway from a to o; /vowel morph 0.8, /vowel to u
1689
+ /vowel to off back to one vowel; changing the vowel keeps your mix
1690
+ /vocal formant -4 the same, under the voice umbrella
1691
+ automate formant-shift points 0:-6 8:6
1692
+ ```
1693
+
1694
+ Shifts of 2 to 4 st sound natural; 7 and beyond are a cartoon. The shift is a cepstral spectral-envelope warp (Röbel and Rodet 2005; Smith, Spectral Audio Signal Processing): each STFT frame (1024 points at 24 kHz and below, 2048 above, hop a quarter frame, Hann analysis and synthesis) is divided by its envelope and multiplied by the envelope read at `k / 2^(st/12)`, the gain clamped to 24 dB and the phase left alone. The envelope's lifter follows the voice: 0.75 of a pitch period from a 5-frame median autocorrelation f0, clamped to 1..2 ms, 1 ms when unvoiced. Measured on the synthetic voice fixture at 22.05 kHz, ±2 and ±4 st land within 1 cent of the original pitch and 3 to 5.5 dB RMS of the envelope of a voice synthesized with moved formants (against 4 to 10 dB unprocessed), pre-echo stays below -15 dB, and it costs about 4 ms per audio-second. Frames are anchored to absolute hop multiples, so a preview window plays exactly the same samples as the full render. `shift 0` without automation leaves the sound untouched. This is not the sampler's `shift … formant`, which keeps formants while the pitch moves.
1695
+
1696
+ Before 0.7, `fx formant` was an alias of the vowel filter. `/fx formant o` and `set_fx {effect: "formant", vowel}` now answer "formant now shifts formants at constant pitch; the vowel filter is `vowel`".
1697
+
1698
+ Ctrl-K › Voice › **formant**: shift and mix (left/right adjust, `x` resets, space auditions with staged A/B); **Effects › more effects › vowel** gains To and Morph. Mix lists the `formant-shift`, `formant-mix` and `vowel-morph` lanes. The agent's `set_formant` tool takes `shift`, `mix`, `preset` and `off` and can be previewed with `preview_sound`; `set_fx vowel` takes `to` and `morph`. In the SDK: `fx: { formant: { shift: -4 }, vowel: { vowel: "a", to: "o", morph: 0.5 } }`.
1699
+
1700
+ ### Singing voice
1701
+
1702
+ `sing choir` (or `/sing choir`, or the instrument words `aah`, `ooh`, `choir`, `chorale`, `khoomei`, `sygyt`, `kargyraa`) turns the focused track into the built-in singing voice: a synthetic glottal source (the Liljencrants-Fant model, shaped by Fant's Rd voice quality and band-limited by pitch so nothing aliases) through a five-formant Klatt cascade with soprano, alto, tenor and bass vowel tables. It is a synthetic voice, not a recording and not anyone's voice, and it renders offline and deterministically. It reads pitch through the track's tuning, and glide, articulation, pedal and the tempo map work as they do for winds.
1703
+
1704
+ Presets: `aah ooh choir oohchoir chorale airy glass` (Choir), `lament soprano basso` (Solo) and `drone khoomei sygyt kargyraa` (Throat). `sing` with no arguments shows the track's voice; `sing list` lists the presets; `sing <preset> <param> <value>…` applies a preset and overrides; `sing <param> <value>` changes one value and `sing <param> off` returns it to the preset; `sing off` clears the voice and returns the track to the plain `sine` tone, so it sounds the same after a save and reload.
1705
+
1706
+ Parameters: `voice` (`auto`, `soprano`, `alto`, `tenor`, `bass`; auto picks one type per part from its median pitch, so an SATB part keeps its own timbre), `vowel` (`a e i o u`, or a morph such as `a>o` across each note), `morph`, `formant` (semitones, a bigger or smaller singer), `bright` (voice quality: breathy to pressed; velocity also presses), `breath`, `jitter`, `shimmer`, `attack`, `release`, `vib`, `vibmod`, `vibdelay` (vibrato that arrives after the onset), `voices` (1 to 8; more than one is a stereo ensemble with seeded scatter of pitch, timing, vibrato and formants), `spread` (cents), `ring` (the singer's formant near 3 kHz), `drone`, `overtone`, `harmonics` and `sub`, and `gain`. Lanes `sing-morph`, `sing-formant`, `sing-bright`, `sing-breath`, `sing-vibmod`, `sing-ring`, `sing-overtone` and `sing-sub` automate them.
1707
+
1708
+ Vowels per note: `/note vowel o` (or `a>u`, or `off`) on the selection, last note, a bar or note ids, and `sing vowels a e i o u` cycles vowels over the track's notes. A note's own `vowel` wins over the track's; a note with a `lyric` sings that syllable's vowel. High notes open the jaw: when the pitch rises above the first formant the voice raises it to follow, as trained sopranos do.
1709
+
1710
+ Throat singing: `khoomei`, `sygyt` and `kargyraa` hold one drone (`drone D3`, a note name or MIDI number) for each phrase, and each melody note picks the drone harmonic nearest to it (folded by octaves into `harmonics`, 6 to 12 by default) and sharpens the overtone filter onto it, so the melody whistles above the drone. `kargyraa` adds a subharmonic an octave below (`sub`). Without a `drone` of your own, the drone follows the song key: the key root nearest the preset's drone, so sygyt keeps its whistle near 2 kHz and kargyraa its growl near A2 (`sing` and the menu show it as, for example, `drone E2 (key)`). A throat preset on a track with no notes writes an 8-note demo line an octave above the drone, so `sing khoomei` sounds in one step. In play mode a throat track is one voice: a new key releases the last.
1711
+
1712
+ Menu: Ctrl-K › Voice › voice presets: a Choir, Solo or Throat preset (space auditions) with the sing rows, with the drone and overtone rows in a Throat sub-menu; on a sung track, the **Vowels** page sets the notes' vowels (Ctrl-K › Sound › performance). The agent tools are `set_sing` and `set_vowels` (both previewable), and `set_instrument` takes the instrument words. In the SDK: `track({ instrument: sing("khoomei", { drone: "D3" }) })`, `instrument: "choir"`, and `note("C4", 0, 1, 0.8, { vowel: "a>o" })`.
1713
+
1714
+ ### Pitch
1715
+
1716
+ dawg can read the melody out of audio: a sampler voice (for example a stem from `resample` or `split_stems`) or, once clips land, an audio clip. Nothing in the score changes until you ask for guide notes.
1717
+
1718
+ - `/vocal pitch` reports the focused audio's detected key, median pitch and range with cents (`stem · voice stem · key · a major · median C#4 -8c · range A3 -5c to F#4 +30c · 14 notes`). Add `clip` or `voice` (a sampler voice), optionally followed by its name in any case, to choose the source, and `bass`, `tenor`, `alto` or `soprano` to narrow the search range (auto is 70-1400 Hz; bass goes down to 55 Hz).
1719
+ - `/vocal pitch trace on` draws the sung pitch over the note highway: one dot per column on the lane of the nearest semitone, in the warning color when it is more than 15 cents off. `/vocal pitch trace off` hides it.
1720
+ - `/vocal notes` turns the melody into a new guide-notes track (`<track>-notes`, or `as <name>`), one note per sung note, placed through the tempo map where the audio plays. Velocity follows the voicing confidence.
1721
+ - Ctrl-K › Voice › pitch shows the detected key and median, with Analyze, Trace and Make notes rows.
1722
+ - Agent tools: `analyze_pitch` (read-only: key, median, range and the note list in file seconds) and `pitch_to_notes`.
1723
+
1724
+ The tracker is a pYIN-style estimator on an exact 16 kHz copy of the audio, with a 5 ms hop, a voicing probability per frame and a Viterbi path that resists octave jumps; on the test voices it stays within 10 cents of the truth on held notes and costs about 20 ms per audio second. Curves are cached in `.dawg/analysis/` (`<sha256>.<voice>.v<version>.f0`, at most 64 MB, oldest removed first; `/pack cache` shows the size), so a second look is instant. A corrupt or old cache file is ignored and rebuilt, and the cached and fresh curves are identical.
1725
+
1726
+ ### Vocoder
1727
+
1728
+ A vocoder makes one sound talk with another: a voice (the modulator) shapes the spectrum of an instrument (the carrier) band by band, so the instrument sings the voice's words at the instrument's pitch. dawg's vocoder sits on the carrier track: `vocoder.src` names the voice track, and the carrier is the track itself.
1729
+
1730
+ The one-step way: focus a vocal track (a sampler voice or clips) and type `/vocoder`. dawg adds a `<name> vocoder` track playing the built-in carrier, points it at the vocal and mutes the vocal, in one undo step; the carrier follows the song's chords when it has harmonic tracks, otherwise it drones on the song key's root. `/vocal vocoder` is the same command. On a synth track with one vocal in the song, `/vocoder` drives that synth instead. `/vocoder talkbox` or `/vocoder formant 3` on the vocal does the same with those settings, and on a vocal that already drives a carrier `/vocoder` focuses that carrier (`/vocoder new` makes another). With nothing to vocode it changes nothing and says how to bring a voice in. Muting the source does not silence the vocoder: the vocoder listens before the source's mute, volume, pan and sends.
1731
+
1732
+ `/vocoder` and `vocode` echo the modulator's license. Vocode only your own recordings or audio you hold the rights to. To vocode your own voice, load a recording of it onto a track (`/sample take.wav`), then `/vocoder talkbox` on that track.
1733
+
1734
+ | Command | Does |
1735
+ | -------------------------------------------- | -------------------------------------------------------------------------------------- |
1736
+ | `/vocoder [preset]` | vocode the focused vocal (a new carrier), or set the focused carrier's preset |
1737
+ | `/vocoder src <track>` | the modulator: a track id or name slug (`lead-vox`); preset words win over track names |
1738
+ | `/vocoder <param> <value>` / `<param> reset` | set or reset any parameter below (`att` and `rel` are Strudel spellings) |
1739
+ | `/vocoder reset` / `off` / `presets` | back to the preset's values; remove the vocoder; list presets |
1740
+ | `instrument vocoder` | the built-in carrier: saw, supersaw, pulse or noise following notes, chords or a drone |
1741
+
1742
+ Presets (all 24 bands or fewer): **classic** (70s and 80s band vocoder lead, the default), **robot** (12 bands, a pulse drone), **talkbox** (an LPC mouth filter on a saw), **choir** (stereo supersaw chord pad), **glass** (bright, formant +3), **whisper** (noise carrier), **smear** (long release wash; try `freeze`) and **lofi** (8 narrow bands under 4 kHz).
1743
+
1744
+ Register matters for the talkbox: a carrier note sounds only the harmonics of its pitch, so a vowel's first formant (250-700 Hz) needs a carrier fundamental well below it. Keep talkbox chords and lines around A2-A3 (`voicing -8` on a progression, or write them an octave or two down); above about 300 Hz /i/ and /u/ blur into /a/. `/track rm <name>` removes a track and drops any `vocoder.src` that named it, so the carrier plays alone; a project file whose src names a missing track fails `dawg check` with that fix.
1745
+
1746
+ | Parameter | Range | Default | Does |
1747
+ | ---------- | ----------------------------------- | --------- | ------------------------------------------------------------------------------------------------ |
1748
+ | `tap` | `chain`, `dry` | chain | listen to the source after its mono chain (before pan) or before its effects |
1749
+ | `mode` | `channel`, `talkbox` | channel | a band bank, or an LPC talkbox (order sr/2000, 20 ms frames) |
1750
+ | `carrier` | `saw`, `supersaw`, `pulse`, `noise` | supersaw | the built-in carrier (`instrument vocoder` only) |
1751
+ | `follow` | `notes`, `chords`, `drone` | notes | the built-in carrier's pitch: its notes, the song's chords, or `root` |
1752
+ | `root` | 24..96 | 45 | the drone pitch (MIDI), and the octave chords are voiced from |
1753
+ | `spread` | 0..1 st | 0.15 | supersaw detune |
1754
+ | `bands` | 4..40 | 16 | channel bands, spaced evenly in log frequency (heavy above 24) |
1755
+ | `lo`, `hi` | 50..1000 Hz, 2000..12000 Hz | 100, 8000 | the lowest and highest band centers |
1756
+ | `width` | 0.25..4 | 1 | band width as a multiple of the spacing |
1757
+ | `attack` | 0.0005..0.2 s | 0.005 | envelope follower attack |
1758
+ | `release` | 0.005..2 s | 0.04 | envelope follower release; long releases smear |
1759
+ | `formant` | ±24 st (talkbox ±12) | 0 | move the voice's formants: + is smaller and brighter |
1760
+ | `unvoiced` | 0..1 | 0.5 | noise in place of the carrier on s, f, sh and t |
1761
+ | `sens` | 0..1 | 0.5 | how readily a frame counts as unvoiced |
1762
+ | `hiss` | 0..1 | 0 | the source's top end passed straight through |
1763
+ | `gate` | -90..0 dBFS or `auto` | auto | silence below this source level; `auto` reads the source's noise floor |
1764
+ | `enhance` | on/off | on | whiten the carrier so every band speaks |
1765
+ | `depth` | 0..1 | 1 | how much the voice shapes the carrier |
1766
+ | `freeze` | on/off | off | hold the last sung vowel through every rest (a `vocoder-freeze` lane holds whatever is sounding) |
1767
+ | `mix` | 0..1 | 1 | wet against the plain carrier |
1768
+ | `gain` | ±24 dB | 0 | output trim (a fixed makeup gain and a soft peak guard at 1.0 come first) |
1769
+ | `seed` | integer | track id | the unvoiced noise seed |
1770
+
1771
+ How it works: channel mode splits both signals into the same bands (cascaded RBJ band-passes, laid out from `lo` to `hi` the same at every sample rate), follows each modulator band's level, and multiplies the carrier's band by it. Each band's envelope is advanced by its filter's group delay plus the attack, so consonants stay on time. A formant shift reads the envelopes at a fractional band index. Talkbox mode fits an all-pole mouth filter to each 20 ms frame of the voice (on an absolute hop grid, so windows agree) and runs the carrier through it, which keeps vowels sharper with fewer artefacts. Frames that are both high-band heavy and aperiodic count as unvoiced and get seeded noise (keyed to the song sample) instead of the carrier, as hardware vocoders do with their sibilance switch. A stereo carrier (a supersaw) gets one analysis and two synthesis banks.
1772
+
1773
+ Menu: **Ctrl-K › Voice › vocoder**: a Source picker, Preset and one row per parameter; **Ctrl-K › Voice › voice presets** (Vocoder) makes a carrier track or picks a preset. The Mix lane picker lists `vocoder-spread`, `-width`, `-release`, `-formant`, `-unvoiced`, `-hiss`, `-depth`, `-freeze` (a 0/1 step lane), `-mix` and `-gain`. Agent tools: `set_vocoder` (preset, src and a params object; previewable) and `vocode` (makes the carrier from a source and echoes the source's license). In `song.ts`: `vocoder("talkbox", { src: "lead-vox", formant: 2 })` as an instrument or as a track's `vocoder` field (SDK 1.32.0).
1774
+
1775
+ Cost: a 16-band channel vocoder renders at about 45 ms per audio-second, talkbox about 10 ms (measured on an M-series Mac); renders reuse the source's cached audio, and an edit to the source's pan, reverb, delay or sends does not re-render the vocoder.
1776
+
1777
+ ### Autotune
1778
+
1779
+ `/autotune hard` corrects the pitch of a track's audio: every clip and every sampler voice on the track, after the sampler's shift step. One word is enough; the default is `pop`. Presets go from hard to gentle:
1780
+
1781
+ | Preset | Sound |
1782
+ | --------- | --------------------------------------- |
1783
+ | `hard` | instant, stepped notes |
1784
+ | `robot` | stepped on every step of the tuning |
1785
+ | `warble` | hard with wide synthetic vibrato |
1786
+ | `trap` | fast and glossy |
1787
+ | `pop` | polished but sung (the default) |
1788
+ | `natural` | keeps scoops and vibrato |
1789
+ | `gentle` | barely there |
1790
+ | `guided` | notes to a written melody, vibrato kept |
1791
+ | `locked` | hard tune locked to a melody |
1792
+
1793
+ Targets (`to`): `scale` (the default) uses the track or song key and the 0.5 tuning, so maqam, raga and n-EDO tunings work, and with no key it is chromatic; `chromatic` uses every step of the tuning; `chord` follows the chord timeline; `notes` follows another track's notes (`/autotune to notes lead`, a routing edge like a sidechain) or, without `from`, the track's own notes (so `/autotune guided` and `/autotune locked` work in one step; a track with no notes is told which tracks it could follow). A key override that names a maqam or raga (`key D bayati`, `key C yaman`) brings that scale's intonation when neither the song nor the track sets a tuning. The `chord` target leaves out the tuned track, its guide and other vocal or autotuned tracks, so sung pitches never count as chord tones. A note's `drift` expression overrides how much slow drift is removed under it.
1794
+
1795
+ Parameters: `speed` 0..400 ms (retune time, 0 is instant; `0.2s` reads as 200 ms), `relax` 0..1 (slower retune on held notes), `hold` 50..1000 ms (when a note counts as held), `flex` 0..100 (higher only pulls notes already near a target, like Antares Flex-Tune), `glide` 0..500 ms (time between targets; a bare number is ms, `40ms` and `0.04s` also work, as with `/glide`), `amount` 0..1, `vib` 0..12 Hz and `vibmod` 0..1 semitones (added vibrato, on voiced frames only), `center` and `drift` 0..1 (notes targets), `key` (overrides the song key) and `voice` (`auto`, `bass`, `tenor`, `alto`, `soprano`: the tracker's range). Corrections are weighted by voicing, guarded against octave errors, and use hysteresis so vibrato does not flip targets.
1796
+
1797
+ Reach it as `/autotune [preset] [param value …]`, `/autotune off`, `/autotune presets`, `/vocal autotune …`, Ctrl-K › Voice › autotune (rows for every parameter; left/right adjusts, `x` resets, space auditions), the agent tool `autotune_vocal`, and the SDK: `track({ autotune: "hard" })` or `autotune("pop", { speed: 40 })`. `/tune` stays the tuning command; `/tune hard` hints at `/autotune`.
1798
+
1799
+ Rendering is deterministic: tuned spans are cached (128 MB of their own) by the audio's identity (a shifted or fitted sample by its shift or fit key), the settings, the targets and the engine version; with chord or note targets a sampler voice tunes only the frames it plays, and stereo samples keep both channels. In play mode a span longer than 0.25 s plays untuned until its correction is ready; the correction runs in slices so play mode stays responsive. Correction uses the pitch tracker and PSOLA of `/vocal pitch`; a clip is tuned once per placement (its offset and take nudge join the key), and a reversed clip or voice plays untuned. License lines for tuned clips made by `/vocal say` come with `say` in 0.7.1.
1552
1800
 
1553
1801
  ## Menus
1554
1802
 
1555
- `/menu` or `Ctrl-K` (on an empty prompt, in play mode too) opens the edit menu, drawn with the same overlay as the model picker. Every edit the agent can make is reachable from it with keys alone. Each row shows a plain label and the current value with its unit (s, Hz, oct, st, dB, BPM, bars); the line under the list describes the focused row and shows, dimmed, the prompt command the row runs, so the menu teaches the commands. `/menu <section>` opens a section directly (`/menu effects`, `/menu arrange`); the old names `parameters`, `sounds`, `track`, `automation` and `transport` still work.
1803
+ `/menu` or `Ctrl-K` (on an empty prompt, in play mode too) opens the edit menu, drawn with the same overlay as the model picker. Every edit the agent can make is reachable from it with keys alone. Each row shows a plain label and the current value with its unit (s, Hz, oct, st, dB, BPM, bars); the line under the list describes the focused row and shows, dimmed, the prompt command the row runs, so the menu teaches the commands. `/menu <section>` opens a section directly (`/menu effects`, `/menu tuning`); the old names `parameters`, `sounds`, `track`, `automation` and `transport` still work.
1804
+
1805
+ | Section | Rows (most used first) |
1806
+ | -------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
1807
+ | Sound | instrument, preset, the instrument's parameters, **advanced**; **synth filter**; **keys**, **organ**, **guitar** or **granular** when the instrument has them; **track tuning**; **performance** (articulation, glide, bend, vibrato, pedal, humanize); **instruments** (Keys › Electric, Organs · Strings › Bowed · Mallets and bells · Winds and brass · Granular · Wavetable · all instruments · sample packs · use a sample) |
1808
+ | Voice | always shown (on a track without a voice it offers "turn this track into a voice"): **sing** (preset, vowels, throat), **lyrics**, **clips**, **pitch**, **autotune**, **formant**, **vocoder**, **voice presets** |
1809
+ | Effects | Filter, Auto filter, Distortion, Tremolo, Compressor, Chorus, Delay, Reverb, Guitar rig, Shoegaze, **more effects** (dj filter, vowel, bitcrush, phaser, leslie, post gain, …); a "voice effects → Voice" row |
1810
+ | Rhythm | the **euclid editor** (`/euclid`), **grooves** (`/pattern`), **kits** (`/kit`, synth then samples), **grid** |
1811
+ | Chords and key | **key** (tonic, scale), **tuning** (song tuning, reference, root, equal steps, Scala file, ratios, cents, keyboard map), **play** (mode, voicing, spread, bass, sevenths, perform, pattern, arp rate, arp octaves), **progression**, **idiom** |
1812
+ | Mix | the focused track's name, mute, solo, volume, pan; **mixer** (every track's level, the drawer's `mix` page); **all tracks** (choosing one focuses it); **automation** (each lane with its points as `beat N value` rows, add points, ramp, clear lane); **master** |
1813
+ | Arrange | **tracks** (add, focus, rename, move, remove), **sections** (each section's loop, jump, mute, transpose, gain, build, drop, fill, duplicate, move, rename, remove; mark bars, add section), **form**, **style** (find, blend, families) |
1814
+ | Project | play, tempo, beats per bar, **tempo and meter**, loop length, grid, click, count-in bars, calibration, **export** (project file, MIDI, WAV, stems), **resample**, **session** (rename, fork, resume), **agent** (model, show-me, model key), **help and guides** (help, guides, keys) |
1815
+
1816
+ Breadcrumbs in the docs and guides use these labels, written `Ctrl-K › Chords and key › tuning`. `/menu <topic>` opens the same section that `/help <topic>` and `/guide <topic>` explain.
1556
1817
 
1557
- | Section | Rows (most used first) |
1558
- | ---------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
1559
- | Sound | instrument, preset; a synth's attack, decay, sustain, release, filter cutoff/res/env, detune, vibrato, FM amount, then **advanced** with every synth parameter; a wavetable track's table and wavetable parameters; **granular** (a granular track's preset, source and parameters; **granular (convert)** elsewhere); a sampler's mode and voices; **performance** (glide, pedal, soft pedal and sostenuto on a piano, velocity curve, humanize); **browse sounds** (instruments, wavetables, Granular, Keys with Electric, packs) |
1560
- | Effects | the core effects (filter, auto filter, distortion, tremolo, compressor, chorus, delay, reverb) with presets and simple parameters, **more effects** (dj filter, vowel, bitcrush, phaser, leslie, post gain, orbit, duck), and **advanced** per effect (see Effects) |
1561
- | Rhythm | the euclid editor (`/euclid`), drum patterns (`/pattern`), drum kits (`/kit`, synth then samples) |
1562
- | Chords | play-mode chord mode, key tonic and mode, voicing, spread, bass, sevenths, perform, pattern, arp rate, arp octaves, progression, style |
1563
- | Mix & automation | the focused track's name, mute, solo, volume, pan; **all tracks** (choosing one focuses it); **automation**: each `AUTOMATION_LANES` lane with its points as `beat N value` rows, add points, ramp, clear lane; **master**: target, eq, glue, tape, width, limiter |
1564
- | Project | play, tempo, beats per bar, loop length, grid, click, count-in bars |
1565
- | Arrange | every section (loop, jump, mute, transpose, gain, build, drop, fill, duplicate, move, rename, unmark, delete), mark bars, add section, form, loop off (see Arrange) |
1818
+ `/menu <id>` takes one of the ten topic ids (`sound`, `voice`, `effects`, `rhythm`, `chords`, `mix`, `arrange`, `project`, `keys`, `agent`), any topic alias (`drums`, `mixer`, `fx`) or any row name it knows (`tuning`, `performance`, `master`, `export`, `models`, …). `/menu keys` opens the `?` panel. An unknown id answers `no menu "sond" · did you mean /menu sound? · /menu <topic or row>`. In a fader, `x` resets any number row; tempo, meter and loop length apply at once rather than staging, and the hint says `applies at once`. Labels fit 16 columns. Below the root, sibling labels share one case rule. A row's path reads `Ctrl-K › Chords and key › tuning`, and show-me's finish hint uses the same path.
1566
1819
 
1567
1820
  Every list, picker and editor uses the same keys (see **Keys** below). In the menu:
1568
1821
 
1569
- | Key | Does |
1570
- | --------------------------- | ------------------------------------------------------------------------- |
1571
- | `↑` `↓` / `k` `j` | move |
1572
- | `Enter` / `→` / `l` | open a section, pick from a list, or open a number's fader drawer |
1573
- | `←` `→` / `h` `l` / `-` `+` | adjust a value by its step (cutoff moves 25%) or cycle a choice |
1574
- | `Space` | toggle on/off; elsewhere, hear the focused track (see Previewing changes) |
1575
- | digits | type a value; `Enter` stages it in the fader drawer, `Esc` cancels |
1576
- | `/` | filter the current list by name, value or command |
1577
- | `x` / `Delete` | reset the focused value to its default; on an automation point, remove it |
1578
- | `Esc` / `←` / `h` | clear the filter, then back one level, then close |
1579
- | `?` | the keys for this screen |
1822
+ | Key | Does |
1823
+ | -------------------- | ---------------------------------------------------------------------------------------- |
1824
+ | `↑` `↓` / `k` `j` | move |
1825
+ | `Enter` | open or confirm: open a section, run an action, open a number's fader, keep staged edits |
1826
+ | `→` / `l` | go in; on a value row, adjust up |
1827
+ | `←` / `h` | back one level; on a value row, adjust down |
1828
+ | `-` `+` | adjust a value by its step (cutoff moves 25%) or cycle a choice |
1829
+ | `Tab` / `Shift-Tab` | next / previous field (in the fader and the rhythm editor) |
1830
+ | `Space` | toggle on/off; elsewhere, hear the focused track (see Previewing changes) |
1831
+ | `0-9` `.` | type a value; `Enter` stages it in the fader drawer, `Esc` cancels |
1832
+ | `/` | filter the current list by name, value or command |
1833
+ | `x` / `d` / `Delete` | reset the focused value to its default; on an automation point, remove it |
1834
+ | `Esc` | revert staged edits, else clear the filter, then back one level, then close |
1835
+ | `?` | the keys for this screen, each key listed once |
1836
+
1837
+ The key sets live in `tui/grammar.ts` (`KEY_UP`, `KEY_DOWN`, `KEY_LEFT`, `KEY_RIGHT`, `KEY_TAB`, `KEY_BACKTAB`, …); the menu, fader, rhythm editor and prompt import them. Play mode's `Tab` is a sustain latch, the one documented exception, because letters are notes there.
1580
1838
 
1581
1839
  Automation rows take `beat:value` pairs (`2:800` or `0:200 4:8000`); a ramp is two pairs, start and end, and the renderer interpolates between points. Turning an effect's first field up switches it on with defaults. Each change runs the command it shows through the normal prompt path, so it is one `ScoreOperation`, one receipt, one undo step, and it syncs to other windows and the project files.
1582
1840
 
@@ -1585,7 +1843,7 @@ Automation rows take `beat:value` pairs (`2:800` or `0:200 4:8000`); a ramp is t
1585
1843
  Editing a number opens a fader drawer: a panel docked directly above the prompt, over the bottom of the piano roll, which stays visible above it. It opens from `Enter` (or a second click) on a number row in the menu, or from a bare parameter at the prompt: `volume`, `pan`, `fx filter` (every filter param, focused on the first number) or `fx reverb mix` (focused on mix). The drawer stacks every param of that device (all of the filter's, or the Mix screen's), so one drawer covers a device.
1586
1844
 
1587
1845
  ```text
1588
- ╭─ menu › Effects › Filter ─────────── loop off · B staged 1 · ● staged [keep] [revert]─╮
1846
+ ╭─ menu › Effects › filter ──── loop off · A/B: 1 change staged · enter keep · esc revert ─╮
1589
1847
  │ type lpf │ hpf │ bpf │
1590
1848
  │ │
1591
1849
  │ › cutoff 1200 Hz ← 800 Hz 20 Hz … 20000 Hz │
@@ -1597,20 +1855,55 @@ Each field shows its name, current value with unit, the committed value while a
1597
1855
 
1598
1856
  Every change is staged on the audition loop (see **Previewing changes**), filed under its field so repeated nudges replace one staged edit; the piano roll and the loop, when it plays, follow at once. `Enter` keeps everything staged as one revision and one undo step; `Esc` reverts. A setting the loop cannot stage (tempo, loop length) applies directly.
1599
1857
 
1600
- | Key | In the drawer |
1601
- | --------------------------- | ----------------------------------------------------- |
1602
- | `←` `→` / `-` `+` / `h` `l` | step by the param's step |
1603
- | `Shift`-`←` `→` / `{` `}` | coarse step (five steps) |
1604
- | `[` `]` / `Alt`-`←` `→` | fine step (a tenth of a step) |
1605
- | `PgUp` `PgDn` | big step (twenty) |
1606
- | `Home` `End` | minimum / maximum |
1607
- | `1`-`9` `.` | type an exact value; `Enter` sets it, `Esc` cancels |
1608
- | `0` / `d` | back to the default |
1609
- | `↑` `↓` / `Tab` `Shift-Tab` | previous / next param of this device |
1610
- | `Enter` | keep every staged change (one undo step); none: close |
1611
- | `Esc` | revert staged changes and close |
1612
- | `Space` `a` `c` | audition loop · A/B · solo ↔ in context |
1613
- | `?` | these keys |
1858
+ | Key | In the drawer |
1859
+ | --------------------------- | ------------------------------------------------------------------------------------------------------------- |
1860
+ | `←` `→` / `-` `+` / `h` `l` | step by the param's step |
1861
+ | `Shift`-`←` `→` / `{` `}` | coarse step (five steps) |
1862
+ | `[` `]` / `Alt`-`←` `→` | fine step (a tenth of a step; skips detents) |
1863
+ | `PgUp` `PgDn` | big step (twenty) |
1864
+ | `Home` `End` | minimum / maximum |
1865
+ | `0`-`9` `.` | type an exact value; `Enter` sets it, `Esc` cancels |
1866
+ | `x` / `d` / `Delete` | back to the default |
1867
+ | `↑` `↓` / `Tab` `Shift-Tab` | previous / next param of this device (on a knob page `↑` `↓` pick a knob and `Tab` pages knobs ↔ every param) |
1868
+ | `Enter` | keep every staged change (one undo step); none: close |
1869
+ | `Esc` | revert staged changes and close |
1870
+ | `Space` `a` `c` | audition loop · A/B · solo ↔ in context |
1871
+ | `?` | these keys |
1872
+
1873
+ ### Four knobs
1874
+
1875
+ Where a level has them, the drawer opens on four knobs first: the OP-1's four color encoders, the same four on every screen.
1876
+
1877
+ | Knob | Color | Means | Examples |
1878
+ | ---- | ------ | ------ | -------------------------------------------- |
1879
+ | `●` | blue | moves | preset, position, pan, filter type, tempo |
1880
+ | `▲` | green | sizes | attack, decay, length, reverb send, feedback |
1881
+ | `■` | white | shapes | filter, tone, brightness, cutoff |
1882
+ | `◆` | orange | level | track volume, effect mix, loudness target |
1883
+
1884
+ ```text
1885
+ ╭─ menu › Sound ───────────────────────────────────────────────────────────────╮
1886
+ │ ●›preset pad │ lead │ pluck │ bass │ sub │ acid │ keys │ bell │ › │
1887
+ │ ▲ attack 0.003 s [−] ───────────────────────────────── [+] │
1888
+ │ ■ synth filter off [−] ───────────────────────────────── [+] │
1889
+ │ ◆ volume 1 · 0.0 dB [−] ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━● [+] │
1890
+ ╰─────────── ↑↓ knob · ←→ turn · tab all · enter keep · ⇧ coarse · esc revert ─╯
1891
+ ```
1892
+
1893
+ The shape glyph always goes with the color, so `NO_COLOR` and the mono theme keep every cue; without Unicode the glyphs are `o ^ # *`. `↑` `↓` pick a knob (an empty slot, drawn `·`, is skipped), `←` `→` turn it, `Shift` turns it coarse and `x` resets it. `Tab` pages to every param of the level (today's drawer) and back. Every turn runs the row's own command (`synth cutoff 1760`, `fx reverb mix 0.3`, `volume 0.8`), so the receipt teaches the words and show-me types the same lines.
1894
+
1895
+ The drawer has knobs on Sound (by engine: synth, wavetable, piano, electric, organ, string, wind, modal, sing, granular, sampler, kit), Mix, Mix › master, Project (tempo, loop length, beats per bar) and each effect. Which row each knob turns is one table, `src/tui/knob-map.ts` (`KNOB_MAPS`): four Ctrl-K paths per page, checked against every instrument family by a test, so a patch's macros can later map onto the same four knobs by adding rows. An effect without a row takes its first three number params and its mix.
1896
+
1897
+ | Command | Opens |
1898
+ | ------------------------------------------------ | ----------------------------------------------------------------------------------------- |
1899
+ | `synth` | the focused track's sound knobs (and the synth summary receipt) |
1900
+ | `knobs [sound\|mix\|master\|tempo\|fx <effect>]` | that page's four knobs (bare: sound) |
1901
+ | `volume` / `pan` / `fx reverb mix` | the level's knobs, on that knob (a param that is not a knob opens every param) |
1902
+ | `mix` | the mixer page: every track's level as an orange fader; `Tab` flips to this track's knobs |
1903
+
1904
+ The mixer page is also `Ctrl-K › Mix › mixer`. Each row runs `volume <track> <0..1>` (`volume drums 0.5`, and `pan <track> <-1..1>`), which sets that track without moving the focus.
1905
+
1906
+ Detents catch the values people reach for: volume 1 (0 dB), pan 0 (center), whole-number tempo, mix 0, 0.5 and 1, and a filter's octave cutoffs. A drag or step that lands within 2% of the range snaps onto the detent, a key step that jumps over one stops on it, and a fine step skips them. The landing flashes the detent's name for one frame unless `/motion off`. While anything is staged the title reads `A/B: 1 change staged · enter keep · esc revert`, and the badge's key words are click targets.
1614
1907
 
1615
1908
  On a choice, `←` `→` move between options and `1`-`9` pick one by number. Sizes: two rows per field (value line, then bar) while the drawer takes at most half the piano roll; one row per field when shorter, as a window that follows the focused field; and at the 8-row terminal minimum a single borderless row with the focused field. The piano roll always keeps at least 40% of its rows above the drawer (at least three).
1616
1909
 
@@ -1618,27 +1911,48 @@ On a choice, `←` `→` move between options and `1`-`9` pick one by number. Si
1618
1911
 
1619
1912
  dawg turns on SGR mouse reporting (modes 1000, 1002 and 1006) and turns it off again on exit, on `SIGTERM`/`SIGHUP`, on a crash, and around external editors. `--no-mouse` or `DAWG_MOUSE=0` (and `TERM=dumb`) leave it off, so the terminal's own selection and scrollback work; a terminal without mouse support ignores the modes and every key still works. Legacy X10 reports are swallowed rather than typed into the prompt.
1620
1913
 
1621
- | Where | Click / wheel |
1622
- | ------------------- | -------------------------------------------------------------------- |
1623
- | fader `[−]` `[+]` | step (shift-click: coarse) |
1624
- | fader bar | set the value at that point; drag to slide it (past the ends clamps) |
1625
- | fader option | choose it |
1626
- | fader name | focus that field |
1627
- | `[keep]` `[revert]` | the same as `Enter` / `Esc` |
1628
- | wheel on a fader | step it (up raises; shift: coarse) |
1629
- | list / menu row | select it; a click on the selected row opens it (`Enter`) |
1630
- | wheel on a list | move through it |
1631
- | header `▶/⏸ BPM` | play / pause |
1632
- | header track name | the track list (`/tracks`); click a track to focus it |
1633
- | header model | the model picker |
1914
+ | Where | Click / wheel |
1915
+ | ------------------------------- | -------------------------------------------------------------------- |
1916
+ | fader `[−]` `[+]` | step (shift-click: coarse) |
1917
+ | fader bar | set the value at that point; drag to slide it (past the ends clamps) |
1918
+ | fader option | choose it |
1919
+ | fader name | focus that field |
1920
+ | badge `enter keep` `esc revert` | the same as `Enter` / `Esc` |
1921
+ | wheel on a fader | step it (up raises; shift: coarse) |
1922
+ | list / menu row | select it; a click on the selected row opens it (`Enter`) |
1923
+ | wheel on a list | move through it |
1924
+ | header `▶/⏸ BPM` | play / pause |
1925
+ | header track name | the track list (`/tracks`); click a track to focus it |
1926
+ | header model | the model picker |
1634
1927
 
1635
1928
  Hit-testing uses the same paint pass that draws the frame: each painter records its click regions into the frame's `HitMap` (`tui/hits.ts`), so targets never drift from what is on screen. The piano roll does not place notes on click (a note needs pitch, length and velocity that a click does not carry); clicks there are ignored.
1636
1929
 
1930
+ ## Terminal sizes
1931
+
1932
+ 80x24 is the design target; the UI also works from **60x16** up to as large as the terminal goes, and redraws cleanly on every resize.
1933
+
1934
+ **Minimum.** Below 60 columns the play-mode key strip and the header's bar position drop out and the prompt's spend line clips; below 16 rows the prompt loses its footer and the drawer its last knob. So under 60x16 dawg shows `terminal too small · W×H · need ≥ 60×16`, centered, updating as you resize (ASCII without unicode). Playback, the daemon and the agent keep running; `ctrl-c` and `q` quit, `space` plays or pauses, and every other key is ignored, so nothing changes until the real UI returns, on the first frame the size allows. A 1x1 or 0-column terminal draws nothing and never throws.
1935
+
1936
+ **Resize.** `SIGWINCH` bursts from a drag coalesce into at most one frame per 33 ms plus a trailing one (72 signals measured as about 16 repaints). Each size change clears the screen and resets the frame diff, so no stale cells survive. Focus, the selected knob, the menu path, drawer state, the loop and the playhead view are model state and are untouched; scroll positions re-clamp to the new page, and a panel scrolled to its end (the transcript, a guide) stays pinned to the end. Agent streaming and show-me typing carry on through it.
1937
+
1938
+ **Large terminals.** Data views use the space, text holds a measure, and no glyph is stretched past legibility:
1939
+
1940
+ | View | On a large terminal |
1941
+ | ----------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
1942
+ | highway | lanes stop at 10 columns; a wider highway shows more pitches (one to three octaves) and centers the rest; a taller one looks further ahead (at most 4 rows per beat) |
1943
+ | TAPE | more bars across and every track down, until the song runs out |
1944
+ | drawer (knobs, mixer) | rows stop at 96 columns; a wide drawer lists every param of the page beside the four knobs (what Tab shows); a taller drawer fits more mixer tracks |
1945
+ | knob strip | each slot at most 40 columns, left-aligned |
1946
+ | help, guides, menus, transcript, `?` keys | a panel at most 100 columns of text, left-aligned; with 36 or more columns free, the song keeps drawing beside it |
1947
+ | prompt, header, footer, cards | full width (one line each; long text truncates with `…`) |
1948
+
1949
+ A steady frame (compose plus encode) stays under 16 ms at 500x150 (measured 2 to 3 ms; text measurement skips grapheme segmentation for ASCII and caches the rest). `bun run sizes` walks every screen through the full size matrix and flags a frame over budget, a wrapped or scrolled frame, a lost header, footer, screen or focus, and a text panel past its measure; `bun run check` runs a sample of it.
1950
+
1637
1951
  ## Previewing changes
1638
1952
 
1639
1953
  Hear a sound change before you keep it. In the edit menu (every section: Sound, Effects, Rhythm, Chords, Mix), `Space` starts a short loop of the focused track; `Space` again stops it. The song pauses while the loop plays, so only one thing sounds at a time.
1640
1954
 
1641
- The loop is the track's own notes over its loop region when that is four bars or shorter, otherwise the two bars under the playhead (or the track's first two bars with notes, if those are empty). A track with no notes plays a short phrase by role: a chord for pads and keys, a riff for bass and leads, a groove for kits and drums, and one held note for wavetables so position and envelope changes are audible. It plays solo by default; `c` switches to the whole mix with the track in it.
1955
+ The loop is the track's own notes over its loop when that is four bars or shorter, otherwise the two bars under the playhead (or the track's first two bars with notes, if those are empty). A track with no notes plays a short phrase by role: a chord for pads and keys, a riff for bass and leads, a groove for kits and drums, and one held note for wavetables so position and envelope changes are audible. It plays solo by default; `c` switches to the whole mix with the track in it.
1642
1956
 
1643
1957
  While the loop plays, each change you make is **staged**, not committed. The loop re-renders only the changed track through the normal renderer and stem cache, in the render worker, and swaps it in within about 100 ms (a reverb mix change re-mixes the cached tail and is heard in about 65 ms). Every swap, here and in the song player, crossfades old to new over 20 ms at the same beat, so changes never click; renders and exports never pass through the crossfade. Held keys are coalesced so only the latest value renders. The menu title shows `●` and `B staged N`, and each changed row shows the staged value beside the committed one (`mix 0.5 ← 0.3`).
1644
1958
 
@@ -1656,7 +1970,7 @@ Kept changes are one `ScoreOperation` (`preview.commit`, listing the commands),
1656
1970
 
1657
1971
  **The rhythm editor and the chord settings stage too.** `/euclid` and the chord settings (the menu's Chords section, `/menu chords`) use the same loop and keys. In `/euclid`, `Space` loops the drum track; pulses, steps, rotate, typed values, a new row, `off` and `freeze` are staged, the title shows `●` and `B staged N`, and a changed lane shows `E(5,16) ← E(4,16)`. Chord settings are window settings rather than score edits, so while the Chords section is open the loop plays the focused track's chord phrase (two bars of the song key's progression, voiced and performed by the current settings: inversion, spread, bass, sevenths, block/strum/arp, pattern); a staged setting changes the B phrase, and `a` flips back to the committed settings. `Enter` keeps every staged change as one undo step: rhythm edits as one `preview.commit` revision, and chord settings applied at once (a key change, the only one stored in the score, as one revision). `Esc` reverts with nothing written. With the loop off, both screens commit each change at once, as before.
1658
1972
 
1659
- **Lists audition on hover.** With the loop on, moving the cursor through a list plays the highlighted item on the loop: the wavetable list (built-in, pack and project tables), instruments, drum kits and patterns, and every choice list (filter type, warp mode, presets). It is the browser-preview model of Ableton and Bitwig, applied to the loop you are already hearing. Each move replaces the previous hover, so the staged count stays at one, and fast moves skip straight to the latest item. A pack item that has to be fetched shows `fetching…` in the title; the cursor keeps moving and the item plays once it arrives. `Enter` chooses the item (it stays staged until you keep), `Esc` or `←` leaves the list and drops the hover. The `/kit` and `/pattern` pickers work the same way: `Space` starts the loop, moving hears each kit or groove, `Enter` keeps it, `Esc` cancels.
1973
+ **Lists audition on hover.** With the loop on, moving the cursor through a list plays the highlighted item on the loop: the wavetable list (built-in, pack and project tables), instruments, kits and grooves, and every choice list (filter type, warp mode, presets). It is the browser-preview model of Ableton and Bitwig, applied to the loop you are already hearing. Each move replaces the previous hover, so the staged count stays at one, and fast moves skip straight to the latest item. A pack item that has to be fetched shows `fetching…` in the title; the cursor keeps moving and the item plays once it arrives. `Enter` chooses the item (it stays staged until you keep), `Esc` or `←` leaves the list and drops the hover. The `/kit` and `/pattern` pickers work the same way: `Space` starts the loop, moving hears each kit or groove, `Enter` keeps it, `Esc` cancels.
1660
1974
 
1661
1975
  | Key in a list | While auditioning |
1662
1976
  | ------------- | --------------------------------------------------- |
@@ -1670,7 +1984,7 @@ Kept changes are one `ScoreOperation` (`preview.commit`, listing the commands),
1670
1984
  **What you see.** While the loop plays, the menu title adds a level meter of the looping track or mix: RMS as an eight-cell bar over -48..0 dBFS, the peak in dB, and `!` in the last cell when the loop clips (`♪ solo · B staged 2 · 64 ms · █████··· -9 dB`; the `ms` is the last key-to-swap time). A focused cutoff row draws its low- or high-pass curve on a log axis, an attack/decay/sustain/release row draws the envelope with the other stages, and the wavetable position row marks its place in the table:
1671
1985
 
1672
1986
  ```text
1673
- │ cutoff (lpf/hpf) or centre (bpf) frequency ▇▇▇▇▇▇▇▇▇▇▇▅▂▁▁▁ › fx filt… │
1987
+ │ cutoff (lpf/hpf) or center (bpf) frequency ▇▇▇▇▇▇▇▇▇▇▇▅▂▁▁▁ › fx filt… │
1674
1988
  ```
1675
1989
 
1676
1990
  **The agent can listen too.** The `preview_sound {trackId?, changes?, bars?, context?, play?}` tool renders the same loop score for a track, or for candidate sound tool calls (`changes: [{tool: "set_wavetable", args: {...}}]`, any of `set_fx`, `set_synth`, `set_wavetable`, `set_instrument`, `set_sample`, `set_effects`, `set_mix`, `set_automation`, `set_drum_kit`, `use_sound`) applied to a copy of the score. Nothing is committed. It returns RMS and peak dBFS, the spectral centroid and a one-line description for the current and the candidate sound, plus a comparison (`3.0 dB louder, brighter (×2.00 centroid)`). When the window is quiet (no song or audition loop playing) it plays the candidate once, so the agent can say how it sounds before committing with the normal tools. `/try agent off` keeps agent previews silent (numbers only), `/try agent on` turns them back on; `DAWG_AGENT_PREVIEW=off` starts with them off.