@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.
Files changed (275) hide show
  1. package/CHANGELOG.md +126 -0
  2. package/DAWG.md +655 -141
  3. package/README.md +27 -25
  4. package/core/autotune.ts +1119 -0
  5. package/core/chords.ts +759 -23
  6. package/core/clips.ts +499 -0
  7. package/core/diff.ts +182 -99
  8. package/core/expression.ts +158 -6
  9. package/core/fx.ts +354 -32
  10. package/core/granular.ts +91 -0
  11. package/core/instruments.ts +180 -0
  12. package/core/keys.ts +573 -4
  13. package/core/loop.ts +5 -0
  14. package/core/lyrics.ts +297 -0
  15. package/core/master.ts +3 -3
  16. package/core/resonators.ts +306 -4
  17. package/core/routing.ts +165 -0
  18. package/core/score.ts +1181 -16
  19. package/core/sdk/eval-child.ts +7 -2
  20. package/core/sdk/eval.ts +35 -6
  21. package/core/sdk/print.ts +354 -6
  22. package/core/sdk/sync-lyrics.ts +49 -0
  23. package/core/sdk/v1.ts +3160 -69
  24. package/core/sections.ts +298 -24
  25. package/core/sing.ts +815 -0
  26. package/core/strings.ts +237 -2
  27. package/core/style-provenance.ts +80 -0
  28. package/core/styles/africa-mena-southasia.ts +2893 -0
  29. package/core/styles/americas.ts +3810 -0
  30. package/core/styles/art.ts +4993 -0
  31. package/core/styles/base.ts +123 -0
  32. package/core/styles/cycles.ts +106 -0
  33. package/core/styles/electronic.ts +2723 -0
  34. package/core/styles/europe-asia-pacific.ts +2838 -0
  35. package/core/styles/excerpt.ts +29 -0
  36. package/core/styles/gamelan.ts +283 -0
  37. package/core/styles/generate.ts +2199 -0
  38. package/core/styles/index.ts +515 -0
  39. package/core/styles/parts.ts +106 -0
  40. package/core/styles/pop.ts +3189 -0
  41. package/core/styles/rock.ts +2993 -0
  42. package/core/styles/roots.ts +4175 -0
  43. package/core/styles/schema.ts +429 -0
  44. package/core/styles/taxonomy.ts +940 -0
  45. package/core/styles/validate.ts +528 -0
  46. package/core/tempo.ts +32 -2
  47. package/core/tuning.ts +19 -3
  48. package/core/vocoder.ts +524 -0
  49. package/core/winds.ts +652 -0
  50. package/guides/agent.md +29 -0
  51. package/guides/arrange.md +30 -0
  52. package/guides/audition.md +20 -14
  53. package/guides/automation.md +12 -6
  54. package/guides/chords.md +16 -14
  55. package/guides/effects.md +17 -15
  56. package/guides/faders.md +20 -16
  57. package/guides/files.md +13 -9
  58. package/guides/getting-started.md +15 -11
  59. package/guides/keys.md +19 -15
  60. package/guides/media.md +17 -12
  61. package/guides/mix.md +12 -6
  62. package/guides/music.md +23 -8
  63. package/guides/notes.md +15 -9
  64. package/guides/performance.md +15 -10
  65. package/guides/play.md +21 -13
  66. package/guides/project.md +25 -8
  67. package/guides/providers.md +20 -14
  68. package/guides/resample.md +32 -0
  69. package/guides/rhythm.md +17 -13
  70. package/guides/sessions.md +17 -7
  71. package/guides/show-me.md +31 -0
  72. package/guides/sound.md +25 -9
  73. package/guides/sounds.md +15 -12
  74. package/guides/styles.md +31 -0
  75. package/guides/tempo.md +15 -10
  76. package/guides/tracks.md +15 -10
  77. package/guides/tuning.md +32 -0
  78. package/guides/voice.md +31 -0
  79. package/guides/web-search.md +18 -8
  80. package/native/prebuilt/darwin-arm64/libdawg_sink.dylib +0 -0
  81. package/native/prebuilt/darwin-x64/libdawg_sink.dylib +0 -0
  82. package/native/prebuilt/linux-arm64/libdawg_sink.so +0 -0
  83. package/native/prebuilt/linux-x64/libdawg_sink.so +0 -0
  84. package/native/prebuilt/manifest.json +21 -0
  85. package/package.json +5 -2
  86. package/src/agent/agent.ts +131 -13
  87. package/src/agent/calibration-tools.ts +53 -0
  88. package/src/agent/chord-tools.ts +153 -1
  89. package/src/agent/clip-tools.ts +453 -0
  90. package/src/agent/command-agent.ts +369 -0
  91. package/src/agent/drum-tools.ts +2 -2
  92. package/src/agent/expression-tools.ts +91 -0
  93. package/src/agent/gateway.ts +246 -60
  94. package/src/agent/models.ts +53 -12
  95. package/src/agent/ops.ts +15 -1
  96. package/src/agent/pack-tools.ts +1 -1
  97. package/src/agent/planner.ts +13 -0
  98. package/src/agent/portable-schema.ts +80 -0
  99. package/src/agent/preview-tool.ts +5 -1
  100. package/src/agent/provider.ts +22 -8
  101. package/src/agent/resample-tool.ts +131 -0
  102. package/src/agent/rhythm-tools.ts +1 -1
  103. package/src/agent/section-tools.ts +1 -1
  104. package/src/agent/show-me.ts +497 -0
  105. package/src/agent/steer.ts +15 -0
  106. package/src/agent/style-tools.ts +217 -0
  107. package/src/agent/tool-error.ts +12 -0
  108. package/src/agent/tools.ts +307 -37
  109. package/src/agent/usage.ts +2 -2
  110. package/src/agent/voice-tools.ts +925 -0
  111. package/src/agent/xcb-agent.ts +16 -6
  112. package/src/argv.ts +38 -0
  113. package/src/audio/analysis.ts +253 -0
  114. package/src/audio/arrange.ts +58 -3
  115. package/src/audio/autotune-engine.ts +101 -0
  116. package/src/audio/autotune.ts +640 -0
  117. package/src/audio/clips.ts +240 -0
  118. package/src/audio/doctor.ts +86 -0
  119. package/src/audio/dsp/bandbank.ts +138 -0
  120. package/src/audio/dsp/envelope.ts +171 -0
  121. package/src/audio/dsp/follow.ts +120 -0
  122. package/src/audio/dsp/formant.ts +427 -0
  123. package/src/audio/dsp/glottal.ts +243 -0
  124. package/src/audio/dsp/interp.ts +7 -2
  125. package/src/audio/dsp/lpc.ts +50 -0
  126. package/src/audio/dsp/periodicity.ts +59 -0
  127. package/src/audio/dsp/pitch.ts +995 -0
  128. package/src/audio/dsp/psola.ts +199 -0
  129. package/src/audio/dsp/shift.ts +256 -0
  130. package/src/audio/effects/chain.ts +21 -4
  131. package/src/audio/effects/common.ts +57 -0
  132. package/src/audio/effects/convolution.ts +113 -5
  133. package/src/audio/effects/filter.ts +48 -69
  134. package/src/audio/effects/formant.ts +263 -0
  135. package/src/audio/effects/gaze.ts +354 -0
  136. package/src/audio/engine.ts +206 -33
  137. package/src/audio/fit.ts +80 -3
  138. package/src/audio/granular.ts +352 -21
  139. package/src/audio/instrument-check.ts +67 -47
  140. package/src/audio/instruments.ts +36 -3
  141. package/src/audio/keys/calibration.ts +56 -0
  142. package/src/audio/keys/electric.ts +427 -0
  143. package/src/audio/keys/engine.ts +156 -20
  144. package/src/audio/keys/organ.ts +1335 -0
  145. package/src/audio/keys/piano.ts +72 -8
  146. package/src/audio/keys/sympathetic.ts +127 -0
  147. package/src/audio/kits.ts +135 -6
  148. package/src/audio/live-worker.ts +3 -1
  149. package/src/audio/live.ts +247 -26
  150. package/src/audio/native.ts +615 -0
  151. package/src/audio/preview.ts +45 -3
  152. package/src/audio/render-worker.ts +2 -0
  153. package/src/audio/renderer.ts +2 -0
  154. package/src/audio/resample.ts +286 -0
  155. package/src/audio/resonators.ts +10 -2
  156. package/src/audio/sampler.ts +252 -24
  157. package/src/audio/samples.ts +46 -3
  158. package/src/audio/sing/analysis.ts +193 -0
  159. package/src/audio/sing/engine.ts +949 -0
  160. package/src/audio/strings/body.ts +29 -16
  161. package/src/audio/strings/bow.ts +699 -0
  162. package/src/audio/strings/engine.ts +274 -22
  163. package/src/audio/strings/measure.test-helpers.ts +2 -0
  164. package/src/audio/synth/oscillators.ts +31 -21
  165. package/src/audio/synth/voice.ts +34 -1
  166. package/src/audio/vocoder/bank.ts +314 -0
  167. package/src/audio/vocoder/carrier.ts +165 -0
  168. package/src/audio/vocoder/control.ts +68 -0
  169. package/src/audio/vocoder/detect.ts +50 -0
  170. package/src/audio/vocoder/index.ts +304 -0
  171. package/src/audio/vocoder/talkbox.ts +143 -0
  172. package/src/audio/wav.ts +608 -85
  173. package/src/audio/winds/engine.ts +306 -0
  174. package/src/audio/winds/filters.ts +153 -0
  175. package/src/audio/winds/pitch.ts +104 -0
  176. package/src/audio/winds/trim.ts +104 -0
  177. package/src/audio/winds/trims.ts +917 -0
  178. package/src/audio/winds/trims1.ts +297 -0
  179. package/src/audio/winds/voice.ts +492 -0
  180. package/src/auth/cli.ts +38 -36
  181. package/src/auth/credentials.ts +30 -1
  182. package/src/auth/login.ts +15 -9
  183. package/src/auth/tui.ts +19 -10
  184. package/src/commands/arrange.ts +44 -29
  185. package/src/commands/autotune.ts +421 -0
  186. package/src/commands/calibration.ts +74 -0
  187. package/src/commands/clips.ts +887 -0
  188. package/src/commands/drums.ts +3 -2
  189. package/src/commands/edit.ts +11 -4
  190. package/src/commands/expression.ts +104 -23
  191. package/src/commands/formant.ts +221 -0
  192. package/src/commands/fx.ts +105 -33
  193. package/src/commands/grammar.ts +558 -0
  194. package/src/commands/granular.ts +7 -2
  195. package/src/commands/help.ts +652 -339
  196. package/src/commands/history.ts +18 -0
  197. package/src/commands/keys.ts +293 -17
  198. package/src/commands/modal.ts +67 -4
  199. package/src/commands/music.ts +1 -1
  200. package/src/commands/nearest.ts +53 -0
  201. package/src/commands/pack.ts +9 -2
  202. package/src/commands/param-range.ts +56 -0
  203. package/src/commands/parses.ts +130 -0
  204. package/src/commands/progression.ts +170 -0
  205. package/src/commands/resample.ts +281 -0
  206. package/src/commands/rhythm.ts +3 -0
  207. package/src/commands/rig.ts +8 -25
  208. package/src/commands/sample.ts +49 -2
  209. package/src/commands/shift.ts +119 -0
  210. package/src/commands/sing.ts +478 -0
  211. package/src/commands/string.ts +28 -2
  212. package/src/commands/strum.ts +485 -0
  213. package/src/commands/style.ts +415 -0
  214. package/src/commands/time.ts +6 -3
  215. package/src/commands/tuning.ts +3 -3
  216. package/src/commands/vocal-pitch.ts +616 -0
  217. package/src/commands/vocal.ts +147 -0
  218. package/src/commands/vocoder.ts +627 -0
  219. package/src/commands/wind.ts +244 -0
  220. package/src/fs/durable.ts +50 -0
  221. package/src/lang/glossary.ts +493 -0
  222. package/src/launch-args.ts +163 -0
  223. package/src/main.ts +1521 -255
  224. package/src/media/cli.ts +20 -3
  225. package/src/media/import.ts +3 -1
  226. package/src/project/check.ts +26 -3
  227. package/src/project/clip-pins.ts +72 -0
  228. package/src/project/init.ts +23 -8
  229. package/src/project/sync.ts +418 -86
  230. package/src/render.ts +36 -1
  231. package/src/session/daemon.ts +3 -0
  232. package/src/session/meta.ts +14 -0
  233. package/src/session/origin.ts +154 -0
  234. package/src/session/port.ts +22 -4
  235. package/src/session/presence.ts +50 -7
  236. package/src/session/protocol.ts +5 -1
  237. package/src/session/rebase.ts +18 -5
  238. package/src/session/receipt.ts +258 -0
  239. package/src/session/store.ts +65 -43
  240. package/src/tui/arrange-menu.ts +65 -31
  241. package/src/tui/audition.ts +1 -1
  242. package/src/tui/euclid.ts +18 -13
  243. package/src/tui/fader.ts +228 -41
  244. package/src/tui/granular-menu.ts +2 -4
  245. package/src/tui/menu-clips.ts +297 -0
  246. package/src/tui/menu-time.ts +20 -13
  247. package/src/tui/menu-voice.ts +405 -0
  248. package/src/tui/menu.ts +1377 -193
  249. package/src/tui/modal-menu.ts +38 -11
  250. package/src/tui/performance-menu.ts +46 -1
  251. package/src/tui/play-chords.ts +108 -5
  252. package/src/tui/play-mode.ts +16 -1
  253. package/src/tui/play-session.ts +81 -16
  254. package/src/tui/sing-menu.ts +278 -0
  255. package/src/tui/style-menu.ts +104 -0
  256. package/src/tui/vocoder-menu.ts +244 -0
  257. package/src/tui/wind-menu.ts +144 -0
  258. package/src/version.ts +8 -0
  259. package/src/web/fetch.ts +115 -29
  260. package/tui/activity.ts +180 -9
  261. package/tui/app.ts +274 -40
  262. package/tui/clip-row.ts +132 -0
  263. package/tui/delight.ts +144 -0
  264. package/tui/drawer.ts +70 -22
  265. package/tui/frame-gate.ts +76 -0
  266. package/tui/grammar.ts +112 -63
  267. package/tui/guide.ts +42 -4
  268. package/tui/highway.ts +269 -25
  269. package/tui/hints.ts +192 -0
  270. package/tui/input.ts +60 -9
  271. package/tui/keys.ts +1 -1
  272. package/tui/play-strip.ts +68 -14
  273. package/tui/prompt.ts +1 -1
  274. package/tui/screen.ts +144 -14
  275. package/tui/theme.ts +27 -0
package/core/fx.ts CHANGED
@@ -12,10 +12,13 @@
12
12
  * under `track.fx.<name>` with every parameter stored once enabled.
13
13
  */
14
14
 
15
- import { MODAL_LANE_PARAMS } from "./resonators.ts";
15
+ import { MODAL_LANE_PARAMS, WIND_LANE_PARAMS } from "./resonators.ts";
16
+ import { VOCODER_LANE_PARAMS } from "./vocoder.ts";
16
17
  import { SYNTH_LANE_PARAMS } from "./synth.ts";
17
18
  import { STRING_LANE_PARAMS } from "./strings.ts";
18
19
  import { KEYS_LANE_PARAMS } from "./keys.ts";
20
+ import { GRANULAR_LANE_PARAMS } from "./granular.ts";
21
+ import { SING_LANE_PARAMS } from "./sing.ts";
19
22
  import {
20
23
  FxValidationError,
21
24
  normalizeParam,
@@ -48,6 +51,8 @@ export const FX_CHAIN = Object.freeze([
48
51
  "filter",
49
52
  "djf",
50
53
  "autofilter",
54
+ // 0.7 formant shift at constant pitch (src/audio/effects/formant.ts).
55
+ "formant",
51
56
  "vowel",
52
57
  "crush",
53
58
  "distort",
@@ -55,9 +60,16 @@ export const FX_CHAIN = Object.freeze([
55
60
  "stomp",
56
61
  "head",
57
62
  "cab",
63
+ // 0.6.1 shoegaze (src/audio/effects/gaze.ts): a held tremolo arm, the
64
+ // dominant note's feedback bloom, a volume swell; double (mono in, stereo
65
+ // out) runs where the chain turns stereo.
66
+ "wobble",
67
+ "bloom",
68
+ "swell",
58
69
  "tremolo",
59
70
  "compressor",
60
71
  "pan",
72
+ "double",
61
73
  "phaser",
62
74
  "chorus",
63
75
  "leslie",
@@ -102,7 +114,7 @@ const sync = (fallback: number, strudel?: readonly string[]): NumberParam => ({
102
114
  default: fallback,
103
115
  step: 0.25,
104
116
  unit: "beats",
105
- doc: "LFO cycle length in beats (tempo-synced); 0 uses rate in Hz",
117
+ doc: "LFO period in beats (tempo-synced); 0 uses rate in Hz",
106
118
  ...(strudel ? { strudel } : {}),
107
119
  });
108
120
  const mix = (fallback: number, strudel?: readonly string[]): NumberParam => ({
@@ -116,6 +128,25 @@ const mix = (fallback: number, strudel?: readonly string[]): NumberParam => ({
116
128
  ...(strudel ? { strudel } : {}),
117
129
  });
118
130
 
131
+ /** Vowels of the `vowel` effect (src/audio/dsp/formant.ts tables). */
132
+ export const VOWEL_VALUES = Object.freeze([
133
+ "a",
134
+ "e",
135
+ "i",
136
+ "o",
137
+ "u",
138
+ "ae",
139
+ "aa",
140
+ "oe",
141
+ "ue",
142
+ "y",
143
+ "uh",
144
+ "un",
145
+ "en",
146
+ "an",
147
+ "on",
148
+ ] as const);
149
+
119
150
  /**
120
151
  * Effects stored under `track.fx`. Every number is bounded; enabling an
121
152
  * effect stores all of its parameters so a document says exactly what plays.
@@ -123,7 +154,7 @@ const mix = (fallback: number, strudel?: readonly string[]): NumberParam => ({
123
154
  export const FX_SPECS = Object.freeze({
124
155
  autofilter: {
125
156
  label: "auto filter",
126
- doc: "filter whose cutoff an LFO sweeps around a centre, optionally opened by the input level",
157
+ doc: "filter whose cutoff an LFO sweeps around a center, optionally opened by the input level",
127
158
  simple: ["type", "cutoff", "depth", "sync", "shape"],
128
159
  strudel:
129
160
  "none built in (Strudel fakes it with lpf(sine.range(..)).seg(n)); names follow lpf/lpq",
@@ -143,7 +174,7 @@ export const FX_SPECS = Object.freeze({
143
174
  step: "log",
144
175
  unit: "Hz",
145
176
  automate: true,
146
- doc: "centre cutoff",
177
+ doc: "center cutoff",
147
178
  strudel: ["lpf", "cutoff"],
148
179
  },
149
180
  resonance: {
@@ -164,7 +195,7 @@ export const FX_SPECS = Object.freeze({
164
195
  step: 0.25,
165
196
  unit: "oct",
166
197
  automate: true,
167
- doc: "sweep width in octaves around the centre",
198
+ doc: "sweep width in octaves around the center",
168
199
  },
169
200
  sync: sync(4),
170
201
  rate: rate(0.5),
@@ -208,11 +239,30 @@ export const FX_SPECS = Object.freeze({
208
239
  default: 0.5,
209
240
  step: 0.02,
210
241
  automate: true,
211
- doc: "0 dark … 0.5 open … 1 thin",
242
+ doc: "0 dark (20 Hz low-pass) … 0.5 open … 1 thin (10 kHz high-pass)",
212
243
  strudel: ["djf"],
213
244
  },
214
245
  },
215
246
  },
247
+ formant: {
248
+ label: "formant",
249
+ doc: "formant shift at constant pitch: moves the spectral envelope (throat or gender knob), any source",
250
+ simple: ["shift", "mix"],
251
+ strudel: "none (not a Strudel concept)",
252
+ params: {
253
+ shift: {
254
+ kind: "number",
255
+ min: -12,
256
+ max: 12,
257
+ default: 0,
258
+ step: 0.5,
259
+ unit: "st",
260
+ automate: true,
261
+ doc: "semitones the formants move (negative deeper, positive smaller); pitch stays",
262
+ },
263
+ mix: mix(1),
264
+ },
265
+ },
216
266
  vowel: {
217
267
  label: "vowel",
218
268
  doc: "formant filter bank: five band-passes per vowel",
@@ -221,28 +271,30 @@ export const FX_SPECS = Object.freeze({
221
271
  params: {
222
272
  vowel: {
223
273
  kind: "enum",
224
- values: [
225
- "a",
226
- "e",
227
- "i",
228
- "o",
229
- "u",
230
- "ae",
231
- "aa",
232
- "oe",
233
- "ue",
234
- "y",
235
- "uh",
236
- "un",
237
- "en",
238
- "an",
239
- "on",
240
- ],
274
+ values: VOWEL_VALUES,
241
275
  default: "a",
242
276
  doc: "vowel formants (a e i o u plus the extended set)",
243
277
  strudel: ["vowel"],
244
278
  },
245
279
  mix: mix(1),
280
+ // 0.7: optional morph target; absent keeps the static bank exactly.
281
+ to: {
282
+ kind: "enum",
283
+ values: VOWEL_VALUES,
284
+ default: "a",
285
+ optional: true,
286
+ doc: "vowel to morph toward (absent: no morph)",
287
+ },
288
+ morph: {
289
+ kind: "number",
290
+ min: 0,
291
+ max: 1,
292
+ default: 0,
293
+ step: 0.05,
294
+ optional: true,
295
+ automate: true,
296
+ doc: "position between vowel (0) and to (1), log-frequency formant morph",
297
+ },
246
298
  },
247
299
  },
248
300
  crush: {
@@ -459,7 +511,7 @@ export const FX_SPECS = Object.freeze({
459
511
  step: 1,
460
512
  unit: "dB",
461
513
  optional: true,
462
- doc: "noise gate threshold before the amp (absent: no gate)",
514
+ doc: "noise gate threshold before the amp (absent: no gate; 0 dB gates all)",
463
515
  },
464
516
  level: {
465
517
  kind: "number",
@@ -490,11 +542,140 @@ export const FX_SPECS = Object.freeze({
490
542
  max: 1,
491
543
  default: 0.3,
492
544
  step: 0.05,
493
- doc: "microphone position: 0 centre (bright) .. 1 edge (dark)",
545
+ doc: "microphone position: 0 center (bright) .. 1 edge (dark)",
494
546
  },
495
547
  mix: mix(1),
496
548
  },
497
549
  },
550
+ wobble: {
551
+ label: "wobble",
552
+ doc: "held tremolo arm: slow seeded pitch wow and flutter of the whole track",
553
+ simple: ["depth", "rate", "drift"],
554
+ strudel: "(no Strudel equivalent; dawg shoegaze)",
555
+ params: {
556
+ depth: {
557
+ kind: "number",
558
+ min: 0,
559
+ max: 100,
560
+ default: 20,
561
+ step: 1,
562
+ unit: "c",
563
+ automate: true,
564
+ doc: "peak pitch deviation in cents",
565
+ },
566
+ rate: {
567
+ kind: "number",
568
+ min: 0.05,
569
+ max: 8,
570
+ default: 0.5,
571
+ step: 0.05,
572
+ unit: "Hz",
573
+ doc: "wobble rate",
574
+ },
575
+ drift: {
576
+ kind: "number",
577
+ min: 0,
578
+ max: 1,
579
+ default: 0.3,
580
+ step: 0.05,
581
+ doc: "0 periodic .. 1 wandering (seeded, so every render matches)",
582
+ },
583
+ },
584
+ },
585
+ bloom: {
586
+ label: "bloom",
587
+ doc: "feedback bloom: the top held note grows a singing harmonic",
588
+ simple: ["amount", "harm", "delay"],
589
+ strudel: "(no Strudel equivalent; dawg shoegaze)",
590
+ params: {
591
+ amount: {
592
+ kind: "number",
593
+ min: 0,
594
+ max: 1,
595
+ default: 0.5,
596
+ step: 0.05,
597
+ doc: "level of the feedback partial",
598
+ },
599
+ harm: {
600
+ kind: "number",
601
+ min: 1,
602
+ max: 4,
603
+ default: 2,
604
+ step: 1,
605
+ doc: "harmonic that feeds back: 1 the note, 2 its octave, 3 the fifth above",
606
+ },
607
+ delay: {
608
+ kind: "number",
609
+ min: 0,
610
+ max: 4,
611
+ default: 0.6,
612
+ step: 0.05,
613
+ unit: "s",
614
+ doc: "how long a note is held before it starts to feed back",
615
+ },
616
+ time: {
617
+ kind: "number",
618
+ min: 0.05,
619
+ max: 4,
620
+ default: 1,
621
+ step: 0.05,
622
+ unit: "s",
623
+ doc: "how fast the feedback grows",
624
+ },
625
+ },
626
+ },
627
+ swell: {
628
+ label: "swell",
629
+ doc: "volume swell: each strum fades in, no pick attack",
630
+ simple: ["time", "mix"],
631
+ strudel: "(no Strudel equivalent; dawg shoegaze)",
632
+ params: {
633
+ time: {
634
+ kind: "number",
635
+ min: 0.01,
636
+ max: 4,
637
+ default: 0.4,
638
+ step: 0.01,
639
+ unit: "s",
640
+ doc: "rise time after each onset",
641
+ },
642
+ mix: mix(1),
643
+ },
644
+ },
645
+ double: {
646
+ label: "double",
647
+ doc: "automatic double tracking: a seeded second take, spread left and right",
648
+ simple: ["time", "drift", "width"],
649
+ strudel: "(no Strudel equivalent; dawg shoegaze)",
650
+ params: {
651
+ time: {
652
+ kind: "number",
653
+ min: 5,
654
+ max: 60,
655
+ default: 22,
656
+ step: 1,
657
+ unit: "ms",
658
+ doc: "how late the second take plays",
659
+ },
660
+ drift: {
661
+ kind: "number",
662
+ min: 0,
663
+ max: 10,
664
+ default: 3,
665
+ step: 0.5,
666
+ unit: "ms",
667
+ doc: "how far the second take's timing wanders",
668
+ },
669
+ width: {
670
+ kind: "number",
671
+ min: 0,
672
+ max: 1,
673
+ default: 0.6,
674
+ step: 0.05,
675
+ doc: "0 centered .. 1 the two takes hard left and right",
676
+ },
677
+ },
678
+ },
498
679
  tremolo: {
499
680
  label: "tremolo",
500
681
  doc: "volume modulation",
@@ -634,7 +815,7 @@ export const FX_SPECS = Object.freeze({
634
815
  default: 1000,
635
816
  step: "log",
636
817
  unit: "Hz",
637
- doc: "sweep centre",
818
+ doc: "sweep center",
638
819
  strudel: ["phasercenter", "phc"],
639
820
  },
640
821
  sweep: {
@@ -818,7 +999,7 @@ export const TRACK_EFFECT_SPECS = Object.freeze({
818
999
  step: "log",
819
1000
  unit: "Hz",
820
1001
  automate: true,
821
- doc: "cutoff (lpf/hpf) or centre (bpf) frequency",
1002
+ doc: "cutoff (lpf/hpf) or center (bpf) frequency",
822
1003
  strudel: [
823
1004
  "lpf",
824
1005
  "cutoff",
@@ -1006,6 +1187,14 @@ export const CORE_EFFECTS: readonly EffectName[] = Object.freeze([
1006
1187
  "reverb",
1007
1188
  ] as const);
1008
1189
 
1190
+ /** The 0.6.1 shoegaze stages (Effects > Shoegaze), in chain order. */
1191
+ export const SHOEGAZE_EFFECTS: readonly EffectName[] = Object.freeze([
1192
+ "wobble",
1193
+ "bloom",
1194
+ "swell",
1195
+ "double",
1196
+ ] as const);
1197
+
1009
1198
  export function isEffectName(value: unknown): value is EffectName {
1010
1199
  return (
1011
1200
  typeof value === "string" &&
@@ -1076,12 +1265,22 @@ export function normalizeFx(input: unknown): TrackFx | undefined {
1076
1265
 
1077
1266
  /** Automation lane name of an `fx` parameter, e.g. `distort-drive`. */
1078
1267
  export type FxLane =
1079
- `${FxName | "reverb" | "synth" | "string" | "keys" | "modal"}-${string}`;
1268
+ `${FxName | "reverb" | "synth" | "string" | "keys" | "modal" | "grain" | "wind" | "sing" | "vocoder"}-${string}`;
1080
1269
 
1081
1270
  /** Every automatable `fx` parameter as `{ lane, effect, param, spec }`. */
1082
1271
  export const FX_LANES: readonly Readonly<{
1083
1272
  lane: FxLane;
1084
- effect: FxName | "reverb" | "synth" | "string" | "keys" | "modal";
1273
+ effect:
1274
+ | FxName
1275
+ | "reverb"
1276
+ | "synth"
1277
+ | "string"
1278
+ | "keys"
1279
+ | "modal"
1280
+ | "grain"
1281
+ | "wind"
1282
+ | "sing"
1283
+ | "vocoder";
1085
1284
  param: string;
1086
1285
  spec: NumberParam;
1087
1286
  }>[] = Object.freeze([
@@ -1137,6 +1336,43 @@ export const FX_LANES: readonly Readonly<{
1137
1336
  spec,
1138
1337
  }),
1139
1338
  ),
1339
+ // Granular parameters (core/granular.ts, 0.6.1), read every 32 frames;
1340
+ // grain-scoped ones are latched at each grain's onset.
1341
+ ...GRANULAR_LANE_PARAMS.map(({ param, spec }) =>
1342
+ Object.freeze({
1343
+ lane: `grain-${param}` as FxLane,
1344
+ effect: "grain" as const,
1345
+ param,
1346
+ spec,
1347
+ }),
1348
+ ),
1349
+ // Wind parameters (core/winds.ts), read every 32-sample control tick.
1350
+ ...WIND_LANE_PARAMS.map(({ param, spec }) =>
1351
+ Object.freeze({
1352
+ lane: `wind-${param}` as FxLane,
1353
+ effect: "wind" as const,
1354
+ param,
1355
+ spec,
1356
+ }),
1357
+ ),
1358
+ // Sing parameters (core/sing.ts, 0.7), read every 32-sample control tick.
1359
+ ...SING_LANE_PARAMS.map(({ param, spec }) =>
1360
+ Object.freeze({
1361
+ lane: `sing-${param}` as FxLane,
1362
+ effect: "sing" as const,
1363
+ param,
1364
+ spec,
1365
+ }),
1366
+ ),
1367
+ // Vocoder (core/vocoder.ts, 0.7), read per 32-sample block on the song grid.
1368
+ ...VOCODER_LANE_PARAMS.map(({ param, spec }) =>
1369
+ Object.freeze({
1370
+ lane: `vocoder-${param}` as FxLane,
1371
+ effect: "vocoder" as const,
1372
+ param,
1373
+ spec,
1374
+ }),
1375
+ ),
1140
1376
  ]);
1141
1377
 
1142
1378
  /**
@@ -1183,6 +1419,12 @@ export const FX_PRESETS: Readonly<
1183
1419
  resonance: 0.6,
1184
1420
  },
1185
1421
  },
1422
+ formant: {
1423
+ deep: { shift: -4 },
1424
+ giant: { shift: -8 },
1425
+ bright: { shift: 3 },
1426
+ tiny: { shift: 7 },
1427
+ },
1186
1428
  vowel: { a: { vowel: "a" }, o: { vowel: "o" }, ee: { vowel: "i" } },
1187
1429
  crush: {
1188
1430
  "8-bit": { bits: 8, coarse: 1 },
@@ -1222,6 +1464,24 @@ export const FX_PRESETS: Readonly<
1222
1464
  "1x15": { type: "1x15" },
1223
1465
  di: { type: "di" },
1224
1466
  },
1467
+ wobble: {
1468
+ glide: { depth: 35, rate: 0.4, drift: 0.4 },
1469
+ tape: { depth: 12, rate: 0.8, drift: 0.6 },
1470
+ seasick: { depth: 60, rate: 0.25, drift: 0.2 },
1471
+ },
1472
+ bloom: {
1473
+ octave: { amount: 0.5, harm: 2, delay: 0.6, time: 1 },
1474
+ fifth: { amount: 0.4, harm: 3, delay: 0.8, time: 1.2 },
1475
+ ebow: { amount: 0.8, harm: 1, delay: 0.1, time: 0.6 },
1476
+ },
1477
+ swell: {
1478
+ slow: { time: 0.8 },
1479
+ violin: { time: 0.3 },
1480
+ },
1481
+ double: {
1482
+ tight: { time: 12, drift: 1.5, width: 0.5 },
1483
+ wide: { time: 28, drift: 3, width: 0.7 },
1484
+ },
1225
1485
  tremolo: {
1226
1486
  gentle: { sync: 1, depth: 0.3, shape: "sine" },
1227
1487
  "eighth-chop": { sync: 0.5, depth: 0.9, shape: "square" },
@@ -1390,14 +1650,70 @@ export const RIG_PRESETS: Readonly<
1390
1650
  head: { type: "chime", gain: 3, treble: 7 },
1391
1651
  cab: { type: "2x12", mic: 0.2 },
1392
1652
  compressor: { threshold: -20, ratio: 4, attack: 0.01, release: 0.15 },
1653
+ // 0.6.1: the double-tracked jangle of a twelve-string record. A stored
1654
+ // 0.6.0 jangle (no double) keeps rendering as it was; `rig jangle` again
1655
+ // adds it. The SDK's rig("jangle") keeps its 0.6.0 stages (see
1656
+ // RIG_ADDED), so the printer writes this double out explicitly.
1657
+ double: { time: 12, drift: 1.5, width: 0.5 },
1393
1658
  },
1394
1659
  alt: {
1395
1660
  stomp: { type: "rat", gain: 6, tone: 0.4 },
1396
1661
  head: { type: "crunch", gain: 4 },
1397
1662
  cab: { type: "4x12" },
1398
1663
  },
1664
+ // 0.6.1 shoegaze rigs (src/audio/effects/gaze.ts): fuzz into a held
1665
+ // tremolo arm, a feedback bloom, doubled takes and a long wash.
1666
+ shoegaze: {
1667
+ stomp: { type: "fuzz", gain: 7, tone: 0.45 },
1668
+ head: { type: "chime", gain: 4 },
1669
+ cab: { type: "2x12" },
1670
+ wobble: { depth: 25, rate: 0.4, drift: 0.4 },
1671
+ bloom: { amount: 0.4, harm: 2, delay: 0.6, time: 1 },
1672
+ double: { time: 18, drift: 2.5, width: 0.4 },
1673
+ postgain: { gain: 0.86 },
1674
+ reverb: { mix: 0.35, size: 0.9, fade: 4, predelay: 0.02, dim: 6000 },
1675
+ },
1676
+ glide: {
1677
+ stomp: { type: "face", gain: 5, tone: 0.5 },
1678
+ head: { type: "clean", gain: 4, treble: 5 },
1679
+ cab: { type: "2x12" },
1680
+ wobble: { depth: 35, rate: 0.4, drift: 0.4 },
1681
+ postgain: { gain: 0.8 },
1682
+ reverb: { mix: 0.35, size: 0.7, fade: 2.5, predelay: 0.01, dim: 6000 },
1683
+ },
1684
+ dreampop: {
1685
+ head: { type: "clean", gain: 2, treble: 6 },
1686
+ cab: { type: "1x12" },
1687
+ chorus: { rate: 0.6, depth: 0.3, mix: 0.35 },
1688
+ double: { time: 14, drift: 2, width: 0.4 },
1689
+ postgain: { gain: 1.45 },
1690
+ reverb: { mix: 0.4, size: 0.85, fade: 5, predelay: 0.03, dim: 7000 },
1691
+ },
1692
+ swell: {
1693
+ head: { type: "clean", gain: 3, treble: 5 },
1694
+ cab: { type: "1x12" },
1695
+ swell: { time: 0.5 },
1696
+ postgain: { gain: 1.25 },
1697
+ reverb: { mix: 0.45, size: 0.8, fade: 4, predelay: 0.02, dim: 6500 },
1698
+ },
1699
+ ebow: {
1700
+ stomp: { type: "od", gain: 3, tone: 0.5, level: 3 },
1701
+ head: { type: "lead", gain: 5, mid: 6 },
1702
+ cab: { type: "4x12" },
1703
+ swell: { time: 0.25 },
1704
+ bloom: { amount: 0.8, harm: 1, delay: 0.1, time: 0.6 },
1705
+ },
1399
1706
  });
1400
1707
 
1708
+ /**
1709
+ * Effects a rig preset gained after SDK `rig(name)` was fixed. `rig(name)`
1710
+ * leaves them out, so an older song.ts spreading `...rig("jangle")` keeps
1711
+ * its sound; the printer writes them next to the spread, and a stored
1712
+ * track with or without them still reads as that rig.
1713
+ */
1714
+ export const RIG_ADDED: Readonly<Record<string, readonly FxName[]>> =
1715
+ Object.freeze({ jangle: Object.freeze(["double"] as FxName[]) });
1716
+
1401
1717
  export function isRigPreset(name: string): boolean {
1402
1718
  return Object.prototype.hasOwnProperty.call(RIG_PRESETS, name);
1403
1719
  }
@@ -1476,8 +1792,11 @@ export function rigReverb(name: string): FxValues | undefined {
1476
1792
  export function rigPresetOf(fx: TrackFx | undefined): string | undefined {
1477
1793
  if (!fx || !RIG_STAGES.some((stage) => fx[stage])) return undefined;
1478
1794
  for (const name of Object.keys(RIG_PRESETS)) {
1479
- const want = applyRigPreset(undefined, name) ?? {};
1480
- const keys = new Set([...RIG_STAGES, ...Object.keys(want)]);
1795
+ const want: Partial<Record<FxName, FxValues>> = {
1796
+ ...(applyRigPreset(undefined, name) ?? {}),
1797
+ };
1798
+ for (const added of RIG_ADDED[name] ?? []) delete want[added];
1799
+ const keys = new Set<string>([...RIG_STAGES, ...Object.keys(want)]);
1481
1800
  if (
1482
1801
  [...keys].every(
1483
1802
  (key) =>
@@ -1492,7 +1811,10 @@ export function rigPresetOf(fx: TrackFx | undefined): string | undefined {
1492
1811
 
1493
1812
  /** The effects of `fx` a printed `...rig(name)` stands for. */
1494
1813
  export function rigPresetKeys(name: string): readonly FxName[] {
1495
- return Object.keys(RIG_PRESETS[name] ?? {}).filter(isFxName);
1814
+ const added = RIG_ADDED[name] ?? [];
1815
+ return Object.keys(RIG_PRESETS[name] ?? {}).filter(
1816
+ (key): key is FxName => isFxName(key) && !added.includes(key),
1817
+ );
1496
1818
  }
1497
1819
 
1498
1820
  export function effectPresetNames(effect: EffectName): readonly string[] {
package/core/granular.ts CHANGED
@@ -77,6 +77,14 @@ export type TrackGranular = Readonly<{
77
77
  release?: number;
78
78
  veltone?: number;
79
79
  gain?: number;
80
+ /** Optional (0.6.1): grain rate as a note value on the tempo map. */
81
+ sync?: GrainSync;
82
+ /** Optional (0.6.1): snap grain pitches to the scale or chord. */
83
+ quant?: GrainQuant;
84
+ /** Optional (0.6.1): one voice; legato notes retarget it. */
85
+ mono?: boolean;
86
+ /** Optional (0.6.1): the sustain pedal freezes the head. */
87
+ pedal?: boolean;
80
88
  /**
81
89
  * The instrument `grain off` returns to (set when granular turns on;
82
90
  * absent: a sampler, a wavetable, or the source's synth).
@@ -102,6 +110,30 @@ const n = (
102
110
  ...extra,
103
111
  });
104
112
 
113
+ export const GRAIN_SYNC_VALUES = Object.freeze([
114
+ "off",
115
+ "1/64",
116
+ "1/32",
117
+ "1/16t",
118
+ "1/16",
119
+ "1/16d",
120
+ "1/8t",
121
+ "1/8",
122
+ "1/8d",
123
+ "1/4t",
124
+ "1/4",
125
+ "1/4d",
126
+ "1/2",
127
+ "1/1",
128
+ ] as const);
129
+ export type GrainSync = (typeof GRAIN_SYNC_VALUES)[number];
130
+ export const GRAIN_QUANT_VALUES = Object.freeze([
131
+ "off",
132
+ "scale",
133
+ "chord",
134
+ ] as const);
135
+ export type GrainQuant = (typeof GRAIN_QUANT_VALUES)[number];
136
+
105
137
  /**
106
138
  * The granular parameter table: one row per stored number, enum or flag,
107
139
  * in stored key order. It drives validation, the printer, the menu, the
@@ -223,8 +255,63 @@ export const GRANULAR_PARAMS: Readonly<Record<string, ParamSpec>> =
223
255
  }),
224
256
  veltone: n(0, 1, 0, 0.05, "soft notes are darker"),
225
257
  gain: n(0, 4, 1, 0.05, "voice level"),
258
+ // 0.6.1 grainplay (appended; absent keeps 0.6.0 behaviour).
259
+ sync: Object.freeze({
260
+ kind: "enum",
261
+ values: GRAIN_SYNC_VALUES,
262
+ default: "off",
263
+ doc: "grain rate as a note value on the tempo map (off: grain/overlap)",
264
+ }),
265
+ quant: Object.freeze({
266
+ kind: "enum",
267
+ values: GRAIN_QUANT_VALUES,
268
+ default: "off",
269
+ doc: "snap grain pitches to the song scale or the sounding chord",
270
+ }),
271
+ mono: Object.freeze({
272
+ kind: "boolean",
273
+ default: false,
274
+ doc: "one voice: legato notes retarget its pitch, the cloud keeps going",
275
+ }),
276
+ pedal: Object.freeze({
277
+ kind: "boolean",
278
+ default: false,
279
+ doc: "the sustain pedal freezes the head while it is down",
280
+ }),
226
281
  });
227
282
 
283
+ /** Lanes `grain-<param>` (0.6.1): every number row marked `automate`. */
284
+ export const GRANULAR_LANE_PARAMS: readonly Readonly<{
285
+ param: string;
286
+ spec: Extract<ParamSpec, { kind: "number" }>;
287
+ }>[] = Object.freeze(
288
+ Object.entries(GRANULAR_PARAMS)
289
+ .filter(
290
+ (entry): entry is [string, Extract<ParamSpec, { kind: "number" }>] =>
291
+ entry[1].kind === "number" && entry[1].automate === true,
292
+ )
293
+ .map(([param, spec]) => Object.freeze({ param, spec })),
294
+ );
295
+
296
+ /** Note values `sync` takes, with their length in beats. */
297
+ export const GRAIN_SYNC_BEATS: Readonly<Record<string, number>> = Object.freeze(
298
+ {
299
+ "1/64": 1 / 16,
300
+ "1/32": 1 / 8,
301
+ "1/16t": 1 / 6,
302
+ "1/16": 1 / 4,
303
+ "1/16d": 3 / 8,
304
+ "1/8t": 1 / 3,
305
+ "1/8": 1 / 2,
306
+ "1/8d": 3 / 4,
307
+ "1/4t": 2 / 3,
308
+ "1/4": 1,
309
+ "1/4d": 3 / 2,
310
+ "1/2": 2,
311
+ "1/1": 4,
312
+ },
313
+ );
314
+
228
315
  export type GranularParamName = keyof typeof GRANULAR_PARAMS & string;
229
316
 
230
317
  /** Resolved settings the voice reads (presets and defaults applied). */
@@ -256,6 +343,10 @@ export type GranularSettings = Readonly<{
256
343
  release: number;
257
344
  veltone: number;
258
345
  gain: number;
346
+ sync: GrainSync;
347
+ quant: GrainQuant;
348
+ mono: boolean;
349
+ pedal: boolean;
259
350
  }>;
260
351
 
261
352
  /**