@msb235/blits 0.7.1 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (153) hide show
  1. package/CHANGELOG.md +180 -0
  2. package/README.md +22 -4
  3. package/dist/book.js +4 -4
  4. package/dist/chain.d.ts +1 -1
  5. package/dist/chain.js +57 -54
  6. package/dist/channels.d.ts +26 -5
  7. package/dist/channels.js +12 -8
  8. package/dist/clock.d.ts +8 -1
  9. package/dist/clock.js +14 -3
  10. package/dist/clone.d.ts +3 -0
  11. package/dist/clone.js +16 -1
  12. package/dist/closed.d.ts +1 -1
  13. package/dist/closed.js +3 -2
  14. package/dist/color.d.ts +0 -11
  15. package/dist/color.js +4 -24
  16. package/dist/columns.js +0 -1
  17. package/dist/copyout.d.ts +6 -0
  18. package/dist/copyout.js +38 -0
  19. package/dist/crowd.d.ts +7 -5
  20. package/dist/crowd.js +32 -18
  21. package/dist/cue.d.ts +3 -1
  22. package/dist/cue.js +31 -20
  23. package/dist/departed.d.ts +34 -0
  24. package/dist/departed.js +73 -0
  25. package/dist/easing.d.ts +5 -0
  26. package/dist/easing.js +31 -2
  27. package/dist/everyone.d.ts +56 -0
  28. package/dist/everyone.js +221 -0
  29. package/dist/fade.d.ts +8 -1
  30. package/dist/fade.js +34 -6
  31. package/dist/fill.d.ts +22 -13
  32. package/dist/fill.js +104 -41
  33. package/dist/fit.js +2 -1
  34. package/dist/fold.d.ts +37 -0
  35. package/dist/fold.js +213 -28
  36. package/dist/gather.d.ts +15 -9
  37. package/dist/gather.js +60 -25
  38. package/dist/gone.js +10 -0
  39. package/dist/handle.d.ts +16 -5
  40. package/dist/handle.js +56 -6
  41. package/dist/held.d.ts +8 -5
  42. package/dist/held.js +56 -58
  43. package/dist/history.d.ts +17 -13
  44. package/dist/history.js +47 -28
  45. package/dist/hosts.js +55 -8
  46. package/dist/index.d.ts +9 -4
  47. package/dist/index.js +5 -2
  48. package/dist/keyfill.d.ts +7 -0
  49. package/dist/keyfill.js +124 -0
  50. package/dist/keyrows.d.ts +1 -0
  51. package/dist/keyrows.js +1 -1
  52. package/dist/lane.d.ts +32 -2
  53. package/dist/lane.js +5 -0
  54. package/dist/lanes.d.ts +32 -8
  55. package/dist/lanes.js +69 -37
  56. package/dist/leavings.d.ts +26 -0
  57. package/dist/leavings.js +70 -0
  58. package/dist/meet.js +14 -8
  59. package/dist/mixer.d.ts +39 -7
  60. package/dist/mixer.js +50 -10
  61. package/dist/motion.d.ts +2 -0
  62. package/dist/motion.js +4 -1
  63. package/dist/motions.d.ts +22 -15
  64. package/dist/motions.js +40 -34
  65. package/dist/move.js +23 -16
  66. package/dist/moved.d.ts +11 -0
  67. package/dist/moved.js +29 -0
  68. package/dist/origin.d.ts +19 -0
  69. package/dist/origin.js +68 -0
  70. package/dist/owed.d.ts +4 -4
  71. package/dist/owed.js +8 -8
  72. package/dist/owner.d.ts +1 -1
  73. package/dist/owner.js +2 -2
  74. package/dist/pace.d.ts +13 -5
  75. package/dist/pace.js +21 -8
  76. package/dist/pack.d.ts +3 -1
  77. package/dist/pack.js +15 -24
  78. package/dist/paging.d.ts +8 -2
  79. package/dist/paging.js +44 -4
  80. package/dist/patch.d.ts +14 -9
  81. package/dist/patch.js +83 -21
  82. package/dist/place.js +18 -3
  83. package/dist/project.d.ts +12 -1
  84. package/dist/project.js +101 -83
  85. package/dist/pull.d.ts +2 -0
  86. package/dist/pull.js +8 -0
  87. package/dist/qualify.js +10 -2
  88. package/dist/reading.d.ts +5 -0
  89. package/dist/reading.js +5 -0
  90. package/dist/record.d.ts +9 -0
  91. package/dist/record.js +42 -0
  92. package/dist/relink.d.ts +11 -5
  93. package/dist/relink.js +7 -5
  94. package/dist/restored.d.ts +41 -0
  95. package/dist/restored.js +91 -0
  96. package/dist/revive.d.ts +10 -3
  97. package/dist/revive.js +31 -23
  98. package/dist/roster.d.ts +11 -0
  99. package/dist/roster.js +46 -0
  100. package/dist/rotation.d.ts +29 -0
  101. package/dist/rotation.js +91 -0
  102. package/dist/rows.d.ts +5 -5
  103. package/dist/rows.js +5 -5
  104. package/dist/sample.d.ts +3 -2
  105. package/dist/sample.js +32 -15
  106. package/dist/seek.js +97 -114
  107. package/dist/shared.d.ts +6 -0
  108. package/dist/shared.js +15 -0
  109. package/dist/signals.d.ts +24 -3
  110. package/dist/signals.js +50 -11
  111. package/dist/spans.js +7 -0
  112. package/dist/stack.d.ts +11 -0
  113. package/dist/stack.js +47 -0
  114. package/dist/standing.d.ts +10 -0
  115. package/dist/standing.js +79 -0
  116. package/dist/store.d.ts +3 -3
  117. package/dist/store.js +3 -3
  118. package/dist/strays.js +3 -0
  119. package/dist/tape.d.ts +8 -5
  120. package/dist/tape.js +21 -13
  121. package/dist/testing.d.ts +35 -0
  122. package/dist/testing.js +116 -0
  123. package/dist/touch.d.ts +6 -0
  124. package/dist/touch.js +28 -0
  125. package/dist/transport.d.ts +49 -6
  126. package/dist/transport.js +89 -19
  127. package/dist/turns.d.ts +18 -0
  128. package/dist/turns.js +72 -0
  129. package/dist/tweened.js +1 -1
  130. package/dist/types/channel.d.ts +46 -0
  131. package/dist/types/channel.js +1 -0
  132. package/dist/types/history.d.ts +139 -0
  133. package/dist/types/history.js +1 -0
  134. package/dist/types/mix.d.ts +336 -0
  135. package/dist/types/mix.js +1 -0
  136. package/dist/types/patch.d.ts +187 -0
  137. package/dist/types/patch.js +1 -0
  138. package/dist/types/place.d.ts +157 -0
  139. package/dist/types/place.js +1 -0
  140. package/dist/types/transport.d.ts +71 -0
  141. package/dist/types/transport.js +1 -0
  142. package/dist/types/voice.d.ts +323 -0
  143. package/dist/types/voice.js +1 -0
  144. package/dist/types.d.ts +7 -1162
  145. package/dist/unpage.d.ts +2 -2
  146. package/dist/unpage.js +53 -25
  147. package/dist/voice.d.ts +90 -22
  148. package/dist/voice.js +105 -43
  149. package/dist/wave.d.ts +42 -0
  150. package/dist/wave.js +62 -0
  151. package/dist/weigh.js +1 -1
  152. package/docs/schema.html +42 -36
  153. package/package.json +8 -3
package/dist/paging.d.ts CHANGED
@@ -51,13 +51,19 @@ export declare class Keys {
51
51
  subject(key: Key): unknown;
52
52
  }
53
53
  /**
54
- * Hands records leaving memory to the store, the last of any that share a time, which is the one
55
- * a read finds. `reach` is how far back memory still reaches.
54
+ * Hands records leaving memory to the store, the last of any made in one frame, which is the one a
55
+ * read finds. `reach` is how far back memory still reaches.
56
56
  */
57
57
  export declare function pageOut<I, O, E extends {
58
58
  at: number;
59
+ seq: number;
59
60
  }>(mix: Mixer<I, O>, stream: PagedStream, voice: number | undefined, subject: Key, out: readonly E[], data: (e: E) => unknown, reach: number): void;
60
61
  /** Gives records to the store; memory now reaches back no further than `reach`. */
61
62
  export declare function page(transport: Transport, records: Paged[], reach: number): void;
63
+ /**
64
+ * With a store, hands it what the transport itself keeps from before memory's reach: announced
65
+ * marks, changes of its rate, and frames, the last at or before the reach staying.
66
+ */
67
+ export declare function pageTransport(transport: Transport): void;
62
68
  /** Loads what a seek or read to `t` needs where it reaches past memory. */
63
69
  export declare function prepare(transport: Transport, t: number): Promise<void>;
package/dist/paging.js CHANGED
@@ -68,8 +68,8 @@ export class Keys {
68
68
  }
69
69
  }
70
70
  /**
71
- * Hands records leaving memory to the store, the last of any that share a time, which is the one
72
- * a read finds. `reach` is how far back memory still reaches.
71
+ * Hands records leaving memory to the store, the last of any made in one frame, which is the one a
72
+ * read finds. `reach` is how far back memory still reaches.
73
73
  */
74
74
  export function pageOut(mix, stream, voice, subject, out, data, reach) {
75
75
  const pager = mix.transport.pager;
@@ -79,9 +79,9 @@ export function pageOut(mix, stream, voice, subject, out, data, reach) {
79
79
  const records = [];
80
80
  for (let i = 0; i < out.length; i++) {
81
81
  const e = out[i];
82
- if (i + 1 < out.length && out[i + 1].at === e.at)
82
+ if (i + 1 < out.length && out[i + 1].seq === e.seq)
83
83
  continue;
84
- const r = { mix: name, stream, at: e.at, data: data(e) };
84
+ const r = { mix: name, stream, at: e.at, seq: e.seq, data: data(e) };
85
85
  if (voice !== undefined)
86
86
  r.voice = voice;
87
87
  if (subject !== undefined)
@@ -100,6 +100,46 @@ export function page(transport, records, reach) {
100
100
  if (reach > transport.floor)
101
101
  transport.floor = reach;
102
102
  }
103
+ /**
104
+ * With a store, hands it what the transport itself keeps from before memory's reach: announced
105
+ * marks, changes of its rate, and frames, the last at or before the reach staying.
106
+ */
107
+ export function pageTransport(transport) {
108
+ const history = transport.history;
109
+ if (history === undefined || transport.pager === null)
110
+ return;
111
+ const reach = transport.now - history.ms;
112
+ const records = [];
113
+ const frames = transport.frames;
114
+ let n = 0;
115
+ while (n + 1 < frames.length && frames[n + 1].at <= reach)
116
+ n++;
117
+ for (const f of frames.splice(0, n))
118
+ records.push({ mix: '', stream: 'frame', at: f.at, seq: f.seq, data: f.u });
119
+ const pace = transport.pace;
120
+ const timeOf = (u) => (pace === null ? u : pace.reading(u));
121
+ if (transport.announced.some((a) => timeOf(a.at) < reach)) {
122
+ const kept = [];
123
+ for (const a of transport.announced)
124
+ if (timeOf(a.at) < reach)
125
+ records.push({
126
+ mix: '',
127
+ stream: 'mark',
128
+ subject: a.order,
129
+ at: timeOf(a.at),
130
+ seq: a.seq,
131
+ data: a,
132
+ });
133
+ else
134
+ kept.push(a);
135
+ transport.announced = kept;
136
+ }
137
+ if (pace !== null)
138
+ for (const c of pace.shed(reach))
139
+ records.push({ mix: '', stream: 'pace', at: c.anchorElapsed, seq: c.seq, data: c });
140
+ if (records.length > 0)
141
+ page(transport, records, reach);
142
+ }
103
143
  /** Loads what a seek or read to `t` needs where it reaches past memory. */
104
144
  export async function prepare(transport, t) {
105
145
  const pager = transport.pager;
package/dist/patch.d.ts CHANGED
@@ -35,7 +35,7 @@ export interface KeysOptions<O> {
35
35
  ease?: Easing;
36
36
  /** A curve for one channel, where it differs from the rest. */
37
37
  easeBy?: (channel: keyof O) => Easing | undefined;
38
- /** Milliseconds one channel waits before it starts moving, within the duration. */
38
+ /** Milliseconds one channel waits before it starts moving; it then travels in the time left. */
39
39
  delayBy?: (channel: keyof O) => number;
40
40
  /**
41
41
  * How one channel interpolates, where it should differ from the channel's own `lerp`. A mix hands
@@ -45,8 +45,6 @@ export interface KeysOptions<O> {
45
45
  /** The channels these stops were written against, which `cue` checks a mix's kit against. */
46
46
  kit?: Partial<Kit<O>>;
47
47
  }
48
- /** The options a `keys` patch was built with, for an engine reading its stops rather than calling it. */
49
- export declare function keysOptionsOf<I, O, S>(p: Patch<I, O, S>): KeysOptions<O> | undefined;
50
48
  interface Point {
51
49
  at: number;
52
50
  value: unknown;
@@ -66,6 +64,12 @@ export interface Track {
66
64
  tailAts: number[] | null;
67
65
  delay: number;
68
66
  lerp: ((a: never, b: never, u: number) => unknown) | undefined;
67
+ /** Where every stop is a plain number lerped straight across: the stops flat, for `readNumber`. */
68
+ numbers: {
69
+ ats: Float64Array;
70
+ values: Float64Array;
71
+ eases: (Curve | undefined)[];
72
+ } | null;
69
73
  }
70
74
  export interface Built {
71
75
  tracks: Track[];
@@ -96,14 +100,14 @@ export declare function shift(delay: number, phase: number, duration: number): n
96
100
  /**
97
101
  * Where `phase` falls among `n` stop phases `ats[off + k * stride]`, in order: -2 for no stops, -1
98
102
  * at or before the first, `n` at or past the last, and otherwise the first stop past it, where the
99
- * segment from the one before ends. With `segment`'s, the one copy of the search.
103
+ * segment from the one before ends. `segment` and `readNumber` make the same search.
100
104
  */
101
105
  export declare function locate(ats: ArrayLike<number>, off: number, stride: number, n: number, phase: number): number;
102
106
  /** How far along a segment from phase `a` to `b` a phase is, before easing. */
103
107
  export declare function fraction(phase: number, a: number, b: number): number;
104
108
  /**
105
- * Finds where one track is at a phase, into `seg`: the one copy of the search, which `read` and a
106
- * lane folding stops straight into a subject's values both make. `base` stands in for the value at
109
+ * Finds where one track is at a phase, into `seg`: the search `read` and a lane folding stops
110
+ * straight into a subject's values both make. `base` stands in for the value at
107
111
  * phase 0, which is how `from: 'current'` starts a voice wherever the subject already is.
108
112
  */
109
113
  export declare function segment(track: Track, phase: number, base: unknown): number;
@@ -125,9 +129,10 @@ export declare function intosOf(built: Built, channels: readonly Channel<unknown
125
129
  export declare function readKeys(built: Built, phase: number, out: Record<string, unknown>, base?: Record<string, unknown>, lerps?: readonly (Lerp | undefined)[],
126
130
  /** Per channel, how fast a retargeted subject was moving, units per ms. */
127
131
  slopes?: Record<string, unknown>, intos?: readonly (LerpInto | undefined)[], scratch?: Scratch): Record<string, unknown>;
128
- /** A patch's `duration`, or the deprecated `period` where only that is set. */
129
- export declare const durationOf: <I, O, S>(p: Patch<I, O, S>) => number;
130
- /** The built form of a patch's stops, made on first ask for a patch `keys()` did not build. */
132
+ /**
133
+ * The built form of a patch's stops, made on first ask for a patch `keys()` did not build: a copy
134
+ * of one, or one another copy of blits made, each read by the fields it carries.
135
+ */
131
136
  export declare function builtOf<I, O, S>(p: Patch<I, O, S>): Built;
132
137
  /** Evaluates a stop list into a fresh delta. */
133
138
  export declare function evalKeys<O>(stops: readonly Keyframe<O>[], writes: readonly (keyof O)[], phase: number, duration: number, opts: KeysOptions<O>, base?: Partial<O>): Partial<O>;
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,3 +1,5 @@
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';
@@ -37,6 +39,10 @@ function startAt(mix, voice, start) {
37
39
  voice.start = start;
38
40
  voice.anchorNow = start;
39
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);
40
46
  noted(mix, voice);
41
47
  return true;
42
48
  }
@@ -266,7 +272,7 @@ class Pick {
266
272
  /**
267
273
  * The mix time a query answers: `mark` of the voice it picks, among those cued and those that have
268
274
  * left, never the asker; or a mark the host announced on the score, which is the same time for
269
- * 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
270
276
  * among the asker's siblings, the voices its owner holds.
271
277
  */
272
278
  function timeOf(mix, key, mark, self) {
@@ -290,8 +296,17 @@ function timeOf(mix, key, mark, self) {
290
296
  : listed
291
297
  ? holders(mix, score)
292
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);
293
302
  for (let i = 0; i < mixes.length; i++) {
294
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
+ }
295
310
  if (score === undefined || m.projecting || mix.projecting) {
296
311
  for (const v of [...m.gone, ...m.cued])
297
312
  if (matches(v))
@@ -300,7 +315,7 @@ function timeOf(mix, key, mark, self) {
300
315
  }
301
316
  const cued = scored(m, score);
302
317
  const list = scoredGone(m, score);
303
- if (cued.size === 0 && list.length === 0) {
318
+ if (cued.size === 0 && list.length === 0 && !departed) {
304
319
  if (listed) {
305
320
  unhold(m, score);
306
321
  i--;
@@ -359,7 +374,7 @@ function timeOf(mix, key, mark, self) {
359
374
  (q.name === undefined || a.name === q.name) &&
360
375
  (q.tag === undefined || a.tags.includes(q.tag)))
361
376
  picked.take(a.order, Number.isNaN(a.at) ? undefined : mixAt(mix, a.at));
362
- return picked.answer();
377
+ return (picked.any ? picked : forgotten).answer();
363
378
  }
364
379
  /** `self`'s read of `m`'s gone voices for one query, started over where the list has changed under it. */
365
380
  function foldOf(m, list, key, mark, self, resolver) {
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;