@msb235/blits 0.15.0 → 0.16.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 +79 -0
- package/README.md +1 -1
- package/dist/ahead.js +5 -4
- package/dist/bare.js +22 -7
- package/dist/chain.js +2 -3
- package/dist/color.d.ts +36 -14
- package/dist/color.js +186 -58
- package/dist/columns.d.ts +3 -12
- package/dist/columns.js +56 -41
- package/dist/crowd.d.ts +8 -10
- package/dist/crowd.js +17 -18
- package/dist/cue.js +7 -4
- package/dist/delay.d.ts +3 -0
- package/dist/delay.js +7 -0
- package/dist/due.js +1 -1
- package/dist/easing.d.ts +2 -0
- package/dist/easing.js +4 -0
- package/dist/everyone.d.ts +22 -6
- package/dist/everyone.js +47 -8
- package/dist/fade.d.ts +11 -0
- package/dist/fade.js +44 -8
- package/dist/fill.js +1 -1
- package/dist/fold.js +4 -7
- package/dist/held.js +5 -5
- package/dist/hosts.d.ts +7 -0
- package/dist/hosts.js +18 -5
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -1
- package/dist/lane.d.ts +2 -5
- package/dist/lane.js +14 -10
- package/dist/lanes.d.ts +33 -5
- package/dist/lanes.js +34 -28
- package/dist/lch.d.ts +23 -0
- package/dist/lch.js +126 -0
- package/dist/meet.js +5 -3
- package/dist/mixer.d.ts +6 -2
- package/dist/mixer.js +7 -0
- package/dist/motions.d.ts +4 -1
- package/dist/motions.js +16 -2
- package/dist/move.js +15 -10
- package/dist/named.d.ts +6 -1
- package/dist/named.js +29 -1
- package/dist/numbers.d.ts +5 -0
- package/dist/numbers.js +29 -0
- package/dist/owner.d.ts +7 -2
- package/dist/owner.js +13 -3
- package/dist/project.js +3 -8
- package/dist/pull.d.ts +11 -0
- package/dist/pull.js +18 -4
- package/dist/qualify.js +10 -11
- package/dist/renumber.d.ts +10 -0
- package/dist/renumber.js +75 -0
- package/dist/roles.js +1 -3
- package/dist/roster.d.ts +6 -0
- package/dist/roster.js +8 -1
- package/dist/rows.js +27 -19
- package/dist/sample.js +2 -1
- package/dist/seat.d.ts +17 -0
- package/dist/seat.js +25 -0
- package/dist/seek.js +1 -13
- package/dist/shared.d.ts +1 -1
- package/dist/shared.js +1 -1
- package/dist/spans.d.ts +4 -1
- package/dist/spans.js +6 -3
- package/dist/strays.js +3 -2
- package/dist/touch.js +1 -2
- package/dist/trace.d.ts +4 -0
- package/dist/trace.js +255 -0
- package/dist/turns.js +2 -1
- package/dist/types/mix.d.ts +15 -0
- package/dist/types/trace.d.ts +51 -0
- package/dist/types/trace.js +1 -0
- package/dist/types/voice.d.ts +13 -7
- package/dist/types.d.ts +1 -0
- package/dist/unreached.d.ts +2 -0
- package/dist/unreached.js +7 -0
- package/dist/voice.d.ts +22 -0
- package/dist/voice.js +44 -4
- package/docs/schema.html +48 -11
- package/package.json +1 -1
package/dist/mixer.js
CHANGED
|
@@ -33,6 +33,7 @@ import { Fitting, refit } from './spans.js';
|
|
|
33
33
|
import { Store } from './store.js';
|
|
34
34
|
import { record } from './tape.js';
|
|
35
35
|
import { touch } from './touch.js';
|
|
36
|
+
import { traceMix } from './trace.js';
|
|
36
37
|
import { announce, Transport } from './transport.js';
|
|
37
38
|
import { none } from './voice.js';
|
|
38
39
|
import { gave, historyFor, register, unregister } from './watch.js';
|
|
@@ -234,6 +235,8 @@ export class Mixer {
|
|
|
234
235
|
roleDepth = 0;
|
|
235
236
|
slotOf = new Map();
|
|
236
237
|
lanes;
|
|
238
|
+
/** The sharing hooks of the lanes this entry was built with, which a projection's copy keeps. */
|
|
239
|
+
share;
|
|
237
240
|
handles = null;
|
|
238
241
|
/** What `book` made, still booking; null while there is none. */
|
|
239
242
|
bookers = null;
|
|
@@ -265,6 +268,7 @@ export class Mixer {
|
|
|
265
268
|
});
|
|
266
269
|
if (lanesOf === null && opts.lanes === true)
|
|
267
270
|
throw new Error("blits: '@msb235/blits/lean' has no lanes; take mix from '@msb235/blits'");
|
|
271
|
+
this.share = lanesOf?.share ?? null;
|
|
268
272
|
this.lanes =
|
|
269
273
|
lanesOf !== null && (opts.lanes ?? defaults.lanes) ? new lanesOf(laneHost(this)) : null;
|
|
270
274
|
this.name = opts.name;
|
|
@@ -435,6 +439,9 @@ export class Mixer {
|
|
|
435
439
|
project(at) {
|
|
436
440
|
return projectAll(this.transport, at).of(this);
|
|
437
441
|
}
|
|
442
|
+
trace(from, to, opts) {
|
|
443
|
+
return traceMix(this, from, to, opts);
|
|
444
|
+
}
|
|
438
445
|
announce(name, opts = {}) {
|
|
439
446
|
this.attached();
|
|
440
447
|
announce(this.transport, name, opts, this.slot, this.name);
|
package/dist/motions.d.ts
CHANGED
|
@@ -64,11 +64,14 @@ export declare class Motions<I> {
|
|
|
64
64
|
/** Whether the channel it was cued on holds a number; undefined until it is cued. */
|
|
65
65
|
private holdsNumber;
|
|
66
66
|
private channel;
|
|
67
|
+
/** Whether the channel it was cued on travels round the hue, as `ink` does, and its stretches too. */
|
|
68
|
+
round: boolean;
|
|
67
69
|
/**
|
|
68
70
|
* Records what the channel the patch is cued on holds, refusing a value of the other kind: at once
|
|
69
71
|
* where every subject's value is one kind, else on the first sample of a subject whose value is not.
|
|
72
|
+
* `round` for a channel that travels round the hue, as `ink` does.
|
|
70
73
|
*/
|
|
71
|
-
cuedOn(channel: string, holdsNumber: boolean): void;
|
|
74
|
+
cuedOn(channel: string, holdsNumber: boolean, round?: boolean): void;
|
|
72
75
|
frame(subject: I): number;
|
|
73
76
|
constructor(shape: Shape<I>);
|
|
74
77
|
/** The patch's number for `subject`, made with its first stretch on first ask. */
|
package/dist/motions.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { applied, change, commit, cut, dueTo, insert, peek, queue, rewind } from './changes.js';
|
|
2
|
-
import { closed } from './closed.js';
|
|
2
|
+
import { BOUND, closed } from './closed.js';
|
|
3
|
+
import { closedLch } from './lch.js';
|
|
3
4
|
import { made, pack, prune, reclaim, release, restretch, runAt, runThen, unpack, unrelease, } from './lives.js';
|
|
4
5
|
import { absent, Numbers } from './numbers.js';
|
|
5
6
|
import { reading } from './reading.js';
|
|
@@ -16,6 +17,7 @@ const PENDING = 2 /* RunFlag.PENDING */;
|
|
|
16
17
|
const OLDER = 4 /* RunFlag.OLDER */;
|
|
17
18
|
const LANDED = 8 /* RunFlag.LANDED */;
|
|
18
19
|
const wrongKind = (channel, holdsNumber) => new Error(`blits: channel ${channel} holds ${holdsNumber ? 'a number' : 'an array'}, and this motion patch gives a subject ${holdsNumber ? 'an array' : 'a number'}`);
|
|
20
|
+
const notOklab = (channel) => new Error(`blits: channel ${channel} goes round the hue, and a motion on it moves oklab() values, four numbers each`);
|
|
19
21
|
/**
|
|
20
22
|
* A motion patch's subjects: the stretch each is playing, in one flat array by a number the patch
|
|
21
23
|
* gives it, with earlier stretches a read back may reach and changes not yet applied beside them.
|
|
@@ -68,11 +70,18 @@ export class Motions {
|
|
|
68
70
|
/** Whether the channel it was cued on holds a number; undefined until it is cued. */
|
|
69
71
|
holdsNumber = undefined;
|
|
70
72
|
channel = '';
|
|
73
|
+
/** Whether the channel it was cued on travels round the hue, as `ink` does, and its stretches too. */
|
|
74
|
+
round = false;
|
|
71
75
|
/**
|
|
72
76
|
* Records what the channel the patch is cued on holds, refusing a value of the other kind: at once
|
|
73
77
|
* where every subject's value is one kind, else on the first sample of a subject whose value is not.
|
|
78
|
+
* `round` for a channel that travels round the hue, as `ink` does.
|
|
74
79
|
*/
|
|
75
|
-
cuedOn(channel, holdsNumber) {
|
|
80
|
+
cuedOn(channel, holdsNumber, round = false) {
|
|
81
|
+
if (round && this.shape.law[0] === BOUND)
|
|
82
|
+
throw new Error(`blits: channel ${channel} goes round the hue, and a glide's bounds are a box on its OKLab axes; glide it without bounds`);
|
|
83
|
+
if (round && this.n >= 0 && this.n !== 4)
|
|
84
|
+
throw notOklab(channel);
|
|
76
85
|
const every = this.shape.scalarEvery();
|
|
77
86
|
if (every !== undefined && every !== holdsNumber)
|
|
78
87
|
throw wrongKind(channel, holdsNumber);
|
|
@@ -82,6 +91,7 @@ export class Motions {
|
|
|
82
91
|
throw wrongKind(channel, holdsNumber);
|
|
83
92
|
this.holdsNumber = holdsNumber;
|
|
84
93
|
this.channel = channel;
|
|
94
|
+
this.round = round;
|
|
85
95
|
}
|
|
86
96
|
frame(subject) {
|
|
87
97
|
return this.owner === null ? Number.NaN : this.owner.frame(this.ownerId, subject);
|
|
@@ -127,6 +137,8 @@ export class Motions {
|
|
|
127
137
|
}
|
|
128
138
|
/** Sets the axes every subject moves on, at the first. */
|
|
129
139
|
size(n) {
|
|
140
|
+
if (this.round && n !== 4)
|
|
141
|
+
throw notOklab(this.channel);
|
|
130
142
|
this.n = n;
|
|
131
143
|
this.stride = 3 + 3 * n;
|
|
132
144
|
if (shared.xs.length < n)
|
|
@@ -426,6 +438,8 @@ export class Motions {
|
|
|
426
438
|
this.evaluate(seg.at, seg.ms, seg.x0, 0, seg.v0, 0, seg.to, 0, t, xo, vo);
|
|
427
439
|
}
|
|
428
440
|
evaluate(at, ms, x0, x, v0, v, to, g, t, xo, vo) {
|
|
441
|
+
if (this.round)
|
|
442
|
+
return closedLch(this.runs, this.shape.ease, at, ms, x0, x, v0, v, to, g, t, xo, vo);
|
|
429
443
|
return closed(this.runs, this.shape.ease, this.n, at, ms, x0, x, v0, v, to, g, t, xo, vo);
|
|
430
444
|
}
|
|
431
445
|
}
|
package/dist/move.js
CHANGED
|
@@ -2,16 +2,15 @@ import { settleArcs } from './arcs.js';
|
|
|
2
2
|
import { changed } from './chain.js';
|
|
3
3
|
import { elapsedWith } from './clock.js';
|
|
4
4
|
import { byId, popDue, schedule } from './due.js';
|
|
5
|
-
import {
|
|
6
|
-
import { beginFade, part, retire } from './fade.js';
|
|
5
|
+
import { beginFade, part, retire, rouse, stay } from './fade.js';
|
|
7
6
|
import { expireGone, keepGone } from './gone.js';
|
|
8
7
|
import { begin } from './held.js';
|
|
9
8
|
import { noted } from './history.js';
|
|
10
9
|
import { leave as leaveClock } from './origin.js';
|
|
11
|
-
import { ownerReading } from './owner.js';
|
|
10
|
+
import { ownerReading, plays } from './owner.js';
|
|
12
11
|
import { mixAt, place, repin, startAt, startOf } from './place.js';
|
|
13
12
|
import { pageVoice } from './revive.js';
|
|
14
|
-
import { delist } from './roster.js';
|
|
13
|
+
import { delist, detour } from './roster.js';
|
|
15
14
|
import { scoredLeft, scoredRemove } from './scored.js';
|
|
16
15
|
import { lapse } from './spans.js';
|
|
17
16
|
import { unplay } from './strays.js';
|
|
@@ -80,6 +79,8 @@ function moveTo(mix, now) {
|
|
|
80
79
|
if (voice.state !== 'pending') {
|
|
81
80
|
if (voice.turns)
|
|
82
81
|
turn(mix, voice, now);
|
|
82
|
+
if (voice.state === 'live' && voice.out !== null && !voice.quietAt(now))
|
|
83
|
+
rouse(mix, voice, now);
|
|
83
84
|
// Its passes run out at its end running forward, and at its start running back. A clock
|
|
84
85
|
// standing still at or before its start is where it was: waiting, or frozen there.
|
|
85
86
|
const way = heading(voice, now);
|
|
@@ -87,7 +88,7 @@ function moveTo(mix, now) {
|
|
|
87
88
|
const back = way < 0;
|
|
88
89
|
const atStart = !voice.beginless && at <= voice.soonest;
|
|
89
90
|
// An owner run back to its start stays for what it holds, and leaves with its last child.
|
|
90
|
-
const holds = voice.holding !== null && voice
|
|
91
|
+
const holds = voice.holding !== null && plays(voice, now);
|
|
91
92
|
const over = back
|
|
92
93
|
? atStart && !holds
|
|
93
94
|
: Number.isFinite(voice.span) && at >= voice.span + voice.latest;
|
|
@@ -95,11 +96,11 @@ function moveTo(mix, now) {
|
|
|
95
96
|
voice.play(true, now, mix.transport.seq);
|
|
96
97
|
// The fade starts when its passes ran out, not at the frame that noticed, so it plays
|
|
97
98
|
// the same at any frame rate and a read at another time can find it.
|
|
98
|
-
if (voice.state === 'live') {
|
|
99
|
+
if (voice.state === 'live' && voice.out === null) {
|
|
99
100
|
if (back ? voice.freezesBefore : voice.freezesAfter)
|
|
100
101
|
voice.state = 'frozen';
|
|
101
102
|
else
|
|
102
|
-
beginFade(mix, voice, { back }, Math.max(startOf(voice), Math.min(now, voice.endsAt())));
|
|
103
|
+
beginFade(mix, voice, { back, stays: voice.owner !== null && mix.opts.history !== undefined }, Math.max(startOf(voice), Math.min(now, voice.endsAt())));
|
|
103
104
|
}
|
|
104
105
|
}
|
|
105
106
|
else if (voice.state === 'frozen' && !(atStart && way === 0))
|
|
@@ -126,8 +127,12 @@ function moveTo(mix, now) {
|
|
|
126
127
|
else if (settled)
|
|
127
128
|
retire(mix, voice);
|
|
128
129
|
}
|
|
129
|
-
else if (spent >= over)
|
|
130
|
-
|
|
130
|
+
else if (spent >= over) {
|
|
131
|
+
if (voice.out.stays)
|
|
132
|
+
stay(mix, voice, at + over);
|
|
133
|
+
else
|
|
134
|
+
retire(mix, voice, at + over);
|
|
135
|
+
}
|
|
131
136
|
}
|
|
132
137
|
if (!mix.projecting)
|
|
133
138
|
schedule(mix, voice);
|
|
@@ -137,7 +142,7 @@ function moveTo(mix, now) {
|
|
|
137
142
|
settleArcs(mix, now);
|
|
138
143
|
}
|
|
139
144
|
if (mix.sharers.length > 0 && !mix.projecting)
|
|
140
|
-
outgrow(mix);
|
|
145
|
+
mix.share?.outgrow(mix);
|
|
141
146
|
mix.stirred = false;
|
|
142
147
|
mix.asleep = false;
|
|
143
148
|
if (mix.retired.length > 0)
|
package/dist/named.d.ts
CHANGED
|
@@ -5,7 +5,12 @@
|
|
|
5
5
|
export declare class Named<I> {
|
|
6
6
|
readonly list: readonly I[];
|
|
7
7
|
private readonly set;
|
|
8
|
-
|
|
8
|
+
/** Each subject's `stagger` answer, asked at cue; null for a voice with no stagger. */
|
|
9
|
+
readonly delays: Map<I, number> | null;
|
|
10
|
+
/** The longest of `delays`, 0 at least, and the least, 0 at most: a voice's bounds at cue. */
|
|
11
|
+
readonly latest: number;
|
|
12
|
+
readonly soonest: number;
|
|
13
|
+
constructor(subjects: Iterable<I>, stagger?: (subject: I) => number);
|
|
9
14
|
get size(): number;
|
|
10
15
|
has(subject: I): boolean;
|
|
11
16
|
}
|
package/dist/named.js
CHANGED
|
@@ -7,11 +7,19 @@ const FEW = 8;
|
|
|
7
7
|
export class Named {
|
|
8
8
|
list;
|
|
9
9
|
set;
|
|
10
|
-
|
|
10
|
+
/** Each subject's `stagger` answer, asked at cue; null for a voice with no stagger. */
|
|
11
|
+
delays = null;
|
|
12
|
+
/** The longest of `delays`, 0 at least, and the least, 0 at most: a voice's bounds at cue. */
|
|
13
|
+
latest = 0;
|
|
14
|
+
soonest = 0;
|
|
15
|
+
constructor(subjects, stagger) {
|
|
11
16
|
// An array of distinct subjects, as nearly every voice names, is copied without iterating it.
|
|
12
17
|
if (Array.isArray(subjects) && distinct(subjects)) {
|
|
13
18
|
this.list = subjects.slice();
|
|
14
19
|
this.set = null;
|
|
20
|
+
if (stagger)
|
|
21
|
+
this.delays = delaysOf(this.list, stagger);
|
|
22
|
+
[this.latest, this.soonest] = boundsOf(this.delays);
|
|
15
23
|
return;
|
|
16
24
|
}
|
|
17
25
|
const list = [];
|
|
@@ -32,6 +40,9 @@ export class Named {
|
|
|
32
40
|
// Exactly as long as it is: a list grown by push keeps room for 17.
|
|
33
41
|
this.list = list.slice();
|
|
34
42
|
this.set = set;
|
|
43
|
+
if (stagger)
|
|
44
|
+
this.delays = delaysOf(this.list, stagger);
|
|
45
|
+
[this.latest, this.soonest] = boundsOf(this.delays);
|
|
35
46
|
}
|
|
36
47
|
get size() {
|
|
37
48
|
return this.list.length;
|
|
@@ -40,6 +51,23 @@ export class Named {
|
|
|
40
51
|
return this.set === null ? this.list.includes(subject) : this.set.has(subject);
|
|
41
52
|
}
|
|
42
53
|
}
|
|
54
|
+
function delaysOf(list, stagger) {
|
|
55
|
+
const delays = new Map();
|
|
56
|
+
for (const s of list)
|
|
57
|
+
delays.set(s, stagger(s));
|
|
58
|
+
return delays;
|
|
59
|
+
}
|
|
60
|
+
function boundsOf(delays) {
|
|
61
|
+
let latest = 0;
|
|
62
|
+
let soonest = 0;
|
|
63
|
+
for (const delay of delays?.values() ?? []) {
|
|
64
|
+
if (delay > latest)
|
|
65
|
+
latest = delay;
|
|
66
|
+
if (delay < soonest)
|
|
67
|
+
soonest = delay;
|
|
68
|
+
}
|
|
69
|
+
return [latest, soonest];
|
|
70
|
+
}
|
|
43
71
|
/** Whether a short array names no subject twice, as `includes` compares; a long one says no. */
|
|
44
72
|
function distinct(subjects) {
|
|
45
73
|
const n = subjects.length;
|
package/dist/numbers.d.ts
CHANGED
|
@@ -36,5 +36,10 @@ export declare class Numbers<I> {
|
|
|
36
36
|
/** Whether `slot` is a number in use, its subject perhaps collected and not yet released. */
|
|
37
37
|
has(slot: number): boolean;
|
|
38
38
|
subject(slot: number): I | typeof absent;
|
|
39
|
+
/**
|
|
40
|
+
* Hands the subject numbered `from` the free number `to`, and frees `from` without telling the
|
|
41
|
+
* owner, whose stores the caller moves; false, changing nothing, where either does not hold.
|
|
42
|
+
*/
|
|
43
|
+
move(from: number, to: number): boolean;
|
|
39
44
|
release(slot: number): void;
|
|
40
45
|
}
|
package/dist/numbers.js
CHANGED
|
@@ -82,6 +82,35 @@ export class Numbers {
|
|
|
82
82
|
}
|
|
83
83
|
return ref;
|
|
84
84
|
}
|
|
85
|
+
/**
|
|
86
|
+
* Hands the subject numbered `from` the free number `to`, and frees `from` without telling the
|
|
87
|
+
* owner, whose stores the caller moves; false, changing nothing, where either does not hold.
|
|
88
|
+
*/
|
|
89
|
+
move(from, to) {
|
|
90
|
+
const ref = this.refs[from];
|
|
91
|
+
const free = this.free;
|
|
92
|
+
if (ref === undefined || to >= this.next || this.refs[to] !== undefined || free === null)
|
|
93
|
+
return false;
|
|
94
|
+
const at = free.lastIndexOf(to);
|
|
95
|
+
if (at < 0)
|
|
96
|
+
return false;
|
|
97
|
+
if (ref instanceof WeakRef) {
|
|
98
|
+
const subject = ref.deref();
|
|
99
|
+
if (subject === undefined)
|
|
100
|
+
return false;
|
|
101
|
+
if (ref === this.lone)
|
|
102
|
+
this.loneSlot = to;
|
|
103
|
+
else if (this.registry !== null) {
|
|
104
|
+
this.registry.unregister(ref);
|
|
105
|
+
this.registry.register(subject, to, ref);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
free.splice(at, 1);
|
|
109
|
+
free.push(from);
|
|
110
|
+
this.refs[to] = ref;
|
|
111
|
+
this.refs[from] = undefined;
|
|
112
|
+
return true;
|
|
113
|
+
}
|
|
85
114
|
release(slot) {
|
|
86
115
|
const ref = this.refs[slot];
|
|
87
116
|
if (ref === undefined)
|
package/dist/owner.d.ts
CHANGED
|
@@ -36,8 +36,13 @@ export declare function mixTime<I, O>(owner: Voice<I, O>, t: number): number;
|
|
|
36
36
|
export declare function turnedBack<I, O>(voice: Voice<I, O>): boolean;
|
|
37
37
|
/** Takes a child into its owner. */
|
|
38
38
|
export declare function adopt<I, O>(owner: Voice<I, O>, child: Voice<I, O>): void;
|
|
39
|
-
/**
|
|
40
|
-
|
|
39
|
+
/**
|
|
40
|
+
* A child left at mix time `now`: its owner, where that was its last child still playing and it
|
|
41
|
+
* should leave too.
|
|
42
|
+
*/
|
|
43
|
+
export declare function orphan<I, O>(child: Voice<I, O>, now: number): Voice<I, O> | null;
|
|
44
|
+
/** Whether an owner holds a child at mix time `now` that is not kept quiet past its passes. */
|
|
45
|
+
export declare function plays<I, O>(owner: Voice<I, O>, now: number): boolean;
|
|
41
46
|
/** A child finished its passes: its owner has played once every child it held has. */
|
|
42
47
|
export declare function childPlayed<I, O>(owner: Voice<I, O>, at: number, seq: number): void;
|
|
43
48
|
/** Every voice an owner holds, its children's children included. */
|
package/dist/owner.js
CHANGED
|
@@ -76,14 +76,24 @@ export function adopt(owner, child) {
|
|
|
76
76
|
h.children.push(child);
|
|
77
77
|
h.kids++;
|
|
78
78
|
}
|
|
79
|
-
/**
|
|
80
|
-
|
|
79
|
+
/**
|
|
80
|
+
* A child left at mix time `now`: its owner, where that was its last child still playing and it
|
|
81
|
+
* should leave too.
|
|
82
|
+
*/
|
|
83
|
+
export function orphan(child, now) {
|
|
81
84
|
const owner = child.owner;
|
|
82
85
|
const h = owner.holding;
|
|
83
86
|
const i = h.children.indexOf(child);
|
|
84
87
|
if (i >= 0)
|
|
85
88
|
h.children.splice(i, 1);
|
|
86
|
-
return
|
|
89
|
+
return !plays(owner, now) && owner.state !== 'done' ? owner : null;
|
|
90
|
+
}
|
|
91
|
+
/** Whether an owner holds a child at mix time `now` that is not kept quiet past its passes. */
|
|
92
|
+
export function plays(owner, now) {
|
|
93
|
+
for (const child of owner.holding.children)
|
|
94
|
+
if (!child.quietAt(now))
|
|
95
|
+
return true;
|
|
96
|
+
return false;
|
|
87
97
|
}
|
|
88
98
|
/** A child finished its passes: its owner has played once every child it held has. */
|
|
89
99
|
export function childPlayed(owner, at, seq) {
|
package/dist/project.js
CHANGED
|
@@ -7,7 +7,7 @@ import { fadesAt } from './fade.js';
|
|
|
7
7
|
import { meet, origin } from './held.js';
|
|
8
8
|
import { copyHeld, last, lastWithin } from './history.js';
|
|
9
9
|
import { Mixer } from './mixer.js';
|
|
10
|
-
import { heldByInput,
|
|
10
|
+
import { heldByInput, relink } from './owner.js';
|
|
11
11
|
import { reading } from './reading.js';
|
|
12
12
|
import { record } from './record.js';
|
|
13
13
|
import { recount } from './roster.js';
|
|
@@ -72,6 +72,7 @@ export function projectAll(transport, at) {
|
|
|
72
72
|
}, null);
|
|
73
73
|
copy.slot = mix.slot;
|
|
74
74
|
copy.projecting = true;
|
|
75
|
+
copy.share = mix.share;
|
|
75
76
|
copy.places = mix.places;
|
|
76
77
|
copies.set(mix, copy);
|
|
77
78
|
if (ahead)
|
|
@@ -183,13 +184,7 @@ function copyBack(mix, c, t, cut) {
|
|
|
183
184
|
/** Sets each copied voice's state to what its clock and fade make it at `t`. */
|
|
184
185
|
export function stateAt(c, t) {
|
|
185
186
|
for (const copy of c.cued)
|
|
186
|
-
copy.state = copy.
|
|
187
|
-
? 'fading'
|
|
188
|
-
: (copy.owner === null ? t : ownerReading(copy.owner, t)) < copy.start
|
|
189
|
-
? 'pending'
|
|
190
|
-
: copy.freezesAfter && copy.elapsedAt(t) >= copy.span + copy.latest
|
|
191
|
-
? 'frozen'
|
|
192
|
-
: 'live';
|
|
187
|
+
copy.state = copy.stateAt(t);
|
|
193
188
|
}
|
|
194
189
|
/**
|
|
195
190
|
* A projection back starts each subject from the latest copy kept at or before `t`, else from its
|
package/dist/pull.d.ts
CHANGED
|
@@ -1,5 +1,16 @@
|
|
|
1
1
|
import type { Mixer } from './mixer.js';
|
|
2
2
|
import type { Columns } from './types.js';
|
|
3
|
+
/** One channel `pull` writes: where in the kit, how many numbers a subject, and its rest. */
|
|
4
|
+
export interface Column {
|
|
5
|
+
key: string;
|
|
6
|
+
slot: number;
|
|
7
|
+
axes: number;
|
|
8
|
+
out: Float64Array;
|
|
9
|
+
/** The rest, clamped, or NaN for a channel with none. */
|
|
10
|
+
rest: Float64Array;
|
|
11
|
+
bounds: readonly [number, number] | undefined;
|
|
12
|
+
}
|
|
13
|
+
export declare function clampRun(out: Float64Array, at: number, n: number, [lo, hi]: readonly [number, number]): void;
|
|
3
14
|
/** After a probe, keeps the pose a retarget from `'current'` reads as the subject's last. */
|
|
4
15
|
export declare function keep<I, O>(this: Mixer<I, O>, subject: I, pose: O, out: O | undefined): void;
|
|
5
16
|
export declare function pull<I, O>(mix: Mixer<I, O>, subjects: Iterable<I>, into: Columns<O>): void;
|
package/dist/pull.js
CHANGED
|
@@ -1,8 +1,13 @@
|
|
|
1
1
|
import { axesOf } from './channels.js';
|
|
2
2
|
import { copy } from './clone.js';
|
|
3
|
-
import { clampRun, flush, pullRun, writeLater } from './columns.js';
|
|
4
3
|
/** What `pull` remembers at a position it has read no subject at. */
|
|
5
4
|
const UNREAD = Symbol('unread');
|
|
5
|
+
export function clampRun(out, at, n, [lo, hi]) {
|
|
6
|
+
for (let i = at; i < at + n; i++) {
|
|
7
|
+
const x = out[i];
|
|
8
|
+
out[i] = x < lo ? lo : x > hi ? hi : x;
|
|
9
|
+
}
|
|
10
|
+
}
|
|
6
11
|
/** Writes a folded pose's value into subject `n`'s places in a column. */
|
|
7
12
|
function writeValue(c, v, n) {
|
|
8
13
|
const at = n * c.axes;
|
|
@@ -111,7 +116,7 @@ export function pull(mix, subjects, into) {
|
|
|
111
116
|
}
|
|
112
117
|
finally {
|
|
113
118
|
if (lanes !== null)
|
|
114
|
-
flush(lanes);
|
|
119
|
+
lanes.pulls.flush(lanes);
|
|
115
120
|
}
|
|
116
121
|
// Every head is current only if nothing relinked or changed version while they were read.
|
|
117
122
|
const unchanged = live && mix.version === version && mix.relinks === relinks;
|
|
@@ -135,7 +140,7 @@ function readAll(mix, list, columns, live, current, since, whole) {
|
|
|
135
140
|
const runs = live && whole;
|
|
136
141
|
for (let n = 0; n < list.length; n++) {
|
|
137
142
|
if (runs) {
|
|
138
|
-
n = pullRun(lanes, slots, list, was, since, n, columns, now, mix.version);
|
|
143
|
+
n = lanes.pulls.pullRun(lanes, slots, list, was, since, n, columns, now, mix.version);
|
|
139
144
|
if (n === list.length)
|
|
140
145
|
break;
|
|
141
146
|
}
|
|
@@ -152,12 +157,21 @@ function readAll(mix, list, columns, live, current, since, whole) {
|
|
|
152
157
|
was[n] = subject;
|
|
153
158
|
heads[n] = head;
|
|
154
159
|
slot = head.slot;
|
|
160
|
+
// Numbered out of the list's order, as swap-remove leaves a subject: renumbered into it.
|
|
161
|
+
const to = n === 0 ? 0 : slots[n - 1] + 1;
|
|
162
|
+
if (runs && slot >= 0 && slot !== to && to >= 0) {
|
|
163
|
+
if (lanes.pulls.renumber(mix, subject, head, to)) {
|
|
164
|
+
for (let j = was.indexOf(subject); j >= 0; j = was.indexOf(subject, j + 1))
|
|
165
|
+
slots[j] = to;
|
|
166
|
+
slot = to;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
155
169
|
slots[n] = slot;
|
|
156
170
|
}
|
|
157
171
|
}
|
|
158
172
|
const laned = live && lanes?.prepare(slot, subject, mix.version, heads[n] ?? null) === true;
|
|
159
173
|
if (laned && whole && lanes.whole && !lanes.owes(slot)) {
|
|
160
|
-
writeLater(lanes, slot, columns, n);
|
|
174
|
+
lanes.pulls.writeLater(lanes, slot, columns, n, slots);
|
|
161
175
|
continue;
|
|
162
176
|
}
|
|
163
177
|
const head = live ? heads[n] : null;
|
package/dist/qualify.js
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
import { numericOf } from './channels.js';
|
|
2
2
|
import { flush } from './columns.js';
|
|
3
|
-
import { crowdable } from './crowd.js';
|
|
4
3
|
import { byId } from './due.js';
|
|
5
4
|
import { weighAt } from './everyone.js';
|
|
6
5
|
import { wake } from './fill.js';
|
|
6
|
+
import { crowdable } from './hosts.js';
|
|
7
7
|
import { Lane } from './lane.js';
|
|
8
8
|
import { reach } from './meet.js';
|
|
9
9
|
import { bury, compactSparse, join, regroup, retouchRow, sameCrowds } from './rows.js';
|
|
10
10
|
import { settle } from './sample.js';
|
|
11
|
+
import { seated, seatOf } from './seat.js';
|
|
11
12
|
/**
|
|
12
13
|
* Which channels run as lanes and which voices run on them: the fixed point where every voice
|
|
13
14
|
* writing a laned channel fits and writes only laned channels. Only channels some laned voice
|
|
@@ -150,17 +151,16 @@ export function requalify(lanes, version) {
|
|
|
150
151
|
for (const c of lanes.crowds)
|
|
151
152
|
for (let p = 0; p < c.size; p++) {
|
|
152
153
|
const v = c.voices[p];
|
|
153
|
-
if (
|
|
154
|
+
if (seated(lanes, v, c, p) && v.state === 'done')
|
|
154
155
|
bury(lanes, c, p);
|
|
155
156
|
}
|
|
156
157
|
// A crowd voice that started or began fading takes it up on its row, as `retouch` does.
|
|
157
158
|
for (const v of touched) {
|
|
158
159
|
if (v.state === 'done')
|
|
159
160
|
continue;
|
|
160
|
-
const
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
retouchRow(lanes, c, p, v);
|
|
161
|
+
const s = seatOf(lanes, v);
|
|
162
|
+
if (s !== null)
|
|
163
|
+
retouchRow(lanes, s.crowd, s.row, v);
|
|
164
164
|
}
|
|
165
165
|
compactSparse(lanes);
|
|
166
166
|
return;
|
|
@@ -306,14 +306,13 @@ export function retouch(lanes) {
|
|
|
306
306
|
const known = v.id <= lanes.known;
|
|
307
307
|
if (v.id > lanes.known)
|
|
308
308
|
lanes.known = v.id;
|
|
309
|
-
const
|
|
310
|
-
|
|
311
|
-
if (c !== undefined && p !== undefined) {
|
|
309
|
+
const s = seatOf(lanes, v);
|
|
310
|
+
if (s !== null) {
|
|
312
311
|
if (v.state === 'done') {
|
|
313
|
-
bury(lanes,
|
|
312
|
+
bury(lanes, s.crowd, s.row);
|
|
314
313
|
continue;
|
|
315
314
|
}
|
|
316
|
-
retouchRow(lanes,
|
|
315
|
+
retouchRow(lanes, s.crowd, s.row, v);
|
|
317
316
|
continue;
|
|
318
317
|
}
|
|
319
318
|
if (v.state === 'done' && !known)
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { Mixer } from './mixer.js';
|
|
2
|
+
import type { Subject } from './subject.js';
|
|
3
|
+
/**
|
|
4
|
+
* Gives a subject the free number `to` in place of its own, carrying everything kept under the old
|
|
5
|
+
* one, so `pull` can keep a list's numbers in its order: a list kept dense by swap-remove moves its
|
|
6
|
+
* last subject into the place a dropped one left, whose number the drop just freed. Says whether it
|
|
7
|
+
* did; it does not where some store it would have to carry is in use: while a fill runs, under
|
|
8
|
+
* history, in a read ahead, or with roles, a bound or anything owed on the lanes.
|
|
9
|
+
*/
|
|
10
|
+
export declare function renumber<I, O>(mix: Mixer<I, O>, subject: I, head: Subject<unknown>, to: number): boolean;
|
package/dist/renumber.js
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { resight } from './everyone.js';
|
|
2
|
+
import { goneIndex } from './gone.js';
|
|
3
|
+
import { reunreach } from './unreached.js';
|
|
4
|
+
/**
|
|
5
|
+
* Gives a subject the free number `to` in place of its own, carrying everything kept under the old
|
|
6
|
+
* one, so `pull` can keep a list's numbers in its order: a list kept dense by swap-remove moves its
|
|
7
|
+
* last subject into the place a dropped one left, whose number the drop just freed. Says whether it
|
|
8
|
+
* did; it does not where some store it would have to carry is in use: while a fill runs, under
|
|
9
|
+
* history, in a read ahead, or with roles, a bound or anything owed on the lanes.
|
|
10
|
+
*/
|
|
11
|
+
export function renumber(mix, subject, head, to) {
|
|
12
|
+
const lanes = mix.lanes;
|
|
13
|
+
const from = head.slot;
|
|
14
|
+
if (from < 0 ||
|
|
15
|
+
lanes.filling ||
|
|
16
|
+
lanes.host.keeps ||
|
|
17
|
+
mix.projecting ||
|
|
18
|
+
lanes.roles.length > 0 ||
|
|
19
|
+
lanes.owed.any() ||
|
|
20
|
+
lanes.lanes.some((lane) => lane.voice.bound !== null) ||
|
|
21
|
+
!lanes.numbers.move(from, to))
|
|
22
|
+
return false;
|
|
23
|
+
for (const lane of lanes.lanes)
|
|
24
|
+
lane.renumber(from, to);
|
|
25
|
+
const placed = lanes.placed;
|
|
26
|
+
for (const id of placed.all(from)) {
|
|
27
|
+
placed.remove(from, id);
|
|
28
|
+
const s = lanes.seats.get(id);
|
|
29
|
+
if (s === undefined || s.crowd.list[s.row] !== from)
|
|
30
|
+
continue;
|
|
31
|
+
s.crowd.list[s.row] = to;
|
|
32
|
+
placed.add(to, id);
|
|
33
|
+
}
|
|
34
|
+
const per = lanes.per;
|
|
35
|
+
per.copyWithin(to * 6 /* Per.SLOT */, from * 6 /* Per.SLOT */, (from + 1) * 6 /* Per.SLOT */);
|
|
36
|
+
per[from * 6 /* Per.SLOT */ + 0 /* Per.FILLED */] = lanes.fills;
|
|
37
|
+
lanes.unlinked[to] = lanes.unlinked[from];
|
|
38
|
+
lanes.subjects[to] = lanes.subjects[from];
|
|
39
|
+
lanes.subjects[from] = undefined;
|
|
40
|
+
for (const ch of lanes.laned)
|
|
41
|
+
ch.values.copyWithin(to * ch.axes, from * ch.axes, (from + 1) * ch.axes);
|
|
42
|
+
mix.places?.forgot(from);
|
|
43
|
+
mix.places?.number(subject, to);
|
|
44
|
+
const cap = lanes.cap;
|
|
45
|
+
for (const voice of mix.general) {
|
|
46
|
+
reunreach(voice, from, to);
|
|
47
|
+
resight(voice, from, to, cap);
|
|
48
|
+
reblend(mix, voice, from, to);
|
|
49
|
+
}
|
|
50
|
+
for (const voice of goneIndex(mix).general)
|
|
51
|
+
reunreach(voice, from, to);
|
|
52
|
+
for (let held = head; held !== null; held = held.next)
|
|
53
|
+
if (held.voice !== null)
|
|
54
|
+
reblend(mix, held.voice, from, to);
|
|
55
|
+
head.slot = to;
|
|
56
|
+
return true;
|
|
57
|
+
}
|
|
58
|
+
/** Moves a blend's read for the subject this frame, if it has made one, to its new number. */
|
|
59
|
+
function reblend(mix, voice, from, to) {
|
|
60
|
+
const of = voice.blend?.of;
|
|
61
|
+
if (of === undefined || from >= of.frames.length || of.frames[from] !== mix.frame)
|
|
62
|
+
return;
|
|
63
|
+
if (to >= of.frames.length) {
|
|
64
|
+
const size = Math.max(to + 1, of.frames.length * 2, 64);
|
|
65
|
+
const frames = new Float64Array(size);
|
|
66
|
+
const values = new Float64Array(size);
|
|
67
|
+
frames.set(of.frames);
|
|
68
|
+
values.set(of.values);
|
|
69
|
+
of.frames = frames;
|
|
70
|
+
of.values = values;
|
|
71
|
+
}
|
|
72
|
+
of.frames[to] = mix.frame;
|
|
73
|
+
of.values[to] = of.values[from];
|
|
74
|
+
of.frames[from] = Number.NaN;
|
|
75
|
+
}
|
package/dist/roles.js
CHANGED
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
import { first } from './clone.js';
|
|
2
|
-
import { open as openRecord } from './everyone.js';
|
|
3
2
|
import { merged, unband } from './fold.js';
|
|
4
3
|
import { roleScratch } from './role.js';
|
|
5
4
|
// V8 reads a local const as a constant, where it reads an import from its module on every call.
|
|
6
|
-
const open = openRecord;
|
|
7
5
|
/**
|
|
8
6
|
* Whether a fold's next voice is its chain's next record, `held`, and not the next voice sharing
|
|
9
7
|
* one record, `sharers[q]`: the two lists are each in voice order, and a fold takes them as one.
|
|
@@ -54,7 +52,7 @@ export function foldRoles(subject, pose, head, laned, dry, except, owed) {
|
|
|
54
52
|
continue;
|
|
55
53
|
const held = rec;
|
|
56
54
|
if (!mine)
|
|
57
|
-
open(voice, held, head.slot);
|
|
55
|
+
this.share.open(voice, held, head.slot);
|
|
58
56
|
const delta = this.read(voice, subject, now, dry, held);
|
|
59
57
|
if (owed >= 0 && voice.laned)
|
|
60
58
|
this.lanes.paid(voice.id, owed, held.weight);
|
package/dist/roster.d.ts
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
import type { Mixer } from './mixer.js';
|
|
2
2
|
import type { Voice } from './voice.js';
|
|
3
|
+
/**
|
|
4
|
+
* Counts what makes a mix's fold more than a walk of each chain: a role, a voice sharing one
|
|
5
|
+
* record, a voice that stopped sharing this frame. Called wherever one of the three changes, so
|
|
6
|
+
* `foldWith` asks one number.
|
|
7
|
+
*/
|
|
8
|
+
export declare function detour<I, O>(mix: Mixer<I, O>): void;
|
|
3
9
|
/** Counts a voice just put in the list into everything a sync and a fold skip work by. */
|
|
4
10
|
export declare function enlist<I, O>(mix: Mixer<I, O>, voice: Voice<I, O>): void;
|
|
5
11
|
/**
|
package/dist/roster.js
CHANGED
|
@@ -1,6 +1,13 @@
|
|
|
1
1
|
import { index, unindex } from './chain.js';
|
|
2
|
-
import { detour } from './everyone.js';
|
|
3
2
|
import { Store } from './store.js';
|
|
3
|
+
/**
|
|
4
|
+
* Counts what makes a mix's fold more than a walk of each chain: a role, a voice sharing one
|
|
5
|
+
* record, a voice that stopped sharing this frame. Called wherever one of the three changes, so
|
|
6
|
+
* `foldWith` asks one number.
|
|
7
|
+
*/
|
|
8
|
+
export function detour(mix) {
|
|
9
|
+
mix.detours = mix.roleVoices + mix.sharers.length + (mix.unshared ? 1 : 0);
|
|
10
|
+
}
|
|
4
11
|
function tally(mix, voice, by) {
|
|
5
12
|
if (voice.spec.role !== undefined)
|
|
6
13
|
mix.roleVoices += by;
|