@msb235/blits 0.7.1 → 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 (153) hide show
  1. package/CHANGELOG.md +180 -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 +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 +213 -28
  36. package/dist/gather.d.ts +15 -9
  37. package/dist/gather.js +60 -25
  38. package/dist/gone.js +10 -0
  39. package/dist/handle.d.ts +16 -5
  40. package/dist/handle.js +56 -6
  41. package/dist/held.d.ts +8 -5
  42. package/dist/held.js +56 -58
  43. package/dist/history.d.ts +17 -13
  44. package/dist/history.js +47 -28
  45. package/dist/hosts.js +55 -8
  46. package/dist/index.d.ts +9 -4
  47. package/dist/index.js +5 -2
  48. package/dist/keyfill.d.ts +7 -0
  49. package/dist/keyfill.js +124 -0
  50. package/dist/keyrows.d.ts +1 -0
  51. package/dist/keyrows.js +1 -1
  52. package/dist/lane.d.ts +32 -2
  53. package/dist/lane.js +5 -0
  54. package/dist/lanes.d.ts +32 -8
  55. package/dist/lanes.js +69 -37
  56. package/dist/leavings.d.ts +26 -0
  57. package/dist/leavings.js +70 -0
  58. package/dist/meet.js +14 -8
  59. package/dist/mixer.d.ts +39 -7
  60. package/dist/mixer.js +50 -10
  61. package/dist/motion.d.ts +2 -0
  62. package/dist/motion.js +4 -1
  63. package/dist/motions.d.ts +22 -15
  64. package/dist/motions.js +40 -34
  65. package/dist/move.js +23 -16
  66. package/dist/moved.d.ts +11 -0
  67. package/dist/moved.js +29 -0
  68. package/dist/origin.d.ts +19 -0
  69. package/dist/origin.js +68 -0
  70. package/dist/owed.d.ts +4 -4
  71. package/dist/owed.js +8 -8
  72. package/dist/owner.d.ts +1 -1
  73. package/dist/owner.js +2 -2
  74. package/dist/pace.d.ts +13 -5
  75. package/dist/pace.js +21 -8
  76. package/dist/pack.d.ts +3 -1
  77. package/dist/pack.js +15 -24
  78. package/dist/paging.d.ts +8 -2
  79. package/dist/paging.js +44 -4
  80. package/dist/patch.d.ts +14 -9
  81. package/dist/patch.js +83 -21
  82. package/dist/place.js +18 -3
  83. package/dist/project.d.ts +12 -1
  84. package/dist/project.js +101 -83
  85. package/dist/pull.d.ts +2 -0
  86. package/dist/pull.js +8 -0
  87. package/dist/qualify.js +10 -2
  88. package/dist/reading.d.ts +5 -0
  89. package/dist/reading.js +5 -0
  90. package/dist/record.d.ts +9 -0
  91. package/dist/record.js +42 -0
  92. package/dist/relink.d.ts +11 -5
  93. package/dist/relink.js +7 -5
  94. package/dist/restored.d.ts +41 -0
  95. package/dist/restored.js +91 -0
  96. package/dist/revive.d.ts +10 -3
  97. package/dist/revive.js +31 -23
  98. package/dist/roster.d.ts +11 -0
  99. package/dist/roster.js +46 -0
  100. package/dist/rotation.d.ts +29 -0
  101. package/dist/rotation.js +91 -0
  102. package/dist/rows.d.ts +5 -5
  103. package/dist/rows.js +5 -5
  104. package/dist/sample.d.ts +3 -2
  105. package/dist/sample.js +32 -15
  106. package/dist/seek.js +97 -114
  107. package/dist/shared.d.ts +6 -0
  108. package/dist/shared.js +15 -0
  109. package/dist/signals.d.ts +24 -3
  110. package/dist/signals.js +50 -11
  111. package/dist/spans.js +7 -0
  112. package/dist/stack.d.ts +11 -0
  113. package/dist/stack.js +47 -0
  114. package/dist/standing.d.ts +10 -0
  115. package/dist/standing.js +79 -0
  116. package/dist/store.d.ts +3 -3
  117. package/dist/store.js +3 -3
  118. package/dist/strays.js +3 -0
  119. package/dist/tape.d.ts +8 -5
  120. package/dist/tape.js +21 -13
  121. package/dist/testing.d.ts +35 -0
  122. package/dist/testing.js +116 -0
  123. package/dist/touch.d.ts +6 -0
  124. package/dist/touch.js +28 -0
  125. package/dist/transport.d.ts +49 -6
  126. package/dist/transport.js +89 -19
  127. package/dist/turns.d.ts +18 -0
  128. package/dist/turns.js +72 -0
  129. package/dist/tweened.js +1 -1
  130. package/dist/types/channel.d.ts +46 -0
  131. package/dist/types/channel.js +1 -0
  132. package/dist/types/history.d.ts +139 -0
  133. package/dist/types/history.js +1 -0
  134. package/dist/types/mix.d.ts +336 -0
  135. package/dist/types/mix.js +1 -0
  136. package/dist/types/patch.d.ts +187 -0
  137. package/dist/types/patch.js +1 -0
  138. package/dist/types/place.d.ts +157 -0
  139. package/dist/types/place.js +1 -0
  140. package/dist/types/transport.d.ts +71 -0
  141. package/dist/types/transport.js +1 -0
  142. package/dist/types/voice.d.ts +323 -0
  143. package/dist/types/voice.js +1 -0
  144. package/dist/types.d.ts +7 -1162
  145. package/dist/unpage.d.ts +2 -2
  146. package/dist/unpage.js +53 -25
  147. package/dist/voice.d.ts +90 -22
  148. package/dist/voice.js +105 -43
  149. package/dist/wave.d.ts +42 -0
  150. package/dist/wave.js +62 -0
  151. package/dist/weigh.js +1 -1
  152. package/docs/schema.html +42 -36
  153. package/package.json +8 -3
package/dist/motions.js CHANGED
@@ -43,7 +43,7 @@ const wrongKind = (channel, holdsNumber) => new Error(`blits: channel ${channel}
43
43
  * The one copy, read by `at` and by a mix's lane alike.
44
44
  *
45
45
  * After the law, each subject's run of `stride` numbers is its release time, its flags, a tween's
46
- * seconds for the stretch, then `x0`, `v0` and `to` per axis. A live sample reads this one buffer and nothing else the patch owns: at
46
+ * ms for the stretch, then `x0`, `v0` and `to` per axis. A live sample reads this one buffer and nothing else the patch owns: at
47
47
  * 10k springs of one subject each, a buffer per field, a map lookup per sample and the law in an
48
48
  * object of its own took a call from 130 ns to 490.
49
49
  */
@@ -52,6 +52,8 @@ export class Motions {
52
52
  numbers = new Numbers(this);
53
53
  /** Numbers by subject once a second is numbered: till then the one subject is found by `numbers`. */
54
54
  slots = new Store();
55
+ /** How many numbers have been given up, so a number kept elsewhere can tell it is stale. */
56
+ freed = 0;
55
57
  /** Axes per subject; -1 until the first subject sets it. */
56
58
  n = -1;
57
59
  cap = 0;
@@ -163,10 +165,10 @@ export class Motions {
163
165
  */
164
166
  released = [];
165
167
  /**
166
- * Forgets a subject, so a later ask starts it afresh from `from`. Given the mix time `at`, under
167
- * history, its run is kept for a rewind before then, until `reach` passes it.
168
+ * Forgets a subject, so a later ask starts it afresh from `from`. Given the mix time `at` and frame
169
+ * `seq`, under history, its run is kept for a rewind before then, until `reach` passes it.
168
170
  */
169
- release(subject, at, reach = Number.NEGATIVE_INFINITY) {
171
+ release(subject, at, reach = Number.NEGATIVE_INFINITY, seq = Number.NaN) {
170
172
  const s = this.known(subject);
171
173
  if (s === undefined)
172
174
  return;
@@ -176,19 +178,21 @@ export class Motions {
176
178
  if (e.at >= reach)
177
179
  kept.push(e);
178
180
  else
179
- this.owner?.page(this.ownerId, 'released', e.subject, [{ at: e.at, data: runOf(e) }]);
181
+ this.owner?.page(this.ownerId, 'released', e.subject, [
182
+ { at: e.at, seq: e.seq, data: runOf(e) },
183
+ ]);
180
184
  this.released = kept;
181
- this.released.push({ subject, at, ...this.runAt(s) });
185
+ this.released.push({ subject, at, seq, ...this.runAt(s) });
182
186
  }
183
187
  this.slots.delete(subject);
184
188
  this.numbers.release(s);
185
189
  }
186
- /** Puts back each subject released after mix time `t`, as it stood when released. */
187
- unrelease(t) {
188
- const back = this.released.filter((e) => e.at > t).sort((a, b) => a.at - b.at);
190
+ /** Puts back each subject released after frame `seq`, as it stood when released. */
191
+ unrelease(seq) {
192
+ const back = this.released.filter((e) => e.seq > seq).sort((a, b) => a.seq - b.seq);
189
193
  if (back.length === 0)
190
194
  return;
191
- this.released = this.released.filter((e) => e.at <= t);
195
+ this.released = this.released.filter((e) => e.seq <= seq);
192
196
  const done = new Set();
193
197
  for (const e of back) {
194
198
  if (done.has(e.subject))
@@ -239,24 +243,24 @@ export class Motions {
239
243
  }
240
244
  }
241
245
  /** A released run a history store gave back, for a rewind to before its release. */
242
- reclaim(subject, at, run) {
243
- if (this.released.some((e) => e.at === at && Object.is(e.subject, subject)))
246
+ reclaim(subject, at, seq, run) {
247
+ if (this.released.some((e) => e.seq === seq && Object.is(e.subject, subject)))
244
248
  return;
245
- this.released.push({ subject, at, ...run });
249
+ this.released.push({ subject, at, seq, ...run });
246
250
  }
247
251
  /**
248
- * Stretches a history store gave back, each by the mix time it was in force by: each life of the
252
+ * Stretches a history store gave back, each by the frame it was in force by: each life of the
249
253
  * subject, ended by a release, takes those from its own, ahead of the earliest it still keeps.
250
254
  */
251
255
  restretch(subject, back) {
252
256
  const lives = this.released
253
257
  .filter((e) => Object.is(e.subject, subject))
254
- .sort((x, y) => x.at - y.at);
258
+ .sort((x, y) => x.seq - y.seq);
255
259
  let from = Number.NEGATIVE_INFINITY;
256
- const within = (to) => back.filter((r) => r.at > from && r.at <= to).map((r) => r.data);
260
+ const within = (to) => back.filter((r) => r.seq > from && r.seq <= to).map((r) => r.data);
257
261
  for (const e of lives) {
258
- e.stretches = ahead(within(e.at), e.stretches);
259
- from = e.at;
262
+ e.stretches = ahead(within(e.seq), e.stretches);
263
+ from = e.seq;
260
264
  }
261
265
  const s = this.known(subject);
262
266
  if (s === undefined)
@@ -298,7 +302,7 @@ export class Motions {
298
302
  return true;
299
303
  }
300
304
  /**
301
- * Copies subject `s`'s current stretch into `out` from `o` as its release time, its seconds, then
305
+ * Copies subject `s`'s current stretch into `out` from `o` as its release time, its ms, then
302
306
  * `x0`, `v0` and `to` per axis; false, copying nothing, while it has a change pending or an
303
307
  * earlier stretch kept, which only `sample` handles.
304
308
  */
@@ -354,7 +358,7 @@ export class Motions {
354
358
  this.peek(s, t, xo, vo);
355
359
  }
356
360
  /**
357
- * Where subject `s`'s run starts in `runs` (its release time, then flags, seconds, `x0`, `v0`
361
+ * Where subject `s`'s run starts in `runs` (its release time, then flags, ms, `x0`, `v0`
358
362
  * and `to`) while it has nothing pending and no earlier stretch kept; -1 for any other.
359
363
  */
360
364
  bareAt(s) {
@@ -455,6 +459,7 @@ export class Motions {
455
459
  this.check(c.to, this.n);
456
460
  this.check(c.v, this.n);
457
461
  c.made = this.owner === null ? Number.NaN : this.owner.now();
462
+ c.seq = this.owner === null ? Number.NaN : this.owner.seq();
458
463
  if (c.at === undefined) {
459
464
  const at = this.frame(subject);
460
465
  if (!Number.isNaN(at))
@@ -511,6 +516,7 @@ export class Motions {
511
516
  this.runs[i] = on ? flags | bit : flags & ~bit;
512
517
  }
513
518
  forget(s) {
519
+ this.freed++;
514
520
  if (this.pending !== null)
515
521
  this.pending[s] = undefined;
516
522
  if (this.older !== null)
@@ -519,16 +525,16 @@ export class Motions {
519
525
  this.causes[s] = undefined;
520
526
  }
521
527
  /**
522
- * Takes back every change made after mix time `t`: each subject plays from its earliest stretch
528
+ * Takes back every change made after frame `seq`: each subject plays from its earliest stretch
523
529
  * kept, with the changes made by then queued to be applied again.
524
530
  */
525
- rewind(t) {
526
- this.unrelease(t);
531
+ rewind(seq) {
532
+ this.unrelease(seq);
527
533
  for (let s = 0; s < this.numbers.size; s++)
528
534
  if (this.numbers.subject(s) !== absent)
529
- this.cut(s, t);
535
+ this.cut(s, seq);
530
536
  }
531
- cut(s, t) {
537
+ cut(s, seq) {
532
538
  const older = this.older?.[s];
533
539
  const pending = this.pending?.[s];
534
540
  const current = this.latest(s);
@@ -536,7 +542,7 @@ export class Motions {
536
542
  const kept = [];
537
543
  let cut = false;
538
544
  for (const c of [...stretches.slice(1).map((seg) => seg.change), ...(pending ?? [])])
539
- if (c === undefined || c.made === undefined || !(c.made > t)) {
545
+ if (c === undefined || c.seq === undefined || !(c.seq > seq)) {
540
546
  if (c !== undefined)
541
547
  kept.push(c);
542
548
  }
@@ -684,12 +690,15 @@ export class Motions {
684
690
  list.splice(i, 0, seg);
685
691
  }
686
692
  /**
687
- * The mix time a stretch was in force by, which a store keys it under: when the change that
688
- * ended it was made, which falls in the same life of the subject.
693
+ * The mix time and frame a stretch was in force by, which a store keys it under: when the change
694
+ * that ended it was made, which falls in the same life of the subject.
689
695
  */
690
696
  made(next) {
691
- const made = next.change?.made;
692
- return made === undefined || Number.isNaN(made) ? (this.owner?.now() ?? Number.NaN) : made;
697
+ const c = next.change;
698
+ const made = c?.made;
699
+ if (made === undefined || Number.isNaN(made))
700
+ return { at: this.owner?.now() ?? Number.NaN, seq: this.owner?.seq() ?? Number.NaN };
701
+ return { at: made, seq: c?.seq ?? Number.NaN };
693
702
  }
694
703
  /** Lets go of stretches older than the one in force at `reading.horizon`. */
695
704
  prune(s) {
@@ -706,10 +715,7 @@ export class Motions {
706
715
  const out = list.splice(0, n);
707
716
  const subject = this.numbers.subject(s);
708
717
  if (this.owner !== null && subject !== absent)
709
- this.owner.page(this.ownerId, 'stretch', subject, out.map((seg, i) => ({
710
- at: this.made(out[i + 1] ?? list[0] ?? this.latest(s)),
711
- data: seg,
712
- })));
718
+ this.owner.page(this.ownerId, 'stretch', subject, out.map((seg, i) => ({ ...this.made(out[i + 1] ?? list[0] ?? this.latest(s)), data: seg })));
713
719
  if (list.length === 0) {
714
720
  this.older[s] = undefined;
715
721
  this.flag(s, OLDER, false);
package/dist/move.js CHANGED
@@ -1,11 +1,13 @@
1
- import { changed, unindex } from './chain.js';
1
+ import { changed } from './chain.js';
2
2
  import { popDue, schedule } from './due.js';
3
+ import { detour } from './everyone.js';
3
4
  import { beginFade, part, retire } from './fade.js';
4
5
  import { expireGone, keepGone } from './gone.js';
5
- import { started } from './held.js';
6
+ import { reorigin } from './held.js';
6
7
  import { ownerReading } from './owner.js';
7
8
  import { mixAt, place, repin, startOf } from './place.js';
8
9
  import { pageVoice } from './revive.js';
10
+ import { delist } from './roster.js';
9
11
  import { scoredLeft, scoredRemove } from './scored.js';
10
12
  import { lapse } from './spans.js';
11
13
  import { unplay } from './strays.js';
@@ -31,6 +33,11 @@ export function move(mix, now) {
31
33
  }
32
34
  function moveTo(mix, now) {
33
35
  mix.now = now;
36
+ mix.looked = false;
37
+ if (mix.unshared) {
38
+ mix.unshared = false;
39
+ detour(mix);
40
+ }
34
41
  mix.frame = nextFrame();
35
42
  mix.reducedNow = mix.reduced;
36
43
  for (const a of mix.announced)
@@ -52,18 +59,19 @@ function moveTo(mix, now) {
52
59
  continue;
53
60
  if (Number.isNaN(voice.opened))
54
61
  voice.opened = now;
55
- if ((voice.state === 'live' || voice.state === 'frozen') && voice.outAt <= now)
56
- beginFade(mix, voice, { over: Number.isNaN(voice.outOver) ? undefined : voice.outOver }, Math.max(startOf(voice), voice.outAt));
57
62
  if (voice.state === 'pending' &&
58
63
  (voice.owner === null ? now : ownerReading(voice.owner, now)) >= voice.start) {
59
64
  voice.state = 'live';
60
- started(mix, voice);
65
+ reorigin(mix, voice, now);
61
66
  changed(mix, voice);
62
67
  }
68
+ // After it starts, so a fade due by now on a voice that started since the last frame begins.
69
+ if ((voice.state === 'live' || voice.state === 'frozen') && voice.outAt <= now)
70
+ beginFade(mix, voice, { over: Number.isNaN(voice.outOver) ? undefined : voice.outOver }, Math.max(startOf(voice), voice.outAt));
63
71
  if (voice.state !== 'pending' && Number.isFinite(voice.span)) {
64
72
  const end = voice.span + voice.latest;
65
73
  if (voice.elapsedAt(now) >= end) {
66
- voice.play(true, now);
74
+ voice.play(true, now, mix.transport.seq);
67
75
  // The fade starts when the last pass ended, not at the frame that noticed, so it plays
68
76
  // the same at any frame rate and a read at another time can find it.
69
77
  if (voice.state === 'live') {
@@ -154,8 +162,12 @@ function prune(mix) {
154
162
  }
155
163
  voices.length = kept;
156
164
  }
157
- if (pruned)
165
+ if (pruned) {
158
166
  mix.general = mix.general.filter((v) => v.state !== 'done');
167
+ if (mix.sharers.length > 0)
168
+ mix.sharers = mix.sharers.filter((v) => v.state !== 'done');
169
+ detour(mix);
170
+ }
159
171
  }
160
172
  /** The voice's place in a list in id order, or -1 where it is not there. */
161
173
  function indexOf(voices, voice) {
@@ -180,18 +192,13 @@ function leave(mix, voice) {
180
192
  mix.gone.push(voice);
181
193
  scoredLeft(mix, voice);
182
194
  }
183
- else
195
+ else {
184
196
  mix.parters.delete(voice);
197
+ mix.departed.add(mix, voice);
198
+ }
185
199
  unplay(mix, voice);
186
200
  changed(mix, voice);
187
- if (voice.named !== null)
188
- unindex(mix, voice);
189
- if (voice.holding !== null && mix.owners !== null)
190
- mix.owners = mix.owners.filter((v) => v !== voice);
191
- if (voice.spec.locus !== undefined)
192
- mix.loci--;
193
- if (voice.spec.anchor !== undefined)
194
- mix.anchored--;
201
+ delist(mix, voice);
195
202
  return voice.named === null;
196
203
  }
197
204
  /** Lets go of the voices that have left and that history no longer reaches. */
@@ -0,0 +1,11 @@
1
+ import type { Motions } from './motions.js';
2
+ import type { Subject } from './voice.js';
3
+ /**
4
+ * A motion voice's delta for a subject on the general path: what its patch's `at` gives, written
5
+ * into the record's last delta and the array that holds, where `at` makes an object and an array a
6
+ * call, and the patch's number for the subject kept on the record, where `at` looks it up a call.
7
+ * `setting` has the voice time to sample at.
8
+ */
9
+ export declare function moved<I>(run: Motions<I>, key: string, subject: I, setting: {
10
+ readonly elapsed: number;
11
+ }, rec: Subject<unknown>): Record<string, unknown>;
package/dist/moved.js ADDED
@@ -0,0 +1,29 @@
1
+ import { doubles } from './channels.js';
2
+ /**
3
+ * A motion voice's delta for a subject on the general path: what its patch's `at` gives, written
4
+ * into the record's last delta and the array that holds, where `at` makes an object and an array a
5
+ * call, and the patch's number for the subject kept on the record, where `at` looks it up a call.
6
+ * `setting` has the voice time to sample at.
7
+ */
8
+ export function moved(run, key, subject, setting, rec) {
9
+ let s = rec.run;
10
+ if (rec.freed !== run.freed) {
11
+ s = run.slot(subject);
12
+ rec.run = s;
13
+ rec.freed = run.freed;
14
+ }
15
+ const xs = run.xs;
16
+ run.sample(s, setting.elapsed, xs, run.vs);
17
+ const delta = rec.delta ?? {};
18
+ if (run.scalar(s)) {
19
+ delta[key] = xs[0];
20
+ return delta;
21
+ }
22
+ const n = run.n;
23
+ const held = delta[key];
24
+ const out = Array.isArray(held) && held.length === n ? held : doubles(n, 0);
25
+ for (let i = 0; i < n; i++)
26
+ out[i] = xs[i];
27
+ delta[key] = out;
28
+ return delta;
29
+ }
@@ -0,0 +1,19 @@
1
+ import { type Clock } from './clock.js';
2
+ import type { Voice } from './voice.js';
3
+ /** A clock a voice ran on before its current one, until its local time `until`. */
4
+ export interface Past extends Clock {
5
+ until: number;
6
+ /** The voice's `rebuilds` while it ran on this clock. */
7
+ rebuilds: number;
8
+ }
9
+ /**
10
+ * Keeps the clock a voice is leaving at its local time `u`, ahead of a rate change, ramp or seek,
11
+ * where a subject's origin could still be found on it.
12
+ */
13
+ export declare function leave<I, O>(voice: Voice<I, O>, u: number): void;
14
+ /**
15
+ * The mix time a voice's clock first read `elapsed`, or will by its controls now, through its owners'
16
+ * clocks. A clock entered already past it, by a seek, answers where that clock would have read it.
17
+ * `rebuilt` starts from the last seek that rebuilt state, as a stateful record does.
18
+ */
19
+ export declare function originOf<I, O>(voice: Voice<I, O>, elapsed: number, rebuilt: boolean): number;
package/dist/origin.js ADDED
@@ -0,0 +1,68 @@
1
+ import { elapsedWith, timeWith } from './clock.js';
2
+ /** The most past clocks a voice keeps. */
3
+ const MOST = 256;
4
+ /**
5
+ * Keeps the clock a voice is leaving at its local time `u`, ahead of a rate change, ramp or seek,
6
+ * where a subject's origin could still be found on it.
7
+ */
8
+ export function leave(voice, u) {
9
+ const past = voice.clocks;
10
+ if (past !== null && past.length >= MOST)
11
+ return;
12
+ // A voice with neither stagger nor children is only ever asked when its clock read 0.
13
+ const reach = voice.holding !== null || voice.spec.stagger !== undefined ? Number.POSITIVE_INFINITY : 0;
14
+ if (past !== null && reached(past, 0) >= reach && reached(past, fresh(voice)) >= reach)
15
+ return;
16
+ const { anchorNow, anchorElapsed, rate, ramp, rebuilds } = voice;
17
+ const left = { anchorNow, anchorElapsed, rate, ramp, until: u, rebuilds };
18
+ if (past === null)
19
+ voice.clocks = [left];
20
+ else
21
+ past.push(left);
22
+ }
23
+ /** The most a voice's past clocks from index `from` read before they were left. */
24
+ function reached(past, from) {
25
+ let most = Number.NEGATIVE_INFINITY;
26
+ for (let i = from; i < past.length; i++) {
27
+ const p = past[i];
28
+ const e = elapsedWith(p, p.until);
29
+ if (e > most)
30
+ most = e;
31
+ }
32
+ return most;
33
+ }
34
+ /** Where a walk for a record rebuilt by the voice's last seek starts: its first clock since. */
35
+ function fresh(voice) {
36
+ const past = voice.clocks;
37
+ if (past === null)
38
+ return 0;
39
+ let i = 0;
40
+ while (i < past.length && past[i].rebuilds !== voice.rebuilds)
41
+ i++;
42
+ return i;
43
+ }
44
+ /**
45
+ * The mix time a voice's clock first read `elapsed`, or will by its controls now, through its owners'
46
+ * clocks. A clock entered already past it, by a seek, answers where that clock would have read it.
47
+ * `rebuilt` starts from the last seek that rebuilt state, as a stateful record does.
48
+ */
49
+ export function originOf(voice, elapsed, rebuilt) {
50
+ const past = voice.clocks;
51
+ let t = Number.NaN;
52
+ if (past !== null) {
53
+ for (let i = rebuilt ? fresh(voice) : 0; i < past.length; i++) {
54
+ const p = past[i];
55
+ const at = timeWith(p, elapsed);
56
+ if (at <= p.until) {
57
+ t = at;
58
+ break;
59
+ }
60
+ }
61
+ }
62
+ if (Number.isNaN(t)) {
63
+ t = timeWith(voice, elapsed);
64
+ if (t === Number.POSITIVE_INFINITY)
65
+ t = voice.start + elapsed;
66
+ }
67
+ return voice.owner === null ? t : originOf(voice.owner, t, rebuilt);
68
+ }
package/dist/owed.d.ts CHANGED
@@ -17,8 +17,8 @@ export declare class Owed {
17
17
  owes(slot: number): boolean;
18
18
  /** Whether the subject at `slot` owes voice `id` this fill. */
19
19
  owesVoice(slot: number, id: number): boolean;
20
- /** The subject at `slot` owes the voices `met`, which the caller may reuse after. */
21
- owe(slot: number, met: readonly number[]): void;
22
- /** Whether `met` is the last list a subject came to owe this fill. */
23
- last(met: readonly number[]): boolean;
20
+ /** The subject at `slot` owes the first `n` voices of `met`, which the caller may reuse after. */
21
+ owe(slot: number, met: readonly number[], n: number): void;
22
+ /** Whether the first `n` of `met` are the last list a subject came to owe this fill. */
23
+ last(met: readonly number[], n: number): boolean;
24
24
  }
package/dist/owed.js CHANGED
@@ -31,19 +31,19 @@ export class Owed {
31
31
  owesVoice(slot, id) {
32
32
  return this.lists[this.at[slot]].includes(id);
33
33
  }
34
- /** The subject at `slot` owes the voices `met`, which the caller may reuse after. */
35
- owe(slot, met) {
36
- if (!this.last(met))
37
- this.lists.push(met.slice());
34
+ /** The subject at `slot` owes the first `n` voices of `met`, which the caller may reuse after. */
35
+ owe(slot, met, n) {
36
+ if (!this.last(met, n))
37
+ this.lists.push(met.slice(0, n));
38
38
  this.fill[slot] = this.now;
39
39
  this.at[slot] = this.lists.length - 1;
40
40
  }
41
- /** Whether `met` is the last list a subject came to owe this fill. */
42
- last(met) {
41
+ /** Whether the first `n` of `met` are the last list a subject came to owe this fill. */
42
+ last(met, n) {
43
43
  const last = this.lists[this.lists.length - 1];
44
- if (last === undefined || last.length !== met.length)
44
+ if (last === undefined || last.length !== n)
45
45
  return false;
46
- for (let i = 0; i < met.length; i++)
46
+ for (let i = 0; i < n; i++)
47
47
  if (last[i] !== met[i])
48
48
  return false;
49
49
  return true;
package/dist/owner.d.ts CHANGED
@@ -24,7 +24,7 @@ export declare function adopt<I, O>(owner: Voice<I, O>, child: Voice<I, O>): voi
24
24
  /** A child left: its owner, where that was its last child and it should leave too. */
25
25
  export declare function orphan<I, O>(child: Voice<I, O>): Voice<I, O> | null;
26
26
  /** A child finished its passes: its owner has played once every child it held has. */
27
- export declare function childPlayed<I, O>(owner: Voice<I, O>, at: number): void;
27
+ export declare function childPlayed<I, O>(owner: Voice<I, O>, at: number, seq: number): void;
28
28
  /** Every voice an owner holds, its children's children included. */
29
29
  export declare function descendants<I, O>(owner: Voice<I, O>, visit: (v: Voice<I, O>) => void): void;
30
30
  /** Whether any owner above a voice reads a signal for its weight, which lanes cannot fill. */
package/dist/owner.js CHANGED
@@ -48,10 +48,10 @@ export function orphan(child) {
48
48
  return h.children.length === 0 && owner.state !== 'done' ? owner : null;
49
49
  }
50
50
  /** A child finished its passes: its owner has played once every child it held has. */
51
- export function childPlayed(owner, at) {
51
+ export function childPlayed(owner, at, seq) {
52
52
  const h = owner.holding;
53
53
  if (++h.played === h.kids)
54
- owner.play(true, at);
54
+ owner.play(true, at, seq);
55
55
  }
56
56
  /** Every voice an owner holds, its children's children included. */
57
57
  export function descendants(owner, visit) {
package/dist/pace.d.ts CHANGED
@@ -1,4 +1,8 @@
1
1
  import { type Clock } from './clock.js';
2
+ /** A clock in a pace's log, with the frame that set it, which a history store's cut goes by. */
3
+ export interface Paced extends Clock {
4
+ seq: number;
5
+ }
2
6
  /**
3
7
  * A mix's own rate, as a clock: mix time as a function of host time, which here means the host's
4
8
  * timestamp less what `rebase` took out. Each entry is the clock from the host time it took effect,
@@ -9,7 +13,7 @@ import { type Clock } from './clock.js';
9
13
  export declare class Pace {
10
14
  private readonly keeps;
11
15
  private log;
12
- constructor(keeps: boolean, log?: Clock[]);
16
+ constructor(keeps: boolean, log?: Paced[]);
13
17
  private get clock();
14
18
  /** Set before the first sync, the clock waits to be anchored there, reading host time until then. */
15
19
  private get waiting();
@@ -30,11 +34,15 @@ export declare class Pace {
30
34
  /** Whether the mix clock stands still from host time `u` on, short of another change. */
31
35
  stopped(u: number): boolean;
32
36
  /**
33
- * Sets the rate from host time `u`, at once or over `over` host ms, the way a handle sets a
34
- * voice's. Under history, lets go of the entries a read no longer reaches: those over before mix
35
- * time `reach`.
37
+ * Sets the rate from host time `u`, in frame `seq`, at once or over `over` host ms, the way a
38
+ * handle sets a voice's. Under history, lets go of the entries a read no longer reaches: those
39
+ * over before mix time `reach`.
36
40
  */
37
- change(u: number, rate: number, over: number, reach: number): void;
41
+ change(u: number, rate: number, over: number, reach: number, seq: number): void;
42
+ /** Lets go of the clocks over before mix time `reach`, and answers them. */
43
+ shed(reach: number): Paced[];
44
+ /** Puts back clocks a history store kept, ahead of the oldest still held. */
45
+ unshed(back: readonly Paced[]): void;
38
46
  /** Lets go of the clocks set after host time `u`, for a seek back to then. */
39
47
  cut(u: number): void;
40
48
  /** A copy holding the clocks that took effect before host time `u`, for a read back to then. */
package/dist/pace.js CHANGED
@@ -9,7 +9,9 @@ import { elapsedWith, rateWith, retime, timeWith } from './clock.js';
9
9
  export class Pace {
10
10
  keeps;
11
11
  log;
12
- constructor(keeps, log = [{ anchorNow: 0, anchorElapsed: 0, rate: 1, ramp: null }]) {
12
+ constructor(keeps, log = [
13
+ { anchorNow: 0, anchorElapsed: 0, rate: 1, ramp: null, seq: Number.NEGATIVE_INFINITY },
14
+ ]) {
13
15
  this.keeps = keeps;
14
16
  this.log = log;
15
17
  }
@@ -75,13 +77,13 @@ export class Pace {
75
77
  return r.to === 0 && !this.waiting && u >= c.anchorNow + r.over;
76
78
  }
77
79
  /**
78
- * Sets the rate from host time `u`, at once or over `over` host ms, the way a handle sets a
79
- * voice's. Under history, lets go of the entries a read no longer reaches: those over before mix
80
- * time `reach`.
80
+ * Sets the rate from host time `u`, in frame `seq`, at once or over `over` host ms, the way a
81
+ * handle sets a voice's. Under history, lets go of the entries a read no longer reaches: those
82
+ * over before mix time `reach`.
81
83
  */
82
- change(u, rate, over, reach) {
84
+ change(u, rate, over, reach, seq) {
83
85
  const was = this.clock;
84
- const next = { ...was };
86
+ const next = { ...was, seq };
85
87
  if (Number.isNaN(u) || this.waiting) {
86
88
  const from = this.rateAt(Number.NaN);
87
89
  next.ramp = over > 0 && rate !== from ? { from, to: rate, over } : null;
@@ -96,11 +98,22 @@ export class Pace {
96
98
  log.push(next);
97
99
  else
98
100
  log[log.length - 1] = next;
101
+ this.shed(reach);
102
+ }
103
+ /** Lets go of the clocks over before mix time `reach`, and answers them. */
104
+ shed(reach) {
105
+ const log = this.log;
99
106
  let drop = 0;
100
107
  while (drop + 1 < log.length && log[drop + 1].anchorElapsed <= reach)
101
108
  drop++;
102
- if (drop > 0)
103
- log.splice(0, drop);
109
+ return drop > 0 ? log.splice(0, drop) : [];
110
+ }
111
+ /** Puts back clocks a history store kept, ahead of the oldest still held. */
112
+ unshed(back) {
113
+ const first = this.log[0].anchorNow;
114
+ const older = back.filter((c) => c.anchorNow < first).sort((a, b) => a.anchorNow - b.anchorNow);
115
+ if (older.length > 0)
116
+ this.log = [...older, ...this.log];
104
117
  }
105
118
  /** Lets go of the clocks set after host time `u`, for a seek back to then. */
106
119
  cut(u) {
package/dist/pack.d.ts CHANGED
@@ -11,6 +11,7 @@ export interface PackedRecord {
11
11
  state: unknown;
12
12
  stepped: number;
13
13
  ticks: number;
14
+ rebuilt: number;
14
15
  /**
15
16
  * What `setting.keep` held, in the order its owners first kept it. An owner is code, which no
16
17
  * store keeps, and a voice revived from a descriptor has new ones; the same code keeps in the same
@@ -19,9 +20,10 @@ export interface PackedRecord {
19
20
  kept: unknown[] | null;
20
21
  base?: Record<string, unknown>;
21
22
  slope?: Record<string, unknown>;
22
- snaps?: [at: number, held: PackedRecord][];
23
+ snaps?: [at: number, seq: number, held: PackedRecord][];
23
24
  inputs?: {
24
25
  at: number;
26
+ seq: number;
25
27
  value: number;
26
28
  }[];
27
29
  }
package/dist/pack.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import { clone } from './clone.js';
2
+ import { record } from './record.js';
2
3
  /** A voice's record of one subject as plain data, its patch's `pack` taking its state. */
3
4
  export function packHeld(voice, h) {
4
5
  const patch = voice.patch;
@@ -14,6 +15,7 @@ export function packHeld(voice, h) {
14
15
  state: h.state === undefined ? undefined : patch.pack ? patch.pack(h.state) : clone(h.state),
15
16
  stepped: h.stepped,
16
17
  ticks: h.ticks,
18
+ rebuilt: h.rebuilt,
17
19
  kept: kept.length === 0 ? null : kept.map(clone),
18
20
  };
19
21
  if (h.base !== undefined)
@@ -21,7 +23,7 @@ export function packHeld(voice, h) {
21
23
  if (h.slope !== undefined)
22
24
  p.slope = clone(h.slope);
23
25
  if (h.snaps !== undefined)
24
- p.snaps = h.snaps.map((s) => [s.at, packHeld(voice, s.held)]);
26
+ p.snaps = h.snaps.map((s) => [s.at, s.seq, packHeld(voice, s.held)]);
25
27
  if (h.inputs !== undefined)
26
28
  p.inputs = h.inputs.map((e) => ({ ...e }));
27
29
  return p;
@@ -29,28 +31,17 @@ export function packHeld(voice, h) {
29
31
  /** A record made again from `packHeld`'s data, for `voice`. */
30
32
  export function unpackHeld(voice, p) {
31
33
  const patch = voice.patch;
32
- const h = {
33
- reaches: p.reaches,
34
- delay: p.delay,
35
- since: p.since,
36
- shown: p.shown,
37
- weight: p.weight,
38
- rested: p.rested,
39
- bands: p.bands === null ? null : Uint8Array.from(p.bands),
40
- state: p.state === undefined ? undefined : patch.unpack ? patch.unpack(p.state) : p.state,
41
- stepped: p.stepped,
42
- ticks: p.ticks,
43
- probed: Number.NaN,
44
- delta: null,
45
- phase: 0,
46
- seeks: 0,
47
- kept: null,
48
- voice,
49
- next: null,
50
- version: Number.NaN,
51
- loci: null,
52
- slot: -1,
53
- };
34
+ const bands = p.bands === null ? null : Uint8Array.from(p.bands);
35
+ const h = record(voice, p.reaches, p.state === undefined ? undefined : patch.unpack ? patch.unpack(p.state) : p.state);
36
+ h.delay = p.delay;
37
+ h.since = p.since;
38
+ h.shown = p.shown;
39
+ h.weight = p.weight;
40
+ h.rested = p.rested;
41
+ h.bands = bands;
42
+ h.stepped = p.stepped;
43
+ h.ticks = p.ticks;
44
+ h.rebuilt = p.rebuilt;
54
45
  if (p.kept !== null)
55
46
  h.unkept = p.kept;
56
47
  if (p.base !== undefined)
@@ -58,7 +49,7 @@ export function unpackHeld(voice, p) {
58
49
  if (p.slope !== undefined)
59
50
  h.slope = p.slope;
60
51
  if (p.snaps !== undefined)
61
- h.snaps = p.snaps.map(([at, held]) => ({ at, held: unpackHeld(voice, held) }));
52
+ h.snaps = p.snaps.map(([at, seq, held]) => ({ at, seq, held: unpackHeld(voice, held) }));
62
53
  if (p.inputs !== undefined)
63
54
  h.inputs = p.inputs;
64
55
  return h;