@msb235/blits 0.7.1 → 0.8.1
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 +193 -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 +8 -0
- package/dist/clone.js +22 -0
- 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 +216 -31
- package/dist/frames.d.ts +21 -0
- package/dist/frames.js +59 -0
- 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/locus.js +2 -2
- 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 +39 -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 +45 -6
- package/dist/transport.js +64 -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/patch.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { lerpInto } from './channels.js';
|
|
2
|
-
import { curve } from './easing.js';
|
|
1
|
+
import { lerpInto, lerpNumber } from './channels.js';
|
|
2
|
+
import { curve, startSlope } from './easing.js';
|
|
3
3
|
/**
|
|
4
4
|
* The procedural form. Flicker, roving, a hash walk, a starter strike.
|
|
5
5
|
*
|
|
@@ -12,7 +12,6 @@ export function patch(duration, at, opts) {
|
|
|
12
12
|
return {
|
|
13
13
|
form: 'fn',
|
|
14
14
|
duration,
|
|
15
|
-
period: duration,
|
|
16
15
|
writes,
|
|
17
16
|
kit: opts.kit,
|
|
18
17
|
reads: opts.reads,
|
|
@@ -23,11 +22,6 @@ export function patch(duration, at, opts) {
|
|
|
23
22
|
unpack: opts.unpack,
|
|
24
23
|
};
|
|
25
24
|
}
|
|
26
|
-
const options = new WeakMap();
|
|
27
|
-
/** The options a `keys` patch was built with, for an engine reading its stops rather than calling it. */
|
|
28
|
-
export function keysOptionsOf(p) {
|
|
29
|
-
return options.get(p);
|
|
30
|
-
}
|
|
31
25
|
const linear = (a, b, u) => a + (b - a) * u;
|
|
32
26
|
function interpolate(a, b, u) {
|
|
33
27
|
if (typeof a === 'number' && typeof b === 'number')
|
|
@@ -64,6 +58,10 @@ function build(stops, writes, duration, opts) {
|
|
|
64
58
|
}
|
|
65
59
|
all.sort((x, y) => x.at - y.at);
|
|
66
60
|
const tail = all.filter((pt) => pt.at !== 0);
|
|
61
|
+
const lerp = opts.lerpBy?.(channel) ?? opts.kit?.[channel]?.lerp;
|
|
62
|
+
const plain = all.length > 0 &&
|
|
63
|
+
(lerp === undefined || lerp === lerpNumber) &&
|
|
64
|
+
all.every((pt) => typeof pt.value === 'number');
|
|
67
65
|
tracks.push({
|
|
68
66
|
channel: channel,
|
|
69
67
|
all,
|
|
@@ -71,7 +69,14 @@ function build(stops, writes, duration, opts) {
|
|
|
71
69
|
ats: all.map((pt) => pt.at),
|
|
72
70
|
tailAts: null,
|
|
73
71
|
delay: opts.delayBy?.(channel) ?? 0,
|
|
74
|
-
lerp
|
|
72
|
+
lerp,
|
|
73
|
+
numbers: plain
|
|
74
|
+
? {
|
|
75
|
+
ats: Float64Array.from(all, (pt) => pt.at),
|
|
76
|
+
values: Float64Array.from(all, (pt) => pt.value),
|
|
77
|
+
eases: all.map((pt) => pt.ease),
|
|
78
|
+
}
|
|
79
|
+
: null,
|
|
75
80
|
});
|
|
76
81
|
}
|
|
77
82
|
return { tracks, duration };
|
|
@@ -100,12 +105,17 @@ export function shifted(track, phase, duration) {
|
|
|
100
105
|
}
|
|
101
106
|
/** A phase once a channel's delay of `delay` ms in a duration of `duration` applies. */
|
|
102
107
|
export function shift(delay, phase, duration) {
|
|
103
|
-
|
|
108
|
+
if (delay === 0 || duration === 0)
|
|
109
|
+
return phase;
|
|
110
|
+
// The channel travels in what is left after its wait, so it lands with the rest at phase 1.
|
|
111
|
+
if (delay >= duration)
|
|
112
|
+
return phase >= 1 ? 1 : 0;
|
|
113
|
+
return Math.max(0, (phase * duration - delay) / (duration - delay));
|
|
104
114
|
}
|
|
105
115
|
/**
|
|
106
116
|
* Where `phase` falls among `n` stop phases `ats[off + k * stride]`, in order: -2 for no stops, -1
|
|
107
117
|
* at or before the first, `n` at or past the last, and otherwise the first stop past it, where the
|
|
108
|
-
* segment from the one before ends.
|
|
118
|
+
* segment from the one before ends. `segment` and `readNumber` make the same search.
|
|
109
119
|
*/
|
|
110
120
|
export function locate(ats, off, stride, n, phase) {
|
|
111
121
|
if (n === 0)
|
|
@@ -132,8 +142,8 @@ export function fraction(phase, a, b) {
|
|
|
132
142
|
return (phase - a) / (b - a);
|
|
133
143
|
}
|
|
134
144
|
/**
|
|
135
|
-
* Finds where one track is at a phase, into `seg`: the
|
|
136
|
-
*
|
|
145
|
+
* Finds where one track is at a phase, into `seg`: the search `read` and a lane folding stops
|
|
146
|
+
* straight into a subject's values both make. `base` stands in for the value at
|
|
137
147
|
* phase 0, which is how `from: 'current'` starts a voice wherever the subject already is.
|
|
138
148
|
*/
|
|
139
149
|
export function segment(track, phase, base) {
|
|
@@ -179,10 +189,44 @@ export function segment(track, phase, base) {
|
|
|
179
189
|
seg.ease = b.ease;
|
|
180
190
|
return BETWEEN;
|
|
181
191
|
}
|
|
192
|
+
/**
|
|
193
|
+
* A plain-number track at a phase, in exactly `segment`'s and `read`'s arithmetic: the stop at or
|
|
194
|
+
* past the phase by binary search, the fraction before easing, then a straight lerp.
|
|
195
|
+
*/
|
|
196
|
+
function readNumber(t, phase) {
|
|
197
|
+
const ats = t.ats;
|
|
198
|
+
const values = t.values;
|
|
199
|
+
const n = ats.length;
|
|
200
|
+
if (phase <= ats[0])
|
|
201
|
+
return values[0];
|
|
202
|
+
if (phase >= ats[n - 1])
|
|
203
|
+
return values[n - 1];
|
|
204
|
+
let lo = 1;
|
|
205
|
+
let hi = n - 1;
|
|
206
|
+
while (lo < hi) {
|
|
207
|
+
const mid = (lo + hi) >> 1;
|
|
208
|
+
if (ats[mid] >= phase)
|
|
209
|
+
hi = mid;
|
|
210
|
+
else
|
|
211
|
+
lo = mid + 1;
|
|
212
|
+
}
|
|
213
|
+
const a = ats[lo - 1];
|
|
214
|
+
const u = (phase - a) / (ats[lo] - a);
|
|
215
|
+
const ease = t.eases[lo];
|
|
216
|
+
const eased = ease ? ease(u) : u;
|
|
217
|
+
const from = values[lo - 1];
|
|
218
|
+
return from + (values[lo] - from) * eased;
|
|
219
|
+
}
|
|
182
220
|
/** Reads one track at a phase; `base` as `segment` takes it. */
|
|
183
221
|
function read(track, phase, base, lerp,
|
|
184
222
|
/** Units per ms, for a retarget's first segment. */
|
|
185
223
|
slope, duration, into, reuse) {
|
|
224
|
+
const numbers = track.numbers;
|
|
225
|
+
if (numbers !== null &&
|
|
226
|
+
base === undefined &&
|
|
227
|
+
slope === undefined &&
|
|
228
|
+
(lerp === undefined || lerp === lerpNumber || track.lerp !== undefined))
|
|
229
|
+
return readNumber(numbers, phase);
|
|
186
230
|
const found = segment(track, phase, base);
|
|
187
231
|
if (found === NOTHING)
|
|
188
232
|
return undefined;
|
|
@@ -208,7 +252,9 @@ slope, duration, into, reuse) {
|
|
|
208
252
|
// add c·u(1−u)², which is 0 at both ends, flat at the end, and fixes the slope at the start.
|
|
209
253
|
if (slope === undefined || !first)
|
|
210
254
|
return value;
|
|
211
|
-
const e0 = ease ? ease
|
|
255
|
+
const e0 = ease ? startSlope(ease) : 1;
|
|
256
|
+
if (e0 === Number.POSITIVE_INFINITY)
|
|
257
|
+
return value;
|
|
212
258
|
const bend = (s, a, z) => (s * duration * len - (z - a) * e0) * u * (1 - u) * (1 - u);
|
|
213
259
|
if (typeof value === 'number' && typeof slope === 'number')
|
|
214
260
|
return value + bend(slope, aValue, bValue);
|
|
@@ -262,14 +308,15 @@ slopes, intos, scratch) {
|
|
|
262
308
|
}
|
|
263
309
|
return out;
|
|
264
310
|
}
|
|
265
|
-
/** A patch's `duration`, or the deprecated `period` where only that is set. */
|
|
266
|
-
export const durationOf = (p) => p.duration ?? p.period ?? 0;
|
|
267
311
|
const cache = new WeakMap();
|
|
268
|
-
/**
|
|
312
|
+
/**
|
|
313
|
+
* The built form of a patch's stops, made on first ask for a patch `keys()` did not build: a copy
|
|
314
|
+
* of one, or one another copy of blits made, each read by the fields it carries.
|
|
315
|
+
*/
|
|
269
316
|
export function builtOf(p) {
|
|
270
317
|
let held = cache.get(p);
|
|
271
318
|
if (held === undefined) {
|
|
272
|
-
held = build(p.keys, p.writes,
|
|
319
|
+
held = build(p.keys, p.writes, p.duration, p);
|
|
273
320
|
cache.set(p, held);
|
|
274
321
|
}
|
|
275
322
|
return held;
|
|
@@ -289,13 +336,28 @@ export function keys(duration, stops, opts = {}) {
|
|
|
289
336
|
const p = {
|
|
290
337
|
form: 'keys',
|
|
291
338
|
duration,
|
|
292
|
-
period: duration,
|
|
293
339
|
writes,
|
|
294
340
|
kit: opts.kit,
|
|
295
341
|
keys: stops,
|
|
296
|
-
|
|
342
|
+
...given(opts),
|
|
343
|
+
// A copy of the patch carries this `at` with fields of its own, which it is read by.
|
|
344
|
+
at(phase) {
|
|
345
|
+
return readKeys(this === p ? made : builtOf(this), phase, {});
|
|
346
|
+
},
|
|
297
347
|
};
|
|
298
|
-
options.set(p, opts);
|
|
299
348
|
cache.set(p, made);
|
|
300
349
|
return p;
|
|
301
350
|
}
|
|
351
|
+
/** The `keys` options a patch carries as fields, those given only: an absent one adds no key. */
|
|
352
|
+
function given(opts) {
|
|
353
|
+
const out = {};
|
|
354
|
+
if (opts.ease !== undefined)
|
|
355
|
+
out.ease = opts.ease;
|
|
356
|
+
if (opts.easeBy !== undefined)
|
|
357
|
+
out.easeBy = opts.easeBy;
|
|
358
|
+
if (opts.delayBy !== undefined)
|
|
359
|
+
out.delayBy = opts.delayBy;
|
|
360
|
+
if (opts.lerpBy !== undefined)
|
|
361
|
+
out.lerpBy = opts.lerpBy;
|
|
362
|
+
return out;
|
|
363
|
+
}
|
package/dist/place.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { departedMatches } from './departed.js';
|
|
2
|
+
import { reorigin } from './held.js';
|
|
1
3
|
import { noted } from './history.js';
|
|
2
4
|
import { markOf } from './marks.js';
|
|
3
5
|
import { mixTime, ownerReading } from './owner.js';
|
|
@@ -37,6 +39,10 @@ function startAt(mix, voice, start) {
|
|
|
37
39
|
voice.start = start;
|
|
38
40
|
voice.anchorNow = start;
|
|
39
41
|
voice.anchorElapsed = 0;
|
|
42
|
+
voice.clocks = null;
|
|
43
|
+
// A clock moved outright, as a seek moves it: what a record computed at this mix time is stale.
|
|
44
|
+
voice.seeks++;
|
|
45
|
+
reorigin(mix, voice, mix.now);
|
|
40
46
|
noted(mix, voice);
|
|
41
47
|
return true;
|
|
42
48
|
}
|
|
@@ -266,7 +272,7 @@ class Pick {
|
|
|
266
272
|
/**
|
|
267
273
|
* The mix time a query answers: `mark` of the voice it picks, among those cued and those that have
|
|
268
274
|
* left, never the asker; or a mark the host announced on the score, which is the same time for
|
|
269
|
-
* any
|
|
275
|
+
* any mark. Undefined while what it picks has no such time. A query naming no score looks
|
|
270
276
|
* among the asker's siblings, the voices its owner holds.
|
|
271
277
|
*/
|
|
272
278
|
function timeOf(mix, key, mark, self) {
|
|
@@ -290,8 +296,17 @@ function timeOf(mix, key, mark, self) {
|
|
|
290
296
|
: listed
|
|
291
297
|
? holders(mix, score)
|
|
292
298
|
: mix.transport.members;
|
|
299
|
+
// Voices forgotten on leaving answer only where no voice the mix still knows does.
|
|
300
|
+
const owner = self.owner?.id ?? -1;
|
|
301
|
+
const forgotten = new Pick(resolver, now);
|
|
293
302
|
for (let i = 0; i < mixes.length; i++) {
|
|
294
303
|
const m = mixes[i];
|
|
304
|
+
let departed = false;
|
|
305
|
+
for (const d of m.departed.all)
|
|
306
|
+
if (departedMatches(d, q, score, anywhere, owner)) {
|
|
307
|
+
forgotten.take(d.id, d.marks[mark]);
|
|
308
|
+
departed = true;
|
|
309
|
+
}
|
|
295
310
|
if (score === undefined || m.projecting || mix.projecting) {
|
|
296
311
|
for (const v of [...m.gone, ...m.cued])
|
|
297
312
|
if (matches(v))
|
|
@@ -300,7 +315,7 @@ function timeOf(mix, key, mark, self) {
|
|
|
300
315
|
}
|
|
301
316
|
const cued = scored(m, score);
|
|
302
317
|
const list = scoredGone(m, score);
|
|
303
|
-
if (cued.size === 0 && list.length === 0) {
|
|
318
|
+
if (cued.size === 0 && list.length === 0 && !departed) {
|
|
304
319
|
if (listed) {
|
|
305
320
|
unhold(m, score);
|
|
306
321
|
i--;
|
|
@@ -359,7 +374,7 @@ function timeOf(mix, key, mark, self) {
|
|
|
359
374
|
(q.name === undefined || a.name === q.name) &&
|
|
360
375
|
(q.tag === undefined || a.tags.includes(q.tag)))
|
|
361
376
|
picked.take(a.order, Number.isNaN(a.at) ? undefined : mixAt(mix, a.at));
|
|
362
|
-
return picked.answer();
|
|
377
|
+
return (picked.any ? picked : forgotten).answer();
|
|
363
378
|
}
|
|
364
379
|
/** `self`'s read of `m`'s gone voices for one query, started over where the list has changed under it. */
|
|
365
380
|
function foldOf(m, list, key, mark, self, resolver) {
|
package/dist/project.d.ts
CHANGED
|
@@ -1,7 +1,18 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Mixer } from './mixer.js';
|
|
2
|
+
import { type Cut, Transport } from './transport.js';
|
|
2
3
|
import type { TransportProjection } from './types.js';
|
|
4
|
+
import type { Subject, Voice } from './voice.js';
|
|
3
5
|
/**
|
|
4
6
|
* Every mix on a transport read at mix time `t`, through copies of them on a transport of their
|
|
5
7
|
* own, moved together so an anchor across mixes answers as it would live.
|
|
6
8
|
*/
|
|
7
9
|
export declare function projectAll(transport: Transport, t: number): TransportProjection;
|
|
10
|
+
/** Sets each copied voice's state to what its clock and fade make it at `t`. */
|
|
11
|
+
export declare function stateAt<I, O>(c: Mixer<I, O>, t: number): void;
|
|
12
|
+
/** Gives a projection's copy what it reads of the live mix as it is, before its voices are copied. */
|
|
13
|
+
export declare function mirror<I, O>(mix: Mixer<I, O>, c: Mixer<I, O>): void;
|
|
14
|
+
/**
|
|
15
|
+
* A projection back starts each subject from the latest copy kept at or before `t`, else from its
|
|
16
|
+
* voice's start with fresh state, which is exact for a voice stepped at a fixed interval.
|
|
17
|
+
*/
|
|
18
|
+
export declare function recall<I, O>(mix: Mixer<I, O>, voice: Voice<I, O>, subject: I, t: number, cut: Cut): Subject<unknown> | undefined;
|
package/dist/project.js
CHANGED
|
@@ -1,14 +1,16 @@
|
|
|
1
1
|
import { ownBlends } from './blend.js';
|
|
2
|
-
import {
|
|
3
|
-
import { copyHeld, last,
|
|
2
|
+
import { open, sighted, weightAt } from './everyone.js';
|
|
3
|
+
import { copyHeld, last, lastWithin } from './history.js';
|
|
4
4
|
import { Mixer } from './mixer.js';
|
|
5
5
|
import { move } from './move.js';
|
|
6
6
|
import { heldByInput, ownerReading, relink } from './owner.js';
|
|
7
7
|
import { pin } from './place.js';
|
|
8
8
|
import { reading } from './reading.js';
|
|
9
|
-
import {
|
|
9
|
+
import { record } from './record.js';
|
|
10
|
+
import { recount } from './roster.js';
|
|
11
|
+
import { copyStanding, stands } from './standing.js';
|
|
10
12
|
import { hostAt } from './tape.js';
|
|
11
|
-
import { Transport } from './transport.js';
|
|
13
|
+
import { Transport, within } from './transport.js';
|
|
12
14
|
import { cover } from './unpage.js';
|
|
13
15
|
import { unreached } from './unreached.js';
|
|
14
16
|
/**
|
|
@@ -18,24 +20,36 @@ import { unreached } from './unreached.js';
|
|
|
18
20
|
export function projectAll(transport, t) {
|
|
19
21
|
const ahead = Number.isNaN(transport.now) || t >= transport.now;
|
|
20
22
|
const tape = transport.tape;
|
|
21
|
-
const
|
|
23
|
+
const stamp = ahead && tape !== undefined ? tape.timestampAt(tape.undoDepth()) : undefined;
|
|
24
|
+
const pace = transport.pace;
|
|
25
|
+
const next = stamp === undefined || pace === null ? stamp : pace.reading(stamp);
|
|
22
26
|
if (next !== undefined && next <= t)
|
|
23
27
|
throw new Error(`blits: the tape holds calls at ${next} that a read ahead to ${t} cannot play; seek there to see them`);
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
+
// Behind, the last frame at or before `t`, read as it showed when `t` is that frame's own time.
|
|
29
|
+
let cut = { seq: transport.seq, strict: false };
|
|
30
|
+
let u = hostAt(transport, t);
|
|
31
|
+
const kept = transport.history !== undefined;
|
|
32
|
+
if (!ahead && kept) {
|
|
28
33
|
cover(transport, t);
|
|
34
|
+
const frame = transport.frames.at(t);
|
|
35
|
+
if (frame !== undefined) {
|
|
36
|
+
cut = { seq: frame.seq, strict: frame.at === t };
|
|
37
|
+
if (frame.at === t)
|
|
38
|
+
u = frame.u;
|
|
39
|
+
}
|
|
29
40
|
}
|
|
30
|
-
|
|
31
|
-
|
|
41
|
+
else if (!ahead)
|
|
42
|
+
stands(transport, t);
|
|
32
43
|
const c = new Transport(undefined, true);
|
|
33
44
|
c.offset = transport.offset;
|
|
34
45
|
c.u = u;
|
|
35
46
|
c.nextId = transport.nextId;
|
|
36
47
|
c.now = ahead ? transport.now : t;
|
|
37
|
-
c.pace =
|
|
38
|
-
|
|
48
|
+
c.pace =
|
|
49
|
+
pace === null || !within(cut, transport.pacedSeq)
|
|
50
|
+
? null
|
|
51
|
+
: pace.until(ahead ? Number.POSITIVE_INFINITY : u);
|
|
52
|
+
c.announced = (ahead ? transport.announced : transport.announced.filter((a) => within(cut, a.seq))).map((a) => ({ ...a }));
|
|
39
53
|
const copies = new Map();
|
|
40
54
|
for (const mix of transport.members) {
|
|
41
55
|
const copy = new Mixer(mix.kit, {
|
|
@@ -49,8 +63,10 @@ export function projectAll(transport, t) {
|
|
|
49
63
|
copies.set(mix, copy);
|
|
50
64
|
if (ahead)
|
|
51
65
|
copyAhead(mix, copy);
|
|
66
|
+
else if (kept)
|
|
67
|
+
copyBack(mix, copy, t, cut);
|
|
52
68
|
else
|
|
53
|
-
|
|
69
|
+
copyStanding(mix, copy, t, cut);
|
|
54
70
|
}
|
|
55
71
|
if (ahead)
|
|
56
72
|
for (const copy of copies.values())
|
|
@@ -83,13 +99,11 @@ export function projectAll(transport, t) {
|
|
|
83
99
|
}
|
|
84
100
|
/** A mix's copy for a read ahead: its voices as they stand, each subject carried from the live one. */
|
|
85
101
|
function copyAhead(mix, c) {
|
|
86
|
-
c
|
|
87
|
-
c.wantsPose = mix.wantsPose;
|
|
88
|
-
c.reducedNow = mix.reducedNow;
|
|
102
|
+
mirror(mix, c);
|
|
89
103
|
c.cued = mix.cued
|
|
90
104
|
.filter((v) => v.state !== 'done')
|
|
91
105
|
.map((v) => {
|
|
92
|
-
const copy = v.copy((subject) => carry(mix, v, subject));
|
|
106
|
+
const copy = v.copy((subject) => carry(mix, v, subject, c, copy), undefined, true);
|
|
93
107
|
const at = mix.pins?.get(v);
|
|
94
108
|
if (at !== undefined)
|
|
95
109
|
pin(c, copy, at);
|
|
@@ -98,15 +112,14 @@ function copyAhead(mix, c) {
|
|
|
98
112
|
ownBlends(c.cued);
|
|
99
113
|
if (mix.owners !== null)
|
|
100
114
|
relink(c.cued);
|
|
101
|
-
|
|
115
|
+
recount(c);
|
|
102
116
|
}
|
|
103
|
-
/** A mix's copy for a read back to `t`: its voices then, from the controls and copies kept. */
|
|
104
|
-
function copyBack(mix, c, t) {
|
|
105
|
-
c
|
|
106
|
-
c.wantsPose = mix.wantsPose;
|
|
107
|
-
c.reducedNow = mix.reducedNow;
|
|
117
|
+
/** A mix's copy for a read back to `t`, cut at `cut`: its voices then, from the controls and copies kept. */
|
|
118
|
+
function copyBack(mix, c, t, cut) {
|
|
119
|
+
mirror(mix, c);
|
|
108
120
|
c.backward = true;
|
|
109
|
-
|
|
121
|
+
// Copied as the frame's first probe began, so its own frame shows it.
|
|
122
|
+
const was = lastWithin(mix.hostLog, { seq: cut.seq, strict: false });
|
|
110
123
|
const host = mix.opts.host;
|
|
111
124
|
const then = was && typeof host === 'object' && host !== null
|
|
112
125
|
? Object.assign(Object.create(host), was.fields)
|
|
@@ -114,11 +127,11 @@ function copyBack(mix, c, t) {
|
|
|
114
127
|
if (was)
|
|
115
128
|
c.hostThen = was;
|
|
116
129
|
c.cued = [...mix.cued, ...mix.gone]
|
|
117
|
-
.filter((v) => v.
|
|
130
|
+
.filter((v) => v.cuedSeq <= cut.seq && !within(cut, v.doneSeq, true))
|
|
118
131
|
.sort((a, b) => a.id - b.id)
|
|
119
132
|
.map((v) => {
|
|
120
133
|
const log = v.log;
|
|
121
|
-
const copy = v.copy((subject) => recall(mix, v, subject, t),
|
|
134
|
+
const copy = v.copy((subject) => recall(mix, v, subject, t, cut), lastWithin(log, cut) ?? log[0]);
|
|
122
135
|
if (then !== undefined)
|
|
123
136
|
copy.setting.host = then;
|
|
124
137
|
return copy;
|
|
@@ -126,30 +139,39 @@ function copyBack(mix, c, t) {
|
|
|
126
139
|
ownBlends(c.cued);
|
|
127
140
|
if (mix.owners !== null)
|
|
128
141
|
relink(c.cued);
|
|
142
|
+
stateAt(c, t);
|
|
143
|
+
recount(c);
|
|
144
|
+
}
|
|
145
|
+
/** Sets each copied voice's state to what its clock and fade make it at `t`. */
|
|
146
|
+
export function stateAt(c, t) {
|
|
129
147
|
for (const copy of c.cued)
|
|
130
|
-
copy.state =
|
|
131
|
-
|
|
148
|
+
copy.state = copy.out
|
|
149
|
+
? 'fading'
|
|
150
|
+
: (copy.owner === null ? t : ownerReading(copy.owner, t)) < copy.start
|
|
132
151
|
? 'pending'
|
|
133
|
-
: copy.
|
|
134
|
-
? '
|
|
135
|
-
:
|
|
136
|
-
? 'frozen'
|
|
137
|
-
: 'live';
|
|
138
|
-
count(c);
|
|
152
|
+
: copy.freezesAfter && copy.elapsedAt(t) >= copy.span + copy.latest
|
|
153
|
+
? 'frozen'
|
|
154
|
+
: 'live';
|
|
139
155
|
}
|
|
140
|
-
/**
|
|
141
|
-
function
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
for (const voice of mix.cued)
|
|
146
|
-
index(mix, voice);
|
|
147
|
-
mix.loci = mix.cued.filter((v) => v.spec.locus !== undefined).length;
|
|
148
|
-
mix.anchored = mix.cued.filter((v) => v.spec.anchor !== undefined).length;
|
|
149
|
-
mix.steps.push(null, ++mix.version);
|
|
156
|
+
/** Gives a projection's copy what it reads of the live mix as it is, before its voices are copied. */
|
|
157
|
+
export function mirror(mix, c) {
|
|
158
|
+
c.pose = mix.pose;
|
|
159
|
+
c.wantsPose = mix.wantsPose;
|
|
160
|
+
c.reducedNow = mix.reducedNow;
|
|
150
161
|
}
|
|
151
162
|
/** A voice's record of a subject, the one it shares among those it does not reach included. */
|
|
152
163
|
function recordOf(mix, voice, subject) {
|
|
164
|
+
// A voice sharing one record has that for every subject a chain has asked it about, pointed at
|
|
165
|
+
// the subject for the caller to copy from.
|
|
166
|
+
if (voice.sharing) {
|
|
167
|
+
const slot = mix.chains.get(subject)?.slot ?? -1;
|
|
168
|
+
if (!sighted(voice, slot))
|
|
169
|
+
return undefined;
|
|
170
|
+
const all = voice.everyone;
|
|
171
|
+
open(voice, all, slot);
|
|
172
|
+
all.weight = weightAt(voice, slot);
|
|
173
|
+
return all;
|
|
174
|
+
}
|
|
153
175
|
const held = voice.subjects.get(subject);
|
|
154
176
|
if (held !== undefined || voice.unreachedBits === null)
|
|
155
177
|
return held;
|
|
@@ -157,12 +179,19 @@ function recordOf(mix, voice, subject) {
|
|
|
157
179
|
? voice.unreached
|
|
158
180
|
: undefined;
|
|
159
181
|
}
|
|
160
|
-
/**
|
|
161
|
-
|
|
182
|
+
/**
|
|
183
|
+
* A projection ahead starts each subject from where the live mix holds it, its `since` worked out
|
|
184
|
+
* again where it was still ahead, since the copy's clock may have moved it.
|
|
185
|
+
*/
|
|
186
|
+
function carry(mix, voice, subject, c, copy) {
|
|
162
187
|
const live = recordOf(mix, voice, subject);
|
|
163
188
|
if (live === undefined)
|
|
164
189
|
return undefined;
|
|
165
190
|
const h = copyHeld(voice, live);
|
|
191
|
+
if (h.reaches && !(h.since <= mix.now)) {
|
|
192
|
+
h.since = c.sinceOf(copy, h.delay);
|
|
193
|
+
h.shown = c.shownOf(copy, h.since);
|
|
194
|
+
}
|
|
166
195
|
h.from = h.stepped;
|
|
167
196
|
return h;
|
|
168
197
|
}
|
|
@@ -170,8 +199,8 @@ function carry(mix, voice, subject) {
|
|
|
170
199
|
* A projection back starts each subject from the latest copy kept at or before `t`, else from its
|
|
171
200
|
* voice's start with fresh state, which is exact for a voice stepped at a fixed interval.
|
|
172
201
|
*/
|
|
173
|
-
function recall(mix, voice, subject, t) {
|
|
174
|
-
const live =
|
|
202
|
+
export function recall(mix, voice, subject, t, cut) {
|
|
203
|
+
const live = voice.left?.at(subject, cut) ?? recordOf(mix, voice, subject);
|
|
175
204
|
if (live === undefined)
|
|
176
205
|
return undefined;
|
|
177
206
|
const snap = live.snaps && last(live.snaps, t, true);
|
|
@@ -182,33 +211,18 @@ function recall(mix, voice, subject, t) {
|
|
|
182
211
|
return h;
|
|
183
212
|
}
|
|
184
213
|
const stepped = live.since < t ? live.since : t;
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
ticks: 0,
|
|
198
|
-
probed: Number.NaN,
|
|
199
|
-
delta: null,
|
|
200
|
-
phase: 0,
|
|
201
|
-
seeks: 0,
|
|
202
|
-
kept: null,
|
|
203
|
-
voice,
|
|
204
|
-
next: null,
|
|
205
|
-
version: Number.NaN,
|
|
206
|
-
loci: null,
|
|
207
|
-
slot: -1,
|
|
208
|
-
from: stepped,
|
|
209
|
-
unknown: voice.spec.from === 'current',
|
|
210
|
-
replay: live.inputs,
|
|
211
|
-
};
|
|
214
|
+
const h = record(voice, live.reaches, live.reaches && voice.patch.state
|
|
215
|
+
? voice.patch.state(subject)
|
|
216
|
+
: undefined);
|
|
217
|
+
h.delay = live.delay;
|
|
218
|
+
h.since = live.since;
|
|
219
|
+
h.shown = live.shown;
|
|
220
|
+
h.stepped = stepped;
|
|
221
|
+
h.rebuilt = voice.rebuilds;
|
|
222
|
+
h.from = stepped;
|
|
223
|
+
h.unknown = voice.spec.from === 'current';
|
|
224
|
+
h.replay = live.inputs;
|
|
225
|
+
return h;
|
|
212
226
|
}
|
|
213
227
|
/** Per channel, the least sure voice that fed it this frame. */
|
|
214
228
|
function doubts(mix, subject) {
|
|
@@ -231,19 +245,23 @@ function doubts(mix, subject) {
|
|
|
231
245
|
for (const slot of voice.slots)
|
|
232
246
|
out[mix.names[slot]] = 'held';
|
|
233
247
|
}
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
continue;
|
|
238
|
-
if (held.weight <= 0)
|
|
239
|
-
continue;
|
|
248
|
+
const feed = (voice, held) => {
|
|
249
|
+
if (voice.state === 'done' || held.weight <= 0)
|
|
250
|
+
return;
|
|
240
251
|
const d = doubtOf(mix, voice, held);
|
|
241
252
|
for (const slot of voice.slots) {
|
|
242
253
|
const name = mix.names[slot];
|
|
243
254
|
if (rank[d] > rank[out[name]])
|
|
244
255
|
out[name] = d;
|
|
245
256
|
}
|
|
246
|
-
}
|
|
257
|
+
};
|
|
258
|
+
for (let held = mix.chain(subject); held !== null; held = held.next)
|
|
259
|
+
if (held.voice !== null)
|
|
260
|
+
feed(held.voice, held);
|
|
261
|
+
// A voice sharing one record is in no chain; its record holds the weight the fold just gave.
|
|
262
|
+
for (const voice of mix.sharers)
|
|
263
|
+
if (voice.everyone !== null && voice.state !== 'pending')
|
|
264
|
+
feed(voice, voice.everyone);
|
|
247
265
|
return out;
|
|
248
266
|
}
|
|
249
267
|
function doubtOf(mix, voice, held) {
|
package/dist/pull.d.ts
CHANGED
|
@@ -3,3 +3,5 @@ import type { Columns } from './types.js';
|
|
|
3
3
|
/** After a probe, keeps the pose a retarget from `'current'` reads as the subject's last. */
|
|
4
4
|
export declare function keep<I, O>(this: Mixer<I, O>, subject: I, pose: O, out: O | undefined): void;
|
|
5
5
|
export declare function pull<I, O>(mix: Mixer<I, O>, subjects: Iterable<I>, into: Columns<O>): void;
|
|
6
|
+
/** Forgets the array `pull` last read, whose heads a seek has made stale. */
|
|
7
|
+
export declare function unpull<I, O>(mix: Mixer<I, O>): void;
|
package/dist/pull.js
CHANGED
|
@@ -174,3 +174,11 @@ function columnsOf(mix, into) {
|
|
|
174
174
|
}
|
|
175
175
|
return columns;
|
|
176
176
|
}
|
|
177
|
+
/** Forgets the array `pull` last read, whose heads a seek has made stale. */
|
|
178
|
+
export function unpull(mix) {
|
|
179
|
+
mix.pulled = [];
|
|
180
|
+
mix.pulledHeads = [];
|
|
181
|
+
mix.pulledSlots = new Int32Array(0);
|
|
182
|
+
mix.pulledVersion = Number.NaN;
|
|
183
|
+
mix.pulledRelinks = -1;
|
|
184
|
+
}
|
package/dist/qualify.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { numericOf } from './channels.js';
|
|
2
2
|
import { flush } from './columns.js';
|
|
3
3
|
import { crowdable } from './crowd.js';
|
|
4
|
+
import { weighAt } from './everyone.js';
|
|
4
5
|
import { wake } from './fill.js';
|
|
5
6
|
import { Lane } from './lane.js';
|
|
6
7
|
import { bury, compactSparse, join, regroup, retouchRow, sameCrowds } from './rows.js';
|
|
@@ -169,6 +170,9 @@ export function requalify(lanes, version) {
|
|
|
169
170
|
const ch = {
|
|
170
171
|
name: host.names[slot],
|
|
171
172
|
op: n.op,
|
|
173
|
+
plain: n.op !== 'last' && n.op !== 'own' && n.turn === undefined,
|
|
174
|
+
turn: n.turn ?? 0,
|
|
175
|
+
channel,
|
|
172
176
|
scalar,
|
|
173
177
|
rest: typeof rest === 'number' ? rest : rest[0],
|
|
174
178
|
axes: n.axes,
|
|
@@ -332,8 +336,12 @@ function leave(lanes, lane) {
|
|
|
332
336
|
const rec = lane.records[p];
|
|
333
337
|
const slot = lane.list[p];
|
|
334
338
|
const w = done ? undefined : lanes.reported(lane, slot);
|
|
335
|
-
if (rec !== undefined && w !== undefined)
|
|
336
|
-
|
|
339
|
+
if (rec !== undefined && w !== undefined) {
|
|
340
|
+
if (lane.voice.sharing)
|
|
341
|
+
weighAt(lane.voice, slot, w, lanes.cap);
|
|
342
|
+
else
|
|
343
|
+
rec.weight = w;
|
|
344
|
+
}
|
|
337
345
|
if (rec !== undefined && lane.motion !== undefined)
|
|
338
346
|
settle(lanes, lane, p, slot, rec);
|
|
339
347
|
}
|
package/dist/reading.d.ts
CHANGED
|
@@ -20,4 +20,9 @@ export declare const reading: {
|
|
|
20
20
|
* One count for every mix: a change to a spring in one mix refills the lanes of all of them.
|
|
21
21
|
*/
|
|
22
22
|
moved: number;
|
|
23
|
+
/**
|
|
24
|
+
* How many changes an `input` signal has reported, in any mix, so a fill can tell an input moved
|
|
25
|
+
* since it read it. One count for every mix, as `moved` is.
|
|
26
|
+
*/
|
|
27
|
+
inputs: number;
|
|
23
28
|
};
|
package/dist/reading.js
CHANGED
|
@@ -20,4 +20,9 @@ export const reading = {
|
|
|
20
20
|
* One count for every mix: a change to a spring in one mix refills the lanes of all of them.
|
|
21
21
|
*/
|
|
22
22
|
moved: 0,
|
|
23
|
+
/**
|
|
24
|
+
* How many changes an `input` signal has reported, in any mix, so a fill can tell an input moved
|
|
25
|
+
* since it read it. One count for every mix, as `moved` is.
|
|
26
|
+
*/
|
|
27
|
+
inputs: 0,
|
|
23
28
|
};
|
package/dist/record.d.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { Subject } from './voice.js';
|
|
2
|
+
/**
|
|
3
|
+
* A subject's record for `voice` holding every field one can come to hold, in one order, so every
|
|
4
|
+
* record in a mix shares one hidden class: a record built elsewhere, or given `base`, `snaps` or
|
|
5
|
+
* `from` later, took a class of its own, and a fold walking a chain of several kinds reads each
|
|
6
|
+
* field the slow way. The caller sets what it knows; nothing here takes a number, which a call V8
|
|
7
|
+
* does not inline would box.
|
|
8
|
+
*/
|
|
9
|
+
export declare function record(voice: object | null, reaches: boolean, state: unknown): Subject<unknown>;
|