@msb235/blits 0.3.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 +314 -0
  2. package/README.md +3 -2
  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 +143 -122
  47. package/dist/lanes.js +236 -742
  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 -169
  55. package/dist/mixer.js +321 -1793
  56. package/dist/motion.d.ts +104 -19
  57. package/dist/motion.js +448 -180
  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 +261 -42
  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 +151 -46
  102. package/package.json +5 -2
package/dist/lanes.js CHANGED
@@ -1,218 +1,41 @@
1
- import { foldNumber, numericOf } from './channels.js';
2
- import { clampWeight, passAt, phaseAt } from './clock.js';
3
- import { motionOf } 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 = 7;
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
- const SLOT = 6;
56
- const FILLED = 0;
57
- const SEEN = 1;
58
- const IDLE = 2;
59
- const LANE_PROBE = 3;
60
- const GENERAL_PROBE = 4;
61
- const LANE_FILL = 5;
62
- /**
63
- * Below what share of its subjects probed last frame a lane stops filling, and above what share it
64
- * fills again; between the two it keeps doing what it did, so a host near the line does not flip
65
- * every frame. Measured at 10k and 1k subjects (2026-10-02, one local machine): filling breaks even
66
- * with probing through the general path at about 19% probed for keys at both sizes, 71-77% for a
67
- * spring, and 63% (1k) to about 100% (10k) for a stateless fn.
68
- */
69
- const SPARSE = { keys: { stop: 0.15, start: 0.25 }, other: { stop: 0.6, start: 0.8 } };
70
- /** The least prime at least `n`, and 1 below 2. */
71
- function primeFrom(n) {
72
- if (n < 2)
73
- return 1;
74
- for (let c = n;; c++) {
75
- let prime = true;
76
- for (let d = 2; d * d <= c; d++)
77
- if (c % d === 0) {
78
- prime = false;
79
- break;
80
- }
81
- if (prime)
82
- return c;
83
- }
84
- }
85
- /**
86
- * What one laned voice keeps per subject it reaches, by position in `list`. A subject gets a
87
- * position when a probe of it first finds the voice playing, which is when the general path first
88
- * sees it too.
89
- */
90
- class Lane {
91
- voice;
92
- /** The subject number at each position. */
93
- list = [];
94
- at = new Map();
95
- /** Whether too few of its subjects were probed lately to fill it: theirs take the general path. */
96
- idle = false;
97
- /** The lines it goes idle and busy at, by its patch form. */
98
- line;
99
- /**
100
- * A `fn` or `keys` voice naming one subject, which shares nothing across subjects for a fill to
101
- * save, so it stays idle: its subject takes the general path, and the channel stays a lane for
102
- * the voices that do share.
103
- */
104
- solo;
105
- /**
106
- * When the voice started playing on its lane, in the order lanes did; 0 while it waits. A subject
107
- * checked against every lane up to some epoch has met this one if this one's is no later.
108
- */
109
- epoch = 0;
110
- /**
111
- * Per position, `STRIDE` numbers side by side, so one subject's are one read from memory: its
112
- * delay, its `since`, its weight at the last fill, its weight at the last fill a probe read, a
113
- * motion patch's own number for it (-1 until asked), and the fill and seeks of its last sample.
114
- */
115
- data = new Float64Array(0);
116
- records = [];
117
- /**
118
- * For a motion voice, per position, the delta its last sample made; null where that sample was
119
- * kept only in `samples`, until a probe asks for it.
120
- */
121
- deltas = [];
122
- /** For a motion voice, per position, its last sample's value on each of `axes` axes. */
123
- samples = new Float64Array(0);
124
- axes = 0;
125
- /** What a keys read writes into, reused across subjects. */
126
- delta = {};
127
- /** The arrays keyed reads interpolate into: the lane's own, so a probe mid-fill cannot move them. */
128
- scratch = [];
129
- /** The laned channel of each channel the voice writes, in `writes` order. */
130
- chans = [];
131
- /** The value read for each of `chans`, for the subject being folded. */
132
- values = [];
133
- /**
134
- * Within one fill, the last elapsed placed and the last `since` weighed, with what they gave, so
135
- * subjects sharing a delay or a start share the arithmetic: the same inputs give the same bits.
136
- */
137
- placed = false;
138
- placedAt = 0;
139
- phase = 0;
140
- pass = 0;
141
- read = false;
142
- readAt = 0;
143
- weighed = false;
144
- weighedSince = 0;
145
- fade = 1;
146
- /** Whether the voice has no fade in or out this fill, so its envelope is 1 for every subject. */
147
- flat = false;
148
- /** A motion patch's state, which the lane samples in place of calling the patch. */
149
- motion;
150
- constructor(voice) {
151
- this.voice = voice;
152
- this.motion = motionOf(voice.patch);
153
- this.line = voice.built !== null ? SPARSE.keys : SPARSE.other;
154
- this.solo = voice.named !== null && voice.named.size === 1 && this.motion === undefined;
155
- this.idle = this.solo;
156
- }
157
- positionOf(slot) {
158
- return this.at.get(slot) ?? -1;
159
- }
160
- /** Gives a subject the voice reaches a position, from what its record fixed on first sight. */
161
- add(slot, held) {
162
- const p = this.list.length;
163
- this.list.push(slot);
164
- this.at.set(slot, p);
165
- if ((p + 1) * STRIDE > this.data.length) {
166
- const data = new Float64Array(Math.max(p + 1, (this.data.length / STRIDE) * 2, 4) * STRIDE);
167
- data.set(this.data);
168
- this.data = data;
169
- }
170
- const o = p * STRIDE;
171
- this.data[o + DELAY] = held.delay;
172
- this.data[o + SINCE] = held.since;
173
- this.data[o + WEIGHT] = 0;
174
- this.data[o + PROBED] = 0;
175
- this.data[o + MSLOT] = -1;
176
- this.data[o + SAMPLED] = -1;
177
- this.records[p] = held;
178
- this.deltas[p] = null;
179
- }
180
- /** A subject lost its number: the last position moves into its place. */
181
- remove(slot) {
182
- const p = this.at.get(slot);
183
- if (p === undefined)
184
- return;
185
- const last = this.list.length - 1;
186
- if (p !== last) {
187
- const moved = this.list[last];
188
- this.list[p] = moved;
189
- this.at.set(moved, p);
190
- this.data.copyWithin(p * STRIDE, last * STRIDE, (last + 1) * STRIDE);
191
- this.records[p] = this.records[last];
192
- this.deltas[p] = this.deltas[last] ?? null;
193
- const n = this.axes;
194
- if (n > 0)
195
- this.samples.copyWithin(p * n, last * n, (last + 1) * n);
196
- }
197
- this.list.pop();
198
- this.at.delete(slot);
199
- this.records[last] = undefined;
200
- this.deltas[last] = null;
201
- }
202
- }
9
+ import { reportedRow, unplace } from './rows.js';
203
10
  /**
204
11
  * The lanes of one mix: subject numbers, which channels and voices run on lanes, and the values a
205
12
  * frame's fill leaves for probes to copy.
206
13
  */
14
+ // biome-ignore lint/suspicious/noUnsafeDeclarationMerging: every member of `Methods` is installed below
207
15
  export class Lanes {
208
16
  host;
209
17
  numbers;
210
18
  lanes = [];
211
- /** The lanes a fill visits: every lane but the solo ones. */
212
- runs = [];
213
19
  /** The lanes over every subject that have started playing, by epoch. */
214
20
  dense = [];
215
21
  byId = new Map();
22
+ /** The crowds of single-subject motion voices, one per channel, and which holds each voice. */
23
+ crowds = [];
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;
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();
216
39
  laned = [];
217
40
  /** By kit slot, the laned channel there. */
218
41
  bySlot = [];
@@ -222,6 +45,12 @@ export class Lanes {
222
45
  whole = false;
223
46
  cap = 0;
224
47
  epochs = 0;
48
+ /** The latest epoch of a lane over every subject, which every subject meets on its next probe. */
49
+ wide = 0;
50
+ /** Voices that joined, started or left since the last qualify, settled at the next probe. */
51
+ touched = [];
52
+ /** The highest voice id the last qualify or settling of `touched` considered. */
53
+ known = 0;
225
54
  /**
226
55
  * Per subject number, `SLOT` numbers side by side, so a probe reads one place in memory: which
227
56
  * fill last wrote it (0 for none); the latest lane epoch a probe of it has checked it against;
@@ -248,6 +77,15 @@ export class Lanes {
248
77
  * the general path has to make.
249
78
  */
250
79
  late = [];
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
+ */
84
+ queueSlots = new Int32Array(0);
85
+ queueRows = new Int32Array(0);
86
+ queueCounts = new Int32Array(0);
87
+ queued = 0;
88
+ queuedFor = null;
251
89
  keeps = false;
252
90
  now = Number.NaN;
253
91
  filledAt = Number.NaN;
@@ -270,7 +108,7 @@ export class Lanes {
270
108
  qualifiedVersion = Number.NaN;
271
109
  constructor(host) {
272
110
  this.host = host;
273
- this.numbers = new Numbers((slot) => this.forget(slot));
111
+ this.numbers = new Numbers(this);
274
112
  }
275
113
  /** A change that may move a voice on or off its lane: qualify again, then fill again. */
276
114
  invalidate() {
@@ -281,17 +119,25 @@ export class Lanes {
281
119
  refill() {
282
120
  this.filledVersion = Number.NaN;
283
121
  }
122
+ /**
123
+ * A voice joined, started playing or left: the next probe takes it on or off its crowd where it
124
+ * can, and qualifies again where it cannot.
125
+ */
126
+ touch(voice) {
127
+ this.touched.push(voice);
128
+ this.filledVersion = Number.NaN;
129
+ }
284
130
  /** Numbers a subject the mix sees for the first time. */
285
131
  number(subject) {
286
132
  const slot = this.numbers.take(subject);
287
133
  this.live++;
288
134
  this.grow(slot + 1);
289
- this.per[slot * SLOT + FILLED] = 0;
290
- this.per[slot * SLOT + SEEN] = 0;
291
- this.per[slot * SLOT + IDLE] = 0;
292
- this.per[slot * SLOT + LANE_PROBE] = 0;
293
- this.per[slot * SLOT + GENERAL_PROBE] = 0;
294
- 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;
295
141
  if (this.filling)
296
142
  this.late.push(slot);
297
143
  return slot;
@@ -299,32 +145,104 @@ export class Lanes {
299
145
  release(slot) {
300
146
  this.numbers.release(slot);
301
147
  }
148
+ /** A voice faded a subject out of itself alone: its lane lets the subject's position go. */
149
+ part(id, slot) {
150
+ const lane = this.byId.get(id);
151
+ if (lane !== undefined) {
152
+ if (lane.positionOf(slot) < 0)
153
+ return;
154
+ if (lane.idle)
155
+ reach(this, slot, -1);
156
+ lane.remove(slot);
157
+ return;
158
+ }
159
+ const c = this.crowdOf.get(id);
160
+ const p = c?.rowOf.get(id);
161
+ if (c !== undefined && p !== undefined && c.list[p] === slot)
162
+ unplace(this, c, p);
163
+ }
164
+ /** A voice brought a subject back: its next probe meets every lane again, as it met them first. */
165
+ rejoin(slot) {
166
+ if (slot < this.cap)
167
+ this.per[slot * 6 /* Per.SLOT */ + 1 /* Per.SEEN */] = -1;
168
+ }
302
169
  /**
303
170
  * A laned voice's weight for a subject at the last frame a probe read it from the lane; undefined
304
171
  * where the subject's record holds it instead, because its last probe took the general path or
305
172
  * the voice is not on a lane.
306
173
  */
307
174
  weightOf(id, slot) {
308
- const lane = this.byId.get(id);
309
- if (lane === undefined || slot < 0)
175
+ if (slot < 0)
310
176
  return undefined;
311
- return this.reported(lane, slot);
177
+ const lane = this.byId.get(id);
178
+ if (lane !== undefined)
179
+ return this.reported(lane, slot);
180
+ const c = this.crowdOf.get(id);
181
+ const p = c?.rowOf.get(id);
182
+ return c === undefined || p === undefined ? undefined : reportedRow(this, c, p, slot);
312
183
  }
313
184
  reported(lane, slot) {
314
- const o = slot * SLOT;
315
- 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 */]))
316
187
  return undefined;
317
188
  const p = lane.positionOf(slot);
318
189
  if (p < 0)
319
190
  return undefined;
320
- 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 */)];
321
192
  }
322
193
  /**
323
194
  * Fills the lanes once a frame, and says whether this probe of a subject reads its values from
324
195
  * them. One that a lane has not met yet, newly numbered or newly reached, takes the general path
325
196
  * this frame, which is where it is first seen; so does one probed from inside a fill.
326
197
  */
327
- prepare(slot, subject, now, version) {
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 */)
201
+ return this.general(slot);
202
+ if (slot < 0)
203
+ return false;
204
+ if (!this.probedThisFrame(slot))
205
+ this.distinct++;
206
+ const probe = ++this.probes;
207
+ const per = this.per;
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
+ }
220
+ }
221
+ if (lane) {
222
+ per[o + 3 /* Per.LANE_PROBE */] = probe;
223
+ per[o + 5 /* Per.LANE_FILL */] = this.fills;
224
+ this.subjects[slot] = subject;
225
+ this.holding = true;
226
+ }
227
+ else
228
+ per[o + 4 /* Per.GENERAL_PROBE */] = probe;
229
+ return lane;
230
+ }
231
+ /** Whether this frame's lanes are filled and nothing since asks `begin` to look again. */
232
+ filled(now, version) {
233
+ return (this.filledAt === now &&
234
+ this.frameAt === now &&
235
+ this.filledVersion === version &&
236
+ this.qualifiedVersion === version &&
237
+ this.moved === reading.moved &&
238
+ this.touched.length === 0 &&
239
+ !this.filling);
240
+ }
241
+ /**
242
+ * What a probe does before its subject, once a frame's lanes are settled: qualifies and fills as
243
+ * needed. Says whether the subject takes the general path or the lanes are filled.
244
+ */
245
+ begin(now, version) {
328
246
  if (now !== this.frameAt) {
329
247
  this.lastFrom = this.frameProbes;
330
248
  this.lastTo = this.probes;
@@ -335,87 +253,79 @@ export class Lanes {
335
253
  this.fresh = true;
336
254
  }
337
255
  if (this.filling)
338
- return this.general(slot);
339
- if (this.qualifiedVersion !== version)
340
- this.requalify(version);
341
- if (this.laned.length === 0)
342
- return this.general(slot);
343
- // Only solo lanes: nothing to fill, and every subject takes the general path.
344
- if (this.runs.length === 0) {
345
- if (slot < 0)
346
- return false;
347
- if (this.per[slot * SLOT + SEEN] < this.epochs)
348
- this.meet(slot, subject);
349
- return this.general(slot);
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);
350
264
  }
351
- if (slot >= 0 && !this.probedThisFrame(slot))
352
- this.distinct++;
265
+ else if (this.touched.length > 0 && !retouch(this))
266
+ requalify(this, version);
267
+ if (this.laned.length === 0)
268
+ return 0 /* Begin.GENERAL */;
353
269
  if (this.filledAt !== now || this.filledVersion !== version || this.moved !== reading.moved) {
354
- this.fillAll(now, version);
270
+ fillAll(this, now, version);
355
271
  // A patch call in that fill made kept state, which took its voice off its lane: fill without it.
356
272
  if (this.qualifiedVersion !== version) {
357
- this.requalify(version);
273
+ requalify(this, version);
358
274
  if (this.laned.length === 0)
359
- return this.general(slot);
360
- this.fillAll(now, version);
275
+ return 0 /* Begin.GENERAL */;
276
+ fillAll(this, now, version);
361
277
  }
362
278
  }
363
- if (slot < 0)
364
- return false;
365
- const probe = ++this.probes;
366
- const per = this.per;
367
- const o = slot * SLOT;
368
- let lane = per[o + FILLED] === this.fills && per[o + IDLE] === 0;
369
- if (per[o + SEEN] < this.epochs && this.meet(slot, subject)) {
370
- // The fill ran before the subject had these positions, so it reads the general path all frame.
371
- per[o + FILLED] = 0;
372
- lane = false;
373
- }
374
- if (lane) {
375
- per[o + LANE_PROBE] = probe;
376
- per[o + LANE_FILL] = this.fills;
377
- this.subjects[slot] = subject;
378
- this.holding = true;
379
- }
380
- else
381
- per[o + GENERAL_PROBE] = probe;
382
- return lane;
279
+ return 1 /* Begin.READY */;
383
280
  }
384
281
  general(slot) {
385
282
  if (slot >= 0)
386
- this.per[slot * SLOT + GENERAL_PROBE] = ++this.probes;
283
+ this.per[slot * 6 /* Per.SLOT */ + 4 /* Per.GENERAL_PROBE */] = ++this.probes;
387
284
  return false;
388
285
  }
389
286
  /** Whether a probe has read the subject since the frame began, on either path. */
390
287
  probedThisFrame(slot) {
391
288
  const from = this.frameProbes;
392
- const o = slot * SLOT;
393
- 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;
394
307
  }
395
308
  /**
396
- * Writes a subject's values into each column at subject `n`'s places, for a probe that reads from
397
- * the lanes while every voice is on one: a channel no lane holds is at rest.
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.
398
311
  */
399
- write(slot, columns, n) {
400
- const bySlot = this.bySlot;
401
- for (let k = 0; k < columns.length; k++) {
402
- const c = columns[k];
403
- const axes = c.axes;
404
- const out = c.out;
405
- const at = n * axes;
406
- const ch = bySlot[c.slot];
407
- if (ch === undefined) {
408
- for (let a = 0; a < axes; a++)
409
- out[at + a] = c.rest[a];
312
+ rests(slot) {
313
+ for (const ch of this.laned) {
314
+ const values = ch.values;
315
+ if (ch.scalar) {
316
+ if (!(Math.abs(values[slot] - ch.rest) < 1e-9))
317
+ return false;
410
318
  continue;
411
319
  }
412
- const values = ch.values;
413
- const base = slot * axes;
414
- for (let a = 0; a < axes; a++)
415
- out[at + a] = values[base + a];
416
- if (c.bounds !== undefined)
417
- clampRun(out, at, 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;
418
327
  }
328
+ return true;
419
329
  }
420
330
  /** Writes a subject's laned values into a pose, each array channel into a new array. */
421
331
  copy(slot, pose) {
@@ -423,7 +333,7 @@ export class Lanes {
423
333
  for (let c = 0; c < laned.length; c++) {
424
334
  const ch = laned[c];
425
335
  const axes = ch.axes;
426
- if (axes === 1) {
336
+ if (ch.scalar) {
427
337
  pose[ch.name] = ch.values[slot];
428
338
  continue;
429
339
  }
@@ -435,60 +345,27 @@ export class Lanes {
435
345
  pose[ch.name] = arr;
436
346
  }
437
347
  }
438
- /**
439
- * Gives a probed subject a position on every lane that started playing since the subject was last
440
- * checked and that reaches it. True when any did, so the probe takes the general path.
441
- */
442
- meet(slot, subject) {
443
- const from = this.per[slot * SLOT + SEEN];
444
- this.per[slot * SLOT + SEEN] = this.epochs;
445
- let met = false;
446
- const dense = this.dense;
447
- for (let i = dense.length - 1; i >= 0; i--) {
448
- const lane = dense[i];
449
- if (lane.epoch <= from)
450
- break;
451
- const held = this.host.meet(lane.voice, subject);
452
- if (!held.reaches)
453
- continue;
454
- lane.add(slot, held);
455
- if (lane.idle)
456
- this.reach(slot, 1);
457
- met = true;
458
- }
459
- const naming = this.host.naming(subject);
460
- if (naming !== undefined)
461
- for (const voice of naming) {
462
- const lane = this.byId.get(voice.id);
463
- if (lane === undefined || lane.epoch <= from || lane.positionOf(slot) >= 0)
464
- continue;
465
- lane.add(slot, this.host.meet(voice, subject));
466
- if (lane.idle)
467
- this.reach(slot, 1);
468
- met = true;
469
- }
470
- return met;
471
- }
472
- /** Counts an idle lane more or fewer reaching a subject. */
473
- reach(slot, by) {
474
- const i = slot * SLOT + IDLE;
475
- this.per[i] = this.per[i] + by;
476
- }
477
348
  forget(slot) {
349
+ this.host.forgot(slot);
478
350
  this.live--;
479
351
  for (const lane of this.lanes)
480
352
  lane.remove(slot);
353
+ for (const c of this.crowds)
354
+ for (let p = 0; p < c.size; p++)
355
+ if (c.list[p] === slot)
356
+ unplace(this, c, p);
481
357
  if (slot < this.cap)
482
- this.per[slot * SLOT + FILLED] = 0;
358
+ this.per[slot * 6 /* Per.SLOT */ + 0 /* Per.FILLED */] = 0;
483
359
  this.subjects[slot] = undefined;
484
360
  }
485
361
  grow(size) {
486
362
  if (size <= this.cap)
487
363
  return;
488
364
  const cap = Math.max(size, this.cap * 2, 64);
489
- const per = new Float64Array(cap * SLOT);
365
+ const per = new Float64Array(cap * 6 /* Per.SLOT */);
490
366
  per.set(this.per);
491
367
  this.per = per;
368
+ this.owed.grow(cap);
492
369
  for (const ch of this.laned) {
493
370
  const values = new Float64Array(cap * ch.axes);
494
371
  values.set(ch.values);
@@ -496,433 +373,50 @@ export class Lanes {
496
373
  }
497
374
  this.cap = cap;
498
375
  }
499
- requalify(version) {
500
- const host = this.host;
501
- this.qualifiedVersion = version;
502
- this.filledVersion = Number.NaN;
503
- const present = host.voices.filter((v) => v.state !== 'done');
504
- const numeric = host.channels.map((c) => numericOf(c) !== undefined);
505
- const { channels, voices } = qualify(numeric,
506
- // A voice writing nothing has no lane to fill, so it stays where its calls are made.
507
- present.map((v) => ({ id: v.id, fits: v.slots.length > 0 && host.fits(v), slots: v.slots })));
508
- for (const lane of this.lanes)
509
- if (!voices.has(lane.voice.id))
510
- this.leave(lane);
511
- const kept = [];
512
- for (const v of present) {
513
- if (!voices.has(v.id))
514
- continue;
515
- const lane = this.byId.get(v.id) ?? new Lane(v);
516
- v.laned = true;
517
- if (lane.epoch === 0 && (v.state === 'live' || v.state === 'fading'))
518
- lane.epoch = ++this.epochs;
519
- kept.push(lane);
520
- }
521
- this.lanes = kept;
522
- this.runs = kept.filter((l) => !l.solo);
523
- this.dense = kept
524
- .filter((l) => l.voice.named === null && l.epoch > 0)
525
- .sort((a, b) => a.epoch - b.epoch);
526
- this.whole = kept.length === present.length;
527
- this.byId.clear();
528
- for (const lane of kept)
529
- this.byId.set(lane.voice.id, lane);
530
- // With no lane left no fill comes to let go of the subjects the last probes held.
531
- if (kept.length === 0)
532
- this.subjects = [];
533
- this.laned = [];
534
- this.bySlot = host.channels.map(() => undefined);
535
- channels.forEach((on, slot) => {
536
- if (!on)
537
- return;
538
- const channel = host.channels[slot];
539
- const n = numericOf(channel);
540
- const rest = channel.rest;
541
- const ch = {
542
- name: host.names[slot],
543
- op: n.op,
544
- rest: typeof rest === 'number' ? rest : rest[0],
545
- axes: n.axes,
546
- values: new Float64Array(this.cap * n.axes),
547
- start: typeof rest === 'number' ? [] : rest,
548
- };
549
- this.laned.push(ch);
550
- this.bySlot[slot] = ch;
551
- });
552
- this.copies.length = 0;
553
- for (const ch of this.bySlot)
554
- this.copies.push(ch !== undefined);
555
- for (const lane of kept) {
556
- lane.chans = lane.voice.slots.map((s) => this.bySlot[s]);
557
- lane.values = lane.chans.map(() => undefined);
558
- }
559
- }
560
- /** Hands a voice back to the general path, with the weights `weightOf` reports kept on its records. */
561
- leave(lane) {
562
- if (lane.idle)
563
- this.wake(lane);
564
- for (let p = 0; p < lane.list.length; p++) {
565
- const rec = lane.records[p];
566
- const slot = lane.list[p];
567
- const w = this.reported(lane, slot);
568
- if (rec !== undefined && w !== undefined)
569
- rec.weight = w;
570
- if (rec !== undefined && lane.motion !== undefined)
571
- this.settle(lane, p, slot, rec);
572
- }
573
- lane.voice.laned = false;
574
- }
575
- fillAll(now, version) {
576
- const host = this.host;
577
- this.filling = true;
578
- try {
579
- this.fills++;
580
- this.now = now;
581
- this.filledAt = now;
582
- this.filledVersion = version;
583
- const size = this.numbers.size;
584
- this.keeps = host.keeps;
585
- const decide = this.fresh;
586
- this.fresh = false;
587
- const share = this.live > 0 ? this.lastDistinct / this.live : 1;
588
- // One pass, deciding and running each lane, since a mix of many small lanes pays per visit.
589
- let busy = false;
590
- for (const lane of this.runs) {
591
- if (decide) {
592
- // A lane with few subjects, such as one naming its own, goes by the share of all probed;
593
- // one with a single subject shares nothing a fill could save, as a solo lane does not.
594
- const line = lane.line;
595
- const n = lane.list.length;
596
- if (n >= 64)
597
- this.pace(lane);
598
- else if (n === 1 && lane.motion === undefined) {
599
- if (!lane.idle)
600
- this.rest(lane);
601
- }
602
- else if (lane.idle ? share >= line.start : share < line.stop)
603
- this.flip(lane);
604
- }
605
- if (lane.idle)
606
- continue;
607
- if (!busy) {
608
- busy = true;
609
- for (const ch of this.laned)
610
- ch.values.fill(ch.rest, 0, size * ch.axes);
611
- }
612
- this.run(lane);
613
- }
614
- // With every lane idle there was nothing to fill, and no subject reads a lane this frame.
615
- if (busy)
616
- for (let s = 0; s < size; s++)
617
- this.per[s * SLOT + FILLED] = this.fills;
618
- for (const slot of this.late)
619
- this.per[slot * SLOT + FILLED] = 0;
620
- if (this.holding) {
621
- this.subjects.fill(undefined, 0, size);
622
- this.holding = false;
623
- }
624
- this.moved = reading.moved;
625
- }
626
- finally {
627
- this.filling = false;
628
- this.late.length = 0;
629
- }
630
- }
631
376
  /**
632
- * Sets a lane idle or busy by the share of its subjects probed in the last frame that had a probe,
633
- * read from about 256 of them a prime stride apart, from an offset that moves each frame, so a host
634
- * probing every second or third subject is not read as probing all or none. Both paths give the
635
- * same values, so this only picks the cheaper.
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.
636
379
  */
637
- pace(lane) {
638
- const list = lane.list;
639
- const line = lane.line;
640
- const from = this.lastFrom;
641
- const to = this.lastTo;
642
- const step = primeFrom(Math.floor(list.length / 256));
643
- let looked = 0;
644
- let probed = 0;
645
- for (let p = this.fills % step; p < list.length; p += step) {
646
- const slot = list[p];
647
- const a = this.per[slot * SLOT + LANE_PROBE];
648
- const b = this.per[slot * SLOT + GENERAL_PROBE];
649
- if ((a > from && a <= to) || (b > from && b <= to))
650
- probed++;
651
- looked++;
652
- }
653
- const share = probed / looked;
654
- if (lane.idle ? share >= line.start : share < line.stop)
655
- this.flip(lane);
656
- }
657
- flip(lane) {
658
- if (lane.idle)
659
- this.wake(lane);
660
- else
661
- this.rest(lane);
662
- }
663
- /** Leaves a lane's subjects to the general path until it wakes, keeping what `weightOf` reports. */
664
- rest(lane) {
665
- lane.idle = true;
666
- const list = lane.list;
667
- const data = lane.data;
668
- for (let p = 0; p < list.length; p++) {
669
- const slot = list[p];
670
- this.reach(slot, 1);
671
- // A probe read this subject from the last fill, which this lane's weight stays from.
672
- if (this.per[slot * SLOT + LANE_FILL] === this.fills - 1) {
673
- const o = p * STRIDE;
674
- data[o + PROBED] = data[o + WEIGHT];
675
- }
676
- }
677
- }
678
- wake(lane) {
679
- lane.idle = false;
680
- for (const slot of lane.list)
681
- this.reach(slot, -1);
682
- }
683
- subjectAt(slot) {
684
- let subject = this.subjects[slot];
685
- if (subject === undefined) {
686
- subject = this.numbers.subject(slot);
687
- this.subjects[slot] = subject;
688
- this.holding = true;
689
- }
690
- return subject;
691
- }
692
- /** One voice's contribution to every subject it plays on. */
693
- run(lane) {
694
- const voice = lane.voice;
695
- if (voice.state !== 'live' && voice.state !== 'fading')
380
+ stretchChanged(id, s) {
381
+ const c = this.crowdOf.get(id);
382
+ const p = c?.rowOf.get(id);
383
+ if (c !== undefined && p !== undefined) {
384
+ c.restale(p, c.hot[p * c.stride + 0 /* Hot.FLAGS */]);
696
385
  return;
697
- lane.placed = false;
698
- lane.read = false;
699
- lane.weighed = false;
700
- // With no fade in or out the envelope is 1 for every subject, which is what it would return.
701
- lane.flat = !((voice.fade.in ?? 0) > 0) && voice.out === null;
702
- lane.fade = 1;
703
- const elapsed = voice.elapsedAt(this.now);
704
- const period = voice.patch.period;
705
- const passes = voice.passes;
706
- const list = lane.list;
707
- for (let p = 0; p < list.length; p++) {
708
- this.one(lane, p, list[p], elapsed, period, passes);
709
- // Its patch just made kept state: no further call this fill, the general path makes them.
710
- if (voice.keeping)
711
- return;
712
386
  }
713
- }
714
- one(lane, p, slot, elapsedNow, period, passes) {
715
- const host = this.host;
716
- const voice = lane.voice;
717
- const data = lane.data;
718
- const o = p * STRIDE;
719
- // A probe read this subject from the last fill: keep the weight it read for `weightOf`.
720
- if (this.per[slot * SLOT + LANE_FILL] === this.fills - 1)
721
- data[o + PROBED] = data[o + WEIGHT];
722
- const delay = data[o + DELAY];
723
- const elapsed = elapsedNow - delay;
724
- if (elapsed < 0) {
725
- data[o + WEIGHT] = 0;
726
- return;
727
- }
728
- if (!lane.placed || !Object.is(elapsed, lane.placedAt)) {
729
- lane.placed = true;
730
- lane.placedAt = elapsed;
731
- lane.phase = phaseAt(elapsed, period, passes);
732
- lane.pass = passAt(elapsed, period, passes);
733
- }
734
- const since = data[o + SINCE];
735
- if (!lane.flat && (!lane.weighed || !Object.is(since, lane.weighedSince))) {
736
- lane.fade = host.envelope(voice, since);
737
- lane.weighed = true;
738
- lane.weighedSince = since;
739
- }
740
- const w = clampWeight(voice.weight * lane.fade);
741
- data[o + WEIGHT] = w;
742
- if (voice.built !== null) {
743
- if (!lane.read || !Object.is(elapsed, lane.readAt)) {
744
- readKeys(voice.built, lane.phase, lane.delta, undefined, voice.lerps, undefined, voice.intos, lane.scratch);
745
- lane.read = true;
746
- lane.readAt = elapsed;
747
- this.gather(lane, lane.delta);
748
- }
749
- if (w > 0)
750
- this.fold(lane, slot, w);
751
- return;
752
- }
753
- const subject = this.subjectAt(slot);
754
- if (subject === absent)
755
- return;
756
- const held = lane.records[p];
757
- // The general path makes a voice's first call for a subject, where it first sees it play.
758
- if (Number.isNaN(held.probed)) {
759
- this.late.push(slot);
760
- return;
761
- }
762
- if (lane.motion !== undefined) {
763
- this.move(lane, lane.motion, p, slot, subject, held, elapsed, delay, w);
764
- return;
765
- }
766
- // Called already this frame, by the general path or a fill before a refill: reuse, as a probe does.
767
- if (held.probed === this.now && held.delta !== null && held.seeks === voice.seeks) {
768
- if (w > 0)
769
- this.foldDelta(lane, slot, held.delta, w);
770
- return;
771
- }
772
- const kept = reading.kept;
773
- host.ready(voice, subject, held, elapsed, lane.pass, w);
774
- host.horizon(voice, delay);
775
- const delta = voice.patch.at(lane.phase, subject, voice.setting);
776
- // What `influence` leaves on the record, so a probe on the general path this frame reuses it.
777
- held.delta = delta;
778
- held.probed = this.now;
779
- held.seeks = voice.seeks;
780
- if (this.keeps)
781
- host.after(voice, held);
782
- if (reading.kept !== kept && !voice.keeping)
783
- host.kept(voice);
784
- if (w > 0)
785
- this.foldDelta(lane, slot, delta, w);
786
- }
787
- /**
788
- * A motion voice's value for a subject, sampled from the patch's state as its `at` would, but only
789
- * where that sample changes nothing: one with a change to stamp, commit or let go of goes to the
790
- * general path, which samples it if and when a probe asks, as it would with no lanes. A probe
791
- * this frame already read keeps the value it read, as the general path's record would.
792
- */
793
- move(lane, run, p, slot, subject, held, elapsed, delay, w) {
794
- const voice = lane.voice;
795
- const data = lane.data;
796
- const o = p * STRIDE;
797
- const ch = lane.chans[0];
798
- this.settle(lane, p, slot, held);
799
- const delta = held.delta;
800
- if (delta === null ||
801
- held.probed !== this.now ||
802
- held.seeks !== voice.seeks ||
803
- !this.probedThisFrame(slot)) {
804
- let ms = data[o + MSLOT];
805
- if (ms < 0) {
806
- ms = run.slot(subject);
807
- data[o + MSLOT] = ms;
808
- }
809
- this.host.horizon(voice, delay);
810
- if (!run.quiet(ms, elapsed)) {
811
- this.late.push(slot);
812
- return;
813
- }
814
- run.sample(ms, elapsed, run.xs, run.vs);
815
- // Kept as numbers, not a delta: one is built only if a probe this frame asks for it.
816
- const n = run.n;
817
- if (lane.axes !== n) {
818
- lane.axes = n;
819
- lane.samples = new Float64Array(0);
820
- }
821
- if ((p + 1) * n > lane.samples.length) {
822
- const grown = new Float64Array(Math.max(p + 1, (lane.samples.length / n) * 2, 4) * n);
823
- grown.set(lane.samples);
824
- lane.samples = grown;
825
- }
826
- for (let a = 0; a < n; a++)
827
- lane.samples[p * n + a] = run.xs[a];
828
- lane.deltas[p] = null;
829
- data[o + SAMPLED] = this.fills;
830
- data[o + SEEKS] = voice.seeks;
831
- if (w <= 0)
832
- return;
833
- // A sample of other axes than the channel's folds as the general path folds it.
834
- if (n === ch.axes && run.scalar(ms) === (ch.axes === 1))
835
- this.foldRun(ch, slot, run.xs, w);
836
- else
837
- this.foldInto(ch, slot, run.value(ms, run.xs), w);
838
- return;
839
- }
840
- lane.deltas[p] = delta;
841
- data[o + SAMPLED] = this.fills;
842
- data[o + SEEKS] = voice.seeks;
843
- if (w > 0)
844
- this.foldInto(ch, slot, delta[ch.name], w);
845
- }
846
- /** The delta a motion voice's last sample for position `p` made, built from `samples`. */
847
- sampled(lane, p) {
848
- const run = lane.motion;
849
- const n = lane.axes;
850
- for (let a = 0; a < n; a++)
851
- run.xs[a] = lane.samples[p * n + a];
852
- const ms = lane.data[p * STRIDE + MSLOT];
853
- const delta = { [lane.chans[0].name]: run.value(ms, run.xs) };
854
- lane.deltas[p] = delta;
855
- return delta;
856
- }
857
- /**
858
- * A probe this frame read a motion voice's value for the subject from an earlier fill: write onto
859
- * its record what the general path's sample would have, so a later read this frame reuses it.
860
- */
861
- settle(lane, p, slot, held) {
862
- const o = p * STRIDE;
863
- if ((held.probed === this.now && held.seeks === lane.data[o + SEEKS]) ||
864
- !(this.per[slot * SLOT + LANE_PROBE] > this.frameProbes) ||
865
- lane.data[o + SAMPLED] !== this.per[slot * SLOT + LANE_FILL])
866
- return;
867
- held.delta = lane.deltas[p] ?? this.sampled(lane, p);
868
- held.probed = this.now;
869
- held.seeks = lane.data[o + SEEKS];
870
- }
871
- /** For a keys voice: the delta's value for each channel it writes, read once per phase. */
872
- gather(lane, delta) {
873
- const chans = lane.chans;
874
- for (let i = 0; i < chans.length; i++)
875
- lane.values[i] = delta[chans[i].name];
876
- }
877
- /** Folds what `gather` read into a subject's laned values. */
878
- fold(lane, slot, w) {
879
- const chans = lane.chans;
880
- for (let i = 0; i < chans.length; i++)
881
- this.foldInto(chans[i], slot, lane.values[i], w);
882
- }
883
- /**
884
- * Folds a delta a patch returned into a subject's laned values. The first few channels are read
885
- * at a site of their own, which in most mixes sees one channel name and stays fast, where one
886
- * site reading every name in turn slows every read.
887
- */
888
- foldDelta(lane, slot, delta, w) {
889
- const chans = lane.chans;
890
- const n = chans.length;
891
- let ch = chans[0];
892
- if (n > 0)
893
- this.foldInto(ch, slot, delta[ch.name], w);
894
- ch = chans[1];
895
- if (n > 1)
896
- this.foldInto(ch, slot, delta[ch.name], w);
897
- ch = chans[2];
898
- if (n > 2)
899
- this.foldInto(ch, slot, delta[ch.name], w);
900
- for (let i = 3; i < n; i++) {
901
- ch = chans[i];
902
- this.foldInto(ch, slot, delta[ch.name], w);
903
- }
904
- }
905
- /** Folds a motion sample's axes, kept apart from `foldInto` so neither reads two array kinds. */
906
- foldRun(ch, slot, xs, w) {
907
- const values = ch.values;
908
- const base = slot * ch.axes;
909
- for (let a = 0; a < ch.axes; a++)
910
- values[base + a] = foldNumber(ch.op, values[base + a], xs[a], w);
911
- }
912
- foldInto(ch, slot, value, w) {
913
- if (value === undefined)
914
- return;
915
- const values = ch.values;
916
- if (ch.axes === 1) {
917
- values[slot] = foldNumber(ch.op, values[slot], value, w);
387
+ const lane = this.byId.get(id);
388
+ const q = lane === undefined ? -1 : lane.positionOfMotion(s);
389
+ if (q >= 0)
390
+ lane.fix(q);
391
+ }
392
+ /** Something on a voice changed: a crowd copies it again at the next fill. */
393
+ voiceChanged(id) {
394
+ const c = this.crowdOf.get(id);
395
+ const p = c?.rowOf.get(id);
396
+ if (c === undefined || p === undefined)
918
397
  return;
919
- }
920
- const base = slot * ch.axes;
921
- // Indexed as `vec`'s fold indexes it, so a typed array folds as an array does.
922
- const arr = Array.isArray(value) || ArrayBuffer.isView(value) ? value : null;
923
- for (let a = 0; a < ch.axes; a++) {
924
- const v = arr === null ? ch.rest : (arr[a] ?? ch.rest);
925
- values[base + a] = foldNumber(ch.op, values[base + a], v, w);
926
- }
398
+ const f = p * c.stride + 0 /* Hot.FLAGS */;
399
+ c.hot[f] = c.hot[f] | 64 /* Flag.VOICE */;
927
400
  }
928
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 });