@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.
- package/CHANGELOG.md +63 -0
- package/DAWG.md +592 -49
- package/README.md +4 -4
- package/core/chords.ts +492 -4
- package/core/diff.ts +9 -0
- package/core/expression.ts +143 -6
- package/core/fx.ts +689 -2
- package/core/granular.ts +619 -0
- package/core/instruments.ts +442 -0
- package/core/keys.ts +955 -0
- package/core/resonators.ts +862 -0
- package/core/score.ts +529 -8
- package/core/sdk/eval-child.ts +2 -0
- package/core/sdk/print.ts +247 -16
- package/core/sdk/sync-instruments.ts +58 -0
- package/core/sdk/v1.ts +2417 -40
- package/core/sections.ts +44 -10
- package/core/strings.ts +1080 -0
- package/core/winds.ts +652 -0
- package/guides/automation.md +1 -0
- package/guides/chords.md +3 -1
- package/guides/effects.md +5 -2
- package/guides/media.md +1 -1
- package/guides/performance.md +3 -0
- package/guides/resample.md +26 -0
- package/guides/sounds.md +7 -2
- package/guides/tempo.md +1 -0
- package/package.json +1 -1
- package/src/agent/agent.ts +13 -0
- package/src/agent/chord-tools.ts +153 -1
- package/src/agent/expression-tools.ts +91 -0
- package/src/agent/granular-tools.ts +138 -0
- package/src/agent/models.ts +4 -4
- package/src/agent/ops.ts +42 -2
- package/src/agent/preview-tool.ts +7 -1
- package/src/agent/resample-tool.ts +131 -0
- package/src/agent/tools.ts +726 -17
- package/src/agent/xcb-agent.ts +13 -0
- package/src/audio/arrange.ts +42 -3
- package/src/audio/dsp/bank.ts +233 -0
- package/src/audio/dsp/envelope.ts +161 -0
- package/src/audio/dsp/fft.ts +6 -0
- package/src/audio/dsp/filters.ts +57 -0
- package/src/audio/dsp/interp.ts +112 -0
- package/src/audio/dsp/modal.ts +684 -0
- package/src/audio/dsp/onset.ts +197 -0
- package/src/audio/dsp/oversample.ts +202 -0
- package/src/audio/dsp/rng.ts +37 -0
- package/src/audio/dsp/shape.ts +81 -0
- package/src/audio/dsp/shift.ts +256 -0
- package/src/audio/dsp/stft.ts +73 -0
- package/src/audio/dsp/window.ts +77 -0
- package/src/audio/effects/chain.ts +23 -3
- package/src/audio/effects/common.ts +14 -0
- package/src/audio/effects/convolution.ts +106 -1
- package/src/audio/effects/gaze.ts +354 -0
- package/src/audio/effects/rig/cab.ts +99 -0
- package/src/audio/effects/rig/filters.ts +152 -0
- package/src/audio/effects/rig/gate.ts +39 -0
- package/src/audio/effects/rig/head.ts +487 -0
- package/src/audio/effects/rig/index.ts +170 -0
- package/src/audio/effects/rig/section.ts +78 -0
- package/src/audio/effects/rig/stomp.ts +238 -0
- package/src/audio/engine.ts +56 -6
- package/src/audio/fit.ts +447 -0
- package/src/audio/granular.ts +995 -0
- package/src/audio/instrument-check.ts +137 -0
- package/src/audio/instruments.ts +140 -0
- package/src/audio/keys/dsp.ts +323 -0
- package/src/audio/keys/electric.ts +420 -0
- package/src/audio/keys/engine.ts +485 -0
- package/src/audio/keys/organ.ts +1335 -0
- package/src/audio/keys/piano.ts +476 -0
- package/src/audio/keys/sympathetic.ts +127 -0
- package/src/audio/live-worker.ts +56 -0
- package/src/audio/live.ts +361 -15
- package/src/audio/loudness.ts +65 -20
- package/src/audio/preview.ts +17 -1
- package/src/audio/resample.ts +285 -0
- package/src/audio/resonators.ts +295 -0
- package/src/audio/sampler.ts +328 -35
- package/src/audio/samples.ts +56 -8
- package/src/audio/strings/body.ts +263 -0
- package/src/audio/strings/bow.ts +656 -0
- package/src/audio/strings/engine.ts +615 -0
- package/src/audio/strings/loop.ts +119 -0
- package/src/audio/strings/measure.test-helpers.ts +200 -0
- package/src/audio/strings/pluck.ts +354 -0
- package/src/audio/warp.ts +61 -0
- package/src/audio/wav.ts +191 -19
- package/src/audio/winds/engine.ts +302 -0
- package/src/audio/winds/filters.ts +153 -0
- package/src/audio/winds/pitch.ts +104 -0
- package/src/audio/winds/trim.ts +80 -0
- package/src/audio/winds/trims.ts +917 -0
- package/src/audio/winds/voice.ts +479 -0
- package/src/commands/expression.ts +111 -23
- package/src/commands/fit.ts +135 -0
- package/src/commands/fx.ts +35 -1
- package/src/commands/granular.ts +432 -0
- package/src/commands/help.ts +159 -5
- package/src/commands/keys.ts +629 -0
- package/src/commands/modal.ts +310 -0
- package/src/commands/resample.ts +281 -0
- package/src/commands/rig.ts +264 -0
- package/src/commands/sample.ts +52 -2
- package/src/commands/shift.ts +119 -0
- package/src/commands/string.ts +201 -0
- package/src/commands/strum.ts +473 -0
- package/src/commands/time.ts +4 -1
- package/src/commands/wind.ts +244 -0
- package/src/main.ts +339 -15
- package/src/project/check.ts +17 -0
- package/src/render.ts +49 -4
- package/src/session/presence.ts +16 -3
- package/src/tui/audition.ts +2 -2
- package/src/tui/granular-menu.ts +278 -0
- package/src/tui/menu.ts +934 -9
- package/src/tui/modal-menu.ts +145 -0
- package/src/tui/performance-menu.ts +42 -0
- package/src/tui/play-chords.ts +104 -3
- package/src/tui/play-mode.ts +1 -0
- package/src/tui/play-session.ts +70 -2
- 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
|
+
}
|