@hraness/dawg 0.5.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 (124) hide show
  1. package/CHANGELOG.md +63 -0
  2. package/DAWG.md +592 -49
  3. package/README.md +4 -4
  4. package/core/chords.ts +492 -4
  5. package/core/diff.ts +9 -0
  6. package/core/expression.ts +143 -6
  7. package/core/fx.ts +689 -2
  8. package/core/granular.ts +619 -0
  9. package/core/instruments.ts +442 -0
  10. package/core/keys.ts +955 -0
  11. package/core/resonators.ts +862 -0
  12. package/core/score.ts +529 -8
  13. package/core/sdk/eval-child.ts +2 -0
  14. package/core/sdk/print.ts +247 -16
  15. package/core/sdk/sync-instruments.ts +58 -0
  16. package/core/sdk/v1.ts +2417 -40
  17. package/core/sections.ts +44 -10
  18. package/core/strings.ts +1080 -0
  19. package/core/winds.ts +652 -0
  20. package/guides/automation.md +1 -0
  21. package/guides/chords.md +3 -1
  22. package/guides/effects.md +5 -2
  23. package/guides/media.md +1 -1
  24. package/guides/performance.md +3 -0
  25. package/guides/resample.md +26 -0
  26. package/guides/sounds.md +7 -2
  27. package/guides/tempo.md +1 -0
  28. package/package.json +1 -1
  29. package/src/agent/agent.ts +13 -0
  30. package/src/agent/chord-tools.ts +153 -1
  31. package/src/agent/expression-tools.ts +91 -0
  32. package/src/agent/granular-tools.ts +138 -0
  33. package/src/agent/models.ts +4 -4
  34. package/src/agent/ops.ts +42 -2
  35. package/src/agent/preview-tool.ts +7 -1
  36. package/src/agent/resample-tool.ts +131 -0
  37. package/src/agent/tools.ts +726 -17
  38. package/src/agent/xcb-agent.ts +13 -0
  39. package/src/audio/arrange.ts +42 -3
  40. package/src/audio/dsp/bank.ts +233 -0
  41. package/src/audio/dsp/envelope.ts +161 -0
  42. package/src/audio/dsp/fft.ts +6 -0
  43. package/src/audio/dsp/filters.ts +57 -0
  44. package/src/audio/dsp/interp.ts +112 -0
  45. package/src/audio/dsp/modal.ts +684 -0
  46. package/src/audio/dsp/onset.ts +197 -0
  47. package/src/audio/dsp/oversample.ts +202 -0
  48. package/src/audio/dsp/rng.ts +37 -0
  49. package/src/audio/dsp/shape.ts +81 -0
  50. package/src/audio/dsp/shift.ts +256 -0
  51. package/src/audio/dsp/stft.ts +73 -0
  52. package/src/audio/dsp/window.ts +77 -0
  53. package/src/audio/effects/chain.ts +23 -3
  54. package/src/audio/effects/common.ts +14 -0
  55. package/src/audio/effects/convolution.ts +106 -1
  56. package/src/audio/effects/gaze.ts +354 -0
  57. package/src/audio/effects/rig/cab.ts +99 -0
  58. package/src/audio/effects/rig/filters.ts +152 -0
  59. package/src/audio/effects/rig/gate.ts +39 -0
  60. package/src/audio/effects/rig/head.ts +487 -0
  61. package/src/audio/effects/rig/index.ts +170 -0
  62. package/src/audio/effects/rig/section.ts +78 -0
  63. package/src/audio/effects/rig/stomp.ts +238 -0
  64. package/src/audio/engine.ts +56 -6
  65. package/src/audio/fit.ts +447 -0
  66. package/src/audio/granular.ts +995 -0
  67. package/src/audio/instrument-check.ts +137 -0
  68. package/src/audio/instruments.ts +140 -0
  69. package/src/audio/keys/dsp.ts +323 -0
  70. package/src/audio/keys/electric.ts +420 -0
  71. package/src/audio/keys/engine.ts +485 -0
  72. package/src/audio/keys/organ.ts +1335 -0
  73. package/src/audio/keys/piano.ts +476 -0
  74. package/src/audio/keys/sympathetic.ts +127 -0
  75. package/src/audio/live-worker.ts +56 -0
  76. package/src/audio/live.ts +361 -15
  77. package/src/audio/loudness.ts +65 -20
  78. package/src/audio/preview.ts +17 -1
  79. package/src/audio/resample.ts +285 -0
  80. package/src/audio/resonators.ts +295 -0
  81. package/src/audio/sampler.ts +328 -35
  82. package/src/audio/samples.ts +56 -8
  83. package/src/audio/strings/body.ts +263 -0
  84. package/src/audio/strings/bow.ts +656 -0
  85. package/src/audio/strings/engine.ts +615 -0
  86. package/src/audio/strings/loop.ts +119 -0
  87. package/src/audio/strings/measure.test-helpers.ts +200 -0
  88. package/src/audio/strings/pluck.ts +354 -0
  89. package/src/audio/warp.ts +61 -0
  90. package/src/audio/wav.ts +191 -19
  91. package/src/audio/winds/engine.ts +302 -0
  92. package/src/audio/winds/filters.ts +153 -0
  93. package/src/audio/winds/pitch.ts +104 -0
  94. package/src/audio/winds/trim.ts +80 -0
  95. package/src/audio/winds/trims.ts +917 -0
  96. package/src/audio/winds/voice.ts +479 -0
  97. package/src/commands/expression.ts +111 -23
  98. package/src/commands/fit.ts +135 -0
  99. package/src/commands/fx.ts +35 -1
  100. package/src/commands/granular.ts +432 -0
  101. package/src/commands/help.ts +159 -5
  102. package/src/commands/keys.ts +629 -0
  103. package/src/commands/modal.ts +310 -0
  104. package/src/commands/resample.ts +281 -0
  105. package/src/commands/rig.ts +264 -0
  106. package/src/commands/sample.ts +52 -2
  107. package/src/commands/shift.ts +119 -0
  108. package/src/commands/string.ts +201 -0
  109. package/src/commands/strum.ts +473 -0
  110. package/src/commands/time.ts +4 -1
  111. package/src/commands/wind.ts +244 -0
  112. package/src/main.ts +339 -15
  113. package/src/project/check.ts +17 -0
  114. package/src/render.ts +49 -4
  115. package/src/session/presence.ts +16 -3
  116. package/src/tui/audition.ts +2 -2
  117. package/src/tui/granular-menu.ts +278 -0
  118. package/src/tui/menu.ts +934 -9
  119. package/src/tui/modal-menu.ts +145 -0
  120. package/src/tui/performance-menu.ts +42 -0
  121. package/src/tui/play-chords.ts +104 -3
  122. package/src/tui/play-mode.ts +1 -0
  123. package/src/tui/play-session.ts +70 -2
  124. package/src/tui/wind-menu.ts +144 -0
@@ -0,0 +1,244 @@
1
+ /**
2
+ * The `wind` prompt command (0.6.1): blown waveguides on the focused track
3
+ * (core/winds.ts). The menu and the `set_wind` agent tool build the same
4
+ * edits.
5
+ *
6
+ * wind show the track's preset and overrides
7
+ * wind <preset> play a preset: flute recorder whistle
8
+ * ney shakuhachi panpipe suling bansuri
9
+ * clarinet bassclarinet oboe bassoon sax
10
+ * altosax barisax trumpet harmon plunger
11
+ * trombone tuba horn (and aliases such
12
+ * as saxophone frenchhorn panflute)
13
+ * wind <preset> <param> <value>… a preset with overrides
14
+ * wind preset <name> the same, spelled out
15
+ * wind mute <open|straight|cup|harmon|plunger>
16
+ * wind <param> <value> [<param> <value>…] breath 0.8, players 4 …
17
+ * wind <param> off back to the preset's value
18
+ * wind reset clear overrides (keep the preset)
19
+ * wind off back to the legacy wind tone
20
+ * wind list | presets wind the presets with one-line characters
21
+ *
22
+ * Any edit turns the track into a wind-engine track (`instrument "wind"`
23
+ * plus a `wind` field); a stored `wind` with no field keeps the legacy tone.
24
+ * One `updateTrack` revision and one undo step per command.
25
+ */
26
+ import { FxValidationError } from "../../core/params.ts";
27
+ import {
28
+ DEFAULT_WIND_PRESET,
29
+ normalizeWind,
30
+ WIND_INSTRUMENT,
31
+ WIND_PARAMS,
32
+ WIND_PRESETS,
33
+ WIND_PRESET_NAMES,
34
+ WIND_SIMPLE,
35
+ windParamName,
36
+ windPresetFor,
37
+ type TrackWind,
38
+ } from "../../core/resonators.ts";
39
+ import {
40
+ ScoreValidationError,
41
+ updateTrack,
42
+ type TrackScore,
43
+ } from "../../core/score.ts";
44
+ import { nearestWord } from "../audio/instrument-check.ts";
45
+ import { parseParamValue } from "./fx.ts";
46
+
47
+ export type WindCommand =
48
+ | { type: "wind-show" }
49
+ | { type: "wind-list" }
50
+ | { type: "wind-reset" }
51
+ | { type: "wind-off" }
52
+ | { type: "wind-preset"; preset: string }
53
+ | { type: "wind-usage"; message: string }
54
+ | {
55
+ type: "wind-set";
56
+ /** A preset applied before the values (`wind trumpet mute harmon`). */
57
+ preset?: string;
58
+ /** `null` returns a parameter to the preset's value. */
59
+ values: Readonly<Record<string, number | string | boolean | null>>;
60
+ };
61
+
62
+ export const WIND_USAGE =
63
+ "wind <preset> | wind <param> <value> | wind mute <name> | wind reset | wind off | wind presets";
64
+
65
+ function rangeOf(name: string): string {
66
+ const spec = WIND_PARAMS[name]!;
67
+ if (spec.kind === "number") return `${name} is ${spec.min}..${spec.max}`;
68
+ if (spec.kind === "enum") return `${name} is one of ${spec.values.join(" ")}`;
69
+ return `${name} is on or off`;
70
+ }
71
+
72
+ export function parseWindCommand(prompt: string): WindCommand | undefined {
73
+ const words = prompt.trim().toLowerCase().split(/\s+/);
74
+ if (words[0] === "presets" && words[1] === "wind" && words.length === 2)
75
+ return { type: "wind-list" };
76
+ if (words[0] !== "wind") return undefined;
77
+ if (words.length === 1) return { type: "wind-show" };
78
+ if (prompt.length > 512) return undefined;
79
+ const rest = words.slice(1);
80
+ if (rest.length === 1 && rest[0] === "reset") return { type: "wind-reset" };
81
+ if (rest.length === 1 && rest[0] === "off") return { type: "wind-off" };
82
+ if (rest.length === 1 && (rest[0] === "list" || rest[0] === "presets"))
83
+ return { type: "wind-list" };
84
+ if (rest[0] === "preset") {
85
+ const preset = rest.length === 2 ? windPresetFor(rest[1]!) : undefined;
86
+ return preset
87
+ ? { type: "wind-preset", preset }
88
+ : {
89
+ type: "wind-usage",
90
+ message: `wind preset is one of ${WIND_PRESET_NAMES.join(" ")}`,
91
+ };
92
+ }
93
+ if (rest.length === 1) {
94
+ const preset = windPresetFor(rest[0]!);
95
+ if (preset) return { type: "wind-preset", preset };
96
+ const mute = WIND_PARAMS.mute;
97
+ if (mute?.kind === "enum" && mute.values.includes(rest[0]!))
98
+ return { type: "wind-set", values: { mute: rest[0]! } };
99
+ return { type: "wind-usage", message: WIND_USAGE };
100
+ }
101
+ // `wind <preset> <param> <value>…`: the preset, then its overrides.
102
+ const lead = rest.length % 2 === 1 ? windPresetFor(rest[0]!) : undefined;
103
+ if (lead) rest.shift();
104
+ if (rest.length % 2 !== 0) return { type: "wind-usage", message: WIND_USAGE };
105
+ const values: Record<string, number | string | boolean | null> = {};
106
+ for (let index = 0; index < rest.length; index += 2) {
107
+ const name = windParamName(rest[index]!);
108
+ if (!name) {
109
+ const word = rest[index]!.slice(0, 24);
110
+ const near = nearestWord(word, Object.keys(WIND_PARAMS));
111
+ return {
112
+ type: "wind-usage",
113
+ message: `wind has no parameter ${word}${near ? ` · did you mean ${near}?` : ""} · ${WIND_SIMPLE.join(" ")} …`,
114
+ };
115
+ }
116
+ const word = rest[index + 1]!;
117
+ if (word === "off" && name !== "stopped") {
118
+ values[name] = null;
119
+ continue;
120
+ }
121
+ if (word === "unset") {
122
+ values[name] = null;
123
+ continue;
124
+ }
125
+ const value = parseParamValue(WIND_PARAMS[name]!, word);
126
+ if (value === undefined)
127
+ return { type: "wind-usage", message: rangeOf(name) };
128
+ values[name] = value;
129
+ }
130
+ return lead
131
+ ? { type: "wind-set", preset: lead, values }
132
+ : { type: "wind-set", values };
133
+ }
134
+
135
+ /** `preset sax · breath 0.8 · players 4`, overrides in canonical order. */
136
+ export function describeWind(wind: TrackWind | undefined): string {
137
+ if (!wind) return "not a wind-engine track";
138
+ const preset = wind.preset ?? DEFAULT_WIND_PRESET;
139
+ const overrides = Object.entries(wind)
140
+ .filter(([key]) => key !== "preset")
141
+ .map(([key, value]) => `${key} ${value}`);
142
+ return [`preset ${preset}`, ...overrides].join(" · ");
143
+ }
144
+
145
+ /** One line per preset, for `wind list` and the agent brief. */
146
+ export function windListLines(): string[] {
147
+ return WIND_PRESET_NAMES.map(
148
+ (name) => `${name.padEnd(13)} ${WIND_PRESETS[name].doc}`,
149
+ );
150
+ }
151
+
152
+ export type WindResult = Readonly<{
153
+ ok: boolean;
154
+ message: string;
155
+ next?: TrackScore;
156
+ kind?: string;
157
+ payload?: Record<string, unknown>;
158
+ }>;
159
+
160
+ /**
161
+ * The wind field a command leaves on a track (pure; the agent tool shares
162
+ * it). A preset switch keeps the overrides; `reset` keeps the preset.
163
+ */
164
+ export function nextWind(
165
+ current: TrackWind | undefined,
166
+ command: Extract<
167
+ WindCommand,
168
+ { type: "wind-reset" | "wind-preset" | "wind-set" }
169
+ >,
170
+ ): TrackWind {
171
+ if (command.type === "wind-reset")
172
+ return normalizeWind(current?.preset ? { preset: current.preset } : {})!;
173
+ const base: Record<string, unknown> = { ...current };
174
+ if (command.type === "wind-preset") base.preset = command.preset;
175
+ else {
176
+ if (command.preset) base.preset = command.preset;
177
+ for (const [key, value] of Object.entries(command.values)) {
178
+ if (value === null) delete base[key];
179
+ else base[key] = value;
180
+ }
181
+ }
182
+ return normalizeWind(base)!;
183
+ }
184
+
185
+ export function applyWindCommand(
186
+ score: TrackScore,
187
+ trackId: string,
188
+ command: WindCommand,
189
+ ): WindResult {
190
+ if (command.type === "wind-usage")
191
+ return { ok: false, message: `wind · ${command.message}` };
192
+ if (command.type === "wind-list")
193
+ return {
194
+ ok: true,
195
+ message: `wind presets · ${WIND_PRESET_NAMES.join(" ")}`,
196
+ };
197
+ const track = score.tracks.find((candidate) => candidate.id === trackId);
198
+ if (!track) return { ok: false, message: `no track · ${trackId}` };
199
+ const current = track.instrument === WIND_INSTRUMENT ? track.wind : undefined;
200
+ if (command.type === "wind-show")
201
+ return {
202
+ ok: true,
203
+ message: current
204
+ ? `wind · ${describeWind(current)}`
205
+ : `wind · ${trackId} plays ${track.instrument} · wind <preset> to switch (${WIND_PRESET_NAMES.join(" ")})`,
206
+ };
207
+ if (track.sampler || track.instrument === "kit")
208
+ return {
209
+ ok: false,
210
+ message: `wind · ${trackId} is a ${track.sampler ? "sampler" : "drum"} track`,
211
+ };
212
+ if (command.type === "wind-off") {
213
+ if (!current) return { ok: true, message: "wind · already off" };
214
+ // The stored word stays `wind`, which is the legacy tone without a field.
215
+ const next = updateTrack(score, trackId, { wind: null });
216
+ return {
217
+ ok: true,
218
+ message: `wind · off (legacy wind tone) · wind ${current.preset ?? DEFAULT_WIND_PRESET} turns it back on`,
219
+ next,
220
+ kind: "score.wind",
221
+ payload: { trackId, instrument: WIND_INSTRUMENT, wind: null },
222
+ };
223
+ }
224
+ let wind: TrackWind;
225
+ let next: TrackScore;
226
+ try {
227
+ wind = nextWind(current, command);
228
+ next = updateTrack(score, trackId, { instrument: WIND_INSTRUMENT, wind });
229
+ } catch (error) {
230
+ if (
231
+ error instanceof ScoreValidationError ||
232
+ error instanceof FxValidationError
233
+ )
234
+ return { ok: false, message: `wind · ${error.message}` };
235
+ throw error;
236
+ }
237
+ return {
238
+ ok: true,
239
+ message: `wind · ${describeWind(wind)}`,
240
+ next,
241
+ kind: "score.wind",
242
+ payload: { trackId, instrument: WIND_INSTRUMENT, wind },
243
+ };
244
+ }