@hraness/dawg 0.6.0 → 0.6.1

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 (93) hide show
  1. package/CHANGELOG.md +34 -0
  2. package/DAWG.md +338 -51
  3. package/README.md +4 -4
  4. package/core/chords.ts +492 -4
  5. package/core/diff.ts +5 -0
  6. package/core/expression.ts +143 -6
  7. package/core/fx.ts +258 -6
  8. package/core/granular.ts +91 -0
  9. package/core/instruments.ts +161 -0
  10. package/core/keys.ts +573 -4
  11. package/core/resonators.ts +291 -3
  12. package/core/score.ts +349 -3
  13. package/core/sdk/print.ts +78 -3
  14. package/core/sdk/v1.ts +1320 -36
  15. package/core/sections.ts +12 -4
  16. package/core/strings.ts +237 -2
  17. package/core/winds.ts +652 -0
  18. package/guides/automation.md +1 -0
  19. package/guides/chords.md +3 -1
  20. package/guides/effects.md +3 -2
  21. package/guides/media.md +1 -1
  22. package/guides/performance.md +3 -0
  23. package/guides/resample.md +26 -0
  24. package/guides/sounds.md +7 -6
  25. package/package.json +1 -1
  26. package/src/agent/agent.ts +8 -2
  27. package/src/agent/chord-tools.ts +153 -1
  28. package/src/agent/expression-tools.ts +91 -0
  29. package/src/agent/ops.ts +4 -1
  30. package/src/agent/preview-tool.ts +2 -1
  31. package/src/agent/resample-tool.ts +131 -0
  32. package/src/agent/tools.ts +200 -15
  33. package/src/agent/xcb-agent.ts +8 -2
  34. package/src/audio/arrange.ts +21 -0
  35. package/src/audio/dsp/envelope.ts +161 -0
  36. package/src/audio/dsp/shift.ts +256 -0
  37. package/src/audio/effects/chain.ts +18 -3
  38. package/src/audio/effects/common.ts +14 -0
  39. package/src/audio/effects/convolution.ts +106 -1
  40. package/src/audio/effects/gaze.ts +354 -0
  41. package/src/audio/engine.ts +46 -5
  42. package/src/audio/fit.ts +45 -0
  43. package/src/audio/granular.ts +352 -21
  44. package/src/audio/instrument-check.ts +9 -5
  45. package/src/audio/instruments.ts +32 -3
  46. package/src/audio/keys/electric.ts +420 -0
  47. package/src/audio/keys/engine.ts +143 -19
  48. package/src/audio/keys/organ.ts +1335 -0
  49. package/src/audio/keys/piano.ts +50 -6
  50. package/src/audio/keys/sympathetic.ts +127 -0
  51. package/src/audio/live-worker.ts +3 -1
  52. package/src/audio/live.ts +136 -9
  53. package/src/audio/preview.ts +15 -1
  54. package/src/audio/resample.ts +285 -0
  55. package/src/audio/resonators.ts +10 -2
  56. package/src/audio/sampler.ts +168 -21
  57. package/src/audio/samples.ts +26 -1
  58. package/src/audio/strings/body.ts +29 -16
  59. package/src/audio/strings/bow.ts +656 -0
  60. package/src/audio/strings/engine.ts +266 -20
  61. package/src/audio/strings/measure.test-helpers.ts +2 -0
  62. package/src/audio/wav.ts +119 -19
  63. package/src/audio/winds/engine.ts +302 -0
  64. package/src/audio/winds/filters.ts +153 -0
  65. package/src/audio/winds/pitch.ts +104 -0
  66. package/src/audio/winds/trim.ts +80 -0
  67. package/src/audio/winds/trims.ts +917 -0
  68. package/src/audio/winds/voice.ts +479 -0
  69. package/src/commands/expression.ts +103 -22
  70. package/src/commands/fx.ts +4 -1
  71. package/src/commands/granular.ts +7 -2
  72. package/src/commands/help.ts +91 -8
  73. package/src/commands/keys.ts +287 -11
  74. package/src/commands/modal.ts +67 -4
  75. package/src/commands/resample.ts +281 -0
  76. package/src/commands/rig.ts +5 -1
  77. package/src/commands/sample.ts +49 -2
  78. package/src/commands/shift.ts +119 -0
  79. package/src/commands/string.ts +28 -2
  80. package/src/commands/strum.ts +473 -0
  81. package/src/commands/wind.ts +244 -0
  82. package/src/main.ts +129 -7
  83. package/src/project/check.ts +5 -1
  84. package/src/render.ts +16 -0
  85. package/src/session/presence.ts +16 -3
  86. package/src/tui/audition.ts +1 -1
  87. package/src/tui/menu.ts +637 -35
  88. package/src/tui/modal-menu.ts +34 -7
  89. package/src/tui/performance-menu.ts +42 -0
  90. package/src/tui/play-chords.ts +104 -3
  91. package/src/tui/play-mode.ts +1 -0
  92. package/src/tui/play-session.ts +34 -9
  93. package/src/tui/wind-menu.ts +144 -0
@@ -8,6 +8,7 @@
8
8
  * glide <ms>|off <target> portamento into each note
9
9
  * glide <ms>|0|off [legato|mono|poly] the track's glide (`glide mono`)
10
10
  * pedal <beat>-<beat>…|bars [<bar>-<bar>]|down|half|up <beat>|off
11
+ * pedal soft|sost <same forms> una corda and sostenuto (0.6.1)
11
12
  * velcurve linear|soft|hard|fixed [<v>]
12
13
  * humanize <ms> [<vel%> [<len%>]] [seed <n>]|on|off|reseed|seed <n>
13
14
  * humanize <ms> [<vel%> [<len%>]]|exact|off <target> per-note amounts
@@ -32,6 +33,7 @@ import {
32
33
  normalizeBend,
33
34
  normalizeHumanize,
34
35
  normalizePedal,
36
+ SOSTENUTO_STATES,
35
37
  normalizeTrackGlide,
36
38
  normalizeVelocityCurve,
37
39
  normalizeVibrato,
@@ -53,6 +55,7 @@ import {
53
55
  type Note,
54
56
  type Track,
55
57
  } from "../../core/score.ts";
58
+ import { softPedalNote } from "../../core/keys.ts";
56
59
  import { barStartTick, loopTicksOf } from "../../core/tempo.ts";
57
60
 
58
61
  export type NoteTarget =
@@ -73,11 +76,15 @@ export type ExpressionCommand =
73
76
  | { type: "vibrato"; vibrato: NoteVibrato | null; target: NoteTarget }
74
77
  | { type: "note-glide"; glide: number | null; target: NoteTarget }
75
78
  | { type: "track-glide"; time?: number | null; mode?: GlideMode }
76
- | { type: "pedal-spans"; spans: readonly { from: number; to: number }[] }
77
- | { type: "pedal-bars"; from?: number; to?: number }
78
- | { type: "pedal-list" }
79
- | { type: "pedal-event"; state: PedalState; beat: number }
80
- | { type: "pedal-off" }
79
+ | {
80
+ type: "pedal-spans";
81
+ spans: readonly { from: number; to: number }[];
82
+ lane?: PedalLane;
83
+ }
84
+ | { type: "pedal-bars"; from?: number; to?: number; lane?: PedalLane }
85
+ | { type: "pedal-list"; lane?: PedalLane }
86
+ | { type: "pedal-event"; state: PedalState; beat: number; lane?: PedalLane }
87
+ | { type: "pedal-off"; lane?: PedalLane }
81
88
  | { type: "velcurve"; curve: VelocityCurveName; fixed?: number }
82
89
  | {
83
90
  type: "humanize";
@@ -299,7 +306,31 @@ function nearWord(text: string, word: string): boolean {
299
306
  return previous[word.length]! <= 2;
300
307
  }
301
308
 
309
+ /**
310
+ * Which pedal a `pedal` command edits (0.6.1): absent is the sustain
311
+ * pedal, `soft` the una corda (Track.softPedal), `sost` the sostenuto.
312
+ */
313
+ export type PedalLane = "soft" | "sost";
314
+
315
+ const PEDAL_LANES: Readonly<Record<string, PedalLane>> = {
316
+ soft: "soft",
317
+ unacorda: "soft",
318
+ sost: "sost",
319
+ sostenuto: "sost",
320
+ };
321
+
302
322
  function parsePedal(words: string[]): ExpressionCommand | undefined {
323
+ const lane = words[0] ? PEDAL_LANES[words[0]] : undefined;
324
+ if (lane) {
325
+ const command = parsePedalLane(words.slice(1));
326
+ return command && command.type.startsWith("pedal-")
327
+ ? ({ ...command, lane } as ExpressionCommand)
328
+ : undefined;
329
+ }
330
+ return parsePedalLane(words);
331
+ }
332
+
333
+ function parsePedalLane(words: string[]): ExpressionCommand | undefined {
303
334
  const [first, ...rest] = words;
304
335
  if (!first) return { type: "pedal-list" };
305
336
  if (OFF.test(first) && rest.length === 0) return { type: "pedal-off" };
@@ -564,6 +595,16 @@ export function describePerformance(score: TrackScore, track: Track): string {
564
595
  const parts = [
565
596
  `glide ${track.glide ? `${ms(track.glide.time)} ${track.glide.mode}` : "off"}`,
566
597
  `pedal ${track.pedal ? `${track.pedal.length} event${track.pedal.length === 1 ? "" : "s"}` : "off"}`,
598
+ ...(track.softPedal
599
+ ? [
600
+ `soft ${track.softPedal.length} event${track.softPedal.length === 1 ? "" : "s"}`,
601
+ ]
602
+ : []),
603
+ ...(track.sostenuto
604
+ ? [
605
+ `sost ${track.sostenuto.length} event${track.sostenuto.length === 1 ? "" : "s"}`,
606
+ ]
607
+ : []),
567
608
  `velcurve ${track.velocityCurve ? `${track.velocityCurve.curve}${track.velocityCurve.fixed !== undefined ? ` ${track.velocityCurve.fixed}` : ""}` : "linear"}`,
568
609
  `humanize ${describeHumanize(track.humanize)}`,
569
610
  ];
@@ -680,10 +721,24 @@ function mergePedal(
680
721
  current: readonly PedalEvent[] | undefined,
681
722
  added: readonly PedalEvent[],
682
723
  maxTick: number,
724
+ label?: string,
725
+ states?: readonly PedalState[],
683
726
  ): readonly PedalEvent[] | undefined {
684
727
  const byTick = new Map((current ?? []).map((event) => [event.tick, event]));
685
728
  for (const event of added) byTick.set(event.tick, event);
686
- return normalizePedal([...byTick.values()], maxTick);
729
+ return normalizePedal([...byTick.values()], maxTick, label, states);
730
+ }
731
+
732
+ /** The Track field, message label and allowed states of a pedal lane. */
733
+ function pedalLaneOf(lane: PedalLane | undefined): {
734
+ field: "pedal" | "softPedal" | "sostenuto";
735
+ label: string;
736
+ states?: readonly PedalState[];
737
+ } {
738
+ if (lane === "soft") return { field: "softPedal", label: "soft pedal" };
739
+ if (lane === "sost")
740
+ return { field: "sostenuto", label: "sostenuto", states: SOSTENUTO_STATES };
741
+ return { field: "pedal", label: "pedal" };
687
742
  }
688
743
 
689
744
  /** Apply `command` to the focused track `trackId`. */
@@ -771,11 +826,12 @@ export function applyExpressionCommand(
771
826
  );
772
827
  }
773
828
  case "pedal-list": {
774
- const events = track.pedal ?? [];
829
+ const { field, label } = pedalLaneOf(command.lane);
830
+ const events = track[field] ?? [];
775
831
  if (events.length === 0)
776
832
  return {
777
833
  ok: true,
778
- message: `pedal · none on ${trackId} · pedal bars to add`,
834
+ message: `${label} · none on ${trackId} · ${command.lane ? `pedal ${command.lane} 0-4` : "pedal bars"} to add`,
779
835
  };
780
836
  const shown = events
781
837
  .slice(0, 12)
@@ -784,24 +840,45 @@ export function applyExpressionCommand(
784
840
  `${Math.round((event.tick / score.ticksPerBeat) * 1000) / 1000} ${event.state}`,
785
841
  );
786
842
  if (events.length > 12) shown.push(`+${events.length - 12} more`);
787
- return { ok: true, message: `pedal · ${shown.join(" · ")} · ${trackId}` };
843
+ return {
844
+ ok: true,
845
+ message: `${label} · ${shown.join(" · ")} · ${trackId}`,
846
+ };
847
+ }
848
+ case "pedal-off": {
849
+ const { field, label } = pedalLaneOf(command.lane);
850
+ return track[field]
851
+ ? trackResult(
852
+ score,
853
+ track,
854
+ { [field]: null },
855
+ `${label} · off · ${trackId}`,
856
+ )
857
+ : { ok: true, message: `${label} · already off · ${trackId}` };
788
858
  }
789
- case "pedal-off":
790
- return track.pedal
791
- ? trackResult(score, track, { pedal: null }, `pedal · off · ${trackId}`)
792
- : { ok: true, message: `pedal · already off · ${trackId}` };
793
859
  case "pedal-spans":
794
860
  case "pedal-bars":
795
861
  case "pedal-event": {
862
+ const { field, label, states } = pedalLaneOf(command.lane);
796
863
  let added: PedalEvent[];
797
864
  if (command.type === "pedal-bars") {
798
865
  const from = command.from ?? 1;
799
866
  if (from > score.bars)
800
867
  return {
801
868
  ok: false,
802
- message: `pedal · the loop has ${score.bars} bar${score.bars === 1 ? "" : "s"}`,
869
+ message: `${label} · the loop has ${score.bars} bar${score.bars === 1 ? "" : "s"}`,
803
870
  };
804
- added = barPedal(score, from, command.to ?? score.bars);
871
+ const to = Math.min(command.to ?? score.bars, score.bars);
872
+ // Soft and sostenuto hold through the bars (no re-pedalling).
873
+ added = command.lane
874
+ ? [
875
+ { tick: barStartTick(score, from - 1), state: "down" },
876
+ {
877
+ tick: Math.min(barStartTick(score, to), maxTick),
878
+ state: "up",
879
+ },
880
+ ]
881
+ : barPedal(score, from, to);
805
882
  } else if (command.type === "pedal-event") {
806
883
  added = [
807
884
  { tick: Math.round(command.beat * tpb), state: command.state },
@@ -815,7 +892,7 @@ export function applyExpressionCommand(
815
892
  if (added.some((event) => event.tick > maxTick))
816
893
  return {
817
894
  ok: false,
818
- message: `pedal · beats run 0..${maxTick / tpb} in this loop`,
895
+ message: `${label} · beats run 0..${maxTick / tpb} in this loop`,
819
896
  };
820
897
  let pedal: readonly PedalEvent[] | undefined;
821
898
  try {
@@ -825,11 +902,11 @@ export function applyExpressionCommand(
825
902
  command.type === "pedal-bars" &&
826
903
  command.from === undefined &&
827
904
  command.to === undefined
828
- ? normalizePedal(added, maxTick)
829
- : mergePedal(track.pedal, added, maxTick);
905
+ ? normalizePedal(added, maxTick, field, states)
906
+ : mergePedal(track[field], added, maxTick, field, states);
830
907
  } catch (error) {
831
908
  if (error instanceof ExpressionValidationError)
832
- return { ok: false, message: `pedal · ${error.message}` };
909
+ return { ok: false, message: `${label} · ${error.message}` };
833
910
  throw error;
834
911
  }
835
912
  const downs = (pedal ?? []).filter((event) => event.state === "down");
@@ -840,11 +917,15 @@ export function applyExpressionCommand(
840
917
  : []),
841
918
  ...(halves.length > 0 ? [`${halves.length} half`] : []),
842
919
  ].join(", ");
920
+ // Una corda is stored anyway (an instrument change may follow), but
921
+ // say when nothing will sound different.
922
+ const silent =
923
+ field === "softPedal" && pedal ? softPedalNote(track) : undefined;
843
924
  return trackResult(
844
925
  score,
845
926
  track,
846
- { pedal: pedal ?? null },
847
- `pedal · ${pedal?.length ?? 0} event${pedal?.length === 1 ? "" : "s"} (${held}) · ${trackId}`,
927
+ { [field]: pedal ?? null },
928
+ `${label} · ${pedal?.length ?? 0} event${pedal?.length === 1 ? "" : "s"} (${held}) · ${trackId}${silent ? ` · no effect: ${silent}` : ""}`,
848
929
  );
849
930
  }
850
931
  case "velcurve": {
@@ -933,7 +1014,7 @@ export const EXPRESSION_USAGE = Object.freeze({
933
1014
  glide:
934
1015
  "glide <ms>|<s>s|off [legato|mono|poly] (track) · glide <ms>|off <target> (notes) · glide 60 mono · SDK glide: 0.06 (s)",
935
1016
  pedal:
936
- "pedal <beat>-<beat>…|bars [<bar>-<bar>]|down|half|up <beat>|off · pedal 0-3.5 4-7.5 · pedal bars",
1017
+ "pedal [soft|sost] <beat>-<beat>…|bars [<bar>-<bar>]|down|half|up <beat>|off · pedal 0-3.5 4-7.5 · pedal bars · pedal soft 0-8 (una corda) · pedal sost 0-4 (holds keys down at 0)",
937
1018
  velcurve:
938
1019
  "velcurve linear|soft|hard|fixed [<v 0..1>] · velcurve soft · velcurve fixed 0.8",
939
1020
  humanize:
@@ -4,11 +4,12 @@
4
4
  * fx list the focused track's effects
5
5
  * fx <effect> on|off|reset enable with defaults, remove, reset
6
6
  * fx <effect> preset <name> load a named preset (FX_PRESETS)
7
+ * fx <effect> turn it on (same as `on`)
7
8
  * fx <effect> <param> <value> set one parameter (enables it)
8
9
  * fx <effect> <number> set its first parameter (`fx orbit 2`)
9
10
  * fx <effect> <param> <value> <param> <value>… several at once
10
11
  * fx reverb ir <impulse>|off convolution reverb (Strudel `ir`):
11
- * room|hall|plate, a pack sound or a
12
+ * room|hall|plate|reverse|gate|spring, a pack sound or a
12
13
  * project file; `fx ir hall` too
13
14
  *
14
15
  * Effects are every `FX_CHAIN` stage except pan (see core/fx.ts), plus
@@ -147,6 +148,8 @@ export function parseFxCommand(prompt: string): FxCommand | undefined {
147
148
  if (effect === "reverb" && irWord(words[2]) && words.length === 4)
148
149
  return irCommand(words[3]!);
149
150
  const rest = words.slice(2).map((word) => word.toLowerCase());
151
+ // `fx wobble`, `fx double`: a bare effect name turns it on.
152
+ if (rest.length === 0) return { type: "fx-on", effect };
150
153
  if (rest.length === 1 && (rest[0] === "on" || rest[0] === "off"))
151
154
  return { type: rest[0] === "on" ? "fx-on" : "fx-off", effect };
152
155
  if (rest.length === 1 && rest[0] === "reset")
@@ -160,7 +160,9 @@ export function parseGranularCommand(
160
160
  const name = rest[index]!;
161
161
  const spec = GRANULAR_PARAMS[name];
162
162
  if (!spec) return undefined;
163
- const word = rest[index + 1]!;
163
+ // Dotted note values as the rest of dawg writes them: `1/8.` is `1/8d`.
164
+ const word =
165
+ name === "sync" ? rest[index + 1]!.replace(/\.$/, "d") : rest[index + 1]!;
164
166
  if ((word === "off" || word === "unset") && spec.kind !== "boolean") {
165
167
  values[name] = null;
166
168
  continue;
@@ -170,7 +172,10 @@ export function parseGranularCommand(
170
172
  ? noteNumber(word)
171
173
  : parseParamValue(spec, word);
172
174
  if (value === undefined) return undefined;
173
- values[name] = value;
175
+ // 0.6.1 switches: off removes the field, so on-then-off leaves the
176
+ // score (and its sha256) as it was.
177
+ values[name] =
178
+ value === false && (name === "mono" || name === "pedal") ? null : value;
174
179
  }
175
180
  return { type: "grain-set", values };
176
181
  }
@@ -4,6 +4,7 @@
4
4
  * app commands take a slash (bare aliases keep working but are listed once,
5
5
  * in their canonical form).
6
6
  */
7
+ import { GUITAR_TUNING_NAMES } from "../../core/chords.ts";
7
8
  import { EXPRESSION_USAGE } from "./expression.ts";
8
9
 
9
10
  export type HelpGroup = "music" | "session" | "window" | "keys";
@@ -72,10 +73,30 @@ export const HELP_SECTIONS: readonly HelpSection[] = [
72
73
  summary:
73
74
  "plucked strings · string sitar · string buzz 0.8 · string ring 6",
74
75
  },
76
+ {
77
+ command: "bowed [<preset>] | <param> <value> | presets",
78
+ summary:
79
+ "bowed strings · bowed violin · bowed cellos · bowed pressure 0.7 · bowed sord 1",
80
+ },
75
81
  {
76
82
  command: "rig <preset> | reset",
77
83
  summary:
78
- "guitar rig · rig crunch · rig metal · rig funk · rig lists them",
84
+ "guitar rig · rig crunch · rig metal · rig shoegaze · rig lists them",
85
+ },
86
+ {
87
+ command:
88
+ "guitar tune <name|notes> | capo | hand | ring | position | reset",
89
+ summary: "guitar fretting · guitar tune dadgad · guitar capo 2",
90
+ },
91
+ {
92
+ command: "strum [chords] [pattern] [strokes D-DU-UDU] [speed 22ms]",
93
+ summary:
94
+ "strummed guitar chords · strum G D Em C folk · strum I V vi IV · strum alone strums the track's chords",
95
+ },
96
+ {
97
+ command: "fx wobble|bloom|swell|double [param value]",
98
+ summary:
99
+ "shoegaze · fx wobble depth 30 · fx double · fx reverb ir builtin:reverse",
79
100
  },
80
101
  {
81
102
  command: "track jangle|punk|funk|ragged|gtr-lead|gtr-metal|bachata",
@@ -94,12 +115,33 @@ export const HELP_SECTIONS: readonly HelpSection[] = [
94
115
  command: "keys <param> <value> | preset <name> | reset",
95
116
  summary: "piano params · keys hardness 0.3 decay 1.5 · keys lists them",
96
117
  },
118
+ {
119
+ command: "epiano|wurli|clav [preset <name>] | <param> <value>",
120
+ summary:
121
+ "electric keys · suitcase dyno funkclav · epiano vibe 0.6 · clav pickup bridge",
122
+ },
123
+ {
124
+ command:
125
+ "tonewheel [<drawbars>] | combo [<registers>] [<voice>] | pipe [<stops>] [<row> <value> …]",
126
+ summary:
127
+ "organs · tonewheel 888800008 perc 3rd · gospel · combo 08880 flute · pipe plenum · keys perc 3rd",
128
+ },
129
+ {
130
+ command: "rotary slow|fast|stop [at <beat>]",
131
+ summary:
132
+ "organ rotary speaker speed · rotary fast at 16 writes the keys-rotary lane",
133
+ },
97
134
  {
98
135
  command:
99
136
  "modal <preset> | <param> <value> | mallet <name> | reset | off",
100
137
  summary:
101
138
  "mallets and bells · modal vibes · modal hardness 0.8 · modal presets",
102
139
  },
140
+ {
141
+ command: "wind <preset> | <param> <value> | mute <name> | reset | off",
142
+ summary:
143
+ "winds and brass · wind flute · wind sax · wind players 4 · wind presets",
144
+ },
103
145
  {
104
146
  command: "art <articulation>|off [target]",
105
147
  summary:
@@ -122,6 +164,11 @@ export const HELP_SECTIONS: readonly HelpSection[] = [
122
164
  command: "pedal <beat>-<beat>... | bars | down|half|up <beat> | off",
123
165
  summary: "sustain pedal · pedal 0-3.5 4-7.5 · pedal bars",
124
166
  },
167
+ {
168
+ command: "pedal soft|sost <beat>-<beat>... | bars | off",
169
+ summary:
170
+ "piano una corda and sostenuto · pedal soft 0-8 · pedal sost 0-4",
171
+ },
125
172
  {
126
173
  command: "velcurve linear|soft|hard|fixed [<v>]",
127
174
  summary: "how velocity maps to level · velcurve fixed 0.6 (0..1)",
@@ -242,6 +289,20 @@ export const HELP_SECTIONS: readonly HelpSection[] = [
242
289
  summary: "repeats pace accent prob swing …",
243
290
  },
244
291
  { command: "grid <voice> <x.X.>", summary: "explicit steps · X accent" },
292
+ {
293
+ command: "shift <semitones> [formant keep|follow|<n>] [<voice>]",
294
+ summary:
295
+ "a sample's pitch without changing its length · shift 0 clears",
296
+ },
297
+ {
298
+ command: "fade [in|out] <seconds> [<voice>]",
299
+ summary: "a sample's fade in and out (Strudel fadeInTime/fadeTime)",
300
+ },
301
+ {
302
+ command:
303
+ "resample <track>|orbit <n>|master [section <name>|bars a-b] [grain]",
304
+ summary: "render to a pinned WAV on a new sampler (or granular) track",
305
+ },
245
306
  { command: "undo", summary: "step back · Ctrl-Z" },
246
307
  { command: "redo", summary: "step forward · Ctrl-Y" },
247
308
  ],
@@ -603,7 +664,7 @@ export const USAGE: Readonly<Record<string, string>> = {
603
664
  click: "/click on|off|<volume> · /click 50%",
604
665
  "count-in": "/count-in 0|1|2",
605
666
  chords:
606
- "/chords auto|manual|off · voicing <n> · spread · bass · perform · rate · octaves · sevenths · preset · style",
667
+ "/chords auto|manual|off · voicing <n> · spread · bass · perform · rate · octaves · sevenths · preset · style · strokes · speed",
607
668
  key: "key <tonic> <mode> | none · key A minor",
608
669
  scale: "scale [<tonic>] <name> | list · scale D hijaz",
609
670
  tuning:
@@ -635,14 +696,14 @@ export const USAGE: Readonly<Record<string, string>> = {
635
696
  bars: "bars takes 1…256 · bars 8",
636
697
  extend: "extend <count> bars · extend 4 bars",
637
698
  instrument:
638
- "instrument <name> · sine piano pluck bass saw square triangle wavetable kit · pianos: grand upright felt honkytonk prepared · synth: sawtooth supersaw pulse white pink z_square…",
699
+ "instrument <name> · sine piano pluck bass saw square triangle wavetable kit · pianos: grand upright felt honkytonk prepared · electric: epiano suitcase dyno wurli clav funkclav · synth: sawtooth supersaw pulse white pink z_square…",
639
700
  volume: "volume takes 0…1 · volume 0.8",
640
701
  vol: "volume takes 0…1 · volume 0.8",
641
702
  pan: "pan takes -1…1 · pan -0.5",
642
703
  wt: "wt <table> | wt <0..1> | wt list · wt basic · wt wt_digital:2",
643
704
  wavetable: "wt <table> | wt <0..1> | wt list · wt basic · wt wt_digital:2",
644
705
  grain:
645
- "grain <preset> | <param> <value> | on [voice V] | src synth:<name>|voice V | reset | off | presets · grain cloud · grain pitch 12",
706
+ "grain <preset> | <param> <value> | on [voice V] | src synth:<name>|voice V | reset | off | presets · sync 1/64…1/1 (t triplet, d or . dotted) · grain cloud · grain pitch 12 · grain sync 1/8.",
646
707
  granular:
647
708
  "grain <preset> | <param> <value> | on [voice V] | src synth:<name>|voice V | reset | off | presets · grain cloud",
648
709
  warpmode: "warpmode none|asym|bendp|bendm|bendmp|sync|quant",
@@ -665,11 +726,17 @@ export const USAGE: Readonly<Record<string, string>> = {
665
726
  export: "/export <file> · /export loop.track.json",
666
727
  import: "/import <file> · /import loop.track.json",
667
728
  sample:
668
- "/sample <path> [as <voice>] · /sample set <voice> <control> <value>… · /sample set brk fit on clip 1",
729
+ "/sample <path> [as <voice>] · /sample set <voice> <control> <value>… · /sample set brk fit on clip 1 · /sample set soft vel 0-63 rr a",
669
730
  samples: "/sample · lists the focused track's voices",
670
731
  fitmode:
671
732
  "/fitmode [repitch|beats|tones|auto|off] [<voice>] · /fitmode beats · /fitmode auto brk",
672
733
  len: "/len <beats> [<voice>] · /len 16 · /len off",
734
+ shift:
735
+ "shift <semitones> [formant keep|follow|<n>] [<voice>] · shift 7 formant keep · shift 0",
736
+ fade: "fade [in|out] <seconds> [<voice>] · fade out 0.5 · fade in 0.05 · fade off",
737
+ resample:
738
+ "resample <track>|orbit <n>|master [section <name>|bars a-b] [post] [grain] [as <id>] · resample lead · resample drums bars 1-2 grain · resample master section chorus",
739
+ bounce: "resample <track>|orbit <n>|master [section <name>|bars a-b] [grain]",
673
740
  view: "/view focus | all",
674
741
  transcript: "/transcript",
675
742
  log: "/transcript",
@@ -685,16 +752,32 @@ export const USAGE: Readonly<Record<string, string>> = {
685
752
  synth: "synth <param> <value> | preset <name> · synth lpf 1200",
686
753
  string:
687
754
  "string <preset> | preset <name> | <param> <value> | presets | reset | off · string koto",
688
- rig: "rig clean|crunch|punk|ragged|lead|metal|fuzz|octave|funk|wah|bachata|spring|bassdrive|reese|jangle|alt | reset",
755
+ bowed:
756
+ "bowed [violin|viola|cello|contrabass|fiddle|erhu|kamancheh|violins|violas|cellos|contrabasses|pizz|trem] | <param> <value> | presets · bowed violin · bowed pressure 0.7",
757
+ rig: "rig clean|crunch|punk|ragged|lead|metal|fuzz|octave|funk|wah|bachata|spring|bassdrive|reese|jangle|alt|shoegaze|glide|dreampop|swell|ebow | reset",
758
+ guitar: `guitar tune ${GUITAR_TUNING_NAMES.join("|")} | E A D G B E · capo 0..12 · hand 3..6 · ring 0..1 · position · reset`,
759
+ strum:
760
+ "strum G D Em C [folk|pop|punk|…] [strokes D-DU-UDU] [speed 22ms] [each 4] [at 0] · strum alone strums the track's chords",
689
761
  stomp:
690
762
  "stomp fuzz|face|od|rat|octave | gain <0-10> tone <0-1> level <dB> | off",
691
763
  head: "head clean|chime|crunch|lead|high|solid|bass | gain bass mid treble presence master <0-10> | gate <dB> | off",
692
764
  cab: "cab 1x12|2x12|4x12|1x10|open|8x10|1x15|di | mic <0-1> | off",
693
- keys: "keys <param> <value> | preset <name> | reset | presets · keys hardness 0.3 · keys stretch 0",
765
+ keys: "keys <param> <value> | preset <name> | reset | presets · keys hardness 0.3 · keys stretch 0 · keys sym 0.5 (sympathetic bloom under the sustain pedal)",
694
766
  piano:
695
767
  "piano [grand|ballad|upright|felt|lofi|honkytonk|prepared] · piano ballad",
768
+ epiano:
769
+ "epiano [preset epiano|suitcase|dyno] | bark bell tone vibe vibehz decay release <value> · epiano vibe 0.6 · rhodes",
770
+ wurli:
771
+ "wurli [preset wurli] | bark bell tone trem decay release <value> · wurli trem 0.5",
772
+ clav: "clav [preset clav|funkclav] | pickup neck|bridge|both|out | mute tone decay release <value> · clav pickup bridge",
773
+ tonewheel:
774
+ "tonewheel [<9 drawbar digits>] | hammond | b3 | gospel | jazzorgan · tonewheel 888800008",
775
+ combo: "combo [<5 register digits>] | farfisa | vox · combo 08880",
776
+ pipe: "pipe [plenum|flutes|cornet|reeds|strings|full | <stop> …] | church · pipe principal8,octave4",
777
+ rotary: "rotary slow|fast|stop [at <beat>] · rotary fast · rotary fast at 16",
696
778
  modal:
697
- "modal <preset> | <body> | <param> <value> | mallet <name> | reset | off | presets · modal vibes · modal saron · modal ring 3",
779
+ "modal <preset> | <body> | <param> <value> | mallet <name> | pair <track> | gamelan | reset | off | presets · modal vibes · modal gangsa · modal ring 3",
780
+ wind: "wind <preset> | <param> <value> | mute <name> | reset | off | presets · wind flute · wind trumpet mute harmon · wind players 4",
698
781
  pack: "/pack list | info <name> | use <pack>/<sound> | add <url>",
699
782
  kit: "/kit [name] · /kit syn909",
700
783
  euclid: "/euclid [voice] · euclid hat 7 16",