@msb235/blits 0.7.1 → 0.8.1

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 +193 -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 +8 -0
  11. package/dist/clone.js +22 -0
  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 +31 -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 +34 -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 +216 -31
  36. package/dist/frames.d.ts +21 -0
  37. package/dist/frames.js +59 -0
  38. package/dist/gather.d.ts +15 -9
  39. package/dist/gather.js +60 -25
  40. package/dist/gone.js +10 -0
  41. package/dist/handle.d.ts +16 -5
  42. package/dist/handle.js +56 -6
  43. package/dist/held.d.ts +8 -5
  44. package/dist/held.js +56 -58
  45. package/dist/history.d.ts +17 -13
  46. package/dist/history.js +47 -28
  47. package/dist/hosts.js +55 -8
  48. package/dist/index.d.ts +9 -4
  49. package/dist/index.js +5 -2
  50. package/dist/keyfill.d.ts +7 -0
  51. package/dist/keyfill.js +124 -0
  52. package/dist/keyrows.d.ts +1 -0
  53. package/dist/keyrows.js +1 -1
  54. package/dist/lane.d.ts +32 -2
  55. package/dist/lane.js +5 -0
  56. package/dist/lanes.d.ts +32 -8
  57. package/dist/lanes.js +69 -37
  58. package/dist/leavings.d.ts +26 -0
  59. package/dist/leavings.js +70 -0
  60. package/dist/locus.js +2 -2
  61. package/dist/meet.js +14 -8
  62. package/dist/mixer.d.ts +39 -7
  63. package/dist/mixer.js +50 -10
  64. package/dist/motion.d.ts +2 -0
  65. package/dist/motion.js +4 -1
  66. package/dist/motions.d.ts +22 -15
  67. package/dist/motions.js +40 -34
  68. package/dist/move.js +23 -16
  69. package/dist/moved.d.ts +11 -0
  70. package/dist/moved.js +29 -0
  71. package/dist/origin.d.ts +19 -0
  72. package/dist/origin.js +68 -0
  73. package/dist/owed.d.ts +4 -4
  74. package/dist/owed.js +8 -8
  75. package/dist/owner.d.ts +1 -1
  76. package/dist/owner.js +2 -2
  77. package/dist/pace.d.ts +13 -5
  78. package/dist/pace.js +21 -8
  79. package/dist/pack.d.ts +3 -1
  80. package/dist/pack.js +15 -24
  81. package/dist/paging.d.ts +8 -2
  82. package/dist/paging.js +39 -4
  83. package/dist/patch.d.ts +14 -9
  84. package/dist/patch.js +83 -21
  85. package/dist/place.js +18 -3
  86. package/dist/project.d.ts +12 -1
  87. package/dist/project.js +101 -83
  88. package/dist/pull.d.ts +2 -0
  89. package/dist/pull.js +8 -0
  90. package/dist/qualify.js +10 -2
  91. package/dist/reading.d.ts +5 -0
  92. package/dist/reading.js +5 -0
  93. package/dist/record.d.ts +9 -0
  94. package/dist/record.js +42 -0
  95. package/dist/relink.d.ts +11 -5
  96. package/dist/relink.js +7 -5
  97. package/dist/restored.d.ts +41 -0
  98. package/dist/restored.js +91 -0
  99. package/dist/revive.d.ts +10 -3
  100. package/dist/revive.js +31 -23
  101. package/dist/roster.d.ts +11 -0
  102. package/dist/roster.js +46 -0
  103. package/dist/rotation.d.ts +29 -0
  104. package/dist/rotation.js +91 -0
  105. package/dist/rows.d.ts +5 -5
  106. package/dist/rows.js +5 -5
  107. package/dist/sample.d.ts +3 -2
  108. package/dist/sample.js +32 -15
  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 +45 -6
  129. package/dist/transport.js +64 -19
  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 +53 -25
  150. package/dist/voice.d.ts +90 -22
  151. package/dist/voice.js +105 -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
- import { copy as copyValue } from './clone.js';
2
+ import { copy as copyValue, first } 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;
@@ -257,25 +298,62 @@ export function apply(pose, voice, held, delta, weight) {
257
298
  const on = this.passes(band === 0 ? undefined : band === 1, weight);
258
299
  bands[i] = on ? 1 : 2;
259
300
  if (on)
260
- pose[key] = pose[key] === undefined ? copy(value) : merged(channel, pose[key], value);
301
+ pose[key] = pose[key] === undefined ? first(value) : merged(channel, pose[key], value);
261
302
  }
262
303
  }
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,18 +603,21 @@ 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
- pose[key] = pose[key] === undefined ? copy(value) : merged(channel, pose[key], value);
620
+ pose[key] = pose[key] === undefined ? first(value) : merged(channel, pose[key], value);
436
621
  }
437
622
  }
438
623
  return pose;
@@ -0,0 +1,21 @@
1
+ import type { Frame } from './transport.js';
2
+ /**
3
+ * The frames a transport's history can still reach, oldest first. A frame leaves the front on
4
+ * most syncs, so the front is an index that moves and the array closes up only now and then.
5
+ */
6
+ export declare class Frames {
7
+ private list;
8
+ private head;
9
+ get length(): number;
10
+ push(frame: Frame): void;
11
+ /** Lets go of every frame before the last one at or before `reach`, giving each to `each`. */
12
+ shed(reach: number, each?: (frame: Frame) => void): void;
13
+ /**
14
+ * The frame a seek or read to mix time `t` lands on: the last one standing at or before `t`,
15
+ * since rate 0 can hold the clock at `t` for many.
16
+ */
17
+ at(t: number): Frame | undefined;
18
+ /** Every frame held, oldest first. */
19
+ all(): Frame[];
20
+ set(frames: Frame[]): void;
21
+ }
package/dist/frames.js ADDED
@@ -0,0 +1,59 @@
1
+ /** Past this many frames let go of at the front, the list is closed up, once they are half of it. */
2
+ const SLACK = 512;
3
+ /**
4
+ * The frames a transport's history can still reach, oldest first. A frame leaves the front on
5
+ * most syncs, so the front is an index that moves and the array closes up only now and then.
6
+ */
7
+ export class Frames {
8
+ list = [];
9
+ head = 0;
10
+ get length() {
11
+ return this.list.length - this.head;
12
+ }
13
+ push(frame) {
14
+ this.list.push(frame);
15
+ }
16
+ /** Lets go of every frame before the last one at or before `reach`, giving each to `each`. */
17
+ shed(reach, each) {
18
+ const list = this.list;
19
+ let head = this.head;
20
+ while (head + 1 < list.length && list[head + 1].at <= reach) {
21
+ each?.(list[head]);
22
+ head++;
23
+ }
24
+ if (head >= SLACK && head * 2 >= list.length) {
25
+ list.splice(0, head);
26
+ head = 0;
27
+ }
28
+ this.head = head;
29
+ }
30
+ /**
31
+ * The frame a seek or read to mix time `t` lands on: the last one standing at or before `t`,
32
+ * since rate 0 can hold the clock at `t` for many.
33
+ */
34
+ at(t) {
35
+ const list = this.list;
36
+ let lo = this.head;
37
+ let hi = list.length - 1;
38
+ let found;
39
+ while (lo <= hi) {
40
+ const mid = (lo + hi) >> 1;
41
+ const f = list[mid];
42
+ if (f.at <= t) {
43
+ found = f;
44
+ lo = mid + 1;
45
+ }
46
+ else
47
+ hi = mid - 1;
48
+ }
49
+ return found;
50
+ }
51
+ /** Every frame held, oldest first. */
52
+ all() {
53
+ return this.list.slice(this.head);
54
+ }
55
+ set(frames) {
56
+ this.list = frames;
57
+ this.head = 0;
58
+ }
59
+ }
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;