@hraness/dawg 0.6.1 → 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.
Files changed (253) hide show
  1. package/CHANGELOG.md +92 -0
  2. package/DAWG.md +343 -116
  3. package/README.md +27 -25
  4. package/core/autotune.ts +1119 -0
  5. package/core/chords.ts +271 -23
  6. package/core/clips.ts +499 -0
  7. package/core/diff.ts +182 -104
  8. package/core/expression.ts +15 -0
  9. package/core/fx.ts +99 -29
  10. package/core/instruments.ts +19 -0
  11. package/core/keys.ts +3 -3
  12. package/core/loop.ts +5 -0
  13. package/core/lyrics.ts +297 -0
  14. package/core/master.ts +3 -3
  15. package/core/resonators.ts +16 -2
  16. package/core/routing.ts +165 -0
  17. package/core/score.ts +832 -13
  18. package/core/sdk/eval-child.ts +7 -2
  19. package/core/sdk/eval.ts +35 -6
  20. package/core/sdk/print.ts +276 -3
  21. package/core/sdk/sync-lyrics.ts +49 -0
  22. package/core/sdk/v1.ts +1849 -42
  23. package/core/sections.ts +292 -26
  24. package/core/sing.ts +815 -0
  25. package/core/style-provenance.ts +80 -0
  26. package/core/styles/africa-mena-southasia.ts +2893 -0
  27. package/core/styles/americas.ts +3810 -0
  28. package/core/styles/art.ts +4993 -0
  29. package/core/styles/base.ts +123 -0
  30. package/core/styles/cycles.ts +106 -0
  31. package/core/styles/electronic.ts +2723 -0
  32. package/core/styles/europe-asia-pacific.ts +2838 -0
  33. package/core/styles/excerpt.ts +29 -0
  34. package/core/styles/gamelan.ts +283 -0
  35. package/core/styles/generate.ts +2199 -0
  36. package/core/styles/index.ts +515 -0
  37. package/core/styles/parts.ts +106 -0
  38. package/core/styles/pop.ts +3189 -0
  39. package/core/styles/rock.ts +2993 -0
  40. package/core/styles/roots.ts +4175 -0
  41. package/core/styles/schema.ts +429 -0
  42. package/core/styles/taxonomy.ts +940 -0
  43. package/core/styles/validate.ts +528 -0
  44. package/core/tempo.ts +32 -2
  45. package/core/tuning.ts +19 -3
  46. package/core/vocoder.ts +524 -0
  47. package/guides/agent.md +29 -0
  48. package/guides/arrange.md +30 -0
  49. package/guides/audition.md +20 -14
  50. package/guides/automation.md +12 -7
  51. package/guides/chords.md +15 -15
  52. package/guides/effects.md +17 -16
  53. package/guides/faders.md +20 -16
  54. package/guides/files.md +13 -9
  55. package/guides/getting-started.md +15 -11
  56. package/guides/keys.md +19 -15
  57. package/guides/media.md +17 -12
  58. package/guides/mix.md +12 -6
  59. package/guides/music.md +23 -8
  60. package/guides/notes.md +15 -9
  61. package/guides/performance.md +15 -13
  62. package/guides/play.md +21 -13
  63. package/guides/project.md +25 -8
  64. package/guides/providers.md +20 -14
  65. package/guides/resample.md +15 -9
  66. package/guides/rhythm.md +17 -13
  67. package/guides/sessions.md +17 -7
  68. package/guides/show-me.md +31 -0
  69. package/guides/sound.md +25 -9
  70. package/guides/sounds.md +15 -13
  71. package/guides/styles.md +31 -0
  72. package/guides/tempo.md +15 -10
  73. package/guides/tracks.md +15 -10
  74. package/guides/tuning.md +32 -0
  75. package/guides/voice.md +31 -0
  76. package/guides/web-search.md +18 -8
  77. package/native/prebuilt/darwin-arm64/libdawg_sink.dylib +0 -0
  78. package/native/prebuilt/darwin-x64/libdawg_sink.dylib +0 -0
  79. package/native/prebuilt/linux-arm64/libdawg_sink.so +0 -0
  80. package/native/prebuilt/linux-x64/libdawg_sink.so +0 -0
  81. package/native/prebuilt/manifest.json +21 -0
  82. package/package.json +5 -2
  83. package/src/agent/agent.ts +126 -14
  84. package/src/agent/calibration-tools.ts +53 -0
  85. package/src/agent/clip-tools.ts +453 -0
  86. package/src/agent/command-agent.ts +369 -0
  87. package/src/agent/drum-tools.ts +2 -2
  88. package/src/agent/expression-tools.ts +1 -1
  89. package/src/agent/gateway.ts +246 -60
  90. package/src/agent/models.ts +53 -12
  91. package/src/agent/ops.ts +12 -1
  92. package/src/agent/pack-tools.ts +1 -1
  93. package/src/agent/planner.ts +13 -0
  94. package/src/agent/portable-schema.ts +80 -0
  95. package/src/agent/preview-tool.ts +4 -1
  96. package/src/agent/provider.ts +22 -8
  97. package/src/agent/rhythm-tools.ts +1 -1
  98. package/src/agent/section-tools.ts +1 -1
  99. package/src/agent/show-me.ts +497 -0
  100. package/src/agent/steer.ts +15 -0
  101. package/src/agent/style-tools.ts +217 -0
  102. package/src/agent/tool-error.ts +12 -0
  103. package/src/agent/tools.ts +108 -23
  104. package/src/agent/usage.ts +2 -2
  105. package/src/agent/voice-tools.ts +925 -0
  106. package/src/agent/xcb-agent.ts +11 -7
  107. package/src/argv.ts +38 -0
  108. package/src/audio/analysis.ts +253 -0
  109. package/src/audio/arrange.ts +37 -3
  110. package/src/audio/autotune-engine.ts +101 -0
  111. package/src/audio/autotune.ts +640 -0
  112. package/src/audio/clips.ts +240 -0
  113. package/src/audio/doctor.ts +86 -0
  114. package/src/audio/dsp/bandbank.ts +138 -0
  115. package/src/audio/dsp/envelope.ts +10 -0
  116. package/src/audio/dsp/follow.ts +120 -0
  117. package/src/audio/dsp/formant.ts +427 -0
  118. package/src/audio/dsp/glottal.ts +243 -0
  119. package/src/audio/dsp/interp.ts +7 -2
  120. package/src/audio/dsp/lpc.ts +50 -0
  121. package/src/audio/dsp/periodicity.ts +59 -0
  122. package/src/audio/dsp/pitch.ts +995 -0
  123. package/src/audio/dsp/psola.ts +199 -0
  124. package/src/audio/effects/chain.ts +3 -1
  125. package/src/audio/effects/common.ts +43 -0
  126. package/src/audio/effects/convolution.ts +7 -4
  127. package/src/audio/effects/filter.ts +48 -69
  128. package/src/audio/effects/formant.ts +263 -0
  129. package/src/audio/engine.ts +160 -28
  130. package/src/audio/fit.ts +35 -3
  131. package/src/audio/instrument-check.ts +59 -43
  132. package/src/audio/instruments.ts +4 -0
  133. package/src/audio/keys/calibration.ts +56 -0
  134. package/src/audio/keys/electric.ts +8 -1
  135. package/src/audio/keys/engine.ts +13 -1
  136. package/src/audio/keys/piano.ts +22 -2
  137. package/src/audio/kits.ts +135 -6
  138. package/src/audio/live.ts +114 -20
  139. package/src/audio/native.ts +615 -0
  140. package/src/audio/preview.ts +30 -2
  141. package/src/audio/render-worker.ts +2 -0
  142. package/src/audio/renderer.ts +2 -0
  143. package/src/audio/resample.ts +2 -1
  144. package/src/audio/sampler.ts +85 -4
  145. package/src/audio/samples.ts +20 -2
  146. package/src/audio/sing/analysis.ts +193 -0
  147. package/src/audio/sing/engine.ts +949 -0
  148. package/src/audio/strings/bow.ts +48 -5
  149. package/src/audio/strings/engine.ts +8 -2
  150. package/src/audio/synth/oscillators.ts +31 -21
  151. package/src/audio/synth/voice.ts +34 -1
  152. package/src/audio/vocoder/bank.ts +314 -0
  153. package/src/audio/vocoder/carrier.ts +165 -0
  154. package/src/audio/vocoder/control.ts +68 -0
  155. package/src/audio/vocoder/detect.ts +50 -0
  156. package/src/audio/vocoder/index.ts +304 -0
  157. package/src/audio/vocoder/talkbox.ts +143 -0
  158. package/src/audio/wav.ts +500 -77
  159. package/src/audio/winds/engine.ts +5 -1
  160. package/src/audio/winds/trim.ts +28 -4
  161. package/src/audio/winds/trims1.ts +297 -0
  162. package/src/audio/winds/voice.ts +15 -2
  163. package/src/auth/cli.ts +38 -36
  164. package/src/auth/credentials.ts +30 -1
  165. package/src/auth/login.ts +15 -9
  166. package/src/auth/tui.ts +19 -10
  167. package/src/commands/arrange.ts +44 -29
  168. package/src/commands/autotune.ts +421 -0
  169. package/src/commands/calibration.ts +74 -0
  170. package/src/commands/clips.ts +887 -0
  171. package/src/commands/drums.ts +3 -2
  172. package/src/commands/edit.ts +11 -4
  173. package/src/commands/expression.ts +1 -1
  174. package/src/commands/formant.ts +221 -0
  175. package/src/commands/fx.ts +101 -32
  176. package/src/commands/grammar.ts +558 -0
  177. package/src/commands/help.ts +610 -380
  178. package/src/commands/history.ts +18 -0
  179. package/src/commands/keys.ts +8 -8
  180. package/src/commands/modal.ts +1 -1
  181. package/src/commands/music.ts +1 -1
  182. package/src/commands/nearest.ts +53 -0
  183. package/src/commands/pack.ts +9 -2
  184. package/src/commands/param-range.ts +56 -0
  185. package/src/commands/parses.ts +130 -0
  186. package/src/commands/progression.ts +170 -0
  187. package/src/commands/rhythm.ts +3 -0
  188. package/src/commands/rig.ts +3 -24
  189. package/src/commands/sing.ts +478 -0
  190. package/src/commands/strum.ts +13 -1
  191. package/src/commands/style.ts +415 -0
  192. package/src/commands/time.ts +6 -3
  193. package/src/commands/tuning.ts +3 -3
  194. package/src/commands/vocal-pitch.ts +616 -0
  195. package/src/commands/vocal.ts +147 -0
  196. package/src/commands/vocoder.ts +627 -0
  197. package/src/commands/wind.ts +2 -2
  198. package/src/fs/durable.ts +50 -0
  199. package/src/lang/glossary.ts +493 -0
  200. package/src/launch-args.ts +163 -0
  201. package/src/main.ts +1397 -253
  202. package/src/media/cli.ts +20 -3
  203. package/src/media/import.ts +3 -1
  204. package/src/project/check.ts +21 -2
  205. package/src/project/clip-pins.ts +72 -0
  206. package/src/project/init.ts +23 -8
  207. package/src/project/sync.ts +418 -86
  208. package/src/render.ts +20 -1
  209. package/src/session/daemon.ts +3 -0
  210. package/src/session/meta.ts +14 -0
  211. package/src/session/origin.ts +154 -0
  212. package/src/session/port.ts +22 -4
  213. package/src/session/presence.ts +34 -4
  214. package/src/session/protocol.ts +5 -1
  215. package/src/session/rebase.ts +18 -5
  216. package/src/session/receipt.ts +258 -0
  217. package/src/session/store.ts +65 -43
  218. package/src/tui/arrange-menu.ts +65 -31
  219. package/src/tui/audition.ts +1 -1
  220. package/src/tui/euclid.ts +18 -13
  221. package/src/tui/fader.ts +228 -41
  222. package/src/tui/granular-menu.ts +2 -4
  223. package/src/tui/menu-clips.ts +297 -0
  224. package/src/tui/menu-time.ts +20 -13
  225. package/src/tui/menu-voice.ts +405 -0
  226. package/src/tui/menu.ts +759 -177
  227. package/src/tui/modal-menu.ts +6 -6
  228. package/src/tui/performance-menu.ts +5 -2
  229. package/src/tui/play-chords.ts +4 -2
  230. package/src/tui/play-mode.ts +15 -1
  231. package/src/tui/play-session.ts +47 -7
  232. package/src/tui/sing-menu.ts +278 -0
  233. package/src/tui/style-menu.ts +104 -0
  234. package/src/tui/vocoder-menu.ts +244 -0
  235. package/src/tui/wind-menu.ts +3 -3
  236. package/src/version.ts +8 -0
  237. package/src/web/fetch.ts +115 -29
  238. package/tui/activity.ts +180 -9
  239. package/tui/app.ts +274 -40
  240. package/tui/clip-row.ts +132 -0
  241. package/tui/delight.ts +144 -0
  242. package/tui/drawer.ts +70 -22
  243. package/tui/frame-gate.ts +76 -0
  244. package/tui/grammar.ts +112 -63
  245. package/tui/guide.ts +42 -4
  246. package/tui/highway.ts +269 -25
  247. package/tui/hints.ts +192 -0
  248. package/tui/input.ts +60 -9
  249. package/tui/keys.ts +1 -1
  250. package/tui/play-strip.ts +68 -14
  251. package/tui/prompt.ts +1 -1
  252. package/tui/screen.ts +144 -14
  253. package/tui/theme.ts +27 -0
@@ -0,0 +1,524 @@
1
+ /**
2
+ * Vocoder (0.7 "Voice", lane vocoder): a track's sound (the carrier) shaped
3
+ * by another track's sound (the modulator, `src`). Pure and import-light,
4
+ * like `core/winds.ts`: the parameter table, the eight presets and the
5
+ * validator that the score, the prompt, the menu, the agent and the SDK
6
+ * printer share. The DSP lives in `src/audio/vocoder/`.
7
+ *
8
+ * Two ways in:
9
+ * - any track gains `vocoder: { src, ... }`: its own voice is the carrier;
10
+ * - instrument `vocoder` with a `vocoder` field plays a built-in carrier
11
+ * (saw, supersaw, pulse or noise) following the track's notes, the song's
12
+ * chords or a drone.
13
+ *
14
+ * The stage runs only when `vocoder.src` is present; without it the track
15
+ * plays dry (a `vocoder` instrument without a source is silent). Absent
16
+ * field: today's sound, byte for byte.
17
+ */
18
+ import {
19
+ FxValidationError,
20
+ isRecord,
21
+ normalizeParams,
22
+ type NumberParam,
23
+ type ParamSpec,
24
+ } from "./params.ts";
25
+
26
+ /** Instrument value that selects the built-in carrier. */
27
+ export const VOCODER_INSTRUMENT = "vocoder" as const;
28
+
29
+ export const VOCODER_PRESET_NAMES = Object.freeze([
30
+ "classic",
31
+ "robot",
32
+ "talkbox",
33
+ "choir",
34
+ "glass",
35
+ "whisper",
36
+ "smear",
37
+ "lofi",
38
+ ] as const);
39
+ export type VocoderPresetName = (typeof VOCODER_PRESET_NAMES)[number];
40
+
41
+ /** Default preset of a `vocoder` field that names none. */
42
+ export const DEFAULT_VOCODER_PRESET: VocoderPresetName = "classic";
43
+
44
+ /** Modulator taps (append-only). */
45
+ export const VOCODER_TAPS = Object.freeze(["chain", "dry"]);
46
+ /** Modes (append-only): a band bank or an LPC talkbox. */
47
+ export const VOCODER_MODES = Object.freeze(["channel", "talkbox"]);
48
+ /** Built-in carriers (append-only; `glottal` is 0.7.1). */
49
+ export const VOCODER_CARRIERS = Object.freeze([
50
+ "saw",
51
+ "supersaw",
52
+ "pulse",
53
+ "noise",
54
+ ]);
55
+ /** Built-in carrier pitch (append-only; `voice` is 0.7.1). */
56
+ export const VOCODER_FOLLOWS = Object.freeze(["notes", "chords", "drone"]);
57
+
58
+ /** `gate` value that reads the threshold off the modulator's own assets. */
59
+ export const VOCODER_GATE_AUTO = "auto" as const;
60
+
61
+ /** Talkbox formant bound (the channel bank takes the full ±24). */
62
+ export const TALKBOX_FORMANT_MAX = 12;
63
+
64
+ const num = (
65
+ min: number,
66
+ max: number,
67
+ fallback: number,
68
+ step: number | "log",
69
+ doc: string,
70
+ extra: Partial<NumberParam> = {},
71
+ ): NumberParam =>
72
+ Object.freeze({
73
+ kind: "number",
74
+ min,
75
+ max,
76
+ default: fallback,
77
+ step,
78
+ optional: true,
79
+ doc,
80
+ ...extra,
81
+ });
82
+
83
+ const choice = (values: readonly string[], fallback: string, doc: string) =>
84
+ Object.freeze({
85
+ kind: "enum" as const,
86
+ values,
87
+ default: fallback,
88
+ optional: true,
89
+ doc,
90
+ });
91
+
92
+ /**
93
+ * Every vocoder parameter in table order (`src` and `preset` excluded). All
94
+ * optional: an absent one takes the preset's value, then the default here.
95
+ * `automate` ones have a `vocoder-<param>` lane. `gate` also takes "auto".
96
+ */
97
+ export const VOCODER_PARAMS: Readonly<Record<string, ParamSpec>> =
98
+ Object.freeze({
99
+ tap: choice(
100
+ VOCODER_TAPS,
101
+ "chain",
102
+ "modulator after its mono chain (pre-pan) or dry before it",
103
+ ),
104
+ mode: choice(VOCODER_MODES, "channel", "band bank or LPC talkbox"),
105
+ carrier: choice(
106
+ VOCODER_CARRIERS,
107
+ "supersaw",
108
+ "built-in carrier (instrument vocoder only)",
109
+ ),
110
+ follow: choice(
111
+ VOCODER_FOLLOWS,
112
+ "notes",
113
+ "built-in carrier pitch: notes, song chords or a drone",
114
+ ),
115
+ root: num(24, 96, 45, 1, "drone pitch (MIDI)", { integer: true }),
116
+ spread: num(0, 1, 0.15, 0.05, "supersaw detune spread (semitones)", {
117
+ unit: "st",
118
+ automate: true,
119
+ }),
120
+ bands: num(4, 40, 16, 1, "channel bands (heavy above 24)", {
121
+ integer: true,
122
+ }),
123
+ lo: num(50, 1000, 100, "log", "lowest band center", { unit: "Hz" }),
124
+ hi: num(2000, 12000, 8000, "log", "highest band center", { unit: "Hz" }),
125
+ width: num(0.25, 4, 1, 0.05, "band width (x spacing)", {
126
+ automate: true,
127
+ }),
128
+ attack: num(0.0005, 0.2, 0.005, "log", "follower attack", {
129
+ unit: "s",
130
+ strudel: ["att"],
131
+ }),
132
+ release: num(0.005, 2, 0.04, "log", "follower release (long = smear)", {
133
+ unit: "s",
134
+ automate: true,
135
+ strudel: ["rel"],
136
+ }),
137
+ formant: num(
138
+ -24,
139
+ 24,
140
+ 0,
141
+ 0.5,
142
+ "formant shift (+ is smaller and brighter; talkbox ±12)",
143
+ { unit: "st", automate: true },
144
+ ),
145
+ unvoiced: num(0, 1, 0.5, 0.05, "noise carrier on fricatives", {
146
+ automate: true,
147
+ }),
148
+ sens: num(0, 1, 0.5, 0.05, "unvoiced detection sensitivity"),
149
+ hiss: num(0, 1, 0, 0.05, "high-passed modulator added through", {
150
+ automate: true,
151
+ }),
152
+ gate: num(-90, 0, -60, 1, 'modulator gate in dBFS, or "auto"', {
153
+ unit: "dB",
154
+ }),
155
+ enhance: Object.freeze({
156
+ kind: "boolean" as const,
157
+ default: true,
158
+ optional: true,
159
+ doc: "whiten the carrier so every band speaks",
160
+ }),
161
+ depth: num(0, 1, 1, 0.05, "how much the voice shapes the carrier", {
162
+ automate: true,
163
+ }),
164
+ freeze: Object.freeze({
165
+ kind: "boolean" as const,
166
+ default: false,
167
+ optional: true,
168
+ doc: "hold the last sung vowel through rests",
169
+ }),
170
+ mix: num(0, 1, 1, 0.05, "wet against the carrier", { automate: true }),
171
+ gain: num(-24, 24, 0, 0.5, "output trim", { unit: "dB", automate: true }),
172
+ seed: num(0, 2 ** 31, 1, 1, "noise seed (absent: from the track id)", {
173
+ integer: true,
174
+ }),
175
+ });
176
+
177
+ export type VocoderTap = "chain" | "dry";
178
+ export type VocoderMode = "channel" | "talkbox";
179
+ export type VocoderCarrier = "saw" | "supersaw" | "pulse" | "noise";
180
+ export type VocoderFollow = "notes" | "chords" | "drone";
181
+
182
+ /** Overrides over a preset, all optional. */
183
+ export type VocoderOverrides = Readonly<{
184
+ tap?: VocoderTap;
185
+ mode?: VocoderMode;
186
+ carrier?: VocoderCarrier;
187
+ follow?: VocoderFollow;
188
+ root?: number;
189
+ spread?: number;
190
+ bands?: number;
191
+ lo?: number;
192
+ hi?: number;
193
+ width?: number;
194
+ attack?: number;
195
+ release?: number;
196
+ formant?: number;
197
+ unvoiced?: number;
198
+ sens?: number;
199
+ hiss?: number;
200
+ gate?: number | "auto";
201
+ enhance?: boolean;
202
+ depth?: number;
203
+ freeze?: boolean;
204
+ mix?: number;
205
+ gain?: number;
206
+ seed?: number;
207
+ }>;
208
+
209
+ /** A track's stored vocoder: the modulator, a preset and overrides. */
210
+ export type TrackVocoder = Readonly<
211
+ { src?: string; preset?: VocoderPresetName } & VocoderOverrides
212
+ >;
213
+
214
+ /** Everything the DSP reads, resolved: defaults <- preset <- overrides. */
215
+ export type ResolvedVocoder = Readonly<{
216
+ tap: VocoderTap;
217
+ mode: VocoderMode;
218
+ carrier: VocoderCarrier;
219
+ follow: VocoderFollow;
220
+ root: number;
221
+ spread: number;
222
+ bands: number;
223
+ lo: number;
224
+ hi: number;
225
+ width: number;
226
+ attack: number;
227
+ release: number;
228
+ formant: number;
229
+ unvoiced: number;
230
+ sens: number;
231
+ hiss: number;
232
+ gate: number | "auto";
233
+ enhance: boolean;
234
+ depth: number;
235
+ freeze: boolean;
236
+ mix: number;
237
+ gain: number;
238
+ /** Undefined: derived from the track id by the renderer. */
239
+ seed?: number;
240
+ }>;
241
+
242
+ export const VOCODER_DEFAULTS: ResolvedVocoder = Object.freeze({
243
+ tap: "chain",
244
+ mode: "channel",
245
+ carrier: "supersaw",
246
+ follow: "notes",
247
+ root: 45,
248
+ spread: 0.15,
249
+ bands: 16,
250
+ lo: 100,
251
+ hi: 8000,
252
+ width: 1,
253
+ attack: 0.005,
254
+ release: 0.04,
255
+ formant: 0,
256
+ unvoiced: 0.5,
257
+ sens: 0.5,
258
+ hiss: 0,
259
+ gate: "auto",
260
+ enhance: true,
261
+ depth: 1,
262
+ freeze: false,
263
+ mix: 1,
264
+ gain: 0,
265
+ });
266
+
267
+ export type VocoderPreset = Readonly<{
268
+ /** One line for the menu and /help. */
269
+ doc: string;
270
+ values: VocoderOverrides;
271
+ }>;
272
+
273
+ /** Presets over the defaults (all at 24 bands or fewer). */
274
+ export const VOCODER_PRESETS: Readonly<
275
+ Record<VocoderPresetName, VocoderPreset>
276
+ > = Object.freeze({
277
+ classic: { doc: "70s/80s band vocoder lead", values: {} },
278
+ robot: {
279
+ doc: "monotone machine voice on a drone",
280
+ values: {
281
+ bands: 12,
282
+ carrier: "pulse",
283
+ follow: "drone",
284
+ root: 45,
285
+ release: 0.03,
286
+ unvoiced: 0.3,
287
+ },
288
+ },
289
+ talkbox: {
290
+ doc: "wet mouth-shaped lead, sharp vowels",
291
+ values: { mode: "talkbox", carrier: "saw", unvoiced: 0.3, hiss: 0.1 },
292
+ },
293
+ choir: {
294
+ doc: "stereo vocal-stack chord pad",
295
+ values: {
296
+ bands: 24,
297
+ spread: 0.25,
298
+ follow: "chords",
299
+ release: 0.08,
300
+ width: 1.3,
301
+ },
302
+ },
303
+ glass: {
304
+ doc: "bright crystalline lead",
305
+ values: {
306
+ bands: 24,
307
+ carrier: "saw",
308
+ formant: 3,
309
+ hiss: 0.3,
310
+ attack: 0.001,
311
+ },
312
+ },
313
+ whisper: {
314
+ doc: "breathy unpitched ghost voice",
315
+ values: { bands: 24, carrier: "noise", unvoiced: 0, hiss: 0.2 },
316
+ },
317
+ smear: {
318
+ doc: "blurred vowel wash (try freeze)",
319
+ values: {
320
+ bands: 24,
321
+ follow: "chords",
322
+ release: 0.6,
323
+ attack: 0.02,
324
+ width: 1.5,
325
+ },
326
+ },
327
+ lofi: {
328
+ doc: "narrow murky 8-band",
329
+ values: {
330
+ bands: 8,
331
+ carrier: "pulse",
332
+ formant: -3,
333
+ width: 0.7,
334
+ hi: 4000,
335
+ // The narrow layout loses ~5 dB the fixed makeup does not restore.
336
+ gain: 5,
337
+ },
338
+ },
339
+ });
340
+
341
+ /** Parameters with a `vocoder-<param>` lane (freeze is a 0/1 step lane). */
342
+ export const VOCODER_LANE_PARAMS: readonly Readonly<{
343
+ param: string;
344
+ spec: NumberParam;
345
+ }>[] = Object.freeze([
346
+ ...Object.entries(VOCODER_PARAMS)
347
+ .filter(
348
+ (entry): entry is [string, NumberParam] =>
349
+ entry[1].kind === "number" && entry[1].automate === true,
350
+ )
351
+ .map(([param, spec]) => Object.freeze({ param, spec })),
352
+ Object.freeze({
353
+ param: "freeze",
354
+ spec: num(0, 1, 0, 1, "hold the vowel while 1", { automate: true }),
355
+ }),
356
+ ]);
357
+
358
+ export function isVocoderPreset(name: string): name is VocoderPresetName {
359
+ return (VOCODER_PRESET_NAMES as readonly string[]).includes(name);
360
+ }
361
+
362
+ export function isVocoderParam(name: string): boolean {
363
+ return Object.prototype.hasOwnProperty.call(VOCODER_PARAMS, name);
364
+ }
365
+
366
+ /** Strudel spelling (`att`, `rel`) to a vocoder parameter name. */
367
+ export function vocoderParamName(name: string): string | undefined {
368
+ if (isVocoderParam(name)) return name;
369
+ for (const [key, spec] of Object.entries(VOCODER_PARAMS))
370
+ if (spec.strudel?.includes(name)) return key;
371
+ return undefined;
372
+ }
373
+
374
+ /**
375
+ * Validates a `vocoder` field: `{ src?, preset?, ...overrides }`, null or
376
+ * absent meaning none. Keys come out in canonical order (src, preset, then
377
+ * `VOCODER_PARAMS` order). `{}` is kept: it means the default preset with no
378
+ * modulator (the track plays dry). Whether `src` names another track is the
379
+ * score's check.
380
+ */
381
+ export function normalizeVocoder(input: unknown): TrackVocoder | undefined {
382
+ if (input === undefined || input === null) return undefined;
383
+ if (!isRecord(input))
384
+ throw new FxValidationError("track vocoder must be an object or null");
385
+ const params: Record<string, unknown> = {};
386
+ let presetName: VocoderPresetName | undefined;
387
+ let src: string | undefined;
388
+ let gate: number | "auto" | undefined;
389
+ for (const [key, value] of Object.entries(input)) {
390
+ if (value === undefined || value === null) continue;
391
+ if (key === "src") {
392
+ if (typeof value !== "string" || value.length === 0 || value.length > 64)
393
+ throw new FxValidationError(
394
+ "vocoder src must be a track id (1 to 64 characters)",
395
+ );
396
+ src = value;
397
+ continue;
398
+ }
399
+ if (key === "preset") {
400
+ const lower = typeof value === "string" ? value.toLowerCase() : "";
401
+ if (!isVocoderPreset(lower))
402
+ throw new FxValidationError(
403
+ `vocoder preset must be one of ${VOCODER_PRESET_NAMES.join(", ")}`,
404
+ );
405
+ presetName = lower;
406
+ continue;
407
+ }
408
+ const name = vocoderParamName(key);
409
+ if (!name)
410
+ throw new FxValidationError(
411
+ `vocoder has no parameter "${key.slice(0, 32)}"`,
412
+ );
413
+ if (name === "gate" && value === VOCODER_GATE_AUTO) {
414
+ gate = VOCODER_GATE_AUTO;
415
+ continue;
416
+ }
417
+ params[name] = value;
418
+ }
419
+ const values = normalizeParams(VOCODER_PARAMS, params, "vocoder", false);
420
+ const out: Record<string, unknown> = {};
421
+ if (src !== undefined) out.src = src;
422
+ if (presetName) out.preset = presetName;
423
+ for (const key of Object.keys(VOCODER_PARAMS)) {
424
+ if (key === "gate" && gate !== undefined) out.gate = gate;
425
+ else if (values[key] !== undefined) out[key] = values[key];
426
+ }
427
+ return Object.freeze(out) as TrackVocoder;
428
+ }
429
+
430
+ /** The preset a `vocoder` field plays. */
431
+ export function vocoderPresetOf(
432
+ vocoder: TrackVocoder | undefined,
433
+ ): VocoderPresetName {
434
+ return vocoder?.preset ?? DEFAULT_VOCODER_PRESET;
435
+ }
436
+
437
+ /**
438
+ * The settings a `vocoder` field plays: defaults, then the preset, then
439
+ * overrides, then lane values from `lane`. A talkbox's formant is clamped
440
+ * to ±12 st.
441
+ */
442
+ export function resolveVocoder(
443
+ vocoder: TrackVocoder | undefined,
444
+ lane?: (param: string) => number | undefined,
445
+ ): ResolvedVocoder {
446
+ const out: Record<string, unknown> = {
447
+ ...VOCODER_DEFAULTS,
448
+ ...VOCODER_PRESETS[vocoderPresetOf(vocoder)].values,
449
+ };
450
+ if (vocoder)
451
+ for (const [key, value] of Object.entries(vocoder))
452
+ if (key !== "preset" && key !== "src" && value !== undefined)
453
+ out[key] = value;
454
+ if (lane)
455
+ for (const { param } of VOCODER_LANE_PARAMS) {
456
+ const value = lane(param);
457
+ if (value === undefined) continue;
458
+ out[param] = param === "freeze" ? value >= 0.5 : value;
459
+ }
460
+ if (out.mode === "talkbox")
461
+ out.formant = Math.max(
462
+ -TALKBOX_FORMANT_MAX,
463
+ Math.min(TALKBOX_FORMANT_MAX, out.formant as number),
464
+ );
465
+ return Object.freeze(out) as ResolvedVocoder;
466
+ }
467
+
468
+ /** The value one key plays (for menu rows and `/vocoder <param>`). */
469
+ export function vocoderValue(
470
+ vocoder: TrackVocoder | undefined,
471
+ key: string,
472
+ ): number | string | boolean | undefined {
473
+ return (resolveVocoder(vocoder) as Record<string, unknown>)[key] as
474
+ number | string | boolean | undefined;
475
+ }
476
+
477
+ /** Ring after the modulator stops: the release plus 50 ms, at most 2.05 s. */
478
+ export function vocoderTailSeconds(vocoder: TrackVocoder | undefined): number {
479
+ if (!vocoder) return 0;
480
+ return Math.min(2.05, resolveVocoder(vocoder).release + 0.05);
481
+ }
482
+
483
+ /** Pre-roll a window needs so its envelopes have settled. */
484
+ export function vocoderPrerollSeconds(
485
+ vocoder: TrackVocoder | undefined,
486
+ ): number {
487
+ if (!vocoder) return 0;
488
+ return Math.max(0.5, 5 * resolveVocoder(vocoder).release + 0.1);
489
+ }
490
+
491
+ /**
492
+ * Score-level checks over a track list: `src` never names its own track,
493
+ * at most `maxTracks` vocoder tracks, and no modulator cycle (A vocodes B
494
+ * while B vocodes A). Returns an error message or undefined. `src` naming
495
+ * no track is left to the edit surfaces, because a preview or live score
496
+ * holds only some of the song's tracks.
497
+ */
498
+ export function vocoderScoreError(
499
+ tracks: readonly Readonly<{ id: string; vocoder?: TrackVocoder }>[],
500
+ maxTracks: number,
501
+ ): string | undefined {
502
+ const vocoded = tracks.filter((track) => track.vocoder);
503
+ if (vocoded.length > maxTracks)
504
+ return `a song holds at most ${maxTracks} vocoder tracks`;
505
+ const src = new Map<string, string>();
506
+ for (const track of vocoded) {
507
+ const from = track.vocoder!.src;
508
+ if (from === undefined) continue;
509
+ if (from === track.id)
510
+ return `track ${track.id} cannot vocode itself (vocoder src)`;
511
+ src.set(track.id, from);
512
+ }
513
+ for (const start of src.keys()) {
514
+ const path = [start];
515
+ let at = src.get(start);
516
+ while (at !== undefined) {
517
+ if (at === start) return `vocoder cycle: ${[...path, at].join(" -> ")}`;
518
+ if (path.includes(at)) break;
519
+ path.push(at);
520
+ at = src.get(at);
521
+ }
522
+ }
523
+ return undefined;
524
+ }
@@ -0,0 +1,29 @@
1
+ ---
2
+ id: agent
3
+ title: Agent
4
+ order: 11
5
+ ---
6
+
7
+ The agent is optional: commands work offline. With a provider, plain
8
+ requests go to it, and it types the commands it runs so you learn them.
9
+
10
+ ## Ask
11
+
12
+ - "make the reverb wetter and play a C minor arpeggio"
13
+
14
+ ## Type it yourself
15
+
16
+ - `model key` · `model` picker · `model fast` · `logout`
17
+ - `showme on` · `showme quiet` · `/auth --check`
18
+
19
+ ## Menu
20
+
21
+ - Ctrl-K › Project › agent › model · show-me · model key
22
+
23
+ ## Keys
24
+
25
+ - Enter sends · Alt-Enter runs next · Ctrl-Q NOW/NEXT · Esc stops
26
+
27
+ ## Next
28
+
29
+ - `guide show-me` · `guide providers` · `guide web-search`
@@ -0,0 +1,30 @@
1
+ ---
2
+ id: arrange
3
+ title: Arrange
4
+ order: 9
5
+ ---
6
+
7
+ Notes make tracks, tracks make sections, sections make the form.
8
+
9
+ ## Ask
10
+
11
+ - "make 16 bars of deep house" · "add a chorus that lifts at bar 9"
12
+
13
+ ## Type it yourself
14
+
15
+ - `add E4 at 2 for 0.5` · `track bass` · `tracks`
16
+ - `section chorus 9-16` · `form verse verse chorus verse`
17
+ - `build into chorus` · `drop chorus` · `fill chorus`
18
+ - `style deep-house 16` · `bars 8` · `extend 4 bars`
19
+
20
+ ## Menu
21
+
22
+ - Ctrl-K › Arrange › tracks · sections · form · style
23
+
24
+ ## Keys
25
+
26
+ - `view all` shows every track · Ctrl-O the transcript
27
+
28
+ ## Next
29
+
30
+ - `guide notes` · `guide tracks` · `guide tempo` · `guide styles`
@@ -1,26 +1,32 @@
1
1
  ---
2
2
  id: audition
3
3
  title: Menu and audition loop
4
- parent: sound
5
- order: 5
4
+ parent: keys
5
+ order: 1
6
6
  ---
7
7
 
8
- Ctrl-K (or `/menu`) holds every edit the agent can make. Each row shows
9
- its value and, dimmed, the command it runs, so the menu teaches commands.
8
+ Each menu row shows its value and, dimmed, the command it runs, so the
9
+ menu teaches the commands. Changes are staged on a loop until you keep.
10
10
 
11
- ## Menu keys
11
+ ## Ask
12
12
 
13
- - ↑↓ move · → or Enter open · ←→ adjust a value
14
- - Enter on a number opens its fader drawer (see Faders and the mouse)
15
- - `x` reset · `/` filter · Esc back
16
- - `/menu effects` opens a section (sound effects rhythm chords mix project)
13
+ - "try a darker filter on the lead before keeping it"
17
14
 
18
- ## Audition loop
15
+ ## Type it yourself
19
16
 
20
- - Space loops the focused track; changes are staged, not kept
21
- - `a` A/B: committed sound against staged · `c` solo or in context
17
+ - `menu effects` opens a topic · `try fx reverb mix 0.6` stages one
18
+
19
+ ## Menu
20
+
21
+ - Ctrl-K opens Sound, Voice, Effects, Rhythm, Chords and key, Mix,
22
+ Arrange and Project: the same topics as `help` and `guide`
23
+
24
+ ## Keys
25
+
26
+ - ↑↓ move · → or Enter open · ←→ adjust a value · `x` reset · Esc back
27
+ - Space loops the track · `a` A/B · `c` solo or in context
22
28
  - Enter keeps every staged change as one undo step · Esc reverts
23
29
 
24
- ## Try a command
30
+ ## Next
25
31
 
26
- - `/try fx reverb mix 0.6` · `/try synth lpf 800` stage on the loop
32
+ - `guide faders` · `guide keys`
@@ -1,8 +1,8 @@
1
1
  ---
2
2
  id: automation
3
3
  title: Automation
4
- parent: sound
5
- order: 3
4
+ parent: mix
5
+ order: 1
6
6
  ---
7
7
 
8
8
  A lane moves a value over beats. Every numeric effect parameter has one.
@@ -16,12 +16,17 @@ A lane moves a value over beats. Every numeric effect parameter has one.
16
16
 
17
17
  - `automate volume at 0 0.2` · `automate volume at 4 1`
18
18
  - `automate filter points 0:400 4:6000` several points at once
19
- - `automate pan at 0 -1` · `automate delay-mix at 4 0`
20
- - `automate distort-drive points 0:1 8:6`
21
- - `automate filter remove 4` delete one point
22
- - `automate grain-pos points 0:0.1 8:0.9` sweeps a granular track's head (every movable `grain-<param>`)
19
+ - `automate delay-mix at 4 0` · `automate filter remove 4`
23
20
  - `clear filter automation` · `clear automation`
24
21
 
25
22
  ## Menu
26
23
 
27
- - Ctrl-K › Mix › automation: each lane, add points, ramp, clear lane
24
+ - Ctrl-K › Mix › automation: each lane, add points, ramp, clear
25
+
26
+ ## Keys
27
+
28
+ - In a lane row ←→ moves the point · `x` clears the lane
29
+
30
+ ## Next
31
+
32
+ - `guide mix` · `guide resample`