@hraness/dawg 0.5.0 → 0.6.1

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 (124) hide show
  1. package/CHANGELOG.md +63 -0
  2. package/DAWG.md +592 -49
  3. package/README.md +4 -4
  4. package/core/chords.ts +492 -4
  5. package/core/diff.ts +9 -0
  6. package/core/expression.ts +143 -6
  7. package/core/fx.ts +689 -2
  8. package/core/granular.ts +619 -0
  9. package/core/instruments.ts +442 -0
  10. package/core/keys.ts +955 -0
  11. package/core/resonators.ts +862 -0
  12. package/core/score.ts +529 -8
  13. package/core/sdk/eval-child.ts +2 -0
  14. package/core/sdk/print.ts +247 -16
  15. package/core/sdk/sync-instruments.ts +58 -0
  16. package/core/sdk/v1.ts +2417 -40
  17. package/core/sections.ts +44 -10
  18. package/core/strings.ts +1080 -0
  19. package/core/winds.ts +652 -0
  20. package/guides/automation.md +1 -0
  21. package/guides/chords.md +3 -1
  22. package/guides/effects.md +5 -2
  23. package/guides/media.md +1 -1
  24. package/guides/performance.md +3 -0
  25. package/guides/resample.md +26 -0
  26. package/guides/sounds.md +7 -2
  27. package/guides/tempo.md +1 -0
  28. package/package.json +1 -1
  29. package/src/agent/agent.ts +13 -0
  30. package/src/agent/chord-tools.ts +153 -1
  31. package/src/agent/expression-tools.ts +91 -0
  32. package/src/agent/granular-tools.ts +138 -0
  33. package/src/agent/models.ts +4 -4
  34. package/src/agent/ops.ts +42 -2
  35. package/src/agent/preview-tool.ts +7 -1
  36. package/src/agent/resample-tool.ts +131 -0
  37. package/src/agent/tools.ts +726 -17
  38. package/src/agent/xcb-agent.ts +13 -0
  39. package/src/audio/arrange.ts +42 -3
  40. package/src/audio/dsp/bank.ts +233 -0
  41. package/src/audio/dsp/envelope.ts +161 -0
  42. package/src/audio/dsp/fft.ts +6 -0
  43. package/src/audio/dsp/filters.ts +57 -0
  44. package/src/audio/dsp/interp.ts +112 -0
  45. package/src/audio/dsp/modal.ts +684 -0
  46. package/src/audio/dsp/onset.ts +197 -0
  47. package/src/audio/dsp/oversample.ts +202 -0
  48. package/src/audio/dsp/rng.ts +37 -0
  49. package/src/audio/dsp/shape.ts +81 -0
  50. package/src/audio/dsp/shift.ts +256 -0
  51. package/src/audio/dsp/stft.ts +73 -0
  52. package/src/audio/dsp/window.ts +77 -0
  53. package/src/audio/effects/chain.ts +23 -3
  54. package/src/audio/effects/common.ts +14 -0
  55. package/src/audio/effects/convolution.ts +106 -1
  56. package/src/audio/effects/gaze.ts +354 -0
  57. package/src/audio/effects/rig/cab.ts +99 -0
  58. package/src/audio/effects/rig/filters.ts +152 -0
  59. package/src/audio/effects/rig/gate.ts +39 -0
  60. package/src/audio/effects/rig/head.ts +487 -0
  61. package/src/audio/effects/rig/index.ts +170 -0
  62. package/src/audio/effects/rig/section.ts +78 -0
  63. package/src/audio/effects/rig/stomp.ts +238 -0
  64. package/src/audio/engine.ts +56 -6
  65. package/src/audio/fit.ts +447 -0
  66. package/src/audio/granular.ts +995 -0
  67. package/src/audio/instrument-check.ts +137 -0
  68. package/src/audio/instruments.ts +140 -0
  69. package/src/audio/keys/dsp.ts +323 -0
  70. package/src/audio/keys/electric.ts +420 -0
  71. package/src/audio/keys/engine.ts +485 -0
  72. package/src/audio/keys/organ.ts +1335 -0
  73. package/src/audio/keys/piano.ts +476 -0
  74. package/src/audio/keys/sympathetic.ts +127 -0
  75. package/src/audio/live-worker.ts +56 -0
  76. package/src/audio/live.ts +361 -15
  77. package/src/audio/loudness.ts +65 -20
  78. package/src/audio/preview.ts +17 -1
  79. package/src/audio/resample.ts +285 -0
  80. package/src/audio/resonators.ts +295 -0
  81. package/src/audio/sampler.ts +328 -35
  82. package/src/audio/samples.ts +56 -8
  83. package/src/audio/strings/body.ts +263 -0
  84. package/src/audio/strings/bow.ts +656 -0
  85. package/src/audio/strings/engine.ts +615 -0
  86. package/src/audio/strings/loop.ts +119 -0
  87. package/src/audio/strings/measure.test-helpers.ts +200 -0
  88. package/src/audio/strings/pluck.ts +354 -0
  89. package/src/audio/warp.ts +61 -0
  90. package/src/audio/wav.ts +191 -19
  91. package/src/audio/winds/engine.ts +302 -0
  92. package/src/audio/winds/filters.ts +153 -0
  93. package/src/audio/winds/pitch.ts +104 -0
  94. package/src/audio/winds/trim.ts +80 -0
  95. package/src/audio/winds/trims.ts +917 -0
  96. package/src/audio/winds/voice.ts +479 -0
  97. package/src/commands/expression.ts +111 -23
  98. package/src/commands/fit.ts +135 -0
  99. package/src/commands/fx.ts +35 -1
  100. package/src/commands/granular.ts +432 -0
  101. package/src/commands/help.ts +159 -5
  102. package/src/commands/keys.ts +629 -0
  103. package/src/commands/modal.ts +310 -0
  104. package/src/commands/resample.ts +281 -0
  105. package/src/commands/rig.ts +264 -0
  106. package/src/commands/sample.ts +52 -2
  107. package/src/commands/shift.ts +119 -0
  108. package/src/commands/string.ts +201 -0
  109. package/src/commands/strum.ts +473 -0
  110. package/src/commands/time.ts +4 -1
  111. package/src/commands/wind.ts +244 -0
  112. package/src/main.ts +339 -15
  113. package/src/project/check.ts +17 -0
  114. package/src/render.ts +49 -4
  115. package/src/session/presence.ts +16 -3
  116. package/src/tui/audition.ts +2 -2
  117. package/src/tui/granular-menu.ts +278 -0
  118. package/src/tui/menu.ts +934 -9
  119. package/src/tui/modal-menu.ts +145 -0
  120. package/src/tui/performance-menu.ts +42 -0
  121. package/src/tui/play-chords.ts +104 -3
  122. package/src/tui/play-mode.ts +1 -0
  123. package/src/tui/play-session.ts +70 -2
  124. package/src/tui/wind-menu.ts +144 -0
package/DAWG.md CHANGED
@@ -180,7 +180,7 @@ Every subprocess goes through the injectable `CommandRunner` in `src/auth/runner
180
180
 
181
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.
182
182
 
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` (≈ 22,100 input + 600 output tokens, measured from the system prompt, tool schemas and a fixture brief over about 2 requests) × price.
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.
184
184
 
185
185
  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
186
 
@@ -235,7 +235,7 @@ Score format. The score stays `track.loop/v1` with `version: 1`: every addition
235
235
  Every track has one fixed effects chain (`FX_CHAIN` in `core/fx.ts`, DSP in `src/audio/effects/`):
236
236
 
237
237
  ```text
238
- filter → djf → autofilter → vowel → crush → distort → tremolo → compressor → pan → phaser → chorus → leslie → postgain → delay → reverb → [mix: orbit → duck]
238
+ filter → djf → autofilter → vowel → crush → distort → stomp → head → cab → tremolo → compressor → pan → phaser → chorus → leslie → postgain → delay → reverb → [mix: orbit → duck]
239
239
  ```
240
240
 
241
241
  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.
@@ -332,7 +332,7 @@ Parameters (**bold** effect = shown in the simple menu; Lane = automation lane):
332
332
  | reverb | lowpass (optional) | 200..20000 Hz | 8000 | `roomlp`, `rlp` | |
333
333
  | reverb | dim (optional) | 200..20000 Hz | 3000 | `roomdim`, `rdim` | |
334
334
  | 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` | |
335
+ | reverb | ir (optional) | `builtin:room\|hall\|plate\|reverse\|gate\|spring`, pack sound or project WAV | off | `iresponse`, `ir` | |
336
336
  | orbit | orbit | 1..16 (integer) | 2 | `orbit`, `o` | |
337
337
  | orbit | shared (optional) | on/off | off | | |
338
338
  | duck | orbit | 1..16 (integer) | 1 | `duckorbit`, `duck` | |
@@ -342,6 +342,86 @@ Parameters (**bold** effect = shown in the simple menu; Lane = automation lane):
342
342
 
343
343
  Strudel mapping notes: Strudel's `lpf`/`hpf`/`bpf` each set a separate filter; dawg has one track filter whose `type` selects the response, so `lpf(800)` is `filter {type: "lpf", cutoff: 800}` and `lpq`/`hpq`/`bpq` map to `resonance`. `delay` in Strudel is the wet level (dawg `delay.mix`), `delaytime` is seconds (dawg `delay.time`; `beats` is the tempo-synced form), `delayfeedback` is `delay.feedback`. `room` is `reverb.mix`, `size`/`roomsize` is `reverb.size`, `roomfade`/`roomlp`/`roomdim` are `fade`/`lowpass`/`dim`. `distort` and `shape` are the distortion drive with `type: "shape"` for Strudel's `shape` curve; `crush` is bits and `coarse` is the sample-hold factor. `phaser`/`phaserdepth`/`phasercenter`/`phasersweep`, `tremolo*`, `leslie`/`lrate`/`lsize`, `postgain` and `compressor` keep their names. `orbit` groups tracks for `duckorbit`/`duckdepth`/`duckattack`/`duckonset` sidechaining; By default each track keeps its own delay and reverb; `fx orbit 2 shared on` makes the track send to its orbit's one shared delay and reverb, as Strudel orbits do (see **Orbit buses**). `iresponse`/`ir` is `reverb.ir`.
344
344
 
345
+ ### Guitar rig (stomp, head, cab)
346
+
347
+ A guitar rig is three effects in the chain after `distort` (`src/audio/effects/rig/`): a **stomp** box, an amp **head** with an optional noise gate, and a speaker **cab**inet. They run once per track on the mono voice sum, never per voice, and each nonlinear section has its own half-band oversampler (4x at 22.05 kHz, 2x at 44.1/48 kHz) with antiderivative-antialiased shapers, so a 1 kHz fuzz keeps its aliases at or below -60 dB in the audible band. A full rig costs about 15-25 ms per track-second.
348
+
349
+ ```text
350
+ rig crunch a whole rig: stomp + head + cab (one undo step)
351
+ rig show the focused track's rig
352
+ rig reset remove all three stages
353
+ stomp fuzz | stomp gain 7 head lead | head treble 7 gate -55 | cab 4x12 | cab mic 0.6
354
+ 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
356
+ ```
357
+
358
+ - **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
+ - **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.
361
+
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.
363
+
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).
365
+
366
+ | Effect | Param | Range | Default | Lane |
367
+ | --------- | --------------- | --------------------------------------------------- | ------- | ------------ |
368
+ | **stomp** | type | fuzz / face / od / rat / octave | od | |
369
+ | **stomp** | gain | 0..10 | 5 | `stomp-gain` |
370
+ | **stomp** | tone | 0..1 | 0.5 | `stomp-tone` |
371
+ | **stomp** | level | -24..12 dB | 0 | |
372
+ | stomp | octave | 0..1 | 0.7 | |
373
+ | stomp | mix | 0..1 | 1 | |
374
+ | **head** | type | clean / chime / crunch / lead / high / solid / bass | crunch | |
375
+ | **head** | gain | 0..10 | 5 | `head-gain` |
376
+ | **head** | bass | 0..10 | 5 | |
377
+ | **head** | mid | 0..10 | 5 | |
378
+ | **head** | treble | 0..10 | 5 | |
379
+ | head | presence | 0..10 | 5 | |
380
+ | head | master | 0..10 | 5 | |
381
+ | head | sag (optional) | 0..1 | 0.3 | |
382
+ | head | gate (optional) | -96..0 dB | off | |
383
+ | head | level | -24..12 dB | 0 | |
384
+ | **cab** | type | 1x12 / 2x12 / 4x12 / 1x10 / open / 8x10 / 1x15 / di | 2x12 | |
385
+ | **cab** | mic | 0..1 | 0.3 | |
386
+ | **cab** | mix | 0..1 | 1 | |
387
+
388
+ ### Shoegaze (wobble, bloom, swell, double) (0.6.1)
389
+
390
+ 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.
391
+
392
+ ```text
393
+ fx wobble held tremolo arm: seeded pitch wow and flutter (depth 20 c)
394
+ fx wobble depth 35 rate 0.4 drift 0.5
395
+ fx bloom feedback: the top held note grows a singing harmonic
396
+ fx swell time 0.5 volume swell: each strum fades in, no pick attack
397
+ fx double a second, seeded take a few ms late, spread left and right
398
+ rig shoegaze fuzz + chime head + wobble + bloom + double + a big reverb
399
+ fx reverb ir builtin:reverse reverse-gate swell after the attack, not a pre-verb (also builtin:gate and builtin:spring)
400
+ track shoegaze a new electric guitar track with the shoegaze rig
401
+ ```
402
+
403
+ - **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.
404
+ - **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.
405
+ - **swell** (`time` s, `mix`) restarts a raised-cosine fade at each note onset: a volume-pedal swell without the pick.
406
+ - **double** (`time` 5..60 ms, `drift` ms, `width`) is automatic double tracking: a second take through a modulated delay whose timing wanders by `drift`, panned against the dry take. Left/right correlation stays between 0.3 and 0.8.
407
+
408
+ Rig presets gain `shoegaze glide dreampop swell ebow` (the `swell` rig is the swell effect plus a clean amp and a room; `ebow` is swell plus a bloom on the note itself). `jangle` now also writes a light `double`; a project that loaded jangle before 0.6.1 keeps its stored stages and renders unchanged until `rig jangle` is applied again. In `song.ts`, `rig("jangle")` and `instrument: "jangle"` keep the 0.6.0 stages (no double) so existing files render unchanged; add `double: {}` for the 0.6.1 sound. `instrument: "shoegaze"`, `"dreampop"` and `"ebow"` (the rig names that are also instrument words; `glide` and `swell` are reached with `rig glide`/`rig swell` or `fx: rig("glide")`) also set the rig's reverb wash, like `track shoegaze`; `rig()` returns effects only, so pair it with `reverb: rigReverb("shoegaze")`. The rigs carry a `postgain` trim so the shoegaze rigs land within 2.5 dB of `clean` on a held chord, and `shoegaze`, `glide` and `dreampop` also on a dense strum. `swell` and `ebow` restart their fade on every onset, so a fast strum through them sits several dB lower; they are made for held notes and slow chords. The built-in impulses `reverse` (energy rising to a hard stop: a reverse-gate swell that rises after each attack, not a pre-verb that swells into the note; no rig preset uses it, so reach it with `fx reverb ir builtin:reverse`), `gate` (a dense, flat 250 ms burst cut short) and `spring` (a dispersive, chirping tank) are generated from seeded noise like `room hall plate`; no recorded IR ships. Menu: **Effects › Shoegaze** and the rig preset row in **Effects › Guitar rig**. Agent: `set_fx` and `set_rig`. SDK: `fx: { ...rig("shoegaze") }` or `fx: { wobble: { depth: 30 } }`. A full shoegaze rig costs under 25 ms per track-second.
409
+
410
+ | Effect | Param | Range | Default | Lane |
411
+ | ---------- | ------ | ------------ | ------- | -------------- |
412
+ | **wobble** | depth | 0..100 cents | 20 | `wobble-depth` |
413
+ | **wobble** | rate | 0.05..8 Hz | 0.5 | |
414
+ | **wobble** | drift | 0..1 | 0.3 | |
415
+ | **bloom** | amount | 0..1 | 0.5 | |
416
+ | **bloom** | harm | 1..4 | 2 | |
417
+ | **bloom** | delay | 0..4 s | 0.6 | |
418
+ | bloom | time | 0.05..4 s | 1 | |
419
+ | **swell** | time | 0.01..4 s | 0.4 | |
420
+ | **swell** | mix | 0..1 | 1 | `swell-mix` |
421
+ | **double** | time | 5..60 ms | 22 | |
422
+ | **double** | drift | 0..10 ms | 3 | |
423
+ | **double** | width | 0..1 | 0.6 | |
424
+
345
425
  ## Master and loudness
346
426
 
347
427
  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.
@@ -398,7 +478,7 @@ Parameters (**bold** unit = shown in the simple menu; the rest are under `advanc
398
478
 
399
479
  <!-- master-params:end -->
400
480
 
401
- `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).
481
+ A song with a master renders at 48 kHz; a song without one keeps the engine's 22,050 Hz, as dawg 0.4 wrote it, so older projects export byte-identically. `--rate 48000` (or 44100) picks the rate for any render. `dawg render out.wav --normalize streaming` (or a LUFS number) applies a target for that export only, without changing the song; `--measure` prints the loudness line after any render. When samples hit 16-bit full scale, `dawg render` prints a `warning · N samples clip` line on stderr with the fix (lower volumes, or a master with a limiter). While the loop plays, the header shows `-14.1 LUFS (-14) · TP -1.0` for the last rendered loop, with or without a master, so you can read a mix before mastering it. The bracket is the target, and the reading turns to a warning colour when it is more than 0.5 LU off the target or the true peak passes the limiter's ceiling (-1 dBTP without one). With a master the loop monitors at the engine's 22,050 Hz while exports and `master measure` run at 48 kHz; the header shows the 48 kHz reading once it is measured in the background (a `~` marks the monitor estimate until then). EQ above about 9.9 kHz is not audible while monitoring but is in the export. The menu has it under **Mix & automation › master** (`/menu master`), where `Space` stages master changes for A/B like other sound edits. The agent has `set_master` and `measure_mix` (integrated, short-term and momentary LUFS, loudness range, true peak, spectral balance in five bands (sub, bass, low-mid, high-mid, high) and stereo correlation, with `bypass_master` to compare); it masters only when asked and measures before and after. In the SDK: `song({ master: { glue: { ratio: 2 }, limiter: { ceiling: -1 }, target: -14 } })` (SDK 1.17.0).
402
482
 
403
483
  ## Synth
404
484
 
@@ -507,30 +587,180 @@ FM operators 2–8 repeat the `fm` rows with a suffix (`fm2`, `fmh2`, `fmattack2
507
587
 
508
588
  ### Strudel parity
509
589
 
510
- | Strudel | dawg | Status |
511
- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------- | ---------------------------------------------------- |
512
- | `s`/`sound` sine, sawtooth, square, triangle, supersaw, pulse, user, white, pink, brown, crackle | `instrument` | done |
513
- | `noise`, `density` | `synth.noise`, `synth.density` | done |
514
- | `unison`, `spread`, `detune` | `synth.*` | done |
515
- | `pw`, `pwrate`, `pwsweep` | `synth.*` | done |
516
- | `fm`/`fmi`, `fmh`, `fmattack/fmdecay/fmsustain/fmrelease`, `fmenv`, `fmwave`, operators 2–8 | `synth.*` | done |
517
- | `attack/decay/sustain/release`, `adsr`, `gain`, `velocity` | `synth.*`; `adsr` is command shorthand; velocity is the note's | done |
518
- | `penv`, `pattack/pdecay/psustain/prelease`, `pcurve`, `panchor` | `synth.*` | done |
519
- | `vib`/`vibrato`, `vibmod` | `synth.*` | done |
520
- | `lpf/hpf/bpf`, `lpq/hpq/bpq`, `lpenv/hpenv/bpenv` and their ADSRs, `ftype`, `fanchor` | `synth.*` (per voice); also the track `filter` effect | done |
521
- | `partials`, `phases` | `synth.partials`, `synth.phases` | done |
522
- | `vowel`, `coarse`, `crush`, `shape`, `distort`, `djf` | effects `vowel`, `crush`, `distort`, `djf` | done (see Effects) |
523
- | `phaser*`, `tremolo*`, `leslie`/`lrate`/`lsize`, `compressor*`, `postgain` | effects of the same names | done |
524
- | `room`, `size`, `roomfade`, `roomlp`, `roomdim` | `reverb` | done |
525
- | `delay`, `delaytime`, `delayfeedback` | `delay` | done |
526
- | `pan` | track `pan` | done |
527
- | `orbit`, `duckorbit`/`duckdepth`/`duckattack`/`duckonset` | effects `orbit` (+ `shared`), `duck` | done: `shared` sends to one delay + reverb per orbit |
528
- | `iresponse`/`ir` | `reverb.ir` | done: FFT convolution; built-ins or a pinned sample |
529
- | `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) |
530
- | zzfx `duration` | note length | done: a note's length is its duration |
531
- | raw `zzfx([...])` parameter array | `synth zzfx …`, SDK `zzfx([...])`, `set_synth {zzfx}` | done: ZzFX's documented layout → named controls |
532
- | soundfonts `gm_*`, drum banks, dirt-samples | sampler and sample packs | not this engine: hosted samples, see Sample packs |
533
- | 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) |
590
+ | Strudel | dawg | Status |
591
+ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -------------------------------------------------------------- | ---------------------------------------------------- |
592
+ | `s`/`sound` sine, sawtooth, square, triangle, supersaw, pulse, user, white, pink, brown, crackle | `instrument` | done |
593
+ | `noise`, `density` | `synth.noise`, `synth.density` | done |
594
+ | `unison`, `spread`, `detune` | `synth.*` | done |
595
+ | `pw`, `pwrate`, `pwsweep` | `synth.*` | done |
596
+ | `fm`/`fmi`, `fmh`, `fmattack/fmdecay/fmsustain/fmrelease`, `fmenv`, `fmwave`, operators 2–8 | `synth.*` | done |
597
+ | `attack/decay/sustain/release`, `adsr`, `gain`, `velocity` | `synth.*`; `adsr` is command shorthand; velocity is the note's | done |
598
+ | `penv`, `pattack/pdecay/psustain/prelease`, `pcurve`, `panchor` | `synth.*` | done |
599
+ | `vib`/`vibrato`, `vibmod` | `synth.*` | done |
600
+ | `lpf/hpf/bpf`, `lpq/hpq/bpq`, `lpenv/hpenv/bpenv` and their ADSRs, `ftype`, `fanchor` | `synth.*` (per voice); also the track `filter` effect | done |
601
+ | `partials`, `phases` | `synth.partials`, `synth.phases` | done |
602
+ | `vowel`, `coarse`, `crush`, `shape`, `distort`, `djf` | effects `vowel`, `crush`, `distort`, `djf` | done (see Effects) |
603
+ | `phaser*`, `tremolo*`, `leslie`/`lrate`/`lsize`, `compressor*`, `postgain` | effects of the same names | done |
604
+ | `room`, `size`, `roomfade`, `roomlp`, `roomdim` | `reverb` | done |
605
+ | `delay`, `delaytime`, `delayfeedback` | `delay` | done |
606
+ | `pan` | track `pan` | done |
607
+ | `orbit`, `duckorbit`/`duckdepth`/`duckattack`/`duckonset` | effects `orbit` (+ `shared`), `duck` | done: `shared` sends to one delay + reverb per orbit |
608
+ | `iresponse`/`ir` | `reverb.ir` | done: FFT convolution; built-ins or a pinned sample |
609
+ | `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) |
610
+ | zzfx `duration` | note length | done: a note's length is its duration |
611
+ | raw `zzfx([...])` parameter array | `synth zzfx …`, SDK `zzfx([...])`, `set_synth {zzfx}` | done: ZzFX's documented layout → named controls |
612
+ | soundfonts `gm_*`, drum banks, dirt-samples | sampler and sample packs | not this engine: hosted samples, see Sample packs |
613
+ | sample controls `begin`, `end`, `speed`, `unit`, `loop`, `loopBegin`/`loopb`, `loopEnd`/`loope`, `clip`/`legato`, `fit`, `loopAt`, `accelerate`, `squiz`, `cut`, `gain`, `vel`, `rr` | sampler voice fields; `/sample set`, `set_sample` | done (see Samples) |
614
+ | fitting to tempo (Ableton Repitch/Beats/Tones; Strudel `fit`) | `bpm` `fitmode` `len`; `/fitmode`, `fit_sample` | done (see Fitting samples) |
615
+
616
+ ## Keys (modelled piano)
617
+
618
+ A track whose `instrument` is a piano family (`grand`, `upright`, `felt`, `honkytonk`, `prepared`) and which has a `keys` field plays dawg's modelled piano (`src/audio/keys/`): a felt hammer of the chosen hardness strikes a bank of stretched, inharmonic string modes (two or three detuned unison strings per key, with a fast first stage and a slow aftersound), a soundboard knock, dampers that stop a released key in about a second, and a small body EQ per family. The 0.5 sustain pedal (down, half, up) holds the dampers off. It is built in: nothing downloads and every render is byte-identical.
619
+
620
+ `piano` keeps two meanings on purpose. A project already stored as `instrument: "piano"` keeps the legacy tone forever. Every new write of the word (`piano`, `instrument piano`, `set_instrument piano`, the menu) stores `instrument: "grand"` with `keys: { preset: "grand" }`. `organ` stays the synth preset (the modelled organs are `tonewheel`, `combo` and `pipe`, below). The sampled Salamander grand is still in the browser under instruments.
621
+
622
+ Tuning: each key's first partial sits on the track's tuning (12-TET or any table, 19-EDO included; an unmapped degree is silent). By default the octaves are stretched from the strings' own inharmonicity, as a piano tuner would: low octaves are tuned between the 2:1 and 4:2 beats and the treble is beatless 2:1 to the stretched note below, so the octave from A3 to A4 beats under 1 Hz. `keys stretch 0` keeps every key exactly on the tuning. Bends and glides keep each string mode under the Nyquist limit (modes that would alias are muted), and each note fades over its last 250 ms so it ends inside the 8 s loop-tail window.
623
+
624
+ Polyphony is 64 voices; a new key steals the oldest released voice, then the oldest held one.
625
+
626
+ Prompt grammar (one undo step per command):
627
+
628
+ ```text
629
+ piano the modelled grand (also: grand)
630
+ piano ballad a preset: grand ballad upright felt lofi honkytonk prepared
631
+ upright | felt | honkytonk | prepared the preset word alone
632
+ keys list this track's piano settings
633
+ keys presets every preset with its styles
634
+ keys preset lofi load a preset (instrument, keys and its effects)
635
+ keys hardness 0.3 decay 1.5 any parameter
636
+ keys hardness off unset one parameter (back to the preset)
637
+ keys reset the family's own sound (keys: {})
638
+ automate keys-hardness points 0:0.2 8:0.8 automatable parameters have lanes
639
+ ```
640
+
641
+ Presets: `grand` (concert grand, bright and long, three-string unisons), `ballad` (darker grand, softer hammer, more aftersound, plus a room reverb), `upright` (boxy, more inharmonic, shorter), `felt` (felt strip down, muted and intimate, audible mechanics, a small room), `lofi` (felt piano with tape wow, a 3.5 kHz low-pass filter and a 10-bit crush), `honkytonk` (16-cent unisons, bright saloon upright), `prepared` (bolts, rubber and screws on 60% of keys, seeded per key, so the same key always carries the same preparation). A preset is stored as `keys.preset`; its values are read at render, so overrides stay small. The effects a preset brings (`ballad`, `felt`, `lofi`) are ordinary track fields (`reverb`, `filter`, `fx.crush`) and stay editable; switching presets or `keys reset` removes them while they still hold the preset's values, and the same preset word in `song.ts` (`instrument: "lofi"`) brings the same effects. A bare `lofi` stays the drum kit and crush preset word; type `piano lofi`.
642
+
643
+ The menu has the pianos under **Sound > browse sounds > Keys**, and for a piano track a **Sound > Keys** page and the simple rows (preset, hardness, decay, release, felt) on the **Sound** page. The agent's `set_keys` tool takes the same presets and names; `set_instrument` and `create_track` take the piano words. In the SDK: `track({ instrument: "grand", keys: { hardness: 0.3 } })`.
644
+
645
+ | Param | Range | Default | Lane | What it does |
646
+ | ------------ | ------------------------------------- | ------------------ | --------------- | -------------------------------------------------------------------- |
647
+ | **hardness** | 0..1 | 0.5 | `keys-hardness` | hammer felt hardness: brightness at a given velocity |
648
+ | **touch** | 0..1 | 1 | `keys-touch` | velocity sensitivity (0 plays every note at 0.8) |
649
+ | **inharm** | 0..4 x | 1 (upright 2.5) | | inharmonicity multiplier (0 harmonic) |
650
+ | **unison** | 0..30 cents | 0.7 (honkytonk 16) | | detune spread of the unison strings |
651
+ | **decay** | 0.1..4 x | 1 (upright 0.6) | `keys-decay` | sustain time multiplier |
652
+ | **release** | 0.1..4 x | 1 | `keys-release` | damper time multiplier (how fast a released key stops) |
653
+ | **strike** | 0.04..0.3 | 0.12 | | hammer position along the string |
654
+ | **after** | 0..1 | 0.3 | | aftersound share (the slow second stage of the decay) |
655
+ | **knock** | 0..1 | 0.5 | `keys-knock` | soundboard knock and hammer thump |
656
+ | **noise** | 0..1 | 0.25 (felt 0.6) | `keys-noise` | key-off and damper mechanics |
657
+ | **felt** | 0..1 | 0 (felt 1) | `keys-felt` | felt strip between hammers and strings |
658
+ | **prep** | 0..1 | 0 (prepared 0.6) | | share of keys carrying a preparation (seeded per key) |
659
+ | **width** | 0..1 | 0.6 | | keyboard stereo spread, bass left and treble right |
660
+ | **stretch** | 0..1 | 1 | | octave stretch from the strings' inharmonicity; 0 keeps tuning exact |
661
+ | **body** | grand upright felt honkytonk prepared | the family's own | | body EQ voicing |
662
+ | **vib** | 0..64 Hz | 0 | | pitch wobble rate (tape wow); note vibrato replaces it |
663
+ | **vibmod** | 0..24 semitones | 0.5 | | pitch wobble depth |
664
+ | **sym** | 0..1 | 0 | | sympathetic string resonance while the sustain pedal is down |
665
+
666
+ 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.
667
+
668
+ `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.
669
+
670
+ ### Soft pedal and sostenuto (0.6.1)
671
+
672
+ The modelled pianos have the other two pedals of a grand. Both are pedal lanes like the sustain pedal (`[{ tick, state }]`, at most 1024 events) and absent means today's sound:
673
+
674
+ - **Soft pedal** (`softPedal`, una corda, the left pedal): while it is down each note's hammer is shifted so it strikes fewer strings with a softer part of the felt: the unison narrows, the hammer's high partials are rolled off and the level drops about 3 dB, so the note is quieter and darker (a 10%+ lower spectral centroid and at least 3 dB less 2-4 kHz energy on middle C). `half` is half the shift. It is read at each note's onset, so a note struck before the pedal keeps its tone.
675
+ - **Sostenuto** (`sostenuto`, the middle pedal, `down` and `up` only): keys already held when it goes down keep their dampers up until it lifts; notes struck afterwards damp at their own release. A held key struck again while the pedal is down keeps ringing to the lift (the rod keeps its damper up), and a key still held through a lift is caught again by the next press. It works alongside the sustain pedal. Only the modelled pianos with a `keys` object hear the soft pedal; `pedal soft` says so on any other track.
676
+
677
+ ```text
678
+ pedal soft 0-8 una corda from beat 0 to 8 (also: down|half|up <beat>, bars, off)
679
+ pedal sost 0-4 sostenuto down at 0, up at 4 (holds the keys down at 0)
680
+ pedal soft list the lane; pedal sost off clears it
681
+ ```
682
+
683
+ Menu: **Sound › performance** has **soft pedal** and **sostenuto** rows (off, or held over every bar) on a modelled piano track. Agent: `set_piano_pedals` (`soft`, `sostenuto`: events, `"bars"` or null). SDK (1.26.0): `track({ instrument: "grand", softPedal: [[0, "down"], [8, "up"]], sostenuto: [[1, "down"], [4, "up"]] })`.
684
+
685
+ ### Electric keys (0.6.1)
686
+
687
+ Three electric keyboard families on the same `keys` field, built in and byte-identical across renders:
688
+
689
+ - **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`.
690
+ - **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.
691
+ - **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.
692
+
693
+ 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.
694
+
695
+ ```text
696
+ epiano | wurli | clav the family (also rhodes wurlitzer clavinet)
697
+ epiano preset suitcase a preset: epiano suitcase dyno | wurli | clav funkclav
698
+ epiano vibe 0.6 bark 0.5 any parameter of the family
699
+ clav pickup bridge mute 0.4
700
+ automate keys-vibe points 0:0 8:0.8 automatable parameters have lanes
701
+ ```
702
+
703
+ | Param | Families | Range | Default | Lane | What it does |
704
+ | ---------- | ------------- | -------------------- | ------- | ----------- | ------------------------------------- |
705
+ | **bark** | epiano, wurli | 0..1 | 0.35 | | pickup drive: growl when played hard |
706
+ | **bell** | epiano, wurli | 0..1 | 0.5 | | tine or reed bell ping |
707
+ | **tone** | all three | 0..12000 Hz | 0 (off) | `keys-tone` | output low-pass |
708
+ | **vibe** | epiano | 0..1 | 0 | `keys-vibe` | suitcase stereo vibrato depth |
709
+ | **vibehz** | epiano | 0.5..12 Hz | 4 | | suitcase vibrato rate |
710
+ | **trem** | wurli | 0..1 | 0 | `keys-trem` | tremolo depth at 5.6 Hz |
711
+ | **pickup** | clav | neck bridge both out | both | | pickup switch |
712
+ | **mute** | clav | 0..1 | 0 | | mute slider: damps the upper partials |
713
+
714
+ `hardness`, `touch`, `decay`, `release`, `width`, `vib` and `vibmod` apply to the electric families too. The menu has them under **Sound › browse sounds › Keys › Electric**, with their rows on the **Sound** page; the agent's `set_instrument` and `set_keys` take the same words; the SDK takes `track({ instrument: "epiano", keys: { vibe: 0.6 } })` or `track({ instrument: "suitcase" })`. The models are dawg's own, from public descriptions of the instruments (tine and tone-bar cantilever, electromagnetic and electrostatic pickups, the Clavinet's pickup switching), with no sampled audio.
715
+
716
+ ### Organs (tonewheel, combo, pipe)
717
+
718
+ 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`.
719
+
720
+ - **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.
721
+ - **combo**: divider-style combo organ with five registers `16' 8' 4' 2⅔' 2'` (five digits), a flute, reed or bright voice and a vibrato.
722
+ - **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.
723
+
724
+ 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.
725
+
726
+ 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`.
727
+
728
+ ```text
729
+ tonewheel the tonewheel organ (also: hammond, b3)
730
+ tonewheel 888800008 the organ with those drawbars
731
+ gospel | jazzorgan tonewheel presets
732
+ combo | combo 08880 | farfisa | vox the combo organ
733
+ pipe | pipe flutes | pipe principal8 octave4 the pipe organ with a registration or stops
734
+ flutes | cornet | reeds | celeste pipe presets
735
+ tonewheel 888800008 perc 3rd a verb takes more rows (combo 08880 flute)
736
+ rotary slow | fast | stop the rotary speaker
737
+ rotary fast at 16 switch it at beat 16 (a keys-rotary lane point)
738
+ keys drawbars 888000000 any organ row (keys perc 3rd, keys scanner v2, keys stops plenum)
739
+ automate keys-rotary points 0:1 4:2 spin the rotor up at beat 4
740
+ ```
741
+
742
+ Presets: `tonewheel` (888000000, scanner C3, slow rotary), `gospel` (888800008, 3rd percussion, fast rotary, driven), `jazzorgan` (888000000 with soft 3rd percussion), `combo` (reed registers 08800 with vibrato), `vox` (bright 08880), `pipe` (plenum in a church reverb), `flutes` (gedackt 8' and flute 4' with tremulant), `cornet`, `reeds`, `celeste` (gamba and celeste beating).
743
+
744
+ The menu has them under **Sound > browse sounds > Keys > Organs**; for an organ track the **Sound** page shows the preset, a **Drawbars** (tonewheel), **Registers** (combo) or **Stops** (pipe) sub-menu with one row per footage or stop, and the family's rows. The agent's `set_keys` takes `drawbars`, `registers`, `stops` and `rotary` next to `params`. In the SDK: `track({ instrument: "tonewheel", keys: { drawbars: "888800008", rotary: "fast" } })` or `keys: { stops: ["principal8", "octave4"] }`.
745
+
746
+ | Param | Family | Range | Default | Lane | What it does |
747
+ | ------------- | --------------- | --------------------------- | ------------------------------------- | ------------- | ---------------------------------------------- |
748
+ | **drawbars** | tonewheel | nine digits 0-8 | 888000000 | | drawbar registration, 16' to 1' |
749
+ | **perc** | tonewheel | off 2nd 3rd | off | | single-trigger percussion; on mutes the 1' bar |
750
+ | **percdecay** | tonewheel | fast slow | fast | | percussion decay (0.6 s or 1.8 s) |
751
+ | **percvol** | tonewheel | normal soft | normal | | soft: percussion 6 dB down, drawbars unmuted |
752
+ | **click** | tonewheel | 0..1 | 0.5 | | key click |
753
+ | **scanner** | tonewheel | off v1 v2 v3 c1 c2 c3 | c3 | | scanner vibrato or chorus |
754
+ | **drive** | tonewheel combo | 0..1 | 0.15 (combo 0) | `keys-drive` | preamp overdrive |
755
+ | **rotary** | tonewheel combo | slow fast stop | slow (combo stop) | `keys-rotary` | rotary speaker speed |
756
+ | **registers** | combo | five digits 0-8 | 08800 | | combo registers, 16' to 2' |
757
+ | **voice** | combo | flute reed bright | reed | | register timbre |
758
+ | **stops** | pipe | stop names or registrations | principal8 octave4 fifteenth2 mixture | | drawn stops |
759
+ | **chiff** | pipe | 0..1 | 0.4 | | flue pipe attack noise |
760
+ | **wind** | pipe | 0..1 | 0.3 | | wind instability |
761
+ | **trem** | pipe | 0..1 | 0 | | tremulant depth |
762
+
763
+ 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.
534
764
 
535
765
  ## Samples
536
766
 
@@ -578,7 +808,64 @@ Semantics follow Strudel's sampler:
578
808
 
579
809
  `/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.
580
810
 
581
- Every voice starts and stops with a 1–3 ms fade, so cuts do not click. 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.
811
+ ### Velocity layers and round robin (0.6.1)
812
+
813
+ Two optional voice fields borrowed from SFZ make a multisampled instrument:
814
+
815
+ | Field | Values | What it does |
816
+ | ----- | ----------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
817
+ | `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 |
818
+ | `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 |
819
+
820
+ 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.
821
+
822
+ ### Fitting samples to the song (0.6)
823
+
824
+ 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`:
825
+
826
+ | Field | Values | What it does |
827
+ | --------- | ------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
828
+ | `bpm` | 20..400 | the sample's own tempo: the window advances one source beat per song beat (times `abs(speed)`), through the tempo map, ramps and fermatas included; a 174 BPM break in a 128 BPM song plays 128/174 as fast |
829
+ | `len` | beats, > 0 | the window lasts `len` song beats through the tempo map |
830
+ | `fitmode` | `repitch` (default), `beats`, `tones` | `repitch` is tape: speed and pitch move together. `beats` cuts the window at its onsets (SuperFlux, 30 ms minimum gap) and places each slice on its new time unstretched, so every hit stays sharp: drums, speech. `tones` time-stretches with a phase vocoder with identity phase locking and keeps the pitch: pads, loops, vocals |
831
+
832
+ How they combine with Strudel's controls and the 0.5 tempo map:
833
+
834
+ | Set | Window length | Pitch |
835
+ | ----------------------------------------------------------- | ---------------------------------------------------------------------------- | --------------------------------------- |
836
+ | none of them | `speed`, `unit`, `fit`, `loopAt` as before | moves with speed |
837
+ | `fit` + `fitmode beats/tones` | the note (fit wins over `bpm` and `len`) | kept |
838
+ | `bpm` | source beats ÷ `bpm` × song beats, following tempo changes | `repitch`: moves; `beats`/`tones`: kept |
839
+ | `len` (no `bpm`) | `len` song beats, following tempo changes | `repitch`: moves; `beats`/`tones`: kept |
840
+ | `speed` with `bpm` | `abs(speed)` source beats per song beat (2 = double time); negative reverses | as above |
841
+ | `unit` with `bpm`/`len` | ignored once fitted | — |
842
+ | keyed root, `cents`, glide | the fitted buffer is repitched on top: play the root to keep the time | moves |
843
+ | `loopBegin`/`loopEnd`, `clip`, `accelerate`, `squiz`, `cut` | apply to the fitted buffer | as before |
844
+
845
+ `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" })`.
846
+
847
+ ### Shift and fade (0.6.1)
848
+
849
+ `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.
850
+
851
+ `fade out 0.5` and `fade in 0.05` (Strudel `fadeTime` and `fadeInTime`, stored as `fadeTime` and `fadeInTime`, 0..2 s) shape a voice's start and end in place of the short default declick, and follow the fitted length when the voice is fitted. `fade off` clears both.
852
+
853
+ Both act on the focused sampler voice (`shift 7 vox` names one). The menu's **Sound › Sample** voice rows list Shift, Formant, Fade in and Fade out, the agent's `set_sample` tool takes `shift`, `formant`, `fadeTime` and `fadeInTime`, and the SDK writes `sample("samples/vox.wav", { shift: 7, formant: 0, fadeTime: 0.5 })`.
854
+
855
+ ### Resample (0.6.1)
856
+
857
+ `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.
858
+
859
+ - 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.
860
+ - The new sampler voice plays the file at gain 2 with no fade in, so it reproduces the source stem within −60 dB.
861
+ - The voice records where it came from in `from: { source: "track:lead" | "orbit:2" | "master", section?, bars?, score }`, `score` being the sha256 of the score it was rendered from. It is informational; the renderer never reads it.
862
+ - Up to 600 s. `section` uses the song's sections; `bars 1-2` is 1-based and inclusive.
863
+
864
+ **Project › Resample** lists each track, orbit and the mix with a `→ sampler` and `→ granular` row. The agent's `resample {source: track|orbit|master, trackId?, orbit?, section?, bars?, post?, grain?, as?}` tool does the same.
865
+
866
+ Fitted windows are computed once and kept in a 64 MB least-recently-used cache of the played window only. Only the frames a note can reach are fitted (a held keyed or clipped note fits its length plus the release; a one-shot or looped voice fits the whole window). Renders and exports always compute them; in play mode a fit up to 8 s (of source or output, whichever is longer) is computed on the spot (under 160 ms), and a longer one stays silent with "fitting" in the status line until it is ready ("fit ready" then), never at the wrong pitch. Audition previews fit synchronously.
867
+
868
+ 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.
582
869
 
583
870
  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.
584
871
 
@@ -676,6 +963,239 @@ The agent's `make_wavetable` tool (`src/audio/wavetable-maker.ts`, `src/media/wa
676
963
 
677
964
  All arithmetic is float64 in a fixed order with no randomness, so the same input and options give the same bytes. Project tables live under `tracks/<slug>/wavetables/`; `/wt list` and the menu's table picker list them, `/wt vox.wav` (or a full `tracks/…` path) picks one for the focused track, and `track.ts` refers to it as `wavetable("./wavetables/vox.wav")`. The score keeps the project path and its sha256; evaluation re-hashes it like sampler files. A file that changed since it was picked plays with a warning; a missing one is a load problem naming `make_wavetable`.
678
965
 
966
+ ## Plucked strings
967
+
968
+ `instrument: "string"` with a `string` field plays a physical model of a plucked or struck string (`src/audio/strings/`): an extended Karplus-Strong loop (Jaffe and Smith 1983) tuned exactly with a first-order Thiran allpass, a one-pole loss designed from two decay times (Välimäki et al. 1996), an allpass dispersion cascade for stiff strings (Van Duyne and Smith 1994), a raised-cosine pluck shaped by the pluck-position comb, a modal body, a sympathetic string bank and seeded unison courses. Pitches come from the 0.5 tuning tables and pitch curves (bends, glides, cents), so a string track plays 19-EDO or just intonation and follows `bend`. Bare legacy words (`sitar`, `ebass`, `pluck`, `cello`) keep their old tone; the engine runs only when the track has a `string` field.
969
+
970
+ `string sitar`, or `instrument nylon` / `instrument koto` with the resolver words (`nylon`, `steel`, `harpsichord`, `koto` …; the bare words `sitar`, `ebass` and `pluck` keep their legacy voices), picks one of 22 presets, each a full parameter set frozen by a hash test:
971
+
972
+ | Preset | Sound |
973
+ | --------------------------------------- | ---------------------------------------------------------------------- |
974
+ | `nylon` `steel` `electric` `jangle` | classical, steel-string, clean electric and electric 12-string guitars |
975
+ | `ebass` `slap` `upright` `motown` | electric bass, slap, upright pizzicato, flatwound muted P-bass |
976
+ | `sitar` `tanpura` | jawari buzz with taraf sympathetic strings; open-string drone |
977
+ | `harpsichord` `lute` `harp` | 8'+8' harpsichord (velocity-flat), gut lute courses, concert harp |
978
+ | `oud` `setar` `tar` `santur` `dulcimer` | fretless oud, Persian setar and tar, santur and hammered dulcimer |
979
+ | `koto` `banjo` `tres` `requinto` | koto, 5-string banjo, Cuban tres, bachata requinto |
980
+
981
+ Preset aliases (after `string`): `classical` (nylon), `acoustic` and `guitar` (steel), `12string` (jangle), `bassguitar` and `fender` (ebass), `doublebass` (upright), `cembalo` (harpsichord), `hammered` (dulcimer), `sehtar` (setar).
982
+
983
+ | Parameter | Range | Meaning |
984
+ | -------------------------- | ------------------------ | --------------------------------------------------------------------------- |
985
+ | `ring` (Strudel `decay`) | 0.05..60 s | how long a note rings: the fundamental's T60 at C4 |
986
+ | `track` | 0..1.5 | higher notes ring shorter: T60 x (f/C4)^-track |
987
+ | `damp` `bright` | 0..1 | high-frequency loss; excitation brightness at full velocity |
988
+ | `pos` | 0.02..0.5 | pluck position from the bridge (0.5 round, 0.04 nasal) |
989
+ | `exciter` | pick finger hammer noise | how the string is set in motion |
990
+ | `mute` | 0..1 | palm mute (staccato and ghost articulations damp the same way) |
991
+ | `buzz` (`jawari`) | 0..1 | bridge buzz: a zero-mean, energy-preserving bridge allpass, pitch-locked |
992
+ | `stiff` | 0..1 | inharmonicity B = 1e-6 x 400^stiff |
993
+ | `body` `size` | type, 0.25..5 | body resonance (`guitar`, `gourd`, `board`, `skin`, `bass` …) and its scale |
994
+ | `sym` `symtune` | 0..1, mode | sympathetic strings, tuned to the song `scale`, `open` strings or a `drone` |
995
+ | `unison` `detune` `spread` | 1..8, 0..1, 0..1 | strings per course, their detune and stereo spread |
996
+ | `oct` `octbelow` | 0..1, key | octave strings (12-string, harpsichord 4'), only below a key |
997
+ | `vel` `pickup` `noise` | 0..1 | velocity sensitivity, magnetic pickup position, excitation noise |
998
+ | `vib` `vibmod` `vibdelay` | Hz, st, s | preset vibrato (a note's own vibrato wins) |
999
+ | `release` `voices` `gain` | s, 1..32, 0..2 | damping after note-off, polyphony cap, level |
1000
+
1001
+ Sympathetic strings follow the song key's own scale, so `key C bhairav` tunes the taraf to Bhairav (shuddha Ni, komal Re and Dha) and a maqam key keeps its quarter tones. The `drone` tuning is Sa-Pa-Sa, or Sa-Ma-Sa (tivra Ma when the scale has it, else Sa-Ni-Sa) in a raga without Pa such as Marwa. Changing the key re-renders a string track's stem.
1002
+
1003
+ `ring`, `damp`, `pos`, `bright`, `mute`, `buzz`, `vib`, `vibmod` and `gain` automate as `string-<param>` lanes (`automate string-buzz points 0:0 4:0.8`). Measured on the renderer, on one string with the body and sympathetic strings off: every fifth key within 0.1 cent of the tuning table (sitar with buzz within 1 cent above C4). With the preset defaults a unison course (the harpsichord's 8'+8') is tuned around the table pitch and beats, and the sitar's sympathetic strings ring with the played note, so a single-peak pitch reading of the whole sound can drift a few cents; a course's tuning belongs to its key, so every strike of a key beats the same way and the harpsichord's level stays within 0.5 dB across velocities. Also measured: the fundamental's decay within 10% of `ring` at every fifth key, a C-major triad at velocity 0.8 near -6 dBFS for every preset, and under 20 ms of render per voice-second.
1004
+
1005
+ ```ts
1006
+ instrument: stringed("sitar", { buzz: 0.8, sym: 0.5 }),
1007
+ ```
1008
+
1009
+ | Command | Does |
1010
+ | ------------------------------------------ | ----------------------------------------------------------------- |
1011
+ | `string` · `string presets` | the focused track's preset and overrides · the preset list |
1012
+ | `string <preset>` | make the focused track that string instrument |
1013
+ | `string <param> <value> [<param> <value>]` | override parameters (`string buzz 0.8 sym 0.5`, `string decay 6`) |
1014
+ | `string <param> off` · `string reset` | back to the preset's value · drop every override |
1015
+ | `string off` | back to the legacy `pluck` voice |
1016
+
1017
+ The menu has the presets under Sound › browse sounds › Strings and every string parameter on the Sound page of a string track (left/right adjust, `x` resets, space auditions with staged A/B). The agent's `set_string {trackId?, preset?, params?, reset?, off?}` runs the same command. SDK 1.21.0: `stringed(preset, params)` as a track's `instrument`, or `track({ instrument: "string", string: { preset: "koto", ring: 4 } })`; the printer writes `stringed(...)` back.
1018
+
1019
+ ### Bowed strings
1020
+
1021
+ `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).
1022
+
1023
+ `bowed` plays the cello preset; `bowed <preset>` (or `string <preset>`) picks one of 13 bowed presets appended to the table (35 in all):
1024
+
1025
+ | Preset | Sound |
1026
+ | ------------------------------------------ | ------------------------------------------------------------------------- |
1027
+ | `violin` `viola` `cello` `contrabass` | solo orchestral strings |
1028
+ | `fiddle` `erhu` `kamancheh` (`kemence`) | folk fiddle, erhu (skin body, wide vibrato), Persian spike fiddle |
1029
+ | `violins` `violas` `cellos` `contrabasses` | sections: seeded unison players with their own detune, vibrato and spread |
1030
+ | `pizz` (`pizzicato`) · `trem` (`tremolo`) | plucked violin section · tremolo section |
1031
+
1032
+ 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" })`.
1033
+
1034
+ | Parameter | Range | Meaning |
1035
+ | ------------------------- | ---------- | --------------------------------------------------------------------------- |
1036
+ | `pressure` | 0..1 | bow force inside the playable range: flautando at 0, gritty at 1 |
1037
+ | `speed` | 0..1 | bow speed at full dynamics (loudness) |
1038
+ | `attack` | 0.005..4 s | bow-speed ramp at the start of a stroke (swells) |
1039
+ | `vib` `vibmod` `vibdelay` | Hz, st, s | vibrato rate, depth and onset delay (a note's own vibrato wins) |
1040
+ | `tremhz` | 0..16 Hz | tremolo bowing: rapid strokes per second (0 off) |
1041
+ | `sord` | 0..1 | con sordino: the practice mute, darker and softer |
1042
+ | `dyn` (`expression`) | 0..1 | dynamics on top of velocity (like MIDI CC11): drives bow speed and pressure |
1043
+
1044
+ 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.
1045
+
1046
+ 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.
1047
+
1048
+ | Command | Does |
1049
+ | -------------------------------------------- | ------------------------------------------------------- |
1050
+ | `bowed` · `bowed <preset>` · `bowed presets` | the cello · a bowed preset · the bowed presets |
1051
+ | `bowed <param> <value> …` | the same as `string <param> <value> …` (`bowed sord 1`) |
1052
+
1053
+ The menu lists them under **Sound › browse sounds › Strings › Bowed**, and the **Sound** page on a bowed track shows `pressure speed vib sord dyn bright ring body` first (the rest under advanced). SDK 1.31.0: `stringed("violin", { pressure: 0.7 })` or `stringed({ preset: "cello", sord: 1 })`.
1054
+
1055
+ ## Granular
1056
+
1057
+ `instrument: "granular"` plays a track's sound as a cloud of short grains. A read head moves through a source; every few milliseconds a grain (a windowed slice, repitched to the note) starts at the head plus a little random spray. It is the classic asynchronous granular model (Roads, _Microsound_; Granulator II, Mutable Clouds), with Strudel-style short names. Everything renders offline and deterministically: grains draw from the counter-based generator in `src/audio/dsp/rng.ts`, keyed on (`seed`, track, pitch, start tick, occurrence), so the same project gives the same bytes and a repeated note varies the way a real cloud does.
1058
+
1059
+ Sources. With nothing loaded a granular track grains a built-in synth render (`synth:pad` at C4, made into a held, seamless loop of its sustained part so a long note never falls into silence), so `grain cloud` sounds at once. `src` takes any synth preset or sound (`synth:bell`, `synth:supersaw@48`, `synth:pad@c3`; a sound plays with the track's own `synth` params, and `grain on` after `synth preset pad` grains `synth:pad`), or a sample: on a sampler track `grain on` (or `grain on voice vox`) grains that voice, pinned by sha256 like the sampler file, and the track keeps its sampler so `grain off` goes back. A missing source is a sample load problem in `/tracks`, never a silent track. `root` is the note that plays the source at its own pitch (a MIDI number or a note name: `grain root c4`). `grain off` returns to the voice the track had when granular turned on (stored as `granular.from`).
1060
+
1061
+ Presets (each is a few overrides over the defaults; `microloop`, `sparkle` and `backwards` come into their own on a resampled phrase, and are textures on a synth source):
1062
+
1063
+ | preset | sound |
1064
+ | ----------- | ---------------------------------------------------- |
1065
+ | `cloud` | slow-scanning soft cloud, wide (the `granular` word) |
1066
+ | `hold` | held, shimmering sustain of one moment |
1067
+ | `sparkle` | octave-and-fifth sparkle over a slow scan |
1068
+ | `swarm` | dense, detuned, fully wide |
1069
+ | `stutter` | dry 45 ms repeats that latch and follow the music |
1070
+ | `microloop` | tight looping grains that slowly advance |
1071
+ | `backwards` | backwards swells |
1072
+ | `dust` | sparse random crackles across the source |
1073
+
1074
+ Parameters: `begin`/`end` (the region, 0..1), `pos` (head start), `scan` (head speed, 1 = the source's own speed, 0 held, negative backwards), `grain` (s, 5 ms..2 s), `overlap` (grains sounding at once; density = overlap / grain), `jitter` (onset randomness), `spray` (s of read offset), `pitch` and `detune` (semitones), `shimmer` and `shimint` (chance a grain plays an interval up, 12 by default), `spread` (stereo), `window` (`hann tukey gauss tri perc rperc`), `reverse` (chance a grain plays backwards), `freeze`, `repeat` and `hold` (beat-repeat latches), `drift` and `drate` (a slow random walk of the head), `attack`, `release`, `veltone`, `gain`, `seed` and `root`. After a note ends the grains keep coming while the voice fades over `release`, and the note rings for `release + 1.5 × grain` (at most 10 s). `hold` is both a preset (`grain hold`) and a parameter (`grain hold 4`). Past 16 voices the oldest released voice is stolen first, then the oldest held one.
1075
+
1076
+ Quality and cost. Grains read a shared semitone-level band-limited bank (`src/audio/dsp/bank.ts`: windowed-sinc levels built on demand in 4096-frame Float32 chunks, a 64 MB LRU, the level re-picked every 32 frames), so upward shifts and shimmer do not alias. Pitch is exact to within 1 cent across C1..C8 and in other tunings; a cold note-on renders its first block in under 10 ms and the densest preset costs at most 7 ms per voice-second at 48 kHz, so play mode and the audition loop stay live.
1077
+
1078
+ | Command | What it does |
1079
+ | ----------------------------------------- | ------------------------------------------------------------------- |
1080
+ | `grain` · `grain presets` | the focused track's granular settings with values · the presets |
1081
+ | `grain <preset>` | make the focused track a grain cloud of its own sound with a preset |
1082
+ | `grain on [voice V]` · `grain off` | grain this track's sound (a sampler voice) · back to its voice |
1083
+ | `grain src synth:<name>[@note]` | a built-in synth source |
1084
+ | `grain src voice <V>` | one of this track's sampler voices as source |
1085
+ | `grain <param> <value> …` · `grain reset` | override parameters (`off` unsets one) · drop the overrides |
1086
+ | `track cloud` · `track hold-2` | a new granular track named after a preset |
1087
+ | `track pad grain swarm` | focus or create a track and grain it in one step |
1088
+
1089
+ In the ctrl-k menu, **Sound › granular** edits the preset, source and every parameter of a granular track (on any other pitched track it reads **granular (convert)** and offers `grain on` and the presets); **Sound › browse sounds › Granular** lists the presets. `grain` edits stage in the audition loop, so space plays them and `a` compares A/B. The agent's `set_granular {trackId, preset?, src?, voice?, params?, reset?, off?}` tool takes the same names, and the SDK writes:
1090
+
1091
+ ```ts
1092
+ instrument: granular("cloud", { scan: 0.1, seed: 7 }),
1093
+ instrument: granular({ src: "synth:bell@72", grain: 0.08, shimmer: 0.3 }),
1094
+ instrument: granular("hold", { src: "samples/choir.wav", root: "A3" }),
1095
+ ```
1096
+
1097
+ ### Grain play (0.6.1)
1098
+
1099
+ 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.
1100
+
1101
+ | Parameter | Values (default) | What it does |
1102
+ | --------- | ---------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
1103
+ | `sync` | `off` `1/64` `1/32` `1/16t` `1/16` `1/16d` `1/8t` `1/8` `1/8d` `1/4t` `1/4` `1/4d` `1/2` `1/1` | Grains start on a note-value grid that follows the song's tempo map (ramps included), as Ableton Granulator II and Output Portal sync their grain rate. `grain` still sets each grain's length; `overlap` is ignored. `jitter` moves onsets off the grid by up to a fraction of a step. Measured within 1 ms of 117.19 ms per 1/16 at 128 BPM. |
1104
+ | `quant` | `off` `scale` `chord` | Snaps each grain's pitch offset (`pitch`, `detune`, `shimmer`) to the nearest pitch of the song key's scale, or of the pitch classes sounding on the song's other pitched tracks at that grain's onset, falling back to the scale when nothing sounds (scale quantise, as on Output Portal and Bitwig's Sampler). The played note itself is never moved. |
1105
+ | `mono` | `on` `off` (off) | One voice: a note that starts before the previous one ends (legato) retargets that voice's pitch and keeps its grain stream and head, so a melody glides through one cloud; a detached note starts a new voice and cuts the old one's tail. |
1106
+ | `pedal` | `on` `off` (off) | The track's sustain pedal freezes the head while it is down (the cloud keeps playing the same spot), as on Mutable Clouds' freeze and the Hologram Microcosm hold. |
1107
+
1108
+ `grain sync 1/16`, `grain quant scale`, `grain mono on` and `grain pedal on` set them (`off` unsets); **Sound › granular** lists them as rows, `set_granular` takes them in `params`, and the SDK writes `granular("cloud", { sync: "1/16", quant: "scale", mono: true })`.
1109
+
1110
+ Lanes: every numeric parameter that moves well over time has a `grain-<param>` automation lane: `grain-pos`, `grain-scan`, `grain-grain`, `grain-overlap`, `grain-jitter`, `grain-spray`, `grain-pitch`, `grain-detune`, `grain-shimmer`, `grain-spread`, `grain-reverse`, `grain-repeat` and `grain-drift`. A lane replaces the parameter's value while it has points (`automate grain-pos points 0:0.1 8:0.9` sweeps the head across the source), is read every 32 frames, and latches per grain at its onset, so a block render equals a whole render. They are in **Mix & automation**'s lane picker on granular tracks and stored in `track.fxAutomation` like the effect lanes.
1111
+
1112
+ ## Mallets and bells (modal)
1113
+
1114
+ `instrument: "modal"` plays struck bars, tines, bells, bowls and drums on a modal resonator bank (`src/audio/dsp/modal.ts`, `src/audio/resonators.ts`): each note excites a table of measured mode ratios through a mallet pulse, each mode rings as a two-pole resonator with its own decay, and the strike point weights the modes the way it does on a real bar (the node at the centre of a marimba bar mutes the second mode). It is dawg's own engine, ported from the reviewed 0.6 prototype.
1115
+
1116
+ Presets (a word picks one): `marimba` `vibes` `xylophone` `glock` `celesta` `chimes` `kalimba` `mbira` `steelpan` `bowl` `gong` `timpani`; aliases `vibraphone`, `glockenspiel`, `tubular`, `thumbpiano`, `gongageng`, `steeldrum`, `singingbowl`, `kettledrum` and `tubularbells`. `instrument vibes` (or any preset word) switches the focused track. Plain `marimba` with no `modal` field keeps the pre-0.6 marimba voice byte-identical, so old projects sound the same; use `modal marimba` for the modal one (the `instrument marimba` receipt says so). One-shot renders let a modal tail ring up to 30 s (bowls and gongs ring out instead of stopping at the 8 s loop-fold cap). `dawg check` warns about tracks still on the legacy `marimba`, `modal` or `wind` words.
1117
+
1118
+ | Parameter | Range | Meaning |
1119
+ | -------------------------- | ------------------ | --------------------------------------------------------------------------------- |
1120
+ | `mallet` | yarn … brass | `yarn` `cord` `rubber` `plastic` `brass`; sets `hardness` |
1121
+ | `hardness` | 0..1 | mallet hardness: soft rounds off the high modes, hard adds them; velocity adds |
1122
+ | `position` | 0..1 | strike point: 0 the end or edge, 0.5 the centre |
1123
+ | `ring` | 0.05..30 s (log) | ring time (T60) at middle C |
1124
+ | `tilt` | 0..2 | how much faster high modes and high notes decay |
1125
+ | `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 |
1126
+ | `motor` `motordepth` | 0..12 Hz, 0..1 | vibraphone motor tremolo |
1127
+ | `ombak` | 0..12 Hz | paired-instrument beating (gamelan) |
1128
+ | `buzz` `click` | 0..1 | mbira bottle-cap buzz, mallet contact click |
1129
+ | `strikebend` `strikedecay` | ±24 st, 0.001..2 s | the pitch glide at the strike (timpani); Strudel `penv`/`pdecay` |
1130
+ | `gain` | 0..2 | level |
1131
+
1132
+ `hardness`, `position`, `ring`, `tilt`, `damp`, `motordepth`, `buzz`, `click` and `gain` have `modal-<param>` automation lanes, read at each note's onset. Notes honour the track or song tuning, note `cents`, bends, articulation (accents strike harder, staccato damps), the sustain pedal (holds dampers off), velocity curves, humanize and the tempo map. Up to 32 voices ring at once (the oldest is stolen with a short fade); tails ring up to 30 s and stop early once silent. Rendering is seeded and float64 in a fixed order, so renders are byte-identical across runs and workers.
1133
+
1134
+ ```ts
1135
+ instrument: modal("vibes", { motor: 4, hardness: 0.6 }),
1136
+ instrument: modal("marimba", { mallet: "rubber", ring: 2 }),
1137
+ ```
1138
+
1139
+ | Command | Does |
1140
+ | ---------------------------------------- | ---------------------------------------------------------- |
1141
+ | `modal` · `modal presets` (`modal list`) | the focused track's preset and overrides · every preset |
1142
+ | `modal <preset>` · `modal preset <name>` | make the focused track a modal track with that preset |
1143
+ | `modal mallet <name>` | pick a mallet (sets hardness; the later of the two wins) |
1144
+ | `modal <body>` | set the body (`modal saron`, `modal kempul`): `modal body` |
1145
+ | `modal <param> <value> …` | set parameters (`modal ring 3 hardness 0.7`); `off` clears |
1146
+ | `modal reset` | clear overrides, keep the preset |
1147
+ | `modal off` | leave the engine for the legacy marimba voice |
1148
+
1149
+ The menu's **Sound › browse sounds › Mallets and bells** lists the presets, and the **Sound** page shows the preset, mallet, the simple parameters and an **advanced** group on a modal track. The agent's `set_modal {trackId, preset?, mallet?, params?, reset?}` tool takes the same names.
1150
+
1151
+ ### Gamelan (0.6.1)
1152
+
1153
+ The modal engine also plays Javanese and Balinese bronzes, small bells and frame drums: `saron` `demung` `slenthem` `gangsa` `gender` `bonang` `kenong` `kethuk` `kempul`, the bells `crotales` `musicbox` `toypiano`, and the drums `daf` `bodhran` `tabla` (aliases `crotale`, `musicalbox`, `framedrum`). `modal gamelan` lists them; the menu has them under **Sound › browse sounds › Mallets and bells › Gamelan**. Gamelan is tuned in slendro or pelog, not 12-TET, so pair the presets with `tuning slendro` or `tuning pelog` (the 0.5 tuning tables).
1154
+
1155
+ Balinese instruments come in pairs: the pengumbang is tuned low and the pengisep a few hertz high, so a unison beats (_ombak_, "wave") at that difference, typically 5 to 8 Hz. `modal pair <track>` makes the focused track the pengisep of the named partner: it sounds `ombak` Hz above it (gangsa's 7 Hz by default), the partner plays one voice at its own pitch, and the two together beat at exactly `ombak` Hz instead of each track beating on its own. `modal pair off` undoes it. `modal ombak 6` or `modal pair t2` on a track that is not modal yet starts it as a `gangsa`. In the SDK: `modal("gangsa", { ombak: 6, pair: "pengumbang" })`.
1156
+
1157
+ 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.
1158
+
1159
+ ## Winds and brass (wind engine)
1160
+
1161
+ `instrument: "wind"` with a `wind` field plays blown instruments on breath-driven digital waveguides (`src/audio/winds/`): a bore delay line tuned exactly with a Thiran allpass, closed by a reed table (clarinet), a conical reed (saxophones, oboe, bassoon), an air jet (flutes) or a lip resonator (brass), with loss and bell filters, breath noise and a breath envelope. Reed and jet run at 2x with first-order antiderivative antialiasing (`src/audio/dsp/shape.ts`, `oversample.ts`); each preset carries per-semitone pitch and level trims for 22.05, 44.1 and 48 kHz, generated by `bun scripts/calibrate-winds.ts`, so every preset plays within 3 cents across its range. It is dawg's own model, after Smith's digital waveguides, Cook's STK reed and jet models and Välimäki's fractional-delay filters, ported from the reviewed 0.6 prototype; no samples.
1162
+
1163
+ Presets (a word picks one): flutes `flute` `recorder` `whistle` `ney` `shakuhachi` `panpipe` `suling` `bansuri`; reeds `clarinet` `bassclarinet` `oboe` `bassoon`; saxophones `sax` (tenor) `altosax` `barisax`; brass `trumpet` `harmon` `plunger` `trombone` `tuba` `horn`. Aliases: `tinwhistle` `pennywhistle` `nay` `panflute` `panpipes` `saxophone` `tenorsax` `tenor` `alto` `bari` `baritonesax` `frenchhorn` `mutedtrumpet` `wahtrumpet`. `instrument flute` (or any preset word) switches the focused track. The bare legacy word `wind` with no `wind` field keeps its pre-0.6 tone byte-identically; `wind flute` gives the engine. `presets wind` (or `wind presets`) lists them; the menu has them under **Sound › browse sounds › Winds and brass**, and the **Sound** page shows the simple rows `breath` `bright` `mute` `players` `growl` with the rest under advanced.
1164
+
1165
+ | Parameter | Range | Meaning |
1166
+ | ------------------ | -------------------------------- | ----------------------------------------------------------------------------- |
1167
+ | `model` | jet reed sax lips | exciter and bore (set by the preset) |
1168
+ | `breath` | 0..1 | blowing pressure: louder and fuller; too little and a reed does not speak |
1169
+ | `noise` | 0..1 | breath noise |
1170
+ | `attack` `release` | 0.001..2 s, 0.005..2 s | breath rise and fall; Strudel `att`/`rel` |
1171
+ | `vib` `vibmod` | 0..12 Hz, 0..1 st | vibrato rate and depth |
1172
+ | `reed` | 0..1 | reed stiffness, jet offset or lip tension |
1173
+ | `bright` | 0..1 | bore loss: dark to bright |
1174
+ | `stopped` | on/off | stopped pipe, odd harmonics (jet only: panpipe) |
1175
+ | `mute` | open straight cup harmon plunger | brass mute |
1176
+ | `wah` `wahenv` | 0..1 | plunger opening, and how much it opens with each note (doo-wah) |
1177
+ | `growl` `flutter` | 0..1 | hum into the horn, flutter tongue |
1178
+ | `players` | 1..8 | section size: extra players double the chord tones, slightly detuned and late |
1179
+ | `gain` | 0..2 | level |
1180
+
1181
+ `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.
1182
+
1183
+ ```ts
1184
+ instrument: "flute",
1185
+ instrument: wind("trumpet", { mute: "harmon", players: 3 }),
1186
+ instrument: wind("sax", { breath: 0.8, growl: 0.3 }),
1187
+ ```
1188
+
1189
+ | Command | What it does |
1190
+ | ---------------------------------------- | --------------------------------------------------------------- |
1191
+ | `wind` · `wind presets` · `presets wind` | the focused track's preset and overrides · every preset |
1192
+ | `wind <preset>` | make the focused track a wind track with that preset |
1193
+ | `wind <param> <value> …` | set parameters (`wind breath 0.8 players 3`); `off` clears one |
1194
+ | `wind mute <name>` | `open` `straight` `cup` `harmon` `plunger` |
1195
+ | `wind reset` · `wind off` | clear overrides, keep the preset · back to the legacy wind tone |
1196
+
1197
+ The agent's `set_wind {trackId, preset?, params?, reset?}` tool takes the same names, and `set_instrument` accepts every wind word.
1198
+
679
1199
  ## Rhythm (Euclidean rows)
680
1200
 
681
1201
  A drum part can be stored as generators instead of notes: each row owns one voice of a `kit` or oneshot `sampler` track and dawg expands it into ordinary notes, so rendering, diffs and sync are unchanged while you, the agent and `track.ts` edit four numbers instead of sixteen hits. The model follows the Torso T-1's Shape and Groove sections; the Euclidean patterns and rotation match Strudel's `euclid`/`euclidRot` exactly (`E(3,8)` is `x..x..x.`, a positive rotate moves the pattern later).
@@ -758,6 +1278,7 @@ From Orchid's documentation and reviews:
758
1278
  - 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).
759
1279
  - 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).
760
1280
  - "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.
1281
+ - 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.
761
1282
  - 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.
762
1283
 
763
1284
  dawg's own design:
@@ -863,6 +1384,27 @@ Rendering. Synth and wavetable voices start at the tuned frequency; keyed sample
863
1384
 
864
1385
  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.
865
1386
 
1387
+ ### Guitar strumming (0.6.1)
1388
+
1389
+ The `guitar` perform mode, the `strum` command, `strum_chords` and SDK `strum()` share one fretboard voicer and one stroke engine in `core/chords.ts`.
1390
+
1391
+ ```text
1392
+ guitar show the track's fretting (Track.guitar)
1393
+ guitar tune dadgad a tuning name, or open strings low to high: guitar tune D A D G A D
1394
+ guitar capo 2 · hand 5 · ring 0.8 · position 5 · guitar reset
1395
+ strum G D Em C folk chords (symbols or roman numerals), one bar each
1396
+ strum I V vi IV strokes D-DU-UDU speed 30ms each 2 at 4
1397
+ strum strum the block chords already on the track
1398
+ /chords perform guitar play mode chords strum on the fretboard; [ ] change speed
1399
+ ```
1400
+
1401
+ - **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.
1402
+ - **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.
1403
+ - **Strokes.** A grid on `step` (an eighth by default) of `D` down, `U` up, `d` `u` light strokes, `x` a muted chuck, and `-` or `.` a rest (the strings ring on), or a named pattern: `down folk pop punk funk reggae waltz jangle island`. Down strokes go low to high, up strokes high to low over three or four strings, and a new stroke cuts the strings it restrikes. Patterns are dawg's own: the classic folk/pop eighth-note patterns of beginner method books.
1404
+ - **Speed.** The time a full six-string down stroke takes, 22 ms by default (0..200 ms), the spread a real pick takes across the strings; it is in milliseconds so it stays the same at any tempo (`speed 1/32b` gives it in beats, converted at the song tempo). At 120 BPM the first-to-last note spread equals `speed` within 1 ms.
1405
+
1406
+ Track.guitar is stored only when set (`{ tune, capo, hand, ring, position }`). Menu: **Sound › guitar** (on guitar-like tracks: tune, capo, hand, ring, position, strum the chords, reset) and **Chords › strokes / speed**. Agent: `set_guitar`, `strum_chords`, and `write_chords` with `perform: "guitar"`, `strokes`, `speed`. SDK: `track({ guitar: { tune: "dadgad", capo: 2 } })` and `strum("G D Em C", { strokes: "folk", speed: 30 })`.
1407
+
866
1408
  ### Chord mode
867
1409
 
868
1410
  Play mode has a chord sub-mode modelled on the Orchid's Key mode. It is `auto` by default when the focused track can play chords (pitched synths, piano, soundfonts, keyed samplers; not tracks whose instrument, name or id says bass, kit, drum or perc) in 12-TET without a mono or legato glide, otherwise `manual`, so a track in pelog, just intonation or another non-12 tuning, or a TB-303-style legato line, records single notes. Choosing a mode by hand (`Q`, `/chords`, the menu) sticks for the session.
@@ -873,22 +1415,23 @@ Play mode has a chord sub-mode modelled on the Orchid's Key mode. It is `auto` b
873
1415
 
874
1416
  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.
875
1417
 
876
- | Key | Does (chord mode on) |
877
- | --------- | ------------------------------------------------------------------------------- |
878
- | `Q` | auto ⇄ manual |
879
- | `1 2 3 4` | latch chord type dim / min / maj / sus (two latched make a combined chord) |
880
- | `5 6 7 8` | latch extension 6 / m7 / M7 / 9 (any number; on top of the type or auto chord) |
881
- | `0` | clear every latch |
882
- | `-` / `=` | voicing dial down / up (-12..12; walks inversions) |
883
- | `9` | next perform mode (block, strum-up, strum-down, arp-up, …, harp, slop, pattern) |
884
- | `B` | next bass mode: off, chords, unison, single, solo (bass in C2–B2) |
885
- | `N` | play the suggested next chord (the `next` chord in the header) |
1418
+ | Key | Does (chord mode on) |
1419
+ | --------- | --------------------------------------------------------------------------------------- |
1420
+ | `Q` | auto ⇄ manual |
1421
+ | `1 2 3 4` | latch chord type dim / min / maj / sus (two latched make a combined chord) |
1422
+ | `5 6 7 8` | latch extension 6 / m7 / M7 / 9 (any number; on top of the type or auto chord) |
1423
+ | `0` | clear every latch |
1424
+ | `-` / `=` | voicing dial down / up (-12..12; walks inversions) |
1425
+ | `9` | next perform mode (block, strum-up, strum-down, arp-up, …, harp, slop, pattern, guitar) |
1426
+ | `[` / `]` | perform guitar: strum slower / faster (5 ms steps, 0..200 ms) |
1427
+ | `B` | next bass mode: off, chords, unison, single, solo (bass in C2–B2) |
1428
+ | `N` | play the suggested next chord (the `next` chord in the header) |
886
1429
 
887
1430
  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.
888
1431
 
889
1432
  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.
890
1433
 
891
- `/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.
1434
+ `/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.
892
1435
 
893
1436
  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.
894
1437
 
@@ -1011,15 +1554,15 @@ The arrangement strip is one row under the header that shows the sections over t
1011
1554
 
1012
1555
  `/menu` or `Ctrl-K` (on an empty prompt, in play mode too) opens the edit menu, drawn with the same overlay as the model picker. Every edit the agent can make is reachable from it with keys alone. Each row shows a plain label and the current value with its unit (s, Hz, oct, st, dB, BPM, bars); the line under the list describes the focused row and shows, dimmed, the prompt command the row runs, so the menu teaches the commands. `/menu <section>` opens a section directly (`/menu effects`, `/menu arrange`); the old names `parameters`, `sounds`, `track`, `automation` and `transport` still work.
1013
1556
 
1014
- | Section | Rows (most used first) |
1015
- | ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
1016
- | 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; a sampler's mode and voices; **performance** (glide, pedal, velocity curve, humanize); **browse sounds** (instruments, wavetables, packs) |
1017
- | 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) |
1018
- | Rhythm | the euclid editor (`/euclid`), drum patterns (`/pattern`), drum kits (`/kit`, synth then samples) |
1019
- | Chords | play-mode chord mode, key tonic and mode, voicing, spread, bass, sevenths, perform, pattern, arp rate, arp octaves, progression, style |
1020
- | 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 |
1021
- | Project | play, tempo, beats per bar, loop length, grid, click, count-in bars |
1022
- | 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) |
1557
+ | Section | Rows (most used first) |
1558
+ | ---------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
1559
+ | Sound | instrument, preset; a synth's attack, decay, sustain, release, filter cutoff/res/env, detune, vibrato, FM amount, then **advanced** with every synth parameter; a wavetable track's table and wavetable parameters; **granular** (a granular track's preset, source and parameters; **granular (convert)** elsewhere); a sampler's mode and voices; **performance** (glide, pedal, soft pedal and sostenuto on a piano, velocity curve, humanize); **browse sounds** (instruments, wavetables, Granular, Keys with Electric, packs) |
1560
+ | Effects | the core effects (filter, auto filter, distortion, tremolo, compressor, chorus, delay, reverb) with presets and simple parameters, **more effects** (dj filter, vowel, bitcrush, phaser, leslie, post gain, orbit, duck), and **advanced** per effect (see Effects) |
1561
+ | Rhythm | the euclid editor (`/euclid`), drum patterns (`/pattern`), drum kits (`/kit`, synth then samples) |
1562
+ | Chords | play-mode chord mode, key tonic and mode, voicing, spread, bass, sevenths, perform, pattern, arp rate, arp octaves, progression, style |
1563
+ | Mix & automation | the focused track's name, mute, solo, volume, pan; **all tracks** (choosing one focuses it); **automation**: each `AUTOMATION_LANES` lane with its points as `beat N value` rows, add points, ramp, clear lane; **master**: target, eq, glue, tape, width, limiter |
1564
+ | Project | play, tempo, beats per bar, loop length, grid, click, count-in bars |
1565
+ | Arrange | every section (loop, jump, mute, transpose, gain, build, drop, fill, duplicate, move, rename, unmark, delete), mark bars, add section, form, loop off (see Arrange) |
1023
1566
 
1024
1567
  Every list, picker and editor uses the same keys (see **Keys** below). In the menu:
1025
1568