@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/store.js
CHANGED
|
@@ -1,22 +1,47 @@
|
|
|
1
|
+
/** What every store reads before its first write: never written, so always empty. */
|
|
2
|
+
const noWeak = new WeakMap();
|
|
3
|
+
const noStrong = new Map();
|
|
1
4
|
/**
|
|
2
5
|
* Per-subject storage keyed by identity. Objects go in a WeakMap and are forgotten when the host
|
|
3
6
|
* drops them; anything else — a string id, the unit subject of a mix with no subject dimension —
|
|
4
7
|
* goes in a Map that only `drop` and `clear` empty, which is why a host keying by value should
|
|
5
|
-
* call `drop`.
|
|
8
|
+
* call `drop`. Each map is made when first written, since a mix holds a store per voice and patch;
|
|
9
|
+
* until then it is a shared empty one, so the field always holds a map.
|
|
6
10
|
*/
|
|
7
11
|
export class Store {
|
|
8
|
-
weak =
|
|
9
|
-
strong =
|
|
12
|
+
weak = noWeak;
|
|
13
|
+
strong = noStrong;
|
|
14
|
+
/**
|
|
15
|
+
* The first key that is not an object, and its value, held here until a second comes: a voice of
|
|
16
|
+
* one subject keyed by id would otherwise spend a Map's smallest table on it.
|
|
17
|
+
*/
|
|
18
|
+
oneKey = none;
|
|
19
|
+
oneValue = undefined;
|
|
10
20
|
get(key) {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
21
|
+
if (typeof key === 'object' && key !== null)
|
|
22
|
+
return this.weak.get(key);
|
|
23
|
+
return same(this.oneKey, key) ? this.oneValue : this.strong.get(key);
|
|
14
24
|
}
|
|
15
25
|
set(key, value) {
|
|
16
|
-
if (typeof key === 'object' && key !== null)
|
|
26
|
+
if (typeof key === 'object' && key !== null) {
|
|
27
|
+
if (this.weak === noWeak)
|
|
28
|
+
this.weak = new WeakMap();
|
|
17
29
|
this.weak.set(key, value);
|
|
18
|
-
|
|
19
|
-
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
if (this.strong === noStrong && (this.oneKey === none || same(this.oneKey, key))) {
|
|
33
|
+
this.oneKey = key;
|
|
34
|
+
this.oneValue = value;
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
if (this.strong === noStrong)
|
|
38
|
+
this.strong = new Map();
|
|
39
|
+
if (this.oneKey !== none) {
|
|
40
|
+
this.strong.set(this.oneKey, this.oneValue);
|
|
41
|
+
this.oneKey = none;
|
|
42
|
+
this.oneValue = undefined;
|
|
43
|
+
}
|
|
44
|
+
this.strong.set(key, value);
|
|
20
45
|
}
|
|
21
46
|
has(key) {
|
|
22
47
|
return this.get(key) !== undefined;
|
|
@@ -24,10 +49,20 @@ export class Store {
|
|
|
24
49
|
delete(key) {
|
|
25
50
|
if (typeof key === 'object' && key !== null)
|
|
26
51
|
this.weak.delete(key);
|
|
52
|
+
else if (same(this.oneKey, key)) {
|
|
53
|
+
this.oneKey = none;
|
|
54
|
+
this.oneValue = undefined;
|
|
55
|
+
}
|
|
27
56
|
else
|
|
28
57
|
this.strong.delete(key);
|
|
29
58
|
}
|
|
30
59
|
clear() {
|
|
60
|
+
this.oneKey = none;
|
|
61
|
+
this.oneValue = undefined;
|
|
31
62
|
this.strong.clear();
|
|
32
63
|
}
|
|
33
64
|
}
|
|
65
|
+
/** No key held inline. */
|
|
66
|
+
const none = Symbol('none');
|
|
67
|
+
/** A Map's key equality, SameValueZero: NaN finds NaN. */
|
|
68
|
+
const same = (a, b) => a === b || Object.is(a, b);
|
package/dist/strays.d.ts
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { Mixer } from './mixer.js';
|
|
2
|
+
import type { MotionOwner, Motions } from './motion.js';
|
|
3
|
+
import type { Voice } from './voice.js';
|
|
4
|
+
/** Notes a motion patch that may hold state for a subject no voice playing it reaches. */
|
|
5
|
+
export declare function stray<I, O>(mix: Mixer<I, O>, motion: Motions<I>, subject: I): void;
|
|
6
|
+
/** A motion voice left the list: if its patch plays on, its subjects' state there may stray. */
|
|
7
|
+
export declare function unplay<I, O>(mix: Mixer<I, O>, voice: Voice<I, O>): void;
|
|
8
|
+
/**
|
|
9
|
+
* What a motion patch asks of the mix playing it, by its voice's id. It holds the mix weakly, so a
|
|
10
|
+
* patch the host keeps does not keep a mix it has let go of alive; a retired voice's patch no
|
|
11
|
+
* longer asks.
|
|
12
|
+
*/
|
|
13
|
+
export declare function motionOwner<I, O>(mix: WeakRef<Mixer<I, O>>): MotionOwner;
|
|
14
|
+
/** The voice in the list with this id, which is in id order. */
|
|
15
|
+
export declare function cuedById<I, O>(mix: Mixer<I, O>, id: number): Voice<I, O> | undefined;
|
|
16
|
+
/**
|
|
17
|
+
* A subject's voice time at the latest frame, where a motion patch places an untimed change and a
|
|
18
|
+
* `read` with no time; NaN until the voice has started and met the subject, and while the
|
|
19
|
+
* subject's own time is still short of its stagger. A retired voice's patch no longer asks.
|
|
20
|
+
*/
|
|
21
|
+
export declare function frameOf<I, O>(mix: Mixer<I, O>, voice: Voice<I, O>, subject: I): number;
|
package/dist/strays.js
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { unpart } from './fade.js';
|
|
2
|
+
/** Notes a motion patch that may hold state for a subject no voice playing it reaches. */
|
|
3
|
+
export function stray(mix, motion, subject) {
|
|
4
|
+
const list = mix.strays.get(subject);
|
|
5
|
+
if (list === undefined)
|
|
6
|
+
mix.strays.set(subject, [motion]);
|
|
7
|
+
else if (!list.includes(motion))
|
|
8
|
+
list.push(motion);
|
|
9
|
+
}
|
|
10
|
+
/** A motion voice left the list: if its patch plays on, its subjects' state there may stray. */
|
|
11
|
+
export function unplay(mix, voice) {
|
|
12
|
+
const motion = voice.motion;
|
|
13
|
+
if (motion === undefined)
|
|
14
|
+
return;
|
|
15
|
+
const left = (mix.playing.get(motion) ?? 1) - 1;
|
|
16
|
+
if (left <= 0) {
|
|
17
|
+
mix.playing.delete(motion);
|
|
18
|
+
mix.strayAll.delete(motion);
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
mix.playing.set(motion, left);
|
|
22
|
+
if (voice.named === null)
|
|
23
|
+
mix.strayAll.add(motion);
|
|
24
|
+
else
|
|
25
|
+
for (const subject of voice.named)
|
|
26
|
+
stray(mix, motion, subject);
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* What a motion patch asks of the mix playing it, by its voice's id. It holds the mix weakly, so a
|
|
30
|
+
* patch the host keeps does not keep a mix it has let go of alive; a retired voice's patch no
|
|
31
|
+
* longer asks.
|
|
32
|
+
*/
|
|
33
|
+
export function motionOwner(mix) {
|
|
34
|
+
return {
|
|
35
|
+
frame(id, subject) {
|
|
36
|
+
const m = mix.deref();
|
|
37
|
+
const v = m === undefined ? undefined : cuedById(m, id);
|
|
38
|
+
return m === undefined || v === undefined ? Number.NaN : frameOf(m, v, subject);
|
|
39
|
+
},
|
|
40
|
+
revive(id, subject) {
|
|
41
|
+
const m = mix.deref();
|
|
42
|
+
if (m === undefined)
|
|
43
|
+
return;
|
|
44
|
+
m.stir();
|
|
45
|
+
const v = cuedById(m, id);
|
|
46
|
+
if (v === undefined)
|
|
47
|
+
return;
|
|
48
|
+
if (v.motion !== undefined && v.named !== null && !v.named.has(subject))
|
|
49
|
+
stray(m, v.motion, subject);
|
|
50
|
+
unpart(m, v, subject);
|
|
51
|
+
},
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
/** The voice in the list with this id, which is in id order. */
|
|
55
|
+
export function cuedById(mix, id) {
|
|
56
|
+
const cued = mix.cued;
|
|
57
|
+
let lo = 0;
|
|
58
|
+
let hi = cued.length - 1;
|
|
59
|
+
while (lo <= hi) {
|
|
60
|
+
const mid = (lo + hi) >> 1;
|
|
61
|
+
const v = cued[mid];
|
|
62
|
+
if (v.id === id)
|
|
63
|
+
return v;
|
|
64
|
+
if (v.id < id)
|
|
65
|
+
lo = mid + 1;
|
|
66
|
+
else
|
|
67
|
+
hi = mid - 1;
|
|
68
|
+
}
|
|
69
|
+
return undefined;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* A subject's voice time at the latest frame, where a motion patch places an untimed change and a
|
|
73
|
+
* `read` with no time; NaN until the voice has started and met the subject, and while the
|
|
74
|
+
* subject's own time is still short of its stagger. A retired voice's patch no longer asks.
|
|
75
|
+
*/
|
|
76
|
+
export function frameOf(mix, voice, subject) {
|
|
77
|
+
if (Number.isNaN(mix.now) || voice.state === 'pending')
|
|
78
|
+
return Number.NaN;
|
|
79
|
+
const held = voice.subjects.get(subject);
|
|
80
|
+
if (held === undefined || !held.reaches)
|
|
81
|
+
return Number.NaN;
|
|
82
|
+
const t = voice.elapsedAt(mix.now) - held.delay;
|
|
83
|
+
return t < 0 ? Number.NaN : t;
|
|
84
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { Curve } from './easing.js';
|
|
2
|
+
/** Turns the velocity `eased` works out on or off. */
|
|
3
|
+
export declare function sloped(on: boolean): void;
|
|
4
|
+
/** How far through a stretch released at `at` and lasting `ms` voice time `t` is, from 0. */
|
|
5
|
+
export declare function progress(t: number, at: number, ms: number): number;
|
|
6
|
+
/** The share of a tween's way left to go at `u`, 0 once it has arrived. */
|
|
7
|
+
export declare function left(ease: Curve, u: number): number;
|
|
8
|
+
/** An axis of a tween from `x0` to `goal` with `share` of the way left. */
|
|
9
|
+
export declare function between(x0: number, goal: number, share: number): number;
|
|
10
|
+
/** A tween's stretch at `u` into `xo` and `vo`: true once it has arrived. */
|
|
11
|
+
export declare function eased(ease: Curve, n: number, u: number, ms: number, x0: ArrayLike<number>, x: number, to: ArrayLike<number>, g: number, xo: Float64Array, vo: Float64Array): boolean;
|
package/dist/tweened.js
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/** How far a tween's slope is read either side of `u`, for an easing that is only a function. */
|
|
2
|
+
const SPAN = 1e-4;
|
|
3
|
+
/**
|
|
4
|
+
* Whether an evaluation wants a tween's velocity, which costs two more reads of its easing. Only
|
|
5
|
+
* `read` asks for it; a mix and a retarget never use it.
|
|
6
|
+
*/
|
|
7
|
+
let slope = false;
|
|
8
|
+
/** The last tween sample's time, curve and length, and the share of the way it had left to go. */
|
|
9
|
+
const memo = { u: Number.NaN, ease: undefined, left: 0 };
|
|
10
|
+
/** Turns the velocity `eased` works out on or off. */
|
|
11
|
+
export function sloped(on) {
|
|
12
|
+
slope = on;
|
|
13
|
+
}
|
|
14
|
+
/** How far through a stretch released at `at` and lasting `ms` voice time `t` is, from 0. */
|
|
15
|
+
export function progress(t, at, ms) {
|
|
16
|
+
return Math.max(0, t - at) / ms;
|
|
17
|
+
}
|
|
18
|
+
/** The share of a tween's way left to go at `u`, 0 once it has arrived. */
|
|
19
|
+
export function left(ease, u) {
|
|
20
|
+
// Subjects released together share `u`, so a frame reads a bezier once, not once each.
|
|
21
|
+
if (u !== memo.u || ease !== memo.ease) {
|
|
22
|
+
memo.u = u;
|
|
23
|
+
memo.ease = ease;
|
|
24
|
+
memo.left = u >= 1 ? 0 : 1 - ease(u);
|
|
25
|
+
}
|
|
26
|
+
return memo.left;
|
|
27
|
+
}
|
|
28
|
+
/** An axis of a tween from `x0` to `goal` with `share` of the way left. */
|
|
29
|
+
export function between(x0, goal, share) {
|
|
30
|
+
return share === 0 ? goal : goal + (x0 - goal) * share;
|
|
31
|
+
}
|
|
32
|
+
/** A tween's stretch at `u` into `xo` and `vo`: true once it has arrived. */
|
|
33
|
+
export function eased(ease, n, u, ms, x0, x, to, g, xo, vo) {
|
|
34
|
+
const share = left(ease, u);
|
|
35
|
+
let rate = 0;
|
|
36
|
+
if (slope && u < 1) {
|
|
37
|
+
const lo = Math.max(0, u - SPAN);
|
|
38
|
+
const hi = Math.min(1, u + SPAN);
|
|
39
|
+
rate = ((ease(hi) - ease(lo)) / (hi - lo)) * (1000 / ms);
|
|
40
|
+
}
|
|
41
|
+
for (let i = 0; i < n; i++) {
|
|
42
|
+
const a = x0[x + i];
|
|
43
|
+
const goal = to[g + i];
|
|
44
|
+
xo[i] = between(a, goal, share);
|
|
45
|
+
vo[i] = -(a - goal) * rate;
|
|
46
|
+
}
|
|
47
|
+
return share === 0;
|
|
48
|
+
}
|
package/dist/types.d.ts
CHANGED
|
@@ -67,10 +67,10 @@ export interface Keyframe<O> {
|
|
|
67
67
|
*
|
|
68
68
|
* @category state
|
|
69
69
|
*/
|
|
70
|
-
export interface Setting<S = void> {
|
|
70
|
+
export interface Setting<S = void, H = unknown> {
|
|
71
71
|
/**
|
|
72
|
-
* The mix clock at this frame: the host's timestamp less any time `rebase` took out
|
|
73
|
-
* for every probe in the frame.
|
|
72
|
+
* The mix clock at this frame: the host's timestamp less any time `rebase` took out, run at the
|
|
73
|
+
* mix's `rate`. Identical for every probe in the frame.
|
|
74
74
|
*/
|
|
75
75
|
timestamp: number;
|
|
76
76
|
/**
|
|
@@ -88,14 +88,15 @@ export interface Setting<S = void> {
|
|
|
88
88
|
weight: number;
|
|
89
89
|
/** This subject's state, when the patch declares one. */
|
|
90
90
|
state: S;
|
|
91
|
-
/** Anything the host adds for its own patches
|
|
92
|
-
host:
|
|
91
|
+
/** Anything the host adds for its own patches: the mix's `host`, typed as its `H`. */
|
|
92
|
+
host: H;
|
|
93
93
|
/**
|
|
94
94
|
* The state `owner` keeps for this voice and this subject, made by `init` on first ask. The mix
|
|
95
95
|
* holds it, so a read at another time can copy it instead of moving it. A stateful signal keeps
|
|
96
|
-
* its state here and nowhere else. With lanes on, a patch that first calls it
|
|
97
|
-
* playing can have that state advanced once for one subject not probed on the
|
|
98
|
-
*
|
|
96
|
+
* its state here and nowhere else. With lanes on, a patch or weight signal that first calls it
|
|
97
|
+
* partway through playing can have that state advanced once for one subject not probed on the
|
|
98
|
+
* frame it starts; one that keeps state from its first call, or a patch that declares `state`,
|
|
99
|
+
* never does.
|
|
99
100
|
*/
|
|
100
101
|
keep<K>(owner: object, init: () => K): K;
|
|
101
102
|
/**
|
|
@@ -103,7 +104,7 @@ export interface Setting<S = void> {
|
|
|
103
104
|
* host drains it, and never calls back. Meant for `step`, where under `stepMs` the timestamp is
|
|
104
105
|
* the interval the event happened in rather than the frame that sampled it. Sent from a stateless
|
|
105
106
|
* patch's `at` while it runs on a lane, it goes out for every subject the mix has met, probed that
|
|
106
|
-
* frame or not.
|
|
107
|
+
* frame or not; `at` is not asked for a subject at weight 0, so nothing is sent from it there.
|
|
107
108
|
*/
|
|
108
109
|
send(event: unknown): void;
|
|
109
110
|
}
|
|
@@ -153,14 +154,16 @@ export type MotionSpec = {
|
|
|
153
154
|
*
|
|
154
155
|
* @category patch
|
|
155
156
|
*/
|
|
156
|
-
export interface Patch<I, O, S = void> {
|
|
157
|
+
export interface Patch<I, O, S = void, H = unknown> {
|
|
157
158
|
/**
|
|
158
159
|
* Which authoring form built it: `'fn'` from `patch`, `'keys'` from `keys`, `'motion'` from
|
|
159
160
|
* `spring`, `glide` or `tween`. An engine declares which forms it runs.
|
|
160
161
|
*/
|
|
161
162
|
readonly form: 'fn' | 'keys' | 'motion';
|
|
162
|
-
/** Milliseconds one pass lasts. 0
|
|
163
|
-
readonly
|
|
163
|
+
/** Milliseconds one pass lasts, whether or not the voice loops. 0 has no passes: phase and pass stay 0. */
|
|
164
|
+
readonly duration: number;
|
|
165
|
+
/** @deprecated Use `duration`. A patch that sets only `period` still plays. */
|
|
166
|
+
readonly period?: number;
|
|
164
167
|
/** The channels this patch contributes to. Every key `at` sets, and no others. */
|
|
165
168
|
readonly writes: readonly (keyof O)[];
|
|
166
169
|
/**
|
|
@@ -170,15 +173,18 @@ export interface Patch<I, O, S = void> {
|
|
|
170
173
|
readonly kit?: Partial<Kit<O>>;
|
|
171
174
|
/** The fields of `setting.host` this patch reads. `cue` refuses a mix whose host lacks one. */
|
|
172
175
|
readonly reads?: readonly string[];
|
|
173
|
-
/**
|
|
174
|
-
|
|
176
|
+
/**
|
|
177
|
+
* `phase` is 0..1 across one pass, wrapping. Not asked for a subject its voice gives no weight, unless the voice is fading to rest;
|
|
178
|
+
* `step` still runs.
|
|
179
|
+
*/
|
|
180
|
+
at(phase: number, subject: I, setting: Setting<S, H>): Partial<O>;
|
|
175
181
|
/** Per-subject state, created on the first frame this patch sees a subject. */
|
|
176
182
|
state?(subject: I): S;
|
|
177
183
|
/**
|
|
178
184
|
* Advances state once per subject per sampled frame, by the whole gap since it last advanced; or,
|
|
179
185
|
* where the mix sets `stepMs`, once per whole interval of that length.
|
|
180
186
|
*/
|
|
181
|
-
step?(state: S, dt: number, subject: I, setting: Setting<S>): void;
|
|
187
|
+
step?(state: S, dt: number, subject: I, setting: Setting<S, H>): void;
|
|
182
188
|
/** Present when the patch was authored as keyframes, so an engine that reads data can. */
|
|
183
189
|
readonly keys?: readonly Keyframe<O>[];
|
|
184
190
|
/** Present on a `'motion'` patch: its kind and constants, for an engine that reads data. */
|
|
@@ -197,7 +203,7 @@ export interface Patch<I, O, S = void> {
|
|
|
197
203
|
*
|
|
198
204
|
* @category signal
|
|
199
205
|
*/
|
|
200
|
-
export type Signal<I> = ((subject: I, setting: Setting) => number) & {
|
|
206
|
+
export type Signal<I, H = unknown> = ((subject: I, setting: Setting<void, H>) => number) & {
|
|
201
207
|
readonly input?: boolean;
|
|
202
208
|
};
|
|
203
209
|
/**
|
|
@@ -286,19 +292,91 @@ export interface Marked {
|
|
|
286
292
|
name: string | undefined;
|
|
287
293
|
tags: readonly string[];
|
|
288
294
|
}
|
|
295
|
+
/**
|
|
296
|
+
* An event at a time on a voice's own clock, which `book` hands a host ahead of time. It plays once
|
|
297
|
+
* per pass, for the voice and not per subject, so `stagger` does not spread it.
|
|
298
|
+
*
|
|
299
|
+
* @category score
|
|
300
|
+
*/
|
|
301
|
+
export interface Hit<E = unknown> {
|
|
302
|
+
/** Voice ms into each pass: 0 up to the patch's `duration`, or any time at all for a patch with none. */
|
|
303
|
+
at: number;
|
|
304
|
+
event: E;
|
|
305
|
+
}
|
|
306
|
+
/**
|
|
307
|
+
* One pass of one hit, as `book` takes it.
|
|
308
|
+
*
|
|
309
|
+
* @category score
|
|
310
|
+
*/
|
|
311
|
+
export interface BookedHit<E = unknown> {
|
|
312
|
+
/** On the host's clock. */
|
|
313
|
+
timestamp: number;
|
|
314
|
+
voice: number;
|
|
315
|
+
/** Which of the voice's `hits`, by index. */
|
|
316
|
+
hit: number;
|
|
317
|
+
/** Which pass, 0 first. */
|
|
318
|
+
pass: number;
|
|
319
|
+
event: E;
|
|
320
|
+
score: string | undefined;
|
|
321
|
+
name: string | undefined;
|
|
322
|
+
tags: readonly string[];
|
|
323
|
+
}
|
|
324
|
+
/**
|
|
325
|
+
* What `book` takes: an outside clock, how far ahead to book on it, and what to do with each item.
|
|
326
|
+
*
|
|
327
|
+
* @category score
|
|
328
|
+
*/
|
|
329
|
+
export interface BookOptions<E = unknown> {
|
|
330
|
+
/**
|
|
331
|
+
* The outside clock, in ms: `audio.currentTime * 1000`, `performance.now()`, or whatever the host
|
|
332
|
+
* schedules against. Read once a sync. The mix maps its host time onto it by an offset that folds
|
|
333
|
+
* in 5% of each frame's difference, and starts over from the clock's own reading on a jump past
|
|
334
|
+
* 50 ms, such as a suspended context or a hidden tab.
|
|
335
|
+
*/
|
|
336
|
+
clock: () => number;
|
|
337
|
+
/** How far ahead to book, host ms. It has to cover the longest gap between two syncs. */
|
|
338
|
+
ahead: number;
|
|
339
|
+
/** How far past an item first seen late may be and still be taken, host ms. */
|
|
340
|
+
late: number;
|
|
341
|
+
/**
|
|
342
|
+
* Called once per item: a mark as `marks` lists it, or a hit. `when` is on the outside clock;
|
|
343
|
+
* `lateBy` is how many host ms past the item was when first seen, 0 for one booked ahead, which
|
|
344
|
+
* is then taken at the clock's now. Return a `stop` to hear about a booking whose time moved by
|
|
345
|
+
* more than 1 ms or that went away (a seek, a rate or ramp on the voice or the mix, a fade, a
|
|
346
|
+
* `rebase`, an anchor's target moving, the voice leaving); the item is then booked again, at its
|
|
347
|
+
* new time, at the same sync.
|
|
348
|
+
*/
|
|
349
|
+
take(item: Marked | BookedHit<E>, when: number, lateBy: number): {
|
|
350
|
+
stop(): void;
|
|
351
|
+
} | undefined;
|
|
352
|
+
/** Book only the voices, and announced marks, carrying this tag. */
|
|
353
|
+
tag?: string;
|
|
354
|
+
/** Book only this score's voices and marks. */
|
|
355
|
+
score?: string;
|
|
356
|
+
}
|
|
357
|
+
/**
|
|
358
|
+
* A running booker, returned by `book`.
|
|
359
|
+
*
|
|
360
|
+
* @category score
|
|
361
|
+
*/
|
|
362
|
+
export interface Booker {
|
|
363
|
+
/** Stops every booking still ahead, and books nothing more. */
|
|
364
|
+
stop(): void;
|
|
365
|
+
}
|
|
289
366
|
/**
|
|
290
367
|
* What `cue` takes: a patch, and the clock, weight and reach it plays with.
|
|
291
368
|
*
|
|
292
369
|
* @category voice
|
|
293
370
|
*/
|
|
294
|
-
export interface VoiceSpec<I, O> {
|
|
295
|
-
patch: Patch<I, O, unknown>;
|
|
371
|
+
export interface VoiceSpec<I, O, H = unknown> {
|
|
372
|
+
patch: Patch<I, O, unknown, H>;
|
|
296
373
|
/**
|
|
297
374
|
* Which subjects this voice reaches. Default: all of them. The predicate is fixed at `cue`; it
|
|
298
375
|
* runs per subject the first time the mix sees that subject, and the answer is kept. Every
|
|
299
376
|
* voice's predicate meets every subject, so voices that each reach a known few cost the square
|
|
300
|
-
* of their number
|
|
301
|
-
*
|
|
377
|
+
* of their number in time; name those with `subjects` instead. A no is kept as a bit by the
|
|
378
|
+
* number lanes give the subject, but with `lanes: false` as a map entry per voice per subject
|
|
379
|
+
* asked (about 37 MB for 1,000 such voices over 1,000 subjects).
|
|
302
380
|
*/
|
|
303
381
|
target?: (subject: I) => boolean;
|
|
304
382
|
/**
|
|
@@ -336,14 +414,14 @@ export interface VoiceSpec<I, O> {
|
|
|
336
414
|
*/
|
|
337
415
|
hold?: 'before' | 'after' | 'both';
|
|
338
416
|
/** Steady weight, or a signal read per subject per frame. Default 1. */
|
|
339
|
-
weight?: number | Signal<I>;
|
|
417
|
+
weight?: number | Signal<I, H>;
|
|
340
418
|
/** Ramp in and out, ms. Out applies on `fade()` and when a finite loop ends. */
|
|
341
419
|
fade?: FadeSpec;
|
|
342
420
|
/** Voices sharing a locus are alternatives: the mix folds them through each channel's `lerp`. */
|
|
343
421
|
locus?: string;
|
|
344
422
|
/** A `keys` voice whose first stop reads wherever this subject is now. */
|
|
345
423
|
from?: 'current';
|
|
346
|
-
/** Words a source attaches to this voice, so its events can be
|
|
424
|
+
/** Words a source attaches to this voice, so its events and its handle can be taken as a set. */
|
|
347
425
|
tags?: readonly string[];
|
|
348
426
|
/** What other voices call this one by. A label blits never reads. */
|
|
349
427
|
name?: string;
|
|
@@ -355,6 +433,52 @@ export interface VoiceSpec<I, O> {
|
|
|
355
433
|
score?: string;
|
|
356
434
|
/** Where it sits relative to the clock or to other voices, in place of `start`. */
|
|
357
435
|
anchor?: Placement;
|
|
436
|
+
/**
|
|
437
|
+
* Events at times on this voice's clock, which `book` hands a host ahead of time. Patch-sent
|
|
438
|
+
* events are not known ahead, so they stay drain-only; these are the plan.
|
|
439
|
+
*/
|
|
440
|
+
hits?: readonly Hit[];
|
|
441
|
+
/**
|
|
442
|
+
* The owner this voice plays under, a handle `owns` returned. Its `start` and `anchor` are then on
|
|
443
|
+
* the owner's clock, in ms from when the owner starts, and default to where that clock is now; a
|
|
444
|
+
* bare name in an anchor means a sibling, another voice the owner holds. Its clock runs on the
|
|
445
|
+
* owner's, so the owner's rate, ramp and seeks move it, and its weight is multiplied by the
|
|
446
|
+
* owner's weight and fade. Without a `hold` of its own it takes the owner's.
|
|
447
|
+
*/
|
|
448
|
+
owner?: Handle<I>;
|
|
449
|
+
}
|
|
450
|
+
/**
|
|
451
|
+
* What `owns` takes: an owner has no patch, and plays only through the voices it holds.
|
|
452
|
+
*
|
|
453
|
+
* @category voice
|
|
454
|
+
*/
|
|
455
|
+
export interface OwnerSpec<I, H = unknown> {
|
|
456
|
+
/**
|
|
457
|
+
* When the owner starts, on its own owner's clock: the host's for an owner on the mix, as a
|
|
458
|
+
* voice's `start` is. Default: now.
|
|
459
|
+
*/
|
|
460
|
+
start?: number;
|
|
461
|
+
/** Its clock's rate, multiplied into every child's own. Default 1. */
|
|
462
|
+
rate?: number;
|
|
463
|
+
/**
|
|
464
|
+
* Multiplied into every child's weight per subject, as the child's own fade is, before the child's
|
|
465
|
+
* locus folds it: a number, or a signal read per subject per frame. Default 1.
|
|
466
|
+
*/
|
|
467
|
+
weight?: number | Signal<I, H>;
|
|
468
|
+
/** One ramp in and out over every child, multiplied into their weights as `weight` is. */
|
|
469
|
+
fade?: FadeSpec;
|
|
470
|
+
/** The hold every child without one of its own takes. */
|
|
471
|
+
hold?: 'before' | 'after' | 'both';
|
|
472
|
+
/** Words a source attaches to it, as a voice's. */
|
|
473
|
+
tags?: readonly string[];
|
|
474
|
+
/** What other voices call it by, among its siblings. */
|
|
475
|
+
name?: string;
|
|
476
|
+
/** The plan it belongs to, as a voice's. */
|
|
477
|
+
score?: string;
|
|
478
|
+
/** Where it sits relative to its own owner's clock or to other voices, in place of `start`. */
|
|
479
|
+
anchor?: Placement;
|
|
480
|
+
/** The owner it plays under, so owners nest. */
|
|
481
|
+
owner?: Handle<I>;
|
|
358
482
|
}
|
|
359
483
|
/**
|
|
360
484
|
* How a voice leaves when it is faded: the whole voice, or one subject.
|
|
@@ -388,6 +512,8 @@ export interface Handle<I = unknown> {
|
|
|
388
512
|
readonly id: number;
|
|
389
513
|
/** `held`: a voice holding after has played every pass and shows its last frame until faded. */
|
|
390
514
|
readonly state: 'pending' | 'live' | 'held' | 'fading' | 'done';
|
|
515
|
+
/** The handle of the owner this voice plays under; undefined for one on the mix clock. */
|
|
516
|
+
readonly owner: Handle<I> | undefined;
|
|
391
517
|
/** Live. Writes land on the next sync. */
|
|
392
518
|
weight: number;
|
|
393
519
|
/** Playback rate now. Setting it changes speed at once; `ramp` eases into a new one. */
|
|
@@ -425,12 +551,12 @@ export interface Handle<I = unknown> {
|
|
|
425
551
|
*
|
|
426
552
|
* @category mix
|
|
427
553
|
*/
|
|
428
|
-
export interface MixOptions {
|
|
554
|
+
export interface MixOptions<H = unknown> {
|
|
429
555
|
engine?: Engine;
|
|
430
556
|
/** Reduced motion: fades and warm-ups snap, `dt` reads Infinity. */
|
|
431
557
|
reduce?: boolean | (() => boolean);
|
|
432
|
-
/** Merged into every `setting.host`. */
|
|
433
|
-
host?:
|
|
558
|
+
/** Merged into every `setting.host`. Its type is the mix's `H`, which every patch and signal it cues reads. */
|
|
559
|
+
host?: H;
|
|
434
560
|
/**
|
|
435
561
|
* The band a rest-less channel's influence switches on and off across. It has to clear the frame
|
|
436
562
|
* noise in a real signal; these are placeholders until the klieg port measures one.
|
|
@@ -514,11 +640,26 @@ export type Columns<O> = {
|
|
|
514
640
|
*
|
|
515
641
|
* @category mix
|
|
516
642
|
*/
|
|
517
|
-
export interface Mix<I, O> {
|
|
643
|
+
export interface Mix<I, O, H = unknown> {
|
|
518
644
|
/** Cues a voice. Throws when the engine cannot run the patch's form, or the kit lacks a channel. */
|
|
519
|
-
cue(spec: VoiceSpec<I, O>): Handle<I>;
|
|
520
|
-
/**
|
|
521
|
-
|
|
645
|
+
cue(spec: VoiceSpec<I, O, H>): Handle<I>;
|
|
646
|
+
/**
|
|
647
|
+
* N voices whose weights split one signal, cued into one locus so they fold as alternatives. The
|
|
648
|
+
* signal is read once per subject per frame, with the first member's setting, and every member
|
|
649
|
+
* takes its share of that one reading; a signal keeping state keeps it once, not per member.
|
|
650
|
+
*/
|
|
651
|
+
blend(patches: readonly Patch<I, O, unknown, H>[], by: Signal<I, H>, spec?: Omit<VoiceSpec<I, O, H>, 'patch' | 'weight' | 'locus'>): Handle<I>[];
|
|
652
|
+
/**
|
|
653
|
+
* Cues an owner: a voice with no patch that holds the voices cued with `owner` set to its handle,
|
|
654
|
+
* and plays them on its own clock, so they can be placed, timed and faded as one. Its handle acts
|
|
655
|
+
* on all of them: `rate` and `ramp` multiply into theirs, `seek` moves their clocks with its own
|
|
656
|
+
* and leaves their state where it is, and `weight` and `fade()` multiply into their weights, by
|
|
657
|
+
* what its `weightOf` reports; it fades as a whole, not by subject or at rest. It is
|
|
658
|
+
* `played` once every voice it held has finished its passes, and leaves, `done`, with its last; a
|
|
659
|
+
* fade that ends takes the rest with it. One that never holds a voice stays until faded. Owners
|
|
660
|
+
* nest. Throws for a `loop`: a pass would have to restart its children.
|
|
661
|
+
*/
|
|
662
|
+
owns(spec: OwnerSpec<I, H>): Handle<I>;
|
|
522
663
|
/** The host reports the clock, once a frame. Nothing advances at the call. */
|
|
523
664
|
sync(timestamp: number): void;
|
|
524
665
|
/**
|
|
@@ -526,7 +667,30 @@ export interface Mix<I, O> {
|
|
|
526
667
|
* hidden tab comes back. Every voice, fade and subject resumes where it was left.
|
|
527
668
|
*/
|
|
528
669
|
rebase(): void;
|
|
529
|
-
/**
|
|
670
|
+
/**
|
|
671
|
+
* The mix's own playback rate, multiplied into every voice's: 1 is real time and 0 pauses the
|
|
672
|
+
* whole mix. A negative rate throws, since the mix only goes forward. Setting it changes speed at
|
|
673
|
+
* once; `ramp` eases into a new one. It scales mix time, which everything a voice owns runs on:
|
|
674
|
+
* its clock, its fades and a handle's ramp, `stagger`, an anchor's `by`, `stepMs`, `maxDt`,
|
|
675
|
+
* `history.ms`, every `dt`, and `setting.timestamp`. Timestamps on the host's clock name the same
|
|
676
|
+
* moment whatever the rate does: a `start`, an anchor given as a number, an announced mark, what
|
|
677
|
+
* `marks` reports and the time `project` reads, so a start ahead of a paused mix waits for the
|
|
678
|
+
* host's clock to reach it. A weight signal is still asked at every probe, so one reading host
|
|
679
|
+
* input follows it while the mix is paused.
|
|
680
|
+
*/
|
|
681
|
+
rate: number;
|
|
682
|
+
/**
|
|
683
|
+
* Moves the mix's rate to `rate` linearly over `over` ms of the host's clock, as `Handle.ramp`
|
|
684
|
+
* moves a voice's. Mix time integrates the ramp and a voice's clock integrates mix time, so a ramp
|
|
685
|
+
* on both multiplies and every position stays continuous. A later `rate` write or `ramp` replaces
|
|
686
|
+
* it.
|
|
687
|
+
*/
|
|
688
|
+
ramp(rate: number, over: number): void;
|
|
689
|
+
/**
|
|
690
|
+
* The merged pose for one subject at the synced frame, written into `out` when given. Without
|
|
691
|
+
* it, each call makes a new object, so a host reading every subject every frame passes `out` or
|
|
692
|
+
* reads by `pull`.
|
|
693
|
+
*/
|
|
530
694
|
probe(subject: I, out?: O): O;
|
|
531
695
|
/**
|
|
532
696
|
* Writes each subject's pose into arrays, one per channel, subject by subject in the order given:
|
|
@@ -544,14 +708,24 @@ export interface Mix<I, O> {
|
|
|
544
708
|
* older than the history reaches.
|
|
545
709
|
*/
|
|
546
710
|
project(timestamp: number): Projection<I, O>;
|
|
547
|
-
/**
|
|
711
|
+
/**
|
|
712
|
+
* Every channel at rest for this subject this frame, so a host can skip the write. After a probe
|
|
713
|
+
* of the subject this frame, it answers for the pose that probe gave, without folding again.
|
|
714
|
+
*/
|
|
548
715
|
atRest(subject: I): boolean;
|
|
716
|
+
/**
|
|
717
|
+
* A handle on every voice still in the mix, pending, live, held or fading, in the order they were
|
|
718
|
+
* cued; given a tag, only the voices whose `tags` carry it. Each is the handle `cue` returned for
|
|
719
|
+
* that voice, so a listed handle is `===` the cued one.
|
|
720
|
+
*/
|
|
721
|
+
voices(tag?: string): Handle<I>[];
|
|
549
722
|
/** Anything still contributing, fading, or pending. */
|
|
550
723
|
readonly live: boolean;
|
|
551
724
|
/**
|
|
552
725
|
* Another frame would change no pose, so a host's frame loop may sleep: every voice is done, held
|
|
553
|
-
* at a plain weight, or a motion whose every subject has landed on its target.
|
|
554
|
-
*
|
|
726
|
+
* at a plain weight, or a motion whose every subject has landed on its target. At rate 0 it is
|
|
727
|
+
* enough that every voice not done has started, at a plain weight, with no anchor. A change made
|
|
728
|
+
* since the last sync, a retarget or a rate included, makes it false until the next.
|
|
555
729
|
*/
|
|
556
730
|
readonly inert: boolean;
|
|
557
731
|
/** Fades every voice out: over `over` when given, over each voice's own `fade.out` otherwise. */
|
|
@@ -588,6 +762,15 @@ export interface Mix<I, O> {
|
|
|
588
762
|
* whoever drains them.
|
|
589
763
|
*/
|
|
590
764
|
drain<E = unknown>(tag?: string): Sent<I, E>[];
|
|
765
|
+
/**
|
|
766
|
+
* Hands the host each mark and hit before it comes, against a clock of its own, so it can schedule
|
|
767
|
+
* the thing itself: a sound on an `AudioContext`, a MIDI message with a timestamp, a video loaded
|
|
768
|
+
* before the voice that shows it starts. Booking runs at each `sync`, `ahead` ms out, and a
|
|
769
|
+
* booking whose time moves is stopped and taken again. Nothing is booked twice: a mark is known by
|
|
770
|
+
* its voice and which mark, a hit by its voice, index and pass. `project` books nothing, and
|
|
771
|
+
* nothing about booking is replayed under `history`. A mix with no booker pays nothing for it.
|
|
772
|
+
*/
|
|
773
|
+
book<E = unknown>(opts: BookOptions<E>): Booker;
|
|
591
774
|
}
|
|
592
775
|
/**
|
|
593
776
|
* The implementation behind a mix: which patch forms it runs, and how it makes one.
|
|
@@ -598,5 +781,5 @@ export interface Engine {
|
|
|
598
781
|
readonly name: string;
|
|
599
782
|
/** Which patch forms this engine can run. A voice it cannot run is refused at `cue`, by name. */
|
|
600
783
|
readonly runs: ReadonlySet<'fn' | 'keys' | 'motion'>;
|
|
601
|
-
create<I, O>(kit: Kit<O>, opts: MixOptions): Mix<I, O>;
|
|
784
|
+
create<I, O, H = unknown>(kit: Kit<O>, opts: MixOptions<H>): Mix<I, O, H>;
|
|
602
785
|
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { Voice } from './voice.js';
|
|
2
|
+
/** Whether the voice's shared unreached record stands for the subject numbered `slot`. */
|
|
3
|
+
export declare function unreached<I, O>(voice: Voice<I, O>, slot: number): boolean;
|
|
4
|
+
/** Marks or clears the subject numbered `slot` as one the voice does not reach. */
|
|
5
|
+
export declare function unreach<I, O>(voice: Voice<I, O>, slot: number, on: boolean): void;
|