@hraness/dawg 0.5.0 → 0.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +63 -0
- package/DAWG.md +592 -49
- package/README.md +4 -4
- package/core/chords.ts +492 -4
- package/core/diff.ts +9 -0
- package/core/expression.ts +143 -6
- package/core/fx.ts +689 -2
- package/core/granular.ts +619 -0
- package/core/instruments.ts +442 -0
- package/core/keys.ts +955 -0
- package/core/resonators.ts +862 -0
- package/core/score.ts +529 -8
- package/core/sdk/eval-child.ts +2 -0
- package/core/sdk/print.ts +247 -16
- package/core/sdk/sync-instruments.ts +58 -0
- package/core/sdk/v1.ts +2417 -40
- package/core/sections.ts +44 -10
- package/core/strings.ts +1080 -0
- package/core/winds.ts +652 -0
- package/guides/automation.md +1 -0
- package/guides/chords.md +3 -1
- package/guides/effects.md +5 -2
- package/guides/media.md +1 -1
- package/guides/performance.md +3 -0
- package/guides/resample.md +26 -0
- package/guides/sounds.md +7 -2
- package/guides/tempo.md +1 -0
- package/package.json +1 -1
- package/src/agent/agent.ts +13 -0
- package/src/agent/chord-tools.ts +153 -1
- package/src/agent/expression-tools.ts +91 -0
- package/src/agent/granular-tools.ts +138 -0
- package/src/agent/models.ts +4 -4
- package/src/agent/ops.ts +42 -2
- package/src/agent/preview-tool.ts +7 -1
- package/src/agent/resample-tool.ts +131 -0
- package/src/agent/tools.ts +726 -17
- package/src/agent/xcb-agent.ts +13 -0
- package/src/audio/arrange.ts +42 -3
- package/src/audio/dsp/bank.ts +233 -0
- package/src/audio/dsp/envelope.ts +161 -0
- package/src/audio/dsp/fft.ts +6 -0
- package/src/audio/dsp/filters.ts +57 -0
- package/src/audio/dsp/interp.ts +112 -0
- package/src/audio/dsp/modal.ts +684 -0
- package/src/audio/dsp/onset.ts +197 -0
- package/src/audio/dsp/oversample.ts +202 -0
- package/src/audio/dsp/rng.ts +37 -0
- package/src/audio/dsp/shape.ts +81 -0
- package/src/audio/dsp/shift.ts +256 -0
- package/src/audio/dsp/stft.ts +73 -0
- package/src/audio/dsp/window.ts +77 -0
- package/src/audio/effects/chain.ts +23 -3
- package/src/audio/effects/common.ts +14 -0
- package/src/audio/effects/convolution.ts +106 -1
- package/src/audio/effects/gaze.ts +354 -0
- package/src/audio/effects/rig/cab.ts +99 -0
- package/src/audio/effects/rig/filters.ts +152 -0
- package/src/audio/effects/rig/gate.ts +39 -0
- package/src/audio/effects/rig/head.ts +487 -0
- package/src/audio/effects/rig/index.ts +170 -0
- package/src/audio/effects/rig/section.ts +78 -0
- package/src/audio/effects/rig/stomp.ts +238 -0
- package/src/audio/engine.ts +56 -6
- package/src/audio/fit.ts +447 -0
- package/src/audio/granular.ts +995 -0
- package/src/audio/instrument-check.ts +137 -0
- package/src/audio/instruments.ts +140 -0
- package/src/audio/keys/dsp.ts +323 -0
- package/src/audio/keys/electric.ts +420 -0
- package/src/audio/keys/engine.ts +485 -0
- package/src/audio/keys/organ.ts +1335 -0
- package/src/audio/keys/piano.ts +476 -0
- package/src/audio/keys/sympathetic.ts +127 -0
- package/src/audio/live-worker.ts +56 -0
- package/src/audio/live.ts +361 -15
- package/src/audio/loudness.ts +65 -20
- package/src/audio/preview.ts +17 -1
- package/src/audio/resample.ts +285 -0
- package/src/audio/resonators.ts +295 -0
- package/src/audio/sampler.ts +328 -35
- package/src/audio/samples.ts +56 -8
- package/src/audio/strings/body.ts +263 -0
- package/src/audio/strings/bow.ts +656 -0
- package/src/audio/strings/engine.ts +615 -0
- package/src/audio/strings/loop.ts +119 -0
- package/src/audio/strings/measure.test-helpers.ts +200 -0
- package/src/audio/strings/pluck.ts +354 -0
- package/src/audio/warp.ts +61 -0
- package/src/audio/wav.ts +191 -19
- package/src/audio/winds/engine.ts +302 -0
- package/src/audio/winds/filters.ts +153 -0
- package/src/audio/winds/pitch.ts +104 -0
- package/src/audio/winds/trim.ts +80 -0
- package/src/audio/winds/trims.ts +917 -0
- package/src/audio/winds/voice.ts +479 -0
- package/src/commands/expression.ts +111 -23
- package/src/commands/fit.ts +135 -0
- package/src/commands/fx.ts +35 -1
- package/src/commands/granular.ts +432 -0
- package/src/commands/help.ts +159 -5
- package/src/commands/keys.ts +629 -0
- package/src/commands/modal.ts +310 -0
- package/src/commands/resample.ts +281 -0
- package/src/commands/rig.ts +264 -0
- package/src/commands/sample.ts +52 -2
- package/src/commands/shift.ts +119 -0
- package/src/commands/string.ts +201 -0
- package/src/commands/strum.ts +473 -0
- package/src/commands/time.ts +4 -1
- package/src/commands/wind.ts +244 -0
- package/src/main.ts +339 -15
- package/src/project/check.ts +17 -0
- package/src/render.ts +49 -4
- package/src/session/presence.ts +16 -3
- package/src/tui/audition.ts +2 -2
- package/src/tui/granular-menu.ts +278 -0
- package/src/tui/menu.ts +934 -9
- package/src/tui/modal-menu.ts +145 -0
- package/src/tui/performance-menu.ts +42 -0
- package/src/tui/play-chords.ts +104 -3
- package/src/tui/play-mode.ts +1 -0
- package/src/tui/play-session.ts +70 -2
- package/src/tui/wind-menu.ts +144 -0
package/core/sdk/print.ts
CHANGED
|
@@ -8,11 +8,26 @@
|
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
import { DRUM_VOICES, isDrumInstrument } from "../drums.ts";
|
|
11
|
+
import type { TrackGranular } from "../granular.ts";
|
|
11
12
|
import { DEFAULT_FIXED_VELOCITY } from "../expression.ts";
|
|
12
13
|
import type { RhythmRow } from "../euclid.ts";
|
|
13
|
-
import {
|
|
14
|
+
import {
|
|
15
|
+
FX_LANES,
|
|
16
|
+
RIG_STAGES,
|
|
17
|
+
fxSpec,
|
|
18
|
+
rigPresetKeys,
|
|
19
|
+
rigPresetOf,
|
|
20
|
+
type FxName,
|
|
21
|
+
type FxValues,
|
|
22
|
+
} from "../fx.ts";
|
|
14
23
|
import { MASTER_SPECS, MASTER_UNITS, type SongMaster } from "../master.ts";
|
|
15
24
|
import { midiToPitch } from "../pitch.ts";
|
|
25
|
+
import {
|
|
26
|
+
MODAL_INSTRUMENT,
|
|
27
|
+
MODAL_PARAMS,
|
|
28
|
+
type TrackModal,
|
|
29
|
+
} from "../resonators.ts";
|
|
30
|
+
import { WIND_INSTRUMENT, WIND_PARAMS, type TrackWind } from "../winds.ts";
|
|
16
31
|
import { rhythmVoicePitch, rowInSync } from "../rhythm.ts";
|
|
17
32
|
import {
|
|
18
33
|
BUILTIN_TABLE_PREFIX,
|
|
@@ -35,6 +50,7 @@ import {
|
|
|
35
50
|
import { trackSlug } from "../slug.ts";
|
|
36
51
|
import { barStartTick } from "../tempo.ts";
|
|
37
52
|
import type { Tuning } from "../tuning.ts";
|
|
53
|
+
import { DEFAULT_STRING_PRESET, type TrackString } from "../strings.ts";
|
|
38
54
|
import { DEFAULT_HIT_LENGTH, DEFAULT_VELOCITY } from "./v1.ts";
|
|
39
55
|
|
|
40
56
|
const WIDTH = 80;
|
|
@@ -137,6 +153,10 @@ const RESERVED = new Set([
|
|
|
137
153
|
"every",
|
|
138
154
|
"sampler",
|
|
139
155
|
"wavetable",
|
|
156
|
+
"rig",
|
|
157
|
+
"granular",
|
|
158
|
+
"modal",
|
|
159
|
+
"wind",
|
|
140
160
|
"slices",
|
|
141
161
|
"euclid",
|
|
142
162
|
"euclidRot",
|
|
@@ -381,22 +401,62 @@ export function printTrack(score: TrackScore, track: Track): string {
|
|
|
381
401
|
used.add("note");
|
|
382
402
|
return printNote(score, note, !kit && !slots, INDENT + INDENT);
|
|
383
403
|
});
|
|
404
|
+
const grained =
|
|
405
|
+
track.instrument === "granular" && track.granular !== undefined;
|
|
406
|
+
if (grained) used.add("granular");
|
|
384
407
|
if (track.sampler) used.add("sampler");
|
|
385
408
|
const wavetable = isWavetableInstrument(track.instrument)
|
|
386
409
|
? wavetableOf(track)
|
|
387
410
|
: undefined;
|
|
388
411
|
if (wavetable) used.add("wavetable");
|
|
412
|
+
const stringed = track.instrument === "string" && track.string !== undefined;
|
|
413
|
+
if (stringed) used.add("stringed");
|
|
414
|
+
const modalCall =
|
|
415
|
+
track.instrument === MODAL_INSTRUMENT && track.modal
|
|
416
|
+
? printModal(track.modal, INDENT)
|
|
417
|
+
: undefined;
|
|
418
|
+
if (modalCall?.startsWith("modal(")) used.add("modal");
|
|
419
|
+
const windCall =
|
|
420
|
+
track.instrument === WIND_INSTRUMENT && track.wind
|
|
421
|
+
? printWind(track.wind, INDENT)
|
|
422
|
+
: undefined;
|
|
423
|
+
if (windCall?.startsWith("wind(")) used.add("wind");
|
|
389
424
|
|
|
390
425
|
const entries: string[] = [
|
|
391
426
|
`id: ${str(track.id)}`,
|
|
392
427
|
`name: ${str(track.name)}`,
|
|
393
428
|
];
|
|
394
|
-
if (
|
|
429
|
+
if (grained) {
|
|
430
|
+
entries.push(`instrument: ${printGranular(track.granular!, INDENT)}`);
|
|
431
|
+
// The sampler a granular track keeps for `grain off`.
|
|
432
|
+
if (track.sampler)
|
|
433
|
+
entries.push(`sampler: ${printSampler(track.sampler, INDENT)}`);
|
|
434
|
+
} else if (track.sampler) {
|
|
395
435
|
entries.push(`instrument: ${printSampler(track.sampler, INDENT)}`);
|
|
396
436
|
} else if (wavetable) {
|
|
397
437
|
entries.push(`instrument: ${printWavetable(wavetable, INDENT)}`);
|
|
438
|
+
} else if (stringed) {
|
|
439
|
+
entries.push(`instrument: ${printStringed(track.string!, INDENT)}`);
|
|
440
|
+
} else if (modalCall) {
|
|
441
|
+
entries.push(`instrument: ${modalCall}`);
|
|
442
|
+
} else if (windCall) {
|
|
443
|
+
entries.push(`instrument: ${windCall}`);
|
|
398
444
|
} else entries.push(`instrument: ${str(track.instrument)}`);
|
|
399
445
|
if (track.kit) entries.push(`kit: ${str(track.kit)}`);
|
|
446
|
+
if (track.granular && !grained)
|
|
447
|
+
entries.push(
|
|
448
|
+
`granular: ${obj(
|
|
449
|
+
[
|
|
450
|
+
...(track.granular.preset
|
|
451
|
+
? [["preset", str(track.granular.preset)] as const]
|
|
452
|
+
: []),
|
|
453
|
+
...granularEntries(track.granular, INDENT),
|
|
454
|
+
],
|
|
455
|
+
INDENT,
|
|
456
|
+
"granular: ".length,
|
|
457
|
+
1,
|
|
458
|
+
)}`,
|
|
459
|
+
);
|
|
400
460
|
if (track.time) {
|
|
401
461
|
const beats = (ticks: number) => num(ticks / score.ticksPerBeat);
|
|
402
462
|
const fields: (readonly [string, string])[] = [];
|
|
@@ -480,18 +540,31 @@ export function printTrack(score: TrackScore, track: Track): string {
|
|
|
480
540
|
)}`,
|
|
481
541
|
);
|
|
482
542
|
if (track.fx) {
|
|
483
|
-
|
|
543
|
+
// A whole rig preset prints as `...rig("crunch")` (SDK 1.22.0).
|
|
544
|
+
const rigName = rigPresetOf(track.fx);
|
|
545
|
+
if (rigName) used.add("rig");
|
|
546
|
+
const effects = (Object.entries(track.fx) as [FxName, FxValues][]).filter(
|
|
547
|
+
([effect]) =>
|
|
548
|
+
!rigName ||
|
|
549
|
+
!(
|
|
550
|
+
(RIG_STAGES as readonly string[]).includes(effect) ||
|
|
551
|
+
rigPresetKeys(rigName).includes(effect)
|
|
552
|
+
),
|
|
553
|
+
);
|
|
484
554
|
const inner = INDENT + INDENT;
|
|
485
|
-
const
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
.
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
555
|
+
const rigLine = rigName ? [`${inner}...rig(${str(rigName)}),`] : [];
|
|
556
|
+
const body = rigLine.concat(
|
|
557
|
+
effects.map(([effect, values]) => {
|
|
558
|
+
// Only values that differ from the default: decoding fills the rest.
|
|
559
|
+
const params = Object.entries(fxSpec(effect).params)
|
|
560
|
+
.filter(
|
|
561
|
+
([key, spec]) =>
|
|
562
|
+
values[key] !== undefined && values[key] !== spec.default,
|
|
563
|
+
)
|
|
564
|
+
.map(([key]) => [key, value(values[key]!)] as const);
|
|
565
|
+
return `${inner}${effect}: ${params.length === 0 ? "{}" : obj(params, inner, `${effect}: `.length, 1)},`;
|
|
566
|
+
}),
|
|
567
|
+
);
|
|
495
568
|
entries.push(`fx: {\n${body.join("\n")}\n${INDENT}}`);
|
|
496
569
|
}
|
|
497
570
|
if (track.synth) {
|
|
@@ -506,6 +579,21 @@ export function printTrack(score: TrackScore, track: Track): string {
|
|
|
506
579
|
);
|
|
507
580
|
entries.push(`synth: ${obj(params, INDENT, "synth: ".length, 1)}`);
|
|
508
581
|
}
|
|
582
|
+
if (track.string && !stringed) {
|
|
583
|
+
const params = Object.entries(track.string).map(
|
|
584
|
+
([key, v]) => [key, value(v!)] as const,
|
|
585
|
+
);
|
|
586
|
+
entries.push(`string: ${obj(params, INDENT, "string: ".length, 1)}`);
|
|
587
|
+
}
|
|
588
|
+
if (track.keys) {
|
|
589
|
+
// `{}` is meaningful: it turns the modelled piano on.
|
|
590
|
+
const params = Object.entries(track.keys).map(
|
|
591
|
+
([key, v]) => [key, value(v)] as const,
|
|
592
|
+
);
|
|
593
|
+
entries.push(
|
|
594
|
+
`keys: ${params.length === 0 ? "{}" : obj(params, INDENT, "keys: ".length, 1)}`,
|
|
595
|
+
);
|
|
596
|
+
}
|
|
509
597
|
entries.push(...performanceEntries(score, track));
|
|
510
598
|
const lanes: [string, readonly AutomationPoint[] | undefined][] = [
|
|
511
599
|
["volume", track.volumeAutomation],
|
|
@@ -575,6 +663,11 @@ export function printTrack(score: TrackScore, track: Track): string {
|
|
|
575
663
|
"every",
|
|
576
664
|
"sampler",
|
|
577
665
|
"wavetable",
|
|
666
|
+
"stringed",
|
|
667
|
+
"rig",
|
|
668
|
+
"granular",
|
|
669
|
+
"modal",
|
|
670
|
+
"wind",
|
|
578
671
|
"euclid",
|
|
579
672
|
"grid",
|
|
580
673
|
]
|
|
@@ -725,13 +818,15 @@ function performanceEntries(score: TrackScore, track: Track): string[] {
|
|
|
725
818
|
1,
|
|
726
819
|
)}`,
|
|
727
820
|
);
|
|
728
|
-
|
|
729
|
-
const
|
|
821
|
+
for (const key of ["pedal", "softPedal", "sostenuto"] as const) {
|
|
822
|
+
const lane = track[key];
|
|
823
|
+
if (!lane) continue;
|
|
824
|
+
const events = lane.map(
|
|
730
825
|
(event) =>
|
|
731
826
|
`[${num(event.tick / score.ticksPerBeat)}, ${str(event.state)}]`,
|
|
732
827
|
);
|
|
733
828
|
entries.push(
|
|
734
|
-
|
|
829
|
+
`${key}: ${list(events, INDENT, `${key}: `.length, 1, events.length > 1)}`,
|
|
735
830
|
);
|
|
736
831
|
}
|
|
737
832
|
if (track.velocityCurve)
|
|
@@ -768,6 +863,20 @@ function performanceEntries(score: TrackScore, track: Track): string[] {
|
|
|
768
863
|
)}`,
|
|
769
864
|
);
|
|
770
865
|
}
|
|
866
|
+
if (track.guitar) {
|
|
867
|
+
const { tune, capo, hand, ring, position } = track.guitar;
|
|
868
|
+
const fields: [string, string][] = [];
|
|
869
|
+
if (tune !== undefined)
|
|
870
|
+
fields.push([
|
|
871
|
+
"tune",
|
|
872
|
+
typeof tune === "string" ? str(tune) : `[${tune.map(num).join(", ")}]`,
|
|
873
|
+
]);
|
|
874
|
+
if (capo !== undefined) fields.push(["capo", num(capo)]);
|
|
875
|
+
if (hand !== undefined) fields.push(["hand", num(hand)]);
|
|
876
|
+
if (ring !== undefined) fields.push(["ring", num(ring)]);
|
|
877
|
+
if (position !== undefined) fields.push(["position", num(position)]);
|
|
878
|
+
entries.push(`guitar: ${obj(fields, INDENT, "guitar: ".length, 1)}`);
|
|
879
|
+
}
|
|
771
880
|
return entries;
|
|
772
881
|
}
|
|
773
882
|
|
|
@@ -859,6 +968,108 @@ function printSampler(sampler: Sampler, indent: string): string {
|
|
|
859
968
|
return `sampler(\n${inner}${shifted},\n${inner}{ mode: ${str(sampler.mode)} },\n${indent})`;
|
|
860
969
|
}
|
|
861
970
|
|
|
971
|
+
/** `stringed("sitar", { buzz: 0.8 })`: the preset, then its overrides. */
|
|
972
|
+
function printStringed(settings: TrackString, indent: string): string {
|
|
973
|
+
const name = str(settings.preset ?? DEFAULT_STRING_PRESET);
|
|
974
|
+
const params = Object.entries(settings)
|
|
975
|
+
.filter(([key]) => key !== "preset")
|
|
976
|
+
.map(([key, v]) => [key, value(v!)] as const);
|
|
977
|
+
if (params.length === 0) return `stringed(${name})`;
|
|
978
|
+
const inline = `stringed(${name}, { ${params.map(([k, v]) => `${k}: ${v}`).join(", ")} })`;
|
|
979
|
+
if (indent.length + "instrument: ".length + inline.length + 1 <= WIDTH)
|
|
980
|
+
return inline;
|
|
981
|
+
const inner = indent + INDENT;
|
|
982
|
+
return `stringed(${name}, {\n${params.map(([k, v]) => `${inner}${k}: ${v},`).join("\n")}\n${indent}})`;
|
|
983
|
+
}
|
|
984
|
+
|
|
985
|
+
/** `[key, literal]` for each stored granular field but the preset. */
|
|
986
|
+
function granularEntries(
|
|
987
|
+
settings: TrackGranular,
|
|
988
|
+
indent: string,
|
|
989
|
+
): (readonly [string, string])[] {
|
|
990
|
+
return Object.entries(settings)
|
|
991
|
+
.filter(([key]) => key !== "preset")
|
|
992
|
+
.map(([key, v]) => [
|
|
993
|
+
key,
|
|
994
|
+
typeof v === "object"
|
|
995
|
+
? printSample(v as SampleRef, indent + INDENT, `${key}: `.length)
|
|
996
|
+
: value(v as number | string | boolean),
|
|
997
|
+
]);
|
|
998
|
+
}
|
|
999
|
+
|
|
1000
|
+
/** `granular("cloud", { scan: 0.1 })`: the preset, then its overrides. */
|
|
1001
|
+
function printGranular(settings: TrackGranular, indent: string): string {
|
|
1002
|
+
const inner = indent + INDENT;
|
|
1003
|
+
const params = granularEntries(settings, indent);
|
|
1004
|
+
const name = settings.preset ? str(settings.preset) : undefined;
|
|
1005
|
+
if (params.length === 0) return name ? `granular(${name})` : "granular()";
|
|
1006
|
+
const head = name ? `granular(${name}, ` : "granular(";
|
|
1007
|
+
const inline = `${head}{ ${params.map(([k, v]) => `${k}: ${v}`).join(", ")} })`;
|
|
1008
|
+
if (
|
|
1009
|
+
!inline.includes("\n") &&
|
|
1010
|
+
indent.length + "instrument: ".length + inline.length + 1 <= WIDTH
|
|
1011
|
+
)
|
|
1012
|
+
return inline;
|
|
1013
|
+
return `${head}{\n${params.map(([k, v]) => property(k, v, inner)).join("\n")}\n${indent}})`;
|
|
1014
|
+
}
|
|
1015
|
+
|
|
1016
|
+
/**
|
|
1017
|
+
* A modal track's instrument: the preset word alone (`"vibes"`) when there
|
|
1018
|
+
* are no overrides, else `modal("vibes", { motor: 4 })` with keys in
|
|
1019
|
+
* `MODAL_PARAMS` order. `marimba` is a legacy word, so the modal marimba
|
|
1020
|
+
* always prints as `modal("marimba")`.
|
|
1021
|
+
*/
|
|
1022
|
+
function printModal(settings: TrackModal, indent: string): string {
|
|
1023
|
+
const params: [string, string][] = [];
|
|
1024
|
+
for (const key of Object.keys(MODAL_PARAMS) as (keyof TrackModal)[]) {
|
|
1025
|
+
const value = settings[key];
|
|
1026
|
+
if (value === undefined) continue;
|
|
1027
|
+
params.push([key, typeof value === "number" ? num(value) : str(value)]);
|
|
1028
|
+
}
|
|
1029
|
+
if (settings.pair !== undefined) params.push(["pair", str(settings.pair)]);
|
|
1030
|
+
const preset = settings.preset;
|
|
1031
|
+
if (params.length === 0 && preset && preset !== "marimba") return str(preset);
|
|
1032
|
+
const head = preset ? str(preset) : "";
|
|
1033
|
+
if (params.length === 0) return `modal(${head})`;
|
|
1034
|
+
const lead = head ? `${head}, ` : "";
|
|
1035
|
+
const inline = `modal(${lead}{ ${params.map(([k, v]) => `${k}: ${v}`).join(", ")} })`;
|
|
1036
|
+
if (indent.length + "instrument: ".length + inline.length + 1 <= WIDTH)
|
|
1037
|
+
return inline;
|
|
1038
|
+
const inner = indent + INDENT;
|
|
1039
|
+
return `modal(${lead}{\n${params.map(([k, v]) => `${inner}${k}: ${v},`).join("\n")}\n${indent}})`;
|
|
1040
|
+
}
|
|
1041
|
+
|
|
1042
|
+
/**
|
|
1043
|
+
* A wind track's instrument: the preset word alone (`"flute"`) when there
|
|
1044
|
+
* are no overrides, else `wind("sax", { breath: 0.8 })` with keys in
|
|
1045
|
+
* `WIND_PARAMS` order. Every wind preset word is also an instrument word.
|
|
1046
|
+
*/
|
|
1047
|
+
function printWind(settings: TrackWind, indent: string): string {
|
|
1048
|
+
const params: [string, string][] = [];
|
|
1049
|
+
for (const key of Object.keys(WIND_PARAMS)) {
|
|
1050
|
+
const value = (settings as Record<string, unknown>)[key];
|
|
1051
|
+
if (value === undefined) continue;
|
|
1052
|
+
params.push([
|
|
1053
|
+
key,
|
|
1054
|
+
typeof value === "number"
|
|
1055
|
+
? num(value)
|
|
1056
|
+
: typeof value === "boolean"
|
|
1057
|
+
? String(value)
|
|
1058
|
+
: str(String(value)),
|
|
1059
|
+
]);
|
|
1060
|
+
}
|
|
1061
|
+
const preset = settings.preset;
|
|
1062
|
+
if (params.length === 0 && preset) return str(preset);
|
|
1063
|
+
const head = preset ? str(preset) : "";
|
|
1064
|
+
if (params.length === 0) return `wind(${head})`;
|
|
1065
|
+
const lead = head ? `${head}, ` : "";
|
|
1066
|
+
const inline = `wind(${lead}{ ${params.map(([k, v]) => `${k}: ${v}`).join(", ")} })`;
|
|
1067
|
+
if (indent.length + "instrument: ".length + inline.length + 1 <= WIDTH)
|
|
1068
|
+
return inline;
|
|
1069
|
+
const inner = indent + INDENT;
|
|
1070
|
+
return `wind(${lead}{\n${params.map(([k, v]) => `${inner}${k}: ${v},`).join("\n")}\n${indent}})`;
|
|
1071
|
+
}
|
|
1072
|
+
|
|
862
1073
|
/** `wavetable("basic", { wt: 0.5 })`; pack tables keep their pin. */
|
|
863
1074
|
function printWavetable(settings: TrackWavetable, indent: string): string {
|
|
864
1075
|
const src = settings.table.src;
|
|
@@ -915,6 +1126,26 @@ function printSample(ref: SampleRef, indent: string, prefix: number): string {
|
|
|
915
1126
|
if (ref.accelerate !== undefined)
|
|
916
1127
|
entries.push(["accelerate", num(ref.accelerate)]);
|
|
917
1128
|
if (ref.squiz !== undefined) entries.push(["squiz", num(ref.squiz)]);
|
|
1129
|
+
if (ref.bpm !== undefined) entries.push(["bpm", num(ref.bpm)]);
|
|
1130
|
+
if (ref.fitmode !== undefined) entries.push(["fitmode", str(ref.fitmode)]);
|
|
1131
|
+
if (ref.len !== undefined) entries.push(["len", num(ref.len)]);
|
|
1132
|
+
if (ref.shift !== undefined) entries.push(["shift", num(ref.shift)]);
|
|
1133
|
+
if (ref.formant !== undefined) entries.push(["formant", num(ref.formant)]);
|
|
1134
|
+
if (ref.fadeInTime !== undefined)
|
|
1135
|
+
entries.push(["fadeInTime", num(ref.fadeInTime)]);
|
|
1136
|
+
if (ref.fadeTime !== undefined) entries.push(["fadeTime", num(ref.fadeTime)]);
|
|
1137
|
+
if (ref.from !== undefined) {
|
|
1138
|
+
const from = ref.from;
|
|
1139
|
+
const parts: [string, string][] = [["source", str(from.source)]];
|
|
1140
|
+
if (from.section !== undefined) parts.push(["section", str(from.section)]);
|
|
1141
|
+
if (from.bars !== undefined)
|
|
1142
|
+
parts.push(["bars", `[${num(from.bars[0])}, ${num(from.bars[1])}]`]);
|
|
1143
|
+
parts.push(["score", str(from.score)]);
|
|
1144
|
+
entries.push(["from", obj(parts, indent + INDENT, "from: ".length, 1)]);
|
|
1145
|
+
}
|
|
1146
|
+
if (ref.vel !== undefined)
|
|
1147
|
+
entries.push(["vel", `[${num(ref.vel[0])}, ${num(ref.vel[1])}]`]);
|
|
1148
|
+
if (ref.rr !== undefined) entries.push(["rr", str(ref.rr)]);
|
|
918
1149
|
if (entries.length === 1) return str(ref.src);
|
|
919
1150
|
return obj(entries, indent, prefix, 1);
|
|
920
1151
|
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The SDK is one vendored file with no imports, so `core/sdk/v1.ts` carries
|
|
3
|
+
* a copy of the instrument word resolver (`core/instruments.ts`) between two
|
|
4
|
+
* marker lines. `bun core/sdk/sync-instruments.ts` rewrites it and
|
|
5
|
+
* `sync-instruments.test.ts` fails when it is stale. Same scheme as
|
|
6
|
+
* `sync-chords.ts`.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { readFile, writeFile } from "node:fs/promises";
|
|
10
|
+
import { join } from "node:path";
|
|
11
|
+
import { format } from "prettier";
|
|
12
|
+
|
|
13
|
+
export const BEGIN_MARKER =
|
|
14
|
+
"// BEGIN instrument words: generated from core/instruments.ts by core/sdk/sync-instruments.ts";
|
|
15
|
+
export const END_MARKER = "// END instrument words";
|
|
16
|
+
|
|
17
|
+
const ROOT = join(import.meta.dir, "..", "..");
|
|
18
|
+
export const INSTRUMENTS_PATH = join(ROOT, "core", "instruments.ts");
|
|
19
|
+
export const SDK_PATH = join(ROOT, "core", "sdk", "v1.ts");
|
|
20
|
+
|
|
21
|
+
/** The resolver block (markers included) for a `core/instruments.ts` source. */
|
|
22
|
+
export function instrumentBlock(source: string): string {
|
|
23
|
+
const start = source.indexOf("// ---- instrument words");
|
|
24
|
+
if (start < 0) throw new Error("core/instruments.ts layout changed");
|
|
25
|
+
const body = source
|
|
26
|
+
.slice(source.indexOf("\n", start) + 1)
|
|
27
|
+
.replace(/^export /gm, "")
|
|
28
|
+
.trim();
|
|
29
|
+
return `${BEGIN_MARKER}\n${body}\n${END_MARKER}`;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** `sdk` with its resolver block replaced, formatted like the repo. */
|
|
33
|
+
export async function syncedSdk(
|
|
34
|
+
sdk: string,
|
|
35
|
+
instruments: string,
|
|
36
|
+
): Promise<string> {
|
|
37
|
+
const from = sdk.indexOf(BEGIN_MARKER);
|
|
38
|
+
const to = sdk.indexOf(END_MARKER);
|
|
39
|
+
if (from < 0 || to < 0)
|
|
40
|
+
throw new Error("v1.ts has no instrument word markers");
|
|
41
|
+
const next =
|
|
42
|
+
sdk.slice(0, from) +
|
|
43
|
+
instrumentBlock(instruments) +
|
|
44
|
+
sdk.slice(to + END_MARKER.length);
|
|
45
|
+
return format(next, { parser: "typescript", filepath: SDK_PATH });
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (import.meta.main) {
|
|
49
|
+
const [sdk, instruments] = await Promise.all([
|
|
50
|
+
readFile(SDK_PATH, "utf8"),
|
|
51
|
+
readFile(INSTRUMENTS_PATH, "utf8"),
|
|
52
|
+
]);
|
|
53
|
+
const next = await syncedSdk(sdk, instruments);
|
|
54
|
+
if (next !== sdk) await writeFile(SDK_PATH, next);
|
|
55
|
+
console.log(
|
|
56
|
+
next === sdk ? "v1.ts instrument words up to date" : "v1.ts updated",
|
|
57
|
+
);
|
|
58
|
+
}
|