@hraness/dawg 0.4.1 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (166) hide show
  1. package/CHANGELOG.md +99 -0
  2. package/DAWG.md +551 -33
  3. package/README.md +4 -4
  4. package/core/chords.ts +288 -7
  5. package/core/diff.ts +41 -12
  6. package/core/expression.ts +1241 -0
  7. package/core/fx.ts +437 -2
  8. package/core/granular.ts +528 -0
  9. package/core/instruments.ts +281 -0
  10. package/core/keys.ts +386 -0
  11. package/core/loop.ts +23 -0
  12. package/core/master.ts +455 -0
  13. package/core/midi.ts +452 -0
  14. package/core/resonators.ts +574 -0
  15. package/core/rhythm.ts +7 -2
  16. package/core/score.ts +717 -62
  17. package/core/sdk/eval-child.ts +40 -3
  18. package/core/sdk/print.ts +614 -27
  19. package/core/sdk/sync-instruments.ts +58 -0
  20. package/core/sdk/v1.ts +2824 -45
  21. package/core/sections.ts +2072 -0
  22. package/core/strings.ts +845 -0
  23. package/core/synth.ts +11 -1
  24. package/core/tempo.ts +1318 -0
  25. package/core/tuning.ts +1180 -0
  26. package/guides/audition.md +26 -0
  27. package/guides/automation.md +26 -0
  28. package/guides/chords.md +28 -0
  29. package/guides/effects.md +29 -0
  30. package/guides/faders.md +29 -0
  31. package/guides/files.md +28 -0
  32. package/guides/getting-started.md +26 -0
  33. package/guides/index.ts +75 -0
  34. package/guides/keys.md +27 -0
  35. package/guides/media.md +27 -0
  36. package/guides/mix.md +23 -0
  37. package/guides/music.md +14 -0
  38. package/guides/notes.md +26 -0
  39. package/guides/performance.md +26 -0
  40. package/guides/play.md +24 -0
  41. package/guides/project.md +13 -0
  42. package/guides/providers.md +25 -0
  43. package/guides/rhythm.md +26 -0
  44. package/guides/sessions.md +21 -0
  45. package/guides/sound.md +15 -0
  46. package/guides/sounds.md +29 -0
  47. package/guides/tempo.md +27 -0
  48. package/guides/tracks.md +25 -0
  49. package/guides/web-search.md +21 -0
  50. package/package.json +3 -1
  51. package/src/agent/agent.ts +9 -0
  52. package/src/agent/brief.ts +77 -3
  53. package/src/agent/chord-tools.ts +9 -1
  54. package/src/agent/expression-tools.ts +336 -0
  55. package/src/agent/granular-tools.ts +138 -0
  56. package/src/agent/master-tools.ts +299 -0
  57. package/src/agent/models.ts +4 -4
  58. package/src/agent/ops.ts +68 -6
  59. package/src/agent/planner.ts +45 -2
  60. package/src/agent/preview-tool.ts +27 -4
  61. package/src/agent/section-tools.ts +411 -0
  62. package/src/agent/time-tools.ts +290 -0
  63. package/src/agent/tools.ts +574 -16
  64. package/src/agent/tuning-tools.ts +301 -0
  65. package/src/agent/xcb-agent.ts +9 -0
  66. package/src/audio/arrange.ts +489 -0
  67. package/src/audio/audition.ts +16 -3
  68. package/src/audio/click.ts +113 -1
  69. package/src/audio/clock.ts +71 -5
  70. package/src/audio/dsp/bank.ts +233 -0
  71. package/src/audio/dsp/fft.ts +6 -0
  72. package/src/audio/dsp/filters.ts +57 -0
  73. package/src/audio/dsp/interp.ts +112 -0
  74. package/src/audio/dsp/modal.ts +684 -0
  75. package/src/audio/dsp/onset.ts +197 -0
  76. package/src/audio/dsp/oversample.ts +202 -0
  77. package/src/audio/dsp/rng.ts +37 -0
  78. package/src/audio/dsp/shape.ts +81 -0
  79. package/src/audio/dsp/stft.ts +73 -0
  80. package/src/audio/dsp/window.ts +77 -0
  81. package/src/audio/effects/bus.ts +5 -4
  82. package/src/audio/effects/chain.ts +6 -1
  83. package/src/audio/effects/common.ts +30 -1
  84. package/src/audio/effects/dynamics.ts +2 -1
  85. package/src/audio/effects/filter.ts +5 -3
  86. package/src/audio/effects/modulation.ts +5 -1
  87. package/src/audio/effects/rig/cab.ts +99 -0
  88. package/src/audio/effects/rig/filters.ts +152 -0
  89. package/src/audio/effects/rig/gate.ts +39 -0
  90. package/src/audio/effects/rig/head.ts +487 -0
  91. package/src/audio/effects/rig/index.ts +170 -0
  92. package/src/audio/effects/rig/section.ts +78 -0
  93. package/src/audio/effects/rig/stomp.ts +238 -0
  94. package/src/audio/effects/space.ts +92 -2
  95. package/src/audio/engine.ts +100 -21
  96. package/src/audio/fit.ts +402 -0
  97. package/src/audio/granular.ts +664 -0
  98. package/src/audio/instrument-check.ts +133 -0
  99. package/src/audio/instruments.ts +111 -0
  100. package/src/audio/keys/dsp.ts +323 -0
  101. package/src/audio/keys/engine.ts +361 -0
  102. package/src/audio/keys/piano.ts +432 -0
  103. package/src/audio/live-worker.ts +54 -0
  104. package/src/audio/live.ts +263 -17
  105. package/src/audio/loudness.ts +596 -0
  106. package/src/audio/master.ts +660 -0
  107. package/src/audio/measure-worker.ts +45 -0
  108. package/src/audio/measure.ts +110 -0
  109. package/src/audio/player.ts +11 -6
  110. package/src/audio/preview.ts +76 -14
  111. package/src/audio/render-worker.ts +6 -1
  112. package/src/audio/renderer.ts +7 -1
  113. package/src/audio/resonators.ts +287 -0
  114. package/src/audio/sampler.ts +269 -34
  115. package/src/audio/samples.ts +30 -7
  116. package/src/audio/strings/body.ts +250 -0
  117. package/src/audio/strings/engine.ts +369 -0
  118. package/src/audio/strings/loop.ts +119 -0
  119. package/src/audio/strings/measure.test-helpers.ts +198 -0
  120. package/src/audio/strings/pluck.ts +354 -0
  121. package/src/audio/synth/voice.ts +60 -13
  122. package/src/audio/synth/zzfx.ts +10 -4
  123. package/src/audio/warp.ts +147 -0
  124. package/src/audio/wav.ts +377 -30
  125. package/src/commands/arrange.ts +949 -0
  126. package/src/commands/expression.ts +941 -0
  127. package/src/commands/fit.ts +135 -0
  128. package/src/commands/fx.ts +31 -0
  129. package/src/commands/granular.ts +427 -0
  130. package/src/commands/help.ts +355 -27
  131. package/src/commands/keys.ts +353 -0
  132. package/src/commands/master.ts +361 -0
  133. package/src/commands/modal.ts +247 -0
  134. package/src/commands/music.ts +6 -1
  135. package/src/commands/rig.ts +260 -0
  136. package/src/commands/sample.ts +3 -0
  137. package/src/commands/string.ts +175 -0
  138. package/src/commands/synth.ts +11 -1
  139. package/src/commands/time.ts +967 -0
  140. package/src/commands/tuning.ts +490 -0
  141. package/src/main.ts +919 -41
  142. package/src/project/check.ts +13 -0
  143. package/src/render.ts +139 -8
  144. package/src/session/daemon.ts +18 -8
  145. package/src/session/naming.ts +8 -1
  146. package/src/session/rebase.ts +21 -0
  147. package/src/tui/arrange-menu.ts +390 -0
  148. package/src/tui/audition.ts +38 -5
  149. package/src/tui/fader.ts +409 -0
  150. package/src/tui/granular-menu.ts +278 -0
  151. package/src/tui/menu-time.ts +401 -0
  152. package/src/tui/menu.ts +967 -23
  153. package/src/tui/modal-menu.ts +118 -0
  154. package/src/tui/performance-menu.ts +235 -0
  155. package/src/tui/play-chords.ts +3 -1
  156. package/src/tui/play-mode.ts +150 -6
  157. package/src/tui/play-session.ts +459 -43
  158. package/tui/app.ts +262 -13
  159. package/tui/arrange-strip.ts +174 -0
  160. package/tui/drawer.ts +478 -0
  161. package/tui/grammar.ts +63 -1
  162. package/tui/guide.ts +351 -0
  163. package/tui/highway.ts +87 -4
  164. package/tui/hits.ts +68 -0
  165. package/tui/input.ts +7 -0
  166. package/tui/keys.ts +72 -0
@@ -4,6 +4,7 @@
4
4
  * app commands take a slash (bare aliases keep working but are listed once,
5
5
  * in their canonical form).
6
6
  */
7
+ import { EXPRESSION_USAGE } from "./expression.ts";
7
8
 
8
9
  export type HelpGroup = "music" | "session" | "window" | "keys";
9
10
 
@@ -52,6 +53,12 @@ export const HELP_SECTIONS: readonly HelpSection[] = [
52
53
  command: "wtenv|wtattack|wtdecay|wtrate|wtdepth|warp <n>",
53
54
  summary: "scan the table · warpmode bendp",
54
55
  },
56
+ {
57
+ command:
58
+ "grain <preset> | <param> <value> | on [voice V] | src synth:<name>|voice V | reset | off",
59
+ summary:
60
+ "granular cloud · grain cloud · grain scan 0.2 · track swarm-2 · grain presets",
61
+ },
55
62
  {
56
63
  command: "fx <effect> <param> <value> | on | off | preset <name>",
57
64
  summary: "effects · fx delay mix 0.3 · fx reverb on · fx lists them",
@@ -60,6 +67,71 @@ export const HELP_SECTIONS: readonly HelpSection[] = [
60
67
  command: "synth <param> <value> | preset <name>",
61
68
  summary: "synth voice · synth lpf 1200 · synth lists every param",
62
69
  },
70
+ {
71
+ command: "string <preset> | <param> <value> | presets | reset | off",
72
+ summary:
73
+ "plucked strings · string sitar · string buzz 0.8 · string ring 6",
74
+ },
75
+ {
76
+ command: "rig <preset> | reset",
77
+ summary:
78
+ "guitar rig · rig crunch · rig metal · rig funk · rig lists them",
79
+ },
80
+ {
81
+ command: "track jangle|punk|funk|ragged|gtr-lead|gtr-metal|bachata",
82
+ summary: "new guitar track with that rig",
83
+ },
84
+ {
85
+ command: "stomp|head|cab <type> | <param> <value>",
86
+ summary:
87
+ "pedal, amp, cabinet · stomp rat · head gain 7 gate -55 · cab 4x12",
88
+ },
89
+ {
90
+ command: "piano [<preset>] | grand | upright | felt | honkytonk",
91
+ summary: "modelled piano · piano ballad · piano lofi · piano prepared",
92
+ },
93
+ {
94
+ command: "keys <param> <value> | preset <name> | reset",
95
+ summary: "piano params · keys hardness 0.3 decay 1.5 · keys lists them",
96
+ },
97
+ {
98
+ command:
99
+ "modal <preset> | <param> <value> | mallet <name> | reset | off",
100
+ summary:
101
+ "mallets and bells · modal vibes · modal hardness 0.8 · modal presets",
102
+ },
103
+ {
104
+ command: "art <articulation>|off [target]",
105
+ summary:
106
+ "staccato legato accent tenuto marcato ghost · art staccato bars 1-2",
107
+ },
108
+ {
109
+ command: "glide <ms>|0 [legato|mono|poly] | glide <ms> <target>",
110
+ summary:
111
+ "portamento in ms · glide 60 mono · glide 0 off · SDK 0.06 (s)",
112
+ },
113
+ {
114
+ command: "bend <cents>|scoop|fall|doit|<at:cents>... [target]",
115
+ summary: "pitch curve over each note · bend +200 bar 3",
116
+ },
117
+ {
118
+ command: "vibrato <rate> <depth> [<delay>] [target]",
119
+ summary: "per-note vibrato · vibrato 5.5 30 0.2",
120
+ },
121
+ {
122
+ command: "pedal <beat>-<beat>... | bars | down|half|up <beat> | off",
123
+ summary: "sustain pedal · pedal 0-3.5 4-7.5 · pedal bars",
124
+ },
125
+ {
126
+ command: "velcurve linear|soft|hard|fixed [<v>]",
127
+ summary: "how velocity maps to level · velcurve fixed 0.6 (0..1)",
128
+ },
129
+ {
130
+ command: "humanize <ms> [<vel%> [<len%>]] [seed <n>|<target>] | off",
131
+ summary:
132
+ "seeded feel at render · humanize 10 8 5 · humanize 20 bars 2-3",
133
+ },
134
+ { command: "expression", summary: "this track's performance settings" },
63
135
  { command: "filter <hz> [res]", summary: "low-pass · filter off" },
64
136
  { command: "delay <beats> [fb] [mix]", summary: "ping-pong · delay off" },
65
137
  { command: "reverb <mix> [size]", summary: "room · reverb off" },
@@ -76,12 +148,82 @@ export const HELP_SECTIONS: readonly HelpSection[] = [
76
148
  summary: "drop one point",
77
149
  },
78
150
  { command: "clear [<lane>] automation", summary: "drop a lane's points" },
151
+ {
152
+ command: "master <unit> on|off | preset <name> | <param> <value>",
153
+ summary:
154
+ "song master · eq glue tape width limiter · master glue ratio 4",
155
+ },
156
+ {
157
+ command: "master <target> | target <lufs> | measure | off",
158
+ summary:
159
+ "loudness · master streaming · master target -9 · master measure",
160
+ },
79
161
  { command: "track name <text>", summary: "rename this track" },
80
162
  { command: "meter <1..16>", summary: "beats per bar" },
163
+ {
164
+ command: "tempo <bpm> at <beat>|bar <n> [ramp|exp]",
165
+ summary: "tempo change · tempo 90 at bar 9 ramp · tempo clear",
166
+ },
167
+ {
168
+ command: "rit|accel [<n> bars] [to <bpm>] [at bar <n>]",
169
+ summary: "gradual · rit 4 bars to 80 · a tempo · tempo primo",
170
+ },
171
+ {
172
+ command: "fermata [at <beat>|at bar <n>|at end] [<extra beats>]",
173
+ summary: "hold a beat · fermata at 31 2 · fermata clear",
174
+ },
175
+ {
176
+ command: "meter <n>/<d> [at bar <n>]",
177
+ summary: "meter change · meter 7/8 at bar 5 · meter clear",
178
+ },
179
+ {
180
+ command: "track rate|phase|cycle <n> | off",
181
+ summary: "polytempo · track rate 3/2 · track cycle 3",
182
+ },
183
+ {
184
+ command: "track phasing <beats> [over <beats>|hold <n>]",
185
+ summary: "Reich phasing · track phasing 3 hold 8 · track time off",
186
+ },
81
187
  {
82
188
  command: "key <tonic> <mode> | none",
83
189
  summary: "song key · key A minor · key F# dorian",
84
190
  },
191
+ {
192
+ command: "scale [<tonic>] <name> | list",
193
+ summary: "song scale · scale D hijaz · scale yaman · scale list",
194
+ },
195
+ {
196
+ command: "tuning <name> | edo <n> | scl <file> | off",
197
+ summary: "song tuning · tuning 19-edo · tuning just · tuning list",
198
+ },
199
+ {
200
+ command: "tuning ref <hz> | root <note> | map linear|nearest",
201
+ summary: "A4 reference · degree-0 key · keys per step",
202
+ },
203
+ {
204
+ command: "tuning track <…> | track off",
205
+ summary: "this track's tuning · off follows the song",
206
+ },
207
+ {
208
+ command: "cents <id> <±c>",
209
+ summary: "detune one note · cents n3 -14 · add E4-14c at 0",
210
+ },
211
+ {
212
+ command: "section <name> <a>-<b> | add | dup | move | rename | delete",
213
+ summary: "song sections · section chorus 9-16 · section lists them",
214
+ },
215
+ {
216
+ command: "section loop | jump | mute | vary <name>",
217
+ summary: "section loop chorus · section mute verse drums",
218
+ },
219
+ {
220
+ command: "form <section…> | off | bake",
221
+ summary: "song form · form intro verse chorus*2 outro",
222
+ },
223
+ {
224
+ command: "build | drop | fill [<section> | <a>-<b>]",
225
+ summary: "riser, roll, sweep · pre-drop cut and impact · drum fill",
226
+ },
85
227
  {
86
228
  command: "hit <voice> at <beat>",
87
229
  summary: "kit tracks · hit kick at 0",
@@ -112,12 +254,28 @@ export const HELP_SECTIONS: readonly HelpSection[] = [
112
254
  { command: "/rename <name>|--auto", summary: "name this session" },
113
255
  { command: "/fork [<name>]", summary: "copy into a new session" },
114
256
  { command: "/status", summary: "name · revision · digest · storage" },
115
- { command: "/export <file>", summary: "write track.loop/v1 JSON" },
257
+ {
258
+ command: "/export <file>",
259
+ summary: "write track.loop/v1 JSON, or MIDI for .mid",
260
+ },
116
261
  { command: "/import <file>", summary: "replace the score from a file" },
117
262
  {
118
263
  command: "/sample [<path> [as <voice>]]",
119
264
  summary: "add a sample voice · list voices",
120
265
  },
266
+ {
267
+ command: "/bpm <n> [<voice>]",
268
+ summary:
269
+ "the sample's own tempo (slash needed: bare bpm is song tempo)",
270
+ },
271
+ {
272
+ command: "/fitmode [repitch|beats|tones|auto] [<voice>]",
273
+ summary: "how it fits · alone suggests one from the sound",
274
+ },
275
+ {
276
+ command: "/len <beats> [<voice>]",
277
+ summary: "the sample lasts n beats of the song",
278
+ },
121
279
  ],
122
280
  },
123
281
  {
@@ -143,6 +301,10 @@ export const HELP_SECTIONS: readonly HelpSection[] = [
143
301
  },
144
302
  { command: "/auth [--check]", summary: "provider and audio status" },
145
303
  { command: "/play [on|off]", summary: "keyboard play mode · Ctrl-P" },
304
+ {
305
+ command: "/play degrees|chromatic",
306
+ summary: "home row plays the key's scale degrees (any tuning) · i",
307
+ },
146
308
  {
147
309
  command: "/pattern [name]",
148
310
  summary: "drum groove picker · moving previews",
@@ -179,6 +341,10 @@ export const HELP_SECTIONS: readonly HelpSection[] = [
179
341
  command: "/help [topic]",
180
342
  summary: "start here · /help all for everything",
181
343
  },
344
+ {
345
+ command: "/guide [topic]",
346
+ summary: "short how-to guides · F1",
347
+ },
182
348
  ],
183
349
  },
184
350
  {
@@ -219,13 +385,9 @@ export const HELP_GUIDE: readonly HelpSection[] = [
219
385
  { command: "ctrl-p", summary: "play notes on the computer keyboard" },
220
386
  {
221
387
  command: "ctrl-k",
222
- summary: "menu: every sound, effect, rhythm, chord",
223
- },
224
- { command: "?", summary: "keys for the screen you are on" },
225
- {
226
- command: "ctrl-z · ctrl-y",
227
- summary: "undo · redo (every change is one)",
388
+ summary: "menu: sound, effects, performance, mix, master, arrange …",
228
389
  },
390
+ { command: "? · ctrl-z · ctrl-y", summary: "keys here · undo · redo" },
229
391
  ],
230
392
  },
231
393
  {
@@ -244,8 +406,10 @@ export const HELP_GUIDE: readonly HelpSection[] = [
244
406
  {
245
407
  group: "make drums" as HelpGroup,
246
408
  entries: [
247
- { command: "/pattern", summary: "pick a groove · moving previews it" },
248
- { command: "/kit", summary: "pick a drum kit" },
409
+ {
410
+ command: "/pattern · /kit",
411
+ summary: "pick a groove · pick a drum kit",
412
+ },
249
413
  { command: "/euclid", summary: "rhythm editor: pulses, steps, rotation" },
250
414
  ],
251
415
  },
@@ -254,38 +418,71 @@ export const HELP_GUIDE: readonly HelpSection[] = [
254
418
  entries: [
255
419
  {
256
420
  command: "ctrl-k › Sound",
257
- summary: "instrument, envelope, filter, wavetable",
421
+ summary: "instrument, strings, envelope, filter, wavetable",
258
422
  },
259
423
  { command: "ctrl-k › Effects", summary: "delay, reverb, distortion …" },
260
- { command: "fx delay mix 0.3", summary: "the same from the prompt" },
424
+ {
425
+ command: "master streaming",
426
+ summary: "finish: -14 LUFS, limiter · master measure",
427
+ },
261
428
  { command: "/try fx reverb mix 0.6", summary: "hear it before keeping" },
262
429
  ],
263
430
  },
431
+ {
432
+ group: "shape the performance" as HelpGroup,
433
+ entries: [
434
+ {
435
+ command: "art staccato bar 2",
436
+ summary: "humanize 8 5 · ctrl-k › Sound › performance",
437
+ },
438
+ ],
439
+ },
264
440
  {
265
441
  group: "chords" as HelpGroup,
266
442
  entries: [
267
443
  { command: "key A minor", summary: "set the song key" },
268
444
  {
269
445
  command: "ctrl-p, then q",
270
- summary: "chord mode: one key plays a chord",
446
+ summary: "chord mode · 1–4 type · 5–8 extension · n next",
447
+ },
448
+ ],
449
+ },
450
+ {
451
+ group: "song structure" as HelpGroup,
452
+ entries: [
453
+ {
454
+ command: "section verse 1-8",
455
+ summary: "name bars · form verse chorus*2 orders them",
271
456
  },
272
457
  {
273
- command: "1–4 · 5–8 · n",
274
- summary: "chord type · extension · next chord",
458
+ command: "build · drop · fill",
459
+ summary: "transitions · ctrl-k › Arrange · /help arrange",
275
460
  },
276
461
  ],
277
462
  },
278
463
  {
279
464
  group: "more" as HelpGroup,
280
465
  entries: [
281
- { command: "/help all", summary: "every command and key" },
282
- { command: "/help music", summary: "or session, window, keys" },
466
+ {
467
+ command: "rit 4 bars to 80",
468
+ summary: "tempo, fermatas, meter · tuning pelog · scale yaman",
469
+ },
470
+ {
471
+ command: "/help all",
472
+ summary: "every command and key · /guide feature guides · F1",
473
+ },
283
474
  ],
284
475
  },
285
476
  ];
286
477
 
287
478
  /** Topics `/help <topic>` takes, besides `all`. */
288
- export const HELP_TOPICS = ["music", "session", "window", "keys"] as const;
479
+ export const HELP_TOPICS = [
480
+ "music",
481
+ "session",
482
+ "window",
483
+ "keys",
484
+ "arrange",
485
+ ] as const;
289
486
 
290
487
  /**
291
488
  * Rows for `/help [topic]`: the guide with no topic, the full reference for
@@ -299,10 +496,47 @@ export function helpTopicLines(
299
496
  if (!name) return sectionLines(HELP_GUIDE, width, 20);
300
497
  if (name === "all" || name === "commands" || name === "reference")
301
498
  return helpLines(width);
499
+ if (name === "arrange" || name === "arrangement" || name === "sections")
500
+ return sectionLines([arrangeSection()], width);
302
501
  const group = HELP_SECTIONS.find((section) => section.group === name);
303
502
  return group ? sectionLines([group], width) : undefined;
304
503
  }
305
504
 
505
+ /** `/help arrange`: every arranging command with its full usage. */
506
+ function arrangeSection(): HelpSection {
507
+ const verbs = ["section", "sections", "form", "build", "drop", "fill"];
508
+ return {
509
+ group: "arrange" as HelpGroup,
510
+ entries: [
511
+ // Usage strings run long: wrapped onto continuation rows.
512
+ ...verbs.flatMap((verb) =>
513
+ wrapWords(USAGE[verb] ?? "", 46).map((summary, index) => ({
514
+ command: index === 0 ? verb : "",
515
+ summary,
516
+ })),
517
+ ),
518
+ { command: "/menu arrange", summary: "the Arrange menu (ctrl-k)" },
519
+ {
520
+ command: "dawg render --section <name>",
521
+ summary: "export one section",
522
+ },
523
+ ],
524
+ };
525
+ }
526
+
527
+ function wrapWords(text: string, width: number): string[] {
528
+ const rows: string[] = [];
529
+ let row = "";
530
+ for (const word of text.split(" ")) {
531
+ if (row && row.length + 1 + word.length > width) {
532
+ rows.push(row);
533
+ row = word;
534
+ } else row = row ? `${row} ${word}` : word;
535
+ }
536
+ if (row) rows.push(row);
537
+ return rows;
538
+ }
539
+
306
540
  function sectionLines(
307
541
  sections: readonly HelpSection[],
308
542
  width: number,
@@ -365,15 +599,31 @@ export function helpText(): string {
365
599
  }
366
600
 
367
601
  /** Usage for a known verb, shown instead of sending a near-miss to the agent. */
368
- const USAGE: Readonly<Record<string, string>> = {
602
+ export const USAGE: Readonly<Record<string, string>> = {
369
603
  click: "/click on|off|<volume> · /click 50%",
370
604
  "count-in": "/count-in 0|1|2",
371
605
  chords:
372
606
  "/chords auto|manual|off · voicing <n> · spread · bass · perform · rate · octaves · sevenths · preset · style",
373
607
  key: "key <tonic> <mode> | none · key A minor",
608
+ scale: "scale [<tonic>] <name> | list · scale D hijaz",
609
+ tuning:
610
+ "tuning <name> | edo <n> | ratios … | cents … | scl <file> [kbm <file>] | ref <hz> | root <note> | map linear|nearest | track … | off · tuning 19-edo",
611
+ tune: "tuning <name> | edo <n> | scl <file> | off · tuning list",
612
+ cents: "cents <id> <±cents> · cents n3 -14",
374
613
  grid: "/grid 1/4|1/8|1/8T|1/16|1/16T|1/32",
375
- tempo: "tempo takes 20…300 · tempo 120",
376
- bpm: "tempo takes 20…300 · tempo 120",
614
+ tempo:
615
+ "tempo takes 20…300 · tempo 120 · tempo 90 at bar 9 [ramp|exp] · tempo remove bar 9 · tempo clear · tempo map",
616
+ bpm: "tempo takes 20…300 · tempo 120 · a sample's own tempo: /bpm 174 [<voice>] · /bpm off",
617
+ rit: "rit [<n> bars|beats] [to <bpm>] [at bar <n>|<beat>] [exp] · rit 4 bars to 80",
618
+ ritardando:
619
+ "rit [<n> bars|beats] [to <bpm>] [at bar <n>|<beat>] [exp] · rit 4 bars to 80",
620
+ rall: "rit [<n> bars|beats] [to <bpm>] [at bar <n>|<beat>] [exp] · rall 2 bars",
621
+ accel:
622
+ "accel [<n> bars|beats] [to <bpm>] [at bar <n>|<beat>] [exp] · accel 8 bars to 174 at bar 9",
623
+ accelerando:
624
+ "accel [<n> bars|beats] [to <bpm>] [at bar <n>|<beat>] [exp] · accel 8 bars to 174",
625
+ fermata:
626
+ "fermata [at <beat>|at bar <n>|at end] [<extra beats>] · fermata at 31 2 · fermata remove 31 · fermata clear",
377
627
  add: "add <note> at <beat> [for <beats>] · add C4 at 0",
378
628
  put: "add <note> at <beat> [for <beats>] · add C4 at 0",
379
629
  remove: "remove <id> · ids show in the transcript",
@@ -385,12 +635,16 @@ const USAGE: Readonly<Record<string, string>> = {
385
635
  bars: "bars takes 1…256 · bars 8",
386
636
  extend: "extend <count> bars · extend 4 bars",
387
637
  instrument:
388
- "instrument <name> · sine piano pluck bass saw square triangle wavetable kit · synth: sawtooth supersaw pulse white pink z_square…",
638
+ "instrument <name> · sine piano pluck bass saw square triangle wavetable kit · pianos: grand upright felt honkytonk prepared · synth: sawtooth supersaw pulse white pink z_square…",
389
639
  volume: "volume takes 0…1 · volume 0.8",
390
640
  vol: "volume takes 0…1 · volume 0.8",
391
641
  pan: "pan takes -1…1 · pan -0.5",
392
642
  wt: "wt <table> | wt <0..1> | wt list · wt basic · wt wt_digital:2",
393
643
  wavetable: "wt <table> | wt <0..1> | wt list · wt basic · wt wt_digital:2",
644
+ grain:
645
+ "grain <preset> | <param> <value> | on [voice V] | src synth:<name>|voice V | reset | off | presets · grain cloud · grain pitch 12",
646
+ granular:
647
+ "grain <preset> | <param> <value> | on [voice V] | src synth:<name>|voice V | reset | off | presets · grain cloud",
394
648
  warpmode: "warpmode none|asym|bendp|bendm|bendmp|sync|quant",
395
649
  filter: "filter <hz> [res] · filter 800 0.3 · filter off",
396
650
  delay: "delay <beats> [fb] [mix] · delay 0.75 0.4 0.3 · delay off",
@@ -400,7 +654,8 @@ const USAGE: Readonly<Record<string, string>> = {
400
654
  hit: "hit <voice> at <beat> · hit kick at 0",
401
655
  pattern: "pattern <voice> <beats...> | every <step> · pattern kick every 1",
402
656
  clear: "clear · clear <voice> · clear [<lane>] automation",
403
- track: "/track <name> · /track drums",
657
+ track:
658
+ "/track <name> · /track drums · track rate <0.125..8>|<a>/<b>|off · track phase <beats> · track cycle <beats> · track phasing <beats> [over <beats>]",
404
659
  tracks: "/tracks",
405
660
  sessions: "/sessions",
406
661
  resume: "/resume [<n>|<name>|<id>]",
@@ -412,6 +667,9 @@ const USAGE: Readonly<Record<string, string>> = {
412
667
  sample:
413
668
  "/sample <path> [as <voice>] · /sample set <voice> <control> <value>… · /sample set brk fit on clip 1",
414
669
  samples: "/sample · lists the focused track's voices",
670
+ fitmode:
671
+ "/fitmode [repitch|beats|tones|auto|off] [<voice>] · /fitmode beats · /fitmode auto brk",
672
+ len: "/len <beats> [<voice>] · /len 16 · /len off",
415
673
  view: "/view focus | all",
416
674
  transcript: "/transcript",
417
675
  log: "/transcript",
@@ -422,15 +680,49 @@ const USAGE: Readonly<Record<string, string>> = {
422
680
  logout: "/logout [provider]",
423
681
  auth: "/auth [--check]",
424
682
  help: "/help [topic] · /help all · /help music|session|window|keys",
683
+ guide: "/guide [topic] · /guide chords · F1",
425
684
  fx: "fx <effect> <param> <value> | on | off | preset <name> · fx delay mix 0.3",
426
685
  synth: "synth <param> <value> | preset <name> · synth lpf 1200",
686
+ string:
687
+ "string <preset> | preset <name> | <param> <value> | presets | reset | off · string koto",
688
+ rig: "rig clean|crunch|punk|ragged|lead|metal|fuzz|octave|funk|wah|bachata|spring|bassdrive|reese|jangle|alt | reset",
689
+ stomp:
690
+ "stomp fuzz|face|od|rat|octave | gain <0-10> tone <0-1> level <dB> | off",
691
+ head: "head clean|chime|crunch|lead|high|solid|bass | gain bass mid treble presence master <0-10> | gate <dB> | off",
692
+ cab: "cab 1x12|2x12|4x12|1x10|open|8x10|1x15|di | mic <0-1> | off",
693
+ keys: "keys <param> <value> | preset <name> | reset | presets · keys hardness 0.3 · keys stretch 0",
694
+ piano:
695
+ "piano [grand|ballad|upright|felt|lofi|honkytonk|prepared] · piano ballad",
696
+ modal:
697
+ "modal <preset> | <body> | <param> <value> | mallet <name> | reset | off | presets · modal vibes · modal saron · modal ring 3",
427
698
  pack: "/pack list | info <name> | use <pack>/<sound> | add <url>",
428
699
  kit: "/kit [name] · /kit syn909",
429
700
  euclid: "/euclid [voice] · euclid hat 7 16",
430
- menu: "/menu [sound|effects|rhythm|chords|mix|project]",
701
+ menu: "/menu [sound|effects|rhythm|chords|mix|master|project|tuning]",
702
+ master:
703
+ "master <unit> on|off|preset <name>|<param> <value> · master streaming|club|loud · master target -14 · master measure · master off",
431
704
  try: "/try <sound command> · /try fx reverb mix 0.6",
432
- play: "/play [on|off] · Ctrl-P",
433
- meter: "meter <1..16> · meter 3",
705
+ play: "/play [on|off|degrees|chromatic] · Ctrl-P · i toggles degrees",
706
+ meter:
707
+ "meter <1..16> · meter 3 · meter 7/8 [at bar <n>] · meter remove bar <n> · meter clear",
708
+ art: EXPRESSION_USAGE.art,
709
+ articulation: EXPRESSION_USAGE.art,
710
+ bend: EXPRESSION_USAGE.bend,
711
+ vibrato: EXPRESSION_USAGE.vibrato,
712
+ glide: EXPRESSION_USAGE.glide,
713
+ portamento: EXPRESSION_USAGE.glide,
714
+ pedal: EXPRESSION_USAGE.pedal,
715
+ sustain: EXPRESSION_USAGE.pedal,
716
+ velcurve: EXPRESSION_USAGE.velcurve,
717
+ humanize: EXPRESSION_USAGE.humanize,
718
+ section:
719
+ "section [mark] <name> <a>-<b> | add [<name>] [<n>] | dup | move <name> to <bar> | rename <name> to <new> | delete | unmark | mute | vary | reset | loop <name>|off | jump <name> · section chorus 9-16",
720
+ sections: "section · lists sections, the form and the loop",
721
+ form: "form <section…> | off | bake · form verse verse chorus verse · form verse chorus*2",
722
+ build:
723
+ "build [into <section> | <section> | <a>-<b>] [<n> bars] [riser] [roll] [sweep] [uplifter] · build into chorus",
724
+ drop: "drop [<section> | at <bar>] [cut <beats>] [no impact] · drop chorus",
725
+ fill: "fill [<section> | at <bar>] [toms|roll|kick] [<n> beats] [no crash] · fill chorus (the beats before it)",
434
726
  undo: "undo · Ctrl-Z",
435
727
  redo: "redo · Ctrl-Y",
436
728
  };
@@ -530,6 +822,42 @@ export function looksLikeProse(command: string): boolean {
530
822
  */
531
823
  export function usageHint(command: string): string | undefined {
532
824
  if (looksLikeProse(command)) return undefined;
533
- const verb = command.trim().toLowerCase().replace(/^\//, "").split(/\s+/)[0];
534
- return verb ? USAGE[verb] : undefined;
825
+ const words = command.trim().toLowerCase().replace(/^\//, "").split(/\s+/);
826
+ const verb = words[0];
827
+ if (!verb) return undefined;
828
+ // `build tension`, `drop bass`, `form a hook`: everyday words, so only
829
+ // arguments that look like the grammar earn a usage hint; the rest is
830
+ // a request for the agent.
831
+ if (
832
+ ARRANGE_VERBS.has(verb) &&
833
+ !command.trim().startsWith("/") &&
834
+ !words.slice(1).some((word) => /\d/.test(word) || ARRANGE_WORDS.has(word))
835
+ )
836
+ return undefined;
837
+ return USAGE[verb];
535
838
  }
839
+
840
+ const ARRANGE_VERBS: ReadonlySet<string> = new Set([
841
+ "build",
842
+ "drop",
843
+ "fill",
844
+ "form",
845
+ ]);
846
+ const ARRANGE_WORDS: ReadonlySet<string> = new Set([
847
+ "at",
848
+ "into",
849
+ "cut",
850
+ "no",
851
+ "impact",
852
+ "crash",
853
+ "riser",
854
+ "roll",
855
+ "sweep",
856
+ "uplifter",
857
+ "toms",
858
+ "kick",
859
+ "beats",
860
+ "bars",
861
+ "bake",
862
+ "off",
863
+ ]);