@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/mixer.js CHANGED
@@ -2,8 +2,10 @@ import { blended, blendOf, laneRead, shareKept } from './blend.js';
2
2
  import { aims, chain, linkable, relink } from './chain.js';
3
3
  import { lerpInto } from './channels.js';
4
4
  import { cue } from './cue.js';
5
- import { beginFade, parting } from './fade.js';
6
- import { apply, baseFor, clamp, contribution, fold, folded, foldLoci, foldWith, isRest, keyed, linked, near, owedBy, passes, read, slopeFor, tick, } from './fold.js';
5
+ import { DepartedIndex } from './departed.js';
6
+ import { beginFade, leftWith, parting } from './fade.js';
7
+ import { apply, baseFor, clamp, contribution, fold, foldDetour, folded, foldLoci, foldSharers, foldWith, isRest, keyed, linked, near, owedBy, passes, read, shared, slopeFor, tick, } from './fold.js';
8
+ import { GoneIndex, goneIndex } from './gone.js';
7
9
  import { held, shownOf, sinceOf, unreachedOf } from './held.js';
8
10
  import { leave, releasing } from './history.js';
9
11
  import { book, laneHost } from './hosts.js';
@@ -17,13 +19,20 @@ import { projectAll } from './project.js';
17
19
  import { keep, pull } from './pull.js';
18
20
  import { Steps } from './relink.js';
19
21
  import { Outs } from './revive.js';
22
+ import { ScoreIndex } from './scored.js';
20
23
  import { seek } from './seek.js';
21
24
  import { Fitting, refit } from './spans.js';
22
25
  import { Store } from './store.js';
23
26
  import { record } from './tape.js';
27
+ import { touch } from './touch.js';
24
28
  import { announce, Transport } from './transport.js';
25
29
  import { none } from './voice.js';
26
30
  import { base, capped, fadeOf, horizonFor, ownedBy, ownerBase, prime, weigh, weighOwned, } from './weigh.js';
31
+ /**
32
+ * What a mix takes where its options say nothing. Not exported from the package: the suite turns
33
+ * `lanes` off to run every test on the general path, which is what makes each one an equivalence check.
34
+ */
35
+ export const defaults = { lanes: true };
27
36
  // biome-ignore lint/suspicious/noUnsafeDeclarationMerging: every member of `Methods` is installed below
28
37
  export class Mixer {
29
38
  kit;
@@ -31,12 +40,19 @@ export class Mixer {
31
40
  cued = [];
32
41
  /** Under `history`, voices that have left but that a read back may still reach. */
33
42
  gone = [];
43
+ goneIx = new GoneIndex();
44
+ scored = new ScoreIndex();
34
45
  /** Set on a projection's own mixer: it sends nothing, keeps no history, and reads without committing. */
35
46
  projecting = false;
36
47
  /** Set on a projection reading back: a subject it has nothing on starts from its voice's start. */
37
48
  backward = false;
38
49
  /** True during a sync, so a control change it makes is recorded as showing in that frame. */
39
50
  moving = false;
51
+ /**
52
+ * Whether a probe or pull has read the mix since its clock last moved. A control change made
53
+ * before that shows in this frame live, so it is recorded as showing in it.
54
+ */
55
+ looked = false;
40
56
  /**
41
57
  * Under `history` with `inputs`, the host fields patches read, copied each frame they changed. In
42
58
  * a projection reading back, the copy in force then.
@@ -52,20 +68,23 @@ export class Mixer {
52
68
  /** Taken off its transport, so it takes no more calls until a seek puts it back. */
53
69
  dropped = false;
54
70
  name;
55
- /** This mix's frame among every mix's: moved by a sync, and by a control change or drop within one. */
71
+ /**
72
+ * This mix's frame among every mix's: moved by a sync or seek, and by a control change, drop,
73
+ * or touch within one.
74
+ */
56
75
  frame = nextFrame();
57
76
  /** Pending voices whose start the host gave, by its host time, kept where the rate puts it. */
58
77
  pins = null;
59
78
  wantsPose = false;
60
79
  /** The pose `pull` folds a subject into where it cannot read straight from the lanes. */
61
80
  scratch;
62
- /** The last array `pull` read, by position, with each subject's chain head, to skip the lookup. */
63
81
  /** Live voices by the earliest mix time `moveTo` would change each, a binary heap. */
64
82
  due = [];
65
83
  /** Voices retired since `moveTo` last pruned them, in the order they retired. */
66
84
  retired = [];
67
85
  /** Visit every voice each sync, as before the due queue; for tests that compare the two. */
68
86
  walkAll = false;
87
+ /** The last array `pull` read, by position, with each subject's chain head, to skip the lookup. */
69
88
  pulled = [];
70
89
  pulledHeads = [];
71
90
  /** Each remembered head's lane slot, and the `version` and `relinks` they were all current at. */
@@ -85,6 +104,9 @@ export class Mixer {
85
104
  /** Records a change to the mix's voices or controls. */
86
105
  stir() {
87
106
  this.stirs++;
107
+ // A change a sync makes follows from the clock; one a host makes does not.
108
+ if (!this.moving)
109
+ this.transport.settle();
88
110
  if (this.stirred)
89
111
  return;
90
112
  this.stirred = true;
@@ -109,8 +131,8 @@ export class Mixer {
109
131
  }
110
132
  /**
111
133
  * Per subject, the first record of the chain through every live voice that reaches it, or a stub
112
- * where none does. Relinked when `version` moves, which is whenever the list or a voice's pending
113
- * state changes.
134
+ * where none does. Relinked when `version` moves, or when a voice naming the subject marks its
135
+ * head stale: see `changed` in `chain.ts`.
114
136
  */
115
137
  chains = new Store();
116
138
  version = 0;
@@ -119,6 +141,8 @@ export class Mixer {
119
141
  named = new Store();
120
142
  /** Voices a subject has been faded out of, which `drop` looks in besides those that reach it. */
121
143
  parters = new Set();
144
+ /** What an anchor can still find of voices that left without history, or past its reach. */
145
+ departed = new DepartedIndex();
122
146
  /**
123
147
  * Motion patches that may hold state for a subject no voice still reaching it plays, which `drop`
124
148
  * frees besides those voices' own: per subject, a patch a voice naming it left while another
@@ -132,6 +156,15 @@ export class Mixer {
132
156
  naming = 0;
133
157
  /** The voices in the list that name no subjects, in voice order. */
134
158
  general = [];
159
+ /** Those of `general` that keep one record for every subject, in voice order: see `everyone.ts`. */
160
+ sharers = [];
161
+ /**
162
+ * Whether a voice stopped sharing this frame: a probe or pull under way then holds chains linked
163
+ * before it joined them, and takes each again.
164
+ */
165
+ unshared = false;
166
+ /** 0 where a fold is a walk of the chain: see `detour` in `everyone.ts`. */
167
+ detours = 0;
135
168
  /** How many voices in the list carry a locus; with none, a fold allocates nothing. */
136
169
  loci = 0;
137
170
  /** What every motion patch it plays asks for its voice's time, holding this mix weakly. */
@@ -192,7 +225,7 @@ export class Mixer {
192
225
  this.names.forEach((k, i) => {
193
226
  this.slotOf.set(k, i);
194
227
  });
195
- this.lanes = opts.lanes === false ? null : new Lanes(laneHost(this));
228
+ this.lanes = (opts.lanes ?? defaults.lanes) ? new Lanes(laneHost(this)) : null;
196
229
  this.name = opts.name;
197
230
  const shared = opts.transport;
198
231
  if (shared !== undefined) {
@@ -311,6 +344,7 @@ export class Mixer {
311
344
  return book(this, opts);
312
345
  }
313
346
  probe(subject, out) {
347
+ this.looked = true;
314
348
  const pose = this.fold(subject, out);
315
349
  this.keep(subject, pose, out);
316
350
  // Where the lanes answer `atRest` from their own values, there is nothing to keep.
@@ -319,6 +353,7 @@ export class Mixer {
319
353
  return pose;
320
354
  }
321
355
  pull(subjects, into) {
356
+ this.looked = true;
322
357
  pull(this, subjects, into);
323
358
  }
324
359
  seek(time) {
@@ -427,8 +462,10 @@ export class Mixer {
427
462
  return motion !== undefined && voice.elapsedAt(now) >= voice.latest && motion.landed();
428
463
  }
429
464
  mute(opts) {
430
- for (const voice of this.cued)
465
+ for (const voice of this.cued) {
466
+ voice.written = true;
431
467
  beginFade(this, voice, { over: opts?.over });
468
+ }
432
469
  record(this, 'mute', () => this.mute(opts));
433
470
  }
434
471
  drop(subject) {
@@ -449,7 +486,7 @@ export class Mixer {
449
486
  // of a voice that does not name it is in `parters`, and a patch's own state in `motions`.
450
487
  const kept = releasing(this);
451
488
  const forget = (voice) => {
452
- leave(this, voice, subject, voice.subjects.get(subject));
489
+ leave(this, voice, subject, leftWith(this, voice, subject));
453
490
  voice.subjects.delete(subject);
454
491
  voice.motion?.release(subject, ...kept);
455
492
  voice.parts?.delete(subject);
@@ -462,8 +499,13 @@ export class Mixer {
462
499
  if (named !== undefined)
463
500
  for (const voice of [...named])
464
501
  forget(voice);
465
- for (const voice of this.gone)
502
+ const gone = goneIndex(this);
503
+ for (const voice of gone.general)
466
504
  forget(voice);
505
+ const goneNaming = gone.named.get(subject);
506
+ if (goneNaming !== undefined)
507
+ for (const voice of goneNaming)
508
+ forget(voice);
467
509
  if (this.owners !== null)
468
510
  for (const voice of this.owners)
469
511
  forget(voice);
@@ -485,6 +527,10 @@ export class Mixer {
485
527
  motion.release(subject, ...kept);
486
528
  record(this, 'drop', () => this.drop(subject));
487
529
  }
530
+ touch(subject) {
531
+ this.attached();
532
+ touch(this, subject);
533
+ }
488
534
  drain(tag) {
489
535
  const all = this.sent;
490
536
  if (all.length === 0)
@@ -509,7 +555,7 @@ export class Mixer {
509
555
  * is first asked, when probes start writing it.
510
556
  */
511
557
  restStamps = null;
512
- /** What a probe's record of rest is good for: moved by a new frame, version or relink. */
558
+ /** What a probe's record of rest is good for: moved by a new frame, version, relink, or stir. */
513
559
  restsEpoch = 0;
514
560
  restsFrame = Number.NaN;
515
561
  restsVersion = Number.NaN;
@@ -543,6 +589,9 @@ const methods = {
543
589
  foldWith,
544
590
  owedBy,
545
591
  foldLoci,
592
+ foldDetour,
593
+ foldSharers,
594
+ shared,
546
595
  read,
547
596
  contribution,
548
597
  keyed,
package/dist/motion.d.ts CHANGED
@@ -33,6 +33,8 @@ export type Moving<I, O, V extends Value> = Patch<I, O, void> & {
33
33
  * Where `subject` is and how fast it moves at voice time `at`, default the mix's latest frame,
34
34
  * counting every change due by then. With no time given, undefined until a frame of its voice
35
35
  * has met the subject, while the subject is still inside its stagger, and once that voice is gone.
36
+ * At any time, undefined for a subject the patch has never met or changed, or that the mix
37
+ * dropped.
36
38
  */
37
39
  read(subject: I, at?: number): Motion<V> | undefined;
38
40
  /**
package/dist/motion.js CHANGED
@@ -26,7 +26,6 @@ class MotionPatch {
26
26
  }
27
27
  form = 'motion';
28
28
  duration = 0;
29
- period = 0;
30
29
  get motion() {
31
30
  return this.motions.spec();
32
31
  }
@@ -191,6 +190,8 @@ export function spring(writes, opts) {
191
190
  const k = opts.stiffness ?? 170;
192
191
  const c = opts.damping ?? 26;
193
192
  const m = opts.mass ?? 1;
193
+ if (!(k > 0) || !(m > 0) || !(c >= 0))
194
+ throw new RangeError('blits: a spring takes a positive stiffness and mass, and damping of 0 or more');
194
195
  const settle = opts.settle ?? 1e-4;
195
196
  const w0 = Math.sqrt(k / m);
196
197
  const zeta = c / (2 * Math.sqrt(k * m));
@@ -215,6 +216,8 @@ export function spring(writes, opts) {
215
216
  */
216
217
  export function glide(writes, opts) {
217
218
  const ms = opts.ms ?? 325;
219
+ if (!(ms > 0))
220
+ throw new RangeError('blits: a glide takes a positive ms');
218
221
  const tau = ms / 1000;
219
222
  const settle = opts.settle ?? 1e-4;
220
223
  const shape = new GlideShape([COAST, settle, tau, 0, 0], { kind: 'glide', ms, settle }, opts, tau);
package/dist/motions.d.ts CHANGED
@@ -23,6 +23,8 @@ export interface Change {
23
23
  v?: number[];
24
24
  /** The mix clock when it was made, NaN where no mix was playing the patch. */
25
25
  made?: number;
26
+ /** The frame it was made in, which a rewind partitions by; NaN where no mix was playing the patch. */
27
+ seq?: number;
26
28
  }
27
29
  /**
28
30
  * The mix playing a patch, asked with its voice's id: an owner and an id, where a pair of closures
@@ -35,6 +37,8 @@ export interface MotionOwner {
35
37
  revive(id: number, subject: unknown): void;
36
38
  /** The mix clock at its latest frame; NaN before the first. */
37
39
  now(): number;
40
+ /** The number of the frame the mix is playing. */
41
+ seq(): number;
38
42
  /**
39
43
  * The host made a change, already given its time, which a seek makes again on the patch its
40
44
  * voice plays then: a voice a history store paged out comes back with a patch of its own.
@@ -43,9 +47,10 @@ export interface MotionOwner {
43
47
  /** History leaving memory for a subject, which a mix with a history store pages out. */
44
48
  page(id: number, stream: 'released' | 'stretch', subject: unknown, out: Paging[]): void;
45
49
  }
46
- /** A record of a motion patch's history leaving memory, at a mix time. */
50
+ /** A record of a motion patch's history leaving memory, at a mix time and frame. */
47
51
  export interface Paging {
48
52
  at: number;
53
+ seq: number;
49
54
  data: unknown;
50
55
  }
51
56
  /** A subject's run as plain data: its flags, its stretches oldest first, and changes still to apply. */
@@ -75,7 +80,7 @@ export interface Shape<I> {
75
80
  scalarEvery(): boolean | undefined;
76
81
  /** A tween's easing, which its law cannot hold; undefined, but present, on every other shape. */
77
82
  ease: Curve | undefined;
78
- /** A tween's seconds for a stretch the subject starts now; undefined, but present, on the rest. */
83
+ /** A tween's ms for a stretch the subject starts now; undefined, but present, on the rest. */
79
84
  ms: ((subject: I) => number) | undefined;
80
85
  /** The patch's kind and constants, as `Patch.motion` reports them. */
81
86
  spec(): MotionSpec;
@@ -86,7 +91,7 @@ export interface Shape<I> {
86
91
  * The one copy, read by `at` and by a mix's lane alike.
87
92
  *
88
93
  * After the law, each subject's run of `stride` numbers is its release time, its flags, a tween's
89
- * 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
94
+ * 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
90
95
  * 10k springs of one subject each, a buffer per field, a map lookup per sample and the law in an
91
96
  * object of its own took a call from 130 ns to 490.
92
97
  */
@@ -95,6 +100,8 @@ export declare class Motions<I> {
95
100
  private readonly numbers;
96
101
  /** Numbers by subject once a second is numbered: till then the one subject is found by `numbers`. */
97
102
  private readonly slots;
103
+ /** How many numbers have been given up, so a number kept elsewhere can tell it is stale. */
104
+ freed: number;
98
105
  /** Axes per subject; -1 until the first subject sets it. */
99
106
  n: number;
100
107
  private cap;
@@ -149,11 +156,11 @@ export declare class Motions<I> {
149
156
  */
150
157
  private released;
151
158
  /**
152
- * Forgets a subject, so a later ask starts it afresh from `from`. Given the mix time `at`, under
153
- * history, its run is kept for a rewind before then, until `reach` passes it.
159
+ * Forgets a subject, so a later ask starts it afresh from `from`. Given the mix time `at` and frame
160
+ * `seq`, under history, its run is kept for a rewind before then, until `reach` passes it.
154
161
  */
155
- release(subject: I, at?: number, reach?: number): void;
156
- /** Puts back each subject released after mix time `t`, as it stood when released. */
162
+ release(subject: I, at?: number, reach?: number, seq?: number): void;
163
+ /** Puts back each subject released after frame `seq`, as it stood when released. */
157
164
  private unrelease;
158
165
  /** Subject `s`'s run as it stands. */
159
166
  private runAt;
@@ -162,9 +169,9 @@ export declare class Motions<I> {
162
169
  /** Puts a subject's run back as `pack` or a release left it, replacing any it has now. */
163
170
  unpack(subject: I, run: Run): void;
164
171
  /** A released run a history store gave back, for a rewind to before its release. */
165
- reclaim(subject: I, at: number, run: Run): void;
172
+ reclaim(subject: I, at: number, seq: number, run: Run): void;
166
173
  /**
167
- * Stretches a history store gave back, each by the mix time it was in force by: each life of the
174
+ * Stretches a history store gave back, each by the frame it was in force by: each life of the
168
175
  * subject, ended by a release, takes those from its own, ahead of the earliest it still keeps.
169
176
  */
170
177
  restretch(subject: I, back: readonly Paging[]): void;
@@ -184,7 +191,7 @@ export declare class Motions<I> {
184
191
  /** Whether `law` holds this patch's law, number for number, as `Object.is` compares them. */
185
192
  hasLaw(law: Float64Array): boolean;
186
193
  /**
187
- * Copies subject `s`'s current stretch into `out` from `o` as its release time, its seconds, then
194
+ * Copies subject `s`'s current stretch into `out` from `o` as its release time, its ms, then
188
195
  * `x0`, `v0` and `to` per axis; false, copying nothing, while it has a change pending or an
189
196
  * earlier stretch kept, which only `sample` handles.
190
197
  */
@@ -200,7 +207,7 @@ export declare class Motions<I> {
200
207
  */
201
208
  sample(s: number, t: number, xo: Float64Array, vo: Float64Array): void;
202
209
  /**
203
- * Where subject `s`'s run starts in `runs` (its release time, then flags, seconds, `x0`, `v0`
210
+ * Where subject `s`'s run starts in `runs` (its release time, then flags, ms, `x0`, `v0`
204
211
  * and `to`) while it has nothing pending and no earlier stretch kept; -1 for any other.
205
212
  */
206
213
  bareAt(s: number): number;
@@ -238,10 +245,10 @@ export declare class Motions<I> {
238
245
  private flag;
239
246
  forget(s: number): void;
240
247
  /**
241
- * Takes back every change made after mix time `t`: each subject plays from its earliest stretch
248
+ * Takes back every change made after frame `seq`: each subject plays from its earliest stretch
242
249
  * kept, with the changes made by then queued to be applied again.
243
250
  */
244
- rewind(t: number): void;
251
+ rewind(seq: number): void;
245
252
  private cut;
246
253
  /** Where subject `s`'s run starts in `runs`. */
247
254
  base(s: number): number;
@@ -255,8 +262,8 @@ export declare class Motions<I> {
255
262
  private commit;
256
263
  private insert;
257
264
  /**
258
- * The mix time a stretch was in force by, which a store keys it under: when the change that
259
- * ended it was made, which falls in the same life of the subject.
265
+ * The mix time and frame a stretch was in force by, which a store keys it under: when the change
266
+ * that ended it was made, which falls in the same life of the subject.
260
267
  */
261
268
  private made;
262
269
  /** Lets go of stretches older than the one in force at `reading.horizon`. */
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,10 +1,14 @@
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
- import { started } from './held.js';
5
+ import { expireGone, keepGone } from './gone.js';
6
+ import { reorigin } from './held.js';
5
7
  import { ownerReading } from './owner.js';
6
8
  import { mixAt, place, repin, startOf } from './place.js';
7
9
  import { pageVoice } from './revive.js';
10
+ import { delist } from './roster.js';
11
+ import { scoredLeft, scoredRemove } from './scored.js';
8
12
  import { lapse } from './spans.js';
9
13
  import { unplay } from './strays.js';
10
14
  /** Numbers every mix's frames apart, so a blend's read is never taken for another mix's or frame's. */
@@ -29,6 +33,11 @@ export function move(mix, now) {
29
33
  }
30
34
  function moveTo(mix, now) {
31
35
  mix.now = now;
36
+ mix.looked = false;
37
+ if (mix.unshared) {
38
+ mix.unshared = false;
39
+ detour(mix);
40
+ }
32
41
  mix.frame = nextFrame();
33
42
  mix.reducedNow = mix.reduced;
34
43
  for (const a of mix.announced)
@@ -50,18 +59,19 @@ function moveTo(mix, now) {
50
59
  continue;
51
60
  if (Number.isNaN(voice.opened))
52
61
  voice.opened = now;
53
- if ((voice.state === 'live' || voice.state === 'frozen') && voice.outAt <= now)
54
- beginFade(mix, voice, { over: Number.isNaN(voice.outOver) ? undefined : voice.outOver }, Math.max(startOf(voice), voice.outAt));
55
62
  if (voice.state === 'pending' &&
56
63
  (voice.owner === null ? now : ownerReading(voice.owner, now)) >= voice.start) {
57
64
  voice.state = 'live';
58
- started(mix, voice);
65
+ reorigin(mix, voice, now);
59
66
  changed(mix, voice);
60
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));
61
71
  if (voice.state !== 'pending' && Number.isFinite(voice.span)) {
62
72
  const end = voice.span + voice.latest;
63
73
  if (voice.elapsedAt(now) >= end) {
64
- voice.play(true, now);
74
+ voice.play(true, now, mix.transport.seq);
65
75
  // The fade starts when the last pass ended, not at the frame that noticed, so it plays
66
76
  // the same at any frame rate and a read at another time can find it.
67
77
  if (voice.state === 'live') {
@@ -152,8 +162,12 @@ function prune(mix) {
152
162
  }
153
163
  voices.length = kept;
154
164
  }
155
- if (pruned)
165
+ if (pruned) {
156
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
+ }
157
171
  }
158
172
  /** The voice's place in a list in id order, or -1 where it is not there. */
159
173
  function indexOf(voices, voice) {
@@ -173,20 +187,18 @@ function indexOf(voices, voice) {
173
187
  }
174
188
  /** Lets go of a retired voice's place in the mix; whether it was among the voices naming no subject. */
175
189
  function leave(mix, voice) {
176
- if (mix.opts.history)
190
+ scoredRemove(mix, [voice]);
191
+ if (mix.opts.history) {
177
192
  mix.gone.push(voice);
178
- else
193
+ scoredLeft(mix, voice);
194
+ }
195
+ else {
179
196
  mix.parters.delete(voice);
197
+ mix.departed.add(mix, voice);
198
+ }
180
199
  unplay(mix, voice);
181
200
  changed(mix, voice);
182
- if (voice.named !== null)
183
- unindex(mix, voice);
184
- if (voice.holding !== null && mix.owners !== null)
185
- mix.owners = mix.owners.filter((v) => v !== voice);
186
- if (voice.spec.locus !== undefined)
187
- mix.loci--;
188
- if (voice.spec.anchor !== undefined)
189
- mix.anchored--;
201
+ delist(mix, voice);
190
202
  return voice.named === null;
191
203
  }
192
204
  /** Lets go of the voices that have left and that history no longer reaches. */
@@ -196,11 +208,10 @@ export function forget(mix) {
196
208
  // With a store a voice that has left pages out where it can, and stays where it cannot.
197
209
  if (mix.transport.pager !== null) {
198
210
  const reach = mix.now - history.ms;
199
- mix.gone = mix.gone.filter((v) => v.doneAt >= reach || !pageVoice(mix, v, reach));
211
+ keepGone(mix, (v) => v.doneAt >= reach || !pageVoice(mix, v, reach));
200
212
  }
201
213
  else {
202
- const reach = mix.transport.keepsFrom();
203
- mix.gone = mix.gone.filter((v) => v.doneAt >= reach);
214
+ expireGone(mix, mix.transport.keepsFrom());
204
215
  }
205
216
  }
206
217
  }