@hraness/dawg 0.6.0 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +126 -0
- package/DAWG.md +655 -141
- package/README.md +27 -25
- package/core/autotune.ts +1119 -0
- package/core/chords.ts +759 -23
- package/core/clips.ts +499 -0
- package/core/diff.ts +182 -99
- package/core/expression.ts +158 -6
- package/core/fx.ts +354 -32
- package/core/granular.ts +91 -0
- package/core/instruments.ts +180 -0
- package/core/keys.ts +573 -4
- package/core/loop.ts +5 -0
- package/core/lyrics.ts +297 -0
- package/core/master.ts +3 -3
- package/core/resonators.ts +306 -4
- package/core/routing.ts +165 -0
- package/core/score.ts +1181 -16
- package/core/sdk/eval-child.ts +7 -2
- package/core/sdk/eval.ts +35 -6
- package/core/sdk/print.ts +354 -6
- package/core/sdk/sync-lyrics.ts +49 -0
- package/core/sdk/v1.ts +3160 -69
- package/core/sections.ts +298 -24
- package/core/sing.ts +815 -0
- package/core/strings.ts +237 -2
- 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/core/winds.ts +652 -0
- package/guides/agent.md +29 -0
- package/guides/arrange.md +30 -0
- package/guides/audition.md +20 -14
- package/guides/automation.md +12 -6
- package/guides/chords.md +16 -14
- package/guides/effects.md +17 -15
- 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 +12 -6
- package/guides/music.md +23 -8
- package/guides/notes.md +15 -9
- package/guides/performance.md +15 -10
- package/guides/play.md +21 -13
- package/guides/project.md +25 -8
- package/guides/providers.md +20 -14
- package/guides/resample.md +32 -0
- package/guides/rhythm.md +17 -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 -12
- package/guides/styles.md +31 -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 +5 -2
- package/src/agent/agent.ts +131 -13
- package/src/agent/calibration-tools.ts +53 -0
- package/src/agent/chord-tools.ts +153 -1
- package/src/agent/clip-tools.ts +453 -0
- package/src/agent/command-agent.ts +369 -0
- package/src/agent/drum-tools.ts +2 -2
- package/src/agent/expression-tools.ts +91 -0
- package/src/agent/gateway.ts +246 -60
- package/src/agent/models.ts +53 -12
- package/src/agent/ops.ts +15 -1
- package/src/agent/pack-tools.ts +1 -1
- package/src/agent/planner.ts +13 -0
- package/src/agent/portable-schema.ts +80 -0
- package/src/agent/preview-tool.ts +5 -1
- package/src/agent/provider.ts +22 -8
- package/src/agent/resample-tool.ts +131 -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 +307 -37
- package/src/agent/usage.ts +2 -2
- package/src/agent/voice-tools.ts +925 -0
- package/src/agent/xcb-agent.ts +16 -6
- package/src/argv.ts +38 -0
- package/src/audio/analysis.ts +253 -0
- package/src/audio/arrange.ts +58 -3
- 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/doctor.ts +86 -0
- package/src/audio/dsp/bandbank.ts +138 -0
- package/src/audio/dsp/envelope.ts +171 -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/dsp/shift.ts +256 -0
- package/src/audio/effects/chain.ts +21 -4
- package/src/audio/effects/common.ts +57 -0
- package/src/audio/effects/convolution.ts +113 -5
- package/src/audio/effects/filter.ts +48 -69
- package/src/audio/effects/formant.ts +263 -0
- package/src/audio/effects/gaze.ts +354 -0
- package/src/audio/engine.ts +206 -33
- package/src/audio/fit.ts +80 -3
- package/src/audio/granular.ts +352 -21
- package/src/audio/instrument-check.ts +67 -47
- package/src/audio/instruments.ts +36 -3
- package/src/audio/keys/calibration.ts +56 -0
- package/src/audio/keys/electric.ts +427 -0
- package/src/audio/keys/engine.ts +156 -20
- package/src/audio/keys/organ.ts +1335 -0
- package/src/audio/keys/piano.ts +72 -8
- package/src/audio/keys/sympathetic.ts +127 -0
- package/src/audio/kits.ts +135 -6
- package/src/audio/live-worker.ts +3 -1
- package/src/audio/live.ts +247 -26
- package/src/audio/native.ts +615 -0
- package/src/audio/preview.ts +45 -3
- package/src/audio/render-worker.ts +2 -0
- package/src/audio/renderer.ts +2 -0
- package/src/audio/resample.ts +286 -0
- package/src/audio/resonators.ts +10 -2
- package/src/audio/sampler.ts +252 -24
- package/src/audio/samples.ts +46 -3
- package/src/audio/sing/analysis.ts +193 -0
- package/src/audio/sing/engine.ts +949 -0
- package/src/audio/strings/body.ts +29 -16
- package/src/audio/strings/bow.ts +699 -0
- package/src/audio/strings/engine.ts +274 -22
- package/src/audio/strings/measure.test-helpers.ts +2 -0
- 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 +608 -85
- package/src/audio/winds/engine.ts +306 -0
- package/src/audio/winds/filters.ts +153 -0
- package/src/audio/winds/pitch.ts +104 -0
- package/src/audio/winds/trim.ts +104 -0
- package/src/audio/winds/trims.ts +917 -0
- package/src/audio/winds/trims1.ts +297 -0
- package/src/audio/winds/voice.ts +492 -0
- 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 +44 -29
- 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 +104 -23
- package/src/commands/formant.ts +221 -0
- package/src/commands/fx.ts +105 -33
- package/src/commands/grammar.ts +558 -0
- package/src/commands/granular.ts +7 -2
- package/src/commands/help.ts +652 -339
- package/src/commands/history.ts +18 -0
- package/src/commands/keys.ts +293 -17
- package/src/commands/modal.ts +67 -4
- 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 +130 -0
- package/src/commands/progression.ts +170 -0
- package/src/commands/resample.ts +281 -0
- package/src/commands/rhythm.ts +3 -0
- package/src/commands/rig.ts +8 -25
- package/src/commands/sample.ts +49 -2
- package/src/commands/shift.ts +119 -0
- package/src/commands/sing.ts +478 -0
- package/src/commands/string.ts +28 -2
- package/src/commands/strum.ts +485 -0
- 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 +616 -0
- package/src/commands/vocal.ts +147 -0
- package/src/commands/vocoder.ts +627 -0
- package/src/commands/wind.ts +244 -0
- package/src/fs/durable.ts +50 -0
- package/src/lang/glossary.ts +493 -0
- package/src/launch-args.ts +163 -0
- package/src/main.ts +1521 -255
- package/src/media/cli.ts +20 -3
- package/src/media/import.ts +3 -1
- package/src/project/check.ts +26 -3
- 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 +36 -1
- package/src/session/daemon.ts +3 -0
- package/src/session/meta.ts +14 -0
- package/src/session/origin.ts +154 -0
- package/src/session/port.ts +22 -4
- package/src/session/presence.ts +50 -7
- package/src/session/protocol.ts +5 -1
- package/src/session/rebase.ts +18 -5
- package/src/session/receipt.ts +258 -0
- package/src/session/store.ts +65 -43
- package/src/tui/arrange-menu.ts +65 -31
- package/src/tui/audition.ts +1 -1
- package/src/tui/euclid.ts +18 -13
- package/src/tui/fader.ts +228 -41
- package/src/tui/granular-menu.ts +2 -4
- 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 +1377 -193
- package/src/tui/modal-menu.ts +38 -11
- package/src/tui/performance-menu.ts +46 -1
- package/src/tui/play-chords.ts +108 -5
- package/src/tui/play-mode.ts +16 -1
- package/src/tui/play-session.ts +81 -16
- package/src/tui/sing-menu.ts +278 -0
- package/src/tui/style-menu.ts +104 -0
- package/src/tui/vocoder-menu.ts +244 -0
- package/src/tui/wind-menu.ts +144 -0
- package/src/version.ts +8 -0
- package/src/web/fetch.ts +115 -29
- package/tui/activity.ts +180 -9
- package/tui/app.ts +274 -40
- package/tui/clip-row.ts +132 -0
- package/tui/delight.ts +144 -0
- package/tui/drawer.ts +70 -22
- package/tui/frame-gate.ts +76 -0
- package/tui/grammar.ts +112 -63
- package/tui/guide.ts +42 -4
- package/tui/highway.ts +269 -25
- package/tui/hints.ts +192 -0
- package/tui/input.ts +60 -9
- package/tui/keys.ts +1 -1
- package/tui/play-strip.ts +68 -14
- package/tui/prompt.ts +1 -1
- package/tui/screen.ts +144 -14
- package/tui/theme.ts +27 -0
|
@@ -0,0 +1,493 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One vocabulary for dawg: the ten topic ids every door accepts (`/help`,
|
|
3
|
+
* `/guide`, `/menu`), the canonical word for each concept with the words it
|
|
4
|
+
* retired, and the British spellings dawg writes the American way.
|
|
5
|
+
*
|
|
6
|
+
* Retired words keep working wherever they were typed (aliases); they appear
|
|
7
|
+
* in no user-facing text except alias notes. The consistency lint imports
|
|
8
|
+
* TERMS and SPELLING; help, guides and docs are checked against them.
|
|
9
|
+
*
|
|
10
|
+
* Pure data and string helpers, no imports: the site can load it too.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
/** The ten topics, in the order every door lists them. */
|
|
14
|
+
export const TOPICS = [
|
|
15
|
+
"sound",
|
|
16
|
+
"voice",
|
|
17
|
+
"effects",
|
|
18
|
+
"rhythm",
|
|
19
|
+
"chords",
|
|
20
|
+
"mix",
|
|
21
|
+
"arrange",
|
|
22
|
+
"project",
|
|
23
|
+
"keys",
|
|
24
|
+
"agent",
|
|
25
|
+
] as const;
|
|
26
|
+
|
|
27
|
+
export type TopicId = (typeof TOPICS)[number];
|
|
28
|
+
|
|
29
|
+
/** One line per topic, for `/help` and the guide index. */
|
|
30
|
+
export const TOPIC_SUMMARY: Readonly<Record<TopicId, string>> = {
|
|
31
|
+
sound: "instruments, presets, performance, samples",
|
|
32
|
+
voice: "sing, lyrics, clips, pitch, autotune, formant, vocoder",
|
|
33
|
+
effects: "filter, delay, reverb, guitar rig, shoegaze, more",
|
|
34
|
+
rhythm: "drums, euclid, grid, grooves, kits, sample packs",
|
|
35
|
+
chords: "key, tuning, progressions, chord play",
|
|
36
|
+
mix: "volume, pan, mute, solo, automation, master",
|
|
37
|
+
arrange: "notes, tracks, sections, form, styles",
|
|
38
|
+
project: "transport, tempo, meter, loop, export, undo, sessions",
|
|
39
|
+
keys: "the keys of every screen",
|
|
40
|
+
agent: "model, model key, show-me",
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Old ids and everyday words that open a topic. The left side is what a
|
|
45
|
+
* person may type after `/help`, `/guide` or `/menu`; the right is the
|
|
46
|
+
* topic it opens. A guide or menu node with the same id still wins in its
|
|
47
|
+
* own door (`/guide performance` opens the performance guide).
|
|
48
|
+
*/
|
|
49
|
+
export const TOPIC_ALIASES: Readonly<Record<string, TopicId>> = {
|
|
50
|
+
// retired topic ids
|
|
51
|
+
music: "arrange",
|
|
52
|
+
session: "project",
|
|
53
|
+
window: "keys",
|
|
54
|
+
// sound
|
|
55
|
+
sounds: "sound",
|
|
56
|
+
instrument: "sound",
|
|
57
|
+
instruments: "sound",
|
|
58
|
+
performance: "sound",
|
|
59
|
+
expression: "sound",
|
|
60
|
+
samples: "sound",
|
|
61
|
+
// voice
|
|
62
|
+
vocal: "voice",
|
|
63
|
+
vocals: "voice",
|
|
64
|
+
sing: "voice",
|
|
65
|
+
clips: "voice",
|
|
66
|
+
lyrics: "voice",
|
|
67
|
+
autotune: "voice",
|
|
68
|
+
formant: "voice",
|
|
69
|
+
vocoder: "voice",
|
|
70
|
+
// effects
|
|
71
|
+
fx: "effects",
|
|
72
|
+
effect: "effects",
|
|
73
|
+
rig: "effects",
|
|
74
|
+
// rhythm
|
|
75
|
+
drums: "rhythm",
|
|
76
|
+
grooves: "rhythm",
|
|
77
|
+
patterns: "rhythm",
|
|
78
|
+
kits: "rhythm",
|
|
79
|
+
packs: "rhythm",
|
|
80
|
+
// chords and key
|
|
81
|
+
key: "chords",
|
|
82
|
+
scale: "chords",
|
|
83
|
+
scales: "chords",
|
|
84
|
+
tuning: "chords",
|
|
85
|
+
progression: "chords",
|
|
86
|
+
// mix
|
|
87
|
+
master: "mix",
|
|
88
|
+
automation: "mix",
|
|
89
|
+
mixer: "mix",
|
|
90
|
+
// arrange
|
|
91
|
+
arrangement: "arrange",
|
|
92
|
+
sections: "arrange",
|
|
93
|
+
form: "arrange",
|
|
94
|
+
style: "arrange",
|
|
95
|
+
styles: "arrange",
|
|
96
|
+
genre: "arrange",
|
|
97
|
+
tracks: "arrange",
|
|
98
|
+
notes: "arrange",
|
|
99
|
+
// project
|
|
100
|
+
transport: "project",
|
|
101
|
+
tempo: "project",
|
|
102
|
+
meter: "project",
|
|
103
|
+
time: "project",
|
|
104
|
+
export: "project",
|
|
105
|
+
files: "project",
|
|
106
|
+
undo: "project",
|
|
107
|
+
// keys
|
|
108
|
+
keyboard: "keys",
|
|
109
|
+
shortcuts: "keys",
|
|
110
|
+
mouse: "keys",
|
|
111
|
+
// agent
|
|
112
|
+
model: "agent",
|
|
113
|
+
models: "agent",
|
|
114
|
+
showme: "agent",
|
|
115
|
+
"show-me": "agent",
|
|
116
|
+
sessions: "project",
|
|
117
|
+
login: "agent",
|
|
118
|
+
ai: "agent",
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
/** A topic id for `word` (an id or an alias), or undefined. */
|
|
122
|
+
export function resolveTopic(word: string | undefined): TopicId | undefined {
|
|
123
|
+
const name = (word ?? "").trim().toLowerCase().replace(/^\//, "");
|
|
124
|
+
if ((TOPICS as readonly string[]).includes(name)) return name as TopicId;
|
|
125
|
+
return TOPIC_ALIASES[name];
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/** Levenshtein distance, small inputs only. */
|
|
129
|
+
function distance(a: string, b: string): number {
|
|
130
|
+
const row = Array.from({ length: b.length + 1 }, (_, index) => index);
|
|
131
|
+
for (let i = 1; i <= a.length; i++) {
|
|
132
|
+
let previous = row[0]!;
|
|
133
|
+
row[0] = i;
|
|
134
|
+
for (let j = 1; j <= b.length; j++) {
|
|
135
|
+
const current = row[j]!;
|
|
136
|
+
row[j] = Math.min(
|
|
137
|
+
row[j]! + 1,
|
|
138
|
+
row[j - 1]! + 1,
|
|
139
|
+
previous + (a[i - 1] === b[j - 1] ? 0 : 1),
|
|
140
|
+
);
|
|
141
|
+
previous = current;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
return row[b.length]!;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* The closest topic id to `word`, comparing against ids and aliases (an
|
|
149
|
+
* alias answers with its topic). Ties go to the earlier topic.
|
|
150
|
+
*/
|
|
151
|
+
export function nearestTopic(
|
|
152
|
+
word: string,
|
|
153
|
+
extra: readonly string[] = [],
|
|
154
|
+
): string | undefined {
|
|
155
|
+
const name = word.trim().toLowerCase().replace(/^\//, "");
|
|
156
|
+
// The same reach as nearestCommand: one edit for a short word, two else.
|
|
157
|
+
const limit = name.length <= 4 ? 1 : 2;
|
|
158
|
+
let best: { id: string; score: number } | undefined;
|
|
159
|
+
const candidates: [string, string][] = [
|
|
160
|
+
...TOPICS.map((id): [string, string] => [id, id]),
|
|
161
|
+
...extra.map((id): [string, string] => [id, id]),
|
|
162
|
+
...Object.entries(TOPIC_ALIASES),
|
|
163
|
+
];
|
|
164
|
+
for (const [spelling, id] of candidates) {
|
|
165
|
+
const score = distance(name, spelling);
|
|
166
|
+
if (score <= limit && (!best || score < best.score)) best = { id, score };
|
|
167
|
+
}
|
|
168
|
+
return best?.id;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* `no topic X · did you mean Y · /help` for any door; without a near
|
|
173
|
+
* topic, `no topic X · /help`.
|
|
174
|
+
*/
|
|
175
|
+
export function topicMiss(word: string, extra: readonly string[] = []): string {
|
|
176
|
+
const name = word.trim().replace(/^\//, "");
|
|
177
|
+
const near = nearestTopic(name, extra);
|
|
178
|
+
return near
|
|
179
|
+
? `no topic ${name} · did you mean ${near} · /help`
|
|
180
|
+
: `no topic ${name} · /help`;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Guitar presets that `track <name>` still creates (an alias); `rig
|
|
185
|
+
* <name>` is the canonical word for the amp and pedals.
|
|
186
|
+
*/
|
|
187
|
+
export const GUITAR_TRACK_PRESETS: readonly string[] = [
|
|
188
|
+
"jangle",
|
|
189
|
+
"punk",
|
|
190
|
+
"funk",
|
|
191
|
+
"ragged",
|
|
192
|
+
"gtr-lead",
|
|
193
|
+
"gtr-metal",
|
|
194
|
+
"bachata",
|
|
195
|
+
];
|
|
196
|
+
|
|
197
|
+
/** One concept: its one word, the words it retired, and typed aliases. */
|
|
198
|
+
export type Term = Readonly<{
|
|
199
|
+
term: string;
|
|
200
|
+
meaning: string;
|
|
201
|
+
/**
|
|
202
|
+
* Retired words, as phrases matched whole-word in prose. Only phrases
|
|
203
|
+
* that are unambiguous in dawg's text are listed (not "part" or "take").
|
|
204
|
+
*/
|
|
205
|
+
losers: readonly string[];
|
|
206
|
+
/** Typed spellings that still work and are documented only as aliases. */
|
|
207
|
+
aliases: readonly string[];
|
|
208
|
+
/**
|
|
209
|
+
* Longer phrases that contain a loser but are canonical (`ctrl-p play
|
|
210
|
+
* mode` holds the loser `ctrl-p play`); they are blanked before matching.
|
|
211
|
+
*/
|
|
212
|
+
allowed?: readonly string[];
|
|
213
|
+
}>;
|
|
214
|
+
|
|
215
|
+
export const TERMS: readonly Term[] = [
|
|
216
|
+
{ term: "track", meaning: "one instrument line", losers: [], aliases: [] },
|
|
217
|
+
{
|
|
218
|
+
term: "note",
|
|
219
|
+
meaning: "a pitched or drum event",
|
|
220
|
+
losers: [],
|
|
221
|
+
aliases: [],
|
|
222
|
+
},
|
|
223
|
+
{
|
|
224
|
+
term: "drum",
|
|
225
|
+
meaning: "one row of a drum track (kick, snare, hat)",
|
|
226
|
+
losers: ["drum voice", "drum voices"],
|
|
227
|
+
aliases: [],
|
|
228
|
+
},
|
|
229
|
+
{
|
|
230
|
+
term: "sample",
|
|
231
|
+
meaning: "an audio file a sampler plays, or a pack item",
|
|
232
|
+
losers: ["sample voice", "use a sound"],
|
|
233
|
+
aliases: [],
|
|
234
|
+
},
|
|
235
|
+
{
|
|
236
|
+
term: "instrument",
|
|
237
|
+
meaning: "the engine a track plays",
|
|
238
|
+
losers: ["browse sounds"],
|
|
239
|
+
aliases: [],
|
|
240
|
+
},
|
|
241
|
+
{
|
|
242
|
+
term: "preset",
|
|
243
|
+
meaning: "a saved setting of an instrument or effect",
|
|
244
|
+
losers: [],
|
|
245
|
+
aliases: ["presets"],
|
|
246
|
+
},
|
|
247
|
+
{
|
|
248
|
+
term: "sound",
|
|
249
|
+
meaning: "the menu section that shapes the focused track",
|
|
250
|
+
losers: [],
|
|
251
|
+
aliases: [],
|
|
252
|
+
},
|
|
253
|
+
{
|
|
254
|
+
term: "voice",
|
|
255
|
+
meaning:
|
|
256
|
+
"sung or spoken audio: sing, lyrics, clips, pitch, autotune, formant, vocoder",
|
|
257
|
+
losers: [],
|
|
258
|
+
aliases: [],
|
|
259
|
+
},
|
|
260
|
+
{
|
|
261
|
+
term: "register",
|
|
262
|
+
meaning: "an organ stop or combo selection",
|
|
263
|
+
losers: [],
|
|
264
|
+
aliases: [],
|
|
265
|
+
},
|
|
266
|
+
{
|
|
267
|
+
term: "polyphony",
|
|
268
|
+
meaning: "simultaneous grains or notes",
|
|
269
|
+
losers: [],
|
|
270
|
+
aliases: ["voice V"],
|
|
271
|
+
},
|
|
272
|
+
{
|
|
273
|
+
term: "effect",
|
|
274
|
+
meaning: "a processor on a track; fx is only the verb",
|
|
275
|
+
losers: ["FX"],
|
|
276
|
+
aliases: ["fx"],
|
|
277
|
+
},
|
|
278
|
+
{
|
|
279
|
+
term: "lane",
|
|
280
|
+
meaning: "an automation lane only",
|
|
281
|
+
losers: [],
|
|
282
|
+
aliases: [],
|
|
283
|
+
},
|
|
284
|
+
{
|
|
285
|
+
term: "clip",
|
|
286
|
+
meaning: "an audio region on a track",
|
|
287
|
+
losers: [],
|
|
288
|
+
aliases: [],
|
|
289
|
+
},
|
|
290
|
+
{
|
|
291
|
+
term: "section",
|
|
292
|
+
meaning: "a named bar range",
|
|
293
|
+
losers: [],
|
|
294
|
+
aliases: [],
|
|
295
|
+
},
|
|
296
|
+
{ term: "form", meaning: "the order of sections", losers: [], aliases: [] },
|
|
297
|
+
{
|
|
298
|
+
term: "style",
|
|
299
|
+
meaning: "a generative song recipe",
|
|
300
|
+
losers: ["genre", "genres"],
|
|
301
|
+
aliases: ["genre"],
|
|
302
|
+
},
|
|
303
|
+
{
|
|
304
|
+
term: "groove",
|
|
305
|
+
meaning: "a ready-made drum pattern",
|
|
306
|
+
losers: ["drum pattern", "drum patterns"],
|
|
307
|
+
aliases: ["pattern"],
|
|
308
|
+
},
|
|
309
|
+
{
|
|
310
|
+
term: "idiom",
|
|
311
|
+
meaning: "the progression flavor chords suggest from",
|
|
312
|
+
losers: [],
|
|
313
|
+
aliases: ["chords style"],
|
|
314
|
+
},
|
|
315
|
+
{
|
|
316
|
+
term: "kit",
|
|
317
|
+
meaning: "a set of drum samples",
|
|
318
|
+
losers: ["drum kit", "drum kits"],
|
|
319
|
+
aliases: [],
|
|
320
|
+
},
|
|
321
|
+
{
|
|
322
|
+
term: "key",
|
|
323
|
+
meaning: "tonic and scale (key A dorian)",
|
|
324
|
+
losers: [],
|
|
325
|
+
aliases: ["scale"],
|
|
326
|
+
},
|
|
327
|
+
{
|
|
328
|
+
term: "tuning",
|
|
329
|
+
meaning: "how pitches are tuned (edo, scl, ratios)",
|
|
330
|
+
losers: ["temperament"],
|
|
331
|
+
aliases: [],
|
|
332
|
+
},
|
|
333
|
+
{
|
|
334
|
+
term: "tempo",
|
|
335
|
+
meaning: "speed, displayed as BPM",
|
|
336
|
+
losers: [],
|
|
337
|
+
aliases: ["bpm"],
|
|
338
|
+
},
|
|
339
|
+
{ term: "loop", meaning: "the playback region", losers: [], aliases: [] },
|
|
340
|
+
{
|
|
341
|
+
term: "remove",
|
|
342
|
+
meaning: "delete something",
|
|
343
|
+
losers: [],
|
|
344
|
+
aliases: ["rm", "delete"],
|
|
345
|
+
},
|
|
346
|
+
{
|
|
347
|
+
term: "list",
|
|
348
|
+
meaning: "show choices",
|
|
349
|
+
losers: [],
|
|
350
|
+
aliases: ["presets", "ls"],
|
|
351
|
+
},
|
|
352
|
+
{
|
|
353
|
+
term: "agent",
|
|
354
|
+
meaning: "the optional model that acts in dawg",
|
|
355
|
+
losers: ["AI assistant", "assistant"],
|
|
356
|
+
aliases: [],
|
|
357
|
+
},
|
|
358
|
+
{
|
|
359
|
+
term: "model",
|
|
360
|
+
meaning: "which LLM the agent uses",
|
|
361
|
+
losers: [],
|
|
362
|
+
aliases: ["models"],
|
|
363
|
+
},
|
|
364
|
+
{
|
|
365
|
+
term: "agent key",
|
|
366
|
+
meaning: "the optional provider key, set with model key",
|
|
367
|
+
losers: ["sign in", "sign-in", "signed in", "signs in", "login"],
|
|
368
|
+
aliases: ["login"],
|
|
369
|
+
},
|
|
370
|
+
{
|
|
371
|
+
term: "play",
|
|
372
|
+
meaning: "the transport (Space)",
|
|
373
|
+
losers: [],
|
|
374
|
+
aliases: [],
|
|
375
|
+
},
|
|
376
|
+
{
|
|
377
|
+
term: "play mode",
|
|
378
|
+
meaning: "ctrl-p turns the computer keyboard into an instrument",
|
|
379
|
+
losers: ["keys mode", "keyboard mode", "ctrl-p play", "Ctrl-P play"],
|
|
380
|
+
aliases: [],
|
|
381
|
+
allowed: ["ctrl-p play mode", "Ctrl-P play mode"],
|
|
382
|
+
},
|
|
383
|
+
{
|
|
384
|
+
term: "rig",
|
|
385
|
+
meaning: "a guitar amp and pedal preset (rig jangle)",
|
|
386
|
+
losers: GUITAR_TRACK_PRESETS.map((name) => `track ${name}`),
|
|
387
|
+
aliases: GUITAR_TRACK_PRESETS.map((name) => `track ${name}`),
|
|
388
|
+
},
|
|
389
|
+
{
|
|
390
|
+
term: "sawtooth",
|
|
391
|
+
meaning: "the saw wave, listed once as sawtooth",
|
|
392
|
+
losers: ["saw square", "pluck bass saw"],
|
|
393
|
+
aliases: ["saw"],
|
|
394
|
+
},
|
|
395
|
+
{
|
|
396
|
+
term: "now / next",
|
|
397
|
+
meaning: "prompt modes: send now, or run after the current request",
|
|
398
|
+
losers: ["STEER", "QUEUE"],
|
|
399
|
+
aliases: [],
|
|
400
|
+
},
|
|
401
|
+
];
|
|
402
|
+
|
|
403
|
+
/** British spellings dawg writes the American way (lowercase keys). */
|
|
404
|
+
export const SPELLING: Readonly<Record<string, string>> = {
|
|
405
|
+
centre: "center",
|
|
406
|
+
centred: "centered",
|
|
407
|
+
centres: "centers",
|
|
408
|
+
modelled: "modeled",
|
|
409
|
+
modelling: "modeling",
|
|
410
|
+
behaviour: "behavior",
|
|
411
|
+
behaviours: "behaviors",
|
|
412
|
+
cancelled: "canceled",
|
|
413
|
+
cancelling: "canceling",
|
|
414
|
+
colour: "color",
|
|
415
|
+
colours: "colors",
|
|
416
|
+
coloured: "colored",
|
|
417
|
+
analyse: "analyze",
|
|
418
|
+
analysed: "analyzed",
|
|
419
|
+
analyses: "analyzes",
|
|
420
|
+
labelled: "labeled",
|
|
421
|
+
labelling: "labeling",
|
|
422
|
+
towards: "toward",
|
|
423
|
+
normalise: "normalize",
|
|
424
|
+
normalised: "normalized",
|
|
425
|
+
normalises: "normalizes",
|
|
426
|
+
organise: "organize",
|
|
427
|
+
organised: "organized",
|
|
428
|
+
recognise: "recognize",
|
|
429
|
+
recognised: "recognized",
|
|
430
|
+
realise: "realize",
|
|
431
|
+
optimise: "optimize",
|
|
432
|
+
optimised: "optimized",
|
|
433
|
+
favourite: "favorite",
|
|
434
|
+
favourites: "favorites",
|
|
435
|
+
grey: "gray",
|
|
436
|
+
travelling: "traveling",
|
|
437
|
+
levelled: "leveled",
|
|
438
|
+
signalled: "signaled",
|
|
439
|
+
synthesise: "synthesize",
|
|
440
|
+
synthesised: "synthesized",
|
|
441
|
+
quantise: "quantize",
|
|
442
|
+
quantised: "quantized",
|
|
443
|
+
randomise: "randomize",
|
|
444
|
+
randomised: "randomized",
|
|
445
|
+
customise: "customize",
|
|
446
|
+
initialise: "initialize",
|
|
447
|
+
licence: "license",
|
|
448
|
+
dialogue: "dialog",
|
|
449
|
+
catalogue: "catalog",
|
|
450
|
+
};
|
|
451
|
+
|
|
452
|
+
/** Typed spellings kept for old projects and muscle memory (aliases). */
|
|
453
|
+
export const SPELLING_ALIASES: readonly string[] = ["phasercentre"];
|
|
454
|
+
|
|
455
|
+
/** Whether a line is an alias note, where retired words may appear. */
|
|
456
|
+
export function isAliasNote(line: string): boolean {
|
|
457
|
+
return /\balias(es)?\b|\bretired\b|\bold spelling\b/i.test(line);
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
/**
|
|
461
|
+
* Retired words and British spellings in `text`, one finding per hit:
|
|
462
|
+
* `losers` match whole-word (case-sensitive for all-caps losers such as
|
|
463
|
+
* FX), spellings case-insensitively. Alias-note lines are skipped.
|
|
464
|
+
*/
|
|
465
|
+
export function lintText(text: string): string[] {
|
|
466
|
+
const hits: string[] = [];
|
|
467
|
+
for (const line of text.split("\n")) {
|
|
468
|
+
if (isAliasNote(line)) continue;
|
|
469
|
+
for (const { term, losers, allowed = [] } of TERMS) {
|
|
470
|
+
let text = line;
|
|
471
|
+
for (const phrase of allowed) text = text.split(phrase).join(" ");
|
|
472
|
+
for (const loser of losers) {
|
|
473
|
+
// Case-sensitive, so `FX` is not `fx`; a capital first letter
|
|
474
|
+
// (sentence start) still matches a lowercase loser.
|
|
475
|
+
const escaped = loser.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
476
|
+
const first = escaped[0]!;
|
|
477
|
+
const head =
|
|
478
|
+
first.toUpperCase() === first
|
|
479
|
+
? first
|
|
480
|
+
: `[${first}${first.toUpperCase()}]`;
|
|
481
|
+
const pattern = new RegExp(
|
|
482
|
+
`(?<![\\w/.\`-])${head}${escaped.slice(1)}(?![\\w\`-])`,
|
|
483
|
+
);
|
|
484
|
+
if (pattern.test(text)) hits.push(`${loser} → ${term}: ${line.trim()}`);
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
for (const word of line.match(/[A-Za-z]+/g) ?? []) {
|
|
488
|
+
const american = SPELLING[word.toLowerCase()];
|
|
489
|
+
if (american) hits.push(`${word} → ${american}: ${line.trim()}`);
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
return hits;
|
|
493
|
+
}
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The `dawg [options]` launch grammar and the small argv contract the plain
|
|
3
|
+
* subcommands share. Pure: no I/O, so every mistake is rejected before
|
|
4
|
+
* `.dawg/` (or any project file) could be written.
|
|
5
|
+
*/
|
|
6
|
+
export { parseSimpleArgv, type SimpleArgv } from "./argv.ts";
|
|
7
|
+
import { parseThemeName, THEME_NAMES, type ThemeName } from "../tui/theme.ts";
|
|
8
|
+
|
|
9
|
+
/** Subcommands that parse their own argv; the launch grammar skips them. */
|
|
10
|
+
export const SUBCOMMANDS: readonly string[] = [
|
|
11
|
+
"login",
|
|
12
|
+
"logout",
|
|
13
|
+
"auth",
|
|
14
|
+
"model",
|
|
15
|
+
"sessions",
|
|
16
|
+
"render",
|
|
17
|
+
"init",
|
|
18
|
+
"check",
|
|
19
|
+
"media",
|
|
20
|
+
];
|
|
21
|
+
export const VALUE_FLAGS = [
|
|
22
|
+
"--session",
|
|
23
|
+
"--track",
|
|
24
|
+
"--import",
|
|
25
|
+
"--export",
|
|
26
|
+
"--theme",
|
|
27
|
+
] as const;
|
|
28
|
+
export type ValueFlag = (typeof VALUE_FLAGS)[number];
|
|
29
|
+
export const BOOLEAN_FLAGS: readonly string[] = [
|
|
30
|
+
"--new",
|
|
31
|
+
"--demo",
|
|
32
|
+
"--help",
|
|
33
|
+
"-h",
|
|
34
|
+
"--version",
|
|
35
|
+
"-v",
|
|
36
|
+
"--reduce-motion",
|
|
37
|
+
"--no-mouse",
|
|
38
|
+
];
|
|
39
|
+
|
|
40
|
+
/** A `--track` value resolved to the id `/track` would use. */
|
|
41
|
+
export type TrackArg = { id: string; name: string };
|
|
42
|
+
|
|
43
|
+
export type LaunchArgs = {
|
|
44
|
+
subcommand: string | undefined;
|
|
45
|
+
flags: ReadonlySet<string>;
|
|
46
|
+
session: string | undefined;
|
|
47
|
+
track: TrackArg | undefined;
|
|
48
|
+
importPath: string | undefined;
|
|
49
|
+
exportPath: string | undefined;
|
|
50
|
+
theme: ThemeName | undefined;
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
export type LaunchParse =
|
|
54
|
+
{ ok: true; args: LaunchArgs } | { ok: false; problem: string };
|
|
55
|
+
|
|
56
|
+
const TRACK_NAME = /^[a-z0-9._ -]{1,64}$/i;
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* `--track Bass Guitar` → id `bass-guitar`, name `bass guitar`: the rule
|
|
60
|
+
* `/track` applies (letters, digits, `.`, `_`, `-` and spaces, at most 64),
|
|
61
|
+
* lowercased with runs of spaces collapsed. Undefined when invalid.
|
|
62
|
+
*/
|
|
63
|
+
export function normalizeTrackArg(value: string): TrackArg | undefined {
|
|
64
|
+
const name = value.trim().replace(/\s+/g, " ").toLowerCase();
|
|
65
|
+
if (!TRACK_NAME.test(name)) return undefined;
|
|
66
|
+
// `.` and `..` are path segments, never track ids.
|
|
67
|
+
if (/^\.+$/.test(name)) return undefined;
|
|
68
|
+
return { id: name.replace(/ /g, "-"), name };
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/** The existing track `arg` names (by id or name), else `arg.id`. */
|
|
72
|
+
export function resolveTrackArg(
|
|
73
|
+
tracks: readonly { id: string; name?: string | undefined }[],
|
|
74
|
+
arg: TrackArg,
|
|
75
|
+
): string {
|
|
76
|
+
const found =
|
|
77
|
+
tracks.find((track) => track.id === arg.id) ??
|
|
78
|
+
tracks.find((track) => track.id.toLowerCase() === arg.id) ??
|
|
79
|
+
tracks.find((track) => (track.name ?? track.id).toLowerCase() === arg.name);
|
|
80
|
+
return found?.id ?? arg.id;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Parses `dawg [options]`. A subcommand as the first word ends parsing (the
|
|
85
|
+
* subcommand owns the rest). Value flags need a value that does not start
|
|
86
|
+
* with `-`; each may be given once; `--track` and `--theme` are validated
|
|
87
|
+
* here so a bad value is one line and exit 2, never a stack trace.
|
|
88
|
+
*/
|
|
89
|
+
export function parseLaunchArgs(argv: readonly string[]): LaunchParse {
|
|
90
|
+
const flags = new Set<string>();
|
|
91
|
+
const values = new Map<ValueFlag, string>();
|
|
92
|
+
const first = argv[0];
|
|
93
|
+
if (first !== undefined && !first.startsWith("-")) {
|
|
94
|
+
if (SUBCOMMANDS.includes(first))
|
|
95
|
+
return { ok: true, args: emptyArgs(first, flags) };
|
|
96
|
+
return { ok: false, problem: `unknown command · ${clip(first)}` };
|
|
97
|
+
}
|
|
98
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
99
|
+
const arg = argv[index]!;
|
|
100
|
+
if ((VALUE_FLAGS as readonly string[]).includes(arg)) {
|
|
101
|
+
const flag = arg as ValueFlag;
|
|
102
|
+
const value = argv[index + 1];
|
|
103
|
+
if (value === undefined || value.length === 0 || value.startsWith("-"))
|
|
104
|
+
return { ok: false, problem: `${flag} needs a value` };
|
|
105
|
+
if (values.has(flag))
|
|
106
|
+
return { ok: false, problem: `${flag} given twice` };
|
|
107
|
+
values.set(flag, value);
|
|
108
|
+
index += 1;
|
|
109
|
+
continue;
|
|
110
|
+
}
|
|
111
|
+
if (BOOLEAN_FLAGS.includes(arg)) {
|
|
112
|
+
flags.add(arg);
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
115
|
+
if (arg.startsWith("-"))
|
|
116
|
+
return { ok: false, problem: `unknown option · ${clip(arg)}` };
|
|
117
|
+
return { ok: false, problem: `unknown command · ${clip(arg)}` };
|
|
118
|
+
}
|
|
119
|
+
let track: TrackArg | undefined;
|
|
120
|
+
const trackValue = values.get("--track");
|
|
121
|
+
if (trackValue !== undefined) {
|
|
122
|
+
track = normalizeTrackArg(trackValue);
|
|
123
|
+
if (!track) return { ok: false, problem: "invalid track name" };
|
|
124
|
+
}
|
|
125
|
+
let theme: ThemeName | undefined;
|
|
126
|
+
const themeValue = values.get("--theme");
|
|
127
|
+
if (themeValue !== undefined) {
|
|
128
|
+
theme = parseThemeName(themeValue);
|
|
129
|
+
if (!theme)
|
|
130
|
+
return {
|
|
131
|
+
ok: false,
|
|
132
|
+
problem: `unknown theme ${clip(themeValue)} · ${THEME_NAMES.join(" | ")}`,
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
return {
|
|
136
|
+
ok: true,
|
|
137
|
+
args: {
|
|
138
|
+
subcommand: undefined,
|
|
139
|
+
flags,
|
|
140
|
+
session: values.get("--session"),
|
|
141
|
+
track,
|
|
142
|
+
importPath: values.get("--import"),
|
|
143
|
+
exportPath: values.get("--export"),
|
|
144
|
+
theme,
|
|
145
|
+
},
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function emptyArgs(subcommand: string, flags: Set<string>): LaunchArgs {
|
|
150
|
+
return {
|
|
151
|
+
subcommand,
|
|
152
|
+
flags,
|
|
153
|
+
session: undefined,
|
|
154
|
+
track: undefined,
|
|
155
|
+
importPath: undefined,
|
|
156
|
+
exportPath: undefined,
|
|
157
|
+
theme: undefined,
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function clip(value: string): string {
|
|
162
|
+
return value.length > 40 ? `${value.slice(0, 40)}…` : value;
|
|
163
|
+
}
|