@hraness/dawg 0.6.0 → 0.7.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 +126 -0
- package/DAWG.md +655 -141
- package/README.md +27 -25
- package/core/autotune.ts +1119 -0
- package/core/chords.ts +759 -23
- package/core/clips.ts +499 -0
- package/core/diff.ts +182 -99
- package/core/expression.ts +158 -6
- package/core/fx.ts +354 -32
- package/core/granular.ts +91 -0
- package/core/instruments.ts +180 -0
- package/core/keys.ts +573 -4
- package/core/loop.ts +5 -0
- package/core/lyrics.ts +297 -0
- package/core/master.ts +3 -3
- package/core/resonators.ts +306 -4
- package/core/routing.ts +165 -0
- package/core/score.ts +1181 -16
- package/core/sdk/eval-child.ts +7 -2
- package/core/sdk/eval.ts +35 -6
- package/core/sdk/print.ts +354 -6
- package/core/sdk/sync-lyrics.ts +49 -0
- package/core/sdk/v1.ts +3160 -69
- package/core/sections.ts +298 -24
- package/core/sing.ts +815 -0
- package/core/strings.ts +237 -2
- package/core/style-provenance.ts +80 -0
- package/core/styles/africa-mena-southasia.ts +2893 -0
- package/core/styles/americas.ts +3810 -0
- package/core/styles/art.ts +4993 -0
- package/core/styles/base.ts +123 -0
- package/core/styles/cycles.ts +106 -0
- package/core/styles/electronic.ts +2723 -0
- package/core/styles/europe-asia-pacific.ts +2838 -0
- package/core/styles/excerpt.ts +29 -0
- package/core/styles/gamelan.ts +283 -0
- package/core/styles/generate.ts +2199 -0
- package/core/styles/index.ts +515 -0
- package/core/styles/parts.ts +106 -0
- package/core/styles/pop.ts +3189 -0
- package/core/styles/rock.ts +2993 -0
- package/core/styles/roots.ts +4175 -0
- package/core/styles/schema.ts +429 -0
- package/core/styles/taxonomy.ts +940 -0
- package/core/styles/validate.ts +528 -0
- package/core/tempo.ts +32 -2
- package/core/tuning.ts +19 -3
- package/core/vocoder.ts +524 -0
- package/core/winds.ts +652 -0
- package/guides/agent.md +29 -0
- package/guides/arrange.md +30 -0
- package/guides/audition.md +20 -14
- package/guides/automation.md +12 -6
- package/guides/chords.md +16 -14
- package/guides/effects.md +17 -15
- package/guides/faders.md +20 -16
- package/guides/files.md +13 -9
- package/guides/getting-started.md +15 -11
- package/guides/keys.md +19 -15
- package/guides/media.md +17 -12
- package/guides/mix.md +12 -6
- package/guides/music.md +23 -8
- package/guides/notes.md +15 -9
- package/guides/performance.md +15 -10
- package/guides/play.md +21 -13
- package/guides/project.md +25 -8
- package/guides/providers.md +20 -14
- package/guides/resample.md +32 -0
- package/guides/rhythm.md +17 -13
- package/guides/sessions.md +17 -7
- package/guides/show-me.md +31 -0
- package/guides/sound.md +25 -9
- package/guides/sounds.md +15 -12
- package/guides/styles.md +31 -0
- package/guides/tempo.md +15 -10
- package/guides/tracks.md +15 -10
- package/guides/tuning.md +32 -0
- package/guides/voice.md +31 -0
- package/guides/web-search.md +18 -8
- package/native/prebuilt/darwin-arm64/libdawg_sink.dylib +0 -0
- package/native/prebuilt/darwin-x64/libdawg_sink.dylib +0 -0
- package/native/prebuilt/linux-arm64/libdawg_sink.so +0 -0
- package/native/prebuilt/linux-x64/libdawg_sink.so +0 -0
- package/native/prebuilt/manifest.json +21 -0
- package/package.json +5 -2
- package/src/agent/agent.ts +131 -13
- package/src/agent/calibration-tools.ts +53 -0
- package/src/agent/chord-tools.ts +153 -1
- package/src/agent/clip-tools.ts +453 -0
- package/src/agent/command-agent.ts +369 -0
- package/src/agent/drum-tools.ts +2 -2
- package/src/agent/expression-tools.ts +91 -0
- package/src/agent/gateway.ts +246 -60
- package/src/agent/models.ts +53 -12
- package/src/agent/ops.ts +15 -1
- package/src/agent/pack-tools.ts +1 -1
- package/src/agent/planner.ts +13 -0
- package/src/agent/portable-schema.ts +80 -0
- package/src/agent/preview-tool.ts +5 -1
- package/src/agent/provider.ts +22 -8
- package/src/agent/resample-tool.ts +131 -0
- package/src/agent/rhythm-tools.ts +1 -1
- package/src/agent/section-tools.ts +1 -1
- package/src/agent/show-me.ts +497 -0
- package/src/agent/steer.ts +15 -0
- package/src/agent/style-tools.ts +217 -0
- package/src/agent/tool-error.ts +12 -0
- package/src/agent/tools.ts +307 -37
- package/src/agent/usage.ts +2 -2
- package/src/agent/voice-tools.ts +925 -0
- package/src/agent/xcb-agent.ts +16 -6
- package/src/argv.ts +38 -0
- package/src/audio/analysis.ts +253 -0
- package/src/audio/arrange.ts +58 -3
- package/src/audio/autotune-engine.ts +101 -0
- package/src/audio/autotune.ts +640 -0
- package/src/audio/clips.ts +240 -0
- package/src/audio/doctor.ts +86 -0
- package/src/audio/dsp/bandbank.ts +138 -0
- package/src/audio/dsp/envelope.ts +171 -0
- package/src/audio/dsp/follow.ts +120 -0
- package/src/audio/dsp/formant.ts +427 -0
- package/src/audio/dsp/glottal.ts +243 -0
- package/src/audio/dsp/interp.ts +7 -2
- package/src/audio/dsp/lpc.ts +50 -0
- package/src/audio/dsp/periodicity.ts +59 -0
- package/src/audio/dsp/pitch.ts +995 -0
- package/src/audio/dsp/psola.ts +199 -0
- package/src/audio/dsp/shift.ts +256 -0
- package/src/audio/effects/chain.ts +21 -4
- package/src/audio/effects/common.ts +57 -0
- package/src/audio/effects/convolution.ts +113 -5
- package/src/audio/effects/filter.ts +48 -69
- package/src/audio/effects/formant.ts +263 -0
- package/src/audio/effects/gaze.ts +354 -0
- package/src/audio/engine.ts +206 -33
- package/src/audio/fit.ts +80 -3
- package/src/audio/granular.ts +352 -21
- package/src/audio/instrument-check.ts +67 -47
- package/src/audio/instruments.ts +36 -3
- package/src/audio/keys/calibration.ts +56 -0
- package/src/audio/keys/electric.ts +427 -0
- package/src/audio/keys/engine.ts +156 -20
- package/src/audio/keys/organ.ts +1335 -0
- package/src/audio/keys/piano.ts +72 -8
- package/src/audio/keys/sympathetic.ts +127 -0
- package/src/audio/kits.ts +135 -6
- package/src/audio/live-worker.ts +3 -1
- package/src/audio/live.ts +247 -26
- package/src/audio/native.ts +615 -0
- package/src/audio/preview.ts +45 -3
- package/src/audio/render-worker.ts +2 -0
- package/src/audio/renderer.ts +2 -0
- package/src/audio/resample.ts +286 -0
- package/src/audio/resonators.ts +10 -2
- package/src/audio/sampler.ts +252 -24
- package/src/audio/samples.ts +46 -3
- package/src/audio/sing/analysis.ts +193 -0
- package/src/audio/sing/engine.ts +949 -0
- package/src/audio/strings/body.ts +29 -16
- package/src/audio/strings/bow.ts +699 -0
- package/src/audio/strings/engine.ts +274 -22
- package/src/audio/strings/measure.test-helpers.ts +2 -0
- package/src/audio/synth/oscillators.ts +31 -21
- package/src/audio/synth/voice.ts +34 -1
- package/src/audio/vocoder/bank.ts +314 -0
- package/src/audio/vocoder/carrier.ts +165 -0
- package/src/audio/vocoder/control.ts +68 -0
- package/src/audio/vocoder/detect.ts +50 -0
- package/src/audio/vocoder/index.ts +304 -0
- package/src/audio/vocoder/talkbox.ts +143 -0
- package/src/audio/wav.ts +608 -85
- package/src/audio/winds/engine.ts +306 -0
- package/src/audio/winds/filters.ts +153 -0
- package/src/audio/winds/pitch.ts +104 -0
- package/src/audio/winds/trim.ts +104 -0
- package/src/audio/winds/trims.ts +917 -0
- package/src/audio/winds/trims1.ts +297 -0
- package/src/audio/winds/voice.ts +492 -0
- package/src/auth/cli.ts +38 -36
- package/src/auth/credentials.ts +30 -1
- package/src/auth/login.ts +15 -9
- package/src/auth/tui.ts +19 -10
- package/src/commands/arrange.ts +44 -29
- package/src/commands/autotune.ts +421 -0
- package/src/commands/calibration.ts +74 -0
- package/src/commands/clips.ts +887 -0
- package/src/commands/drums.ts +3 -2
- package/src/commands/edit.ts +11 -4
- package/src/commands/expression.ts +104 -23
- package/src/commands/formant.ts +221 -0
- package/src/commands/fx.ts +105 -33
- package/src/commands/grammar.ts +558 -0
- package/src/commands/granular.ts +7 -2
- package/src/commands/help.ts +652 -339
- package/src/commands/history.ts +18 -0
- package/src/commands/keys.ts +293 -17
- package/src/commands/modal.ts +67 -4
- package/src/commands/music.ts +1 -1
- package/src/commands/nearest.ts +53 -0
- package/src/commands/pack.ts +9 -2
- package/src/commands/param-range.ts +56 -0
- package/src/commands/parses.ts +130 -0
- package/src/commands/progression.ts +170 -0
- package/src/commands/resample.ts +281 -0
- package/src/commands/rhythm.ts +3 -0
- package/src/commands/rig.ts +8 -25
- package/src/commands/sample.ts +49 -2
- package/src/commands/shift.ts +119 -0
- package/src/commands/sing.ts +478 -0
- package/src/commands/string.ts +28 -2
- package/src/commands/strum.ts +485 -0
- package/src/commands/style.ts +415 -0
- package/src/commands/time.ts +6 -3
- package/src/commands/tuning.ts +3 -3
- package/src/commands/vocal-pitch.ts +616 -0
- package/src/commands/vocal.ts +147 -0
- package/src/commands/vocoder.ts +627 -0
- package/src/commands/wind.ts +244 -0
- package/src/fs/durable.ts +50 -0
- package/src/lang/glossary.ts +493 -0
- package/src/launch-args.ts +163 -0
- package/src/main.ts +1521 -255
- package/src/media/cli.ts +20 -3
- package/src/media/import.ts +3 -1
- package/src/project/check.ts +26 -3
- package/src/project/clip-pins.ts +72 -0
- package/src/project/init.ts +23 -8
- package/src/project/sync.ts +418 -86
- package/src/render.ts +36 -1
- package/src/session/daemon.ts +3 -0
- package/src/session/meta.ts +14 -0
- package/src/session/origin.ts +154 -0
- package/src/session/port.ts +22 -4
- package/src/session/presence.ts +50 -7
- package/src/session/protocol.ts +5 -1
- package/src/session/rebase.ts +18 -5
- package/src/session/receipt.ts +258 -0
- package/src/session/store.ts +65 -43
- package/src/tui/arrange-menu.ts +65 -31
- package/src/tui/audition.ts +1 -1
- package/src/tui/euclid.ts +18 -13
- package/src/tui/fader.ts +228 -41
- package/src/tui/granular-menu.ts +2 -4
- package/src/tui/menu-clips.ts +297 -0
- package/src/tui/menu-time.ts +20 -13
- package/src/tui/menu-voice.ts +405 -0
- package/src/tui/menu.ts +1377 -193
- package/src/tui/modal-menu.ts +38 -11
- package/src/tui/performance-menu.ts +46 -1
- package/src/tui/play-chords.ts +108 -5
- package/src/tui/play-mode.ts +16 -1
- package/src/tui/play-session.ts +81 -16
- package/src/tui/sing-menu.ts +278 -0
- package/src/tui/style-menu.ts +104 -0
- package/src/tui/vocoder-menu.ts +244 -0
- package/src/tui/wind-menu.ts +144 -0
- package/src/version.ts +8 -0
- package/src/web/fetch.ts +115 -29
- package/tui/activity.ts +180 -9
- package/tui/app.ts +274 -40
- package/tui/clip-row.ts +132 -0
- package/tui/delight.ts +144 -0
- package/tui/drawer.ts +70 -22
- package/tui/frame-gate.ts +76 -0
- package/tui/grammar.ts +112 -63
- package/tui/guide.ts +42 -4
- package/tui/highway.ts +269 -25
- package/tui/hints.ts +192 -0
- package/tui/input.ts +60 -9
- package/tui/keys.ts +1 -1
- package/tui/play-strip.ts +68 -14
- package/tui/prompt.ts +1 -1
- package/tui/screen.ts +144 -14
- package/tui/theme.ts +27 -0
package/core/keys.ts
CHANGED
|
@@ -35,6 +35,70 @@ export function isPianoFamily(
|
|
|
35
35
|
return (PIANO_FAMILIES as readonly string[]).includes(instrument ?? "");
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
+
/**
|
|
39
|
+
* Electric keys (0.6.1, keys-electric): the Rhodes-style tine piano, the
|
|
40
|
+
* Wurlitzer-style reed piano and the clavinet. They share `Track.keys` and
|
|
41
|
+
* the `KEYS_PARAMS` table with the pianos; `KEYS_PARAM_FAMILIES` says which
|
|
42
|
+
* rows each family reads.
|
|
43
|
+
*/
|
|
44
|
+
export const ELECTRIC_FAMILIES = Object.freeze([
|
|
45
|
+
"epiano",
|
|
46
|
+
"wurli",
|
|
47
|
+
"clav",
|
|
48
|
+
] as const);
|
|
49
|
+
|
|
50
|
+
export type ElectricFamily = (typeof ELECTRIC_FAMILIES)[number];
|
|
51
|
+
|
|
52
|
+
export function isElectricFamily(
|
|
53
|
+
instrument: string | undefined,
|
|
54
|
+
): instrument is ElectricFamily {
|
|
55
|
+
return (ELECTRIC_FAMILIES as readonly string[]).includes(instrument ?? "");
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Why una corda would not sound on this track (0.6.1), or undefined when it
|
|
60
|
+
* does: only the modeled pianos with a `keys` object read `softPedal`.
|
|
61
|
+
*/
|
|
62
|
+
export function softPedalNote(
|
|
63
|
+
track: Readonly<{ id: string; instrument: string; keys?: unknown }>,
|
|
64
|
+
): string | undefined {
|
|
65
|
+
if (!isPianoFamily(track.instrument))
|
|
66
|
+
return `${track.id} is ${track.instrument}; una corda acts on the modeled pianos (${PIANO_FAMILIES.join(" ")})`;
|
|
67
|
+
if (track.keys === undefined)
|
|
68
|
+
return `${track.id} has no modeled piano yet; keys preset ${track.instrument} first`;
|
|
69
|
+
return undefined;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** The organ families (f061-organ): tonewheel, combo and pipe organs. */
|
|
73
|
+
export const ORGAN_FAMILIES = Object.freeze([
|
|
74
|
+
"tonewheel",
|
|
75
|
+
"combo",
|
|
76
|
+
"pipe",
|
|
77
|
+
] as const);
|
|
78
|
+
|
|
79
|
+
export type OrganFamily = (typeof ORGAN_FAMILIES)[number];
|
|
80
|
+
|
|
81
|
+
export function isOrganFamily(
|
|
82
|
+
instrument: string | undefined,
|
|
83
|
+
): instrument is OrganFamily {
|
|
84
|
+
return (ORGAN_FAMILIES as readonly string[]).includes(instrument ?? "");
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/** Every keys family (the instruments that read `Track.keys`). */
|
|
88
|
+
export const KEYS_FAMILIES = Object.freeze([
|
|
89
|
+
...PIANO_FAMILIES,
|
|
90
|
+
...ELECTRIC_FAMILIES,
|
|
91
|
+
...ORGAN_FAMILIES,
|
|
92
|
+
] as const);
|
|
93
|
+
|
|
94
|
+
export type KeysFamily = (typeof KEYS_FAMILIES)[number];
|
|
95
|
+
|
|
96
|
+
export function isKeysFamily(
|
|
97
|
+
instrument: string | undefined,
|
|
98
|
+
): instrument is KeysFamily {
|
|
99
|
+
return (KEYS_FAMILIES as readonly string[]).includes(instrument ?? "");
|
|
100
|
+
}
|
|
101
|
+
|
|
38
102
|
/** Body EQ voicings (soundboard and case colour). */
|
|
39
103
|
export const PIANO_BODIES = Object.freeze([
|
|
40
104
|
"grand",
|
|
@@ -157,8 +221,316 @@ export const KEYS_PARAMS: Readonly<Record<string, ParamSpec>> = Object.freeze({
|
|
|
157
221
|
doc: "pitch wobble depth in semitones",
|
|
158
222
|
strudel: ["vibmod", "vmod"],
|
|
159
223
|
},
|
|
224
|
+
// keys-electric (0.6.1): epiano, wurli and clav rows.
|
|
225
|
+
bark: unit(0.35, "pickup drive: growl when played hard (epiano, wurli)"),
|
|
226
|
+
bell: unit(0.5, "tine or reed bell ping (epiano, wurli)"),
|
|
227
|
+
tone: {
|
|
228
|
+
kind: "number",
|
|
229
|
+
min: 0,
|
|
230
|
+
max: 12000,
|
|
231
|
+
default: 0,
|
|
232
|
+
step: 100,
|
|
233
|
+
unit: "Hz",
|
|
234
|
+
automate: true,
|
|
235
|
+
doc: "output low-pass of the electric keys; 0 is off",
|
|
236
|
+
},
|
|
237
|
+
pickup: {
|
|
238
|
+
kind: "enum",
|
|
239
|
+
values: ["neck", "bridge", "both", "out"],
|
|
240
|
+
default: "both",
|
|
241
|
+
doc: "clav pickup switch; out is both pickups out of phase",
|
|
242
|
+
},
|
|
243
|
+
mute: unit(0, "clav mute slider: damps the upper partials"),
|
|
244
|
+
vibe: unit(
|
|
245
|
+
0,
|
|
246
|
+
"suitcase stereo vibrato depth: antiphase left/right pan (epiano)",
|
|
247
|
+
true,
|
|
248
|
+
),
|
|
249
|
+
vibehz: {
|
|
250
|
+
kind: "number",
|
|
251
|
+
min: 0.5,
|
|
252
|
+
max: 12,
|
|
253
|
+
default: 4,
|
|
254
|
+
step: 0.25,
|
|
255
|
+
unit: "Hz",
|
|
256
|
+
doc: "suitcase vibrato rate (epiano)",
|
|
257
|
+
},
|
|
258
|
+
trem: unit(
|
|
259
|
+
0,
|
|
260
|
+
"tremolo depth: reed piano at 5.6 Hz (wurli), pipe tremulant (pipe)",
|
|
261
|
+
true,
|
|
262
|
+
),
|
|
263
|
+
// keys-electric (0.6.1): piano sympathetic resonance (pianos only; 0 keeps
|
|
264
|
+
// 0.6.0 renders byte-identical).
|
|
265
|
+
sym: unit(
|
|
266
|
+
0,
|
|
267
|
+
"sympathetic string resonance while the sustain pedal is down; 0 is off",
|
|
268
|
+
),
|
|
269
|
+
// organ (f061-organ): tonewheel, combo and pipe rows (ORGAN_ROWS says
|
|
270
|
+
// which family reads each). The drawbars, registers and stops strings
|
|
271
|
+
// live in ORGAN_TEXT (ParamSpec has no text kind).
|
|
272
|
+
perc: {
|
|
273
|
+
kind: "enum",
|
|
274
|
+
values: ["off", "2nd", "3rd"],
|
|
275
|
+
default: "off",
|
|
276
|
+
doc: "tonewheel percussion harmonic, single-trigger; on mutes the 1' bar",
|
|
277
|
+
},
|
|
278
|
+
percdecay: {
|
|
279
|
+
kind: "enum",
|
|
280
|
+
values: ["fast", "slow"],
|
|
281
|
+
default: "fast",
|
|
282
|
+
doc: "tonewheel percussion decay (fast 0.6 s, slow 1.8 s)",
|
|
283
|
+
},
|
|
284
|
+
click: unit(0.5, "tonewheel key click (contact bounce)"),
|
|
285
|
+
scanner: {
|
|
286
|
+
kind: "enum",
|
|
287
|
+
values: ["off", "v1", "v2", "v3", "c1", "c2", "c3"],
|
|
288
|
+
default: "c3",
|
|
289
|
+
doc: "tonewheel scanner vibrato (v1-v3) or chorus (c1-c3)",
|
|
290
|
+
},
|
|
291
|
+
drive: unit(0.15, "organ preamp overdrive", true),
|
|
292
|
+
rotary: {
|
|
293
|
+
kind: "enum",
|
|
294
|
+
values: ["slow", "fast", "stop"],
|
|
295
|
+
default: "slow",
|
|
296
|
+
doc: "rotary speaker at the start (lane keys-rotary: 0 stop, 1 slow, 2 fast)",
|
|
297
|
+
},
|
|
298
|
+
voice: {
|
|
299
|
+
kind: "enum",
|
|
300
|
+
values: ["flute", "reed", "bright"],
|
|
301
|
+
default: "reed",
|
|
302
|
+
doc: "combo register timbre: filtered square, reed pulse, bright saw",
|
|
303
|
+
},
|
|
304
|
+
chiff: unit(0.4, "pipe speech: the noise at each flue pipe's attack"),
|
|
305
|
+
wind: unit(0.3, "pipe wind instability (slow pitch and level drift)"),
|
|
306
|
+
percvol: {
|
|
307
|
+
kind: "enum",
|
|
308
|
+
values: ["normal", "soft"],
|
|
309
|
+
default: "normal",
|
|
310
|
+
doc: "tonewheel percussion volume: soft is about 6 dB down and keeps the drawbars at full level",
|
|
311
|
+
},
|
|
160
312
|
});
|
|
161
313
|
|
|
314
|
+
/**
|
|
315
|
+
* The families each keys parameter applies to (menus and listings show a
|
|
316
|
+
* track only its family's rows). A parameter missing here is a piano row.
|
|
317
|
+
*/
|
|
318
|
+
export const KEYS_PARAM_FAMILIES: Readonly<
|
|
319
|
+
Record<string, readonly KeysFamily[]>
|
|
320
|
+
> = Object.freeze({
|
|
321
|
+
hardness: KEYS_FAMILIES,
|
|
322
|
+
touch: KEYS_FAMILIES,
|
|
323
|
+
decay: KEYS_FAMILIES,
|
|
324
|
+
release: KEYS_FAMILIES,
|
|
325
|
+
width: KEYS_FAMILIES,
|
|
326
|
+
vib: KEYS_FAMILIES,
|
|
327
|
+
vibmod: KEYS_FAMILIES,
|
|
328
|
+
bark: ["epiano", "wurli"],
|
|
329
|
+
bell: ["epiano", "wurli"],
|
|
330
|
+
tone: ELECTRIC_FAMILIES,
|
|
331
|
+
pickup: ["clav"],
|
|
332
|
+
mute: ["clav"],
|
|
333
|
+
vibe: ["epiano"],
|
|
334
|
+
vibehz: ["epiano"],
|
|
335
|
+
trem: ["wurli", "pipe"],
|
|
336
|
+
// organ (f061-organ): the organ rows; organs list theirs in ORGAN_ROWS.
|
|
337
|
+
perc: ["tonewheel"],
|
|
338
|
+
percdecay: ["tonewheel"],
|
|
339
|
+
percvol: ["tonewheel"],
|
|
340
|
+
click: ["tonewheel"],
|
|
341
|
+
scanner: ["tonewheel"],
|
|
342
|
+
drive: ["tonewheel", "combo"],
|
|
343
|
+
rotary: ["tonewheel", "combo"],
|
|
344
|
+
voice: ["combo"],
|
|
345
|
+
chiff: ["pipe"],
|
|
346
|
+
wind: ["pipe"],
|
|
347
|
+
});
|
|
348
|
+
|
|
349
|
+
/** The keys parameters a family reads, in `KEYS_PARAMS` order. */
|
|
350
|
+
export function keysParamsFor(instrument: string | undefined): string[] {
|
|
351
|
+
if (isOrganFamily(instrument)) return [...ORGAN_ROWS[instrument]];
|
|
352
|
+
const family = isKeysFamily(instrument) ? instrument : "grand";
|
|
353
|
+
return Object.keys(KEYS_PARAMS).filter((name) => {
|
|
354
|
+
const families = KEYS_PARAM_FAMILIES[name];
|
|
355
|
+
return families ? families.includes(family) : isPianoFamily(family);
|
|
356
|
+
});
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
/**
|
|
360
|
+
* Organ text rows (f061-organ), stored as strings after the KEYS_PARAMS
|
|
361
|
+
* values: `drawbars` nine digits 0-8 (16' 5⅓' 8' 4' 2⅔' 2' 1⅗' 1⅓' 1'),
|
|
362
|
+
* `registers` five digits 0-8 (combo 16' 8' 4' 2⅔' 2'), `stops` pipe stop
|
|
363
|
+
* names and registrations separated by spaces.
|
|
364
|
+
*/
|
|
365
|
+
export const ORGAN_TEXT = Object.freeze({
|
|
366
|
+
drawbars: Object.freeze({
|
|
367
|
+
digits: 9,
|
|
368
|
+
default: "888000000",
|
|
369
|
+
doc: "tonewheel drawbars, nine digits 0-8: 16' 5⅓' 8' 4' 2⅔' 2' 1⅗' 1⅓' 1'",
|
|
370
|
+
}),
|
|
371
|
+
registers: Object.freeze({
|
|
372
|
+
digits: 5,
|
|
373
|
+
default: "08800",
|
|
374
|
+
doc: "combo registers, five digits 0-8: 16' 8' 4' 2⅔' 2'",
|
|
375
|
+
}),
|
|
376
|
+
stops: Object.freeze({
|
|
377
|
+
digits: 0,
|
|
378
|
+
default: "principal8 octave4 fifteenth2 mixture",
|
|
379
|
+
doc: "pipe stops (or a registration: plenum flutes cornet reeds strings full)",
|
|
380
|
+
}),
|
|
381
|
+
} as const);
|
|
382
|
+
|
|
383
|
+
export type OrganTextParam = keyof typeof ORGAN_TEXT;
|
|
384
|
+
|
|
385
|
+
export function isOrganText(name: string): name is OrganTextParam {
|
|
386
|
+
return Object.prototype.hasOwnProperty.call(ORGAN_TEXT, name);
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
/** Drawbar footages in drawbar order (labels for the menu and docs). */
|
|
390
|
+
export const DRAWBAR_FEET = Object.freeze([
|
|
391
|
+
"16'",
|
|
392
|
+
"5⅓'",
|
|
393
|
+
"8'",
|
|
394
|
+
"4'",
|
|
395
|
+
"2⅔'",
|
|
396
|
+
"2'",
|
|
397
|
+
"1⅗'",
|
|
398
|
+
"1⅓'",
|
|
399
|
+
"1'",
|
|
400
|
+
]);
|
|
401
|
+
|
|
402
|
+
/** Combo register footages in register order. */
|
|
403
|
+
export const REGISTER_FEET = Object.freeze(["16'", "8'", "4'", "2⅔'", "2'"]);
|
|
404
|
+
|
|
405
|
+
/** Pipe stops, low to high pitch with the reeds last. */
|
|
406
|
+
export const PIPE_STOPS = Object.freeze([
|
|
407
|
+
"subbass16",
|
|
408
|
+
"bourdon16",
|
|
409
|
+
"principal8",
|
|
410
|
+
"flute8",
|
|
411
|
+
"gedackt8",
|
|
412
|
+
"gamba8",
|
|
413
|
+
"celeste8",
|
|
414
|
+
"octave4",
|
|
415
|
+
"flute4",
|
|
416
|
+
"nazard",
|
|
417
|
+
"fifteenth2",
|
|
418
|
+
"piccolo2",
|
|
419
|
+
"tierce",
|
|
420
|
+
"larigot",
|
|
421
|
+
"mixture",
|
|
422
|
+
"trombone16",
|
|
423
|
+
"trumpet8",
|
|
424
|
+
"oboe8",
|
|
425
|
+
"krummhorn8",
|
|
426
|
+
] as const);
|
|
427
|
+
|
|
428
|
+
/** Named registrations: a word that stands for a set of stops. */
|
|
429
|
+
export const PIPE_REGISTRATIONS: Readonly<Record<string, readonly string[]>> =
|
|
430
|
+
Object.freeze({
|
|
431
|
+
plenum: ["subbass16", "principal8", "octave4", "fifteenth2", "mixture"],
|
|
432
|
+
flutes: ["gedackt8", "flute4"],
|
|
433
|
+
cornet: ["flute8", "flute4", "nazard", "piccolo2", "tierce"],
|
|
434
|
+
reeds: ["trumpet8", "oboe8", "nazard", "tierce", "larigot"],
|
|
435
|
+
strings: ["gamba8", "celeste8"],
|
|
436
|
+
full: PIPE_STOPS.filter((stop) => stop !== "celeste8"),
|
|
437
|
+
});
|
|
438
|
+
|
|
439
|
+
/** The stop names a `stops` value sounds (registrations expanded, deduped). */
|
|
440
|
+
export function pipeStops(text: string): string[] {
|
|
441
|
+
const out: string[] = [];
|
|
442
|
+
for (const word of text.trim().split(/\s+/)) {
|
|
443
|
+
const names = PIPE_REGISTRATIONS[word] ?? [word];
|
|
444
|
+
for (const name of names) if (!out.includes(name)) out.push(name);
|
|
445
|
+
}
|
|
446
|
+
return out.filter((name) => (PIPE_STOPS as readonly string[]).includes(name));
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
/** Validates one organ text value; returns the stored string. */
|
|
450
|
+
export function normalizeOrganText(
|
|
451
|
+
name: OrganTextParam,
|
|
452
|
+
value: unknown,
|
|
453
|
+
): string {
|
|
454
|
+
if (typeof value !== "string")
|
|
455
|
+
throw new FxValidationError(`keys ${name} must be a string`);
|
|
456
|
+
const spec = ORGAN_TEXT[name];
|
|
457
|
+
if (spec.digits > 0) {
|
|
458
|
+
if (!new RegExp(`^[0-8]{${spec.digits}}$`).test(value))
|
|
459
|
+
throw new FxValidationError(
|
|
460
|
+
`keys ${name} must be ${spec.digits} digits 0-8 (like ${spec.default})`,
|
|
461
|
+
);
|
|
462
|
+
return value;
|
|
463
|
+
}
|
|
464
|
+
const words = value.trim().toLowerCase().split(/\s+/).filter(Boolean);
|
|
465
|
+
if (words.length === 0)
|
|
466
|
+
throw new FxValidationError("keys stops must name at least one stop");
|
|
467
|
+
for (const word of words)
|
|
468
|
+
if (
|
|
469
|
+
!(PIPE_STOPS as readonly string[]).includes(word) &&
|
|
470
|
+
!Object.prototype.hasOwnProperty.call(PIPE_REGISTRATIONS, word)
|
|
471
|
+
)
|
|
472
|
+
throw new FxValidationError(
|
|
473
|
+
`keys stops: unknown stop "${word}" (stops ${PIPE_STOPS.join(" ")}; registrations ${Object.keys(PIPE_REGISTRATIONS).join(" ")})`,
|
|
474
|
+
);
|
|
475
|
+
return words.join(" ");
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
/** The KEYS_PARAMS rows and text rows each organ family reads. */
|
|
479
|
+
export const ORGAN_ROWS: Readonly<Record<OrganFamily, readonly string[]>> =
|
|
480
|
+
Object.freeze({
|
|
481
|
+
tonewheel: [
|
|
482
|
+
"drawbars",
|
|
483
|
+
"perc",
|
|
484
|
+
"percdecay",
|
|
485
|
+
"percvol",
|
|
486
|
+
"click",
|
|
487
|
+
"scanner",
|
|
488
|
+
"drive",
|
|
489
|
+
"rotary",
|
|
490
|
+
],
|
|
491
|
+
combo: ["registers", "voice", "vib", "vibmod", "drive", "rotary"],
|
|
492
|
+
pipe: ["stops", "chiff", "wind", "trem"],
|
|
493
|
+
});
|
|
494
|
+
|
|
495
|
+
/** Every organ-only row name (KEYS_PARAMS rows and text rows). */
|
|
496
|
+
export const ORGAN_PARAM_NAMES: readonly string[] = Object.freeze([
|
|
497
|
+
"perc",
|
|
498
|
+
"percdecay",
|
|
499
|
+
"click",
|
|
500
|
+
"scanner",
|
|
501
|
+
"drive",
|
|
502
|
+
"rotary",
|
|
503
|
+
"voice",
|
|
504
|
+
"chiff",
|
|
505
|
+
"wind",
|
|
506
|
+
"trem",
|
|
507
|
+
"percvol",
|
|
508
|
+
...Object.keys(ORGAN_TEXT),
|
|
509
|
+
]);
|
|
510
|
+
|
|
511
|
+
/** Each organ family's own defaults over KEYS_PARAMS. */
|
|
512
|
+
export const ORGAN_FAMILY_DEFAULTS: Readonly<
|
|
513
|
+
Record<OrganFamily, Readonly<Record<string, number | string>>>
|
|
514
|
+
> = Object.freeze({
|
|
515
|
+
tonewheel: Object.freeze({ drawbars: ORGAN_TEXT.drawbars.default }),
|
|
516
|
+
combo: Object.freeze({
|
|
517
|
+
registers: ORGAN_TEXT.registers.default,
|
|
518
|
+
vib: 6,
|
|
519
|
+
vibmod: 0.12,
|
|
520
|
+
drive: 0,
|
|
521
|
+
rotary: "stop",
|
|
522
|
+
}),
|
|
523
|
+
pipe: Object.freeze({ stops: ORGAN_TEXT.stops.default }),
|
|
524
|
+
});
|
|
525
|
+
|
|
526
|
+
/**
|
|
527
|
+
* The rows a keys family shows and reads, in display order (an organ's
|
|
528
|
+
* ORGAN_ROWS, else its KEYS_PARAM_FAMILIES rows).
|
|
529
|
+
*/
|
|
530
|
+
export function keysRowsFor(instrument: string | undefined): string[] {
|
|
531
|
+
return keysParamsFor(instrument);
|
|
532
|
+
}
|
|
533
|
+
|
|
162
534
|
/** The parameters shown first in the menu and `keys` listing. */
|
|
163
535
|
export const KEYS_SIMPLE = Object.freeze([
|
|
164
536
|
"hardness",
|
|
@@ -167,6 +539,25 @@ export const KEYS_SIMPLE = Object.freeze([
|
|
|
167
539
|
"felt",
|
|
168
540
|
]);
|
|
169
541
|
|
|
542
|
+
/** The electric families' first rows. */
|
|
543
|
+
export const ELECTRIC_SIMPLE: Readonly<
|
|
544
|
+
Record<ElectricFamily, readonly string[]>
|
|
545
|
+
> = Object.freeze({
|
|
546
|
+
epiano: Object.freeze(["bark", "bell", "tone", "vibe"]),
|
|
547
|
+
wurli: Object.freeze(["bark", "bell", "tone", "trem"]),
|
|
548
|
+
clav: Object.freeze(["pickup", "mute", "tone", "decay"]),
|
|
549
|
+
});
|
|
550
|
+
|
|
551
|
+
/** The first rows for a keys family. */
|
|
552
|
+
export function keysSimpleFor(
|
|
553
|
+
instrument: string | undefined,
|
|
554
|
+
): readonly string[] {
|
|
555
|
+
if (isOrganFamily(instrument)) return ORGAN_ROWS[instrument];
|
|
556
|
+
return isElectricFamily(instrument)
|
|
557
|
+
? ELECTRIC_SIMPLE[instrument]
|
|
558
|
+
: KEYS_SIMPLE;
|
|
559
|
+
}
|
|
560
|
+
|
|
170
561
|
export type TrackKeys = Readonly<
|
|
171
562
|
Record<string, number | string> & { preset?: string }
|
|
172
563
|
>;
|
|
@@ -187,6 +578,15 @@ export const PIANO_FAMILY_DEFAULTS: Readonly<
|
|
|
187
578
|
prepared: Object.freeze({ prep: 0.6, body: "prepared" }),
|
|
188
579
|
});
|
|
189
580
|
|
|
581
|
+
/** Each electric family's own defaults over `KEYS_PARAMS`. */
|
|
582
|
+
export const ELECTRIC_FAMILY_DEFAULTS: Readonly<
|
|
583
|
+
Record<ElectricFamily, Readonly<Record<string, number | string>>>
|
|
584
|
+
> = Object.freeze({
|
|
585
|
+
epiano: Object.freeze({ width: 0.3, tone: 6000 }),
|
|
586
|
+
wurli: Object.freeze({ width: 0.3, bark: 0.5, bell: 0.2, tone: 5000 }),
|
|
587
|
+
clav: Object.freeze({ width: 0.3 }),
|
|
588
|
+
});
|
|
589
|
+
|
|
190
590
|
/** Effects a preset sets with the voice (applied over the effect defaults). */
|
|
191
591
|
export type KeysPresetFx = Readonly<{
|
|
192
592
|
/** The track filter (`Track.filter`). */
|
|
@@ -197,7 +597,7 @@ export type KeysPresetFx = Readonly<{
|
|
|
197
597
|
}>;
|
|
198
598
|
|
|
199
599
|
export type KeysPreset = Readonly<{
|
|
200
|
-
instrument:
|
|
600
|
+
instrument: KeysFamily;
|
|
201
601
|
/** The preset's overrides over the family defaults. */
|
|
202
602
|
keys: Readonly<Record<string, number | string>>;
|
|
203
603
|
doc: string;
|
|
@@ -259,9 +659,132 @@ export const KEYS_PRESETS: Readonly<Record<string, KeysPreset>> = Object.freeze(
|
|
|
259
659
|
doc: "bolts, rubber and screws on 60% of keys (seeded per key)",
|
|
260
660
|
styles: "Cage, Aphex-style, Oneohtrix Point Never",
|
|
261
661
|
},
|
|
662
|
+
// keys-electric (0.6.1)
|
|
663
|
+
epiano: {
|
|
664
|
+
instrument: "epiano",
|
|
665
|
+
keys: {},
|
|
666
|
+
doc: "Rhodes-style stage tine piano: bell and bark with velocity",
|
|
667
|
+
styles: "jazz, soul, Flying Lotus, Radiohead",
|
|
668
|
+
},
|
|
669
|
+
suitcase: {
|
|
670
|
+
instrument: "epiano",
|
|
671
|
+
keys: { vibe: 0.7, vibehz: 4, bark: 0.3 },
|
|
672
|
+
doc: "tine piano with the suitcase stereo vibrato, left and right in turn",
|
|
673
|
+
styles: "70s soul, Stevie Wonder ballads, neo-soul",
|
|
674
|
+
},
|
|
675
|
+
dyno: {
|
|
676
|
+
instrument: "epiano",
|
|
677
|
+
keys: { bell: 0.85, tone: 9000, hardness: 0.7 },
|
|
678
|
+
doc: "bright, bell-heavy tine piano",
|
|
679
|
+
styles: "80s ballads, city pop",
|
|
680
|
+
},
|
|
681
|
+
wurli: {
|
|
682
|
+
instrument: "wurli",
|
|
683
|
+
keys: {},
|
|
684
|
+
doc: "Wurlitzer-style reed piano, nasal and growly",
|
|
685
|
+
styles: "soul, funk, Radiohead, Supertramp",
|
|
686
|
+
},
|
|
687
|
+
clav: {
|
|
688
|
+
instrument: "clav",
|
|
689
|
+
keys: {},
|
|
690
|
+
doc: "clavinet, both pickups",
|
|
691
|
+
styles: "funk, disco, reggae",
|
|
692
|
+
},
|
|
693
|
+
funkclav: {
|
|
694
|
+
instrument: "clav",
|
|
695
|
+
keys: { pickup: "out", mute: 0.3 },
|
|
696
|
+
doc: "clavinet with both pickups out of phase and the mute slider up",
|
|
697
|
+
styles: "Stevie Wonder funk, Bill Withers",
|
|
698
|
+
},
|
|
699
|
+
// organ (f061-organ)
|
|
700
|
+
tonewheel: {
|
|
701
|
+
instrument: "tonewheel",
|
|
702
|
+
keys: {},
|
|
703
|
+
doc: "tonewheel organ 888000000, scanner C3, slow rotary",
|
|
704
|
+
styles: "jazz, soul, gospel comping, Beach House",
|
|
705
|
+
},
|
|
706
|
+
gospel: {
|
|
707
|
+
instrument: "tonewheel",
|
|
708
|
+
keys: {
|
|
709
|
+
drawbars: "888800008",
|
|
710
|
+
perc: "3rd",
|
|
711
|
+
rotary: "fast",
|
|
712
|
+
drive: 0.35,
|
|
713
|
+
},
|
|
714
|
+
doc: "888800008, 3rd percussion (mutes the 1'), fast rotary, driven",
|
|
715
|
+
styles: "gospel, Motown, rock",
|
|
716
|
+
},
|
|
717
|
+
jazzorgan: {
|
|
718
|
+
instrument: "tonewheel",
|
|
719
|
+
keys: {
|
|
720
|
+
drawbars: "888000000",
|
|
721
|
+
perc: "3rd",
|
|
722
|
+
percvol: "soft",
|
|
723
|
+
scanner: "c3",
|
|
724
|
+
},
|
|
725
|
+
doc: "888000000 with soft fast 3rd percussion, slow rotary",
|
|
726
|
+
styles: "Jimmy Smith jazz, soul jazz",
|
|
727
|
+
},
|
|
728
|
+
combo: {
|
|
729
|
+
instrument: "combo",
|
|
730
|
+
keys: {},
|
|
731
|
+
doc: "Farfisa-style reed registers 08800 with vibrato",
|
|
732
|
+
styles: "Beach House, 60s garage, Glass, Reich",
|
|
733
|
+
},
|
|
734
|
+
vox: {
|
|
735
|
+
instrument: "combo",
|
|
736
|
+
keys: { registers: "08880", voice: "bright", vib: 5.5, vibmod: 0.1 },
|
|
737
|
+
doc: "Vox-style bright saw registers 08880",
|
|
738
|
+
styles: "60s, Doors-ish, shoegaze drones",
|
|
739
|
+
},
|
|
740
|
+
pipe: {
|
|
741
|
+
instrument: "pipe",
|
|
742
|
+
keys: { stops: "plenum" },
|
|
743
|
+
reverb: { mix: 0.35, size: 0.9 },
|
|
744
|
+
doc: "plenum: principal chorus, mixture and 16' in a church",
|
|
745
|
+
styles: "Bach fugue, Bach cantata",
|
|
746
|
+
},
|
|
747
|
+
flutes: {
|
|
748
|
+
instrument: "pipe",
|
|
749
|
+
keys: { stops: "flutes", trem: 0.4, chiff: 0.5 },
|
|
750
|
+
reverb: { mix: 0.3, size: 0.8 },
|
|
751
|
+
doc: "gedackt 8' and flute 4' with tremulant",
|
|
752
|
+
styles: "Bach chorale preludes, Eno, ambient",
|
|
753
|
+
},
|
|
754
|
+
cornet: {
|
|
755
|
+
instrument: "pipe",
|
|
756
|
+
keys: { stops: "cornet" },
|
|
757
|
+
reverb: { mix: 0.3, size: 0.8 },
|
|
758
|
+
doc: "the cornet solo combination (8' 4' 2⅔' 2' 1⅗')",
|
|
759
|
+
styles: "Bach, Buxtehude, French classical",
|
|
760
|
+
},
|
|
761
|
+
reeds: {
|
|
762
|
+
instrument: "pipe",
|
|
763
|
+
keys: { stops: "reeds" },
|
|
764
|
+
reverb: { mix: 0.3, size: 0.85 },
|
|
765
|
+
doc: "trumpet and oboe with nazard, tierce and larigot",
|
|
766
|
+
styles: "Messiaen",
|
|
767
|
+
},
|
|
768
|
+
celeste: {
|
|
769
|
+
instrument: "pipe",
|
|
770
|
+
keys: { stops: "strings", wind: 0.4 },
|
|
771
|
+
reverb: { mix: 0.35, size: 0.9 },
|
|
772
|
+
doc: "gamba and celeste: slow beating strings",
|
|
773
|
+
styles: "Messiaen, ambient, La Monte Young drones",
|
|
774
|
+
},
|
|
262
775
|
},
|
|
263
776
|
);
|
|
264
777
|
|
|
778
|
+
/** Organ words that name a keys preset (aliases of the presets). */
|
|
779
|
+
export const ORGAN_ALIASES: Readonly<Record<string, string>> = Object.freeze({
|
|
780
|
+
hammond: "tonewheel",
|
|
781
|
+
b3: "tonewheel",
|
|
782
|
+
farfisa: "combo",
|
|
783
|
+
church: "pipe",
|
|
784
|
+
pipeorgan: "pipe",
|
|
785
|
+
churchorgan: "pipe",
|
|
786
|
+
});
|
|
787
|
+
|
|
265
788
|
export function isKeysPreset(name: string): boolean {
|
|
266
789
|
return Object.prototype.hasOwnProperty.call(KEYS_PRESETS, name);
|
|
267
790
|
}
|
|
@@ -274,6 +797,10 @@ export function isKeysParam(name: string): boolean {
|
|
|
274
797
|
export function keysParamName(name: string): string | undefined {
|
|
275
798
|
const lower = name.toLowerCase();
|
|
276
799
|
if (isKeysParam(lower)) return lower;
|
|
800
|
+
if (isOrganText(lower)) return lower;
|
|
801
|
+
if (lower === "reg" || lower === "registration" || lower === "stop")
|
|
802
|
+
return "stops";
|
|
803
|
+
if (lower === "drawbar") return "drawbars";
|
|
277
804
|
if (lower === "aftersound") return "after";
|
|
278
805
|
if (lower === "prepared") return "prep";
|
|
279
806
|
for (const [key, spec] of Object.entries(KEYS_PARAMS))
|
|
@@ -292,9 +819,30 @@ export const KEYS_LANE_PARAMS: readonly Readonly<{
|
|
|
292
819
|
(entry): entry is [string, NumberParam] =>
|
|
293
820
|
entry[1].kind === "number" && entry[1].automate === true,
|
|
294
821
|
)
|
|
295
|
-
.map(([param, spec]) => Object.freeze({ param, spec }))
|
|
822
|
+
.map(([param, spec]) => Object.freeze({ param, spec }))
|
|
823
|
+
.concat(
|
|
824
|
+
// f061-organ: the rotary speed as a lane (0 stop, 1 slow, 2 fast);
|
|
825
|
+
// the rotors glide between speeds with their own inertia.
|
|
826
|
+
Object.freeze({
|
|
827
|
+
param: "rotary",
|
|
828
|
+
spec: Object.freeze({
|
|
829
|
+
kind: "number",
|
|
830
|
+
min: 0,
|
|
831
|
+
max: 2,
|
|
832
|
+
default: 1,
|
|
833
|
+
step: 1,
|
|
834
|
+
integer: true,
|
|
835
|
+
doc: "rotary speed: 0 stop, 1 slow, 2 fast",
|
|
836
|
+
}) as NumberParam,
|
|
837
|
+
}),
|
|
838
|
+
),
|
|
296
839
|
);
|
|
297
840
|
|
|
841
|
+
/** The `rotary` enum as its lane number (0 stop, 1 slow, 2 fast). */
|
|
842
|
+
export function rotarySpeed(value: unknown): number {
|
|
843
|
+
return value === "fast" ? 2 : value === "stop" ? 0 : 1;
|
|
844
|
+
}
|
|
845
|
+
|
|
298
846
|
/**
|
|
299
847
|
* Validates `Track.keys`: overrides in `KEYS_PARAMS` order, then `preset`.
|
|
300
848
|
* `{}` stays `{}` (its presence turns the modelled piano on); null and
|
|
@@ -305,6 +853,7 @@ export function normalizeKeys(input: unknown): TrackKeys | undefined {
|
|
|
305
853
|
if (!isRecord(input))
|
|
306
854
|
throw new FxValidationError("track keys must be an object or null");
|
|
307
855
|
const params: Record<string, unknown> = {};
|
|
856
|
+
const text: Partial<Record<OrganTextParam, string>> = {};
|
|
308
857
|
let preset: string | undefined;
|
|
309
858
|
for (const [key, value] of Object.entries(input)) {
|
|
310
859
|
if (value === undefined || value === null) continue;
|
|
@@ -316,6 +865,10 @@ export function normalizeKeys(input: unknown): TrackKeys | undefined {
|
|
|
316
865
|
preset = value;
|
|
317
866
|
continue;
|
|
318
867
|
}
|
|
868
|
+
if (isOrganText(key)) {
|
|
869
|
+
text[key] = normalizeOrganText(key, value);
|
|
870
|
+
continue;
|
|
871
|
+
}
|
|
319
872
|
if (!isKeysParam(key)) {
|
|
320
873
|
const name = keysParamName(key);
|
|
321
874
|
throw new FxValidationError(
|
|
@@ -327,6 +880,11 @@ export function normalizeKeys(input: unknown): TrackKeys | undefined {
|
|
|
327
880
|
const values = normalizeParams(KEYS_PARAMS, params, "keys", false);
|
|
328
881
|
return Object.freeze({
|
|
329
882
|
...(values as Record<string, number | string>),
|
|
883
|
+
...Object.fromEntries(
|
|
884
|
+
Object.keys(ORGAN_TEXT)
|
|
885
|
+
.filter((key) => text[key as OrganTextParam] !== undefined)
|
|
886
|
+
.map((key) => [key, text[key as OrganTextParam]!]),
|
|
887
|
+
),
|
|
330
888
|
...(preset ? { preset } : {}),
|
|
331
889
|
});
|
|
332
890
|
}
|
|
@@ -344,6 +902,10 @@ export function resolvedKeys(
|
|
|
344
902
|
out[key] = spec.default as number | string;
|
|
345
903
|
if (isPianoFamily(instrument))
|
|
346
904
|
Object.assign(out, PIANO_FAMILY_DEFAULTS[instrument]);
|
|
905
|
+
if (isElectricFamily(instrument))
|
|
906
|
+
Object.assign(out, ELECTRIC_FAMILY_DEFAULTS[instrument]);
|
|
907
|
+
if (isOrganFamily(instrument))
|
|
908
|
+
Object.assign(out, ORGAN_FAMILY_DEFAULTS[instrument]);
|
|
347
909
|
const preset = keys?.preset ? KEYS_PRESETS[keys.preset] : undefined;
|
|
348
910
|
if (preset) Object.assign(out, preset.keys);
|
|
349
911
|
for (const [key, value] of Object.entries(keys ?? {}))
|
|
@@ -351,6 +913,13 @@ export function resolvedKeys(
|
|
|
351
913
|
return out;
|
|
352
914
|
}
|
|
353
915
|
|
|
916
|
+
/** Other names for the electric keys presets (keys-electric, 0.6.1). */
|
|
917
|
+
const KEYS_ALIASES: Readonly<Record<string, string>> = Object.freeze({
|
|
918
|
+
rhodes: "epiano",
|
|
919
|
+
wurlitzer: "wurli",
|
|
920
|
+
clavinet: "clav",
|
|
921
|
+
});
|
|
922
|
+
|
|
354
923
|
/**
|
|
355
924
|
* What a new write of a keys word stores: `piano` and `grand` become the
|
|
356
925
|
* modelled grand, a preset name its instrument and overrides. Undefined
|
|
@@ -358,7 +927,7 @@ export function resolvedKeys(
|
|
|
358
927
|
*/
|
|
359
928
|
export function pianoWrite(word: string):
|
|
360
929
|
| (Readonly<{
|
|
361
|
-
instrument:
|
|
930
|
+
instrument: KeysFamily;
|
|
362
931
|
keys: TrackKeys;
|
|
363
932
|
preset?: string;
|
|
364
933
|
}> &
|
|
@@ -372,7 +941,7 @@ export function pianoWrite(word: string):
|
|
|
372
941
|
: "upright"
|
|
373
942
|
: lower === "feltpiano"
|
|
374
943
|
? "felt"
|
|
375
|
-
: lower;
|
|
944
|
+
: (KEYS_ALIASES[lower] ?? ORGAN_ALIASES[lower] ?? lower);
|
|
376
945
|
if (!isKeysPreset(name)) return undefined;
|
|
377
946
|
const preset = KEYS_PRESETS[name]!;
|
|
378
947
|
return Object.freeze({
|