@msb235/blits 0.14.0 → 0.15.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 +154 -0
- package/README.md +3 -3
- package/dist/ahead.d.ts +26 -0
- package/dist/ahead.js +264 -0
- package/dist/around.d.ts +11 -0
- package/dist/around.js +32 -0
- package/dist/bare.js +15 -7
- package/dist/blank.d.ts +10 -0
- package/dist/blank.js +26 -0
- package/dist/blend.d.ts +2 -1
- package/dist/blend.js +1 -6
- package/dist/bound.d.ts +70 -0
- package/dist/bound.js +351 -0
- package/dist/chain.d.ts +2 -1
- package/dist/chain.js +7 -5
- package/dist/changes.d.ts +24 -0
- package/dist/changes.js +189 -0
- package/dist/channels.d.ts +39 -13
- package/dist/channels.js +14 -5
- package/dist/color.d.ts +12 -10
- package/dist/color.js +25 -25
- package/dist/columns.js +7 -3
- package/dist/contribution.d.ts +30 -0
- package/dist/contribution.js +374 -0
- package/dist/copyout.d.ts +10 -0
- package/dist/copyout.js +69 -24
- package/dist/crowd.d.ts +8 -2
- package/dist/crowd.js +4 -5
- package/dist/cue.d.ts +8 -1
- package/dist/cue.js +39 -13
- package/dist/cut.d.ts +8 -0
- package/dist/cut.js +4 -0
- package/dist/drop.d.ts +9 -0
- package/dist/drop.js +84 -0
- package/dist/due.d.ts +24 -5
- package/dist/due.js +113 -33
- package/dist/easing.d.ts +4 -4
- package/dist/easing.js +3 -3
- package/dist/everyone.d.ts +3 -2
- package/dist/everyone.js +3 -3
- package/dist/fade.d.ts +2 -1
- package/dist/fade.js +2 -1
- package/dist/fill.d.ts +3 -2
- package/dist/fill.js +51 -19
- package/dist/fit.d.ts +44 -26
- package/dist/fit.js +10 -9
- package/dist/fold.d.ts +4 -37
- package/dist/fold.js +5 -513
- package/dist/gather.d.ts +12 -7
- package/dist/gather.js +179 -44
- package/dist/gone.js +2 -2
- package/dist/grid.d.ts +47 -0
- package/dist/grid.js +247 -0
- package/dist/handle.d.ts +2 -0
- package/dist/handle.js +14 -8
- package/dist/held.d.ts +2 -1
- package/dist/history.d.ts +6 -5
- package/dist/history.js +7 -9
- package/dist/hosts.js +9 -15
- package/dist/index.d.ts +5 -4
- package/dist/keyfill.d.ts +1 -1
- package/dist/keyfill.js +5 -3
- package/dist/keyrows.d.ts +12 -0
- package/dist/keyrows.js +99 -12
- package/dist/lane.d.ts +36 -6
- package/dist/lane.js +32 -1
- package/dist/lanes.d.ts +23 -8
- package/dist/lanes.js +29 -8
- package/dist/leavings.d.ts +2 -2
- package/dist/leavings.js +1 -1
- package/dist/lives.d.ts +47 -0
- package/dist/lives.js +180 -0
- package/dist/meet.d.ts +2 -2
- package/dist/meet.js +90 -4
- package/dist/mixer.d.ts +54 -36
- package/dist/mixer.js +53 -171
- package/dist/motion.d.ts +14 -118
- package/dist/motion.js +16 -8
- package/dist/motions.d.ts +62 -155
- package/dist/motions.js +56 -370
- package/dist/moved.d.ts +1 -1
- package/dist/moved.js +16 -7
- package/dist/named.d.ts +0 -1
- package/dist/named.js +20 -2
- package/dist/numbers.d.ts +2 -0
- package/dist/numbers.js +4 -0
- package/dist/owed.d.ts +2 -0
- package/dist/owed.js +4 -0
- package/dist/pack.d.ts +2 -1
- package/dist/pack.js +2 -3
- package/dist/patch.d.ts +7 -3
- package/dist/patch.js +6 -2
- package/dist/places.d.ts +53 -0
- package/dist/places.js +171 -0
- package/dist/plot.d.ts +12 -0
- package/dist/plot.js +52 -0
- package/dist/project.d.ts +4 -4
- package/dist/project.js +9 -212
- package/dist/pull.js +11 -8
- package/dist/qualify.js +84 -21
- package/dist/reading.d.ts +6 -0
- package/dist/reading.js +5 -0
- package/dist/record.d.ts +5 -1
- package/dist/record.js +3 -2
- package/dist/relink.d.ts +1 -1
- package/dist/restored.d.ts +2 -2
- package/dist/restored.js +1 -1
- package/dist/rests.d.ts +9 -0
- package/dist/rests.js +76 -0
- package/dist/revive.d.ts +3 -2
- package/dist/revive.js +2 -2
- package/dist/{locus.d.ts → role.d.ts} +10 -9
- package/dist/{locus.js → role.js} +3 -3
- package/dist/roles.d.ts +10 -0
- package/dist/roles.js +158 -0
- package/dist/roster.js +3 -3
- package/dist/rotation.d.ts +1 -1
- package/dist/rotation.js +1 -1
- package/dist/rows.js +1 -1
- package/dist/sample.d.ts +2 -2
- package/dist/sample.js +33 -7
- package/dist/seek.js +5 -5
- package/dist/setting.d.ts +27 -0
- package/dist/setting.js +55 -0
- package/dist/shared.d.ts +1 -1
- package/dist/shared.js +1 -1
- package/dist/signals.d.ts +64 -32
- package/dist/signals.js +20 -18
- package/dist/stack.d.ts +5 -5
- package/dist/stack.js +5 -5
- package/dist/standing.d.ts +2 -1
- package/dist/standing.js +3 -2
- package/dist/store.d.ts +9 -0
- package/dist/store.js +24 -0
- package/dist/strays.d.ts +2 -1
- package/dist/strays.js +20 -13
- package/dist/stretch.d.ts +112 -0
- package/dist/stretch.js +9 -0
- package/dist/subject.d.ts +142 -0
- package/dist/subject.js +1 -0
- package/dist/tape.d.ts +9 -5
- package/dist/tape.js +5 -1
- package/dist/then.d.ts +9 -0
- package/dist/then.js +57 -0
- package/dist/ticker.d.ts +4 -4
- package/dist/transport.d.ts +0 -8
- package/dist/transport.js +0 -4
- package/dist/turns.d.ts +4 -3
- package/dist/turns.js +4 -3
- package/dist/types/arc.d.ts +12 -11
- package/dist/types/channel.d.ts +12 -8
- package/dist/types/history.d.ts +35 -19
- package/dist/types/mix.d.ts +222 -163
- package/dist/types/motion.d.ts +151 -0
- package/dist/types/motion.js +1 -0
- package/dist/types/patch.d.ts +67 -34
- package/dist/types/place.d.ts +51 -36
- package/dist/types/transport.d.ts +20 -18
- package/dist/types/voice.d.ts +136 -70
- package/dist/types/watch.d.ts +88 -40
- package/dist/types.d.ts +1 -0
- package/dist/voice.d.ts +7 -143
- package/dist/voice.js +28 -79
- package/dist/warn.d.ts +2 -0
- package/dist/warn.js +1 -1
- package/dist/watch.d.ts +2 -2
- package/dist/watch.js +5 -3
- package/dist/wave.d.ts +4 -3
- package/dist/wave.js +3 -2
- package/dist/weigh.d.ts +7 -1
- package/dist/weigh.js +27 -0
- package/docs/schema.html +212 -34
- package/package.json +2 -2
package/dist/qualify.js
CHANGED
|
@@ -5,6 +5,7 @@ import { byId } from './due.js';
|
|
|
5
5
|
import { weighAt } from './everyone.js';
|
|
6
6
|
import { wake } from './fill.js';
|
|
7
7
|
import { Lane } from './lane.js';
|
|
8
|
+
import { reach } from './meet.js';
|
|
8
9
|
import { bury, compactSparse, join, regroup, retouchRow, sameCrowds } from './rows.js';
|
|
9
10
|
import { settle } from './sample.js';
|
|
10
11
|
/**
|
|
@@ -57,21 +58,21 @@ export function requalify(lanes, version) {
|
|
|
57
58
|
keepCallsOff(present, fits, host.channels);
|
|
58
59
|
const candidates = () => present.map((v, i) => ({ id: v.id, fits: fits[i], slots: v.slots }));
|
|
59
60
|
let { channels, voices } = qualify(numeric, candidates());
|
|
60
|
-
// A
|
|
61
|
+
// A role folds its members together, so they run on lanes all together or not at all.
|
|
61
62
|
for (let split = true; split;) {
|
|
62
63
|
split = false;
|
|
63
|
-
const
|
|
64
|
+
const roles = new Map();
|
|
64
65
|
present.forEach((v, i) => {
|
|
65
|
-
const name = v.spec.
|
|
66
|
+
const name = v.spec.role;
|
|
66
67
|
if (name === undefined)
|
|
67
68
|
return;
|
|
68
|
-
const at =
|
|
69
|
+
const at = roles.get(name);
|
|
69
70
|
if (at === undefined)
|
|
70
|
-
|
|
71
|
+
roles.set(name, [i]);
|
|
71
72
|
else
|
|
72
73
|
at.push(i);
|
|
73
74
|
});
|
|
74
|
-
for (const at of
|
|
75
|
+
for (const at of roles.values()) {
|
|
75
76
|
const on = at.filter((i) => voices.has(present[i].id)).length;
|
|
76
77
|
if (on === 0 || on === at.length)
|
|
77
78
|
continue;
|
|
@@ -97,18 +98,18 @@ export function requalify(lanes, version) {
|
|
|
97
98
|
const lane = lanes.byId.get(v.id) ?? new Lane(v);
|
|
98
99
|
v.laned = true;
|
|
99
100
|
if (lane.epoch === 0 && v.state !== 'pending')
|
|
100
|
-
|
|
101
|
+
opened(lanes, lane);
|
|
101
102
|
kept.push(lane);
|
|
102
103
|
}
|
|
103
104
|
lanes.lanes = kept;
|
|
104
|
-
lanes.
|
|
105
|
-
const
|
|
105
|
+
lanes.roles = [];
|
|
106
|
+
const roles = new Map();
|
|
106
107
|
for (const lane of kept) {
|
|
107
108
|
lane.group = null;
|
|
108
|
-
const name = lane.voice.spec.
|
|
109
|
+
const name = lane.voice.spec.role;
|
|
109
110
|
if (name === undefined)
|
|
110
111
|
continue;
|
|
111
|
-
let g =
|
|
112
|
+
let g = roles.get(name);
|
|
112
113
|
if (g === undefined) {
|
|
113
114
|
g = {
|
|
114
115
|
members: [],
|
|
@@ -117,13 +118,17 @@ export function requalify(lanes, version) {
|
|
|
117
118
|
sum: new Float64Array(lanes.cap),
|
|
118
119
|
values: [],
|
|
119
120
|
taken: [],
|
|
121
|
+
contiguous: false,
|
|
122
|
+
inline: false,
|
|
120
123
|
};
|
|
121
|
-
|
|
122
|
-
lanes.
|
|
124
|
+
roles.set(name, g);
|
|
125
|
+
lanes.roles.push(g);
|
|
123
126
|
}
|
|
124
127
|
g.members.push(lane);
|
|
125
128
|
lane.group = g;
|
|
126
129
|
}
|
|
130
|
+
for (const [name, g] of roles)
|
|
131
|
+
g.contiguous = contiguous(present, voices, name, g.members);
|
|
127
132
|
lanes.dense = kept
|
|
128
133
|
.filter((l) => l.voice.named === null && l.epoch > 0)
|
|
129
134
|
.sort((a, b) => a.epoch - b.epoch);
|
|
@@ -161,6 +166,7 @@ export function requalify(lanes, version) {
|
|
|
161
166
|
return;
|
|
162
167
|
}
|
|
163
168
|
lanes.laned = [];
|
|
169
|
+
lanes.copier = null;
|
|
164
170
|
lanes.bySlot = host.channels.map(() => undefined);
|
|
165
171
|
channels.forEach((on, slot) => {
|
|
166
172
|
if (!on)
|
|
@@ -170,10 +176,12 @@ export function requalify(lanes, version) {
|
|
|
170
176
|
// A rest-less channel's values are NaN until some voice passes the band: absent from the pose.
|
|
171
177
|
const rest = (channel.rest ?? Number.NaN);
|
|
172
178
|
const scalar = typeof rest === 'number' && n.axes === 1;
|
|
179
|
+
const op = n.folds ?? n.op;
|
|
173
180
|
const ch = {
|
|
174
181
|
name: host.names[slot],
|
|
175
|
-
op
|
|
176
|
-
plain:
|
|
182
|
+
op,
|
|
183
|
+
plain: op !== 'last' && op !== 'own' && n.turn === undefined,
|
|
184
|
+
straight: n.folds === undefined,
|
|
177
185
|
turn: n.turn ?? 0,
|
|
178
186
|
channel,
|
|
179
187
|
scalar,
|
|
@@ -263,7 +271,7 @@ export function open(lanes, v) {
|
|
|
263
271
|
lanes.wide = epoch;
|
|
264
272
|
return epoch;
|
|
265
273
|
}
|
|
266
|
-
for (const subject of v.named) {
|
|
274
|
+
for (const subject of v.named.list) {
|
|
267
275
|
const slot = lanes.host.slotOf(subject);
|
|
268
276
|
if (slot < 0 || slot >= lanes.cap)
|
|
269
277
|
continue;
|
|
@@ -318,8 +326,8 @@ export function retouch(lanes) {
|
|
|
318
326
|
}
|
|
319
327
|
if (v.state === 'done')
|
|
320
328
|
return qualify();
|
|
321
|
-
// A
|
|
322
|
-
if (v.spec.
|
|
329
|
+
// A role's members are on lanes together or not at all, so one joining may move the others.
|
|
330
|
+
if (v.spec.role !== undefined)
|
|
323
331
|
return qualify();
|
|
324
332
|
const laned = v.slots.some((slot) => lanes.bySlot[slot] !== undefined);
|
|
325
333
|
const fits = v.slots.length > 0 && lanes.host.fits(v);
|
|
@@ -341,6 +349,46 @@ export function retouch(lanes) {
|
|
|
341
349
|
lanes.subjects = [];
|
|
342
350
|
return true;
|
|
343
351
|
}
|
|
352
|
+
/** A lane's voice started playing: its epoch, and room up front where it reaches every subject. */
|
|
353
|
+
function opened(lanes, lane) {
|
|
354
|
+
const v = lane.voice;
|
|
355
|
+
lane.epoch = open(lanes, v);
|
|
356
|
+
if (v.named === null && v.spec.target === undefined) {
|
|
357
|
+
lane.reserve(lanes.live, lanes.cap);
|
|
358
|
+
if (placesAll(lanes, lane))
|
|
359
|
+
placeAll(lanes, lane);
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
/**
|
|
363
|
+
* Whether a lane just opened can take every numbered subject now, its first call or read for each
|
|
364
|
+
* made by the fill: a stateless fn or keys voice sharing one record, which every subject would meet
|
|
365
|
+
* at its next probe, in a host that probed every subject last frame. One by one, each probe would
|
|
366
|
+
* meet it and make its first call or read there (`foldNow` in `meet.ts`).
|
|
367
|
+
*/
|
|
368
|
+
function placesAll(lanes, lane) {
|
|
369
|
+
const v = lane.voice;
|
|
370
|
+
return (v.sharing &&
|
|
371
|
+
v.everyone !== null &&
|
|
372
|
+
lane.motion === undefined &&
|
|
373
|
+
!v.keeping &&
|
|
374
|
+
typeof v.spec.weight !== 'function' &&
|
|
375
|
+
lane.list.length === 0 &&
|
|
376
|
+
lanes.lastDistinct >= lanes.live);
|
|
377
|
+
}
|
|
378
|
+
/** Gives every numbered subject a position on a lane `placesAll` passed. */
|
|
379
|
+
function placeAll(lanes, lane) {
|
|
380
|
+
const rec = lane.voice.everyone;
|
|
381
|
+
const numbers = lanes.numbers;
|
|
382
|
+
const size = Math.min(numbers.size, lanes.cap);
|
|
383
|
+
for (let slot = 0; slot < size; slot++) {
|
|
384
|
+
if (!numbers.has(slot))
|
|
385
|
+
continue;
|
|
386
|
+
lane.add(slot, rec);
|
|
387
|
+
if (lane.idle)
|
|
388
|
+
reach(lanes, slot, 1);
|
|
389
|
+
}
|
|
390
|
+
lane.every = true;
|
|
391
|
+
}
|
|
344
392
|
/**
|
|
345
393
|
* A new voice that fits and writes only laned channels, and that no crowd takes, gets a lane at
|
|
346
394
|
* the end of the order, its id being past every other: what a qualify would give it, since such a
|
|
@@ -350,7 +398,7 @@ function enlane(lanes, v) {
|
|
|
350
398
|
const lane = new Lane(v);
|
|
351
399
|
v.laned = true;
|
|
352
400
|
if (v.state !== 'pending')
|
|
353
|
-
|
|
401
|
+
opened(lanes, lane);
|
|
354
402
|
lane.chans = v.slots.map((s) => lanes.bySlot[s]);
|
|
355
403
|
lane.values = lane.chans.map(() => undefined);
|
|
356
404
|
lanes.lanes.push(lane);
|
|
@@ -360,13 +408,13 @@ function enlane(lanes, v) {
|
|
|
360
408
|
}
|
|
361
409
|
/**
|
|
362
410
|
* A laned voice that started playing or finished, as a qualify would take it: a lane leaving
|
|
363
|
-
* cannot lane or unlane another channel. False for a
|
|
411
|
+
* cannot lane or unlane another channel. False for a role member, which a qualify must take.
|
|
364
412
|
*/
|
|
365
413
|
function relane(lanes, lane) {
|
|
366
414
|
const v = lane.voice;
|
|
367
415
|
if (v.state !== 'done') {
|
|
368
416
|
if (lane.epoch === 0 && v.state !== 'pending') {
|
|
369
|
-
|
|
417
|
+
opened(lanes, lane);
|
|
370
418
|
if (v.named === null)
|
|
371
419
|
lanes.dense.push(lane);
|
|
372
420
|
}
|
|
@@ -410,3 +458,18 @@ function leave(lanes, lane) {
|
|
|
410
458
|
}
|
|
411
459
|
lane.voice.laned = false;
|
|
412
460
|
}
|
|
461
|
+
/**
|
|
462
|
+
* Whether a role's members, in the order lanes run, have no laned or crowded voice of another
|
|
463
|
+
* role or none between them, so its every subject folds at its first member's turn.
|
|
464
|
+
*/
|
|
465
|
+
function contiguous(present, voices, name, members) {
|
|
466
|
+
for (let i = 1; i < members.length; i++)
|
|
467
|
+
if (members[i].voice.id <= members[i - 1].voice.id)
|
|
468
|
+
return false;
|
|
469
|
+
const lo = members[0].voice.id;
|
|
470
|
+
const hi = members[members.length - 1].voice.id;
|
|
471
|
+
for (const v of present)
|
|
472
|
+
if (v.id > lo && v.id < hi && voices.has(v.id) && v.spec.role !== name)
|
|
473
|
+
return false;
|
|
474
|
+
return true;
|
|
475
|
+
}
|
package/dist/reading.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { Cut } from './cut.js';
|
|
1
2
|
/**
|
|
2
3
|
* Internal: what kind of read the mix is making right now, for patches that keep state of their own
|
|
3
4
|
* outside the mix. Not exported from the package.
|
|
@@ -32,4 +33,9 @@ export declare const reading: {
|
|
|
32
33
|
* commits none. Null for every other read.
|
|
33
34
|
*/
|
|
34
35
|
ahead: Map<object, Map<unknown, unknown[]>> | null;
|
|
36
|
+
/**
|
|
37
|
+
* While a read back under history reads: the frame it reads, so a motion patch reads the run a
|
|
38
|
+
* subject had then and only the changes made by then. Null for every other read.
|
|
39
|
+
*/
|
|
40
|
+
back: Cut | null;
|
|
35
41
|
};
|
package/dist/reading.js
CHANGED
|
@@ -32,4 +32,9 @@ export const reading = {
|
|
|
32
32
|
* commits none. Null for every other read.
|
|
33
33
|
*/
|
|
34
34
|
ahead: null,
|
|
35
|
+
/**
|
|
36
|
+
* While a read back under history reads: the frame it reads, so a motion patch reads the run a
|
|
37
|
+
* subject had then and only the changes made by then. Null for every other read.
|
|
38
|
+
*/
|
|
39
|
+
back: null,
|
|
35
40
|
};
|
package/dist/record.d.ts
CHANGED
|
@@ -1,4 +1,8 @@
|
|
|
1
|
-
import type { Subject } from './
|
|
1
|
+
import type { Subject } from './subject.js';
|
|
2
|
+
/** Whether a record was left at rest during a handover. */
|
|
3
|
+
export declare const isRested: (h: {
|
|
4
|
+
restedAt: number;
|
|
5
|
+
}) => boolean;
|
|
2
6
|
/** A record with every field written out, optional ones too, as every record in a mix is built. */
|
|
3
7
|
export type WholeRecord = {
|
|
4
8
|
[K in Field]: Subject<unknown>[K];
|
package/dist/record.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
/** Whether a record was left at rest during a handover. */
|
|
2
|
+
export const isRested = (h) => !Number.isNaN(h.restedAt);
|
|
1
3
|
/**
|
|
2
4
|
* A subject's record for `voice` holding every field one can come to hold, in one order, so every
|
|
3
5
|
* record in a mix shares one hidden class: a record built elsewhere, or given `base`, `snaps` or
|
|
@@ -12,7 +14,6 @@ export function record(voice, reaches, state) {
|
|
|
12
14
|
since: 0,
|
|
13
15
|
shown: 0,
|
|
14
16
|
weight: 0,
|
|
15
|
-
rested: false,
|
|
16
17
|
restedAt: Number.NaN,
|
|
17
18
|
bands: null,
|
|
18
19
|
state,
|
|
@@ -37,7 +38,7 @@ export function record(voice, reaches, state) {
|
|
|
37
38
|
voice,
|
|
38
39
|
next: null,
|
|
39
40
|
version: Number.NaN,
|
|
40
|
-
|
|
41
|
+
roles: null,
|
|
41
42
|
slot: -1,
|
|
42
43
|
run: -1,
|
|
43
44
|
freed: -1,
|
package/dist/relink.d.ts
CHANGED
package/dist/restored.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
+
import type { Cut } from './cut.js';
|
|
1
2
|
import type { Leavings } from './leavings.js';
|
|
2
3
|
import { Store } from './store.js';
|
|
3
|
-
import type {
|
|
4
|
-
import type { Subject } from './voice.js';
|
|
4
|
+
import type { Subject } from './subject.js';
|
|
5
5
|
/**
|
|
6
6
|
* The seeks back after the one a record was last put back, written or deleted at, which every
|
|
7
7
|
* record stamped then shares. The next seek fills it; a later seek to as early a moment joins it
|
package/dist/restored.js
CHANGED
|
@@ -138,7 +138,7 @@ export class Restored extends Store {
|
|
|
138
138
|
}
|
|
139
139
|
/** `get`, given what `own` holds for `key`. */
|
|
140
140
|
from(key, own) {
|
|
141
|
-
const mark = own
|
|
141
|
+
const mark = own?.reaches;
|
|
142
142
|
const at = (mark ? own.restored : this.at.get(key)) ?? this.first;
|
|
143
143
|
const now = this.stamp;
|
|
144
144
|
if (at === now)
|
package/dist/rests.d.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { Mixer } from './mixer.js';
|
|
2
|
+
import type { Voice } from './voice.js';
|
|
3
|
+
export declare function atRest<I, O>(this: Mixer<I, O>, subject: I): boolean;
|
|
4
|
+
export declare function rests<I, O>(this: Mixer<I, O>, pose: O): boolean;
|
|
5
|
+
export declare function restful<I, O>(this: Mixer<I, O>): boolean;
|
|
6
|
+
/** Whether a voice will change no pose from `now` on, short of a change made to it. */
|
|
7
|
+
export declare function still<I, O>(this: Mixer<I, O>, voice: Voice<I, O>, now: number): boolean;
|
|
8
|
+
export declare function restsLaned<I, O>(this: Mixer<I, O>): boolean;
|
|
9
|
+
export declare function restsKey<I, O>(this: Mixer<I, O>): number;
|
package/dist/rests.js
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { near } from './fold.js';
|
|
2
|
+
import { Store } from './store.js';
|
|
3
|
+
export function atRest(subject) {
|
|
4
|
+
this.restStamps ??= new Store();
|
|
5
|
+
const head = this.linked(subject);
|
|
6
|
+
const laned = this.linkedLaned;
|
|
7
|
+
const lanes = this.lanes;
|
|
8
|
+
// Every voice on lanes and nothing to clamp: the pose a fold would make is the lanes' values.
|
|
9
|
+
if (laned && this.restsLaned()) {
|
|
10
|
+
const slot = head.slot;
|
|
11
|
+
if (!lanes.owes(slot))
|
|
12
|
+
return lanes.rests(slot);
|
|
13
|
+
}
|
|
14
|
+
// A probe this frame answers for the pose it gave the host.
|
|
15
|
+
const since = (this.restStamps.get(subject) ?? Number.NaN) - 2 * this.restsKey();
|
|
16
|
+
if (since >= 0)
|
|
17
|
+
return since === 1;
|
|
18
|
+
this.restScratch ??= this.pose();
|
|
19
|
+
const pose = this.foldWith(subject, this.restScratch, head, laned, true);
|
|
20
|
+
return this.rests((this.bounded.length === 0 ? pose : this.clamp(pose)));
|
|
21
|
+
}
|
|
22
|
+
export function rests(pose) {
|
|
23
|
+
for (let i = 0; i < this.names.length; i++) {
|
|
24
|
+
const channel = this.channels[i];
|
|
25
|
+
const value = pose[this.names[i]];
|
|
26
|
+
if (channel.rest === undefined) {
|
|
27
|
+
if (value !== undefined)
|
|
28
|
+
return false;
|
|
29
|
+
}
|
|
30
|
+
else if (!near(value, channel.rest))
|
|
31
|
+
return false;
|
|
32
|
+
}
|
|
33
|
+
return true;
|
|
34
|
+
}
|
|
35
|
+
export function restful() {
|
|
36
|
+
// Standing still, a voice changes no pose but by its weight signal, or by an anchor or start
|
|
37
|
+
// that host time may yet reach.
|
|
38
|
+
if (this.pace?.stopped(this.u))
|
|
39
|
+
return this.cued.every((v) => v.state === 'done' ||
|
|
40
|
+
(v.state !== 'pending' &&
|
|
41
|
+
typeof v.spec.weight !== 'function' &&
|
|
42
|
+
v.spec.anchor === undefined));
|
|
43
|
+
return this.cued.every((v) => this.still(v, this.now));
|
|
44
|
+
}
|
|
45
|
+
/** Whether a voice will change no pose from `now` on, short of a change made to it. */
|
|
46
|
+
export function still(voice, now) {
|
|
47
|
+
if (voice.state === 'done')
|
|
48
|
+
return true;
|
|
49
|
+
if (voice.state !== 'frozen' && voice.state !== 'live')
|
|
50
|
+
return false;
|
|
51
|
+
if (typeof voice.spec.weight === 'function' || voice.parts !== null)
|
|
52
|
+
return false;
|
|
53
|
+
const fadeIn = this.reducedNow ? 0 : (voice.fade.in ?? 0);
|
|
54
|
+
if (fadeIn > 0 && now - this.sinceOf(voice, voice.latest) < fadeIn)
|
|
55
|
+
return false;
|
|
56
|
+
if (voice.state === 'frozen' || voice.holding !== null)
|
|
57
|
+
return true;
|
|
58
|
+
const motion = voice.motion;
|
|
59
|
+
return motion !== undefined && voice.elapsedAt(now) >= voice.latest && motion.landed();
|
|
60
|
+
}
|
|
61
|
+
export function restsLaned() {
|
|
62
|
+
return this.lanes.whole && this.bounded.length === 0;
|
|
63
|
+
}
|
|
64
|
+
export function restsKey() {
|
|
65
|
+
if (this.frame !== this.restsFrame ||
|
|
66
|
+
this.version !== this.restsVersion ||
|
|
67
|
+
this.relinks !== this.restsRelinks ||
|
|
68
|
+
this.stirs !== this.restsStirs) {
|
|
69
|
+
this.restsEpoch++;
|
|
70
|
+
this.restsFrame = this.frame;
|
|
71
|
+
this.restsVersion = this.version;
|
|
72
|
+
this.restsRelinks = this.relinks;
|
|
73
|
+
this.restsStirs = this.stirs;
|
|
74
|
+
}
|
|
75
|
+
return this.restsEpoch;
|
|
76
|
+
}
|
package/dist/revive.d.ts
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import type { Mixer } from './mixer.js';
|
|
2
|
-
import type { Run } from './motions.js';
|
|
3
2
|
import type { Past } from './origin.js';
|
|
4
3
|
import { type PackedRecord } from './pack.js';
|
|
5
4
|
import { type Key } from './paging.js';
|
|
5
|
+
import type { Run } from './stretch.js';
|
|
6
|
+
import type { Parted, Parting } from './subject.js';
|
|
6
7
|
import type { Described } from './types.js';
|
|
7
|
-
import { type Controls, type
|
|
8
|
+
import { type Controls, type Ramp, type Rise, Voice } from './voice.js';
|
|
8
9
|
/** A voice that has left, as plain data: the descriptor it was cued with, its controls and its records. */
|
|
9
10
|
interface PackedVoice {
|
|
10
11
|
as: Described;
|
package/dist/revive.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { playable } from './cue.js';
|
|
1
|
+
import { playable, roleOf } from './cue.js';
|
|
2
2
|
import { VoiceHandle } from './handle.js';
|
|
3
3
|
import { handle } from './hosts.js';
|
|
4
4
|
import { Leavings } from './leavings.js';
|
|
@@ -120,7 +120,7 @@ export function reviveVoice(mix, id, d) {
|
|
|
120
120
|
const revive = mix.opts.history?.revive;
|
|
121
121
|
if (revive === undefined)
|
|
122
122
|
throw new Error('blits: a paged voice needs history.revive');
|
|
123
|
-
const spec = { ...revive(d.as), as: d.as };
|
|
123
|
+
const spec = roleOf(mix, { ...revive(d.as), as: d.as });
|
|
124
124
|
playable(mix, spec);
|
|
125
125
|
const voice = new Voice(id, spec, spec.patch, spec.fade ?? {}, Number.NEGATIVE_INFINITY, d.start, mix.slotOf, mix.channels, mix.opts.host, mix.send, null);
|
|
126
126
|
voice.clocks = d.clocks;
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import type { Mixer } from './mixer.js';
|
|
2
|
-
import type { Subject
|
|
2
|
+
import type { Subject } from './subject.js';
|
|
3
|
+
import type { Voice } from './voice.js';
|
|
3
4
|
/**
|
|
4
|
-
* What a fold with a
|
|
5
|
-
* voice order, each a voice's own or a
|
|
6
|
-
* slot, a
|
|
5
|
+
* What a fold with a role in play gathers, kept from one fold to the next: the contributions in
|
|
6
|
+
* voice order, each a voice's own or a role's first member; every role member; and per channel
|
|
7
|
+
* slot, a role's folding value, the weight taken into it and the array it owns.
|
|
7
8
|
*/
|
|
8
|
-
export interface
|
|
9
|
+
export interface RoleScratch<I, O> {
|
|
9
10
|
n: number;
|
|
10
11
|
voices: (Voice<I, O> | null)[];
|
|
11
12
|
helds: (Subject<unknown> | null)[];
|
|
@@ -27,17 +28,17 @@ export interface LocusScratch<I, O> {
|
|
|
27
28
|
touched: number[];
|
|
28
29
|
owned: (number[] | undefined)[];
|
|
29
30
|
}
|
|
30
|
-
export declare function
|
|
31
|
+
export declare function roleScratch<I, O>(channels: number): RoleScratch<I, O>;
|
|
31
32
|
/**
|
|
32
33
|
* `value` copied into the array `k` owns for the slot, as long as the channel's rest, where a
|
|
33
34
|
* shorter value leaves the rest's own fill: what the channel's fold and lerp read past its end.
|
|
34
35
|
*/
|
|
35
|
-
export declare function own<I, O>(k:
|
|
36
|
+
export declare function own<I, O>(k: RoleScratch<I, O>, slot: number, value: number[], rest: number[]): number[];
|
|
36
37
|
/**
|
|
37
|
-
* Folds one
|
|
38
|
+
* Folds one role's members into one contribution through each channel's own `lerp`: its value per
|
|
38
39
|
* channel goes in `k.values`, the channels in `k.touched` in the order first met, and its weight
|
|
39
40
|
* is returned. A stock `last` channel has nothing to lerp, so it takes its heaviest member's value,
|
|
40
41
|
* the later cued of two as heavy. A channel folding by scale takes its value in an array of `k`'s own, lerped in
|
|
41
42
|
* place, since the fold only reads it.
|
|
42
43
|
*/
|
|
43
|
-
export declare function
|
|
44
|
+
export declare function foldRole<I, O>(this: Mixer<I, O>, k: RoleScratch<I, O>, group: number): number;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { first } from './clone.js';
|
|
2
|
-
export function
|
|
2
|
+
export function roleScratch(channels) {
|
|
3
3
|
return {
|
|
4
4
|
n: 0,
|
|
5
5
|
voices: [],
|
|
@@ -36,13 +36,13 @@ export function own(k, slot, value, rest) {
|
|
|
36
36
|
return out;
|
|
37
37
|
}
|
|
38
38
|
/**
|
|
39
|
-
* Folds one
|
|
39
|
+
* Folds one role's members into one contribution through each channel's own `lerp`: its value per
|
|
40
40
|
* channel goes in `k.values`, the channels in `k.touched` in the order first met, and its weight
|
|
41
41
|
* is returned. A stock `last` channel has nothing to lerp, so it takes its heaviest member's value,
|
|
42
42
|
* the later cued of two as heavy. A channel folding by scale takes its value in an array of `k`'s own, lerped in
|
|
43
43
|
* place, since the fold only reads it.
|
|
44
44
|
*/
|
|
45
|
-
export function
|
|
45
|
+
export function foldRole(k, group) {
|
|
46
46
|
k.touched.length = 0;
|
|
47
47
|
let sum = 0;
|
|
48
48
|
for (let m = 0; m < k.m; m++)
|
package/dist/roles.d.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { Mixer } from './mixer.js';
|
|
2
|
+
import type { Subject } from './subject.js';
|
|
3
|
+
import type { Voice } from './voice.js';
|
|
4
|
+
/**
|
|
5
|
+
* Whether a fold's next voice is its chain's next record, `held`, and not the next voice sharing
|
|
6
|
+
* one record, `sharers[q]`: the two lists are each in voice order, and a fold takes them as one.
|
|
7
|
+
*/
|
|
8
|
+
export declare function chainFirst<I, O>(held: Subject<unknown> | null, sharers: readonly Voice<I, O>[], q: number): boolean;
|
|
9
|
+
/** `foldWith`'s voices with a role in play: each role folds at its first member's place. */
|
|
10
|
+
export declare function foldRoles<I, O>(this: Mixer<I, O>, subject: I, pose: Record<string, unknown>, head: Subject<unknown>, laned: boolean, dry: boolean, except: number | undefined, owed: number): O;
|
package/dist/roles.js
ADDED
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import { first } from './clone.js';
|
|
2
|
+
import { open as openRecord } from './everyone.js';
|
|
3
|
+
import { merged, unband } from './fold.js';
|
|
4
|
+
import { roleScratch } from './role.js';
|
|
5
|
+
// V8 reads a local const as a constant, where it reads an import from its module on every call.
|
|
6
|
+
const open = openRecord;
|
|
7
|
+
/**
|
|
8
|
+
* Whether a fold's next voice is its chain's next record, `held`, and not the next voice sharing
|
|
9
|
+
* one record, `sharers[q]`: the two lists are each in voice order, and a fold takes them as one.
|
|
10
|
+
*/
|
|
11
|
+
export function chainFirst(held, sharers, q) {
|
|
12
|
+
if (held === null)
|
|
13
|
+
return false;
|
|
14
|
+
if (q >= sharers.length || held.voice === null)
|
|
15
|
+
return true;
|
|
16
|
+
const next = sharers[q];
|
|
17
|
+
return next.id > held.voice.id;
|
|
18
|
+
}
|
|
19
|
+
/** `foldWith`'s voices with a role in play: each role folds at its first member's place. */
|
|
20
|
+
export function foldRoles(subject, pose, head, laned, dry, except, owed) {
|
|
21
|
+
const now = this.now;
|
|
22
|
+
// A patch reading the pose can fold again from inside this one, so each depth has its own.
|
|
23
|
+
let k = this.roleScratch[this.roleDepth];
|
|
24
|
+
if (k === undefined) {
|
|
25
|
+
k = roleScratch(this.channels.length);
|
|
26
|
+
this.roleScratch[this.roleDepth] = k;
|
|
27
|
+
}
|
|
28
|
+
this.roleDepth++;
|
|
29
|
+
try {
|
|
30
|
+
k.n = 0;
|
|
31
|
+
k.m = 0;
|
|
32
|
+
k.names.length = 0;
|
|
33
|
+
const sharers = this.sharers;
|
|
34
|
+
let q = 0;
|
|
35
|
+
let at = head;
|
|
36
|
+
while (at !== null || q < sharers.length) {
|
|
37
|
+
const mine = chainFirst(at, sharers, q);
|
|
38
|
+
let voice;
|
|
39
|
+
let rec;
|
|
40
|
+
if (mine) {
|
|
41
|
+
rec = at;
|
|
42
|
+
voice = rec.voice;
|
|
43
|
+
at = rec.next;
|
|
44
|
+
}
|
|
45
|
+
else {
|
|
46
|
+
voice = sharers[q++];
|
|
47
|
+
rec = voice.state === 'pending' ? null : voice.everyone;
|
|
48
|
+
}
|
|
49
|
+
if (voice === null || rec === null)
|
|
50
|
+
continue;
|
|
51
|
+
if (laned && voice.laned && !this.owedBy(owed, voice))
|
|
52
|
+
continue;
|
|
53
|
+
if (voice.id === except || voice.state === 'done')
|
|
54
|
+
continue;
|
|
55
|
+
const held = rec;
|
|
56
|
+
if (!mine)
|
|
57
|
+
open(voice, held, head.slot);
|
|
58
|
+
const delta = this.read(voice, subject, now, dry, held);
|
|
59
|
+
if (owed >= 0 && voice.laned)
|
|
60
|
+
this.lanes.paid(voice.id, owed, held.weight);
|
|
61
|
+
if (!mine)
|
|
62
|
+
q = this.shared(voice, head.slot, held, q);
|
|
63
|
+
if (delta === null)
|
|
64
|
+
continue;
|
|
65
|
+
const role = voice.spec.role;
|
|
66
|
+
let group = -1;
|
|
67
|
+
if (role !== undefined) {
|
|
68
|
+
group = k.names.indexOf(role);
|
|
69
|
+
const m = k.m++;
|
|
70
|
+
k.mVoices[m] = voice;
|
|
71
|
+
k.mDeltas[m] = delta;
|
|
72
|
+
k.mWeights[m] = this.w;
|
|
73
|
+
if (group >= 0) {
|
|
74
|
+
k.mGroups[m] = group;
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
group = k.names.length;
|
|
78
|
+
k.names.push(role);
|
|
79
|
+
k.mGroups[m] = group;
|
|
80
|
+
}
|
|
81
|
+
const n = k.n++;
|
|
82
|
+
k.voices[n] = voice;
|
|
83
|
+
k.helds[n] = held;
|
|
84
|
+
k.deltas[n] = delta;
|
|
85
|
+
k.weights[n] = role === undefined ? this.w : 0;
|
|
86
|
+
k.groups[n] = group;
|
|
87
|
+
}
|
|
88
|
+
let bands = head.roles;
|
|
89
|
+
if (bands === null) {
|
|
90
|
+
bands = new Map();
|
|
91
|
+
head.roles = bands;
|
|
92
|
+
}
|
|
93
|
+
for (let n = 0; n < k.n; n++) {
|
|
94
|
+
const group = k.groups[n];
|
|
95
|
+
if (group < 0) {
|
|
96
|
+
const weight = k.weights[n];
|
|
97
|
+
if (weight > 0)
|
|
98
|
+
this.apply(pose, k.voices[n], k.helds[n], k.deltas[n], weight);
|
|
99
|
+
else
|
|
100
|
+
unband(k.helds[n]);
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
103
|
+
const weight = this.foldRole(k, group);
|
|
104
|
+
const name = k.names[group];
|
|
105
|
+
if (!(weight > 0)) {
|
|
106
|
+
bands.get(name)?.fill(2);
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
109
|
+
let on = bands.get(name);
|
|
110
|
+
if (on === undefined) {
|
|
111
|
+
on = new Uint8Array(this.channels.length);
|
|
112
|
+
bands.set(name, on);
|
|
113
|
+
}
|
|
114
|
+
for (let t = 0; t < k.touched.length; t++) {
|
|
115
|
+
const slot = k.touched[t];
|
|
116
|
+
const value = k.values[slot];
|
|
117
|
+
k.values[slot] = undefined;
|
|
118
|
+
const key = this.names[slot];
|
|
119
|
+
const channel = this.channels[slot];
|
|
120
|
+
// A channel folds at the weight of the members that wrote it, not the whole role.
|
|
121
|
+
const taken = k.taken[slot];
|
|
122
|
+
const w = taken > 1 ? 1 : taken;
|
|
123
|
+
if (channel.rest !== undefined && channel.scale) {
|
|
124
|
+
// pose[key] is the copy of rest this fold made, so it is ours to write into.
|
|
125
|
+
pose[key] = channel.fold
|
|
126
|
+
? channel.fold(pose[key], value, w)
|
|
127
|
+
: merged(channel, pose[key], channel.scale(value, w));
|
|
128
|
+
continue;
|
|
129
|
+
}
|
|
130
|
+
const band = on[slot];
|
|
131
|
+
const passes = this.passes(band === 0 ? undefined : band === 1, w);
|
|
132
|
+
on[slot] = passes ? 1 : 2;
|
|
133
|
+
if (passes)
|
|
134
|
+
pose[key] = pose[key] === undefined ? first(value) : merged(channel, pose[key], value);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
return pose;
|
|
138
|
+
}
|
|
139
|
+
finally {
|
|
140
|
+
for (let n = 0; n < k.n; n++) {
|
|
141
|
+
k.voices[n] = null;
|
|
142
|
+
k.helds[n] = null;
|
|
143
|
+
k.deltas[n] = null;
|
|
144
|
+
}
|
|
145
|
+
for (let m = 0; m < k.m; m++) {
|
|
146
|
+
k.mVoices[m] = null;
|
|
147
|
+
k.mDeltas[m] = null;
|
|
148
|
+
}
|
|
149
|
+
// Left set only where a channel's lerp threw partway, which the next fold must not inherit.
|
|
150
|
+
for (let t = 0; t < k.touched.length; t++) {
|
|
151
|
+
const slot = k.touched[t];
|
|
152
|
+
k.met[slot] = 0;
|
|
153
|
+
k.values[slot] = undefined;
|
|
154
|
+
}
|
|
155
|
+
k.touched.length = 0;
|
|
156
|
+
this.roleDepth--;
|
|
157
|
+
}
|
|
158
|
+
}
|
package/dist/roster.js
CHANGED
|
@@ -2,8 +2,8 @@ import { index, unindex } from './chain.js';
|
|
|
2
2
|
import { detour } from './everyone.js';
|
|
3
3
|
import { Store } from './store.js';
|
|
4
4
|
function tally(mix, voice, by) {
|
|
5
|
-
if (voice.spec.
|
|
6
|
-
mix.
|
|
5
|
+
if (voice.spec.role !== undefined)
|
|
6
|
+
mix.roleVoices += by;
|
|
7
7
|
const e = voice.spec.earliest;
|
|
8
8
|
if (voice.spec.anchor !== undefined || (e !== undefined && typeof e !== 'number'))
|
|
9
9
|
mix.anchored += by;
|
|
@@ -36,7 +36,7 @@ export function recount(mix) {
|
|
|
36
36
|
mix.naming = 0;
|
|
37
37
|
mix.general = [];
|
|
38
38
|
mix.sharers = [];
|
|
39
|
-
mix.
|
|
39
|
+
mix.roleVoices = 0;
|
|
40
40
|
mix.anchored = 0;
|
|
41
41
|
for (const voice of mix.cued) {
|
|
42
42
|
index(mix, voice);
|