@hraness/dawg 0.6.0 → 0.7.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 (275) hide show
  1. package/CHANGELOG.md +126 -0
  2. package/DAWG.md +655 -141
  3. package/README.md +27 -25
  4. package/core/autotune.ts +1119 -0
  5. package/core/chords.ts +759 -23
  6. package/core/clips.ts +499 -0
  7. package/core/diff.ts +182 -99
  8. package/core/expression.ts +158 -6
  9. package/core/fx.ts +354 -32
  10. package/core/granular.ts +91 -0
  11. package/core/instruments.ts +180 -0
  12. package/core/keys.ts +573 -4
  13. package/core/loop.ts +5 -0
  14. package/core/lyrics.ts +297 -0
  15. package/core/master.ts +3 -3
  16. package/core/resonators.ts +306 -4
  17. package/core/routing.ts +165 -0
  18. package/core/score.ts +1181 -16
  19. package/core/sdk/eval-child.ts +7 -2
  20. package/core/sdk/eval.ts +35 -6
  21. package/core/sdk/print.ts +354 -6
  22. package/core/sdk/sync-lyrics.ts +49 -0
  23. package/core/sdk/v1.ts +3160 -69
  24. package/core/sections.ts +298 -24
  25. package/core/sing.ts +815 -0
  26. package/core/strings.ts +237 -2
  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/core/winds.ts +652 -0
  50. package/guides/agent.md +29 -0
  51. package/guides/arrange.md +30 -0
  52. package/guides/audition.md +20 -14
  53. package/guides/automation.md +12 -6
  54. package/guides/chords.md +16 -14
  55. package/guides/effects.md +17 -15
  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 +12 -6
  62. package/guides/music.md +23 -8
  63. package/guides/notes.md +15 -9
  64. package/guides/performance.md +15 -10
  65. package/guides/play.md +21 -13
  66. package/guides/project.md +25 -8
  67. package/guides/providers.md +20 -14
  68. package/guides/resample.md +32 -0
  69. package/guides/rhythm.md +17 -13
  70. package/guides/sessions.md +17 -7
  71. package/guides/show-me.md +31 -0
  72. package/guides/sound.md +25 -9
  73. package/guides/sounds.md +15 -12
  74. package/guides/styles.md +31 -0
  75. package/guides/tempo.md +15 -10
  76. package/guides/tracks.md +15 -10
  77. package/guides/tuning.md +32 -0
  78. package/guides/voice.md +31 -0
  79. package/guides/web-search.md +18 -8
  80. package/native/prebuilt/darwin-arm64/libdawg_sink.dylib +0 -0
  81. package/native/prebuilt/darwin-x64/libdawg_sink.dylib +0 -0
  82. package/native/prebuilt/linux-arm64/libdawg_sink.so +0 -0
  83. package/native/prebuilt/linux-x64/libdawg_sink.so +0 -0
  84. package/native/prebuilt/manifest.json +21 -0
  85. package/package.json +5 -2
  86. package/src/agent/agent.ts +131 -13
  87. package/src/agent/calibration-tools.ts +53 -0
  88. package/src/agent/chord-tools.ts +153 -1
  89. package/src/agent/clip-tools.ts +453 -0
  90. package/src/agent/command-agent.ts +369 -0
  91. package/src/agent/drum-tools.ts +2 -2
  92. package/src/agent/expression-tools.ts +91 -0
  93. package/src/agent/gateway.ts +246 -60
  94. package/src/agent/models.ts +53 -12
  95. package/src/agent/ops.ts +15 -1
  96. package/src/agent/pack-tools.ts +1 -1
  97. package/src/agent/planner.ts +13 -0
  98. package/src/agent/portable-schema.ts +80 -0
  99. package/src/agent/preview-tool.ts +5 -1
  100. package/src/agent/provider.ts +22 -8
  101. package/src/agent/resample-tool.ts +131 -0
  102. package/src/agent/rhythm-tools.ts +1 -1
  103. package/src/agent/section-tools.ts +1 -1
  104. package/src/agent/show-me.ts +497 -0
  105. package/src/agent/steer.ts +15 -0
  106. package/src/agent/style-tools.ts +217 -0
  107. package/src/agent/tool-error.ts +12 -0
  108. package/src/agent/tools.ts +307 -37
  109. package/src/agent/usage.ts +2 -2
  110. package/src/agent/voice-tools.ts +925 -0
  111. package/src/agent/xcb-agent.ts +16 -6
  112. package/src/argv.ts +38 -0
  113. package/src/audio/analysis.ts +253 -0
  114. package/src/audio/arrange.ts +58 -3
  115. package/src/audio/autotune-engine.ts +101 -0
  116. package/src/audio/autotune.ts +640 -0
  117. package/src/audio/clips.ts +240 -0
  118. package/src/audio/doctor.ts +86 -0
  119. package/src/audio/dsp/bandbank.ts +138 -0
  120. package/src/audio/dsp/envelope.ts +171 -0
  121. package/src/audio/dsp/follow.ts +120 -0
  122. package/src/audio/dsp/formant.ts +427 -0
  123. package/src/audio/dsp/glottal.ts +243 -0
  124. package/src/audio/dsp/interp.ts +7 -2
  125. package/src/audio/dsp/lpc.ts +50 -0
  126. package/src/audio/dsp/periodicity.ts +59 -0
  127. package/src/audio/dsp/pitch.ts +995 -0
  128. package/src/audio/dsp/psola.ts +199 -0
  129. package/src/audio/dsp/shift.ts +256 -0
  130. package/src/audio/effects/chain.ts +21 -4
  131. package/src/audio/effects/common.ts +57 -0
  132. package/src/audio/effects/convolution.ts +113 -5
  133. package/src/audio/effects/filter.ts +48 -69
  134. package/src/audio/effects/formant.ts +263 -0
  135. package/src/audio/effects/gaze.ts +354 -0
  136. package/src/audio/engine.ts +206 -33
  137. package/src/audio/fit.ts +80 -3
  138. package/src/audio/granular.ts +352 -21
  139. package/src/audio/instrument-check.ts +67 -47
  140. package/src/audio/instruments.ts +36 -3
  141. package/src/audio/keys/calibration.ts +56 -0
  142. package/src/audio/keys/electric.ts +427 -0
  143. package/src/audio/keys/engine.ts +156 -20
  144. package/src/audio/keys/organ.ts +1335 -0
  145. package/src/audio/keys/piano.ts +72 -8
  146. package/src/audio/keys/sympathetic.ts +127 -0
  147. package/src/audio/kits.ts +135 -6
  148. package/src/audio/live-worker.ts +3 -1
  149. package/src/audio/live.ts +247 -26
  150. package/src/audio/native.ts +615 -0
  151. package/src/audio/preview.ts +45 -3
  152. package/src/audio/render-worker.ts +2 -0
  153. package/src/audio/renderer.ts +2 -0
  154. package/src/audio/resample.ts +286 -0
  155. package/src/audio/resonators.ts +10 -2
  156. package/src/audio/sampler.ts +252 -24
  157. package/src/audio/samples.ts +46 -3
  158. package/src/audio/sing/analysis.ts +193 -0
  159. package/src/audio/sing/engine.ts +949 -0
  160. package/src/audio/strings/body.ts +29 -16
  161. package/src/audio/strings/bow.ts +699 -0
  162. package/src/audio/strings/engine.ts +274 -22
  163. package/src/audio/strings/measure.test-helpers.ts +2 -0
  164. package/src/audio/synth/oscillators.ts +31 -21
  165. package/src/audio/synth/voice.ts +34 -1
  166. package/src/audio/vocoder/bank.ts +314 -0
  167. package/src/audio/vocoder/carrier.ts +165 -0
  168. package/src/audio/vocoder/control.ts +68 -0
  169. package/src/audio/vocoder/detect.ts +50 -0
  170. package/src/audio/vocoder/index.ts +304 -0
  171. package/src/audio/vocoder/talkbox.ts +143 -0
  172. package/src/audio/wav.ts +608 -85
  173. package/src/audio/winds/engine.ts +306 -0
  174. package/src/audio/winds/filters.ts +153 -0
  175. package/src/audio/winds/pitch.ts +104 -0
  176. package/src/audio/winds/trim.ts +104 -0
  177. package/src/audio/winds/trims.ts +917 -0
  178. package/src/audio/winds/trims1.ts +297 -0
  179. package/src/audio/winds/voice.ts +492 -0
  180. package/src/auth/cli.ts +38 -36
  181. package/src/auth/credentials.ts +30 -1
  182. package/src/auth/login.ts +15 -9
  183. package/src/auth/tui.ts +19 -10
  184. package/src/commands/arrange.ts +44 -29
  185. package/src/commands/autotune.ts +421 -0
  186. package/src/commands/calibration.ts +74 -0
  187. package/src/commands/clips.ts +887 -0
  188. package/src/commands/drums.ts +3 -2
  189. package/src/commands/edit.ts +11 -4
  190. package/src/commands/expression.ts +104 -23
  191. package/src/commands/formant.ts +221 -0
  192. package/src/commands/fx.ts +105 -33
  193. package/src/commands/grammar.ts +558 -0
  194. package/src/commands/granular.ts +7 -2
  195. package/src/commands/help.ts +652 -339
  196. package/src/commands/history.ts +18 -0
  197. package/src/commands/keys.ts +293 -17
  198. package/src/commands/modal.ts +67 -4
  199. package/src/commands/music.ts +1 -1
  200. package/src/commands/nearest.ts +53 -0
  201. package/src/commands/pack.ts +9 -2
  202. package/src/commands/param-range.ts +56 -0
  203. package/src/commands/parses.ts +130 -0
  204. package/src/commands/progression.ts +170 -0
  205. package/src/commands/resample.ts +281 -0
  206. package/src/commands/rhythm.ts +3 -0
  207. package/src/commands/rig.ts +8 -25
  208. package/src/commands/sample.ts +49 -2
  209. package/src/commands/shift.ts +119 -0
  210. package/src/commands/sing.ts +478 -0
  211. package/src/commands/string.ts +28 -2
  212. package/src/commands/strum.ts +485 -0
  213. package/src/commands/style.ts +415 -0
  214. package/src/commands/time.ts +6 -3
  215. package/src/commands/tuning.ts +3 -3
  216. package/src/commands/vocal-pitch.ts +616 -0
  217. package/src/commands/vocal.ts +147 -0
  218. package/src/commands/vocoder.ts +627 -0
  219. package/src/commands/wind.ts +244 -0
  220. package/src/fs/durable.ts +50 -0
  221. package/src/lang/glossary.ts +493 -0
  222. package/src/launch-args.ts +163 -0
  223. package/src/main.ts +1521 -255
  224. package/src/media/cli.ts +20 -3
  225. package/src/media/import.ts +3 -1
  226. package/src/project/check.ts +26 -3
  227. package/src/project/clip-pins.ts +72 -0
  228. package/src/project/init.ts +23 -8
  229. package/src/project/sync.ts +418 -86
  230. package/src/render.ts +36 -1
  231. package/src/session/daemon.ts +3 -0
  232. package/src/session/meta.ts +14 -0
  233. package/src/session/origin.ts +154 -0
  234. package/src/session/port.ts +22 -4
  235. package/src/session/presence.ts +50 -7
  236. package/src/session/protocol.ts +5 -1
  237. package/src/session/rebase.ts +18 -5
  238. package/src/session/receipt.ts +258 -0
  239. package/src/session/store.ts +65 -43
  240. package/src/tui/arrange-menu.ts +65 -31
  241. package/src/tui/audition.ts +1 -1
  242. package/src/tui/euclid.ts +18 -13
  243. package/src/tui/fader.ts +228 -41
  244. package/src/tui/granular-menu.ts +2 -4
  245. package/src/tui/menu-clips.ts +297 -0
  246. package/src/tui/menu-time.ts +20 -13
  247. package/src/tui/menu-voice.ts +405 -0
  248. package/src/tui/menu.ts +1377 -193
  249. package/src/tui/modal-menu.ts +38 -11
  250. package/src/tui/performance-menu.ts +46 -1
  251. package/src/tui/play-chords.ts +108 -5
  252. package/src/tui/play-mode.ts +16 -1
  253. package/src/tui/play-session.ts +81 -16
  254. package/src/tui/sing-menu.ts +278 -0
  255. package/src/tui/style-menu.ts +104 -0
  256. package/src/tui/vocoder-menu.ts +244 -0
  257. package/src/tui/wind-menu.ts +144 -0
  258. package/src/version.ts +8 -0
  259. package/src/web/fetch.ts +115 -29
  260. package/tui/activity.ts +180 -9
  261. package/tui/app.ts +274 -40
  262. package/tui/clip-row.ts +132 -0
  263. package/tui/delight.ts +144 -0
  264. package/tui/drawer.ts +70 -22
  265. package/tui/frame-gate.ts +76 -0
  266. package/tui/grammar.ts +112 -63
  267. package/tui/guide.ts +42 -4
  268. package/tui/highway.ts +269 -25
  269. package/tui/hints.ts +192 -0
  270. package/tui/input.ts +60 -9
  271. package/tui/keys.ts +1 -1
  272. package/tui/play-strip.ts +68 -14
  273. package/tui/prompt.ts +1 -1
  274. package/tui/screen.ts +144 -14
  275. package/tui/theme.ts +27 -0
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` marks or moves a `loop` section when none spans those bars), `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. 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,7 @@ 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, and the audio devices. The renderer is deterministic and independently testable; a native or sample-backed instrument backend can replace it behind the same player port.
192
207
  Set `DAWG_AUDIO=0` for headless sessions.
193
208
 
194
209
  Use `DAWG_DEMO=1 bun run src/main.ts` for a deterministic non-interactive frame stream while developing the renderer.
@@ -212,16 +227,16 @@ tracks/<slug>/samples/ audio a sampler references by relative path
212
227
 
213
228
  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
229
 
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.
230
+ 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
231
 
217
232
  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
233
 
219
234
  Two-way sync (`src/project/sync.ts`) runs in every window of a project:
220
235
 
221
236
  - 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.
237
+ - 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.
238
+ - 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.
239
+ - 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
240
  - 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
241
 
227
242
  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 +250,7 @@ Score format. The score stays `track.loop/v1` with `version: 1`: every addition
235
250
  Every track has one fixed effects chain (`FX_CHAIN` in `core/fx.ts`, DSP in `src/audio/effects/`):
236
251
 
237
252
  ```text
238
- filter → djf → autofilter → vowel → crush → distort → stomp → head → cab → tremolo → compressor → pan → phaser → chorus → leslie → postgain → delay → reverb → [mix: orbit → duck]
253
+ filter → djf → autofilter → formant → vowel → crush → distort → stomp → head → cab → tremolo → compressor → pan → phaser → chorus → leslie → postgain → delay → reverb → [mix: orbit → duck]
239
254
  ```
240
255
 
241
256
  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 +279,7 @@ automate distort-drive points 0:1 8:6 every numeric fx param has a lane
264
279
 
265
280
  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
281
 
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`.
282
+ 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
283
 
269
284
  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
285
 
@@ -286,8 +301,12 @@ Parameters (**bold** effect = shown in the simple menu; Lane = automation lane):
286
301
  | **autofilter** | shape | sine / tri / square / saw / ramp / random | sine | | |
287
302
  | autofilter | phase | 0..1 | 0 | | |
288
303
  | autofilter | follow | -6..6 oct | 0 | `lpenv (per note, see synth)` | `autofilter-follow` |
304
+ | **formant** | shift | -12..12 st | 0 | | `formant-shift` |
305
+ | **formant** | mix | 0..1 | 1 | | `formant-mix` |
289
306
  | **vowel** | vowel | a / e / i / o / u / ae / aa / oe / ue / y / uh / un / en / an / on | a | `vowel` | |
290
307
  | **vowel** | mix | 0..1 | 1 | | `vowel-mix` |
308
+ | vowel | to (optional) | a / e / i / o / u / ae / aa / oe / ue / y / uh / un / en / an / on | (none) | | |
309
+ | vowel | morph (optional) | 0..1 | 0 | | `vowel-morph` |
291
310
  | **crush** | bits | 1..16 | 8 | `crush` | `crush-bits` |
292
311
  | **crush** | coarse | 1..64 | 1 | `coarse` | |
293
312
  | **crush** | mix | 0..1 | 1 | | `crush-mix` |
@@ -332,7 +351,7 @@ Parameters (**bold** effect = shown in the simple menu; Lane = automation lane):
332
351
  | reverb | lowpass (optional) | 200..20000 Hz | 8000 | `roomlp`, `rlp` | |
333
352
  | reverb | dim (optional) | 200..20000 Hz | 3000 | `roomdim`, `rdim` | |
334
353
  | reverb | predelay (optional) | 0..0.5 s | 0.02 | | |
335
- | reverb | ir (optional) | `builtin:room\|hall\|plate`, pack sound or project WAV | off | `iresponse`, `ir` | |
354
+ | reverb | ir (optional) | `builtin:room\|hall\|plate\|reverse\|gate\|spring`, pack sound or project WAV | off | `iresponse`, `ir` | |
336
355
  | orbit | orbit | 1..16 (integer) | 2 | `orbit`, `o` | |
337
356
  | orbit | shared (optional) | on/off | off | | |
338
357
  | duck | orbit | 1..16 (integer) | 1 | `duckorbit`, `duck` | |
@@ -352,16 +371,16 @@ rig show the focused track's rig
352
371
  rig reset remove all three stages
353
372
  stomp fuzz | stomp gain 7 head lead | head treble 7 gate -55 | cab 4x12 | cab mic 0.6
354
373
  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
374
+ rig jangle the jangle rig on the focused track (alias: track jangle makes a new guitar track)
356
375
  ```
357
376
 
358
377
  - **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
378
  - **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.
379
+ - **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
380
 
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.
381
+ 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
382
 
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).
383
+ `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
384
 
366
385
  | Effect | Param | Range | Default | Lane |
367
386
  | --------- | --------------- | --------------------------------------------------- | ------- | ------------ |
@@ -385,6 +404,43 @@ Rig presets (`RIG_PRESETS` in `core/fx.ts`): `clean crunch punk ragged lead meta
385
404
  | **cab** | mic | 0..1 | 0.3 | |
386
405
  | **cab** | mix | 0..1 | 1 | |
387
406
 
407
+ ### Shoegaze (wobble, bloom, swell, double) (0.6.1)
408
+
409
+ Four effects that sit after `cab` and before `tremolo` (`double` runs after `pan`, because it makes the stereo image), in `src/audio/effects/gaze.ts`. They run once per track, never per voice, and use seeded randomness only, so every render is identical.
410
+
411
+ ```text
412
+ fx wobble held tremolo arm: seeded pitch wow and flutter (depth 20 c)
413
+ fx wobble depth 35 rate 0.4 drift 0.5
414
+ fx bloom feedback: the top held note grows a singing harmonic
415
+ fx swell time 0.5 volume swell: each strum fades in, no pick attack
416
+ fx double a second, seeded take a few ms late, spread left and right
417
+ rig shoegaze fuzz + chime head + wobble + bloom + double + a big reverb
418
+ fx reverb ir builtin:reverse reverse-gate swell after the attack, not a pre-verb (also builtin:gate and builtin:spring)
419
+ track shoegaze alias: a new electric guitar track named shoegaze, with the shoegaze rig
420
+ ```
421
+
422
+ - **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.
423
+ - **bloom** (`amount`, `harm` 1..4, `delay` s, `time` s) finds the highest held note at each moment (EffectContext.notes) and grows a phase-continuous partial at its `harm`-th harmonic after the note has been held `delay` seconds, the way an amp in feedback picks one note. Each note's partial is seeded by the note id, so it is stable when other notes change.
424
+ - **swell** (`time` s, `mix`) restarts a raised-cosine fade at each note onset: a volume-pedal swell without the pick.
425
+ - **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.
426
+
427
+ 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.
428
+
429
+ | Effect | Param | Range | Default | Lane |
430
+ | ---------- | ------ | ------------ | ------- | -------------- |
431
+ | **wobble** | depth | 0..100 cents | 20 | `wobble-depth` |
432
+ | **wobble** | rate | 0.05..8 Hz | 0.5 | |
433
+ | **wobble** | drift | 0..1 | 0.3 | |
434
+ | **bloom** | amount | 0..1 | 0.5 | |
435
+ | **bloom** | harm | 1..4 | 2 | |
436
+ | **bloom** | delay | 0..4 s | 0.6 | |
437
+ | bloom | time | 0.05..4 s | 1 | |
438
+ | **swell** | time | 0.01..4 s | 0.4 | |
439
+ | **swell** | mix | 0..1 | 1 | `swell-mix` |
440
+ | **double** | time | 5..60 ms | 22 | |
441
+ | **double** | drift | 0..10 ms | 3 | |
442
+ | **double** | width | 0..1 | 0.6 | |
443
+
388
444
  ## Master and loudness
389
445
 
390
446
  The song master is an optional chain after every track, orbit bus and duck are summed: **EQ** (low shelf, two bells, high shelf) → **glue** (stereo-linked bus compressor with soft knee, parallel mix and a sidechain high-pass) → **tape** (saturation with bias and a tone roll-off) → **width** (mid/side, mono below a cutoff) → **limiter** (true-peak brickwall with lookahead). A song with no master renders byte-identically to 0.4; an absent unit is skipped and a 0 dB EQ band is skipped.
@@ -400,7 +456,7 @@ The song master is an optional chain after every track, orbit bus and duck are s
400
456
 
401
457
  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).
402
458
 
403
- 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`).
459
+ 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`).
404
460
 
405
461
  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.
406
462
 
@@ -411,10 +467,10 @@ Parameters (**bold** unit = shown in the simple menu; the rest are under `advanc
411
467
  | **eq** | low | -12..12 dB | 0 | low shelf gain |
412
468
  | eq | lowfreq | 20..1000 Hz | 100 | low shelf corner |
413
469
  | **eq** | bell1 | -12..12 dB | 0 | first bell gain |
414
- | eq | bell1freq | 40..16000 Hz | 400 | first bell centre |
470
+ | eq | bell1freq | 40..16000 Hz | 400 | first bell center |
415
471
  | eq | bell1q | 0.1..10 | 1 | first bell width: higher is narrower |
416
472
  | **eq** | bell2 | -12..12 dB | 0 | second bell gain |
417
- | eq | bell2freq | 200..18000 Hz | 3000 | second bell centre |
473
+ | eq | bell2freq | 200..18000 Hz | 3000 | second bell center |
418
474
  | eq | bell2q | 0.1..10 | 1 | second bell width: higher is narrower |
419
475
  | **eq** | high | -12..12 dB | 0 | high shelf gain |
420
476
  | eq | highfreq | 1000..20000 Hz | 10000 | high shelf corner |
@@ -432,7 +488,7 @@ Parameters (**bold** unit = shown in the simple menu; the rest are under `advanc
432
488
  | tape | tone | 2000..20000 Hz | 20000 | high-frequency roll-off after the curve; 20000 is off |
433
489
  | **tape** | mix | 0..1 | 1 | dry/wet |
434
490
  | **width** | width | 0..2 | 1 | side level: 0 is mono, 1 unchanged, 2 twice as wide |
435
- | **width** | mono | 0..300 Hz | 120 | below this the mix is mono (keeps bass centred); 0 is off |
491
+ | **width** | mono | 0..300 Hz | 120 | below this the mix is mono (keeps bass centered); 0 is off |
436
492
  | **limiter** | ceiling | -12..0 dBTP | -1 | highest true peak out |
437
493
  | **limiter** | gain | 0..24 dB | 0 | drive into the limiter (a target sets it itself) |
438
494
  | **limiter** | release | 1..1000 ms | 100 | recovery time; short is louder, long is cleaner |
@@ -441,7 +497,7 @@ Parameters (**bold** unit = shown in the simple menu; the rest are under `advanc
441
497
 
442
498
  <!-- master-params:end -->
443
499
 
444
- 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. 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).
500
+ 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).
445
501
 
446
502
  ## Synth
447
503
 
@@ -550,37 +606,37 @@ FM operators 2–8 repeat the `fm` rows with a suffix (`fm2`, `fmh2`, `fmattack2
550
606
 
551
607
  ### Strudel parity
552
608
 
553
- | Strudel | dawg | Status |
554
- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------- | ---------------------------------------------------- |
555
- | `s`/`sound` sine, sawtooth, square, triangle, supersaw, pulse, user, white, pink, brown, crackle | `instrument` | done |
556
- | `noise`, `density` | `synth.noise`, `synth.density` | done |
557
- | `unison`, `spread`, `detune` | `synth.*` | done |
558
- | `pw`, `pwrate`, `pwsweep` | `synth.*` | done |
559
- | `fm`/`fmi`, `fmh`, `fmattack/fmdecay/fmsustain/fmrelease`, `fmenv`, `fmwave`, operators 2–8 | `synth.*` | done |
560
- | `attack/decay/sustain/release`, `adsr`, `gain`, `velocity` | `synth.*`; `adsr` is command shorthand; velocity is the note's | done |
561
- | `penv`, `pattack/pdecay/psustain/prelease`, `pcurve`, `panchor` | `synth.*` | done |
562
- | `vib`/`vibrato`, `vibmod` | `synth.*` | done |
563
- | `lpf/hpf/bpf`, `lpq/hpq/bpq`, `lpenv/hpenv/bpenv` and their ADSRs, `ftype`, `fanchor` | `synth.*` (per voice); also the track `filter` effect | done |
564
- | `partials`, `phases` | `synth.partials`, `synth.phases` | done |
565
- | `vowel`, `coarse`, `crush`, `shape`, `distort`, `djf` | effects `vowel`, `crush`, `distort`, `djf` | done (see Effects) |
566
- | `phaser*`, `tremolo*`, `leslie`/`lrate`/`lsize`, `compressor*`, `postgain` | effects of the same names | done |
567
- | `room`, `size`, `roomfade`, `roomlp`, `roomdim` | `reverb` | done |
568
- | `delay`, `delaytime`, `delayfeedback` | `delay` | done |
569
- | `pan` | track `pan` | done |
570
- | `orbit`, `duckorbit`/`duckdepth`/`duckattack`/`duckonset` | effects `orbit` (+ `shared`), `duck` | done: `shared` sends to one delay + reverb per orbit |
571
- | `iresponse`/`ir` | `reverb.ir` | done: FFT convolution; built-ins or a pinned sample |
572
- | `z_sine`…`z_noise`; `zrand`, `curve`, `slide`, `deltaSlide`, `pitchJump`, `pitchJumpTime`, `lfo`, `noise`, `zmod`, `zcrush`, `zdelay`, `tremolo` | ZzFX sounds, `synth.*` | done (clean-room; units documented above) |
573
- | zzfx `duration` | note length | done: a note's length is its duration |
574
- | raw `zzfx([...])` parameter array | `synth zzfx …`, SDK `zzfx([...])`, `set_synth {zzfx}` | done: ZzFX's documented layout → named controls |
575
- | soundfonts `gm_*`, drum banks, dirt-samples | sampler and sample packs | not this engine: hosted samples, see Sample packs |
576
- | sample controls `begin`, `end`, `speed`, `unit`, `loop`, `loopBegin`/`loopb`, `loopEnd`/`loope`, `clip`/`legato`, `fit`, `loopAt`, `accelerate`, `squiz`, `cut`, `gain` | sampler voice fields; `/sample set`, `set_sample` | done (see Samples) |
577
- | fitting to tempo (Ableton Repitch/Beats/Tones; Strudel `fit`) | `bpm` `fitmode` `len`; `/fitmode`, `fit_sample` | done (see Fitting samples) |
578
-
579
- ## Keys (modelled piano)
580
-
581
- 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.
582
-
583
- `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 sampled Salamander grand is still in the browser under instruments.
609
+ | Strudel | dawg | Status |
610
+ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -------------------------------------------------------------- | ---------------------------------------------------- |
611
+ | `s`/`sound` sine, sawtooth, square, triangle, supersaw, pulse, user, white, pink, brown, crackle | `instrument` | done |
612
+ | `noise`, `density` | `synth.noise`, `synth.density` | done |
613
+ | `unison`, `spread`, `detune` | `synth.*` | done |
614
+ | `pw`, `pwrate`, `pwsweep` | `synth.*` | done |
615
+ | `fm`/`fmi`, `fmh`, `fmattack/fmdecay/fmsustain/fmrelease`, `fmenv`, `fmwave`, operators 2–8 | `synth.*` | done |
616
+ | `attack/decay/sustain/release`, `adsr`, `gain`, `velocity` | `synth.*`; `adsr` is command shorthand; velocity is the note's | done |
617
+ | `penv`, `pattack/pdecay/psustain/prelease`, `pcurve`, `panchor` | `synth.*` | done |
618
+ | `vib`/`vibrato`, `vibmod` | `synth.*` | done |
619
+ | `lpf/hpf/bpf`, `lpq/hpq/bpq`, `lpenv/hpenv/bpenv` and their ADSRs, `ftype`, `fanchor` | `synth.*` (per voice); also the track `filter` effect | done |
620
+ | `partials`, `phases` | `synth.partials`, `synth.phases` | done |
621
+ | `vowel`, `coarse`, `crush`, `shape`, `distort`, `djf` | effects `vowel`, `crush`, `distort`, `djf` | done (see Effects) |
622
+ | `phaser*`, `tremolo*`, `leslie`/`lrate`/`lsize`, `compressor*`, `postgain` | effects of the same names | done |
623
+ | `room`, `size`, `roomfade`, `roomlp`, `roomdim` | `reverb` | done |
624
+ | `delay`, `delaytime`, `delayfeedback` | `delay` | done |
625
+ | `pan` | track `pan` | done |
626
+ | `orbit`, `duckorbit`/`duckdepth`/`duckattack`/`duckonset` | effects `orbit` (+ `shared`), `duck` | done: `shared` sends to one delay + reverb per orbit |
627
+ | `iresponse`/`ir` | `reverb.ir` | done: FFT convolution; built-ins or a pinned sample |
628
+ | `z_sine`…`z_noise`; `zrand`, `curve`, `slide`, `deltaSlide`, `pitchJump`, `pitchJumpTime`, `lfo`, `noise`, `zmod`, `zcrush`, `zdelay`, `tremolo` | ZzFX sounds, `synth.*` | done (clean-room; units documented above) |
629
+ | zzfx `duration` | note length | done: a note's length is its duration |
630
+ | raw `zzfx([...])` parameter array | `synth zzfx …`, SDK `zzfx([...])`, `set_synth {zzfx}` | done: ZzFX's documented layout → named controls |
631
+ | soundfonts `gm_*`, drum banks, dirt-samples | sampler and sample packs | not this engine: hosted samples, see Sample packs |
632
+ | 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) |
633
+ | fitting to tempo (Ableton Repitch/Beats/Tones; Strudel `fit`) | `bpm` `fitmode` `len`; `/fitmode`, `fit_sample` | done (see Fitting samples) |
634
+
635
+ ## Keys (modeled piano)
636
+
637
+ 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.
638
+
639
+ `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.
584
640
 
585
641
  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.
586
642
 
@@ -589,7 +645,7 @@ Polyphony is 64 voices; a new key steals the oldest released voice, then the old
589
645
  Prompt grammar (one undo step per command):
590
646
 
591
647
  ```text
592
- piano the modelled grand (also: grand)
648
+ piano the modeled grand (also: grand)
593
649
  piano ballad a preset: grand ballad upright felt lofi honkytonk prepared
594
650
  upright | felt | honkytonk | prepared the preset word alone
595
651
  keys list this track's piano settings
@@ -601,9 +657,9 @@ keys reset the family's own sound (keys: {})
601
657
  automate keys-hardness points 0:0.2 8:0.8 automatable parameters have lanes
602
658
  ```
603
659
 
604
- 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`.
660
+ 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`.
605
661
 
606
- 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) in **Sound > Parameters**. 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 } })`.
662
+ 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 } })`.
607
663
 
608
664
  | Param | Range | Default | Lane | What it does |
609
665
  | ------------ | ------------------------------------- | ------------------ | --------------- | -------------------------------------------------------------------- |
@@ -624,9 +680,107 @@ The menu has the pianos under **Sound > browse sounds > Keys**, and for a piano
624
680
  | **body** | grand upright felt honkytonk prepared | the family's own | | body EQ voicing |
625
681
  | **vib** | 0..64 Hz | 0 | | pitch wobble rate (tape wow); note vibrato replaces it |
626
682
  | **vibmod** | 0..24 semitones | 0.5 | | pitch wobble depth |
683
+ | **sym** | 0..1 | 0 | | sympathetic string resonance while the sustain pedal is down |
627
684
 
628
685
  Lanes are read at each note's onset. The model is dawg's own, from public literature (Fletcher's inharmonicity B·n² law, Railsback stretch, Weinreich's coupled unison strings and two-stage decay, Chaigne and Askenfelt's felt-hammer model, Bank's modal piano synthesis), with no sampled audio.
629
686
 
687
+ `sym` (0.6.1) adds a per-track bank of 36 tuned strings (C2..B4) that ring along with what you play while the sustain pedal is down, as the undamped strings of a real piano do. It costs nothing when `sym` is 0 or the track has no sustain pedal, and runs at half rate at 44.1 and 48 kHz.
688
+
689
+ ### Soft pedal and sostenuto (0.6.1)
690
+
691
+ 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:
692
+
693
+ - **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.
694
+ - **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.
695
+
696
+ ```text
697
+ pedal soft 0-8 una corda from beat 0 to 8 (also: down|half|up <beat>, bars, off)
698
+ pedal sost 0-4 sostenuto down at 0, up at 4 (holds the keys down at 0)
699
+ pedal soft list the lane; pedal sost off clears it
700
+ ```
701
+
702
+ 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"]] })`.
703
+
704
+ ### Electric keys (0.6.1)
705
+
706
+ Three electric keyboard families on the same `keys` field, built in and byte-identical across renders:
707
+
708
+ - **epiano** (`rhodes`): a tine piano. Each hammer strikes a tine cantilever (three modes: the fundamental, the bell partial and the clang), read by an electromagnetic pickup whose nonlinear response is computed at 2x oversampling, so `bark` growls when played hard without aliasing (aliases of a hard-driven key 96 stay below -50 dB). `vibe` is the suitcase stereo vibrato: the left and right channels swap in antiphase at `vibehz`.
709
+ - **wurli** (`wurlitzer`): a reed piano with a capacitive pickup: nasal, with a growl on loud notes; `trem` is the built-in 5.6 Hz tremolo.
710
+ - **clav** (`clavinet`): struck strings read by neck and bridge pickups. `pickup` chooses `neck`, `bridge`, `both` or `out` (both, out of phase: thin and funky) and `mute` is the mute slider. Pickup positions vary slightly per track (seeded from the track id, so the same track always sounds the same). Releasing a key damps it with the yarn damper and a small release plunk.
711
+
712
+ Presets: `epiano` (stage tine piano), `suitcase` (with the stereo vibrato), `dyno` (bright, bell-heavy), `wurli`, `clav` (both pickups) and `funkclav` (pickups out of phase, mute up). The preset word alone loads it (`suitcase`, `funkclav`); `keys` stays the legacy word.
713
+
714
+ ```text
715
+ epiano | wurli | clav the family (also rhodes wurlitzer clavinet)
716
+ epiano preset suitcase a preset: epiano suitcase dyno | wurli | clav funkclav
717
+ epiano vibe 0.6 bark 0.5 any parameter of the family
718
+ clav pickup bridge mute 0.4
719
+ automate keys-vibe points 0:0 8:0.8 automatable parameters have lanes
720
+ ```
721
+
722
+ | Param | Families | Range | Default | Lane | What it does |
723
+ | ---------- | ------------- | -------------------- | ------- | ----------- | ------------------------------------- |
724
+ | **bark** | epiano, wurli | 0..1 | 0.35 | | pickup drive: growl when played hard |
725
+ | **bell** | epiano, wurli | 0..1 | 0.5 | | tine or reed bell ping |
726
+ | **tone** | all three | 0..12000 Hz | 0 (off) | `keys-tone` | output low-pass |
727
+ | **vibe** | epiano | 0..1 | 0 | `keys-vibe` | suitcase stereo vibrato depth |
728
+ | **vibehz** | epiano | 0.5..12 Hz | 4 | | suitcase vibrato rate |
729
+ | **trem** | wurli | 0..1 | 0 | `keys-trem` | tremolo depth at 5.6 Hz |
730
+ | **pickup** | clav | neck bridge both out | both | | pickup switch |
731
+ | **mute** | clav | 0..1 | 0 | | mute slider: damps the upper partials |
732
+
733
+ `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.
734
+
735
+ ### Organs (tonewheel, combo, pipe)
736
+
737
+ Three organ families run on the same keys engine (`src/audio/keys/organ.ts`) when the track's `instrument` is `tonewheel`, `combo` or `pipe` and it carries `keys`. `organ` stays the legacy sine preset and renders byte-identically; reach the engine with the verbs or presets below, or the aliases `hammond`, `b3`, `farfisa`, `church`, `pipeorgan`.
738
+
739
+ - **tonewheel**: 91 free-running tonewheels at the gear ratios of the classic organ, phase-locked to song time (a key opens a wheel already turning, so the same chord sounds the same wherever it lands and two keys sharing a wheel share its phase). Nine drawbars `16' 5⅓' 8' 4' 2⅔' 2' 1⅗' 1⅓' 1'` stored as nine digits 0-8 (`888000000`). Single-trigger percussion (2nd or 3rd harmonic, fast or slow) fires only when every key was up and, as on the original, mutes the 1' bar while it is on. Key click, a scanner vibrato/chorus (V1-V3, C1-C3), a preamp drive and a two-rotor rotary speaker (horn and drum, slow, fast or stop) whose rotors glide between speeds with their own inertia: about a second for the horn and several for the drum.
740
+ - **combo**: divider-style combo organ with five registers `16' 8' 4' 2⅔' 2'` (five digits), a flute, reed or bright voice and a vibrato.
741
+ - **pipe**: band-limited additive pipe ranks with chiff, wind unsteadiness and a tremulant. `stops` lists stop names (`subbass16 bourdon16 principal8 flute8 gedackt8 gamba8 celeste8 octave4 flute4 nazard fifteenth2 piccolo2 tierce larigot mixture trombone16 trumpet8 oboe8 krummhorn8`) or registrations (`plenum flutes cornet reeds strings full`). Each rank sits on the track's tuning (12-TET or a table such as 19-EDO); mutation and mixture ranks are tuned pure (quints 3·f, tierces 5·f) so they fuse with the foundation. Drawbar and rank footages are octaves of the table, so a 19-EDO organ keeps its octaves.
742
+
743
+ A row belongs to its family: `rotary fast` on a pipe organ or `keys drawbars` on a grand is refused with the families that read it, and `keys` on an organ lists its own rows. Single-trigger percussion is one envelope per track: every key struck together sounds it, and a key added while another is held gets the envelope's decayed level. Drive changes the tone at roughly steady loudness.
744
+
745
+ The scanner, drive and rotary run once per track after its voices (the keys per-track post hook), so chords share one rotor; in play mode the live synth keeps one per track and shares its wheel and rotor clock. Rotary and drive are lanes: `keys-rotary` (0 stop, 1 slow, 2 fast; the rotors spin up or down with inertia) and `keys-drive`.
746
+
747
+ ```text
748
+ tonewheel the tonewheel organ (also: hammond, b3)
749
+ tonewheel 888800008 the organ with those drawbars
750
+ gospel | jazzorgan tonewheel presets
751
+ combo | combo 08880 | farfisa | vox the combo organ
752
+ pipe | pipe flutes | pipe principal8 octave4 the pipe organ with a registration or stops
753
+ flutes | cornet | reeds | celeste pipe presets
754
+ tonewheel 888800008 perc 3rd a verb takes more rows (combo 08880 flute)
755
+ rotary slow | fast | stop the rotary speaker
756
+ rotary fast at 16 switch it at beat 16 (a keys-rotary lane point)
757
+ keys drawbars 888000000 any organ row (keys perc 3rd, keys scanner v2, keys stops plenum)
758
+ automate keys-rotary points 0:1 4:2 spin the rotor up at beat 4
759
+ ```
760
+
761
+ 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).
762
+
763
+ 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"] }`.
764
+
765
+ | Param | Family | Range | Default | Lane | What it does |
766
+ | ------------- | --------------- | --------------------------- | ------------------------------------- | ------------- | ---------------------------------------------- |
767
+ | **drawbars** | tonewheel | nine digits 0-8 | 888000000 | | drawbar registration, 16' to 1' |
768
+ | **perc** | tonewheel | off 2nd 3rd | off | | single-trigger percussion; on mutes the 1' bar |
769
+ | **percdecay** | tonewheel | fast slow | fast | | percussion decay (0.6 s or 1.8 s) |
770
+ | **percvol** | tonewheel | normal soft | normal | | soft: percussion 6 dB down, drawbars unmuted |
771
+ | **click** | tonewheel | 0..1 | 0.5 | | key click |
772
+ | **scanner** | tonewheel | off v1 v2 v3 c1 c2 c3 | c3 | | scanner vibrato or chorus |
773
+ | **drive** | tonewheel combo | 0..1 | 0.15 (combo 0) | `keys-drive` | preamp overdrive |
774
+ | **rotary** | tonewheel combo | slow fast stop | slow (combo stop) | `keys-rotary` | rotary speaker speed |
775
+ | **registers** | combo | five digits 0-8 | 08800 | | combo registers, 16' to 2' |
776
+ | **voice** | combo | flute reed bright | reed | | register timbre |
777
+ | **stops** | pipe | stop names or registrations | principal8 octave4 fifteenth2 mixture | | drawn stops |
778
+ | **chiff** | pipe | 0..1 | 0.4 | | flue pipe attack noise |
779
+ | **wind** | pipe | 0..1 | 0.3 | | wind instability |
780
+ | **trem** | pipe | 0..1 | 0 | | tremulant depth |
781
+
782
+ The organ models are dawg's own, from public descriptions of the tonewheel generator (91 wheels, the 2:1 gearing per octave and its 1' foldback at the top), the scanner vibrato, the rotary speaker's horn and drum rotor speeds, and additive pipe-organ synthesis; no sampled audio.
783
+
630
784
  ## Samples
631
785
 
632
786
  A track whose instrument is `sampler(...)` plays audio files instead of a synth. Voices live in `tracks/<slug>/samples/` and `src` is relative to the track directory (`samples/kick.wav`); a project-relative `tracks/<slug>/samples/kick.wav` works too.
@@ -652,7 +806,7 @@ export default track({
652
806
 
653
807
  Semantics follow Strudel's sampler:
654
808
 
655
- | Strudel | dawg | Behaviour |
809
+ | Strudel | dawg | Behavior |
656
810
  | ------------------------------------------------------- | ---------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------ |
657
811
  | `samples({ kick: "kick.wav" })` | `sampler({ kick: "samples/kick.wav" })` | one voice per name |
658
812
  | `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 |
@@ -673,6 +827,17 @@ Semantics follow Strudel's sampler:
673
827
 
674
828
  `/sample set <voice> <control> <value>…` edits these on the focused sampler track (`/sample set brk fit on clip 1`, `/sample set hat cut hats`, `off` unsets one), each voice in the menu's Parameters section has the same controls, and the agent's `set_sample` tool takes them by name.
675
829
 
830
+ ### Velocity layers and round robin (0.6.1)
831
+
832
+ Two optional voice fields borrowed from SFZ make a multisampled instrument:
833
+
834
+ | Field | Values | What it does |
835
+ | ----- | ----------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
836
+ | `vel` | `[lo, hi]`, MIDI 0..127 | the velocity range this voice plays (SFZ `lovel`/`hivel`). Keyed voices with the same root are layers: a note plays the one whose range holds its velocity. One-shot layers on a kit share a pad only through an `rr` group (below); a one-shot voice with `vel` and no `rr` keeps its own pad and plays at every velocity |
837
+ | `rr` | group name | round robin (SFZ `seq_length`): voices in a group with the same root and a matching layer take turns, A B A B, in note order, so a repeated hit never sounds machine-gunned |
838
+
839
+ The picker chooses the layer by velocity first (when no layer of the group holds the velocity, the nearest layer plays), then the next voice in the group from a counter that starts at a turn seeded by the track id and the group, so renders stay deterministic but two tracks with the same samples do not alternate in lockstep. For a kit pad with soft and hard hits, put both voices in one group: `sample snare-soft vel 0-63 rr sn` and `sample snare-hard vel 64-127 rr sn`; either pad then plays the layer the velocity asks for. A note's velocity maps to MIDI as `round(velocity x 127)`; a boundary velocity belongs to the layer whose range contains it (`[0,63]` and `[64,127]` switch at 64). `/sample set snare1 vel 0-63 rr sn`, the menu's **Velocity layer** and **Round robin** rows on each voice, `set_sample {params: {vel: [64,127], rr: "sn"}}` and `sample("samples/sn1.wav", { vel: [0, 63], rr: "sn" })` set them; `off` clears. Voices without them play as before.
840
+
676
841
  ### Fitting samples to the song (0.6)
677
842
 
678
843
  A voice can follow the song's time instead of its own rate. Give it its own tempo (`bpm`) or a length in beats (`len`), and pick how it changes time with `fitmode`:
@@ -698,13 +863,32 @@ How they combine with Strudel's controls and the 0.5 tempo map:
698
863
 
699
864
  `fitmode beats` or `tones` needs `bpm`, `len` or `fit` (validation error otherwise). `/bpm 174`, `/len 16` and `/fitmode beats` act on the focused sampler voice (name it when the track has several: `/bpm 174 brk`; `off` unsets; song tempo stays the bare word `tempo <n>`, and a bare `bpm <n>` without the slash also sets the song tempo, so only `/bpm` sets the sample's); `/fitmode auto` suggests a mode from the sound (crest factor above 5 and more than 2 onsets a second fit as `beats`, the rest as `tones`). The menu's Sound section lists `bpm`, `fitmode` and `len` on each voice, the agent's `fit_sample` tool takes them, and the SDK is `sample("samples/amen.wav", { bpm: 174, fitmode: "beats" })`.
700
865
 
866
+ ### Shift and fade (0.6.1)
867
+
868
+ `shift <semitones>` moves a sampler voice's pitch without changing its length (−24..24). It is a phase-vocoder stretch by 2^(st/12) followed by a band-limited read-back at that rate (identity phase locking, Laroche and Dolson 1999; the same idea as Strudel's `stretch`), cached like a fit and applied after any `fitmode`, so a fitted loop can also be transposed. By default the formants move with the pitch, like a tape; `shift 7 formant keep` (stored `formant: 0`) keeps them where they were, so a voice or a guitar keeps its body, and `formant <n>` moves them `n` semitones on their own. Formants are kept with an envelope drawn through the harmonic peaks (a cepstral true envelope, Röbel and Rodet 2005, where no peak stands): measured on voices from 110 to 330 Hz, ±7 and +12 st land within 1 cent and keep the first formant peak within 3%. Each frame keeps its energy through the correction, so keeping formants stays within 1.5 dB of the plain shift's level. `shift 0` clears the shift but keeps a formant move (`shift 0 formant 3` moves only the formants); `shift off` clears both.
869
+
870
+ `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.
871
+
872
+ 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 })`.
873
+
874
+ ### Resample (0.6.1)
875
+
876
+ `resample <track>|orbit <n>|master [section <name>|bars a-b] [post] [grain] [as <id>]` renders one track, one orbit or the whole mix to `tracks/<slug>/samples/<name>.wav` through the same offline renderer as `dawg render`, pins its sha256 and adds a track that plays it: a one-shot sampler track with one note across the range, or with `grain` a granular track (the `cloud` preset) holding one note there. The source stays as it is; mute it to hear only the copy. The file ends 20 ms after the sound falls to digital silence, and a source that is silent over the range is refused with a receipt instead of adding a silent track. Like freezing and flattening in a DAW (Ableton's Resampling input, Bitwig's bounce in place), it turns a part into material you can chop, grain or shift.
877
+
878
+ - The render is pre-master (the song master is left out) unless `post` is given or the source is `master`. The same score always gives the same bytes, so the sha256 is stable.
879
+ - The new sampler voice plays the file at gain 2 with no fade in, so it reproduces the source stem within −60 dB.
880
+ - 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.
881
+ - Up to 600 s. `section` uses the song's sections; `bars 1-2` is 1-based and inclusive.
882
+
883
+ **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.
884
+
701
885
  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.
702
886
 
703
887
  Every voice starts and stops with a 1–3 ms fade, so cuts do not click. Without `bpm`, `len` or `fitmode` there is no time-stretch, as in Strudel's default. A sampler track goes through the same volume and pan automation, filter, delay and reverb as any other track and is a cached stem like any other; the stem's cache key includes each voice's sha256, so replacing a file re-renders it.
704
888
 
705
889
  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.
706
890
 
707
- 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.
891
+ 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.
708
892
 
709
893
  ## Sample packs
710
894
 
@@ -731,11 +915,11 @@ instrument: sampler({
731
915
  | `/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 |
732
916
  | `/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 |
733
917
 
734
- **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.
918
+ **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.
735
919
 
736
920
  **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.
737
921
 
738
- `/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.
922
+ 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.
739
923
 
740
924
  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).
741
925
 
@@ -796,7 +980,7 @@ The agent's `make_wavetable` tool (`src/audio/wavetable-maker.ts`, `src/media/wa
796
980
  - **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.
797
981
  - **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.
798
982
 
799
- 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`.
983
+ 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`.
800
984
 
801
985
  ## Plucked strings
802
986
 
@@ -849,7 +1033,43 @@ instrument: stringed("sitar", { buzz: 0.8, sym: 0.5 }),
849
1033
  | `string <param> off` · `string reset` | back to the preset's value · drop every override |
850
1034
  | `string off` | back to the legacy `pluck` voice |
851
1035
 
852
- The menu has the presets under Sound › browse sounds › Strings and every string parameter in Sound › Parameters on 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.
1036
+ 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.
1037
+
1038
+ ### Bowed strings
1039
+
1040
+ `exciter: "bow"` (0.6.1) drives the same string with a bow instead of a pluck: a bowed waveguide after the STK `Bowed` model (Smith; Cook and Scavone), with a friction table whose slope follows bow force and a bridge reflection through the string loss, so the string sticks and slips in Helmholtz motion. A Schelleng guard keeps every setting playable: bow force maps inside the measured minimum and maximum for the note's pitch and bow position (Schelleng 1973), so `pressure 0` is a breathy flautando and `pressure 1` a gritty but still pitched sound, never a squeal or silence; strings with periods under 24 samples run 2x oversampled, and a pitch lock keeps the note within 1 cent from G3 to C7 at 22.05 and 48 kHz. The `violin` body adds the main air and wood resonances (A0, CBR, B1-, B1+ and the bridge hill).
1041
+
1042
+ `bowed` plays the cello preset; `bowed <preset>` (or `string <preset>`) picks one of 13 bowed presets appended to the table (35 in all):
1043
+
1044
+ | Preset | Sound |
1045
+ | ------------------------------------------ | ------------------------------------------------------------------------- |
1046
+ | `violin` `viola` `cello` `contrabass` | solo orchestral strings |
1047
+ | `fiddle` `erhu` `kamancheh` (`kemence`) | folk fiddle, erhu (skin body, wide vibrato), Persian spike fiddle |
1048
+ | `violins` `violas` `cellos` `contrabasses` | sections: seeded unison players with their own detune, vibrato and spread |
1049
+ | `pizz` (`pizzicato`) · `trem` (`tremolo`) | plucked violin section · tremolo section |
1050
+
1051
+ The words `violin`, `viola`, `fiddle`, `erhu`, `kamancheh`, `violins`, `violas`, `cellos`, `contrabasses` and `bowed-cello` switch a track to these presets. The bare legacy words `cello`, `contrabass` and `strings` keep their pre-0.6.1 voices byte-identically; reach the bowed cello with `bowed cello`, `string cello`, `instrument bowed-cello`, `set_string {preset: "cello"}` or `stringed({ preset: "cello" })`.
1052
+
1053
+ | Parameter | Range | Meaning |
1054
+ | ------------------------- | ---------- | --------------------------------------------------------------------------- |
1055
+ | `pressure` | 0..1 | bow force inside the playable range: flautando at 0, gritty at 1 |
1056
+ | `speed` | 0..1 | bow speed at full dynamics (loudness) |
1057
+ | `attack` | 0.005..4 s | bow-speed ramp at the start of a stroke (swells) |
1058
+ | `vib` `vibmod` `vibdelay` | Hz, st, s | vibrato rate, depth and onset delay (a note's own vibrato wins) |
1059
+ | `tremhz` | 0..16 Hz | tremolo bowing: rapid strokes per second (0 off) |
1060
+ | `sord` | 0..1 | con sordino: the practice mute, darker and softer |
1061
+ | `dyn` (`expression`) | 0..1 | dynamics on top of velocity (like MIDI CC11): drives bow speed and pressure |
1062
+
1063
+ Phrasing. A true legato is a slur: a single note that starts while the previous single note is held and that note lets go within a 64th note or 150 ms, or a note with the `legato` articulation. The string retunes within 5-10 ms and keeps the bow, with no new attack (four slurred notes are one onset); the bow point and loss follow the new pitch, so slurs up to two octaves land within a cent at a fresh note's level, and wider leaps start a new stroke. A note held under a moving line (a pedal) keeps sounding. Chords and double stops start new strokes. Velocity sets the stroke's dynamics and presses harder (pressure + 0.3 x (velocity - 0.5)); `staccato` and `ghost` are detache strokes (attack 10 ms, release 30 ms), and `accent` and `marcato` bite (pressure +0.2 for the first 80 ms). Section presets seed each player's vibrato rate (x0.92-1.08), depth (x0.8-1.2) and phase, and start players up to 25 ms apart (the first stays on the grid). `pressure`, `speed`, `sord` and `dyn` automate as `string-<param>` lanes, read every 32 samples, so `automate string-dyn points 0:0.2 4:1` is a crescendo inside held notes; `string-pos` sets each note's bow point (sul ponticello near 0.05, sul tasto near 0.4). Play mode caps a section at two unison players and sounds the first 0.75 s at once, the rest following in the background; renders use every player.
1064
+
1065
+ Measured: tuning within 1 cent to C7 at 22.05 and 48 kHz; 0 of 1296 pressure/speed/position/pitch settings leave Helmholtz motion; the free string after the bow lifts decays within 10% of `ring`; ff is at least 1.15x brighter (spectral centroid) than pp; 8 s of `violins` in play mode renders in under 40 ms.
1066
+
1067
+ | Command | Does |
1068
+ | -------------------------------------------- | ------------------------------------------------------- |
1069
+ | `bowed` · `bowed <preset>` · `bowed presets` | the cello · a bowed preset · the bowed presets |
1070
+ | `bowed <param> <value> …` | the same as `string <param> <value> …` (`bowed sord 1`) |
1071
+
1072
+ 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 })`.
853
1073
 
854
1074
  ## Granular
855
1075
 
@@ -885,7 +1105,7 @@ Quality and cost. Grains read a shared semitone-level band-limited bank (`src/au
885
1105
  | `track cloud` · `track hold-2` | a new granular track named after a preset |
886
1106
  | `track pad grain swarm` | focus or create a track and grain it in one step |
887
1107
 
888
- 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:
1108
+ **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:
889
1109
 
890
1110
  ```ts
891
1111
  instrument: granular("cloud", { scan: 0.1, seed: 7 }),
@@ -893,9 +1113,24 @@ instrument: granular({ src: "synth:bell@72", grain: 0.08, shimmer: 0.3 }),
893
1113
  instrument: granular("hold", { src: "samples/choir.wav", root: "A3" }),
894
1114
  ```
895
1115
 
1116
+ ### Grain play (0.6.1)
1117
+
1118
+ Four optional parameters make a granular track playable like an instrument rather than a texture. Each is absent by default, and absent renders byte-identically to 0.6.0.
1119
+
1120
+ | Parameter | Values (default) | What it does |
1121
+ | --------- | ---------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
1122
+ | `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. |
1123
+ | `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. |
1124
+ | `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. |
1125
+ | `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. |
1126
+
1127
+ `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 })`.
1128
+
1129
+ 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.
1130
+
896
1131
  ## Mallets and bells (modal)
897
1132
 
898
- `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.
1133
+ `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.
899
1134
 
900
1135
  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.
901
1136
 
@@ -903,7 +1138,7 @@ Presets (a word picks one): `marimba` `vibes` `xylophone` `glock` `celesta` `chi
903
1138
  | -------------------------- | ------------------ | --------------------------------------------------------------------------------- |
904
1139
  | `mallet` | yarn … brass | `yarn` `cord` `rubber` `plastic` `brass`; sets `hardness` |
905
1140
  | `hardness` | 0..1 | mallet hardness: soft rounds off the high modes, hard adds them; velocity adds |
906
- | `position` | 0..1 | strike point: 0 the end or edge, 0.5 the centre |
1141
+ | `position` | 0..1 | strike point: 0 the end or edge, 0.5 the center |
907
1142
  | `ring` | 0.05..30 s (log) | ring time (T60) at middle C |
908
1143
  | `tilt` | 0..2 | how much faster high modes and high notes decay |
909
1144
  | `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 |
@@ -930,7 +1165,55 @@ instrument: modal("marimba", { mallet: "rubber", ring: 2 }),
930
1165
  | `modal reset` | clear overrides, keep the preset |
931
1166
  | `modal off` | leave the engine for the legacy marimba voice |
932
1167
 
933
- The menu's **Sound › browse sounds › Mallets and bells** lists the presets, and **Sound › Parameters** 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.
1168
+ 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.
1169
+
1170
+ ### Gamelan (0.6.1)
1171
+
1172
+ 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).
1173
+
1174
+ 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" })`.
1175
+
1176
+ Live, an undamped bar (`damp 0`: gongs, kempul, bowls) keeps ringing after you release the key in play mode and the audition loop: up to 32 ringing voices, the oldest faded over 250 ms, and the loop's fold fades a tail that would outlast it the same way. The 15 presets are appended to the table (existing ones unchanged), and `RESONATOR_TABLE_VERSION` is 2, so stems re-render once.
1177
+
1178
+ ## Winds and brass (wind engine)
1179
+
1180
+ `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.
1181
+
1182
+ 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.
1183
+
1184
+ | Parameter | Range | Meaning |
1185
+ | ------------------ | -------------------------------- | ----------------------------------------------------------------------------- |
1186
+ | `model` | jet reed sax lips | exciter and bore (set by the preset) |
1187
+ | `breath` | 0..1 | blowing pressure: louder and fuller; too little and a reed does not speak |
1188
+ | `noise` | 0..1 | breath noise |
1189
+ | `attack` `release` | 0.001..2 s, 0.005..2 s | breath rise and fall; Strudel `att`/`rel` |
1190
+ | `vib` `vibmod` | 0..12 Hz, 0..1 st | vibrato rate and depth |
1191
+ | `reed` | 0..1 | reed stiffness, jet offset or lip tension |
1192
+ | `bright` | 0..1 | bore loss: dark to bright |
1193
+ | `stopped` | on/off | stopped pipe, odd harmonics (jet only: panpipe) |
1194
+ | `mute` | open straight cup harmon plunger | brass mute |
1195
+ | `wah` `wahenv` | 0..1 | plunger opening, and how much it opens with each note (doo-wah) |
1196
+ | `growl` `flutter` | 0..1 | hum into the horn, flutter tongue |
1197
+ | `players` | 1..8 | section size: extra players double the chord tones, slightly detuned and late |
1198
+ | `gain` | 0..2 | level |
1199
+
1200
+ `breath`, `noise`, `wah`, `growl` and `flutter` have `wind-<param>` automation lanes, read continuously, so a breath lane swells a held note. A single line slurs by default: a lone note that starts under (or right at the end of) a lone held note continues the same breath with a legato pitch change (a track `glide` setting, a `staccato` note or a bend tongues it instead); chords sound as separate voices. Velocity brightens the tone (brass the most, flutes the least), accents and marcato blow harder, and notes honour tuning, `cents`, bends, humanize and the tempo map. Up to 24 voices sound at once; the oldest is stolen with an 80 ms fade.
1201
+
1202
+ ```ts
1203
+ instrument: "flute",
1204
+ instrument: wind("trumpet", { mute: "harmon", players: 3 }),
1205
+ instrument: wind("sax", { breath: 0.8, growl: 0.3 }),
1206
+ ```
1207
+
1208
+ | Command | What it does |
1209
+ | ---------------------------------------- | --------------------------------------------------------------- |
1210
+ | `wind` · `wind presets` · `presets wind` | the focused track's preset and overrides · every preset |
1211
+ | `wind <preset>` | make the focused track a wind track with that preset |
1212
+ | `wind <param> <value> …` | set parameters (`wind breath 0.8 players 3`); `off` clears one |
1213
+ | `wind mute <name>` | `open` `straight` `cup` `harmon` `plunger` |
1214
+ | `wind reset` · `wind off` | clear overrides, keep the preset · back to the legacy wind tone |
1215
+
1216
+ The agent's `set_wind {trackId, preset?, params?, reset?}` tool takes the same names, and `set_instrument` accepts every wind word.
934
1217
 
935
1218
  ## Rhythm (Euclidean rows)
936
1219
 
@@ -965,7 +1248,7 @@ export default track({
965
1248
  });
966
1249
  ```
967
1250
 
968
- | Field | Range (default) | T-1 parameter | Behaviour |
1251
+ | Field | Range (default) | T-1 parameter | Behavior |
969
1252
  | -------------------- | ------------------------------------ | ---------------- | --------------------------------------------------------------------------------------------- |
970
1253
  | `steps` | 1..64 (16) | Steps | Length of one pass; the row repeats every pass to the end of the loop. |
971
1254
  | `pulses` | 0..steps (4) | Pulses | Hits spread over the steps by Bjorklund's algorithm. |
@@ -977,7 +1260,7 @@ export default track({
977
1260
  | `pace` | -1..1 (0) | Pace | > 0 slows the repeats down progressively, < 0 speeds them up. |
978
1261
  | `ramp` | -1..1 (0) | Ramp | Velocity across the repeats: > 0 builds, < 0 fades. |
979
1262
  | `velocity` | 0..1 (0.8) | Velocity | Base velocity. |
980
- | `accent`, `accents` | 0..1 (0), 1..pulses (1) | Accent | Lifts `E(accents, pulses)` of the pulses (or the `X` steps) towards full velocity. |
1263
+ | `accent`, `accents` | 0..1 (0), 1..pulses (1) | Accent | Lifts `E(accents, pulses)` of the pulses (or the `X` steps) toward full velocity. |
981
1264
  | `gate`, `legato` | 0.05..4 steps (1), boolean | Sustain | Note length in steps; `legato` holds each hit to the next one (Strudel `euclidLegato`). |
982
1265
  | `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. |
983
1266
  | `swing` | -0.5..0.5 step (0) | Timing | Every second step later (> 0) or earlier. |
@@ -1014,6 +1297,7 @@ From Orchid's documentation and reviews:
1014
1297
  - Bass: an optional engine that plays the chord's root under every chord. Its menu (manual 10.2, "How to use Bass on Orchid") has Chords Only (bass only under chords), Unison (single notes play bass and treble together), Single Notes (single notes play only bass; the treble sounds only for chords) and Solo (the treble is muted, even for chords).
1015
1298
  - Performance modes: Strum (and 2-octave), Slop (random timing per note for a humanised feel that varies with every press), Arpeggiator (and 2-octave, tempo-synced; more chord notes make a longer pattern), Pattern (fixed rhythms) and Harp (a sweep across several octaves).
1016
1299
  - "Secret chords" (firmware 3.84+, Orchid manual section 14.8): two type buttons held together play extra chords. dim+sus is a power chord (C5), maj+sus augmented (C+), min+sus Cm(add4); min+dim with the 6 button is Cm(b6), maj+dim with 6 is C(b6), and maj+min with m7 is C7♯9. dawg's `COMBINED_TYPES` is this table.
1300
+ - Typed upper tensions (0.6.1): chord symbols also accept `11 m11 maj11 add11 madd11 13 m13 maj13 7b9 7#11 maj7#11 7b13 13b9` (`strum C11`, `strum_chords`, SDK `strum()`). They are typed-only (no pad buttons) and name back as typed; the guitar voicer drops the 5th, then the 11th beside a 3rd, then the 9th, and never the 3rd or 7th.
1017
1301
  - Orchid has no generator that writes a progression for you. Key mode is its "easy chord progressions" feature: you pick the order, every key is in key.
1018
1302
 
1019
1303
  dawg's own design:
@@ -1026,6 +1310,10 @@ dawg's own design:
1026
1310
  - 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).
1027
1311
  - 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.
1028
1312
 
1313
+ - 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.
1314
+
1315
+ 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**.
1316
+
1029
1317
  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.
1030
1318
 
1031
1319
  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).
@@ -1055,23 +1343,29 @@ Per track:
1055
1343
 
1056
1344
  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.
1057
1345
 
1058
- 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 } })`.
1346
+ 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 } })`.
1347
+
1348
+ ## Sound calibration
1349
+
1350
+ 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.
1351
+
1352
+ 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.
1059
1353
 
1060
1354
  ## Tunings and scales
1061
1355
 
1062
- 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.
1356
+ 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.
1063
1357
 
1064
1358
  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).
1065
1359
 
1066
- - 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.
1360
+ - 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.
1067
1361
  - 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.
1068
1362
  - 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.
1069
1363
  - 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.
1070
1364
  - 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.
1071
- - 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.
1365
+ - 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.
1072
1366
  - 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.
1073
1367
 
1074
- 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.
1368
+ 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.
1075
1369
 
1076
1370
  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.
1077
1371
 
@@ -1092,7 +1386,7 @@ Commands and menu.
1092
1386
  | `scale [<tonic>] <name>` | the song key and scale (`scale D hijaz`) |
1093
1387
  | `cents <id> <±c>` | detune one note |
1094
1388
 
1095
- 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")`).
1389
+ 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")`).
1096
1390
 
1097
1391
  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.
1098
1392
 
@@ -1119,9 +1413,30 @@ Rendering. Synth and wavetable voices start at the tuned frequency; keyed sample
1119
1413
 
1120
1414
  Recording keeps each note's velocity from `C`/`V`. Sustain is recorded the way Logic's Musical Typing records its `Tab` sustain key: as pedal events (`down` when `Tab` latches or Shift starts holding, `up` when it lets go) on the track, at the playhead and not quantized, while the notes keep the length the key was held. Terminals report key-down only, so `Tab` latches rather than holds. A chord-mode press keeps its held length on its voices instead.
1121
1415
 
1416
+ ### Guitar strumming (0.6.1)
1417
+
1418
+ The `guitar` perform mode, the `strum` command, `strum_chords` and SDK `strum()` share one fretboard voicer and one stroke engine in `core/chords.ts`.
1419
+
1420
+ ```text
1421
+ guitar show the track's fretting (Track.guitar)
1422
+ guitar tune dadgad a tuning name, or open strings low to high: guitar tune D A D G A D
1423
+ guitar capo 2 · hand 5 · ring 0.8 · position 5 · guitar reset
1424
+ strum G D Em C folk chords (symbols or roman numerals), one bar each
1425
+ strum I V vi IV strokes D-DU-UDU speed 30ms each 2 at 4
1426
+ strum strum the block chords already on the track
1427
+ /chords perform guitar play mode chords strum on the fretboard; [ ] change speed
1428
+ ```
1429
+
1430
+ - **Voicer.** Each chord is fitted to the strings within `hand` frets (default 4) above the capo, preferring open strings (`ring` 0 closed shapes .. 1 ringing open strings) and the `position` fret. A barre never lies over a string that plays open, the bass is the chord's root or slash bass, and when a chord has more notes than strings fit it drops the fifth, then the 11th, then the 9th, as guitarists do. Every one of the 96 common shapes (12 roots × 8 qualities) is playable within 4 frets in standard tuning.
1431
+ - **Tunings** (`GUITAR_TUNINGS`): `standard dropd doubledropd dadgad openg opend opene halfdown nashville bass ukulele requinto`, or any 3..12 open-string notes. The capo moves every string up.
1432
+ - **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.
1433
+ - **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.
1434
+
1435
+ 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 })`.
1436
+
1122
1437
  ### Chord mode
1123
1438
 
1124
- 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.
1439
+ 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.
1125
1440
 
1126
1441
  - `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.
1127
1442
  - `manual`: note keys play single notes as before; latch a chord type or extension and they play that chord on the pressed root.
@@ -1129,22 +1444,23 @@ Play mode has a chord sub-mode modelled on the Orchid's Key mode. It is `auto` b
1129
1444
 
1130
1445
  Terminals send no key releases, so the Orchid's held left-hand buttons are latches here: press once to latch, again to release, `0` clears them all.
1131
1446
 
1132
- | Key | Does (chord mode on) |
1133
- | --------- | ------------------------------------------------------------------------------- |
1134
- | `Q` | auto ⇄ manual |
1135
- | `1 2 3 4` | latch chord type dim / min / maj / sus (two latched make a combined chord) |
1136
- | `5 6 7 8` | latch extension 6 / m7 / M7 / 9 (any number; on top of the type or auto chord) |
1137
- | `0` | clear every latch |
1138
- | `-` / `=` | voicing dial down / up (-12..12; walks inversions) |
1139
- | `9` | next perform mode (block, strum-up, strum-down, arp-up, …, harp, slop, pattern) |
1140
- | `B` | next bass mode: off, chords, unison, single, solo (bass in C2–B2) |
1141
- | `N` | play the suggested next chord (the `next` chord in the header) |
1447
+ | Key | Does (chord mode on) |
1448
+ | --------- | --------------------------------------------------------------------------------------- |
1449
+ | `Q` | auto ⇄ manual |
1450
+ | `1 2 3 4` | latch chord type dim / min / maj / sus (two latched make a combined chord) |
1451
+ | `5 6 7 8` | latch extension 6 / m7 / M7 / 9 (any number; on top of the type or auto chord) |
1452
+ | `0` | clear every latch |
1453
+ | `-` / `=` | voicing dial down / up (-12..12; walks inversions) |
1454
+ | `9` | next perform mode (block, strum-up, strum-down, arp-up, …, harp, slop, pattern, guitar) |
1455
+ | `[` / `]` | perform guitar: strum slower / faster (5 ms steps, 0..200 ms) |
1456
+ | `B` | next bass mode: off, chords, unison, single, solo (bass in C2–B2) |
1457
+ | `N` | play the suggested next chord (the `next` chord in the header) |
1142
1458
 
1143
1459
  The header gains `AUTO C major · Dm (ii) · next G`: mode, key (`(assumed)` when the score has none and C major is used), the last chord with its numeral, and the suggested next chord. A legend row under the keyboard strip lists the number-row latches (`1 dim 2 min 3 maj 4 sus 5 6 6 m7 7 M7 8 9 0 clear -= voicing 0 9 block b bass off n next q auto`), with latched ones lit; at 80 columns the row ends where it fits. The full chord state is in the `?` panel, in the header's words: `chords AUTO C major · Dm (ii) · next G · min+m7 · voicing +1 · arp-up · bass chords`. The suggestion comes from the progression engine: the next chord of the chosen preset when the last chord is in it, otherwise a seeded step of the style's transition graph.
1144
1460
 
1145
1461
  Each chord is voice-led from the previous one and sounds through the live voice path. Recording quantizes the press like a note and lays the chord out with the perform mode over its held length (arpeggios at `rate`, `grid` by default; patterns from the press's quantized start), plus the bass note. Under `unison`, `single` and `solo` a single note in manual mode also records its bass (and, for `unison`, the note itself); `solo` records chords as bass only; each bar is still one revision and one undo step.
1146
1462
 
1147
- `/chords` with no argument prints the settings; `/chords auto|manual|off`, `voicing <n>`, `spread close|open|wide`, `bass off|chords|unison|single|solo` (`on` means `chords`), `sevenths on|off`, `perform <mode>`, `pattern <1..13|name>` (also selects the pattern perform mode), `rate grid|1/4|1/8|1/16|1/32`, `octaves 1..4`, `preset <name>|none`, `style pop|jazz|modal|classical`. `key <tonic> <mode>` (`key A minor`, `key F# dorian`, `key none`) sets the song key as one score edit. The same settings and the key are in `/menu` under Chords.
1463
+ `/chords` with no argument prints the settings; `/chords auto|manual|off`, `voicing <n>`, `spread close|open|wide`, `bass off|chords|unison|single|solo` (`on` means `chords`), `sevenths on|off`, `perform <mode>`, `pattern <1..13|name>` (also selects the pattern perform mode), `rate grid|1/4|1/8|1/16|1/32`, `octaves 1..4`, `preset <name>|none`, `style pop|jazz|modal|classical`, and for the guitar perform mode `strokes <name|grid>` and `speed <ms|beats>` (`speed 30ms`, `speed 1/32b`). `key <tonic> <mode>` (`key A minor`, `key F# dorian`, `key none`) sets the song key as one score edit. The same settings and the key are in `/menu` under Chords.
1148
1464
 
1149
1465
  The base octave follows the instrument: C3 (MIDI 48) by default, C2 for bass instruments or tracks named bass, C4 for saw/square/triangle/pluck leads. Kits start at C2, so `A` is the GM kick, `S` the snare, `T` the closed hat. On a one-shot sampler track the keys walk the voices in name order from slot 36 (`A` the first voice, `W` the second, chromatically), and the strip shows voice names; a keyed sampler starts at the C below its lowest root and repitches from it.
1150
1466
 
@@ -1178,7 +1494,7 @@ meter 7/8 at bar 5 meter change on a bar line, lasting until the next
1178
1494
  meter remove bar 5 | meter clear
1179
1495
  track rate 3/2 polytempo: the focused track plays at 1.5× the song tempo (0.125..8 or a/b)
1180
1496
  track phase 0.5 start the track half a beat later
1181
- track cycle 3 polymeter: loop the track's first 3 beats against the song's bars
1497
+ track loop 3 polymeter: loop the track's first 3 beats against the song's bars
1182
1498
  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)
1183
1499
  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)
1184
1500
  track time off follow the song again
@@ -1191,9 +1507,9 @@ track time off follow the song again
1191
1507
  - **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.
1192
1508
  - **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.
1193
1509
 
1194
- 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**).
1510
+ 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**).
1195
1511
 
1196
- ## Drum patterns and kits
1512
+ ## Grooves and kits
1197
1513
 
1198
1514
  **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`.
1199
1515
 
@@ -1206,7 +1522,7 @@ The menu has the same controls under **Project › Tempo & meter** (`/menu tempo
1206
1522
 
1207
1523
  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.
1208
1524
 
1209
- 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.
1525
+ 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.
1210
1526
 
1211
1527
  **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.
1212
1528
 
@@ -1220,7 +1536,7 @@ Patterns: `house`, `disco`, `techno`, `minimal`, `electro`, `breakbeat`, `amen-s
1220
1536
  | `electro` | tight short kick, clicky rim, ticking hats (alias `minimal`) |
1221
1537
  | `trap` | distorted long 808, crisp hats, high snare |
1222
1538
 
1223
- 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.
1539
+ 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.
1224
1540
 
1225
1541
  ## Arrange (sections and form)
1226
1542
 
@@ -1248,7 +1564,7 @@ Sections are markers over the timeline, like the arranger track in Studio One or
1248
1564
  | `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 |
1249
1565
  | `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 |
1250
1566
 
1251
- 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.
1567
+ 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.
1252
1568
 
1253
1569
  Generators write ordinary notes, tracks and automation, so everything they make can be edited or undone (one step per command):
1254
1570
 
@@ -1263,33 +1579,229 @@ Section mutes and variations govern every bar of their section: a note held from
1263
1579
 
1264
1580
  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).
1265
1581
 
1582
+ ## Styles
1583
+
1584
+ `/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`.
1585
+
1586
+ 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.
1587
+
1588
+ | Command | Does |
1589
+ | -------------------------------------------- | -------------------------------------------------------------------------------- |
1590
+ | `style` | the families and their root styles |
1591
+ | `style list [<id>]` | the children of a style |
1592
+ | `style search <words>` | ranked search over ids, names, aliases and regions |
1593
+ | `style info <id>` | path, meter, tempo, groove, tuning, harmony and roles |
1594
+ | `style <id> [bars] [seed]` | replace the song with one in that style (1 to 64 bars, default 8; one undo step) |
1595
+ | `style blend <a> <b> [weight] [bars] [seed]` | mix two cards; weight 0 is `a`, 1 is `b` (default 0.5) |
1596
+ | `style again` | the song's style with the next seed |
1597
+
1598
+ 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.
1599
+
1600
+ 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.
1601
+
1602
+ ## Voice
1603
+
1604
+ 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".
1605
+
1606
+ A track may store `clips`, `takes` and note `lyric`s (see docs/project-format.md). Projects without them sound exactly as before.
1607
+
1608
+ ### Clips and lyrics
1609
+
1610
+ 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.
1611
+
1612
+ - **`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.
1613
+ - **`/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).
1614
+ - **`/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`.
1615
+ - **`/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.
1616
+
1617
+ 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`.
1618
+
1619
+ 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:
1620
+
1621
+ ```ts
1622
+ track({
1623
+ id: "vox",
1624
+ instrument: "vocal",
1625
+ clips: [
1626
+ audio("tracks/vox/samples/verse.wav", { at: 8, gain: 0.7, fadeTime: 0.2 }),
1627
+ ...repeatAudio(audio("tracks/vox/samples/hey.wav", { at: 16 }), {
1628
+ every: 4,
1629
+ until: 32,
1630
+ }),
1631
+ ],
1632
+ notes: lyrics("hel-lo _ world", [
1633
+ note("C4", 8),
1634
+ note("D4", 9),
1635
+ note("E4", 10),
1636
+ ]),
1637
+ });
1638
+ ```
1639
+
1640
+ `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.
1641
+
1642
+ ### Formant shift and vowel morph
1643
+
1644
+ 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`.
1645
+
1646
+ | Knob | Range | Default | Lane | Does |
1647
+ | ---------------- | ----------- | ------- | --------------- | --------------------------------------------------------------------------- |
1648
+ | formant shift | -12..12 st | 0 | `formant-shift` | moves the spectral envelope; negative is deeper or bigger, positive smaller |
1649
+ | formant mix | 0..1 | 1 | `formant-mix` | blends the shifted and the dry signal |
1650
+ | vowel morph (to) | 0..1 (to v) | 0 | `vowel-morph` | glides the vowel filter's five formants from `vowel` toward `to` (log Hz) |
1651
+
1652
+ ```text
1653
+ /formant -4 deeper (pitch stays); /formant 3 0.5 is smaller at half mix
1654
+ /formant giant presets deep giant bright tiny; /formant off removes it
1655
+ /vowel a o 0.5 vowel filter halfway from a to o; /vowel morph 0.8, /vowel to u
1656
+ /vowel to off back to one vowel; changing the vowel keeps your mix
1657
+ /vocal formant -4 the same, under the voice umbrella
1658
+ automate formant-shift points 0:-6 8:6
1659
+ ```
1660
+
1661
+ 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.
1662
+
1663
+ 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`".
1664
+
1665
+ 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 } }`.
1666
+
1667
+ ### Singing voice
1668
+
1669
+ `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.
1670
+
1671
+ 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.
1672
+
1673
+ 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.
1674
+
1675
+ 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.
1676
+
1677
+ 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.
1678
+
1679
+ 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" })`.
1680
+
1681
+ ### Pitch
1682
+
1683
+ 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.
1684
+
1685
+ - `/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).
1686
+ - `/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.
1687
+ - `/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.
1688
+ - Ctrl-K › Voice › pitch shows the detected key and median, with Analyze, Trace and Make notes rows.
1689
+ - Agent tools: `analyze_pitch` (read-only: key, median, range and the note list in file seconds) and `pitch_to_notes`.
1690
+
1691
+ 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.
1692
+
1693
+ ### Vocoder
1694
+
1695
+ 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.
1696
+
1697
+ 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.
1698
+
1699
+ `/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.
1700
+
1701
+ | Command | Does |
1702
+ | -------------------------------------------- | -------------------------------------------------------------------------------------- |
1703
+ | `/vocoder [preset]` | vocode the focused vocal (a new carrier), or set the focused carrier's preset |
1704
+ | `/vocoder src <track>` | the modulator: a track id or name slug (`lead-vox`); preset words win over track names |
1705
+ | `/vocoder <param> <value>` / `<param> reset` | set or reset any parameter below (`att` and `rel` are Strudel spellings) |
1706
+ | `/vocoder reset` / `off` / `presets` | back to the preset's values; remove the vocoder; list presets |
1707
+ | `instrument vocoder` | the built-in carrier: saw, supersaw, pulse or noise following notes, chords or a drone |
1708
+
1709
+ 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).
1710
+
1711
+ 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.
1712
+
1713
+ | Parameter | Range | Default | Does |
1714
+ | ---------- | ----------------------------------- | --------- | ------------------------------------------------------------------------------------------------ |
1715
+ | `tap` | `chain`, `dry` | chain | listen to the source after its mono chain (before pan) or before its effects |
1716
+ | `mode` | `channel`, `talkbox` | channel | a band bank, or an LPC talkbox (order sr/2000, 20 ms frames) |
1717
+ | `carrier` | `saw`, `supersaw`, `pulse`, `noise` | supersaw | the built-in carrier (`instrument vocoder` only) |
1718
+ | `follow` | `notes`, `chords`, `drone` | notes | the built-in carrier's pitch: its notes, the song's chords, or `root` |
1719
+ | `root` | 24..96 | 45 | the drone pitch (MIDI), and the octave chords are voiced from |
1720
+ | `spread` | 0..1 st | 0.15 | supersaw detune |
1721
+ | `bands` | 4..40 | 16 | channel bands, spaced evenly in log frequency (heavy above 24) |
1722
+ | `lo`, `hi` | 50..1000 Hz, 2000..12000 Hz | 100, 8000 | the lowest and highest band centers |
1723
+ | `width` | 0.25..4 | 1 | band width as a multiple of the spacing |
1724
+ | `attack` | 0.0005..0.2 s | 0.005 | envelope follower attack |
1725
+ | `release` | 0.005..2 s | 0.04 | envelope follower release; long releases smear |
1726
+ | `formant` | ±24 st (talkbox ±12) | 0 | move the voice's formants: + is smaller and brighter |
1727
+ | `unvoiced` | 0..1 | 0.5 | noise in place of the carrier on s, f, sh and t |
1728
+ | `sens` | 0..1 | 0.5 | how readily a frame counts as unvoiced |
1729
+ | `hiss` | 0..1 | 0 | the source's top end passed straight through |
1730
+ | `gate` | -90..0 dBFS or `auto` | auto | silence below this source level; `auto` reads the source's noise floor |
1731
+ | `enhance` | on/off | on | whiten the carrier so every band speaks |
1732
+ | `depth` | 0..1 | 1 | how much the voice shapes the carrier |
1733
+ | `freeze` | on/off | off | hold the last sung vowel through every rest (a `vocoder-freeze` lane holds whatever is sounding) |
1734
+ | `mix` | 0..1 | 1 | wet against the plain carrier |
1735
+ | `gain` | ±24 dB | 0 | output trim (a fixed makeup gain and a soft peak guard at 1.0 come first) |
1736
+ | `seed` | integer | track id | the unvoiced noise seed |
1737
+
1738
+ 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.
1739
+
1740
+ 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).
1741
+
1742
+ 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.
1743
+
1744
+ ### Autotune
1745
+
1746
+ `/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:
1747
+
1748
+ | Preset | Sound |
1749
+ | --------- | --------------------------------------- |
1750
+ | `hard` | instant, stepped notes |
1751
+ | `robot` | stepped on every step of the tuning |
1752
+ | `warble` | hard with wide synthetic vibrato |
1753
+ | `trap` | fast and glossy |
1754
+ | `pop` | polished but sung (the default) |
1755
+ | `natural` | keeps scoops and vibrato |
1756
+ | `gentle` | barely there |
1757
+ | `guided` | notes to a written melody, vibrato kept |
1758
+ | `locked` | hard tune locked to a melody |
1759
+
1760
+ 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.
1761
+
1762
+ 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.
1763
+
1764
+ 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`.
1765
+
1766
+ 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.
1767
+
1266
1768
  ## Menus
1267
1769
 
1268
- `/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.
1770
+ `/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.
1771
+
1772
+ | Section | Rows (most used first) |
1773
+ | -------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
1774
+ | 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) |
1775
+ | 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** |
1776
+ | 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 |
1777
+ | Rhythm | the **euclid editor** (`/euclid`), **grooves** (`/pattern`), **kits** (`/kit`, synth then samples), **grid** |
1778
+ | 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** |
1779
+ | Mix | the focused track's name, mute, solo, volume, pan; **all tracks** (choosing one focuses it); **automation** (each lane with its points as `beat N value` rows, add points, ramp, clear lane); **master** |
1780
+ | 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) |
1781
+ | 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) |
1269
1782
 
1270
- | Section | Rows (most used first) |
1271
- | ---------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
1272
- | 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, velocity curve, humanize); **browse sounds** (instruments, wavetables, Granular, packs) |
1273
- | 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) |
1274
- | Rhythm | the euclid editor (`/euclid`), drum patterns (`/pattern`), drum kits (`/kit`, synth then samples) |
1275
- | Chords | play-mode chord mode, key tonic and mode, voicing, spread, bass, sevenths, perform, pattern, arp rate, arp octaves, progression, style |
1276
- | 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 |
1277
- | Project | play, tempo, beats per bar, loop length, grid, click, count-in bars |
1278
- | 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) |
1783
+ 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.
1784
+
1785
+ `/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.
1279
1786
 
1280
1787
  Every list, picker and editor uses the same keys (see **Keys** below). In the menu:
1281
1788
 
1282
- | Key | Does |
1283
- | --------------------------- | ------------------------------------------------------------------------- |
1284
- | `↑` `↓` / `k` `j` | move |
1285
- | `Enter` / `→` / `l` | open a section, pick from a list, or open a number's fader drawer |
1286
- | `←` `→` / `h` `l` / `-` `+` | adjust a value by its step (cutoff moves 25%) or cycle a choice |
1287
- | `Space` | toggle on/off; elsewhere, hear the focused track (see Previewing changes) |
1288
- | digits | type a value; `Enter` stages it in the fader drawer, `Esc` cancels |
1289
- | `/` | filter the current list by name, value or command |
1290
- | `x` / `Delete` | reset the focused value to its default; on an automation point, remove it |
1291
- | `Esc` / `←` / `h` | clear the filter, then back one level, then close |
1292
- | `?` | the keys for this screen |
1789
+ | Key | Does |
1790
+ | -------------------- | ---------------------------------------------------------------------------------------- |
1791
+ | `↑` `↓` / `k` `j` | move |
1792
+ | `Enter` | open or confirm: open a section, run an action, open a number's fader, keep staged edits |
1793
+ | `→` / `l` | go in; on a value row, adjust up |
1794
+ | `←` / `h` | back one level; on a value row, adjust down |
1795
+ | `-` `+` | adjust a value by its step (cutoff moves 25%) or cycle a choice |
1796
+ | `Tab` / `Shift-Tab` | next / previous field (in the fader and the rhythm editor) |
1797
+ | `Space` | toggle on/off; elsewhere, hear the focused track (see Previewing changes) |
1798
+ | `0-9` `.` | type a value; `Enter` stages it in the fader drawer, `Esc` cancels |
1799
+ | `/` | filter the current list by name, value or command |
1800
+ | `x` / `d` / `Delete` | reset the focused value to its default; on an automation point, remove it |
1801
+ | `Esc` | revert staged edits, else clear the filter, then back one level, then close |
1802
+ | `?` | the keys for this screen, each key listed once |
1803
+
1804
+ 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.
1293
1805
 
1294
1806
  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.
1295
1807
 
@@ -1298,7 +1810,7 @@ Automation rows take `beat:value` pairs (`2:800` or `0:200 4:8000`); a ramp is t
1298
1810
  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.
1299
1811
 
1300
1812
  ```text
1301
- ╭─ menu › Effects › Filter ─────────── loop off · B staged 1 · ● staged [keep] [revert]─╮
1813
+ ╭─ menu › Effects › filter ──── loop off · A/B: 1 change staged · enter keep · esc revert ─╮
1302
1814
  │ type lpf │ hpf │ bpf │
1303
1815
  │ │
1304
1816
  │ › cutoff 1200 Hz ← 800 Hz 20 Hz … 20000 Hz │
@@ -1314,36 +1826,38 @@ Every change is staged on the audition loop (see **Previewing changes**), filed
1314
1826
  | --------------------------- | ----------------------------------------------------- |
1315
1827
  | `←` `→` / `-` `+` / `h` `l` | step by the param's step |
1316
1828
  | `Shift`-`←` `→` / `{` `}` | coarse step (five steps) |
1317
- | `[` `]` / `Alt`-`←` `→` | fine step (a tenth of a step) |
1829
+ | `[` `]` / `Alt`-`←` `→` | fine step (a tenth of a step; skips detents) |
1318
1830
  | `PgUp` `PgDn` | big step (twenty) |
1319
1831
  | `Home` `End` | minimum / maximum |
1320
- | `1`-`9` `.` | type an exact value; `Enter` sets it, `Esc` cancels |
1321
- | `0` / `d` | back to the default |
1832
+ | `0`-`9` `.` | type an exact value; `Enter` sets it, `Esc` cancels |
1833
+ | `x` / `d` / `Delete` | back to the default |
1322
1834
  | `↑` `↓` / `Tab` `Shift-Tab` | previous / next param of this device |
1323
1835
  | `Enter` | keep every staged change (one undo step); none: close |
1324
1836
  | `Esc` | revert staged changes and close |
1325
1837
  | `Space` `a` `c` | audition loop · A/B · solo ↔ in context |
1326
1838
  | `?` | these keys |
1327
1839
 
1840
+ 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.
1841
+
1328
1842
  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).
1329
1843
 
1330
1844
  ## Mouse
1331
1845
 
1332
1846
  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.
1333
1847
 
1334
- | Where | Click / wheel |
1335
- | ------------------- | -------------------------------------------------------------------- |
1336
- | fader `[−]` `[+]` | step (shift-click: coarse) |
1337
- | fader bar | set the value at that point; drag to slide it (past the ends clamps) |
1338
- | fader option | choose it |
1339
- | fader name | focus that field |
1340
- | `[keep]` `[revert]` | the same as `Enter` / `Esc` |
1341
- | wheel on a fader | step it (up raises; shift: coarse) |
1342
- | list / menu row | select it; a click on the selected row opens it (`Enter`) |
1343
- | wheel on a list | move through it |
1344
- | header `▶/⏸ BPM` | play / pause |
1345
- | header track name | the track list (`/tracks`); click a track to focus it |
1346
- | header model | the model picker |
1848
+ | Where | Click / wheel |
1849
+ | ------------------------------- | -------------------------------------------------------------------- |
1850
+ | fader `[−]` `[+]` | step (shift-click: coarse) |
1851
+ | fader bar | set the value at that point; drag to slide it (past the ends clamps) |
1852
+ | fader option | choose it |
1853
+ | fader name | focus that field |
1854
+ | badge `enter keep` `esc revert` | the same as `Enter` / `Esc` |
1855
+ | wheel on a fader | step it (up raises; shift: coarse) |
1856
+ | list / menu row | select it; a click on the selected row opens it (`Enter`) |
1857
+ | wheel on a list | move through it |
1858
+ | header `▶/⏸ BPM` | play / pause |
1859
+ | header track name | the track list (`/tracks`); click a track to focus it |
1860
+ | header model | the model picker |
1347
1861
 
1348
1862
  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.
1349
1863
 
@@ -1351,7 +1865,7 @@ Hit-testing uses the same paint pass that draws the frame: each painter records
1351
1865
 
1352
1866
  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.
1353
1867
 
1354
- 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.
1868
+ 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.
1355
1869
 
1356
1870
  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`).
1357
1871
 
@@ -1369,7 +1883,7 @@ Kept changes are one `ScoreOperation` (`preview.commit`, listing the commands),
1369
1883
 
1370
1884
  **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.
1371
1885
 
1372
- **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.
1886
+ **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.
1373
1887
 
1374
1888
  | Key in a list | While auditioning |
1375
1889
  | ------------- | --------------------------------------------------- |
@@ -1383,7 +1897,7 @@ Kept changes are one `ScoreOperation` (`preview.commit`, listing the commands),
1383
1897
  **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:
1384
1898
 
1385
1899
  ```text
1386
- │ cutoff (lpf/hpf) or centre (bpf) frequency ▇▇▇▇▇▇▇▇▇▇▇▅▂▁▁▁ › fx filt… │
1900
+ │ cutoff (lpf/hpf) or center (bpf) frequency ▇▇▇▇▇▇▇▇▇▇▇▅▂▁▁▁ › fx filt… │
1387
1901
  ```
1388
1902
 
1389
1903
  **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.