@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.
- package/CHANGELOG.md +99 -0
- package/DAWG.md +551 -33
- package/README.md +4 -4
- package/core/chords.ts +288 -7
- package/core/diff.ts +41 -12
- package/core/expression.ts +1241 -0
- package/core/fx.ts +437 -2
- package/core/granular.ts +528 -0
- package/core/instruments.ts +281 -0
- package/core/keys.ts +386 -0
- package/core/loop.ts +23 -0
- package/core/master.ts +455 -0
- package/core/midi.ts +452 -0
- package/core/resonators.ts +574 -0
- package/core/rhythm.ts +7 -2
- package/core/score.ts +717 -62
- package/core/sdk/eval-child.ts +40 -3
- package/core/sdk/print.ts +614 -27
- package/core/sdk/sync-instruments.ts +58 -0
- package/core/sdk/v1.ts +2824 -45
- package/core/sections.ts +2072 -0
- package/core/strings.ts +845 -0
- package/core/synth.ts +11 -1
- package/core/tempo.ts +1318 -0
- package/core/tuning.ts +1180 -0
- package/guides/audition.md +26 -0
- package/guides/automation.md +26 -0
- package/guides/chords.md +28 -0
- package/guides/effects.md +29 -0
- package/guides/faders.md +29 -0
- package/guides/files.md +28 -0
- package/guides/getting-started.md +26 -0
- package/guides/index.ts +75 -0
- package/guides/keys.md +27 -0
- package/guides/media.md +27 -0
- package/guides/mix.md +23 -0
- package/guides/music.md +14 -0
- package/guides/notes.md +26 -0
- package/guides/performance.md +26 -0
- package/guides/play.md +24 -0
- package/guides/project.md +13 -0
- package/guides/providers.md +25 -0
- package/guides/rhythm.md +26 -0
- package/guides/sessions.md +21 -0
- package/guides/sound.md +15 -0
- package/guides/sounds.md +29 -0
- package/guides/tempo.md +27 -0
- package/guides/tracks.md +25 -0
- package/guides/web-search.md +21 -0
- package/package.json +3 -1
- package/src/agent/agent.ts +9 -0
- package/src/agent/brief.ts +77 -3
- package/src/agent/chord-tools.ts +9 -1
- package/src/agent/expression-tools.ts +336 -0
- package/src/agent/granular-tools.ts +138 -0
- package/src/agent/master-tools.ts +299 -0
- package/src/agent/models.ts +4 -4
- package/src/agent/ops.ts +68 -6
- package/src/agent/planner.ts +45 -2
- package/src/agent/preview-tool.ts +27 -4
- package/src/agent/section-tools.ts +411 -0
- package/src/agent/time-tools.ts +290 -0
- package/src/agent/tools.ts +574 -16
- package/src/agent/tuning-tools.ts +301 -0
- package/src/agent/xcb-agent.ts +9 -0
- package/src/audio/arrange.ts +489 -0
- package/src/audio/audition.ts +16 -3
- package/src/audio/click.ts +113 -1
- package/src/audio/clock.ts +71 -5
- package/src/audio/dsp/bank.ts +233 -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/stft.ts +73 -0
- package/src/audio/dsp/window.ts +77 -0
- package/src/audio/effects/bus.ts +5 -4
- package/src/audio/effects/chain.ts +6 -1
- package/src/audio/effects/common.ts +30 -1
- package/src/audio/effects/dynamics.ts +2 -1
- package/src/audio/effects/filter.ts +5 -3
- package/src/audio/effects/modulation.ts +5 -1
- 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/effects/space.ts +92 -2
- package/src/audio/engine.ts +100 -21
- package/src/audio/fit.ts +402 -0
- package/src/audio/granular.ts +664 -0
- package/src/audio/instrument-check.ts +133 -0
- package/src/audio/instruments.ts +111 -0
- package/src/audio/keys/dsp.ts +323 -0
- package/src/audio/keys/engine.ts +361 -0
- package/src/audio/keys/piano.ts +432 -0
- package/src/audio/live-worker.ts +54 -0
- package/src/audio/live.ts +263 -17
- package/src/audio/loudness.ts +596 -0
- package/src/audio/master.ts +660 -0
- package/src/audio/measure-worker.ts +45 -0
- package/src/audio/measure.ts +110 -0
- package/src/audio/player.ts +11 -6
- package/src/audio/preview.ts +76 -14
- package/src/audio/render-worker.ts +6 -1
- package/src/audio/renderer.ts +7 -1
- package/src/audio/resonators.ts +287 -0
- package/src/audio/sampler.ts +269 -34
- package/src/audio/samples.ts +30 -7
- package/src/audio/strings/body.ts +250 -0
- package/src/audio/strings/engine.ts +369 -0
- package/src/audio/strings/loop.ts +119 -0
- package/src/audio/strings/measure.test-helpers.ts +198 -0
- package/src/audio/strings/pluck.ts +354 -0
- package/src/audio/synth/voice.ts +60 -13
- package/src/audio/synth/zzfx.ts +10 -4
- package/src/audio/warp.ts +147 -0
- package/src/audio/wav.ts +377 -30
- package/src/commands/arrange.ts +949 -0
- package/src/commands/expression.ts +941 -0
- package/src/commands/fit.ts +135 -0
- package/src/commands/fx.ts +31 -0
- package/src/commands/granular.ts +427 -0
- package/src/commands/help.ts +355 -27
- package/src/commands/keys.ts +353 -0
- package/src/commands/master.ts +361 -0
- package/src/commands/modal.ts +247 -0
- package/src/commands/music.ts +6 -1
- package/src/commands/rig.ts +260 -0
- package/src/commands/sample.ts +3 -0
- package/src/commands/string.ts +175 -0
- package/src/commands/synth.ts +11 -1
- package/src/commands/time.ts +967 -0
- package/src/commands/tuning.ts +490 -0
- package/src/main.ts +919 -41
- package/src/project/check.ts +13 -0
- package/src/render.ts +139 -8
- package/src/session/daemon.ts +18 -8
- package/src/session/naming.ts +8 -1
- package/src/session/rebase.ts +21 -0
- package/src/tui/arrange-menu.ts +390 -0
- package/src/tui/audition.ts +38 -5
- package/src/tui/fader.ts +409 -0
- package/src/tui/granular-menu.ts +278 -0
- package/src/tui/menu-time.ts +401 -0
- package/src/tui/menu.ts +967 -23
- package/src/tui/modal-menu.ts +118 -0
- package/src/tui/performance-menu.ts +235 -0
- package/src/tui/play-chords.ts +3 -1
- package/src/tui/play-mode.ts +150 -6
- package/src/tui/play-session.ts +459 -43
- package/tui/app.ts +262 -13
- package/tui/arrange-strip.ts +174 -0
- package/tui/drawer.ts +478 -0
- package/tui/grammar.ts +63 -1
- package/tui/guide.ts +351 -0
- package/tui/highway.ts +87 -4
- package/tui/hits.ts +68 -0
- package/tui/input.ts +7 -0
- package/tui/keys.ts +72 -0
package/core/strings.ts
ADDED
|
@@ -0,0 +1,845 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* String instruments (0.6 lane A, plucked and struck): the `Track.string`
|
|
3
|
+
* parameter table, the frozen preset table and the words that pick them.
|
|
4
|
+
*
|
|
5
|
+
* `Track.string` stores only a preset name and the parameters the user
|
|
6
|
+
* changed; absent keys take the preset's value, then the table default.
|
|
7
|
+
* The engine (`src/audio/strings/`) runs only when `instrument` is
|
|
8
|
+
* `"string"` AND `string` is present, so a stored legacy `sitar`, `ebass`,
|
|
9
|
+
* `cello` or `pluck` keeps today's voice.
|
|
10
|
+
*/
|
|
11
|
+
import {
|
|
12
|
+
FxValidationError,
|
|
13
|
+
isRecord,
|
|
14
|
+
normalizeParam,
|
|
15
|
+
type NumberParam,
|
|
16
|
+
type ParamSpec,
|
|
17
|
+
} from "./params.ts";
|
|
18
|
+
|
|
19
|
+
/** The `Track.instrument` value of the string engine. */
|
|
20
|
+
export const STRING_INSTRUMENT = "string" as const;
|
|
21
|
+
|
|
22
|
+
/** A track's string settings: a preset plus parameter overrides. */
|
|
23
|
+
export type TrackString = Readonly<
|
|
24
|
+
{ preset?: string } & Record<string, number | string | undefined>
|
|
25
|
+
>;
|
|
26
|
+
|
|
27
|
+
const log = "log" as const;
|
|
28
|
+
|
|
29
|
+
/** Every string parameter (section 4 of the strings spec, plucked subset). */
|
|
30
|
+
export const STRING_PARAMS: Readonly<Record<string, ParamSpec>> = Object.freeze(
|
|
31
|
+
{
|
|
32
|
+
exciter: {
|
|
33
|
+
kind: "enum",
|
|
34
|
+
values: ["pick", "finger", "hammer", "noise"],
|
|
35
|
+
default: "pick",
|
|
36
|
+
doc: "how the string is set in motion",
|
|
37
|
+
},
|
|
38
|
+
ring: {
|
|
39
|
+
kind: "number",
|
|
40
|
+
min: 0.05,
|
|
41
|
+
max: 60,
|
|
42
|
+
default: 3,
|
|
43
|
+
step: log,
|
|
44
|
+
unit: "s",
|
|
45
|
+
automate: true,
|
|
46
|
+
doc: "how long a note rings (T60 at C4)",
|
|
47
|
+
strudel: ["decay"],
|
|
48
|
+
},
|
|
49
|
+
track: {
|
|
50
|
+
kind: "number",
|
|
51
|
+
min: 0,
|
|
52
|
+
max: 1.5,
|
|
53
|
+
default: 0.5,
|
|
54
|
+
step: 0.05,
|
|
55
|
+
doc: "higher notes ring shorter: T60 x (f/C4)^-track",
|
|
56
|
+
},
|
|
57
|
+
damp: {
|
|
58
|
+
kind: "number",
|
|
59
|
+
min: 0,
|
|
60
|
+
max: 1,
|
|
61
|
+
default: 0.4,
|
|
62
|
+
step: 0.05,
|
|
63
|
+
automate: true,
|
|
64
|
+
doc: "high-frequency loss (dark, dead strings at 1)",
|
|
65
|
+
},
|
|
66
|
+
stiff: {
|
|
67
|
+
kind: "number",
|
|
68
|
+
min: 0,
|
|
69
|
+
max: 1,
|
|
70
|
+
default: 0,
|
|
71
|
+
step: 0.05,
|
|
72
|
+
doc: "inharmonicity B = 1e-6 x 400^stiff (0 off, 0.5 a wound bass string)",
|
|
73
|
+
},
|
|
74
|
+
pos: {
|
|
75
|
+
kind: "number",
|
|
76
|
+
min: 0.02,
|
|
77
|
+
max: 0.5,
|
|
78
|
+
default: 0.15,
|
|
79
|
+
step: 0.01,
|
|
80
|
+
automate: true,
|
|
81
|
+
doc: "pluck or strike position from the bridge (0.5 round, 0.04 nasal)",
|
|
82
|
+
},
|
|
83
|
+
bright: {
|
|
84
|
+
kind: "number",
|
|
85
|
+
min: 0,
|
|
86
|
+
max: 1,
|
|
87
|
+
default: 0.6,
|
|
88
|
+
step: 0.05,
|
|
89
|
+
automate: true,
|
|
90
|
+
doc: "excitation brightness at full velocity",
|
|
91
|
+
},
|
|
92
|
+
noise: {
|
|
93
|
+
kind: "number",
|
|
94
|
+
min: 0,
|
|
95
|
+
max: 1,
|
|
96
|
+
default: 0.04,
|
|
97
|
+
step: 0.02,
|
|
98
|
+
doc: "noise share of the excitation (pick scrape, finger squeak)",
|
|
99
|
+
},
|
|
100
|
+
release: {
|
|
101
|
+
kind: "number",
|
|
102
|
+
min: 0.005,
|
|
103
|
+
max: 10,
|
|
104
|
+
default: 0.1,
|
|
105
|
+
step: log,
|
|
106
|
+
unit: "s",
|
|
107
|
+
doc: "damping time after note-off",
|
|
108
|
+
},
|
|
109
|
+
mute: {
|
|
110
|
+
kind: "number",
|
|
111
|
+
min: 0,
|
|
112
|
+
max: 1,
|
|
113
|
+
default: 0,
|
|
114
|
+
step: 0.05,
|
|
115
|
+
automate: true,
|
|
116
|
+
doc: "palm mute: shortens the ring and darkens the string",
|
|
117
|
+
},
|
|
118
|
+
buzz: {
|
|
119
|
+
kind: "number",
|
|
120
|
+
min: 0,
|
|
121
|
+
max: 1,
|
|
122
|
+
default: 0,
|
|
123
|
+
step: 0.05,
|
|
124
|
+
automate: true,
|
|
125
|
+
doc: "bridge buzz (sitar, tanpura jawari); 0 off",
|
|
126
|
+
strudel: ["jawari"],
|
|
127
|
+
},
|
|
128
|
+
vel: {
|
|
129
|
+
kind: "number",
|
|
130
|
+
min: 0,
|
|
131
|
+
max: 1,
|
|
132
|
+
default: 1,
|
|
133
|
+
step: 0.05,
|
|
134
|
+
doc: "velocity sensitivity of level and brightness (harpsichord 0.05)",
|
|
135
|
+
},
|
|
136
|
+
pickup: {
|
|
137
|
+
kind: "number",
|
|
138
|
+
min: 0,
|
|
139
|
+
max: 0.5,
|
|
140
|
+
default: 0,
|
|
141
|
+
step: 0.01,
|
|
142
|
+
doc: "magnetic pickup position (0 = acoustic bridge)",
|
|
143
|
+
},
|
|
144
|
+
vib: {
|
|
145
|
+
kind: "number",
|
|
146
|
+
min: 0,
|
|
147
|
+
max: 12,
|
|
148
|
+
default: 0,
|
|
149
|
+
step: 0.25,
|
|
150
|
+
unit: "Hz",
|
|
151
|
+
automate: true,
|
|
152
|
+
doc: "vibrato rate (a note's own vibrato overrides it)",
|
|
153
|
+
strudel: ["vib"],
|
|
154
|
+
},
|
|
155
|
+
vibmod: {
|
|
156
|
+
kind: "number",
|
|
157
|
+
min: 0,
|
|
158
|
+
max: 2,
|
|
159
|
+
default: 0.18,
|
|
160
|
+
step: 0.02,
|
|
161
|
+
unit: "st",
|
|
162
|
+
automate: true,
|
|
163
|
+
doc: "vibrato depth in semitones",
|
|
164
|
+
strudel: ["vibmod"],
|
|
165
|
+
},
|
|
166
|
+
vibdelay: {
|
|
167
|
+
kind: "number",
|
|
168
|
+
min: 0,
|
|
169
|
+
max: 2,
|
|
170
|
+
default: 0.25,
|
|
171
|
+
step: 0.05,
|
|
172
|
+
unit: "s",
|
|
173
|
+
doc: "vibrato onset delay",
|
|
174
|
+
},
|
|
175
|
+
body: {
|
|
176
|
+
kind: "enum",
|
|
177
|
+
values: [
|
|
178
|
+
"none",
|
|
179
|
+
"guitar",
|
|
180
|
+
"steel",
|
|
181
|
+
"small",
|
|
182
|
+
"bowl",
|
|
183
|
+
"gourd",
|
|
184
|
+
"skin",
|
|
185
|
+
"board",
|
|
186
|
+
"violin",
|
|
187
|
+
"bass",
|
|
188
|
+
],
|
|
189
|
+
default: "none",
|
|
190
|
+
doc: "body resonance",
|
|
191
|
+
},
|
|
192
|
+
size: {
|
|
193
|
+
kind: "number",
|
|
194
|
+
min: 0.25,
|
|
195
|
+
max: 5,
|
|
196
|
+
default: 1,
|
|
197
|
+
step: log,
|
|
198
|
+
doc: "body scale: mode frequencies x 1/size (2 is a body twice as big)",
|
|
199
|
+
},
|
|
200
|
+
sym: {
|
|
201
|
+
kind: "number",
|
|
202
|
+
min: 0,
|
|
203
|
+
max: 1,
|
|
204
|
+
default: 0,
|
|
205
|
+
step: 0.05,
|
|
206
|
+
doc: "sympathetic strings level",
|
|
207
|
+
},
|
|
208
|
+
symtune: {
|
|
209
|
+
kind: "enum",
|
|
210
|
+
values: ["scale", "open", "drone"],
|
|
211
|
+
default: "scale",
|
|
212
|
+
doc: "what the sympathetic strings are tuned to",
|
|
213
|
+
},
|
|
214
|
+
unison: {
|
|
215
|
+
kind: "number",
|
|
216
|
+
min: 1,
|
|
217
|
+
max: 8,
|
|
218
|
+
default: 1,
|
|
219
|
+
step: 1,
|
|
220
|
+
integer: true,
|
|
221
|
+
doc: "strings per course",
|
|
222
|
+
strudel: ["unison"],
|
|
223
|
+
},
|
|
224
|
+
detune: {
|
|
225
|
+
kind: "number",
|
|
226
|
+
min: 0,
|
|
227
|
+
max: 1,
|
|
228
|
+
default: 0.1,
|
|
229
|
+
step: 0.01,
|
|
230
|
+
unit: "st",
|
|
231
|
+
doc: "total detune spread across the course",
|
|
232
|
+
strudel: ["detune"],
|
|
233
|
+
},
|
|
234
|
+
spread: {
|
|
235
|
+
kind: "number",
|
|
236
|
+
min: 0,
|
|
237
|
+
max: 1,
|
|
238
|
+
default: 0.5,
|
|
239
|
+
step: 0.05,
|
|
240
|
+
doc: "stereo spread across the strings of a course",
|
|
241
|
+
strudel: ["spread"],
|
|
242
|
+
},
|
|
243
|
+
oct: {
|
|
244
|
+
kind: "number",
|
|
245
|
+
min: 0,
|
|
246
|
+
max: 1,
|
|
247
|
+
default: 0,
|
|
248
|
+
step: 0.05,
|
|
249
|
+
doc: "octave string level (12-string, harpsichord 4', tres)",
|
|
250
|
+
},
|
|
251
|
+
octbelow: {
|
|
252
|
+
kind: "number",
|
|
253
|
+
min: 0,
|
|
254
|
+
max: 128,
|
|
255
|
+
default: 128,
|
|
256
|
+
step: 1,
|
|
257
|
+
integer: true,
|
|
258
|
+
doc: "octave strings only below this MIDI key",
|
|
259
|
+
},
|
|
260
|
+
voices: {
|
|
261
|
+
kind: "number",
|
|
262
|
+
min: 1,
|
|
263
|
+
max: 32,
|
|
264
|
+
default: 8,
|
|
265
|
+
step: 1,
|
|
266
|
+
integer: true,
|
|
267
|
+
doc: "polyphony cap (the oldest chord is released first)",
|
|
268
|
+
},
|
|
269
|
+
gain: {
|
|
270
|
+
kind: "number",
|
|
271
|
+
min: 0,
|
|
272
|
+
max: 2,
|
|
273
|
+
default: 1,
|
|
274
|
+
step: 0.05,
|
|
275
|
+
automate: true,
|
|
276
|
+
doc: "output level (presets carry a calibrated trim)",
|
|
277
|
+
strudel: ["gain"],
|
|
278
|
+
},
|
|
279
|
+
},
|
|
280
|
+
);
|
|
281
|
+
|
|
282
|
+
/** Simple rows first in the menu; the rest are listed under "advanced". */
|
|
283
|
+
export const STRING_SIMPLE_PARAMS: readonly string[] = Object.freeze([
|
|
284
|
+
"ring",
|
|
285
|
+
"bright",
|
|
286
|
+
"damp",
|
|
287
|
+
"pos",
|
|
288
|
+
"mute",
|
|
289
|
+
"buzz",
|
|
290
|
+
"body",
|
|
291
|
+
"sym",
|
|
292
|
+
]);
|
|
293
|
+
|
|
294
|
+
/** Parameter name for a Strudel alias (`decay` -> `ring`), or the name. */
|
|
295
|
+
export function stringParamName(word: string): string | undefined {
|
|
296
|
+
if (STRING_PARAMS[word]) return word;
|
|
297
|
+
for (const [name, spec] of Object.entries(STRING_PARAMS))
|
|
298
|
+
if (spec.strudel?.includes(word)) return name;
|
|
299
|
+
return undefined;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
/** String parameters with a `string-<param>` lane (read at note onsets). */
|
|
303
|
+
export const STRING_LANE_PARAMS: readonly Readonly<{
|
|
304
|
+
param: string;
|
|
305
|
+
spec: NumberParam;
|
|
306
|
+
}>[] = Object.freeze(
|
|
307
|
+
Object.entries(STRING_PARAMS)
|
|
308
|
+
.filter(
|
|
309
|
+
(entry): entry is [string, NumberParam] =>
|
|
310
|
+
entry[1].kind === "number" && entry[1].automate === true,
|
|
311
|
+
)
|
|
312
|
+
.map(([param, spec]) => Object.freeze({ param, spec })),
|
|
313
|
+
);
|
|
314
|
+
|
|
315
|
+
export type StringValues = Readonly<Record<string, number | string>>;
|
|
316
|
+
|
|
317
|
+
export type StringPreset = Readonly<{
|
|
318
|
+
doc: string;
|
|
319
|
+
styles: string;
|
|
320
|
+
values: StringValues;
|
|
321
|
+
}>;
|
|
322
|
+
|
|
323
|
+
// Prototype base (proto/strings/presets.ts): every preset starts here.
|
|
324
|
+
const BASE: StringValues = {
|
|
325
|
+
exciter: "pick",
|
|
326
|
+
ring: 3,
|
|
327
|
+
track: 0.5,
|
|
328
|
+
damp: 0.4,
|
|
329
|
+
stiff: 0,
|
|
330
|
+
pos: 0.15,
|
|
331
|
+
bright: 0.6,
|
|
332
|
+
noise: 0.04,
|
|
333
|
+
release: 0.1,
|
|
334
|
+
buzz: 0,
|
|
335
|
+
vel: 1,
|
|
336
|
+
pickup: 0,
|
|
337
|
+
body: "none",
|
|
338
|
+
size: 1,
|
|
339
|
+
sym: 0,
|
|
340
|
+
symtune: "open",
|
|
341
|
+
unison: 1,
|
|
342
|
+
detune: 0,
|
|
343
|
+
spread: 0,
|
|
344
|
+
oct: 0,
|
|
345
|
+
octbelow: 128,
|
|
346
|
+
voices: 6,
|
|
347
|
+
};
|
|
348
|
+
|
|
349
|
+
function preset(
|
|
350
|
+
doc: string,
|
|
351
|
+
styles: string,
|
|
352
|
+
values: StringValues,
|
|
353
|
+
): StringPreset {
|
|
354
|
+
return Object.freeze({
|
|
355
|
+
doc,
|
|
356
|
+
styles,
|
|
357
|
+
values: Object.freeze({ ...BASE, ...values }),
|
|
358
|
+
});
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
/**
|
|
362
|
+
* The plucked presets, ported from the prototype (stiffness rewritten from
|
|
363
|
+
* B = 0.002 s^2 to the 1e-6 x 400^stiff scale, `gain` is the per-preset
|
|
364
|
+
* level calibration). Frozen by a sha256 test: a retune gets a new name.
|
|
365
|
+
*/
|
|
366
|
+
export const STRING_PRESETS: Readonly<Record<string, StringPreset>> =
|
|
367
|
+
Object.freeze({
|
|
368
|
+
nylon: preset(
|
|
369
|
+
"nylon-string classical guitar, warm fingerstyle",
|
|
370
|
+
"bachata rhythm, bossa, Mitski, ballads",
|
|
371
|
+
{
|
|
372
|
+
ring: 3,
|
|
373
|
+
damp: 0.45,
|
|
374
|
+
stiff: 0.194,
|
|
375
|
+
pos: 0.15,
|
|
376
|
+
exciter: "finger",
|
|
377
|
+
bright: 0.55,
|
|
378
|
+
noise: 0.05,
|
|
379
|
+
release: 0.12,
|
|
380
|
+
body: "guitar",
|
|
381
|
+
sym: 0.1,
|
|
382
|
+
gain: 0.756,
|
|
383
|
+
},
|
|
384
|
+
),
|
|
385
|
+
steel: preset(
|
|
386
|
+
"steel-string acoustic, bright pick",
|
|
387
|
+
"Neil Young, folk, jangle, bachata segunda",
|
|
388
|
+
{
|
|
389
|
+
ring: 4,
|
|
390
|
+
damp: 0.25,
|
|
391
|
+
stiff: 0.425,
|
|
392
|
+
pos: 0.12,
|
|
393
|
+
exciter: "pick",
|
|
394
|
+
bright: 0.7,
|
|
395
|
+
noise: 0.08,
|
|
396
|
+
body: "steel",
|
|
397
|
+
sym: 0.12,
|
|
398
|
+
gain: 0.41,
|
|
399
|
+
},
|
|
400
|
+
),
|
|
401
|
+
electric: preset(
|
|
402
|
+
"clean electric guitar, bridge-ish pickup",
|
|
403
|
+
"angular rock, funk, jazz, Radiohead",
|
|
404
|
+
{
|
|
405
|
+
ring: 5,
|
|
406
|
+
damp: 0.3,
|
|
407
|
+
stiff: 0.381,
|
|
408
|
+
pos: 0.2,
|
|
409
|
+
exciter: "pick",
|
|
410
|
+
bright: 0.7,
|
|
411
|
+
pickup: 0.18,
|
|
412
|
+
gain: 0.211,
|
|
413
|
+
},
|
|
414
|
+
),
|
|
415
|
+
jangle: preset(
|
|
416
|
+
"electric 12-string, octave courses",
|
|
417
|
+
"the Smiths, jangle rock, Byrds",
|
|
418
|
+
{
|
|
419
|
+
ring: 5,
|
|
420
|
+
damp: 0.25,
|
|
421
|
+
stiff: 0.381,
|
|
422
|
+
pos: 0.18,
|
|
423
|
+
exciter: "pick",
|
|
424
|
+
bright: 0.8,
|
|
425
|
+
pickup: 0.15,
|
|
426
|
+
unison: 2,
|
|
427
|
+
detune: 0.08,
|
|
428
|
+
spread: 0.6,
|
|
429
|
+
oct: 0.75,
|
|
430
|
+
octbelow: 60,
|
|
431
|
+
voices: 12,
|
|
432
|
+
gain: 0.149,
|
|
433
|
+
},
|
|
434
|
+
),
|
|
435
|
+
ebass: preset("electric bass, fingers", "Motown, funk, soul, salsa", {
|
|
436
|
+
ring: 6,
|
|
437
|
+
track: 0.3,
|
|
438
|
+
damp: 0.35,
|
|
439
|
+
stiff: 0.806,
|
|
440
|
+
pos: 0.18,
|
|
441
|
+
exciter: "finger",
|
|
442
|
+
bright: 0.45,
|
|
443
|
+
release: 0.06,
|
|
444
|
+
pickup: 0.15,
|
|
445
|
+
voices: 4,
|
|
446
|
+
gain: 0.207,
|
|
447
|
+
}),
|
|
448
|
+
slap: preset(
|
|
449
|
+
"slap bass, thumb near the neck with fret clatter",
|
|
450
|
+
"funk, disco",
|
|
451
|
+
{
|
|
452
|
+
ring: 4,
|
|
453
|
+
track: 0.3,
|
|
454
|
+
damp: 0.2,
|
|
455
|
+
stiff: 0.806,
|
|
456
|
+
pos: 0.25,
|
|
457
|
+
exciter: "hammer",
|
|
458
|
+
bright: 0.95,
|
|
459
|
+
release: 0.05,
|
|
460
|
+
pickup: 0.15,
|
|
461
|
+
buzz: 0.12,
|
|
462
|
+
voices: 4,
|
|
463
|
+
gain: 0.198,
|
|
464
|
+
},
|
|
465
|
+
),
|
|
466
|
+
upright: preset(
|
|
467
|
+
"upright bass pizzicato",
|
|
468
|
+
"jazz, salsa tumbao, Louis Armstrong",
|
|
469
|
+
{
|
|
470
|
+
ring: 1.6,
|
|
471
|
+
track: 0.4,
|
|
472
|
+
damp: 0.55,
|
|
473
|
+
stiff: 0.696,
|
|
474
|
+
pos: 0.25,
|
|
475
|
+
exciter: "finger",
|
|
476
|
+
bright: 0.35,
|
|
477
|
+
release: 0.08,
|
|
478
|
+
body: "bass",
|
|
479
|
+
voices: 4,
|
|
480
|
+
gain: 0.686,
|
|
481
|
+
},
|
|
482
|
+
),
|
|
483
|
+
motown: preset(
|
|
484
|
+
"flatwound P-bass with a foam mute: short, round, thumpy",
|
|
485
|
+
"Motown, soul, 60s pop",
|
|
486
|
+
{
|
|
487
|
+
ring: 1.2,
|
|
488
|
+
track: 0.3,
|
|
489
|
+
damp: 0.7,
|
|
490
|
+
stiff: 0.806,
|
|
491
|
+
pos: 0.2,
|
|
492
|
+
exciter: "finger",
|
|
493
|
+
bright: 0.3,
|
|
494
|
+
release: 0.05,
|
|
495
|
+
pickup: 0.15,
|
|
496
|
+
mute: 0.3,
|
|
497
|
+
voices: 4,
|
|
498
|
+
gain: 0.227,
|
|
499
|
+
},
|
|
500
|
+
),
|
|
501
|
+
sitar: preset(
|
|
502
|
+
"sitar: jawari buzz and taraf sympathetic strings",
|
|
503
|
+
"raga, psychedelia",
|
|
504
|
+
{
|
|
505
|
+
ring: 5,
|
|
506
|
+
track: 0.3,
|
|
507
|
+
damp: 0.25,
|
|
508
|
+
stiff: 0.194,
|
|
509
|
+
pos: 0.08,
|
|
510
|
+
exciter: "pick",
|
|
511
|
+
bright: 0.8,
|
|
512
|
+
buzz: 0.6,
|
|
513
|
+
release: 0.4,
|
|
514
|
+
body: "gourd",
|
|
515
|
+
sym: 0.35,
|
|
516
|
+
symtune: "scale",
|
|
517
|
+
voices: 3,
|
|
518
|
+
gain: 0.403,
|
|
519
|
+
},
|
|
520
|
+
),
|
|
521
|
+
tanpura: preset(
|
|
522
|
+
"tanpura drone, open strings with strong jawari",
|
|
523
|
+
"raga drone, La Monte Young",
|
|
524
|
+
{
|
|
525
|
+
ring: 9,
|
|
526
|
+
track: 0.2,
|
|
527
|
+
damp: 0.2,
|
|
528
|
+
stiff: 0.098,
|
|
529
|
+
pos: 0.05,
|
|
530
|
+
exciter: "finger",
|
|
531
|
+
bright: 0.45,
|
|
532
|
+
buzz: 0.9,
|
|
533
|
+
release: 1.5,
|
|
534
|
+
body: "gourd",
|
|
535
|
+
size: 1.25,
|
|
536
|
+
voices: 4,
|
|
537
|
+
gain: 0.68,
|
|
538
|
+
},
|
|
539
|
+
),
|
|
540
|
+
harpsichord: preset(
|
|
541
|
+
"harpsichord 8'+8' (oct adds the 4', pos 0.04 is the lute stop)",
|
|
542
|
+
"Bach, baroque continuo",
|
|
543
|
+
{
|
|
544
|
+
ring: 4,
|
|
545
|
+
track: 0.6,
|
|
546
|
+
damp: 0.15,
|
|
547
|
+
stiff: 0.098,
|
|
548
|
+
pos: 0.12,
|
|
549
|
+
exciter: "pick",
|
|
550
|
+
bright: 0.9,
|
|
551
|
+
noise: 0.02,
|
|
552
|
+
release: 0.08,
|
|
553
|
+
vel: 0.05,
|
|
554
|
+
body: "board",
|
|
555
|
+
unison: 2,
|
|
556
|
+
detune: 0.03,
|
|
557
|
+
spread: 0.3,
|
|
558
|
+
voices: 24,
|
|
559
|
+
gain: 0.164,
|
|
560
|
+
},
|
|
561
|
+
),
|
|
562
|
+
lute: preset(
|
|
563
|
+
"renaissance lute, gut double courses",
|
|
564
|
+
"Dowland, early music, cantata continuo",
|
|
565
|
+
{
|
|
566
|
+
ring: 2.5,
|
|
567
|
+
damp: 0.5,
|
|
568
|
+
pos: 0.18,
|
|
569
|
+
exciter: "finger",
|
|
570
|
+
bright: 0.45,
|
|
571
|
+
body: "bowl",
|
|
572
|
+
unison: 2,
|
|
573
|
+
detune: 0.04,
|
|
574
|
+
spread: 0.3,
|
|
575
|
+
voices: 8,
|
|
576
|
+
gain: 0.541,
|
|
577
|
+
},
|
|
578
|
+
),
|
|
579
|
+
oud: preset("fretless oud with a plectrum", "maqam, Persian, Arabic", {
|
|
580
|
+
ring: 2.5,
|
|
581
|
+
damp: 0.4,
|
|
582
|
+
pos: 0.12,
|
|
583
|
+
exciter: "pick",
|
|
584
|
+
bright: 0.55,
|
|
585
|
+
body: "bowl",
|
|
586
|
+
size: 0.909,
|
|
587
|
+
unison: 2,
|
|
588
|
+
detune: 0.03,
|
|
589
|
+
spread: 0.2,
|
|
590
|
+
voices: 6,
|
|
591
|
+
gain: 0.338,
|
|
592
|
+
}),
|
|
593
|
+
setar: preset(
|
|
594
|
+
"Persian setar, nail-plucked, drone strings",
|
|
595
|
+
"Persian dastgah",
|
|
596
|
+
{
|
|
597
|
+
ring: 2.5,
|
|
598
|
+
damp: 0.3,
|
|
599
|
+
pos: 0.1,
|
|
600
|
+
exciter: "pick",
|
|
601
|
+
bright: 0.65,
|
|
602
|
+
body: "small",
|
|
603
|
+
sym: 0.15,
|
|
604
|
+
symtune: "drone",
|
|
605
|
+
voices: 4,
|
|
606
|
+
gain: 0.415,
|
|
607
|
+
},
|
|
608
|
+
),
|
|
609
|
+
tar: preset("Persian tar, skin-topped double bowl", "Persian dastgah", {
|
|
610
|
+
ring: 2.2,
|
|
611
|
+
damp: 0.25,
|
|
612
|
+
pos: 0.1,
|
|
613
|
+
exciter: "pick",
|
|
614
|
+
bright: 0.75,
|
|
615
|
+
body: "skin",
|
|
616
|
+
unison: 2,
|
|
617
|
+
detune: 0.03,
|
|
618
|
+
voices: 6,
|
|
619
|
+
gain: 0.28,
|
|
620
|
+
}),
|
|
621
|
+
santur: preset(
|
|
622
|
+
"santur: light mallets, four-string courses, undamped",
|
|
623
|
+
"Persian, celtic",
|
|
624
|
+
{
|
|
625
|
+
ring: 6,
|
|
626
|
+
track: 0.5,
|
|
627
|
+
damp: 0.25,
|
|
628
|
+
pos: 0.12,
|
|
629
|
+
exciter: "hammer",
|
|
630
|
+
bright: 0.7,
|
|
631
|
+
release: 3,
|
|
632
|
+
body: "board",
|
|
633
|
+
size: 0.833,
|
|
634
|
+
sym: 0.12,
|
|
635
|
+
symtune: "scale",
|
|
636
|
+
unison: 4,
|
|
637
|
+
detune: 0.1,
|
|
638
|
+
spread: 0.4,
|
|
639
|
+
voices: 24,
|
|
640
|
+
gain: 0.292,
|
|
641
|
+
},
|
|
642
|
+
),
|
|
643
|
+
dulcimer: preset(
|
|
644
|
+
"hammered dulcimer, softer hammers",
|
|
645
|
+
"celtic, Appalachian",
|
|
646
|
+
{
|
|
647
|
+
ring: 5,
|
|
648
|
+
damp: 0.35,
|
|
649
|
+
pos: 0.13,
|
|
650
|
+
exciter: "hammer",
|
|
651
|
+
bright: 0.55,
|
|
652
|
+
release: 2.5,
|
|
653
|
+
body: "board",
|
|
654
|
+
unison: 2,
|
|
655
|
+
detune: 0.06,
|
|
656
|
+
spread: 0.3,
|
|
657
|
+
voices: 24,
|
|
658
|
+
gain: 0.451,
|
|
659
|
+
},
|
|
660
|
+
),
|
|
661
|
+
koto: preset(
|
|
662
|
+
"koto, picks near the bridge; bends via note bend",
|
|
663
|
+
"Japanese, Ryuichi Sakamoto",
|
|
664
|
+
{
|
|
665
|
+
ring: 3,
|
|
666
|
+
damp: 0.4,
|
|
667
|
+
pos: 0.08,
|
|
668
|
+
exciter: "pick",
|
|
669
|
+
bright: 0.6,
|
|
670
|
+
release: 0.5,
|
|
671
|
+
body: "board",
|
|
672
|
+
size: 1.43,
|
|
673
|
+
voices: 13,
|
|
674
|
+
gain: 0.392,
|
|
675
|
+
},
|
|
676
|
+
),
|
|
677
|
+
harp: preset(
|
|
678
|
+
"concert harp, plucked mid-string, open strings ring",
|
|
679
|
+
"Debussy, celtic, cantatas, film",
|
|
680
|
+
{
|
|
681
|
+
ring: 5,
|
|
682
|
+
track: 0.7,
|
|
683
|
+
damp: 0.35,
|
|
684
|
+
pos: 0.45,
|
|
685
|
+
exciter: "finger",
|
|
686
|
+
bright: 0.4,
|
|
687
|
+
release: 1.2,
|
|
688
|
+
body: "board",
|
|
689
|
+
sym: 0.15,
|
|
690
|
+
symtune: "scale",
|
|
691
|
+
voices: 32,
|
|
692
|
+
gain: 0.692,
|
|
693
|
+
},
|
|
694
|
+
),
|
|
695
|
+
banjo: preset(
|
|
696
|
+
"5-string banjo, skin head, finger picks",
|
|
697
|
+
"bluegrass, folk",
|
|
698
|
+
{
|
|
699
|
+
ring: 2,
|
|
700
|
+
damp: 0.2,
|
|
701
|
+
pos: 0.1,
|
|
702
|
+
exciter: "pick",
|
|
703
|
+
bright: 0.85,
|
|
704
|
+
body: "skin",
|
|
705
|
+
voices: 5,
|
|
706
|
+
gain: 0.376,
|
|
707
|
+
},
|
|
708
|
+
),
|
|
709
|
+
tres: preset(
|
|
710
|
+
"Cuban tres, paired courses with octaves",
|
|
711
|
+
"salsa, son montuno",
|
|
712
|
+
{
|
|
713
|
+
ring: 2.5,
|
|
714
|
+
damp: 0.25,
|
|
715
|
+
pos: 0.12,
|
|
716
|
+
exciter: "pick",
|
|
717
|
+
bright: 0.75,
|
|
718
|
+
body: "small",
|
|
719
|
+
unison: 2,
|
|
720
|
+
detune: 0.03,
|
|
721
|
+
oct: 0.5,
|
|
722
|
+
voices: 6,
|
|
723
|
+
gain: 0.238,
|
|
724
|
+
},
|
|
725
|
+
),
|
|
726
|
+
requinto: preset(
|
|
727
|
+
"bachata requinto, small bright steel guitar",
|
|
728
|
+
"bachata lead",
|
|
729
|
+
{
|
|
730
|
+
ring: 3,
|
|
731
|
+
damp: 0.2,
|
|
732
|
+
stiff: 0.329,
|
|
733
|
+
pos: 0.1,
|
|
734
|
+
exciter: "pick",
|
|
735
|
+
bright: 0.85,
|
|
736
|
+
body: "small",
|
|
737
|
+
voices: 6,
|
|
738
|
+
gain: 0.36,
|
|
739
|
+
},
|
|
740
|
+
),
|
|
741
|
+
});
|
|
742
|
+
|
|
743
|
+
/** Preset names in table order. */
|
|
744
|
+
export const STRING_PRESET_NAMES: readonly string[] = Object.freeze(
|
|
745
|
+
Object.keys(STRING_PRESETS),
|
|
746
|
+
);
|
|
747
|
+
|
|
748
|
+
/** The preset a bare `string: {}` plays. */
|
|
749
|
+
export const DEFAULT_STRING_PRESET = "nylon";
|
|
750
|
+
|
|
751
|
+
/** Words that pick a preset (resolved before the preset table). */
|
|
752
|
+
export const STRING_ALIASES: Readonly<Record<string, string>> = Object.freeze({
|
|
753
|
+
guitar: "steel",
|
|
754
|
+
acoustic: "steel",
|
|
755
|
+
classical: "nylon",
|
|
756
|
+
"12string": "jangle",
|
|
757
|
+
bassguitar: "ebass",
|
|
758
|
+
fender: "ebass",
|
|
759
|
+
doublebass: "upright",
|
|
760
|
+
cembalo: "harpsichord",
|
|
761
|
+
hammered: "dulcimer",
|
|
762
|
+
sehtar: "setar",
|
|
763
|
+
});
|
|
764
|
+
|
|
765
|
+
/** A preset name for a word or alias, or undefined. */
|
|
766
|
+
export function stringPresetName(word: string): string | undefined {
|
|
767
|
+
const name = STRING_ALIASES[word] ?? word;
|
|
768
|
+
return STRING_PRESETS[name] ? name : undefined;
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
export function isStringTrack(
|
|
772
|
+
track: Readonly<{ instrument: string; string?: TrackString }>,
|
|
773
|
+
): boolean {
|
|
774
|
+
return track.instrument === STRING_INSTRUMENT && track.string !== undefined;
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
/**
|
|
778
|
+
* Validates `track.string`: a known preset, known parameter names (Strudel
|
|
779
|
+
* aliases are rewritten), values in range. Keys come back preset first,
|
|
780
|
+
* then in table order, so equal documents print equally. `{}` is kept: it
|
|
781
|
+
* is the marker that turns the engine on with the default preset.
|
|
782
|
+
*/
|
|
783
|
+
export function normalizeString(input: unknown): TrackString | undefined {
|
|
784
|
+
if (input === undefined || input === null) return undefined;
|
|
785
|
+
if (!isRecord(input))
|
|
786
|
+
throw new FxValidationError("track string must be an object or null");
|
|
787
|
+
const values: Record<string, unknown> = {};
|
|
788
|
+
let presetName: string | undefined;
|
|
789
|
+
for (const [key, value] of Object.entries(input)) {
|
|
790
|
+
if (value === undefined || value === null) continue;
|
|
791
|
+
if (key === "preset") {
|
|
792
|
+
const name =
|
|
793
|
+
typeof value === "string" ? stringPresetName(value) : undefined;
|
|
794
|
+
if (!name)
|
|
795
|
+
throw new FxValidationError(
|
|
796
|
+
`string preset must be one of ${STRING_PRESET_NAMES.join(", ")}`,
|
|
797
|
+
);
|
|
798
|
+
presetName = name;
|
|
799
|
+
continue;
|
|
800
|
+
}
|
|
801
|
+
const name = stringParamName(key);
|
|
802
|
+
if (!name)
|
|
803
|
+
throw new FxValidationError(
|
|
804
|
+
`string has no parameter "${key.slice(0, 32)}"`,
|
|
805
|
+
);
|
|
806
|
+
values[name] = value;
|
|
807
|
+
}
|
|
808
|
+
const out: Record<string, number | string> = {};
|
|
809
|
+
if (presetName) out.preset = presetName;
|
|
810
|
+
for (const [name, spec] of Object.entries(STRING_PARAMS)) {
|
|
811
|
+
if (values[name] === undefined) continue;
|
|
812
|
+
out[name] = normalizeParam(spec, values[name], `string ${name}`) as
|
|
813
|
+
number | string;
|
|
814
|
+
}
|
|
815
|
+
return Object.freeze(out);
|
|
816
|
+
}
|
|
817
|
+
|
|
818
|
+
/** The preset a track's string settings play. */
|
|
819
|
+
export function stringPresetOf(settings: TrackString | undefined): string {
|
|
820
|
+
const name = settings?.preset;
|
|
821
|
+
return typeof name === "string" && STRING_PRESETS[name]
|
|
822
|
+
? name
|
|
823
|
+
: DEFAULT_STRING_PRESET;
|
|
824
|
+
}
|
|
825
|
+
|
|
826
|
+
/** Every parameter's value: overrides, then the preset, then the default. */
|
|
827
|
+
export function resolveString(settings: TrackString | undefined): StringValues {
|
|
828
|
+
const presetValues = STRING_PRESETS[stringPresetOf(settings)]!.values;
|
|
829
|
+
const out: Record<string, number | string> = {};
|
|
830
|
+
for (const [name, spec] of Object.entries(STRING_PARAMS)) {
|
|
831
|
+
const own = settings?.[name];
|
|
832
|
+
out[name] =
|
|
833
|
+
own !== undefined && typeof own !== "boolean"
|
|
834
|
+
? own
|
|
835
|
+
: (presetValues[name] ?? (spec.default as number | string));
|
|
836
|
+
}
|
|
837
|
+
return Object.freeze(out);
|
|
838
|
+
}
|
|
839
|
+
|
|
840
|
+
/** Stable text of the preset table (its sha256 is pinned by a test). */
|
|
841
|
+
export function stringPresetTableText(): string {
|
|
842
|
+
return JSON.stringify(
|
|
843
|
+
STRING_PRESET_NAMES.map((name) => [name, STRING_PRESETS[name]!.values]),
|
|
844
|
+
);
|
|
845
|
+
}
|