@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/transport.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { listedAll } from './marks.js';
2
2
  import { move, nextFrame, waits } from './move.js';
3
3
  import { Pace } from './pace.js';
4
- import { Pager, prepare } from './paging.js';
4
+ import { Pager, pageTransport, prepare } from './paging.js';
5
5
  import { pin } from './place.js';
6
6
  import { projectAll } from './project.js';
7
7
  import { scoredDrop, scoredJoin } from './scored.js';
@@ -9,6 +9,10 @@ import { seek } from './seek.js';
9
9
  import { record, replay, tapeOf } from './tape.js';
10
10
  /** How far the tape's reach moves before it is pruned again, ms of mix time. */
11
11
  const TAPE_STEP_MS = 1000;
12
+ /** Whether a change made in frame `seq` falls inside `cut`; `early` for one made before the frame was read. */
13
+ export function within(cut, seq, early = false) {
14
+ return seq < cut.seq || (seq === cut.seq && (!cut.strict || early));
15
+ }
12
16
  /**
13
17
  * The clock its mixes play on: host time less what `rebase` took out, at the rate its `pace`
14
18
  * sets, moved by `sync` and `seek`, and the tape of calls a seek plays again. A mix made alone
@@ -29,6 +33,13 @@ export class Transport {
29
33
  woken = false;
30
34
  /** The mix clock at the last sync or seek. */
31
35
  now = Number.NaN;
36
+ /**
37
+ * The number of the frame being played: each sync and seek begins one, and so does each host
38
+ * time the tape plays calls again at.
39
+ */
40
+ seq = 0;
41
+ /** Under history, the frames a seek or read back may reach, oldest first. */
42
+ frames = [];
32
43
  /** Host time at the last sync: the host's timestamp less every gap `rebase` took out. */
33
44
  u = Number.NaN;
34
45
  offset = 0;
@@ -42,6 +53,20 @@ export class Transport {
42
53
  taped = Number.NEGATIVE_INFINITY;
43
54
  /** The rate; null while it has never been set, when mix time is host time. */
44
55
  pace = null;
56
+ /** The latest mix time a voice any mix on it let go of from `gone` had left at. */
57
+ forgotTo = Number.NEGATIVE_INFINITY;
58
+ /**
59
+ * The latest mix time a host changed a mix on it, or a voice or a subject left one: without
60
+ * history, the earliest time a read back still finds the mixes as they stand now.
61
+ */
62
+ settled = Number.NEGATIVE_INFINITY;
63
+ /** Records a change or a leaving at the mix time now; before the first sync, nothing. */
64
+ settle() {
65
+ if (this.now > this.settled)
66
+ this.settled = this.now;
67
+ }
68
+ /** The frame the rate was first set in, which a seek or read back to before it finds unset. */
69
+ pacedSeq = Number.POSITIVE_INFINITY;
45
70
  /** True while the tape makes a recorded call again, so it is not recorded twice. */
46
71
  replaying = false;
47
72
  announced = [];
@@ -64,7 +89,7 @@ export class Transport {
64
89
  get projecting() {
65
90
  return this.members.some((m) => m.projecting);
66
91
  }
67
- /** Takes a mix on, after every mix that joined before it; `slot` keeps a copy's in place. */
92
+ /** Takes a mix on, after every mix that joined before it; `slot` keeps one a seek puts back in place. */
68
93
  join(mix, slot = this.slots++) {
69
94
  mix.slot = slot;
70
95
  const i = this.members.findIndex((m) => m.slot > slot);
@@ -83,16 +108,16 @@ export class Transport {
83
108
  scoredDrop(m);
84
109
  m.dropped = true;
85
110
  if (this.history !== undefined) {
86
- this.dropped.push({ mix: m, at: this.now });
111
+ this.dropped.push({ mix: m, at: this.now, seq: this.seq });
87
112
  record(this, 'drop', () => this.drop(mix));
88
113
  }
89
114
  }
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);
115
+ /** A seek back to frame `seq`: every mix dropped after it is back on, as it stood then. */
116
+ undrop(seq) {
117
+ const back = this.dropped.filter((d) => d.seq > seq);
93
118
  if (back.length === 0)
94
119
  return;
95
- this.dropped = this.dropped.filter((d) => d.at <= t);
120
+ this.dropped = this.dropped.filter((d) => d.seq <= seq);
96
121
  for (const d of back) {
97
122
  d.mix.dropped = false;
98
123
  this.join(d.mix, d.mix.slot);
@@ -151,6 +176,8 @@ export class Transport {
151
176
  }
152
177
  }
153
178
  syncAt(timestamp) {
179
+ if (Number.isNaN(timestamp))
180
+ throw new RangeError('blits: sync was given NaN; it takes the host clock as a number');
154
181
  if (timestamp < this.last && !this.rebasing)
155
182
  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
183
  if (this.rebasing && !Number.isNaN(this.last))
@@ -167,17 +194,21 @@ export class Transport {
167
194
  this.born = now;
168
195
  // Calls the host made before, played again where a seek went back past them; one may set the
169
196
  // 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);
197
+ // A call played again at this sync's own host time began its frame, moved there before it.
198
+ let begun = false;
199
+ if (this.tape !== undefined && later) {
200
+ begun = replay(this, (at) => at <= u) === u;
173
201
  pace = this.pace;
174
- now = reading();
202
+ now = pace === null ? u : pace.reading(u);
203
+ }
204
+ if (!begun) {
205
+ this.tick(now);
206
+ // Host time moving while the mix clock stands still lands what waits on host time or the host.
207
+ const moved = now !== this.now;
208
+ for (const m of this.members)
209
+ if (moved || (later && pace !== null && waits(m)))
210
+ move(m, now);
175
211
  }
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
212
  const history = this.history;
182
213
  this.kept();
183
214
  const reach = this.keepsFrom();
@@ -189,6 +220,13 @@ export class Transport {
189
220
  }
190
221
  if (history !== undefined && this.dropped.length > 0)
191
222
  this.dropped = this.dropped.filter((d) => d.at >= reach);
223
+ const frames = this.frames;
224
+ let n = 0;
225
+ while (n + 1 < frames.length && frames[n + 1].at <= reach)
226
+ n++;
227
+ if (n > 0)
228
+ frames.splice(0, n);
229
+ pageTransport(this);
192
230
  this.woken = false;
193
231
  // It is a frame too, which asks every weight signal again, so one reading input follows it.
194
232
  if (still)
@@ -203,8 +241,38 @@ export class Transport {
203
241
  b.sync();
204
242
  }
205
243
  }
244
+ /** Begins a frame at mix time `at` and the host time the transport stands at. */
245
+ tick(at) {
246
+ const seq = ++this.seq;
247
+ if (this.history !== undefined)
248
+ this.frames.push({ seq, at, u: this.u });
249
+ for (const m of this.members)
250
+ m.looked = false;
251
+ }
252
+ /**
253
+ * The frame a seek or read to mix time `t` lands on: the last one standing at or before `t`,
254
+ * since rate 0 can hold the clock at `t` for many.
255
+ */
256
+ frameAt(t) {
257
+ const frames = this.frames;
258
+ let lo = 0;
259
+ let hi = frames.length - 1;
260
+ let found;
261
+ while (lo <= hi) {
262
+ const mid = (lo + hi) >> 1;
263
+ const f = frames[mid];
264
+ if (f.at <= t) {
265
+ found = f;
266
+ lo = mid + 1;
267
+ }
268
+ else
269
+ hi = mid - 1;
270
+ }
271
+ return found;
272
+ }
206
273
  rebase() {
207
274
  this.rebasing = true;
275
+ this.settle();
208
276
  }
209
277
  /**
210
278
  * Moves `floor` past what the mix clock standing at `now` has let go of. With a store nothing is
@@ -242,6 +310,7 @@ export class Transport {
242
310
  throw new RangeError(`blits: a mix's rate is a finite number, 0 or more, not ${rate}`);
243
311
  if (this.pace === null) {
244
312
  this.pace = new Pace(this.history !== undefined);
313
+ this.pacedSeq = this.seq;
245
314
  // Until now mix time was host time, so a pending voice's start is the host time it was given.
246
315
  for (const m of this.members)
247
316
  for (const v of m.cued)
@@ -249,7 +318,7 @@ export class Transport {
249
318
  pin(m, v, v.start);
250
319
  }
251
320
  const reach = this.history === undefined ? Number.NEGATIVE_INFINITY : this.keepsFrom();
252
- this.pace.change(this.u, rate, over, reach);
321
+ this.pace.change(this.u, rate, over, reach, this.seq);
253
322
  for (const m of this.members)
254
323
  m.stir();
255
324
  record(this, 'rate', () => this.ramp(rate, over));
@@ -263,14 +332,15 @@ export function announce(transport, name, opts, slot, mix) {
263
332
  score: opts.score,
264
333
  tags: opts.tags ?? [],
265
334
  at,
266
- made: Number.isNaN(transport.now) ? Number.NEGATIVE_INFINITY : transport.now,
267
335
  order: transport.nextId++,
336
+ seq: transport.seq,
268
337
  slot,
269
338
  mix,
270
339
  };
271
340
  transport.announced.push(mark);
341
+ transport.settle();
272
342
  record(transport, 'announce', () => {
273
- transport.announced.push({ ...mark });
343
+ transport.announced.push({ ...mark, seq: transport.seq });
274
344
  });
275
345
  }
276
346
  /**
@@ -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 {};