@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/expression.ts
CHANGED
|
@@ -167,6 +167,8 @@ export type TrackGlide = Readonly<{ time: number; mode: GlideMode }>;
|
|
|
167
167
|
export const DEFAULT_GLIDE_SECONDS = 0.06;
|
|
168
168
|
|
|
169
169
|
export const PEDAL_STATES = ["down", "half", "up"] as const;
|
|
170
|
+
/** Sostenuto has no half position (0.6.1). */
|
|
171
|
+
export const SOSTENUTO_STATES = ["down", "up"] as const;
|
|
170
172
|
export type PedalState = (typeof PEDAL_STATES)[number];
|
|
171
173
|
|
|
172
174
|
/** A sustain pedal (MIDI CC64) change at a tick. */
|
|
@@ -386,16 +388,18 @@ export function normalizeTrackGlide(value: unknown): TrackGlide | undefined {
|
|
|
386
388
|
export function normalizePedal(
|
|
387
389
|
value: unknown,
|
|
388
390
|
maxTick: number,
|
|
391
|
+
label = "pedal",
|
|
392
|
+
states: readonly PedalState[] = PEDAL_STATES,
|
|
389
393
|
): readonly PedalEvent[] | undefined {
|
|
390
394
|
if (value === undefined || value === null) return undefined;
|
|
391
395
|
if (!Array.isArray(value) || value.length > EXPRESSION_LIMITS.maxPedalEvents)
|
|
392
396
|
throw new ExpressionValidationError(
|
|
393
|
-
|
|
397
|
+
`${label} must be an array of at most ${EXPRESSION_LIMITS.maxPedalEvents} events`,
|
|
394
398
|
);
|
|
395
399
|
const byTick = new Map<number, PedalEvent>();
|
|
396
400
|
for (const item of value as unknown[]) {
|
|
397
|
-
const event = record(item,
|
|
398
|
-
onlyKeys(event, ["tick", "state"],
|
|
401
|
+
const event = record(item, `${label} event`);
|
|
402
|
+
onlyKeys(event, ["tick", "state"], `${label} event`);
|
|
399
403
|
const tick = event.tick;
|
|
400
404
|
if (
|
|
401
405
|
typeof tick !== "number" ||
|
|
@@ -404,11 +408,11 @@ export function normalizePedal(
|
|
|
404
408
|
tick > maxTick
|
|
405
409
|
)
|
|
406
410
|
throw new ExpressionValidationError(
|
|
407
|
-
|
|
411
|
+
`${label} tick must be an integer 0..${maxTick}`,
|
|
408
412
|
);
|
|
409
|
-
if (!
|
|
413
|
+
if (!states.includes(event.state as PedalState))
|
|
410
414
|
throw new ExpressionValidationError(
|
|
411
|
-
|
|
415
|
+
`${label} state must be one of ${states.join(", ")}`,
|
|
412
416
|
);
|
|
413
417
|
// The last event at a tick wins.
|
|
414
418
|
byTick.set(tick, Object.freeze({ tick, state: event.state as PedalState }));
|
|
@@ -551,6 +555,80 @@ export function pedalStateAt(
|
|
|
551
555
|
return state;
|
|
552
556
|
}
|
|
553
557
|
|
|
558
|
+
/** A stretch of song ticks `[from, to)` placed at `offset` in a slice. */
|
|
559
|
+
export type PedalPiece = Readonly<{ from: number; to: number; offset: number }>;
|
|
560
|
+
|
|
561
|
+
/**
|
|
562
|
+
* Pedal events for a slice made of `pieces` (a render window, a section
|
|
563
|
+
* pass, an audition region): the state in force at each piece's start is
|
|
564
|
+
* restated at its offset (`carry`, for sustain and una corda; sostenuto
|
|
565
|
+
* passes false, since a pedal already down latches no key struck later),
|
|
566
|
+
* events inside the piece move by `offset - from`, and a pedal still down
|
|
567
|
+
* at the piece's end is lifted there. Undefined when nothing is left.
|
|
568
|
+
*/
|
|
569
|
+
export function slicePedal(
|
|
570
|
+
events: readonly PedalEvent[] | undefined,
|
|
571
|
+
pieces: readonly PedalPiece[],
|
|
572
|
+
carry = true,
|
|
573
|
+
): readonly PedalEvent[] | undefined {
|
|
574
|
+
if (!events || events.length === 0) return events;
|
|
575
|
+
const byTick = new Map<number, PedalEvent>();
|
|
576
|
+
const put = (tick: number, state: PedalState) =>
|
|
577
|
+
byTick.set(tick, Object.freeze({ tick, state }));
|
|
578
|
+
for (const { from, to, offset } of pieces) {
|
|
579
|
+
let state: PedalState = "up";
|
|
580
|
+
if (carry)
|
|
581
|
+
for (const event of events) {
|
|
582
|
+
if (event.tick >= from) break;
|
|
583
|
+
state = event.state;
|
|
584
|
+
}
|
|
585
|
+
// Restate up too: the previous piece may have ended with it down.
|
|
586
|
+
if (state !== "up" || byTick.size > 0) put(offset, state);
|
|
587
|
+
for (const event of events) {
|
|
588
|
+
if (event.tick < from || event.tick >= to) continue;
|
|
589
|
+
put(event.tick - from + offset, event.state);
|
|
590
|
+
state = event.state;
|
|
591
|
+
}
|
|
592
|
+
if (state !== "up" && Number.isFinite(to)) put(offset + to - from, "up");
|
|
593
|
+
}
|
|
594
|
+
const out = [...byTick.values()].sort((a, b) => a.tick - b.tick);
|
|
595
|
+
// Drop events that restate the state already in force.
|
|
596
|
+
const kept: PedalEvent[] = [];
|
|
597
|
+
let last: PedalState = "up";
|
|
598
|
+
for (const event of out)
|
|
599
|
+
if (event.state !== last) {
|
|
600
|
+
kept.push(event);
|
|
601
|
+
last = event.state;
|
|
602
|
+
}
|
|
603
|
+
return kept.length > 0 ? Object.freeze(kept) : undefined;
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
/** The track with its three pedal lanes cut to `pieces` (see slicePedal). */
|
|
607
|
+
export function slicePedals<T extends PedalTrack>(
|
|
608
|
+
track: T,
|
|
609
|
+
pieces: readonly PedalPiece[],
|
|
610
|
+
): T {
|
|
611
|
+
if (!track.pedal && !track.softPedal && !track.sostenuto) return track;
|
|
612
|
+
const out: Record<string, unknown> = { ...track };
|
|
613
|
+
for (const [field, carry] of [
|
|
614
|
+
["pedal", true],
|
|
615
|
+
["softPedal", true],
|
|
616
|
+
["sostenuto", false],
|
|
617
|
+
] as const) {
|
|
618
|
+
if (!track[field]) continue;
|
|
619
|
+
const next = slicePedal(track[field], pieces, carry);
|
|
620
|
+
if (next) out[field] = next;
|
|
621
|
+
else delete out[field];
|
|
622
|
+
}
|
|
623
|
+
return out as T;
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
type PedalTrack = Readonly<{
|
|
627
|
+
pedal?: readonly PedalEvent[];
|
|
628
|
+
softPedal?: readonly PedalEvent[];
|
|
629
|
+
sostenuto?: readonly PedalEvent[];
|
|
630
|
+
}>;
|
|
631
|
+
|
|
554
632
|
// ---------------------------------------------------------------------------
|
|
555
633
|
// Performance
|
|
556
634
|
|
|
@@ -674,6 +752,7 @@ export function hasTrackPerformance(track: Track | undefined): boolean {
|
|
|
674
752
|
track !== undefined &&
|
|
675
753
|
(track.glide !== undefined ||
|
|
676
754
|
track.pedal !== undefined ||
|
|
755
|
+
track.sostenuto !== undefined ||
|
|
677
756
|
track.velocityCurve !== undefined ||
|
|
678
757
|
track.humanize !== undefined)
|
|
679
758
|
);
|
|
@@ -869,6 +948,64 @@ export function performNotes(
|
|
|
869
948
|
};
|
|
870
949
|
});
|
|
871
950
|
}
|
|
951
|
+
// 3b. Sostenuto (0.6.1): a key held (written length after articulation)
|
|
952
|
+
// when the sostenuto goes down keeps its damper up until the sostenuto
|
|
953
|
+
// lifts; keys struck later damp at their own release (or the sustain
|
|
954
|
+
// pedal's). The rod keeps a caught damper raised, so the same pitch
|
|
955
|
+
// struck again while that sostenuto is down rings to the lift too; the
|
|
956
|
+
// earlier instance stops at the restrike so voices do not pile up. A key
|
|
957
|
+
// still held through a lift and a new press is caught again.
|
|
958
|
+
const sostenuto = track?.sostenuto;
|
|
959
|
+
if (sostenuto) {
|
|
960
|
+
const spans: { down: number; lift: number; pitches: Set<number> }[] = [];
|
|
961
|
+
let open: number | undefined;
|
|
962
|
+
for (const event of sostenuto) {
|
|
963
|
+
if (event.state === "down" && open === undefined) open = event.tick;
|
|
964
|
+
else if (event.state === "up" && open !== undefined) {
|
|
965
|
+
spans.push({ down: open, lift: event.tick, pitches: new Set() });
|
|
966
|
+
open = undefined;
|
|
967
|
+
}
|
|
968
|
+
}
|
|
969
|
+
if (open !== undefined)
|
|
970
|
+
spans.push({ down: open, lift: timing.endTick, pitches: new Set() });
|
|
971
|
+
const caughtBy = working.map((item) => {
|
|
972
|
+
const keyUp = item.start + item.bendLength;
|
|
973
|
+
return spans.filter((span) => {
|
|
974
|
+
const held = item.start <= span.down && span.down < keyUp;
|
|
975
|
+
if (held) span.pitches.add(item.note.pitch);
|
|
976
|
+
return held;
|
|
977
|
+
});
|
|
978
|
+
});
|
|
979
|
+
working = working.map((item, index) => {
|
|
980
|
+
const keyUp = item.start + item.bendLength;
|
|
981
|
+
let lift = -Infinity;
|
|
982
|
+
for (const span of caughtBy[index]!) lift = Math.max(lift, span.lift);
|
|
983
|
+
for (const span of spans)
|
|
984
|
+
if (
|
|
985
|
+
span.pitches.has(item.note.pitch) &&
|
|
986
|
+
span.down < item.start &&
|
|
987
|
+
item.start < span.lift
|
|
988
|
+
)
|
|
989
|
+
lift = Math.max(lift, span.lift);
|
|
990
|
+
if (lift === -Infinity) return item;
|
|
991
|
+
const current = item.start + item.duration;
|
|
992
|
+
let end = Math.max(current, Math.min(lift, timing.endTick));
|
|
993
|
+
if (end <= current) return item;
|
|
994
|
+
const restrike = working.find(
|
|
995
|
+
(other) =>
|
|
996
|
+
other !== item &&
|
|
997
|
+
other.note.pitch === item.note.pitch &&
|
|
998
|
+
other.start > item.start &&
|
|
999
|
+
other.start < end,
|
|
1000
|
+
);
|
|
1001
|
+
if (restrike) end = Math.max(current, restrike.start);
|
|
1002
|
+
return {
|
|
1003
|
+
...item,
|
|
1004
|
+
duration: end - item.start,
|
|
1005
|
+
pedalled: end > keyUp || item.pedalled === true,
|
|
1006
|
+
};
|
|
1007
|
+
});
|
|
1008
|
+
}
|
|
872
1009
|
// 4. Glide and monophony (on the written timing).
|
|
873
1010
|
let performed = glideAndMono(
|
|
874
1011
|
track,
|