@msb235/blits 0.5.0 → 0.7.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 +163 -0
- package/README.md +32 -8
- package/dist/bare.js +7 -1
- package/dist/book.d.ts +8 -1
- package/dist/book.js +35 -4
- package/dist/chain.d.ts +4 -3
- package/dist/chain.js +10 -6
- package/dist/channels.d.ts +12 -19
- package/dist/channels.js +18 -118
- package/dist/clock.d.ts +12 -5
- package/dist/clock.js +13 -5
- package/dist/closed.d.ts +15 -0
- package/dist/closed.js +144 -0
- package/dist/color.d.ts +61 -0
- package/dist/color.js +229 -0
- package/dist/columns.js +2 -1
- package/dist/crowd.d.ts +4 -4
- package/dist/crowd.js +11 -8
- package/dist/cue.d.ts +2 -0
- package/dist/cue.js +78 -27
- package/dist/due.js +5 -2
- package/dist/fade.d.ts +13 -0
- package/dist/fade.js +55 -4
- package/dist/fill.d.ts +1 -1
- package/dist/fill.js +17 -10
- package/dist/fit.d.ts +144 -0
- package/dist/fit.js +181 -0
- package/dist/fold.d.ts +3 -3
- package/dist/fold.js +25 -18
- package/dist/gather.d.ts +6 -0
- package/dist/gather.js +18 -0
- package/dist/handle.d.ts +27 -2
- package/dist/handle.js +60 -11
- package/dist/held.js +7 -3
- package/dist/history.d.ts +15 -2
- package/dist/history.js +54 -9
- package/dist/hosts.d.ts +2 -2
- package/dist/hosts.js +19 -6
- package/dist/index.d.ts +10 -2
- package/dist/index.js +6 -1
- package/dist/lane.d.ts +4 -4
- package/dist/lane.js +5 -5
- package/dist/lanes.d.ts +32 -9
- package/dist/lanes.js +53 -24
- package/dist/locus.d.ts +3 -3
- package/dist/locus.js +1 -1
- package/dist/marks.d.ts +3 -0
- package/dist/marks.js +81 -13
- package/dist/meet.d.ts +3 -2
- package/dist/meet.js +15 -3
- package/dist/mixer.d.ts +55 -35
- package/dist/mixer.js +173 -83
- package/dist/motion.d.ts +2 -199
- package/dist/motion.js +2 -652
- package/dist/motions.d.ts +266 -0
- package/dist/motions.js +724 -0
- package/dist/move.d.ts +1 -1
- package/dist/move.js +27 -11
- package/dist/owner.d.ts +2 -2
- package/dist/owner.js +5 -5
- package/dist/pace.d.ts +2 -0
- package/dist/pace.js +8 -0
- package/dist/pack.d.ts +31 -0
- package/dist/pack.js +65 -0
- package/dist/paging.d.ts +63 -0
- package/dist/paging.js +110 -0
- package/dist/patch.d.ts +3 -0
- package/dist/patch.js +2 -0
- package/dist/place.js +74 -25
- package/dist/project.d.ts +7 -4
- package/dist/project.js +117 -78
- package/dist/pull.js +4 -3
- package/dist/qualify.js +4 -2
- package/dist/revive.d.ts +58 -0
- package/dist/revive.js +158 -0
- package/dist/sample.d.ts +1 -1
- package/dist/sample.js +6 -1
- package/dist/seek.d.ts +3 -0
- package/dist/seek.js +311 -0
- package/dist/signals.d.ts +3 -2
- package/dist/signals.js +12 -6
- package/dist/spans.d.ts +29 -0
- package/dist/spans.js +165 -0
- package/dist/strays.d.ts +1 -1
- package/dist/strays.js +21 -0
- package/dist/tape.d.ts +21 -0
- package/dist/tape.js +65 -0
- package/dist/ticker.d.ts +73 -0
- package/dist/ticker.js +115 -0
- package/dist/transport.d.ts +121 -0
- package/dist/transport.js +271 -0
- package/dist/types.d.ts +427 -50
- package/dist/unpage.d.ts +8 -0
- package/dist/unpage.js +163 -0
- package/dist/voice.d.ts +78 -12
- package/dist/voice.js +126 -12
- package/dist/weigh.js +2 -2
- package/docs/schema.html +140 -48
- package/package.json +2 -1
package/dist/move.d.ts
CHANGED
|
@@ -6,4 +6,4 @@ export declare function waits<I, O>(mix: Mixer<I, O>): boolean;
|
|
|
6
6
|
/** Moves the mix clock to `now`: voices start, finite loops end, and fades finish. */
|
|
7
7
|
export declare function move<I, O>(mix: Mixer<I, O>, now: number): void;
|
|
8
8
|
/** Lets go of the voices that have left and that history no longer reaches. */
|
|
9
|
-
export declare function forget<I, O>(mix: Mixer<I, O
|
|
9
|
+
export declare function forget<I, O>(mix: Mixer<I, O>): void;
|
package/dist/move.js
CHANGED
|
@@ -4,6 +4,8 @@ import { beginFade, part, retire } from './fade.js';
|
|
|
4
4
|
import { started } from './held.js';
|
|
5
5
|
import { ownerReading } from './owner.js';
|
|
6
6
|
import { mixAt, place, repin, startOf } from './place.js';
|
|
7
|
+
import { pageVoice } from './revive.js';
|
|
8
|
+
import { lapse } from './spans.js';
|
|
7
9
|
import { unplay } from './strays.js';
|
|
8
10
|
/** Numbers every mix's frames apart, so a blend's read is never taken for another mix's or frame's. */
|
|
9
11
|
let frames = 0;
|
|
@@ -38,7 +40,7 @@ function moveTo(mix, now) {
|
|
|
38
40
|
place(mix);
|
|
39
41
|
if (mix.announced.length > 0) {
|
|
40
42
|
// Kept while still ahead, or while history reaches it; anchors waiting on one were placed above.
|
|
41
|
-
const reach =
|
|
43
|
+
const reach = mix.opts.history === undefined ? now : mix.transport.keepsFrom();
|
|
42
44
|
mix.announced = mix.announced.filter((a) => mixAt(mix, a.at) >= reach);
|
|
43
45
|
}
|
|
44
46
|
// A live mix visits only the voices due by now; a projection, which copies few, visits all.
|
|
@@ -48,8 +50,8 @@ function moveTo(mix, now) {
|
|
|
48
50
|
continue;
|
|
49
51
|
if (Number.isNaN(voice.opened))
|
|
50
52
|
voice.opened = now;
|
|
51
|
-
if ((voice.state === 'live' || voice.state === '
|
|
52
|
-
beginFade(mix, voice, {}, Math.max(startOf(voice), voice.outAt));
|
|
53
|
+
if ((voice.state === 'live' || voice.state === 'frozen') && voice.outAt <= now)
|
|
54
|
+
beginFade(mix, voice, { over: Number.isNaN(voice.outOver) ? undefined : voice.outOver }, Math.max(startOf(voice), voice.outAt));
|
|
53
55
|
if (voice.state === 'pending' &&
|
|
54
56
|
(voice.owner === null ? now : ownerReading(voice.owner, now)) >= voice.start) {
|
|
55
57
|
voice.state = 'live';
|
|
@@ -59,17 +61,17 @@ function moveTo(mix, now) {
|
|
|
59
61
|
if (voice.state !== 'pending' && Number.isFinite(voice.span)) {
|
|
60
62
|
const end = voice.span + voice.latest;
|
|
61
63
|
if (voice.elapsedAt(now) >= end) {
|
|
62
|
-
voice.play(true);
|
|
64
|
+
voice.play(true, now);
|
|
63
65
|
// The fade starts when the last pass ended, not at the frame that noticed, so it plays
|
|
64
66
|
// the same at any frame rate and a read at another time can find it.
|
|
65
67
|
if (voice.state === 'live') {
|
|
66
|
-
if (voice.
|
|
67
|
-
voice.state = '
|
|
68
|
+
if (voice.freezesAfter)
|
|
69
|
+
voice.state = 'frozen';
|
|
68
70
|
else
|
|
69
71
|
beginFade(mix, voice, {}, Math.max(startOf(voice), Math.min(now, voice.timeAt(end))));
|
|
70
72
|
}
|
|
71
73
|
}
|
|
72
|
-
else if (voice.state === '
|
|
74
|
+
else if (voice.state === 'frozen')
|
|
73
75
|
voice.state = 'live';
|
|
74
76
|
}
|
|
75
77
|
// A projection reads a finished ramp as weight 0, and leaves the live voice's subjects be.
|
|
@@ -77,6 +79,11 @@ function moveTo(mix, now) {
|
|
|
77
79
|
for (const [subject, r] of voice.parts)
|
|
78
80
|
if (now - r.at >= r.over)
|
|
79
81
|
part(mix, voice, subject, r.at + r.over);
|
|
82
|
+
const back = voice.back;
|
|
83
|
+
if (back !== null && now >= back.at + (1 - back.from) * back.over && !mix.projecting) {
|
|
84
|
+
voice.back = null;
|
|
85
|
+
mix.lanes?.refill();
|
|
86
|
+
}
|
|
80
87
|
if (voice.state === 'fading' && voice.out) {
|
|
81
88
|
const { at, over, rest, deadline } = voice.out;
|
|
82
89
|
const spent = now - at;
|
|
@@ -94,10 +101,12 @@ function moveTo(mix, now) {
|
|
|
94
101
|
if (!mix.projecting)
|
|
95
102
|
schedule(mix, voice);
|
|
96
103
|
}
|
|
104
|
+
if (mix.owners !== null)
|
|
105
|
+
lapse(mix, now);
|
|
97
106
|
mix.stirred = false;
|
|
98
107
|
if (mix.retired.length > 0)
|
|
99
108
|
prune(mix);
|
|
100
|
-
forget(mix
|
|
109
|
+
forget(mix);
|
|
101
110
|
}
|
|
102
111
|
/** Past this many voices retiring in one frame, one pass over the list beats a search for each. */
|
|
103
112
|
const SEARCHED = 8;
|
|
@@ -181,10 +190,17 @@ function leave(mix, voice) {
|
|
|
181
190
|
return voice.named === null;
|
|
182
191
|
}
|
|
183
192
|
/** Lets go of the voices that have left and that history no longer reaches. */
|
|
184
|
-
export function forget(mix
|
|
193
|
+
export function forget(mix) {
|
|
185
194
|
const history = mix.opts.history;
|
|
186
195
|
if (history && mix.gone.length > 0) {
|
|
187
|
-
|
|
188
|
-
|
|
196
|
+
// With a store a voice that has left pages out where it can, and stays where it cannot.
|
|
197
|
+
if (mix.transport.pager !== null) {
|
|
198
|
+
const reach = mix.now - history.ms;
|
|
199
|
+
mix.gone = mix.gone.filter((v) => v.doneAt >= reach || !pageVoice(mix, v, reach));
|
|
200
|
+
}
|
|
201
|
+
else {
|
|
202
|
+
const reach = mix.transport.keepsFrom();
|
|
203
|
+
mix.gone = mix.gone.filter((v) => v.doneAt >= reach);
|
|
204
|
+
}
|
|
189
205
|
}
|
|
190
206
|
}
|
package/dist/owner.d.ts
CHANGED
|
@@ -24,7 +24,7 @@ export declare function adopt<I, O>(owner: Voice<I, O>, child: Voice<I, O>): voi
|
|
|
24
24
|
/** A child left: its owner, where that was its last child and it should leave too. */
|
|
25
25
|
export declare function orphan<I, O>(child: Voice<I, O>): Voice<I, O> | null;
|
|
26
26
|
/** A child finished its passes: its owner has played once every child it held has. */
|
|
27
|
-
export declare function childPlayed<I, O>(owner: Voice<I, O
|
|
27
|
+
export declare function childPlayed<I, O>(owner: Voice<I, O>, at: number): void;
|
|
28
28
|
/** Every voice an owner holds, its children's children included. */
|
|
29
29
|
export declare function descendants<I, O>(owner: Voice<I, O>, visit: (v: Voice<I, O>) => void): void;
|
|
30
30
|
/** Whether any owner above a voice reads a signal for its weight, which lanes cannot fill. */
|
|
@@ -34,7 +34,7 @@ export declare function heldByInput<I, O>(voice: Voice<I, O>): boolean;
|
|
|
34
34
|
/**
|
|
35
35
|
* What one owner multiplies into its children's weight for a subject at mix time `now`: its weight,
|
|
36
36
|
* or its signal's read by `base`, times its own fade, held to 0..1. Its fade in counts from when it
|
|
37
|
-
* started, or from when it first showed where it
|
|
37
|
+
* started, or from when it first showed where it freezes before.
|
|
38
38
|
*/
|
|
39
39
|
export declare function ownWeight<I, O>(owner: Voice<I, O>, now: number, reduced: boolean, base: (owner: Voice<I, O>) => number): number;
|
|
40
40
|
/** What every owner above a voice multiplies into its weight. */
|
package/dist/owner.js
CHANGED
|
@@ -48,10 +48,10 @@ export function orphan(child) {
|
|
|
48
48
|
return h.children.length === 0 && owner.state !== 'done' ? owner : null;
|
|
49
49
|
}
|
|
50
50
|
/** A child finished its passes: its owner has played once every child it held has. */
|
|
51
|
-
export function childPlayed(owner) {
|
|
51
|
+
export function childPlayed(owner, at) {
|
|
52
52
|
const h = owner.holding;
|
|
53
53
|
if (++h.played === h.kids)
|
|
54
|
-
owner.play(true);
|
|
54
|
+
owner.play(true, at);
|
|
55
55
|
}
|
|
56
56
|
/** Every voice an owner holds, its children's children included. */
|
|
57
57
|
export function descendants(owner, visit) {
|
|
@@ -80,13 +80,13 @@ export function heldByInput(voice) {
|
|
|
80
80
|
/**
|
|
81
81
|
* What one owner multiplies into its children's weight for a subject at mix time `now`: its weight,
|
|
82
82
|
* or its signal's read by `base`, times its own fade, held to 0..1. Its fade in counts from when it
|
|
83
|
-
* started, or from when it first showed where it
|
|
83
|
+
* started, or from when it first showed where it freezes before.
|
|
84
84
|
*/
|
|
85
85
|
export function ownWeight(owner, now, reduced, base) {
|
|
86
86
|
const b = typeof owner.spec.weight === 'function' ? base(owner) : owner.weight;
|
|
87
87
|
const start = owner.owner === null ? owner.start : mixTime(owner.owner, owner.start);
|
|
88
|
-
const since = owner.
|
|
89
|
-
return clampWeight(b * envelope(owner.fade.in ?? 0, owner.out, owner.ease, reduced, now, since));
|
|
88
|
+
const since = owner.freezesBefore && owner.opened < start ? owner.opened : start;
|
|
89
|
+
return clampWeight(b * envelope(owner.fade.in ?? 0, owner.out, owner.ease, reduced, now, since, owner.back));
|
|
90
90
|
}
|
|
91
91
|
/** What every owner above a voice multiplies into its weight. */
|
|
92
92
|
export function ownersWeight(voice, now, reduced, base) {
|
package/dist/pace.d.ts
CHANGED
|
@@ -35,6 +35,8 @@ export declare class Pace {
|
|
|
35
35
|
* time `reach`.
|
|
36
36
|
*/
|
|
37
37
|
change(u: number, rate: number, over: number, reach: number): void;
|
|
38
|
+
/** Lets go of the clocks set after host time `u`, for a seek back to then. */
|
|
39
|
+
cut(u: number): void;
|
|
38
40
|
/** A copy holding the clocks that took effect before host time `u`, for a read back to then. */
|
|
39
41
|
until(u: number): Pace;
|
|
40
42
|
}
|
package/dist/pace.js
CHANGED
|
@@ -102,6 +102,14 @@ export class Pace {
|
|
|
102
102
|
if (drop > 0)
|
|
103
103
|
log.splice(0, drop);
|
|
104
104
|
}
|
|
105
|
+
/** Lets go of the clocks set after host time `u`, for a seek back to then. */
|
|
106
|
+
cut(u) {
|
|
107
|
+
const log = this.log;
|
|
108
|
+
let n = 1;
|
|
109
|
+
while (n < log.length && log[n].anchorNow <= u)
|
|
110
|
+
n++;
|
|
111
|
+
log.length = n;
|
|
112
|
+
}
|
|
105
113
|
/** A copy holding the clocks that took effect before host time `u`, for a read back to then. */
|
|
106
114
|
until(u) {
|
|
107
115
|
const log = this.log;
|
package/dist/pack.d.ts
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { Subject, Voice } from './voice.js';
|
|
2
|
+
/** A subject's record as plain data, for a history store. */
|
|
3
|
+
export interface PackedRecord {
|
|
4
|
+
reaches: boolean;
|
|
5
|
+
delay: number;
|
|
6
|
+
since: number;
|
|
7
|
+
shown: number;
|
|
8
|
+
weight: number;
|
|
9
|
+
rested: boolean;
|
|
10
|
+
bands: number[] | null;
|
|
11
|
+
state: unknown;
|
|
12
|
+
stepped: number;
|
|
13
|
+
ticks: number;
|
|
14
|
+
/**
|
|
15
|
+
* What `setting.keep` held, in the order its owners first kept it. An owner is code, which no
|
|
16
|
+
* store keeps, and a voice revived from a descriptor has new ones; the same code keeps in the same
|
|
17
|
+
* order, so each owner takes the next value on its first keep.
|
|
18
|
+
*/
|
|
19
|
+
kept: unknown[] | null;
|
|
20
|
+
base?: Record<string, unknown>;
|
|
21
|
+
slope?: Record<string, unknown>;
|
|
22
|
+
snaps?: [at: number, held: PackedRecord][];
|
|
23
|
+
inputs?: {
|
|
24
|
+
at: number;
|
|
25
|
+
value: number;
|
|
26
|
+
}[];
|
|
27
|
+
}
|
|
28
|
+
/** A voice's record of one subject as plain data, its patch's `pack` taking its state. */
|
|
29
|
+
export declare function packHeld<I, O>(voice: Voice<I, O>, h: Subject<unknown>): PackedRecord;
|
|
30
|
+
/** A record made again from `packHeld`'s data, for `voice`. */
|
|
31
|
+
export declare function unpackHeld<I, O>(voice: Voice<I, O>, p: PackedRecord): Subject<unknown>;
|
package/dist/pack.js
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { clone } from './clone.js';
|
|
2
|
+
/** A voice's record of one subject as plain data, its patch's `pack` taking its state. */
|
|
3
|
+
export function packHeld(voice, h) {
|
|
4
|
+
const patch = voice.patch;
|
|
5
|
+
const kept = [...(h.kept?.values() ?? []), ...(h.unkept ?? [])];
|
|
6
|
+
const p = {
|
|
7
|
+
reaches: h.reaches,
|
|
8
|
+
delay: h.delay,
|
|
9
|
+
since: h.since,
|
|
10
|
+
shown: h.shown,
|
|
11
|
+
weight: h.weight,
|
|
12
|
+
rested: h.rested,
|
|
13
|
+
bands: h.bands === null ? null : Array.from(h.bands),
|
|
14
|
+
state: h.state === undefined ? undefined : patch.pack ? patch.pack(h.state) : clone(h.state),
|
|
15
|
+
stepped: h.stepped,
|
|
16
|
+
ticks: h.ticks,
|
|
17
|
+
kept: kept.length === 0 ? null : kept.map(clone),
|
|
18
|
+
};
|
|
19
|
+
if (h.base !== undefined)
|
|
20
|
+
p.base = clone(h.base);
|
|
21
|
+
if (h.slope !== undefined)
|
|
22
|
+
p.slope = clone(h.slope);
|
|
23
|
+
if (h.snaps !== undefined)
|
|
24
|
+
p.snaps = h.snaps.map((s) => [s.at, packHeld(voice, s.held)]);
|
|
25
|
+
if (h.inputs !== undefined)
|
|
26
|
+
p.inputs = h.inputs.map((e) => ({ ...e }));
|
|
27
|
+
return p;
|
|
28
|
+
}
|
|
29
|
+
/** A record made again from `packHeld`'s data, for `voice`. */
|
|
30
|
+
export function unpackHeld(voice, p) {
|
|
31
|
+
const patch = voice.patch;
|
|
32
|
+
const h = {
|
|
33
|
+
reaches: p.reaches,
|
|
34
|
+
delay: p.delay,
|
|
35
|
+
since: p.since,
|
|
36
|
+
shown: p.shown,
|
|
37
|
+
weight: p.weight,
|
|
38
|
+
rested: p.rested,
|
|
39
|
+
bands: p.bands === null ? null : Uint8Array.from(p.bands),
|
|
40
|
+
state: p.state === undefined ? undefined : patch.unpack ? patch.unpack(p.state) : p.state,
|
|
41
|
+
stepped: p.stepped,
|
|
42
|
+
ticks: p.ticks,
|
|
43
|
+
probed: Number.NaN,
|
|
44
|
+
delta: null,
|
|
45
|
+
phase: 0,
|
|
46
|
+
seeks: 0,
|
|
47
|
+
kept: null,
|
|
48
|
+
voice,
|
|
49
|
+
next: null,
|
|
50
|
+
version: Number.NaN,
|
|
51
|
+
loci: null,
|
|
52
|
+
slot: -1,
|
|
53
|
+
};
|
|
54
|
+
if (p.kept !== null)
|
|
55
|
+
h.unkept = p.kept;
|
|
56
|
+
if (p.base !== undefined)
|
|
57
|
+
h.base = p.base;
|
|
58
|
+
if (p.slope !== undefined)
|
|
59
|
+
h.slope = p.slope;
|
|
60
|
+
if (p.snaps !== undefined)
|
|
61
|
+
h.snaps = p.snaps.map(([at, held]) => ({ at, held: unpackHeld(voice, held) }));
|
|
62
|
+
if (p.inputs !== undefined)
|
|
63
|
+
h.inputs = p.inputs;
|
|
64
|
+
return h;
|
|
65
|
+
}
|
package/dist/paging.d.ts
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import type { Mixer } from './mixer.js';
|
|
2
|
+
import type { Transport } from './transport.js';
|
|
3
|
+
import type { HistoryStore, Paged, PagedStream } from './types.js';
|
|
4
|
+
/**
|
|
5
|
+
* A `seek` or `project` reached past memory into records `prepare` had not loaded. Thrown before
|
|
6
|
+
* anything moves, so a restore is never partial: `await prepare(at)` and ask again.
|
|
7
|
+
*
|
|
8
|
+
* @category mix
|
|
9
|
+
*/
|
|
10
|
+
export declare class HistoryMiss extends Error {
|
|
11
|
+
/** The mix whose records were missing, by `name`. */
|
|
12
|
+
readonly mix: string;
|
|
13
|
+
readonly stream: string;
|
|
14
|
+
/** The mix time asked for. */
|
|
15
|
+
readonly at: number;
|
|
16
|
+
constructor(
|
|
17
|
+
/** The mix whose records were missing, by `name`. */
|
|
18
|
+
mix: string, stream: string,
|
|
19
|
+
/** The mix time asked for. */
|
|
20
|
+
at: number);
|
|
21
|
+
}
|
|
22
|
+
/** A transport's history store: what left memory, and what `prepare` loaded back. */
|
|
23
|
+
export declare class Pager {
|
|
24
|
+
readonly store: HistoryStore;
|
|
25
|
+
/** What the last `prepare` loaded, and every record paged since, until a seek or read takes it. */
|
|
26
|
+
loaded: {
|
|
27
|
+
t: number;
|
|
28
|
+
records: Paged[];
|
|
29
|
+
} | null;
|
|
30
|
+
/** The latest record paged, which a miss names. */
|
|
31
|
+
last: {
|
|
32
|
+
mix: string;
|
|
33
|
+
stream: PagedStream;
|
|
34
|
+
} | null;
|
|
35
|
+
constructor(store: HistoryStore);
|
|
36
|
+
}
|
|
37
|
+
/** A subject's key in history records; undefined for the unit subject. */
|
|
38
|
+
export type Key = string | number | undefined;
|
|
39
|
+
/**
|
|
40
|
+
* Object subjects by key, for a mix with a store, held weakly: a subject the host has let go of
|
|
41
|
+
* can never be probed again, so nothing it kept is put back.
|
|
42
|
+
*/
|
|
43
|
+
export declare class Keys {
|
|
44
|
+
private readonly of;
|
|
45
|
+
private readonly refs;
|
|
46
|
+
private readonly gone;
|
|
47
|
+
constructor(of: ((subject: never) => string | number) | undefined);
|
|
48
|
+
/** The key a subject is paged under; throws for an object subject without `keyOf`. */
|
|
49
|
+
key(subject: unknown): Key;
|
|
50
|
+
/** The subject a key stands for: an object the mix met under it, else the key itself. */
|
|
51
|
+
subject(key: Key): unknown;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Hands records leaving memory to the store, the last of any that share a time, which is the one
|
|
55
|
+
* a read finds. `reach` is how far back memory still reaches.
|
|
56
|
+
*/
|
|
57
|
+
export declare function pageOut<I, O, E extends {
|
|
58
|
+
at: number;
|
|
59
|
+
}>(mix: Mixer<I, O>, stream: PagedStream, voice: number | undefined, subject: Key, out: readonly E[], data: (e: E) => unknown, reach: number): void;
|
|
60
|
+
/** Gives records to the store; memory now reaches back no further than `reach`. */
|
|
61
|
+
export declare function page(transport: Transport, records: Paged[], reach: number): void;
|
|
62
|
+
/** Loads what a seek or read to `t` needs where it reaches past memory. */
|
|
63
|
+
export declare function prepare(transport: Transport, t: number): Promise<void>;
|
package/dist/paging.js
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A `seek` or `project` reached past memory into records `prepare` had not loaded. Thrown before
|
|
3
|
+
* anything moves, so a restore is never partial: `await prepare(at)` and ask again.
|
|
4
|
+
*
|
|
5
|
+
* @category mix
|
|
6
|
+
*/
|
|
7
|
+
export class HistoryMiss extends Error {
|
|
8
|
+
mix;
|
|
9
|
+
stream;
|
|
10
|
+
at;
|
|
11
|
+
constructor(
|
|
12
|
+
/** The mix whose records were missing, by `name`. */
|
|
13
|
+
mix, stream,
|
|
14
|
+
/** The mix time asked for. */
|
|
15
|
+
at) {
|
|
16
|
+
super(`blits: ${at} reaches past memory into ${stream} records of ${mix === '' ? 'the mix' : mix} that were not loaded; await prepare(${at}) first`);
|
|
17
|
+
this.mix = mix;
|
|
18
|
+
this.stream = stream;
|
|
19
|
+
this.at = at;
|
|
20
|
+
this.name = 'HistoryMiss';
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
/** A transport's history store: what left memory, and what `prepare` loaded back. */
|
|
24
|
+
export class Pager {
|
|
25
|
+
store;
|
|
26
|
+
/** What the last `prepare` loaded, and every record paged since, until a seek or read takes it. */
|
|
27
|
+
loaded = null;
|
|
28
|
+
/** The latest record paged, which a miss names. */
|
|
29
|
+
last = null;
|
|
30
|
+
constructor(store) {
|
|
31
|
+
this.store = store;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Object subjects by key, for a mix with a store, held weakly: a subject the host has let go of
|
|
36
|
+
* can never be probed again, so nothing it kept is put back.
|
|
37
|
+
*/
|
|
38
|
+
export class Keys {
|
|
39
|
+
of;
|
|
40
|
+
refs = new Map();
|
|
41
|
+
gone = new FinalizationRegistry((key) => {
|
|
42
|
+
const ref = this.refs.get(key);
|
|
43
|
+
if (ref !== undefined && ref.deref() === undefined)
|
|
44
|
+
this.refs.delete(key);
|
|
45
|
+
});
|
|
46
|
+
constructor(of) {
|
|
47
|
+
this.of = of;
|
|
48
|
+
}
|
|
49
|
+
/** The key a subject is paged under; throws for an object subject without `keyOf`. */
|
|
50
|
+
key(subject) {
|
|
51
|
+
if (subject === undefined || typeof subject === 'string' || typeof subject === 'number')
|
|
52
|
+
return subject;
|
|
53
|
+
const of = this.of;
|
|
54
|
+
if (of === undefined || typeof subject !== 'object' || subject === null)
|
|
55
|
+
throw new Error('blits: a mix whose history has a store pages its subjects by key; give it keyOf for subjects that are not strings or numbers');
|
|
56
|
+
const key = of(subject);
|
|
57
|
+
if (this.refs.get(key)?.deref() !== subject) {
|
|
58
|
+
this.refs.set(key, new WeakRef(subject));
|
|
59
|
+
this.gone.register(subject, key);
|
|
60
|
+
}
|
|
61
|
+
return key;
|
|
62
|
+
}
|
|
63
|
+
/** The subject a key stands for: an object the mix met under it, else the key itself. */
|
|
64
|
+
subject(key) {
|
|
65
|
+
if (key === undefined)
|
|
66
|
+
return undefined;
|
|
67
|
+
return this.refs.get(key)?.deref() ?? key;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Hands records leaving memory to the store, the last of any that share a time, which is the one
|
|
72
|
+
* a read finds. `reach` is how far back memory still reaches.
|
|
73
|
+
*/
|
|
74
|
+
export function pageOut(mix, stream, voice, subject, out, data, reach) {
|
|
75
|
+
const pager = mix.transport.pager;
|
|
76
|
+
if (pager === null || out.length === 0)
|
|
77
|
+
return;
|
|
78
|
+
const name = mix.name ?? '';
|
|
79
|
+
const records = [];
|
|
80
|
+
for (let i = 0; i < out.length; i++) {
|
|
81
|
+
const e = out[i];
|
|
82
|
+
if (i + 1 < out.length && out[i + 1].at === e.at)
|
|
83
|
+
continue;
|
|
84
|
+
const r = { mix: name, stream, at: e.at, data: data(e) };
|
|
85
|
+
if (voice !== undefined)
|
|
86
|
+
r.voice = voice;
|
|
87
|
+
if (subject !== undefined)
|
|
88
|
+
r.subject = subject;
|
|
89
|
+
records.push(r);
|
|
90
|
+
}
|
|
91
|
+
page(mix.transport, records, reach);
|
|
92
|
+
}
|
|
93
|
+
/** Gives records to the store; memory now reaches back no further than `reach`. */
|
|
94
|
+
export function page(transport, records, reach) {
|
|
95
|
+
const pager = transport.pager;
|
|
96
|
+
pager.store.page(records);
|
|
97
|
+
pager.loaded?.records.push(...records);
|
|
98
|
+
const last = records[records.length - 1];
|
|
99
|
+
pager.last = { mix: last.mix, stream: last.stream };
|
|
100
|
+
if (reach > transport.floor)
|
|
101
|
+
transport.floor = reach;
|
|
102
|
+
}
|
|
103
|
+
/** Loads what a seek or read to `t` needs where it reaches past memory. */
|
|
104
|
+
export async function prepare(transport, t) {
|
|
105
|
+
const pager = transport.pager;
|
|
106
|
+
if (pager === null || transport.reaches(t))
|
|
107
|
+
return;
|
|
108
|
+
const records = await pager.store.load(t);
|
|
109
|
+
pager.loaded = { t, records: [...records] };
|
|
110
|
+
}
|
package/dist/patch.d.ts
CHANGED
|
@@ -15,6 +15,9 @@ export interface PatchOptions<I, O, S, H = unknown> {
|
|
|
15
15
|
reads?: readonly string[];
|
|
16
16
|
state?(subject: I): S;
|
|
17
17
|
step?(state: S, dt: number, subject: I, setting: Setting<S, H>): void;
|
|
18
|
+
/** `state` as plain data and back, for a history store: see `Patch.pack`. */
|
|
19
|
+
pack?(state: S): unknown;
|
|
20
|
+
unpack?(data: unknown): S;
|
|
18
21
|
}
|
|
19
22
|
/**
|
|
20
23
|
* The procedural form. Flicker, roving, a hash walk, a starter strike.
|
package/dist/patch.js
CHANGED
package/dist/place.js
CHANGED
|
@@ -17,6 +17,7 @@ export function mixAt(mix, u) {
|
|
|
17
17
|
export function pin(mix, voice, u) {
|
|
18
18
|
mix.pins ??= new Map();
|
|
19
19
|
mix.pins.set(voice, u);
|
|
20
|
+
voice.pinned = u;
|
|
20
21
|
}
|
|
21
22
|
/** Puts each pending voice's start, given in host time, where the mix's rate now puts it. */
|
|
22
23
|
export function repin(mix) {
|
|
@@ -49,20 +50,23 @@ export function checkPlacement(mix, spec, anchor, owner) {
|
|
|
49
50
|
const name = spec.name;
|
|
50
51
|
if (name === undefined)
|
|
51
52
|
return;
|
|
52
|
-
// A voice is known by its owner, its score and its name
|
|
53
|
-
// asker's score under the asker's owner.
|
|
54
|
-
const key = (o, score, n) => `${o?.id ?? 0}\u0000${score ?? ''}\u0000${n}`;
|
|
55
|
-
const
|
|
56
|
-
if (
|
|
57
|
-
return
|
|
53
|
+
// A voice is known by its owner, its score and its name, and by its mix where it names no score;
|
|
54
|
+
// a bare name in an anchor is in the asker's score under the asker's owner.
|
|
55
|
+
const key = (o, score, n, slot) => `${score === undefined ? slot : ''}\u0000${o?.id ?? 0}\u0000${score ?? ''}\u0000${n}`;
|
|
56
|
+
const named = (a, score, o, slot) => {
|
|
57
|
+
if ('all' in a)
|
|
58
|
+
return a.all.flatMap((m) => named(m, score, o, slot));
|
|
59
|
+
if ('any' in a)
|
|
60
|
+
return a.any.flatMap((m) => named(m, score, o, slot));
|
|
58
61
|
const q = 'of' in a ? a.of : 'after' in a ? a.after : 'with' in a ? a.with : a.before;
|
|
59
62
|
if (typeof q === 'string')
|
|
60
|
-
return [key(o, score, q)];
|
|
61
|
-
return q.name === undefined ? [] : [key(o, q.score ?? score, q.name)];
|
|
62
|
-
}
|
|
63
|
-
const
|
|
63
|
+
return [key(o, score, q, slot)];
|
|
64
|
+
return q.name === undefined ? [] : [key(o, q.score ?? score, q.name, slot)];
|
|
65
|
+
};
|
|
66
|
+
const names = (p, score, o, slot) => [p.start, p.in, p.out, p.end].flatMap((a) => a === undefined || typeof a === 'number' ? [] : named(a, score, o, slot));
|
|
67
|
+
const self = key(owner, spec.score, name, mix.slot);
|
|
64
68
|
const seen = new Set();
|
|
65
|
-
const waits = names(anchor, spec.score, owner);
|
|
69
|
+
const waits = names(anchor, spec.score, owner, mix.slot);
|
|
66
70
|
while (waits.length > 0) {
|
|
67
71
|
const n = waits.pop();
|
|
68
72
|
if (n === self)
|
|
@@ -70,11 +74,12 @@ export function checkPlacement(mix, spec, anchor, owner) {
|
|
|
70
74
|
if (seen.has(n))
|
|
71
75
|
continue;
|
|
72
76
|
seen.add(n);
|
|
73
|
-
for (const
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
77
|
+
for (const m of mix.transport.members)
|
|
78
|
+
for (const v of m.cued)
|
|
79
|
+
if (v.spec.name !== undefined &&
|
|
80
|
+
key(v.owner, v.spec.score, v.spec.name, m.slot) === n &&
|
|
81
|
+
v.spec.anchor)
|
|
82
|
+
waits.push(...names(v.spec.anchor, v.spec.score, v.owner, m.slot));
|
|
78
83
|
}
|
|
79
84
|
}
|
|
80
85
|
/**
|
|
@@ -104,7 +109,7 @@ export function place(mix) {
|
|
|
104
109
|
moved = true;
|
|
105
110
|
}
|
|
106
111
|
}
|
|
107
|
-
if (voice.state !== 'fading') {
|
|
112
|
+
if (voice.state !== 'fading' && !voice.outSet) {
|
|
108
113
|
const by = anchor.out ?? anchor.end;
|
|
109
114
|
const t = by === undefined ? undefined : resolve(mix, by, voice);
|
|
110
115
|
if (t !== undefined) {
|
|
@@ -127,6 +132,32 @@ function resolve(mix, a, self) {
|
|
|
127
132
|
const o = self.owner;
|
|
128
133
|
if (typeof a === 'number')
|
|
129
134
|
return o === null ? mixAt(mix, a - mix.offset) : a;
|
|
135
|
+
if ('all' in a) {
|
|
136
|
+
let latest = Number.NEGATIVE_INFINITY;
|
|
137
|
+
for (const m of a.all) {
|
|
138
|
+
const t = member(mix, m, self);
|
|
139
|
+
if (t === undefined)
|
|
140
|
+
return undefined;
|
|
141
|
+
latest = Math.max(latest, t);
|
|
142
|
+
}
|
|
143
|
+
return a.all.length === 0 ? undefined : latest;
|
|
144
|
+
}
|
|
145
|
+
if ('any' in a) {
|
|
146
|
+
// A member still unknown may yet answer earlier than every known one, until one has passed.
|
|
147
|
+
const now = Number.isNaN(mix.now) ? Number.NEGATIVE_INFINITY : localNow(mix, self);
|
|
148
|
+
let earliest = Number.POSITIVE_INFINITY;
|
|
149
|
+
let unknown = false;
|
|
150
|
+
for (const m of a.any) {
|
|
151
|
+
const t = member(mix, m, self);
|
|
152
|
+
if (t === undefined)
|
|
153
|
+
unknown = true;
|
|
154
|
+
else
|
|
155
|
+
earliest = Math.min(earliest, t);
|
|
156
|
+
}
|
|
157
|
+
if (earliest === Number.POSITIVE_INFINITY)
|
|
158
|
+
return undefined;
|
|
159
|
+
return unknown && earliest > now ? undefined : earliest;
|
|
160
|
+
}
|
|
130
161
|
let query;
|
|
131
162
|
let mark;
|
|
132
163
|
let by = a.by ?? 0;
|
|
@@ -155,6 +186,20 @@ function resolve(mix, a, self) {
|
|
|
155
186
|
const local = ownerReading(o, t);
|
|
156
187
|
return Number.isFinite(local) ? local + by : undefined;
|
|
157
188
|
}
|
|
189
|
+
/**
|
|
190
|
+
* What one member of a join answers, or the time it last gave once its target has left, as a lone
|
|
191
|
+
* anchor keeps its voice where its target last put it.
|
|
192
|
+
*/
|
|
193
|
+
function member(mix, a, self) {
|
|
194
|
+
const t = resolve(mix, a, self);
|
|
195
|
+
if (t === undefined)
|
|
196
|
+
return self.answers?.get(a);
|
|
197
|
+
if (!mix.projecting) {
|
|
198
|
+
self.answers ??= new Map();
|
|
199
|
+
self.answers.set(a, t);
|
|
200
|
+
}
|
|
201
|
+
return t;
|
|
202
|
+
}
|
|
158
203
|
/**
|
|
159
204
|
* The mix time a query answers: `mark` of the voice it picks, among those cued and those that have
|
|
160
205
|
* left, never the asker; or a mark the host announced on the score, which is the same time for
|
|
@@ -165,17 +210,21 @@ function timeOf(mix, q, mark, self) {
|
|
|
165
210
|
const score = q.score ?? self.spec.score;
|
|
166
211
|
const anywhere = q.score !== undefined;
|
|
167
212
|
const found = [];
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
(
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
213
|
+
// A query naming a score looks in every mix on the transport; one naming none, in its own.
|
|
214
|
+
const mixes = anywhere ? mix.transport.members : [mix];
|
|
215
|
+
for (const m of mixes)
|
|
216
|
+
for (const v of [...m.gone, ...m.cued])
|
|
217
|
+
if (v !== self &&
|
|
218
|
+
v.spec.score === score &&
|
|
219
|
+
(anywhere || v.owner === self.owner) &&
|
|
220
|
+
(q.name === undefined || v.spec.name === q.name) &&
|
|
221
|
+
(q.tag === undefined || (v.spec.tags ?? none).includes(q.tag)) &&
|
|
222
|
+
(q.writes === undefined || v.patch.writes.includes(q.writes)))
|
|
223
|
+
found.push({ order: v.id, t: markOf(m, v, mark) });
|
|
176
224
|
if (q.writes === undefined && (anywhere || self.owner === null))
|
|
177
225
|
for (const a of mix.announced)
|
|
178
226
|
if (a.score === score &&
|
|
227
|
+
(score !== undefined || a.slot === mix.slot) &&
|
|
179
228
|
(q.name === undefined || a.name === q.name) &&
|
|
180
229
|
(q.tag === undefined || a.tags.includes(q.tag)))
|
|
181
230
|
found.push({ order: a.order, t: Number.isNaN(a.at) ? undefined : mixAt(mix, a.at) });
|
package/dist/project.d.ts
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
|
-
import
|
|
2
|
-
import type {
|
|
3
|
-
/**
|
|
4
|
-
|
|
1
|
+
import { Transport } from './transport.js';
|
|
2
|
+
import type { TransportProjection } from './types.js';
|
|
3
|
+
/**
|
|
4
|
+
* Every mix on a transport read at mix time `t`, through copies of them on a transport of their
|
|
5
|
+
* own, moved together so an anchor across mixes answers as it would live.
|
|
6
|
+
*/
|
|
7
|
+
export declare function projectAll(transport: Transport, t: number): TransportProjection;
|