@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/resonators.ts
CHANGED
|
@@ -18,6 +18,8 @@
|
|
|
18
18
|
* maximum weight.
|
|
19
19
|
*/
|
|
20
20
|
import { resolveInstrumentWord } from "./instruments.ts";
|
|
21
|
+
import { WIND_INSTRUMENT, windPresetFor, type TrackWind } from "./winds.ts";
|
|
22
|
+
import { SING_INSTRUMENT, isSingPreset, type TrackSing } from "./sing.ts";
|
|
21
23
|
import {
|
|
22
24
|
FxValidationError,
|
|
23
25
|
isRecord,
|
|
@@ -84,6 +86,22 @@ export const MODAL_PRESET_NAMES = Object.freeze([
|
|
|
84
86
|
"bowl",
|
|
85
87
|
"gong",
|
|
86
88
|
"timpani",
|
|
89
|
+
// f061-gamelan-winds: gamelan, small bells and frame drums (lane A2).
|
|
90
|
+
"crotales",
|
|
91
|
+
"musicbox",
|
|
92
|
+
"toypiano",
|
|
93
|
+
"saron",
|
|
94
|
+
"demung",
|
|
95
|
+
"slenthem",
|
|
96
|
+
"gangsa",
|
|
97
|
+
"gender",
|
|
98
|
+
"bonang",
|
|
99
|
+
"kenong",
|
|
100
|
+
"kethuk",
|
|
101
|
+
"kempul",
|
|
102
|
+
"daf",
|
|
103
|
+
"bodhran",
|
|
104
|
+
"tabla",
|
|
87
105
|
] as const);
|
|
88
106
|
export type ModalPresetName = (typeof MODAL_PRESET_NAMES)[number];
|
|
89
107
|
|
|
@@ -99,8 +117,29 @@ export const MODAL_ALIASES: Readonly<Record<string, ModalPresetName>> =
|
|
|
99
117
|
singingbowl: "bowl",
|
|
100
118
|
kettledrum: "timpani",
|
|
101
119
|
tubularbells: "chimes",
|
|
120
|
+
// f061-gamelan-winds. `gamelan` is a browse group, not an alias.
|
|
121
|
+
crotale: "crotales",
|
|
122
|
+
musicalbox: "musicbox",
|
|
123
|
+
framedrum: "bodhran",
|
|
102
124
|
});
|
|
103
125
|
|
|
126
|
+
/**
|
|
127
|
+
* The gamelan presets, in browse order (the `gamelan` hint opens these
|
|
128
|
+
* under Mallets and bells).
|
|
129
|
+
*/
|
|
130
|
+
export const GAMELAN_PRESETS: readonly ModalPresetName[] = Object.freeze([
|
|
131
|
+
"saron",
|
|
132
|
+
"demung",
|
|
133
|
+
"slenthem",
|
|
134
|
+
"gangsa",
|
|
135
|
+
"gender",
|
|
136
|
+
"bonang",
|
|
137
|
+
"kenong",
|
|
138
|
+
"kethuk",
|
|
139
|
+
"kempul",
|
|
140
|
+
"gong",
|
|
141
|
+
]);
|
|
142
|
+
|
|
104
143
|
const num = (
|
|
105
144
|
min: number,
|
|
106
145
|
max: number,
|
|
@@ -137,7 +176,7 @@ export const MODAL_PARAMS: Readonly<Record<string, ParamSpec>> = Object.freeze({
|
|
|
137
176
|
hardness: num(0, 1, 0.4, 0.05, "mallet hardness: 0 yarn/felt, 1 brass", {
|
|
138
177
|
automate: true,
|
|
139
178
|
}),
|
|
140
|
-
position: num(0, 1, 0.42, 0.05, "strike point: 0 end or edge, 0.5
|
|
179
|
+
position: num(0, 1, 0.42, 0.05, "strike point: 0 end or edge, 0.5 center", {
|
|
141
180
|
automate: true,
|
|
142
181
|
}),
|
|
143
182
|
ring: num(0.05, 30, 1.6, "log", "ring time (T60) at middle C", {
|
|
@@ -364,6 +403,186 @@ export const MODAL_PRESETS: Readonly<Record<ModalPresetName, ModalPreset>> =
|
|
|
364
403
|
"pedal timpani, felt sticks, a small strike glide",
|
|
365
404
|
"Beethoven, Brahms, Shostakovich, orchestral",
|
|
366
405
|
),
|
|
406
|
+
// f061-gamelan-winds (lane A2): appended; rows above are unchanged.
|
|
407
|
+
// Gamelan ranges follow the instruments' slendro/pelog octaves; tune
|
|
408
|
+
// them with `tuning slendro` or `tuning pelog` (core/tuning.ts).
|
|
409
|
+
crotales: preset(
|
|
410
|
+
{
|
|
411
|
+
body: "crotale",
|
|
412
|
+
ring: 6,
|
|
413
|
+
tilt: 0.4,
|
|
414
|
+
hardness: 0.85,
|
|
415
|
+
position: 0.5,
|
|
416
|
+
gain: 1.6,
|
|
417
|
+
},
|
|
418
|
+
[84, 108],
|
|
419
|
+
"antique cymbals, long shimmering ring",
|
|
420
|
+
"Messiaen, ambient",
|
|
421
|
+
),
|
|
422
|
+
musicbox: preset(
|
|
423
|
+
{
|
|
424
|
+
body: "musicbox",
|
|
425
|
+
ring: 1.8,
|
|
426
|
+
tilt: 0.7,
|
|
427
|
+
hardness: 1,
|
|
428
|
+
position: 0.8,
|
|
429
|
+
gain: 1.7,
|
|
430
|
+
},
|
|
431
|
+
[60, 100],
|
|
432
|
+
"plucked steel comb",
|
|
433
|
+
"ambient, OPN, A. G. Cook",
|
|
434
|
+
),
|
|
435
|
+
toypiano: preset(
|
|
436
|
+
{
|
|
437
|
+
body: "toypiano",
|
|
438
|
+
ring: 1,
|
|
439
|
+
tilt: 0.6,
|
|
440
|
+
hardness: 0.9,
|
|
441
|
+
position: 0.6,
|
|
442
|
+
click: 0.3,
|
|
443
|
+
gain: 2,
|
|
444
|
+
},
|
|
445
|
+
[60, 96],
|
|
446
|
+
"hammered toy-piano rods, clanky",
|
|
447
|
+
"hyperpop, A. G. Cook, Four Tet",
|
|
448
|
+
),
|
|
449
|
+
saron: preset(
|
|
450
|
+
{
|
|
451
|
+
body: "saron",
|
|
452
|
+
ring: 2.5,
|
|
453
|
+
tilt: 0.6,
|
|
454
|
+
hardness: 0.85,
|
|
455
|
+
position: 0.5,
|
|
456
|
+
damp: 0.85,
|
|
457
|
+
release: 0.1,
|
|
458
|
+
},
|
|
459
|
+
[60, 84],
|
|
460
|
+
"saron barung: bronze keys, horn mallet, the balungan melody; damped at the next note (tutupan)",
|
|
461
|
+
"Javanese gamelan, Reich",
|
|
462
|
+
),
|
|
463
|
+
demung: preset(
|
|
464
|
+
{
|
|
465
|
+
body: "saron",
|
|
466
|
+
ring: 3.2,
|
|
467
|
+
tilt: 0.6,
|
|
468
|
+
hardness: 0.75,
|
|
469
|
+
position: 0.5,
|
|
470
|
+
damp: 0.8,
|
|
471
|
+
release: 0.12,
|
|
472
|
+
},
|
|
473
|
+
[48, 72],
|
|
474
|
+
"demung: the saron an octave lower, fuller",
|
|
475
|
+
"Javanese gamelan",
|
|
476
|
+
),
|
|
477
|
+
slenthem: preset(
|
|
478
|
+
{ body: "gender", ring: 4.5, tilt: 0.5, hardness: 0.25, position: 0.5 },
|
|
479
|
+
[43, 60],
|
|
480
|
+
"slenthem: low bronze keys over tubes, padded mallet",
|
|
481
|
+
"Javanese gamelan, ambient",
|
|
482
|
+
),
|
|
483
|
+
gangsa: preset(
|
|
484
|
+
{
|
|
485
|
+
body: "saron",
|
|
486
|
+
ring: 2.2,
|
|
487
|
+
tilt: 0.6,
|
|
488
|
+
hardness: 0.9,
|
|
489
|
+
position: 0.5,
|
|
490
|
+
ombak: 7,
|
|
491
|
+
damp: 0.85,
|
|
492
|
+
release: 0.1,
|
|
493
|
+
},
|
|
494
|
+
[60, 89],
|
|
495
|
+
"Balinese metallophone: pengumbang and pengisep beating at 7 Hz (ombak)",
|
|
496
|
+
"Balinese gamelan, Lopatin, Four Tet",
|
|
497
|
+
),
|
|
498
|
+
gender: preset(
|
|
499
|
+
{ body: "gender", ring: 5, tilt: 0.5, hardness: 0.3, position: 0.5 },
|
|
500
|
+
[48, 84],
|
|
501
|
+
"gender: thin bronze keys over tubes, padded mallets, long ring",
|
|
502
|
+
"Javanese gamelan, ambient",
|
|
503
|
+
),
|
|
504
|
+
bonang: preset(
|
|
505
|
+
{
|
|
506
|
+
body: "bonang",
|
|
507
|
+
ring: 1.8,
|
|
508
|
+
tilt: 0.7,
|
|
509
|
+
hardness: 0.6,
|
|
510
|
+
position: 0.5,
|
|
511
|
+
damp: 0.7,
|
|
512
|
+
release: 0.12,
|
|
513
|
+
},
|
|
514
|
+
[55, 84],
|
|
515
|
+
"bonang: small kettle gongs in a row, padded sticks",
|
|
516
|
+
"Javanese gamelan, Reich",
|
|
517
|
+
),
|
|
518
|
+
kenong: preset(
|
|
519
|
+
{ body: "bonang", ring: 4, tilt: 0.5, hardness: 0.45, position: 0.5 },
|
|
520
|
+
[55, 72],
|
|
521
|
+
"kenong: large kettle gong that marks the phrase",
|
|
522
|
+
"Javanese gamelan",
|
|
523
|
+
),
|
|
524
|
+
kethuk: preset(
|
|
525
|
+
{
|
|
526
|
+
body: "bonang",
|
|
527
|
+
ring: 0.5,
|
|
528
|
+
tilt: 0.7,
|
|
529
|
+
hardness: 0.5,
|
|
530
|
+
position: 0.5,
|
|
531
|
+
damp: 1,
|
|
532
|
+
release: 0.08,
|
|
533
|
+
},
|
|
534
|
+
[55, 67],
|
|
535
|
+
"kethuk: small kettle gong, struck and damped (tutupan)",
|
|
536
|
+
"Javanese gamelan",
|
|
537
|
+
),
|
|
538
|
+
kempul: preset(
|
|
539
|
+
{ body: "kempul", ring: 6, tilt: 0.4, hardness: 0.25, position: 0.5 },
|
|
540
|
+
[43, 67],
|
|
541
|
+
"kempul: hanging gong, slow beating",
|
|
542
|
+
"Javanese gamelan",
|
|
543
|
+
),
|
|
544
|
+
daf: preset(
|
|
545
|
+
{
|
|
546
|
+
body: "frame",
|
|
547
|
+
ring: 0.5,
|
|
548
|
+
tilt: 0.8,
|
|
549
|
+
hardness: 0.6,
|
|
550
|
+
position: 0.85,
|
|
551
|
+
buzz: 0.6,
|
|
552
|
+
},
|
|
553
|
+
[43, 55],
|
|
554
|
+
"Persian frame drum with ring jingles",
|
|
555
|
+
"Persian, Sufi",
|
|
556
|
+
),
|
|
557
|
+
bodhran: preset(
|
|
558
|
+
{
|
|
559
|
+
body: "frame",
|
|
560
|
+
ring: 0.35,
|
|
561
|
+
tilt: 0.9,
|
|
562
|
+
hardness: 0.5,
|
|
563
|
+
position: 0.8,
|
|
564
|
+
damp: 0.4,
|
|
565
|
+
gain: 2,
|
|
566
|
+
},
|
|
567
|
+
[40, 52],
|
|
568
|
+
"Irish frame drum, tipper",
|
|
569
|
+
"celtic",
|
|
570
|
+
),
|
|
571
|
+
tabla: preset(
|
|
572
|
+
{
|
|
573
|
+
body: "tabla",
|
|
574
|
+
ring: 1.2,
|
|
575
|
+
tilt: 0.5,
|
|
576
|
+
hardness: 0.7,
|
|
577
|
+
position: 0.75,
|
|
578
|
+
strikebend: 0.4,
|
|
579
|
+
strikedecay: 0.12,
|
|
580
|
+
gain: 2,
|
|
581
|
+
},
|
|
582
|
+
[57, 69],
|
|
583
|
+
"dayan: tuned treble drum with near-harmonic syahi modes",
|
|
584
|
+
"Hindustani, Talvin Singh, Four Tet",
|
|
585
|
+
),
|
|
367
586
|
});
|
|
368
587
|
|
|
369
588
|
/** Default preset of a `modal` field that names none. */
|
|
@@ -388,6 +607,13 @@ export type TrackModal = Readonly<{
|
|
|
388
607
|
strikedecay?: number;
|
|
389
608
|
gain?: number;
|
|
390
609
|
body?: ModalBody;
|
|
610
|
+
/**
|
|
611
|
+
* f061-gamelan-winds: the id of this track's partner in an ombak pair.
|
|
612
|
+
* A paired track (the pengisep) plays one voice `ombak` Hz above the
|
|
613
|
+
* pitch instead of its own twin, so it beats against its partner (the
|
|
614
|
+
* pengumbang). Only this track's render changes.
|
|
615
|
+
*/
|
|
616
|
+
pair?: string;
|
|
391
617
|
}>;
|
|
392
618
|
|
|
393
619
|
export function isModalPreset(name: string): name is ModalPresetName {
|
|
@@ -425,8 +651,17 @@ export function normalizeModal(input: unknown): TrackModal | undefined {
|
|
|
425
651
|
throw new FxValidationError("track modal must be an object or null");
|
|
426
652
|
const params: Record<string, unknown> = {};
|
|
427
653
|
let presetName: ModalPresetName | undefined;
|
|
654
|
+
let pair: string | undefined;
|
|
428
655
|
for (const [key, value] of Object.entries(input)) {
|
|
429
656
|
if (value === undefined || value === null) continue;
|
|
657
|
+
if (key === "pair") {
|
|
658
|
+
if (typeof value !== "string" || !PAIR_ID.test(value))
|
|
659
|
+
throw new FxValidationError(
|
|
660
|
+
"modal pair must be a track id (letters, digits, - or _)",
|
|
661
|
+
);
|
|
662
|
+
pair = value;
|
|
663
|
+
continue;
|
|
664
|
+
}
|
|
430
665
|
if (key === "preset") {
|
|
431
666
|
if (typeof value !== "string")
|
|
432
667
|
throw new FxValidationError(
|
|
@@ -451,6 +686,7 @@ export function normalizeModal(input: unknown): TrackModal | undefined {
|
|
|
451
686
|
return Object.freeze({
|
|
452
687
|
...(presetName ? { preset: presetName } : {}),
|
|
453
688
|
...values,
|
|
689
|
+
...(pair === undefined ? {} : { pair }),
|
|
454
690
|
}) as TrackModal;
|
|
455
691
|
}
|
|
456
692
|
|
|
@@ -468,7 +704,12 @@ export function modalSettings(
|
|
|
468
704
|
if (modal?.mallet) out.hardness = MODAL_MALLETS[modal.mallet];
|
|
469
705
|
if (modal)
|
|
470
706
|
for (const [key, value] of Object.entries(modal))
|
|
471
|
-
if (
|
|
707
|
+
if (
|
|
708
|
+
key !== "preset" &&
|
|
709
|
+
key !== "mallet" &&
|
|
710
|
+
key !== "pair" &&
|
|
711
|
+
value !== undefined
|
|
712
|
+
)
|
|
472
713
|
out[key] = value;
|
|
473
714
|
if (lane)
|
|
474
715
|
for (const key of MODAL_LANE_PARAMS) {
|
|
@@ -496,7 +737,10 @@ export const MAX_RESONATOR_TAIL = 30;
|
|
|
496
737
|
/** Voices one modal track sounds at once; the oldest onset is stolen. */
|
|
497
738
|
export const MAX_RESONATOR_VOICES = 32;
|
|
498
739
|
/** Joins the stem cache key so mode-table changes invalidate stems. */
|
|
499
|
-
export const RESONATOR_TABLE_VERSION =
|
|
740
|
+
export const RESONATOR_TABLE_VERSION = 2;
|
|
741
|
+
|
|
742
|
+
/** A modal `pair` value: a track id. */
|
|
743
|
+
const PAIR_ID = /^[A-Za-z0-9_-]{1,64}$/;
|
|
500
744
|
|
|
501
745
|
/**
|
|
502
746
|
* A modal track's ring-out after its last note-off, in seconds: the preset
|
|
@@ -524,8 +768,29 @@ export function modalTailSeconds(
|
|
|
524
768
|
export function instrumentPatchForWord(word: string): {
|
|
525
769
|
instrument: string;
|
|
526
770
|
modal?: TrackModal;
|
|
771
|
+
wind?: TrackWind;
|
|
772
|
+
sing?: TrackSing;
|
|
527
773
|
} {
|
|
528
774
|
const meaning = resolveInstrumentWord(word);
|
|
775
|
+
// f07-sing: `choir`, `aah`, `khoomei` sing a preset.
|
|
776
|
+
if (meaning?.field === "sing" && meaning.instrument === SING_INSTRUMENT) {
|
|
777
|
+
const preset =
|
|
778
|
+
meaning.preset && isSingPreset(meaning.preset)
|
|
779
|
+
? meaning.preset
|
|
780
|
+
: undefined;
|
|
781
|
+
return {
|
|
782
|
+
instrument: SING_INSTRUMENT,
|
|
783
|
+
sing: Object.freeze(preset ? { preset } : {}) as TrackSing,
|
|
784
|
+
};
|
|
785
|
+
}
|
|
786
|
+
// f061-gamelan-winds: `flute`, `sax`, `tuba` play a wind preset.
|
|
787
|
+
if (meaning?.field === "wind" && meaning.instrument === WIND_INSTRUMENT) {
|
|
788
|
+
const preset = meaning.preset ? windPresetFor(meaning.preset) : undefined;
|
|
789
|
+
return {
|
|
790
|
+
instrument: WIND_INSTRUMENT,
|
|
791
|
+
wind: Object.freeze(preset ? { preset } : {}) as TrackWind,
|
|
792
|
+
};
|
|
793
|
+
}
|
|
529
794
|
if (meaning?.field === "modal" && meaning.instrument === MODAL_INSTRUMENT) {
|
|
530
795
|
const preset = meaning.preset ? modalPresetFor(meaning.preset) : undefined;
|
|
531
796
|
return {
|
|
@@ -538,7 +803,41 @@ export function instrumentPatchForWord(word: string): {
|
|
|
538
803
|
|
|
539
804
|
/** True for a word that resolves to the modal engine (`vibes`, `gong`). */
|
|
540
805
|
export function isModalWord(word: string): boolean {
|
|
541
|
-
|
|
806
|
+
const meaning = resolveInstrumentWord(word);
|
|
807
|
+
return (
|
|
808
|
+
meaning?.instrument === MODAL_INSTRUMENT ||
|
|
809
|
+
(meaning?.field === "wind" && meaning.instrument === WIND_INSTRUMENT) ||
|
|
810
|
+
(meaning?.field === "sing" && meaning.instrument === SING_INSTRUMENT)
|
|
811
|
+
);
|
|
812
|
+
}
|
|
813
|
+
|
|
814
|
+
/**
|
|
815
|
+
* `dawg check` notes for ombak pairs whose partner is missing or is not a
|
|
816
|
+
* modal track: the pengisep then plays ombak Hz sharp with nothing to beat
|
|
817
|
+
* against.
|
|
818
|
+
*/
|
|
819
|
+
export function modalPairWarnings(
|
|
820
|
+
tracks: readonly Readonly<{
|
|
821
|
+
id: string;
|
|
822
|
+
instrument: string;
|
|
823
|
+
modal?: Readonly<{ pair?: string }>;
|
|
824
|
+
}>[],
|
|
825
|
+
): string[] {
|
|
826
|
+
const out: string[] = [];
|
|
827
|
+
for (const track of tracks) {
|
|
828
|
+
const pair = track.modal?.pair;
|
|
829
|
+
if (pair === undefined) continue;
|
|
830
|
+
const partner = tracks.find((candidate) => candidate.id === pair);
|
|
831
|
+
if (!partner)
|
|
832
|
+
out.push(
|
|
833
|
+
`track ${track.id}: modal pair "${pair}" names no track; it plays ombak Hz sharp alone`,
|
|
834
|
+
);
|
|
835
|
+
else if (partner.instrument !== MODAL_INSTRUMENT || !partner.modal)
|
|
836
|
+
out.push(
|
|
837
|
+
`track ${track.id}: modal pair "${pair}" is not a modal track; give it the same preset (modal gangsa)`,
|
|
838
|
+
);
|
|
839
|
+
}
|
|
840
|
+
return out;
|
|
542
841
|
}
|
|
543
842
|
|
|
544
843
|
/**
|
|
@@ -572,3 +871,6 @@ export function legacyResonatorWarnings(
|
|
|
572
871
|
}
|
|
573
872
|
return out;
|
|
574
873
|
}
|
|
874
|
+
|
|
875
|
+
// f061-gamelan-winds: blown instruments live in core/winds.ts.
|
|
876
|
+
export * from "./winds.ts";
|
package/core/routing.ts
ADDED
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cross-track references (0.7). A track field that names another track (the
|
|
3
|
+
* vocoder's `src`, autotune's `from`, 0.7.1 harmony's `guide`) registers
|
|
4
|
+
* here once, at module load, with the edges it reads and how it forgets a
|
|
5
|
+
* removed track. `removeTrack` then drops dangling references, the renderer
|
|
6
|
+
* orders stems with `routingOrder`, and stem digests include the notes a
|
|
7
|
+
* track follows through `notesDigestInputs`.
|
|
8
|
+
*
|
|
9
|
+
* Audio edges carry rendered sound (the target must render first, and a
|
|
10
|
+
* cycle is an error). Notes edges only read the other track's notes, so
|
|
11
|
+
* they never order rendering and never form a cycle.
|
|
12
|
+
*/
|
|
13
|
+
import {
|
|
14
|
+
registerTrackRefDrop,
|
|
15
|
+
ScoreValidationError,
|
|
16
|
+
type Note,
|
|
17
|
+
type Track,
|
|
18
|
+
type TrackScore,
|
|
19
|
+
} from "./score.ts";
|
|
20
|
+
import { trackSlug } from "./slug.ts";
|
|
21
|
+
|
|
22
|
+
export type TrackRefKind = "audio" | "notes";
|
|
23
|
+
|
|
24
|
+
/** One reference from a track to another track. */
|
|
25
|
+
export type TrackRef = Readonly<{ trackId: string; kind: TrackRefKind }>;
|
|
26
|
+
|
|
27
|
+
export type TrackRefSpec = Readonly<{
|
|
28
|
+
/** The tracks `track` references through this field. */
|
|
29
|
+
refs(track: Track): readonly TrackRef[];
|
|
30
|
+
/** `track` without its references to `removedId`. */
|
|
31
|
+
drop(track: Track, removedId: string): Track;
|
|
32
|
+
}>;
|
|
33
|
+
|
|
34
|
+
const specs = new Map<string, TrackRefSpec>();
|
|
35
|
+
|
|
36
|
+
/** Registers a referencing field once; registering it again replaces it. */
|
|
37
|
+
export function registerTrackRefs(field: string, spec: TrackRefSpec): void {
|
|
38
|
+
specs.set(field, spec);
|
|
39
|
+
registerTrackRefDrop(field, spec.drop);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** Every reference `track` holds, in field registration order. */
|
|
43
|
+
export function trackRefs(track: Track): readonly TrackRef[] {
|
|
44
|
+
const refs: TrackRef[] = [];
|
|
45
|
+
for (const spec of specs.values()) refs.push(...spec.refs(track));
|
|
46
|
+
return refs;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Finds a track by id, then by name slug (`Lead Vox` and `lead-vox` both
|
|
51
|
+
* match). Returns undefined when nothing matches.
|
|
52
|
+
*/
|
|
53
|
+
export function resolveTrackRef(
|
|
54
|
+
score: TrackScore,
|
|
55
|
+
idOrSlug: string,
|
|
56
|
+
): Track | undefined {
|
|
57
|
+
const byId = score.tracks.find((track) => track.id === idOrSlug);
|
|
58
|
+
if (byId) return byId;
|
|
59
|
+
const slug = trackSlug(idOrSlug);
|
|
60
|
+
return score.tracks.find((track) => trackSlug(track.name) === slug);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Track ids in render order: every audio source before the tracks that
|
|
65
|
+
* read it, otherwise score order. References to missing tracks are ignored.
|
|
66
|
+
* A cycle throws a ScoreValidationError that names it (`a -> b -> a`).
|
|
67
|
+
*/
|
|
68
|
+
export function routingOrder(score: TrackScore): readonly string[] {
|
|
69
|
+
const ids = new Set(score.tracks.map((track) => track.id));
|
|
70
|
+
const sources = new Map<string, string[]>();
|
|
71
|
+
for (const track of score.tracks)
|
|
72
|
+
sources.set(
|
|
73
|
+
track.id,
|
|
74
|
+
trackRefs(track)
|
|
75
|
+
.filter((ref) => ref.kind === "audio" && ids.has(ref.trackId))
|
|
76
|
+
.map((ref) => ref.trackId),
|
|
77
|
+
);
|
|
78
|
+
const order: string[] = [];
|
|
79
|
+
const state = new Map<string, "visiting" | "done">();
|
|
80
|
+
const stack: string[] = [];
|
|
81
|
+
const visit = (id: string): void => {
|
|
82
|
+
const seen = state.get(id);
|
|
83
|
+
if (seen === "done") return;
|
|
84
|
+
if (seen === "visiting") {
|
|
85
|
+
const cycle = [...stack.slice(stack.indexOf(id)), id];
|
|
86
|
+
throw new ScoreValidationError(
|
|
87
|
+
`track routing has a cycle: ${cycle.join(" -> ")}`,
|
|
88
|
+
"routing-cycle",
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
state.set(id, "visiting");
|
|
92
|
+
stack.push(id);
|
|
93
|
+
for (const source of sources.get(id) ?? []) visit(source);
|
|
94
|
+
stack.pop();
|
|
95
|
+
state.set(id, "done");
|
|
96
|
+
order.push(id);
|
|
97
|
+
};
|
|
98
|
+
for (const track of score.tracks) visit(track.id);
|
|
99
|
+
return order;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* The notes a track's render depends on through notes edges, by referenced
|
|
104
|
+
* track id (sorted, deduplicated), for its stem digest. Missing tracks and
|
|
105
|
+
* self references contribute nothing.
|
|
106
|
+
*/
|
|
107
|
+
export function notesDigestInputs(
|
|
108
|
+
score: TrackScore,
|
|
109
|
+
trackId: string,
|
|
110
|
+
): readonly Readonly<{ trackId: string; notes: readonly Note[] }>[] {
|
|
111
|
+
const track = score.tracks.find((candidate) => candidate.id === trackId);
|
|
112
|
+
if (!track) return [];
|
|
113
|
+
const ids = new Set(score.tracks.map((candidate) => candidate.id));
|
|
114
|
+
const followed = [
|
|
115
|
+
...new Set(
|
|
116
|
+
trackRefs(track)
|
|
117
|
+
.filter(
|
|
118
|
+
(ref) =>
|
|
119
|
+
ref.kind === "notes" &&
|
|
120
|
+
ref.trackId !== trackId &&
|
|
121
|
+
ids.has(ref.trackId),
|
|
122
|
+
)
|
|
123
|
+
.map((ref) => ref.trackId),
|
|
124
|
+
),
|
|
125
|
+
].sort();
|
|
126
|
+
return followed.map((id) => ({
|
|
127
|
+
trackId: id,
|
|
128
|
+
notes: score.notes.filter((note) => note.trackId === id),
|
|
129
|
+
}));
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* The vocoder's modulator (0.7): `vocoder.src` is an audio edge, so the
|
|
134
|
+
* modulator renders first and removing it drops the `src` (the carrier then
|
|
135
|
+
* plays dry until a new source is set).
|
|
136
|
+
*/
|
|
137
|
+
registerTrackRefs("vocoder", {
|
|
138
|
+
refs: (track) =>
|
|
139
|
+
track.vocoder?.src !== undefined
|
|
140
|
+
? [{ trackId: track.vocoder.src, kind: "audio" }]
|
|
141
|
+
: [],
|
|
142
|
+
drop: (track, removedId) => {
|
|
143
|
+
if (track.vocoder?.src !== removedId) return track;
|
|
144
|
+
const { src: _src, ...rest } = track.vocoder;
|
|
145
|
+
return { ...track, vocoder: rest };
|
|
146
|
+
},
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
// ---------------------------------------------------------------------------
|
|
150
|
+
// Registered reference fields (one block per lane).
|
|
151
|
+
|
|
152
|
+
// autotune: `Track.autotune.from` follows another track's notes.
|
|
153
|
+
registerTrackRefs("autotune.from", {
|
|
154
|
+
refs: (track) =>
|
|
155
|
+
track.autotune?.from !== undefined
|
|
156
|
+
? [{ trackId: track.autotune.from, kind: "notes" }]
|
|
157
|
+
: [],
|
|
158
|
+
drop: (track, removedId) => {
|
|
159
|
+
if (track.autotune?.from !== removedId) return track;
|
|
160
|
+
// Without its melody the track follows its own notes; `to` and the
|
|
161
|
+
// preset stay as the user set them.
|
|
162
|
+
const { from: _from, ...autotune } = track.autotune;
|
|
163
|
+
return { ...track, autotune };
|
|
164
|
+
},
|
|
165
|
+
});
|