@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/fold.js ADDED
@@ -0,0 +1,475 @@
1
+ import { heldTime, passAt, phaseAt, silent } from './clock.js';
2
+ import { copy as copyValue } from './clone.js';
3
+ import { recordHost, remember } from './history.js';
4
+ import { stateful } from './hosts.js';
5
+ import { locusScratch } from './locus.js';
6
+ import { readKeys } from './patch.js';
7
+ import { reading } from './reading.js';
8
+ // V8 reads a local const as a constant, where it reads an import from its module on every call.
9
+ const copy = copyValue;
10
+ /** A record's band state, made when a rest-less channel first asks. */
11
+ function bandsFor(held, n) {
12
+ const bands = new Uint8Array(n);
13
+ held.bands = bands;
14
+ return bands;
15
+ }
16
+ export function near(a, b) {
17
+ if (typeof a === 'number' && typeof b === 'number')
18
+ return Math.abs(a - b) < 1e-9;
19
+ if (Array.isArray(a) && Array.isArray(b))
20
+ return a.length === b.length && a.every((v, i) => near(v, b[i]));
21
+ return a === b;
22
+ }
23
+ /**
24
+ * `merge(acc, b)`, where a result that is `b` itself, as `last()` gives, is copied into `acc` or a
25
+ * new array: `b` belongs to a patch or the mix's scratch, and a host may edit the pose. `acc` is a
26
+ * value this fold made.
27
+ */
28
+ function merged(channel, acc, b) {
29
+ const r = channel.merge(acc, b);
30
+ if (r !== b || !Array.isArray(r))
31
+ return r;
32
+ if (!Array.isArray(acc) || acc.length !== r.length)
33
+ return [...r];
34
+ for (let i = 0; i < r.length; i++)
35
+ acc[i] = r[i];
36
+ return acc;
37
+ }
38
+ /**
39
+ * One live voice's delta for one subject this frame, through its record for the subject, or null
40
+ * when it does not reach. Its weight is left in `w`.
41
+ */
42
+ export function influence(voice, subject, now, held) {
43
+ held.weight = 0;
44
+ if (!held.reaches)
45
+ return null;
46
+ if (voice.out?.rest && held.rested)
47
+ return null;
48
+ const raw = voice.elapsedAt(now) - held.delay;
49
+ if (raw < 0 && !voice.holdsBefore)
50
+ return null;
51
+ const elapsed = heldTime(raw, voice.holdsBefore, voice.holdsAfter, voice.span);
52
+ // A held subject's clock stands still at the edge it holds: -1 before, 1 after, 0 playing.
53
+ const still = raw < 0 ? -1 : voice.holdsAfter && raw > voice.span ? 1 : 0;
54
+ const duration = voice.duration;
55
+ const phase = phaseAt(elapsed, duration, voice.passes);
56
+ const pass = passAt(elapsed, duration, voice.passes);
57
+ this.prime(voice, held, now, elapsed, pass);
58
+ const setting = voice.setting;
59
+ // A signal that keeps state makes its voice stateful, as a patch that does: a lane would call
60
+ // it for subjects no probe asked about.
61
+ const keptAtWeigh = reading.kept;
62
+ const weight = this.weigh(voice, subject, now, held);
63
+ if (reading.kept !== keptAtWeigh && !voice.keeping)
64
+ stateful(this, voice);
65
+ setting.weight = weight;
66
+ held.weight = weight;
67
+ if (held.probed === now && held.delta && held.seeks === voice.seeks) {
68
+ if (voice.holder !== held && voice.scratch.length > 0)
69
+ this.keyed(voice, subject, held);
70
+ this.w = weight;
71
+ return held.delta;
72
+ }
73
+ const keptBefore = reading.kept;
74
+ const history = this.opts.history;
75
+ if (history?.inputs && voice.patch.reads !== undefined && !this.projecting)
76
+ recordHost(this, now);
77
+ reading.horizon = this.horizonFor(voice, held.delay, now);
78
+ const tick = this.opts.stepMs;
79
+ if (voice.patch.step && held.probed !== now && still >= 0) {
80
+ // Held after, it steps once more to where its last pass ended, and no further.
81
+ const to = still === 1 ? voice.timeAt(held.delay + voice.span) : now;
82
+ if (tick !== undefined && tick > 0 && !this.reducedNow)
83
+ this.tick(voice, subject, held, tick, to);
84
+ else if (still === 0 ? now !== held.stepped : to > held.stepped) {
85
+ if (still === 1)
86
+ setting.dt = this.capped(to - held.stepped);
87
+ voice.patch.step(held.state, setting.dt, subject, setting);
88
+ held.stepped = to;
89
+ }
90
+ }
91
+ let delta = null;
92
+ // Keys read even when silent: a `from: 'current'` voice takes its base on its first read.
93
+ if (voice.built) {
94
+ held.phase = phase;
95
+ delta = this.keyed(voice, subject, held);
96
+ }
97
+ else if (!silent(voice, weight)) {
98
+ delta = voice.patch.at(phase, subject, setting);
99
+ }
100
+ held.delta = delta;
101
+ held.probed = now;
102
+ held.seeks = voice.seeks;
103
+ if (history !== undefined)
104
+ remember(this, voice, held);
105
+ if (reading.kept !== keptBefore && !voice.keeping)
106
+ stateful(this, voice);
107
+ if (delta === null)
108
+ return null;
109
+ if (voice.out?.rest && this.isRest(delta)) {
110
+ held.weight = 0;
111
+ held.rested = true;
112
+ voice.restedCount++;
113
+ return null;
114
+ }
115
+ this.w = weight;
116
+ return delta;
117
+ }
118
+ /** Reads a keys voice's stops at `held.phase` into the subject's delta, through the voice's scratch. */
119
+ export function keyed(voice, subject, held) {
120
+ let base;
121
+ if (voice.spec.from === 'current') {
122
+ if (held.base === undefined) {
123
+ held.base = this.baseFor(voice, subject);
124
+ held.slope = this.slopeFor(voice, subject);
125
+ }
126
+ base = held.base;
127
+ }
128
+ const delta = readKeys(voice.built, held.phase, held.delta ?? {}, base, voice.lerps, held.slope, voice.intos, voice.scratch);
129
+ held.delta = delta;
130
+ if (voice.scratch.length > 0)
131
+ voice.holder = held;
132
+ return delta;
133
+ }
134
+ /**
135
+ * Runs `step` once per whole interval between where this subject last stopped and now, on a grid
136
+ * counted from when its delay ran out, and leaves the remainder for the next sample. Counting
137
+ * rather than adding keeps the grid where it is however many samples it is reached through.
138
+ */
139
+ export function tick(voice, subject, held, tick, to) {
140
+ const now = this.now;
141
+ const setting = voice.setting;
142
+ const frameDt = setting.dt;
143
+ let n = held.ticks;
144
+ // The epsilon keeps an interval like 1000 / 120 from landing a hair short of a whole count.
145
+ const due = Math.floor((to - held.since) / tick + 1e-9);
146
+ const cap = this.opts.maxDt;
147
+ if (cap !== undefined) {
148
+ const most = Math.floor(cap / tick);
149
+ if (due - n > most)
150
+ n = due - most;
151
+ }
152
+ const step = voice.patch.step;
153
+ setting.dt = tick;
154
+ for (n++; n <= due; n++) {
155
+ setting.timestamp = held.since + n * tick;
156
+ step(held.state, tick, subject, setting);
157
+ }
158
+ held.ticks = due > held.ticks ? due : held.ticks;
159
+ held.stepped = held.since + held.ticks * tick;
160
+ setting.timestamp = now;
161
+ setting.dt = frameDt;
162
+ }
163
+ /**
164
+ * Stop 0 for a retargeting voice: the subject's pose in the frame before this voice contributes.
165
+ * With no such frame on record — a voice cued before the subject was ever probed, or a host that
166
+ * samples into its own object — it is this frame's pose with every other voice folded in.
167
+ */
168
+ export function baseFor(voice, subject) {
169
+ let prior = this.pose.get(subject)?.pose;
170
+ if (prior === undefined && !this.folding.has(voice.id)) {
171
+ this.folding.add(voice.id);
172
+ try {
173
+ prior = this.fold(subject, undefined, false, voice.id);
174
+ }
175
+ finally {
176
+ this.folding.delete(voice.id);
177
+ }
178
+ }
179
+ const base = {};
180
+ for (const key of voice.patch.writes) {
181
+ const channel = this.kit[key];
182
+ const value = prior?.[key];
183
+ const fallback = channel.rest;
184
+ if (value !== undefined)
185
+ base[key] = copy(value);
186
+ else if (fallback !== undefined)
187
+ base[key] = copy(fallback);
188
+ }
189
+ return base;
190
+ }
191
+ /**
192
+ * How fast each numeric channel this voice writes was moving, from the subject's last two probed
193
+ * poses, in units per ms. Undefined with fewer than two on record, which starts the voice at rest.
194
+ */
195
+ export function slopeFor(voice, subject) {
196
+ const rec = this.pose.get(subject);
197
+ if (rec?.prev === undefined || !(rec.at > rec.prevAt))
198
+ return undefined;
199
+ const dt = rec.at - rec.prevAt;
200
+ const now = rec.pose;
201
+ const before = rec.prev;
202
+ const slope = {};
203
+ for (const key of voice.patch.writes) {
204
+ const a = before[key];
205
+ const b = now[key];
206
+ if (typeof a === 'number' && typeof b === 'number')
207
+ slope[key] = (b - a) / dt;
208
+ else if (Array.isArray(a) && Array.isArray(b) && a.length === b.length)
209
+ slope[key] = b.map((v, i) => (v - a[i]) / dt);
210
+ }
211
+ return slope;
212
+ }
213
+ export function isRest(delta) {
214
+ for (const key of Object.keys(delta)) {
215
+ if (delta[key] === undefined)
216
+ continue;
217
+ const channel = this.kit[key];
218
+ if (channel.rest === undefined)
219
+ return false;
220
+ if (!near(delta[key], channel.rest))
221
+ return false;
222
+ }
223
+ return true;
224
+ }
225
+ /** Whether a rest-less channel's influence is switched on, across a band rather than an edge. */
226
+ export function passes(was, w) {
227
+ const { on, off } = this.band;
228
+ return w >= on ? true : w <= off ? false : (was ?? w >= on);
229
+ }
230
+ /** Folds one voice's delta into the pose, its band state kept on the subject's record. */
231
+ export function apply(pose, voice, held, delta, weight) {
232
+ const slots = voice.slots;
233
+ for (let i = 0; i < slots.length; i++) {
234
+ const slot = slots[i];
235
+ const key = this.names[slot];
236
+ const value = delta[key];
237
+ if (value === undefined)
238
+ continue;
239
+ const channel = this.channels[slot];
240
+ if (channel.rest !== undefined && channel.scale) {
241
+ // pose[key] is the copy of rest this fold made, so it is ours to write into.
242
+ pose[key] = channel.fold
243
+ ? channel.fold(pose[key], value, weight)
244
+ : merged(channel, pose[key], channel.scale(value, weight));
245
+ continue;
246
+ }
247
+ const bands = held.bands ?? bandsFor(held, slots.length);
248
+ const band = bands[i];
249
+ const on = this.passes(band === 0 ? undefined : band === 1, weight);
250
+ bands[i] = on ? 1 : 2;
251
+ if (on)
252
+ pose[key] = pose[key] === undefined ? copy(value) : merged(channel, pose[key], value);
253
+ }
254
+ }
255
+ /** Clamps every bounded channel of a folded pose into its range, in place. */
256
+ export function clamp(pose) {
257
+ for (const slot of this.bounded) {
258
+ const [lo, hi] = this.channels[slot].bounds;
259
+ const key = this.names[slot];
260
+ const v = pose[key];
261
+ if (typeof v === 'number')
262
+ pose[key] = v < lo ? lo : v > hi ? hi : v;
263
+ else if (Array.isArray(v))
264
+ for (let i = 0; i < v.length; i++) {
265
+ const x = v[i];
266
+ v[i] = x < lo ? lo : x > hi ? hi : x;
267
+ }
268
+ }
269
+ return pose;
270
+ }
271
+ export function fold(subject, out, dry = false, except) {
272
+ const pose = this.folded(subject, out, dry, except);
273
+ return (this.bounded.length === 0 ? pose : this.clamp(pose));
274
+ }
275
+ export function folded(subject, out, dry = false, except) {
276
+ const head = this.linked(subject);
277
+ return this.foldWith(subject, out ?? {}, head, this.linkedLaned, dry, except);
278
+ }
279
+ /**
280
+ * A subject's chain, linked for this frame, and in `linkedLaned` whether its lanes answer for the
281
+ * voices on them; a subject numbered since the frame's fill folds every voice, laned ones included.
282
+ */
283
+ export function linked(subject) {
284
+ const now = this.now;
285
+ const head = Number.isNaN(now) ? null : this.chain(subject, now);
286
+ this.linkedLaned =
287
+ head !== null && this.lanes?.prepare(head.slot, subject, now, this.version, head) === true;
288
+ return head;
289
+ }
290
+ /** The fold after a subject's chain is linked and its lanes asked whether it reads from them. */
291
+ export function foldWith(subject, out, head, laned, dry, except) {
292
+ const now = this.now;
293
+ const pose = out;
294
+ const lanes = this.lanes;
295
+ const skip = laned ? lanes.copies : null;
296
+ for (let i = 0; i < this.names.length; i++) {
297
+ if (skip?.[i])
298
+ continue;
299
+ const rest = this.channels[i].rest;
300
+ const key = this.names[i];
301
+ // Never `delete`: it drops a reused out object into dictionary mode for good.
302
+ if (rest !== undefined)
303
+ pose[key] = copy(rest);
304
+ else if (pose[key] !== undefined)
305
+ pose[key] = undefined;
306
+ }
307
+ if (head === null)
308
+ return pose;
309
+ // A subject owing lanes it just met folds those voices here, after the lanes' values.
310
+ let owed = -1;
311
+ if (laned) {
312
+ const l = lanes;
313
+ l.copy(head.slot, pose);
314
+ if (l.owes(head.slot))
315
+ owed = head.slot;
316
+ else if (l.whole)
317
+ return pose;
318
+ }
319
+ if (this.loci === 0) {
320
+ for (let held = head; held !== null; held = held.next) {
321
+ const voice = held.voice;
322
+ if (voice === null || (laned && voice.laned && !this.owedBy(owed, voice)))
323
+ continue;
324
+ if (voice.id === except || voice.state === 'done')
325
+ continue;
326
+ const delta = this.read(voice, subject, now, dry, held);
327
+ if (owed >= 0 && voice.laned)
328
+ lanes.paid(voice.id, owed, held.weight);
329
+ if (delta === null || this.w <= 0)
330
+ continue;
331
+ this.apply(pose, voice, held, delta, this.w);
332
+ }
333
+ return pose;
334
+ }
335
+ return this.foldLoci(subject, pose, head, laned, dry, except, owed);
336
+ }
337
+ /** Whether the subject numbered `owed` reads laned `voice` from the general path this fill. */
338
+ export function owedBy(owed, voice) {
339
+ return owed >= 0 && this.lanes.owesVoice(owed, voice.id);
340
+ }
341
+ /** `foldWith`'s voices with a locus in play: each locus folds at its first member's place. */
342
+ export function foldLoci(subject, pose, head, laned, dry, except, owed) {
343
+ const now = this.now;
344
+ // A patch reading the pose can fold again from inside this one, so each depth has its own.
345
+ let k = this.locusScratch[this.locusDepth];
346
+ if (k === undefined) {
347
+ k = locusScratch(this.channels.length);
348
+ this.locusScratch[this.locusDepth] = k;
349
+ }
350
+ this.locusDepth++;
351
+ try {
352
+ k.n = 0;
353
+ k.m = 0;
354
+ k.names.length = 0;
355
+ for (let held = head; held !== null; held = held.next) {
356
+ const voice = held.voice;
357
+ if (voice === null || (laned && voice.laned && !this.owedBy(owed, voice)))
358
+ continue;
359
+ if (voice.id === except || voice.state === 'done')
360
+ continue;
361
+ const delta = this.read(voice, subject, now, dry, held);
362
+ if (owed >= 0 && voice.laned)
363
+ this.lanes.paid(voice.id, owed, held.weight);
364
+ if (delta === null)
365
+ continue;
366
+ const locus = voice.spec.locus;
367
+ let group = -1;
368
+ if (locus !== undefined) {
369
+ group = k.names.indexOf(locus);
370
+ const m = k.m++;
371
+ k.mVoices[m] = voice;
372
+ k.mDeltas[m] = delta;
373
+ k.mWeights[m] = this.w;
374
+ if (group >= 0) {
375
+ k.mGroups[m] = group;
376
+ continue;
377
+ }
378
+ group = k.names.length;
379
+ k.names.push(locus);
380
+ k.mGroups[m] = group;
381
+ }
382
+ const n = k.n++;
383
+ k.voices[n] = voice;
384
+ k.helds[n] = held;
385
+ k.deltas[n] = delta;
386
+ k.weights[n] = locus === undefined ? this.w : 0;
387
+ k.groups[n] = group;
388
+ }
389
+ let bands = head.loci;
390
+ if (bands === null) {
391
+ bands = new Map();
392
+ head.loci = bands;
393
+ }
394
+ for (let n = 0; n < k.n; n++) {
395
+ const group = k.groups[n];
396
+ if (group < 0) {
397
+ const weight = k.weights[n];
398
+ if (weight > 0)
399
+ this.apply(pose, k.voices[n], k.helds[n], k.deltas[n], weight);
400
+ continue;
401
+ }
402
+ const weight = this.foldLocus(k, group);
403
+ if (weight <= 0)
404
+ continue;
405
+ const name = k.names[group];
406
+ let on = bands.get(name);
407
+ if (on === undefined) {
408
+ on = new Uint8Array(this.channels.length);
409
+ bands.set(name, on);
410
+ }
411
+ for (let t = 0; t < k.touched.length; t++) {
412
+ const slot = k.touched[t];
413
+ const value = k.values[slot];
414
+ k.values[slot] = undefined;
415
+ const key = this.names[slot];
416
+ const channel = this.channels[slot];
417
+ if (channel.rest !== undefined && channel.scale) {
418
+ // pose[key] is the copy of rest this fold made, so it is ours to write into.
419
+ pose[key] = channel.fold
420
+ ? channel.fold(pose[key], value, weight)
421
+ : merged(channel, pose[key], channel.scale(value, weight));
422
+ continue;
423
+ }
424
+ const band = on[slot];
425
+ const passes = this.passes(band === 0 ? undefined : band === 1, weight);
426
+ on[slot] = passes ? 1 : 2;
427
+ if (passes)
428
+ pose[key] = pose[key] === undefined ? copy(value) : merged(channel, pose[key], value);
429
+ }
430
+ }
431
+ return pose;
432
+ }
433
+ finally {
434
+ for (let n = 0; n < k.n; n++) {
435
+ k.voices[n] = null;
436
+ k.helds[n] = null;
437
+ k.deltas[n] = null;
438
+ }
439
+ for (let m = 0; m < k.m; m++) {
440
+ k.mVoices[m] = null;
441
+ k.mDeltas[m] = null;
442
+ }
443
+ // Left set only where a channel's lerp threw partway, which the next fold must not inherit.
444
+ for (let t = 0; t < k.touched.length; t++) {
445
+ const slot = k.touched[t];
446
+ k.met[slot] = 0;
447
+ k.values[slot] = undefined;
448
+ }
449
+ k.touched.length = 0;
450
+ this.locusDepth--;
451
+ }
452
+ }
453
+ /** `influence`, or for a dry fold a subject already probed this frame, without advancing it. */
454
+ export function read(voice, subject, now, dry, held) {
455
+ if (dry) {
456
+ if (held.reaches && held.probed === now && held.delta && held.seeks === voice.seeks) {
457
+ const setting = voice.setting;
458
+ // The setting the probe that read the delta had, but for `dt`: a dry read advances nothing.
459
+ const elapsed = heldTime(voice.elapsedAt(now) - held.delay, voice.holdsBefore, voice.holdsAfter, voice.span);
460
+ setting.timestamp = now;
461
+ setting.dt = 0;
462
+ setting.elapsed = elapsed;
463
+ setting.pass = passAt(elapsed, voice.duration, voice.passes);
464
+ setting.weight = 0;
465
+ setting.state = held.state;
466
+ voice.keepOn = held;
467
+ this.w = this.weigh(voice, subject, now, held);
468
+ held.weight = this.w;
469
+ if (voice.holder !== held && voice.scratch.length > 0)
470
+ this.keyed(voice, subject, held);
471
+ return held.delta;
472
+ }
473
+ }
474
+ return this.influence(voice, subject, now, held);
475
+ }
@@ -0,0 +1,24 @@
1
+ import { type Lane, type Laned, type Locus } from './lane.js';
2
+ import type { Lanes } from './lanes.js';
3
+ /**
4
+ * Every member of a locus, in voice order, gathered rather than folded: a member's delta for a
5
+ * subject is lerped into what the members before it left there by its share of the weight
6
+ * taken, as the general path's `foldLocus` does, so each subject's locus can fold in at its
7
+ * first member's place in the order.
8
+ */
9
+ export declare function gatherLocus<I, O>(lanes: Lanes<I, O>, g: Locus<I, O>): void;
10
+ /** A locus member's turn in the order: each subject it was the first member of takes the locus. */
11
+ export declare function foldLocus<I, O>(lanes: Lanes<I, O>, lane: Lane<I, O>, g: Locus<I, O>): void;
12
+ /** For a keys voice: the delta's value for each channel it writes, read once per phase. */
13
+ export declare function gather<I, O>(this: Lanes<I, O>, lane: Lane<I, O>, delta: Record<string, unknown>): void;
14
+ /** Folds what `gather` read into a subject's laned values. */
15
+ export declare function fold<I, O>(this: Lanes<I, O>, lane: Lane<I, O>, slot: number, w: number): void;
16
+ /**
17
+ * Folds a delta a patch returned into a subject's laned values. The first few channels are read
18
+ * at a site of their own, which in most mixes sees one channel name and stays fast, where one
19
+ * site reading every name in turn slows every read.
20
+ */
21
+ export declare function foldDelta<I, O>(this: Lanes<I, O>, chans: readonly Laned[], slot: number, delta: Record<string, unknown>, w: number): void;
22
+ /** Folds a motion sample's axes, kept apart from `foldInto` so neither reads two array kinds. */
23
+ export declare function foldRun<I, O>(this: Lanes<I, O>, ch: Laned, slot: number, xs: Float64Array, w: number): void;
24
+ export declare function foldInto<I, O>(this: Lanes<I, O>, ch: Laned, slot: number, value: unknown, w: number): void;
package/dist/gather.js ADDED
@@ -0,0 +1,186 @@
1
+ import { foldNumber, lerpNumber } from './channels.js';
2
+ /** `xs`, or a copy at least `n` long where it is shorter. */
3
+ function sized(xs, n) {
4
+ if (xs.length >= n)
5
+ return xs;
6
+ const grown = new Float64Array(n);
7
+ grown.set(xs);
8
+ return grown;
9
+ }
10
+ /**
11
+ * Every member of a locus, in voice order, gathered rather than folded: a member's delta for a
12
+ * subject is lerped into what the members before it left there by its share of the weight
13
+ * taken, as the general path's `foldLocus` does, so each subject's locus can fold in at its
14
+ * first member's place in the order.
15
+ */
16
+ export function gatherLocus(lanes, g) {
17
+ const cap = lanes.cap;
18
+ if (g.met.length < cap) {
19
+ g.met = sized(g.met, cap);
20
+ g.first = sized(g.first, cap);
21
+ g.sum = sized(g.sum, cap);
22
+ }
23
+ for (let k = 0; k < g.values.length; k++) {
24
+ const values = g.values[k];
25
+ const taken = g.taken[k];
26
+ const axes = lanes.laned[k].axes;
27
+ if (values !== undefined)
28
+ g.values[k] = sized(values, cap * axes);
29
+ if (taken !== undefined)
30
+ g.taken[k] = sized(taken, cap);
31
+ }
32
+ lanes.into = g;
33
+ try {
34
+ for (const lane of g.members) {
35
+ const voice = lane.voice;
36
+ if (lane.idle || voice.state === 'pending' || voice.state === 'done')
37
+ continue;
38
+ lanes.intoId = voice.id;
39
+ lanes.reset(lane);
40
+ const elapsed = voice.elapsedAt(lanes.now);
41
+ const duration = voice.duration;
42
+ const passes = voice.passes;
43
+ const list = lane.list;
44
+ for (let p = 0; p < list.length; p++) {
45
+ const slot = list[p];
46
+ if (lanes.one(lane, p, slot, elapsed, duration, passes)) {
47
+ meetLocus(lanes, g, slot);
48
+ g.sum[slot] =
49
+ g.sum[slot] + lane.data[p * 8 /* Row.STRIDE */ + 2 /* Row.WEIGHT */];
50
+ }
51
+ if (voice.keeping)
52
+ break;
53
+ }
54
+ }
55
+ }
56
+ finally {
57
+ lanes.into = null;
58
+ }
59
+ }
60
+ /** The first a fill hears of a subject in a locus: from the member now gathering. */
61
+ function meetLocus(lanes, g, slot) {
62
+ if (g.met[slot] === lanes.fills)
63
+ return;
64
+ g.met[slot] = lanes.fills;
65
+ g.first[slot] = lanes.intoId;
66
+ g.sum[slot] = 0;
67
+ for (const taken of g.taken)
68
+ if (taken !== undefined)
69
+ taken[slot] = 0;
70
+ }
71
+ /** A member's value for one channel of a subject, lerped into its locus: `foldLocus`'s step. */
72
+ function gatherInto(lanes, g, ch, slot, value, w) {
73
+ meetLocus(lanes, g, slot);
74
+ const k = ch.index;
75
+ let values = g.values[k];
76
+ let takenAt = g.taken[k];
77
+ if (values === undefined || takenAt === undefined) {
78
+ values = new Float64Array(lanes.cap * ch.axes);
79
+ takenAt = new Float64Array(lanes.cap);
80
+ g.values[k] = values;
81
+ g.taken[k] = takenAt;
82
+ }
83
+ const taken = takenAt[slot];
84
+ const total = taken + w;
85
+ const u = w / total;
86
+ if (ch.scalar) {
87
+ values[slot] =
88
+ taken === 0 ? value : lerpNumber(values[slot], value, u);
89
+ }
90
+ else {
91
+ // Read past its end as the rest, as `vec`'s lerp and fold both read an array.
92
+ const arr = Array.isArray(value) || ArrayBuffer.isView(value) ? value : null;
93
+ const base = slot * ch.axes;
94
+ for (let a = 0; a < ch.axes; a++) {
95
+ const v = arr === null ? ch.rest : (arr[a] ?? ch.rest);
96
+ values[base + a] = taken === 0 ? v : lerpNumber(values[base + a], v, u);
97
+ }
98
+ }
99
+ takenAt[slot] = total;
100
+ }
101
+ /** A locus member's turn in the order: each subject it was the first member of takes the locus. */
102
+ export function foldLocus(lanes, lane, g) {
103
+ const id = lane.voice.id;
104
+ const fills = lanes.fills;
105
+ const list = lane.list;
106
+ for (let p = 0; p < list.length; p++) {
107
+ const slot = list[p];
108
+ if (g.met[slot] !== fills || g.first[slot] !== id)
109
+ continue;
110
+ const sum = g.sum[slot];
111
+ if (sum <= 0)
112
+ continue;
113
+ const w = sum > 1 ? 1 : sum;
114
+ for (let k = 0; k < g.values.length; k++) {
115
+ const takenAt = g.taken[k];
116
+ if (takenAt === undefined || !(takenAt[slot] > 0))
117
+ continue;
118
+ const ch = lanes.laned[k];
119
+ const values = g.values[k];
120
+ const into = ch.values;
121
+ const base = slot * ch.axes;
122
+ for (let a = 0; a < ch.axes; a++)
123
+ into[base + a] = foldNumber(ch.op, into[base + a], values[base + a], w);
124
+ }
125
+ }
126
+ }
127
+ /** For a keys voice: the delta's value for each channel it writes, read once per phase. */
128
+ export function gather(lane, delta) {
129
+ const chans = lane.chans;
130
+ for (let i = 0; i < chans.length; i++)
131
+ lane.values[i] = delta[chans[i].name];
132
+ }
133
+ /** Folds what `gather` read into a subject's laned values. */
134
+ export function fold(lane, slot, w) {
135
+ const chans = lane.chans;
136
+ for (let i = 0; i < chans.length; i++)
137
+ this.foldInto(chans[i], slot, lane.values[i], w);
138
+ }
139
+ /**
140
+ * Folds a delta a patch returned into a subject's laned values. The first few channels are read
141
+ * at a site of their own, which in most mixes sees one channel name and stays fast, where one
142
+ * site reading every name in turn slows every read.
143
+ */
144
+ export function foldDelta(chans, slot, delta, w) {
145
+ const n = chans.length;
146
+ let ch = chans[0];
147
+ if (n > 0)
148
+ this.foldInto(ch, slot, delta[ch.name], w);
149
+ ch = chans[1];
150
+ if (n > 1)
151
+ this.foldInto(ch, slot, delta[ch.name], w);
152
+ ch = chans[2];
153
+ if (n > 2)
154
+ this.foldInto(ch, slot, delta[ch.name], w);
155
+ for (let i = 3; i < n; i++) {
156
+ ch = chans[i];
157
+ this.foldInto(ch, slot, delta[ch.name], w);
158
+ }
159
+ }
160
+ /** Folds a motion sample's axes, kept apart from `foldInto` so neither reads two array kinds. */
161
+ export function foldRun(ch, slot, xs, w) {
162
+ const values = ch.values;
163
+ const base = slot * ch.axes;
164
+ for (let a = 0; a < ch.axes; a++)
165
+ values[base + a] = foldNumber(ch.op, values[base + a], xs[a], w);
166
+ }
167
+ export function foldInto(ch, slot, value, w) {
168
+ if (value === undefined)
169
+ return;
170
+ if (this.into !== null) {
171
+ gatherInto(this, this.into, ch, slot, value, w);
172
+ return;
173
+ }
174
+ const values = ch.values;
175
+ if (ch.scalar) {
176
+ values[slot] = foldNumber(ch.op, values[slot], value, w);
177
+ return;
178
+ }
179
+ const base = slot * ch.axes;
180
+ // Indexed as `vec`'s fold indexes it, so a typed array folds as an array does.
181
+ const arr = Array.isArray(value) || ArrayBuffer.isView(value) ? value : null;
182
+ for (let a = 0; a < ch.axes; a++) {
183
+ const v = arr === null ? ch.rest : (arr[a] ?? ch.rest);
184
+ values[base + a] = foldNumber(ch.op, values[base + a], v, w);
185
+ }
186
+ }