@hraness/dawg 0.6.1 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (277) hide show
  1. package/CHANGELOG.md +118 -0
  2. package/DAWG.md +454 -140
  3. package/README.md +30 -26
  4. package/core/autotune.ts +1119 -0
  5. package/core/chords.ts +271 -23
  6. package/core/clips.ts +499 -0
  7. package/core/diff.ts +184 -104
  8. package/core/expression.ts +15 -0
  9. package/core/fx.ts +99 -29
  10. package/core/ids.ts +52 -0
  11. package/core/instruments.ts +19 -0
  12. package/core/keys.ts +3 -3
  13. package/core/loop.ts +8 -0
  14. package/core/lyrics.ts +297 -0
  15. package/core/master.ts +3 -3
  16. package/core/range.ts +581 -0
  17. package/core/resonators.ts +16 -2
  18. package/core/routing.ts +165 -0
  19. package/core/score.ts +955 -14
  20. package/core/sdk/eval-child.ts +7 -2
  21. package/core/sdk/eval.ts +35 -6
  22. package/core/sdk/print.ts +287 -5
  23. package/core/sdk/sync-lyrics.ts +49 -0
  24. package/core/sdk/v1.ts +2132 -46
  25. package/core/sections.ts +480 -35
  26. package/core/sing.ts +815 -0
  27. package/core/style-provenance.ts +80 -0
  28. package/core/styles/africa-mena-southasia.ts +2893 -0
  29. package/core/styles/americas.ts +3810 -0
  30. package/core/styles/art.ts +4993 -0
  31. package/core/styles/base.ts +123 -0
  32. package/core/styles/cycles.ts +106 -0
  33. package/core/styles/electronic.ts +2723 -0
  34. package/core/styles/europe-asia-pacific.ts +2838 -0
  35. package/core/styles/excerpt.ts +29 -0
  36. package/core/styles/gamelan.ts +283 -0
  37. package/core/styles/generate.ts +2199 -0
  38. package/core/styles/index.ts +515 -0
  39. package/core/styles/parts.ts +106 -0
  40. package/core/styles/pop.ts +3189 -0
  41. package/core/styles/rock.ts +2993 -0
  42. package/core/styles/roots.ts +4175 -0
  43. package/core/styles/schema.ts +429 -0
  44. package/core/styles/taxonomy.ts +940 -0
  45. package/core/styles/validate.ts +528 -0
  46. package/core/tempo.ts +32 -2
  47. package/core/tuning.ts +19 -3
  48. package/core/vocoder.ts +524 -0
  49. package/guides/agent.md +29 -0
  50. package/guides/arrange.md +31 -0
  51. package/guides/audio.md +31 -0
  52. package/guides/audition.md +20 -14
  53. package/guides/automation.md +12 -7
  54. package/guides/chords.md +15 -15
  55. package/guides/effects.md +17 -16
  56. package/guides/faders.md +20 -16
  57. package/guides/files.md +13 -9
  58. package/guides/getting-started.md +15 -11
  59. package/guides/keys.md +19 -15
  60. package/guides/media.md +17 -12
  61. package/guides/mix.md +15 -7
  62. package/guides/music.md +23 -8
  63. package/guides/notes.md +15 -9
  64. package/guides/panes.md +32 -0
  65. package/guides/performance.md +15 -13
  66. package/guides/play.md +21 -13
  67. package/guides/project.md +26 -8
  68. package/guides/providers.md +20 -14
  69. package/guides/resample.md +15 -9
  70. package/guides/rhythm.md +18 -13
  71. package/guides/sessions.md +17 -7
  72. package/guides/show-me.md +31 -0
  73. package/guides/sound.md +25 -9
  74. package/guides/sounds.md +15 -13
  75. package/guides/styles.md +31 -0
  76. package/guides/tape.md +32 -0
  77. package/guides/tempo.md +15 -10
  78. package/guides/tracks.md +15 -10
  79. package/guides/tuning.md +32 -0
  80. package/guides/voice.md +31 -0
  81. package/guides/web-search.md +18 -8
  82. package/native/prebuilt/darwin-arm64/libdawg_sink.dylib +0 -0
  83. package/native/prebuilt/darwin-x64/libdawg_sink.dylib +0 -0
  84. package/native/prebuilt/linux-arm64/libdawg_sink.so +0 -0
  85. package/native/prebuilt/linux-x64/libdawg_sink.so +0 -0
  86. package/native/prebuilt/manifest.json +21 -0
  87. package/package.json +6 -2
  88. package/src/agent/agent.ts +130 -18
  89. package/src/agent/calibration-tools.ts +53 -0
  90. package/src/agent/clip-tools.ts +453 -0
  91. package/src/agent/command-agent.ts +378 -0
  92. package/src/agent/drum-tools.ts +2 -2
  93. package/src/agent/expression-tools.ts +1 -1
  94. package/src/agent/gateway.ts +246 -60
  95. package/src/agent/models.ts +53 -12
  96. package/src/agent/ops.ts +27 -1
  97. package/src/agent/pack-tools.ts +1 -1
  98. package/src/agent/planner.ts +25 -0
  99. package/src/agent/portable-schema.ts +80 -0
  100. package/src/agent/preview-tool.ts +4 -1
  101. package/src/agent/provider.ts +22 -8
  102. package/src/agent/range-tools.ts +216 -0
  103. package/src/agent/rhythm-tools.ts +1 -1
  104. package/src/agent/section-tools.ts +1 -1
  105. package/src/agent/show-me.ts +497 -0
  106. package/src/agent/steer.ts +15 -0
  107. package/src/agent/style-tools.ts +217 -0
  108. package/src/agent/tool-error.ts +12 -0
  109. package/src/agent/tools.ts +110 -23
  110. package/src/agent/usage.ts +2 -2
  111. package/src/agent/voice-tools.ts +925 -0
  112. package/src/agent/xcb-agent.ts +13 -10
  113. package/src/argv.ts +38 -0
  114. package/src/audio/analysis.ts +253 -0
  115. package/src/audio/arrange.ts +49 -5
  116. package/src/audio/audio-command.ts +218 -0
  117. package/src/audio/autotune-engine.ts +101 -0
  118. package/src/audio/autotune.ts +640 -0
  119. package/src/audio/clips.ts +240 -0
  120. package/src/audio/devices.ts +264 -0
  121. package/src/audio/doctor.ts +157 -0
  122. package/src/audio/dsp/bandbank.ts +138 -0
  123. package/src/audio/dsp/envelope.ts +10 -0
  124. package/src/audio/dsp/follow.ts +120 -0
  125. package/src/audio/dsp/formant.ts +427 -0
  126. package/src/audio/dsp/glottal.ts +243 -0
  127. package/src/audio/dsp/interp.ts +7 -2
  128. package/src/audio/dsp/lpc.ts +50 -0
  129. package/src/audio/dsp/periodicity.ts +59 -0
  130. package/src/audio/dsp/pitch.ts +995 -0
  131. package/src/audio/dsp/psola.ts +199 -0
  132. package/src/audio/effects/chain.ts +3 -1
  133. package/src/audio/effects/common.ts +43 -0
  134. package/src/audio/effects/convolution.ts +7 -4
  135. package/src/audio/effects/filter.ts +48 -69
  136. package/src/audio/effects/formant.ts +263 -0
  137. package/src/audio/engine.ts +360 -40
  138. package/src/audio/fit.ts +35 -3
  139. package/src/audio/instrument-check.ts +59 -43
  140. package/src/audio/instruments.ts +4 -0
  141. package/src/audio/keys/calibration.ts +56 -0
  142. package/src/audio/keys/electric.ts +8 -1
  143. package/src/audio/keys/engine.ts +13 -1
  144. package/src/audio/keys/piano.ts +22 -2
  145. package/src/audio/kits.ts +135 -6
  146. package/src/audio/live.ts +114 -20
  147. package/src/audio/native.ts +615 -0
  148. package/src/audio/preview.ts +30 -2
  149. package/src/audio/render-worker.ts +2 -0
  150. package/src/audio/renderer.ts +2 -0
  151. package/src/audio/resample.ts +2 -1
  152. package/src/audio/sampler.ts +85 -4
  153. package/src/audio/samples.ts +20 -2
  154. package/src/audio/sing/analysis.ts +193 -0
  155. package/src/audio/sing/engine.ts +949 -0
  156. package/src/audio/strings/bow.ts +48 -5
  157. package/src/audio/strings/engine.ts +8 -2
  158. package/src/audio/synth/oscillators.ts +31 -21
  159. package/src/audio/synth/voice.ts +34 -1
  160. package/src/audio/vocoder/bank.ts +314 -0
  161. package/src/audio/vocoder/carrier.ts +165 -0
  162. package/src/audio/vocoder/control.ts +68 -0
  163. package/src/audio/vocoder/detect.ts +50 -0
  164. package/src/audio/vocoder/index.ts +304 -0
  165. package/src/audio/vocoder/talkbox.ts +143 -0
  166. package/src/audio/wav.ts +500 -77
  167. package/src/audio/winds/engine.ts +5 -1
  168. package/src/audio/winds/trim.ts +28 -4
  169. package/src/audio/winds/trims1.ts +297 -0
  170. package/src/audio/winds/voice.ts +15 -2
  171. package/src/auth/cli.ts +38 -36
  172. package/src/auth/credentials.ts +30 -1
  173. package/src/auth/login.ts +15 -9
  174. package/src/auth/tui.ts +19 -10
  175. package/src/commands/arrange.ts +82 -32
  176. package/src/commands/autotune.ts +421 -0
  177. package/src/commands/calibration.ts +74 -0
  178. package/src/commands/clips.ts +887 -0
  179. package/src/commands/drums.ts +3 -2
  180. package/src/commands/edit.ts +11 -4
  181. package/src/commands/expression.ts +1 -1
  182. package/src/commands/formant.ts +221 -0
  183. package/src/commands/fx.ts +101 -32
  184. package/src/commands/grammar.ts +560 -0
  185. package/src/commands/help.ts +774 -366
  186. package/src/commands/history.ts +139 -10
  187. package/src/commands/keys.ts +25 -8
  188. package/src/commands/modal.ts +1 -1
  189. package/src/commands/music.ts +1 -1
  190. package/src/commands/nearest.ts +53 -0
  191. package/src/commands/pack.ts +9 -2
  192. package/src/commands/param-range.ts +56 -0
  193. package/src/commands/parses.ts +135 -0
  194. package/src/commands/progression.ts +170 -0
  195. package/src/commands/range.ts +763 -0
  196. package/src/commands/resample.ts +13 -10
  197. package/src/commands/rhythm.ts +3 -0
  198. package/src/commands/rig.ts +3 -24
  199. package/src/commands/sample.ts +11 -1
  200. package/src/commands/sing.ts +474 -0
  201. package/src/commands/strum.ts +13 -1
  202. package/src/commands/style.ts +415 -0
  203. package/src/commands/time.ts +6 -3
  204. package/src/commands/tuning.ts +3 -3
  205. package/src/commands/vocal-pitch.ts +617 -0
  206. package/src/commands/vocal.ts +147 -0
  207. package/src/commands/vocoder.ts +627 -0
  208. package/src/commands/wind.ts +2 -2
  209. package/src/fs/durable.ts +50 -0
  210. package/src/identity/actor.ts +127 -0
  211. package/src/lang/glossary.ts +511 -0
  212. package/src/launch-args.ts +267 -0
  213. package/src/main.ts +2508 -308
  214. package/src/media/cli.ts +20 -3
  215. package/src/media/import.ts +3 -1
  216. package/src/project/check.ts +21 -2
  217. package/src/project/clip-pins.ts +72 -0
  218. package/src/project/init.ts +23 -8
  219. package/src/project/sync.ts +418 -86
  220. package/src/render.ts +20 -1
  221. package/src/session/client.ts +107 -3
  222. package/src/session/clipboard.ts +79 -0
  223. package/src/session/daemon.ts +199 -5
  224. package/src/session/live-host.ts +232 -0
  225. package/src/session/meta.ts +14 -0
  226. package/src/session/origin.ts +174 -0
  227. package/src/session/port.ts +109 -14
  228. package/src/session/presence.ts +34 -4
  229. package/src/session/protocol.ts +365 -7
  230. package/src/session/rebase.ts +63 -12
  231. package/src/session/receipt.ts +265 -0
  232. package/src/session/shared-live.ts +131 -0
  233. package/src/session/store.ts +126 -45
  234. package/src/tui/arrange-menu.ts +225 -23
  235. package/src/tui/audition.ts +1 -1
  236. package/src/tui/euclid.ts +113 -27
  237. package/src/tui/fader.ts +282 -42
  238. package/src/tui/granular-menu.ts +2 -4
  239. package/src/tui/knob-fields.ts +107 -0
  240. package/src/tui/knob-map.ts +198 -0
  241. package/src/tui/menu-clips.ts +297 -0
  242. package/src/tui/menu-time.ts +20 -13
  243. package/src/tui/menu-voice.ts +405 -0
  244. package/src/tui/menu.ts +987 -179
  245. package/src/tui/modal-menu.ts +6 -6
  246. package/src/tui/performance-menu.ts +5 -2
  247. package/src/tui/play-chords.ts +4 -2
  248. package/src/tui/play-mode.ts +15 -1
  249. package/src/tui/play-session.ts +243 -28
  250. package/src/tui/sing-menu.ts +278 -0
  251. package/src/tui/style-menu.ts +104 -0
  252. package/src/tui/tape-mode.ts +580 -0
  253. package/src/tui/tape-view.ts +263 -0
  254. package/src/tui/vocoder-menu.ts +244 -0
  255. package/src/tui/wind-menu.ts +3 -3
  256. package/src/version.ts +8 -0
  257. package/src/web/fetch.ts +115 -29
  258. package/tui/activity.ts +180 -9
  259. package/tui/app.ts +491 -84
  260. package/tui/clip-row.ts +132 -0
  261. package/tui/delight.ts +193 -0
  262. package/tui/drawer.ts +233 -27
  263. package/tui/frame-gate.ts +76 -0
  264. package/tui/grammar.ts +143 -65
  265. package/tui/guide.ts +50 -5
  266. package/tui/highway.ts +309 -30
  267. package/tui/hints.ts +197 -0
  268. package/tui/hits.ts +7 -1
  269. package/tui/input.ts +60 -9
  270. package/tui/keys.ts +1 -1
  271. package/tui/knobs.ts +268 -0
  272. package/tui/play-strip.ts +80 -14
  273. package/tui/prompt.ts +1 -1
  274. package/tui/screen.ts +151 -18
  275. package/tui/tape.ts +439 -0
  276. package/tui/text.ts +35 -2
  277. package/tui/theme.ts +46 -1
@@ -1,13 +1,29 @@
1
1
  /**
2
2
  * The command reference, in one place: `/help` renders it in the overlay,
3
- * `dawg --help` prints it, and the docs mirror it. Music words are bare;
4
- * app commands take a slash (bare aliases keep working but are listed once,
5
- * in their canonical form).
3
+ * `dawg --help` prints it, and the docs mirror it. Groups are the ten topic
4
+ * ids (src/lang/glossary.ts), the same ids `/guide` and `/menu` take.
5
+ * A topic may have sub-groups (`sound · performance`) so no group runs
6
+ * past about fifteen rows. Commands are written bare, since the slash is
7
+ * optional; only the free-text window verbs (`/rename`, `/fork`,
8
+ * `/resume`), `/auth`, `/bpm` (a sample's tempo; bare `bpm` is song tempo)
9
+ * and `/play` (play mode; bare `play` is the transport) keep it.
6
10
  */
7
11
  import { GUITAR_TUNING_NAMES } from "../../core/chords.ts";
12
+ import { KEYS, type KeyScreen } from "../../tui/grammar.ts";
13
+ import { displayWidth, truncate } from "../../tui/text.ts";
14
+ import {
15
+ resolveTopic,
16
+ topicMiss,
17
+ TOPIC_SUMMARY,
18
+ TOPICS,
19
+ type TopicId,
20
+ } from "../lang/glossary.ts";
8
21
  import { EXPRESSION_USAGE } from "./expression.ts";
22
+ import { editDistance } from "./nearest.ts";
23
+ import { VOCAL_VERBS } from "./vocal.ts";
9
24
 
10
- export type HelpGroup = "music" | "session" | "window" | "keys";
25
+ /** A topic id, or a heading of the start page. */
26
+ export type HelpGroup = string;
11
27
 
12
28
  export type HelpEntry = Readonly<{
13
29
  /** Canonical form, e.g. `pan <-1..1>`. */
@@ -23,29 +39,16 @@ export type HelpSection = Readonly<{
23
39
 
24
40
  export const HELP_SECTIONS: readonly HelpSection[] = [
25
41
  {
26
- group: "music",
42
+ group: "sound",
27
43
  entries: [
28
- { command: "play", summary: "start the transport" },
29
- { command: "pause", summary: "stop the transport" },
30
- { command: "tempo <bpm>", summary: "20–300 BPM" },
31
- { command: "add <note> at <beat> [for <beats>]", summary: "add C4 at 0" },
32
- { command: "remove <id>", summary: "delete a note" },
33
- { command: "move <id> to <beat>", summary: "shift a note" },
34
- { command: "length <id> <beats>", summary: "resize a note" },
35
- { command: "velocity <id> <0..1>", summary: "note loudness" },
36
- { command: "bars <count>", summary: "loop length, 1–256" },
37
- { command: "extend <count> bars", summary: "lengthen the loop" },
38
44
  {
39
45
  command: "instrument <name>",
40
- summary: "sine piano pluck bass saw square triangle wavetable kit",
46
+ summary: "sine piano pluck bass sawtooth square triangle wavetable kit",
47
+ },
48
+ {
49
+ command: "synth <param> <value> | preset <name>",
50
+ summary: "the synth · synth lpf 1200 · synth alone lists every param",
41
51
  },
42
- { command: "volume <0..1>", summary: "track level" },
43
- { command: "pan <-1..1>", summary: "left … right" },
44
- { command: "mute", summary: "silence this track" },
45
- { command: "unmute", summary: "hear it again" },
46
- { command: "solo", summary: "only this track" },
47
- { command: "unsolo", summary: "every track again" },
48
- { command: "clear", summary: "remove this track's notes" },
49
52
  {
50
53
  command: "wt <table> | wt <0..1> | wt list",
51
54
  summary: "wavetable synth · wt basic · wt wt_digital:2 · wt 0.5",
@@ -56,92 +59,68 @@ export const HELP_SECTIONS: readonly HelpSection[] = [
56
59
  },
57
60
  {
58
61
  command:
59
- "grain <preset> | <param> <value> | on [voice V] | src synth:<name>|voice V | reset | off",
62
+ "grain <preset> | <param> <value> | on [voice V] | src synth:<name>|voice V",
60
63
  summary:
61
64
  "granular cloud · grain cloud · grain scan 0.2 · track swarm-2 · grain presets",
62
65
  },
63
66
  {
64
- command: "fx <effect> <param> <value> | on | off | preset <name>",
65
- summary: "effects · fx delay mix 0.3 · fx reverb on · fx lists them",
66
- },
67
- {
68
- command: "synth <param> <value> | preset <name>",
69
- summary: "synth voice · synth lpf 1200 · synth lists every param",
70
- },
71
- {
72
- command: "string <preset> | <param> <value> | presets | reset | off",
73
- summary:
74
- "plucked strings · string sitar · string buzz 0.8 · string ring 6",
67
+ command: "piano [<preset>] | grand | upright | felt | honkytonk",
68
+ summary: "modeled piano · piano ballad · piano lofi · piano prepared",
75
69
  },
76
70
  {
77
- command: "bowed [<preset>] | <param> <value> | presets",
78
- summary:
79
- "bowed strings · bowed violin · bowed cellos · bowed pressure 0.7 · bowed sord 1",
71
+ command: "keys <param> <value> | preset <name> | reset",
72
+ summary: "piano params · keys hardness 0.3 decay 1.5 · keys lists them",
80
73
  },
81
74
  {
82
- command: "rig <preset> | reset",
75
+ command: "epiano|wurli|clav [preset <name>] | <param> <value>",
83
76
  summary:
84
- "guitar rig · rig crunch · rig metal · rig shoegaze · rig lists them",
77
+ "electric keys · suitcase dyno funkclav · epiano vibe 0.6 · clav pickup bridge",
85
78
  },
86
79
  {
87
80
  command:
88
- "guitar tune <name|notes> | capo | hand | ring | position | reset",
89
- summary: "guitar fretting · guitar tune dadgad · guitar capo 2",
90
- },
91
- {
92
- command: "strum [chords] [pattern] [strokes D-DU-UDU] [speed 22ms]",
81
+ "tonewheel [<drawbars>] | combo [<tabs>] [<register>] | pipe [<stops>]",
93
82
  summary:
94
- "strummed guitar chords · strum G D Em C folk · strum I V vi IV · strum alone strums the track's chords",
83
+ "organs · tonewheel 888800008 perc 3rd · gospel · combo 08880 flute · pipe plenum · keys perc 3rd",
95
84
  },
96
85
  {
97
- command: "fx wobble|bloom|swell|double [param value]",
86
+ command: "rotary slow|fast|stop [at <beat>]",
98
87
  summary:
99
- "shoegaze · fx wobble depth 30 · fx double · fx reverb ir builtin:reverse",
100
- },
101
- {
102
- command: "track jangle|punk|funk|ragged|gtr-lead|gtr-metal|bachata",
103
- summary: "new guitar track with that rig",
88
+ "organ rotary speaker speed · rotary fast at 16 writes the keys-rotary lane",
104
89
  },
105
90
  {
106
- command: "stomp|head|cab <type> | <param> <value>",
91
+ command: "string <preset> | <param> <value> | presets | reset | off",
107
92
  summary:
108
- "pedal, amp, cabinet · stomp rat · head gain 7 gate -55 · cab 4x12",
109
- },
110
- {
111
- command: "piano [<preset>] | grand | upright | felt | honkytonk",
112
- summary: "modelled piano · piano ballad · piano lofi · piano prepared",
113
- },
114
- {
115
- command: "keys <param> <value> | preset <name> | reset",
116
- summary: "piano params · keys hardness 0.3 decay 1.5 · keys lists them",
93
+ "plucked strings · string sitar · string buzz 0.8 · string ring 6",
117
94
  },
118
95
  {
119
- command: "epiano|wurli|clav [preset <name>] | <param> <value>",
96
+ command: "bowed [<preset>] | <param> <value> | presets",
120
97
  summary:
121
- "electric keys · suitcase dyno funkclav · epiano vibe 0.6 · clav pickup bridge",
98
+ "bowed strings · bowed violin · bowed cellos · bowed pressure 0.7 · bowed sord 1",
122
99
  },
123
100
  {
124
101
  command:
125
- "tonewheel [<drawbars>] | combo [<registers>] [<voice>] | pipe [<stops>] [<row> <value> …]",
126
- summary:
127
- "organs · tonewheel 888800008 perc 3rd · gospel · combo 08880 flute · pipe plenum · keys perc 3rd",
128
- },
129
- {
130
- command: "rotary slow|fast|stop [at <beat>]",
131
- summary:
132
- "organ rotary speaker speed · rotary fast at 16 writes the keys-rotary lane",
102
+ "guitar tune <name|notes> | capo | hand | ring | position | reset",
103
+ summary: "guitar fretting · guitar tune dadgad · guitar capo 2",
133
104
  },
134
105
  {
135
106
  command:
136
107
  "modal <preset> | <param> <value> | mallet <name> | reset | off",
137
- summary:
138
- "mallets and bells · modal vibes · modal hardness 0.8 · modal presets",
108
+ summary: "mallets · modal vibes · modal hardness 0.8 · modal presets",
139
109
  },
140
110
  {
141
111
  command: "wind <preset> | <param> <value> | mute <name> | reset | off",
142
112
  summary:
143
113
  "winds and brass · wind flute · wind sax · wind players 4 · wind presets",
144
114
  },
115
+ {
116
+ command: "/try <sound command>",
117
+ summary: "hear it on a loop first · a A/B · enter keep",
118
+ },
119
+ ],
120
+ },
121
+ {
122
+ group: "sound · performance",
123
+ entries: [
145
124
  {
146
125
  command: "art <articulation>|off [target]",
147
126
  summary:
@@ -179,58 +158,180 @@ export const HELP_SECTIONS: readonly HelpSection[] = [
179
158
  "seeded feel at render · humanize 10 8 5 · humanize 20 bars 2-3",
180
159
  },
181
160
  { command: "expression", summary: "this track's performance settings" },
182
- { command: "filter <hz> [res]", summary: "low-pass · filter off" },
183
- { command: "delay <beats> [fb] [mix]", summary: "ping-pong · delay off" },
184
- { command: "reverb <mix> [size]", summary: "room · reverb off" },
161
+ ],
162
+ },
163
+ {
164
+ group: "sound · samples",
165
+ entries: [
185
166
  {
186
- command: "automate <lane> at <beat> <value>",
187
- summary: "volume pan filter resonance delay-feedback delay-mix wt",
167
+ command: "/sample [<path> [as <sample>]]",
168
+ summary: "add a sample · alone lists the samples",
188
169
  },
189
170
  {
190
- command: "automate <lane> points <b:v>...",
191
- summary: "several points · automate pan points 0:-1 4:1",
171
+ command: "/bpm <n> [<sample>]",
172
+ summary: "a sample's own tempo (the slash matters: bare bpm is tempo)",
192
173
  },
193
174
  {
194
- command: "automate <lane> remove <beat>",
195
- summary: "drop one point",
175
+ command: "fitmode [repitch|beats|tones|auto] [<sample>]",
176
+ summary: "how it fits · alone suggests one from the sound",
196
177
  },
197
- { command: "clear [<lane>] automation", summary: "drop a lane's points" },
198
178
  {
199
- command: "master <unit> on|off | preset <name> | <param> <value>",
179
+ command: "len <beats> [<sample>]",
180
+ summary: "the sample lasts n beats of the song",
181
+ },
182
+ {
183
+ command: "shift <semitones> [formant keep|follow|<n>] [<sample>]",
200
184
  summary:
201
- "song master · eq glue tape width limiter · master glue ratio 4",
185
+ "a sample's pitch without changing its length · shift 0 clears",
202
186
  },
203
187
  {
204
- command: "master <target> | target <lufs> | measure | off",
188
+ command: "fade [in|out] <seconds> [<sample>]",
189
+ summary: "a sample's fade in and out (Strudel fadeInTime/fadeTime)",
190
+ },
191
+ {
192
+ command:
193
+ "resample <track>|orbit <n>|master [section <name>|bars a-b] [grain]",
194
+ summary: "render to a pinned WAV on a new sampler (or granular) track",
195
+ },
196
+ ],
197
+ },
198
+ {
199
+ group: "voice",
200
+ entries: [
201
+ {
202
+ command: "sing [preset] [param value]",
205
203
  summary:
206
- "loudness · master streaming · master target -9 · master measure",
204
+ "built-in singing voice: aah ooh choir chorale airy glass lament soprano basso, throat: drone khoomei sygyt kargyraa",
207
205
  },
208
- { command: "track name <text>", summary: "rename this track" },
209
- { command: "meter <1..16>", summary: "beats per bar" },
210
206
  {
211
- command: "tempo <bpm> at <beat>|bar <n> [ramp|exp]",
212
- summary: "tempo change · tempo 90 at bar 9 ramp · tempo clear",
207
+ command: "sing vowels <v> …",
208
+ summary:
209
+ "vowels for the track's notes in order, cycled · sing vowels a e i o",
213
210
  },
214
211
  {
215
- command: "rit|accel [<n> bars] [to <bpm>] [at bar <n>]",
216
- summary: "gradual · rit 4 bars to 80 · a tempo · tempo primo",
212
+ command: "note vowel <v|a>u> [target]",
213
+ summary: "sung vowel for selected notes · note vowel o bar 2",
217
214
  },
218
215
  {
219
- command: "fermata [at <beat>|at bar <n>|at end] [<extra beats>]",
220
- summary: "hold a beat · fermata at 31 2 · fermata clear",
216
+ command: "lyrics [bar] sun-lit morn-ing",
217
+ summary: "syllables onto the notes (- splits, _ holds, ~ skips)",
221
218
  },
222
219
  {
223
- command: "meter <n>/<d> [at bar <n>]",
224
- summary: "meter change · meter 7/8 at bar 5 · meter clear",
220
+ command: "autotune [preset] [field value …] | off | presets",
221
+ summary:
222
+ "pitch correction · autotune hard · autotune gentle · autotune to notes melody · autotune key D bayati",
225
223
  },
226
224
  {
227
- command: "track rate|phase|cycle <n> | off",
228
- summary: "polytempo · track rate 3/2 · track cycle 3",
225
+ command:
226
+ "/formant <-12..12> [mix] | deep | giant | bright | tiny | on | off",
227
+ summary:
228
+ "formant shift at constant pitch · /formant -4 deeper · /formant 3 smaller",
229
229
  },
230
230
  {
231
- command: "track phasing <beats> [over <beats>|hold <n>]",
232
- summary: "Reich phasing · track phasing 3 hold 8 · track time off",
231
+ command:
232
+ "/vowel <v> [<to> [<morph>]] | ee | to <v>|off | morph <0..1> | off",
233
+ summary: "vowel filter · /vowel a · /vowel a o 0.5 morphs a toward o",
234
+ },
235
+ {
236
+ command:
237
+ "vocoder [preset] | src <track> | <param> <value|reset> | off | presets",
238
+ summary:
239
+ "vocode the focused voice onto a synth (classic robot talkbox choir glass whisper smear lofi) · help vocoder lists params",
240
+ },
241
+ ],
242
+ },
243
+ {
244
+ group: "voice · clips",
245
+ entries: [
246
+ { command: "vocal", summary: "voice tools: lists every verb" },
247
+ {
248
+ command:
249
+ "clip [id] gain -3 | gain by -3 | fade .01 .2 | fade in .01 | split 7",
250
+ summary: "edit an audio clip (dB, seconds, 1-based bars)",
251
+ },
252
+ {
253
+ command:
254
+ "clip [id] trim offset 1 dur 4|end | rev | repeat 2 [to 32] | mute | rm",
255
+ summary: "trim, reverse, repeat, mute or remove a clip",
233
256
  },
257
+ ...VOCAL_VERBS.map((verb) => ({
258
+ command: `vocal ${verb.usage}`,
259
+ summary: verb.summary,
260
+ })),
261
+ ],
262
+ },
263
+ {
264
+ group: "effects",
265
+ entries: [
266
+ {
267
+ command: "fx <effect> <param> <value> | on | off | preset <name>",
268
+ summary:
269
+ "effects · fx delay mix 0.3 · fx reverb on · fx alone lists them",
270
+ },
271
+ {
272
+ command: "filter <hz> [res] · delay <beats> [fb] [mix] · reverb <mix>",
273
+ summary:
274
+ "shortcuts for fx filter, delay and reverb · filter 1200 · delay 0.5 · reverb 0.3",
275
+ },
276
+ {
277
+ command: "rig <preset> | reset",
278
+ summary:
279
+ "guitar amp and pedals · rig crunch · rig jangle · rig metal · rig alone lists them",
280
+ },
281
+ {
282
+ command: "stomp|head|cab <type> | <param> <value>",
283
+ summary:
284
+ "pedal, amp, cabinet · stomp rat · head gain 7 gate -55 · cab 4x12",
285
+ },
286
+ {
287
+ command: "fx wobble|bloom|swell|double [param value]",
288
+ summary:
289
+ "shoegaze · fx wobble depth 30 · fx double · fx reverb ir builtin:reverse",
290
+ },
291
+ ],
292
+ },
293
+ {
294
+ group: "rhythm",
295
+ entries: [
296
+ {
297
+ command: "hit <drum> at <beat>",
298
+ summary: "one hit on a kit track · hit kick at 0",
299
+ },
300
+ {
301
+ command: "pattern <drum> <beats...> | every <step>",
302
+ summary: "a drum on beats · pattern kick every 1",
303
+ },
304
+ { command: "clear <drum>", summary: "remove one drum's hits" },
305
+ {
306
+ command: "euclid <drum> <pulses> [<steps>] [rotate <n>]",
307
+ summary: "generated rhythm · euclid hat 7 16 rotate 2",
308
+ },
309
+ {
310
+ command: "euclid <drum> <field> <value> | off | freeze",
311
+ summary: "repeats pace accent prob swing …",
312
+ },
313
+ { command: "grid <drum> <x.X.>", summary: "explicit steps · X accent" },
314
+ {
315
+ command: "pattern [name]",
316
+ summary: "groove picker · hear each groove move",
317
+ },
318
+ {
319
+ command: "/kit [name]",
320
+ summary: "kit picker · synth kits, then sample kits",
321
+ },
322
+ {
323
+ command: "pack list|info|use|add",
324
+ summary: "sample packs · pack use 909/bd",
325
+ },
326
+ {
327
+ command: "euclid [drum]",
328
+ summary: "euclid rhythm editor · Ctrl-K › Rhythm",
329
+ },
330
+ ],
331
+ },
332
+ {
333
+ group: "chords",
334
+ entries: [
234
335
  {
235
336
  command: "key <tonic> <mode> | none",
236
337
  summary: "song key · key A minor · key F# dorian",
@@ -256,298 +357,473 @@ export const HELP_SECTIONS: readonly HelpSection[] = [
256
357
  summary: "detune one note · cents n3 -14 · add E4-14c at 0",
257
358
  },
258
359
  {
259
- command: "section <name> <a>-<b> | add | dup | move | rename | delete",
260
- summary: "song sections · section chorus 9-16 · section lists them",
360
+ command: "progression <chords> [each 4] [at 0] [bass]",
361
+ summary:
362
+ "sustained voice-led chords · progression i7 IV7 each 8 · progression Am7 D9 bass",
261
363
  },
262
364
  {
263
- command: "section loop | jump | mute | vary <name>",
264
- summary: "section loop chorus · section mute verse drums",
365
+ command: "strum [chords] [pattern] [strokes D-DU-UDU] [speed 22ms]",
366
+ summary:
367
+ "strummed guitar chords · strum G D Em C folk · strum I V vi IV · strum alone strums the track's chords",
265
368
  },
266
369
  {
267
- command: "form <section…> | off | bake",
268
- summary: "song form · form intro verse chorus*2 outro",
370
+ command: "/chords auto|manual|off",
371
+ summary: "chords in play mode · /chords alone shows settings",
269
372
  },
270
373
  {
271
- command: "build | drop | fill [<section> | <a>-<b>]",
272
- summary: "riser, roll, sweep · pre-drop cut and impact · drum fill",
374
+ command: "play degrees|in-key|chromatic",
375
+ summary:
376
+ "in play mode the home row plays the key's degrees (any tuning)",
273
377
  },
378
+ ],
379
+ },
380
+ {
381
+ group: "mix",
382
+ entries: [
383
+ { command: "volume <0..1>", summary: "track level" },
384
+ { command: "pan <-1..1>", summary: "left … right" },
274
385
  {
275
- command: "hit <voice> at <beat>",
276
- summary: "kit tracks · hit kick at 0",
386
+ command: "knobs [sound|mix|master|tempo|fx <effect>]",
387
+ summary: "four knobs ● moves ▲ sizes ■ shapes ◆ level · tab all params",
277
388
  },
278
389
  {
279
- command: "pattern <voice> <beats...> | every <step>",
280
- summary: "pattern kick every 1",
390
+ command: "mix | volume <track> <0..1> | pan <track> <-1..1>",
391
+ summary: "the mixer page · volume drums 0.5 sets drums, focus stays",
281
392
  },
282
- { command: "clear <voice>", summary: "remove one drum voice" },
393
+ { command: "mute", summary: "silence this track" },
394
+ { command: "unmute", summary: "hear it again" },
395
+ { command: "solo", summary: "only this track" },
396
+ { command: "unsolo", summary: "every track again" },
283
397
  {
284
- command: "euclid <voice> <pulses> [<steps>] [rotate <n>]",
285
- summary: "generated rhythm · euclid hat 7 16 rotate 2",
398
+ command: "automate <lane> at <beat> <value>",
399
+ summary: "volume pan filter resonance delay-feedback delay-mix wt",
286
400
  },
287
401
  {
288
- command: "euclid <voice> <field> <value> | off | freeze",
289
- summary: "repeats pace accent prob swing …",
402
+ command: "automate <lane> points <b:v>...",
403
+ summary: "several points · automate pan points 0:-1 4:1",
290
404
  },
291
- { command: "grid <voice> <x.X.>", summary: "explicit steps · X accent" },
405
+ { command: "automate <lane> remove <beat>", summary: "drop one point" },
406
+ { command: "clear [<lane>] automation", summary: "drop a lane's points" },
292
407
  {
293
- command: "shift <semitones> [formant keep|follow|<n>] [<voice>]",
408
+ command: "master <unit> on|off | preset <name> | <param> <value>",
294
409
  summary:
295
- "a sample's pitch without changing its length · shift 0 clears",
410
+ "song master · eq glue tape width limiter · master glue ratio 4",
296
411
  },
297
412
  {
298
- command: "fade [in|out] <seconds> [<voice>]",
299
- summary: "a sample's fade in and out (Strudel fadeInTime/fadeTime)",
413
+ command: "master <target> | target <lufs> | measure | off",
414
+ summary:
415
+ "loudness · master streaming · master target -9 · master measure",
416
+ },
417
+ ],
418
+ },
419
+ {
420
+ group: "arrange",
421
+ entries: [
422
+ { command: "add <note> at <beat> [for <beats>]", summary: "add C4 at 0" },
423
+ { command: "remove <id>", summary: "delete a note" },
424
+ { command: "move <id> to <beat>", summary: "shift a note" },
425
+ { command: "length <id> <beats>", summary: "resize a note" },
426
+ { command: "velocity <id> <0..1>", summary: "note loudness" },
427
+ { command: "clear", summary: "remove this track's notes" },
428
+ { command: "track <name>", summary: "focus a track, creating it if new" },
429
+ { command: "tracks", summary: "list tracks" },
430
+ {
431
+ command: "track rm|move <name> [<position>]",
432
+ summary:
433
+ "remove a track (Ctrl-Z undoes) or move it · rm and delete are aliases",
300
434
  },
435
+ { command: "track name <text>", summary: "rename this track" },
301
436
  {
302
- command:
303
- "resample <track>|orbit <n>|master [section <name>|bars a-b] [grain]",
304
- summary: "render to a pinned WAV on a new sampler (or granular) track",
437
+ command: "track rate|phase|loop <n> | off",
438
+ summary: "polytempo · track rate 3/2 · track loop 3",
439
+ },
440
+ {
441
+ command: "track phasing <beats> [over <beats>|hold <n>]",
442
+ summary: "Reich phasing · track phasing 3 hold 8 · track time off",
305
443
  },
306
- { command: "undo", summary: "step back · Ctrl-Z" },
307
- { command: "redo", summary: "step forward · Ctrl-Y" },
308
444
  ],
309
445
  },
310
446
  {
311
- group: "session",
447
+ group: "arrange · song",
312
448
  entries: [
313
- { command: "/sessions", summary: "list sessions in this workspace" },
314
- { command: "/resume [<n>|<name>|<id>]", summary: "switch session" },
315
- { command: "/rename <name>|--auto", summary: "name this session" },
316
- { command: "/fork [<name>]", summary: "copy into a new session" },
317
- { command: "/status", summary: "name · revision · digest · storage" },
318
449
  {
319
- command: "/export <file>",
320
- summary: "write track.loop/v1 JSON, or MIDI for .mid",
450
+ command: "section <name> <a>-<b> | add | dup | move | rename | remove",
451
+ summary:
452
+ "song sections · section chorus 9-16 · section alone lists them",
321
453
  },
322
- { command: "/import <file>", summary: "replace the score from a file" },
323
454
  {
324
- command: "/sample [<path> [as <voice>]]",
325
- summary: "add a sample voice · list voices",
455
+ command: "section loop | jump | mute | vary <name>",
456
+ summary: "section loop chorus · section mute verse drums",
326
457
  },
327
458
  {
328
- command: "/bpm <n> [<voice>]",
329
- summary:
330
- "the sample's own tempo (slash needed: bare bpm is song tempo)",
459
+ command: "form <section…> | off | bake",
460
+ summary: "song form · form intro verse chorus*2 outro",
331
461
  },
332
462
  {
333
- command: "/fitmode [repitch|beats|tones|auto] [<voice>]",
334
- summary: "how it fits · alone suggests one from the sound",
463
+ command: "build | drop | fill [<section> | <a>-<b>]",
464
+ summary: "riser, roll, sweep · pre-drop cut and impact · drum fill",
335
465
  },
336
466
  {
337
- command: "/len <beats> [<voice>]",
338
- summary: "the sample lasts n beats of the song",
467
+ command: "style <id> [bars] [seed]",
468
+ summary: "a whole song in a style · style bebop 16 3 · style again",
339
469
  },
470
+ {
471
+ command: "style list|search|info · style blend <a> <b> [w]",
472
+ summary: "the style tree · style search maqam",
473
+ },
474
+ { command: "bars <count>", summary: "song length, 1–256" },
475
+ { command: "extend <count> bars", summary: "lengthen the song" },
340
476
  ],
341
477
  },
342
478
  {
343
- group: "window",
479
+ group: "arrange · range",
344
480
  entries: [
345
481
  {
346
- command: "/track <name>",
347
- summary: "focus a track, creating it if new",
482
+ command: "/tape [on|off]",
483
+ summary: "every track across the bars · c copy x cut v paste · ctrl-t",
348
484
  },
349
- { command: "/tracks", summary: "list tracks" },
350
- { command: "/view focus|all", summary: "one track or every track" },
351
- { command: "/transcript", summary: "scrollable log · Ctrl-O" },
352
- { command: "/theme default|high-contrast|mono", summary: "colors" },
353
- { command: "/motion on|off", summary: "animation" },
354
- { command: "/model [alias]", summary: "pick a model · cost per prompt" },
355
485
  {
356
- command: "/login [gateway|openrouter|codex|claude]",
357
- summary: "sign in to a provider",
486
+ command: "keys record [replace|off]",
487
+ summary: "record the loop in play · one undo step a pass · r R on tape",
358
488
  },
359
489
  {
360
- command: "/logout [provider]",
361
- summary: "forget keys and the saved choice",
490
+ command: "loop <a>-<b> | <section> | next | prev | off",
491
+ summary: "the loop range · loop 5-6 · loop next steps it along",
362
492
  },
363
- { command: "/auth [--check]", summary: "provider and audio status" },
364
- { command: "/play [on|off]", summary: "keyboard play mode · Ctrl-P" },
365
493
  {
366
- command: "/play degrees|chromatic",
367
- summary: "home row plays the key's scale degrees (any tuning) · i",
494
+ command: "copy [<track>|all] <a>-<b> to <bar> [x<N>] [insert|merge]",
495
+ summary: "copy bass 5-6 to 7 x2 · copy all chorus to 17 insert",
368
496
  },
369
497
  {
370
- command: "/pattern [name]",
371
- summary: "drum groove picker · moving previews",
498
+ command: "move [<track>|all] <a>-<b> to <bar> [insert]",
499
+ summary: "move bass 5-6 to 9 · the source empties",
372
500
  },
373
501
  {
374
- command: "/kit [name]",
375
- summary: "drum kit picker · synth kits, then samples",
502
+ command: "clear [<track>|all] <a>-<b>",
503
+ summary: "clear bass 5-6 · empty bars, the bars stay",
376
504
  },
377
505
  {
378
- command: "/pack list|info|use|add",
379
- summary: "sample packs · /pack use 909/bd",
506
+ command: "copy [<track>|all] <a>-<b> · paste [at <bar>] [x<N>]",
507
+ summary: "the clipboard · copy all 1-4 · paste at 9",
380
508
  },
381
509
  {
382
- command: "/euclid [voice]",
383
- summary: "T-1 style rhythm editor · Rhythm in /menu",
510
+ command: "reverse [<track>|all] <a>-<b>",
511
+ summary: "mirror the bars · reverse bass 5-6",
384
512
  },
385
513
  {
386
- command: "/menu [section]",
387
- summary: "every setting by hand · Ctrl-K",
514
+ command: "bars insert <n> at <bar> | bars remove <a>-<b>",
515
+ summary: "shift later music · sections, clips, automation, tempo",
388
516
  },
389
517
  {
390
- command: "/try <sound command>",
391
- summary: "hear it on a loop first · a A/B · enter keep",
518
+ command: "jump <bar>[.<beat>] | <section>",
519
+ summary: "move the playhead · jump 5 · jump 5.3 · jump chorus",
520
+ },
521
+ {
522
+ command: "section split <name> at <bar> | join <name>",
523
+ summary: "section split chorus at 13 · section join chorus",
524
+ },
525
+ {
526
+ command: "form print",
527
+ summary: "print form to tape · alias of form bake",
528
+ },
529
+ ],
530
+ },
531
+ {
532
+ group: "project",
533
+ entries: [
534
+ { command: "play", summary: "start the transport" },
535
+ { command: "pause", summary: "stop the transport" },
536
+ {
537
+ command: "/play [on|off]",
538
+ summary: "play mode: the computer keyboard plays notes · ctrl-p",
539
+ },
540
+ { command: "tempo <bpm>", summary: "20–300 BPM" },
541
+ {
542
+ command: "tempo <bpm> at <beat>|bar <n> [ramp|exp]",
543
+ summary: "tempo change · tempo 90 at bar 9 ramp · tempo clear",
544
+ },
545
+ {
546
+ command: "rit|accel [<n> bars] [to <bpm>] [at bar <n>]",
547
+ summary: "gradual · rit 4 bars to 80 · a tempo · tempo primo",
548
+ },
549
+ {
550
+ command: "fermata [at <beat>|at bar <n>|at end] [<extra beats>]",
551
+ summary: "hold a beat · fermata at 31 2 · fermata clear",
552
+ },
553
+ { command: "meter <1..16>", summary: "beats per bar" },
554
+ {
555
+ command: "meter <n>/<d> [at bar <n>]",
556
+ summary: "meter change · meter 7/8 at bar 5 · meter clear",
392
557
  },
393
558
  {
394
559
  command: "/click on|off|<volume>",
395
- summary: "metronome · /count-in 0-2 · /grid 1/16",
560
+ summary: "metronome · /count-in 0-2 · grid 1/16",
396
561
  },
397
562
  {
398
- command: "/chords auto|manual|off",
399
- summary: "play-mode chords · /chords for settings",
563
+ command: "export <file> [stems]",
564
+ summary:
565
+ "JSON, MIDI for .mid, WAV for .wav · export song.wav · export mix.wav stems",
400
566
  },
567
+ { command: "import <file>", summary: "replace the score from a file" },
401
568
  {
402
- command: "/help [topic]",
403
- summary: "start here · /help all for everything",
569
+ command: "calibration [0|1|latest|off]",
570
+ summary: "sound fixes · 1 chokes hats, levels keys · 0 legacy",
404
571
  },
405
572
  {
406
- command: "/guide [topic]",
407
- summary: "short how-to guides · F1",
573
+ command: "undo [all]",
574
+ summary: "this pane's last edit · Ctrl-Z · all: anyone's",
408
575
  },
576
+ {
577
+ command: "redo [all]",
578
+ summary: "step forward · Ctrl-Y · all: anyone's",
579
+ },
580
+ { command: "/status", summary: "name · revision · digest · storage" },
409
581
  ],
410
582
  },
411
583
  {
412
- group: "keys",
584
+ group: "project · window",
413
585
  entries: [
414
- { command: "Enter", summary: "submit" },
415
- { command: "Shift-Enter", summary: "newline" },
416
- { command: "Alt-Enter", summary: "queue the request" },
417
- { command: "Ctrl-Q", summary: "toggle queue mode" },
418
- { command: "Ctrl-Z / Ctrl-Y", summary: "undo / redo" },
419
- { command: "Ctrl-O", summary: "transcript" },
420
- { command: "Esc", summary: "cancel the agent · back one level" },
421
- { command: "?", summary: "keys for the screen you are on" },
422
- { command: "Space", summary: "play/pause on an empty prompt" },
586
+ { command: "/view focus|all", summary: "one track or every track" },
587
+ { command: "/transcript", summary: "scrollable log · Ctrl-O" },
588
+ { command: "/theme default|high-contrast|mono", summary: "colors" },
589
+ { command: "/motion on|off", summary: "animation" },
423
590
  {
424
- command: "Ctrl-P",
425
- summary: "play mode · Z/X octave · R record · Q chords",
591
+ command: "/menu [section]",
592
+ summary: "every setting by hand · Ctrl-K · /menu <topic>",
426
593
  },
427
594
  {
428
- command: "Ctrl-K",
429
- summary: "menu · ←→ adjust · / filter · x reset",
595
+ command: "help [topic]",
596
+ summary: "start here · help <topic> · help all for everything",
430
597
  },
431
- { command: "Ctrl-C", summary: "exit" },
598
+ { command: "/guide [topic]", summary: "short how-to guides · F1" },
432
599
  ],
433
600
  },
434
- ];
435
-
436
- /**
437
- * `/help` with no topic: a short, task-first guide. Each row is something to
438
- * type or press, then what it does. The full reference is `/help all` (or one
439
- * group: `/help music`).
440
- */
441
- export const HELP_GUIDE: readonly HelpSection[] = [
442
601
  {
443
- group: "start here" as HelpGroup,
602
+ group: "project · session",
444
603
  entries: [
445
- { command: "type a request", summary: "“add a walking bass in A minor”" },
446
- { command: "ctrl-p", summary: "play notes on the computer keyboard" },
447
- {
448
- command: "ctrl-k",
449
- summary: "menu: sound, effects, performance, mix, master, arrange …",
450
- },
451
- { command: "? · ctrl-z · ctrl-y", summary: "keys here · undo · redo" },
604
+ { command: "/sessions", summary: "list sessions in this workspace" },
605
+ { command: "/resume [<n>|<name>|<id>]", summary: "switch session" },
606
+ { command: "/rename <name>|--auto", summary: "name this session" },
607
+ { command: "/fork [<name>]", summary: "copy into a new session" },
452
608
  ],
453
609
  },
454
610
  {
455
- group: "play notes" as HelpGroup,
611
+ group: "project · audio",
456
612
  entries: [
457
613
  {
458
- command: "ctrl-p, a s d f …",
459
- summary: "piano keys · z x octave · esc leave",
614
+ command: "audio",
615
+ summary: "the output and input in use, and the lists",
460
616
  },
461
617
  {
462
- command: "r, then space",
463
- summary: "record over the loop (one undo a bar)",
618
+ command: "audio out <name|default>",
619
+ summary: "play through one output · saved on this machine",
464
620
  },
465
- ],
466
- },
467
- {
468
- group: "make drums" as HelpGroup,
469
- entries: [
470
621
  {
471
- command: "/pattern · /kit",
472
- summary: "pick a groove · pick a drum kit",
622
+ command: "audio in <name|default>",
623
+ summary: "the input to meter (recording takes comes later)",
473
624
  },
474
- { command: "/euclid", summary: "rhythm editor: pulses, steps, rotation" },
625
+ { command: "audio test", summary: "a short tone, then the input level" },
475
626
  ],
476
627
  },
477
628
  {
478
- group: "shape the sound" as HelpGroup,
629
+ group: "project · panes",
479
630
  entries: [
631
+ { command: "panes", summary: "who has this session open, on what" },
480
632
  {
481
- command: "ctrl-k › Sound",
482
- summary: "instrument, strings, envelope, filter, wavetable",
633
+ command: "pane <screen> [<track>]",
634
+ summary: "copies dawg pane … for another terminal",
483
635
  },
484
- { command: "ctrl-k › Effects", summary: "delay, reverb, distortion …" },
636
+ { command: "pin · unpin", summary: "keep this pane on its track" },
485
637
  {
486
- command: "master streaming",
487
- summary: "finish: -14 LUFS, limiter · master measure",
638
+ command: "follow [<letter>] · unfollow",
639
+ summary: "follow another pane's track · follow b",
488
640
  },
489
- { command: "/try fx reverb mix 0.6", summary: "hear it before keeping" },
490
641
  ],
491
642
  },
492
643
  {
493
- group: "shape the performance" as HelpGroup,
644
+ group: "agent",
494
645
  entries: [
495
646
  {
496
- command: "art staccato bar 2",
497
- summary: "humanize 8 5 · ctrl-k › Sound › performance",
647
+ command: "/model [alias]",
648
+ summary: "pick the agent's model · /model lists them · /model fast",
498
649
  },
499
- ],
500
- },
501
- {
502
- group: "chords" as HelpGroup,
503
- entries: [
504
- { command: "key A minor", summary: "set the song key" },
505
650
  {
506
- command: "ctrl-p, then q",
507
- summary: "chord mode · 1–4 type · 5–8 extension · n next",
651
+ command: "/model key [gateway|openrouter|codex|claude]",
652
+ summary: "add an agent key (optional) · finds existing setups first",
508
653
  },
509
- ],
510
- },
511
- {
512
- group: "song structure" as HelpGroup,
513
- entries: [
514
654
  {
515
- command: "section verse 1-8",
516
- summary: "name bars · form verse chorus*2 orders them",
655
+ command: "/logout [provider]",
656
+ summary: "forget keys and the saved choice",
517
657
  },
658
+ { command: "/auth [--check]", summary: "provider and audio status" },
518
659
  {
519
- command: "build · drop · fill",
520
- summary: "transitions · ctrl-k › Arrange · /help arrange",
660
+ command: "/showme on|quiet|off",
661
+ summary:
662
+ "the agent types its commands in your prompt bar as it streams",
521
663
  },
522
664
  ],
523
665
  },
666
+ ];
667
+
668
+ /**
669
+ * `/help` with no topic: start here, then the ten topics. Each row is
670
+ * something to type or press, then what it does. The full reference is
671
+ * `/help all`; one topic is `/help <topic>`.
672
+ */
673
+ export const HELP_GUIDE: readonly HelpSection[] = [
524
674
  {
525
- group: "more" as HelpGroup,
675
+ group: "start here",
526
676
  entries: [
527
677
  {
528
- command: "rit 4 bars to 80",
529
- summary: "tempo, fermatas, meter · tuning pelog · scale yaman",
678
+ command: "type a request",
679
+ summary: "“add a walking bass in A minor” (with an agent)",
680
+ },
681
+ {
682
+ command: "style deep-house",
683
+ summary: "a whole song in a style · style list",
530
684
  },
531
685
  {
532
- command: "/help all",
533
- summary: "every command and key · /guide feature guides · F1",
686
+ command: "space · ctrl-p",
687
+ summary: "play · play mode: the keyboard plays notes",
534
688
  },
689
+ { command: "ctrl-k", summary: "menu: every setting by hand" },
690
+ { command: "? · ctrl-z · ctrl-y", summary: "keys here · undo · redo" },
691
+ ],
692
+ },
693
+ {
694
+ group: "topics · help <topic>",
695
+ entries: [
696
+ ...TOPICS.map((id) => ({ command: id, summary: TOPIC_SUMMARY[id] })),
697
+ { command: "all", summary: "every command · /guide <topic> · F1" },
535
698
  ],
536
699
  },
537
700
  ];
538
701
 
539
- /** Topics `/help <topic>` takes, besides `all`. */
540
- export const HELP_TOPICS = [
541
- "music",
542
- "session",
543
- "window",
544
- "keys",
545
- "arrange",
546
- ] as const;
702
+ /**
703
+ * `/help arrange`: one screen around the range verbs (design F11), each row
704
+ * a line to type. The full syntax of every arranging command stays in
705
+ * `/help all` and `/help <command>` (`/help copy`, `/help section`).
706
+ */
707
+ export const ARRANGE_PAGE: HelpSection = {
708
+ group: "arrange",
709
+ entries: [
710
+ {
711
+ command: "loop 5-6 · loop chorus",
712
+ summary: "loop the bars · loop next · loop off",
713
+ },
714
+ {
715
+ command: "copy bass 5-6 to 7",
716
+ summary: "copy bars · x2 repeats · all = every track",
717
+ },
718
+ {
719
+ command: "move bass 5-6 to 9",
720
+ summary: "move bars · the source empties",
721
+ },
722
+ {
723
+ command: "clear bass 5-6",
724
+ summary: "empty bars · reverse bass 5-6 mirrors",
725
+ },
726
+ {
727
+ command: "copy all 1-4 · paste at 9",
728
+ summary: "the clipboard, for later",
729
+ },
730
+ { command: "jump 5 · jump chorus", summary: "move the playhead" },
731
+ { command: "ctrl-t · tape", summary: "every track across the bars" },
732
+ {
733
+ command: "section chorus 9-16",
734
+ summary: "name bars · section loop chorus",
735
+ },
736
+ {
737
+ command: "form verse verse chorus",
738
+ summary: "song form · form print lays it out",
739
+ },
740
+ {
741
+ command: "build · drop · fill chorus",
742
+ summary: "riser · pre-drop cut · drum fill",
743
+ },
744
+ { command: "style deep-house 16", summary: "a whole song in a style" },
745
+ ],
746
+ };
747
+
748
+ /**
749
+ * `/help panes`: several terminals on one session (§12). Its rows are the
750
+ * `project · panes` reference group under a heading of its own.
751
+ */
752
+ function panesLines(width: number): string[] {
753
+ const group = HELP_SECTIONS.find((s) => s.group === "project · panes")!;
754
+ return [
755
+ ...sectionLines([{ ...group, group: "panes" }], width),
756
+ "",
757
+ truncate("dawg pane tape in another terminal · guide panes", width),
758
+ ];
759
+ }
760
+
761
+ /** Topics `/help <topic>` takes, besides `all` (aliases resolve too). */
762
+ export const HELP_TOPICS = TOPICS;
763
+
764
+ /**
765
+ * `/help keys`: every screen's `?` panel, straight from tui/grammar.ts
766
+ * KEYS, so the reference and the panels never disagree.
767
+ */
768
+ export function keysLines(width = 80): string[] {
769
+ const seen = new Set<string>();
770
+ const sections = (Object.keys(KEYS) as KeyScreen[]).flatMap((screen) =>
771
+ KEYS[screen].map((section) => ({
772
+ title:
773
+ section.title === screen || screen === "prompt"
774
+ ? section.title
775
+ : `${screen} · ${section.title}`,
776
+ rows: section.rows,
777
+ })),
778
+ );
779
+ const column =
780
+ Math.min(
781
+ 24,
782
+ Math.max(
783
+ ...sections.flatMap((s) => s.rows.map(([keys]) => displayWidth(keys))),
784
+ ),
785
+ ) + 2;
786
+ const lines = ["── keys"];
787
+ for (const section of sections) {
788
+ const key = JSON.stringify(section.rows);
789
+ if (seen.has(key)) continue;
790
+ seen.add(key);
791
+ lines.push("", truncate(`── ${section.title}`, width));
792
+ for (const [keys, action] of section.rows)
793
+ lines.push(
794
+ truncate(
795
+ `${keys}${" ".repeat(Math.max(1, column - displayWidth(keys)))}${action}`,
796
+ width,
797
+ ),
798
+ );
799
+ }
800
+ return lines;
801
+ }
802
+
803
+ /** The last rows of a topic page: the same id in the other two doors. */
804
+ function doors(id: TopicId, width: number): string[] {
805
+ return ["", truncate(`guide ${id} · menu ${id} · help all`, width)];
806
+ }
807
+
808
+ /** A topic's groups: `sound`, then `sound · performance`, `sound · samples`. */
809
+ export function topicSections(id: TopicId): readonly HelpSection[] {
810
+ return HELP_SECTIONS.filter(
811
+ (section) => section.group === id || section.group.startsWith(`${id} · `),
812
+ );
813
+ }
814
+
815
+ /** One topic's page: its groups, then where else the topic opens. */
816
+ function topicLines(id: TopicId, width: number): string[] {
817
+ if (id === "keys") return [...keysLines(width), ...doors(id, width)];
818
+ if (id === "arrange")
819
+ return [...sectionLines([ARRANGE_PAGE], width), ...doors(id, width)];
820
+ return [...sectionLines(topicSections(id), width), ...doors(id, width)];
821
+ }
547
822
 
548
823
  /**
549
- * Rows for `/help [topic]`: the guide with no topic, the full reference for
550
- * `all`, one group for its name; undefined for an unknown topic.
824
+ * Rows for `/help [topic]`: the start page with no topic, the full
825
+ * reference for `all`, a topic for its id or an alias, a command's rows
826
+ * for its verb (`/help vocoder`); undefined for an unknown word.
551
827
  */
552
828
  export function helpTopicLines(
553
829
  topic: string | undefined,
@@ -557,34 +833,92 @@ export function helpTopicLines(
557
833
  if (!name) return sectionLines(HELP_GUIDE, width, 20);
558
834
  if (name === "all" || name === "commands" || name === "reference")
559
835
  return helpLines(width);
560
- if (name === "arrange" || name === "arrangement" || name === "sections")
561
- return sectionLines([arrangeSection()], width);
562
- const group = HELP_SECTIONS.find((section) => section.group === name);
563
- return group ? sectionLines([group], width) : undefined;
836
+ if ((TOPICS as readonly string[]).includes(name))
837
+ return topicLines(name as TopicId, width);
838
+ if (name === "panes" || name === "pane") return panesLines(width);
839
+ const spelled = HELP_COMMAND_ALIASES[name];
840
+ if (spelled) return commandTopic(spelled, width, name);
841
+ const alias = resolveTopic(name);
842
+ // A topic alias wins unless a reference row starts with the word:
843
+ // `/help sections` is the arrange topic, `/help scale` the command.
844
+ const command =
845
+ alias && !hasRow(name) ? undefined : commandTopic(name, width);
846
+ if (command)
847
+ return alias
848
+ ? [...command, "", truncate(`see also help ${alias}`, width)]
849
+ : command;
850
+ return alias ? topicLines(alias, width) : undefined;
564
851
  }
565
852
 
566
- /** `/help arrange`: every arranging command with its full usage. */
567
- function arrangeSection(): HelpSection {
568
- const verbs = ["section", "sections", "form", "build", "drop", "fill"];
569
- return {
570
- group: "arrange" as HelpGroup,
571
- entries: [
572
- // Usage strings run long: wrapped onto continuation rows.
573
- ...verbs.flatMap((verb) =>
574
- wrapWords(USAGE[verb] ?? "", 46).map((summary, index) => ({
575
- command: index === 0 ? verb : "",
576
- summary,
577
- })),
578
- ),
579
- { command: "/menu arrange", summary: "the Arrange menu (ctrl-k)" },
580
- {
581
- command: "dawg render --section <name>",
582
- summary: "export one section",
583
- },
584
- ],
585
- };
853
+ /** True when a reference row starts with `name` (`/scale`, `scale …`). */
854
+ function hasRow(name: string): boolean {
855
+ const verb = name.replace(/[^a-z0-9-]/g, "");
856
+ return HELP_SECTIONS.some((section) =>
857
+ section.entries.some((entry) =>
858
+ new RegExp(`^/?${verb}(\\s|$)`, "i").test(entry.command),
859
+ ),
860
+ );
586
861
  }
587
862
 
863
+ /**
864
+ * `/help <command>` (`/help vocoder`, `/help clip`): the command's rows
865
+ * from every group, then its full usage wrapped; undefined when no command
866
+ * has that name.
867
+ */
868
+ function commandTopic(
869
+ name: string,
870
+ width: number,
871
+ usageKey = name,
872
+ ): string[] | undefined {
873
+ const pattern = name
874
+ .replace(/[^a-z0-9 -]/g, "")
875
+ .trim()
876
+ .replace(/ +/g, "\\s+");
877
+ const own = (command: string) =>
878
+ new RegExp(`^/?${pattern}(\\s|$)`, "i").test(command);
879
+ const rows = HELP_SECTIONS.flatMap((section) =>
880
+ section.entries.filter((entry) => own(entry.command)),
881
+ );
882
+ const usage = USAGE[usageKey] ?? USAGE[name];
883
+ if (rows.length === 0 && !usage) return undefined;
884
+ const wrap = Math.max(30, width - 2);
885
+ // A usage line that repeats a row keeps only what it adds (examples).
886
+ const extra = usage
887
+ ? rows.reduce((text, entry) => {
888
+ const bare = (value: string) =>
889
+ value.replace(/^\//, "").replace(/\s*\|\s*/g, "|");
890
+ return bare(text).startsWith(bare(entry.command))
891
+ ? bare(text)
892
+ .slice(bare(entry.command).length)
893
+ .replace(/^\s*·\s*/, "")
894
+ : text;
895
+ }, usage)
896
+ : "";
897
+ // Examples a row summary already shows are not repeated.
898
+ const shown = rows.map((entry) => entry.summary).join(" · ");
899
+ const added = extra
900
+ .split(" · ")
901
+ .filter((part) => part && !shown.split(" · ").includes(part))
902
+ .join(" · ");
903
+ return [
904
+ `── ${name}`,
905
+ ...rows.flatMap((entry) => [
906
+ ...wrapWords(entry.command, wrap),
907
+ ...wrapWords(entry.summary, wrap - 2).map((line) => ` ${line}`),
908
+ ]),
909
+ ...(added ? ["", ...wrapWords(added, wrap)] : []),
910
+ ];
911
+ }
912
+
913
+ /**
914
+ * Typed aliases that /help shows under their one word (design §8.2): the
915
+ * alias opens the page and the title, heading and rows name the canonical
916
+ * command.
917
+ */
918
+ export const HELP_COMMAND_ALIASES: Readonly<Record<string, string>> = {
919
+ login: "model key",
920
+ };
921
+
588
922
  function wrapWords(text: string, width: number): string[] {
589
923
  const rows: string[] = [];
590
924
  let row = "";
@@ -614,18 +948,21 @@ function sectionLines(
614
948
  );
615
949
  for (const section of sections) {
616
950
  if (lines.length > 0) lines.push("");
617
- lines.push(`── ${section.group}`);
951
+ lines.push(truncate(`── ${section.group}`, width));
618
952
  for (const entry of section.entries) {
619
953
  const pad = Math.max(1, column - entry.command.length);
620
954
  lines.push(
621
- `${entry.command}${" ".repeat(pad)}${entry.summary}`.slice(0, width),
955
+ truncate(`${entry.command}${" ".repeat(pad)}${entry.summary}`, width),
622
956
  );
623
957
  }
624
958
  }
625
959
  return lines;
626
960
  }
627
961
 
628
- /** Overlay rows: a heading per group, then `command summary` lines. */
962
+ /**
963
+ * `/help all`: a heading per topic, then `command summary` lines, then
964
+ * the keys of every screen.
965
+ */
629
966
  export function helpLines(width = 80): string[] {
630
967
  const lines: string[] = [];
631
968
  const column = Math.min(
@@ -636,27 +973,25 @@ export function helpLines(width = 80): string[] {
636
973
  );
637
974
  for (const section of HELP_SECTIONS) {
638
975
  if (lines.length > 0) lines.push("");
639
- lines.push(`── ${section.group}`);
976
+ lines.push(truncate(`── ${section.group}`, width));
640
977
  for (const entry of section.entries) {
641
978
  const pad = Math.max(1, column - entry.command.length);
642
979
  lines.push(
643
- `${entry.command}${" ".repeat(pad)}${entry.summary}`.slice(0, width),
980
+ truncate(`${entry.command}${" ".repeat(pad)}${entry.summary}`, width),
644
981
  );
645
982
  }
646
983
  }
647
- return lines;
984
+ return [...lines, "", ...keysLines(width)];
648
985
  }
649
986
 
650
987
  /** The `Commands:` block of `dawg --help`. */
651
988
  export function helpText(): string {
652
- return HELP_SECTIONS.filter((section) => section.group !== "keys")
653
- .map(
654
- (section) =>
655
- `${section.group}:\n${section.entries
656
- .map((entry) => ` ${entry.command}`)
657
- .join("\n")}`,
658
- )
659
- .join("\n");
989
+ return HELP_SECTIONS.map(
990
+ (section) =>
991
+ `${section.group}:\n${section.entries
992
+ .map((entry) => ` ${entry.command}`)
993
+ .join("\n")}`,
994
+ ).join("\n");
660
995
  }
661
996
 
662
997
  /** Usage for a known verb, shown instead of sending a near-miss to the agent. */
@@ -669,12 +1004,15 @@ export const USAGE: Readonly<Record<string, string>> = {
669
1004
  scale: "scale [<tonic>] <name> | list · scale D hijaz",
670
1005
  tuning:
671
1006
  "tuning <name> | edo <n> | ratios … | cents … | scl <file> [kbm <file>] | ref <hz> | root <note> | map linear|nearest | track … | off · tuning 19-edo",
672
- tune: "tuning <name> | edo <n> | scl <file> | off · tuning list",
1007
+ calibration: "calibration [0|1|latest|off] · calibration latest",
1008
+ tune: "tuning <name> | edo <n> | scl <file> | off · tuning list · pitch correction is autotune",
1009
+ autotune:
1010
+ "autotune [hard|robot|warble|trap|pop|natural|gentle|guided|locked] | to scale|chromatic|chord|notes [track] | key <key> | speed hold glide <ms> | relax amount center drift <0..1> | flex <0..100> | vib <Hz> vibmod <st> | voice auto|bass|tenor|alto|soprano | reset | off | presets · autotune hard · autotune natural flex 40",
673
1011
  cents: "cents <id> <±cents> · cents n3 -14",
674
- grid: "/grid 1/4|1/8|1/8T|1/16|1/16T|1/32",
1012
+ grid: "grid 1/4|1/8|1/8T|1/16|1/16T|1/32",
675
1013
  tempo:
676
1014
  "tempo takes 20…300 · tempo 120 · tempo 90 at bar 9 [ramp|exp] · tempo remove bar 9 · tempo clear · tempo map",
677
- bpm: "tempo takes 20…300 · tempo 120 · a sample's own tempo: /bpm 174 [<voice>] · /bpm off",
1015
+ bpm: "tempo takes 20…300 · tempo 120 · a sample's own tempo: /bpm 174 [<sample>] · /bpm off",
678
1016
  rit: "rit [<n> bars|beats] [to <bpm>] [at bar <n>|<beat>] [exp] · rit 4 bars to 80",
679
1017
  ritardando:
680
1018
  "rit [<n> bars|beats] [to <bpm>] [at bar <n>|<beat>] [exp] · rit 4 bars to 80",
@@ -689,14 +1027,28 @@ export const USAGE: Readonly<Record<string, string>> = {
689
1027
  put: "add <note> at <beat> [for <beats>] · add C4 at 0",
690
1028
  remove: "remove <id> · ids show in the transcript",
691
1029
  delete: "remove <id> · ids show in the transcript",
692
- move: "move <id> to <beat>",
1030
+ move: "move <id> to <beat> · move [<track>|all] <a>-<b> to <bar> [insert] · move bass 5-6 to 9",
693
1031
  length: "length <id> <beats>",
694
1032
  velocity: "velocity <id> <0..1>",
695
1033
  vel: "velocity <id> <0..1>",
696
- bars: "bars takes 1…256 · bars 8",
1034
+ bars: "bars <count> (1…256) | bars insert <n> at <bar> | bars remove <a>-<b> · bars 8 · bars insert 2 at 3",
1035
+ copy: "copy [<track>|all] [<a>-<b>|<section>] [to <bar>] [x<N>] [insert|merge] · copy bass 5-6 to 7 x2",
1036
+ paste:
1037
+ "paste [at <bar>] [x<N>] [insert|merge] · paste at 9 · copy bass 5-6 fills the clipboard",
1038
+ reverse: "reverse [<track>|all] [<a>-<b>|<section>] · reverse bass 5-6",
1039
+ jump: "jump <bar>[.<beat>] | <section> · jump 5 · jump 5.3 · jump chorus",
1040
+ loop: "loop <a>-<b> | <section> | next | prev | off · loop 5-6 · loop chorus · loop off",
1041
+ pane: "pane home|play|tape|sound|menu [<track>] [pin|follow [<letter>]] · pane tape · pane sound bass pin",
1042
+ follow: "follow [<letter>] · follow b · unfollow stops",
1043
+ panes: "panes · who has this session open, on what · pane tape",
1044
+ knobs:
1045
+ "knobs [sound|mix|master|tempo|fx <effect>] · knobs mix · knobs fx reverb",
1046
+ mix: "mix · the mixer page · volume drums 0.5 · pan drums -0.2",
1047
+ audio:
1048
+ "audio [out|in <name|default>] | test · audio out default · audio test",
697
1049
  extend: "extend <count> bars · extend 4 bars",
698
1050
  instrument:
699
- "instrument <name> · sine piano pluck bass saw square triangle wavetable kit · pianos: grand upright felt honkytonk prepared · electric: epiano suitcase dyno wurli clav funkclav · synth: sawtooth supersaw pulse white pink z_square…",
1051
+ "instrument <name> · sine piano pluck bass sawtooth square triangle wavetable kit · pianos: grand upright felt honkytonk prepared · electric: epiano suitcase dyno wurli clav funkclav · synth: supersaw pulse white pink z_square · voices: vocal aah ooh choir chorale khoomei sygyt kargyraa vocoder…",
700
1052
  volume: "volume takes 0…1 · volume 0.8",
701
1053
  vol: "volume takes 0…1 · volume 0.8",
702
1054
  pan: "pan takes -1…1 · pan -0.5",
@@ -712,28 +1064,29 @@ export const USAGE: Readonly<Record<string, string>> = {
712
1064
  reverb: "reverb <mix> [size] · reverb 0.3 0.6 · reverb off",
713
1065
  automate: "automate <lane> at <beat> <value> · automate volume at 0 0.5",
714
1066
  automation: "automate <lane> at <beat> <value> · automate volume at 0 0.5",
715
- hit: "hit <voice> at <beat> · hit kick at 0",
716
- pattern: "pattern <voice> <beats...> | every <step> · pattern kick every 1",
717
- clear: "clear · clear <voice> · clear [<lane>] automation",
1067
+ hit: "hit <drum> at <beat> · hit kick at 0",
1068
+ pattern: "pattern <drum> <beats...> | every <step> · pattern kick every 1",
1069
+ clear:
1070
+ "clear · clear <drum> · clear [<lane>] automation · clear [<track>|all] <a>-<b> · clear bass 5-6",
718
1071
  track:
719
- "/track <name> · /track drums · track rate <0.125..8>|<a>/<b>|off · track phase <beats> · track cycle <beats> · track phasing <beats> [over <beats>]",
720
- tracks: "/tracks",
1072
+ "track <name> · track drums · track rm <name> · track move <name> <position> · track rate <0.125..8>|<a>/<b>|off · track phase <beats> · track loop <beats> · track phasing <beats> [over <beats>]",
1073
+ tracks: "tracks",
721
1074
  sessions: "/sessions",
722
1075
  resume: "/resume [<n>|<name>|<id>]",
723
1076
  rename: "/rename <name> | --auto",
724
1077
  fork: "/fork [<name>]",
725
1078
  status: "/status",
726
- export: "/export <file> · /export loop.track.json",
727
- import: "/import <file> · /import loop.track.json",
1079
+ export: "export <file> · export loop.track.json",
1080
+ import: "import <file> · import loop.track.json",
728
1081
  sample:
729
- "/sample <path> [as <voice>] · /sample set <voice> <control> <value>… · /sample set brk fit on clip 1 · /sample set soft vel 0-63 rr a",
730
- samples: "/sample · lists the focused track's voices",
1082
+ "/sample <path> [as <sample>] · /sample set <sample> <control> <value>… · /sample set brk fit on clip 1 · /sample set soft vel 0-63 rr a",
1083
+ samples: "/sample · lists the focused track's samples",
731
1084
  fitmode:
732
- "/fitmode [repitch|beats|tones|auto|off] [<voice>] · /fitmode beats · /fitmode auto brk",
733
- len: "/len <beats> [<voice>] · /len 16 · /len off",
1085
+ "fitmode [repitch|beats|tones|auto|off] [<sample>] · fitmode beats · fitmode auto brk",
1086
+ len: "len <beats> [<sample>] · len 16 · len off",
734
1087
  shift:
735
- "shift <semitones> [formant keep|follow|<n>] [<voice>] · shift 7 formant keep · shift 0",
736
- fade: "fade [in|out] <seconds> [<voice>] · fade out 0.5 · fade in 0.05 · fade off",
1088
+ "shift <semitones> [formant keep|follow|<n>] [<sample>] · shift 7 formant keep · shift 0",
1089
+ fade: "fade [in|out] <seconds> [<sample>] · fade out 0.5 · fade in 0.05 · fade off",
737
1090
  resample:
738
1091
  "resample <track>|orbit <n>|master [section <name>|bars a-b] [post] [grain] [as <id>] · resample lead · resample drums bars 1-2 grain · resample master section chorus",
739
1092
  bounce: "resample <track>|orbit <n>|master [section <name>|bars a-b] [grain]",
@@ -742,12 +1095,14 @@ export const USAGE: Readonly<Record<string, string>> = {
742
1095
  log: "/transcript",
743
1096
  theme: "/theme default | high-contrast | mono",
744
1097
  motion: "/motion on | off",
745
- model: "/model [alias | vendor/model]",
746
- login: "/login [gateway | openrouter | codex | claude]",
1098
+ model:
1099
+ "/model [fast | alias | vendor/model] · /model key [gateway | openrouter | codex | claude]",
1100
+ models: "/model [fast | alias | vendor/model] · /model key [provider]",
1101
+ login: "/model key [gateway | openrouter | codex | claude]",
747
1102
  logout: "/logout [provider]",
748
1103
  auth: "/auth [--check]",
749
- help: "/help [topic] · /help all · /help music|session|window|keys",
750
- guide: "/guide [topic] · /guide chords · F1",
1104
+ help: "help [topic] · help all · help sound|voice|effects|rhythm|chords|mix|arrange|project|keys|agent",
1105
+ guide: "/guide [topic] · /guide voice · F1 · the same topics as help",
751
1106
  fx: "fx <effect> <param> <value> | on | off | preset <name> · fx delay mix 0.3",
752
1107
  synth: "synth <param> <value> | preset <name> · synth lpf 1200",
753
1108
  string:
@@ -756,6 +1111,8 @@ export const USAGE: Readonly<Record<string, string>> = {
756
1111
  "bowed [violin|viola|cello|contrabass|fiddle|erhu|kamancheh|violins|violas|cellos|contrabasses|pizz|trem] | <param> <value> | presets · bowed violin · bowed pressure 0.7",
757
1112
  rig: "rig clean|crunch|punk|ragged|lead|metal|fuzz|octave|funk|wah|bachata|spring|bassdrive|reese|jangle|alt|shoegaze|glide|dreampop|swell|ebow | reset",
758
1113
  guitar: `guitar tune ${GUITAR_TUNING_NAMES.join("|")} | E A D G B E · capo 0..12 · hand 3..6 · ring 0..1 · position · reset`,
1114
+ progression:
1115
+ "progression i7 IV7 i7 IV7 [each 8] [at 0] [bass] · numerals in the song key or symbols (Am7 D9) · block chords that hold for each span",
759
1116
  strum:
760
1117
  "strum G D Em C [folk|pop|punk|…] [strokes D-DU-UDU] [speed 22ms] [each 4] [at 0] · strum alone strums the track's chords",
761
1118
  stomp:
@@ -778,14 +1135,26 @@ export const USAGE: Readonly<Record<string, string>> = {
778
1135
  modal:
779
1136
  "modal <preset> | <body> | <param> <value> | mallet <name> | pair <track> | gamelan | reset | off | presets · modal vibes · modal gangsa · modal ring 3",
780
1137
  wind: "wind <preset> | <param> <value> | mute <name> | reset | off | presets · wind flute · wind trumpet mute harmon · wind players 4",
781
- pack: "/pack list | info <name> | use <pack>/<sound> | add <url>",
1138
+ formant:
1139
+ "/formant <-12..12> [mix] | deep|giant|bright|tiny | on | off · /formant -4 · /formant 3 0.5",
1140
+ vowel:
1141
+ "/vowel <v> [<to> [<morph 0..1>]] | ee | to <v>|off | morph <0..1> | mix <0..1> | off · /vowel a o 0.5",
1142
+ lyrics:
1143
+ 'lyrics [bar] <syllables> | clear [bar] · lyrics "sun-lit morn-ing" (- splits, _ holds, ~ skips)',
1144
+ sing: "sing <preset> | <param> <value> | drone <D3> | vowels a e i … | reset | off | presets · sing choir · sing khoomei drone D3 · sing vowel o voices 6",
1145
+ vocoder:
1146
+ "vocoder [preset] | src <track> | <param> <value|reset> | reset | off | presets · vocoder talkbox · vocoder src vox · vocoder formant +3 · vocoder gate auto · params: tap mode carrier follow root spread bands lo hi width attack release formant unvoiced sens hiss gate enhance depth freeze mix gain seed",
1147
+ pack: "pack list | info <name> | use <pack>/<sound> | add <url>",
782
1148
  kit: "/kit [name] · /kit syn909",
783
- euclid: "/euclid [voice] · euclid hat 7 16",
784
- menu: "/menu [sound|effects|rhythm|chords|mix|master|project|tuning]",
1149
+ euclid: "euclid [drum] · euclid hat 7 16",
1150
+ menu: "/menu [sound|voice|effects|rhythm|chords|mix|arrange|project|keys|agent] or any row name · /menu tuning · Ctrl-K",
1151
+ style:
1152
+ "style [list [id]|search <words>|info <id>|<id> [bars] [seed]|blend <a> <b> [w] [bars] [seed]|again] · style deep-house 16 · style blend bebop bossa-nova 0.3",
785
1153
  master:
786
1154
  "master <unit> on|off|preset <name>|<param> <value> · master streaming|club|loud · master target -14 · master measure · master off",
787
1155
  try: "/try <sound command> · /try fx reverb mix 0.6",
788
- play: "/play [on|off|degrees|chromatic] · Ctrl-P · i toggles degrees",
1156
+ play: "/play [on|off|degrees|in-key|chromatic] · ctrl-p play mode · i toggles degrees",
1157
+ tape: "/tape [on|off] · ctrl-t · every track across the bars · c copy · x cut · v paste · \\ loop",
789
1158
  meter:
790
1159
  "meter <1..16> · meter 3 · meter 7/8 [at bar <n>] · meter remove bar <n> · meter clear",
791
1160
  art: EXPRESSION_USAGE.art,
@@ -799,9 +1168,9 @@ export const USAGE: Readonly<Record<string, string>> = {
799
1168
  velcurve: EXPRESSION_USAGE.velcurve,
800
1169
  humanize: EXPRESSION_USAGE.humanize,
801
1170
  section:
802
- "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",
1171
+ "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> | split <name> at <bar> | join <name> · section chorus 9-16",
803
1172
  sections: "section · lists sections, the form and the loop",
804
- form: "form <section…> | off | bake · form verse verse chorus verse · form verse chorus*2",
1173
+ form: "form <section…> | off | bake (print) · form verse verse chorus verse · form verse chorus*2",
805
1174
  build:
806
1175
  "build [into <section> | <section> | <a>-<b>] [<n> bars] [riser] [roll] [sweep] [uplifter] · build into chorus",
807
1176
  drop: "drop [<section> | at <bar>] [cut <beats>] [no impact] · drop chorus",
@@ -810,37 +1179,32 @@ export const USAGE: Readonly<Record<string, string>> = {
810
1179
  redo: "redo · Ctrl-Y",
811
1180
  };
812
1181
 
813
- /** Verbs a typo can be matched against, slash or bare as they are typed. */
1182
+ /** Verbs a typo can be matched against, without their slash. */
814
1183
  const KNOWN_VERBS: readonly string[] = [
815
1184
  ...new Set(
816
1185
  [
817
1186
  ...HELP_SECTIONS.flatMap((section) =>
818
- section.group === "keys"
819
- ? []
820
- : section.entries.map((entry) => entry.command.split(/[\s|[]/)[0]!),
1187
+ section.entries.map((entry) => entry.command.split(/[\s|[]/)[0]!),
821
1188
  ),
822
1189
  ...Object.values(USAGE).map((usage) => usage.split(/[\s|[]/)[0]!),
823
- ].filter((verb) => /^\/?[a-z][\w-]*$/i.test(verb)),
1190
+ ]
1191
+ .map((verb) => verb.replace(/^\//, ""))
1192
+ .filter((verb) => /^[a-z][\w-]*$/i.test(verb)),
824
1193
  ),
825
1194
  ];
826
1195
 
827
- function editDistance(a: string, b: string): number {
828
- const row = Array.from({ length: b.length + 1 }, (_, index) => index);
829
- for (let i = 1; i <= a.length; i += 1) {
830
- let previous = row[0]!;
831
- row[0] = i;
832
- for (let j = 1; j <= b.length; j += 1) {
833
- const current = row[j]!;
834
- row[j] = Math.min(
835
- row[j]! + 1,
836
- row[j - 1]! + 1,
837
- previous + (a[i - 1] === b[j - 1] ? 0 : 1),
838
- );
839
- previous = current;
840
- }
841
- }
842
- return row[b.length]!;
843
- }
1196
+ /** Window verbs whose free text would otherwise read as a typo fix. */
1197
+ const SLASH_ONLY: ReadonlySet<string> = new Set([
1198
+ "rename",
1199
+ "fork",
1200
+ "resume",
1201
+ "login",
1202
+ "logout",
1203
+ "auth",
1204
+ "bpm",
1205
+ ]);
1206
+
1207
+ export { editDistance } from "./nearest.ts";
844
1208
 
845
1209
  /**
846
1210
  * The known command nearest to the first word of `command` (`/clik` →
@@ -848,19 +1212,25 @@ function editDistance(a: string, b: string): number {
848
1212
  */
849
1213
  export function nearestCommand(command: string): string | undefined {
850
1214
  const word = command.trim().split(/\s+/)[0]?.toLowerCase() ?? "";
1215
+ const slash = word.startsWith("/");
851
1216
  const bare = word.replace(/^\//, "");
852
1217
  if (!bare) return undefined;
853
1218
  let best: { verb: string; distance: number; score: number } | undefined;
854
1219
  for (const verb of KNOWN_VERBS) {
855
- const distance = editDistance(bare, verb.replace(/^\//, ""));
856
- if (distance === 0 && verb === word) continue;
857
- // Ties go to the form typed: `/patern` → `/pattern`, not `pattern`.
858
- const sameForm = verb.startsWith("/") === word.startsWith("/");
859
- const score = distance * 2 + (sameForm ? 0 : 1);
1220
+ const distance = editDistance(bare, verb);
1221
+ if (distance === 0 && !slash) continue;
1222
+ // Ties go to a dropped letter (`/clik` → `/click`, not `/clip`), the
1223
+ // commonest slip, then a changed one, then an extra one.
1224
+ const shape =
1225
+ verb.length > bare.length ? 0 : verb.length === bare.length ? 1 : 2;
1226
+ const score = distance * 3 + shape;
860
1227
  if (!best || score < best.score) best = { verb, distance, score };
861
1228
  }
862
1229
  const limit = bare.length <= 4 ? 1 : 2;
863
- return best && best.distance <= limit ? best.verb : undefined;
1230
+ if (!best || best.distance > limit) return undefined;
1231
+ // Answer in the form typed (`/clik` → `/click`), except an exact word
1232
+ // that failed with a slash (`/fx delay on` → `fx`): its slash was wrong.
1233
+ return slash && best.distance > 0 ? `/${best.verb}` : best.verb;
864
1234
  }
865
1235
 
866
1236
  /**
@@ -877,7 +1247,7 @@ export function typoFix(
877
1247
  const [first, ...rest] = text.split(/\s+/);
878
1248
  const word = first!.toLowerCase();
879
1249
  for (const verb of KNOWN_VERBS) {
880
- if (verb.startsWith("/") || verb === word) continue;
1250
+ if (SLASH_ONLY.has(verb) || verb === word) continue;
881
1251
  if (editDistance(word, verb) !== 1) continue;
882
1252
  const candidate = [verb, ...rest].join(" ");
883
1253
  if (parses(candidate)) return candidate;
@@ -917,7 +1287,18 @@ export function usageHint(command: string): string | undefined {
917
1287
  !words.slice(1).some((word) => /\d/.test(word) || ARRANGE_WORDS.has(word))
918
1288
  )
919
1289
  return undefined;
920
- return USAGE[verb];
1290
+ const usage = USAGE[verb];
1291
+ return usage === undefined ? undefined : usageForm(usage);
1292
+ }
1293
+
1294
+ /**
1295
+ * Every usage line reads `usage · <cmd> <args>`; a range refusal keeps
1296
+ * its own template (`pan takes -1…1 · pan -0.5`).
1297
+ */
1298
+ export function usageForm(usage: string): string {
1299
+ return / takes /.test(usage) || usage.startsWith("usage · ")
1300
+ ? usage
1301
+ : `usage · ${usage}`;
921
1302
  }
922
1303
 
923
1304
  const ARRANGE_VERBS: ReadonlySet<string> = new Set([
@@ -944,3 +1325,30 @@ const ARRANGE_WORDS: ReadonlySet<string> = new Set([
944
1325
  "bake",
945
1326
  "off",
946
1327
  ]);
1328
+
1329
+ /** The overlay title for `/help [topic]`: an alias shows its topic. */
1330
+ export function helpTitle(topic: string | undefined): string {
1331
+ const name = topic?.trim().toLowerCase().replace(/^\//, "");
1332
+ if (!name) return "help";
1333
+ const spelled = HELP_COMMAND_ALIASES[name];
1334
+ if (spelled) return `help · ${spelled}`;
1335
+ const id = (TOPICS as readonly string[]).includes(name)
1336
+ ? name
1337
+ : USAGE[name] || commandHelpExists(name)
1338
+ ? name
1339
+ : (resolveTopic(name) ?? name);
1340
+ return `help · ${id}`;
1341
+ }
1342
+
1343
+ function commandHelpExists(name: string): boolean {
1344
+ return HELP_SECTIONS.some((section) =>
1345
+ section.entries.some((entry) =>
1346
+ entry.command.replace(/^\//, "").startsWith(`${name} `),
1347
+ ),
1348
+ );
1349
+ }
1350
+
1351
+ /** `no topic X · did you mean Y · /help`, for `/help X` with no page. */
1352
+ export function helpMiss(topic: string): string {
1353
+ return topicMiss(topic, ["all"]);
1354
+ }