@msb235/blits 0.7.1 → 0.8.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 +180 -0
- package/README.md +22 -4
- package/dist/book.js +4 -4
- package/dist/chain.d.ts +1 -1
- package/dist/chain.js +57 -54
- package/dist/channels.d.ts +26 -5
- package/dist/channels.js +12 -8
- package/dist/clock.d.ts +8 -1
- package/dist/clock.js +14 -3
- package/dist/clone.d.ts +3 -0
- package/dist/clone.js +16 -1
- package/dist/closed.d.ts +1 -1
- package/dist/closed.js +3 -2
- package/dist/color.d.ts +0 -11
- package/dist/color.js +4 -24
- package/dist/columns.js +0 -1
- package/dist/copyout.d.ts +6 -0
- package/dist/copyout.js +38 -0
- package/dist/crowd.d.ts +7 -5
- package/dist/crowd.js +32 -18
- package/dist/cue.d.ts +3 -1
- package/dist/cue.js +31 -20
- package/dist/departed.d.ts +34 -0
- package/dist/departed.js +73 -0
- package/dist/easing.d.ts +5 -0
- package/dist/easing.js +31 -2
- package/dist/everyone.d.ts +56 -0
- package/dist/everyone.js +221 -0
- package/dist/fade.d.ts +8 -1
- package/dist/fade.js +34 -6
- package/dist/fill.d.ts +22 -13
- package/dist/fill.js +104 -41
- package/dist/fit.js +2 -1
- package/dist/fold.d.ts +37 -0
- package/dist/fold.js +213 -28
- package/dist/gather.d.ts +15 -9
- package/dist/gather.js +60 -25
- package/dist/gone.js +10 -0
- package/dist/handle.d.ts +16 -5
- package/dist/handle.js +56 -6
- package/dist/held.d.ts +8 -5
- package/dist/held.js +56 -58
- package/dist/history.d.ts +17 -13
- package/dist/history.js +47 -28
- package/dist/hosts.js +55 -8
- package/dist/index.d.ts +9 -4
- package/dist/index.js +5 -2
- package/dist/keyfill.d.ts +7 -0
- package/dist/keyfill.js +124 -0
- package/dist/keyrows.d.ts +1 -0
- package/dist/keyrows.js +1 -1
- package/dist/lane.d.ts +32 -2
- package/dist/lane.js +5 -0
- package/dist/lanes.d.ts +32 -8
- package/dist/lanes.js +69 -37
- package/dist/leavings.d.ts +26 -0
- package/dist/leavings.js +70 -0
- package/dist/meet.js +14 -8
- package/dist/mixer.d.ts +39 -7
- package/dist/mixer.js +50 -10
- package/dist/motion.d.ts +2 -0
- package/dist/motion.js +4 -1
- package/dist/motions.d.ts +22 -15
- package/dist/motions.js +40 -34
- package/dist/move.js +23 -16
- package/dist/moved.d.ts +11 -0
- package/dist/moved.js +29 -0
- package/dist/origin.d.ts +19 -0
- package/dist/origin.js +68 -0
- package/dist/owed.d.ts +4 -4
- package/dist/owed.js +8 -8
- package/dist/owner.d.ts +1 -1
- package/dist/owner.js +2 -2
- package/dist/pace.d.ts +13 -5
- package/dist/pace.js +21 -8
- package/dist/pack.d.ts +3 -1
- package/dist/pack.js +15 -24
- package/dist/paging.d.ts +8 -2
- package/dist/paging.js +44 -4
- package/dist/patch.d.ts +14 -9
- package/dist/patch.js +83 -21
- package/dist/place.js +18 -3
- package/dist/project.d.ts +12 -1
- package/dist/project.js +101 -83
- package/dist/pull.d.ts +2 -0
- package/dist/pull.js +8 -0
- package/dist/qualify.js +10 -2
- package/dist/reading.d.ts +5 -0
- package/dist/reading.js +5 -0
- package/dist/record.d.ts +9 -0
- package/dist/record.js +42 -0
- package/dist/relink.d.ts +11 -5
- package/dist/relink.js +7 -5
- package/dist/restored.d.ts +41 -0
- package/dist/restored.js +91 -0
- package/dist/revive.d.ts +10 -3
- package/dist/revive.js +31 -23
- package/dist/roster.d.ts +11 -0
- package/dist/roster.js +46 -0
- package/dist/rotation.d.ts +29 -0
- package/dist/rotation.js +91 -0
- package/dist/rows.d.ts +5 -5
- package/dist/rows.js +5 -5
- package/dist/sample.d.ts +3 -2
- package/dist/sample.js +32 -15
- package/dist/seek.js +97 -114
- package/dist/shared.d.ts +6 -0
- package/dist/shared.js +15 -0
- package/dist/signals.d.ts +24 -3
- package/dist/signals.js +50 -11
- package/dist/spans.js +7 -0
- package/dist/stack.d.ts +11 -0
- package/dist/stack.js +47 -0
- package/dist/standing.d.ts +10 -0
- package/dist/standing.js +79 -0
- package/dist/store.d.ts +3 -3
- package/dist/store.js +3 -3
- package/dist/strays.js +3 -0
- package/dist/tape.d.ts +8 -5
- package/dist/tape.js +21 -13
- package/dist/testing.d.ts +35 -0
- package/dist/testing.js +116 -0
- package/dist/touch.d.ts +6 -0
- package/dist/touch.js +28 -0
- package/dist/transport.d.ts +49 -6
- package/dist/transport.js +89 -19
- package/dist/turns.d.ts +18 -0
- package/dist/turns.js +72 -0
- package/dist/tweened.js +1 -1
- package/dist/types/channel.d.ts +46 -0
- package/dist/types/channel.js +1 -0
- package/dist/types/history.d.ts +139 -0
- package/dist/types/history.js +1 -0
- package/dist/types/mix.d.ts +336 -0
- package/dist/types/mix.js +1 -0
- package/dist/types/patch.d.ts +187 -0
- package/dist/types/patch.js +1 -0
- package/dist/types/place.d.ts +157 -0
- package/dist/types/place.js +1 -0
- package/dist/types/transport.d.ts +71 -0
- package/dist/types/transport.js +1 -0
- package/dist/types/voice.d.ts +323 -0
- package/dist/types/voice.js +1 -0
- package/dist/types.d.ts +7 -1162
- package/dist/unpage.d.ts +2 -2
- package/dist/unpage.js +53 -25
- package/dist/voice.d.ts +90 -22
- package/dist/voice.js +105 -43
- package/dist/wave.d.ts +42 -0
- package/dist/wave.js +62 -0
- package/dist/weigh.js +1 -1
- package/docs/schema.html +42 -36
- package/package.json +8 -3
package/dist/handle.js
CHANGED
|
@@ -1,4 +1,16 @@
|
|
|
1
1
|
import { retime } from './clock.js';
|
|
2
|
+
import { motionOf } from './motion.js';
|
|
3
|
+
import { leave } from './origin.js';
|
|
4
|
+
/** Refuses a voice rate no clock can run at. */
|
|
5
|
+
export function finiteRate(rate) {
|
|
6
|
+
if (!Number.isFinite(rate))
|
|
7
|
+
throw new RangeError(`blits: a voice's rate is a finite number, not ${rate}`);
|
|
8
|
+
}
|
|
9
|
+
/** Refuses a voice weight that is no number: NaN would fold into every channel the voice writes. */
|
|
10
|
+
export function plainWeight(weight) {
|
|
11
|
+
if (Number.isNaN(weight))
|
|
12
|
+
throw new RangeError('blits: a voice’s weight is a number, not NaN');
|
|
13
|
+
}
|
|
2
14
|
/**
|
|
3
15
|
* A voice's handle: the host's only way to control a voice once cued. A voice a history store
|
|
4
16
|
* pages out is let go of here too, so the handle answers as a gone voice's does, and a seek that
|
|
@@ -49,9 +61,11 @@ export class VoiceHandle {
|
|
|
49
61
|
}
|
|
50
62
|
// A write to a paged voice changes nothing: it had left when the host made it, as it has on replay.
|
|
51
63
|
set weight(w) {
|
|
64
|
+
plainWeight(w);
|
|
52
65
|
const voice = this.#voice;
|
|
53
66
|
if (voice === null)
|
|
54
67
|
return;
|
|
68
|
+
voice.written = true;
|
|
55
69
|
voice.weight = w;
|
|
56
70
|
this.#host.changed(voice);
|
|
57
71
|
this.#host.record('weight', () => {
|
|
@@ -66,28 +80,40 @@ export class VoiceHandle {
|
|
|
66
80
|
this.ramp(r, 0);
|
|
67
81
|
}
|
|
68
82
|
ramp(r, over) {
|
|
83
|
+
finiteRate(r);
|
|
69
84
|
const voice = this.#voice;
|
|
70
85
|
if (voice === null)
|
|
71
86
|
return;
|
|
72
|
-
|
|
87
|
+
voice.written = true;
|
|
88
|
+
const now = this.#host.nowFor(voice);
|
|
89
|
+
// A pending voice's clock reads 0 at its start, and a rate taken from now would cross 0 sooner:
|
|
90
|
+
// a subject it freezes before would play, and step, ahead of a voice that has not started.
|
|
91
|
+
const u = voice.state === 'pending' && voice.start > now && r > 0 ? voice.start : now;
|
|
92
|
+
leave(voice, u);
|
|
93
|
+
retime(voice, u, r, over);
|
|
73
94
|
this.#host.changed(voice);
|
|
74
95
|
this.#host.record('rate', () => this.ramp(r, over));
|
|
75
96
|
}
|
|
76
|
-
seek(elapsed) {
|
|
97
|
+
seek(elapsed, opts) {
|
|
77
98
|
const voice = this.#voice;
|
|
78
99
|
if (voice === null)
|
|
79
|
-
return;
|
|
80
|
-
voice.
|
|
100
|
+
return 'exact';
|
|
101
|
+
voice.written = true;
|
|
102
|
+
const u = this.#host.nowFor(voice);
|
|
103
|
+
leave(voice, u);
|
|
104
|
+
voice.rebase(u);
|
|
81
105
|
voice.anchorElapsed = elapsed;
|
|
82
106
|
voice.seeks++;
|
|
83
|
-
this.#host.sought(voice);
|
|
107
|
+
const doubt = this.#host.sought(voice, opts?.state !== 'keep');
|
|
84
108
|
this.#host.changed(voice);
|
|
85
|
-
this.#host.record('seek', () => this.seek(elapsed));
|
|
109
|
+
this.#host.record('seek', () => this.seek(elapsed, opts));
|
|
110
|
+
return doubt;
|
|
86
111
|
}
|
|
87
112
|
fade(opts) {
|
|
88
113
|
const voice = this.#voice;
|
|
89
114
|
if (voice === null)
|
|
90
115
|
return;
|
|
116
|
+
voice.written = true;
|
|
91
117
|
this.#host.fade(voice, opts);
|
|
92
118
|
this.#host.record('fade', () => this.fade(opts));
|
|
93
119
|
}
|
|
@@ -95,6 +121,7 @@ export class VoiceHandle {
|
|
|
95
121
|
const voice = this.#voice;
|
|
96
122
|
if (voice === null)
|
|
97
123
|
return;
|
|
124
|
+
voice.written = true;
|
|
98
125
|
this.#host.rise(voice, opts);
|
|
99
126
|
this.#host.record('rise', () => this.rise(opts));
|
|
100
127
|
}
|
|
@@ -102,4 +129,27 @@ export class VoiceHandle {
|
|
|
102
129
|
const voice = this.#voice;
|
|
103
130
|
return voice === null ? 0 : this.#host.weightOf(voice, subject);
|
|
104
131
|
}
|
|
132
|
+
// The patch records each of these itself, so a seek plays them again without the handle.
|
|
133
|
+
to(subject, target, at) {
|
|
134
|
+
const voice = this.#voice;
|
|
135
|
+
if (voice !== null)
|
|
136
|
+
moves(voice.patch, 'to', 'a spring or a tween').to(subject, target, at);
|
|
137
|
+
}
|
|
138
|
+
push(subject, velocity, at) {
|
|
139
|
+
const voice = this.#voice;
|
|
140
|
+
if (voice !== null)
|
|
141
|
+
moves(voice.patch, 'push', 'a spring or a glide').push(subject, velocity, at);
|
|
142
|
+
}
|
|
143
|
+
read(subject, at) {
|
|
144
|
+
const voice = this.#voice;
|
|
145
|
+
if (voice === null || motionOf(voice.patch) === undefined)
|
|
146
|
+
return undefined;
|
|
147
|
+
return voice.patch.read(subject, at);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
/** A voice's patch as a motion that takes `verb`, or a throw naming the kinds that do. */
|
|
151
|
+
function moves(patch, verb, kinds) {
|
|
152
|
+
if (motionOf(patch) === undefined || typeof patch[verb] !== 'function')
|
|
153
|
+
throw new Error(`blits: ${verb} needs a voice playing ${kinds}`);
|
|
154
|
+
return patch;
|
|
105
155
|
}
|
package/dist/held.d.ts
CHANGED
|
@@ -3,12 +3,15 @@ import type { Subject, Voice } from './voice.js';
|
|
|
3
3
|
/** The record a voice gives every subject it does not reach. */
|
|
4
4
|
export declare function unreachedOf<I, O>(this: Mixer<I, O>, voice: Voice<I, O>, now: number): Subject<unknown>;
|
|
5
5
|
/** What this voice holds for this subject, made on first sight with `target` and `stagger` asked once. */
|
|
6
|
-
export declare function held<I, O>(this: Mixer<I, O>, voice: Voice<I, O>, subject: I,
|
|
6
|
+
export declare function held<I, O>(this: Mixer<I, O>, voice: Voice<I, O>, subject: I, slot: number): Subject<unknown>;
|
|
7
7
|
/**
|
|
8
|
-
*
|
|
9
|
-
*
|
|
8
|
+
* The mix time the voice clock first read `delay`, or will by its controls now. A stateful voice
|
|
9
|
+
* counts from its last seek that rebuilt state, as its records do.
|
|
10
10
|
*/
|
|
11
11
|
export declare function sinceOf<I, O>(this: Mixer<I, O>, voice: Voice<I, O>, delay: number): number;
|
|
12
12
|
export declare function shownOf<I, O>(this: Mixer<I, O>, voice: Voice<I, O>, since: number): number;
|
|
13
|
-
/**
|
|
14
|
-
|
|
13
|
+
/**
|
|
14
|
+
* A voice's start or clock moved, or it went live at `now`: the records whose `since` was still
|
|
15
|
+
* ahead count from where its clock now puts them.
|
|
16
|
+
*/
|
|
17
|
+
export declare function reorigin<I, O>(mix: Mixer<I, O>, voice: Voice<I, O>, now: number): void;
|
package/dist/held.js
CHANGED
|
@@ -1,41 +1,32 @@
|
|
|
1
1
|
import { schedule } from './due.js';
|
|
2
|
-
import {
|
|
2
|
+
import { everyoneOf, inherit, sighted } from './everyone.js';
|
|
3
|
+
import { originOf } from './origin.js';
|
|
4
|
+
import { record } from './record.js';
|
|
3
5
|
import { unreach, unreached } from './unreached.js';
|
|
4
6
|
/** The record a voice gives every subject it does not reach. */
|
|
5
7
|
export function unreachedOf(voice, now) {
|
|
6
8
|
const since = this.sinceOf(voice, 0);
|
|
7
|
-
const none =
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
weight: 0,
|
|
13
|
-
rested: false,
|
|
14
|
-
bands: null,
|
|
15
|
-
state: undefined,
|
|
16
|
-
stepped: now,
|
|
17
|
-
ticks: 0,
|
|
18
|
-
probed: Number.NaN,
|
|
19
|
-
delta: null,
|
|
20
|
-
phase: 0,
|
|
21
|
-
seeks: 0,
|
|
22
|
-
kept: null,
|
|
23
|
-
voice,
|
|
24
|
-
next: null,
|
|
25
|
-
version: Number.NaN,
|
|
26
|
-
loci: null,
|
|
27
|
-
slot: -1,
|
|
28
|
-
};
|
|
9
|
+
const none = record(voice, false, undefined);
|
|
10
|
+
none.since = since;
|
|
11
|
+
none.shown = this.shownOf(voice, since);
|
|
12
|
+
none.stepped = now;
|
|
13
|
+
none.rebuilt = voice.rebuilds;
|
|
29
14
|
voice.unreached = none;
|
|
30
15
|
return none;
|
|
31
16
|
}
|
|
32
17
|
/** What this voice holds for this subject, made on first sight with `target` and `stagger` asked once. */
|
|
33
|
-
export function held(voice, subject,
|
|
18
|
+
export function held(voice, subject, slot) {
|
|
19
|
+
if (voice.sharing) {
|
|
20
|
+
const all = everyoneOf(this, voice);
|
|
21
|
+
if (all !== null)
|
|
22
|
+
return all;
|
|
23
|
+
}
|
|
34
24
|
if (unreached(voice, slot))
|
|
35
25
|
return voice.unreached;
|
|
36
26
|
let held = voice.subjects.get(subject);
|
|
37
27
|
if (held !== undefined)
|
|
38
28
|
return held;
|
|
29
|
+
const now = this.now;
|
|
39
30
|
const reaches = this.aims(voice, subject);
|
|
40
31
|
if (!reaches) {
|
|
41
32
|
// Nothing is kept for a subject the voice does not reach, so one record stands for them all:
|
|
@@ -55,38 +46,36 @@ export function held(voice, subject, now, slot) {
|
|
|
55
46
|
}
|
|
56
47
|
const delay = voice.spec.stagger ? voice.spec.stagger(subject) : 0;
|
|
57
48
|
const since = this.sinceOf(voice, delay);
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
bands: null,
|
|
66
|
-
state: voice.patch.state ? voice.patch.state(subject) : undefined,
|
|
67
|
-
stepped: this.backward && since < now ? since : now,
|
|
68
|
-
ticks: 0,
|
|
69
|
-
probed: Number.NaN,
|
|
70
|
-
delta: null,
|
|
71
|
-
phase: 0,
|
|
72
|
-
seeks: 0,
|
|
73
|
-
kept: null,
|
|
74
|
-
voice,
|
|
75
|
-
next: null,
|
|
76
|
-
version: Number.NaN,
|
|
77
|
-
loci: null,
|
|
78
|
-
slot: -1,
|
|
79
|
-
};
|
|
49
|
+
const shown = this.shownOf(voice, since);
|
|
50
|
+
held = record(voice, true, voice.patch.state ? voice.patch.state(subject) : undefined);
|
|
51
|
+
held.delay = delay;
|
|
52
|
+
held.since = since;
|
|
53
|
+
held.shown = shown;
|
|
54
|
+
held.stepped = since < now ? since : now;
|
|
55
|
+
held.rebuilt = voice.rebuilds;
|
|
80
56
|
if (this.projecting) {
|
|
81
57
|
held.from = held.stepped;
|
|
82
58
|
held.unknown = this.backward && voice.spec.from === 'current';
|
|
83
59
|
}
|
|
84
60
|
voice.subjects.set(subject, held);
|
|
85
|
-
if (
|
|
61
|
+
if (!(since <= now)) {
|
|
86
62
|
voice.early ??= [];
|
|
87
|
-
voice.early
|
|
63
|
+
const early = voice.early;
|
|
64
|
+
// Prunes at each doubling, so records whose origin has passed are not kept to the voice's end.
|
|
65
|
+
if (early.length >= 64 && (early.length & (early.length - 1)) === 0) {
|
|
66
|
+
let kept = 0;
|
|
67
|
+
for (const h of early)
|
|
68
|
+
if (!(h.since <= now))
|
|
69
|
+
early[kept++] = h;
|
|
70
|
+
early.length = kept;
|
|
71
|
+
}
|
|
72
|
+
early.push(held);
|
|
88
73
|
}
|
|
89
|
-
voice.
|
|
74
|
+
// A subject a voice saw while it shared one record was counted then.
|
|
75
|
+
if (sighted(voice, slot))
|
|
76
|
+
inherit(voice, held, slot);
|
|
77
|
+
else
|
|
78
|
+
voice.seen++;
|
|
90
79
|
if (delay > voice.latest) {
|
|
91
80
|
voice.latest = delay;
|
|
92
81
|
// A later end can take a frozen voice live again.
|
|
@@ -96,26 +85,35 @@ export function held(voice, subject, now, slot) {
|
|
|
96
85
|
return held;
|
|
97
86
|
}
|
|
98
87
|
/**
|
|
99
|
-
*
|
|
100
|
-
*
|
|
88
|
+
* The mix time the voice clock first read `delay`, or will by its controls now. A stateful voice
|
|
89
|
+
* counts from its last seek that rebuilt state, as its records do.
|
|
101
90
|
*/
|
|
102
91
|
export function sinceOf(voice, delay) {
|
|
103
|
-
const
|
|
104
|
-
|
|
105
|
-
: voice.start + delay;
|
|
106
|
-
return voice.owner === null ? t : mixTime(voice.owner, t);
|
|
92
|
+
const p = voice.patch;
|
|
93
|
+
return originOf(voice, delay, p.state !== undefined || p.step !== undefined || voice.keeping);
|
|
107
94
|
}
|
|
108
95
|
export function shownOf(voice, since) {
|
|
109
96
|
return voice.freezesBefore && voice.opened < since ? voice.opened : since;
|
|
110
97
|
}
|
|
111
|
-
/**
|
|
112
|
-
|
|
98
|
+
/**
|
|
99
|
+
* A voice's start or clock moved, or it went live at `now`: the records whose `since` was still
|
|
100
|
+
* ahead count from where its clock now puts them.
|
|
101
|
+
*/
|
|
102
|
+
export function reorigin(mix, voice, now) {
|
|
113
103
|
const early = voice.early;
|
|
114
104
|
if (early === null)
|
|
115
105
|
return;
|
|
116
|
-
|
|
106
|
+
let kept = 0;
|
|
117
107
|
for (const held of early) {
|
|
118
108
|
held.since = mix.sinceOf(voice, held.delay);
|
|
119
109
|
held.shown = mix.shownOf(voice, held.since);
|
|
110
|
+
if (held.ticks === 0 && held.stepped > held.since)
|
|
111
|
+
held.stepped = held.since;
|
|
112
|
+
if (!(held.since <= now))
|
|
113
|
+
early[kept++] = held;
|
|
120
114
|
}
|
|
115
|
+
early.length = kept;
|
|
116
|
+
if (kept === 0)
|
|
117
|
+
voice.early = null;
|
|
118
|
+
mix.lanes?.reshown(voice.id);
|
|
121
119
|
}
|
package/dist/history.d.ts
CHANGED
|
@@ -1,13 +1,20 @@
|
|
|
1
1
|
import type { Mixer } from './mixer.js';
|
|
2
|
+
import { type Cut } from './transport.js';
|
|
2
3
|
import { type Subject, type Voice } from './voice.js';
|
|
3
|
-
/**
|
|
4
|
-
* The last entry taken before `t`, or at it where `inclusive` says so, as it does for a change a
|
|
5
|
-
* sync made. A host's change between frames is taken strictly: it shows from the next frame on, as
|
|
6
|
-
* it did live.
|
|
7
|
-
*/
|
|
4
|
+
/** The last entry taken before `t`, or at it where `inclusive` says so. */
|
|
8
5
|
export declare function last<T extends {
|
|
9
6
|
at: number;
|
|
10
7
|
}>(list: readonly T[], t: number, inclusive: boolean | ((e: T) => boolean)): T | undefined;
|
|
8
|
+
/**
|
|
9
|
+
* The last entry of a list kept in the order it was made that falls inside `cut`, an entry made
|
|
10
|
+
* before its frame was read counting as `early`.
|
|
11
|
+
*/
|
|
12
|
+
export declare function lastWithin<T extends {
|
|
13
|
+
seq: number;
|
|
14
|
+
sync?: boolean;
|
|
15
|
+
}>(list: readonly T[], cut: Cut): T | undefined;
|
|
16
|
+
/** Deep equality over plain data, for telling whether a host field changed. */
|
|
17
|
+
export declare function same(a: unknown, b: unknown): boolean;
|
|
11
18
|
/** A voice's elapsed at an earlier mix time, by the controls it had then. */
|
|
12
19
|
export declare function elapsedThen<I, O>(mix: Mixer<I, O>, voice: Voice<I, O>, t: number): number;
|
|
13
20
|
/** Records a change to a voice's controls under `history`, and lets go of what it no longer reaches. */
|
|
@@ -25,11 +32,8 @@ export declare function remember<I, O>(mix: Mixer<I, O>, voice: Voice<I, O>, sub
|
|
|
25
32
|
* for a seek or a read back to before then; without history, keeps nothing.
|
|
26
33
|
*/
|
|
27
34
|
export declare function leave<I, O>(mix: Mixer<I, O>, voice: Voice<I, O>, subject: I, held: Subject<unknown> | undefined): void;
|
|
28
|
-
/**
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
}[] | null, subject: I, t: number): Subject<unknown> | undefined;
|
|
34
|
-
/** The mix time a motion patch keeps a released subject from, and how far back, under history. */
|
|
35
|
-
export declare function releasing<I, O>(mix: Mixer<I, O>): [at?: number, reach?: number];
|
|
35
|
+
/**
|
|
36
|
+
* The mix time a motion patch keeps a released subject from, how far back, and the frame, under
|
|
37
|
+
* history.
|
|
38
|
+
*/
|
|
39
|
+
export declare function releasing<I, O>(mix: Mixer<I, O>): [at?: number, reach?: number, seq?: number];
|
package/dist/history.js
CHANGED
|
@@ -1,15 +1,13 @@
|
|
|
1
1
|
import { elapsedWith } from './clock.js';
|
|
2
2
|
import { clone } from './clone.js';
|
|
3
3
|
import { schedule } from './due.js';
|
|
4
|
+
import { Leavings } from './leavings.js';
|
|
4
5
|
import { packHeld } from './pack.js';
|
|
5
6
|
import { pageOut } from './paging.js';
|
|
6
7
|
import { scoreTouched } from './scored.js';
|
|
8
|
+
import { within } from './transport.js';
|
|
7
9
|
import { none } from './voice.js';
|
|
8
|
-
/**
|
|
9
|
-
* The last entry taken before `t`, or at it where `inclusive` says so, as it does for a change a
|
|
10
|
-
* sync made. A host's change between frames is taken strictly: it shows from the next frame on, as
|
|
11
|
-
* it did live.
|
|
12
|
-
*/
|
|
10
|
+
/** The last entry taken before `t`, or at it where `inclusive` says so. */
|
|
13
11
|
export function last(list, t, inclusive) {
|
|
14
12
|
let lo = 0;
|
|
15
13
|
let hi = list.length - 1;
|
|
@@ -26,8 +24,28 @@ export function last(list, t, inclusive) {
|
|
|
26
24
|
}
|
|
27
25
|
return found;
|
|
28
26
|
}
|
|
27
|
+
/**
|
|
28
|
+
* The last entry of a list kept in the order it was made that falls inside `cut`, an entry made
|
|
29
|
+
* before its frame was read counting as `early`.
|
|
30
|
+
*/
|
|
31
|
+
export function lastWithin(list, cut) {
|
|
32
|
+
let lo = 0;
|
|
33
|
+
let hi = list.length - 1;
|
|
34
|
+
let found;
|
|
35
|
+
while (lo <= hi) {
|
|
36
|
+
const mid = (lo + hi) >> 1;
|
|
37
|
+
const e = list[mid];
|
|
38
|
+
if (within(cut, e.seq, e.sync)) {
|
|
39
|
+
found = e;
|
|
40
|
+
lo = mid + 1;
|
|
41
|
+
}
|
|
42
|
+
else
|
|
43
|
+
hi = mid - 1;
|
|
44
|
+
}
|
|
45
|
+
return found;
|
|
46
|
+
}
|
|
29
47
|
/** Deep equality over plain data, for telling whether a host field changed. */
|
|
30
|
-
function same(a, b) {
|
|
48
|
+
export function same(a, b) {
|
|
31
49
|
if (a === b)
|
|
32
50
|
return true;
|
|
33
51
|
if (typeof a !== 'object' || typeof b !== 'object' || a === null || b === null)
|
|
@@ -59,7 +77,7 @@ export function noted(mix, voice) {
|
|
|
59
77
|
const log = voice.log;
|
|
60
78
|
if (log === null)
|
|
61
79
|
return;
|
|
62
|
-
voice.note(Number.isNaN(mix.now) ? Number.NEGATIVE_INFINITY : mix.now, mix.moving);
|
|
80
|
+
voice.note(Number.isNaN(mix.now) ? Number.NEGATIVE_INFINITY : mix.now, mix.transport.seq, mix.moving || !mix.looked);
|
|
63
81
|
const reach = mix.now - mix.opts.history.ms;
|
|
64
82
|
let drop = 0;
|
|
65
83
|
while (drop + 1 < log.length && log[drop + 1].at <= reach)
|
|
@@ -108,7 +126,8 @@ export function copyHeld(voice, h) {
|
|
|
108
126
|
export function recordHost(mix, now) {
|
|
109
127
|
const log = mix.hostLog;
|
|
110
128
|
const prev = log[log.length - 1];
|
|
111
|
-
|
|
129
|
+
const seq = mix.transport.seq;
|
|
130
|
+
if (prev !== undefined && prev.seq === seq)
|
|
112
131
|
return;
|
|
113
132
|
const host = mix.opts.host;
|
|
114
133
|
const fields = {};
|
|
@@ -118,7 +137,7 @@ export function recordHost(mix, now) {
|
|
|
118
137
|
fields[f] = clone(host[f]);
|
|
119
138
|
if (prev !== undefined && same(prev.fields, fields))
|
|
120
139
|
return;
|
|
121
|
-
log.push({ at: now, fields });
|
|
140
|
+
log.push({ at: now, seq, fields });
|
|
122
141
|
const reach = now - mix.opts.history.ms;
|
|
123
142
|
pageOut(mix, 'host', undefined, undefined, older(log, reach), (e) => e.fields, reach);
|
|
124
143
|
}
|
|
@@ -135,7 +154,7 @@ export function record(mix, voice, subject, held, value) {
|
|
|
135
154
|
prev.value = value;
|
|
136
155
|
return;
|
|
137
156
|
}
|
|
138
|
-
inputs.push({ at: mix.now, value });
|
|
157
|
+
inputs.push({ at: mix.now, seq: mix.transport.seq, value });
|
|
139
158
|
const reach = mix.now - history.ms;
|
|
140
159
|
const out = older(inputs, reach);
|
|
141
160
|
if (out.length > 0)
|
|
@@ -155,7 +174,7 @@ export function remember(mix, voice, subject, held) {
|
|
|
155
174
|
const prev = snaps[snaps.length - 1];
|
|
156
175
|
if (prev !== undefined && mix.now - prev.at < (history.every ?? 200))
|
|
157
176
|
return;
|
|
158
|
-
snaps.push({ at: mix.now, held: copyHeld(voice, held) });
|
|
177
|
+
snaps.push({ at: mix.now, seq: mix.transport.seq, held: copyHeld(voice, held) });
|
|
159
178
|
const reach = mix.now - history.ms;
|
|
160
179
|
const out = older(snaps, reach);
|
|
161
180
|
if (out.length > 0)
|
|
@@ -170,27 +189,27 @@ export function leave(mix, voice, subject, held) {
|
|
|
170
189
|
if (history === undefined || held === undefined)
|
|
171
190
|
return;
|
|
172
191
|
const reach = mix.now - history.ms;
|
|
173
|
-
const left =
|
|
174
|
-
for (const e of voice.left ?? [])
|
|
175
|
-
if (e.at >= reach)
|
|
176
|
-
left.push(e);
|
|
177
|
-
else if (mix.keys !== null)
|
|
178
|
-
pageOut(mix, 'left', voice.id, mix.keys.key(e.subject), [e], (x) => packHeld(voice, x.held), reach);
|
|
179
|
-
left.push({ subject, at: Number.isNaN(mix.now) ? Number.NEGATIVE_INFINITY : mix.now, held });
|
|
192
|
+
const left = voice.left ?? new Leavings();
|
|
180
193
|
voice.left = left;
|
|
194
|
+
const keys = mix.keys;
|
|
195
|
+
left.expire(reach, keys === null
|
|
196
|
+
? null
|
|
197
|
+
: (e) => pageOut(mix, 'left', voice.id, keys.key(e.subject), [e], (x) => [packHeld(voice, x.held), x.sync], reach));
|
|
198
|
+
left.push({
|
|
199
|
+
subject,
|
|
200
|
+
at: Number.isNaN(mix.now) ? Number.NEGATIVE_INFINITY : mix.now,
|
|
201
|
+
seq: mix.transport.seq,
|
|
202
|
+
sync: mix.syncing,
|
|
203
|
+
held,
|
|
204
|
+
});
|
|
181
205
|
}
|
|
182
|
-
/**
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
if (e.at > t && Object.is(e.subject, subject) && (found === undefined || e.at < found.at))
|
|
187
|
-
found = e;
|
|
188
|
-
return found?.held;
|
|
189
|
-
}
|
|
190
|
-
/** The mix time a motion patch keeps a released subject from, and how far back, under history. */
|
|
206
|
+
/**
|
|
207
|
+
* The mix time a motion patch keeps a released subject from, how far back, and the frame, under
|
|
208
|
+
* history.
|
|
209
|
+
*/
|
|
191
210
|
export function releasing(mix) {
|
|
192
211
|
const history = mix.opts.history;
|
|
193
212
|
if (history === undefined || Number.isNaN(mix.now))
|
|
194
213
|
return [];
|
|
195
|
-
return [mix.now, mix.now - history.ms];
|
|
214
|
+
return [mix.now, mix.now - history.ms, mix.transport.seq];
|
|
196
215
|
}
|
package/dist/hosts.js
CHANGED
|
@@ -2,15 +2,18 @@ import { Book } from './book.js';
|
|
|
2
2
|
import { numericOf } from './channels.js';
|
|
3
3
|
import { crowdable } from './crowd.js';
|
|
4
4
|
import { schedule } from './due.js';
|
|
5
|
+
import { unshare, unsight, weightAt } from './everyone.js';
|
|
5
6
|
import { beginFade, beginRise, fadeAt, fadeSubject } from './fade.js';
|
|
6
7
|
import { goneIndex } from './gone.js';
|
|
7
8
|
import { VoiceHandle } from './handle.js';
|
|
9
|
+
import { reorigin } from './held.js';
|
|
8
10
|
import { noted, remember } from './history.js';
|
|
9
11
|
import { hostOf, listed, markOf, readingAt } from './marks.js';
|
|
10
12
|
import { nextFrame } from './move.js';
|
|
11
13
|
import { descendants, ownWeight, signalled } from './owner.js';
|
|
12
14
|
import { localNow } from './place.js';
|
|
13
15
|
import { reading } from './reading.js';
|
|
16
|
+
import { heard } from './signals.js';
|
|
14
17
|
import { record } from './tape.js';
|
|
15
18
|
import { unreach } from './unreached.js';
|
|
16
19
|
export function book(mix, opts) {
|
|
@@ -52,12 +55,15 @@ export function laneHost(mix) {
|
|
|
52
55
|
for (let held = head; held !== null; held = held.next)
|
|
53
56
|
if (held.voice === voice)
|
|
54
57
|
return held;
|
|
55
|
-
return mix.held(voice, subject,
|
|
58
|
+
return mix.held(voice, subject, slot);
|
|
56
59
|
},
|
|
57
60
|
forgot: (slot) => {
|
|
58
61
|
// Only a voice over every subject keeps bits, and one gone may still be read back.
|
|
59
|
-
for (const voice of mix.general)
|
|
62
|
+
for (const voice of mix.general) {
|
|
60
63
|
unreach(voice, slot, false);
|
|
64
|
+
if (voice.sighted !== null)
|
|
65
|
+
unsight(voice, slot);
|
|
66
|
+
}
|
|
61
67
|
for (const voice of goneIndex(mix).general)
|
|
62
68
|
unreach(voice, slot, false);
|
|
63
69
|
},
|
|
@@ -110,8 +116,13 @@ export function fits(mix, voice) {
|
|
|
110
116
|
// A fill weighs a voice's owners once for every subject, which a signal on one would not be.
|
|
111
117
|
if (voice.owner !== null && signalled(voice))
|
|
112
118
|
return false;
|
|
113
|
-
//
|
|
114
|
-
|
|
119
|
+
// What can change between two probes without `Lanes.current` hearing of it keeps a voice off:
|
|
120
|
+
// an input signal that does not report its changes, one that history records per probe, and a
|
|
121
|
+
// host field.
|
|
122
|
+
const w = spec.weight;
|
|
123
|
+
if (typeof w === 'function' && w.input && (mix.opts.history?.inputs || !heard(w)))
|
|
124
|
+
return false;
|
|
125
|
+
if (patch.reads !== undefined && patch.reads.length > 0)
|
|
115
126
|
return false;
|
|
116
127
|
// A locus on lanes gathers keys and fn members; a motion member keeps it on the general path.
|
|
117
128
|
if (spec.locus !== undefined && voice.motion !== undefined)
|
|
@@ -126,17 +137,38 @@ export function fits(mix, voice) {
|
|
|
126
137
|
return false;
|
|
127
138
|
if (patch.state !== undefined || patch.step !== undefined)
|
|
128
139
|
return false;
|
|
129
|
-
if (mix.opts.history?.inputs && patch.reads !== undefined && patch.reads.length > 0)
|
|
130
|
-
return false;
|
|
131
140
|
const built = voice.built;
|
|
132
141
|
return (built === null ||
|
|
133
142
|
built.tracks.every((t, i) => t.lerp === undefined || t.lerp === voice.lerps[i]));
|
|
134
143
|
}
|
|
144
|
+
const RANK = { exact: 0, stepped: 1, held: 2 };
|
|
145
|
+
/** How sure a seek leaves a voice's state: kept is held; rebuilt is exact only on a fixed step. */
|
|
146
|
+
function seekDoubt(mix, voice, rebuild) {
|
|
147
|
+
const patch = voice.patch;
|
|
148
|
+
if (patch.state === undefined && patch.step === undefined && !voice.keeping)
|
|
149
|
+
return 'exact';
|
|
150
|
+
if (!rebuild)
|
|
151
|
+
return 'held';
|
|
152
|
+
const weight = voice.spec.weight;
|
|
153
|
+
if ((patch.reads !== undefined && patch.reads.length > 0) ||
|
|
154
|
+
(typeof weight === 'function' && weight.input))
|
|
155
|
+
return 'held';
|
|
156
|
+
if (voice.keeping)
|
|
157
|
+
return 'stepped';
|
|
158
|
+
if (patch.step === undefined)
|
|
159
|
+
return 'exact';
|
|
160
|
+
const tick = mix.opts.stepMs;
|
|
161
|
+
const fixed = tick !== undefined && tick > 0 && !mix.reducedNow && mix.opts.maxDt === undefined;
|
|
162
|
+
return fixed ? 'exact' : 'stepped';
|
|
163
|
+
}
|
|
135
164
|
/** A voice's patch kept state on a record: it is stateful from now on. */
|
|
136
165
|
export function stateful(mix, voice) {
|
|
137
166
|
if (voice.keeping)
|
|
138
167
|
return;
|
|
139
168
|
voice.keeping = true;
|
|
169
|
+
// The call that kept state was for the subject last sent to, whose record the state is on.
|
|
170
|
+
if (voice.sharing)
|
|
171
|
+
unshare(mix, voice, true, mix.sending.subject);
|
|
140
172
|
mix.lanes?.invalidate();
|
|
141
173
|
}
|
|
142
174
|
export function handle(mix, voice) {
|
|
@@ -153,16 +185,28 @@ function handleHost(mix) {
|
|
|
153
185
|
},
|
|
154
186
|
changed: (voice) => {
|
|
155
187
|
mix.frame = nextFrame();
|
|
188
|
+
reorigin(mix, voice, mix.now);
|
|
189
|
+
if (voice.holding !== null)
|
|
190
|
+
descendants(voice, (v) => reorigin(mix, v, mix.now));
|
|
156
191
|
noted(mix, voice);
|
|
157
192
|
mix.lanes?.refill();
|
|
158
193
|
},
|
|
159
194
|
// A delta read before an owner's seek, or a hit booked, is not taken as standing after it.
|
|
160
|
-
sought: (voice) => {
|
|
195
|
+
sought: (voice, rebuild) => {
|
|
196
|
+
if (rebuild)
|
|
197
|
+
voice.rebuilds++;
|
|
198
|
+
let doubt = seekDoubt(mix, voice, rebuild);
|
|
161
199
|
if (voice.holding !== null)
|
|
162
200
|
descendants(voice, (v) => {
|
|
163
201
|
v.seeks++;
|
|
202
|
+
if (rebuild)
|
|
203
|
+
v.rebuilds++;
|
|
204
|
+
const d = seekDoubt(mix, v, rebuild);
|
|
205
|
+
if (RANK[d] > RANK[doubt])
|
|
206
|
+
doubt = d;
|
|
164
207
|
schedule(mix, v);
|
|
165
208
|
});
|
|
209
|
+
return doubt;
|
|
166
210
|
},
|
|
167
211
|
fade: (voice, opts) => {
|
|
168
212
|
if (voice.holding !== null && opts !== undefined && ('subject' in opts || opts.at === 'rest'))
|
|
@@ -180,11 +224,14 @@ function handleHost(mix) {
|
|
|
180
224
|
return 0;
|
|
181
225
|
if (voice.holding !== null)
|
|
182
226
|
return ownWeight(voice, mix.now, mix.reducedNow, (o) => mix.ownerBase(o, subject));
|
|
227
|
+
const slot = mix.lanes === null ? -1 : (mix.chains.get(subject)?.slot ?? -1);
|
|
183
228
|
if (voice.laned && mix.lanes !== null) {
|
|
184
|
-
const w = mix.lanes.weightOf(voice.id,
|
|
229
|
+
const w = mix.lanes.weightOf(voice.id, slot);
|
|
185
230
|
if (w !== undefined)
|
|
186
231
|
return w;
|
|
187
232
|
}
|
|
233
|
+
if (voice.sharing)
|
|
234
|
+
return weightAt(voice, slot);
|
|
188
235
|
return voice.subjects.get(subject)?.weight ?? 0;
|
|
189
236
|
},
|
|
190
237
|
record: (label, again) => record(mix, label, again),
|
package/dist/index.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
export type { NumberOptions } from './channels.js';
|
|
1
|
+
export type { NumberOptions, Vec } from './channels.js';
|
|
2
2
|
export { kit, last, max, mul, sum, vec } from './channels.js';
|
|
3
3
|
export type { ColorOptions } from './color.js';
|
|
4
|
-
export { color, css,
|
|
4
|
+
export { color, css, mixHex, oklab, toHex } from './color.js';
|
|
5
5
|
export type { Claim, Fit, FitPlan, Order, SpanClaim, Strength } from './fit.js';
|
|
6
6
|
export { conclude, condense, lax, layout, overrun, pipe, plain, shed, } from './fit.js';
|
|
7
7
|
export { mix, mixer } from './mixer.js';
|
|
@@ -10,8 +10,13 @@ export { glide, spring, tween } from './motion.js';
|
|
|
10
10
|
export { HistoryMiss } from './paging.js';
|
|
11
11
|
export type { KeysOptions, PatchOptions } from './patch.js';
|
|
12
12
|
export { keys, patch } from './patch.js';
|
|
13
|
-
export {
|
|
13
|
+
export type { AngleOptions } from './rotation.js';
|
|
14
|
+
export { angle, quat } from './rotation.js';
|
|
15
|
+
export { gate, input, lag, level, peak, slew } from './signals.js';
|
|
16
|
+
export { fold } from './stack.js';
|
|
14
17
|
export type { Ticked, Ticker, TickerOptions } from './ticker.js';
|
|
15
18
|
export { ticker } from './ticker.js';
|
|
16
19
|
export { transport } from './transport.js';
|
|
17
|
-
export type { Anchor, BookedHit, Booker, BookOptions, Channel, Columns, Described, Doubt, Easing, Engine, FadeOptions, FadeSpec, FitResult, Handle, HistoryOptions, HistoryStore, Hit, Keyframe, Kit, Mark, Marked, Mix, MixOptions, MotionSpec, OwnerSpec, Paged, PagedStream, Patch, Placement, Projection, Query, Sent, Setting, Signal, SpanHandle, SpanHints, SpanSpec, Tape, TapeBranch, TapeMaker, TapeOp, Transport, TransportOptions, TransportProjection, VoiceSpec, } from './types.js';
|
|
20
|
+
export type { Anchor, BookedHit, Booker, BookOptions, Channel, Columns, Described, Doubt, Easing, Engine, FadeOptions, FadeSpec, FitResult, Handle, HistoryOptions, HistoryStore, Hit, Keyframe, Kit, Mark, Marked, Mix, MixOptions, MotionSpec, OwnerSpec, Paged, PagedStream, Patch, Placement, Projection, Query, SeekOptions, Sent, Setting, Signal, SpanHandle, SpanHints, SpanSpec, Tape, TapeBranch, TapeMaker, TapeOp, Transport, TransportOptions, TransportProjection, VoiceSpec, } from './types.js';
|
|
21
|
+
export type { WaveOptions, WaveShape } from './wave.js';
|
|
22
|
+
export { wave, waveAt } from './wave.js';
|
package/dist/index.js
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
export { kit, last, max, mul, sum, vec } from './channels.js';
|
|
2
|
-
export { color, css,
|
|
2
|
+
export { color, css, mixHex, oklab, toHex } from './color.js';
|
|
3
3
|
export { conclude, condense, lax, layout, overrun, pipe, plain, shed, } from './fit.js';
|
|
4
4
|
export { mix, mixer } from './mixer.js';
|
|
5
5
|
export { glide, spring, tween } from './motion.js';
|
|
6
6
|
export { HistoryMiss } from './paging.js';
|
|
7
7
|
export { keys, patch } from './patch.js';
|
|
8
|
-
export {
|
|
8
|
+
export { angle, quat } from './rotation.js';
|
|
9
|
+
export { gate, input, lag, level, peak, slew } from './signals.js';
|
|
10
|
+
export { fold } from './stack.js';
|
|
9
11
|
export { ticker } from './ticker.js';
|
|
10
12
|
export { transport } from './transport.js';
|
|
13
|
+
export { wave, waveAt } from './wave.js';
|