@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.
- package/CHANGELOG.md +118 -0
- package/DAWG.md +454 -140
- package/README.md +30 -26
- package/core/autotune.ts +1119 -0
- package/core/chords.ts +271 -23
- package/core/clips.ts +499 -0
- package/core/diff.ts +184 -104
- package/core/expression.ts +15 -0
- package/core/fx.ts +99 -29
- package/core/ids.ts +52 -0
- package/core/instruments.ts +19 -0
- package/core/keys.ts +3 -3
- package/core/loop.ts +8 -0
- package/core/lyrics.ts +297 -0
- package/core/master.ts +3 -3
- package/core/range.ts +581 -0
- package/core/resonators.ts +16 -2
- package/core/routing.ts +165 -0
- package/core/score.ts +955 -14
- package/core/sdk/eval-child.ts +7 -2
- package/core/sdk/eval.ts +35 -6
- package/core/sdk/print.ts +287 -5
- package/core/sdk/sync-lyrics.ts +49 -0
- package/core/sdk/v1.ts +2132 -46
- package/core/sections.ts +480 -35
- package/core/sing.ts +815 -0
- package/core/style-provenance.ts +80 -0
- package/core/styles/africa-mena-southasia.ts +2893 -0
- package/core/styles/americas.ts +3810 -0
- package/core/styles/art.ts +4993 -0
- package/core/styles/base.ts +123 -0
- package/core/styles/cycles.ts +106 -0
- package/core/styles/electronic.ts +2723 -0
- package/core/styles/europe-asia-pacific.ts +2838 -0
- package/core/styles/excerpt.ts +29 -0
- package/core/styles/gamelan.ts +283 -0
- package/core/styles/generate.ts +2199 -0
- package/core/styles/index.ts +515 -0
- package/core/styles/parts.ts +106 -0
- package/core/styles/pop.ts +3189 -0
- package/core/styles/rock.ts +2993 -0
- package/core/styles/roots.ts +4175 -0
- package/core/styles/schema.ts +429 -0
- package/core/styles/taxonomy.ts +940 -0
- package/core/styles/validate.ts +528 -0
- package/core/tempo.ts +32 -2
- package/core/tuning.ts +19 -3
- package/core/vocoder.ts +524 -0
- package/guides/agent.md +29 -0
- package/guides/arrange.md +31 -0
- package/guides/audio.md +31 -0
- package/guides/audition.md +20 -14
- package/guides/automation.md +12 -7
- package/guides/chords.md +15 -15
- package/guides/effects.md +17 -16
- package/guides/faders.md +20 -16
- package/guides/files.md +13 -9
- package/guides/getting-started.md +15 -11
- package/guides/keys.md +19 -15
- package/guides/media.md +17 -12
- package/guides/mix.md +15 -7
- package/guides/music.md +23 -8
- package/guides/notes.md +15 -9
- package/guides/panes.md +32 -0
- package/guides/performance.md +15 -13
- package/guides/play.md +21 -13
- package/guides/project.md +26 -8
- package/guides/providers.md +20 -14
- package/guides/resample.md +15 -9
- package/guides/rhythm.md +18 -13
- package/guides/sessions.md +17 -7
- package/guides/show-me.md +31 -0
- package/guides/sound.md +25 -9
- package/guides/sounds.md +15 -13
- package/guides/styles.md +31 -0
- package/guides/tape.md +32 -0
- package/guides/tempo.md +15 -10
- package/guides/tracks.md +15 -10
- package/guides/tuning.md +32 -0
- package/guides/voice.md +31 -0
- package/guides/web-search.md +18 -8
- package/native/prebuilt/darwin-arm64/libdawg_sink.dylib +0 -0
- package/native/prebuilt/darwin-x64/libdawg_sink.dylib +0 -0
- package/native/prebuilt/linux-arm64/libdawg_sink.so +0 -0
- package/native/prebuilt/linux-x64/libdawg_sink.so +0 -0
- package/native/prebuilt/manifest.json +21 -0
- package/package.json +6 -2
- package/src/agent/agent.ts +130 -18
- package/src/agent/calibration-tools.ts +53 -0
- package/src/agent/clip-tools.ts +453 -0
- package/src/agent/command-agent.ts +378 -0
- package/src/agent/drum-tools.ts +2 -2
- package/src/agent/expression-tools.ts +1 -1
- package/src/agent/gateway.ts +246 -60
- package/src/agent/models.ts +53 -12
- package/src/agent/ops.ts +27 -1
- package/src/agent/pack-tools.ts +1 -1
- package/src/agent/planner.ts +25 -0
- package/src/agent/portable-schema.ts +80 -0
- package/src/agent/preview-tool.ts +4 -1
- package/src/agent/provider.ts +22 -8
- package/src/agent/range-tools.ts +216 -0
- package/src/agent/rhythm-tools.ts +1 -1
- package/src/agent/section-tools.ts +1 -1
- package/src/agent/show-me.ts +497 -0
- package/src/agent/steer.ts +15 -0
- package/src/agent/style-tools.ts +217 -0
- package/src/agent/tool-error.ts +12 -0
- package/src/agent/tools.ts +110 -23
- package/src/agent/usage.ts +2 -2
- package/src/agent/voice-tools.ts +925 -0
- package/src/agent/xcb-agent.ts +13 -10
- package/src/argv.ts +38 -0
- package/src/audio/analysis.ts +253 -0
- package/src/audio/arrange.ts +49 -5
- package/src/audio/audio-command.ts +218 -0
- package/src/audio/autotune-engine.ts +101 -0
- package/src/audio/autotune.ts +640 -0
- package/src/audio/clips.ts +240 -0
- package/src/audio/devices.ts +264 -0
- package/src/audio/doctor.ts +157 -0
- package/src/audio/dsp/bandbank.ts +138 -0
- package/src/audio/dsp/envelope.ts +10 -0
- package/src/audio/dsp/follow.ts +120 -0
- package/src/audio/dsp/formant.ts +427 -0
- package/src/audio/dsp/glottal.ts +243 -0
- package/src/audio/dsp/interp.ts +7 -2
- package/src/audio/dsp/lpc.ts +50 -0
- package/src/audio/dsp/periodicity.ts +59 -0
- package/src/audio/dsp/pitch.ts +995 -0
- package/src/audio/dsp/psola.ts +199 -0
- package/src/audio/effects/chain.ts +3 -1
- package/src/audio/effects/common.ts +43 -0
- package/src/audio/effects/convolution.ts +7 -4
- package/src/audio/effects/filter.ts +48 -69
- package/src/audio/effects/formant.ts +263 -0
- package/src/audio/engine.ts +360 -40
- package/src/audio/fit.ts +35 -3
- package/src/audio/instrument-check.ts +59 -43
- package/src/audio/instruments.ts +4 -0
- package/src/audio/keys/calibration.ts +56 -0
- package/src/audio/keys/electric.ts +8 -1
- package/src/audio/keys/engine.ts +13 -1
- package/src/audio/keys/piano.ts +22 -2
- package/src/audio/kits.ts +135 -6
- package/src/audio/live.ts +114 -20
- package/src/audio/native.ts +615 -0
- package/src/audio/preview.ts +30 -2
- package/src/audio/render-worker.ts +2 -0
- package/src/audio/renderer.ts +2 -0
- package/src/audio/resample.ts +2 -1
- package/src/audio/sampler.ts +85 -4
- package/src/audio/samples.ts +20 -2
- package/src/audio/sing/analysis.ts +193 -0
- package/src/audio/sing/engine.ts +949 -0
- package/src/audio/strings/bow.ts +48 -5
- package/src/audio/strings/engine.ts +8 -2
- package/src/audio/synth/oscillators.ts +31 -21
- package/src/audio/synth/voice.ts +34 -1
- package/src/audio/vocoder/bank.ts +314 -0
- package/src/audio/vocoder/carrier.ts +165 -0
- package/src/audio/vocoder/control.ts +68 -0
- package/src/audio/vocoder/detect.ts +50 -0
- package/src/audio/vocoder/index.ts +304 -0
- package/src/audio/vocoder/talkbox.ts +143 -0
- package/src/audio/wav.ts +500 -77
- package/src/audio/winds/engine.ts +5 -1
- package/src/audio/winds/trim.ts +28 -4
- package/src/audio/winds/trims1.ts +297 -0
- package/src/audio/winds/voice.ts +15 -2
- package/src/auth/cli.ts +38 -36
- package/src/auth/credentials.ts +30 -1
- package/src/auth/login.ts +15 -9
- package/src/auth/tui.ts +19 -10
- package/src/commands/arrange.ts +82 -32
- package/src/commands/autotune.ts +421 -0
- package/src/commands/calibration.ts +74 -0
- package/src/commands/clips.ts +887 -0
- package/src/commands/drums.ts +3 -2
- package/src/commands/edit.ts +11 -4
- package/src/commands/expression.ts +1 -1
- package/src/commands/formant.ts +221 -0
- package/src/commands/fx.ts +101 -32
- package/src/commands/grammar.ts +560 -0
- package/src/commands/help.ts +774 -366
- package/src/commands/history.ts +139 -10
- package/src/commands/keys.ts +25 -8
- package/src/commands/modal.ts +1 -1
- package/src/commands/music.ts +1 -1
- package/src/commands/nearest.ts +53 -0
- package/src/commands/pack.ts +9 -2
- package/src/commands/param-range.ts +56 -0
- package/src/commands/parses.ts +135 -0
- package/src/commands/progression.ts +170 -0
- package/src/commands/range.ts +763 -0
- package/src/commands/resample.ts +13 -10
- package/src/commands/rhythm.ts +3 -0
- package/src/commands/rig.ts +3 -24
- package/src/commands/sample.ts +11 -1
- package/src/commands/sing.ts +474 -0
- package/src/commands/strum.ts +13 -1
- package/src/commands/style.ts +415 -0
- package/src/commands/time.ts +6 -3
- package/src/commands/tuning.ts +3 -3
- package/src/commands/vocal-pitch.ts +617 -0
- package/src/commands/vocal.ts +147 -0
- package/src/commands/vocoder.ts +627 -0
- package/src/commands/wind.ts +2 -2
- package/src/fs/durable.ts +50 -0
- package/src/identity/actor.ts +127 -0
- package/src/lang/glossary.ts +511 -0
- package/src/launch-args.ts +267 -0
- package/src/main.ts +2508 -308
- package/src/media/cli.ts +20 -3
- package/src/media/import.ts +3 -1
- package/src/project/check.ts +21 -2
- package/src/project/clip-pins.ts +72 -0
- package/src/project/init.ts +23 -8
- package/src/project/sync.ts +418 -86
- package/src/render.ts +20 -1
- package/src/session/client.ts +107 -3
- package/src/session/clipboard.ts +79 -0
- package/src/session/daemon.ts +199 -5
- package/src/session/live-host.ts +232 -0
- package/src/session/meta.ts +14 -0
- package/src/session/origin.ts +174 -0
- package/src/session/port.ts +109 -14
- package/src/session/presence.ts +34 -4
- package/src/session/protocol.ts +365 -7
- package/src/session/rebase.ts +63 -12
- package/src/session/receipt.ts +265 -0
- package/src/session/shared-live.ts +131 -0
- package/src/session/store.ts +126 -45
- package/src/tui/arrange-menu.ts +225 -23
- package/src/tui/audition.ts +1 -1
- package/src/tui/euclid.ts +113 -27
- package/src/tui/fader.ts +282 -42
- package/src/tui/granular-menu.ts +2 -4
- package/src/tui/knob-fields.ts +107 -0
- package/src/tui/knob-map.ts +198 -0
- package/src/tui/menu-clips.ts +297 -0
- package/src/tui/menu-time.ts +20 -13
- package/src/tui/menu-voice.ts +405 -0
- package/src/tui/menu.ts +987 -179
- package/src/tui/modal-menu.ts +6 -6
- package/src/tui/performance-menu.ts +5 -2
- package/src/tui/play-chords.ts +4 -2
- package/src/tui/play-mode.ts +15 -1
- package/src/tui/play-session.ts +243 -28
- package/src/tui/sing-menu.ts +278 -0
- package/src/tui/style-menu.ts +104 -0
- package/src/tui/tape-mode.ts +580 -0
- package/src/tui/tape-view.ts +263 -0
- package/src/tui/vocoder-menu.ts +244 -0
- package/src/tui/wind-menu.ts +3 -3
- package/src/version.ts +8 -0
- package/src/web/fetch.ts +115 -29
- package/tui/activity.ts +180 -9
- package/tui/app.ts +491 -84
- package/tui/clip-row.ts +132 -0
- package/tui/delight.ts +193 -0
- package/tui/drawer.ts +233 -27
- package/tui/frame-gate.ts +76 -0
- package/tui/grammar.ts +143 -65
- package/tui/guide.ts +50 -5
- package/tui/highway.ts +309 -30
- package/tui/hints.ts +197 -0
- package/tui/hits.ts +7 -1
- package/tui/input.ts +60 -9
- package/tui/keys.ts +1 -1
- package/tui/knobs.ts +268 -0
- package/tui/play-strip.ts +80 -14
- package/tui/prompt.ts +1 -1
- package/tui/screen.ts +151 -18
- package/tui/tape.ts +439 -0
- package/tui/text.ts +35 -2
- package/tui/theme.ts +46 -1
package/src/commands/help.ts
CHANGED
|
@@ -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.
|
|
4
|
-
*
|
|
5
|
-
*
|
|
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
|
-
|
|
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: "
|
|
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
|
|
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
|
|
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: "
|
|
65
|
-
summary: "
|
|
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: "
|
|
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: "
|
|
75
|
+
command: "epiano|wurli|clav [preset <name>] | <param> <value>",
|
|
83
76
|
summary:
|
|
84
|
-
"
|
|
77
|
+
"electric keys · suitcase dyno funkclav · epiano vibe 0.6 · clav pickup bridge",
|
|
85
78
|
},
|
|
86
79
|
{
|
|
87
80
|
command:
|
|
88
|
-
"
|
|
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
|
-
"
|
|
83
|
+
"organs · tonewheel 888800008 perc 3rd · gospel · combo 08880 flute · pipe plenum · keys perc 3rd",
|
|
95
84
|
},
|
|
96
85
|
{
|
|
97
|
-
command: "
|
|
86
|
+
command: "rotary slow|fast|stop [at <beat>]",
|
|
98
87
|
summary:
|
|
99
|
-
"
|
|
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: "
|
|
91
|
+
command: "string <preset> | <param> <value> | presets | reset | off",
|
|
107
92
|
summary:
|
|
108
|
-
"
|
|
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: "
|
|
96
|
+
command: "bowed [<preset>] | <param> <value> | presets",
|
|
120
97
|
summary:
|
|
121
|
-
"
|
|
98
|
+
"bowed strings · bowed violin · bowed cellos · bowed pressure 0.7 · bowed sord 1",
|
|
122
99
|
},
|
|
123
100
|
{
|
|
124
101
|
command:
|
|
125
|
-
"
|
|
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
|
-
|
|
183
|
-
|
|
184
|
-
|
|
161
|
+
],
|
|
162
|
+
},
|
|
163
|
+
{
|
|
164
|
+
group: "sound · samples",
|
|
165
|
+
entries: [
|
|
185
166
|
{
|
|
186
|
-
command: "
|
|
187
|
-
summary: "
|
|
167
|
+
command: "/sample [<path> [as <sample>]]",
|
|
168
|
+
summary: "add a sample · alone lists the samples",
|
|
188
169
|
},
|
|
189
170
|
{
|
|
190
|
-
command: "
|
|
191
|
-
summary: "
|
|
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: "
|
|
195
|
-
summary: "
|
|
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: "
|
|
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
|
-
"
|
|
185
|
+
"a sample's pitch without changing its length · shift 0 clears",
|
|
202
186
|
},
|
|
203
187
|
{
|
|
204
|
-
command: "
|
|
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
|
-
"
|
|
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: "
|
|
212
|
-
summary:
|
|
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: "
|
|
216
|
-
summary: "
|
|
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: "
|
|
220
|
-
summary: "
|
|
216
|
+
command: "lyrics [bar] sun-lit morn-ing",
|
|
217
|
+
summary: "syllables onto the notes (- splits, _ holds, ~ skips)",
|
|
221
218
|
},
|
|
222
219
|
{
|
|
223
|
-
command: "
|
|
224
|
-
summary:
|
|
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:
|
|
228
|
-
|
|
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:
|
|
232
|
-
|
|
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: "
|
|
260
|
-
summary:
|
|
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: "
|
|
264
|
-
summary:
|
|
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: "
|
|
268
|
-
summary: "
|
|
370
|
+
command: "/chords auto|manual|off",
|
|
371
|
+
summary: "chords in play mode · /chords alone shows settings",
|
|
269
372
|
},
|
|
270
373
|
{
|
|
271
|
-
command: "
|
|
272
|
-
summary:
|
|
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: "
|
|
276
|
-
summary: "
|
|
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: "
|
|
280
|
-
summary: "
|
|
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: "
|
|
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: "
|
|
285
|
-
summary: "
|
|
398
|
+
command: "automate <lane> at <beat> <value>",
|
|
399
|
+
summary: "volume pan filter resonance delay-feedback delay-mix wt",
|
|
286
400
|
},
|
|
287
401
|
{
|
|
288
|
-
command: "
|
|
289
|
-
summary: "
|
|
402
|
+
command: "automate <lane> points <b:v>...",
|
|
403
|
+
summary: "several points · automate pan points 0:-1 4:1",
|
|
290
404
|
},
|
|
291
|
-
{ command: "
|
|
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: "
|
|
408
|
+
command: "master <unit> on|off | preset <name> | <param> <value>",
|
|
294
409
|
summary:
|
|
295
|
-
"
|
|
410
|
+
"song master · eq glue tape width limiter · master glue ratio 4",
|
|
296
411
|
},
|
|
297
412
|
{
|
|
298
|
-
command: "
|
|
299
|
-
summary:
|
|
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
|
-
|
|
304
|
-
|
|
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: "
|
|
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: "
|
|
320
|
-
summary:
|
|
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: "
|
|
325
|
-
summary: "
|
|
455
|
+
command: "section loop | jump | mute | vary <name>",
|
|
456
|
+
summary: "section loop chorus · section mute verse drums",
|
|
326
457
|
},
|
|
327
458
|
{
|
|
328
|
-
command: "
|
|
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: "
|
|
334
|
-
summary: "
|
|
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: "
|
|
338
|
-
summary: "
|
|
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: "
|
|
479
|
+
group: "arrange · range",
|
|
344
480
|
entries: [
|
|
345
481
|
{
|
|
346
|
-
command: "/
|
|
347
|
-
summary: "
|
|
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: "
|
|
357
|
-
summary: "
|
|
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: "
|
|
361
|
-
summary: "
|
|
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: "
|
|
367
|
-
summary: "
|
|
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: "
|
|
371
|
-
summary: "
|
|
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: "
|
|
375
|
-
summary: "
|
|
502
|
+
command: "clear [<track>|all] <a>-<b>",
|
|
503
|
+
summary: "clear bass 5-6 · empty bars, the bars stay",
|
|
376
504
|
},
|
|
377
505
|
{
|
|
378
|
-
command: "
|
|
379
|
-
summary: "
|
|
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: "
|
|
383
|
-
summary: "
|
|
510
|
+
command: "reverse [<track>|all] <a>-<b>",
|
|
511
|
+
summary: "mirror the bars · reverse bass 5-6",
|
|
384
512
|
},
|
|
385
513
|
{
|
|
386
|
-
command: "
|
|
387
|
-
summary: "
|
|
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: "
|
|
391
|
-
summary: "
|
|
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 ·
|
|
560
|
+
summary: "metronome · /count-in 0-2 · grid 1/16",
|
|
396
561
|
},
|
|
397
562
|
{
|
|
398
|
-
command: "
|
|
399
|
-
summary:
|
|
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: "
|
|
403
|
-
summary: "
|
|
569
|
+
command: "calibration [0|1|latest|off]",
|
|
570
|
+
summary: "sound fixes · 1 chokes hats, levels keys · 0 legacy",
|
|
404
571
|
},
|
|
405
572
|
{
|
|
406
|
-
command: "
|
|
407
|
-
summary: "
|
|
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: "
|
|
584
|
+
group: "project · window",
|
|
413
585
|
entries: [
|
|
414
|
-
{ command: "
|
|
415
|
-
{ command: "
|
|
416
|
-
{ command: "
|
|
417
|
-
{ command: "
|
|
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: "
|
|
425
|
-
summary: "
|
|
591
|
+
command: "/menu [section]",
|
|
592
|
+
summary: "every setting by hand · Ctrl-K · /menu <topic>",
|
|
426
593
|
},
|
|
427
594
|
{
|
|
428
|
-
command: "
|
|
429
|
-
summary: "
|
|
595
|
+
command: "help [topic]",
|
|
596
|
+
summary: "start here · help <topic> · help all for everything",
|
|
430
597
|
},
|
|
431
|
-
{ command: "
|
|
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: "
|
|
602
|
+
group: "project · session",
|
|
444
603
|
entries: [
|
|
445
|
-
{ command: "
|
|
446
|
-
{ command: "
|
|
447
|
-
{
|
|
448
|
-
|
|
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: "
|
|
611
|
+
group: "project · audio",
|
|
456
612
|
entries: [
|
|
457
613
|
{
|
|
458
|
-
command: "
|
|
459
|
-
summary: "
|
|
614
|
+
command: "audio",
|
|
615
|
+
summary: "the output and input in use, and the lists",
|
|
460
616
|
},
|
|
461
617
|
{
|
|
462
|
-
command: "
|
|
463
|
-
summary: "
|
|
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: "
|
|
472
|
-
summary: "
|
|
622
|
+
command: "audio in <name|default>",
|
|
623
|
+
summary: "the input to meter (recording takes comes later)",
|
|
473
624
|
},
|
|
474
|
-
{ command: "
|
|
625
|
+
{ command: "audio test", summary: "a short tone, then the input level" },
|
|
475
626
|
],
|
|
476
627
|
},
|
|
477
628
|
{
|
|
478
|
-
group: "
|
|
629
|
+
group: "project · panes",
|
|
479
630
|
entries: [
|
|
631
|
+
{ command: "panes", summary: "who has this session open, on what" },
|
|
480
632
|
{
|
|
481
|
-
command: "
|
|
482
|
-
summary: "
|
|
633
|
+
command: "pane <screen> [<track>]",
|
|
634
|
+
summary: "copies dawg pane … for another terminal",
|
|
483
635
|
},
|
|
484
|
-
{ command: "
|
|
636
|
+
{ command: "pin · unpin", summary: "keep this pane on its track" },
|
|
485
637
|
{
|
|
486
|
-
command: "
|
|
487
|
-
summary: "
|
|
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: "
|
|
644
|
+
group: "agent",
|
|
494
645
|
entries: [
|
|
495
646
|
{
|
|
496
|
-
command: "
|
|
497
|
-
summary: "
|
|
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: "
|
|
507
|
-
summary: "
|
|
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: "
|
|
516
|
-
summary: "
|
|
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: "
|
|
520
|
-
summary:
|
|
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: "
|
|
675
|
+
group: "start here",
|
|
526
676
|
entries: [
|
|
527
677
|
{
|
|
528
|
-
command: "
|
|
529
|
-
summary: "
|
|
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: "
|
|
533
|
-
summary: "
|
|
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
|
-
/**
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
"arrange",
|
|
546
|
-
|
|
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
|
|
550
|
-
* `all`,
|
|
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 (
|
|
561
|
-
return
|
|
562
|
-
|
|
563
|
-
|
|
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
|
-
/**
|
|
567
|
-
function
|
|
568
|
-
const
|
|
569
|
-
return
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
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}
|
|
955
|
+
truncate(`${entry.command}${" ".repeat(pad)}${entry.summary}`, width),
|
|
622
956
|
);
|
|
623
957
|
}
|
|
624
958
|
}
|
|
625
959
|
return lines;
|
|
626
960
|
}
|
|
627
961
|
|
|
628
|
-
/**
|
|
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}
|
|
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.
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
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
|
-
|
|
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: "
|
|
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 [<
|
|
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
|
|
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
|
|
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 <
|
|
716
|
-
pattern: "pattern <
|
|
717
|
-
clear:
|
|
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
|
-
"
|
|
720
|
-
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: "
|
|
727
|
-
import: "
|
|
1079
|
+
export: "export <file> · export loop.track.json",
|
|
1080
|
+
import: "import <file> · import loop.track.json",
|
|
728
1081
|
sample:
|
|
729
|
-
"/sample <path> [as <
|
|
730
|
-
samples: "/sample · lists the focused track's
|
|
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
|
-
"
|
|
733
|
-
len: "
|
|
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>] [<
|
|
736
|
-
fade: "fade [in|out] <seconds> [<
|
|
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:
|
|
746
|
-
|
|
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: "
|
|
750
|
-
guide: "/guide [topic] · /guide
|
|
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
|
-
|
|
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: "
|
|
784
|
-
menu: "/menu [sound|effects|rhythm|chords|mix|
|
|
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] ·
|
|
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,
|
|
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.
|
|
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
|
-
]
|
|
1190
|
+
]
|
|
1191
|
+
.map((verb) => verb.replace(/^\//, ""))
|
|
1192
|
+
.filter((verb) => /^[a-z][\w-]*$/i.test(verb)),
|
|
824
1193
|
),
|
|
825
1194
|
];
|
|
826
1195
|
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
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
|
|
856
|
-
if (distance === 0 &&
|
|
857
|
-
// Ties go to
|
|
858
|
-
|
|
859
|
-
const
|
|
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
|
-
|
|
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 (
|
|
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
|
-
|
|
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
|
+
}
|