@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/cue.js
CHANGED
|
@@ -1,15 +1,58 @@
|
|
|
1
1
|
import { checkHits } from './book.js';
|
|
2
2
|
import { changed, index } from './chain.js';
|
|
3
3
|
import { schedule } from './due.js';
|
|
4
|
+
import { VoiceHandle } from './handle.js';
|
|
4
5
|
import { started } from './held.js';
|
|
5
6
|
import { handle } from './hosts.js';
|
|
6
7
|
import { motionOf } from './motion.js';
|
|
7
|
-
import { adopt, ownerReading } from './owner.js';
|
|
8
|
+
import { adopt, ownerPatch, ownerReading } from './owner.js';
|
|
8
9
|
import { durationOf } from './patch.js';
|
|
9
10
|
import { checkPlacement, localNow, mixAt, pin, place } from './place.js';
|
|
11
|
+
import { checkChild, refit } from './spans.js';
|
|
10
12
|
import { motionOwner } from './strays.js';
|
|
13
|
+
import { record } from './tape.js';
|
|
11
14
|
import { Voice } from './voice.js';
|
|
12
15
|
export function cue(mix, spec) {
|
|
16
|
+
const patch = spec.patch;
|
|
17
|
+
playable(mix, spec);
|
|
18
|
+
if (spec.from === 'current' && patch.form !== 'keys')
|
|
19
|
+
throw new Error("blits: from: 'current' needs a keys patch");
|
|
20
|
+
if (spec.subjects !== undefined && spec.target !== undefined)
|
|
21
|
+
throw new Error('blits: a voice takes target or subjects, not both');
|
|
22
|
+
const anchor = spec.anchor;
|
|
23
|
+
const owner = spec.owner === undefined ? null : ownerOf(mix, spec.owner);
|
|
24
|
+
if (anchor)
|
|
25
|
+
checkPlacement(mix, spec, anchor, owner);
|
|
26
|
+
if (spec.hits !== undefined)
|
|
27
|
+
checkHits(spec.hits, durationOf(patch));
|
|
28
|
+
const anchored = anchor !== undefined && (anchor.start !== undefined || anchor.in !== undefined);
|
|
29
|
+
// A voice an owner holds is placed on the owner's clock: ms from when it starts.
|
|
30
|
+
const local = owner === null ? mix.now : ownerReading(owner, mix.now);
|
|
31
|
+
const start = anchored || owner?.fitting
|
|
32
|
+
? Number.POSITIVE_INFINITY
|
|
33
|
+
: owner !== null
|
|
34
|
+
? (spec.start ?? (local > 0 ? local : 0))
|
|
35
|
+
: spec.start !== undefined
|
|
36
|
+
? mixAt(mix, spec.start - mix.offset)
|
|
37
|
+
: Number.isNaN(mix.now)
|
|
38
|
+
? 0
|
|
39
|
+
: mix.now;
|
|
40
|
+
const voice = new Voice(mix.nextId++, spec, patch, spec.fade ?? {}, local, start, mix.slotOf, mix.channels, mix.opts.host, mix.send, owner);
|
|
41
|
+
if (patch === ownerPatch && mix.fitting !== null)
|
|
42
|
+
voice.fitting = mix.fitting;
|
|
43
|
+
if (owner?.fitting)
|
|
44
|
+
checkChild(spec, voice);
|
|
45
|
+
if (mix.pace !== null && owner === null && spec.start !== undefined && voice.state === 'pending')
|
|
46
|
+
voice.pinned = spec.start - mix.offset;
|
|
47
|
+
enter(mix, voice);
|
|
48
|
+
const h = handle(mix, voice);
|
|
49
|
+
voice.handle = h;
|
|
50
|
+
// By its handle, so a voice a history store paged out and a seek revived is the one cued again.
|
|
51
|
+
record(mix, 'cue', () => recue(mix, VoiceHandle.voiceOf(h) ?? lost(h)));
|
|
52
|
+
return h;
|
|
53
|
+
}
|
|
54
|
+
/** Refuses a spec whose patch the mix cannot play, or whose state its history cannot keep. */
|
|
55
|
+
export function playable(mix, spec) {
|
|
13
56
|
const patch = spec.patch;
|
|
14
57
|
for (const channel of patch.writes) {
|
|
15
58
|
if (!(channel in mix.kit))
|
|
@@ -34,29 +77,30 @@ export function cue(mix, spec) {
|
|
|
34
77
|
if (typeof host !== 'object' || host === null || !(field in host))
|
|
35
78
|
throw new Error(`blits: the patch reads host.${field}, which this mix's host lacks`);
|
|
36
79
|
}
|
|
37
|
-
if (
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
throw new Error(
|
|
80
|
+
if (mix.transport.pager !== null &&
|
|
81
|
+
(patch.state !== undefined || patch.step !== undefined) &&
|
|
82
|
+
(patch.pack === undefined || patch.unpack === undefined))
|
|
83
|
+
throw new Error("blits: this mix's history has a store, which keeps a stateful patch's state as data; give the patch pack and unpack");
|
|
84
|
+
if (spec.as !== undefined && mix.transport.pager !== null && !mix.opts.history?.revive)
|
|
85
|
+
throw new Error('blits: a voice cued with as needs history.revive to rebuild it from');
|
|
86
|
+
}
|
|
87
|
+
/** A voice parked by a seek, cued again as the mix plays past its cue, on the handle it had. */
|
|
88
|
+
function recue(mix, voice) {
|
|
89
|
+
const owner = voice.owner;
|
|
90
|
+
if ((owner === null ? mix.now : ownerReading(owner, mix.now)) >= voice.start)
|
|
91
|
+
voice.state = 'live';
|
|
92
|
+
enter(mix, voice);
|
|
93
|
+
}
|
|
94
|
+
function lost(h) {
|
|
95
|
+
throw new Error(`blits: voice ${h.id} is paged out, and the seek that plays its cue again did not load it`);
|
|
96
|
+
}
|
|
97
|
+
/** Puts a voice just made, or parked by a seek, into the mix. */
|
|
98
|
+
function enter(mix, voice) {
|
|
99
|
+
const spec = voice.spec;
|
|
100
|
+
const owner = voice.owner;
|
|
41
101
|
const anchor = spec.anchor;
|
|
42
|
-
const owner = spec.owner === undefined ? null : ownerOf(mix, spec.owner);
|
|
43
|
-
if (anchor)
|
|
44
|
-
checkPlacement(mix, spec, anchor, owner);
|
|
45
|
-
if (spec.hits !== undefined)
|
|
46
|
-
checkHits(spec.hits, durationOf(patch));
|
|
47
102
|
const anchored = anchor !== undefined && (anchor.start !== undefined || anchor.in !== undefined);
|
|
48
|
-
|
|
49
|
-
const local = owner === null ? mix.now : ownerReading(owner, mix.now);
|
|
50
|
-
const start = anchored
|
|
51
|
-
? Number.POSITIVE_INFINITY
|
|
52
|
-
: owner !== null
|
|
53
|
-
? (spec.start ?? (local > 0 ? local : 0))
|
|
54
|
-
: spec.start !== undefined
|
|
55
|
-
? mixAt(mix, spec.start - mix.offset)
|
|
56
|
-
: Number.isNaN(mix.now)
|
|
57
|
-
? 0
|
|
58
|
-
: mix.now;
|
|
59
|
-
const voice = new Voice(mix.nextId++, spec, patch, spec.fade ?? {}, local, start, mix.slotOf, mix.channels, mix.opts.host, mix.send, owner);
|
|
103
|
+
const motion = voice.motion;
|
|
60
104
|
if (owner !== null)
|
|
61
105
|
adopt(owner, voice);
|
|
62
106
|
if (voice.holding !== null) {
|
|
@@ -68,15 +112,21 @@ export function cue(mix, spec) {
|
|
|
68
112
|
voice.opened = mix.now;
|
|
69
113
|
}
|
|
70
114
|
voice.placing = anchored;
|
|
71
|
-
|
|
72
|
-
|
|
115
|
+
// A start the host gave by its own clock, where the mix's rate puts it.
|
|
116
|
+
if (!Number.isNaN(voice.pinned) && voice.state === 'pending')
|
|
117
|
+
pin(mix, voice, voice.pinned);
|
|
73
118
|
if (motion !== undefined) {
|
|
74
119
|
mix.playing.set(motion, (mix.playing.get(motion) ?? 0) + 1);
|
|
75
120
|
mix.owner ??= motionOwner(new WeakRef(mix));
|
|
76
121
|
motion.owner = mix.owner;
|
|
77
122
|
motion.ownerId = voice.id;
|
|
78
123
|
}
|
|
79
|
-
|
|
124
|
+
// In id order, which a voice cued again after later ones keeps by going in at its place.
|
|
125
|
+
const cued = mix.cued;
|
|
126
|
+
let i = cued.length;
|
|
127
|
+
while (i > 0 && cued[i - 1].id > voice.id)
|
|
128
|
+
i--;
|
|
129
|
+
cued.splice(i, 0, voice);
|
|
80
130
|
index(mix, voice);
|
|
81
131
|
changed(mix, voice);
|
|
82
132
|
mix.stir();
|
|
@@ -95,14 +145,15 @@ export function cue(mix, spec) {
|
|
|
95
145
|
changed(mix, voice);
|
|
96
146
|
}
|
|
97
147
|
}
|
|
148
|
+
if (owner?.fitting)
|
|
149
|
+
refit(mix, owner);
|
|
98
150
|
// After placing, so the controls it starts with are where its anchors put it at the cue.
|
|
99
151
|
if (mix.opts.history) {
|
|
100
152
|
voice.log = [];
|
|
101
153
|
voice.note(Number.NEGATIVE_INFINITY);
|
|
154
|
+
voice.first ??= voice.log[0];
|
|
102
155
|
}
|
|
103
156
|
schedule(mix, voice);
|
|
104
|
-
voice.handle = handle(mix, voice);
|
|
105
|
-
return voice.handle;
|
|
106
157
|
}
|
|
107
158
|
/** The owner a handle names, refused where it is not one of this mix's or has left. */
|
|
108
159
|
function ownerOf(mix, handle) {
|
package/dist/due.js
CHANGED
|
@@ -15,6 +15,9 @@ function dueOf(mix, voice) {
|
|
|
15
15
|
if (voice.parts !== null)
|
|
16
16
|
for (const r of voice.parts.values())
|
|
17
17
|
due = Math.min(due, r.at + r.over);
|
|
18
|
+
const back = voice.back;
|
|
19
|
+
if (back !== null)
|
|
20
|
+
due = Math.min(due, back.at + (1 - back.from) * back.over);
|
|
18
21
|
if (voice.state === 'pending')
|
|
19
22
|
return Math.min(due, Number.isNaN(voice.start) ? due : voice.start);
|
|
20
23
|
if (voice.state === 'fading') {
|
|
@@ -26,10 +29,10 @@ function dueOf(mix, voice) {
|
|
|
26
29
|
due = Math.min(due, voice.outAt);
|
|
27
30
|
if (Number.isFinite(voice.span)) {
|
|
28
31
|
const end = voice.span + voice.latest;
|
|
29
|
-
// A
|
|
32
|
+
// A frozen voice goes live again once its clock is back before its end: by a seek, by a
|
|
30
33
|
// subject staggered later than any before, or by running backwards, which is checked each
|
|
31
34
|
// frame while it can.
|
|
32
|
-
if (voice.state === '
|
|
35
|
+
if (voice.state === 'frozen') {
|
|
33
36
|
if (voice.rate <= 0 || voice.ramp !== null)
|
|
34
37
|
return Number.NEGATIVE_INFINITY;
|
|
35
38
|
if (!Number.isNaN(mix.now) && voice.elapsedAt(mix.now) < end)
|
package/dist/fade.d.ts
CHANGED
|
@@ -16,4 +16,17 @@ export declare function beginFade<I, O>(mix: Mixer<I, O>, voice: Voice<I, O>, op
|
|
|
16
16
|
at?: 'rest';
|
|
17
17
|
deadline?: number;
|
|
18
18
|
}, at?: number): void;
|
|
19
|
+
/**
|
|
20
|
+
* Turns a fading voice around: it climbs back from where its fade out had got to, up the same
|
|
21
|
+
* curve, at the whole curve per `over` ms. A fade waiting for rest is taken back at once. A voice
|
|
22
|
+
* not fading is left as it is.
|
|
23
|
+
*/
|
|
24
|
+
export declare function beginRise<I, O>(mix: Mixer<I, O>, voice: Voice<I, O>, opts: {
|
|
25
|
+
over?: number;
|
|
26
|
+
}): void;
|
|
27
|
+
/**
|
|
28
|
+
* Fades a voice out from mix time `at`: ahead, it plays untouched until then and begins its fade
|
|
29
|
+
* exactly there; at or behind now, the fade began then and is partway.
|
|
30
|
+
*/
|
|
31
|
+
export declare function fadeAt<I, O>(mix: Mixer<I, O>, voice: Voice<I, O>, at: number, over: number | undefined): void;
|
|
19
32
|
export declare function retire<I, O>(mix: Mixer<I, O>, voice: Voice<I, O>, at?: number): void;
|
package/dist/fade.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { changed } from './chain.js';
|
|
2
2
|
import { envelope } from './clock.js';
|
|
3
3
|
import { schedule } from './due.js';
|
|
4
|
-
import { noted } from './history.js';
|
|
4
|
+
import { leave, noted, releasing } from './history.js';
|
|
5
5
|
import { orphan } from './owner.js';
|
|
6
6
|
import { startOf } from './place.js';
|
|
7
|
+
import { lingers, refit } from './spans.js';
|
|
7
8
|
import { unreach } from './unreached.js';
|
|
8
9
|
/** Starts one subject's ramp out of a voice, or takes it out at once for a ramp of 0. */
|
|
9
10
|
export function fadeSubject(mix, voice, subject, over) {
|
|
@@ -43,7 +44,7 @@ export function part(mix, voice, subject, at) {
|
|
|
43
44
|
forgetIn(mix, voice, subject);
|
|
44
45
|
const motion = voice.motion;
|
|
45
46
|
if (motion !== undefined && asks(mix, motion, voice))
|
|
46
|
-
motion.release(subject);
|
|
47
|
+
motion.release(subject, ...releasing(mix));
|
|
47
48
|
}
|
|
48
49
|
/** Brings a subject faded out of a voice back, to be met afresh on its next probe. */
|
|
49
50
|
export function unpart(mix, voice, subject) {
|
|
@@ -63,6 +64,7 @@ function forgetIn(mix, voice, subject) {
|
|
|
63
64
|
unreach(voice, head.slot, false);
|
|
64
65
|
const held = voice.subjects.get(subject);
|
|
65
66
|
if (held !== undefined) {
|
|
67
|
+
leave(mix, voice, subject, held);
|
|
66
68
|
voice.subjects.delete(subject);
|
|
67
69
|
if (held.reaches)
|
|
68
70
|
voice.seen--;
|
|
@@ -102,6 +104,52 @@ export function beginFade(mix, voice, opts, at) {
|
|
|
102
104
|
if (over === 0 && opts.at !== 'rest')
|
|
103
105
|
retire(mix, voice, voice.out.at);
|
|
104
106
|
}
|
|
107
|
+
/**
|
|
108
|
+
* Turns a fading voice around: it climbs back from where its fade out had got to, up the same
|
|
109
|
+
* curve, at the whole curve per `over` ms. A fade waiting for rest is taken back at once. A voice
|
|
110
|
+
* not fading is left as it is.
|
|
111
|
+
*/
|
|
112
|
+
export function beginRise(mix, voice, opts) {
|
|
113
|
+
const out = voice.out;
|
|
114
|
+
if (voice.state !== 'fading' && voice.outSet) {
|
|
115
|
+
// A fade set ahead and not begun is taken back, and the weight never moved.
|
|
116
|
+
voice.outAt = Number.POSITIVE_INFINITY;
|
|
117
|
+
voice.outOver = Number.NaN;
|
|
118
|
+
voice.outSet = false;
|
|
119
|
+
noted(mix, voice);
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
if (voice.state !== 'fading' || out === null)
|
|
123
|
+
return;
|
|
124
|
+
const at = Number.isNaN(mix.now) ? startOf(voice) : mix.now;
|
|
125
|
+
const over = mix.reduced ? 0 : (opts.over ?? voice.fade.in ?? voice.fade.out ?? 0);
|
|
126
|
+
const left = out.rest || out.over === 0 ? 1 : 1 - (at - out.at) / out.over;
|
|
127
|
+
const from = left < 0 ? 0 : left > 1 ? 1 : left;
|
|
128
|
+
voice.out = null;
|
|
129
|
+
voice.outAt = Number.POSITIVE_INFINITY;
|
|
130
|
+
voice.state = 'live';
|
|
131
|
+
voice.back = over > 0 && from < 1 ? { from, at, over } : null;
|
|
132
|
+
noted(mix, voice);
|
|
133
|
+
mix.lanes?.refill();
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Fades a voice out from mix time `at`: ahead, it plays untouched until then and begins its fade
|
|
137
|
+
* exactly there; at or behind now, the fade began then and is partway.
|
|
138
|
+
*/
|
|
139
|
+
export function fadeAt(mix, voice, at, over) {
|
|
140
|
+
if (voice.state === 'done' || voice.state === 'fading')
|
|
141
|
+
return;
|
|
142
|
+
const now = Number.isNaN(mix.now) ? startOf(voice) : mix.now;
|
|
143
|
+
if (at <= now) {
|
|
144
|
+
beginFade(mix, voice, { over }, Math.max(startOf(voice), at));
|
|
145
|
+
mix.stir();
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
voice.outAt = at;
|
|
149
|
+
voice.outOver = over ?? Number.NaN;
|
|
150
|
+
voice.outSet = true;
|
|
151
|
+
noted(mix, voice);
|
|
152
|
+
}
|
|
105
153
|
/** Removes a voice, recording that it left at `at`, default now. */
|
|
106
154
|
/** Whether a motion patch still asks this voice of this mix for its time. */
|
|
107
155
|
function asks(mix, motion, voice) {
|
|
@@ -118,7 +166,7 @@ export function retire(mix, voice, at) {
|
|
|
118
166
|
voice.keepOn = null;
|
|
119
167
|
mix.retired.push(voice);
|
|
120
168
|
voice.doneAt = at ?? (Number.isNaN(mix.now) ? Number.NEGATIVE_INFINITY : mix.now);
|
|
121
|
-
voice.play(false);
|
|
169
|
+
voice.play(false, voice.doneAt);
|
|
122
170
|
voice.resolve();
|
|
123
171
|
// An owner takes what it holds with it, and leaves with its last child.
|
|
124
172
|
if (voice.holding !== null)
|
|
@@ -126,8 +174,11 @@ export function retire(mix, voice, at) {
|
|
|
126
174
|
if (child.state !== 'done')
|
|
127
175
|
retire(mix, child, voice.doneAt);
|
|
128
176
|
if (voice.owner !== null) {
|
|
177
|
+
const span = voice.owner;
|
|
129
178
|
const owner = orphan(voice);
|
|
130
|
-
if (owner !== null)
|
|
179
|
+
if (owner !== null && !lingers(owner, voice.doneAt))
|
|
131
180
|
retire(mix, owner, voice.doneAt);
|
|
181
|
+
else if (span.fitting !== null && span.state !== 'done')
|
|
182
|
+
refit(mix, span);
|
|
132
183
|
}
|
|
133
184
|
}
|
package/dist/fill.d.ts
CHANGED
|
@@ -23,7 +23,7 @@ export declare function reset<I, O>(this: Lanes<I, O>, lane: Lane<I, O>): void;
|
|
|
23
23
|
export declare function run<I, O>(this: Lanes<I, O>, lane: Lane<I, O>): void;
|
|
24
24
|
/**
|
|
25
25
|
* One subject's contribution from a lane's voice. True where the voice gave the subject a delta,
|
|
26
|
-
* as the general path's `
|
|
26
|
+
* as the general path's `contribution` does where it returns one, which is what makes a voice in a
|
|
27
27
|
* locus one of its members for the subject.
|
|
28
28
|
*/
|
|
29
29
|
export declare function one<I, O>(this: Lanes<I, O>, lane: Lane<I, O>, p: number, slot: number, elapsedNow: number, duration: number, passes: number): boolean;
|
package/dist/fill.js
CHANGED
|
@@ -3,7 +3,7 @@ import { passAt, phaseAt, silent, weighed } from './clock.js';
|
|
|
3
3
|
import { flush } from './columns.js';
|
|
4
4
|
import { crowdsUpTo, freshen } from './crowd.js';
|
|
5
5
|
import { foldLocus, gatherLocus } from './gather.js';
|
|
6
|
-
import {
|
|
6
|
+
import { frozenAt } from './lane.js';
|
|
7
7
|
import { reach } from './meet.js';
|
|
8
8
|
import { absent } from './numbers.js';
|
|
9
9
|
import { AT, NOTHING, readKeys, seg, segment, shifted } from './patch.js';
|
|
@@ -39,6 +39,7 @@ export function fillAll(lanes, now, version) {
|
|
|
39
39
|
const decide = lanes.fresh;
|
|
40
40
|
lanes.fresh = false;
|
|
41
41
|
const share = lanes.live > 0 ? lanes.lastDistinct / lanes.live : 1;
|
|
42
|
+
lanes.lately = share < 1 ? lanes.lastFrom : -1;
|
|
42
43
|
// Deciding which lanes and crowds go idle first, then running them, in voice order: a crowd's
|
|
43
44
|
// rows are folded between the lanes on either side of their voices, as the general path
|
|
44
45
|
// folds every voice in order.
|
|
@@ -95,11 +96,9 @@ export function fillAll(lanes, now, version) {
|
|
|
95
96
|
}
|
|
96
97
|
}
|
|
97
98
|
// With every lane idle there was nothing to fill, and no subject reads a lane this frame.
|
|
98
|
-
|
|
99
|
-
for (let s = 0; s < size; s++)
|
|
100
|
-
lanes.per[s * 6 /* Per.SLOT */ + 0 /* Per.FILLED */] = lanes.fills;
|
|
99
|
+
lanes.busyFill = busy ? lanes.fills : 0;
|
|
101
100
|
for (const slot of lanes.late)
|
|
102
|
-
lanes.per[slot * 6 /* Per.SLOT */ + 0 /* Per.FILLED */] =
|
|
101
|
+
lanes.per[slot * 6 /* Per.SLOT */ + 0 /* Per.FILLED */] = lanes.fills;
|
|
103
102
|
if (lanes.holding) {
|
|
104
103
|
lanes.subjects.fill(undefined, 0, size);
|
|
105
104
|
lanes.holding = false;
|
|
@@ -208,7 +207,11 @@ export function reset(lane) {
|
|
|
208
207
|
lane.read = false;
|
|
209
208
|
lane.weighed = false;
|
|
210
209
|
// With no fade in or out the envelope is 1 for every subject, which is what it would return.
|
|
211
|
-
lane.flat =
|
|
210
|
+
lane.flat =
|
|
211
|
+
!((voice.fade.in ?? 0) > 0) &&
|
|
212
|
+
voice.out === null &&
|
|
213
|
+
voice.back === null &&
|
|
214
|
+
voice.owner === null;
|
|
212
215
|
lane.fade = 1;
|
|
213
216
|
}
|
|
214
217
|
/** One voice's contribution to every subject it plays on. */
|
|
@@ -238,7 +241,7 @@ export function run(lane) {
|
|
|
238
241
|
}
|
|
239
242
|
/**
|
|
240
243
|
* One subject's contribution from a lane's voice. True where the voice gave the subject a delta,
|
|
241
|
-
* as the general path's `
|
|
244
|
+
* as the general path's `contribution` does where it returns one, which is what makes a voice in a
|
|
242
245
|
* locus one of its members for the subject.
|
|
243
246
|
*/
|
|
244
247
|
export function one(lane, p, slot, elapsedNow, duration, passes) {
|
|
@@ -249,8 +252,10 @@ export function one(lane, p, slot, elapsedNow, duration, passes) {
|
|
|
249
252
|
// A probe read this subject from the last fill: keep the weight it read for `weightOf`.
|
|
250
253
|
if (this.per[slot * 6 /* Per.SLOT */ + 5 /* Per.LANE_FILL */] === this.fills - 1)
|
|
251
254
|
data[o + 3 /* Row.PROBED */] = data[o + 2 /* Row.WEIGHT */];
|
|
255
|
+
if (this.lately >= 0 && this.unread(slot))
|
|
256
|
+
return false;
|
|
252
257
|
const delay = data[o + 0 /* Row.DELAY */];
|
|
253
|
-
const elapsed =
|
|
258
|
+
const elapsed = frozenAt(voice, elapsedNow - delay);
|
|
254
259
|
if (!(elapsed >= 0)) {
|
|
255
260
|
data[o + 2 /* Row.WEIGHT */] = 0;
|
|
256
261
|
return false;
|
|
@@ -278,6 +283,8 @@ export function one(lane, p, slot, elapsedNow, duration, passes) {
|
|
|
278
283
|
else
|
|
279
284
|
w = weighed(voice.weight, lane.fade, this.parting(voice, slot));
|
|
280
285
|
data[o + 2 /* Row.WEIGHT */] = w;
|
|
286
|
+
this.folding = lane;
|
|
287
|
+
this.rec = lane.records[p] ?? null;
|
|
281
288
|
if (voice.built !== null) {
|
|
282
289
|
if (!lane.read || !Object.is(elapsed, lane.readAt)) {
|
|
283
290
|
this.readKeyed(voice, lane.phase, lane.delta, lane.scratch);
|
|
@@ -367,12 +374,12 @@ export function call(voice, chans, rec, slot, elapsed, phase, pass, delay, w) {
|
|
|
367
374
|
else
|
|
368
375
|
reading.horizon = Number.POSITIVE_INFINITY;
|
|
369
376
|
const delta = voice.patch.at(phase, subject, voice.setting);
|
|
370
|
-
// What `
|
|
377
|
+
// What `contribution` leaves on the record, so a probe on the general path this frame reuses it.
|
|
371
378
|
rec.delta = delta;
|
|
372
379
|
rec.probed = this.now;
|
|
373
380
|
rec.seeks = voice.seeks;
|
|
374
381
|
if (this.keeps)
|
|
375
|
-
host.after(voice, rec);
|
|
382
|
+
host.after(voice, subject, rec);
|
|
376
383
|
if (reading.kept !== kept && !voice.keeping)
|
|
377
384
|
host.kept(voice);
|
|
378
385
|
if (w > 0)
|
package/dist/fit.d.ts
ADDED
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* How hard a claim on time holds when it meets another: the weaker gives way.
|
|
3
|
+
*
|
|
4
|
+
* @category score
|
|
5
|
+
*/
|
|
6
|
+
export type Strength = 'weak' | 'strong' | 'required';
|
|
7
|
+
/**
|
|
8
|
+
* How a span lays its children out: each after the one before has ended, partway through it, or
|
|
9
|
+
* all at once.
|
|
10
|
+
*
|
|
11
|
+
* @category score
|
|
12
|
+
*/
|
|
13
|
+
export type Order = 'queue' | 'stagger' | 'together';
|
|
14
|
+
/**
|
|
15
|
+
* One child of a span as a fit sees it: what is left of it, and the ways it may give way.
|
|
16
|
+
*
|
|
17
|
+
* @category score
|
|
18
|
+
*/
|
|
19
|
+
export interface Claim {
|
|
20
|
+
/** Span ms left of it at its own rate. */
|
|
21
|
+
natural: number;
|
|
22
|
+
/** A playing child has started, and stays at the front. */
|
|
23
|
+
state: 'pending' | 'playing';
|
|
24
|
+
/** How many times faster it may play; 1 for no faster. */
|
|
25
|
+
faster: number;
|
|
26
|
+
/** How many times slower it may play; 1 for no slower. */
|
|
27
|
+
slower: number;
|
|
28
|
+
/** Whether it may run alongside the one before it where the span's order would not have it. */
|
|
29
|
+
overlap: boolean;
|
|
30
|
+
/** Whether it may jump to its end instead of playing. */
|
|
31
|
+
ballast: boolean;
|
|
32
|
+
priority: Strength;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* The span as a fit sees it.
|
|
36
|
+
*
|
|
37
|
+
* @category score
|
|
38
|
+
*/
|
|
39
|
+
export interface SpanClaim {
|
|
40
|
+
/** Span ms left of its budget; Infinity for a span with none. */
|
|
41
|
+
left: number;
|
|
42
|
+
/** Its whole budget. */
|
|
43
|
+
budget: number;
|
|
44
|
+
priority: Strength;
|
|
45
|
+
order: Order;
|
|
46
|
+
/** How far through the one before a staggered child starts, 0..1. */
|
|
47
|
+
share: number;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* What a fit decides, per child in the order given: a rate over its own, and whether it is
|
|
51
|
+
* skipped. `share` is how far through the one before a child that may overlap starts, and `allow`
|
|
52
|
+
* the span ms the span may run past its budget.
|
|
53
|
+
*
|
|
54
|
+
* @category score
|
|
55
|
+
*/
|
|
56
|
+
export interface FitPlan {
|
|
57
|
+
rate: number[];
|
|
58
|
+
skip: boolean[];
|
|
59
|
+
share: number;
|
|
60
|
+
allow: number;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* A strategy for fitting a span's children into its budget: from the plan so far to a new one.
|
|
64
|
+
*
|
|
65
|
+
* @category score
|
|
66
|
+
*/
|
|
67
|
+
export type Fit = (span: SpanClaim, kids: readonly Claim[], plan: FitPlan) => FitPlan;
|
|
68
|
+
/** How far through the one before a child starts under each order, before any fit. */
|
|
69
|
+
export declare function shareOf(order: Order, stagger: number): number;
|
|
70
|
+
/**
|
|
71
|
+
* The plan before any fit: every child at its own rate, played, in the span's order.
|
|
72
|
+
*
|
|
73
|
+
* @category score
|
|
74
|
+
*/
|
|
75
|
+
export declare function plain(span: SpanClaim, kids: readonly Claim[]): FitPlan;
|
|
76
|
+
/**
|
|
77
|
+
* Where a plan puts each child, span ms from now, and how long they take together. A playing child
|
|
78
|
+
* is where it is, at 0; each pending one starts its share through the one before it.
|
|
79
|
+
*
|
|
80
|
+
* @category score
|
|
81
|
+
*/
|
|
82
|
+
export declare function layout(span: SpanClaim, kids: readonly Claim[], plan: FitPlan): {
|
|
83
|
+
at: number[];
|
|
84
|
+
length: number;
|
|
85
|
+
};
|
|
86
|
+
/**
|
|
87
|
+
* Moves every child's rate by one factor toward the budget, before the span's fit: faster where
|
|
88
|
+
* they run past it, each no faster than its `faster` allows, and slower where they leave room in
|
|
89
|
+
* it, each no slower than its `slower` allows, so they fill it.
|
|
90
|
+
*/
|
|
91
|
+
export declare function rescale(span: SpanClaim, kids: readonly Claim[], plan: FitPlan): FitPlan;
|
|
92
|
+
/**
|
|
93
|
+
* Starts children that may overlap sooner after the one before, toward all at once, until they
|
|
94
|
+
* fit.
|
|
95
|
+
*
|
|
96
|
+
* @category score
|
|
97
|
+
*/
|
|
98
|
+
export declare function condense(): Fit;
|
|
99
|
+
/**
|
|
100
|
+
* Jumps children that may be skipped to their end, latest first, until they fit.
|
|
101
|
+
*
|
|
102
|
+
* @category score
|
|
103
|
+
*/
|
|
104
|
+
export declare function shed(): Fit;
|
|
105
|
+
/**
|
|
106
|
+
* Jumps every child left to its end at once, whether or not it fits otherwise.
|
|
107
|
+
*
|
|
108
|
+
* @category score
|
|
109
|
+
*/
|
|
110
|
+
export declare function conclude(): Fit;
|
|
111
|
+
/**
|
|
112
|
+
* Lets the span run past its budget: up to `cap` times it, or without limit.
|
|
113
|
+
*
|
|
114
|
+
* @category score
|
|
115
|
+
*/
|
|
116
|
+
export declare function overrun(opts?: {
|
|
117
|
+
cap?: number;
|
|
118
|
+
}): Fit;
|
|
119
|
+
/**
|
|
120
|
+
* Tries each fit in turn, handing the plan on, and stops once the children fit.
|
|
121
|
+
*
|
|
122
|
+
* @category score
|
|
123
|
+
*/
|
|
124
|
+
export declare function pipe(...fits_: readonly Fit[]): Fit;
|
|
125
|
+
/**
|
|
126
|
+
* Every way of giving way after the span's retiming, overrunning only once the rest are spent.
|
|
127
|
+
*
|
|
128
|
+
* @category score
|
|
129
|
+
*/
|
|
130
|
+
export declare function lax(opts?: {
|
|
131
|
+
cap?: number;
|
|
132
|
+
}): Fit;
|
|
133
|
+
/** The fit a span takes without one of its own. */
|
|
134
|
+
export declare const defaultFit: Fit;
|
|
135
|
+
/**
|
|
136
|
+
* A span's fit settled: the plan, and what it had to do past the strategy, which starts from the
|
|
137
|
+
* children retimed toward the budget. Where the children still
|
|
138
|
+
* do not fit, a span weaker than `strong` lets them run long; otherwise `spill` either skips
|
|
139
|
+
* every child weaker than the span (`instant`), or lets them run long (`overrun`).
|
|
140
|
+
*/
|
|
141
|
+
export declare function settle(span: SpanClaim, kids: readonly Claim[], fit: Fit, spill: 'instant' | 'overrun'): {
|
|
142
|
+
plan: FitPlan;
|
|
143
|
+
fell: boolean;
|
|
144
|
+
};
|