@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
package/core/winds.ts
ADDED
|
@@ -0,0 +1,652 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Blown instruments (0.6.1 "Instruments wave 2", lane f061-gamelan-winds):
|
|
3
|
+
* flutes, reeds, saxes and brass played by one waveguide engine
|
|
4
|
+
* (`src/audio/winds/`). Pure and import-light, like `core/resonators.ts`
|
|
5
|
+
* (which re-exports this file): the parameter table, the 21 presets and the
|
|
6
|
+
* validator that the score, the prompt, the menu, the agent and the SDK
|
|
7
|
+
* printer share.
|
|
8
|
+
*
|
|
9
|
+
* A track plays this engine only when its `instrument` is `"wind"` AND it
|
|
10
|
+
* carries a `wind` object (`{ preset, ...overrides }`). A bare `"wind"`
|
|
11
|
+
* instrument without the field keeps the legacy wind tone byte for byte.
|
|
12
|
+
*
|
|
13
|
+
* Models (STK's waveguides, Cook and Scavone, corrected for exact pitch by
|
|
14
|
+
* per-rate trim tables): `jet` flutes, `reed` clarinets, `sax` conical reeds
|
|
15
|
+
* (saxes, and oboe and bassoon until a true double-reed table lands) and
|
|
16
|
+
* `lips` brass.
|
|
17
|
+
*/
|
|
18
|
+
import {
|
|
19
|
+
FxValidationError,
|
|
20
|
+
isRecord,
|
|
21
|
+
normalizeParams,
|
|
22
|
+
type EnumParam,
|
|
23
|
+
type NumberParam,
|
|
24
|
+
type ParamSpec,
|
|
25
|
+
} from "./params.ts";
|
|
26
|
+
|
|
27
|
+
/** Instrument value that selects the wind engine. */
|
|
28
|
+
export const WIND_INSTRUMENT = "wind" as const;
|
|
29
|
+
|
|
30
|
+
export const WIND_MODELS = Object.freeze(["jet", "reed", "sax", "lips"]);
|
|
31
|
+
export type WindModel = "jet" | "reed" | "sax" | "lips";
|
|
32
|
+
|
|
33
|
+
export const WIND_MUTES = Object.freeze([
|
|
34
|
+
"open",
|
|
35
|
+
"straight",
|
|
36
|
+
"cup",
|
|
37
|
+
"harmon",
|
|
38
|
+
"plunger",
|
|
39
|
+
]);
|
|
40
|
+
export type WindMute = "open" | "straight" | "cup" | "harmon" | "plunger";
|
|
41
|
+
|
|
42
|
+
export const WIND_PRESET_NAMES = Object.freeze([
|
|
43
|
+
"flute",
|
|
44
|
+
"recorder",
|
|
45
|
+
"whistle",
|
|
46
|
+
"ney",
|
|
47
|
+
"shakuhachi",
|
|
48
|
+
"panpipe",
|
|
49
|
+
"suling",
|
|
50
|
+
"bansuri",
|
|
51
|
+
"clarinet",
|
|
52
|
+
"bassclarinet",
|
|
53
|
+
"oboe",
|
|
54
|
+
"bassoon",
|
|
55
|
+
"sax",
|
|
56
|
+
"altosax",
|
|
57
|
+
"barisax",
|
|
58
|
+
"trumpet",
|
|
59
|
+
"harmon",
|
|
60
|
+
"plunger",
|
|
61
|
+
"trombone",
|
|
62
|
+
"tuba",
|
|
63
|
+
"horn",
|
|
64
|
+
] as const);
|
|
65
|
+
export type WindPresetName = (typeof WIND_PRESET_NAMES)[number];
|
|
66
|
+
|
|
67
|
+
/** Extra words that pick a preset (`wind preset saxophone`). */
|
|
68
|
+
export const WIND_ALIASES: Readonly<Record<string, WindPresetName>> =
|
|
69
|
+
Object.freeze({
|
|
70
|
+
tinwhistle: "whistle",
|
|
71
|
+
pennywhistle: "whistle",
|
|
72
|
+
nay: "ney",
|
|
73
|
+
panflute: "panpipe",
|
|
74
|
+
panpipes: "panpipe",
|
|
75
|
+
saxophone: "sax",
|
|
76
|
+
tenorsax: "sax",
|
|
77
|
+
tenor: "sax",
|
|
78
|
+
alto: "altosax",
|
|
79
|
+
bari: "barisax",
|
|
80
|
+
baritonesax: "barisax",
|
|
81
|
+
frenchhorn: "horn",
|
|
82
|
+
mutedtrumpet: "harmon",
|
|
83
|
+
wahtrumpet: "plunger",
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
/** The browse group "Winds and brass", in menu order. */
|
|
87
|
+
export const WIND_GROUPS: readonly Readonly<{
|
|
88
|
+
label: string;
|
|
89
|
+
presets: readonly WindPresetName[];
|
|
90
|
+
}>[] = Object.freeze([
|
|
91
|
+
{
|
|
92
|
+
label: "Flutes",
|
|
93
|
+
presets: [
|
|
94
|
+
"flute",
|
|
95
|
+
"recorder",
|
|
96
|
+
"whistle",
|
|
97
|
+
"ney",
|
|
98
|
+
"shakuhachi",
|
|
99
|
+
"panpipe",
|
|
100
|
+
"suling",
|
|
101
|
+
"bansuri",
|
|
102
|
+
],
|
|
103
|
+
},
|
|
104
|
+
{
|
|
105
|
+
label: "Saxophones",
|
|
106
|
+
presets: ["sax", "altosax", "barisax"],
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
label: "Reeds",
|
|
110
|
+
presets: ["clarinet", "bassclarinet", "oboe", "bassoon"],
|
|
111
|
+
},
|
|
112
|
+
{
|
|
113
|
+
label: "Brass",
|
|
114
|
+
presets: ["trumpet", "harmon", "plunger", "trombone", "tuba", "horn"],
|
|
115
|
+
},
|
|
116
|
+
]);
|
|
117
|
+
|
|
118
|
+
const num = (
|
|
119
|
+
min: number,
|
|
120
|
+
max: number,
|
|
121
|
+
fallback: number,
|
|
122
|
+
step: number | "log",
|
|
123
|
+
doc: string,
|
|
124
|
+
extra: Partial<NumberParam> = {},
|
|
125
|
+
): NumberParam =>
|
|
126
|
+
Object.freeze({
|
|
127
|
+
kind: "number",
|
|
128
|
+
min,
|
|
129
|
+
max,
|
|
130
|
+
default: fallback,
|
|
131
|
+
step,
|
|
132
|
+
optional: true,
|
|
133
|
+
doc,
|
|
134
|
+
...extra,
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Every wind parameter. All are optional: an absent one takes the preset's
|
|
139
|
+
* value, then the default here. `automate` ones have a `wind-<param>` lane
|
|
140
|
+
* the voice reads every 32-sample control tick.
|
|
141
|
+
*/
|
|
142
|
+
export const WIND_PARAMS: Readonly<Record<string, ParamSpec>> = Object.freeze({
|
|
143
|
+
model: Object.freeze({
|
|
144
|
+
kind: "enum",
|
|
145
|
+
values: WIND_MODELS,
|
|
146
|
+
default: "jet",
|
|
147
|
+
optional: true,
|
|
148
|
+
doc: "exciter and bore: jet (flute), reed (clarinet), sax (conical reed), lips (brass)",
|
|
149
|
+
}) as EnumParam,
|
|
150
|
+
breath: num(0, 1, 0.6, 0.05, "blowing pressure: swells, louder and fuller", {
|
|
151
|
+
automate: true,
|
|
152
|
+
}),
|
|
153
|
+
noise: num(0, 1, 0.08, 0.05, "breath noise", { automate: true }),
|
|
154
|
+
attack: num(0.001, 2, 0.04, "log", "breath rise", {
|
|
155
|
+
unit: "s",
|
|
156
|
+
strudel: ["att"],
|
|
157
|
+
}),
|
|
158
|
+
release: num(0.005, 2, 0.08, "log", "breath fall", {
|
|
159
|
+
unit: "s",
|
|
160
|
+
strudel: ["rel"],
|
|
161
|
+
}),
|
|
162
|
+
vib: num(0, 12, 5, 0.25, "vibrato rate", {
|
|
163
|
+
unit: "Hz",
|
|
164
|
+
strudel: ["vibrato"],
|
|
165
|
+
}),
|
|
166
|
+
vibmod: num(0, 1, 0, 0.02, "vibrato depth", {
|
|
167
|
+
unit: "st",
|
|
168
|
+
strudel: ["vmod", "vibdepth", "depth"],
|
|
169
|
+
}),
|
|
170
|
+
reed: num(0, 1, 0.5, 0.05, "reed stiffness, jet offset or lip tension"),
|
|
171
|
+
bright: num(0, 1, 0.5, 0.05, "bore loss: dark to bright"),
|
|
172
|
+
stopped: Object.freeze({
|
|
173
|
+
kind: "boolean",
|
|
174
|
+
default: false,
|
|
175
|
+
optional: true,
|
|
176
|
+
doc: "stopped pipe, odd harmonics (jet only)",
|
|
177
|
+
}),
|
|
178
|
+
mute: Object.freeze({
|
|
179
|
+
kind: "enum",
|
|
180
|
+
values: WIND_MUTES,
|
|
181
|
+
default: "open",
|
|
182
|
+
optional: true,
|
|
183
|
+
doc: "brass mute: open, straight, cup, harmon or plunger",
|
|
184
|
+
}) as EnumParam,
|
|
185
|
+
wah: num(0, 1, 1, 0.05, "plunger opening: 0 closed, 1 open", {
|
|
186
|
+
automate: true,
|
|
187
|
+
}),
|
|
188
|
+
wahenv: num(0, 1, 0, 0.05, "plunger opens with each note (doo-wah)"),
|
|
189
|
+
growl: num(0, 1, 0, 0.05, "hum into the horn: rough, raspy", {
|
|
190
|
+
automate: true,
|
|
191
|
+
}),
|
|
192
|
+
flutter: num(0, 1, 0, 0.05, "flutter tongue (rolled r)", { automate: true }),
|
|
193
|
+
players: num(1, 8, 1, 1, "section size, spread over chord tones", {
|
|
194
|
+
integer: true,
|
|
195
|
+
}),
|
|
196
|
+
gain: num(0, 2, 0.8, 0.05, "level"),
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
/** Menu rows most people reach for. */
|
|
200
|
+
export const WIND_SIMPLE = Object.freeze([
|
|
201
|
+
"breath",
|
|
202
|
+
"bright",
|
|
203
|
+
"mute",
|
|
204
|
+
"players",
|
|
205
|
+
"growl",
|
|
206
|
+
]);
|
|
207
|
+
|
|
208
|
+
/** Resolved settings the engine plays. */
|
|
209
|
+
export type WindSettings = Readonly<{
|
|
210
|
+
model: WindModel;
|
|
211
|
+
breath: number;
|
|
212
|
+
noise: number;
|
|
213
|
+
attack: number;
|
|
214
|
+
release: number;
|
|
215
|
+
vib: number;
|
|
216
|
+
vibmod: number;
|
|
217
|
+
reed: number;
|
|
218
|
+
bright: number;
|
|
219
|
+
stopped: boolean;
|
|
220
|
+
mute: WindMute;
|
|
221
|
+
wah: number;
|
|
222
|
+
wahenv: number;
|
|
223
|
+
growl: number;
|
|
224
|
+
flutter: number;
|
|
225
|
+
players: number;
|
|
226
|
+
gain: number;
|
|
227
|
+
}>;
|
|
228
|
+
|
|
229
|
+
export const WIND_DEFAULTS: WindSettings = Object.freeze({
|
|
230
|
+
model: "jet",
|
|
231
|
+
breath: 0.6,
|
|
232
|
+
noise: 0.08,
|
|
233
|
+
attack: 0.04,
|
|
234
|
+
release: 0.08,
|
|
235
|
+
vib: 5,
|
|
236
|
+
vibmod: 0,
|
|
237
|
+
reed: 0.5,
|
|
238
|
+
bright: 0.5,
|
|
239
|
+
stopped: false,
|
|
240
|
+
mute: "open",
|
|
241
|
+
wah: 1,
|
|
242
|
+
wahenv: 0,
|
|
243
|
+
growl: 0,
|
|
244
|
+
flutter: 0,
|
|
245
|
+
players: 1,
|
|
246
|
+
gain: 0.8,
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
export type WindPreset = Readonly<{
|
|
250
|
+
settings: WindSettings;
|
|
251
|
+
/** MIDI range the instrument is built for (sounding pitch). */
|
|
252
|
+
range: readonly [number, number];
|
|
253
|
+
doc: string;
|
|
254
|
+
styles: string;
|
|
255
|
+
}>;
|
|
256
|
+
|
|
257
|
+
const preset = (
|
|
258
|
+
settings: Partial<WindSettings>,
|
|
259
|
+
range: [number, number],
|
|
260
|
+
doc: string,
|
|
261
|
+
styles: string,
|
|
262
|
+
): WindPreset =>
|
|
263
|
+
Object.freeze({
|
|
264
|
+
settings: Object.freeze({ ...WIND_DEFAULTS, ...settings }),
|
|
265
|
+
range: Object.freeze(range) as readonly [number, number],
|
|
266
|
+
doc,
|
|
267
|
+
styles,
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
/**
|
|
271
|
+
* Presets, ported from the reviewed prototype (proto/resonators/presets.ts)
|
|
272
|
+
* and extended to the 21 of the revised brief. Ranges are sounding pitch
|
|
273
|
+
* (written ranges of the transposing instruments, transposed).
|
|
274
|
+
*/
|
|
275
|
+
export const WIND_PRESETS: Readonly<Record<WindPresetName, WindPreset>> =
|
|
276
|
+
Object.freeze({
|
|
277
|
+
flute: preset(
|
|
278
|
+
{
|
|
279
|
+
model: "jet",
|
|
280
|
+
breath: 0.6,
|
|
281
|
+
noise: 0.12,
|
|
282
|
+
attack: 0.06,
|
|
283
|
+
vib: 5,
|
|
284
|
+
vibmod: 0.12,
|
|
285
|
+
bright: 0.5,
|
|
286
|
+
},
|
|
287
|
+
[60, 96],
|
|
288
|
+
"concert flute, airy with gentle vibrato",
|
|
289
|
+
"Debussy, Bach cantata, jazz flute",
|
|
290
|
+
),
|
|
291
|
+
recorder: preset(
|
|
292
|
+
{ model: "jet", breath: 0.5, noise: 0.06, attack: 0.02, bright: 0.6 },
|
|
293
|
+
[65, 93],
|
|
294
|
+
"alto recorder, pure and straight",
|
|
295
|
+
"Bach cantata, early music",
|
|
296
|
+
),
|
|
297
|
+
whistle: preset(
|
|
298
|
+
{ model: "jet", breath: 0.6, noise: 0.08, attack: 0.015, bright: 0.7 },
|
|
299
|
+
[74, 98],
|
|
300
|
+
"tin whistle, bright and reedy",
|
|
301
|
+
"celtic",
|
|
302
|
+
),
|
|
303
|
+
ney: preset(
|
|
304
|
+
{
|
|
305
|
+
model: "jet",
|
|
306
|
+
breath: 0.45,
|
|
307
|
+
noise: 0.35,
|
|
308
|
+
attack: 0.12,
|
|
309
|
+
vib: 5.5,
|
|
310
|
+
vibmod: 0.08,
|
|
311
|
+
bright: 0.3,
|
|
312
|
+
},
|
|
313
|
+
[55, 86],
|
|
314
|
+
"Persian reed flute, very breathy",
|
|
315
|
+
"Persian, Sufi, ambient",
|
|
316
|
+
),
|
|
317
|
+
shakuhachi: preset(
|
|
318
|
+
{
|
|
319
|
+
model: "jet",
|
|
320
|
+
breath: 0.55,
|
|
321
|
+
noise: 0.3,
|
|
322
|
+
attack: 0.08,
|
|
323
|
+
vib: 4,
|
|
324
|
+
vibmod: 0.2,
|
|
325
|
+
bright: 0.35,
|
|
326
|
+
},
|
|
327
|
+
[62, 86],
|
|
328
|
+
"bamboo end-blown flute, breath accents",
|
|
329
|
+
"Sakamoto, OPN, ambient",
|
|
330
|
+
),
|
|
331
|
+
panpipe: preset(
|
|
332
|
+
{
|
|
333
|
+
model: "jet",
|
|
334
|
+
stopped: true,
|
|
335
|
+
breath: 0.6,
|
|
336
|
+
noise: 0.2,
|
|
337
|
+
attack: 0.03,
|
|
338
|
+
bright: 0.5,
|
|
339
|
+
},
|
|
340
|
+
[60, 96],
|
|
341
|
+
"stopped cane pipes, hollow odd harmonics",
|
|
342
|
+
"ambient, Andean, Eno",
|
|
343
|
+
),
|
|
344
|
+
suling: preset(
|
|
345
|
+
{
|
|
346
|
+
model: "jet",
|
|
347
|
+
breath: 0.5,
|
|
348
|
+
noise: 0.25,
|
|
349
|
+
attack: 0.05,
|
|
350
|
+
vib: 5.5,
|
|
351
|
+
vibmod: 0.1,
|
|
352
|
+
bright: 0.45,
|
|
353
|
+
},
|
|
354
|
+
[67, 93],
|
|
355
|
+
"Sundanese and Balinese ring flute, soft and breathy",
|
|
356
|
+
"gamelan, degung, ambient",
|
|
357
|
+
),
|
|
358
|
+
bansuri: preset(
|
|
359
|
+
{
|
|
360
|
+
model: "jet",
|
|
361
|
+
breath: 0.55,
|
|
362
|
+
noise: 0.22,
|
|
363
|
+
attack: 0.07,
|
|
364
|
+
vib: 5,
|
|
365
|
+
vibmod: 0.15,
|
|
366
|
+
bright: 0.4,
|
|
367
|
+
},
|
|
368
|
+
[55, 84],
|
|
369
|
+
"Indian bamboo flute, warm with deep vibrato",
|
|
370
|
+
"Hindustani, Bollywood, ambient",
|
|
371
|
+
),
|
|
372
|
+
clarinet: preset(
|
|
373
|
+
{ model: "reed", breath: 0.6, noise: 0.05, attack: 0.04, reed: 0.5 },
|
|
374
|
+
[50, 91],
|
|
375
|
+
"cylindrical bore, woody odd harmonics",
|
|
376
|
+
"Brahms, jazz, klezmer, Motown",
|
|
377
|
+
),
|
|
378
|
+
bassclarinet: preset(
|
|
379
|
+
{
|
|
380
|
+
model: "reed",
|
|
381
|
+
breath: 0.6,
|
|
382
|
+
noise: 0.06,
|
|
383
|
+
attack: 0.05,
|
|
384
|
+
reed: 0.4,
|
|
385
|
+
bright: 0.4,
|
|
386
|
+
},
|
|
387
|
+
[37, 72],
|
|
388
|
+
"bass clarinet, dark and hollow",
|
|
389
|
+
"Bitches Brew, Messiaen, film",
|
|
390
|
+
),
|
|
391
|
+
oboe: preset(
|
|
392
|
+
{
|
|
393
|
+
model: "sax",
|
|
394
|
+
breath: 0.6,
|
|
395
|
+
noise: 0.04,
|
|
396
|
+
attack: 0.03,
|
|
397
|
+
vib: 5.5,
|
|
398
|
+
vibmod: 0.08,
|
|
399
|
+
reed: 0.8,
|
|
400
|
+
bright: 0.75,
|
|
401
|
+
},
|
|
402
|
+
[58, 91],
|
|
403
|
+
"double reed, nasal and singing",
|
|
404
|
+
"Bach, Ravel, Morricone",
|
|
405
|
+
),
|
|
406
|
+
bassoon: preset(
|
|
407
|
+
{
|
|
408
|
+
model: "sax",
|
|
409
|
+
breath: 0.6,
|
|
410
|
+
noise: 0.04,
|
|
411
|
+
attack: 0.04,
|
|
412
|
+
vib: 5,
|
|
413
|
+
vibmod: 0.06,
|
|
414
|
+
reed: 0.7,
|
|
415
|
+
bright: 0.35,
|
|
416
|
+
},
|
|
417
|
+
[34, 72],
|
|
418
|
+
"bassoon, reedy and woody low voice",
|
|
419
|
+
"Stravinsky, Prokofiev, orchestral",
|
|
420
|
+
),
|
|
421
|
+
sax: preset(
|
|
422
|
+
{
|
|
423
|
+
model: "sax",
|
|
424
|
+
breath: 0.65,
|
|
425
|
+
noise: 0.1,
|
|
426
|
+
attack: 0.03,
|
|
427
|
+
vib: 5.2,
|
|
428
|
+
vibmod: 0.15,
|
|
429
|
+
},
|
|
430
|
+
[44, 79],
|
|
431
|
+
"tenor sax, warm to raspy with breath",
|
|
432
|
+
"jazz, Motown, soul, funk",
|
|
433
|
+
),
|
|
434
|
+
altosax: preset(
|
|
435
|
+
{
|
|
436
|
+
model: "sax",
|
|
437
|
+
breath: 0.65,
|
|
438
|
+
noise: 0.09,
|
|
439
|
+
attack: 0.03,
|
|
440
|
+
vib: 5.4,
|
|
441
|
+
vibmod: 0.12,
|
|
442
|
+
bright: 0.55,
|
|
443
|
+
},
|
|
444
|
+
[49, 81],
|
|
445
|
+
"alto sax, bright and vocal",
|
|
446
|
+
"bebop, Motown, soul",
|
|
447
|
+
),
|
|
448
|
+
barisax: preset(
|
|
449
|
+
{
|
|
450
|
+
model: "sax",
|
|
451
|
+
breath: 0.68,
|
|
452
|
+
noise: 0.1,
|
|
453
|
+
attack: 0.04,
|
|
454
|
+
vib: 5,
|
|
455
|
+
vibmod: 0.08,
|
|
456
|
+
bright: 0.45,
|
|
457
|
+
},
|
|
458
|
+
[36, 69],
|
|
459
|
+
"baritone sax, honking low end",
|
|
460
|
+
"Motown, funk, ska",
|
|
461
|
+
),
|
|
462
|
+
trumpet: preset(
|
|
463
|
+
{
|
|
464
|
+
model: "lips",
|
|
465
|
+
breath: 0.7,
|
|
466
|
+
noise: 0.03,
|
|
467
|
+
attack: 0.03,
|
|
468
|
+
bright: 0.6,
|
|
469
|
+
vib: 5.5,
|
|
470
|
+
},
|
|
471
|
+
[52, 82],
|
|
472
|
+
"open trumpet, brightens as it gets louder",
|
|
473
|
+
"Louis Armstrong, Motown, salsa, jazz",
|
|
474
|
+
),
|
|
475
|
+
harmon: preset(
|
|
476
|
+
{
|
|
477
|
+
model: "lips",
|
|
478
|
+
breath: 0.7,
|
|
479
|
+
noise: 0.03,
|
|
480
|
+
attack: 0.03,
|
|
481
|
+
bright: 0.6,
|
|
482
|
+
mute: "harmon",
|
|
483
|
+
},
|
|
484
|
+
[52, 82],
|
|
485
|
+
"trumpet with a harmon mute, buzzy and close",
|
|
486
|
+
"jazz, Radiohead, Sakamoto",
|
|
487
|
+
),
|
|
488
|
+
plunger: preset(
|
|
489
|
+
{
|
|
490
|
+
model: "lips",
|
|
491
|
+
breath: 0.75,
|
|
492
|
+
noise: 0.04,
|
|
493
|
+
attack: 0.03,
|
|
494
|
+
bright: 0.6,
|
|
495
|
+
mute: "plunger",
|
|
496
|
+
wah: 0.3,
|
|
497
|
+
},
|
|
498
|
+
[52, 82],
|
|
499
|
+
"trumpet with a plunger: automate wah for ya-ya",
|
|
500
|
+
"Louis Armstrong, Duke Ellington, funk",
|
|
501
|
+
),
|
|
502
|
+
trombone: preset(
|
|
503
|
+
{ model: "lips", breath: 0.7, noise: 0.03, attack: 0.04, bright: 0.45 },
|
|
504
|
+
[40, 72],
|
|
505
|
+
"slide trombone, broad",
|
|
506
|
+
"salsa, Motown, jazz",
|
|
507
|
+
),
|
|
508
|
+
tuba: preset(
|
|
509
|
+
{ model: "lips", breath: 0.7, noise: 0.02, attack: 0.06, bright: 0.3 },
|
|
510
|
+
[28, 58],
|
|
511
|
+
"tuba, round and deep",
|
|
512
|
+
"New Orleans brass band, oompah, orchestral",
|
|
513
|
+
),
|
|
514
|
+
horn: preset(
|
|
515
|
+
{ model: "lips", breath: 0.6, noise: 0.02, attack: 0.06, bright: 0.25 },
|
|
516
|
+
[41, 77],
|
|
517
|
+
"French horn, dark and round",
|
|
518
|
+
"Brahms, Beethoven, orchestral",
|
|
519
|
+
),
|
|
520
|
+
});
|
|
521
|
+
|
|
522
|
+
/** Default preset of a `wind` field that names none. */
|
|
523
|
+
export const DEFAULT_WIND_PRESET: WindPresetName = "flute";
|
|
524
|
+
|
|
525
|
+
/** A track's stored wind settings: a preset plus overrides, all optional. */
|
|
526
|
+
export type TrackWind = Readonly<{
|
|
527
|
+
preset?: WindPresetName;
|
|
528
|
+
model?: WindModel;
|
|
529
|
+
breath?: number;
|
|
530
|
+
noise?: number;
|
|
531
|
+
attack?: number;
|
|
532
|
+
release?: number;
|
|
533
|
+
vib?: number;
|
|
534
|
+
vibmod?: number;
|
|
535
|
+
reed?: number;
|
|
536
|
+
bright?: number;
|
|
537
|
+
stopped?: boolean;
|
|
538
|
+
mute?: WindMute;
|
|
539
|
+
wah?: number;
|
|
540
|
+
wahenv?: number;
|
|
541
|
+
growl?: number;
|
|
542
|
+
flutter?: number;
|
|
543
|
+
players?: number;
|
|
544
|
+
gain?: number;
|
|
545
|
+
}>;
|
|
546
|
+
|
|
547
|
+
export function isWindPreset(name: string): name is WindPresetName {
|
|
548
|
+
return (WIND_PRESET_NAMES as readonly string[]).includes(name);
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
/** A preset word or alias (`saxophone`) to its preset; undefined if neither. */
|
|
552
|
+
export function windPresetFor(word: string): WindPresetName | undefined {
|
|
553
|
+
const lower = word.trim().toLowerCase();
|
|
554
|
+
if (isWindPreset(lower)) return lower;
|
|
555
|
+
return WIND_ALIASES[lower];
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
export function isWindParam(name: string): boolean {
|
|
559
|
+
return Object.prototype.hasOwnProperty.call(WIND_PARAMS, name);
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
/** Strudel spelling (`att`, `rel`) to a wind parameter name. */
|
|
563
|
+
export function windParamName(name: string): string | undefined {
|
|
564
|
+
if (isWindParam(name)) return name;
|
|
565
|
+
for (const [key, spec] of Object.entries(WIND_PARAMS))
|
|
566
|
+
if (spec.strudel?.includes(name)) return key;
|
|
567
|
+
return undefined;
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
/**
|
|
571
|
+
* Validates a `wind` field: `{ preset?, ...overrides }`, null or absent
|
|
572
|
+
* meaning none. Keys come out in canonical order (preset, then
|
|
573
|
+
* `WIND_PARAMS` order). `{}` is kept: it means the default preset.
|
|
574
|
+
*/
|
|
575
|
+
export function normalizeWind(input: unknown): TrackWind | undefined {
|
|
576
|
+
if (input === undefined || input === null) return undefined;
|
|
577
|
+
if (!isRecord(input))
|
|
578
|
+
throw new FxValidationError("track wind must be an object or null");
|
|
579
|
+
const params: Record<string, unknown> = {};
|
|
580
|
+
let presetName: WindPresetName | undefined;
|
|
581
|
+
for (const [key, value] of Object.entries(input)) {
|
|
582
|
+
if (value === undefined || value === null) continue;
|
|
583
|
+
if (key === "preset") {
|
|
584
|
+
const resolved =
|
|
585
|
+
typeof value === "string" ? windPresetFor(value) : undefined;
|
|
586
|
+
if (!resolved)
|
|
587
|
+
throw new FxValidationError(
|
|
588
|
+
`wind preset must be one of ${WIND_PRESET_NAMES.join(", ")}`,
|
|
589
|
+
);
|
|
590
|
+
presetName = resolved;
|
|
591
|
+
continue;
|
|
592
|
+
}
|
|
593
|
+
const name = windParamName(key);
|
|
594
|
+
if (!name)
|
|
595
|
+
throw new FxValidationError(
|
|
596
|
+
`wind has no parameter "${key.slice(0, 32)}"`,
|
|
597
|
+
);
|
|
598
|
+
params[name] = value;
|
|
599
|
+
}
|
|
600
|
+
const values = normalizeParams(WIND_PARAMS, params, "wind", false);
|
|
601
|
+
return Object.freeze({
|
|
602
|
+
...(presetName ? { preset: presetName } : {}),
|
|
603
|
+
...values,
|
|
604
|
+
}) as TrackWind;
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
/** The preset a `wind` field plays. */
|
|
608
|
+
export function windPresetOf(wind: TrackWind | undefined): WindPresetName {
|
|
609
|
+
return wind?.preset ?? DEFAULT_WIND_PRESET;
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
/**
|
|
613
|
+
* The settings a `wind` field plays: defaults, then the preset, then
|
|
614
|
+
* explicit overrides, then any automation lane values from `lane`.
|
|
615
|
+
*/
|
|
616
|
+
export function windSettings(
|
|
617
|
+
wind: TrackWind | undefined,
|
|
618
|
+
lane?: (param: string) => number | undefined,
|
|
619
|
+
): WindSettings {
|
|
620
|
+
const base = WIND_PRESETS[windPresetOf(wind)].settings;
|
|
621
|
+
const out: Record<string, number | string | boolean> = { ...base };
|
|
622
|
+
if (wind)
|
|
623
|
+
for (const [key, value] of Object.entries(wind))
|
|
624
|
+
if (key !== "preset" && value !== undefined) out[key] = value;
|
|
625
|
+
if (lane)
|
|
626
|
+
for (const { param } of WIND_LANE_PARAMS) {
|
|
627
|
+
const value = lane(param);
|
|
628
|
+
if (value !== undefined) out[param] = value;
|
|
629
|
+
}
|
|
630
|
+
return Object.freeze(out) as WindSettings;
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
/** Parameters with a `wind-<param>` automation lane. */
|
|
634
|
+
export const WIND_LANE_PARAMS: readonly Readonly<{
|
|
635
|
+
param: string;
|
|
636
|
+
spec: NumberParam;
|
|
637
|
+
}>[] = Object.freeze(
|
|
638
|
+
Object.entries(WIND_PARAMS)
|
|
639
|
+
.filter(
|
|
640
|
+
(entry): entry is [string, NumberParam] =>
|
|
641
|
+
entry[1].kind === "number" && entry[1].automate === true,
|
|
642
|
+
)
|
|
643
|
+
.map(([param, spec]) => Object.freeze({ param, spec })),
|
|
644
|
+
);
|
|
645
|
+
|
|
646
|
+
/** Voices one wind track sounds at once; the oldest onset is stolen. */
|
|
647
|
+
export const MAX_WIND_VOICES = 24;
|
|
648
|
+
|
|
649
|
+
/** Ring after the last breath ends, in seconds. */
|
|
650
|
+
export function windTailSeconds(wind: TrackWind | undefined): number {
|
|
651
|
+
return windSettings(wind).release + 0.15;
|
|
652
|
+
}
|
package/guides/automation.md
CHANGED
|
@@ -19,6 +19,7 @@ A lane moves a value over beats. Every numeric effect parameter has one.
|
|
|
19
19
|
- `automate pan at 0 -1` · `automate delay-mix at 4 0`
|
|
20
20
|
- `automate distort-drive points 0:1 8:6`
|
|
21
21
|
- `automate filter remove 4` delete one point
|
|
22
|
+
- `automate grain-pos points 0:0.1 8:0.9` sweeps a granular track's head (every movable `grain-<param>`)
|
|
22
23
|
- `clear filter automation` · `clear automation`
|
|
23
24
|
|
|
24
25
|
## Menu
|
package/guides/chords.md
CHANGED
|
@@ -16,13 +16,15 @@ order: 4
|
|
|
16
16
|
- `1 2 3 4` dim min maj sus · `5 6 7 8` add 6, m7, M7, 9
|
|
17
17
|
- `- =` voicing down / up · `9` perform mode · `b` bass mode
|
|
18
18
|
- `n` play the suggested next chord · `0` clear the latches
|
|
19
|
+
- perform guitar strums them; `[ ]` strum slower / faster
|
|
19
20
|
|
|
20
21
|
## Type it yourself
|
|
21
22
|
|
|
22
23
|
- `/chords auto` · `/chords manual` · `/chords off`
|
|
23
24
|
- `/chords voicing 2` · `/chords spread wide` · `/chords bass chords`
|
|
24
25
|
- `/chords perform arp-up` · `/chords sevenths on` · `/chords`
|
|
26
|
+
- `strum G D Em C folk` · `guitar tune dadgad` · `guitar capo 2`
|
|
25
27
|
|
|
26
28
|
## Menu
|
|
27
29
|
|
|
28
|
-
- Ctrl-K › Chords: key, voicing, bass, perform,
|
|
30
|
+
- Ctrl-K › Chords: key, voicing, bass, perform, strokes, speed, style
|
package/guides/effects.md
CHANGED
|
@@ -11,17 +11,20 @@ chorus, delay, reverb, and more (phaser, bitcrush, vowel, leslie).
|
|
|
11
11
|
## Ask
|
|
12
12
|
|
|
13
13
|
- "add a dub delay to the snare" · "more room on the keys"
|
|
14
|
+
- "give the guitar a crunchy amp"
|
|
14
15
|
|
|
15
16
|
## Type it yourself
|
|
16
17
|
|
|
17
18
|
- `fx` list the focused track's effects
|
|
18
19
|
- `fx delay mix 0.3` · `fx filter type hpf cutoff 300`
|
|
19
|
-
- `fx distort drive 4 tone 5000` · `fx reverb ir
|
|
20
|
+
- `fx distort drive 4 tone 5000` · `fx reverb ir builtin:reverse`
|
|
20
21
|
- `fx chorus on` · `fx chorus off` · `fx chorus reset`
|
|
21
22
|
- `fx compressor preset <name>` load a preset
|
|
23
|
+
- `rig crunch` guitar rig · `head lead` · `cab 4x12` · `stomp fuzz`
|
|
24
|
+
- `rig shoegaze` · `fx wobble depth 30` · `fx bloom` · `fx swell` · `fx double`
|
|
22
25
|
- `filter 1200 0.6` · `delay 0.375 0.3` · `reverb 0.4 0.8` · `filter off`
|
|
23
26
|
|
|
24
27
|
## Menu
|
|
25
28
|
|
|
26
|
-
- Ctrl-K › Effects: core effects
|
|
29
|
+
- Ctrl-K › Effects: core effects, Guitar rig, Shoegaze, more, advanced
|
|
27
30
|
- `fx filter` alone opens the filter's faders, stacked
|
package/guides/media.md
CHANGED