@hraness/dawg 0.6.0 → 0.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +34 -0
- package/DAWG.md +338 -51
- package/README.md +4 -4
- package/core/chords.ts +492 -4
- package/core/diff.ts +5 -0
- package/core/expression.ts +143 -6
- package/core/fx.ts +258 -6
- package/core/granular.ts +91 -0
- package/core/instruments.ts +161 -0
- package/core/keys.ts +573 -4
- package/core/resonators.ts +291 -3
- package/core/score.ts +349 -3
- package/core/sdk/print.ts +78 -3
- package/core/sdk/v1.ts +1320 -36
- package/core/sections.ts +12 -4
- package/core/strings.ts +237 -2
- package/core/winds.ts +652 -0
- package/guides/automation.md +1 -0
- package/guides/chords.md +3 -1
- package/guides/effects.md +3 -2
- package/guides/media.md +1 -1
- package/guides/performance.md +3 -0
- package/guides/resample.md +26 -0
- package/guides/sounds.md +7 -6
- package/package.json +1 -1
- package/src/agent/agent.ts +8 -2
- package/src/agent/chord-tools.ts +153 -1
- package/src/agent/expression-tools.ts +91 -0
- package/src/agent/ops.ts +4 -1
- package/src/agent/preview-tool.ts +2 -1
- package/src/agent/resample-tool.ts +131 -0
- package/src/agent/tools.ts +200 -15
- package/src/agent/xcb-agent.ts +8 -2
- package/src/audio/arrange.ts +21 -0
- package/src/audio/dsp/envelope.ts +161 -0
- package/src/audio/dsp/shift.ts +256 -0
- package/src/audio/effects/chain.ts +18 -3
- package/src/audio/effects/common.ts +14 -0
- package/src/audio/effects/convolution.ts +106 -1
- package/src/audio/effects/gaze.ts +354 -0
- package/src/audio/engine.ts +46 -5
- package/src/audio/fit.ts +45 -0
- package/src/audio/granular.ts +352 -21
- package/src/audio/instrument-check.ts +9 -5
- package/src/audio/instruments.ts +32 -3
- package/src/audio/keys/electric.ts +420 -0
- package/src/audio/keys/engine.ts +143 -19
- package/src/audio/keys/organ.ts +1335 -0
- package/src/audio/keys/piano.ts +50 -6
- package/src/audio/keys/sympathetic.ts +127 -0
- package/src/audio/live-worker.ts +3 -1
- package/src/audio/live.ts +136 -9
- package/src/audio/preview.ts +15 -1
- package/src/audio/resample.ts +285 -0
- package/src/audio/resonators.ts +10 -2
- package/src/audio/sampler.ts +168 -21
- package/src/audio/samples.ts +26 -1
- package/src/audio/strings/body.ts +29 -16
- package/src/audio/strings/bow.ts +656 -0
- package/src/audio/strings/engine.ts +266 -20
- package/src/audio/strings/measure.test-helpers.ts +2 -0
- package/src/audio/wav.ts +119 -19
- package/src/audio/winds/engine.ts +302 -0
- package/src/audio/winds/filters.ts +153 -0
- package/src/audio/winds/pitch.ts +104 -0
- package/src/audio/winds/trim.ts +80 -0
- package/src/audio/winds/trims.ts +917 -0
- package/src/audio/winds/voice.ts +479 -0
- package/src/commands/expression.ts +103 -22
- package/src/commands/fx.ts +4 -1
- package/src/commands/granular.ts +7 -2
- package/src/commands/help.ts +91 -8
- package/src/commands/keys.ts +287 -11
- package/src/commands/modal.ts +67 -4
- package/src/commands/resample.ts +281 -0
- package/src/commands/rig.ts +5 -1
- package/src/commands/sample.ts +49 -2
- package/src/commands/shift.ts +119 -0
- package/src/commands/string.ts +28 -2
- package/src/commands/strum.ts +473 -0
- package/src/commands/wind.ts +244 -0
- package/src/main.ts +129 -7
- package/src/project/check.ts +5 -1
- package/src/render.ts +16 -0
- package/src/session/presence.ts +16 -3
- package/src/tui/audition.ts +1 -1
- package/src/tui/menu.ts +637 -35
- package/src/tui/modal-menu.ts +34 -7
- package/src/tui/performance-menu.ts +42 -0
- package/src/tui/play-chords.ts +104 -3
- package/src/tui/play-mode.ts +1 -0
- package/src/tui/play-session.ts +34 -9
- package/src/tui/wind-menu.ts +144 -0
package/core/resonators.ts
CHANGED
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
* maximum weight.
|
|
19
19
|
*/
|
|
20
20
|
import { resolveInstrumentWord } from "./instruments.ts";
|
|
21
|
+
import { WIND_INSTRUMENT, windPresetFor, type TrackWind } from "./winds.ts";
|
|
21
22
|
import {
|
|
22
23
|
FxValidationError,
|
|
23
24
|
isRecord,
|
|
@@ -84,6 +85,22 @@ export const MODAL_PRESET_NAMES = Object.freeze([
|
|
|
84
85
|
"bowl",
|
|
85
86
|
"gong",
|
|
86
87
|
"timpani",
|
|
88
|
+
// f061-gamelan-winds: gamelan, small bells and frame drums (lane A2).
|
|
89
|
+
"crotales",
|
|
90
|
+
"musicbox",
|
|
91
|
+
"toypiano",
|
|
92
|
+
"saron",
|
|
93
|
+
"demung",
|
|
94
|
+
"slenthem",
|
|
95
|
+
"gangsa",
|
|
96
|
+
"gender",
|
|
97
|
+
"bonang",
|
|
98
|
+
"kenong",
|
|
99
|
+
"kethuk",
|
|
100
|
+
"kempul",
|
|
101
|
+
"daf",
|
|
102
|
+
"bodhran",
|
|
103
|
+
"tabla",
|
|
87
104
|
] as const);
|
|
88
105
|
export type ModalPresetName = (typeof MODAL_PRESET_NAMES)[number];
|
|
89
106
|
|
|
@@ -99,8 +116,29 @@ export const MODAL_ALIASES: Readonly<Record<string, ModalPresetName>> =
|
|
|
99
116
|
singingbowl: "bowl",
|
|
100
117
|
kettledrum: "timpani",
|
|
101
118
|
tubularbells: "chimes",
|
|
119
|
+
// f061-gamelan-winds. `gamelan` is a browse group, not an alias.
|
|
120
|
+
crotale: "crotales",
|
|
121
|
+
musicalbox: "musicbox",
|
|
122
|
+
framedrum: "bodhran",
|
|
102
123
|
});
|
|
103
124
|
|
|
125
|
+
/**
|
|
126
|
+
* The gamelan presets, in browse order (the `gamelan` hint opens these
|
|
127
|
+
* under Mallets and bells).
|
|
128
|
+
*/
|
|
129
|
+
export const GAMELAN_PRESETS: readonly ModalPresetName[] = Object.freeze([
|
|
130
|
+
"saron",
|
|
131
|
+
"demung",
|
|
132
|
+
"slenthem",
|
|
133
|
+
"gangsa",
|
|
134
|
+
"gender",
|
|
135
|
+
"bonang",
|
|
136
|
+
"kenong",
|
|
137
|
+
"kethuk",
|
|
138
|
+
"kempul",
|
|
139
|
+
"gong",
|
|
140
|
+
]);
|
|
141
|
+
|
|
104
142
|
const num = (
|
|
105
143
|
min: number,
|
|
106
144
|
max: number,
|
|
@@ -364,6 +402,186 @@ export const MODAL_PRESETS: Readonly<Record<ModalPresetName, ModalPreset>> =
|
|
|
364
402
|
"pedal timpani, felt sticks, a small strike glide",
|
|
365
403
|
"Beethoven, Brahms, Shostakovich, orchestral",
|
|
366
404
|
),
|
|
405
|
+
// f061-gamelan-winds (lane A2): appended; rows above are unchanged.
|
|
406
|
+
// Gamelan ranges follow the instruments' slendro/pelog octaves; tune
|
|
407
|
+
// them with `tuning slendro` or `tuning pelog` (core/tuning.ts).
|
|
408
|
+
crotales: preset(
|
|
409
|
+
{
|
|
410
|
+
body: "crotale",
|
|
411
|
+
ring: 6,
|
|
412
|
+
tilt: 0.4,
|
|
413
|
+
hardness: 0.85,
|
|
414
|
+
position: 0.5,
|
|
415
|
+
gain: 1.6,
|
|
416
|
+
},
|
|
417
|
+
[84, 108],
|
|
418
|
+
"antique cymbals, long shimmering ring",
|
|
419
|
+
"Messiaen, ambient",
|
|
420
|
+
),
|
|
421
|
+
musicbox: preset(
|
|
422
|
+
{
|
|
423
|
+
body: "musicbox",
|
|
424
|
+
ring: 1.8,
|
|
425
|
+
tilt: 0.7,
|
|
426
|
+
hardness: 1,
|
|
427
|
+
position: 0.8,
|
|
428
|
+
gain: 1.7,
|
|
429
|
+
},
|
|
430
|
+
[60, 100],
|
|
431
|
+
"plucked steel comb",
|
|
432
|
+
"ambient, OPN, A. G. Cook",
|
|
433
|
+
),
|
|
434
|
+
toypiano: preset(
|
|
435
|
+
{
|
|
436
|
+
body: "toypiano",
|
|
437
|
+
ring: 1,
|
|
438
|
+
tilt: 0.6,
|
|
439
|
+
hardness: 0.9,
|
|
440
|
+
position: 0.6,
|
|
441
|
+
click: 0.3,
|
|
442
|
+
gain: 2,
|
|
443
|
+
},
|
|
444
|
+
[60, 96],
|
|
445
|
+
"hammered toy-piano rods, clanky",
|
|
446
|
+
"hyperpop, A. G. Cook, Four Tet",
|
|
447
|
+
),
|
|
448
|
+
saron: preset(
|
|
449
|
+
{
|
|
450
|
+
body: "saron",
|
|
451
|
+
ring: 2.5,
|
|
452
|
+
tilt: 0.6,
|
|
453
|
+
hardness: 0.85,
|
|
454
|
+
position: 0.5,
|
|
455
|
+
damp: 0.85,
|
|
456
|
+
release: 0.1,
|
|
457
|
+
},
|
|
458
|
+
[60, 84],
|
|
459
|
+
"saron barung: bronze keys, horn mallet, the balungan melody; damped at the next note (tutupan)",
|
|
460
|
+
"Javanese gamelan, Reich",
|
|
461
|
+
),
|
|
462
|
+
demung: preset(
|
|
463
|
+
{
|
|
464
|
+
body: "saron",
|
|
465
|
+
ring: 3.2,
|
|
466
|
+
tilt: 0.6,
|
|
467
|
+
hardness: 0.75,
|
|
468
|
+
position: 0.5,
|
|
469
|
+
damp: 0.8,
|
|
470
|
+
release: 0.12,
|
|
471
|
+
},
|
|
472
|
+
[48, 72],
|
|
473
|
+
"demung: the saron an octave lower, fuller",
|
|
474
|
+
"Javanese gamelan",
|
|
475
|
+
),
|
|
476
|
+
slenthem: preset(
|
|
477
|
+
{ body: "gender", ring: 4.5, tilt: 0.5, hardness: 0.25, position: 0.5 },
|
|
478
|
+
[43, 60],
|
|
479
|
+
"slenthem: low bronze keys over tubes, padded mallet",
|
|
480
|
+
"Javanese gamelan, ambient",
|
|
481
|
+
),
|
|
482
|
+
gangsa: preset(
|
|
483
|
+
{
|
|
484
|
+
body: "saron",
|
|
485
|
+
ring: 2.2,
|
|
486
|
+
tilt: 0.6,
|
|
487
|
+
hardness: 0.9,
|
|
488
|
+
position: 0.5,
|
|
489
|
+
ombak: 7,
|
|
490
|
+
damp: 0.85,
|
|
491
|
+
release: 0.1,
|
|
492
|
+
},
|
|
493
|
+
[60, 89],
|
|
494
|
+
"Balinese metallophone: pengumbang and pengisep beating at 7 Hz (ombak)",
|
|
495
|
+
"Balinese gamelan, Lopatin, Four Tet",
|
|
496
|
+
),
|
|
497
|
+
gender: preset(
|
|
498
|
+
{ body: "gender", ring: 5, tilt: 0.5, hardness: 0.3, position: 0.5 },
|
|
499
|
+
[48, 84],
|
|
500
|
+
"gender: thin bronze keys over tubes, padded mallets, long ring",
|
|
501
|
+
"Javanese gamelan, ambient",
|
|
502
|
+
),
|
|
503
|
+
bonang: preset(
|
|
504
|
+
{
|
|
505
|
+
body: "bonang",
|
|
506
|
+
ring: 1.8,
|
|
507
|
+
tilt: 0.7,
|
|
508
|
+
hardness: 0.6,
|
|
509
|
+
position: 0.5,
|
|
510
|
+
damp: 0.7,
|
|
511
|
+
release: 0.12,
|
|
512
|
+
},
|
|
513
|
+
[55, 84],
|
|
514
|
+
"bonang: small kettle gongs in a row, padded sticks",
|
|
515
|
+
"Javanese gamelan, Reich",
|
|
516
|
+
),
|
|
517
|
+
kenong: preset(
|
|
518
|
+
{ body: "bonang", ring: 4, tilt: 0.5, hardness: 0.45, position: 0.5 },
|
|
519
|
+
[55, 72],
|
|
520
|
+
"kenong: large kettle gong that marks the phrase",
|
|
521
|
+
"Javanese gamelan",
|
|
522
|
+
),
|
|
523
|
+
kethuk: preset(
|
|
524
|
+
{
|
|
525
|
+
body: "bonang",
|
|
526
|
+
ring: 0.5,
|
|
527
|
+
tilt: 0.7,
|
|
528
|
+
hardness: 0.5,
|
|
529
|
+
position: 0.5,
|
|
530
|
+
damp: 1,
|
|
531
|
+
release: 0.08,
|
|
532
|
+
},
|
|
533
|
+
[55, 67],
|
|
534
|
+
"kethuk: small kettle gong, struck and damped (tutupan)",
|
|
535
|
+
"Javanese gamelan",
|
|
536
|
+
),
|
|
537
|
+
kempul: preset(
|
|
538
|
+
{ body: "kempul", ring: 6, tilt: 0.4, hardness: 0.25, position: 0.5 },
|
|
539
|
+
[43, 67],
|
|
540
|
+
"kempul: hanging gong, slow beating",
|
|
541
|
+
"Javanese gamelan",
|
|
542
|
+
),
|
|
543
|
+
daf: preset(
|
|
544
|
+
{
|
|
545
|
+
body: "frame",
|
|
546
|
+
ring: 0.5,
|
|
547
|
+
tilt: 0.8,
|
|
548
|
+
hardness: 0.6,
|
|
549
|
+
position: 0.85,
|
|
550
|
+
buzz: 0.6,
|
|
551
|
+
},
|
|
552
|
+
[43, 55],
|
|
553
|
+
"Persian frame drum with ring jingles",
|
|
554
|
+
"Persian, Sufi",
|
|
555
|
+
),
|
|
556
|
+
bodhran: preset(
|
|
557
|
+
{
|
|
558
|
+
body: "frame",
|
|
559
|
+
ring: 0.35,
|
|
560
|
+
tilt: 0.9,
|
|
561
|
+
hardness: 0.5,
|
|
562
|
+
position: 0.8,
|
|
563
|
+
damp: 0.4,
|
|
564
|
+
gain: 2,
|
|
565
|
+
},
|
|
566
|
+
[40, 52],
|
|
567
|
+
"Irish frame drum, tipper",
|
|
568
|
+
"celtic",
|
|
569
|
+
),
|
|
570
|
+
tabla: preset(
|
|
571
|
+
{
|
|
572
|
+
body: "tabla",
|
|
573
|
+
ring: 1.2,
|
|
574
|
+
tilt: 0.5,
|
|
575
|
+
hardness: 0.7,
|
|
576
|
+
position: 0.75,
|
|
577
|
+
strikebend: 0.4,
|
|
578
|
+
strikedecay: 0.12,
|
|
579
|
+
gain: 2,
|
|
580
|
+
},
|
|
581
|
+
[57, 69],
|
|
582
|
+
"dayan: tuned treble drum with near-harmonic syahi modes",
|
|
583
|
+
"Hindustani, Talvin Singh, Four Tet",
|
|
584
|
+
),
|
|
367
585
|
});
|
|
368
586
|
|
|
369
587
|
/** Default preset of a `modal` field that names none. */
|
|
@@ -388,6 +606,13 @@ export type TrackModal = Readonly<{
|
|
|
388
606
|
strikedecay?: number;
|
|
389
607
|
gain?: number;
|
|
390
608
|
body?: ModalBody;
|
|
609
|
+
/**
|
|
610
|
+
* f061-gamelan-winds: the id of this track's partner in an ombak pair.
|
|
611
|
+
* A paired track (the pengisep) plays one voice `ombak` Hz above the
|
|
612
|
+
* pitch instead of its own twin, so it beats against its partner (the
|
|
613
|
+
* pengumbang). Only this track's render changes.
|
|
614
|
+
*/
|
|
615
|
+
pair?: string;
|
|
391
616
|
}>;
|
|
392
617
|
|
|
393
618
|
export function isModalPreset(name: string): name is ModalPresetName {
|
|
@@ -425,8 +650,17 @@ export function normalizeModal(input: unknown): TrackModal | undefined {
|
|
|
425
650
|
throw new FxValidationError("track modal must be an object or null");
|
|
426
651
|
const params: Record<string, unknown> = {};
|
|
427
652
|
let presetName: ModalPresetName | undefined;
|
|
653
|
+
let pair: string | undefined;
|
|
428
654
|
for (const [key, value] of Object.entries(input)) {
|
|
429
655
|
if (value === undefined || value === null) continue;
|
|
656
|
+
if (key === "pair") {
|
|
657
|
+
if (typeof value !== "string" || !PAIR_ID.test(value))
|
|
658
|
+
throw new FxValidationError(
|
|
659
|
+
"modal pair must be a track id (letters, digits, - or _)",
|
|
660
|
+
);
|
|
661
|
+
pair = value;
|
|
662
|
+
continue;
|
|
663
|
+
}
|
|
430
664
|
if (key === "preset") {
|
|
431
665
|
if (typeof value !== "string")
|
|
432
666
|
throw new FxValidationError(
|
|
@@ -451,6 +685,7 @@ export function normalizeModal(input: unknown): TrackModal | undefined {
|
|
|
451
685
|
return Object.freeze({
|
|
452
686
|
...(presetName ? { preset: presetName } : {}),
|
|
453
687
|
...values,
|
|
688
|
+
...(pair === undefined ? {} : { pair }),
|
|
454
689
|
}) as TrackModal;
|
|
455
690
|
}
|
|
456
691
|
|
|
@@ -468,7 +703,12 @@ export function modalSettings(
|
|
|
468
703
|
if (modal?.mallet) out.hardness = MODAL_MALLETS[modal.mallet];
|
|
469
704
|
if (modal)
|
|
470
705
|
for (const [key, value] of Object.entries(modal))
|
|
471
|
-
if (
|
|
706
|
+
if (
|
|
707
|
+
key !== "preset" &&
|
|
708
|
+
key !== "mallet" &&
|
|
709
|
+
key !== "pair" &&
|
|
710
|
+
value !== undefined
|
|
711
|
+
)
|
|
472
712
|
out[key] = value;
|
|
473
713
|
if (lane)
|
|
474
714
|
for (const key of MODAL_LANE_PARAMS) {
|
|
@@ -496,7 +736,10 @@ export const MAX_RESONATOR_TAIL = 30;
|
|
|
496
736
|
/** Voices one modal track sounds at once; the oldest onset is stolen. */
|
|
497
737
|
export const MAX_RESONATOR_VOICES = 32;
|
|
498
738
|
/** Joins the stem cache key so mode-table changes invalidate stems. */
|
|
499
|
-
export const RESONATOR_TABLE_VERSION =
|
|
739
|
+
export const RESONATOR_TABLE_VERSION = 2;
|
|
740
|
+
|
|
741
|
+
/** A modal `pair` value: a track id. */
|
|
742
|
+
const PAIR_ID = /^[A-Za-z0-9_-]{1,64}$/;
|
|
500
743
|
|
|
501
744
|
/**
|
|
502
745
|
* A modal track's ring-out after its last note-off, in seconds: the preset
|
|
@@ -524,8 +767,17 @@ export function modalTailSeconds(
|
|
|
524
767
|
export function instrumentPatchForWord(word: string): {
|
|
525
768
|
instrument: string;
|
|
526
769
|
modal?: TrackModal;
|
|
770
|
+
wind?: TrackWind;
|
|
527
771
|
} {
|
|
528
772
|
const meaning = resolveInstrumentWord(word);
|
|
773
|
+
// f061-gamelan-winds: `flute`, `sax`, `tuba` play a wind preset.
|
|
774
|
+
if (meaning?.field === "wind" && meaning.instrument === WIND_INSTRUMENT) {
|
|
775
|
+
const preset = meaning.preset ? windPresetFor(meaning.preset) : undefined;
|
|
776
|
+
return {
|
|
777
|
+
instrument: WIND_INSTRUMENT,
|
|
778
|
+
wind: Object.freeze(preset ? { preset } : {}) as TrackWind,
|
|
779
|
+
};
|
|
780
|
+
}
|
|
529
781
|
if (meaning?.field === "modal" && meaning.instrument === MODAL_INSTRUMENT) {
|
|
530
782
|
const preset = meaning.preset ? modalPresetFor(meaning.preset) : undefined;
|
|
531
783
|
return {
|
|
@@ -538,7 +790,40 @@ export function instrumentPatchForWord(word: string): {
|
|
|
538
790
|
|
|
539
791
|
/** True for a word that resolves to the modal engine (`vibes`, `gong`). */
|
|
540
792
|
export function isModalWord(word: string): boolean {
|
|
541
|
-
|
|
793
|
+
const meaning = resolveInstrumentWord(word);
|
|
794
|
+
return (
|
|
795
|
+
meaning?.instrument === MODAL_INSTRUMENT ||
|
|
796
|
+
(meaning?.field === "wind" && meaning.instrument === WIND_INSTRUMENT)
|
|
797
|
+
);
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
/**
|
|
801
|
+
* `dawg check` notes for ombak pairs whose partner is missing or is not a
|
|
802
|
+
* modal track: the pengisep then plays ombak Hz sharp with nothing to beat
|
|
803
|
+
* against.
|
|
804
|
+
*/
|
|
805
|
+
export function modalPairWarnings(
|
|
806
|
+
tracks: readonly Readonly<{
|
|
807
|
+
id: string;
|
|
808
|
+
instrument: string;
|
|
809
|
+
modal?: Readonly<{ pair?: string }>;
|
|
810
|
+
}>[],
|
|
811
|
+
): string[] {
|
|
812
|
+
const out: string[] = [];
|
|
813
|
+
for (const track of tracks) {
|
|
814
|
+
const pair = track.modal?.pair;
|
|
815
|
+
if (pair === undefined) continue;
|
|
816
|
+
const partner = tracks.find((candidate) => candidate.id === pair);
|
|
817
|
+
if (!partner)
|
|
818
|
+
out.push(
|
|
819
|
+
`track ${track.id}: modal pair "${pair}" names no track; it plays ombak Hz sharp alone`,
|
|
820
|
+
);
|
|
821
|
+
else if (partner.instrument !== MODAL_INSTRUMENT || !partner.modal)
|
|
822
|
+
out.push(
|
|
823
|
+
`track ${track.id}: modal pair "${pair}" is not a modal track; give it the same preset (modal gangsa)`,
|
|
824
|
+
);
|
|
825
|
+
}
|
|
826
|
+
return out;
|
|
542
827
|
}
|
|
543
828
|
|
|
544
829
|
/**
|
|
@@ -572,3 +857,6 @@ export function legacyResonatorWarnings(
|
|
|
572
857
|
}
|
|
573
858
|
return out;
|
|
574
859
|
}
|
|
860
|
+
|
|
861
|
+
// f061-gamelan-winds: blown instruments live in core/winds.ts.
|
|
862
|
+
export * from "./winds.ts";
|