@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/patch.js CHANGED
@@ -1,5 +1,5 @@
1
- import { lerpInto } from './channels.js';
2
- import { curve } from './easing.js';
1
+ import { lerpInto, lerpNumber } from './channels.js';
2
+ import { curve, startSlope } from './easing.js';
3
3
  /**
4
4
  * The procedural form. Flicker, roving, a hash walk, a starter strike.
5
5
  *
@@ -12,7 +12,6 @@ export function patch(duration, at, opts) {
12
12
  return {
13
13
  form: 'fn',
14
14
  duration,
15
- period: duration,
16
15
  writes,
17
16
  kit: opts.kit,
18
17
  reads: opts.reads,
@@ -23,11 +22,6 @@ export function patch(duration, at, opts) {
23
22
  unpack: opts.unpack,
24
23
  };
25
24
  }
26
- const options = new WeakMap();
27
- /** The options a `keys` patch was built with, for an engine reading its stops rather than calling it. */
28
- export function keysOptionsOf(p) {
29
- return options.get(p);
30
- }
31
25
  const linear = (a, b, u) => a + (b - a) * u;
32
26
  function interpolate(a, b, u) {
33
27
  if (typeof a === 'number' && typeof b === 'number')
@@ -64,6 +58,10 @@ function build(stops, writes, duration, opts) {
64
58
  }
65
59
  all.sort((x, y) => x.at - y.at);
66
60
  const tail = all.filter((pt) => pt.at !== 0);
61
+ const lerp = opts.lerpBy?.(channel) ?? opts.kit?.[channel]?.lerp;
62
+ const plain = all.length > 0 &&
63
+ (lerp === undefined || lerp === lerpNumber) &&
64
+ all.every((pt) => typeof pt.value === 'number');
67
65
  tracks.push({
68
66
  channel: channel,
69
67
  all,
@@ -71,7 +69,14 @@ function build(stops, writes, duration, opts) {
71
69
  ats: all.map((pt) => pt.at),
72
70
  tailAts: null,
73
71
  delay: opts.delayBy?.(channel) ?? 0,
74
- lerp: opts.lerpBy?.(channel) ?? opts.kit?.[channel]?.lerp,
72
+ lerp,
73
+ numbers: plain
74
+ ? {
75
+ ats: Float64Array.from(all, (pt) => pt.at),
76
+ values: Float64Array.from(all, (pt) => pt.value),
77
+ eases: all.map((pt) => pt.ease),
78
+ }
79
+ : null,
75
80
  });
76
81
  }
77
82
  return { tracks, duration };
@@ -100,12 +105,17 @@ export function shifted(track, phase, duration) {
100
105
  }
101
106
  /** A phase once a channel's delay of `delay` ms in a duration of `duration` applies. */
102
107
  export function shift(delay, phase, duration) {
103
- return delay === 0 || duration === 0 ? phase : Math.max(0, (phase * duration - delay) / duration);
108
+ if (delay === 0 || duration === 0)
109
+ return phase;
110
+ // The channel travels in what is left after its wait, so it lands with the rest at phase 1.
111
+ if (delay >= duration)
112
+ return phase >= 1 ? 1 : 0;
113
+ return Math.max(0, (phase * duration - delay) / (duration - delay));
104
114
  }
105
115
  /**
106
116
  * Where `phase` falls among `n` stop phases `ats[off + k * stride]`, in order: -2 for no stops, -1
107
117
  * at or before the first, `n` at or past the last, and otherwise the first stop past it, where the
108
- * segment from the one before ends. With `segment`'s, the one copy of the search.
118
+ * segment from the one before ends. `segment` and `readNumber` make the same search.
109
119
  */
110
120
  export function locate(ats, off, stride, n, phase) {
111
121
  if (n === 0)
@@ -132,8 +142,8 @@ export function fraction(phase, a, b) {
132
142
  return (phase - a) / (b - a);
133
143
  }
134
144
  /**
135
- * Finds where one track is at a phase, into `seg`: the one copy of the search, which `read` and a
136
- * lane folding stops straight into a subject's values both make. `base` stands in for the value at
145
+ * Finds where one track is at a phase, into `seg`: the search `read` and a lane folding stops
146
+ * straight into a subject's values both make. `base` stands in for the value at
137
147
  * phase 0, which is how `from: 'current'` starts a voice wherever the subject already is.
138
148
  */
139
149
  export function segment(track, phase, base) {
@@ -179,10 +189,44 @@ export function segment(track, phase, base) {
179
189
  seg.ease = b.ease;
180
190
  return BETWEEN;
181
191
  }
192
+ /**
193
+ * A plain-number track at a phase, in exactly `segment`'s and `read`'s arithmetic: the stop at or
194
+ * past the phase by binary search, the fraction before easing, then a straight lerp.
195
+ */
196
+ function readNumber(t, phase) {
197
+ const ats = t.ats;
198
+ const values = t.values;
199
+ const n = ats.length;
200
+ if (phase <= ats[0])
201
+ return values[0];
202
+ if (phase >= ats[n - 1])
203
+ return values[n - 1];
204
+ let lo = 1;
205
+ let hi = n - 1;
206
+ while (lo < hi) {
207
+ const mid = (lo + hi) >> 1;
208
+ if (ats[mid] >= phase)
209
+ hi = mid;
210
+ else
211
+ lo = mid + 1;
212
+ }
213
+ const a = ats[lo - 1];
214
+ const u = (phase - a) / (ats[lo] - a);
215
+ const ease = t.eases[lo];
216
+ const eased = ease ? ease(u) : u;
217
+ const from = values[lo - 1];
218
+ return from + (values[lo] - from) * eased;
219
+ }
182
220
  /** Reads one track at a phase; `base` as `segment` takes it. */
183
221
  function read(track, phase, base, lerp,
184
222
  /** Units per ms, for a retarget's first segment. */
185
223
  slope, duration, into, reuse) {
224
+ const numbers = track.numbers;
225
+ if (numbers !== null &&
226
+ base === undefined &&
227
+ slope === undefined &&
228
+ (lerp === undefined || lerp === lerpNumber || track.lerp !== undefined))
229
+ return readNumber(numbers, phase);
186
230
  const found = segment(track, phase, base);
187
231
  if (found === NOTHING)
188
232
  return undefined;
@@ -208,7 +252,9 @@ slope, duration, into, reuse) {
208
252
  // add c·u(1−u)², which is 0 at both ends, flat at the end, and fixes the slope at the start.
209
253
  if (slope === undefined || !first)
210
254
  return value;
211
- const e0 = ease ? ease(1e-6) / 1e-6 : 1;
255
+ const e0 = ease ? startSlope(ease) : 1;
256
+ if (e0 === Number.POSITIVE_INFINITY)
257
+ return value;
212
258
  const bend = (s, a, z) => (s * duration * len - (z - a) * e0) * u * (1 - u) * (1 - u);
213
259
  if (typeof value === 'number' && typeof slope === 'number')
214
260
  return value + bend(slope, aValue, bValue);
@@ -262,14 +308,15 @@ slopes, intos, scratch) {
262
308
  }
263
309
  return out;
264
310
  }
265
- /** A patch's `duration`, or the deprecated `period` where only that is set. */
266
- export const durationOf = (p) => p.duration ?? p.period ?? 0;
267
311
  const cache = new WeakMap();
268
- /** The built form of a patch's stops, made on first ask for a patch `keys()` did not build. */
312
+ /**
313
+ * The built form of a patch's stops, made on first ask for a patch `keys()` did not build: a copy
314
+ * of one, or one another copy of blits made, each read by the fields it carries.
315
+ */
269
316
  export function builtOf(p) {
270
317
  let held = cache.get(p);
271
318
  if (held === undefined) {
272
- held = build(p.keys, p.writes, durationOf(p), keysOptionsOf(p) ?? {});
319
+ held = build(p.keys, p.writes, p.duration, p);
273
320
  cache.set(p, held);
274
321
  }
275
322
  return held;
@@ -289,13 +336,28 @@ export function keys(duration, stops, opts = {}) {
289
336
  const p = {
290
337
  form: 'keys',
291
338
  duration,
292
- period: duration,
293
339
  writes,
294
340
  kit: opts.kit,
295
341
  keys: stops,
296
- at: (phase) => readKeys(made, phase, {}),
342
+ ...given(opts),
343
+ // A copy of the patch carries this `at` with fields of its own, which it is read by.
344
+ at(phase) {
345
+ return readKeys(this === p ? made : builtOf(this), phase, {});
346
+ },
297
347
  };
298
- options.set(p, opts);
299
348
  cache.set(p, made);
300
349
  return p;
301
350
  }
351
+ /** The `keys` options a patch carries as fields, those given only: an absent one adds no key. */
352
+ function given(opts) {
353
+ const out = {};
354
+ if (opts.ease !== undefined)
355
+ out.ease = opts.ease;
356
+ if (opts.easeBy !== undefined)
357
+ out.easeBy = opts.easeBy;
358
+ if (opts.delayBy !== undefined)
359
+ out.delayBy = opts.delayBy;
360
+ if (opts.lerpBy !== undefined)
361
+ out.lerpBy = opts.lerpBy;
362
+ return out;
363
+ }
package/dist/place.js CHANGED
@@ -1,6 +1,9 @@
1
+ import { departedMatches } from './departed.js';
2
+ import { reorigin } from './held.js';
1
3
  import { noted } from './history.js';
2
4
  import { markOf } from './marks.js';
3
5
  import { mixTime, ownerReading } from './owner.js';
6
+ import { holders, scored, scoredGone, scoreVersion, unhold } from './scored.js';
4
7
  import { none } from './voice.js';
5
8
  /** What a voice's own owner's clock reads now: the mix clock for a voice no owner holds. */
6
9
  export function localNow(mix, voice) {
@@ -36,6 +39,10 @@ function startAt(mix, voice, start) {
36
39
  voice.start = start;
37
40
  voice.anchorNow = start;
38
41
  voice.anchorElapsed = 0;
42
+ voice.clocks = null;
43
+ // A clock moved outright, as a seek moves it: what a record computed at this mix time is stale.
44
+ voice.seeks++;
45
+ reorigin(mix, voice, mix.now);
39
46
  noted(mix, voice);
40
47
  return true;
41
48
  }
@@ -178,7 +185,7 @@ function resolve(mix, a, self) {
178
185
  mark = 'start';
179
186
  by = -by;
180
187
  }
181
- const t = timeOf(mix, typeof query === 'string' ? { name: query } : query, mark, self);
188
+ const t = timeOf(mix, query, mark, self);
182
189
  if (t === undefined)
183
190
  return undefined;
184
191
  if (o === null)
@@ -200,46 +207,197 @@ function member(mix, a, self) {
200
207
  }
201
208
  return t;
202
209
  }
210
+ /** Picks among the times a query meets, as its resolver says, in the order they are met. */
211
+ class Pick {
212
+ resolver;
213
+ now;
214
+ any = false;
215
+ firstOrder = Number.POSITIVE_INFINITY;
216
+ lastOrder = Number.NEGATIVE_INFINITY;
217
+ first;
218
+ last;
219
+ pick;
220
+ constructor(resolver, now) {
221
+ this.resolver = resolver;
222
+ this.now = now;
223
+ }
224
+ take(order, t) {
225
+ this.any = true;
226
+ if (order < this.firstOrder) {
227
+ this.firstOrder = order;
228
+ this.first = t;
229
+ }
230
+ if (order > this.lastOrder) {
231
+ this.lastOrder = order;
232
+ this.last = t;
233
+ }
234
+ if (t !== undefined)
235
+ this.consider(t);
236
+ }
237
+ /** Takes what `o` picked as though its times had been met here. */
238
+ merge(o) {
239
+ if (!o.any)
240
+ return;
241
+ this.any = true;
242
+ if (o.firstOrder < this.firstOrder) {
243
+ this.firstOrder = o.firstOrder;
244
+ this.first = o.first;
245
+ }
246
+ if (o.lastOrder > this.lastOrder) {
247
+ this.lastOrder = o.lastOrder;
248
+ this.last = o.last;
249
+ }
250
+ if (o.pick !== undefined)
251
+ this.consider(o.pick);
252
+ }
253
+ consider(t) {
254
+ const p = this.pick;
255
+ if (this.resolver === 'earliest'
256
+ ? p === undefined || t < p
257
+ : this.resolver === 'latest'
258
+ ? p === undefined || t > p
259
+ : this.resolver === 'next' && t >= this.now && (p === undefined || t < p))
260
+ this.pick = t;
261
+ }
262
+ answer() {
263
+ if (!this.any)
264
+ return undefined;
265
+ if (this.resolver === 'first')
266
+ return this.first;
267
+ if (this.resolver === 'last')
268
+ return this.last;
269
+ return this.pick;
270
+ }
271
+ }
203
272
  /**
204
273
  * The mix time a query answers: `mark` of the voice it picks, among those cued and those that have
205
274
  * left, never the asker; or a mark the host announced on the score, which is the same time for
206
- * any of the four. Undefined while what it picks has no such time. A query naming no score looks
275
+ * any mark. Undefined while what it picks has no such time. A query naming no score looks
207
276
  * among the asker's siblings, the voices its owner holds.
208
277
  */
209
- function timeOf(mix, q, mark, self) {
278
+ function timeOf(mix, key, mark, self) {
279
+ const q = typeof key === 'string' ? { name: key } : key;
210
280
  const score = q.score ?? self.spec.score;
211
281
  const anywhere = q.score !== undefined;
212
- const found = [];
213
- // A query naming a score looks in every mix on the transport; one naming none, in its own.
214
- const mixes = anywhere ? mix.transport.members : [mix];
215
- for (const m of mixes)
216
- for (const v of [...m.gone, ...m.cued])
217
- if (v !== self &&
218
- v.spec.score === score &&
219
- (anywhere || v.owner === self.owner) &&
220
- (q.name === undefined || v.spec.name === q.name) &&
221
- (q.tag === undefined || (v.spec.tags ?? none).includes(q.tag)) &&
222
- (q.writes === undefined || v.patch.writes.includes(q.writes)))
223
- found.push({ order: v.id, t: markOf(m, v, mark) });
282
+ const resolver = q.resolver ?? 'last';
283
+ const now = Number.isNaN(mix.now) ? Number.NEGATIVE_INFINITY : mix.now;
284
+ const picked = new Pick(resolver, now);
285
+ const matches = (v) => v !== self &&
286
+ v.spec.score === score &&
287
+ (anywhere || v.owner === self.owner) &&
288
+ (q.name === undefined || v.spec.name === q.name) &&
289
+ (q.tag === undefined || (v.spec.tags ?? none).includes(q.tag)) &&
290
+ (q.writes === undefined || v.patch.writes.includes(q.writes));
291
+ // A query naming a score looks in every mix on the transport that holds a voice on it; one
292
+ // naming none, in its own.
293
+ const listed = anywhere && score !== undefined && !mix.projecting;
294
+ const mixes = !anywhere
295
+ ? [mix]
296
+ : listed
297
+ ? holders(mix, score)
298
+ : mix.transport.members;
299
+ // Voices forgotten on leaving answer only where no voice the mix still knows does.
300
+ const owner = self.owner?.id ?? -1;
301
+ const forgotten = new Pick(resolver, now);
302
+ for (let i = 0; i < mixes.length; i++) {
303
+ const m = mixes[i];
304
+ let departed = false;
305
+ for (const d of m.departed.all)
306
+ if (departedMatches(d, q, score, anywhere, owner)) {
307
+ forgotten.take(d.id, d.marks[mark]);
308
+ departed = true;
309
+ }
310
+ if (score === undefined || m.projecting || mix.projecting) {
311
+ for (const v of [...m.gone, ...m.cued])
312
+ if (matches(v))
313
+ picked.take(v.id, markOf(m, v, mark));
314
+ continue;
315
+ }
316
+ const cued = scored(m, score);
317
+ const list = scoredGone(m, score);
318
+ if (cued.size === 0 && list.length === 0 && !departed) {
319
+ if (listed) {
320
+ unhold(m, score);
321
+ i--;
322
+ }
323
+ continue;
324
+ }
325
+ // `next` asks what is still ahead of now, which a running pick cannot keep.
326
+ if (resolver === 'next') {
327
+ for (const v of cued)
328
+ if (matches(v))
329
+ picked.take(v.id, markOf(m, v, mark));
330
+ for (const v of list)
331
+ if (matches(v))
332
+ picked.take(v.id, markOf(m, v, mark));
333
+ continue;
334
+ }
335
+ const fold = foldOf(m, list, key, mark, self, resolver);
336
+ const version = scoreVersion(m, score);
337
+ const reduced = m.reduced;
338
+ if (fold.live === null ||
339
+ fold.liveOf !== m.cued ||
340
+ fold.liveVersion !== version ||
341
+ fold.liveReduced !== reduced) {
342
+ const live = new Pick(resolver, Number.NEGATIVE_INFINITY);
343
+ let keeps = true;
344
+ for (const v of cued) {
345
+ if (!matches(v))
346
+ continue;
347
+ if (v.owner !== null || v.holding !== null || v.fitting !== null)
348
+ keeps = false;
349
+ live.take(v.id, markOf(m, v, mark));
350
+ }
351
+ fold.live = live;
352
+ fold.liveOf = keeps ? m.cued : null;
353
+ fold.liveVersion = version;
354
+ fold.liveReduced = reduced;
355
+ }
356
+ picked.merge(fold.live);
357
+ for (; fold.seen < list.length; fold.seen++) {
358
+ const v = list[fold.seen];
359
+ if (!matches(v))
360
+ continue;
361
+ if (v.owner === null)
362
+ fold.pick.take(v.id, markOf(m, v, mark));
363
+ else
364
+ fold.owned.push(v);
365
+ }
366
+ picked.merge(fold.pick);
367
+ for (const v of fold.owned)
368
+ picked.take(v.id, markOf(m, v, mark));
369
+ }
224
370
  if (q.writes === undefined && (anywhere || self.owner === null))
225
371
  for (const a of mix.announced)
226
372
  if (a.score === score &&
227
373
  (score !== undefined || a.slot === mix.slot) &&
228
374
  (q.name === undefined || a.name === q.name) &&
229
375
  (q.tag === undefined || a.tags.includes(q.tag)))
230
- found.push({ order: a.order, t: Number.isNaN(a.at) ? undefined : mixAt(mix, a.at) });
231
- if (found.length === 0)
232
- return undefined;
233
- found.sort((x, y) => x.order - y.order);
234
- const resolver = q.resolver ?? 'last';
235
- if (resolver === 'first')
236
- return found[0]?.t;
237
- if (resolver === 'last')
238
- return found[found.length - 1]?.t;
239
- const times = found.flatMap((e) => (e.t === undefined ? [] : [e.t])).sort((x, y) => x - y);
240
- if (resolver === 'next') {
241
- const now = Number.isNaN(mix.now) ? Number.NEGATIVE_INFINITY : mix.now;
242
- return times.find((t) => t >= now);
243
- }
244
- return resolver === 'earliest' ? times[0] : times[times.length - 1];
376
+ picked.take(a.order, Number.isNaN(a.at) ? undefined : mixAt(mix, a.at));
377
+ return (picked.any ? picked : forgotten).answer();
378
+ }
379
+ /** `self`'s read of `m`'s gone voices for one query, started over where the list has changed under it. */
380
+ function foldOf(m, list, key, mark, self, resolver) {
381
+ self.folds ??= new Map();
382
+ let byMix = self.folds.get(key);
383
+ if (byMix === undefined) {
384
+ byMix = new Map();
385
+ self.folds.set(key, byMix);
386
+ }
387
+ let fold = byMix.get(m);
388
+ if (fold === undefined || fold.list !== list || fold.mark !== mark) {
389
+ fold = {
390
+ list,
391
+ mark,
392
+ seen: 0,
393
+ pick: new Pick(resolver, Number.NEGATIVE_INFINITY),
394
+ owned: [],
395
+ live: null,
396
+ liveOf: null,
397
+ liveVersion: -1,
398
+ liveReduced: false,
399
+ };
400
+ byMix.set(m, fold);
401
+ }
402
+ return fold;
245
403
  }
package/dist/project.d.ts CHANGED
@@ -1,7 +1,18 @@
1
- import { Transport } from './transport.js';
1
+ import { Mixer } from './mixer.js';
2
+ import { type Cut, Transport } from './transport.js';
2
3
  import type { TransportProjection } from './types.js';
4
+ import type { Subject, Voice } from './voice.js';
3
5
  /**
4
6
  * Every mix on a transport read at mix time `t`, through copies of them on a transport of their
5
7
  * own, moved together so an anchor across mixes answers as it would live.
6
8
  */
7
9
  export declare function projectAll(transport: Transport, t: number): TransportProjection;
10
+ /** Sets each copied voice's state to what its clock and fade make it at `t`. */
11
+ export declare function stateAt<I, O>(c: Mixer<I, O>, t: number): void;
12
+ /** Gives a projection's copy what it reads of the live mix as it is, before its voices are copied. */
13
+ export declare function mirror<I, O>(mix: Mixer<I, O>, c: Mixer<I, O>): void;
14
+ /**
15
+ * A projection back starts each subject from the latest copy kept at or before `t`, else from its
16
+ * voice's start with fresh state, which is exact for a voice stepped at a fixed interval.
17
+ */
18
+ export declare function recall<I, O>(mix: Mixer<I, O>, voice: Voice<I, O>, subject: I, t: number, cut: Cut): Subject<unknown> | undefined;