@msb235/blits 0.10.0 → 0.12.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 (126) hide show
  1. package/CHANGELOG.md +248 -0
  2. package/README.md +15 -4
  3. package/dist/arcs.d.ts +40 -0
  4. package/dist/arcs.js +239 -0
  5. package/dist/blend.js +1 -5
  6. package/dist/book.d.ts +2 -0
  7. package/dist/book.js +27 -3
  8. package/dist/chain.d.ts +17 -1
  9. package/dist/chain.js +57 -15
  10. package/dist/channels.d.ts +2 -0
  11. package/dist/channels.js +12 -1
  12. package/dist/clock.d.ts +14 -0
  13. package/dist/clock.js +9 -1
  14. package/dist/clone.js +16 -4
  15. package/dist/columns.d.ts +4 -5
  16. package/dist/columns.js +11 -5
  17. package/dist/crowd.d.ts +1 -1
  18. package/dist/crowd.js +4 -1
  19. package/dist/cue.js +63 -7
  20. package/dist/departed.d.ts +8 -3
  21. package/dist/departed.js +76 -29
  22. package/dist/due.d.ts +4 -0
  23. package/dist/due.js +16 -3
  24. package/dist/easing.d.ts +12 -1
  25. package/dist/easing.js +7 -1
  26. package/dist/everyone.d.ts +14 -1
  27. package/dist/everyone.js +27 -2
  28. package/dist/fade.d.ts +12 -5
  29. package/dist/fade.js +57 -19
  30. package/dist/fill.js +16 -9
  31. package/dist/fit.d.ts +8 -1
  32. package/dist/fit.js +7 -3
  33. package/dist/fold.d.ts +1 -1
  34. package/dist/fold.js +112 -9
  35. package/dist/frames.d.ts +2 -0
  36. package/dist/frames.js +17 -0
  37. package/dist/gather.js +38 -7
  38. package/dist/handle.d.ts +7 -2
  39. package/dist/handle.js +28 -12
  40. package/dist/held.d.ts +13 -2
  41. package/dist/held.js +43 -9
  42. package/dist/history.d.ts +25 -2
  43. package/dist/history.js +163 -35
  44. package/dist/hosts.js +45 -17
  45. package/dist/index.d.ts +5 -2
  46. package/dist/index.js +3 -1
  47. package/dist/keyfill.js +3 -1
  48. package/dist/lane.d.ts +1 -1
  49. package/dist/lane.js +3 -1
  50. package/dist/lanes.d.ts +31 -7
  51. package/dist/lanes.js +57 -6
  52. package/dist/lean.d.ts +15 -0
  53. package/dist/lean.js +23 -0
  54. package/dist/leavings.d.ts +6 -1
  55. package/dist/leavings.js +16 -2
  56. package/dist/marks.d.ts +7 -3
  57. package/dist/marks.js +105 -27
  58. package/dist/meet.js +1 -0
  59. package/dist/mixer.d.ts +35 -6
  60. package/dist/mixer.js +113 -45
  61. package/dist/motion.d.ts +4 -1
  62. package/dist/motion.js +16 -2
  63. package/dist/motions.d.ts +4 -0
  64. package/dist/motions.js +45 -11
  65. package/dist/move.js +35 -12
  66. package/dist/numbers.d.ts +5 -1
  67. package/dist/numbers.js +4 -0
  68. package/dist/owner.d.ts +18 -2
  69. package/dist/owner.js +44 -3
  70. package/dist/pack.d.ts +1 -0
  71. package/dist/pack.js +3 -0
  72. package/dist/place.d.ts +8 -0
  73. package/dist/place.js +16 -8
  74. package/dist/placed.d.ts +19 -0
  75. package/dist/placed.js +54 -0
  76. package/dist/project.d.ts +5 -2
  77. package/dist/project.js +238 -34
  78. package/dist/pull.js +37 -8
  79. package/dist/qualify.d.ts +10 -0
  80. package/dist/qualify.js +60 -3
  81. package/dist/reading.d.ts +8 -1
  82. package/dist/reading.js +8 -1
  83. package/dist/record.d.ts +6 -0
  84. package/dist/record.js +2 -0
  85. package/dist/restored.d.ts +69 -17
  86. package/dist/restored.js +163 -50
  87. package/dist/revive.d.ts +6 -3
  88. package/dist/revive.js +11 -3
  89. package/dist/roster.js +2 -1
  90. package/dist/rows.js +15 -5
  91. package/dist/seek.d.ts +7 -2
  92. package/dist/seek.js +152 -43
  93. package/dist/shared.d.ts +5 -0
  94. package/dist/shared.js +5 -0
  95. package/dist/signals.d.ts +10 -0
  96. package/dist/signals.js +17 -0
  97. package/dist/spans.d.ts +5 -0
  98. package/dist/spans.js +75 -5
  99. package/dist/store.d.ts +13 -0
  100. package/dist/store.js +23 -0
  101. package/dist/strays.js +50 -2
  102. package/dist/tape.d.ts +29 -4
  103. package/dist/tape.js +49 -11
  104. package/dist/touch.js +3 -2
  105. package/dist/transport.d.ts +11 -2
  106. package/dist/transport.js +26 -10
  107. package/dist/types/arc.d.ts +55 -0
  108. package/dist/types/arc.js +1 -0
  109. package/dist/types/mix.d.ts +57 -14
  110. package/dist/types/patch.d.ts +5 -1
  111. package/dist/types/place.d.ts +11 -4
  112. package/dist/types/transport.d.ts +12 -3
  113. package/dist/types/voice.d.ts +72 -5
  114. package/dist/types/watch.d.ts +126 -0
  115. package/dist/types/watch.js +1 -0
  116. package/dist/types.d.ts +2 -0
  117. package/dist/unpage.d.ts +6 -1
  118. package/dist/unpage.js +34 -2
  119. package/dist/voice.d.ts +93 -16
  120. package/dist/voice.js +60 -3
  121. package/dist/watch.d.ts +26 -0
  122. package/dist/watch.js +441 -0
  123. package/dist/weigh.d.ts +13 -2
  124. package/dist/weigh.js +10 -3
  125. package/docs/schema.html +106 -38
  126. package/package.json +10 -4
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
  }
package/dist/touch.js CHANGED
@@ -1,3 +1,4 @@
1
+ import { headOf } from './chain.js';
1
2
  import { restamp } from './everyone.js';
2
3
  import { nextFrame } from './move.js';
3
4
  /**
@@ -16,10 +17,10 @@ export function touch(mix, subject) {
16
17
  }
17
18
  else {
18
19
  // No voice's `seeks` is below 0, so no record stamped with it reads as current.
19
- let held = mix.chains.get(subject) ?? null;
20
+ let held = headOf(mix, subject) ?? null;
20
21
  for (; held !== null; held = held.next)
21
22
  held.seeks = -1;
22
- const slot = mix.chains.get(subject)?.slot ?? -1;
23
+ const slot = headOf(mix, subject)?.slot ?? -1;
23
24
  for (const voice of mix.sharers)
24
25
  restamp(voice, slot);
25
26
  }
@@ -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
@@ -8,6 +8,7 @@ import { projectAll } from './project.js';
8
8
  import { scoredDrop, scoredJoin } from './scored.js';
9
9
  import { seek } from './seek.js';
10
10
  import { hostAt, record, replay, tapeOf } from './tape.js';
11
+ import { gave, historyFor } from './watch.js';
11
12
  /** How far the tape's reach moves before it is pruned again, ms of mix time. */
12
13
  const TAPE_STEP_MS = 1000;
13
14
  /** Whether a change made in frame `seq` falls inside `cut`; `early` for one made before the frame was read. */
@@ -68,6 +69,11 @@ export class Transport {
68
69
  }
69
70
  /** The frame the rate was first set in, which a seek or read back to before it finds unset. */
70
71
  pacedSeq = Number.POSITIVE_INFINITY;
72
+ /**
73
+ * On a read ahead's own transport, the retargets and pushes its tape's calls made, which
74
+ * `reading.ahead` hands the motion patches while it reads.
75
+ */
76
+ ahead = new Map();
71
77
  /** True while the tape makes a recorded call again, so it is not recorded twice. */
72
78
  replaying = false;
73
79
  announced = [];
@@ -110,7 +116,11 @@ export class Transport {
110
116
  m.dropped = true;
111
117
  if (this.history !== undefined) {
112
118
  this.dropped.push({ mix: m, at: this.now, seq: this.seq });
113
- record(this, 'drop', () => this.drop(mix));
119
+ record(this, 'drop', (stage) => {
120
+ const to = stage.mix(m);
121
+ if (to !== undefined)
122
+ stage.transport(this).drop(to);
123
+ });
114
124
  }
115
125
  }
116
126
  /** A seek back to frame `seq`: every mix dropped after it is back on, as it stood then. */
@@ -124,8 +134,8 @@ export class Transport {
124
134
  this.join(d.mix, d.mix.slot);
125
135
  }
126
136
  }
127
- seek(time) {
128
- seek(this, time);
137
+ seek(to) {
138
+ seek(this, to);
129
139
  }
130
140
  prepare(time) {
131
141
  return prepare(this, time);
@@ -138,8 +148,8 @@ export class Transport {
138
148
  marks(from, to) {
139
149
  return listedAll(this, from, to).map(({ order: _, ...m }) => m);
140
150
  }
141
- project(time) {
142
- return projectAll(this, time);
151
+ project(at) {
152
+ return projectAll(this, at);
143
153
  }
144
154
  get live() {
145
155
  return this.members.some((m) => m.live);
@@ -286,7 +296,8 @@ export class Transport {
286
296
  if (!(rate >= 0 && rate < Number.POSITIVE_INFINITY))
287
297
  throw new RangeError(`blits: a mix's rate is a finite number, 0 or more, not ${rate}`);
288
298
  if (this.pace === null) {
289
- this.pace = new Pace(this.history !== undefined);
299
+ // A read ahead keeps every clock too: it moves once across calls a mix met frame by frame.
300
+ this.pace = new Pace(this.history !== undefined || this.projecting);
290
301
  this.pacedSeq = this.seq;
291
302
  // Until now mix time was host time, so a pending voice's start is the host time it was given.
292
303
  for (const m of this.members)
@@ -298,7 +309,7 @@ export class Transport {
298
309
  this.pace.change(this.u, rate, over, reach, this.seq);
299
310
  for (const m of this.members)
300
311
  m.stir();
301
- record(this, 'rate', () => this.ramp(rate, over));
312
+ record(this, 'rate', (stage) => stage.transport(this).ramp(rate, over));
302
313
  }
303
314
  }
304
315
  /** Puts a mark on a score: one the transport announced, or a mix on it, `slot`. */
@@ -316,8 +327,9 @@ export function announce(transport, name, opts, slot, mix) {
316
327
  };
317
328
  transport.announced.push(mark);
318
329
  transport.settle();
319
- record(transport, 'announce', () => {
320
- transport.announced.push({ ...mark, seq: transport.seq });
330
+ record(transport, 'announce', (stage) => {
331
+ const to = stage.transport(transport);
332
+ to.announced.push({ ...mark, seq: to.seq });
321
333
  });
322
334
  }
323
335
  /**
@@ -326,5 +338,9 @@ export function announce(transport, name, opts, slot, mix) {
326
338
  * @category mix
327
339
  */
328
340
  export function transport(opts = {}) {
329
- return new Transport(opts.history, true);
341
+ const { history, added } = historyFor(opts);
342
+ const t = new Transport(history, true);
343
+ if (added)
344
+ gave(t);
345
+ return t;
330
346
  }
@@ -0,0 +1,55 @@
1
+ import type { Handle, OwnerSpec, VoiceSpec } from './voice.js';
2
+ /**
3
+ * One voice of an arc's part: a voice's spec, less what the arc sets for it. The arc places it,
4
+ * fades it across each boundary by `blend`, and holds its first and last frames for the blend.
5
+ *
6
+ * @category score
7
+ */
8
+ export interface ArcVoice<I, O, H = unknown> extends Omit<VoiceSpec<I, O, H>, 'owner' | 'start' | 'anchor' | 'freeze' | 'fade' | 'faster' | 'slower' | 'overlap' | 'ballast' | 'priority' | 'earliest' | 'rank'> {
9
+ /**
10
+ * How long the part holds the arc, ms on the arc's clock. Default: the patch's duration times
11
+ * its passes, which leaves out a `stagger`, so a part whose last subject lands later says so here.
12
+ */
13
+ duration?: number;
14
+ }
15
+ /**
16
+ * One part of an arc: a voice, or several layered together that play as one and last as long as
17
+ * the longest.
18
+ *
19
+ * @category score
20
+ */
21
+ export type ArcPart<I, O, H = unknown> = ArcVoice<I, O, H> | readonly ArcVoice<I, O, H>[];
22
+ /**
23
+ * What `arc` takes: an owner over three parts that hand over in order, `enter`, `active` and
24
+ * `exit`, any of which may be left out.
25
+ *
26
+ * @category score
27
+ */
28
+ export interface ArcSpec<I, O, H = unknown> extends Omit<OwnerSpec<I, H>, 'freeze'> {
29
+ /** What plays as it arrives. Its end is the arc's `in` mark. */
30
+ enter?: ArcPart<I, O, H>;
31
+ /** What plays between the two, over the hold. Its end is the arc's `coast` mark. */
32
+ active?: ArcPart<I, O, H>;
33
+ /** What plays as it leaves. Its start is the arc's `out` mark, and its end the arc's `end`. */
34
+ exit?: ArcPart<I, O, H>;
35
+ /**
36
+ * How long `active` plays, ms on the arc's clock, looping for it unless its `loop` says how many
37
+ * passes; or `'open'`, until `release`. Default: `active`'s own length, held open where it loops
38
+ * for good, and 0 with no `active`.
39
+ */
40
+ hold?: number | 'open';
41
+ /** A crossfade across each boundary, ms, half before it and half after. Default 0. */
42
+ blend?: number;
43
+ }
44
+ /**
45
+ * An arc's handle: an owner's, and the release of its hold. `fade` takes it out with no exit.
46
+ *
47
+ * @category score
48
+ */
49
+ export interface ArcHandle<I = unknown> extends Handle<I> {
50
+ /**
51
+ * Ends the hold now and plays the exit, its blend starting here; where the enter is still
52
+ * playing, the exit follows it. Nothing once the exit has begun or the arc is fading.
53
+ */
54
+ release(): void;
55
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -1,9 +1,10 @@
1
+ import type { ArcHandle, ArcSpec } from './arc.js';
1
2
  import type { Kit } from './channel.js';
2
3
  import type { HistoryOptions, Tape } from './history.js';
3
4
  import type { Patch, Sent, Signal } from './patch.js';
4
5
  import type { Booker, BookOptions, Marked } from './place.js';
5
6
  import type { Transport } from './transport.js';
6
- import type { Handle, OwnerSpec, SpanHandle, SpanSpec, VoiceSpec } from './voice.js';
7
+ import type { BlendMember, Handle, OwnerSpec, SpanHandle, SpanSpec, VoiceSpec } from './voice.js';
7
8
  /**
8
9
  * How a mix is built: its engine, reduced motion, the host, and its limits.
9
10
  *
@@ -52,6 +53,11 @@ export interface MixOptions<H = unknown> {
52
53
  * string or number subject is its own key; with a store, an object subject needs this.
53
54
  */
54
55
  keyOf?(subject: never): string | number;
56
+ /**
57
+ * What a debugger calls a subject (see `watched`); where it gives nothing, the subject's own
58
+ * `name` or `id`, a DOM element's tag, id and classes, or its class and a number.
59
+ */
60
+ labelOf?(subject: never): string | undefined;
55
61
  /**
56
62
  * Whether a channel may run as a lane: computed for every subject at once in flat arrays, when
57
63
  * every voice writing it can run that way. On by default; what a host reads is the same either
@@ -77,7 +83,7 @@ export interface MixOptions<H = unknown> {
77
83
  /**
78
84
  * How sure a projection is of one channel: `exact` where only the clock and known changes drove
79
85
  * 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
86
+ * 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
87
  * is not known, so the value it had is held.
82
88
  *
83
89
  * @category mix
@@ -119,9 +125,11 @@ export interface Mix<I, O, H = unknown> {
119
125
  /**
120
126
  * N voices whose weights split one signal, cued into one locus so they fold as alternatives. The
121
127
  * 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.
128
+ * takes its share of that one reading, which `share` reads outside the blend; a signal keeping
129
+ * state keeps it once, not per member. One `spec` cues them all, and a member given as a
130
+ * `BlendMember` carries its own `name`, `tags` and `hits` in place of the spec's.
123
131
  */
124
- blend(patches: readonly Patch<I, O, unknown, H>[], by: Signal<I, H>, spec?: Omit<VoiceSpec<I, O, H>, 'patch' | 'weight' | 'locus'>): Handle<I>[];
132
+ 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
133
  /**
126
134
  * Cues an owner: a voice with no patch that holds the voices cued with `owner` set to its handle,
127
135
  * and plays them on its own clock, so they can be placed, timed and faded as one. Its handle acts
@@ -139,9 +147,23 @@ export interface Mix<I, O, H = unknown> {
139
147
  * span's to set, so it takes neither `start` nor an anchored start; its `SpanHints` say how it
140
148
  * may give way. A span with a budget stays until the budget has passed, though its children
141
149
  * 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.
150
+ * its budget does, whichever is later. A voice that loops for good cannot join one. Throws for
151
+ * a `rate` below 0: a span lays its children out forward from its start, and its handle's `rate`
152
+ * turns it once it plays.
143
153
  */
144
154
  span(spec: SpanSpec<I, H>): SpanHandle<I>;
155
+ /**
156
+ * Cues an arc: an owner holding three parts that hand over in order on its clock, `enter`, then
157
+ * `active` for the `hold`, then `exit`, each a voice or several layered. Its marks are its own:
158
+ * `start` as the enter begins, `in` as it ends, `coast` as the hold ends and `out` as the exit
159
+ * begins, which are one time, and `end` as the exit ends, where it leaves and `done` resolves.
160
+ * While the hold is open, `coast`, `out` and `end` are unknown until `release` fixes them. Each
161
+ * part shows its first and last frames through the blends either side of it, and a part left
162
+ * out leaves its time to the one before it. Its `fade` takes it out with no exit. An arc with a
163
+ * finite hold may sit in a span. Throws for a `rate` below 0, a hold below 0, and an `enter` or
164
+ * `exit` that never ends.
165
+ */
166
+ arc(spec: ArcSpec<I, O, H>): ArcHandle<I>;
145
167
  /**
146
168
  * The host reports the clock, once a frame. Nothing advances at the call. Throws for a timestamp
147
169
  * earlier than the last sync's, short of a `rebase`: the host's clock only goes forward, and
@@ -194,8 +216,11 @@ export interface Mix<I, O, H = unknown> {
194
216
  /**
195
217
  * Reads the mix at another mix time, without moving it. At the mix's own time it reads what
196
218
  * `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`
219
+ * it plays what is cued forward, with the calls the tape holds after a `seek` back, and while a
220
+ * voice could carry state (a patch that steps, a signal weight) it reads each subject asked about
221
+ * at every known event on the way: each call, and each mark `marks` would list. It throws
222
+ * for the two a copy cannot take: a `drop` of a subject a motion patch holds, and a retarget or
223
+ * push made before its voice met the subject. Behind it, with `history`, it reads the last frame at or before `time`
199
224
  * as that frame showed, and throws for a time older than the history reaches. On a transport it
200
225
  * reads every mix on it together, so anchors across them answer.
201
226
  *
@@ -204,8 +229,13 @@ export interface Mix<I, O, H = unknown> {
204
229
  * alone: no state, motion, `from: 'current'`, anchor or owner, no write to its handle, and no channel without a rest
205
230
  * at a weight that moves. The mix's rate has never been set. And `time` is no earlier than the
206
231
  * last cue, `drop`, `touch`, `announce` or `rebase`, or the last frame a voice left in.
232
+ *
233
+ * Given `{ frame }`, a number `seq` read, it reads that frame as it showed, as `seek` does; it
234
+ * needs `history`.
207
235
  */
208
- project(time: number): Projection<I, O>;
236
+ project(at: number | {
237
+ readonly frame: number;
238
+ }): Projection<I, O>;
209
239
  /**
210
240
  * Moves the mix to mix time `time`, as it stood at the end of that frame, and plays on from
211
241
  * there; where rate 0 held the clock at `time` for several frames, the last of them. Needs
@@ -214,11 +244,12 @@ export interface Mix<I, O, H = unknown> {
214
244
  * Back, the mix restores itself: controls, records, voices and marks as they stood then. Voices
215
245
  * that left after it are back on the handles the host holds, their `done` and `played` starting
216
246
  * 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`.
247
+ * their cue again, and a write to the handle of one waiting changes nothing. Stateful voices
248
+ * restart from the copy `history` kept nearest before it and step once to it, exact under
249
+ * `stepMs`. A subject faded out of a voice or dropped after it comes back with the record it
250
+ * had, a motion patch's state included. Recorded input and host fields after it are let go:
251
+ * after a seek back, signals and host fields read live again, and `assess` reports what they
252
+ * fed as `held`.
222
253
  *
223
254
  * Forward, through a sync or a later seek, the mix plays the recorded calls again in the frames
224
255
  * they were made in: cues, handle writes, fades, retargets and pushes, its rate, marks,
@@ -230,8 +261,15 @@ export interface Mix<I, O, H = unknown> {
230
261
  * host's time since its last sync. Throws for a time older than history reaches, and before
231
262
  * the first sync. With a history `store`, a time older than memory needs `prepare` first, or
232
263
  * throws `HistoryMiss` before anything moves.
264
+ *
265
+ * Given `{ frame }`, a number `seq` read, it moves to the end of that frame instead, which is
266
+ * how a host reaches an earlier one of several frames rate 0 held at one mix time. The frame has
267
+ * to be one history still holds: not let go with age, and not after a moment an earlier seek went
268
+ * back to. With a store, one older than memory needs `prepare` of its mix time first.
233
269
  */
234
- seek(time: number): void;
270
+ seek(to: number | {
271
+ readonly frame: number;
272
+ }): void;
235
273
  /** As `Transport.prepare`, for a mix made alone. */
236
274
  prepare(time: number): Promise<void>;
237
275
  /**
@@ -241,6 +279,11 @@ export interface Mix<I, O, H = unknown> {
241
279
  readonly now: number;
242
280
  /** The host's timestamp at the last sync; NaN before the first. */
243
281
  readonly timestamp: number;
282
+ /**
283
+ * The number of the frame being played, which `seek({ frame })` and `project({ frame })` take:
284
+ * each sync and seek begins a frame with a higher one, so two frames at one mix time differ.
285
+ */
286
+ readonly seq: number;
244
287
  /** The tape `history.tape` made, undefined without one. */
245
288
  readonly tape: Tape | undefined;
246
289
  /**
@@ -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. */