@msb235/blits 0.13.0 → 0.15.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 (178) hide show
  1. package/CHANGELOG.md +183 -0
  2. package/README.md +20 -3
  3. package/dist/ahead.d.ts +26 -0
  4. package/dist/ahead.js +264 -0
  5. package/dist/around.d.ts +11 -0
  6. package/dist/around.js +32 -0
  7. package/dist/bare.js +15 -7
  8. package/dist/blank.d.ts +10 -0
  9. package/dist/blank.js +26 -0
  10. package/dist/blend.d.ts +2 -1
  11. package/dist/blend.js +1 -6
  12. package/dist/bound.d.ts +70 -0
  13. package/dist/bound.js +351 -0
  14. package/dist/chain.d.ts +2 -1
  15. package/dist/chain.js +7 -5
  16. package/dist/changes.d.ts +24 -0
  17. package/dist/changes.js +189 -0
  18. package/dist/channels.d.ts +39 -13
  19. package/dist/channels.js +14 -5
  20. package/dist/closed.d.ts +1 -0
  21. package/dist/closed.js +45 -1
  22. package/dist/color.d.ts +12 -10
  23. package/dist/color.js +25 -25
  24. package/dist/columns.js +7 -3
  25. package/dist/contribution.d.ts +30 -0
  26. package/dist/contribution.js +374 -0
  27. package/dist/copyout.d.ts +10 -0
  28. package/dist/copyout.js +69 -24
  29. package/dist/crowd.d.ts +8 -2
  30. package/dist/crowd.js +4 -5
  31. package/dist/cue.d.ts +8 -1
  32. package/dist/cue.js +47 -14
  33. package/dist/cut.d.ts +8 -0
  34. package/dist/cut.js +4 -0
  35. package/dist/drop.d.ts +9 -0
  36. package/dist/drop.js +84 -0
  37. package/dist/due.d.ts +24 -5
  38. package/dist/due.js +113 -33
  39. package/dist/easing.d.ts +4 -4
  40. package/dist/easing.js +3 -3
  41. package/dist/everyone.d.ts +3 -2
  42. package/dist/everyone.js +3 -3
  43. package/dist/fade.d.ts +2 -1
  44. package/dist/fade.js +2 -1
  45. package/dist/fill.d.ts +3 -2
  46. package/dist/fill.js +51 -19
  47. package/dist/fit.d.ts +44 -26
  48. package/dist/fit.js +10 -9
  49. package/dist/fling.d.ts +66 -0
  50. package/dist/fling.js +162 -0
  51. package/dist/fold.d.ts +4 -37
  52. package/dist/fold.js +5 -512
  53. package/dist/gather.d.ts +12 -7
  54. package/dist/gather.js +179 -44
  55. package/dist/gone.js +2 -2
  56. package/dist/grid.d.ts +47 -0
  57. package/dist/grid.js +247 -0
  58. package/dist/handle.d.ts +3 -0
  59. package/dist/handle.js +19 -8
  60. package/dist/held.d.ts +2 -1
  61. package/dist/history.d.ts +6 -5
  62. package/dist/history.js +9 -9
  63. package/dist/hosts.js +9 -15
  64. package/dist/index.d.ts +5 -4
  65. package/dist/keyfill.d.ts +1 -1
  66. package/dist/keyfill.js +5 -3
  67. package/dist/keyrows.d.ts +12 -0
  68. package/dist/keyrows.js +99 -12
  69. package/dist/lane.d.ts +36 -6
  70. package/dist/lane.js +32 -1
  71. package/dist/lanes.d.ts +23 -8
  72. package/dist/lanes.js +29 -8
  73. package/dist/leavings.d.ts +2 -2
  74. package/dist/leavings.js +1 -1
  75. package/dist/lives.d.ts +47 -0
  76. package/dist/lives.js +180 -0
  77. package/dist/meet.d.ts +2 -2
  78. package/dist/meet.js +90 -4
  79. package/dist/mixer.d.ts +61 -35
  80. package/dist/mixer.js +64 -169
  81. package/dist/motion.d.ts +19 -106
  82. package/dist/motion.js +60 -13
  83. package/dist/motions.d.ts +62 -150
  84. package/dist/motions.js +57 -369
  85. package/dist/move.js +24 -1
  86. package/dist/moved.d.ts +1 -1
  87. package/dist/moved.js +16 -7
  88. package/dist/named.d.ts +0 -1
  89. package/dist/named.js +20 -2
  90. package/dist/numbers.d.ts +2 -0
  91. package/dist/numbers.js +4 -0
  92. package/dist/owed.d.ts +2 -0
  93. package/dist/owed.js +4 -0
  94. package/dist/pack.d.ts +3 -1
  95. package/dist/pack.js +4 -3
  96. package/dist/patch.d.ts +7 -3
  97. package/dist/patch.js +6 -2
  98. package/dist/places.d.ts +53 -0
  99. package/dist/places.js +171 -0
  100. package/dist/plot.d.ts +12 -0
  101. package/dist/plot.js +52 -0
  102. package/dist/project.d.ts +4 -4
  103. package/dist/project.js +9 -211
  104. package/dist/pull.js +11 -8
  105. package/dist/qualify.js +84 -21
  106. package/dist/reading.d.ts +6 -0
  107. package/dist/reading.js +5 -0
  108. package/dist/record.d.ts +5 -1
  109. package/dist/record.js +4 -2
  110. package/dist/relink.d.ts +1 -1
  111. package/dist/restored.d.ts +2 -2
  112. package/dist/restored.js +1 -1
  113. package/dist/rests.d.ts +9 -0
  114. package/dist/rests.js +76 -0
  115. package/dist/revive.d.ts +3 -2
  116. package/dist/revive.js +2 -2
  117. package/dist/{locus.d.ts → role.d.ts} +10 -9
  118. package/dist/{locus.js → role.js} +3 -3
  119. package/dist/roles.d.ts +10 -0
  120. package/dist/roles.js +158 -0
  121. package/dist/roster.js +3 -3
  122. package/dist/rotation.d.ts +1 -1
  123. package/dist/rotation.js +1 -1
  124. package/dist/rows.js +1 -1
  125. package/dist/sample.d.ts +2 -2
  126. package/dist/sample.js +33 -7
  127. package/dist/seek.js +13 -5
  128. package/dist/setting.d.ts +27 -0
  129. package/dist/setting.js +55 -0
  130. package/dist/shared.d.ts +1 -1
  131. package/dist/shared.js +1 -1
  132. package/dist/signals.d.ts +64 -32
  133. package/dist/signals.js +20 -18
  134. package/dist/stack.d.ts +5 -5
  135. package/dist/stack.js +5 -5
  136. package/dist/standing.d.ts +2 -1
  137. package/dist/standing.js +3 -2
  138. package/dist/store.d.ts +9 -0
  139. package/dist/store.js +24 -0
  140. package/dist/strays.d.ts +2 -1
  141. package/dist/strays.js +21 -14
  142. package/dist/stretch.d.ts +112 -0
  143. package/dist/stretch.js +9 -0
  144. package/dist/subject.d.ts +142 -0
  145. package/dist/subject.js +1 -0
  146. package/dist/tape.d.ts +9 -5
  147. package/dist/tape.js +5 -1
  148. package/dist/then.d.ts +9 -0
  149. package/dist/then.js +57 -0
  150. package/dist/ticker.d.ts +4 -4
  151. package/dist/transport.d.ts +0 -8
  152. package/dist/transport.js +0 -4
  153. package/dist/turns.d.ts +4 -3
  154. package/dist/turns.js +4 -3
  155. package/dist/types/arc.d.ts +12 -11
  156. package/dist/types/channel.d.ts +12 -8
  157. package/dist/types/history.d.ts +35 -19
  158. package/dist/types/mix.d.ts +222 -161
  159. package/dist/types/motion.d.ts +151 -0
  160. package/dist/types/motion.js +1 -0
  161. package/dist/types/patch.d.ts +69 -34
  162. package/dist/types/place.d.ts +51 -36
  163. package/dist/types/transport.d.ts +20 -18
  164. package/dist/types/voice.d.ts +145 -72
  165. package/dist/types/watch.d.ts +88 -40
  166. package/dist/types.d.ts +1 -0
  167. package/dist/voice.d.ts +9 -141
  168. package/dist/voice.js +30 -79
  169. package/dist/warn.d.ts +2 -0
  170. package/dist/warn.js +1 -1
  171. package/dist/watch.d.ts +2 -2
  172. package/dist/watch.js +5 -3
  173. package/dist/wave.d.ts +4 -3
  174. package/dist/wave.js +3 -2
  175. package/dist/weigh.d.ts +7 -1
  176. package/dist/weigh.js +27 -0
  177. package/docs/schema.html +223 -37
  178. package/package.json +6 -2
package/dist/motions.js CHANGED
@@ -1,52 +1,20 @@
1
+ import { applied, change, commit, cut, dueTo, insert, peek, queue, rewind } from './changes.js';
1
2
  import { closed } from './closed.js';
3
+ import { made, pack, prune, reclaim, release, restretch, runAt, runThen, unpack, unrelease, } from './lives.js';
2
4
  import { absent, Numbers } from './numbers.js';
3
5
  import { reading } from './reading.js';
4
6
  import { Store } from './store.js';
7
+ import { numbers, } from './stretch.js';
5
8
  import { sloped } from './tweened.js';
6
- /**
7
- * A patch's law, `[form, settle, k1, k2, k3]`: which closed form its stretches follow, its `settle`,
8
- * and up to three constants that form reads. It heads the patch's `runs`, so `solve` finds them in
9
- * the buffer a sample already reads rather than in an object of their own.
10
- */
11
- const HEAD = 5;
12
- /** A run that shares nothing with the patch's own. */
13
- const runOf = (r) => ({
14
- flags: r.flags,
15
- stretches: r.stretches.map((seg) => ({ ...seg })),
16
- pending: r.pending?.map((c) => ({ ...c })),
17
- });
18
- /** `segs`, in time order, ahead of `list`: those older than its first, one per time. */
19
- function ahead(segs, list) {
20
- const first = list[0].at;
21
- const out = [];
22
- for (const seg of [...segs].sort((x, y) => x.at - y.at))
23
- if (seg.at < first && out[out.length - 1]?.at !== seg.at)
24
- out.push(seg);
25
- return out.length === 0 ? list : [...out, ...list];
26
- }
9
+ const HEAD = 5 /* Law.HEAD */;
27
10
  /** Where every patch's `sample` leaves a value on its way to the mix; none outlives its call. */
28
11
  let shared = { xs: new Float64Array(4), vs: new Float64Array(4) };
29
12
  /** What a patch's `xs` and `vs` are before its first subject sets its axes. */
30
13
  const unsized = new Float64Array(0);
31
- /** Where `applied` reads a stretch at the time a change lands; copied out before it returns. */
32
- let landing = { x: new Float64Array(4), v: new Float64Array(4) };
33
- /** Where `numbers` gathers what it copies, kept an array of doubles so every copy is one too. */
34
- const gathered = [0.5];
35
- /** `n` numbers of `from` from `at`, as an array of their own. */
36
- function numbers(from, at, n) {
37
- // `Array.from` walks a typed array by its iterator: about 500 B a call for three axes.
38
- for (let i = 0; i < n; i++)
39
- gathered[i] = from[at + i];
40
- return gathered.slice(0, n);
41
- }
42
- /**
43
- * A subject's flags: its value is a number; it has changes pending; it has older stretches; the
44
- * stretch it is playing was found landed, which holds until another replaces it.
45
- */
46
- const SCALAR = 1;
47
- const PENDING = 2;
48
- const OLDER = 4;
49
- const LANDED = 8;
14
+ const SCALAR = 1 /* RunFlag.SCALAR */;
15
+ const PENDING = 2 /* RunFlag.PENDING */;
16
+ const OLDER = 4 /* RunFlag.OLDER */;
17
+ const LANDED = 8 /* RunFlag.LANDED */;
50
18
  const wrongKind = (channel, holdsNumber) => new Error(`blits: channel ${channel} holds ${holdsNumber ? 'a number' : 'an array'}, and this motion patch gives a subject ${holdsNumber ? 'an array' : 'a number'}`);
51
19
  /**
52
20
  * A motion patch's subjects: the stretch each is playing, in one flat array by a number the patch
@@ -58,6 +26,7 @@ const wrongKind = (channel, holdsNumber) => new Error(`blits: channel ${channel}
58
26
  * 10k springs of one subject each, a buffer per field, a map lookup per sample and the law in an
59
27
  * object of its own took a call from 130 ns to 490.
60
28
  */
29
+ // biome-ignore lint/suspicious/noUnsafeDeclarationMerging: every member of `Methods` is installed below
61
30
  export class Motions {
62
31
  shape;
63
32
  numbers = new Numbers(this);
@@ -68,6 +37,7 @@ export class Motions {
68
37
  /** Axes per subject; -1 until the first subject sets it. */
69
38
  n = -1;
70
39
  cap = 0;
40
+ /** Numbers in each subject's run; a run starts at `base`. Written only here. */
71
41
  stride = 0;
72
42
  /**
73
43
  * The law, then each subject's run; empty until the first subject, the law read from `shape`.
@@ -124,9 +94,19 @@ export class Motions {
124
94
  const known = this.known(subject);
125
95
  if (known !== undefined)
126
96
  return known;
97
+ const seg = this.fresh(subject);
98
+ const s = this.take(subject);
99
+ this.runs[this.base(s) + 1] = this.freshFlags;
100
+ this.write(s, seg);
101
+ return s;
102
+ }
103
+ /** The flags of the run `fresh` last started. */
104
+ freshFlags = 0;
105
+ /** The stretch a subject met afresh starts with, its axes and the law set up for it, numbering no one. */
106
+ fresh(subject) {
127
107
  const x = this.shape.from(subject);
128
108
  const v = this.shape.velocity(subject, x);
129
- const to = this.shape.aim(x, v, null, subject);
109
+ const to = this.shape.aim(x, v, null, subject, false);
130
110
  const n = this.n < 0 ? x.length : this.n;
131
111
  this.check(x, n);
132
112
  this.check(v, n);
@@ -136,10 +116,14 @@ export class Motions {
136
116
  throw wrongKind(this.channel, this.holdsNumber);
137
117
  if (this.n < 0)
138
118
  this.size(n);
139
- const s = this.take(subject);
140
- this.runs[this.base(s) + 1] = scalar ? SCALAR : 0;
141
- this.write(s, { at: 0, ms: this.shape.ms?.(subject) ?? 0, x0: x, v0: v, to });
142
- return s;
119
+ this.grow(1);
120
+ this.freshFlags = scalar ? SCALAR : 0;
121
+ return { at: 0, ms: this.shape.ms?.(subject) ?? 0, x0: x, v0: v, to };
122
+ }
123
+ /** The run `fresh` starts, as plain data. */
124
+ freshRun(subject) {
125
+ const seg = this.fresh(subject);
126
+ return { flags: this.freshFlags, stretches: [seg], pending: undefined };
143
127
  }
144
128
  /** Sets the axes every subject moves on, at the first. */
145
129
  size(n) {
@@ -175,120 +159,10 @@ export class Motions {
175
159
  * rewind before the release plays them on from there. Empty without history.
176
160
  */
177
161
  released = [];
178
- /**
179
- * Forgets a subject, so a later ask starts it afresh from `from`. Given the mix time `at` and frame
180
- * `seq`, under history, its run is kept for a rewind before then, until `reach` passes it.
181
- */
182
- release(subject, at, reach = Number.NEGATIVE_INFINITY, seq = Number.NaN) {
183
- const s = this.known(subject);
184
- if (s === undefined)
185
- return;
186
- if (at !== undefined) {
187
- const kept = [];
188
- for (const e of this.released)
189
- if (e.at >= reach)
190
- kept.push(e);
191
- else
192
- this.owner?.page(this.ownerId, 'released', e.subject, [
193
- { at: e.at, seq: e.seq, data: runOf(e) },
194
- ]);
195
- this.released = kept;
196
- this.released.push({ subject, at, seq, ...this.runAt(s) });
197
- }
198
- this.slots.delete(subject);
199
- this.numbers.release(s);
200
- }
201
- /** Puts back each subject released after frame `seq`, as it stood when released. */
202
- unrelease(seq) {
203
- const back = this.released.filter((e) => e.seq > seq).sort((a, b) => a.seq - b.seq);
204
- if (back.length === 0)
205
- return;
206
- this.released = this.released.filter((e) => e.seq <= seq);
207
- const done = new Set();
208
- for (const e of back) {
209
- if (done.has(e.subject))
210
- continue;
211
- done.add(e.subject);
212
- this.unpack(e.subject, e);
213
- }
214
- }
215
- /** Subject `s`'s run as it stands. */
216
- runAt(s) {
217
- const older = this.older?.[s];
218
- const pending = this.pending?.[s];
219
- return {
220
- flags: this.runs[this.base(s) + 1],
221
- stretches: older === undefined ? [this.latest(s)] : [...older, this.latest(s)],
222
- pending: pending === undefined ? undefined : [...pending],
223
- };
224
- }
225
162
  /** Whether the patch holds a run for `subject`. */
226
163
  holds(subject) {
227
164
  return this.known(subject) !== undefined;
228
165
  }
229
- /** A subject's run as plain data, for a voice leaving memory; undefined where it has none. */
230
- pack(subject) {
231
- const s = this.known(subject);
232
- return s === undefined ? undefined : runOf(this.runAt(s));
233
- }
234
- /** Puts a subject's run back as `pack` or a release left it, replacing any it has now. */
235
- unpack(subject, run) {
236
- const now = this.known(subject);
237
- if (now !== undefined) {
238
- this.slots.delete(subject);
239
- this.numbers.release(now);
240
- }
241
- if (this.n < 0)
242
- this.size(run.stretches[0].x0.length);
243
- const s = this.take(subject);
244
- this.runs[this.base(s) + 1] = run.flags & ~(OLDER | PENDING);
245
- const latest = run.stretches[run.stretches.length - 1];
246
- this.write(s, latest);
247
- if (run.stretches.length > 1) {
248
- if (this.older === null)
249
- this.older = [undefined];
250
- this.older[s] = run.stretches.slice(0, -1);
251
- this.flag(s, OLDER, true);
252
- }
253
- if (run.pending !== undefined) {
254
- if (this.pending === null)
255
- this.pending = [undefined];
256
- this.pending[s] = run.pending;
257
- this.flag(s, PENDING, true);
258
- }
259
- }
260
- /** A released run a history store gave back, for a rewind to before its release. */
261
- reclaim(subject, at, seq, run) {
262
- if (this.released.some((e) => e.seq === seq && Object.is(e.subject, subject)))
263
- return;
264
- this.released.push({ subject, at, seq, ...run });
265
- }
266
- /**
267
- * Stretches a history store gave back, each by the frame it was in force by: each life of the
268
- * subject, ended by a release, takes those from its own, ahead of the earliest it still keeps.
269
- */
270
- restretch(subject, back) {
271
- const lives = this.released
272
- .filter((e) => Object.is(e.subject, subject))
273
- .sort((x, y) => x.seq - y.seq);
274
- let from = Number.NEGATIVE_INFINITY;
275
- const within = (to) => back.filter((r) => r.seq > from && r.seq <= to).map((r) => r.data);
276
- for (const e of lives) {
277
- e.stretches = ahead(within(e.seq), e.stretches);
278
- from = e.seq;
279
- }
280
- const s = this.known(subject);
281
- if (s === undefined)
282
- return;
283
- const older = this.older?.[s] ?? [];
284
- const list = ahead(within(Number.POSITIVE_INFINITY), [...older, this.latest(s)]);
285
- if (list.length === older.length + 1)
286
- return;
287
- if (this.older === null)
288
- this.older = [undefined];
289
- this.older[s] = list.slice(0, -1);
290
- this.flag(s, OLDER, true);
291
- }
292
166
  /**
293
167
  * Told, with `watchId`, whenever a subject's stretch or what is pending for it changes, so a mix
294
168
  * keeping a copy of it knows to read it again. Set by the mix, cleared when its voice retires. An
@@ -339,7 +213,11 @@ export class Motions {
339
213
  }
340
214
  /** The value `at` hands the mix: the first axis for a number, a fresh array otherwise. */
341
215
  value(s, xs) {
342
- if ((this.runs[this.base(s) + 1] & SCALAR) !== 0)
216
+ return this.valueOf(this.runs[this.base(s) + 1], xs);
217
+ }
218
+ /** The value `at` hands the mix for a run with `flags`. */
219
+ valueOf(flags, xs) {
220
+ if ((flags & SCALAR) !== 0)
343
221
  return xs[0];
344
222
  const n = this.n;
345
223
  const out = new Array(n);
@@ -464,79 +342,6 @@ export class Motions {
464
342
  }
465
343
  return { x, v, s };
466
344
  }
467
- /**
468
- * Queues a retarget or push, untimed at the latest frame, for `subject`'s next read past its time.
469
- * The queue keeps the changes with a time in time order, ahead of any still waiting for one.
470
- */
471
- change(subject, c) {
472
- const back = this.owner === null ? Number.NaN : this.owner.revive(this.ownerId, subject);
473
- const s = this.slot(subject);
474
- this.check(c.to, this.n);
475
- this.check(c.v, this.n);
476
- c.made = this.owner === null ? Number.NaN : this.owner.now();
477
- c.seq = this.owner === null ? Number.NaN : this.owner.seq();
478
- if (c.at === undefined) {
479
- const at = this.frame(subject);
480
- if (!Number.isNaN(at))
481
- c.at = at;
482
- else if (!Number.isNaN(back))
483
- c.at = back;
484
- }
485
- const list = this.pending?.[s];
486
- if (list === undefined) {
487
- if (this.pending === null)
488
- this.pending = [undefined];
489
- this.pending[s] = [c];
490
- this.flag(s, PENDING, true);
491
- }
492
- else if (c.at === undefined)
493
- list.push(c);
494
- else
495
- this.queue(list, c, c.at);
496
- reading.moved++;
497
- const made = { at: c.at, to: c.to, v: c.v };
498
- this.owner?.changed(this.ownerId, this, subject, made);
499
- }
500
- /** Files a change with a time after every one due by then, ahead of those still waiting for one. */
501
- queue(list, c, at) {
502
- let i = list.length;
503
- while (i > 0) {
504
- const before = list[i - 1].at;
505
- if (before !== undefined && before <= at)
506
- break;
507
- i--;
508
- }
509
- list.splice(i, 0, c);
510
- }
511
- /** Every change due by `t` applied to a copy of the stretch playing then, committing nothing. */
512
- peek(s, t, xo, vo) {
513
- let seg = this.playing(s, t);
514
- const list = this.dueTo(s);
515
- if (list !== undefined)
516
- for (const change of list) {
517
- const a = change.at;
518
- if (a === undefined || !(a <= t))
519
- break;
520
- if (a >= seg.at)
521
- seg = this.applied(s, seg, change, a);
522
- }
523
- this.evaluateSegment(seg, t, xo, vo);
524
- }
525
- /** Subject `s`'s changes waiting, with those a read ahead's tape made filed among them by time. */
526
- dueTo(s) {
527
- const own = this.pending?.[s];
528
- const ahead = reading.ahead?.get(this);
529
- if (ahead === undefined)
530
- return own;
531
- const subject = this.numbers.subject(s);
532
- const made = subject === absent ? undefined : ahead.get(subject);
533
- if (made === undefined)
534
- return own;
535
- if (own === undefined)
536
- return made;
537
- // The patch's own were made first, and a stable sort keeps them first at one time.
538
- return [...own.filter((c) => c.at !== undefined), ...made].sort((a, b) => a.at - b.at);
539
- }
540
345
  check(a, n) {
541
346
  if (a !== undefined && a.length !== n)
542
347
  throw new Error('blits: a motion patch moves every subject on the same number of axes');
@@ -556,54 +361,6 @@ export class Motions {
556
361
  if (this.causes !== null)
557
362
  this.causes[s] = undefined;
558
363
  }
559
- /**
560
- * Takes back every change made after frame `seq`: each subject plays from its earliest stretch
561
- * kept, with the changes made by then queued to be applied again.
562
- */
563
- rewind(seq) {
564
- this.unrelease(seq);
565
- for (let s = 0; s < this.numbers.size; s++)
566
- if (this.numbers.subject(s) !== absent)
567
- this.cut(s, seq);
568
- }
569
- cut(s, seq) {
570
- const older = this.older?.[s];
571
- const pending = this.pending?.[s];
572
- const current = this.latest(s);
573
- const stretches = older === undefined ? [current] : [...older, current];
574
- const kept = [];
575
- let cut = false;
576
- for (const c of [...stretches.slice(1).map((seg) => seg.change), ...(pending ?? [])])
577
- if (c === undefined || c.seq === undefined || !(c.seq > seq)) {
578
- if (c !== undefined)
579
- kept.push(c);
580
- }
581
- else
582
- cut = true;
583
- if (!cut)
584
- return;
585
- if (older !== undefined) {
586
- this.older[s] = undefined;
587
- this.flag(s, OLDER, false);
588
- }
589
- this.write(s, stretches[0]);
590
- // Applied again in the order they take effect; one never stamped waits for the next read.
591
- const timed = kept
592
- .filter((c) => c.at !== undefined)
593
- .sort((a, b) => a.at - b.at);
594
- const list = [...timed, ...kept.filter((c) => c.at === undefined)];
595
- if (list.length === 0) {
596
- if (pending !== undefined) {
597
- this.pending[s] = undefined;
598
- this.flag(s, PENDING, false);
599
- }
600
- return;
601
- }
602
- if (this.pending === null)
603
- this.pending = [undefined];
604
- this.pending[s] = list;
605
- this.flag(s, PENDING, true);
606
- }
607
364
  /** Where subject `s`'s run starts in `runs`. */
608
365
  base(s) {
609
366
  return HEAD + s * this.stride;
@@ -665,98 +422,6 @@ export class Motions {
665
422
  }
666
423
  return list[lo];
667
424
  }
668
- applied(s, seg, change, at) {
669
- const n = this.n;
670
- if (landing.x.length < n)
671
- landing = { x: new Float64Array(n), v: new Float64Array(n) };
672
- const { x, v } = landing;
673
- this.evaluateSegment(seg, at, x, v);
674
- const xs = numbers(x, 0, n);
675
- const vs = change.v ?? numbers(v, 0, n);
676
- const subject = this.numbers.subject(s);
677
- const known = subject === absent ? undefined : subject;
678
- const to = this.shape.aim(xs, vs, change.to ?? seg.to, known);
679
- const ms = subject === absent ? seg.ms : (this.shape.ms?.(known) ?? 0);
680
- return { at, ms, x0: xs, v0: vs, to, change };
681
- }
682
- commit(s, t) {
683
- const list = this.pending[s];
684
- // The first read of a subject no frame had met: those waiting for a time take this one.
685
- if (list[list.length - 1].at === undefined) {
686
- let i = list.length;
687
- while (i > 0 && list[i - 1].at === undefined)
688
- i--;
689
- for (const c of list.splice(i)) {
690
- c.at = t;
691
- this.queue(list, c, t);
692
- }
693
- }
694
- while (list.length > 0) {
695
- const change = list[0];
696
- const a = change.at;
697
- if (a > t)
698
- break;
699
- list.shift();
700
- this.insert(s, this.applied(s, this.playing(s, a), change, a));
701
- }
702
- if (list.length === 0) {
703
- this.pending[s] = undefined;
704
- this.flag(s, PENDING, false);
705
- }
706
- }
707
- insert(s, seg) {
708
- let list = this.older?.[s];
709
- if (list === undefined) {
710
- list = [];
711
- if (this.older === null)
712
- this.older = [undefined];
713
- this.older[s] = list;
714
- this.flag(s, OLDER, true);
715
- }
716
- if (seg.at >= this.runs[this.base(s)]) {
717
- list.push(this.latest(s));
718
- this.write(s, seg);
719
- return;
720
- }
721
- let i = list.length;
722
- while (i > 0 && list[i - 1].at > seg.at)
723
- i--;
724
- list.splice(i, 0, seg);
725
- }
726
- /**
727
- * The mix time and frame a stretch was in force by, which a store keys it under: when the change
728
- * that ended it was made, which falls in the same life of the subject.
729
- */
730
- made(c) {
731
- const made = c?.made;
732
- if (made === undefined || Number.isNaN(made))
733
- return { at: this.owner?.now() ?? Number.NaN, seq: this.owner?.seq() ?? Number.NaN };
734
- return { at: made, seq: c?.seq ?? Number.NaN };
735
- }
736
- /** Lets go of stretches older than the one in force at `reading.horizon`. */
737
- prune(s) {
738
- const list = this.older?.[s];
739
- if (list === undefined)
740
- return;
741
- let n = 0;
742
- while (n < list.length &&
743
- (n + 1 < list.length ? list[n + 1].at : this.runs[this.base(s)]) <=
744
- reading.horizon)
745
- n++;
746
- if (n === 0)
747
- return;
748
- const out = list.splice(0, n);
749
- const subject = this.numbers.subject(s);
750
- if (this.owner !== null && subject !== absent)
751
- this.owner.page(this.ownerId, 'stretch', subject, out.map((seg, i) => {
752
- const next = out[i + 1] ?? list[0];
753
- return { ...this.made(next === undefined ? this.causes?.[s] : next.change), data: seg };
754
- }));
755
- if (list.length === 0) {
756
- this.older[s] = undefined;
757
- this.flag(s, OLDER, false);
758
- }
759
- }
760
425
  evaluateSegment(seg, t, xo, vo) {
761
426
  this.evaluate(seg.at, seg.ms, seg.x0, 0, seg.v0, 0, seg.to, 0, t, xo, vo);
762
427
  }
@@ -764,3 +429,26 @@ export class Motions {
764
429
  return closed(this.runs, this.shape.ease, this.n, at, ms, x0, x, v0, v, to, g, t, xo, vo);
765
430
  }
766
431
  }
432
+ const methods = {
433
+ release,
434
+ unrelease,
435
+ runAt,
436
+ pack,
437
+ unpack,
438
+ reclaim,
439
+ runThen,
440
+ restretch,
441
+ made,
442
+ prune,
443
+ change,
444
+ queue,
445
+ peek,
446
+ dueTo,
447
+ rewind,
448
+ cut,
449
+ applied,
450
+ commit,
451
+ insert,
452
+ };
453
+ for (const [name, value] of Object.entries(methods))
454
+ Object.defineProperty(Motions.prototype, name, { value, writable: true, configurable: true });
package/dist/move.js CHANGED
@@ -9,7 +9,7 @@ import { begin } from './held.js';
9
9
  import { noted } from './history.js';
10
10
  import { leave as leaveClock } from './origin.js';
11
11
  import { ownerReading } from './owner.js';
12
- import { mixAt, place, repin, startOf } from './place.js';
12
+ import { mixAt, place, repin, startAt, startOf } from './place.js';
13
13
  import { pageVoice } from './revive.js';
14
14
  import { delist } from './roster.js';
15
15
  import { scoredLeft, scoredRemove } from './scored.js';
@@ -47,6 +47,8 @@ function moveTo(mix, now) {
47
47
  for (const a of mix.announced)
48
48
  if (Number.isNaN(a.at))
49
49
  a.at = mix.u;
50
+ if (mix.waking)
51
+ wake(mix, now);
50
52
  if (mix.pins !== null && mix.pins.size > 0)
51
53
  repin(mix);
52
54
  if (mix.anchored > 0)
@@ -137,10 +139,31 @@ function moveTo(mix, now) {
137
139
  if (mix.sharers.length > 0 && !mix.projecting)
138
140
  outgrow(mix);
139
141
  mix.stirred = false;
142
+ mix.asleep = false;
140
143
  if (mix.retired.length > 0)
141
144
  prune(mix);
142
145
  forget(mix);
143
146
  }
147
+ /** Starts each voice cued while the mix slept at `now`, the first sync after its cue. */
148
+ function wake(mix, now) {
149
+ mix.waking = false;
150
+ for (const voice of mix.cued) {
151
+ if (!voice.waking)
152
+ continue;
153
+ voice.waking = false;
154
+ if (voice.state !== 'pending')
155
+ continue;
156
+ startAt(mix, voice, now);
157
+ // Where a seek back before the cue puts it, so the tape's cue again starts it here.
158
+ const first = voice.first;
159
+ if (first !== null) {
160
+ first.start = now;
161
+ first.anchorNow = now;
162
+ }
163
+ if (!mix.projecting)
164
+ schedule(mix, voice);
165
+ }
166
+ }
144
167
  /**
145
168
  * Which way a voice's clock is running at mix time `now`, through every owner above it: above 0
146
169
  * forward, below 0 back, 0 standing still.
package/dist/moved.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import type { Motions } from './motions.js';
2
- import type { Subject } from './voice.js';
2
+ import type { Subject } from './subject.js';
3
3
  /**
4
4
  * A motion voice's delta for a subject on the general path: what its patch's `at` gives, written
5
5
  * into the record's last delta and the array that holds, where `at` makes an object and an array a
package/dist/moved.js CHANGED
@@ -1,4 +1,6 @@
1
1
  import { doubles } from './channels.js';
2
+ import { reading } from './reading.js';
3
+ import { sampleThen } from './then.js';
2
4
  /**
3
5
  * A motion voice's delta for a subject on the general path: what its patch's `at` gives, written
4
6
  * into the record's last delta and the array that holds, where `at` makes an object and an array a
@@ -6,16 +8,23 @@ import { doubles } from './channels.js';
6
8
  * `setting` has the voice time to sample at.
7
9
  */
8
10
  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;
11
+ let scalar;
12
+ const back = reading.back;
13
+ if (back !== null)
14
+ scalar = (sampleThen(run, subject, setting.elapsed, back) & 1 /* RunFlag.SCALAR */) !== 0;
15
+ else {
16
+ let s = rec.run;
17
+ if (rec.freed !== run.freed) {
18
+ s = run.slot(subject);
19
+ rec.run = s;
20
+ rec.freed = run.freed;
21
+ }
22
+ run.sample(s, setting.elapsed, run.xs, run.vs);
23
+ scalar = run.scalar(s);
14
24
  }
15
25
  const xs = run.xs;
16
- run.sample(s, setting.elapsed, xs, run.vs);
17
26
  const delta = rec.delta ?? {};
18
- if (run.scalar(s)) {
27
+ if (scalar) {
19
28
  delta[key] = xs[0];
20
29
  return delta;
21
30
  }
package/dist/named.d.ts CHANGED
@@ -8,5 +8,4 @@ export declare class Named<I> {
8
8
  constructor(subjects: Iterable<I>);
9
9
  get size(): number;
10
10
  has(subject: I): boolean;
11
- [Symbol.iterator](): Iterator<I>;
12
11
  }
package/dist/named.js CHANGED
@@ -8,6 +8,12 @@ export class Named {
8
8
  list;
9
9
  set;
10
10
  constructor(subjects) {
11
+ // An array of distinct subjects, as nearly every voice names, is copied without iterating it.
12
+ if (Array.isArray(subjects) && distinct(subjects)) {
13
+ this.list = subjects.slice();
14
+ this.set = null;
15
+ return;
16
+ }
11
17
  const list = [];
12
18
  let set = null;
13
19
  for (const s of subjects) {
@@ -33,7 +39,19 @@ export class Named {
33
39
  has(subject) {
34
40
  return this.set === null ? this.list.includes(subject) : this.set.has(subject);
35
41
  }
36
- [Symbol.iterator]() {
37
- return this.list[Symbol.iterator]();
42
+ }
43
+ /** Whether a short array names no subject twice, as `includes` compares; a long one says no. */
44
+ function distinct(subjects) {
45
+ const n = subjects.length;
46
+ if (n > FEW)
47
+ return false;
48
+ for (let i = 1; i < n; i++) {
49
+ const a = subjects[i];
50
+ for (let j = 0; j < i; j++) {
51
+ const b = subjects[j];
52
+ if (a === b || (Number.isNaN(a) && Number.isNaN(b)))
53
+ return false;
54
+ }
38
55
  }
56
+ return true;
39
57
  }
package/dist/numbers.d.ts CHANGED
@@ -33,6 +33,8 @@ export declare class Numbers<I> {
33
33
  /** One past the highest number handed out, so a loop over 0..size meets every live one. */
34
34
  get size(): number;
35
35
  take(subject: I): number;
36
+ /** Whether `slot` is a number in use, its subject perhaps collected and not yet released. */
37
+ has(slot: number): boolean;
36
38
  subject(slot: number): I | typeof absent;
37
39
  release(slot: number): void;
38
40
  }
package/dist/numbers.js CHANGED
@@ -66,6 +66,10 @@ export class Numbers {
66
66
  this.release(dead);
67
67
  return slot;
68
68
  }
69
+ /** Whether `slot` is a number in use, its subject perhaps collected and not yet released. */
70
+ has(slot) {
71
+ return this.refs[slot] !== undefined;
72
+ }
69
73
  subject(slot) {
70
74
  const ref = this.refs[slot];
71
75
  if (ref === undefined)
package/dist/owed.d.ts CHANGED
@@ -13,6 +13,8 @@ export declare class Owed {
13
13
  begin(fill: number): void;
14
14
  /** Makes room for subject numbers below `cap`. */
15
15
  grow(cap: number): void;
16
+ /** Whether any subject owes a laned voice this fill. */
17
+ any(): boolean;
16
18
  /** Whether the subject at `slot` owes some laned voice this fill. */
17
19
  owes(slot: number): boolean;
18
20
  /** Whether the subject at `slot` owes voice `id` this fill. */
package/dist/owed.js CHANGED
@@ -23,6 +23,10 @@ export class Owed {
23
23
  at.set(this.at);
24
24
  this.at = at;
25
25
  }
26
+ /** Whether any subject owes a laned voice this fill. */
27
+ any() {
28
+ return this.lists.length > 0;
29
+ }
26
30
  /** Whether the subject at `slot` owes some laned voice this fill. */
27
31
  owes(slot) {
28
32
  return this.lists.length > 0 && this.fill[slot] === this.now;