@msb235/blits 0.2.1 → 0.4.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/dist/lanes.js ADDED
@@ -0,0 +1,1850 @@
1
+ import { foldNumber, numericOf } from './channels.js';
2
+ import { clampWeight, passAt, phaseAt } from './clock.js';
3
+ import { closed, motionOf, noTouch } from './motion.js';
4
+ import { absent, Numbers } from './numbers.js';
5
+ import { readKeys } from './patch.js';
6
+ 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
+ }
376
+ /**
377
+ * The lanes of one mix: subject numbers, which channels and voices run on lanes, and the values a
378
+ * frame's fill leaves for probes to copy.
379
+ */
380
+ export class Lanes {
381
+ host;
382
+ numbers;
383
+ lanes = [];
384
+ /** The lanes a fill visits: every lane but the solo ones. */
385
+ runs = [];
386
+ /** The lanes over every subject that have started playing, by epoch. */
387
+ dense = [];
388
+ byId = new Map();
389
+ /** The crowds of single-subject motion voices, one per channel, and which holds each voice. */
390
+ crowds = [];
391
+ crowdOf = new Map();
392
+ lawsByKey = new Map();
393
+ laned = [];
394
+ /** By kit slot, the laned channel there. */
395
+ bySlot = [];
396
+ /** By kit slot, whether a fill leaves the channel's values for probes to copy. */
397
+ copies = [];
398
+ /** Whether every voice in the mix is on a lane, so a filled subject has nothing left to fold. */
399
+ whole = false;
400
+ cap = 0;
401
+ epochs = 0;
402
+ /** The latest epoch of a lane over every subject, which every subject meets on its next probe. */
403
+ wide = 0;
404
+ /** Voices that joined, started or left since the last qualify, settled at the next probe. */
405
+ touched = [];
406
+ /** The highest voice id the last qualify or settling of `touched` considered. */
407
+ known = 0;
408
+ /**
409
+ * Per subject number, `SLOT` numbers side by side, so a probe reads one place in memory: which
410
+ * fill last wrote it (0 for none); the latest lane epoch a probe of it has checked it against;
411
+ * how many idle lanes reach it, since a probe reads lanes only where none do; and the probe count
412
+ * at its last probe read from the lanes and at its last probe folded by the general path, with
413
+ * the fill the former read, which say whether a lane or a record holds what `weightOf` reports.
414
+ */
415
+ per = new Float64Array(0);
416
+ fills = 0;
417
+ probes = 0;
418
+ /**
419
+ * Each subject by number as last probed, held from that probe until the next fill uses it, so a
420
+ * fill rarely looks one up through its weak reference, which costs on every look. A host that
421
+ * stops syncing keeps at most one frame's probed subjects alive until its next fill, and none
422
+ * once no lane remains.
423
+ */
424
+ subjects = [];
425
+ /** Whether a probe has held a subject since the last fill let them go. */
426
+ holding = false;
427
+ /** While a fill runs, so a probe a patch makes from inside it takes the general path. */
428
+ filling = false;
429
+ /**
430
+ * Numbers a fill left to the general path: handed out while it ran, or with a voice whose call
431
+ * the general path has to make.
432
+ */
433
+ late = [];
434
+ /** What `writeLater` has queued: lane slots and the rows they go to, for one set of columns. */
435
+ queueSlots = new Int32Array(0);
436
+ queueRows = new Int32Array(0);
437
+ queued = 0;
438
+ inOrder = true;
439
+ queuedFor = null;
440
+ keeps = false;
441
+ now = Number.NaN;
442
+ filledAt = Number.NaN;
443
+ /** `reading.moved` at the last fill, so a retarget or push between two probes refills. */
444
+ moved = 0;
445
+ /** The `now` of the latest probe, and the probe count before its first, to tell a probe this frame. */
446
+ frameAt = Number.NaN;
447
+ frameProbes = 0;
448
+ /** The probe counts the last frame with a probe began and ended at, which lanes go idle by. */
449
+ lastFrom = 0;
450
+ lastTo = 0;
451
+ /** Set when a frame's first probe arrives, so its first fill decides which lanes go idle. */
452
+ fresh = false;
453
+ /** Subjects probed this frame and in the last frame with a probe, counted once each. */
454
+ distinct = 0;
455
+ lastDistinct = 0;
456
+ /** Numbered subjects alive. */
457
+ live = 0;
458
+ filledVersion = Number.NaN;
459
+ qualifiedVersion = Number.NaN;
460
+ constructor(host) {
461
+ this.host = host;
462
+ this.numbers = new Numbers((slot) => this.forget(slot));
463
+ }
464
+ /** A change that may move a voice on or off its lane: qualify again, then fill again. */
465
+ invalidate() {
466
+ this.qualifiedVersion = Number.NaN;
467
+ this.filledVersion = Number.NaN;
468
+ }
469
+ /** A change to a laned voice's weight or clock between probes: fill again. */
470
+ refill() {
471
+ this.filledVersion = Number.NaN;
472
+ }
473
+ /**
474
+ * A voice joined, started playing or left: the next probe takes it on or off its crowd where it
475
+ * can, and qualifies again where it cannot.
476
+ */
477
+ touch(voice) {
478
+ this.touched.push(voice);
479
+ this.filledVersion = Number.NaN;
480
+ }
481
+ /** Numbers a subject the mix sees for the first time. */
482
+ number(subject) {
483
+ const slot = this.numbers.take(subject);
484
+ this.live++;
485
+ 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;
492
+ if (this.filling)
493
+ this.late.push(slot);
494
+ return slot;
495
+ }
496
+ release(slot) {
497
+ this.numbers.release(slot);
498
+ }
499
+ /** A voice faded a subject out of itself alone: its lane lets the subject's position go. */
500
+ part(id, slot) {
501
+ const lane = this.byId.get(id);
502
+ if (lane !== undefined) {
503
+ if (lane.positionOf(slot) < 0)
504
+ return;
505
+ if (lane.idle)
506
+ this.reach(slot, -1);
507
+ lane.remove(slot);
508
+ return;
509
+ }
510
+ const c = this.crowdOf.get(id);
511
+ const p = c?.rowOf.get(id);
512
+ if (c !== undefined && p !== undefined && c.list[p] === slot)
513
+ this.unplace(c, p);
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;
529
+ }
530
+ /** A voice brought a subject back: its next probe meets every lane again, as it met them first. */
531
+ rejoin(slot) {
532
+ if (slot < this.cap)
533
+ this.per[slot * SLOT + SEEN] = -1;
534
+ }
535
+ /**
536
+ * A laned voice's weight for a subject at the last frame a probe read it from the lane; undefined
537
+ * where the subject's record holds it instead, because its last probe took the general path or
538
+ * the voice is not on a lane.
539
+ */
540
+ weightOf(id, slot) {
541
+ if (slot < 0)
542
+ return undefined;
543
+ const lane = this.byId.get(id);
544
+ if (lane !== undefined)
545
+ return this.reported(lane, slot);
546
+ const c = this.crowdOf.get(id);
547
+ const p = c?.rowOf.get(id);
548
+ return c === undefined || p === undefined ? undefined : this.reportedRow(c, p, slot);
549
+ }
550
+ reported(lane, slot) {
551
+ const o = slot * SLOT;
552
+ if (!(this.per[o + LANE_PROBE] > this.per[o + GENERAL_PROBE]))
553
+ return undefined;
554
+ const p = lane.positionOf(slot);
555
+ if (p < 0)
556
+ return undefined;
557
+ return lane.data[p * STRIDE + (this.per[o + LANE_FILL] === this.fills ? WEIGHT : PROBED)];
558
+ }
559
+ /**
560
+ * Fills the lanes once a frame, and says whether this probe of a subject reads its values from
561
+ * them. One that a lane has not met yet, newly numbered or newly reached, takes the general path
562
+ * this frame, which is where it is first seen; so does one probed from inside a fill.
563
+ */
564
+ prepare(slot, subject, now, version) {
565
+ const ready = this.filled(now, version) ? READY : this.begin(now, version);
566
+ if (ready === GENERAL)
567
+ return this.general(slot);
568
+ if (slot < 0)
569
+ 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
+ if (!this.probedThisFrame(slot))
576
+ this.distinct++;
577
+ const probe = ++this.probes;
578
+ 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 && this.meet(slot, subject)) {
582
+ // The fill ran before the subject had these positions, so it reads the general path all frame.
583
+ per[o + FILLED] = 0;
584
+ lane = false;
585
+ }
586
+ if (lane) {
587
+ per[o + LANE_PROBE] = probe;
588
+ per[o + LANE_FILL] = this.fills;
589
+ this.subjects[slot] = subject;
590
+ this.holding = true;
591
+ }
592
+ else
593
+ per[o + GENERAL_PROBE] = probe;
594
+ return lane;
595
+ }
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
+ /** Whether this frame's lanes are filled and nothing since asks `begin` to look again. */
635
+ filled(now, version) {
636
+ return (this.filledAt === now &&
637
+ this.frameAt === now &&
638
+ this.filledVersion === version &&
639
+ this.qualifiedVersion === version &&
640
+ this.moved === reading.moved &&
641
+ this.touched.length === 0 &&
642
+ !this.filling);
643
+ }
644
+ /**
645
+ * 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, whether only solo lanes exist, so
647
+ * there is nothing to fill, or whether the lanes are filled.
648
+ */
649
+ begin(now, version) {
650
+ if (now !== this.frameAt) {
651
+ this.lastFrom = this.frameProbes;
652
+ this.lastTo = this.probes;
653
+ this.lastDistinct = this.distinct;
654
+ this.distinct = 0;
655
+ this.frameAt = now;
656
+ this.frameProbes = this.probes;
657
+ this.fresh = true;
658
+ }
659
+ if (this.filling)
660
+ return GENERAL;
661
+ if (this.qualifiedVersion !== version)
662
+ this.requalify(version);
663
+ else if (this.touched.length > 0 && !this.retouch())
664
+ this.requalify(version);
665
+ 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;
670
+ if (this.filledAt !== now || this.filledVersion !== version || this.moved !== reading.moved) {
671
+ this.fillAll(now, version);
672
+ // A patch call in that fill made kept state, which took its voice off its lane: fill without it.
673
+ if (this.qualifiedVersion !== version) {
674
+ this.requalify(version);
675
+ if (this.laned.length === 0)
676
+ return GENERAL;
677
+ this.fillAll(now, version);
678
+ }
679
+ }
680
+ return READY;
681
+ }
682
+ general(slot) {
683
+ if (slot >= 0)
684
+ this.per[slot * SLOT + GENERAL_PROBE] = ++this.probes;
685
+ return false;
686
+ }
687
+ /** Whether a probe has read the subject since the frame began, on either path. */
688
+ probedThisFrame(slot) {
689
+ const from = this.frameProbes;
690
+ const o = slot * SLOT;
691
+ return (this.per[o + LANE_PROBE] > from || this.per[o + GENERAL_PROBE] > from);
692
+ }
693
+ /**
694
+ * Queues a subject's values for each column at subject `n`'s places, for a `pull` that reads
695
+ * from the lanes while every voice is on one; `flush` writes what is queued, column by column. A
696
+ * channel no lane holds is at rest. Anything queued is written before the lanes fill again.
697
+ */
698
+ writeLater(slot, columns, n) {
699
+ if (columns !== this.queuedFor) {
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
+ }
744
+ const values = ch.values;
745
+ if (block) {
746
+ const s0 = slots[0] * axes;
747
+ out.set(values.subarray(s0, s0 + count * axes), rows[0] * axes);
748
+ }
749
+ else if (axes === 1) {
750
+ for (let i = 0; i < count; i++)
751
+ out[rows[i]] = values[slots[i]];
752
+ }
753
+ else
754
+ for (let i = 0; i < count; i++) {
755
+ const at = rows[i] * axes;
756
+ const base = slots[i] * axes;
757
+ for (let a = 0; a < axes; a++)
758
+ out[at + a] = values[base + a];
759
+ }
760
+ if (c.bounds !== undefined)
761
+ for (let i = 0; i < count; i++)
762
+ clampRun(out, rows[i] * axes, axes, c.bounds);
763
+ }
764
+ }
765
+ /** Writes a subject's laned values into a pose, each array channel into a new array. */
766
+ copy(slot, pose) {
767
+ const laned = this.laned;
768
+ for (let c = 0; c < laned.length; c++) {
769
+ const ch = laned[c];
770
+ const axes = ch.axes;
771
+ if (ch.scalar) {
772
+ pose[ch.name] = ch.values[slot];
773
+ continue;
774
+ }
775
+ // Made as the general path makes it, a copy of rest, so poses from either path share a shape.
776
+ const arr = [...ch.start];
777
+ const base = slot * axes;
778
+ for (let a = 0; a < axes; a++)
779
+ arr[a] = ch.values[base + a];
780
+ pose[ch.name] = arr;
781
+ }
782
+ }
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
+ forget(slot) {
863
+ this.live--;
864
+ for (const lane of this.lanes)
865
+ lane.remove(slot);
866
+ for (const c of this.crowds)
867
+ for (let p = 0; p < c.size; p++)
868
+ if (c.list[p] === slot)
869
+ this.unplace(c, p);
870
+ if (slot < this.cap)
871
+ this.per[slot * SLOT + FILLED] = 0;
872
+ this.subjects[slot] = undefined;
873
+ }
874
+ grow(size) {
875
+ if (size <= this.cap)
876
+ return;
877
+ const cap = Math.max(size, this.cap * 2, 64);
878
+ const per = new Float64Array(cap * SLOT);
879
+ per.set(this.per);
880
+ this.per = per;
881
+ for (const ch of this.laned) {
882
+ const values = new Float64Array(cap * ch.axes);
883
+ values.set(ch.values);
884
+ ch.values = values;
885
+ }
886
+ this.cap = cap;
887
+ }
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
+ /**
962
+ * Rebuilds the crowds from the voices that belong in one, in voice order: a voice already in a
963
+ * crowd keeps its row's numbers, a new one gets a row a probe will place, and a voice no longer in
964
+ * one is handed back to the general path, as a lane's voice is when it leaves.
965
+ */
966
+ regroup(members) {
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) {
1151
+ const c = this.crowdOf.get(id);
1152
+ const p = c?.rowOf.get(id);
1153
+ if (c === undefined || p === undefined)
1154
+ return;
1155
+ const f = p * c.stride + H_FLAGS;
1156
+ c.hot[f] = c.hot[f] | F_STALE;
1157
+ }
1158
+ /** Something on a voice changed: a crowd copies it again at the next fill. */
1159
+ voiceChanged(id) {
1160
+ const c = this.crowdOf.get(id);
1161
+ const p = c?.rowOf.get(id);
1162
+ if (c === undefined || p === undefined)
1163
+ return;
1164
+ const f = p * c.stride + H_FLAGS;
1165
+ c.hot[f] = c.hot[f] | F_VOICE;
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
+ }
1849
+ }
1850
+ }