@msb235/blits 0.4.0 → 0.5.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.
Files changed (102) hide show
  1. package/CHANGELOG.md +262 -0
  2. package/README.md +2 -1
  3. package/dist/bare.d.ts +29 -0
  4. package/dist/bare.js +105 -0
  5. package/dist/blend.d.ts +34 -0
  6. package/dist/blend.js +102 -0
  7. package/dist/book.d.ts +53 -0
  8. package/dist/book.js +218 -0
  9. package/dist/chain.d.ts +22 -0
  10. package/dist/chain.js +150 -0
  11. package/dist/channels.d.ts +2 -0
  12. package/dist/channels.js +3 -1
  13. package/dist/clock.d.ts +50 -3
  14. package/dist/clock.js +102 -9
  15. package/dist/clone.d.ts +7 -0
  16. package/dist/clone.js +57 -0
  17. package/dist/columns.d.ts +29 -0
  18. package/dist/columns.js +183 -0
  19. package/dist/crowd.d.ts +128 -0
  20. package/dist/crowd.js +354 -0
  21. package/dist/cue.d.ts +3 -0
  22. package/dist/cue.js +115 -0
  23. package/dist/due.d.ts +12 -0
  24. package/dist/due.js +93 -0
  25. package/dist/fade.d.ts +19 -0
  26. package/dist/fade.js +133 -0
  27. package/dist/fill.d.ts +42 -0
  28. package/dist/fill.js +381 -0
  29. package/dist/fold.d.ts +51 -0
  30. package/dist/fold.js +475 -0
  31. package/dist/gather.d.ts +24 -0
  32. package/dist/gather.js +186 -0
  33. package/dist/handle.d.ts +31 -0
  34. package/dist/handle.js +56 -0
  35. package/dist/held.d.ts +14 -0
  36. package/dist/held.js +117 -0
  37. package/dist/history.d.ts +22 -0
  38. package/dist/history.js +149 -0
  39. package/dist/hosts.d.ts +16 -0
  40. package/dist/hosts.js +178 -0
  41. package/dist/index.d.ts +1 -1
  42. package/dist/keyrows.d.ts +36 -0
  43. package/dist/keyrows.js +138 -0
  44. package/dist/lane.d.ts +210 -0
  45. package/dist/lane.js +187 -0
  46. package/dist/lanes.d.ts +121 -246
  47. package/dist/lanes.js +150 -1578
  48. package/dist/locus.d.ts +40 -0
  49. package/dist/locus.js +89 -0
  50. package/dist/marks.d.ts +12 -0
  51. package/dist/marks.js +80 -0
  52. package/dist/meet.d.ts +16 -0
  53. package/dist/meet.js +113 -0
  54. package/dist/mixer.d.ts +244 -196
  55. package/dist/mixer.js +242 -2220
  56. package/dist/motion.d.ts +66 -21
  57. package/dist/motion.js +325 -165
  58. package/dist/move.d.ts +9 -0
  59. package/dist/move.js +190 -0
  60. package/dist/named.d.ts +12 -0
  61. package/dist/named.js +39 -0
  62. package/dist/numbers.d.ts +18 -6
  63. package/dist/numbers.js +59 -31
  64. package/dist/owed.d.ts +24 -0
  65. package/dist/owed.js +51 -0
  66. package/dist/owner.d.ts +43 -0
  67. package/dist/owner.js +117 -0
  68. package/dist/pace.d.ts +40 -0
  69. package/dist/pace.js +113 -0
  70. package/dist/patch.d.ts +51 -10
  71. package/dist/patch.js +140 -45
  72. package/dist/place.d.ts +20 -0
  73. package/dist/place.js +196 -0
  74. package/dist/project.d.ts +4 -0
  75. package/dist/project.js +233 -0
  76. package/dist/pull.d.ts +5 -0
  77. package/dist/pull.js +175 -0
  78. package/dist/qualify.d.ts +31 -0
  79. package/dist/qualify.js +345 -0
  80. package/dist/relink.d.ts +25 -0
  81. package/dist/relink.js +59 -0
  82. package/dist/rows.d.ts +33 -0
  83. package/dist/rows.js +282 -0
  84. package/dist/sample.d.ts +21 -0
  85. package/dist/sample.js +232 -0
  86. package/dist/signals.d.ts +12 -9
  87. package/dist/signals.js +8 -5
  88. package/dist/store.d.ts +10 -3
  89. package/dist/store.js +44 -9
  90. package/dist/strays.d.ts +21 -0
  91. package/dist/strays.js +84 -0
  92. package/dist/tweened.d.ts +11 -0
  93. package/dist/tweened.js +48 -0
  94. package/dist/types.d.ts +217 -34
  95. package/dist/unreached.d.ts +5 -0
  96. package/dist/unreached.js +24 -0
  97. package/dist/voice.d.ts +240 -0
  98. package/dist/voice.js +322 -0
  99. package/dist/weigh.d.ts +26 -0
  100. package/dist/weigh.js +89 -0
  101. package/docs/schema.html +118 -40
  102. package/package.json +5 -2
package/dist/lanes.js CHANGED
@@ -1,395 +1,41 @@
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';
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
- /** What `writeLater` has queued: lane slots and the rows they go to, for one set of columns. */
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((slot) => this.forget(slot));
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
- this.reach(slot, -1);
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
- 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;
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 : this.reportedRow(c, p, slot);
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 && 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;
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, whether only solo lanes exist, so
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
- if (this.qualifiedVersion !== version)
662
- this.requalify(version);
663
- else if (this.touched.length > 0 && !this.retouch())
664
- this.requalify(version);
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
- this.fillAll(now, version);
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
- this.requalify(version);
273
+ requalify(this, version);
675
274
  if (this.laned.length === 0)
676
- return GENERAL;
677
- this.fillAll(now, version);
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 + LANE_PROBE] > from || this.per[o + GENERAL_PROBE] > from);
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
- * 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.
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
- 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
- }
312
+ rests(slot) {
313
+ for (const ch of this.laned) {
744
314
  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]];
315
+ if (ch.scalar) {
316
+ if (!(Math.abs(values[slot] - ch.rest) < 1e-9))
317
+ return false;
318
+ continue;
752
319
  }
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);
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
- this.unplace(c, p);
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
- * 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.
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
- 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) {
380
+ stretchChanged(id, s) {
1151
381
  const c = this.crowdOf.get(id);
1152
382
  const p = c?.rowOf.get(id);
1153
- if (c === undefined || p === undefined)
383
+ if (c !== undefined && p !== undefined) {
384
+ c.restale(p, c.hot[p * c.stride + 0 /* Hot.FLAGS */]);
1154
385
  return;
1155
- const f = p * c.stride + H_FLAGS;
1156
- c.hot[f] = c.hot[f] | F_STALE;
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 + 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
- }
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 });