@msb235/blits 0.10.0 → 0.11.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 (94) hide show
  1. package/CHANGELOG.md +151 -0
  2. package/README.md +15 -4
  3. package/dist/blend.js +1 -5
  4. package/dist/book.d.ts +2 -0
  5. package/dist/book.js +27 -3
  6. package/dist/chain.d.ts +11 -0
  7. package/dist/chain.js +24 -2
  8. package/dist/clock.d.ts +14 -0
  9. package/dist/clock.js +9 -1
  10. package/dist/columns.d.ts +4 -5
  11. package/dist/columns.js +11 -5
  12. package/dist/crowd.d.ts +1 -1
  13. package/dist/crowd.js +4 -1
  14. package/dist/cue.js +52 -5
  15. package/dist/departed.d.ts +3 -2
  16. package/dist/departed.js +7 -4
  17. package/dist/due.d.ts +4 -0
  18. package/dist/due.js +16 -3
  19. package/dist/fade.d.ts +12 -5
  20. package/dist/fade.js +55 -17
  21. package/dist/fold.d.ts +1 -1
  22. package/dist/fold.js +53 -6
  23. package/dist/frames.d.ts +2 -0
  24. package/dist/frames.js +17 -0
  25. package/dist/handle.d.ts +5 -2
  26. package/dist/handle.js +20 -12
  27. package/dist/held.d.ts +8 -0
  28. package/dist/held.js +37 -6
  29. package/dist/history.d.ts +6 -0
  30. package/dist/history.js +15 -5
  31. package/dist/hosts.js +18 -6
  32. package/dist/index.d.ts +2 -2
  33. package/dist/index.js +1 -1
  34. package/dist/keyfill.js +3 -1
  35. package/dist/lane.d.ts +1 -1
  36. package/dist/lane.js +3 -1
  37. package/dist/lanes.d.ts +8 -0
  38. package/dist/lanes.js +21 -4
  39. package/dist/lean.d.ts +15 -0
  40. package/dist/lean.js +23 -0
  41. package/dist/leavings.d.ts +6 -1
  42. package/dist/leavings.js +16 -2
  43. package/dist/marks.d.ts +7 -3
  44. package/dist/marks.js +102 -27
  45. package/dist/meet.js +1 -0
  46. package/dist/mixer.d.ts +21 -6
  47. package/dist/mixer.js +70 -29
  48. package/dist/motion.d.ts +4 -1
  49. package/dist/motion.js +16 -2
  50. package/dist/motions.d.ts +4 -0
  51. package/dist/motions.js +45 -11
  52. package/dist/move.js +28 -10
  53. package/dist/owner.d.ts +18 -2
  54. package/dist/owner.js +38 -2
  55. package/dist/pack.d.ts +1 -0
  56. package/dist/pack.js +3 -0
  57. package/dist/placed.d.ts +19 -0
  58. package/dist/placed.js +54 -0
  59. package/dist/project.d.ts +5 -2
  60. package/dist/project.js +236 -32
  61. package/dist/pull.js +37 -8
  62. package/dist/qualify.js +8 -2
  63. package/dist/reading.d.ts +6 -0
  64. package/dist/reading.js +6 -0
  65. package/dist/record.js +1 -0
  66. package/dist/restored.d.ts +7 -4
  67. package/dist/restored.js +13 -10
  68. package/dist/revive.d.ts +5 -3
  69. package/dist/revive.js +10 -3
  70. package/dist/rows.js +15 -5
  71. package/dist/seek.d.ts +7 -2
  72. package/dist/seek.js +90 -35
  73. package/dist/shared.d.ts +5 -0
  74. package/dist/shared.js +5 -0
  75. package/dist/signals.d.ts +10 -0
  76. package/dist/signals.js +17 -0
  77. package/dist/store.d.ts +11 -0
  78. package/dist/store.js +19 -0
  79. package/dist/strays.js +50 -2
  80. package/dist/tape.d.ts +29 -4
  81. package/dist/tape.js +49 -11
  82. package/dist/transport.d.ts +11 -2
  83. package/dist/transport.js +20 -9
  84. package/dist/types/mix.d.ts +39 -14
  85. package/dist/types/patch.d.ts +5 -1
  86. package/dist/types/place.d.ts +11 -4
  87. package/dist/types/transport.d.ts +12 -3
  88. package/dist/types/voice.d.ts +62 -4
  89. package/dist/unpage.d.ts +6 -1
  90. package/dist/unpage.js +34 -2
  91. package/dist/voice.d.ts +57 -11
  92. package/dist/voice.js +43 -3
  93. package/docs/schema.html +68 -36
  94. package/package.json +7 -2
package/dist/tape.js CHANGED
@@ -1,4 +1,13 @@
1
1
  import { move } from './move.js';
2
+ const live = { transport: (of) => of, mix: (of) => of };
3
+ /** The stage the tape is playing calls on; the live one outside a read ahead's replay. */
4
+ let stage = live;
5
+ /** What a call threw on a read ahead's stage, for `replay` to throw once the tape has the entry back. */
6
+ let failed;
7
+ /** Refuses a recorded call a read ahead's copies cannot take. */
8
+ export function unplayable(what) {
9
+ throw new Error(`blits: the tape holds ${what}, which a read ahead cannot play; seek there to see it`);
10
+ }
2
11
  /** What a recorded call's `invert` gives: a mix goes back by restoring, never by undoing a call. */
3
12
  const undone = { apply() { }, invert: () => undone };
4
13
  /**
@@ -15,7 +24,10 @@ export function tapeOf(transport) {
15
24
  coalesceWindowMs: 0,
16
25
  });
17
26
  }
18
- /** Records a host call, which `again` makes once more when the mix plays past it after a seek. */
27
+ /**
28
+ * Records a host call, which `again` makes once more when the mix plays past it after a seek, or
29
+ * when a read ahead does, on the stage it is given.
30
+ */
19
31
  export function record(on, label, again) {
20
32
  const tape = on.tape;
21
33
  if (tape === undefined || on.replaying || on.projecting)
@@ -23,7 +35,16 @@ export function record(on, label, again) {
23
35
  const op = {
24
36
  label,
25
37
  apply() {
26
- again();
38
+ if (stage === live)
39
+ again(stage);
40
+ // Thrown inside the tape's `redo`, it would leave the tape without the entry.
41
+ else
42
+ try {
43
+ again(stage);
44
+ }
45
+ catch (e) {
46
+ failed ??= { error: e };
47
+ }
27
48
  },
28
49
  invert: () => undone,
29
50
  };
@@ -33,16 +54,26 @@ export function record(on, label, again) {
33
54
  export function hostAt(clock, t) {
34
55
  return clock.pace === null ? t : clock.pace.timeOf(t);
35
56
  }
57
+ /**
58
+ * The host time a move ahead to mix time `t` lands at: never behind the clock's own, since rate 0
59
+ * reads `t` at every host time from the earliest `hostAt` gives, and a call stamped earlier than
60
+ * one already on the tape would cut it out of order.
61
+ */
62
+ export function hostAhead(clock, t) {
63
+ const at = hostAt(clock, t);
64
+ return clock.u > at ? clock.u : at;
65
+ }
36
66
  /**
37
67
  * Makes the recorded calls `due` by the host time each was made at again, each host time's in a
38
68
  * frame of its own with every mix on the transport moved to where it stood then, as the host made
39
- * them. Answers the host time of the last frame it played, NaN for none.
69
+ * them. Answers the host time of the last frame it played, NaN for none. Given `onto` and the
70
+ * stage of its copies, it makes them on those, and the caller puts the tape back.
40
71
  */
41
- export function replay(transport, due) {
72
+ export function replay(transport, due, onto = transport, on = live) {
42
73
  const tape = transport.tape;
43
74
  if (tape === undefined)
44
75
  return Number.NaN;
45
- const u = transport.u;
76
+ const u = onto.u;
46
77
  let last = Number.NaN;
47
78
  try {
48
79
  for (;;) {
@@ -51,23 +82,30 @@ export function replay(transport, due) {
51
82
  return last;
52
83
  if (at !== last) {
53
84
  last = at;
54
- transport.u = at;
55
- const now = transport.pace === null ? at : transport.pace.reading(at);
56
- if (now > transport.now)
57
- for (const m of transport.members)
85
+ onto.u = at;
86
+ const now = onto.pace === null ? at : onto.pace.reading(at);
87
+ if (now > onto.now)
88
+ for (const m of onto.members)
58
89
  move(m, now);
59
- transport.tick(transport.now);
90
+ onto.tick(onto.now);
60
91
  }
61
92
  transport.replaying = true;
93
+ stage = on;
62
94
  try {
63
95
  tape.redo();
64
96
  }
65
97
  finally {
66
98
  transport.replaying = false;
99
+ stage = live;
100
+ }
101
+ if (failed !== undefined) {
102
+ const { error } = failed;
103
+ failed = undefined;
104
+ throw error;
67
105
  }
68
106
  }
69
107
  }
70
108
  finally {
71
- transport.u = u;
109
+ onto.u = u;
72
110
  }
73
111
  }
@@ -94,6 +94,11 @@ export declare class Transport implements TransportApi {
94
94
  settle(): void;
95
95
  /** The frame the rate was first set in, which a seek or read back to before it finds unset. */
96
96
  pacedSeq: number;
97
+ /**
98
+ * On a read ahead's own transport, the retargets and pushes its tape's calls made, which
99
+ * `reading.ahead` hands the motion patches while it reads.
100
+ */
101
+ ahead: Map<object, Map<unknown, unknown[]>>;
97
102
  /** True while the tape makes a recorded call again, so it is not recorded twice. */
98
103
  replaying: boolean;
99
104
  announced: Announced[];
@@ -114,7 +119,9 @@ export declare class Transport implements TransportApi {
114
119
  drop<I, O, H>(mix: Mix<I, O, H>): void;
115
120
  /** A seek back to frame `seq`: every mix dropped after it is back on, as it stood then. */
116
121
  undrop(seq: number): void;
117
- seek(time: number): void;
122
+ seek(to: number | {
123
+ readonly frame: number;
124
+ }): void;
118
125
  prepare(time: number): Promise<void>;
119
126
  announce(name: string, opts: {
120
127
  at?: number;
@@ -122,7 +129,9 @@ export declare class Transport implements TransportApi {
122
129
  tags?: readonly string[];
123
130
  }): void;
124
131
  marks(from: number, to: number): Marked[];
125
- project(time: number): TransportProjection;
132
+ project(at: number | {
133
+ readonly frame: number;
134
+ }): TransportProjection;
126
135
  get live(): boolean;
127
136
  get inert(): boolean;
128
137
  onWake(fn: () => void): () => void;
package/dist/transport.js CHANGED
@@ -68,6 +68,11 @@ export class Transport {
68
68
  }
69
69
  /** The frame the rate was first set in, which a seek or read back to before it finds unset. */
70
70
  pacedSeq = Number.POSITIVE_INFINITY;
71
+ /**
72
+ * On a read ahead's own transport, the retargets and pushes its tape's calls made, which
73
+ * `reading.ahead` hands the motion patches while it reads.
74
+ */
75
+ ahead = new Map();
71
76
  /** True while the tape makes a recorded call again, so it is not recorded twice. */
72
77
  replaying = false;
73
78
  announced = [];
@@ -110,7 +115,11 @@ export class Transport {
110
115
  m.dropped = true;
111
116
  if (this.history !== undefined) {
112
117
  this.dropped.push({ mix: m, at: this.now, seq: this.seq });
113
- record(this, 'drop', () => this.drop(mix));
118
+ record(this, 'drop', (stage) => {
119
+ const to = stage.mix(m);
120
+ if (to !== undefined)
121
+ stage.transport(this).drop(to);
122
+ });
114
123
  }
115
124
  }
116
125
  /** A seek back to frame `seq`: every mix dropped after it is back on, as it stood then. */
@@ -124,8 +133,8 @@ export class Transport {
124
133
  this.join(d.mix, d.mix.slot);
125
134
  }
126
135
  }
127
- seek(time) {
128
- seek(this, time);
136
+ seek(to) {
137
+ seek(this, to);
129
138
  }
130
139
  prepare(time) {
131
140
  return prepare(this, time);
@@ -138,8 +147,8 @@ export class Transport {
138
147
  marks(from, to) {
139
148
  return listedAll(this, from, to).map(({ order: _, ...m }) => m);
140
149
  }
141
- project(time) {
142
- return projectAll(this, time);
150
+ project(at) {
151
+ return projectAll(this, at);
143
152
  }
144
153
  get live() {
145
154
  return this.members.some((m) => m.live);
@@ -286,7 +295,8 @@ export class Transport {
286
295
  if (!(rate >= 0 && rate < Number.POSITIVE_INFINITY))
287
296
  throw new RangeError(`blits: a mix's rate is a finite number, 0 or more, not ${rate}`);
288
297
  if (this.pace === null) {
289
- this.pace = new Pace(this.history !== undefined);
298
+ // A read ahead keeps every clock too: it moves once across calls a mix met frame by frame.
299
+ this.pace = new Pace(this.history !== undefined || this.projecting);
290
300
  this.pacedSeq = this.seq;
291
301
  // Until now mix time was host time, so a pending voice's start is the host time it was given.
292
302
  for (const m of this.members)
@@ -298,7 +308,7 @@ export class Transport {
298
308
  this.pace.change(this.u, rate, over, reach, this.seq);
299
309
  for (const m of this.members)
300
310
  m.stir();
301
- record(this, 'rate', () => this.ramp(rate, over));
311
+ record(this, 'rate', (stage) => stage.transport(this).ramp(rate, over));
302
312
  }
303
313
  }
304
314
  /** Puts a mark on a score: one the transport announced, or a mix on it, `slot`. */
@@ -316,8 +326,9 @@ export function announce(transport, name, opts, slot, mix) {
316
326
  };
317
327
  transport.announced.push(mark);
318
328
  transport.settle();
319
- record(transport, 'announce', () => {
320
- transport.announced.push({ ...mark, seq: transport.seq });
329
+ record(transport, 'announce', (stage) => {
330
+ const to = stage.transport(transport);
331
+ to.announced.push({ ...mark, seq: to.seq });
321
332
  });
322
333
  }
323
334
  /**
@@ -3,7 +3,7 @@ import type { HistoryOptions, Tape } from './history.js';
3
3
  import type { Patch, Sent, Signal } from './patch.js';
4
4
  import type { Booker, BookOptions, Marked } from './place.js';
5
5
  import type { Transport } from './transport.js';
6
- import type { Handle, OwnerSpec, SpanHandle, SpanSpec, VoiceSpec } from './voice.js';
6
+ import type { BlendMember, Handle, OwnerSpec, SpanHandle, SpanSpec, VoiceSpec } from './voice.js';
7
7
  /**
8
8
  * How a mix is built: its engine, reduced motion, the host, and its limits.
9
9
  *
@@ -77,7 +77,7 @@ export interface MixOptions<H = unknown> {
77
77
  /**
78
78
  * How sure a projection is of one channel: `exact` where only the clock and known changes drove
79
79
  * it; `stepped` where state was advanced across a gap in one go, which is as good as the patch's
80
- * step makes it; `held` where it depends on input from outside the clock, whose value at that time
80
+ * step makes it, or a signal's state was read back from a copy; `held` where it depends on input from outside the clock, whose value at that time
81
81
  * is not known, so the value it had is held.
82
82
  *
83
83
  * @category mix
@@ -119,9 +119,11 @@ export interface Mix<I, O, H = unknown> {
119
119
  /**
120
120
  * N voices whose weights split one signal, cued into one locus so they fold as alternatives. The
121
121
  * signal is read once per subject per frame, with the first member's setting, and every member
122
- * takes its share of that one reading; a signal keeping state keeps it once, not per member.
122
+ * takes its share of that one reading, which `share` reads outside the blend; a signal keeping
123
+ * state keeps it once, not per member. One `spec` cues them all, and a member given as a
124
+ * `BlendMember` carries its own `name`, `tags` and `hits` in place of the spec's.
123
125
  */
124
- blend(patches: readonly Patch<I, O, unknown, H>[], by: Signal<I, H>, spec?: Omit<VoiceSpec<I, O, H>, 'patch' | 'weight' | 'locus'>): Handle<I>[];
126
+ blend(members: readonly (Patch<I, O, unknown, H> | BlendMember<I, O, H>)[], by: Signal<I, H>, spec?: Omit<VoiceSpec<I, O, H>, 'patch' | 'weight' | 'locus'>): Handle<I>[];
125
127
  /**
126
128
  * Cues an owner: a voice with no patch that holds the voices cued with `owner` set to its handle,
127
129
  * and plays them on its own clock, so they can be placed, timed and faded as one. Its handle acts
@@ -139,7 +141,9 @@ export interface Mix<I, O, H = unknown> {
139
141
  * span's to set, so it takes neither `start` nor an anchored start; its `SpanHints` say how it
140
142
  * may give way. A span with a budget stays until the budget has passed, though its children
141
143
  * finish sooner, so late ones can still join, and its `end` mark is where its children end or
142
- * its budget does, whichever is later. A voice that loops for good cannot join one.
144
+ * its budget does, whichever is later. A voice that loops for good cannot join one. Throws for
145
+ * a `rate` below 0: a span lays its children out forward from its start, and its handle's `rate`
146
+ * turns it once it plays.
143
147
  */
144
148
  span(spec: SpanSpec<I, H>): SpanHandle<I>;
145
149
  /**
@@ -194,8 +198,11 @@ export interface Mix<I, O, H = unknown> {
194
198
  /**
195
199
  * Reads the mix at another mix time, without moving it. At the mix's own time it reads what
196
200
  * `probe` does: what the host has done since the last sync lands at the next. Ahead of the mix
197
- * it plays what is cued forward, and throws for a time past a call the tape will play again after a `seek` back, which
198
- * only a seek there makes; behind it, with `history`, it reads the last frame at or before `time`
201
+ * it plays what is cued forward, with the calls the tape holds after a `seek` back, and while a
202
+ * voice could carry state (a patch that steps, a signal weight) it reads each subject asked about
203
+ * at every known event on the way: each call, and each mark `marks` would list. It throws
204
+ * for the two a copy cannot take: a `drop` of a subject a motion patch holds, and a retarget or
205
+ * push made before its voice met the subject. Behind it, with `history`, it reads the last frame at or before `time`
199
206
  * as that frame showed, and throws for a time older than the history reaches. On a transport it
200
207
  * reads every mix on it together, so anchors across them answer.
201
208
  *
@@ -204,8 +211,13 @@ export interface Mix<I, O, H = unknown> {
204
211
  * alone: no state, motion, `from: 'current'`, anchor or owner, no write to its handle, and no channel without a rest
205
212
  * at a weight that moves. The mix's rate has never been set. And `time` is no earlier than the
206
213
  * last cue, `drop`, `touch`, `announce` or `rebase`, or the last frame a voice left in.
214
+ *
215
+ * Given `{ frame }`, a number `seq` read, it reads that frame as it showed, as `seek` does; it
216
+ * needs `history`.
207
217
  */
208
- project(time: number): Projection<I, O>;
218
+ project(at: number | {
219
+ readonly frame: number;
220
+ }): Projection<I, O>;
209
221
  /**
210
222
  * Moves the mix to mix time `time`, as it stood at the end of that frame, and plays on from
211
223
  * there; where rate 0 held the clock at `time` for several frames, the last of them. Needs
@@ -214,11 +226,12 @@ export interface Mix<I, O, H = unknown> {
214
226
  * Back, the mix restores itself: controls, records, voices and marks as they stood then. Voices
215
227
  * that left after it are back on the handles the host holds, their `done` and `played` starting
216
228
  * over where they had settled since. Voices cued after it wait as `pending` until the mix plays
217
- * their cue again. Stateful voices restart from the copy `history` kept nearest before it and
218
- * step once to it, exact under `stepMs`. A subject faded out of a voice or dropped after it comes
219
- * back with the record it had, a motion patch's state included. Recorded input and host fields
220
- * after it are let go: after a seek back, signals and host fields read live again, and `assess`
221
- * reports what they fed as `held`.
229
+ * their cue again, and a write to the handle of one waiting changes nothing. Stateful voices
230
+ * restart from the copy `history` kept nearest before it and step once to it, exact under
231
+ * `stepMs`. A subject faded out of a voice or dropped after it comes back with the record it
232
+ * had, a motion patch's state included. Recorded input and host fields after it are let go:
233
+ * after a seek back, signals and host fields read live again, and `assess` reports what they
234
+ * fed as `held`.
222
235
  *
223
236
  * Forward, through a sync or a later seek, the mix plays the recorded calls again in the frames
224
237
  * they were made in: cues, handle writes, fades, retargets and pushes, its rate, marks,
@@ -230,8 +243,15 @@ export interface Mix<I, O, H = unknown> {
230
243
  * host's time since its last sync. Throws for a time older than history reaches, and before
231
244
  * the first sync. With a history `store`, a time older than memory needs `prepare` first, or
232
245
  * throws `HistoryMiss` before anything moves.
246
+ *
247
+ * Given `{ frame }`, a number `seq` read, it moves to the end of that frame instead, which is
248
+ * how a host reaches an earlier one of several frames rate 0 held at one mix time. The frame has
249
+ * to be one history still holds: not let go with age, and not after a moment an earlier seek went
250
+ * back to. With a store, one older than memory needs `prepare` of its mix time first.
233
251
  */
234
- seek(time: number): void;
252
+ seek(to: number | {
253
+ readonly frame: number;
254
+ }): void;
235
255
  /** As `Transport.prepare`, for a mix made alone. */
236
256
  prepare(time: number): Promise<void>;
237
257
  /**
@@ -241,6 +261,11 @@ export interface Mix<I, O, H = unknown> {
241
261
  readonly now: number;
242
262
  /** The host's timestamp at the last sync; NaN before the first. */
243
263
  readonly timestamp: number;
264
+ /**
265
+ * The number of the frame being played, which `seek({ frame })` and `project({ frame })` take:
266
+ * each sync and seek begins a frame with a higher one, so two frames at one mix time differ.
267
+ */
268
+ readonly seq: number;
244
269
  /** The tape `history.tape` made, undefined without one. */
245
270
  readonly tape: Tape | undefined;
246
271
  /**
@@ -129,7 +129,11 @@ export interface Patch<I, O, S = void, H = unknown> {
129
129
  readonly form: 'fn' | 'keys' | 'motion';
130
130
  /** Milliseconds one pass lasts, whether or not the voice loops. 0 has no passes: phase and pass stay 0. */
131
131
  readonly duration: number;
132
- /** The channels this patch contributes to. Every key `at` sets, and no others. */
132
+ /**
133
+ * The channels this patch contributes to. Every key `at` sets, and no others: a mix drops any
134
+ * other key, and warns once per patch outside production when it finds one in the first delta a
135
+ * voice's `at` returns.
136
+ */
133
137
  readonly writes: readonly (keyof O)[];
134
138
  /**
135
139
  * The channels this patch was written against. `cue` refuses a mix whose channel of the same name
@@ -8,6 +8,12 @@
8
8
  * @category score
9
9
  */
10
10
  export type Mark = 'start' | 'in' | 'coast' | 'out' | 'end';
11
+ /**
12
+ * A mark by name: one of a voice's own five, or one it places inside itself with `marks`.
13
+ *
14
+ * @category score
15
+ */
16
+ export type MarkName = Mark | (string & {});
11
17
  /**
12
18
  * Selects voices by what the plan knows of them, never by a channel's value, within the asking
13
19
  * voice's own score unless it names another. Where several match,
@@ -26,7 +32,8 @@ export interface Query {
26
32
  resolver?: 'last' | 'first' | 'next' | 'earliest' | 'latest';
27
33
  }
28
34
  /**
29
- * A time given by another voice: `mark` of the voice `of` selects, moved by `by` ms. `after` is
35
+ * A time given by another voice: `mark` of the voice `of` selects, moved by `by` ms, where `mark`
36
+ * is one of its five or one it places inside itself with `marks`. `after` is
30
37
  * sugar for that voice's `end`, `with` for its `start`, and `before` for its start less `by`. A
31
38
  * string selects by name. `all` is the latest of its anchors, known once every one is; `any` the
32
39
  * earliest, known once one has passed or every one is known.
@@ -35,7 +42,7 @@ export interface Query {
35
42
  */
36
43
  export type Anchor = {
37
44
  of: string | Query;
38
- mark: Mark;
45
+ mark: MarkName;
39
46
  by?: number;
40
47
  } | {
41
48
  after: string | Query;
@@ -77,8 +84,8 @@ export interface Placement {
77
84
  export interface Marked {
78
85
  /** On the host's clock. */
79
86
  timestamp: number;
80
- /** Which of a voice's this is; undefined for an announced mark. */
81
- mark: Mark | undefined;
87
+ /** Which of a voice's this is, one of its five or one it placed inside itself; undefined for an announced mark. */
88
+ mark: MarkName | undefined;
82
89
  /** The voice it belongs to; undefined for an announced mark. */
83
90
  voice: number | undefined;
84
91
  score: string | undefined;
@@ -39,13 +39,17 @@ export interface Transport {
39
39
  * tape plays every mix's calls again in the order they were made, so an anchor across mixes
40
40
  * answers as it did. A mix dropped after the moment sought is back on the transport.
41
41
  */
42
- seek(time: number): void;
42
+ seek(to: number | {
43
+ readonly frame: number;
44
+ }): void;
43
45
  /**
44
46
  * Loads from the history `store` what `seek(time)` and `project(time)` will need, where that
45
47
  * reaches past memory. Without a store, or within memory, it loads nothing.
46
48
  */
47
49
  prepare(time: number): Promise<void>;
48
50
  readonly now: number;
51
+ /** As `Mix.seq`: the number of the frame being played, the same for every mix on it. */
52
+ readonly seq: number;
49
53
  readonly tape: Tape | undefined;
50
54
  /** Puts a named mark on a score every mix on it sees. Throws without a `score`. */
51
55
  announce(name: string, opts: {
@@ -55,8 +59,13 @@ export interface Transport {
55
59
  }): void;
56
60
  /** Every mark of every mix on it, and every mark announced on it, earliest first. */
57
61
  marks(from: number, to: number): Marked[];
58
- /** Every mix on it read at mix time `time`, together, so anchors across them answer. */
59
- project(time: number): TransportProjection;
62
+ /**
63
+ * Every mix on it read at mix time `at`, or at the frame `{ frame }` names, together, so anchors
64
+ * across them answer.
65
+ */
66
+ project(at: number | {
67
+ readonly frame: number;
68
+ }): TransportProjection;
60
69
  /** Any mix on it live. */
61
70
  readonly live: boolean;
62
71
  /** Every mix on it inert. */
@@ -81,10 +81,24 @@ export interface VoiceSpec<I, O, H = unknown> extends SpanHints {
81
81
  * so one cued backward with no `seek` has ended as it starts.
82
82
  */
83
83
  seek?: number;
84
+ /**
85
+ * The interval this voice's `step` runs at, in ms of mix time, in place of the mix's `stepMs`.
86
+ * 0 steps it once a frame by the frame's gap, whatever the mix sets. Default: the mix's.
87
+ */
88
+ stepMs?: number;
89
+ /**
90
+ * How a stateful subject first probed after its delay ran out starts stepping. `exact` steps it
91
+ * from that origin, in one step capped by `maxDt` or every `stepMs` interval since, so its state
92
+ * is the same however late the host looked. `fresh` starts it from the probe that first meets it,
93
+ * with fresh state and no catch-up, on the `stepMs` grid counted from the origin: cheaper for a
94
+ * subject met long after its voice started, and probe-dependent by design. A subject met on time,
95
+ * and a handle `seek`'s rebuild, step the same either way. Default: `exact`.
96
+ */
97
+ catchUp?: 'exact' | 'fresh';
84
98
  /**
85
99
  * true loops for good, false plays one pass, n plays n passes. A finite loop leaves when its
86
100
  * passes are done for the latest-staggered subject it has seen — over `fade.out` where one is
87
- * set, at once where none is — unless it freezes after. Default: true.
101
+ * set, at once where none is — unless it freezes after. Default: false.
88
102
  */
89
103
  loop?: boolean | number;
90
104
  /**
@@ -127,6 +141,14 @@ export interface VoiceSpec<I, O, H = unknown> extends SpanHints {
127
141
  * events are not known ahead, so they stay drain-only; these are the plan.
128
142
  */
129
143
  hits?: readonly Hit[];
144
+ /**
145
+ * Named marks inside the voice, beside its five, each at voice ms into a pass as a hit's `at` is:
146
+ * `{ drop: 340 }`. An anchor names one as it names the five, `{ of: 'lead', mark: 'drop' }`, and
147
+ * `marks` lists it and `book` books it under its name. Each is reached once: on the first pass
148
+ * that reaches it from where the voice starts, while its clock runs forward and before it ends.
149
+ * A name cannot be one of the five.
150
+ */
151
+ marks?: Readonly<Record<string, number>>;
130
152
  /**
131
153
  * The owner this voice plays under, a handle `owns` returned. Its `start` and `anchor` are then on
132
154
  * the owner's clock, in ms from when the owner starts, and default to where that clock is now; a
@@ -136,6 +158,22 @@ export interface VoiceSpec<I, O, H = unknown> extends SpanHints {
136
158
  */
137
159
  owner?: Handle<I>;
138
160
  }
161
+ /**
162
+ * One of `mix.blend`'s members, with the labels it carries in place of the spec's: each set here
163
+ * replaces the spec's for this member alone. The rest of the spec times and places every member
164
+ * alike, so they stay one blend.
165
+ *
166
+ * @category blending
167
+ */
168
+ export interface BlendMember<I, O, H = unknown> {
169
+ patch: Patch<I, O, unknown, H>;
170
+ /** What other voices call this member by. Default: the spec's `name`. */
171
+ name?: string;
172
+ /** Default: the spec's `tags`. */
173
+ tags?: readonly string[];
174
+ /** Events at times on this member's clock, as a voice's `hits`. Default: the spec's. */
175
+ hits?: readonly Hit[];
176
+ }
139
177
  /**
140
178
  * What `owns` takes: an owner has no patch, and plays only through the voices it holds.
141
179
  *
@@ -147,8 +185,19 @@ export interface OwnerSpec<I, H = unknown> extends SpanHints {
147
185
  * voice's `start` is. Default: now.
148
186
  */
149
187
  start?: number;
150
- /** Its clock's rate, multiplied into every child's own. Default 1. */
188
+ /**
189
+ * Its clock's rate, multiplied into every child's own. Default 1. Below 0 its clock runs back,
190
+ * and so does every child's: a child plays backward to its start and leaves there, as a voice cued
191
+ * backward does, and the owner leaves with its last child. A span is not cued backward, since it
192
+ * lays out its children forward from its start; its handle's `rate` turns it once it plays.
193
+ */
151
194
  rate?: number;
195
+ /**
196
+ * Where its clock starts, ms, as `seek` on its handle sets: its children play as far in as that
197
+ * puts them. Default 0, cued backward too, since an owner has no end of its own to start from; one
198
+ * cued at a rate below 0 with no seek runs back from 0, before any child placed on its clock.
199
+ */
200
+ seek?: number;
152
201
  /**
153
202
  * Multiplied into every child's weight per subject, as the child's own fade is, before the child's
154
203
  * locus folds it: a number, or a signal read per subject per frame. Default 1.
@@ -166,6 +215,14 @@ export interface OwnerSpec<I, H = unknown> extends SpanHints {
166
215
  score?: string;
167
216
  /** Where it sits relative to its own owner's clock or to other voices, in place of `start`. */
168
217
  anchor?: Placement;
218
+ /**
219
+ * Events at times on its own clock, the one its children's `start` counts on, which `book`
220
+ * hands a host ahead of time with its `score` and `tags`. An owner does not loop, so each is
221
+ * booked once, while it plays: none past where it leaves, and none while its clock runs back.
222
+ */
223
+ hits?: readonly Hit[];
224
+ /** Named marks at times on its own clock, as a voice's `marks`. */
225
+ marks?: Readonly<Record<string, number>>;
169
226
  /** The owner it plays under, so owners nest. */
170
227
  owner?: Handle<I>;
171
228
  }
@@ -294,7 +351,8 @@ export interface Handle<I = unknown> {
294
351
  * Turns a fade out around: the voice climbs back from where its fade had got to, up the same
295
352
  * curve, over `over` ms for the whole curve (default its `fade.in`, else its `fade.out`), and
296
353
  * plays on as if never faded. A fade waiting for rest is taken back at once; a voice not fading
297
- * is left as it is. A voice whose own end began the fade starts it again at its next frame.
354
+ * is left as it is. A voice whose own end began the fade starts it again at its next frame. A
355
+ * voice faded before its start goes back to `pending` and starts on time.
298
356
  */
299
357
  rise(opts?: {
300
358
  over?: number;
@@ -303,7 +361,7 @@ export interface Handle<I = unknown> {
303
361
  * The weight this voice gave `subject` the last frame that subject was probed: after its fades
304
362
  * and its weight signal, before a locus folds it with its alternatives. 0 for a subject it does
305
363
  * not reach, has not started on and does not freeze before, has left at rest, or has never been
306
- * probed for; 0 once done.
364
+ * probed for; 0 once done, and 0 after `mix.seek` goes back until the subject is probed again.
307
365
  * Costs nothing until it is asked.
308
366
  */
309
367
  weightOf(subject: I): number;
package/dist/unpage.d.ts CHANGED
@@ -1,8 +1,13 @@
1
- import type { Transport } from './transport.js';
1
+ import type { Frame, Transport } from './transport.js';
2
2
  /**
3
3
  * Makes memory reach mix time `t` for a seek or read there, from what `prepare` loaded; true where
4
4
  * it took records from the store. Throws before anything moves where it cannot.
5
5
  */
6
6
  export declare function cover(transport: Transport, t: number): boolean;
7
+ /**
8
+ * The frame numbered `seq` for a seek or read to it, memory made to reach it from what `prepare`
9
+ * loaded. Throws before anything moves for a frame history does not hold.
10
+ */
11
+ export declare function coverFrame(transport: Transport, seq: number): Frame;
7
12
  /** A seek back to frame `seq`: what the store and `prepare` hold after it is from a future the tape makes again. */
8
13
  export declare function cut(transport: Transport, seq: number): void;
package/dist/unpage.js CHANGED
@@ -29,6 +29,29 @@ export function cover(transport, t) {
29
29
  transport.floor = loaded.t;
30
30
  return true;
31
31
  }
32
+ /**
33
+ * The frame numbered `seq` for a seek or read to it, memory made to reach it from what `prepare`
34
+ * loaded. Throws before anything moves for a frame history does not hold.
35
+ */
36
+ export function coverFrame(transport, seq) {
37
+ if (!Number.isInteger(seq))
38
+ throw new RangeError(`blits: a frame is a number \`seq\` read, not ${seq}`);
39
+ let frame = transport.frames.numbered(seq);
40
+ const pager = transport.pager;
41
+ if (frame === undefined && pager !== null && seq <= transport.seq) {
42
+ const paged = pager.loaded?.records.find((r) => r.stream === 'frame' && r.mix === '' && r.seq === seq);
43
+ if (paged !== undefined) {
44
+ cover(transport, paged.at);
45
+ frame = transport.frames.numbered(seq);
46
+ }
47
+ else if (seq < (transport.frames.all()[0]?.seq ?? Number.POSITIVE_INFINITY))
48
+ throw new Error(`blits: frame ${seq} reaches past memory; await prepare with its mix time first`);
49
+ }
50
+ if (frame === undefined)
51
+ throw new Error(`blits: frame ${seq} is not one this mix's history holds: not played yet, let go, or after a moment a seek went back to`);
52
+ cover(transport, frame.at);
53
+ return frame;
54
+ }
32
55
  /** A seek back to frame `seq`: what the store and `prepare` hold after it is from a future the tape makes again. */
33
56
  export function cut(transport, seq) {
34
57
  const pager = transport.pager;
@@ -117,8 +140,17 @@ function unpageVoice(voice, records, keys) {
117
140
  const subject = keys.subject(r.subject);
118
141
  if (left.some((e) => e.seq === r.seq && Object.is(e.subject, subject)))
119
142
  continue;
120
- const [p, sync] = r.data;
121
- left.push({ subject, at: r.at, seq: r.seq, sync, held: unpackHeld(voice, p) });
143
+ // A store written before a leaving kept its fades holds two.
144
+ const [p, sync, ramp = null, parted = null] = r.data;
145
+ left.push({
146
+ subject,
147
+ at: r.at,
148
+ seq: r.seq,
149
+ sync,
150
+ held: unpackHeld(voice, p),
151
+ ramp,
152
+ parted,
153
+ });
122
154
  }
123
155
  left.sort((a, b) => a.seq - b.seq);
124
156
  voice.left = Leavings.from(left);