@hraness/dawg 0.4.1 → 0.6.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 (166) hide show
  1. package/CHANGELOG.md +99 -0
  2. package/DAWG.md +551 -33
  3. package/README.md +4 -4
  4. package/core/chords.ts +288 -7
  5. package/core/diff.ts +41 -12
  6. package/core/expression.ts +1241 -0
  7. package/core/fx.ts +437 -2
  8. package/core/granular.ts +528 -0
  9. package/core/instruments.ts +281 -0
  10. package/core/keys.ts +386 -0
  11. package/core/loop.ts +23 -0
  12. package/core/master.ts +455 -0
  13. package/core/midi.ts +452 -0
  14. package/core/resonators.ts +574 -0
  15. package/core/rhythm.ts +7 -2
  16. package/core/score.ts +717 -62
  17. package/core/sdk/eval-child.ts +40 -3
  18. package/core/sdk/print.ts +614 -27
  19. package/core/sdk/sync-instruments.ts +58 -0
  20. package/core/sdk/v1.ts +2824 -45
  21. package/core/sections.ts +2072 -0
  22. package/core/strings.ts +845 -0
  23. package/core/synth.ts +11 -1
  24. package/core/tempo.ts +1318 -0
  25. package/core/tuning.ts +1180 -0
  26. package/guides/audition.md +26 -0
  27. package/guides/automation.md +26 -0
  28. package/guides/chords.md +28 -0
  29. package/guides/effects.md +29 -0
  30. package/guides/faders.md +29 -0
  31. package/guides/files.md +28 -0
  32. package/guides/getting-started.md +26 -0
  33. package/guides/index.ts +75 -0
  34. package/guides/keys.md +27 -0
  35. package/guides/media.md +27 -0
  36. package/guides/mix.md +23 -0
  37. package/guides/music.md +14 -0
  38. package/guides/notes.md +26 -0
  39. package/guides/performance.md +26 -0
  40. package/guides/play.md +24 -0
  41. package/guides/project.md +13 -0
  42. package/guides/providers.md +25 -0
  43. package/guides/rhythm.md +26 -0
  44. package/guides/sessions.md +21 -0
  45. package/guides/sound.md +15 -0
  46. package/guides/sounds.md +29 -0
  47. package/guides/tempo.md +27 -0
  48. package/guides/tracks.md +25 -0
  49. package/guides/web-search.md +21 -0
  50. package/package.json +3 -1
  51. package/src/agent/agent.ts +9 -0
  52. package/src/agent/brief.ts +77 -3
  53. package/src/agent/chord-tools.ts +9 -1
  54. package/src/agent/expression-tools.ts +336 -0
  55. package/src/agent/granular-tools.ts +138 -0
  56. package/src/agent/master-tools.ts +299 -0
  57. package/src/agent/models.ts +4 -4
  58. package/src/agent/ops.ts +68 -6
  59. package/src/agent/planner.ts +45 -2
  60. package/src/agent/preview-tool.ts +27 -4
  61. package/src/agent/section-tools.ts +411 -0
  62. package/src/agent/time-tools.ts +290 -0
  63. package/src/agent/tools.ts +574 -16
  64. package/src/agent/tuning-tools.ts +301 -0
  65. package/src/agent/xcb-agent.ts +9 -0
  66. package/src/audio/arrange.ts +489 -0
  67. package/src/audio/audition.ts +16 -3
  68. package/src/audio/click.ts +113 -1
  69. package/src/audio/clock.ts +71 -5
  70. package/src/audio/dsp/bank.ts +233 -0
  71. package/src/audio/dsp/fft.ts +6 -0
  72. package/src/audio/dsp/filters.ts +57 -0
  73. package/src/audio/dsp/interp.ts +112 -0
  74. package/src/audio/dsp/modal.ts +684 -0
  75. package/src/audio/dsp/onset.ts +197 -0
  76. package/src/audio/dsp/oversample.ts +202 -0
  77. package/src/audio/dsp/rng.ts +37 -0
  78. package/src/audio/dsp/shape.ts +81 -0
  79. package/src/audio/dsp/stft.ts +73 -0
  80. package/src/audio/dsp/window.ts +77 -0
  81. package/src/audio/effects/bus.ts +5 -4
  82. package/src/audio/effects/chain.ts +6 -1
  83. package/src/audio/effects/common.ts +30 -1
  84. package/src/audio/effects/dynamics.ts +2 -1
  85. package/src/audio/effects/filter.ts +5 -3
  86. package/src/audio/effects/modulation.ts +5 -1
  87. package/src/audio/effects/rig/cab.ts +99 -0
  88. package/src/audio/effects/rig/filters.ts +152 -0
  89. package/src/audio/effects/rig/gate.ts +39 -0
  90. package/src/audio/effects/rig/head.ts +487 -0
  91. package/src/audio/effects/rig/index.ts +170 -0
  92. package/src/audio/effects/rig/section.ts +78 -0
  93. package/src/audio/effects/rig/stomp.ts +238 -0
  94. package/src/audio/effects/space.ts +92 -2
  95. package/src/audio/engine.ts +100 -21
  96. package/src/audio/fit.ts +402 -0
  97. package/src/audio/granular.ts +664 -0
  98. package/src/audio/instrument-check.ts +133 -0
  99. package/src/audio/instruments.ts +111 -0
  100. package/src/audio/keys/dsp.ts +323 -0
  101. package/src/audio/keys/engine.ts +361 -0
  102. package/src/audio/keys/piano.ts +432 -0
  103. package/src/audio/live-worker.ts +54 -0
  104. package/src/audio/live.ts +263 -17
  105. package/src/audio/loudness.ts +596 -0
  106. package/src/audio/master.ts +660 -0
  107. package/src/audio/measure-worker.ts +45 -0
  108. package/src/audio/measure.ts +110 -0
  109. package/src/audio/player.ts +11 -6
  110. package/src/audio/preview.ts +76 -14
  111. package/src/audio/render-worker.ts +6 -1
  112. package/src/audio/renderer.ts +7 -1
  113. package/src/audio/resonators.ts +287 -0
  114. package/src/audio/sampler.ts +269 -34
  115. package/src/audio/samples.ts +30 -7
  116. package/src/audio/strings/body.ts +250 -0
  117. package/src/audio/strings/engine.ts +369 -0
  118. package/src/audio/strings/loop.ts +119 -0
  119. package/src/audio/strings/measure.test-helpers.ts +198 -0
  120. package/src/audio/strings/pluck.ts +354 -0
  121. package/src/audio/synth/voice.ts +60 -13
  122. package/src/audio/synth/zzfx.ts +10 -4
  123. package/src/audio/warp.ts +147 -0
  124. package/src/audio/wav.ts +377 -30
  125. package/src/commands/arrange.ts +949 -0
  126. package/src/commands/expression.ts +941 -0
  127. package/src/commands/fit.ts +135 -0
  128. package/src/commands/fx.ts +31 -0
  129. package/src/commands/granular.ts +427 -0
  130. package/src/commands/help.ts +355 -27
  131. package/src/commands/keys.ts +353 -0
  132. package/src/commands/master.ts +361 -0
  133. package/src/commands/modal.ts +247 -0
  134. package/src/commands/music.ts +6 -1
  135. package/src/commands/rig.ts +260 -0
  136. package/src/commands/sample.ts +3 -0
  137. package/src/commands/string.ts +175 -0
  138. package/src/commands/synth.ts +11 -1
  139. package/src/commands/time.ts +967 -0
  140. package/src/commands/tuning.ts +490 -0
  141. package/src/main.ts +919 -41
  142. package/src/project/check.ts +13 -0
  143. package/src/render.ts +139 -8
  144. package/src/session/daemon.ts +18 -8
  145. package/src/session/naming.ts +8 -1
  146. package/src/session/rebase.ts +21 -0
  147. package/src/tui/arrange-menu.ts +390 -0
  148. package/src/tui/audition.ts +38 -5
  149. package/src/tui/fader.ts +409 -0
  150. package/src/tui/granular-menu.ts +278 -0
  151. package/src/tui/menu-time.ts +401 -0
  152. package/src/tui/menu.ts +967 -23
  153. package/src/tui/modal-menu.ts +118 -0
  154. package/src/tui/performance-menu.ts +235 -0
  155. package/src/tui/play-chords.ts +3 -1
  156. package/src/tui/play-mode.ts +150 -6
  157. package/src/tui/play-session.ts +459 -43
  158. package/tui/app.ts +262 -13
  159. package/tui/arrange-strip.ts +174 -0
  160. package/tui/drawer.ts +478 -0
  161. package/tui/grammar.ts +63 -1
  162. package/tui/guide.ts +351 -0
  163. package/tui/highway.ts +87 -4
  164. package/tui/hits.ts +68 -0
  165. package/tui/input.ts +7 -0
  166. package/tui/keys.ts +72 -0
package/core/fx.ts CHANGED
@@ -12,7 +12,10 @@
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
16
  import { SYNTH_LANE_PARAMS } from "./synth.ts";
17
+ import { STRING_LANE_PARAMS } from "./strings.ts";
18
+ import { KEYS_LANE_PARAMS } from "./keys.ts";
16
19
  import {
17
20
  FxValidationError,
18
21
  normalizeParam,
@@ -48,6 +51,10 @@ export const FX_CHAIN = Object.freeze([
48
51
  "vowel",
49
52
  "crush",
50
53
  "distort",
54
+ // 0.6 guitar rig (stomp box, amp head with noise gate, speaker cabinet).
55
+ "stomp",
56
+ "head",
57
+ "cab",
51
58
  "tremolo",
52
59
  "compressor",
53
60
  "pan",
@@ -323,6 +330,171 @@ export const FX_SPECS = Object.freeze({
323
330
  },
324
331
  },
325
332
  },
333
+ stomp: {
334
+ label: "stomp box",
335
+ doc: "guitar pedal before the amp: fuzz (Big Muff), face (Fuzz Face), od (Tube Screamer), rat, octave (Octavia); oversampled",
336
+ simple: ["type", "gain", "tone", "level"],
337
+ strudel: "(no Strudel equivalent; dawg guitar rig)",
338
+ params: {
339
+ type: {
340
+ kind: "enum",
341
+ values: ["fuzz", "face", "od", "rat", "octave"],
342
+ default: "od",
343
+ doc: "circuit: fuzz (Big Muff), face (Fuzz Face), od (Tube Screamer), rat (RAT), octave (Octavia)",
344
+ },
345
+ gain: {
346
+ kind: "number",
347
+ min: 0,
348
+ max: 10,
349
+ default: 5,
350
+ step: 0.25,
351
+ automate: true,
352
+ doc: "the pedal's gain / sustain / distortion knob, 0..10",
353
+ },
354
+ tone: {
355
+ kind: "number",
356
+ min: 0,
357
+ max: 1,
358
+ default: 0.5,
359
+ step: 0.05,
360
+ automate: true,
361
+ doc: "dark 0 .. bright 1",
362
+ },
363
+ level: {
364
+ kind: "number",
365
+ min: -24,
366
+ max: 12,
367
+ default: 0,
368
+ step: 0.5,
369
+ unit: "dB",
370
+ doc: "trim over a level-matched pedal (0 = bypass loudness)",
371
+ },
372
+ octave: {
373
+ kind: "number",
374
+ min: 0,
375
+ max: 1,
376
+ default: 0.7,
377
+ step: 0.05,
378
+ doc: "octave: blend of the octave-up (rectified) path",
379
+ },
380
+ mix: mix(1),
381
+ },
382
+ },
383
+ head: {
384
+ label: "amp head",
385
+ doc: "guitar amp: preamp stages, Yeh-Smith tone stack, power amp with sag, level-matched across types; optional noise gate",
386
+ simple: ["type", "gain", "bass", "mid", "treble"],
387
+ strudel: "(no Strudel equivalent; Strudel amp is linear gain)",
388
+ params: {
389
+ type: {
390
+ kind: "enum",
391
+ values: ["clean", "chime", "crunch", "lead", "high", "solid", "bass"],
392
+ default: "crunch",
393
+ doc: "clean (blackface), chime (AC30), crunch (plexi), lead (JCM800), high (modern high gain), solid (JC-120), bass (SVT)",
394
+ },
395
+ gain: {
396
+ kind: "number",
397
+ min: 0,
398
+ max: 10,
399
+ default: 5,
400
+ step: 0.25,
401
+ automate: true,
402
+ doc: "preamp gain 0..10 (level-matched: more gain, not more volume)",
403
+ },
404
+ bass: {
405
+ kind: "number",
406
+ min: 0,
407
+ max: 10,
408
+ default: 5,
409
+ step: 0.5,
410
+ doc: "tone stack bass 0..10",
411
+ },
412
+ mid: {
413
+ kind: "number",
414
+ min: 0,
415
+ max: 10,
416
+ default: 5,
417
+ step: 0.5,
418
+ doc: "tone stack mid 0..10",
419
+ },
420
+ treble: {
421
+ kind: "number",
422
+ min: 0,
423
+ max: 10,
424
+ default: 5,
425
+ step: 0.5,
426
+ doc: "tone stack treble 0..10",
427
+ },
428
+ presence: {
429
+ kind: "number",
430
+ min: 0,
431
+ max: 10,
432
+ default: 5,
433
+ step: 0.5,
434
+ doc: "power amp presence 0..10 (5 flat)",
435
+ },
436
+ master: {
437
+ kind: "number",
438
+ min: 0,
439
+ max: 10,
440
+ default: 5,
441
+ step: 0.25,
442
+ automate: true,
443
+ doc: "power amp drive 0..10",
444
+ },
445
+ sag: {
446
+ kind: "number",
447
+ min: 0,
448
+ max: 1,
449
+ default: 0.3,
450
+ step: 0.05,
451
+ optional: true,
452
+ doc: "power supply sag 0..1 (absent: the head type's own)",
453
+ },
454
+ gate: {
455
+ kind: "number",
456
+ min: -96,
457
+ max: 0,
458
+ default: -60,
459
+ step: 1,
460
+ unit: "dB",
461
+ optional: true,
462
+ doc: "noise gate threshold before the amp (absent: no gate)",
463
+ },
464
+ level: {
465
+ kind: "number",
466
+ min: -24,
467
+ max: 12,
468
+ default: 0,
469
+ step: 0.5,
470
+ unit: "dB",
471
+ doc: "output level after level matching",
472
+ },
473
+ },
474
+ },
475
+ cab: {
476
+ label: "speaker cabinet",
477
+ doc: "speaker cabinet and microphone (biquad model, no impulse response)",
478
+ simple: ["type", "mic", "mix"],
479
+ strudel: "(no Strudel equivalent; dawg guitar rig)",
480
+ params: {
481
+ type: {
482
+ kind: "enum",
483
+ values: ["1x12", "2x12", "4x12", "1x10", "open", "8x10", "1x15", "di"],
484
+ default: "2x12",
485
+ doc: "1x12, 2x12, 4x12, 1x10, open back, 8x10 and 1x15 bass, di (no speaker)",
486
+ },
487
+ mic: {
488
+ kind: "number",
489
+ min: 0,
490
+ max: 1,
491
+ default: 0.3,
492
+ step: 0.05,
493
+ doc: "microphone position: 0 centre (bright) .. 1 edge (dark)",
494
+ },
495
+ mix: mix(1),
496
+ },
497
+ },
326
498
  tremolo: {
327
499
  label: "tremolo",
328
500
  doc: "volume modulation",
@@ -903,12 +1075,13 @@ export function normalizeFx(input: unknown): TrackFx | undefined {
903
1075
  }
904
1076
 
905
1077
  /** Automation lane name of an `fx` parameter, e.g. `distort-drive`. */
906
- export type FxLane = `${FxName | "reverb" | "synth"}-${string}`;
1078
+ export type FxLane =
1079
+ `${FxName | "reverb" | "synth" | "string" | "keys" | "modal"}-${string}`;
907
1080
 
908
1081
  /** Every automatable `fx` parameter as `{ lane, effect, param, spec }`. */
909
1082
  export const FX_LANES: readonly Readonly<{
910
1083
  lane: FxLane;
911
- effect: FxName | "reverb" | "synth";
1084
+ effect: FxName | "reverb" | "synth" | "string" | "keys" | "modal";
912
1085
  param: string;
913
1086
  spec: NumberParam;
914
1087
  }>[] = Object.freeze([
@@ -936,6 +1109,34 @@ export const FX_LANES: readonly Readonly<{
936
1109
  spec,
937
1110
  }),
938
1111
  ),
1112
+ // String engine parameters (core/strings.ts), read at each note's onset.
1113
+ ...STRING_LANE_PARAMS.map(({ param, spec }) =>
1114
+ Object.freeze({
1115
+ lane: `string-${param}` as FxLane,
1116
+ effect: "string" as const,
1117
+ param,
1118
+ spec,
1119
+ }),
1120
+ ),
1121
+ // Modelled piano parameters (core/keys.ts), read at each note's onset.
1122
+ ...KEYS_LANE_PARAMS.map(({ param, spec }) =>
1123
+ Object.freeze({
1124
+ lane: `keys-${param}` as FxLane,
1125
+ effect: "keys" as const,
1126
+ param,
1127
+ spec,
1128
+ }),
1129
+ ),
1130
+ // Modal parameters (core/resonators.ts), read at each note's onset
1131
+ // (`modal-motordepth` every 32-sample control tick).
1132
+ ...MODAL_LANE_PARAMS.map(({ param, spec }) =>
1133
+ Object.freeze({
1134
+ lane: `modal-${param}` as FxLane,
1135
+ effect: "modal" as const,
1136
+ param,
1137
+ spec,
1138
+ }),
1139
+ ),
939
1140
  ]);
940
1141
 
941
1142
  /**
@@ -973,6 +1174,14 @@ export const FX_PRESETS: Readonly<
973
1174
  cutoff: 400,
974
1175
  resonance: 0.4,
975
1176
  },
1177
+ wah: {
1178
+ type: "bpf",
1179
+ sync: 0.5,
1180
+ depth: 2,
1181
+ shape: "sine",
1182
+ cutoff: 700,
1183
+ resonance: 0.6,
1184
+ },
976
1185
  },
977
1186
  vowel: { a: { vowel: "a" }, o: { vowel: "o" }, ee: { vowel: "i" } },
978
1187
  crush: {
@@ -987,6 +1196,32 @@ export const FX_PRESETS: Readonly<
987
1196
  fold: { drive: 5, type: "fold", tone: 7000, mix: 0.7 },
988
1197
  shape: { drive: 5, type: "shape", tone: 9000 },
989
1198
  },
1199
+ stomp: {
1200
+ muff: { type: "fuzz", gain: 7, tone: 0.5 },
1201
+ face: { type: "face", gain: 6, tone: 0.5 },
1202
+ screamer: { type: "od", gain: 5, tone: 0.5, level: 3 },
1203
+ rat: { type: "rat", gain: 6, tone: 0.4 },
1204
+ octavia: { type: "octave", gain: 6, tone: 0.6, octave: 0.8 },
1205
+ boost: { type: "od", gain: 0, tone: 0.6, level: 6 },
1206
+ },
1207
+ head: {
1208
+ blackface: { type: "clean", gain: 3, treble: 6 },
1209
+ ac: { type: "chime", gain: 4, treble: 6 },
1210
+ plexi: { type: "crunch", gain: 5 },
1211
+ lead: { type: "lead", gain: 6, mid: 6 },
1212
+ modern: { type: "high", gain: 7, bass: 6, mid: 3, treble: 7, gate: -55 },
1213
+ jc: { type: "solid", gain: 2, treble: 6 },
1214
+ svt: { type: "bass", gain: 4, bass: 6 },
1215
+ },
1216
+ cab: {
1217
+ "1x12": { type: "1x12" },
1218
+ "2x12": { type: "2x12" },
1219
+ "4x12": { type: "4x12" },
1220
+ open: { type: "open" },
1221
+ "8x10": { type: "8x10" },
1222
+ "1x15": { type: "1x15" },
1223
+ di: { type: "di" },
1224
+ },
990
1225
  tremolo: {
991
1226
  gentle: { sync: 1, depth: 0.3, shape: "sine" },
992
1227
  "eighth-chop": { sync: 0.5, depth: 0.9, shape: "square" },
@@ -1060,6 +1295,206 @@ export const FX_PRESETS: Readonly<
1060
1295
  },
1061
1296
  } satisfies Partial<Record<EffectName, Record<string, FxValues>>>);
1062
1297
 
1298
+ /** The guitar rig stages, in chain order: what `rig reset` clears. */
1299
+ export const RIG_STAGES = Object.freeze(["stomp", "head", "cab"] as const);
1300
+ export type RigStage = (typeof RIG_STAGES)[number];
1301
+
1302
+ /**
1303
+ * Whole guitar rigs (`rig <name>`): each sets the rig stages it names over
1304
+ * their defaults and clears the ones it leaves out. A few also set one
1305
+ * other effect (a wah, a spring-like reverb, a chorus) the sound needs.
1306
+ */
1307
+ export const RIG_PRESETS: Readonly<
1308
+ Record<string, Readonly<Partial<Record<FxName | "reverb", FxValues>>>>
1309
+ > = Object.freeze({
1310
+ clean: {
1311
+ head: { type: "clean", gain: 3, treble: 6 },
1312
+ cab: { type: "1x12" },
1313
+ },
1314
+ crunch: { head: { type: "crunch", gain: 5 }, cab: { type: "4x12" } },
1315
+ punk: {
1316
+ head: { type: "crunch", gain: 7, mid: 6, master: 6 },
1317
+ cab: { type: "4x12", mic: 0.2 },
1318
+ },
1319
+ ragged: {
1320
+ stomp: { type: "face", gain: 6, tone: 0.6 },
1321
+ head: { type: "chime", gain: 4 },
1322
+ cab: { type: "2x12" },
1323
+ },
1324
+ lead: {
1325
+ stomp: { type: "od", gain: 3, tone: 0.5, level: 3 },
1326
+ head: { type: "lead", gain: 6, mid: 6 },
1327
+ cab: { type: "4x12" },
1328
+ },
1329
+ metal: {
1330
+ stomp: { type: "od", gain: 0, tone: 0.6, level: 6 },
1331
+ head: { type: "high", gain: 7, bass: 6, mid: 3, treble: 7, gate: -55 },
1332
+ cab: { type: "4x12", mic: 0.2 },
1333
+ },
1334
+ fuzz: {
1335
+ stomp: { type: "fuzz", gain: 7, tone: 0.5 },
1336
+ head: { type: "clean", gain: 4 },
1337
+ cab: { type: "2x12" },
1338
+ },
1339
+ octave: {
1340
+ stomp: { type: "octave", gain: 6, tone: 0.6, octave: 0.8 },
1341
+ head: { type: "clean", gain: 3 },
1342
+ cab: { type: "1x12" },
1343
+ },
1344
+ funk: {
1345
+ head: { type: "clean", gain: 2, treble: 7, presence: 6 },
1346
+ cab: { type: "2x12" },
1347
+ autofilter: {
1348
+ type: "bpf",
1349
+ sync: 0,
1350
+ rate: 0.01,
1351
+ depth: 0,
1352
+ follow: 3,
1353
+ cutoff: 500,
1354
+ resonance: 0.6,
1355
+ },
1356
+ },
1357
+ wah: {
1358
+ head: { type: "crunch", gain: 4 },
1359
+ cab: { type: "2x12" },
1360
+ autofilter: {
1361
+ type: "bpf",
1362
+ sync: 0.5,
1363
+ depth: 2,
1364
+ shape: "sine",
1365
+ cutoff: 700,
1366
+ resonance: 0.6,
1367
+ },
1368
+ },
1369
+ bachata: {
1370
+ head: { type: "clean", gain: 2, mid: 6, treble: 7 },
1371
+ cab: { type: "1x12", mic: 0.2 },
1372
+ chorus: { rate: 0.8, depth: 0.25, mix: 0.3 },
1373
+ },
1374
+ spring: {
1375
+ head: { type: "clean", gain: 3, treble: 6 },
1376
+ cab: { type: "open" },
1377
+ reverb: { mix: 0.3, size: 0.35, fade: 1.5, predelay: 0, dim: 3500 },
1378
+ },
1379
+ bassdrive: {
1380
+ stomp: { type: "od", gain: 4, tone: 0.5, mix: 0.6 },
1381
+ head: { type: "bass", gain: 4 },
1382
+ cab: { type: "8x10" },
1383
+ },
1384
+ reese: {
1385
+ stomp: { type: "rat", gain: 3, tone: 0.3, mix: 0.5 },
1386
+ head: { type: "bass", gain: 6, master: 6 },
1387
+ cab: { type: "1x15" },
1388
+ },
1389
+ jangle: {
1390
+ head: { type: "chime", gain: 3, treble: 7 },
1391
+ cab: { type: "2x12", mic: 0.2 },
1392
+ compressor: { threshold: -20, ratio: 4, attack: 0.01, release: 0.15 },
1393
+ },
1394
+ alt: {
1395
+ stomp: { type: "rat", gain: 6, tone: 0.4 },
1396
+ head: { type: "crunch", gain: 4 },
1397
+ cab: { type: "4x12" },
1398
+ },
1399
+ });
1400
+
1401
+ export function isRigPreset(name: string): boolean {
1402
+ return Object.prototype.hasOwnProperty.call(RIG_PRESETS, name);
1403
+ }
1404
+
1405
+ /**
1406
+ * `fx` with rig preset `name` applied: rig stages it leaves out are
1407
+ * removed, every stage it names is filled over its defaults, and other
1408
+ * effects keep their values. `"reset"` removes the rig. A preset's track
1409
+ * effect (the `spring` reverb) is not part of `fx`: see `rigReverb`.
1410
+ */
1411
+ export function applyRigPreset(
1412
+ fx: TrackFx | undefined,
1413
+ name: string,
1414
+ ): TrackFx | undefined {
1415
+ const out: Record<string, unknown> = { ...(fx ?? {}) };
1416
+ for (const stage of RIG_STAGES) delete out[stage];
1417
+ // A previous rig's companion (funk's envelope filter, jangle's
1418
+ // compressor…) goes with it, unless the user has changed it since.
1419
+ for (const [effect, values] of Object.entries(out))
1420
+ if (isRigCompanion(effect, values)) delete out[effect];
1421
+ if (name !== "reset") {
1422
+ if (!isRigPreset(name))
1423
+ throw new FxValidationError(
1424
+ `unknown rig "${name}" (rigs: ${Object.keys(RIG_PRESETS).join(", ")})`,
1425
+ );
1426
+ for (const [effect, values] of Object.entries(RIG_PRESETS[name]!))
1427
+ if (isFxName(effect)) out[effect] = values;
1428
+ }
1429
+ return normalizeFx(out);
1430
+ }
1431
+
1432
+ /** Normalized companion effects (not stages, not reverb) of every rig. */
1433
+ let companions: Map<string, string[]> | undefined;
1434
+
1435
+ /** True when `values` is exactly what some rig preset writes for `effect`. */
1436
+ function isRigCompanion(effect: string, values: unknown): boolean {
1437
+ if ((RIG_STAGES as readonly string[]).includes(effect)) return false;
1438
+ if (!companions) {
1439
+ companions = new Map();
1440
+ for (const preset of Object.values(RIG_PRESETS))
1441
+ for (const [name, raw] of Object.entries(preset)) {
1442
+ if (!isFxName(name) || (RIG_STAGES as readonly string[]).includes(name))
1443
+ continue;
1444
+ const normal = normalizeFx({ [name]: raw })?.[name];
1445
+ const list = companions.get(name) ?? [];
1446
+ list.push(JSON.stringify(normal));
1447
+ companions.set(name, list);
1448
+ }
1449
+ }
1450
+ return companions.get(effect)?.includes(JSON.stringify(values)) ?? false;
1451
+ }
1452
+
1453
+ /** True when `reverb` is exactly the track reverb some rig preset sets. */
1454
+ export function isRigReverb(reverb: unknown): boolean {
1455
+ if (reverb === undefined) return false;
1456
+ const json = JSON.stringify(reverb);
1457
+ return Object.keys(RIG_PRESETS).some(
1458
+ (name) => rigReverb(name) && JSON.stringify(rigReverb(name)) === json,
1459
+ );
1460
+ }
1461
+
1462
+ /** The track reverb a rig preset sets (spring), if any. */
1463
+ export function rigReverb(name: string): FxValues | undefined {
1464
+ if (!isRigPreset(name)) return undefined;
1465
+ const values = (RIG_PRESETS[name] as Record<string, FxValues | undefined>)
1466
+ .reverb;
1467
+ return values === undefined
1468
+ ? undefined
1469
+ : normalizeParams(TRACK_EFFECT_SPECS.reverb.params, values, "reverb");
1470
+ }
1471
+
1472
+ /**
1473
+ * The rig preset whose stages and companion effects `fx` matches exactly,
1474
+ * if any (other effects may be present too).
1475
+ */
1476
+ export function rigPresetOf(fx: TrackFx | undefined): string | undefined {
1477
+ if (!fx || !RIG_STAGES.some((stage) => fx[stage])) return undefined;
1478
+ for (const name of Object.keys(RIG_PRESETS)) {
1479
+ const want = applyRigPreset(undefined, name) ?? {};
1480
+ const keys = new Set([...RIG_STAGES, ...Object.keys(want)]);
1481
+ if (
1482
+ [...keys].every(
1483
+ (key) =>
1484
+ JSON.stringify(fx[key as FxName]) ===
1485
+ JSON.stringify(want[key as FxName]),
1486
+ )
1487
+ )
1488
+ return name;
1489
+ }
1490
+ return undefined;
1491
+ }
1492
+
1493
+ /** The effects of `fx` a printed `...rig(name)` stands for. */
1494
+ export function rigPresetKeys(name: string): readonly FxName[] {
1495
+ return Object.keys(RIG_PRESETS[name] ?? {}).filter(isFxName);
1496
+ }
1497
+
1063
1498
  export function effectPresetNames(effect: EffectName): readonly string[] {
1064
1499
  return Object.keys(FX_PRESETS[effect] ?? {});
1065
1500
  }