@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/mixer.js CHANGED
@@ -2,8 +2,9 @@ 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';
7
8
  import { GoneIndex, goneIndex } from './gone.js';
8
9
  import { held, shownOf, sinceOf, unreachedOf } from './held.js';
9
10
  import { leave, releasing } from './history.js';
@@ -23,9 +24,15 @@ import { seek } from './seek.js';
23
24
  import { Fitting, refit } from './spans.js';
24
25
  import { Store } from './store.js';
25
26
  import { record } from './tape.js';
27
+ import { touch } from './touch.js';
26
28
  import { announce, Transport } from './transport.js';
27
29
  import { none } from './voice.js';
28
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 };
29
36
  // biome-ignore lint/suspicious/noUnsafeDeclarationMerging: every member of `Methods` is installed below
30
37
  export class Mixer {
31
38
  kit;
@@ -41,6 +48,11 @@ export class Mixer {
41
48
  backward = false;
42
49
  /** True during a sync, so a control change it makes is recorded as showing in that frame. */
43
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;
44
56
  /**
45
57
  * Under `history` with `inputs`, the host fields patches read, copied each frame they changed. In
46
58
  * a projection reading back, the copy in force then.
@@ -56,20 +68,23 @@ export class Mixer {
56
68
  /** Taken off its transport, so it takes no more calls until a seek puts it back. */
57
69
  dropped = false;
58
70
  name;
59
- /** 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
+ */
60
75
  frame = nextFrame();
61
76
  /** Pending voices whose start the host gave, by its host time, kept where the rate puts it. */
62
77
  pins = null;
63
78
  wantsPose = false;
64
79
  /** The pose `pull` folds a subject into where it cannot read straight from the lanes. */
65
80
  scratch;
66
- /** The last array `pull` read, by position, with each subject's chain head, to skip the lookup. */
67
81
  /** Live voices by the earliest mix time `moveTo` would change each, a binary heap. */
68
82
  due = [];
69
83
  /** Voices retired since `moveTo` last pruned them, in the order they retired. */
70
84
  retired = [];
71
85
  /** Visit every voice each sync, as before the due queue; for tests that compare the two. */
72
86
  walkAll = false;
87
+ /** The last array `pull` read, by position, with each subject's chain head, to skip the lookup. */
73
88
  pulled = [];
74
89
  pulledHeads = [];
75
90
  /** Each remembered head's lane slot, and the `version` and `relinks` they were all current at. */
@@ -89,6 +104,9 @@ export class Mixer {
89
104
  /** Records a change to the mix's voices or controls. */
90
105
  stir() {
91
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();
92
110
  if (this.stirred)
93
111
  return;
94
112
  this.stirred = true;
@@ -113,8 +131,8 @@ export class Mixer {
113
131
  }
114
132
  /**
115
133
  * Per subject, the first record of the chain through every live voice that reaches it, or a stub
116
- * where none does. Relinked when `version` moves, which is whenever the list or a voice's pending
117
- * 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`.
118
136
  */
119
137
  chains = new Store();
120
138
  version = 0;
@@ -123,6 +141,8 @@ export class Mixer {
123
141
  named = new Store();
124
142
  /** Voices a subject has been faded out of, which `drop` looks in besides those that reach it. */
125
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();
126
146
  /**
127
147
  * Motion patches that may hold state for a subject no voice still reaching it plays, which `drop`
128
148
  * frees besides those voices' own: per subject, a patch a voice naming it left while another
@@ -136,6 +156,15 @@ export class Mixer {
136
156
  naming = 0;
137
157
  /** The voices in the list that name no subjects, in voice order. */
138
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;
139
168
  /** How many voices in the list carry a locus; with none, a fold allocates nothing. */
140
169
  loci = 0;
141
170
  /** What every motion patch it plays asks for its voice's time, holding this mix weakly. */
@@ -196,7 +225,7 @@ export class Mixer {
196
225
  this.names.forEach((k, i) => {
197
226
  this.slotOf.set(k, i);
198
227
  });
199
- this.lanes = opts.lanes === false ? null : new Lanes(laneHost(this));
228
+ this.lanes = (opts.lanes ?? defaults.lanes) ? new Lanes(laneHost(this)) : null;
200
229
  this.name = opts.name;
201
230
  const shared = opts.transport;
202
231
  if (shared !== undefined) {
@@ -315,6 +344,7 @@ export class Mixer {
315
344
  return book(this, opts);
316
345
  }
317
346
  probe(subject, out) {
347
+ this.looked = true;
318
348
  const pose = this.fold(subject, out);
319
349
  this.keep(subject, pose, out);
320
350
  // Where the lanes answer `atRest` from their own values, there is nothing to keep.
@@ -323,6 +353,7 @@ export class Mixer {
323
353
  return pose;
324
354
  }
325
355
  pull(subjects, into) {
356
+ this.looked = true;
326
357
  pull(this, subjects, into);
327
358
  }
328
359
  seek(time) {
@@ -431,8 +462,10 @@ export class Mixer {
431
462
  return motion !== undefined && voice.elapsedAt(now) >= voice.latest && motion.landed();
432
463
  }
433
464
  mute(opts) {
434
- for (const voice of this.cued)
465
+ for (const voice of this.cued) {
466
+ voice.written = true;
435
467
  beginFade(this, voice, { over: opts?.over });
468
+ }
436
469
  record(this, 'mute', () => this.mute(opts));
437
470
  }
438
471
  drop(subject) {
@@ -453,7 +486,7 @@ export class Mixer {
453
486
  // of a voice that does not name it is in `parters`, and a patch's own state in `motions`.
454
487
  const kept = releasing(this);
455
488
  const forget = (voice) => {
456
- leave(this, voice, subject, voice.subjects.get(subject));
489
+ leave(this, voice, subject, leftWith(this, voice, subject));
457
490
  voice.subjects.delete(subject);
458
491
  voice.motion?.release(subject, ...kept);
459
492
  voice.parts?.delete(subject);
@@ -494,6 +527,10 @@ export class Mixer {
494
527
  motion.release(subject, ...kept);
495
528
  record(this, 'drop', () => this.drop(subject));
496
529
  }
530
+ touch(subject) {
531
+ this.attached();
532
+ touch(this, subject);
533
+ }
497
534
  drain(tag) {
498
535
  const all = this.sent;
499
536
  if (all.length === 0)
@@ -518,7 +555,7 @@ export class Mixer {
518
555
  * is first asked, when probes start writing it.
519
556
  */
520
557
  restStamps = null;
521
- /** 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. */
522
559
  restsEpoch = 0;
523
560
  restsFrame = Number.NaN;
524
561
  restsVersion = Number.NaN;
@@ -552,6 +589,9 @@ const methods = {
552
589
  foldWith,
553
590
  owedBy,
554
591
  foldLoci,
592
+ foldDetour,
593
+ foldSharers,
594
+ shared,
555
595
  read,
556
596
  contribution,
557
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,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
+ }