@msb235/blits 0.4.0 → 0.5.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 +262 -0
- package/README.md +2 -1
- package/dist/bare.d.ts +29 -0
- package/dist/bare.js +105 -0
- package/dist/blend.d.ts +34 -0
- package/dist/blend.js +102 -0
- package/dist/book.d.ts +53 -0
- package/dist/book.js +218 -0
- package/dist/chain.d.ts +22 -0
- package/dist/chain.js +150 -0
- package/dist/channels.d.ts +2 -0
- package/dist/channels.js +3 -1
- package/dist/clock.d.ts +50 -3
- package/dist/clock.js +102 -9
- package/dist/clone.d.ts +7 -0
- package/dist/clone.js +57 -0
- package/dist/columns.d.ts +29 -0
- package/dist/columns.js +183 -0
- package/dist/crowd.d.ts +128 -0
- package/dist/crowd.js +354 -0
- package/dist/cue.d.ts +3 -0
- package/dist/cue.js +115 -0
- package/dist/due.d.ts +12 -0
- package/dist/due.js +93 -0
- package/dist/fade.d.ts +19 -0
- package/dist/fade.js +133 -0
- package/dist/fill.d.ts +42 -0
- package/dist/fill.js +381 -0
- package/dist/fold.d.ts +51 -0
- package/dist/fold.js +475 -0
- package/dist/gather.d.ts +24 -0
- package/dist/gather.js +186 -0
- package/dist/handle.d.ts +31 -0
- package/dist/handle.js +56 -0
- package/dist/held.d.ts +14 -0
- package/dist/held.js +117 -0
- package/dist/history.d.ts +22 -0
- package/dist/history.js +149 -0
- package/dist/hosts.d.ts +16 -0
- package/dist/hosts.js +178 -0
- package/dist/index.d.ts +1 -1
- package/dist/keyrows.d.ts +36 -0
- package/dist/keyrows.js +138 -0
- package/dist/lane.d.ts +210 -0
- package/dist/lane.js +187 -0
- package/dist/lanes.d.ts +121 -246
- package/dist/lanes.js +150 -1578
- package/dist/locus.d.ts +40 -0
- package/dist/locus.js +89 -0
- package/dist/marks.d.ts +12 -0
- package/dist/marks.js +80 -0
- package/dist/meet.d.ts +16 -0
- package/dist/meet.js +113 -0
- package/dist/mixer.d.ts +244 -196
- package/dist/mixer.js +242 -2220
- package/dist/motion.d.ts +66 -21
- package/dist/motion.js +325 -165
- package/dist/move.d.ts +9 -0
- package/dist/move.js +190 -0
- package/dist/named.d.ts +12 -0
- package/dist/named.js +39 -0
- package/dist/numbers.d.ts +18 -6
- package/dist/numbers.js +59 -31
- package/dist/owed.d.ts +24 -0
- package/dist/owed.js +51 -0
- package/dist/owner.d.ts +43 -0
- package/dist/owner.js +117 -0
- package/dist/pace.d.ts +40 -0
- package/dist/pace.js +113 -0
- package/dist/patch.d.ts +51 -10
- package/dist/patch.js +140 -45
- package/dist/place.d.ts +20 -0
- package/dist/place.js +196 -0
- package/dist/project.d.ts +4 -0
- package/dist/project.js +233 -0
- package/dist/pull.d.ts +5 -0
- package/dist/pull.js +175 -0
- package/dist/qualify.d.ts +31 -0
- package/dist/qualify.js +345 -0
- package/dist/relink.d.ts +25 -0
- package/dist/relink.js +59 -0
- package/dist/rows.d.ts +33 -0
- package/dist/rows.js +282 -0
- package/dist/sample.d.ts +21 -0
- package/dist/sample.js +232 -0
- package/dist/signals.d.ts +12 -9
- package/dist/signals.js +8 -5
- package/dist/store.d.ts +10 -3
- package/dist/store.js +44 -9
- package/dist/strays.d.ts +21 -0
- package/dist/strays.js +84 -0
- package/dist/tweened.d.ts +11 -0
- package/dist/tweened.js +48 -0
- package/dist/types.d.ts +217 -34
- package/dist/unreached.d.ts +5 -0
- package/dist/unreached.js +24 -0
- package/dist/voice.d.ts +240 -0
- package/dist/voice.js +322 -0
- package/dist/weigh.d.ts +26 -0
- package/dist/weigh.js +89 -0
- package/docs/schema.html +118 -40
- package/package.json +5 -2
package/dist/pace.d.ts
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { type Clock } from './clock.js';
|
|
2
|
+
/**
|
|
3
|
+
* A mix's own rate, as a clock: mix time as a function of host time, which here means the host's
|
|
4
|
+
* timestamp less what `rebase` took out. Each entry is the clock from the host time it took effect,
|
|
5
|
+
* oldest first, and the last is in force; under `history` the entries a read back may reach are
|
|
6
|
+
* kept, and without it only the last. Entries are never changed once logged, so a copy of the log
|
|
7
|
+
* stays as it was.
|
|
8
|
+
*/
|
|
9
|
+
export declare class Pace {
|
|
10
|
+
private readonly keeps;
|
|
11
|
+
private log;
|
|
12
|
+
constructor(keeps: boolean, log?: Clock[]);
|
|
13
|
+
private get clock();
|
|
14
|
+
/** Set before the first sync, the clock waits to be anchored there, reading host time until then. */
|
|
15
|
+
private get waiting();
|
|
16
|
+
/** Mix time at host time `u`, anchoring a clock set before the first sync there. */
|
|
17
|
+
sync(u: number): number;
|
|
18
|
+
/** What the mix clock reads at host time `u`, by the clock in force then. */
|
|
19
|
+
reading(u: number): number;
|
|
20
|
+
/**
|
|
21
|
+
* The mix time of something pinned to host time `u`, seen from host time `now`: what the clock
|
|
22
|
+
* will read there, or Infinity where `u` lies ahead and the clock stands still before it, since
|
|
23
|
+
* the moment the mix reaches is then not `u`'s alone.
|
|
24
|
+
*/
|
|
25
|
+
at(u: number, now: number): number;
|
|
26
|
+
/** The earliest host time the mix clock reads `t`; Infinity where it never will. */
|
|
27
|
+
timeOf(t: number): number;
|
|
28
|
+
/** The rate at host time `u`; before the first sync, the rate the first frame starts at. */
|
|
29
|
+
rateAt(u: number): number;
|
|
30
|
+
/** Whether the mix clock stands still from host time `u` on, short of another change. */
|
|
31
|
+
stopped(u: number): boolean;
|
|
32
|
+
/**
|
|
33
|
+
* Sets the rate from host time `u`, at once or over `over` host ms, the way a handle sets a
|
|
34
|
+
* voice's. Under history, lets go of the entries a read no longer reaches: those over before mix
|
|
35
|
+
* time `reach`.
|
|
36
|
+
*/
|
|
37
|
+
change(u: number, rate: number, over: number, reach: number): void;
|
|
38
|
+
/** A copy holding the clocks that took effect before host time `u`, for a read back to then. */
|
|
39
|
+
until(u: number): Pace;
|
|
40
|
+
}
|
package/dist/pace.js
ADDED
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { elapsedWith, rateWith, retime, timeWith } from './clock.js';
|
|
2
|
+
/**
|
|
3
|
+
* A mix's own rate, as a clock: mix time as a function of host time, which here means the host's
|
|
4
|
+
* timestamp less what `rebase` took out. Each entry is the clock from the host time it took effect,
|
|
5
|
+
* oldest first, and the last is in force; under `history` the entries a read back may reach are
|
|
6
|
+
* kept, and without it only the last. Entries are never changed once logged, so a copy of the log
|
|
7
|
+
* stays as it was.
|
|
8
|
+
*/
|
|
9
|
+
export class Pace {
|
|
10
|
+
keeps;
|
|
11
|
+
log;
|
|
12
|
+
constructor(keeps, log = [{ anchorNow: 0, anchorElapsed: 0, rate: 1, ramp: null }]) {
|
|
13
|
+
this.keeps = keeps;
|
|
14
|
+
this.log = log;
|
|
15
|
+
}
|
|
16
|
+
get clock() {
|
|
17
|
+
return this.log[this.log.length - 1];
|
|
18
|
+
}
|
|
19
|
+
/** Set before the first sync, the clock waits to be anchored there, reading host time until then. */
|
|
20
|
+
get waiting() {
|
|
21
|
+
return Number.isNaN(this.clock.anchorNow);
|
|
22
|
+
}
|
|
23
|
+
/** Mix time at host time `u`, anchoring a clock set before the first sync there. */
|
|
24
|
+
sync(u) {
|
|
25
|
+
if (this.waiting)
|
|
26
|
+
this.log = [{ ...this.clock, anchorNow: u, anchorElapsed: u }];
|
|
27
|
+
return this.reading(u);
|
|
28
|
+
}
|
|
29
|
+
/** What the mix clock reads at host time `u`, by the clock in force then. */
|
|
30
|
+
reading(u) {
|
|
31
|
+
if (this.waiting)
|
|
32
|
+
return u;
|
|
33
|
+
const log = this.log;
|
|
34
|
+
let i = log.length - 1;
|
|
35
|
+
while (i > 0 && log[i].anchorNow > u)
|
|
36
|
+
i--;
|
|
37
|
+
return elapsedWith(log[i], u);
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* The mix time of something pinned to host time `u`, seen from host time `now`: what the clock
|
|
41
|
+
* will read there, or Infinity where `u` lies ahead and the clock stands still before it, since
|
|
42
|
+
* the moment the mix reaches is then not `u`'s alone.
|
|
43
|
+
*/
|
|
44
|
+
at(u, now) {
|
|
45
|
+
if (!(u > now) || this.waiting)
|
|
46
|
+
return this.reading(u);
|
|
47
|
+
const c = this.clock;
|
|
48
|
+
const r = c.ramp;
|
|
49
|
+
const still = r === null ? c.rate <= 0 : r.to <= 0 && u > c.anchorNow + r.over;
|
|
50
|
+
return still ? Number.POSITIVE_INFINITY : elapsedWith(c, u);
|
|
51
|
+
}
|
|
52
|
+
/** The earliest host time the mix clock reads `t`; Infinity where it never will. */
|
|
53
|
+
timeOf(t) {
|
|
54
|
+
if (this.waiting)
|
|
55
|
+
return t;
|
|
56
|
+
const log = this.log;
|
|
57
|
+
let i = 0;
|
|
58
|
+
while (i + 1 < log.length && t > log[i + 1].anchorElapsed)
|
|
59
|
+
i++;
|
|
60
|
+
return timeWith(log[i], t);
|
|
61
|
+
}
|
|
62
|
+
/** The rate at host time `u`; before the first sync, the rate the first frame starts at. */
|
|
63
|
+
rateAt(u) {
|
|
64
|
+
const c = this.clock;
|
|
65
|
+
if (this.waiting || Number.isNaN(u))
|
|
66
|
+
return c.ramp === null ? c.rate : c.ramp.from;
|
|
67
|
+
return rateWith(c, u);
|
|
68
|
+
}
|
|
69
|
+
/** Whether the mix clock stands still from host time `u` on, short of another change. */
|
|
70
|
+
stopped(u) {
|
|
71
|
+
const c = this.clock;
|
|
72
|
+
const r = c.ramp;
|
|
73
|
+
if (r === null)
|
|
74
|
+
return c.rate === 0;
|
|
75
|
+
return r.to === 0 && !this.waiting && u >= c.anchorNow + r.over;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Sets the rate from host time `u`, at once or over `over` host ms, the way a handle sets a
|
|
79
|
+
* voice's. Under history, lets go of the entries a read no longer reaches: those over before mix
|
|
80
|
+
* time `reach`.
|
|
81
|
+
*/
|
|
82
|
+
change(u, rate, over, reach) {
|
|
83
|
+
const was = this.clock;
|
|
84
|
+
const next = { ...was };
|
|
85
|
+
if (Number.isNaN(u) || this.waiting) {
|
|
86
|
+
const from = this.rateAt(Number.NaN);
|
|
87
|
+
next.ramp = over > 0 && rate !== from ? { from, to: rate, over } : null;
|
|
88
|
+
next.rate = rate;
|
|
89
|
+
next.anchorNow = Number.NaN;
|
|
90
|
+
this.log = [next];
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
retime(next, u, rate, over);
|
|
94
|
+
const log = this.log;
|
|
95
|
+
if (this.keeps && was.anchorNow < u)
|
|
96
|
+
log.push(next);
|
|
97
|
+
else
|
|
98
|
+
log[log.length - 1] = next;
|
|
99
|
+
let drop = 0;
|
|
100
|
+
while (drop + 1 < log.length && log[drop + 1].anchorElapsed <= reach)
|
|
101
|
+
drop++;
|
|
102
|
+
if (drop > 0)
|
|
103
|
+
log.splice(0, drop);
|
|
104
|
+
}
|
|
105
|
+
/** A copy holding the clocks that took effect before host time `u`, for a read back to then. */
|
|
106
|
+
until(u) {
|
|
107
|
+
const log = this.log;
|
|
108
|
+
let n = 1;
|
|
109
|
+
while (n < log.length && log[n].anchorNow < u)
|
|
110
|
+
n++;
|
|
111
|
+
return new Pace(this.keeps, log.slice(0, n));
|
|
112
|
+
}
|
|
113
|
+
}
|
package/dist/patch.d.ts
CHANGED
|
@@ -2,11 +2,11 @@ import { type LerpInto } from './channels.js';
|
|
|
2
2
|
import { type Curve } from './easing.js';
|
|
3
3
|
import type { Channel, Easing, Keyframe, Kit, Patch, Setting } from './types.js';
|
|
4
4
|
/**
|
|
5
|
-
* What `patch` takes besides its
|
|
5
|
+
* What `patch` takes besides its duration and its function.
|
|
6
6
|
*
|
|
7
7
|
* @category patch
|
|
8
8
|
*/
|
|
9
|
-
export interface PatchOptions<I, O, S> {
|
|
9
|
+
export interface PatchOptions<I, O, S, H = unknown> {
|
|
10
10
|
/** The channels this patch contributes to. Every key `at` sets, and no others. Default: `kit`'s. */
|
|
11
11
|
writes?: readonly (keyof O)[];
|
|
12
12
|
/** The channels this patch was written against, which `cue` checks a mix's kit against. */
|
|
@@ -14,16 +14,16 @@ export interface PatchOptions<I, O, S> {
|
|
|
14
14
|
/** The fields of `setting.host` this patch reads, which `cue` checks the mix's host for. */
|
|
15
15
|
reads?: readonly string[];
|
|
16
16
|
state?(subject: I): S;
|
|
17
|
-
step?(state: S, dt: number, subject: I, setting: Setting<S>): void;
|
|
17
|
+
step?(state: S, dt: number, subject: I, setting: Setting<S, H>): void;
|
|
18
18
|
}
|
|
19
19
|
/**
|
|
20
20
|
* The procedural form. Flicker, roving, a hash walk, a starter strike.
|
|
21
21
|
*
|
|
22
22
|
* @category patch
|
|
23
23
|
*/
|
|
24
|
-
export declare function patch<I, O, S = void>(
|
|
24
|
+
export declare function patch<I, O, S = void, H = unknown>(duration: number, at: (phase: number, subject: I, setting: Setting<S, H>) => Partial<O>, opts: PatchOptions<I, O, S, H>): Patch<I, O, S, H>;
|
|
25
25
|
/**
|
|
26
|
-
* What `keys` takes besides its
|
|
26
|
+
* What `keys` takes besides its duration and its stops.
|
|
27
27
|
*
|
|
28
28
|
* @category patch
|
|
29
29
|
*/
|
|
@@ -32,7 +32,7 @@ export interface KeysOptions<O> {
|
|
|
32
32
|
ease?: Easing;
|
|
33
33
|
/** A curve for one channel, where it differs from the rest. */
|
|
34
34
|
easeBy?: (channel: keyof O) => Easing | undefined;
|
|
35
|
-
/** Milliseconds one channel waits before it starts moving, within the
|
|
35
|
+
/** Milliseconds one channel waits before it starts moving, within the duration. */
|
|
36
36
|
delayBy?: (channel: keyof O) => number;
|
|
37
37
|
/**
|
|
38
38
|
* How one channel interpolates, where it should differ from the channel's own `lerp`. A mix hands
|
|
@@ -52,19 +52,58 @@ interface Point {
|
|
|
52
52
|
}
|
|
53
53
|
type Lerp = (a: never, b: never, u: number) => unknown;
|
|
54
54
|
/** One channel of a stop list, sorted and resolved once, so a read is a binary search. */
|
|
55
|
-
interface Track {
|
|
55
|
+
export interface Track {
|
|
56
56
|
channel: string;
|
|
57
57
|
/** Every stop carrying this channel, by phase. */
|
|
58
58
|
all: Point[];
|
|
59
59
|
/** The same without stops at 0, which a `from: 'current'` base replaces. */
|
|
60
60
|
tail: Point[];
|
|
61
|
+
/** The phases of `all`, which the search reads, and of `tail`, made on its first search. */
|
|
62
|
+
ats: number[];
|
|
63
|
+
tailAts: number[] | null;
|
|
61
64
|
delay: number;
|
|
62
65
|
lerp: ((a: never, b: never, u: number) => unknown) | undefined;
|
|
63
66
|
}
|
|
64
67
|
export interface Built {
|
|
65
68
|
tracks: Track[];
|
|
66
|
-
|
|
69
|
+
duration: number;
|
|
67
70
|
}
|
|
71
|
+
/** What `segment` found: nothing to read, one value, or a segment to interpolate along. */
|
|
72
|
+
export declare const NOTHING = 0;
|
|
73
|
+
export declare const AT = 1;
|
|
74
|
+
export declare const BETWEEN = 2;
|
|
75
|
+
/**
|
|
76
|
+
* Where `segment` leaves a track's reading: the value, or the segment's two ends, how far along it
|
|
77
|
+
* is before and after its easing, whether it leaves `base`, its length and its easing. Read it
|
|
78
|
+
* before anything else reads a track.
|
|
79
|
+
*/
|
|
80
|
+
export declare const seg: {
|
|
81
|
+
a: unknown;
|
|
82
|
+
b: unknown;
|
|
83
|
+
u: number;
|
|
84
|
+
eased: number;
|
|
85
|
+
first: boolean;
|
|
86
|
+
len: number;
|
|
87
|
+
ease: Curve | undefined;
|
|
88
|
+
};
|
|
89
|
+
/** A track's phase once its channel's own delay applies. */
|
|
90
|
+
export declare function shifted(track: Track, phase: number, duration: number): number;
|
|
91
|
+
/** A phase once a channel's delay of `delay` ms in a duration of `duration` applies. */
|
|
92
|
+
export declare function shift(delay: number, phase: number, duration: number): number;
|
|
93
|
+
/**
|
|
94
|
+
* Where `phase` falls among `n` stop phases `ats[off + k * stride]`, in order: -2 for no stops, -1
|
|
95
|
+
* at or before the first, `n` at or past the last, and otherwise the first stop past it, where the
|
|
96
|
+
* segment from the one before ends. With `segment`'s, the one copy of the search.
|
|
97
|
+
*/
|
|
98
|
+
export declare function locate(ats: ArrayLike<number>, off: number, stride: number, n: number, phase: number): number;
|
|
99
|
+
/** How far along a segment from phase `a` to `b` a phase is, before easing. */
|
|
100
|
+
export declare function fraction(phase: number, a: number, b: number): number;
|
|
101
|
+
/**
|
|
102
|
+
* Finds where one track is at a phase, into `seg`: the one copy of the search, which `read` and a
|
|
103
|
+
* lane folding stops straight into a subject's values both make. `base` stands in for the value at
|
|
104
|
+
* phase 0, which is how `from: 'current'` starts a voice wherever the subject already is.
|
|
105
|
+
*/
|
|
106
|
+
export declare function segment(track: Track, phase: number, base: unknown): number;
|
|
68
107
|
/**
|
|
69
108
|
* Arrays a reader lets keyed reads interpolate into, by track. Each only ever holds what a read made,
|
|
70
109
|
* never a stop's or a base's, and a delta read through it is good until the next read through it.
|
|
@@ -83,14 +122,16 @@ export declare function intosOf(built: Built, channels: readonly Channel<unknown
|
|
|
83
122
|
export declare function readKeys(built: Built, phase: number, out: Record<string, unknown>, base?: Record<string, unknown>, lerps?: readonly (Lerp | undefined)[],
|
|
84
123
|
/** Per channel, how fast a retargeted subject was moving, units per ms. */
|
|
85
124
|
slopes?: Record<string, unknown>, intos?: readonly (LerpInto | undefined)[], scratch?: Scratch): Record<string, unknown>;
|
|
125
|
+
/** A patch's `duration`, or the deprecated `period` where only that is set. */
|
|
126
|
+
export declare const durationOf: <I, O, S>(p: Patch<I, O, S>) => number;
|
|
86
127
|
/** The built form of a patch's stops, made on first ask for a patch `keys()` did not build. */
|
|
87
128
|
export declare function builtOf<I, O, S>(p: Patch<I, O, S>): Built;
|
|
88
129
|
/** Evaluates a stop list into a fresh delta. */
|
|
89
|
-
export declare function evalKeys<O>(stops: readonly Keyframe<O>[], writes: readonly (keyof O)[], phase: number,
|
|
130
|
+
export declare function evalKeys<O>(stops: readonly Keyframe<O>[], writes: readonly (keyof O)[], phase: number, duration: number, opts: KeysOptions<O>, base?: Partial<O>): Partial<O>;
|
|
90
131
|
/**
|
|
91
132
|
* The declarative form. klieg's transition sugar and wod's keyframes.
|
|
92
133
|
*
|
|
93
134
|
* @category patch
|
|
94
135
|
*/
|
|
95
|
-
export declare function keys<I, O>(
|
|
136
|
+
export declare function keys<I, O>(duration: number, stops: readonly Keyframe<O>[], opts?: KeysOptions<O>): Patch<I, O, void>;
|
|
96
137
|
export {};
|
package/dist/patch.js
CHANGED
|
@@ -5,13 +5,14 @@ import { curve } from './easing.js';
|
|
|
5
5
|
*
|
|
6
6
|
* @category patch
|
|
7
7
|
*/
|
|
8
|
-
export function patch(
|
|
8
|
+
export function patch(duration, at, opts) {
|
|
9
9
|
const writes = opts.writes ?? (opts.kit ? Object.keys(opts.kit) : undefined);
|
|
10
10
|
if (writes === undefined)
|
|
11
11
|
throw new Error('blits: a patch needs writes, or a kit to take them from');
|
|
12
12
|
return {
|
|
13
13
|
form: 'fn',
|
|
14
|
-
|
|
14
|
+
duration,
|
|
15
|
+
period: duration,
|
|
15
16
|
writes,
|
|
16
17
|
kit: opts.kit,
|
|
17
18
|
reads: opts.reads,
|
|
@@ -37,7 +38,7 @@ function interpolate(a, b, u) {
|
|
|
37
38
|
}
|
|
38
39
|
return u < 0.5 ? a : b;
|
|
39
40
|
}
|
|
40
|
-
function build(stops, writes,
|
|
41
|
+
function build(stops, writes, duration, opts) {
|
|
41
42
|
const fallback = opts.ease === undefined ? undefined : curve(opts.ease);
|
|
42
43
|
const tracks = [];
|
|
43
44
|
for (const channel of writes) {
|
|
@@ -50,72 +51,165 @@ function build(stops, writes, period, opts) {
|
|
|
50
51
|
continue;
|
|
51
52
|
all.push({
|
|
52
53
|
at: stop.at,
|
|
53
|
-
|
|
54
|
+
// A copy, so every reader sees the stop as it was built: a lane may hold its numbers.
|
|
55
|
+
value: Array.isArray(value)
|
|
56
|
+
? [...value]
|
|
57
|
+
: ArrayBuffer.isView(value)
|
|
58
|
+
? value.slice()
|
|
59
|
+
: value,
|
|
54
60
|
ease: stop.ease === undefined ? trackEase : curve(stop.ease),
|
|
55
61
|
});
|
|
56
62
|
}
|
|
57
63
|
all.sort((x, y) => x.at - y.at);
|
|
64
|
+
const tail = all.filter((pt) => pt.at !== 0);
|
|
58
65
|
tracks.push({
|
|
59
66
|
channel: channel,
|
|
60
67
|
all,
|
|
61
|
-
tail
|
|
68
|
+
tail,
|
|
69
|
+
ats: all.map((pt) => pt.at),
|
|
70
|
+
tailAts: null,
|
|
62
71
|
delay: opts.delayBy?.(channel) ?? 0,
|
|
63
72
|
lerp: opts.lerpBy?.(channel) ?? opts.kit?.[channel]?.lerp,
|
|
64
73
|
});
|
|
65
74
|
}
|
|
66
|
-
return { tracks,
|
|
75
|
+
return { tracks, duration };
|
|
67
76
|
}
|
|
77
|
+
/** What `segment` found: nothing to read, one value, or a segment to interpolate along. */
|
|
78
|
+
export const NOTHING = 0;
|
|
79
|
+
export const AT = 1;
|
|
80
|
+
export const BETWEEN = 2;
|
|
68
81
|
/**
|
|
69
|
-
*
|
|
70
|
-
*
|
|
82
|
+
* Where `segment` leaves a track's reading: the value, or the segment's two ends, how far along it
|
|
83
|
+
* is before and after its easing, whether it leaves `base`, its length and its easing. Read it
|
|
84
|
+
* before anything else reads a track.
|
|
71
85
|
*/
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
86
|
+
export const seg = {
|
|
87
|
+
a: undefined,
|
|
88
|
+
b: undefined,
|
|
89
|
+
u: 0,
|
|
90
|
+
eased: 0,
|
|
91
|
+
first: false,
|
|
92
|
+
len: 0,
|
|
93
|
+
ease: undefined,
|
|
94
|
+
};
|
|
95
|
+
/** A track's phase once its channel's own delay applies. */
|
|
96
|
+
export function shifted(track, phase, duration) {
|
|
97
|
+
return shift(track.delay, phase, duration);
|
|
98
|
+
}
|
|
99
|
+
/** A phase once a channel's delay of `delay` ms in a duration of `duration` applies. */
|
|
100
|
+
export function shift(delay, phase, duration) {
|
|
101
|
+
return delay === 0 || duration === 0 ? phase : Math.max(0, (phase * duration - delay) / duration);
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Where `phase` falls among `n` stop phases `ats[off + k * stride]`, in order: -2 for no stops, -1
|
|
105
|
+
* at or before the first, `n` at or past the last, and otherwise the first stop past it, where the
|
|
106
|
+
* segment from the one before ends. With `segment`'s, the one copy of the search.
|
|
107
|
+
*/
|
|
108
|
+
export function locate(ats, off, stride, n, phase) {
|
|
78
109
|
if (n === 0)
|
|
79
|
-
return
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
let hi = n - 1;
|
|
110
|
+
return -2;
|
|
111
|
+
if (phase <= ats[off])
|
|
112
|
+
return -1;
|
|
113
|
+
if (phase >= ats[off + (n - 1) * stride])
|
|
114
|
+
return n;
|
|
115
|
+
return firstPast(ats, off, stride, 1, n - 1, phase);
|
|
116
|
+
}
|
|
117
|
+
/** The first of stops `lo` to `hi` whose phase is at or past `phase`, the last known to be. */
|
|
118
|
+
function firstPast(ats, off, stride, lo, hi, phase) {
|
|
89
119
|
while (lo < hi) {
|
|
90
120
|
const mid = (lo + hi) >> 1;
|
|
91
|
-
if (
|
|
121
|
+
if (ats[off + mid * stride] >= phase)
|
|
92
122
|
hi = mid;
|
|
93
123
|
else
|
|
94
124
|
lo = mid + 1;
|
|
95
125
|
}
|
|
126
|
+
return lo;
|
|
127
|
+
}
|
|
128
|
+
/** How far along a segment from phase `a` to `b` a phase is, before easing. */
|
|
129
|
+
export function fraction(phase, a, b) {
|
|
130
|
+
return (phase - a) / (b - a);
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Finds where one track is at a phase, into `seg`: the one copy of the search, which `read` and a
|
|
134
|
+
* lane folding stops straight into a subject's values both make. `base` stands in for the value at
|
|
135
|
+
* phase 0, which is how `from: 'current'` starts a voice wherever the subject already is.
|
|
136
|
+
*/
|
|
137
|
+
export function segment(track, phase, base) {
|
|
138
|
+
let lo;
|
|
139
|
+
const o = base === undefined ? 0 : 1;
|
|
140
|
+
const pts = o === 0 ? track.all : track.tail;
|
|
141
|
+
if (o === 0) {
|
|
142
|
+
const n = pts.length;
|
|
143
|
+
lo = locate(track.ats, 0, 1, n, phase);
|
|
144
|
+
if (lo === -2)
|
|
145
|
+
return NOTHING;
|
|
146
|
+
if (lo === -1 || lo === n) {
|
|
147
|
+
seg.a = pts[lo === -1 ? 0 : n - 1].value;
|
|
148
|
+
return AT;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
else {
|
|
152
|
+
// Stop 0 is `base`, at phase 0, and stop k is the tail's k - 1.
|
|
153
|
+
const n = pts.length + 1;
|
|
154
|
+
if (phase <= 0) {
|
|
155
|
+
seg.a = base;
|
|
156
|
+
return AT;
|
|
157
|
+
}
|
|
158
|
+
const last = pts[pts.length - 1];
|
|
159
|
+
if (last === undefined || phase >= last.at) {
|
|
160
|
+
seg.a = last === undefined ? base : last.value;
|
|
161
|
+
return AT;
|
|
162
|
+
}
|
|
163
|
+
track.tailAts ??= pts.map((pt) => pt.at);
|
|
164
|
+
lo = firstPast(track.tailAts, -1, 1, 1, n - 1, phase);
|
|
165
|
+
}
|
|
96
166
|
const b = pts[lo - o];
|
|
97
167
|
const aAt = lo - 1 < o ? 0 : pts[lo - 1 - o].at;
|
|
98
|
-
const
|
|
99
|
-
|
|
168
|
+
const u = fraction(phase, aAt, b.at);
|
|
169
|
+
// Before any of `seg` is written, so an easing that reads stops itself cannot overwrite it.
|
|
100
170
|
const eased = b.ease ? b.ease(u) : u;
|
|
171
|
+
seg.a = lo - 1 < o ? base : pts[lo - 1 - o].value;
|
|
172
|
+
seg.b = b.value;
|
|
173
|
+
seg.u = u;
|
|
174
|
+
seg.eased = eased;
|
|
175
|
+
seg.first = lo - 1 < o;
|
|
176
|
+
seg.len = b.at - aAt;
|
|
177
|
+
seg.ease = b.ease;
|
|
178
|
+
return BETWEEN;
|
|
179
|
+
}
|
|
180
|
+
/** Reads one track at a phase; `base` as `segment` takes it. */
|
|
181
|
+
function read(track, phase, base, lerp,
|
|
182
|
+
/** Units per ms, for a retarget's first segment. */
|
|
183
|
+
slope, duration, into, reuse) {
|
|
184
|
+
const found = segment(track, phase, base);
|
|
185
|
+
if (found === NOTHING)
|
|
186
|
+
return undefined;
|
|
187
|
+
if (found === AT)
|
|
188
|
+
return seg.a;
|
|
189
|
+
const aValue = seg.a;
|
|
190
|
+
const bValue = seg.b;
|
|
191
|
+
const u = seg.u;
|
|
192
|
+
const eased = seg.eased;
|
|
193
|
+
const first = seg.first;
|
|
194
|
+
const len = seg.len;
|
|
195
|
+
const ease = seg.ease;
|
|
101
196
|
let value;
|
|
102
197
|
if (into !== undefined) {
|
|
103
|
-
value = into(reuse, aValue,
|
|
198
|
+
value = into(reuse, aValue, bValue, eased);
|
|
104
199
|
lastRead.wrote = value;
|
|
105
200
|
}
|
|
106
201
|
else {
|
|
107
202
|
const by = track.lerp ?? lerp;
|
|
108
|
-
value = by ? by(aValue,
|
|
203
|
+
value = by ? by(aValue, bValue, eased) : interpolate(aValue, bValue, eased);
|
|
109
204
|
}
|
|
110
205
|
// Leaving a retarget's base, bend the first segment so it starts at the slope the subject had:
|
|
111
206
|
// add c·u(1−u)², which is 0 at both ends, flat at the end, and fixes the slope at the start.
|
|
112
|
-
if (slope === undefined ||
|
|
207
|
+
if (slope === undefined || !first)
|
|
113
208
|
return value;
|
|
114
|
-
const
|
|
115
|
-
const
|
|
116
|
-
const bend = (s, a, z) => (s * period * len - (z - a) * e0) * u * (1 - u) * (1 - u);
|
|
209
|
+
const e0 = ease ? ease(1e-6) / 1e-6 : 1;
|
|
210
|
+
const bend = (s, a, z) => (s * duration * len - (z - a) * e0) * u * (1 - u) * (1 - u);
|
|
117
211
|
if (typeof value === 'number' && typeof slope === 'number')
|
|
118
|
-
return value + bend(slope, aValue,
|
|
212
|
+
return value + bend(slope, aValue, bValue);
|
|
119
213
|
if (Array.isArray(value) && Array.isArray(slope)) {
|
|
120
214
|
const out = value === lastRead.wrote ? value : new Array(value.length);
|
|
121
215
|
for (let i = 0; i < value.length; i++) {
|
|
@@ -123,7 +217,7 @@ slope, period, into, reuse) {
|
|
|
123
217
|
const s = slope[i];
|
|
124
218
|
out[i] =
|
|
125
219
|
typeof v === 'number' && typeof s === 'number'
|
|
126
|
-
? v + bend(s, aValue[i],
|
|
220
|
+
? v + bend(s, aValue[i], bValue[i])
|
|
127
221
|
: v;
|
|
128
222
|
}
|
|
129
223
|
return out;
|
|
@@ -150,15 +244,13 @@ export function intosOf(built, channels) {
|
|
|
150
244
|
export function readKeys(built, phase, out, base, lerps,
|
|
151
245
|
/** Per channel, how fast a retargeted subject was moving, units per ms. */
|
|
152
246
|
slopes, intos, scratch) {
|
|
153
|
-
const
|
|
247
|
+
const duration = built.duration;
|
|
154
248
|
for (let i = 0; i < built.tracks.length; i++) {
|
|
155
249
|
const track = built.tracks[i];
|
|
156
|
-
const delay = track.delay;
|
|
157
|
-
const shifted = delay === 0 || period === 0 ? phase : Math.max(0, (phase * period - delay) / period);
|
|
158
250
|
const perMs = slopes?.[track.channel];
|
|
159
251
|
const into = scratch === undefined ? undefined : intos?.[i];
|
|
160
252
|
lastRead.wrote = undefined;
|
|
161
|
-
const value = read(track, shifted, base?.[track.channel], lerps?.[i],
|
|
253
|
+
const value = read(track, shifted(track, phase, duration), base?.[track.channel], lerps?.[i], duration === 0 ? undefined : perMs, duration, into, into === undefined ? undefined : scratch[i]);
|
|
162
254
|
if (into !== undefined && value === lastRead.wrote)
|
|
163
255
|
scratch[i] = value;
|
|
164
256
|
if (value !== undefined)
|
|
@@ -168,31 +260,34 @@ slopes, intos, scratch) {
|
|
|
168
260
|
}
|
|
169
261
|
return out;
|
|
170
262
|
}
|
|
263
|
+
/** A patch's `duration`, or the deprecated `period` where only that is set. */
|
|
264
|
+
export const durationOf = (p) => p.duration ?? p.period ?? 0;
|
|
171
265
|
const cache = new WeakMap();
|
|
172
266
|
/** The built form of a patch's stops, made on first ask for a patch `keys()` did not build. */
|
|
173
267
|
export function builtOf(p) {
|
|
174
268
|
let held = cache.get(p);
|
|
175
269
|
if (held === undefined) {
|
|
176
|
-
held = build(p.keys, p.writes, p
|
|
270
|
+
held = build(p.keys, p.writes, durationOf(p), keysOptionsOf(p) ?? {});
|
|
177
271
|
cache.set(p, held);
|
|
178
272
|
}
|
|
179
273
|
return held;
|
|
180
274
|
}
|
|
181
275
|
/** Evaluates a stop list into a fresh delta. */
|
|
182
|
-
export function evalKeys(stops, writes, phase,
|
|
183
|
-
return readKeys(build(stops, writes,
|
|
276
|
+
export function evalKeys(stops, writes, phase, duration, opts, base) {
|
|
277
|
+
return readKeys(build(stops, writes, duration, opts), phase, {}, base);
|
|
184
278
|
}
|
|
185
279
|
/**
|
|
186
280
|
* The declarative form. klieg's transition sugar and wod's keyframes.
|
|
187
281
|
*
|
|
188
282
|
* @category patch
|
|
189
283
|
*/
|
|
190
|
-
export function keys(
|
|
284
|
+
export function keys(duration, stops, opts = {}) {
|
|
191
285
|
const writes = [...new Set(stops.flatMap((s) => Object.keys(s.delta)))];
|
|
192
|
-
const made = build(stops, writes,
|
|
286
|
+
const made = build(stops, writes, duration, opts);
|
|
193
287
|
const p = {
|
|
194
288
|
form: 'keys',
|
|
195
|
-
|
|
289
|
+
duration,
|
|
290
|
+
period: duration,
|
|
196
291
|
writes,
|
|
197
292
|
kit: opts.kit,
|
|
198
293
|
keys: stops,
|
package/dist/place.d.ts
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { Mixer } from './mixer.js';
|
|
2
|
+
import type { Placement, VoiceSpec } from './types.js';
|
|
3
|
+
import { type Voice } from './voice.js';
|
|
4
|
+
/** What a voice's own owner's clock reads now: the mix clock for a voice no owner holds. */
|
|
5
|
+
export declare function localNow<I, O>(mix: Mixer<I, O>, voice: Voice<I, O>): number;
|
|
6
|
+
/** A voice's start as mix time. */
|
|
7
|
+
export declare function startOf<I, O>(voice: Voice<I, O>): number;
|
|
8
|
+
/** Mix time at host time `u`, for something pinned there; Infinity while the mix may never get there. */
|
|
9
|
+
export declare function mixAt<I, O>(mix: Mixer<I, O>, u: number): number;
|
|
10
|
+
export declare function pin<I, O>(mix: Mixer<I, O>, voice: Voice<I, O>, u: number): void;
|
|
11
|
+
/** Puts each pending voice's start, given in host time, where the mix's rate now puts it. */
|
|
12
|
+
export declare function repin<I, O>(mix: Mixer<I, O>): void;
|
|
13
|
+
/** Refuses a placement that names a mark twice on one side, or that waits on itself. */
|
|
14
|
+
export declare function checkPlacement<I, O>(mix: Mixer<I, O>, spec: VoiceSpec<I, O>, anchor: Placement, owner: Voice<I, O> | null): void;
|
|
15
|
+
/**
|
|
16
|
+
* Fixes every anchored voice's start and out from what its anchors answer now. A start is fixed
|
|
17
|
+
* while the voice waits and an out until its fade begins; a target that has left keeps the time it
|
|
18
|
+
* last gave. Repeated so a chain of anchors settles in one sync.
|
|
19
|
+
*/
|
|
20
|
+
export declare function place<I, O>(mix: Mixer<I, O>): void;
|