@msb235/blits 0.4.0 → 0.6.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 +289 -0
- package/README.md +2 -1
- package/dist/bare.d.ts +29 -0
- package/dist/bare.js +105 -0
- package/dist/blend.d.ts +34 -0
- package/dist/blend.js +102 -0
- package/dist/book.d.ts +53 -0
- package/dist/book.js +218 -0
- package/dist/chain.d.ts +22 -0
- package/dist/chain.js +150 -0
- package/dist/channels.d.ts +2 -0
- package/dist/channels.js +3 -1
- package/dist/clock.d.ts +50 -3
- package/dist/clock.js +102 -9
- package/dist/clone.d.ts +7 -0
- package/dist/clone.js +57 -0
- package/dist/columns.d.ts +29 -0
- package/dist/columns.js +183 -0
- package/dist/crowd.d.ts +128 -0
- package/dist/crowd.js +354 -0
- package/dist/cue.d.ts +3 -0
- package/dist/cue.js +115 -0
- package/dist/due.d.ts +12 -0
- package/dist/due.js +93 -0
- package/dist/fade.d.ts +19 -0
- package/dist/fade.js +133 -0
- package/dist/fill.d.ts +42 -0
- package/dist/fill.js +381 -0
- package/dist/fold.d.ts +51 -0
- package/dist/fold.js +475 -0
- package/dist/gather.d.ts +24 -0
- package/dist/gather.js +186 -0
- package/dist/handle.d.ts +31 -0
- package/dist/handle.js +56 -0
- package/dist/held.d.ts +14 -0
- package/dist/held.js +117 -0
- package/dist/history.d.ts +22 -0
- package/dist/history.js +149 -0
- package/dist/hosts.d.ts +16 -0
- package/dist/hosts.js +178 -0
- package/dist/index.d.ts +3 -1
- package/dist/index.js +1 -0
- package/dist/keyrows.d.ts +36 -0
- package/dist/keyrows.js +138 -0
- package/dist/lane.d.ts +210 -0
- package/dist/lane.js +187 -0
- package/dist/lanes.d.ts +121 -246
- package/dist/lanes.js +150 -1578
- package/dist/locus.d.ts +40 -0
- package/dist/locus.js +89 -0
- package/dist/marks.d.ts +12 -0
- package/dist/marks.js +80 -0
- package/dist/meet.d.ts +16 -0
- package/dist/meet.js +113 -0
- package/dist/mixer.d.ts +248 -196
- package/dist/mixer.js +270 -2220
- package/dist/motion.d.ts +66 -21
- package/dist/motion.js +325 -165
- package/dist/move.d.ts +9 -0
- package/dist/move.js +190 -0
- package/dist/named.d.ts +12 -0
- package/dist/named.js +39 -0
- package/dist/numbers.d.ts +18 -6
- package/dist/numbers.js +59 -31
- package/dist/owed.d.ts +24 -0
- package/dist/owed.js +51 -0
- package/dist/owner.d.ts +43 -0
- package/dist/owner.js +117 -0
- package/dist/pace.d.ts +40 -0
- package/dist/pace.js +113 -0
- package/dist/patch.d.ts +51 -10
- package/dist/patch.js +140 -45
- package/dist/place.d.ts +20 -0
- package/dist/place.js +196 -0
- package/dist/project.d.ts +4 -0
- package/dist/project.js +233 -0
- package/dist/pull.d.ts +5 -0
- package/dist/pull.js +175 -0
- package/dist/qualify.d.ts +31 -0
- package/dist/qualify.js +345 -0
- package/dist/relink.d.ts +25 -0
- package/dist/relink.js +59 -0
- package/dist/rows.d.ts +33 -0
- package/dist/rows.js +282 -0
- package/dist/sample.d.ts +21 -0
- package/dist/sample.js +232 -0
- package/dist/signals.d.ts +13 -9
- package/dist/signals.js +14 -5
- package/dist/store.d.ts +10 -3
- package/dist/store.js +44 -9
- package/dist/strays.d.ts +21 -0
- package/dist/strays.js +84 -0
- package/dist/ticker.d.ts +48 -0
- package/dist/ticker.js +112 -0
- package/dist/tweened.d.ts +11 -0
- package/dist/tweened.js +48 -0
- package/dist/types.d.ts +237 -43
- package/dist/unreached.d.ts +5 -0
- package/dist/unreached.js +24 -0
- package/dist/voice.d.ts +240 -0
- package/dist/voice.js +324 -0
- package/dist/weigh.d.ts +26 -0
- package/dist/weigh.js +89 -0
- package/docs/schema.html +137 -56
- package/package.json +5 -2
package/dist/lanes.js
CHANGED
|
@@ -1,395 +1,41 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
1
|
+
import { row } from './crowd.js';
|
|
2
|
+
import { call, fillAll, foldKeys, one, parting, readKeyed, reset, run, signalled, subjectAt, } from './fill.js';
|
|
3
|
+
import { fold, foldDelta, foldInto, foldRun, gather } from './gather.js';
|
|
4
|
+
import { meet, owable, reach } from './meet.js';
|
|
5
|
+
import { Numbers } from './numbers.js';
|
|
6
|
+
import { Owed } from './owed.js';
|
|
7
|
+
import { requalify, retouch } from './qualify.js';
|
|
6
8
|
import { reading } from './reading.js';
|
|
7
|
-
|
|
8
|
-
* Which channels run as lanes and which voices run on them: the fixed point where every voice
|
|
9
|
-
* writing a laned channel fits and writes only laned channels. Only channels some laned voice
|
|
10
|
-
* writes are laned.
|
|
11
|
-
*/
|
|
12
|
-
export function qualify(numeric, voices) {
|
|
13
|
-
const open = numeric.slice();
|
|
14
|
-
let changed = true;
|
|
15
|
-
while (changed) {
|
|
16
|
-
changed = false;
|
|
17
|
-
for (const v of voices) {
|
|
18
|
-
if (v.fits && v.slots.every((s) => open[s]))
|
|
19
|
-
continue;
|
|
20
|
-
for (const s of v.slots)
|
|
21
|
-
if (open[s]) {
|
|
22
|
-
open[s] = false;
|
|
23
|
-
changed = true;
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
const laned = new Set();
|
|
28
|
-
const channels = numeric.map(() => false);
|
|
29
|
-
for (const v of voices) {
|
|
30
|
-
if (!v.fits || !v.slots.every((s) => open[s]))
|
|
31
|
-
continue;
|
|
32
|
-
laned.add(v.id);
|
|
33
|
-
for (const s of v.slots)
|
|
34
|
-
channels[s] = true;
|
|
35
|
-
}
|
|
36
|
-
return { channels, voices: laned };
|
|
37
|
-
}
|
|
38
|
-
export function clampRun(out, at, n, [lo, hi]) {
|
|
39
|
-
for (let i = at; i < at + n; i++) {
|
|
40
|
-
const x = out[i];
|
|
41
|
-
out[i] = x < lo ? lo : x > hi ? hi : x;
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
const STRIDE = 8;
|
|
45
|
-
const DELAY = 0;
|
|
46
|
-
const SINCE = 1;
|
|
47
|
-
/** The weight the last fill gave the subject. */
|
|
48
|
-
const WEIGHT = 2;
|
|
49
|
-
/** The weight a fill gave it the last time a probe read it from the lane. */
|
|
50
|
-
const PROBED = 3;
|
|
51
|
-
const MSLOT = 4;
|
|
52
|
-
/** For a motion voice, the last fill that sampled the subject, and the voice's seeks then. */
|
|
53
|
-
const SAMPLED = 5;
|
|
54
|
-
const SEEKS = 6;
|
|
55
|
-
/** 1 once the general path has made the voice's first call for the subject, which never undoes. */
|
|
56
|
-
const MET = 7;
|
|
57
|
-
/** What `begin` found: the general path, only solo lanes, or lanes filled. */
|
|
58
|
-
const GENERAL = 0;
|
|
59
|
-
const SOLO = 1;
|
|
60
|
-
const READY = 2;
|
|
61
|
-
const SLOT = 6;
|
|
62
|
-
const FILLED = 0;
|
|
63
|
-
/**
|
|
64
|
-
* The latest epoch a probe has checked the subject against; or, as `-1 - e`, that a voice naming
|
|
65
|
-
* it started after epoch `e`, so its next probe checks it against everything since `e`.
|
|
66
|
-
*/
|
|
67
|
-
const SEEN = 1;
|
|
68
|
-
const IDLE = 2;
|
|
69
|
-
const LANE_PROBE = 3;
|
|
70
|
-
const GENERAL_PROBE = 4;
|
|
71
|
-
const LANE_FILL = 5;
|
|
72
|
-
/**
|
|
73
|
-
* Below what share of its subjects probed last frame a lane stops filling, and above what share it
|
|
74
|
-
* fills again; between the two it keeps doing what it did, so a host near the line does not flip
|
|
75
|
-
* every frame. Measured at 10k and 1k subjects (2026-10-02, one local machine): filling breaks even
|
|
76
|
-
* with probing through the general path at about 19% probed for keys at both sizes, 71-77% for a
|
|
77
|
-
* spring, and 63% (1k) to about 100% (10k) for a stateless fn.
|
|
78
|
-
*/
|
|
79
|
-
const SPARSE = { keys: { stop: 0.15, start: 0.25 }, other: { stop: 0.6, start: 0.8 } };
|
|
80
|
-
/** The least prime at least `n`, and 1 below 2. */
|
|
81
|
-
function primeFrom(n) {
|
|
82
|
-
if (n < 2)
|
|
83
|
-
return 1;
|
|
84
|
-
for (let c = n;; c++) {
|
|
85
|
-
let prime = true;
|
|
86
|
-
for (let d = 2; d * d <= c; d++)
|
|
87
|
-
if (c % d === 0) {
|
|
88
|
-
prime = false;
|
|
89
|
-
break;
|
|
90
|
-
}
|
|
91
|
-
if (prime)
|
|
92
|
-
return c;
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
/**
|
|
96
|
-
* What one laned voice keeps per subject it reaches, by position in `list`. A subject gets a
|
|
97
|
-
* position when a probe of it first finds the voice playing, which is when the general path first
|
|
98
|
-
* sees it too.
|
|
99
|
-
*/
|
|
100
|
-
class Lane {
|
|
101
|
-
voice;
|
|
102
|
-
/** The subject number at each position. */
|
|
103
|
-
list = [];
|
|
104
|
-
at = new Map();
|
|
105
|
-
/** Whether too few of its subjects were probed lately to fill it: theirs take the general path. */
|
|
106
|
-
idle = false;
|
|
107
|
-
/** The lines it goes idle and busy at, by its patch form. */
|
|
108
|
-
line;
|
|
109
|
-
/**
|
|
110
|
-
* A `fn` or `keys` voice naming one subject, which shares nothing across subjects for a fill to
|
|
111
|
-
* save, so it stays idle: its subject takes the general path, and the channel stays a lane for
|
|
112
|
-
* the voices that do share.
|
|
113
|
-
*/
|
|
114
|
-
solo;
|
|
115
|
-
/**
|
|
116
|
-
* When the voice started playing on its lane, in the order lanes did; 0 while it waits. A subject
|
|
117
|
-
* checked against every lane up to some epoch has met this one if this one's is no later.
|
|
118
|
-
*/
|
|
119
|
-
epoch = 0;
|
|
120
|
-
/**
|
|
121
|
-
* Per position, `STRIDE` numbers side by side, so one subject's are one read from memory: its
|
|
122
|
-
* delay, its `since`, its weight at the last fill, its weight at the last fill a probe read, a
|
|
123
|
-
* motion patch's own number for it (-1 until asked), and the fill and seeks of its last sample.
|
|
124
|
-
*/
|
|
125
|
-
data = new Float64Array(0);
|
|
126
|
-
records = [];
|
|
127
|
-
/**
|
|
128
|
-
* For a motion voice, per position, the delta its last sample made; null where that sample was
|
|
129
|
-
* kept only in `samples`, until a probe asks for it.
|
|
130
|
-
*/
|
|
131
|
-
deltas = [];
|
|
132
|
-
/** For a motion voice, per position, its last sample's value on each of `axes` axes. */
|
|
133
|
-
samples = new Float64Array(0);
|
|
134
|
-
axes = 0;
|
|
135
|
-
/** What a keys read writes into, reused across subjects. */
|
|
136
|
-
delta = {};
|
|
137
|
-
/** The arrays keyed reads interpolate into: the lane's own, so a probe mid-fill cannot move them. */
|
|
138
|
-
scratch = [];
|
|
139
|
-
/** The laned channel of each channel the voice writes, in `writes` order. */
|
|
140
|
-
chans = [];
|
|
141
|
-
/** The value read for each of `chans`, for the subject being folded. */
|
|
142
|
-
values = [];
|
|
143
|
-
/**
|
|
144
|
-
* Within one fill, the last elapsed placed and the last `since` weighed, with what they gave, so
|
|
145
|
-
* subjects sharing a delay or a start share the arithmetic: the same inputs give the same bits.
|
|
146
|
-
*/
|
|
147
|
-
placed = false;
|
|
148
|
-
placedAt = 0;
|
|
149
|
-
phase = 0;
|
|
150
|
-
pass = 0;
|
|
151
|
-
read = false;
|
|
152
|
-
readAt = 0;
|
|
153
|
-
weighed = false;
|
|
154
|
-
weighedSince = 0;
|
|
155
|
-
fade = 1;
|
|
156
|
-
/** Whether the voice has no fade in or out this fill, so its envelope is 1 for every subject. */
|
|
157
|
-
flat = false;
|
|
158
|
-
/** A motion patch's state, which the lane samples in place of calling the patch. */
|
|
159
|
-
motion;
|
|
160
|
-
voiceAt() {
|
|
161
|
-
return this.voice;
|
|
162
|
-
}
|
|
163
|
-
keep(p, xs, n) {
|
|
164
|
-
if (this.axes !== n) {
|
|
165
|
-
this.axes = n;
|
|
166
|
-
this.samples = new Float64Array(0);
|
|
167
|
-
}
|
|
168
|
-
if ((p + 1) * n > this.samples.length) {
|
|
169
|
-
const grown = new Float64Array(Math.max(p + 1, (this.samples.length / n) * 2, 4) * n);
|
|
170
|
-
grown.set(this.samples);
|
|
171
|
-
this.samples = grown;
|
|
172
|
-
}
|
|
173
|
-
for (let a = 0; a < n; a++)
|
|
174
|
-
this.samples[p * n + a] = xs[a];
|
|
175
|
-
}
|
|
176
|
-
kept(p, xs) {
|
|
177
|
-
const n = this.axes;
|
|
178
|
-
for (let a = 0; a < n; a++)
|
|
179
|
-
xs[a] = this.samples[p * n + a];
|
|
180
|
-
}
|
|
181
|
-
motionAt() {
|
|
182
|
-
return this.motion;
|
|
183
|
-
}
|
|
184
|
-
constructor(voice) {
|
|
185
|
-
this.voice = voice;
|
|
186
|
-
this.motion = motionOf(voice.patch);
|
|
187
|
-
this.line = voice.built !== null ? SPARSE.keys : SPARSE.other;
|
|
188
|
-
this.solo = voice.named !== null && voice.named.size === 1 && this.motion === undefined;
|
|
189
|
-
this.idle = this.solo;
|
|
190
|
-
}
|
|
191
|
-
positionOf(slot) {
|
|
192
|
-
return this.at.get(slot) ?? -1;
|
|
193
|
-
}
|
|
194
|
-
/** Gives a subject the voice reaches a position, from what its record fixed on first sight. */
|
|
195
|
-
add(slot, held) {
|
|
196
|
-
const p = this.list.length;
|
|
197
|
-
this.list.push(slot);
|
|
198
|
-
this.at.set(slot, p);
|
|
199
|
-
if ((p + 1) * STRIDE > this.data.length) {
|
|
200
|
-
const data = new Float64Array(Math.max(p + 1, (this.data.length / STRIDE) * 2, 4) * STRIDE);
|
|
201
|
-
data.set(this.data);
|
|
202
|
-
this.data = data;
|
|
203
|
-
}
|
|
204
|
-
const o = p * STRIDE;
|
|
205
|
-
this.data[o + DELAY] = held.delay;
|
|
206
|
-
this.data[o + SINCE] = held.shown;
|
|
207
|
-
this.data[o + WEIGHT] = 0;
|
|
208
|
-
this.data[o + PROBED] = 0;
|
|
209
|
-
this.data[o + MSLOT] = -1;
|
|
210
|
-
this.data[o + SAMPLED] = -1;
|
|
211
|
-
this.data[o + MET] = 0;
|
|
212
|
-
this.records[p] = held;
|
|
213
|
-
this.deltas[p] = null;
|
|
214
|
-
}
|
|
215
|
-
/** A subject lost its number: the last position moves into its place. */
|
|
216
|
-
remove(slot) {
|
|
217
|
-
const p = this.at.get(slot);
|
|
218
|
-
if (p === undefined)
|
|
219
|
-
return;
|
|
220
|
-
const last = this.list.length - 1;
|
|
221
|
-
if (p !== last) {
|
|
222
|
-
const moved = this.list[last];
|
|
223
|
-
this.list[p] = moved;
|
|
224
|
-
this.at.set(moved, p);
|
|
225
|
-
this.data.copyWithin(p * STRIDE, last * STRIDE, (last + 1) * STRIDE);
|
|
226
|
-
this.records[p] = this.records[last];
|
|
227
|
-
this.deltas[p] = this.deltas[last] ?? null;
|
|
228
|
-
const n = this.axes;
|
|
229
|
-
if (n > 0)
|
|
230
|
-
this.samples.copyWithin(p * n, last * n, (last + 1) * n);
|
|
231
|
-
}
|
|
232
|
-
this.list.pop();
|
|
233
|
-
this.at.delete(slot);
|
|
234
|
-
this.records[last] = undefined;
|
|
235
|
-
this.deltas[last] = null;
|
|
236
|
-
}
|
|
237
|
-
}
|
|
238
|
-
/** Whether a laned voice belongs in a crowd: one naming a single subject and writing one channel. */
|
|
239
|
-
function crowdable(v) {
|
|
240
|
-
return v.named !== null && v.named.size === 1 && v.slots.length === 1;
|
|
241
|
-
}
|
|
242
|
-
/**
|
|
243
|
-
* A subject's voice time once its voice's holds apply: held at 0 before it starts and at the end of
|
|
244
|
-
* its passes after, and NaN where it shows nothing.
|
|
245
|
-
*/
|
|
246
|
-
function held(voice, elapsed) {
|
|
247
|
-
if (elapsed < 0)
|
|
248
|
-
return voice.holdsBefore ? 0 : Number.NaN;
|
|
249
|
-
return voice.holdsAfter && elapsed > voice.span ? voice.span : elapsed;
|
|
250
|
-
}
|
|
251
|
-
/** The law of a row that has none, a keys or fn voice's. */
|
|
252
|
-
const none = new Float64Array(0);
|
|
253
|
-
/** Per row of a crowd's `hot`: what the fill reads of the row's voice and its current stretch. */
|
|
254
|
-
const H_FLAGS = 0;
|
|
255
|
-
const H_NOW = 1;
|
|
256
|
-
const H_ELAPSED = 2;
|
|
257
|
-
const H_RATE = 3;
|
|
258
|
-
/** The voice's weight, held to 0..1. */
|
|
259
|
-
const H_WEIGHT = 4;
|
|
260
|
-
const H_SEEKS = 5;
|
|
261
|
-
const H_EPOCH = 6;
|
|
262
|
-
const H_ID = 7;
|
|
263
|
-
const H_AT = 8;
|
|
264
|
-
const H_MS = 9;
|
|
265
|
-
/** `x0`, `v0` and `to`, `axes` numbers each. */
|
|
266
|
-
const H_X0 = 10;
|
|
267
|
-
/** The voice is playing: live, held or fading. */
|
|
268
|
-
const F_PLAYING = 1;
|
|
269
|
-
/** Its weight and clock are `hot`'s: no fade in or out, rate ramp, subject ramp or kept state. */
|
|
270
|
-
const F_FAST = 2;
|
|
271
|
-
/** Its stretch changed since `hot` copied it. */
|
|
272
|
-
const F_STALE = 4;
|
|
273
|
-
/** `hot` holds its stretch, with nothing pending and no earlier stretch kept. */
|
|
274
|
-
const F_BARE = 8;
|
|
275
|
-
/** A probe has met its subject, so its per-subject numbers are set. */
|
|
276
|
-
const F_PLACED = 16;
|
|
277
|
-
/** Its samples fold as the channel's axes, a number for a number and an array for an array. */
|
|
278
|
-
const F_FOLDS = 32;
|
|
279
|
-
/** Something on the voice changed since `hot` copied it. */
|
|
280
|
-
const F_VOICE = 64;
|
|
281
|
-
/** The row's voice is a motion voice; otherwise its patch is keys or a stateless fn. */
|
|
282
|
-
const F_MOTION = 128;
|
|
283
|
-
/** The voice holds before or after, so its clock is read from the voice. */
|
|
284
|
-
const F_HOLDS = 256;
|
|
285
|
-
/**
|
|
286
|
-
* Every laned voice on one channel that reaches a single subject, a row each, in voice order: the
|
|
287
|
-
* per-subject numbers a lane keeps, and a copy of what a fill reads of the voice and, for a motion
|
|
288
|
-
* voice, its patch, so a fill over 100k of them reads a few flat arrays instead of 100k voices,
|
|
289
|
-
* lanes and patch buffers. Each copy is written when its source changes: the voice's by
|
|
290
|
-
* `voiceChanged`, the stretch through the patch's `touched`.
|
|
291
|
-
*/
|
|
292
|
-
class Crowd {
|
|
293
|
-
chans;
|
|
294
|
-
axes;
|
|
295
|
-
/** The subject number at each row. */
|
|
296
|
-
list = [];
|
|
297
|
-
/** `STRIDE` numbers per row, laid out as a lane's. */
|
|
298
|
-
data = new Float64Array(0);
|
|
299
|
-
hot = new Float64Array(0);
|
|
300
|
-
stride;
|
|
301
|
-
samples = new Float64Array(0);
|
|
302
|
-
deltas = [];
|
|
303
|
-
records = [];
|
|
304
|
-
voices = [];
|
|
305
|
-
motions = [];
|
|
306
|
-
eases = [];
|
|
307
|
-
/** Each row's patch law, one array shared by every row whose law is the same. */
|
|
308
|
-
laws = [];
|
|
309
|
-
touches = [];
|
|
310
|
-
rowOf = new Map();
|
|
311
|
-
idle = false;
|
|
312
|
-
line = SPARSE.other;
|
|
313
|
-
/** Where the fill now under way has reached. */
|
|
314
|
-
cursor = 0;
|
|
315
|
-
/** Rows whose voices have left, kept in place until the crowds are next rebuilt. */
|
|
316
|
-
dead = 0;
|
|
317
|
-
xs;
|
|
318
|
-
vs;
|
|
319
|
-
/** What a keys row reads into, folded before the next row reads. */
|
|
320
|
-
delta = {};
|
|
321
|
-
scratch = [];
|
|
322
|
-
constructor(chans, axes) {
|
|
323
|
-
this.chans = chans;
|
|
324
|
-
this.axes = axes;
|
|
325
|
-
this.stride = H_X0 + 3 * axes;
|
|
326
|
-
this.xs = new Float64Array(axes);
|
|
327
|
-
this.vs = new Float64Array(axes);
|
|
328
|
-
}
|
|
329
|
-
voiceAt(p) {
|
|
330
|
-
return this.voices[p];
|
|
331
|
-
}
|
|
332
|
-
/** Samples of another width than the channel's, which `samples` cannot hold, by row. */
|
|
333
|
-
odd = null;
|
|
334
|
-
keep(p, xs, n) {
|
|
335
|
-
if (n === this.axes) {
|
|
336
|
-
for (let a = 0; a < n; a++)
|
|
337
|
-
this.samples[p * n + a] = xs[a];
|
|
338
|
-
this.odd?.delete(p);
|
|
339
|
-
return;
|
|
340
|
-
}
|
|
341
|
-
this.odd ??= new Map();
|
|
342
|
-
this.odd.set(p, xs.slice(0, n));
|
|
343
|
-
}
|
|
344
|
-
kept(p, xs) {
|
|
345
|
-
const odd = this.odd?.get(p);
|
|
346
|
-
if (odd !== undefined) {
|
|
347
|
-
xs.set(odd);
|
|
348
|
-
return;
|
|
349
|
-
}
|
|
350
|
-
const n = this.axes;
|
|
351
|
-
for (let a = 0; a < n; a++)
|
|
352
|
-
xs[a] = this.samples[p * n + a];
|
|
353
|
-
}
|
|
354
|
-
motionAt(p) {
|
|
355
|
-
return this.motions[p];
|
|
356
|
-
}
|
|
357
|
-
get size() {
|
|
358
|
-
return this.list.length;
|
|
359
|
-
}
|
|
360
|
-
/** Makes room for `rows` rows. */
|
|
361
|
-
reserve(rows) {
|
|
362
|
-
if (rows * STRIDE <= this.data.length)
|
|
363
|
-
return;
|
|
364
|
-
const cap = Math.max(rows, (this.data.length / STRIDE) * 2, 16);
|
|
365
|
-
const data = new Float64Array(cap * STRIDE);
|
|
366
|
-
data.set(this.data);
|
|
367
|
-
this.data = data;
|
|
368
|
-
const hot = new Float64Array(cap * this.stride);
|
|
369
|
-
hot.set(this.hot);
|
|
370
|
-
this.hot = hot;
|
|
371
|
-
const samples = new Float64Array(cap * this.axes);
|
|
372
|
-
samples.set(this.samples);
|
|
373
|
-
this.samples = samples;
|
|
374
|
-
}
|
|
375
|
-
}
|
|
9
|
+
import { reportedRow, unplace } from './rows.js';
|
|
376
10
|
/**
|
|
377
11
|
* The lanes of one mix: subject numbers, which channels and voices run on lanes, and the values a
|
|
378
12
|
* frame's fill leaves for probes to copy.
|
|
379
13
|
*/
|
|
14
|
+
// biome-ignore lint/suspicious/noUnsafeDeclarationMerging: every member of `Methods` is installed below
|
|
380
15
|
export class Lanes {
|
|
381
16
|
host;
|
|
382
17
|
numbers;
|
|
383
18
|
lanes = [];
|
|
384
|
-
/** The lanes a fill visits: every lane but the solo ones. */
|
|
385
|
-
runs = [];
|
|
386
19
|
/** The lanes over every subject that have started playing, by epoch. */
|
|
387
20
|
dense = [];
|
|
388
21
|
byId = new Map();
|
|
389
22
|
/** The crowds of single-subject motion voices, one per channel, and which holds each voice. */
|
|
390
23
|
crowds = [];
|
|
391
24
|
crowdOf = new Map();
|
|
25
|
+
/** Whether two crowds share a channel, so their rows must interleave in voice order. */
|
|
26
|
+
overlap = false;
|
|
27
|
+
/** The loci whose voices are on lanes, every member of each on one. */
|
|
28
|
+
loci = [];
|
|
29
|
+
/** While a fill gathers a locus: that locus and the member gathering, where folds go instead. */
|
|
30
|
+
into = null;
|
|
31
|
+
intoId = 0;
|
|
392
32
|
lawsByKey = new Map();
|
|
33
|
+
lastLaw = null;
|
|
34
|
+
/** The lanes the last `meet` gave a position, and whether it placed a crowd row. */
|
|
35
|
+
met = [];
|
|
36
|
+
metCrowd = false;
|
|
37
|
+
/** By subject number, the laned voices its probes fold on the general path this fill. */
|
|
38
|
+
owed = new Owed();
|
|
393
39
|
laned = [];
|
|
394
40
|
/** By kit slot, the laned channel there. */
|
|
395
41
|
bySlot = [];
|
|
@@ -431,11 +77,14 @@ export class Lanes {
|
|
|
431
77
|
* the general path has to make.
|
|
432
78
|
*/
|
|
433
79
|
late = [];
|
|
434
|
-
/**
|
|
80
|
+
/**
|
|
81
|
+
* What `writeLater` and `pullRun` have queued, for one set of columns, as runs: each run's first
|
|
82
|
+
* lane slot, the row it goes to, and how many subjects follow both one apart.
|
|
83
|
+
*/
|
|
435
84
|
queueSlots = new Int32Array(0);
|
|
436
85
|
queueRows = new Int32Array(0);
|
|
86
|
+
queueCounts = new Int32Array(0);
|
|
437
87
|
queued = 0;
|
|
438
|
-
inOrder = true;
|
|
439
88
|
queuedFor = null;
|
|
440
89
|
keeps = false;
|
|
441
90
|
now = Number.NaN;
|
|
@@ -459,7 +108,7 @@ export class Lanes {
|
|
|
459
108
|
qualifiedVersion = Number.NaN;
|
|
460
109
|
constructor(host) {
|
|
461
110
|
this.host = host;
|
|
462
|
-
this.numbers = new Numbers(
|
|
111
|
+
this.numbers = new Numbers(this);
|
|
463
112
|
}
|
|
464
113
|
/** A change that may move a voice on or off its lane: qualify again, then fill again. */
|
|
465
114
|
invalidate() {
|
|
@@ -483,12 +132,12 @@ export class Lanes {
|
|
|
483
132
|
const slot = this.numbers.take(subject);
|
|
484
133
|
this.live++;
|
|
485
134
|
this.grow(slot + 1);
|
|
486
|
-
this.per[slot * SLOT + FILLED] = 0;
|
|
487
|
-
this.per[slot * SLOT + SEEN] = -1;
|
|
488
|
-
this.per[slot * SLOT + IDLE] = 0;
|
|
489
|
-
this.per[slot * SLOT + LANE_PROBE] = 0;
|
|
490
|
-
this.per[slot * SLOT + GENERAL_PROBE] = 0;
|
|
491
|
-
this.per[slot * SLOT + LANE_FILL] = 0;
|
|
135
|
+
this.per[slot * 6 /* Per.SLOT */ + 0 /* Per.FILLED */] = 0;
|
|
136
|
+
this.per[slot * 6 /* Per.SLOT */ + 1 /* Per.SEEN */] = -1;
|
|
137
|
+
this.per[slot * 6 /* Per.SLOT */ + 2 /* Per.IDLE */] = 0;
|
|
138
|
+
this.per[slot * 6 /* Per.SLOT */ + 3 /* Per.LANE_PROBE */] = 0;
|
|
139
|
+
this.per[slot * 6 /* Per.SLOT */ + 4 /* Per.GENERAL_PROBE */] = 0;
|
|
140
|
+
this.per[slot * 6 /* Per.SLOT */ + 5 /* Per.LANE_FILL */] = 0;
|
|
492
141
|
if (this.filling)
|
|
493
142
|
this.late.push(slot);
|
|
494
143
|
return slot;
|
|
@@ -503,34 +152,19 @@ export class Lanes {
|
|
|
503
152
|
if (lane.positionOf(slot) < 0)
|
|
504
153
|
return;
|
|
505
154
|
if (lane.idle)
|
|
506
|
-
|
|
155
|
+
reach(this, slot, -1);
|
|
507
156
|
lane.remove(slot);
|
|
508
157
|
return;
|
|
509
158
|
}
|
|
510
159
|
const c = this.crowdOf.get(id);
|
|
511
160
|
const p = c?.rowOf.get(id);
|
|
512
161
|
if (c !== undefined && p !== undefined && c.list[p] === slot)
|
|
513
|
-
|
|
514
|
-
}
|
|
515
|
-
/** Takes a crowd row off its subject, to be placed again if a probe meets it again. */
|
|
516
|
-
unplace(c, p) {
|
|
517
|
-
const slot = c.list[p];
|
|
518
|
-
if (c.idle && slot >= 0)
|
|
519
|
-
this.reach(slot, -1);
|
|
520
|
-
c.list[p] = -1;
|
|
521
|
-
c.records[p] = undefined;
|
|
522
|
-
c.deltas[p] = null;
|
|
523
|
-
const f = p * c.stride + H_FLAGS;
|
|
524
|
-
c.hot[f] = c.hot[f] & ~F_PLACED;
|
|
525
|
-
const o = p * STRIDE;
|
|
526
|
-
c.data[o + MSLOT] = -1;
|
|
527
|
-
c.data[o + SAMPLED] = -1;
|
|
528
|
-
c.data[o + MET] = 0;
|
|
162
|
+
unplace(this, c, p);
|
|
529
163
|
}
|
|
530
164
|
/** A voice brought a subject back: its next probe meets every lane again, as it met them first. */
|
|
531
165
|
rejoin(slot) {
|
|
532
166
|
if (slot < this.cap)
|
|
533
|
-
this.per[slot * SLOT + SEEN] = -1;
|
|
167
|
+
this.per[slot * 6 /* Per.SLOT */ + 1 /* Per.SEEN */] = -1;
|
|
534
168
|
}
|
|
535
169
|
/**
|
|
536
170
|
* A laned voice's weight for a subject at the last frame a probe read it from the lane; undefined
|
|
@@ -545,92 +179,55 @@ export class Lanes {
|
|
|
545
179
|
return this.reported(lane, slot);
|
|
546
180
|
const c = this.crowdOf.get(id);
|
|
547
181
|
const p = c?.rowOf.get(id);
|
|
548
|
-
return c === undefined || p === undefined ? undefined :
|
|
182
|
+
return c === undefined || p === undefined ? undefined : reportedRow(this, c, p, slot);
|
|
549
183
|
}
|
|
550
184
|
reported(lane, slot) {
|
|
551
|
-
const o = slot * SLOT
|
|
552
|
-
if (!(this.per[o + LANE_PROBE] > this.per[o + GENERAL_PROBE]))
|
|
185
|
+
const o = slot * 6 /* Per.SLOT */;
|
|
186
|
+
if (!(this.per[o + 3 /* Per.LANE_PROBE */] > this.per[o + 4 /* Per.GENERAL_PROBE */]))
|
|
553
187
|
return undefined;
|
|
554
188
|
const p = lane.positionOf(slot);
|
|
555
189
|
if (p < 0)
|
|
556
190
|
return undefined;
|
|
557
|
-
return lane.data[p * STRIDE + (this.per[o + LANE_FILL] === this.fills ? WEIGHT : PROBED)];
|
|
191
|
+
return lane.data[p * 8 /* Row.STRIDE */ + (this.per[o + 5 /* Per.LANE_FILL */] === this.fills ? 2 /* Row.WEIGHT */ : 3 /* Row.PROBED */)];
|
|
558
192
|
}
|
|
559
193
|
/**
|
|
560
194
|
* Fills the lanes once a frame, and says whether this probe of a subject reads its values from
|
|
561
195
|
* them. One that a lane has not met yet, newly numbered or newly reached, takes the general path
|
|
562
196
|
* this frame, which is where it is first seen; so does one probed from inside a fill.
|
|
563
197
|
*/
|
|
564
|
-
prepare(slot, subject, now, version) {
|
|
565
|
-
const ready = this.filled(now, version) ? READY : this.begin(now, version);
|
|
566
|
-
if (ready === GENERAL)
|
|
198
|
+
prepare(slot, subject, now, version, head) {
|
|
199
|
+
const ready = this.filled(now, version) ? 1 /* Begin.READY */ : this.begin(now, version);
|
|
200
|
+
if (ready === 0 /* Begin.GENERAL */)
|
|
567
201
|
return this.general(slot);
|
|
568
202
|
if (slot < 0)
|
|
569
203
|
return false;
|
|
570
|
-
if (ready === SOLO) {
|
|
571
|
-
if (this.per[slot * SLOT + SEEN] < this.wide)
|
|
572
|
-
this.meet(slot, subject);
|
|
573
|
-
return this.general(slot);
|
|
574
|
-
}
|
|
575
204
|
if (!this.probedThisFrame(slot))
|
|
576
205
|
this.distinct++;
|
|
577
206
|
const probe = ++this.probes;
|
|
578
207
|
const per = this.per;
|
|
579
|
-
const o = slot * SLOT
|
|
580
|
-
let lane = per[o + FILLED] === this.fills && per[o + IDLE] === 0;
|
|
581
|
-
if (per[o + SEEN] < this.wide &&
|
|
582
|
-
// The fill ran before the subject had these positions
|
|
583
|
-
|
|
584
|
-
|
|
208
|
+
const o = slot * 6 /* Per.SLOT */;
|
|
209
|
+
let lane = per[o + 0 /* Per.FILLED */] === this.fills && per[o + 2 /* Per.IDLE */] === 0;
|
|
210
|
+
if (per[o + 1 /* Per.SEEN */] < this.wide && meet(this, slot, subject, head)) {
|
|
211
|
+
// The fill ran before the subject had these positions. Where every voice it just met folds
|
|
212
|
+
// after every other laned voice, the general path folds just those onto the lanes' values;
|
|
213
|
+
// otherwise it reads the general path all frame.
|
|
214
|
+
if (lane && per[o + 2 /* Per.IDLE */] === 0 && owable(this, slot))
|
|
215
|
+
this.owed.owe(slot, this.met);
|
|
216
|
+
else {
|
|
217
|
+
per[o + 0 /* Per.FILLED */] = 0;
|
|
218
|
+
lane = false;
|
|
219
|
+
}
|
|
585
220
|
}
|
|
586
221
|
if (lane) {
|
|
587
|
-
per[o + LANE_PROBE] = probe;
|
|
588
|
-
per[o + LANE_FILL] = this.fills;
|
|
222
|
+
per[o + 3 /* Per.LANE_PROBE */] = probe;
|
|
223
|
+
per[o + 5 /* Per.LANE_FILL */] = this.fills;
|
|
589
224
|
this.subjects[slot] = subject;
|
|
590
225
|
this.holding = true;
|
|
591
226
|
}
|
|
592
227
|
else
|
|
593
|
-
per[o + GENERAL_PROBE] = probe;
|
|
228
|
+
per[o + 4 /* Per.GENERAL_PROBE */] = probe;
|
|
594
229
|
return lane;
|
|
595
230
|
}
|
|
596
|
-
/**
|
|
597
|
-
* For a `pull` of a list it read last time in the same order, with every voice on a lane, where
|
|
598
|
-
* `heads` holds each position's remembered chain head unless all are known current: what
|
|
599
|
-
* `prepare` and `writeLater` do for each subject from position `from`, while the subject reads
|
|
600
|
-
* from the lanes; returns the first position that does not, or the list's length.
|
|
601
|
-
*/
|
|
602
|
-
pullRun(slots, list, was, heads, from, columns, now, version) {
|
|
603
|
-
const ready = this.filled(now, version) ? READY : this.begin(now, version);
|
|
604
|
-
if (ready !== READY || !this.whole)
|
|
605
|
-
return from;
|
|
606
|
-
const per = this.per;
|
|
607
|
-
const fills = this.fills;
|
|
608
|
-
const wide = this.wide;
|
|
609
|
-
const frame = this.frameProbes;
|
|
610
|
-
const subjects = this.subjects;
|
|
611
|
-
let n = from;
|
|
612
|
-
for (; n < list.length; n++) {
|
|
613
|
-
const subject = list[n];
|
|
614
|
-
if (was[n] !== subject || (heads !== null && heads[n]?.version !== version))
|
|
615
|
-
break;
|
|
616
|
-
const slot = slots[n];
|
|
617
|
-
const o = slot * SLOT;
|
|
618
|
-
if (slot < 0 ||
|
|
619
|
-
per[o + FILLED] !== fills ||
|
|
620
|
-
per[o + IDLE] !== 0 ||
|
|
621
|
-
per[o + SEEN] < wide)
|
|
622
|
-
break;
|
|
623
|
-
if (!(per[o + LANE_PROBE] > frame || per[o + GENERAL_PROBE] > frame))
|
|
624
|
-
this.distinct++;
|
|
625
|
-
per[o + LANE_PROBE] = ++this.probes;
|
|
626
|
-
per[o + LANE_FILL] = fills;
|
|
627
|
-
subjects[slot] = subject;
|
|
628
|
-
this.writeLater(slot, columns, n);
|
|
629
|
-
}
|
|
630
|
-
if (n > from)
|
|
631
|
-
this.holding = true;
|
|
632
|
-
return n;
|
|
633
|
-
}
|
|
634
231
|
/** Whether this frame's lanes are filled and nothing since asks `begin` to look again. */
|
|
635
232
|
filled(now, version) {
|
|
636
233
|
return (this.filledAt === now &&
|
|
@@ -643,8 +240,7 @@ export class Lanes {
|
|
|
643
240
|
}
|
|
644
241
|
/**
|
|
645
242
|
* What a probe does before its subject, once a frame's lanes are settled: qualifies and fills as
|
|
646
|
-
* needed. Says whether the subject takes the general path
|
|
647
|
-
* there is nothing to fill, or whether the lanes are filled.
|
|
243
|
+
* needed. Says whether the subject takes the general path or the lanes are filled.
|
|
648
244
|
*/
|
|
649
245
|
begin(now, version) {
|
|
650
246
|
if (now !== this.frameAt) {
|
|
@@ -657,110 +253,79 @@ export class Lanes {
|
|
|
657
253
|
this.fresh = true;
|
|
658
254
|
}
|
|
659
255
|
if (this.filling)
|
|
660
|
-
return GENERAL
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
this.
|
|
256
|
+
return 0 /* Begin.GENERAL */;
|
|
257
|
+
// The mix's version moves only when a voice over every subject comes, goes, starts or stops
|
|
258
|
+
// waiting, each of which touches it, so a version past a qualify that stands is a set of touches.
|
|
259
|
+
if (this.qualifiedVersion !== version) {
|
|
260
|
+
if (!Number.isNaN(this.qualifiedVersion) && this.touched.length > 0 && retouch(this))
|
|
261
|
+
this.qualifiedVersion = version;
|
|
262
|
+
else
|
|
263
|
+
requalify(this, version);
|
|
264
|
+
}
|
|
265
|
+
else if (this.touched.length > 0 && !retouch(this))
|
|
266
|
+
requalify(this, version);
|
|
665
267
|
if (this.laned.length === 0)
|
|
666
|
-
return GENERAL
|
|
667
|
-
// Only solo lanes: nothing to fill, and every subject takes the general path.
|
|
668
|
-
if (this.runs.length === 0 && this.crowds.length === 0)
|
|
669
|
-
return SOLO;
|
|
268
|
+
return 0 /* Begin.GENERAL */;
|
|
670
269
|
if (this.filledAt !== now || this.filledVersion !== version || this.moved !== reading.moved) {
|
|
671
|
-
|
|
270
|
+
fillAll(this, now, version);
|
|
672
271
|
// A patch call in that fill made kept state, which took its voice off its lane: fill without it.
|
|
673
272
|
if (this.qualifiedVersion !== version) {
|
|
674
|
-
|
|
273
|
+
requalify(this, version);
|
|
675
274
|
if (this.laned.length === 0)
|
|
676
|
-
return GENERAL
|
|
677
|
-
|
|
275
|
+
return 0 /* Begin.GENERAL */;
|
|
276
|
+
fillAll(this, now, version);
|
|
678
277
|
}
|
|
679
278
|
}
|
|
680
|
-
return READY
|
|
279
|
+
return 1 /* Begin.READY */;
|
|
681
280
|
}
|
|
682
281
|
general(slot) {
|
|
683
282
|
if (slot >= 0)
|
|
684
|
-
this.per[slot * SLOT + GENERAL_PROBE] = ++this.probes;
|
|
283
|
+
this.per[slot * 6 /* Per.SLOT */ + 4 /* Per.GENERAL_PROBE */] = ++this.probes;
|
|
685
284
|
return false;
|
|
686
285
|
}
|
|
687
286
|
/** Whether a probe has read the subject since the frame began, on either path. */
|
|
688
287
|
probedThisFrame(slot) {
|
|
689
288
|
const from = this.frameProbes;
|
|
690
|
-
const o = slot * SLOT
|
|
691
|
-
return (this.per[o +
|
|
289
|
+
const o = slot * 6 /* Per.SLOT */;
|
|
290
|
+
return (this.per[o + 3 /* Per.LANE_PROBE */] > from ||
|
|
291
|
+
this.per[o + 4 /* Per.GENERAL_PROBE */] > from);
|
|
292
|
+
}
|
|
293
|
+
/** Whether the subject at `slot` reads some laned voice from the general path this fill. */
|
|
294
|
+
owes(slot) {
|
|
295
|
+
return this.owed.owes(slot);
|
|
296
|
+
}
|
|
297
|
+
/** Whether the subject at `slot` reads laned voice `id` from the general path this fill. */
|
|
298
|
+
owesVoice(slot, id) {
|
|
299
|
+
return this.owed.owesVoice(slot, id);
|
|
300
|
+
}
|
|
301
|
+
/** The weight the general path gave a voice the subject at `slot` owes, as `weightOf` reports. */
|
|
302
|
+
paid(id, slot, w) {
|
|
303
|
+
const lane = this.byId.get(id);
|
|
304
|
+
const p = lane === undefined ? -1 : lane.positionOf(slot);
|
|
305
|
+
if (p >= 0)
|
|
306
|
+
lane.data[p * 8 /* Row.STRIDE */ + 2 /* Row.WEIGHT */] = w;
|
|
692
307
|
}
|
|
693
308
|
/**
|
|
694
|
-
*
|
|
695
|
-
*
|
|
696
|
-
* channel no lane holds is at rest. Anything queued is written before the lanes fill again.
|
|
309
|
+
* Whether a subject's laned values are at their channels' rest, as `atRest` reads the pose `copy`
|
|
310
|
+
* would write: each number within 1e-9 of its rest.
|
|
697
311
|
*/
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
this.flush();
|
|
701
|
-
this.queuedFor = columns;
|
|
702
|
-
}
|
|
703
|
-
const k = this.queued;
|
|
704
|
-
if (k === this.queueSlots.length) {
|
|
705
|
-
const slots = new Int32Array(Math.max(64, k * 2));
|
|
706
|
-
slots.set(this.queueSlots);
|
|
707
|
-
this.queueSlots = slots;
|
|
708
|
-
const rows = new Int32Array(slots.length);
|
|
709
|
-
rows.set(this.queueRows);
|
|
710
|
-
this.queueRows = rows;
|
|
711
|
-
}
|
|
712
|
-
this.queueSlots[k] = slot;
|
|
713
|
-
this.queueRows[k] = n;
|
|
714
|
-
if (k > 0 &&
|
|
715
|
-
(slot !== this.queueSlots[k - 1] + 1 ||
|
|
716
|
-
n !== this.queueRows[k - 1] + 1))
|
|
717
|
-
this.inOrder = false;
|
|
718
|
-
this.queued = k + 1;
|
|
719
|
-
}
|
|
720
|
-
flush() {
|
|
721
|
-
const count = this.queued;
|
|
722
|
-
const columns = this.queuedFor;
|
|
723
|
-
if (count === 0 || columns === null)
|
|
724
|
-
return;
|
|
725
|
-
this.queued = 0;
|
|
726
|
-
// Subjects numbered in the order the host lists them: each column is one block.
|
|
727
|
-
const block = this.inOrder;
|
|
728
|
-
this.inOrder = true;
|
|
729
|
-
const slots = this.queueSlots;
|
|
730
|
-
const rows = this.queueRows;
|
|
731
|
-
for (let k = 0; k < columns.length; k++) {
|
|
732
|
-
const c = columns[k];
|
|
733
|
-
const axes = c.axes;
|
|
734
|
-
const out = c.out;
|
|
735
|
-
const ch = this.bySlot[c.slot];
|
|
736
|
-
if (ch === undefined) {
|
|
737
|
-
for (let i = 0; i < count; i++) {
|
|
738
|
-
const at = rows[i] * axes;
|
|
739
|
-
for (let a = 0; a < axes; a++)
|
|
740
|
-
out[at + a] = c.rest[a];
|
|
741
|
-
}
|
|
742
|
-
continue;
|
|
743
|
-
}
|
|
312
|
+
rests(slot) {
|
|
313
|
+
for (const ch of this.laned) {
|
|
744
314
|
const values = ch.values;
|
|
745
|
-
if (
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
else if (axes === 1) {
|
|
750
|
-
for (let i = 0; i < count; i++)
|
|
751
|
-
out[rows[i]] = values[slots[i]];
|
|
315
|
+
if (ch.scalar) {
|
|
316
|
+
if (!(Math.abs(values[slot] - ch.rest) < 1e-9))
|
|
317
|
+
return false;
|
|
318
|
+
continue;
|
|
752
319
|
}
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
if (c.bounds !== undefined)
|
|
761
|
-
for (let i = 0; i < count; i++)
|
|
762
|
-
clampRun(out, rows[i] * axes, axes, c.bounds);
|
|
320
|
+
// `copy` lengthens a rest shorter than the channel's axes, and a longer array is never near it.
|
|
321
|
+
if (ch.start.length < ch.axes)
|
|
322
|
+
return false;
|
|
323
|
+
const base = slot * ch.axes;
|
|
324
|
+
for (let a = 0; a < ch.axes; a++)
|
|
325
|
+
if (!(Math.abs(values[base + a] - ch.start[a]) < 1e-9))
|
|
326
|
+
return false;
|
|
763
327
|
}
|
|
328
|
+
return true;
|
|
764
329
|
}
|
|
765
330
|
/** Writes a subject's laned values into a pose, each array channel into a new array. */
|
|
766
331
|
copy(slot, pose) {
|
|
@@ -780,104 +345,27 @@ export class Lanes {
|
|
|
780
345
|
pose[ch.name] = arr;
|
|
781
346
|
}
|
|
782
347
|
}
|
|
783
|
-
/**
|
|
784
|
-
* Gives a probed subject a position on every lane that started playing since the subject was last
|
|
785
|
-
* checked and that reaches it. True when any did, so the probe takes the general path.
|
|
786
|
-
*/
|
|
787
|
-
meet(slot, subject) {
|
|
788
|
-
const seen = this.per[slot * SLOT + SEEN];
|
|
789
|
-
const from = seen < 0 ? -1 - seen : seen;
|
|
790
|
-
this.per[slot * SLOT + SEEN] = this.epochs;
|
|
791
|
-
let met = false;
|
|
792
|
-
const dense = this.dense;
|
|
793
|
-
for (let i = dense.length - 1; i >= 0; i--) {
|
|
794
|
-
const lane = dense[i];
|
|
795
|
-
if (lane.epoch <= from)
|
|
796
|
-
break;
|
|
797
|
-
if (lane.positionOf(slot) >= 0)
|
|
798
|
-
continue;
|
|
799
|
-
const held = this.host.meet(lane.voice, subject);
|
|
800
|
-
if (!held.reaches)
|
|
801
|
-
continue;
|
|
802
|
-
lane.add(slot, held);
|
|
803
|
-
if (lane.idle)
|
|
804
|
-
this.reach(slot, 1);
|
|
805
|
-
met = true;
|
|
806
|
-
}
|
|
807
|
-
const naming = this.host.naming(subject);
|
|
808
|
-
if (naming !== undefined)
|
|
809
|
-
for (const voice of naming) {
|
|
810
|
-
const lane = this.byId.get(voice.id);
|
|
811
|
-
if (lane === undefined) {
|
|
812
|
-
if (this.place(voice, slot, subject, from))
|
|
813
|
-
met = true;
|
|
814
|
-
continue;
|
|
815
|
-
}
|
|
816
|
-
if (lane.epoch <= from || lane.positionOf(slot) >= 0)
|
|
817
|
-
continue;
|
|
818
|
-
const held = this.host.meet(voice, subject);
|
|
819
|
-
if (!held.reaches)
|
|
820
|
-
continue;
|
|
821
|
-
lane.add(slot, held);
|
|
822
|
-
if (lane.idle)
|
|
823
|
-
this.reach(slot, 1);
|
|
824
|
-
met = true;
|
|
825
|
-
}
|
|
826
|
-
return met;
|
|
827
|
-
}
|
|
828
|
-
/** Places a crowd voice's row on the subject a probe met, as `Lane.add` adds a position. */
|
|
829
|
-
place(voice, slot, subject, from) {
|
|
830
|
-
const c = this.crowdOf.get(voice.id);
|
|
831
|
-
const p = c?.rowOf.get(voice.id);
|
|
832
|
-
if (c === undefined || p === undefined)
|
|
833
|
-
return false;
|
|
834
|
-
const h = p * c.stride;
|
|
835
|
-
const flags = c.hot[h + H_FLAGS];
|
|
836
|
-
if (c.hot[h + H_EPOCH] <= from || (flags & F_PLACED) !== 0)
|
|
837
|
-
return false;
|
|
838
|
-
const held = this.host.meet(voice, subject);
|
|
839
|
-
if (!held.reaches)
|
|
840
|
-
return false;
|
|
841
|
-
const o = p * STRIDE;
|
|
842
|
-
c.list[p] = slot;
|
|
843
|
-
c.data[o + DELAY] = held.delay;
|
|
844
|
-
c.data[o + SINCE] = held.shown;
|
|
845
|
-
c.data[o + WEIGHT] = 0;
|
|
846
|
-
c.data[o + PROBED] = 0;
|
|
847
|
-
c.data[o + MSLOT] = -1;
|
|
848
|
-
c.data[o + SAMPLED] = -1;
|
|
849
|
-
c.data[o + MET] = 0;
|
|
850
|
-
c.records[p] = held;
|
|
851
|
-
c.deltas[p] = null;
|
|
852
|
-
c.hot[h + H_FLAGS] = flags | F_PLACED | F_STALE;
|
|
853
|
-
if (c.idle)
|
|
854
|
-
this.reach(slot, 1);
|
|
855
|
-
return true;
|
|
856
|
-
}
|
|
857
|
-
/** Counts an idle lane more or fewer reaching a subject. */
|
|
858
|
-
reach(slot, by) {
|
|
859
|
-
const i = slot * SLOT + IDLE;
|
|
860
|
-
this.per[i] = this.per[i] + by;
|
|
861
|
-
}
|
|
862
348
|
forget(slot) {
|
|
349
|
+
this.host.forgot(slot);
|
|
863
350
|
this.live--;
|
|
864
351
|
for (const lane of this.lanes)
|
|
865
352
|
lane.remove(slot);
|
|
866
353
|
for (const c of this.crowds)
|
|
867
354
|
for (let p = 0; p < c.size; p++)
|
|
868
355
|
if (c.list[p] === slot)
|
|
869
|
-
|
|
356
|
+
unplace(this, c, p);
|
|
870
357
|
if (slot < this.cap)
|
|
871
|
-
this.per[slot * SLOT + FILLED] = 0;
|
|
358
|
+
this.per[slot * 6 /* Per.SLOT */ + 0 /* Per.FILLED */] = 0;
|
|
872
359
|
this.subjects[slot] = undefined;
|
|
873
360
|
}
|
|
874
361
|
grow(size) {
|
|
875
362
|
if (size <= this.cap)
|
|
876
363
|
return;
|
|
877
364
|
const cap = Math.max(size, this.cap * 2, 64);
|
|
878
|
-
const per = new Float64Array(cap * SLOT);
|
|
365
|
+
const per = new Float64Array(cap * 6 /* Per.SLOT */);
|
|
879
366
|
per.set(this.per);
|
|
880
367
|
this.per = per;
|
|
368
|
+
this.owed.grow(cap);
|
|
881
369
|
for (const ch of this.laned) {
|
|
882
370
|
const values = new Float64Array(cap * ch.axes);
|
|
883
371
|
values.set(ch.values);
|
|
@@ -885,275 +373,21 @@ export class Lanes {
|
|
|
885
373
|
}
|
|
886
374
|
this.cap = cap;
|
|
887
375
|
}
|
|
888
|
-
requalify(version) {
|
|
889
|
-
this.flush();
|
|
890
|
-
const host = this.host;
|
|
891
|
-
this.qualifiedVersion = version;
|
|
892
|
-
this.filledVersion = Number.NaN;
|
|
893
|
-
this.touched.length = 0;
|
|
894
|
-
const present = host.voices.filter((v) => v.state !== 'done');
|
|
895
|
-
for (const v of present)
|
|
896
|
-
if (v.id > this.known)
|
|
897
|
-
this.known = v.id;
|
|
898
|
-
const numeric = host.channels.map((c) => numericOf(c) !== undefined);
|
|
899
|
-
const { channels, voices } = qualify(numeric,
|
|
900
|
-
// A voice writing nothing has no lane to fill, so it stays where its calls are made.
|
|
901
|
-
present.map((v) => ({ id: v.id, fits: v.slots.length > 0 && host.fits(v), slots: v.slots })));
|
|
902
|
-
for (const lane of this.lanes)
|
|
903
|
-
if (!voices.has(lane.voice.id))
|
|
904
|
-
this.leave(lane);
|
|
905
|
-
const kept = [];
|
|
906
|
-
let crowded = 0;
|
|
907
|
-
for (const v of present) {
|
|
908
|
-
if (!voices.has(v.id))
|
|
909
|
-
continue;
|
|
910
|
-
if (crowdable(v)) {
|
|
911
|
-
crowded++;
|
|
912
|
-
continue;
|
|
913
|
-
}
|
|
914
|
-
const lane = this.byId.get(v.id) ?? new Lane(v);
|
|
915
|
-
v.laned = true;
|
|
916
|
-
if (lane.epoch === 0 && v.state !== 'pending')
|
|
917
|
-
lane.epoch = this.open(v);
|
|
918
|
-
kept.push(lane);
|
|
919
|
-
}
|
|
920
|
-
this.lanes = kept;
|
|
921
|
-
this.runs = kept.filter((l) => !l.solo);
|
|
922
|
-
this.dense = kept
|
|
923
|
-
.filter((l) => l.voice.named === null && l.epoch > 0)
|
|
924
|
-
.sort((a, b) => a.epoch - b.epoch);
|
|
925
|
-
this.whole = kept.length + crowded === present.length;
|
|
926
|
-
this.byId.clear();
|
|
927
|
-
for (const lane of kept)
|
|
928
|
-
this.byId.set(lane.voice.id, lane);
|
|
929
|
-
// With no lane left no fill comes to let go of the subjects the last probes held.
|
|
930
|
-
if (kept.length === 0 && crowded === 0)
|
|
931
|
-
this.subjects = [];
|
|
932
|
-
this.laned = [];
|
|
933
|
-
this.bySlot = host.channels.map(() => undefined);
|
|
934
|
-
channels.forEach((on, slot) => {
|
|
935
|
-
if (!on)
|
|
936
|
-
return;
|
|
937
|
-
const channel = host.channels[slot];
|
|
938
|
-
const n = numericOf(channel);
|
|
939
|
-
const rest = channel.rest;
|
|
940
|
-
const ch = {
|
|
941
|
-
name: host.names[slot],
|
|
942
|
-
op: n.op,
|
|
943
|
-
scalar: typeof rest === 'number',
|
|
944
|
-
rest: typeof rest === 'number' ? rest : rest[0],
|
|
945
|
-
axes: n.axes,
|
|
946
|
-
values: new Float64Array(this.cap * n.axes),
|
|
947
|
-
start: typeof rest === 'number' ? [] : rest,
|
|
948
|
-
};
|
|
949
|
-
this.laned.push(ch);
|
|
950
|
-
this.bySlot[slot] = ch;
|
|
951
|
-
});
|
|
952
|
-
this.copies.length = 0;
|
|
953
|
-
for (const ch of this.bySlot)
|
|
954
|
-
this.copies.push(ch !== undefined);
|
|
955
|
-
for (const lane of kept) {
|
|
956
|
-
lane.chans = lane.voice.slots.map((s) => this.bySlot[s]);
|
|
957
|
-
lane.values = lane.chans.map(() => undefined);
|
|
958
|
-
}
|
|
959
|
-
this.regroup(present.filter((v) => voices.has(v.id) && crowdable(v)));
|
|
960
|
-
}
|
|
961
376
|
/**
|
|
962
|
-
*
|
|
963
|
-
*
|
|
964
|
-
* one is handed back to the general path, as a lane's voice is when it leaves.
|
|
377
|
+
* A laned voice's patch is about to change subject `s`'s stretch: a crowd copies it again at the
|
|
378
|
+
* next fill, and a lane keeps the sample the old one gave.
|
|
965
379
|
*/
|
|
966
|
-
|
|
967
|
-
const keep = new Set(members.map((v) => v.id));
|
|
968
|
-
for (const c of this.crowds)
|
|
969
|
-
for (let p = 0; p < c.size; p++) {
|
|
970
|
-
const v = c.voices[p];
|
|
971
|
-
if (!keep.has(v.id) || v.state === 'done')
|
|
972
|
-
this.leaveCrowd(c, p);
|
|
973
|
-
}
|
|
974
|
-
const old = this.crowds;
|
|
975
|
-
const was = new Map(this.crowdOf);
|
|
976
|
-
this.crowds = [];
|
|
977
|
-
this.crowdOf.clear();
|
|
978
|
-
for (const v of members) {
|
|
979
|
-
const from = was.get(v.id);
|
|
980
|
-
this.join(v, from, from?.rowOf.get(v.id), old);
|
|
981
|
-
}
|
|
982
|
-
if (this.crowds.length === 0)
|
|
983
|
-
this.lawsByKey.clear();
|
|
984
|
-
}
|
|
985
|
-
/**
|
|
986
|
-
* Gives a voice the next row of its channel's crowd, made if there is none: a copy of the row it
|
|
987
|
-
* had in `from` at `at`, or a new one. `old` holds the crowds being replaced, whose idle state a
|
|
988
|
-
* new crowd on the same channel keeps. Rows stay in voice order only because voices join in it.
|
|
989
|
-
*/
|
|
990
|
-
join(v, from, at, old) {
|
|
991
|
-
const ch = this.bySlot[v.slots[0]];
|
|
992
|
-
let c = this.crowds.find((o) => o.chans[0] === ch);
|
|
993
|
-
if (c === undefined) {
|
|
994
|
-
c = new Crowd([ch], ch.axes);
|
|
995
|
-
const was = old.find((o) => o.chans[0]?.name === ch.name);
|
|
996
|
-
if (was !== undefined)
|
|
997
|
-
c.idle = was.idle;
|
|
998
|
-
this.crowds.push(c);
|
|
999
|
-
}
|
|
1000
|
-
const p = c.size;
|
|
1001
|
-
c.reserve(p + 1);
|
|
1002
|
-
if (from !== undefined && at !== undefined)
|
|
1003
|
-
this.copyRow(from, at, c, p);
|
|
1004
|
-
else
|
|
1005
|
-
this.newRow(c, p, v);
|
|
1006
|
-
if (c.hot[p * c.stride + H_EPOCH] === 0 && v.state !== 'pending')
|
|
1007
|
-
c.hot[p * c.stride + H_EPOCH] = this.open(v);
|
|
1008
|
-
c.rowOf.set(v.id, p);
|
|
1009
|
-
this.crowdOf.set(v.id, c);
|
|
1010
|
-
v.laned = true;
|
|
1011
|
-
}
|
|
1012
|
-
/**
|
|
1013
|
-
* A voice starts playing on a lane or crowd: its epoch. Every subject meets a voice over all of
|
|
1014
|
-
* them on its next probe; a voice naming its subjects marks only those.
|
|
1015
|
-
*/
|
|
1016
|
-
open(v) {
|
|
1017
|
-
const epoch = ++this.epochs;
|
|
1018
|
-
if (v.named === null) {
|
|
1019
|
-
this.wide = epoch;
|
|
1020
|
-
return epoch;
|
|
1021
|
-
}
|
|
1022
|
-
for (const subject of v.named) {
|
|
1023
|
-
const slot = this.host.slotOf(subject);
|
|
1024
|
-
if (slot < 0 || slot >= this.cap)
|
|
1025
|
-
continue;
|
|
1026
|
-
const i = slot * SLOT + SEEN;
|
|
1027
|
-
const seen = this.per[i];
|
|
1028
|
-
const from = seen < 0 ? -1 - seen : seen;
|
|
1029
|
-
this.per[i] = -1 - Math.min(from, epoch - 1);
|
|
1030
|
-
}
|
|
1031
|
-
return epoch;
|
|
1032
|
-
}
|
|
1033
|
-
/**
|
|
1034
|
-
* Takes the voices `touch` named on or off their crowds, each as it now stands; false at the
|
|
1035
|
-
* first that cannot be, which needs a qualify: one that leaves a lane or the general path, which
|
|
1036
|
-
* may open a channel, or one that joins anything but a crowd on a laned channel. Neither a voice
|
|
1037
|
-
* that fits joining laned channels nor a laned one leaving moves the fixed point `qualify` finds.
|
|
1038
|
-
* False too once a crowd is more than half empty rows, which a qualify rebuilds without.
|
|
1039
|
-
*/
|
|
1040
|
-
retouch() {
|
|
1041
|
-
const touched = [...new Set(this.touched)].sort((a, b) => a.id - b.id);
|
|
1042
|
-
this.touched.length = 0;
|
|
1043
|
-
for (const v of touched) {
|
|
1044
|
-
const known = v.id <= this.known;
|
|
1045
|
-
if (v.id > this.known)
|
|
1046
|
-
this.known = v.id;
|
|
1047
|
-
const c = this.crowdOf.get(v.id);
|
|
1048
|
-
const p = c?.rowOf.get(v.id);
|
|
1049
|
-
if (c !== undefined && p !== undefined) {
|
|
1050
|
-
if (v.state === 'done') {
|
|
1051
|
-
this.bury(c, p);
|
|
1052
|
-
continue;
|
|
1053
|
-
}
|
|
1054
|
-
const h = p * c.stride;
|
|
1055
|
-
if (c.hot[h + H_EPOCH] === 0 && v.state !== 'pending')
|
|
1056
|
-
c.hot[h + H_EPOCH] = this.open(v);
|
|
1057
|
-
c.hot[h + H_FLAGS] = c.hot[h + H_FLAGS] | F_VOICE;
|
|
1058
|
-
continue;
|
|
1059
|
-
}
|
|
1060
|
-
if (v.state === 'done' && !known)
|
|
1061
|
-
continue;
|
|
1062
|
-
if (v.state === 'done' || this.byId.has(v.id))
|
|
1063
|
-
return false;
|
|
1064
|
-
const laned = v.slots.some((slot) => this.bySlot[slot] !== undefined);
|
|
1065
|
-
const fits = v.slots.length > 0 && this.host.fits(v);
|
|
1066
|
-
if (!laned && !fits) {
|
|
1067
|
-
this.whole = false;
|
|
1068
|
-
continue;
|
|
1069
|
-
}
|
|
1070
|
-
if (known ||
|
|
1071
|
-
!fits ||
|
|
1072
|
-
!laned ||
|
|
1073
|
-
!crowdable(v) ||
|
|
1074
|
-
this.bySlot[v.slots[0]] === undefined)
|
|
1075
|
-
return false;
|
|
1076
|
-
this.join(v, undefined, undefined, this.crowds);
|
|
1077
|
-
}
|
|
1078
|
-
return !this.crowds.some((c) => c.dead > 64 && c.dead * 2 > c.size);
|
|
1079
|
-
}
|
|
1080
|
-
/** A crowd voice left: its row stays, empty, until the crowds are next rebuilt. */
|
|
1081
|
-
bury(c, p) {
|
|
1082
|
-
const v = c.voices[p];
|
|
1083
|
-
this.leaveCrowd(c, p);
|
|
1084
|
-
c.list[p] = -1;
|
|
1085
|
-
c.records[p] = undefined;
|
|
1086
|
-
c.deltas[p] = null;
|
|
1087
|
-
c.motions[p] = undefined;
|
|
1088
|
-
c.hot[p * c.stride + H_FLAGS] = 0;
|
|
1089
|
-
c.rowOf.delete(v.id);
|
|
1090
|
-
this.crowdOf.delete(v.id);
|
|
1091
|
-
c.dead++;
|
|
1092
|
-
}
|
|
1093
|
-
copyRow(from, p, to, q) {
|
|
1094
|
-
to.list[q] = from.list[p];
|
|
1095
|
-
to.data.set(from.data.subarray(p * STRIDE, (p + 1) * STRIDE), q * STRIDE);
|
|
1096
|
-
to.hot.set(from.hot.subarray(p * from.stride, (p + 1) * from.stride), q * to.stride);
|
|
1097
|
-
to.samples.set(from.samples.subarray(p * from.axes, (p + 1) * from.axes), q * to.axes);
|
|
1098
|
-
to.deltas[q] = from.deltas[p] ?? null;
|
|
1099
|
-
to.records[q] = from.records[p];
|
|
1100
|
-
to.voices[q] = from.voices[p];
|
|
1101
|
-
to.motions[q] = from.motions[p];
|
|
1102
|
-
to.eases[q] = from.eases[p];
|
|
1103
|
-
to.laws[q] = from.laws[p];
|
|
1104
|
-
to.touches[q] = from.touches[p];
|
|
1105
|
-
const odd = from.odd?.get(p);
|
|
1106
|
-
if (odd !== undefined) {
|
|
1107
|
-
to.odd ??= new Map();
|
|
1108
|
-
to.odd.set(q, odd);
|
|
1109
|
-
}
|
|
1110
|
-
}
|
|
1111
|
-
newRow(c, p, v) {
|
|
1112
|
-
const run = motionOf(v.patch);
|
|
1113
|
-
const h = p * c.stride;
|
|
1114
|
-
c.hot.fill(0, h, h + c.stride);
|
|
1115
|
-
c.hot[h + H_FLAGS] = F_VOICE | F_STALE | (run === undefined ? 0 : F_MOTION);
|
|
1116
|
-
c.hot[h + H_ID] = v.id;
|
|
1117
|
-
c.list[p] = -1;
|
|
1118
|
-
const o = p * STRIDE;
|
|
1119
|
-
c.data.fill(0, o, o + STRIDE);
|
|
1120
|
-
c.data[o + MSLOT] = -1;
|
|
1121
|
-
c.data[o + SAMPLED] = -1;
|
|
1122
|
-
c.deltas[p] = null;
|
|
1123
|
-
c.records[p] = undefined;
|
|
1124
|
-
c.voices[p] = v;
|
|
1125
|
-
c.motions[p] = run;
|
|
1126
|
-
if (run === undefined) {
|
|
1127
|
-
c.laws[p] = none;
|
|
1128
|
-
c.eases[p] = undefined;
|
|
1129
|
-
c.touches[p] = noTouch;
|
|
1130
|
-
return;
|
|
1131
|
-
}
|
|
1132
|
-
c.laws[p] = this.lawOf(run);
|
|
1133
|
-
c.eases[p] = run.ease;
|
|
1134
|
-
const id = v.id;
|
|
1135
|
-
const touch = () => this.touchedCrowd(id);
|
|
1136
|
-
run.touched = touch;
|
|
1137
|
-
c.touches[p] = touch;
|
|
1138
|
-
}
|
|
1139
|
-
/** A patch's law, as the one array every crowd row with the same law shares. */
|
|
1140
|
-
lawOf(run) {
|
|
1141
|
-
const law = run.law();
|
|
1142
|
-
const key = law.join(' ');
|
|
1143
|
-
const known = this.lawsByKey.get(key);
|
|
1144
|
-
if (known !== undefined)
|
|
1145
|
-
return known;
|
|
1146
|
-
this.lawsByKey.set(key, law);
|
|
1147
|
-
return law;
|
|
1148
|
-
}
|
|
1149
|
-
/** A crowd voice's patch changed a stretch: its copy is read again at the next fill. */
|
|
1150
|
-
touchedCrowd(id) {
|
|
380
|
+
stretchChanged(id, s) {
|
|
1151
381
|
const c = this.crowdOf.get(id);
|
|
1152
382
|
const p = c?.rowOf.get(id);
|
|
1153
|
-
if (c
|
|
383
|
+
if (c !== undefined && p !== undefined) {
|
|
384
|
+
c.restale(p, c.hot[p * c.stride + 0 /* Hot.FLAGS */]);
|
|
1154
385
|
return;
|
|
1155
|
-
|
|
1156
|
-
|
|
386
|
+
}
|
|
387
|
+
const lane = this.byId.get(id);
|
|
388
|
+
const q = lane === undefined ? -1 : lane.positionOfMotion(s);
|
|
389
|
+
if (q >= 0)
|
|
390
|
+
lane.fix(q);
|
|
1157
391
|
}
|
|
1158
392
|
/** Something on a voice changed: a crowd copies it again at the next fill. */
|
|
1159
393
|
voiceChanged(id) {
|
|
@@ -1161,690 +395,28 @@ export class Lanes {
|
|
|
1161
395
|
const p = c?.rowOf.get(id);
|
|
1162
396
|
if (c === undefined || p === undefined)
|
|
1163
397
|
return;
|
|
1164
|
-
const f = p * c.stride +
|
|
1165
|
-
c.hot[f] = c.hot[f] |
|
|
1166
|
-
}
|
|
1167
|
-
/** Hands a crowd voice back to the general path, as `leave` does a lane's. */
|
|
1168
|
-
leaveCrowd(c, p) {
|
|
1169
|
-
const slot = c.list[p];
|
|
1170
|
-
const rec = c.records[p];
|
|
1171
|
-
if (slot >= 0) {
|
|
1172
|
-
if (c.idle)
|
|
1173
|
-
this.reach(slot, -1);
|
|
1174
|
-
const w = this.reportedRow(c, p, slot);
|
|
1175
|
-
if (rec !== undefined && w !== undefined)
|
|
1176
|
-
rec.weight = w;
|
|
1177
|
-
if (rec !== undefined && c.motions[p] !== undefined)
|
|
1178
|
-
this.settle(c, p, slot, rec);
|
|
1179
|
-
}
|
|
1180
|
-
const run = c.motions[p];
|
|
1181
|
-
if (run !== undefined && run.touched === c.touches[p])
|
|
1182
|
-
run.touched = noTouch;
|
|
1183
|
-
c.voices[p].laned = false;
|
|
1184
|
-
}
|
|
1185
|
-
/** A crowd row's weight as `reported` reads a lane position's. */
|
|
1186
|
-
reportedRow(c, p, slot) {
|
|
1187
|
-
const o = slot * SLOT;
|
|
1188
|
-
if (!(this.per[o + LANE_PROBE] > this.per[o + GENERAL_PROBE]))
|
|
1189
|
-
return undefined;
|
|
1190
|
-
if (c.list[p] !== slot)
|
|
1191
|
-
return undefined;
|
|
1192
|
-
return c.data[p * STRIDE + (this.per[o + LANE_FILL] === this.fills ? WEIGHT : PROBED)];
|
|
1193
|
-
}
|
|
1194
|
-
/** Hands a voice back to the general path, with the weights `weightOf` reports kept on its records. */
|
|
1195
|
-
leave(lane) {
|
|
1196
|
-
if (lane.idle)
|
|
1197
|
-
this.wake(lane);
|
|
1198
|
-
for (let p = 0; p < lane.list.length; p++) {
|
|
1199
|
-
const rec = lane.records[p];
|
|
1200
|
-
const slot = lane.list[p];
|
|
1201
|
-
const w = this.reported(lane, slot);
|
|
1202
|
-
if (rec !== undefined && w !== undefined)
|
|
1203
|
-
rec.weight = w;
|
|
1204
|
-
if (rec !== undefined && lane.motion !== undefined)
|
|
1205
|
-
this.settle(lane, p, slot, rec);
|
|
1206
|
-
}
|
|
1207
|
-
lane.voice.laned = false;
|
|
1208
|
-
}
|
|
1209
|
-
fillAll(now, version) {
|
|
1210
|
-
this.flush();
|
|
1211
|
-
const host = this.host;
|
|
1212
|
-
this.filling = true;
|
|
1213
|
-
try {
|
|
1214
|
-
this.fills++;
|
|
1215
|
-
this.now = now;
|
|
1216
|
-
this.filledAt = now;
|
|
1217
|
-
this.filledVersion = version;
|
|
1218
|
-
const size = this.numbers.size;
|
|
1219
|
-
this.keeps = host.keeps;
|
|
1220
|
-
const decide = this.fresh;
|
|
1221
|
-
this.fresh = false;
|
|
1222
|
-
const share = this.live > 0 ? this.lastDistinct / this.live : 1;
|
|
1223
|
-
// Deciding which lanes and crowds go idle first, then running them, in voice order: a crowd's
|
|
1224
|
-
// rows are folded between the lanes on either side of their voices, as the general path
|
|
1225
|
-
// folds every voice in order.
|
|
1226
|
-
let busy = false;
|
|
1227
|
-
for (const lane of this.runs) {
|
|
1228
|
-
if (decide) {
|
|
1229
|
-
// A lane with few subjects, such as one naming its own, goes by the share of all probed;
|
|
1230
|
-
// one with a single subject shares nothing a fill could save, as a solo lane does not.
|
|
1231
|
-
const line = lane.line;
|
|
1232
|
-
const n = lane.list.length;
|
|
1233
|
-
if (n >= 64)
|
|
1234
|
-
this.pace(lane);
|
|
1235
|
-
else if (n === 1 && lane.motion === undefined) {
|
|
1236
|
-
if (!lane.idle)
|
|
1237
|
-
this.rest(lane);
|
|
1238
|
-
}
|
|
1239
|
-
else if (lane.idle ? share >= line.start : share < line.stop)
|
|
1240
|
-
this.flip(lane);
|
|
1241
|
-
}
|
|
1242
|
-
if (!lane.idle)
|
|
1243
|
-
busy = true;
|
|
1244
|
-
}
|
|
1245
|
-
for (const c of this.crowds) {
|
|
1246
|
-
if (decide) {
|
|
1247
|
-
if (c.size >= 64)
|
|
1248
|
-
this.pace(c);
|
|
1249
|
-
else if (c.idle ? share >= c.line.start : share < c.line.stop)
|
|
1250
|
-
this.flip(c);
|
|
1251
|
-
}
|
|
1252
|
-
c.cursor = 0;
|
|
1253
|
-
if (!c.idle && c.size > 0)
|
|
1254
|
-
busy = true;
|
|
1255
|
-
}
|
|
1256
|
-
if (busy) {
|
|
1257
|
-
for (const ch of this.laned)
|
|
1258
|
-
ch.values.fill(ch.rest, 0, size * ch.axes);
|
|
1259
|
-
for (const lane of this.runs) {
|
|
1260
|
-
if (lane.idle)
|
|
1261
|
-
continue;
|
|
1262
|
-
if (this.crowds.length > 0)
|
|
1263
|
-
this.crowdsUpTo(lane.voice.id);
|
|
1264
|
-
this.run(lane);
|
|
1265
|
-
}
|
|
1266
|
-
if (this.crowds.length > 0)
|
|
1267
|
-
this.crowdsUpTo(Number.POSITIVE_INFINITY);
|
|
1268
|
-
}
|
|
1269
|
-
// With every lane idle there was nothing to fill, and no subject reads a lane this frame.
|
|
1270
|
-
if (busy)
|
|
1271
|
-
for (let s = 0; s < size; s++)
|
|
1272
|
-
this.per[s * SLOT + FILLED] = this.fills;
|
|
1273
|
-
for (const slot of this.late)
|
|
1274
|
-
this.per[slot * SLOT + FILLED] = 0;
|
|
1275
|
-
if (this.holding) {
|
|
1276
|
-
this.subjects.fill(undefined, 0, size);
|
|
1277
|
-
this.holding = false;
|
|
1278
|
-
}
|
|
1279
|
-
this.moved = reading.moved;
|
|
1280
|
-
}
|
|
1281
|
-
finally {
|
|
1282
|
-
this.filling = false;
|
|
1283
|
-
this.late.length = 0;
|
|
1284
|
-
}
|
|
1285
|
-
}
|
|
1286
|
-
/**
|
|
1287
|
-
* Sets a lane idle or busy by the share of its subjects probed in the last frame that had a probe,
|
|
1288
|
-
* read from about 256 of them a prime stride apart, from an offset that moves each frame, so a host
|
|
1289
|
-
* probing every second or third subject is not read as probing all or none. Both paths give the
|
|
1290
|
-
* same values, so this only picks the cheaper.
|
|
1291
|
-
*/
|
|
1292
|
-
pace(lane) {
|
|
1293
|
-
const list = lane.list;
|
|
1294
|
-
const line = lane.line;
|
|
1295
|
-
const from = this.lastFrom;
|
|
1296
|
-
const to = this.lastTo;
|
|
1297
|
-
const step = primeFrom(Math.floor(list.length / 256));
|
|
1298
|
-
let looked = 0;
|
|
1299
|
-
let probed = 0;
|
|
1300
|
-
for (let p = this.fills % step; p < list.length; p += step) {
|
|
1301
|
-
const slot = list[p];
|
|
1302
|
-
if (slot < 0)
|
|
1303
|
-
continue;
|
|
1304
|
-
const a = this.per[slot * SLOT + LANE_PROBE];
|
|
1305
|
-
const b = this.per[slot * SLOT + GENERAL_PROBE];
|
|
1306
|
-
if ((a > from && a <= to) || (b > from && b <= to))
|
|
1307
|
-
probed++;
|
|
1308
|
-
looked++;
|
|
1309
|
-
}
|
|
1310
|
-
const share = looked > 0 ? probed / looked : 1;
|
|
1311
|
-
if (lane.idle ? share >= line.start : share < line.stop)
|
|
1312
|
-
this.flip(lane);
|
|
1313
|
-
}
|
|
1314
|
-
flip(lane) {
|
|
1315
|
-
if (lane.idle)
|
|
1316
|
-
this.wake(lane);
|
|
1317
|
-
else
|
|
1318
|
-
this.rest(lane);
|
|
1319
|
-
}
|
|
1320
|
-
/** Leaves a lane's subjects to the general path until it wakes, keeping what `weightOf` reports. */
|
|
1321
|
-
rest(lane) {
|
|
1322
|
-
lane.idle = true;
|
|
1323
|
-
const list = lane.list;
|
|
1324
|
-
const data = lane.data;
|
|
1325
|
-
for (let p = 0; p < list.length; p++) {
|
|
1326
|
-
const slot = list[p];
|
|
1327
|
-
if (slot < 0)
|
|
1328
|
-
continue;
|
|
1329
|
-
this.reach(slot, 1);
|
|
1330
|
-
// A probe read this subject from the last fill, which this lane's weight stays from.
|
|
1331
|
-
if (this.per[slot * SLOT + LANE_FILL] === this.fills - 1) {
|
|
1332
|
-
const o = p * STRIDE;
|
|
1333
|
-
data[o + PROBED] = data[o + WEIGHT];
|
|
1334
|
-
}
|
|
1335
|
-
}
|
|
1336
|
-
}
|
|
1337
|
-
wake(lane) {
|
|
1338
|
-
lane.idle = false;
|
|
1339
|
-
for (const slot of lane.list)
|
|
1340
|
-
if (slot >= 0)
|
|
1341
|
-
this.reach(slot, -1);
|
|
1342
|
-
}
|
|
1343
|
-
subjectAt(slot) {
|
|
1344
|
-
let subject = this.subjects[slot];
|
|
1345
|
-
if (subject === undefined) {
|
|
1346
|
-
subject = this.numbers.subject(slot);
|
|
1347
|
-
this.subjects[slot] = subject;
|
|
1348
|
-
this.holding = true;
|
|
1349
|
-
}
|
|
1350
|
-
return subject;
|
|
1351
|
-
}
|
|
1352
|
-
/** One voice's contribution to every subject it plays on. */
|
|
1353
|
-
run(lane) {
|
|
1354
|
-
const voice = lane.voice;
|
|
1355
|
-
if (voice.state === 'pending' || voice.state === 'done')
|
|
1356
|
-
return;
|
|
1357
|
-
lane.placed = false;
|
|
1358
|
-
lane.read = false;
|
|
1359
|
-
lane.weighed = false;
|
|
1360
|
-
// With no fade in or out the envelope is 1 for every subject, which is what it would return.
|
|
1361
|
-
lane.flat = !((voice.fade.in ?? 0) > 0) && voice.out === null;
|
|
1362
|
-
lane.fade = 1;
|
|
1363
|
-
if (lane.motion !== undefined && !voice.keeping) {
|
|
1364
|
-
this.runMotion(lane, lane.motion);
|
|
1365
|
-
return;
|
|
1366
|
-
}
|
|
1367
|
-
const elapsed = voice.elapsedAt(this.now);
|
|
1368
|
-
const period = voice.patch.period;
|
|
1369
|
-
const passes = voice.passes;
|
|
1370
|
-
const list = lane.list;
|
|
1371
|
-
for (let p = 0; p < list.length; p++) {
|
|
1372
|
-
this.one(lane, p, list[p], elapsed, period, passes);
|
|
1373
|
-
// Its patch just made kept state: no further call this fill, the general path makes them.
|
|
1374
|
-
if (voice.keeping)
|
|
1375
|
-
return;
|
|
1376
|
-
}
|
|
1377
|
-
}
|
|
1378
|
-
one(lane, p, slot, elapsedNow, period, passes) {
|
|
1379
|
-
const host = this.host;
|
|
1380
|
-
const voice = lane.voice;
|
|
1381
|
-
const data = lane.data;
|
|
1382
|
-
const o = p * STRIDE;
|
|
1383
|
-
// A probe read this subject from the last fill: keep the weight it read for `weightOf`.
|
|
1384
|
-
if (this.per[slot * SLOT + LANE_FILL] === this.fills - 1)
|
|
1385
|
-
data[o + PROBED] = data[o + WEIGHT];
|
|
1386
|
-
const delay = data[o + DELAY];
|
|
1387
|
-
const elapsed = held(voice, elapsedNow - delay);
|
|
1388
|
-
if (!(elapsed >= 0)) {
|
|
1389
|
-
data[o + WEIGHT] = 0;
|
|
1390
|
-
return;
|
|
1391
|
-
}
|
|
1392
|
-
if (!lane.placed || !Object.is(elapsed, lane.placedAt)) {
|
|
1393
|
-
lane.placed = true;
|
|
1394
|
-
lane.placedAt = elapsed;
|
|
1395
|
-
lane.phase = phaseAt(elapsed, period, passes);
|
|
1396
|
-
lane.pass = passAt(elapsed, period, passes);
|
|
1397
|
-
}
|
|
1398
|
-
const since = data[o + SINCE];
|
|
1399
|
-
if (!lane.flat && (!lane.weighed || !Object.is(since, lane.weighedSince))) {
|
|
1400
|
-
lane.fade = host.envelope(voice, since);
|
|
1401
|
-
lane.weighed = true;
|
|
1402
|
-
lane.weighedSince = since;
|
|
1403
|
-
}
|
|
1404
|
-
let w = clampWeight(voice.weight * lane.fade);
|
|
1405
|
-
if (voice.parts !== null) {
|
|
1406
|
-
const subject = this.subjectAt(slot);
|
|
1407
|
-
if (subject !== absent)
|
|
1408
|
-
w *= host.parting(voice, subject);
|
|
1409
|
-
}
|
|
1410
|
-
data[o + WEIGHT] = w;
|
|
1411
|
-
if (voice.built !== null) {
|
|
1412
|
-
if (!lane.read || !Object.is(elapsed, lane.readAt)) {
|
|
1413
|
-
this.readKeyed(voice, lane.phase, lane.delta, lane.scratch);
|
|
1414
|
-
lane.read = true;
|
|
1415
|
-
lane.readAt = elapsed;
|
|
1416
|
-
this.gather(lane, lane.delta);
|
|
1417
|
-
}
|
|
1418
|
-
if (w > 0)
|
|
1419
|
-
this.fold(lane, slot, w);
|
|
1420
|
-
return;
|
|
1421
|
-
}
|
|
1422
|
-
const rec = lane.records[p];
|
|
1423
|
-
// The general path makes a voice's first call for a subject, where it first sees it play.
|
|
1424
|
-
if (Number.isNaN(rec.probed)) {
|
|
1425
|
-
this.late.push(slot);
|
|
1426
|
-
return;
|
|
1427
|
-
}
|
|
1428
|
-
// A motion patch needs the subject only to number it, so the lookup waits until then.
|
|
1429
|
-
if (lane.motion !== undefined) {
|
|
1430
|
-
this.move(lane, lane.motion, p, slot, rec, elapsed, delay, w);
|
|
1431
|
-
return;
|
|
1432
|
-
}
|
|
1433
|
-
this.call(voice, lane.chans, rec, slot, elapsed, lane.phase, lane.pass, delay, w);
|
|
1434
|
-
}
|
|
1435
|
-
/** Reads a keys voice's stops at `phase` into `delta`, interpolating into the reader's `scratch`. */
|
|
1436
|
-
readKeyed(voice, phase, delta, scratch) {
|
|
1437
|
-
readKeys(voice.built, phase, delta, undefined, voice.lerps, undefined, voice.intos, scratch);
|
|
1438
|
-
}
|
|
1439
|
-
/** Calls a stateless fn voice's patch for a subject its general path has met, and folds the delta. */
|
|
1440
|
-
call(voice, chans, rec, slot, elapsed, phase, pass, delay, w) {
|
|
1441
|
-
const host = this.host;
|
|
1442
|
-
const subject = this.subjectAt(slot);
|
|
1443
|
-
if (subject === absent)
|
|
1444
|
-
return;
|
|
1445
|
-
// Called already this frame, by the general path or a fill before a refill: reuse, as a probe does.
|
|
1446
|
-
if (rec.probed === this.now && rec.delta !== null && rec.seeks === voice.seeks) {
|
|
1447
|
-
if (w > 0)
|
|
1448
|
-
this.foldDelta(chans, slot, rec.delta, w);
|
|
1449
|
-
return;
|
|
1450
|
-
}
|
|
1451
|
-
const kept = reading.kept;
|
|
1452
|
-
host.ready(voice, subject, rec, elapsed, pass, w);
|
|
1453
|
-
host.horizon(voice, delay);
|
|
1454
|
-
const delta = voice.patch.at(phase, subject, voice.setting);
|
|
1455
|
-
// What `influence` leaves on the record, so a probe on the general path this frame reuses it.
|
|
1456
|
-
rec.delta = delta;
|
|
1457
|
-
rec.probed = this.now;
|
|
1458
|
-
rec.seeks = voice.seeks;
|
|
1459
|
-
if (this.keeps)
|
|
1460
|
-
host.after(voice, rec);
|
|
1461
|
-
if (reading.kept !== kept && !voice.keeping)
|
|
1462
|
-
host.kept(voice);
|
|
1463
|
-
if (w > 0)
|
|
1464
|
-
this.foldDelta(chans, slot, delta, w);
|
|
1465
|
-
}
|
|
1466
|
-
/** Runs every crowd's rows whose voices come before voice `id`, from where each crowd reached. */
|
|
1467
|
-
crowdsUpTo(id) {
|
|
1468
|
-
for (const c of this.crowds)
|
|
1469
|
-
if (!c.idle && c.cursor < c.size)
|
|
1470
|
-
this.runCrowd(c, id);
|
|
1471
|
-
}
|
|
1472
|
-
/**
|
|
1473
|
-
* A crowd's rows from its cursor up to voice `id`: what `runMotion` does for a lane's subjects,
|
|
1474
|
-
* reading each row's voice and stretch from `hot`, and through `move` for whatever `hot` cannot
|
|
1475
|
-
* answer, as `runMotion` does.
|
|
1476
|
-
*/
|
|
1477
|
-
runCrowd(c, id) {
|
|
1478
|
-
const data = c.data;
|
|
1479
|
-
const hot = c.hot;
|
|
1480
|
-
const H = c.stride;
|
|
1481
|
-
const per = this.per;
|
|
1482
|
-
const list = c.list;
|
|
1483
|
-
const ch = c.chans[0];
|
|
1484
|
-
const n = c.axes;
|
|
1485
|
-
const fills = this.fills;
|
|
1486
|
-
const from = this.frameProbes;
|
|
1487
|
-
const probed = this.probes !== from;
|
|
1488
|
-
const now = this.now;
|
|
1489
|
-
const xs = c.xs;
|
|
1490
|
-
const vs = c.vs;
|
|
1491
|
-
const samples = c.samples;
|
|
1492
|
-
let p = c.cursor;
|
|
1493
|
-
for (; p < list.length; p++) {
|
|
1494
|
-
const h = p * H;
|
|
1495
|
-
if (hot[h + H_ID] >= id)
|
|
1496
|
-
break;
|
|
1497
|
-
let f = hot[h + H_FLAGS];
|
|
1498
|
-
if ((f & F_VOICE) !== 0)
|
|
1499
|
-
f = this.copyVoice(c, p);
|
|
1500
|
-
if ((f & F_PLAYING) === 0 || (f & F_PLACED) === 0)
|
|
1501
|
-
continue;
|
|
1502
|
-
const slot = list[p];
|
|
1503
|
-
const o = p * STRIDE;
|
|
1504
|
-
const q = slot * SLOT;
|
|
1505
|
-
if (per[q + LANE_FILL] === fills - 1)
|
|
1506
|
-
data[o + PROBED] = data[o + WEIGHT];
|
|
1507
|
-
const delay = data[o + DELAY];
|
|
1508
|
-
const fast = (f & F_FAST) !== 0;
|
|
1509
|
-
const voice = c.voices[p];
|
|
1510
|
-
const elapsedNow = fast
|
|
1511
|
-
? hot[h + H_ELAPSED] +
|
|
1512
|
-
(now - hot[h + H_NOW]) * hot[h + H_RATE]
|
|
1513
|
-
: voice.elapsedAt(now);
|
|
1514
|
-
let elapsed = elapsedNow - delay;
|
|
1515
|
-
if ((f & F_HOLDS) !== 0)
|
|
1516
|
-
elapsed = held(voice, elapsed);
|
|
1517
|
-
if (!(elapsed >= 0)) {
|
|
1518
|
-
data[o + WEIGHT] = 0;
|
|
1519
|
-
continue;
|
|
1520
|
-
}
|
|
1521
|
-
let w;
|
|
1522
|
-
if (fast)
|
|
1523
|
-
w = hot[h + H_WEIGHT];
|
|
1524
|
-
else {
|
|
1525
|
-
w = clampWeight(voice.weight * this.host.envelope(voice, data[o + SINCE]));
|
|
1526
|
-
if (voice.parts !== null) {
|
|
1527
|
-
const subject = this.subjectAt(slot);
|
|
1528
|
-
if (subject !== absent)
|
|
1529
|
-
w *= this.host.parting(voice, subject);
|
|
1530
|
-
}
|
|
1531
|
-
}
|
|
1532
|
-
data[o + WEIGHT] = w;
|
|
1533
|
-
if ((f & F_MOTION) === 0) {
|
|
1534
|
-
this.row(c, p, voice, slot, elapsed, delay, w);
|
|
1535
|
-
continue;
|
|
1536
|
-
}
|
|
1537
|
-
if (data[o + MET] === 0) {
|
|
1538
|
-
if (Number.isNaN(c.records[p].probed)) {
|
|
1539
|
-
this.late.push(slot);
|
|
1540
|
-
continue;
|
|
1541
|
-
}
|
|
1542
|
-
data[o + MET] = 1;
|
|
1543
|
-
}
|
|
1544
|
-
const ms = data[o + MSLOT];
|
|
1545
|
-
if ((f & F_STALE) !== 0 && ms >= 0)
|
|
1546
|
-
f = this.copyStretch(c, p, ms);
|
|
1547
|
-
if (ms < 0 ||
|
|
1548
|
-
(f & F_BARE) === 0 ||
|
|
1549
|
-
(probed &&
|
|
1550
|
-
(per[q + LANE_PROBE] > from || per[q + GENERAL_PROBE] > from))) {
|
|
1551
|
-
const run = c.motions[p];
|
|
1552
|
-
this.move(c, run, p, slot, c.records[p], elapsed, delay, w);
|
|
1553
|
-
// The first sample numbered the subject in the patch: copy its stretch from the next fill.
|
|
1554
|
-
if (ms < 0)
|
|
1555
|
-
hot[h + H_FLAGS] = hot[h + H_FLAGS] | F_STALE;
|
|
1556
|
-
continue;
|
|
1557
|
-
}
|
|
1558
|
-
closed(c.laws[p], c.eases[p], n, hot[h + H_AT], hot[h + H_MS], hot, h + H_X0, hot, h + H_X0 + n, hot, h + H_X0 + 2 * n, elapsed, xs, vs);
|
|
1559
|
-
for (let a = 0; a < n; a++)
|
|
1560
|
-
samples[p * n + a] = xs[a];
|
|
1561
|
-
if (c.deltas[p] !== null)
|
|
1562
|
-
c.deltas[p] = null;
|
|
1563
|
-
data[o + SAMPLED] = fills;
|
|
1564
|
-
data[o + SEEKS] = hot[h + H_SEEKS];
|
|
1565
|
-
if (w <= 0)
|
|
1566
|
-
continue;
|
|
1567
|
-
if ((f & F_FOLDS) !== 0)
|
|
1568
|
-
this.foldRun(ch, slot, xs, w);
|
|
1569
|
-
else
|
|
1570
|
-
this.foldInto(ch, slot, c.motions[p].value(ms, xs), w);
|
|
1571
|
-
}
|
|
1572
|
-
c.cursor = p;
|
|
1573
|
-
}
|
|
1574
|
-
/** A keys or fn row's contribution, as `one` makes a lane position's. */
|
|
1575
|
-
row(c, p, voice, slot, elapsed, delay, w) {
|
|
1576
|
-
const period = voice.patch.period;
|
|
1577
|
-
const phase = phaseAt(elapsed, period, voice.passes);
|
|
1578
|
-
if (voice.built !== null) {
|
|
1579
|
-
if (w > 0) {
|
|
1580
|
-
this.readKeyed(voice, phase, c.delta, c.scratch);
|
|
1581
|
-
this.foldDelta(c.chans, slot, c.delta, w);
|
|
1582
|
-
}
|
|
1583
|
-
return;
|
|
1584
|
-
}
|
|
1585
|
-
const rec = c.records[p];
|
|
1586
|
-
if (Number.isNaN(rec.probed)) {
|
|
1587
|
-
this.late.push(slot);
|
|
1588
|
-
return;
|
|
1589
|
-
}
|
|
1590
|
-
this.call(voice, c.chans, rec, slot, elapsed, phase, passAt(elapsed, period, voice.passes), delay, w);
|
|
1591
|
-
}
|
|
1592
|
-
/** Copies what a fill reads of a crowd row's voice into `hot`; returns the row's flags. */
|
|
1593
|
-
copyVoice(c, p) {
|
|
1594
|
-
const v = c.voices[p];
|
|
1595
|
-
const h = p * c.stride;
|
|
1596
|
-
let f = c.hot[h + H_FLAGS] & ~(F_VOICE | F_PLAYING | F_FAST | F_HOLDS);
|
|
1597
|
-
if (v.state === 'live' || v.state === 'held' || v.state === 'fading')
|
|
1598
|
-
f |= F_PLAYING;
|
|
1599
|
-
if (v.holdsBefore || v.holdsAfter)
|
|
1600
|
-
f |= F_HOLDS;
|
|
1601
|
-
if (v.ramp === null &&
|
|
1602
|
-
!((v.fade.in ?? 0) > 0) &&
|
|
1603
|
-
v.out === null &&
|
|
1604
|
-
v.parts === null &&
|
|
1605
|
-
!v.keeping)
|
|
1606
|
-
f |= F_FAST;
|
|
1607
|
-
c.hot[h + H_NOW] = v.anchorNow;
|
|
1608
|
-
c.hot[h + H_ELAPSED] = v.anchorElapsed;
|
|
1609
|
-
c.hot[h + H_RATE] = v.rate;
|
|
1610
|
-
c.hot[h + H_WEIGHT] = clampWeight(v.weight);
|
|
1611
|
-
c.hot[h + H_SEEKS] = v.seeks;
|
|
1612
|
-
c.hot[h + H_FLAGS] = f;
|
|
1613
|
-
return f;
|
|
1614
|
-
}
|
|
1615
|
-
/** Copies a crowd row's current stretch from its patch into `hot`; returns the row's flags. */
|
|
1616
|
-
copyStretch(c, p, ms) {
|
|
1617
|
-
const run = c.motions[p];
|
|
1618
|
-
const h = p * c.stride;
|
|
1619
|
-
let f = c.hot[h + H_FLAGS] & ~(F_STALE | F_BARE | F_FOLDS);
|
|
1620
|
-
const ch = c.chans[0];
|
|
1621
|
-
if (run.n === c.axes && run.stretchInto(ms, c.hot, h + H_AT)) {
|
|
1622
|
-
f |= F_BARE;
|
|
1623
|
-
if (run.scalar(ms) === ch.scalar)
|
|
1624
|
-
f |= F_FOLDS;
|
|
1625
|
-
}
|
|
1626
|
-
c.hot[h + H_FLAGS] = f;
|
|
1627
|
-
return f;
|
|
1628
|
-
}
|
|
1629
|
-
/**
|
|
1630
|
-
* A motion voice's fill: what `one` and `move` do for each subject, in one loop for the common
|
|
1631
|
-
* case, a subject not yet probed this frame with nothing pending, and through `move` for the rest.
|
|
1632
|
-
*/
|
|
1633
|
-
runMotion(lane, run) {
|
|
1634
|
-
const voice = lane.voice;
|
|
1635
|
-
const data = lane.data;
|
|
1636
|
-
const per = this.per;
|
|
1637
|
-
const list = lane.list;
|
|
1638
|
-
const records = lane.records;
|
|
1639
|
-
const ch = lane.chans[0];
|
|
1640
|
-
const fills = this.fills;
|
|
1641
|
-
const from = this.frameProbes;
|
|
1642
|
-
const seeks = voice.seeks;
|
|
1643
|
-
const elapsedNow = voice.elapsedAt(this.now);
|
|
1644
|
-
const flat = lane.flat;
|
|
1645
|
-
const parts = voice.parts !== null;
|
|
1646
|
-
// On a frame's first fill no probe has read a subject yet, so none needs `move` for that.
|
|
1647
|
-
const probed = this.probes !== from;
|
|
1648
|
-
const whole = clampWeight(voice.weight);
|
|
1649
|
-
const n = run.n;
|
|
1650
|
-
if (n > 0) {
|
|
1651
|
-
if (lane.axes !== n) {
|
|
1652
|
-
lane.axes = n;
|
|
1653
|
-
lane.samples = new Float64Array(0);
|
|
1654
|
-
}
|
|
1655
|
-
if (list.length * n > lane.samples.length) {
|
|
1656
|
-
const grown = new Float64Array(Math.max(list.length, (lane.samples.length / n) * 2, 4) * n);
|
|
1657
|
-
grown.set(lane.samples);
|
|
1658
|
-
lane.samples = grown;
|
|
1659
|
-
}
|
|
1660
|
-
}
|
|
1661
|
-
const samples = lane.samples;
|
|
1662
|
-
const xs = run.xs;
|
|
1663
|
-
for (let p = 0; p < list.length; p++) {
|
|
1664
|
-
const slot = list[p];
|
|
1665
|
-
const o = p * STRIDE;
|
|
1666
|
-
const q = slot * SLOT;
|
|
1667
|
-
if (per[q + LANE_FILL] === fills - 1)
|
|
1668
|
-
data[o + PROBED] = data[o + WEIGHT];
|
|
1669
|
-
const delay = data[o + DELAY];
|
|
1670
|
-
const elapsed = elapsedNow - delay;
|
|
1671
|
-
if (elapsed < 0) {
|
|
1672
|
-
data[o + WEIGHT] = 0;
|
|
1673
|
-
continue;
|
|
1674
|
-
}
|
|
1675
|
-
if (!flat) {
|
|
1676
|
-
const since = data[o + SINCE];
|
|
1677
|
-
if (!lane.weighed || !Object.is(since, lane.weighedSince)) {
|
|
1678
|
-
lane.fade = this.host.envelope(voice, since);
|
|
1679
|
-
lane.weighed = true;
|
|
1680
|
-
lane.weighedSince = since;
|
|
1681
|
-
}
|
|
1682
|
-
}
|
|
1683
|
-
let w = flat ? whole : clampWeight(voice.weight * lane.fade);
|
|
1684
|
-
if (parts) {
|
|
1685
|
-
const subject = this.subjectAt(slot);
|
|
1686
|
-
if (subject !== absent)
|
|
1687
|
-
w *= this.host.parting(voice, subject);
|
|
1688
|
-
}
|
|
1689
|
-
data[o + WEIGHT] = w;
|
|
1690
|
-
if (data[o + MET] === 0) {
|
|
1691
|
-
if (Number.isNaN(records[p].probed)) {
|
|
1692
|
-
this.late.push(slot);
|
|
1693
|
-
continue;
|
|
1694
|
-
}
|
|
1695
|
-
data[o + MET] = 1;
|
|
1696
|
-
}
|
|
1697
|
-
const ms = data[o + MSLOT];
|
|
1698
|
-
if (ms < 0 ||
|
|
1699
|
-
(probed &&
|
|
1700
|
-
(per[q + LANE_PROBE] > from || per[q + GENERAL_PROBE] > from)) ||
|
|
1701
|
-
!run.sampleBare(ms, elapsed, xs, run.vs)) {
|
|
1702
|
-
this.move(lane, run, p, slot, records[p], elapsed, delay, w);
|
|
1703
|
-
continue;
|
|
1704
|
-
}
|
|
1705
|
-
for (let a = 0; a < n; a++)
|
|
1706
|
-
samples[p * n + a] = xs[a];
|
|
1707
|
-
if (lane.deltas[p] !== null)
|
|
1708
|
-
lane.deltas[p] = null;
|
|
1709
|
-
data[o + SAMPLED] = fills;
|
|
1710
|
-
data[o + SEEKS] = seeks;
|
|
1711
|
-
if (w <= 0)
|
|
1712
|
-
continue;
|
|
1713
|
-
if (n === ch.axes && run.scalar(ms) === ch.scalar)
|
|
1714
|
-
this.foldRun(ch, slot, xs, w);
|
|
1715
|
-
else
|
|
1716
|
-
this.foldInto(ch, slot, run.value(ms, xs), w);
|
|
1717
|
-
}
|
|
1718
|
-
}
|
|
1719
|
-
/**
|
|
1720
|
-
* A motion voice's value for a subject, sampled from the patch's state as its `at` would, but only
|
|
1721
|
-
* where that sample changes nothing: one with a change to stamp, commit or let go of goes to the
|
|
1722
|
-
* general path, which samples it if and when a probe asks, as it would with no lanes. A probe
|
|
1723
|
-
* this frame already read keeps the value it read, as the general path's record would.
|
|
1724
|
-
*/
|
|
1725
|
-
move(lane, run, p, slot, held, elapsed, delay, w) {
|
|
1726
|
-
const voice = lane.voiceAt(p);
|
|
1727
|
-
const data = lane.data;
|
|
1728
|
-
const o = p * STRIDE;
|
|
1729
|
-
const ch = lane.chans[0];
|
|
1730
|
-
this.settle(lane, p, slot, held);
|
|
1731
|
-
const delta = held.delta;
|
|
1732
|
-
if (delta === null ||
|
|
1733
|
-
held.probed !== this.now ||
|
|
1734
|
-
held.seeks !== voice.seeks ||
|
|
1735
|
-
!this.probedThisFrame(slot)) {
|
|
1736
|
-
let ms = data[o + MSLOT];
|
|
1737
|
-
if (ms < 0) {
|
|
1738
|
-
const subject = this.subjectAt(slot);
|
|
1739
|
-
if (subject === absent)
|
|
1740
|
-
return;
|
|
1741
|
-
ms = run.slot(subject);
|
|
1742
|
-
data[o + MSLOT] = ms;
|
|
1743
|
-
}
|
|
1744
|
-
this.host.horizon(voice, delay);
|
|
1745
|
-
if (!run.quiet(ms, elapsed)) {
|
|
1746
|
-
this.late.push(slot);
|
|
1747
|
-
return;
|
|
1748
|
-
}
|
|
1749
|
-
run.sample(ms, elapsed, run.xs, run.vs);
|
|
1750
|
-
// Kept as numbers, not a delta: one is built only if a probe this frame asks for it.
|
|
1751
|
-
const n = run.n;
|
|
1752
|
-
lane.keep(p, run.xs, n);
|
|
1753
|
-
lane.deltas[p] = null;
|
|
1754
|
-
data[o + SAMPLED] = this.fills;
|
|
1755
|
-
data[o + SEEKS] = voice.seeks;
|
|
1756
|
-
if (w <= 0)
|
|
1757
|
-
return;
|
|
1758
|
-
// A sample of other axes than the channel's folds as the general path folds it.
|
|
1759
|
-
if (n === ch.axes && run.scalar(ms) === ch.scalar)
|
|
1760
|
-
this.foldRun(ch, slot, run.xs, w);
|
|
1761
|
-
else
|
|
1762
|
-
this.foldInto(ch, slot, run.value(ms, run.xs), w);
|
|
1763
|
-
return;
|
|
1764
|
-
}
|
|
1765
|
-
lane.deltas[p] = delta;
|
|
1766
|
-
data[o + SAMPLED] = this.fills;
|
|
1767
|
-
data[o + SEEKS] = voice.seeks;
|
|
1768
|
-
if (w > 0)
|
|
1769
|
-
this.foldInto(ch, slot, delta[ch.name], w);
|
|
1770
|
-
}
|
|
1771
|
-
/** The delta a motion voice's last sample for position `p` made, built from `samples`. */
|
|
1772
|
-
sampled(lane, p) {
|
|
1773
|
-
const run = lane.motionAt(p);
|
|
1774
|
-
lane.kept(p, run.xs);
|
|
1775
|
-
const ms = lane.data[p * STRIDE + MSLOT];
|
|
1776
|
-
const delta = { [lane.chans[0].name]: run.value(ms, run.xs) };
|
|
1777
|
-
lane.deltas[p] = delta;
|
|
1778
|
-
return delta;
|
|
1779
|
-
}
|
|
1780
|
-
/**
|
|
1781
|
-
* A probe this frame read a motion voice's value for the subject from an earlier fill: write onto
|
|
1782
|
-
* its record what the general path's sample would have, so a later read this frame reuses it.
|
|
1783
|
-
*/
|
|
1784
|
-
settle(lane, p, slot, held) {
|
|
1785
|
-
const o = p * STRIDE;
|
|
1786
|
-
if ((held.probed === this.now && held.seeks === lane.data[o + SEEKS]) ||
|
|
1787
|
-
!(this.per[slot * SLOT + LANE_PROBE] > this.frameProbes) ||
|
|
1788
|
-
lane.data[o + SAMPLED] !== this.per[slot * SLOT + LANE_FILL])
|
|
1789
|
-
return;
|
|
1790
|
-
held.delta = lane.deltas[p] ?? this.sampled(lane, p);
|
|
1791
|
-
held.probed = this.now;
|
|
1792
|
-
held.seeks = lane.data[o + SEEKS];
|
|
1793
|
-
}
|
|
1794
|
-
/** For a keys voice: the delta's value for each channel it writes, read once per phase. */
|
|
1795
|
-
gather(lane, delta) {
|
|
1796
|
-
const chans = lane.chans;
|
|
1797
|
-
for (let i = 0; i < chans.length; i++)
|
|
1798
|
-
lane.values[i] = delta[chans[i].name];
|
|
1799
|
-
}
|
|
1800
|
-
/** Folds what `gather` read into a subject's laned values. */
|
|
1801
|
-
fold(lane, slot, w) {
|
|
1802
|
-
const chans = lane.chans;
|
|
1803
|
-
for (let i = 0; i < chans.length; i++)
|
|
1804
|
-
this.foldInto(chans[i], slot, lane.values[i], w);
|
|
1805
|
-
}
|
|
1806
|
-
/**
|
|
1807
|
-
* Folds a delta a patch returned into a subject's laned values. The first few channels are read
|
|
1808
|
-
* at a site of their own, which in most mixes sees one channel name and stays fast, where one
|
|
1809
|
-
* site reading every name in turn slows every read.
|
|
1810
|
-
*/
|
|
1811
|
-
foldDelta(chans, slot, delta, w) {
|
|
1812
|
-
const n = chans.length;
|
|
1813
|
-
let ch = chans[0];
|
|
1814
|
-
if (n > 0)
|
|
1815
|
-
this.foldInto(ch, slot, delta[ch.name], w);
|
|
1816
|
-
ch = chans[1];
|
|
1817
|
-
if (n > 1)
|
|
1818
|
-
this.foldInto(ch, slot, delta[ch.name], w);
|
|
1819
|
-
ch = chans[2];
|
|
1820
|
-
if (n > 2)
|
|
1821
|
-
this.foldInto(ch, slot, delta[ch.name], w);
|
|
1822
|
-
for (let i = 3; i < n; i++) {
|
|
1823
|
-
ch = chans[i];
|
|
1824
|
-
this.foldInto(ch, slot, delta[ch.name], w);
|
|
1825
|
-
}
|
|
1826
|
-
}
|
|
1827
|
-
/** Folds a motion sample's axes, kept apart from `foldInto` so neither reads two array kinds. */
|
|
1828
|
-
foldRun(ch, slot, xs, w) {
|
|
1829
|
-
const values = ch.values;
|
|
1830
|
-
const base = slot * ch.axes;
|
|
1831
|
-
for (let a = 0; a < ch.axes; a++)
|
|
1832
|
-
values[base + a] = foldNumber(ch.op, values[base + a], xs[a], w);
|
|
1833
|
-
}
|
|
1834
|
-
foldInto(ch, slot, value, w) {
|
|
1835
|
-
if (value === undefined)
|
|
1836
|
-
return;
|
|
1837
|
-
const values = ch.values;
|
|
1838
|
-
if (ch.scalar) {
|
|
1839
|
-
values[slot] = foldNumber(ch.op, values[slot], value, w);
|
|
1840
|
-
return;
|
|
1841
|
-
}
|
|
1842
|
-
const base = slot * ch.axes;
|
|
1843
|
-
// Indexed as `vec`'s fold indexes it, so a typed array folds as an array does.
|
|
1844
|
-
const arr = Array.isArray(value) || ArrayBuffer.isView(value) ? value : null;
|
|
1845
|
-
for (let a = 0; a < ch.axes; a++) {
|
|
1846
|
-
const v = arr === null ? ch.rest : (arr[a] ?? ch.rest);
|
|
1847
|
-
values[base + a] = foldNumber(ch.op, values[base + a], v, w);
|
|
1848
|
-
}
|
|
398
|
+
const f = p * c.stride + 0 /* Hot.FLAGS */;
|
|
399
|
+
c.hot[f] = c.hot[f] | 64 /* Flag.VOICE */;
|
|
1849
400
|
}
|
|
1850
401
|
}
|
|
402
|
+
// A fill's per-subject path is methods, installed here from the modules that own them: as
|
|
403
|
+
// functions called directly they made fills 5-8% slower (bench/ab.sh on teitou, 2026-10-05).
|
|
404
|
+
const methods = {
|
|
405
|
+
row,
|
|
406
|
+
signalled,
|
|
407
|
+
parting,
|
|
408
|
+
subjectAt,
|
|
409
|
+
reset,
|
|
410
|
+
run,
|
|
411
|
+
one,
|
|
412
|
+
foldKeys,
|
|
413
|
+
readKeyed,
|
|
414
|
+
call,
|
|
415
|
+
gather,
|
|
416
|
+
fold,
|
|
417
|
+
foldDelta,
|
|
418
|
+
foldRun,
|
|
419
|
+
foldInto,
|
|
420
|
+
};
|
|
421
|
+
for (const [name, value] of Object.entries(methods))
|
|
422
|
+
Object.defineProperty(Lanes.prototype, name, { value, writable: true, configurable: true });
|