@msb235/blits 0.7.0 → 0.8.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 (156) hide show
  1. package/CHANGELOG.md +196 -0
  2. package/README.md +22 -4
  3. package/dist/book.js +4 -4
  4. package/dist/chain.d.ts +1 -1
  5. package/dist/chain.js +57 -54
  6. package/dist/channels.d.ts +26 -5
  7. package/dist/channels.js +12 -8
  8. package/dist/clock.d.ts +8 -1
  9. package/dist/clock.js +14 -3
  10. package/dist/clone.d.ts +3 -0
  11. package/dist/clone.js +16 -1
  12. package/dist/closed.d.ts +1 -1
  13. package/dist/closed.js +3 -2
  14. package/dist/color.d.ts +0 -11
  15. package/dist/color.js +4 -24
  16. package/dist/columns.js +0 -1
  17. package/dist/copyout.d.ts +6 -0
  18. package/dist/copyout.js +38 -0
  19. package/dist/crowd.d.ts +7 -5
  20. package/dist/crowd.js +32 -18
  21. package/dist/cue.d.ts +3 -1
  22. package/dist/cue.js +33 -20
  23. package/dist/departed.d.ts +34 -0
  24. package/dist/departed.js +73 -0
  25. package/dist/easing.d.ts +5 -0
  26. package/dist/easing.js +31 -2
  27. package/dist/everyone.d.ts +56 -0
  28. package/dist/everyone.js +221 -0
  29. package/dist/fade.d.ts +8 -1
  30. package/dist/fade.js +36 -6
  31. package/dist/fill.d.ts +22 -13
  32. package/dist/fill.js +104 -41
  33. package/dist/fit.js +2 -1
  34. package/dist/fold.d.ts +37 -0
  35. package/dist/fold.js +213 -28
  36. package/dist/gather.d.ts +15 -9
  37. package/dist/gather.js +60 -25
  38. package/dist/gone.d.ts +32 -0
  39. package/dist/gone.js +144 -0
  40. package/dist/handle.d.ts +16 -5
  41. package/dist/handle.js +56 -6
  42. package/dist/held.d.ts +8 -5
  43. package/dist/held.js +56 -58
  44. package/dist/history.d.ts +17 -13
  45. package/dist/history.js +49 -28
  46. package/dist/hosts.js +57 -9
  47. package/dist/index.d.ts +9 -4
  48. package/dist/index.js +5 -2
  49. package/dist/keyfill.d.ts +7 -0
  50. package/dist/keyfill.js +124 -0
  51. package/dist/keyrows.d.ts +1 -0
  52. package/dist/keyrows.js +1 -1
  53. package/dist/lane.d.ts +32 -2
  54. package/dist/lane.js +5 -0
  55. package/dist/lanes.d.ts +32 -8
  56. package/dist/lanes.js +69 -37
  57. package/dist/leavings.d.ts +26 -0
  58. package/dist/leavings.js +70 -0
  59. package/dist/meet.js +14 -8
  60. package/dist/mixer.d.ts +43 -7
  61. package/dist/mixer.js +60 -11
  62. package/dist/motion.d.ts +2 -0
  63. package/dist/motion.js +4 -1
  64. package/dist/motions.d.ts +22 -15
  65. package/dist/motions.js +40 -34
  66. package/dist/move.js +31 -20
  67. package/dist/moved.d.ts +11 -0
  68. package/dist/moved.js +29 -0
  69. package/dist/origin.d.ts +19 -0
  70. package/dist/origin.js +68 -0
  71. package/dist/owed.d.ts +4 -4
  72. package/dist/owed.js +8 -8
  73. package/dist/owner.d.ts +1 -1
  74. package/dist/owner.js +2 -2
  75. package/dist/pace.d.ts +13 -5
  76. package/dist/pace.js +21 -8
  77. package/dist/pack.d.ts +3 -1
  78. package/dist/pack.js +15 -24
  79. package/dist/paging.d.ts +8 -2
  80. package/dist/paging.js +44 -4
  81. package/dist/patch.d.ts +14 -9
  82. package/dist/patch.js +83 -21
  83. package/dist/place.js +188 -30
  84. package/dist/project.d.ts +12 -1
  85. package/dist/project.js +101 -83
  86. package/dist/pull.d.ts +2 -0
  87. package/dist/pull.js +8 -0
  88. package/dist/qualify.js +10 -2
  89. package/dist/reading.d.ts +5 -0
  90. package/dist/reading.js +5 -0
  91. package/dist/record.d.ts +9 -0
  92. package/dist/record.js +42 -0
  93. package/dist/relink.d.ts +11 -5
  94. package/dist/relink.js +7 -5
  95. package/dist/restored.d.ts +41 -0
  96. package/dist/restored.js +91 -0
  97. package/dist/revive.d.ts +10 -3
  98. package/dist/revive.js +31 -23
  99. package/dist/roster.d.ts +11 -0
  100. package/dist/roster.js +46 -0
  101. package/dist/rotation.d.ts +29 -0
  102. package/dist/rotation.js +91 -0
  103. package/dist/rows.d.ts +5 -5
  104. package/dist/rows.js +5 -5
  105. package/dist/sample.d.ts +3 -2
  106. package/dist/sample.js +32 -15
  107. package/dist/scored.d.ts +57 -0
  108. package/dist/scored.js +196 -0
  109. package/dist/seek.js +97 -114
  110. package/dist/shared.d.ts +6 -0
  111. package/dist/shared.js +15 -0
  112. package/dist/signals.d.ts +24 -3
  113. package/dist/signals.js +50 -11
  114. package/dist/spans.js +7 -0
  115. package/dist/stack.d.ts +11 -0
  116. package/dist/stack.js +47 -0
  117. package/dist/standing.d.ts +10 -0
  118. package/dist/standing.js +79 -0
  119. package/dist/store.d.ts +3 -3
  120. package/dist/store.js +3 -3
  121. package/dist/strays.js +3 -0
  122. package/dist/tape.d.ts +8 -5
  123. package/dist/tape.js +21 -13
  124. package/dist/testing.d.ts +35 -0
  125. package/dist/testing.js +116 -0
  126. package/dist/touch.d.ts +6 -0
  127. package/dist/touch.js +28 -0
  128. package/dist/transport.d.ts +53 -6
  129. package/dist/transport.js +102 -20
  130. package/dist/turns.d.ts +18 -0
  131. package/dist/turns.js +72 -0
  132. package/dist/tweened.js +1 -1
  133. package/dist/types/channel.d.ts +46 -0
  134. package/dist/types/channel.js +1 -0
  135. package/dist/types/history.d.ts +139 -0
  136. package/dist/types/history.js +1 -0
  137. package/dist/types/mix.d.ts +336 -0
  138. package/dist/types/mix.js +1 -0
  139. package/dist/types/patch.d.ts +187 -0
  140. package/dist/types/patch.js +1 -0
  141. package/dist/types/place.d.ts +157 -0
  142. package/dist/types/place.js +1 -0
  143. package/dist/types/transport.d.ts +71 -0
  144. package/dist/types/transport.js +1 -0
  145. package/dist/types/voice.d.ts +323 -0
  146. package/dist/types/voice.js +1 -0
  147. package/dist/types.d.ts +7 -1162
  148. package/dist/unpage.d.ts +2 -2
  149. package/dist/unpage.js +55 -25
  150. package/dist/voice.d.ts +92 -22
  151. package/dist/voice.js +108 -43
  152. package/dist/wave.d.ts +42 -0
  153. package/dist/wave.js +62 -0
  154. package/dist/weigh.js +1 -1
  155. package/docs/schema.html +42 -36
  156. package/package.json +8 -3
package/dist/fold.js CHANGED
@@ -1,12 +1,16 @@
1
1
  import { frozenTime, passAt, phaseAt, silent } from './clock.js';
2
2
  import { copy as copyValue } from './clone.js';
3
+ import { open as openRecord, shut as shutRecord, weighAt } from './everyone.js';
3
4
  import { recordHost, remember } from './history.js';
4
5
  import { stateful } from './hosts.js';
5
6
  import { locusScratch } from './locus.js';
7
+ import { moved } from './moved.js';
6
8
  import { readKeys } from './patch.js';
7
9
  import { reading } from './reading.js';
8
10
  // V8 reads a local const as a constant, where it reads an import from its module on every call.
9
11
  const copy = copyValue;
12
+ const open = openRecord;
13
+ const shut = shutRecord;
10
14
  /** A record's band state, made when a rest-less channel first asks. */
11
15
  function bandsFor(held, n) {
12
16
  const bands = new Uint8Array(n);
@@ -25,7 +29,7 @@ export function near(a, b) {
25
29
  * new array: `b` belongs to a patch or the mix's scratch, and a host may edit the pose. `acc` is a
26
30
  * value this fold made.
27
31
  */
28
- function merged(channel, acc, b) {
32
+ export function merged(channel, acc, b) {
29
33
  const r = channel.merge(acc, b);
30
34
  if (r !== b || !Array.isArray(r))
31
35
  return r;
@@ -35,6 +39,24 @@ function merged(channel, acc, b) {
35
39
  acc[i] = r[i];
36
40
  return acc;
37
41
  }
42
+ /**
43
+ * A record after a seek that rebuilds: fresh state, stepped again from where the voice's clock now
44
+ * puts this subject's start, which under `stepMs` lands where a voice cued fresh would.
45
+ */
46
+ function rebuild(mix, voice, subject, now, held) {
47
+ held.rebuilt = voice.rebuilds;
48
+ const patch = voice.patch;
49
+ if (patch.state === undefined && patch.step === undefined && held.kept === null)
50
+ return;
51
+ held.state = patch.state ? patch.state(subject) : undefined;
52
+ held.kept = null;
53
+ held.unkept = undefined;
54
+ held.since = mix.sinceOf(voice, held.delay);
55
+ held.shown = mix.shownOf(voice, held.since);
56
+ held.ticks = 0;
57
+ held.stepped = held.since < now ? held.since : now;
58
+ held.probed = Number.NaN;
59
+ }
38
60
  /**
39
61
  * One live voice's delta for one subject this frame, through its record for the subject, or null
40
62
  * when it does not reach. Its weight is left in `w`.
@@ -45,12 +67,16 @@ export function contribution(voice, subject, now, held) {
45
67
  return null;
46
68
  if (voice.out?.rest && held.rested)
47
69
  return null;
70
+ if (held.rebuilt !== voice.rebuilds)
71
+ rebuild(this, voice, subject, now, held);
48
72
  const raw = voice.elapsedAt(now) - held.delay;
49
- if (raw < 0 && !voice.freezesBefore)
73
+ // Not `raw < 0`: see `frozenTime` for the clock that reads NaN before its start.
74
+ const early = !(raw >= 0);
75
+ if (early && !voice.freezesBefore)
50
76
  return null;
51
77
  const elapsed = frozenTime(raw, voice.freezesBefore, voice.freezesAfter, voice.span);
52
78
  // A frozen subject's clock stands still at the edge it freezes at: -1 before, 1 after, 0 playing.
53
- const still = raw < 0 ? -1 : voice.freezesAfter && raw > voice.span ? 1 : 0;
79
+ const still = early ? -1 : voice.freezesAfter && raw > voice.span ? 1 : 0;
54
80
  const duration = voice.duration;
55
81
  const phase = phaseAt(elapsed, duration, voice.passes);
56
82
  const pass = passAt(elapsed, duration, voice.passes);
@@ -79,6 +105,10 @@ export function contribution(voice, subject, now, held) {
79
105
  if (voice.patch.step && held.probed !== now && still >= 0) {
80
106
  // Frozen after, it steps once more to where its last pass ended, and no further.
81
107
  const to = still === 1 ? voice.timeAt(held.delay + voice.span) : now;
108
+ if (held.stepped < held.since) {
109
+ held.stepped = held.since;
110
+ setting.dt = this.capped(now - held.since);
111
+ }
82
112
  if (tick !== undefined && tick > 0 && !this.reducedNow)
83
113
  this.tick(voice, subject, held, tick, to);
84
114
  else if (still === 0 ? now !== held.stepped : to > held.stepped) {
@@ -102,6 +132,9 @@ export function contribution(voice, subject, now, held) {
102
132
  held.phase = phase;
103
133
  delta = this.keyed(voice, subject, held);
104
134
  }
135
+ else if (voice.motion !== undefined) {
136
+ delta = moved(voice.motion, voice.patch.writes[0], subject, setting, held);
137
+ }
105
138
  else {
106
139
  delta = voice.patch.at(phase, subject, setting);
107
140
  }
@@ -230,6 +263,14 @@ export function isRest(delta) {
230
263
  }
231
264
  return true;
232
265
  }
266
+ /**
267
+ * A record's band state once its voice is skipped at weight 0: off, as `passes` would have said, so
268
+ * a weight that comes back inside the band does not find it still on.
269
+ */
270
+ export function unband(held) {
271
+ if (held?.bands)
272
+ held.bands.fill(2);
273
+ }
233
274
  /** Whether a rest-less channel's contribution is switched on, across a band rather than an edge. */
234
275
  export function passes(was, w) {
235
276
  const { on, off } = this.band;
@@ -263,19 +304,56 @@ export function apply(pose, voice, held, delta, weight) {
263
304
  /** Clamps every bounded channel of a folded pose into its range, in place. */
264
305
  export function clamp(pose) {
265
306
  for (const slot of this.bounded) {
266
- const [lo, hi] = this.channels[slot].bounds;
267
307
  const key = this.names[slot];
268
- const v = pose[key];
269
- if (typeof v === 'number')
270
- pose[key] = v < lo ? lo : v > hi ? hi : v;
271
- else if (Array.isArray(v))
272
- for (let i = 0; i < v.length; i++) {
273
- const x = v[i];
274
- v[i] = x < lo ? lo : x > hi ? hi : x;
275
- }
308
+ pose[key] = bound(this.channels[slot], pose[key]);
276
309
  }
277
310
  return pose;
278
311
  }
312
+ /** A folded value held to its channel's bounds: a number clamped, an array clamped in place. */
313
+ export function bound(channel, v) {
314
+ const [lo, hi] = channel.bounds;
315
+ if (typeof v === 'number')
316
+ return v < lo ? lo : v > hi ? hi : v;
317
+ if (Array.isArray(v))
318
+ for (let i = 0; i < v.length; i++) {
319
+ const x = v[i];
320
+ v[i] = x < lo ? lo : x > hi ? hi : x;
321
+ }
322
+ return v;
323
+ }
324
+ /**
325
+ * Whether a fold's next voice is its chain's next record, `held`, and not the next voice sharing
326
+ * one record, `sharers[q]`: the two lists are each in voice order, and a fold takes them as one.
327
+ */
328
+ export function chainFirst(held, sharers, q) {
329
+ if (held === null)
330
+ return false;
331
+ if (q >= sharers.length || held.voice === null)
332
+ return true;
333
+ const next = sharers[q];
334
+ return next.id > held.voice.id;
335
+ }
336
+ /**
337
+ * After a fold read a sharing voice for the subject numbered `slot`: keeps the weight it gave and
338
+ * what the read left on its record, and gives the fold's place in `sharers`, which moves back where
339
+ * the read made the voice share no more.
340
+ */
341
+ export function shared(voice, slot, rec, q) {
342
+ if (voice.sharing) {
343
+ // A projection numbers no subject, and keeps nothing by subject.
344
+ if (slot < 0)
345
+ return q;
346
+ const cap = this.lanes.cap;
347
+ weighAt(voice, slot, rec.weight, cap);
348
+ shut(voice, rec, slot, cap);
349
+ return q;
350
+ }
351
+ const sharers = this.sharers;
352
+ let i = 0;
353
+ while (i < sharers.length && sharers[i].id < voice.id)
354
+ i++;
355
+ return i;
356
+ }
279
357
  export function fold(subject, out, dry = false, except) {
280
358
  const pose = this.folded(subject, out, dry, except);
281
359
  return (this.bounded.length === 0 ? pose : this.clamp(pose));
@@ -296,20 +374,29 @@ export function linked(subject) {
296
374
  }
297
375
  /** The fold after a subject's chain is linked and its lanes asked whether it reads from them. */
298
376
  export function foldWith(subject, out, head, laned, dry, except) {
299
- const now = this.now;
300
377
  const pose = out;
301
378
  const lanes = this.lanes;
302
379
  const skip = laned ? lanes.copies : null;
303
380
  for (let i = 0; i < this.names.length; i++) {
304
381
  if (skip?.[i])
305
382
  continue;
306
- const rest = this.channels[i].rest;
383
+ const channel = this.channels[i];
384
+ const rest = channel.rest;
307
385
  const key = this.names[i];
308
386
  // Never `delete`: it drops a reused out object into dictionary mode for good.
309
- if (rest !== undefined)
310
- pose[key] = copy(rest);
311
- else if (pose[key] !== undefined)
312
- pose[key] = undefined;
387
+ if (rest === undefined) {
388
+ if (pose[key] !== undefined)
389
+ pose[key] = undefined;
390
+ continue;
391
+ }
392
+ // A stock array channel never keeps the pose's array, so one a last fold left in `out` is ours.
393
+ const held = this.lerpsInto[i] === undefined ? undefined : pose[key];
394
+ if (Array.isArray(held) && held !== rest && held.length === rest.length) {
395
+ for (let a = 0; a < held.length; a++)
396
+ held[a] = rest[a];
397
+ }
398
+ else
399
+ pose[key] = channel.copy ? channel.copy(rest) : copy(rest);
313
400
  }
314
401
  if (head === null)
315
402
  return pose;
@@ -323,7 +410,8 @@ export function foldWith(subject, out, head, laned, dry, except) {
323
410
  else if (l.whole)
324
411
  return pose;
325
412
  }
326
- if (this.loci === 0) {
413
+ if (this.detours === 0) {
414
+ const now = this.now;
327
415
  for (let held = head; held !== null; held = held.next) {
328
416
  const voice = held.voice;
329
417
  if (voice === null || (laned && voice.laned && !this.owedBy(owed, voice)))
@@ -333,13 +421,82 @@ export function foldWith(subject, out, head, laned, dry, except) {
333
421
  const delta = this.read(voice, subject, now, dry, held);
334
422
  if (owed >= 0 && voice.laned)
335
423
  lanes.paid(voice.id, owed, held.weight);
424
+ if (!(held.weight > 0))
425
+ unband(held);
336
426
  if (delta === null || this.w <= 0)
337
427
  continue;
338
428
  this.apply(pose, voice, held, delta, this.w);
339
429
  }
340
430
  return pose;
341
431
  }
342
- return this.foldLoci(subject, pose, head, laned, dry, except, owed);
432
+ return this.foldDetour(subject, pose, head, laned, dry, except, owed);
433
+ }
434
+ /**
435
+ * `foldWith`'s voices in a mix with a locus or a voice sharing one record in it. The chain is taken
436
+ * again where a voice stopped sharing since it was linked, as a patch call in this probe's fill can
437
+ * make one do. With no locus, the chain's records fold in voice order, and each voice sharing one
438
+ * record, which is in no chain, before the first chain record that comes after it.
439
+ */
440
+ export function foldDetour(subject, pose, linkedHead, laned, dry, except, owed) {
441
+ const head = linkedHead.version === this.version ? linkedHead : this.chain(subject);
442
+ if (this.loci !== 0)
443
+ return this.foldLoci(subject, pose, head, laned, dry, except, owed);
444
+ const now = this.now;
445
+ const lanes = this.lanes;
446
+ const sharers = this.sharers;
447
+ let q = 0;
448
+ for (let held = head; held !== null; held = held.next) {
449
+ const voice = held.voice;
450
+ if (voice === null)
451
+ continue;
452
+ if (q < sharers.length && sharers[q].id < voice.id)
453
+ q = this.foldSharers(pose, subject, head, q, voice.id, laned, dry, except, owed);
454
+ if (laned && voice.laned && !this.owedBy(owed, voice))
455
+ continue;
456
+ if (voice.id === except || voice.state === 'done')
457
+ continue;
458
+ const delta = this.read(voice, subject, now, dry, held);
459
+ if (owed >= 0 && voice.laned)
460
+ lanes.paid(voice.id, owed, held.weight);
461
+ if (!(held.weight > 0))
462
+ unband(held);
463
+ if (delta === null || this.w <= 0)
464
+ continue;
465
+ this.apply(pose, voice, held, delta, this.w);
466
+ }
467
+ if (q < sharers.length)
468
+ this.foldSharers(pose, subject, head, q, Number.MAX_SAFE_INTEGER, laned, dry, except, owed);
469
+ return pose;
470
+ }
471
+ /**
472
+ * Folds the voices sharing one record from `sharers[q]` up to the first whose id is `before` or
473
+ * more, as `foldWith` folds a chain's records, and gives the place it stopped at.
474
+ */
475
+ export function foldSharers(pose, subject, head, q, before, laned, dry, except, owed) {
476
+ const sharers = this.sharers;
477
+ const now = this.now;
478
+ while (q < sharers.length) {
479
+ const voice = sharers[q];
480
+ if (voice.id >= before)
481
+ break;
482
+ q++;
483
+ const rec = voice.state === 'pending' ? null : voice.everyone;
484
+ if (rec === null)
485
+ continue;
486
+ if (laned && voice.laned && !this.owedBy(owed, voice))
487
+ continue;
488
+ if (voice.id === except || voice.state === 'done')
489
+ continue;
490
+ open(voice, rec, head.slot);
491
+ const delta = this.read(voice, subject, now, dry, rec);
492
+ if (owed >= 0 && voice.laned)
493
+ this.lanes.paid(voice.id, owed, rec.weight);
494
+ q = this.shared(voice, head.slot, rec, q);
495
+ if (delta === null || this.w <= 0)
496
+ continue;
497
+ this.apply(pose, voice, rec, delta, this.w);
498
+ }
499
+ return q;
343
500
  }
344
501
  /** Whether the subject numbered `owed` reads laned `voice` from the general path this fill. */
345
502
  export function owedBy(owed, voice) {
@@ -359,15 +516,36 @@ export function foldLoci(subject, pose, head, laned, dry, except, owed) {
359
516
  k.n = 0;
360
517
  k.m = 0;
361
518
  k.names.length = 0;
362
- for (let held = head; held !== null; held = held.next) {
363
- const voice = held.voice;
364
- if (voice === null || (laned && voice.laned && !this.owedBy(owed, voice)))
519
+ const sharers = this.sharers;
520
+ let q = 0;
521
+ let at = head;
522
+ while (at !== null || q < sharers.length) {
523
+ const mine = chainFirst(at, sharers, q);
524
+ let voice;
525
+ let rec;
526
+ if (mine) {
527
+ rec = at;
528
+ voice = rec.voice;
529
+ at = rec.next;
530
+ }
531
+ else {
532
+ voice = sharers[q++];
533
+ rec = voice.state === 'pending' ? null : voice.everyone;
534
+ }
535
+ if (voice === null || rec === null)
536
+ continue;
537
+ if (laned && voice.laned && !this.owedBy(owed, voice))
365
538
  continue;
366
539
  if (voice.id === except || voice.state === 'done')
367
540
  continue;
541
+ const held = rec;
542
+ if (!mine)
543
+ open(voice, held, head.slot);
368
544
  const delta = this.read(voice, subject, now, dry, held);
369
545
  if (owed >= 0 && voice.laned)
370
546
  this.lanes.paid(voice.id, owed, held.weight);
547
+ if (!mine)
548
+ q = this.shared(voice, head.slot, held, q);
371
549
  if (delta === null)
372
550
  continue;
373
551
  const locus = voice.spec.locus;
@@ -404,12 +582,16 @@ export function foldLoci(subject, pose, head, laned, dry, except, owed) {
404
582
  const weight = k.weights[n];
405
583
  if (weight > 0)
406
584
  this.apply(pose, k.voices[n], k.helds[n], k.deltas[n], weight);
585
+ else
586
+ unband(k.helds[n]);
407
587
  continue;
408
588
  }
409
589
  const weight = this.foldLocus(k, group);
410
- if (weight <= 0)
411
- continue;
412
590
  const name = k.names[group];
591
+ if (!(weight > 0)) {
592
+ bands.get(name)?.fill(2);
593
+ continue;
594
+ }
413
595
  let on = bands.get(name);
414
596
  if (on === undefined) {
415
597
  on = new Uint8Array(this.channels.length);
@@ -421,15 +603,18 @@ export function foldLoci(subject, pose, head, laned, dry, except, owed) {
421
603
  k.values[slot] = undefined;
422
604
  const key = this.names[slot];
423
605
  const channel = this.channels[slot];
606
+ // A channel folds at the weight of the members that wrote it, not the whole locus.
607
+ const taken = k.taken[slot];
608
+ const w = taken > 1 ? 1 : taken;
424
609
  if (channel.rest !== undefined && channel.scale) {
425
610
  // pose[key] is the copy of rest this fold made, so it is ours to write into.
426
611
  pose[key] = channel.fold
427
- ? channel.fold(pose[key], value, weight)
428
- : merged(channel, pose[key], channel.scale(value, weight));
612
+ ? channel.fold(pose[key], value, w)
613
+ : merged(channel, pose[key], channel.scale(value, w));
429
614
  continue;
430
615
  }
431
616
  const band = on[slot];
432
- const passes = this.passes(band === 0 ? undefined : band === 1, weight);
617
+ const passes = this.passes(band === 0 ? undefined : band === 1, w);
433
618
  on[slot] = passes ? 1 : 2;
434
619
  if (passes)
435
620
  pose[key] = pose[key] === undefined ? copy(value) : merged(channel, pose[key], value);
package/dist/gather.d.ts CHANGED
@@ -11,20 +11,26 @@ export declare function gatherLocus<I, O>(lanes: Lanes<I, O>, g: Locus<I, O>): v
11
11
  export declare function foldLocus<I, O>(lanes: Lanes<I, O>, lane: Lane<I, O>, g: Locus<I, O>): void;
12
12
  /** For a keys voice: the delta's value for each channel it writes, read once per phase. */
13
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;
14
+ /** Folds what `gather` read into a subject's laned values, at `Arg.WEIGHT`. */
15
+ export declare function fold<I, O>(this: Lanes<I, O>, lane: Lane<I, O>, slot: number): void;
16
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.
17
+ * Folds a delta a patch returned into a subject's laned values, at `Arg.WEIGHT`. The first few
18
+ * channels are read at a site of their own, which in most mixes sees one channel name and stays
19
+ * fast, where one site reading every name in turn slows every read.
20
20
  */
21
- export declare function foldDelta<I, O>(this: Lanes<I, O>, chans: readonly Laned[], slot: number, delta: Record<string, unknown>, w: number): void;
21
+ export declare function foldDelta<I, O>(this: Lanes<I, O>, chans: readonly Laned[], slot: number, delta: Record<string, unknown>): void;
22
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;
23
+ export declare function foldRun<I, O>(this: Lanes<I, O>, ch: Laned, slot: number, xs: Float64Array): void;
24
+ /** Folds one channel's value into a subject's laned values, or its locus, at `Arg.WEIGHT`. */
25
+ export declare function foldInto<I, O>(this: Lanes<I, O>, ch: Laned, slot: number, value: unknown): void;
25
26
  /**
26
27
  * Whether a rest-less channel's contribution passes the band, for the record `one` is folding: the
27
28
  * general path's `apply` decides it from the same band state on the same record, so a subject
28
29
  * moving between the paths carries it.
29
30
  */
30
- export declare function gate<I, O>(this: Lanes<I, O>, ch: Laned, w: number): boolean;
31
+ export declare function gate<I, O>(this: Lanes<I, O>, ch: Laned): boolean;
32
+ /**
33
+ * `foldInto` for a channel that is not plain, at `Arg.WEIGHT`. A rest-less one asks its band, and
34
+ * true is a pass, which `foldInto` then folds; a rotation is folded here, so false.
35
+ */
36
+ export declare function unplain<I, O>(this: Lanes<I, O>, ch: Laned, slot: number, value: unknown): boolean;
package/dist/gather.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import { foldNumber, lerpNumber } from './channels.js';
2
+ import { shortWay, turnFrom, turnInto, turnToward } from './turns.js';
2
3
  /** `xs`, or a copy at least `n` long where it is shorter. */
3
4
  function sized(xs, n) {
4
5
  if (xs.length >= n)
@@ -37,13 +38,11 @@ export function gatherLocus(lanes, g) {
37
38
  continue;
38
39
  lanes.intoId = voice.id;
39
40
  lanes.reset(lane);
40
- const elapsed = voice.elapsedAt(lanes.now);
41
- const duration = voice.duration;
42
- const passes = voice.passes;
41
+ lane.elapsedNow = voice.elapsedAt(lanes.now);
43
42
  const list = lane.list;
44
43
  for (let p = 0; p < list.length; p++) {
45
44
  const slot = list[p];
46
- if (lanes.one(lane, p, slot, elapsed, duration, passes)) {
45
+ if (lanes.one(lane, p, slot)) {
47
46
  meetLocus(lanes, g, slot);
48
47
  g.sum[slot] =
49
48
  g.sum[slot] + lane.data[p * 8 /* Row.STRIDE */ + 2 /* Row.WEIGHT */];
@@ -68,8 +67,12 @@ function meetLocus(lanes, g, slot) {
68
67
  if (taken !== undefined)
69
68
  taken[slot] = 0;
70
69
  }
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) {
70
+ /**
71
+ * A member's value for one channel of a subject, lerped into its locus: the step of the general
72
+ * path's `foldLocus`.
73
+ */
74
+ function gatherInto(lanes, g, ch, slot, value) {
75
+ const w = lanes.arg[0 /* Arg.WEIGHT */];
73
76
  meetLocus(lanes, g, slot);
74
77
  const k = ch.index;
75
78
  let values = g.values[k];
@@ -83,7 +86,9 @@ function gatherInto(lanes, g, ch, slot, value, w) {
83
86
  const taken = takenAt[slot];
84
87
  const total = taken + w;
85
88
  const u = w / total;
86
- if (ch.scalar) {
89
+ if (!ch.plain)
90
+ turnToward(ch, values, slot * ch.axes, value, taken, u);
91
+ else if (ch.scalar) {
87
92
  values[slot] =
88
93
  taken === 0 ? value : lerpNumber(values[slot], value, u);
89
94
  }
@@ -110,15 +115,21 @@ export function foldLocus(lanes, lane, g) {
110
115
  const sum = g.sum[slot];
111
116
  if (sum <= 0)
112
117
  continue;
113
- const w = sum > 1 ? 1 : sum;
114
118
  for (let k = 0; k < g.values.length; k++) {
115
119
  const takenAt = g.taken[k];
116
- if (takenAt === undefined || !(takenAt[slot] > 0))
120
+ const taken = takenAt === undefined ? 0 : takenAt[slot];
121
+ if (!(taken > 0))
117
122
  continue;
123
+ // A channel folds at the weight of the members that wrote it, not the whole locus.
124
+ const w = taken > 1 ? 1 : taken;
118
125
  const ch = lanes.laned[k];
119
126
  const values = g.values[k];
120
127
  const into = ch.values;
121
128
  const base = slot * ch.axes;
129
+ if (!ch.plain) {
130
+ turnFrom(ch, into, base, values, base, w);
131
+ continue;
132
+ }
122
133
  for (let a = 0; a < ch.axes; a++)
123
134
  into[base + a] = foldNumber(ch.op, into[base + a], values[base + a], w);
124
135
  }
@@ -130,49 +141,56 @@ export function gather(lane, delta) {
130
141
  for (let i = 0; i < chans.length; i++)
131
142
  lane.values[i] = delta[chans[i].name];
132
143
  }
133
- /** Folds what `gather` read into a subject's laned values. */
134
- export function fold(lane, slot, w) {
144
+ /** Folds what `gather` read into a subject's laned values, at `Arg.WEIGHT`. */
145
+ export function fold(lane, slot) {
135
146
  const chans = lane.chans;
136
147
  for (let i = 0; i < chans.length; i++)
137
- this.foldInto(chans[i], slot, lane.values[i], w);
148
+ this.foldInto(chans[i], slot, lane.values[i]);
138
149
  }
139
150
  /**
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.
151
+ * Folds a delta a patch returned into a subject's laned values, at `Arg.WEIGHT`. The first few
152
+ * channels are read at a site of their own, which in most mixes sees one channel name and stays
153
+ * fast, where one site reading every name in turn slows every read.
143
154
  */
144
- export function foldDelta(chans, slot, delta, w) {
155
+ export function foldDelta(chans, slot, delta) {
145
156
  const n = chans.length;
146
157
  let ch = chans[0];
147
158
  if (n > 0)
148
- this.foldInto(ch, slot, delta[ch.name], w);
159
+ this.foldInto(ch, slot, delta[ch.name]);
149
160
  ch = chans[1];
150
161
  if (n > 1)
151
- this.foldInto(ch, slot, delta[ch.name], w);
162
+ this.foldInto(ch, slot, delta[ch.name]);
152
163
  ch = chans[2];
153
164
  if (n > 2)
154
- this.foldInto(ch, slot, delta[ch.name], w);
165
+ this.foldInto(ch, slot, delta[ch.name]);
155
166
  for (let i = 3; i < n; i++) {
156
167
  ch = chans[i];
157
- this.foldInto(ch, slot, delta[ch.name], w);
168
+ this.foldInto(ch, slot, delta[ch.name]);
158
169
  }
159
170
  }
160
171
  /** Folds a motion sample's axes, kept apart from `foldInto` so neither reads two array kinds. */
161
- export function foldRun(ch, slot, xs, w) {
172
+ export function foldRun(ch, slot, xs) {
173
+ if (!ch.plain) {
174
+ this.unplain(ch, slot, ch.scalar ? xs[0] : xs);
175
+ return;
176
+ }
177
+ const w = this.arg[0 /* Arg.WEIGHT */];
162
178
  const values = ch.values;
163
179
  const base = slot * ch.axes;
164
180
  for (let a = 0; a < ch.axes; a++)
165
181
  values[base + a] = foldNumber(ch.op, values[base + a], xs[a], w);
166
182
  }
167
- export function foldInto(ch, slot, value, w) {
183
+ /** Folds one channel's value into a subject's laned values, or its locus, at `Arg.WEIGHT`. */
184
+ export function foldInto(ch, slot, value) {
168
185
  if (value === undefined)
169
186
  return;
170
187
  if (this.into !== null) {
171
- gatherInto(this, this.into, ch, slot, value, w);
188
+ gatherInto(this, this.into, ch, slot, value);
172
189
  return;
173
190
  }
174
- if (ch.op === 'last' && !this.gate(ch, w))
191
+ if (!ch.plain && !this.unplain(ch, slot, value))
175
192
  return;
193
+ const w = this.arg[0 /* Arg.WEIGHT */];
176
194
  const values = ch.values;
177
195
  if (ch.scalar) {
178
196
  values[slot] = foldNumber(ch.op, values[slot], value, w);
@@ -191,7 +209,8 @@ export function foldInto(ch, slot, value, w) {
191
209
  * general path's `apply` decides it from the same band state on the same record, so a subject
192
210
  * moving between the paths carries it.
193
211
  */
194
- export function gate(ch, w) {
212
+ export function gate(ch) {
213
+ const w = this.arg[0 /* Arg.WEIGHT */];
195
214
  const lane = this.folding;
196
215
  const rec = this.rec;
197
216
  const i = lane.chans.indexOf(ch);
@@ -202,3 +221,19 @@ export function gate(ch, w) {
202
221
  bands[i] = on ? 1 : 2;
203
222
  return on;
204
223
  }
224
+ /**
225
+ * `foldInto` for a channel that is not plain, at `Arg.WEIGHT`. A rest-less one asks its band, and
226
+ * true is a pass, which `foldInto` then folds; a rotation is folded here, so false.
227
+ */
228
+ export function unplain(ch, slot, value) {
229
+ if (ch.op === 'last')
230
+ return this.gate(ch);
231
+ const w = this.arg[0 /* Arg.WEIGHT */];
232
+ const values = ch.values;
233
+ // An angle here and not through `turnInto`, where its weight would cross a call as a boxed double.
234
+ if (ch.turn > 0)
235
+ values[slot] = values[slot] + shortWay(value, ch.turn) * w;
236
+ else
237
+ turnInto(ch, values, slot * ch.axes, value, w);
238
+ return false;
239
+ }
package/dist/gone.d.ts ADDED
@@ -0,0 +1,32 @@
1
+ import type { Mixer } from './mixer.js';
2
+ import { Store } from './store.js';
3
+ import type { Voice } from './voice.js';
4
+ /**
5
+ * A mix's gone voices as `drop` and a freed slot look for them: those over every subject, and the
6
+ * rest by each subject they name. Kept in step with `gone`, which only grows by push and is
7
+ * otherwise replaced whole, so it catches up lazily and rebuilds only on a replacement it did not
8
+ * make itself. Under history a churning mix holds thousands, too many to walk per subject dropped.
9
+ */
10
+ export declare class GoneIndex<I, O> {
11
+ /** The `gone` list it was built over, and how much of it. */
12
+ of: readonly Voice<I, O>[] | null;
13
+ count: number;
14
+ general: Voice<I, O>[];
15
+ named: Store<I, Voice<I, O>[]>;
16
+ /** The latest `doneAt` in `of`, and how far any voice finished before one listed ahead of it. */
17
+ latest: number;
18
+ slack: number;
19
+ }
20
+ /** The index caught up with `mix.gone`. */
21
+ export declare function goneIndex<I, O>(mix: Mixer<I, O>): GoneIndex<I, O>;
22
+ /**
23
+ * Keeps the gone voices `keep` says yes to, asking it once each, and takes the rest out of the
24
+ * index one by one. Leaves `gone` as it was when every one stays.
25
+ */
26
+ export declare function keepGone<I, O>(mix: Mixer<I, O>, keep: (v: Voice<I, O>) => boolean): void;
27
+ /**
28
+ * Lets go of the gone voices that finished before `reach`. Voices go in about the order they
29
+ * finish, so only the front can hold one: past the point where a voice finished `slack` after
30
+ * `reach`, none behind it finished before `reach`.
31
+ */
32
+ export declare function expireGone<I, O>(mix: Mixer<I, O>, reach: number): void;