@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/transport.js CHANGED
@@ -1,7 +1,8 @@
1
+ import { Frames } from './frames.js';
1
2
  import { listedAll } from './marks.js';
2
3
  import { move, nextFrame, waits } from './move.js';
3
4
  import { Pace } from './pace.js';
4
- import { Pager, prepare } from './paging.js';
5
+ import { Pager, pageTransport, prepare } from './paging.js';
5
6
  import { pin } from './place.js';
6
7
  import { projectAll } from './project.js';
7
8
  import { scoredDrop, scoredJoin } from './scored.js';
@@ -9,6 +10,10 @@ import { seek } from './seek.js';
9
10
  import { record, replay, tapeOf } from './tape.js';
10
11
  /** How far the tape's reach moves before it is pruned again, ms of mix time. */
11
12
  const TAPE_STEP_MS = 1000;
13
+ /** Whether a change made in frame `seq` falls inside `cut`; `early` for one made before the frame was read. */
14
+ export function within(cut, seq, early = false) {
15
+ return seq < cut.seq || (seq === cut.seq && (!cut.strict || early));
16
+ }
12
17
  /**
13
18
  * The clock its mixes play on: host time less what `rebase` took out, at the rate its `pace`
14
19
  * sets, moved by `sync` and `seek`, and the tape of calls a seek plays again. A mix made alone
@@ -29,6 +34,13 @@ export class Transport {
29
34
  woken = false;
30
35
  /** The mix clock at the last sync or seek. */
31
36
  now = Number.NaN;
37
+ /**
38
+ * The number of the frame being played: each sync and seek begins one, and so does each host
39
+ * time the tape plays calls again at.
40
+ */
41
+ seq = 0;
42
+ /** Under history, the frames a seek or read back may reach, oldest first. */
43
+ frames = new Frames();
32
44
  /** Host time at the last sync: the host's timestamp less every gap `rebase` took out. */
33
45
  u = Number.NaN;
34
46
  offset = 0;
@@ -42,6 +54,20 @@ export class Transport {
42
54
  taped = Number.NEGATIVE_INFINITY;
43
55
  /** The rate; null while it has never been set, when mix time is host time. */
44
56
  pace = null;
57
+ /** The latest mix time a voice any mix on it let go of from `gone` had left at. */
58
+ forgotTo = Number.NEGATIVE_INFINITY;
59
+ /**
60
+ * The latest mix time a host changed a mix on it, or a voice or a subject left one: without
61
+ * history, the earliest time a read back still finds the mixes as they stand now.
62
+ */
63
+ settled = Number.NEGATIVE_INFINITY;
64
+ /** Records a change or a leaving at the mix time now; before the first sync, nothing. */
65
+ settle() {
66
+ if (this.now > this.settled)
67
+ this.settled = this.now;
68
+ }
69
+ /** The frame the rate was first set in, which a seek or read back to before it finds unset. */
70
+ pacedSeq = Number.POSITIVE_INFINITY;
45
71
  /** True while the tape makes a recorded call again, so it is not recorded twice. */
46
72
  replaying = false;
47
73
  announced = [];
@@ -64,7 +90,7 @@ export class Transport {
64
90
  get projecting() {
65
91
  return this.members.some((m) => m.projecting);
66
92
  }
67
- /** Takes a mix on, after every mix that joined before it; `slot` keeps a copy's in place. */
93
+ /** Takes a mix on, after every mix that joined before it; `slot` keeps one a seek puts back in place. */
68
94
  join(mix, slot = this.slots++) {
69
95
  mix.slot = slot;
70
96
  const i = this.members.findIndex((m) => m.slot > slot);
@@ -83,16 +109,16 @@ export class Transport {
83
109
  scoredDrop(m);
84
110
  m.dropped = true;
85
111
  if (this.history !== undefined) {
86
- this.dropped.push({ mix: m, at: this.now });
112
+ this.dropped.push({ mix: m, at: this.now, seq: this.seq });
87
113
  record(this, 'drop', () => this.drop(mix));
88
114
  }
89
115
  }
90
- /** A seek back to `t`: every mix dropped after it is back on, as it stood then. */
91
- undrop(t) {
92
- const back = this.dropped.filter((d) => d.at > t);
116
+ /** A seek back to frame `seq`: every mix dropped after it is back on, as it stood then. */
117
+ undrop(seq) {
118
+ const back = this.dropped.filter((d) => d.seq > seq);
93
119
  if (back.length === 0)
94
120
  return;
95
- this.dropped = this.dropped.filter((d) => d.at <= t);
121
+ this.dropped = this.dropped.filter((d) => d.seq <= seq);
96
122
  for (const d of back) {
97
123
  d.mix.dropped = false;
98
124
  this.join(d.mix, d.mix.slot);
@@ -151,6 +177,8 @@ export class Transport {
151
177
  }
152
178
  }
153
179
  syncAt(timestamp) {
180
+ if (Number.isNaN(timestamp))
181
+ throw new RangeError('blits: sync was given NaN; it takes the host clock as a number');
154
182
  if (timestamp < this.last && !this.rebasing)
155
183
  throw new RangeError(`blits: sync went back from ${this.last} to ${timestamp}; the host's clock only goes forward, and seek moves the mix`);
156
184
  if (this.rebasing && !Number.isNaN(this.last))
@@ -167,17 +195,21 @@ export class Transport {
167
195
  this.born = now;
168
196
  // Calls the host made before, played again where a seek went back past them; one may set the
169
197
  // rate, which moves where this sync lands.
170
- if (this.tape !== undefined && now > this.now) {
171
- const reading = () => (this.pace === null ? u : this.pace.reading(u));
172
- replay(this, reading);
198
+ // A call played again at this sync's own host time began its frame, moved there before it.
199
+ let begun = false;
200
+ if (this.tape !== undefined && later) {
201
+ begun = replay(this, (at) => at <= u) === u;
173
202
  pace = this.pace;
174
- now = reading();
203
+ now = pace === null ? u : pace.reading(u);
204
+ }
205
+ if (!begun) {
206
+ this.tick(now);
207
+ // Host time moving while the mix clock stands still lands what waits on host time or the host.
208
+ const moved = now !== this.now;
209
+ for (const m of this.members)
210
+ if (moved || (later && pace !== null && waits(m)))
211
+ move(m, now);
175
212
  }
176
- // Host time moving while the mix clock stands still lands what waits on host time or the host.
177
- const moved = now !== this.now;
178
- for (const m of this.members)
179
- if (moved || (later && pace !== null && waits(m)))
180
- move(m, now);
181
213
  const history = this.history;
182
214
  this.kept();
183
215
  const reach = this.keepsFrom();
@@ -189,6 +221,8 @@ export class Transport {
189
221
  }
190
222
  if (history !== undefined && this.dropped.length > 0)
191
223
  this.dropped = this.dropped.filter((d) => d.at >= reach);
224
+ this.frames.shed(reach);
225
+ pageTransport(this);
192
226
  this.woken = false;
193
227
  // It is a frame too, which asks every weight signal again, so one reading input follows it.
194
228
  if (still)
@@ -203,8 +237,17 @@ export class Transport {
203
237
  b.sync();
204
238
  }
205
239
  }
240
+ /** Begins a frame at mix time `at` and the host time the transport stands at. */
241
+ tick(at) {
242
+ const seq = ++this.seq;
243
+ if (this.history !== undefined)
244
+ this.frames.push({ seq, at, u: this.u });
245
+ for (const m of this.members)
246
+ m.looked = false;
247
+ }
206
248
  rebase() {
207
249
  this.rebasing = true;
250
+ this.settle();
208
251
  }
209
252
  /**
210
253
  * Moves `floor` past what the mix clock standing at `now` has let go of. With a store nothing is
@@ -242,6 +285,7 @@ export class Transport {
242
285
  throw new RangeError(`blits: a mix's rate is a finite number, 0 or more, not ${rate}`);
243
286
  if (this.pace === null) {
244
287
  this.pace = new Pace(this.history !== undefined);
288
+ this.pacedSeq = this.seq;
245
289
  // Until now mix time was host time, so a pending voice's start is the host time it was given.
246
290
  for (const m of this.members)
247
291
  for (const v of m.cued)
@@ -249,7 +293,7 @@ export class Transport {
249
293
  pin(m, v, v.start);
250
294
  }
251
295
  const reach = this.history === undefined ? Number.NEGATIVE_INFINITY : this.keepsFrom();
252
- this.pace.change(this.u, rate, over, reach);
296
+ this.pace.change(this.u, rate, over, reach, this.seq);
253
297
  for (const m of this.members)
254
298
  m.stir();
255
299
  record(this, 'rate', () => this.ramp(rate, over));
@@ -263,14 +307,15 @@ export function announce(transport, name, opts, slot, mix) {
263
307
  score: opts.score,
264
308
  tags: opts.tags ?? [],
265
309
  at,
266
- made: Number.isNaN(transport.now) ? Number.NEGATIVE_INFINITY : transport.now,
267
310
  order: transport.nextId++,
311
+ seq: transport.seq,
268
312
  slot,
269
313
  mix,
270
314
  };
271
315
  transport.announced.push(mark);
316
+ transport.settle();
272
317
  record(transport, 'announce', () => {
273
- transport.announced.push({ ...mark });
318
+ transport.announced.push({ ...mark, seq: transport.seq });
274
319
  });
275
320
  }
276
321
  /**
@@ -0,0 +1,18 @@
1
+ import type { Laned } from './lane.js';
2
+ /** `d` less whole turns, so within half a turn of 0: the short way round a circle of `turn`. */
3
+ export declare function shortWay(d: number, turn: number): number;
4
+ /**
5
+ * Folds a rotation's value into a subject's numbers at `base`, at weight `w`, in the channel's own
6
+ * arithmetic: an angle the short way round and then as a sum, anything else through the channel's
7
+ * `fold`, which takes its axes as one value.
8
+ */
9
+ export declare function turnInto(ch: Laned, values: Float64Array, base: number, value: unknown, w: number): void;
10
+ /** `turnInto` for a value held as numbers at `from` in `source`, as a locus holds what it gathered. */
11
+ export declare function turnFrom(ch: Laned, values: Float64Array, base: number, source: Float64Array, from: number, w: number): void;
12
+ /**
13
+ * A locus member's rotation lerped by `u` into what the members before it left at `base`, through
14
+ * the channel's own `lerp`; the first member's value as it is, where `taken` is 0.
15
+ */
16
+ export declare function turnToward(ch: Laned, values: Float64Array, base: number, value: unknown, taken: number, u: number): void;
17
+ /** Sets every numbered subject's values on a lane to the channel's rest, as a fill starts from. */
18
+ export declare function rested(ch: Laned, size: number): void;
package/dist/turns.js ADDED
@@ -0,0 +1,72 @@
1
+ import { doubles } from './channels.js';
2
+ /** `d` less whole turns, so within half a turn of 0: the short way round a circle of `turn`. */
3
+ export function shortWay(d, turn) {
4
+ return d - turn * Math.round(d / turn);
5
+ }
6
+ let held = doubles(4, 0);
7
+ let other = doubles(4, 0);
8
+ /** A subject's numbers on a lane as an array a channel's own functions take: `to`, or one as long. */
9
+ function lift(values, base, axes, to) {
10
+ const out = to.length === axes ? to : doubles(axes, 0);
11
+ for (let a = 0; a < axes; a++)
12
+ out[a] = values[base + a];
13
+ return out;
14
+ }
15
+ /**
16
+ * Folds a rotation's value into a subject's numbers at `base`, at weight `w`, in the channel's own
17
+ * arithmetic: an angle the short way round and then as a sum, anything else through the channel's
18
+ * `fold`, which takes its axes as one value.
19
+ */
20
+ export function turnInto(ch, values, base, value, w) {
21
+ if (ch.turn > 0) {
22
+ values[base] = values[base] + shortWay(value, ch.turn) * w;
23
+ return;
24
+ }
25
+ const axes = ch.axes;
26
+ held = lift(values, base, axes, held);
27
+ const out = ch.channel.fold(held, value, w);
28
+ for (let a = 0; a < axes; a++)
29
+ values[base + a] = out[a];
30
+ }
31
+ /** `turnInto` for a value held as numbers at `from` in `source`, as a locus holds what it gathered. */
32
+ export function turnFrom(ch, values, base, source, from, w) {
33
+ if (ch.turn > 0) {
34
+ turnInto(ch, values, base, source[from], w);
35
+ return;
36
+ }
37
+ other = lift(source, from, ch.axes, other);
38
+ turnInto(ch, values, base, other, w);
39
+ }
40
+ /**
41
+ * A locus member's rotation lerped by `u` into what the members before it left at `base`, through
42
+ * the channel's own `lerp`; the first member's value as it is, where `taken` is 0.
43
+ */
44
+ export function turnToward(ch, values, base, value, taken, u) {
45
+ if (ch.turn > 0) {
46
+ const was = values[base];
47
+ const v = value;
48
+ values[base] = taken === 0 ? v : was + shortWay(v - was, ch.turn) * u;
49
+ return;
50
+ }
51
+ const axes = ch.axes;
52
+ let out = value;
53
+ if (taken !== 0) {
54
+ held = lift(values, base, axes, held);
55
+ out = ch.channel.lerp(held, value, u);
56
+ }
57
+ for (let a = 0; a < axes; a++)
58
+ values[base + a] = out[a];
59
+ }
60
+ /** Sets every numbered subject's values on a lane to the channel's rest, as a fill starts from. */
61
+ export function rested(ch, size) {
62
+ const values = ch.values;
63
+ const axes = ch.axes;
64
+ if (ch.op !== 'own') {
65
+ values.fill(ch.rest, 0, size * axes);
66
+ return;
67
+ }
68
+ const start = ch.start;
69
+ for (let i = 0; i < size * axes; i += axes)
70
+ for (let a = 0; a < axes; a++)
71
+ values[i + a] = start[a];
72
+ }
package/dist/tweened.js CHANGED
@@ -5,7 +5,7 @@ const SPAN = 1e-4;
5
5
  * `read` asks for it; a mix and a retarget never use it.
6
6
  */
7
7
  let slope = false;
8
- /** The last tween sample's time, curve and length, and the share of the way it had left to go. */
8
+ /** The last tween sample's progress and curve, and the share of the way it had left to go. */
9
9
  const memo = { u: Number.NaN, ease: undefined, left: 0 };
10
10
  /** Turns the velocity `eased` works out on or off. */
11
11
  export function sloped(on) {
@@ -0,0 +1,46 @@
1
+ /**
2
+ * One field of a delta, with its own arithmetic.
3
+ *
4
+ * @category channel
5
+ */
6
+ export interface Channel<V> {
7
+ /**
8
+ * Names this channel's arithmetic, so a patch written against one channel can be checked against
9
+ * another of the same name. Two channels of one kind must fold identically; the stock ones set it,
10
+ * as `'sum'` or `'vec(3, sum)'`. Absent, a channel matches only itself.
11
+ */
12
+ kind?: string;
13
+ /**
14
+ * The range a numeric value means anything in, such as 0..1 for an opacity. The mix clamps the
15
+ * folded value to it, axis by axis for an array, so neither stacked voices nor a retarget that
16
+ * carries speed can push it out; overshoot stops flat at the bound.
17
+ */
18
+ bounds?: readonly [min: number, max: number];
19
+ /** Identity. Absent means the channel has none: it replaces rather than contributes. */
20
+ rest?: V;
21
+ /** Fold two contributions into one. */
22
+ merge(a: V, b: V): V;
23
+ /** Fade toward `rest` by weight 0..1. Required when `rest` is set; absent otherwise. */
24
+ scale?(v: V, w: number): V;
25
+ /** Interpolate, for retargeting, for blending alternatives, and for folding a locus. */
26
+ lerp(a: V, b: V, u: number): V;
27
+ /**
28
+ * Optional: `merge(into, scale(v, w))`, written into `into` and returned, so a channel whose
29
+ * values are objects need not allocate per contribution. The mix only hands it a value it made.
30
+ */
31
+ fold?(into: V, v: V, w: number): V;
32
+ /**
33
+ * Optional: a fresh copy of `v`, which the mix makes of `rest` for `fold` to write into. The mix
34
+ * copies arrays, typed arrays and plain objects itself; a channel whose `rest` is anything else,
35
+ * such as a class instance, and that has `fold`, needs this or `cue` refuses it.
36
+ */
37
+ copy?(v: V): V;
38
+ }
39
+ /**
40
+ * The channel set for one kind of delta.
41
+ *
42
+ * @category channel
43
+ */
44
+ export type Kit<O> = {
45
+ readonly [K in keyof O]-?: Channel<NonNullable<O[K]>>;
46
+ };
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,139 @@
1
+ import type { VoiceSpec } from './voice.js';
2
+ /**
3
+ * What a mix or transport remembers, for `seek` and `project` to read back: see
4
+ * `MixOptions.history`.
5
+ *
6
+ * @category mix
7
+ */
8
+ export interface HistoryOptions {
9
+ /** How much stays in memory, ms of mix time. With a `store`, the rest is paged out, not dropped. */
10
+ ms: number;
11
+ every?: number;
12
+ inputs?: boolean;
13
+ tape?: TapeMaker;
14
+ /**
15
+ * Where history older than `ms` goes instead of being dropped. A `seek` or `project` reaching
16
+ * past memory needs `prepare` first, or throws `HistoryMiss`. With a store, blits never prunes
17
+ * the tape: the host bounds it.
18
+ */
19
+ store?: HistoryStore;
20
+ /** Rebuilds a cued voice's spec from the descriptor it was cued with, so the voice can be paged out. */
21
+ revive?(d: Described): VoiceSpec<any, any>;
22
+ }
23
+ /**
24
+ * A voice as plain data: `kind` names what built it, and `data` survives `structuredClone`.
25
+ *
26
+ * @category mix
27
+ */
28
+ export interface Described {
29
+ kind: string;
30
+ data: unknown;
31
+ }
32
+ /**
33
+ * Where a history `store` keeps what memory let go of. blits decides what is kept and when it is
34
+ * read back; the store decides where it lives.
35
+ *
36
+ * @category mix
37
+ */
38
+ export interface HistoryStore {
39
+ /** Records leaving memory. One brought back by a seek may be paged again: keep one per key and `seq`. */
40
+ page(out: readonly Paged[]): void;
41
+ /**
42
+ * What a restore to mix time `t` needs, for every mix: from each stream, per voice and subject,
43
+ * the latest record at or before `t` and every record after it. Returning everything is fine.
44
+ */
45
+ load(t: number): Promise<readonly Paged[]>;
46
+ /**
47
+ * A seek went back to frame `seq`: every record whose `seq` is greater is from a future the tape
48
+ * makes again. Forget them.
49
+ */
50
+ cut(seq: number): void;
51
+ }
52
+ /**
53
+ * What a history record holds: a voice's `controls`, a stateful record's `snap`, an `input`
54
+ * signal's reading, a subject's record when it `left` a voice, a motion subject's `released` run
55
+ * or older `stretch`, the `host` fields patches read, a whole `voice` that has left, or the
56
+ * transport's own: a `mark` announced on it, a change of its `pace`, and a `frame` it played.
57
+ *
58
+ * @category mix
59
+ */
60
+ export type PagedStream = 'voice' | 'snap' | 'input' | 'left' | 'released' | 'stretch' | 'host' | 'controls' | 'mark' | 'pace' | 'frame';
61
+ /**
62
+ * One history record leaving memory, as plain data.
63
+ *
64
+ * @category mix
65
+ */
66
+ export interface Paged {
67
+ /** The mix's `name`; '' for a mix made alone without one, and for the transport's own records. */
68
+ mix: string;
69
+ stream: PagedStream;
70
+ /** The voice's id, for every stream but `host` and the transport's own. */
71
+ voice?: number;
72
+ /** The subject's key, for a record of one subject; for a `mark`, the number it was announced as. */
73
+ subject?: string | number;
74
+ /** Mix time. */
75
+ at: number;
76
+ /**
77
+ * The transport's frame it was made in. Rate 0 holds mix time still across frames, and a fade
78
+ * given a time already passed is filed behind the frame that decided it, so a cut goes by this.
79
+ */
80
+ seq: number;
81
+ data: unknown;
82
+ }
83
+ /**
84
+ * One host call a mix recorded on its tape: `apply` makes it again, and `invert` does nothing,
85
+ * since a mix goes back by restoring what it kept rather than by undoing calls.
86
+ *
87
+ * @category mix
88
+ */
89
+ export interface TapeOp {
90
+ apply(adapter: unknown): void;
91
+ invert(): TapeOp;
92
+ label?: string;
93
+ }
94
+ /**
95
+ * One future a tape keeps at its current entry: the calls the host made after the mix last went
96
+ * back here, before it made new ones.
97
+ *
98
+ * @category mix
99
+ */
100
+ export interface TapeBranch {
101
+ /** Its first entry's id, which `switchBranch` takes. */
102
+ id: number;
103
+ label: string;
104
+ timestamp: number;
105
+ /** How many entries run along it from here. */
106
+ length: number;
107
+ /** Whether the mix plays it on from here. */
108
+ current: boolean;
109
+ }
110
+ /**
111
+ * Where a mix keeps the host's calls, by host time, for `seek`. weasel-history's `History`
112
+ * (`@weasel-js/history`) has this shape. A host reads `branches` and calls `switchBranch` to pick
113
+ * which recorded future plays on; moving it any other way is the mix's job.
114
+ *
115
+ * @category mix
116
+ */
117
+ export interface Tape {
118
+ recordEntry(ops: TapeOp[], label: string): void;
119
+ undoDepth(): number;
120
+ timestampAt(i: number): number | undefined;
121
+ depthAt(t: number): number;
122
+ goto(n: number): void;
123
+ redo(): void;
124
+ prune(t: number): void;
125
+ branches(): readonly TapeBranch[];
126
+ switchBranch(id: number): void;
127
+ }
128
+ /**
129
+ * Makes a mix's tape: weasel-history's `createHistory` is one. The mix passes the clock it stamps
130
+ * calls with, branching on, so a call made after going back keeps the old future as a branch, and
131
+ * coalescing off.
132
+ *
133
+ * @category mix
134
+ */
135
+ export type TapeMaker = (adapter: unknown, opts: {
136
+ now: () => number;
137
+ branching: boolean;
138
+ coalesceWindowMs: number;
139
+ }) => Tape;
@@ -0,0 +1 @@
1
+ export {};