@msb235/blits 0.5.0 → 0.7.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 (99) hide show
  1. package/CHANGELOG.md +163 -0
  2. package/README.md +32 -8
  3. package/dist/bare.js +7 -1
  4. package/dist/book.d.ts +8 -1
  5. package/dist/book.js +35 -4
  6. package/dist/chain.d.ts +4 -3
  7. package/dist/chain.js +10 -6
  8. package/dist/channels.d.ts +12 -19
  9. package/dist/channels.js +18 -118
  10. package/dist/clock.d.ts +12 -5
  11. package/dist/clock.js +13 -5
  12. package/dist/closed.d.ts +15 -0
  13. package/dist/closed.js +144 -0
  14. package/dist/color.d.ts +61 -0
  15. package/dist/color.js +229 -0
  16. package/dist/columns.js +2 -1
  17. package/dist/crowd.d.ts +4 -4
  18. package/dist/crowd.js +11 -8
  19. package/dist/cue.d.ts +2 -0
  20. package/dist/cue.js +78 -27
  21. package/dist/due.js +5 -2
  22. package/dist/fade.d.ts +13 -0
  23. package/dist/fade.js +55 -4
  24. package/dist/fill.d.ts +1 -1
  25. package/dist/fill.js +17 -10
  26. package/dist/fit.d.ts +144 -0
  27. package/dist/fit.js +181 -0
  28. package/dist/fold.d.ts +3 -3
  29. package/dist/fold.js +25 -18
  30. package/dist/gather.d.ts +6 -0
  31. package/dist/gather.js +18 -0
  32. package/dist/handle.d.ts +27 -2
  33. package/dist/handle.js +60 -11
  34. package/dist/held.js +7 -3
  35. package/dist/history.d.ts +15 -2
  36. package/dist/history.js +54 -9
  37. package/dist/hosts.d.ts +2 -2
  38. package/dist/hosts.js +19 -6
  39. package/dist/index.d.ts +10 -2
  40. package/dist/index.js +6 -1
  41. package/dist/lane.d.ts +4 -4
  42. package/dist/lane.js +5 -5
  43. package/dist/lanes.d.ts +32 -9
  44. package/dist/lanes.js +53 -24
  45. package/dist/locus.d.ts +3 -3
  46. package/dist/locus.js +1 -1
  47. package/dist/marks.d.ts +3 -0
  48. package/dist/marks.js +81 -13
  49. package/dist/meet.d.ts +3 -2
  50. package/dist/meet.js +15 -3
  51. package/dist/mixer.d.ts +55 -35
  52. package/dist/mixer.js +173 -83
  53. package/dist/motion.d.ts +2 -199
  54. package/dist/motion.js +2 -652
  55. package/dist/motions.d.ts +266 -0
  56. package/dist/motions.js +724 -0
  57. package/dist/move.d.ts +1 -1
  58. package/dist/move.js +27 -11
  59. package/dist/owner.d.ts +2 -2
  60. package/dist/owner.js +5 -5
  61. package/dist/pace.d.ts +2 -0
  62. package/dist/pace.js +8 -0
  63. package/dist/pack.d.ts +31 -0
  64. package/dist/pack.js +65 -0
  65. package/dist/paging.d.ts +63 -0
  66. package/dist/paging.js +110 -0
  67. package/dist/patch.d.ts +3 -0
  68. package/dist/patch.js +2 -0
  69. package/dist/place.js +74 -25
  70. package/dist/project.d.ts +7 -4
  71. package/dist/project.js +117 -78
  72. package/dist/pull.js +4 -3
  73. package/dist/qualify.js +4 -2
  74. package/dist/revive.d.ts +58 -0
  75. package/dist/revive.js +158 -0
  76. package/dist/sample.d.ts +1 -1
  77. package/dist/sample.js +6 -1
  78. package/dist/seek.d.ts +3 -0
  79. package/dist/seek.js +311 -0
  80. package/dist/signals.d.ts +3 -2
  81. package/dist/signals.js +12 -6
  82. package/dist/spans.d.ts +29 -0
  83. package/dist/spans.js +165 -0
  84. package/dist/strays.d.ts +1 -1
  85. package/dist/strays.js +21 -0
  86. package/dist/tape.d.ts +21 -0
  87. package/dist/tape.js +65 -0
  88. package/dist/ticker.d.ts +73 -0
  89. package/dist/ticker.js +115 -0
  90. package/dist/transport.d.ts +121 -0
  91. package/dist/transport.js +271 -0
  92. package/dist/types.d.ts +427 -50
  93. package/dist/unpage.d.ts +8 -0
  94. package/dist/unpage.js +163 -0
  95. package/dist/voice.d.ts +78 -12
  96. package/dist/voice.js +126 -12
  97. package/dist/weigh.js +2 -2
  98. package/docs/schema.html +140 -48
  99. package/package.json +2 -1
@@ -0,0 +1,8 @@
1
+ import type { Transport } from './transport.js';
2
+ /**
3
+ * Makes memory reach mix time `t` for a seek or read there, from what `prepare` loaded; true where
4
+ * it took records from the store. Throws before anything moves where it cannot.
5
+ */
6
+ export declare function cover(transport: Transport, t: number): boolean;
7
+ /** A seek back to `t`: what the store and `prepare` hold after it is from a future the tape makes again. */
8
+ export declare function cut(transport: Transport, t: number, unpaged: boolean): void;
package/dist/unpage.js ADDED
@@ -0,0 +1,163 @@
1
+ import { unpackHeld } from './pack.js';
2
+ import { HistoryMiss } from './paging.js';
3
+ import { reviveVoice } from './revive.js';
4
+ /**
5
+ * Makes memory reach mix time `t` for a seek or read there, from what `prepare` loaded; true where
6
+ * it took records from the store. Throws before anything moves where it cannot.
7
+ */
8
+ export function cover(transport, t) {
9
+ if (transport.reaches(t))
10
+ return false;
11
+ const pager = transport.pager;
12
+ if (pager === null)
13
+ throw new Error(`blits: ${t} is older than this mix's history reaches`);
14
+ const loaded = pager.loaded;
15
+ if (loaded === null || loaded.t > t) {
16
+ const last = pager.last;
17
+ throw new HistoryMiss(last?.mix ?? '', last?.stream ?? 'history', t);
18
+ }
19
+ for (const m of [...transport.members, ...transport.dropped.map((d) => d.mix)]) {
20
+ const name = m.name ?? '';
21
+ const id = m.outs?.missing(loaded.t, (v) => loaded.records.some((r) => r.stream === 'voice' && r.voice === v && r.mix === name));
22
+ if (id !== undefined)
23
+ throw new HistoryMiss(name, 'voice', t);
24
+ }
25
+ pager.loaded = null;
26
+ unpage(transport, loaded.records);
27
+ transport.floor = loaded.t;
28
+ return true;
29
+ }
30
+ /** A seek back to `t`: what the store and `prepare` hold after it is from a future the tape makes again. */
31
+ export function cut(transport, t, unpaged) {
32
+ const pager = transport.pager;
33
+ if (pager === null)
34
+ return;
35
+ if (unpaged)
36
+ pager.store.cut(t);
37
+ const loaded = pager.loaded;
38
+ if (loaded !== null)
39
+ loaded.records = loaded.records.filter((r) => r.at <= t);
40
+ }
41
+ /** Puts records a store gave back into memory ahead of what it still holds, as if never paged. */
42
+ function unpage(transport, records) {
43
+ const mixes = new Map();
44
+ for (const m of [...transport.members, ...transport.dropped.map((d) => d.mix)])
45
+ mixes.set(m.name ?? '', m);
46
+ const by = new Map();
47
+ for (const r of [...records].sort((a, b) => a.at - b.at)) {
48
+ const m = mixes.get(r.mix);
49
+ if (m === undefined)
50
+ continue;
51
+ const list = by.get(m);
52
+ if (list === undefined)
53
+ by.set(m, [r]);
54
+ else
55
+ list.push(r);
56
+ }
57
+ for (const [m, list] of by)
58
+ unpageMix(m, list);
59
+ }
60
+ function unpageMix(mix, records) {
61
+ const voices = new Map();
62
+ for (const v of [...mix.cued, ...mix.gone])
63
+ voices.set(v.id, v);
64
+ for (const r of records)
65
+ if (r.stream === 'voice' && r.voice !== undefined && !voices.has(r.voice)) {
66
+ const v = reviveVoice(mix, r.voice, r.data);
67
+ voices.set(v.id, v);
68
+ mix.gone.push(v);
69
+ }
70
+ const host = records.filter((r) => r.stream === 'host');
71
+ mix.hostLog = before(host.map((r) => ({ at: r.at, fields: r.data })), mix.hostLog);
72
+ const byVoice = new Map();
73
+ for (const r of records) {
74
+ if (r.voice === undefined)
75
+ continue;
76
+ const list = byVoice.get(r.voice);
77
+ if (list === undefined)
78
+ byVoice.set(r.voice, [r]);
79
+ else
80
+ list.push(r);
81
+ }
82
+ const keys = mix.keys;
83
+ for (const [id, list] of byVoice) {
84
+ const voice = voices.get(id);
85
+ if (voice !== undefined)
86
+ unpageVoice(voice, list, keys);
87
+ }
88
+ }
89
+ function unpageVoice(voice, records, keys) {
90
+ const of = (stream) => records.filter((r) => r.stream === stream);
91
+ if (voice.log !== null)
92
+ voice.log = before(of('controls').map((r) => r.data), voice.log);
93
+ const left = voice.left ?? [];
94
+ for (const r of of('left')) {
95
+ const subject = keys.subject(r.subject);
96
+ if (!left.some((e) => e.at === r.at && Object.is(e.subject, subject)))
97
+ left.push({ subject, at: r.at, held: unpackHeld(voice, r.data) });
98
+ }
99
+ left.sort((a, b) => a.at - b.at);
100
+ voice.left = left.length === 0 ? null : left;
101
+ for (const [key, list] of bySubject(of('snap'))) {
102
+ const subject = keys.subject(key);
103
+ for (const [held, snaps] of lives(voice, subject, list))
104
+ held.snaps = before(snaps.map((r) => ({ at: r.at, held: unpackHeld(voice, r.data) })), held.snaps ?? []);
105
+ }
106
+ for (const [key, list] of bySubject(of('input'))) {
107
+ const subject = keys.subject(key);
108
+ for (const [held, inputs] of lives(voice, subject, list))
109
+ held.inputs = before(inputs.map((r) => ({ at: r.at, value: r.data })), held.inputs ?? []);
110
+ }
111
+ const motion = voice.motion;
112
+ if (motion === undefined)
113
+ return;
114
+ for (const r of of('released'))
115
+ motion.reclaim(keys.subject(r.subject), r.at, r.data);
116
+ for (const [key, list] of bySubject(of('stretch')))
117
+ motion.restretch(keys.subject(key), list);
118
+ }
119
+ function bySubject(records) {
120
+ const out = new Map();
121
+ for (const r of records) {
122
+ const list = out.get(r.subject);
123
+ if (list === undefined)
124
+ out.set(r.subject, [r]);
125
+ else
126
+ list.push(r);
127
+ }
128
+ return out;
129
+ }
130
+ /**
131
+ * A subject's records in a voice, each with the paged entries from its life: one ends when the
132
+ * subject leaves the voice, and the record it has now began after its last leaving.
133
+ */
134
+ function lives(voice, subject, records) {
135
+ const left = (voice.left ?? []).filter((e) => Object.is(e.subject, subject));
136
+ const out = [];
137
+ let from = Number.NEGATIVE_INFINITY;
138
+ for (const e of left) {
139
+ out.push([e.held, records.filter((r) => r.at > from && r.at <= e.at)]);
140
+ from = e.at;
141
+ }
142
+ const live = voice.subjects.get(subject);
143
+ if (live?.reaches)
144
+ out.push([live, records.filter((r) => r.at > from)]);
145
+ return out;
146
+ }
147
+ /**
148
+ * `loaded`, in time order, ahead of `list`: those older than its first entry, the last of any that
149
+ * share a time, which is the one a read finds.
150
+ */
151
+ function before(loaded, list) {
152
+ const first = list[0]?.at ?? Number.POSITIVE_INFINITY;
153
+ const out = [];
154
+ for (const e of loaded) {
155
+ if (!(e.at < first))
156
+ continue;
157
+ if (out.length > 0 && out[out.length - 1].at === e.at)
158
+ out[out.length - 1] = e;
159
+ else
160
+ out.push(e);
161
+ }
162
+ return out.length === 0 ? list : [...out, ...list];
163
+ }
package/dist/voice.d.ts CHANGED
@@ -1,12 +1,13 @@
1
1
  import type { Blend } from './blend.js';
2
2
  import { type Clock } from './clock.js';
3
3
  import { type Curve } from './easing.js';
4
- import { type Motions } from './motion.js';
4
+ import type { Motions } from './motions.js';
5
5
  import { Named } from './named.js';
6
6
  import { Holding } from './owner.js';
7
7
  import { type Built, intosOf, type Scratch } from './patch.js';
8
+ import type { Fitting } from './spans.js';
8
9
  import { Store } from './store.js';
9
- import type { Channel, FadeSpec, Handle, Patch, Setting, VoiceSpec } from './types.js';
10
+ import type { Anchor, Channel, FadeSpec, Handle, Patch, Setting, VoiceSpec } from './types.js';
10
11
  export declare const none: readonly string[];
11
12
  /** Everything one voice holds for one subject, chained through the next voice that reaches it. */
12
13
  export interface Subject<S> {
@@ -16,14 +17,14 @@ export interface Subject<S> {
16
17
  delay: number;
17
18
  /** The mix timestamp this subject's delay ran out at, which its step grid counts from. */
18
19
  since: number;
19
- /** The mix timestamp its fade in counts from: `since`, or earlier for a voice holding before. */
20
+ /** The mix timestamp its fade in counts from: `since`, or earlier for a voice freezing before. */
20
21
  shown: number;
21
22
  /** The weight this voice gave this subject the last frame it was probed, 0 where it gave none. */
22
23
  weight: number;
23
24
  /** Left at rest during a handover, so it stops contributing. */
24
25
  rested: boolean;
25
26
  /**
26
- * Per written channel, whether a rest-less influence is on: 0 unknown, 1 on, 2 off; null until
27
+ * Per written channel, whether a rest-less contribution is on: 0 unknown, 1 on, 2 off; null until
27
28
  * a rest-less channel asks.
28
29
  */
29
30
  bands: Uint8Array | null;
@@ -46,6 +47,8 @@ export interface Subject<S> {
46
47
  slope?: Record<string, unknown>;
47
48
  /** What `setting.keep` holds for this voice and subject, by owner; null until it holds any. */
48
49
  kept: Map<object, unknown> | null;
50
+ /** Kept state a history store gave back, which each owner takes in turn on its first keep. */
51
+ unkept?: unknown[];
49
52
  /** Under `history`, copies of this record by the mix time they were taken, oldest first. */
50
53
  snaps?: {
51
54
  at: number;
@@ -70,7 +73,7 @@ export interface Subject<S> {
70
73
  next: Subject<unknown> | null;
71
74
  /**
72
75
  * On the first record, which the mix keeps per subject so a probe makes one lookup: the `version`
73
- * the chain was linked at, and per locus, by channel slot, whether a rest-less influence is on:
76
+ * the chain was linked at, and per locus, by channel slot, whether a rest-less contribution is on:
74
77
  * 0 never decided, 1 on, 2 off.
75
78
  */
76
79
  version: number;
@@ -83,9 +86,13 @@ export interface Controls extends Clock {
83
86
  at: number;
84
87
  weight: number;
85
88
  out: Ramp | null;
89
+ back: Rise | null;
86
90
  /** Where its anchors had placed it then: start, and the start of an anchored fade out. */
87
91
  start: number;
88
92
  outAt: number;
93
+ /** A fade the host set for `outAt`: its ramp, NaN for the voice's own `fade.out`. */
94
+ outOver: number;
95
+ outSet: boolean;
89
96
  /** Made by a sync, so it shows in that frame; a host's change between frames shows from the next. */
90
97
  sync: boolean;
91
98
  }
@@ -96,6 +103,15 @@ export interface Ramp {
96
103
  rest: boolean;
97
104
  deadline?: number;
98
105
  }
106
+ /**
107
+ * A climb back from a fade out that was turned around: from `from`, the place along the fade's
108
+ * curve it had reached, at mix time `at`, rising at the whole curve per `over` ms.
109
+ */
110
+ export interface Rise {
111
+ from: number;
112
+ at: number;
113
+ over: number;
114
+ }
99
115
  export declare class Voice<I, O> {
100
116
  readonly id: number;
101
117
  readonly spec: VoiceSpec<I, O>;
@@ -104,7 +120,7 @@ export declare class Voice<I, O> {
104
120
  start: number;
105
121
  /** The owner whose clock it runs on, its start included; null for one on the mix clock. */
106
122
  owner: Voice<I, O> | null;
107
- state: 'pending' | 'live' | 'held' | 'fading' | 'done';
123
+ state: 'pending' | 'live' | 'frozen' | 'fading' | 'done';
108
124
  rate: number;
109
125
  weight: number;
110
126
  /**
@@ -120,7 +136,9 @@ export declare class Voice<I, O> {
120
136
  over: number;
121
137
  } | null;
122
138
  out: Ramp | null;
123
- readonly subjects: Store<I, Subject<unknown>>;
139
+ /** A fade out turned around, still climbing back; null once back in, or never turned. */
140
+ back: Rise | null;
141
+ subjects: Store<I, Subject<unknown>>;
124
142
  /** The subjects its spec names, or null where it names none. */
125
143
  readonly named: Named<I> | null;
126
144
  /** Kit slot of each channel the patch writes, in `writes` order. */
@@ -151,6 +169,15 @@ export declare class Voice<I, O> {
151
169
  }> | null;
152
170
  /** Subjects faded out of this voice, by the mix time each left at; null while none have. */
153
171
  parted: Map<I, number> | null;
172
+ /**
173
+ * Under history, the records of subjects that left this voice, by the mix time each left at,
174
+ * while a seek or a read back may reach them; null while none have.
175
+ */
176
+ left: {
177
+ subject: I;
178
+ at: number;
179
+ held: Subject<unknown>;
180
+ }[] | null;
154
181
  readonly ease: Curve | undefined;
155
182
  /** How many passes its `loop` plays, worked out once. */
156
183
  readonly passes: number;
@@ -159,9 +186,9 @@ export declare class Voice<I, O> {
159
186
  readonly span: number;
160
187
  /** The state behind a motion patch, undefined for any other. */
161
188
  readonly motion: Motions<I> | undefined;
162
- /** Its `hold`, for a patch it applies to: motion holds its target already. */
163
- readonly holdsBefore: boolean;
164
- readonly holdsAfter: boolean;
189
+ /** Its `freeze`, for a patch it applies to: motion keeps its target already. */
190
+ readonly freezesBefore: boolean;
191
+ readonly freezesAfter: boolean;
165
192
  /** The mix time it first showed: when it was cued, or the first sync after. */
166
193
  opened: number;
167
194
  /** Which of its entries in the mix's due queue is current; older ones are skipped when popped. */
@@ -181,10 +208,20 @@ export declare class Voice<I, O> {
181
208
  doneAt: number;
182
209
  /** Under `history`, its controls after each change, oldest first. */
183
210
  log: Controls[] | null;
211
+ /** Under `history`, its controls as `cue` left them, which a voice parked by a seek takes back. */
212
+ first: Controls | null;
184
213
  /** Where an anchored `out` or `end` puts its fade's start, mix time; Infinity until known. */
185
214
  outAt: number;
215
+ /** The ramp of a fade the host set for `outAt` with `fade({ at })`; NaN for the voice's own. */
216
+ outOver: number;
217
+ /** Whether the host set `outAt` with `fade({ at })`, which no anchor moves. */
218
+ outSet: boolean;
219
+ /** The host time a pinned start was given at, NaN for none: a seek back pins it again. */
220
+ pinned: number;
186
221
  /** Whether its start is still to be fixed by an anchor, so it waits pending. */
187
222
  placing: boolean;
223
+ /** What each member of a join last answered, kept for once its target has left. */
224
+ answers: Map<Anchor, number> | null;
188
225
  /** The record `setting.keep` writes to: the one its patch or signal is being called for. */
189
226
  keepOn: Subject<unknown> | null;
190
227
  /** What `setting.keep` holds when called before any record is. */
@@ -200,14 +237,20 @@ export declare class Voice<I, O> {
200
237
  unreachedBits: Uint32Array | null;
201
238
  /** The handle `cue` returned, which `voices` hands back too. */
202
239
  handle: Handle<I> | null;
240
+ /** With a history store, the key of every subject it has a record of; null without one. */
241
+ keyed: Set<string | number | undefined> | null;
203
242
  /** For an owner, the voices it holds; null for any other voice. */
204
243
  holding: Holding<Voice<I, O>> | null;
244
+ /** For a span, its budget and how its children were last fitted; null for any other voice. */
245
+ fitting: Fitting | null;
205
246
  /**
206
247
  * `done` and `played`, made when first asked for, already settled if the voice is: most hosts
207
248
  * never await either, and a mix may hold tens of thousands of voices.
208
249
  */
209
250
  private finished;
210
251
  private playedAs;
252
+ /** The mix time `played` was settled at, so a seek back to before it can open it again. */
253
+ private playedAt;
211
254
  private donePromise;
212
255
  private doneSettle;
213
256
  private playedPromise;
@@ -218,8 +261,31 @@ export declare class Voice<I, O> {
218
261
  get played(): Promise<boolean>;
219
262
  /** The voice has left: `done` resolves. */
220
263
  resolve(): void;
221
- /** Its finite loop ended (true) or it left first (false), whichever comes first: `played` resolves. */
222
- play(played: boolean): void;
264
+ /**
265
+ * Its finite loop ended (true) or it left first (false), whichever comes first, noticed at mix
266
+ * time `at`: `played` resolves.
267
+ */
268
+ play(played: boolean, at: number): void;
269
+ /** How `played` settled, and the mix time it did; undefined while it has not. */
270
+ get settled(): {
271
+ played: boolean | undefined;
272
+ at: number;
273
+ };
274
+ /** Whether `played` settled true, which its owner counted. */
275
+ get passed(): boolean;
276
+ /** Whether `played` settled false: it left before its last pass ended. */
277
+ get unplayed(): boolean;
278
+ /**
279
+ * A seek back to mix time `t`: `done` and `played`, where they settled after it, start over with
280
+ * fresh promises. True where `played` had settled true, which its owner counted.
281
+ */
282
+ reopen(t: number): boolean;
283
+ /**
284
+ * A seek back to before its cue: out of the mix, as `cue` left it, until the mix plays the cue
285
+ * again. What it played since is forgotten, and `done` and `played` start over where they had
286
+ * settled.
287
+ */
288
+ park(): void;
223
289
  constructor(id: number, spec: VoiceSpec<I, O>, patch: Patch<I, O, unknown>, fade: FadeSpec, now: number, start: number, slotOf: Map<string, number>, channels: readonly Channel<unknown>[], host: unknown, send: (event: unknown) => void,
224
290
  /** The owner whose clock it runs on, its start included; null for one on the mix clock. */
225
291
  owner: Voice<I, O> | null);
package/dist/voice.js CHANGED
@@ -24,7 +24,10 @@ function keeping(voice) {
24
24
  }
25
25
  if (kept.has(owner))
26
26
  return kept.get(owner);
27
- const made = init();
27
+ const unkept = held?.unkept;
28
+ const made = unkept === undefined ? init() : unkept.shift();
29
+ if (unkept?.length === 0 && held !== null)
30
+ held.unkept = undefined;
28
31
  kept.set(owner, made);
29
32
  reading.kept++;
30
33
  return made;
@@ -93,6 +96,8 @@ export class Voice {
93
96
  anchorElapsed = 0;
94
97
  ramp = null;
95
98
  out = null;
99
+ /** A fade out turned around, still climbing back; null once back in, or never turned. */
100
+ back = null;
96
101
  subjects = new Store();
97
102
  /** The subjects its spec names, or null where it names none. */
98
103
  named;
@@ -121,6 +126,11 @@ export class Voice {
121
126
  parts = null;
122
127
  /** Subjects faded out of this voice, by the mix time each left at; null while none have. */
123
128
  parted = null;
129
+ /**
130
+ * Under history, the records of subjects that left this voice, by the mix time each left at,
131
+ * while a seek or a read back may reach them; null while none have.
132
+ */
133
+ left = null;
124
134
  ease;
125
135
  /** How many passes its `loop` plays, worked out once. */
126
136
  passes;
@@ -129,9 +139,9 @@ export class Voice {
129
139
  span;
130
140
  /** The state behind a motion patch, undefined for any other. */
131
141
  motion;
132
- /** Its `hold`, for a patch it applies to: motion holds its target already. */
133
- holdsBefore;
134
- holdsAfter;
142
+ /** Its `freeze`, for a patch it applies to: motion keeps its target already. */
143
+ freezesBefore;
144
+ freezesAfter;
135
145
  /** The mix time it first showed: when it was cued, or the first sync after. */
136
146
  opened = Number.NaN;
137
147
  /** Which of its entries in the mix's due queue is current; older ones are skipped when popped. */
@@ -151,10 +161,20 @@ export class Voice {
151
161
  doneAt = Number.POSITIVE_INFINITY;
152
162
  /** Under `history`, its controls after each change, oldest first. */
153
163
  log = null;
164
+ /** Under `history`, its controls as `cue` left them, which a voice parked by a seek takes back. */
165
+ first = null;
154
166
  /** Where an anchored `out` or `end` puts its fade's start, mix time; Infinity until known. */
155
167
  outAt = Number.POSITIVE_INFINITY;
168
+ /** The ramp of a fade the host set for `outAt` with `fade({ at })`; NaN for the voice's own. */
169
+ outOver = Number.NaN;
170
+ /** Whether the host set `outAt` with `fade({ at })`, which no anchor moves. */
171
+ outSet = false;
172
+ /** The host time a pinned start was given at, NaN for none: a seek back pins it again. */
173
+ pinned = Number.NaN;
156
174
  /** Whether its start is still to be fixed by an anchor, so it waits pending. */
157
175
  placing = false;
176
+ /** What each member of a join last answered, kept for once its target has left. */
177
+ answers = null;
158
178
  /** The record `setting.keep` writes to: the one its patch or signal is being called for. */
159
179
  keepOn = null;
160
180
  /** What `setting.keep` holds when called before any record is. */
@@ -167,14 +187,20 @@ export class Voice {
167
187
  unreachedBits = null;
168
188
  /** The handle `cue` returned, which `voices` hands back too. */
169
189
  handle = null;
190
+ /** With a history store, the key of every subject it has a record of; null without one. */
191
+ keyed = null;
170
192
  /** For an owner, the voices it holds; null for any other voice. */
171
193
  holding;
194
+ /** For a span, its budget and how its children were last fitted; null for any other voice. */
195
+ fitting = null;
172
196
  /**
173
197
  * `done` and `played`, made when first asked for, already settled if the voice is: most hosts
174
198
  * never await either, and a mix may hold tens of thousands of voices.
175
199
  */
176
200
  finished = false;
177
201
  playedAs = undefined;
202
+ /** The mix time `played` was settled at, so a seek back to before it can open it again. */
203
+ playedAt = Number.NaN;
178
204
  donePromise = null;
179
205
  doneSettle = null;
180
206
  playedPromise = null;
@@ -209,14 +235,94 @@ export class Voice {
209
235
  this.finished = true;
210
236
  this.doneSettle?.();
211
237
  }
212
- /** Its finite loop ended (true) or it left first (false), whichever comes first: `played` resolves. */
213
- play(played) {
238
+ /**
239
+ * Its finite loop ended (true) or it left first (false), whichever comes first, noticed at mix
240
+ * time `at`: `played` resolves.
241
+ */
242
+ play(played, at) {
214
243
  if (this.quiet || this.playedAs !== undefined)
215
244
  return;
216
245
  this.playedAs = played;
246
+ this.playedAt = at;
217
247
  this.playedSettle?.(played);
218
248
  if (played && this.owner !== null)
219
- childPlayed(this.owner);
249
+ childPlayed(this.owner, at);
250
+ }
251
+ /** How `played` settled, and the mix time it did; undefined while it has not. */
252
+ get settled() {
253
+ return { played: this.playedAs, at: this.playedAt };
254
+ }
255
+ /** Whether `played` settled true, which its owner counted. */
256
+ get passed() {
257
+ return this.playedAs === true;
258
+ }
259
+ /** Whether `played` settled false: it left before its last pass ended. */
260
+ get unplayed() {
261
+ return this.playedAs === false;
262
+ }
263
+ /**
264
+ * A seek back to mix time `t`: `done` and `played`, where they settled after it, start over with
265
+ * fresh promises. True where `played` had settled true, which its owner counted.
266
+ */
267
+ reopen(t) {
268
+ let unplayed = false;
269
+ if (this.playedAs !== undefined && !(this.playedAt <= t)) {
270
+ unplayed = this.playedAs;
271
+ this.playedAs = undefined;
272
+ this.playedPromise = null;
273
+ this.playedSettle = null;
274
+ }
275
+ if (this.finished && !(this.doneAt <= t)) {
276
+ this.finished = false;
277
+ this.donePromise = null;
278
+ this.doneSettle = null;
279
+ }
280
+ return unplayed;
281
+ }
282
+ /**
283
+ * A seek back to before its cue: out of the mix, as `cue` left it, until the mix plays the cue
284
+ * again. What it played since is forgotten, and `done` and `played` start over where they had
285
+ * settled.
286
+ */
287
+ park() {
288
+ this.reopen(Number.NEGATIVE_INFINITY);
289
+ const c = this.first;
290
+ this.anchorNow = c.anchorNow;
291
+ this.anchorElapsed = c.anchorElapsed;
292
+ this.rate = c.rate;
293
+ this.ramp = c.ramp;
294
+ this.weight = c.weight;
295
+ this.out = c.out;
296
+ this.start = c.start;
297
+ this.outAt = c.outAt;
298
+ this.outOver = c.outOver;
299
+ this.outSet = c.outSet;
300
+ this.log = null;
301
+ this.state = 'pending';
302
+ this.subjects = new Store();
303
+ this.scratch = [];
304
+ this.holder = null;
305
+ this.laned = false;
306
+ this.seeks++;
307
+ this.keeping = false;
308
+ this.parts = null;
309
+ this.parted = null;
310
+ this.opened = Number.NaN;
311
+ this.dueToken++;
312
+ this.early = null;
313
+ this.seen = 0;
314
+ this.latest = 0;
315
+ this.restedCount = 0;
316
+ this.cuedAt = Number.NEGATIVE_INFINITY;
317
+ this.doneAt = Number.POSITIVE_INFINITY;
318
+ this.placing = false;
319
+ this.answers = null;
320
+ this.keepOn = null;
321
+ this.ownKept = null;
322
+ this.unreached = null;
323
+ this.unreachedBits = null;
324
+ if (this.holding !== null)
325
+ this.holding = new Holding();
220
326
  }
221
327
  constructor(id, spec, patch, fade, now, start, slotOf, channels, host, send,
222
328
  /** The owner whose clock it runs on, its start included; null for one on the mix clock. */
@@ -243,11 +349,13 @@ export class Voice {
243
349
  ? this.duration * this.passes
244
350
  : Number.POSITIVE_INFINITY;
245
351
  this.motion = motionOf(patch);
246
- const holds = this.motion === undefined ? spec.hold : undefined;
247
- // A voice with no hold of its own takes its owner's.
248
- const inherits = holds === undefined && this.motion === undefined && owner !== null;
249
- this.holdsBefore = inherits ? owner.holdsBefore : holds === 'before' || holds === 'both';
250
- this.holdsAfter = inherits ? owner.holdsAfter : holds === 'after' || holds === 'both';
352
+ const freezes = this.motion === undefined ? (spec.freeze ?? spec.hold) : undefined;
353
+ // A voice with no freeze of its own takes its owner's.
354
+ const inherits = freezes === undefined && this.motion === undefined && owner !== null;
355
+ this.freezesBefore = inherits
356
+ ? owner.freezesBefore
357
+ : freezes === 'before' || freezes === 'both';
358
+ this.freezesAfter = inherits ? owner.freezesAfter : freezes === 'after' || freezes === 'both';
251
359
  this.setting = new VoiceSetting(host, send, this);
252
360
  this.rate = spec.rate ?? 1;
253
361
  this.weight = typeof spec.weight === 'number' ? spec.weight : 1;
@@ -275,8 +383,11 @@ export class Voice {
275
383
  ramp: this.ramp,
276
384
  weight: this.weight,
277
385
  out: this.out,
386
+ back: this.back,
278
387
  start: this.start,
279
388
  outAt: this.outAt,
389
+ outOver: this.outOver,
390
+ outSet: this.outSet,
280
391
  });
281
392
  }
282
393
  /**
@@ -304,8 +415,11 @@ export class Voice {
304
415
  v.ramp = controls.ramp;
305
416
  v.weight = controls.weight;
306
417
  v.out = controls.out;
418
+ v.back = controls.back;
307
419
  v.start = controls.start;
308
420
  v.outAt = controls.outAt;
421
+ v.outOver = controls.outOver;
422
+ v.outSet = controls.outSet;
309
423
  }
310
424
  v.quiet = true;
311
425
  v.unreached = null;
package/dist/weigh.js CHANGED
@@ -27,7 +27,7 @@ export function horizonFor(voice, delay, now) {
27
27
  }
28
28
  /** The ramp a voice's own fade envelope applies this frame, 0..1. */
29
29
  export function fadeOf(voice, now, since) {
30
- return envelope(voice.fade.in ?? 0, voice.out, voice.ease, this.reducedNow, now, since);
30
+ return envelope(voice.fade.in ?? 0, voice.out, voice.ease, this.reducedNow, now, since, voice.back);
31
31
  }
32
32
  /** What every owner above a voice multiplies into its weight for a subject this frame. */
33
33
  export function ownedBy(voice, subject) {
@@ -84,6 +84,6 @@ export function base(voice, subject, now, held, slot = -1) {
84
84
  ? signal(subject, voice.setting)
85
85
  : this.blended(voice, subject, held, slot);
86
86
  if (signal.input && !was)
87
- record(this, held, base);
87
+ record(this, voice, subject, held, base);
88
88
  return base;
89
89
  }