@msb235/blits 0.6.0 → 0.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (100) hide show
  1. package/CHANGELOG.md +152 -0
  2. package/README.md +31 -7
  3. package/dist/bare.js +7 -1
  4. package/dist/book.d.ts +7 -0
  5. package/dist/book.js +35 -4
  6. package/dist/chain.d.ts +2 -1
  7. package/dist/chain.js +7 -3
  8. package/dist/channels.d.ts +12 -19
  9. package/dist/channels.js +18 -118
  10. package/dist/clock.d.ts +9 -2
  11. package/dist/clock.js +10 -2
  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 +1 -1
  18. package/dist/crowd.js +4 -1
  19. package/dist/cue.d.ts +2 -0
  20. package/dist/cue.js +80 -27
  21. package/dist/due.js +3 -0
  22. package/dist/fade.d.ts +13 -0
  23. package/dist/fade.js +57 -4
  24. package/dist/fill.d.ts +1 -1
  25. package/dist/fill.js +15 -8
  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 +18 -11
  30. package/dist/gather.d.ts +6 -0
  31. package/dist/gather.js +18 -0
  32. package/dist/gone.d.ts +32 -0
  33. package/dist/gone.js +134 -0
  34. package/dist/handle.d.ts +27 -2
  35. package/dist/handle.js +60 -11
  36. package/dist/held.js +4 -0
  37. package/dist/history.d.ts +15 -2
  38. package/dist/history.js +56 -9
  39. package/dist/hosts.d.ts +2 -2
  40. package/dist/hosts.js +20 -6
  41. package/dist/index.d.ts +8 -2
  42. package/dist/index.js +5 -1
  43. package/dist/lane.d.ts +1 -1
  44. package/dist/lanes.d.ts +32 -9
  45. package/dist/lanes.js +53 -24
  46. package/dist/locus.d.ts +3 -3
  47. package/dist/locus.js +1 -1
  48. package/dist/marks.d.ts +3 -0
  49. package/dist/marks.js +80 -12
  50. package/dist/meet.d.ts +3 -2
  51. package/dist/meet.js +15 -3
  52. package/dist/mixer.d.ts +56 -36
  53. package/dist/mixer.js +158 -87
  54. package/dist/motion.d.ts +2 -199
  55. package/dist/motion.js +2 -652
  56. package/dist/motions.d.ts +266 -0
  57. package/dist/motions.js +724 -0
  58. package/dist/move.d.ts +1 -1
  59. package/dist/move.js +28 -8
  60. package/dist/owner.d.ts +1 -1
  61. package/dist/owner.js +3 -3
  62. package/dist/pace.d.ts +2 -0
  63. package/dist/pace.js +8 -0
  64. package/dist/pack.d.ts +31 -0
  65. package/dist/pack.js +65 -0
  66. package/dist/paging.d.ts +63 -0
  67. package/dist/paging.js +110 -0
  68. package/dist/patch.d.ts +3 -0
  69. package/dist/patch.js +2 -0
  70. package/dist/place.js +235 -43
  71. package/dist/project.d.ts +7 -4
  72. package/dist/project.js +117 -78
  73. package/dist/pull.js +4 -3
  74. package/dist/qualify.js +4 -2
  75. package/dist/revive.d.ts +58 -0
  76. package/dist/revive.js +158 -0
  77. package/dist/sample.d.ts +1 -1
  78. package/dist/sample.js +6 -1
  79. package/dist/scored.d.ts +57 -0
  80. package/dist/scored.js +196 -0
  81. package/dist/seek.d.ts +3 -0
  82. package/dist/seek.js +311 -0
  83. package/dist/spans.d.ts +29 -0
  84. package/dist/spans.js +165 -0
  85. package/dist/strays.d.ts +1 -1
  86. package/dist/strays.js +21 -0
  87. package/dist/tape.d.ts +21 -0
  88. package/dist/tape.js +65 -0
  89. package/dist/ticker.d.ts +28 -3
  90. package/dist/ticker.js +3 -0
  91. package/dist/transport.d.ts +125 -0
  92. package/dist/transport.js +283 -0
  93. package/dist/types.d.ts +404 -38
  94. package/dist/unpage.d.ts +8 -0
  95. package/dist/unpage.js +165 -0
  96. package/dist/voice.d.ts +75 -7
  97. package/dist/voice.js +119 -4
  98. package/dist/weigh.js +2 -2
  99. package/docs/schema.html +120 -31
  100. package/package.json +2 -1
package/dist/mixer.d.ts CHANGED
@@ -1,20 +1,26 @@
1
1
  import { blended, laneRead, shareKept } from './blend.js';
2
2
  import type { Book } from './book.js';
3
- import { aims, chain, linkable } from './chain.js';
3
+ import { aims, chain, linkable, relink } from './chain.js';
4
4
  import { type LerpInto } from './channels.js';
5
5
  import type { Due } from './due.js';
6
6
  import { parting } from './fade.js';
7
- import { apply, baseFor, clamp, fold, folded, foldLoci, foldWith, influence, isRest, type Key, keyed, linked, owedBy, passes, read, slopeFor, tick } from './fold.js';
7
+ import { apply, baseFor, clamp, contribution, fold, folded, foldLoci, foldWith, isRest, type Key, keyed, linked, owedBy, passes, read, slopeFor, tick } from './fold.js';
8
+ import { GoneIndex } from './gone.js';
8
9
  import type { HandleHost } from './handle.js';
9
10
  import { held, shownOf, sinceOf, unreachedOf } from './held.js';
10
11
  import { Lanes } from './lanes.js';
11
12
  import { foldLocus, type LocusScratch } from './locus.js';
12
- import type { MotionOwner, Motions } from './motion.js';
13
- import { Pace } from './pace.js';
13
+ import type { MotionOwner, Motions } from './motions.js';
14
+ import type { Pace } from './pace.js';
15
+ import { Keys } from './paging.js';
14
16
  import { keep } from './pull.js';
15
17
  import { Steps } from './relink.js';
18
+ import { Outs } from './revive.js';
19
+ import { ScoreIndex } from './scored.js';
20
+ import { Fitting } from './spans.js';
16
21
  import { Store } from './store.js';
17
- import type { Booker, BookOptions, Channel, Columns, Engine, Handle, Kit, Marked, Mix, MixOptions, OwnerSpec, Patch, Projection, Sent, Signal, VoiceSpec } from './types.js';
22
+ import { type Announced, Transport } from './transport.js';
23
+ import type { Booker, BookOptions, Channel, Columns, Engine, Handle, Kit, Marked, Mix, MixOptions, OwnerSpec, Patch, Projection, Sent, Signal, SpanHandle, SpanSpec, Tape, VoiceSpec } from './types.js';
18
24
  import { type Subject, type Voice } from './voice.js';
19
25
  import { base, capped, fadeOf, horizonFor, ownedBy, ownerBase, prime, weigh, weighOwned } from './weigh.js';
20
26
  export declare class Mixer<I, O> implements Mix<I, O> {
@@ -23,6 +29,8 @@ export declare class Mixer<I, O> implements Mix<I, O> {
23
29
  cued: Voice<I, O>[];
24
30
  /** Under `history`, voices that have left but that a read back may still reach. */
25
31
  gone: Voice<I, O>[];
32
+ goneIx: GoneIndex<I, O>;
33
+ scored: ScoreIndex<I, O>;
26
34
  /** Set on a projection's own mixer: it sends nothing, keeps no history, and reads without committing. */
27
35
  projecting: boolean;
28
36
  /** Set on a projection reading back: a subject it has nothing on starts from its voice's start. */
@@ -37,19 +45,6 @@ export declare class Mixer<I, O> implements Mix<I, O> {
37
45
  at: number;
38
46
  fields: Record<string, unknown>;
39
47
  }[];
40
- /**
41
- * Marks the host announced on a score: `at` in host time, so the mix's rate never moves one, and
42
- * NaN until the next sync for one announced as now before any sync; `made` is the mix time it was
43
- * announced, for a read back to know what was known.
44
- */
45
- announced: {
46
- name: string;
47
- score: string | undefined;
48
- tags: readonly string[];
49
- at: number;
50
- made: number;
51
- order: number;
52
- }[];
53
48
  hostThen: {
54
49
  fields: Record<string, unknown>;
55
50
  } | undefined;
@@ -60,22 +55,17 @@ export declare class Mixer<I, O> implements Mix<I, O> {
60
55
  prev: O | undefined;
61
56
  prevAt: number;
62
57
  }>;
63
- nextId: number;
64
- /**
65
- * The mix clock: host time at the mix's rate. Host time is the host's timestamp less every gap
66
- * `rebase` has taken out, `u` at the last sync.
67
- */
68
- now: number;
69
- u: number;
58
+ /** The clock this mix plays on, which also holds its tape and the marks announced on it. */
59
+ readonly transport: Transport;
60
+ /** Its place among the mixes on its transport, in the order they joined. */
61
+ slot: number;
62
+ /** Taken off its transport, so it takes no more calls until a seek puts it back. */
63
+ dropped: boolean;
64
+ readonly name: string | undefined;
70
65
  /** This mix's frame among every mix's: moved by a sync, and by a control change or drop within one. */
71
66
  frame: number;
72
- offset: number;
73
- /** The mix's own rate; null while it has never been set, when mix time is host time. */
74
- pace: Pace | null;
75
67
  /** Pending voices whose start the host gave, by its host time, kept where the rate puts it. */
76
68
  pins: Map<Voice<I, O>, number> | null;
77
- last: number;
78
- rebasing: boolean;
79
69
  wantsPose: boolean;
80
70
  /** The pose `pull` folds a subject into where it cannot read straight from the lanes. */
81
71
  scratch: O | undefined;
@@ -110,7 +100,7 @@ export declare class Mixer<I, O> implements Mix<I, O> {
110
100
  * where none does. Relinked when `version` moves, which is whenever the list or a voice's pending
111
101
  * state changes.
112
102
  */
113
- readonly chains: Store<I, Subject<unknown>>;
103
+ chains: Store<I, Subject<unknown>>;
114
104
  version: number;
115
105
  readonly steps: Steps<Voice<I, O>>;
116
106
  /** Per subject, the voices whose `subjects` name it, in voice order. */
@@ -143,7 +133,9 @@ export declare class Mixer<I, O> implements Mix<I, O> {
143
133
  subject: I;
144
134
  };
145
135
  readonly send: (event: unknown) => void;
146
- /** The weight `influence` found besides the delta, read by the caller at once. */
136
+ /** The moment a seek went back to: what is stepped again up to it was sent the first time. */
137
+ sentTo: number;
138
+ /** The weight `contribution` found besides the delta, read by the caller at once. */
147
139
  w: number;
148
140
  readonly names: Key<O>[];
149
141
  readonly channels: Channel<unknown>[];
@@ -155,13 +147,36 @@ export declare class Mixer<I, O> implements Mix<I, O> {
155
147
  readonly locusScratch: LocusScratch<I, O>[];
156
148
  locusDepth: number;
157
149
  readonly slotOf: Map<string, number>;
158
- readonly lanes: Lanes<I, O> | null;
150
+ lanes: Lanes<I, O> | null;
159
151
  handles: HandleHost<I, O> | null;
160
152
  /** What `book` made, still booking; null while there is none. */
161
153
  bookers: Book<I, O>[] | null;
154
+ /** The fit a span being cued takes, which `cue` hands its voice. */
155
+ fitting: Fitting | null;
162
156
  /** Every owner still in the mix, null until one is cued. */
163
157
  owners: Voice<I, O>[] | null;
158
+ /** With a history store, the key each subject is paged under; null without one. */
159
+ keys: Keys | null;
160
+ /** With a history store, the voices it paged out whose handles are still held; null without one. */
161
+ outs: Outs | null;
164
162
  constructor(kit: Kit<O>, opts: MixOptions);
163
+ /** Throws where the clock is a shared transport's, which only the transport moves. */
164
+ private own;
165
+ get now(): number;
166
+ set now(v: number);
167
+ get u(): number;
168
+ set u(v: number);
169
+ get offset(): number;
170
+ set offset(v: number);
171
+ get pace(): Pace | null;
172
+ set pace(v: Pace | null);
173
+ get announced(): Announced[];
174
+ set announced(v: Announced[]);
175
+ get nextId(): number;
176
+ set nextId(v: number);
177
+ get last(): number;
178
+ get tape(): Tape | undefined;
179
+ get replaying(): boolean;
165
180
  get reduced(): boolean;
166
181
  get band(): {
167
182
  on: number;
@@ -170,17 +185,21 @@ export declare class Mixer<I, O> implements Mix<I, O> {
170
185
  cue(spec: VoiceSpec<I, O>): Handle<I>;
171
186
  blend(patches: readonly Patch<I, O, unknown>[], by: Signal<I>, spec?: Omit<VoiceSpec<I, O>, 'patch' | 'weight' | 'locus'>): Handle<I>[];
172
187
  owns(spec: OwnerSpec<I>): Handle<I>;
188
+ span(spec: SpanSpec<I>): SpanHandle<I>;
173
189
  sync(timestamp: number): void;
174
- private syncAt;
175
190
  book(opts: BookOptions): Booker;
176
191
  probe(subject: I, out?: O): O;
177
192
  pull(subjects: Iterable<I>, into: Columns<O>): void;
178
- project(timestamp: number): Projection<I, O>;
193
+ seek(time: number): void;
194
+ prepare(time: number): Promise<void>;
195
+ project(time: number): Projection<I, O>;
179
196
  announce(name: string, opts?: {
180
197
  at?: number;
181
198
  score?: string;
182
199
  tags?: readonly string[];
183
200
  }): void;
201
+ /** Throws for a mix taken off its transport. */
202
+ attached(): void;
184
203
  marks(from: number, to: number): Marked[];
185
204
  atRest(subject: I): boolean;
186
205
  rests(pose: O): boolean;
@@ -225,7 +244,7 @@ declare const methods: {
225
244
  owedBy: typeof owedBy;
226
245
  foldLoci: typeof foldLoci;
227
246
  read: typeof read;
228
- influence: typeof influence;
247
+ contribution: typeof contribution;
229
248
  keyed: typeof keyed;
230
249
  tick: typeof tick;
231
250
  apply: typeof apply;
@@ -248,6 +267,7 @@ declare const methods: {
248
267
  shareKept: typeof shareKept;
249
268
  parting: typeof parting;
250
269
  chain: typeof chain;
270
+ relink: typeof relink;
251
271
  linkable: typeof linkable;
252
272
  aims: typeof aims;
253
273
  held: typeof held;
package/dist/mixer.js CHANGED
@@ -1,22 +1,29 @@
1
1
  import { blended, blendOf, laneRead, shareKept } from './blend.js';
2
- import { aims, chain, linkable } from './chain.js';
2
+ import { aims, chain, linkable, relink } from './chain.js';
3
3
  import { lerpInto } from './channels.js';
4
4
  import { cue } from './cue.js';
5
5
  import { beginFade, parting } from './fade.js';
6
- import { apply, baseFor, clamp, fold, folded, foldLoci, foldWith, influence, isRest, keyed, linked, near, owedBy, passes, read, slopeFor, tick, } from './fold.js';
6
+ import { apply, baseFor, clamp, contribution, fold, folded, foldLoci, foldWith, isRest, keyed, linked, near, owedBy, passes, read, slopeFor, tick, } from './fold.js';
7
+ import { GoneIndex, goneIndex } from './gone.js';
7
8
  import { held, shownOf, sinceOf, unreachedOf } from './held.js';
9
+ import { leave, releasing } from './history.js';
8
10
  import { book, laneHost } from './hosts.js';
9
11
  import { Lanes } from './lanes.js';
10
12
  import { foldLocus } from './locus.js';
11
13
  import { listed } from './marks.js';
12
- import { move, nextFrame, waits } from './move.js';
14
+ import { nextFrame } from './move.js';
13
15
  import { ownerPatch } from './owner.js';
14
- import { Pace } from './pace.js';
15
- import { pin } from './place.js';
16
- import { project } from './project.js';
16
+ import { Keys } from './paging.js';
17
+ import { projectAll } from './project.js';
17
18
  import { keep, pull } from './pull.js';
18
19
  import { Steps } from './relink.js';
20
+ import { Outs } from './revive.js';
21
+ import { ScoreIndex } from './scored.js';
22
+ import { seek } from './seek.js';
23
+ import { Fitting, refit } from './spans.js';
19
24
  import { Store } from './store.js';
25
+ import { record } from './tape.js';
26
+ import { announce, Transport } from './transport.js';
20
27
  import { none } from './voice.js';
21
28
  import { base, capped, fadeOf, horizonFor, ownedBy, ownerBase, prime, weigh, weighOwned, } from './weigh.js';
22
29
  // biome-ignore lint/suspicious/noUnsafeDeclarationMerging: every member of `Methods` is installed below
@@ -26,6 +33,8 @@ export class Mixer {
26
33
  cued = [];
27
34
  /** Under `history`, voices that have left but that a read back may still reach. */
28
35
  gone = [];
36
+ goneIx = new GoneIndex();
37
+ scored = new ScoreIndex();
29
38
  /** Set on a projection's own mixer: it sends nothing, keeps no history, and reads without committing. */
30
39
  projecting = false;
31
40
  /** Set on a projection reading back: a subject it has nothing on starts from its voice's start. */
@@ -37,31 +46,20 @@ export class Mixer {
37
46
  * a projection reading back, the copy in force then.
38
47
  */
39
48
  hostLog = [];
40
- /**
41
- * Marks the host announced on a score: `at` in host time, so the mix's rate never moves one, and
42
- * NaN until the next sync for one announced as now before any sync; `made` is the mix time it was
43
- * announced, for a read back to know what was known.
44
- */
45
- announced = [];
46
49
  hostThen;
47
50
  /** Each subject's last two poses and when they were probed, for `from: 'current'`. */
48
51
  pose = new Store();
49
- nextId = 1;
50
- /**
51
- * The mix clock: host time at the mix's rate. Host time is the host's timestamp less every gap
52
- * `rebase` has taken out, `u` at the last sync.
53
- */
54
- now = Number.NaN;
55
- u = Number.NaN;
52
+ /** The clock this mix plays on, which also holds its tape and the marks announced on it. */
53
+ transport;
54
+ /** Its place among the mixes on its transport, in the order they joined. */
55
+ slot = -1;
56
+ /** Taken off its transport, so it takes no more calls until a seek puts it back. */
57
+ dropped = false;
58
+ name;
56
59
  /** This mix's frame among every mix's: moved by a sync, and by a control change or drop within one. */
57
60
  frame = nextFrame();
58
- offset = 0;
59
- /** The mix's own rate; null while it has never been set, when mix time is host time. */
60
- pace = null;
61
61
  /** Pending voices whose start the host gave, by its host time, kept where the rate puts it. */
62
62
  pins = null;
63
- last = Number.NaN;
64
- rebasing = false;
65
63
  wantsPose = false;
66
64
  /** The pose `pull` folds a subject into where it cannot read straight from the lanes. */
67
65
  scratch;
@@ -94,10 +92,13 @@ export class Mixer {
94
92
  if (this.stirred)
95
93
  return;
96
94
  this.stirred = true;
95
+ if (this.syncing)
96
+ return;
97
97
  const fns = this.stirFns;
98
- if (fns !== null && !this.syncing)
98
+ if (fns !== null)
99
99
  for (const fn of [...fns])
100
100
  fn();
101
+ this.transport.woke();
101
102
  }
102
103
  onWake(fn) {
103
104
  if (this.stirFns === null)
@@ -149,7 +150,7 @@ export class Mixer {
149
150
  };
150
151
  send = (event) => {
151
152
  const { voice, subject } = this.sending;
152
- if (voice === null || this.projecting)
153
+ if (voice === null || this.projecting || voice.setting.timestamp <= this.sentTo)
153
154
  return;
154
155
  this.sent.push({
155
156
  timestamp: voice.setting.timestamp,
@@ -159,7 +160,9 @@ export class Mixer {
159
160
  event,
160
161
  });
161
162
  };
162
- /** The weight `influence` found besides the delta, read by the caller at once. */
163
+ /** The moment a seek went back to: what is stepped again up to it was sent the first time. */
164
+ sentTo = Number.NEGATIVE_INFINITY;
165
+ /** The weight `contribution` found besides the delta, read by the caller at once. */
163
166
  w = 0;
164
167
  names;
165
168
  channels;
@@ -175,8 +178,14 @@ export class Mixer {
175
178
  handles = null;
176
179
  /** What `book` made, still booking; null while there is none. */
177
180
  bookers = null;
181
+ /** The fit a span being cued takes, which `cue` hands its voice. */
182
+ fitting = null;
178
183
  /** Every owner still in the mix, null until one is cued. */
179
184
  owners = null;
185
+ /** With a history store, the key each subject is paged under; null without one. */
186
+ keys = null;
187
+ /** With a history store, the voices it paged out whose handles are still held; null without one. */
188
+ outs = null;
180
189
  constructor(kit, opts) {
181
190
  this.kit = kit;
182
191
  this.opts = opts;
@@ -188,6 +197,75 @@ export class Mixer {
188
197
  this.slotOf.set(k, i);
189
198
  });
190
199
  this.lanes = opts.lanes === false ? null : new Lanes(laneHost(this));
200
+ this.name = opts.name;
201
+ const shared = opts.transport;
202
+ if (shared !== undefined) {
203
+ if (opts.history !== undefined)
204
+ throw new Error('blits: a mix on a transport keeps the history the transport keeps, not its own');
205
+ this.opts = { ...opts, history: shared.history };
206
+ this.transport = shared;
207
+ }
208
+ else
209
+ this.transport = new Transport(opts.history, false);
210
+ if (this.transport.pager !== null) {
211
+ this.keys = new Keys(opts.keyOf);
212
+ this.outs = new Outs();
213
+ if (shared !== undefined)
214
+ named(shared, opts.name);
215
+ }
216
+ this.transport.join(this);
217
+ }
218
+ /** Throws where the clock is a shared transport's, which only the transport moves. */
219
+ own(what) {
220
+ if (this.transport.shared)
221
+ throw new Error(`blits: ${this.name ?? 'this mix'} plays on a transport, so ${what} goes to the transport`);
222
+ return this.transport;
223
+ }
224
+ // The transport's clock, read and written as the mix's own by every module that moves it.
225
+ get now() {
226
+ return this.transport.now;
227
+ }
228
+ set now(v) {
229
+ this.transport.now = v;
230
+ }
231
+ get u() {
232
+ return this.transport.u;
233
+ }
234
+ set u(v) {
235
+ this.transport.u = v;
236
+ }
237
+ get offset() {
238
+ return this.transport.offset;
239
+ }
240
+ set offset(v) {
241
+ this.transport.offset = v;
242
+ }
243
+ get pace() {
244
+ return this.transport.pace;
245
+ }
246
+ set pace(v) {
247
+ this.transport.pace = v;
248
+ }
249
+ get announced() {
250
+ return this.transport.announced;
251
+ }
252
+ set announced(v) {
253
+ this.transport.announced = v;
254
+ }
255
+ get nextId() {
256
+ return this.transport.nextId;
257
+ }
258
+ set nextId(v) {
259
+ this.transport.nextId = v;
260
+ }
261
+ get last() {
262
+ return this.transport.last;
263
+ }
264
+ get tape() {
265
+ return this.transport.tape;
266
+ }
267
+ get replaying() {
268
+ return this.transport.replaying;
191
269
  }
192
270
  get reduced() {
193
271
  const r = this.opts.reduce;
@@ -197,6 +275,7 @@ export class Mixer {
197
275
  return this.opts.band ?? { on: 0.6, off: 0.4 };
198
276
  }
199
277
  cue(spec) {
278
+ this.attached();
200
279
  const engine = this.opts.engine ?? mixer;
201
280
  if (!engine.runs.has(spec.patch.form))
202
281
  throw new Error(`blits: engine ${engine.name} does not run ${spec.patch.form} patches`);
@@ -218,38 +297,19 @@ export class Mixer {
218
297
  throw new Error('blits: an owner does not loop: a pass would have to restart its children');
219
298
  return this.cue({ ...spec, patch: ownerPatch });
220
299
  }
221
- sync(timestamp) {
222
- this.syncing = true;
300
+ span(spec) {
301
+ this.fitting = new Fitting(spec);
223
302
  try {
224
- this.syncAt(timestamp);
303
+ const handle = this.owns(spec);
304
+ refit(this, this.owners?.find((v) => v.handle === handle));
305
+ return handle;
225
306
  }
226
307
  finally {
227
- this.syncing = false;
308
+ this.fitting = null;
228
309
  }
229
310
  }
230
- syncAt(timestamp) {
231
- if (this.rebasing && !Number.isNaN(this.last))
232
- this.offset += timestamp - this.last;
233
- this.rebasing = false;
234
- this.last = timestamp;
235
- const u = timestamp - this.offset;
236
- const pace = this.pace;
237
- const now = pace === null ? u : pace.sync(u);
238
- const later = u !== this.u;
239
- const still = later && now === this.now;
240
- this.u = u;
241
- // Host time moving while the mix clock stands still lands what waits on host time or the host.
242
- if (now !== this.now || (later && pace !== null && waits(this)))
243
- move(this, now);
244
- // It is a frame too, which asks every weight signal again, so one reading input follows it.
245
- if (still) {
246
- this.frame = nextFrame();
247
- this.lanes?.refill();
248
- }
249
- const bookers = this.bookers;
250
- if (bookers !== null)
251
- for (const b of bookers)
252
- b.sync();
311
+ sync(timestamp) {
312
+ this.own('sync').sync(timestamp);
253
313
  }
254
314
  book(opts) {
255
315
  return book(this, opts);
@@ -265,20 +325,25 @@ export class Mixer {
265
325
  pull(subjects, into) {
266
326
  pull(this, subjects, into);
267
327
  }
268
- project(timestamp) {
269
- const c = new Mixer(this.kit, { ...this.opts, history: undefined, lanes: false });
270
- return project(this, c, timestamp);
328
+ seek(time) {
329
+ if (this.projecting)
330
+ throw new Error('blits: a projection does not seek');
331
+ seek(this.own('seek'), time);
332
+ }
333
+ prepare(time) {
334
+ return this.own('prepare').prepare(time);
335
+ }
336
+ project(time) {
337
+ return projectAll(this.transport, time).of(this);
271
338
  }
272
339
  announce(name, opts = {}) {
273
- const at = opts.at !== undefined ? opts.at - this.offset : this.u;
274
- this.announced.push({
275
- name,
276
- score: opts.score,
277
- tags: opts.tags ?? none,
278
- at,
279
- made: Number.isNaN(this.now) ? Number.NEGATIVE_INFINITY : this.now,
280
- order: this.nextId++,
281
- });
340
+ this.attached();
341
+ announce(this.transport, name, opts, this.slot, this.name);
342
+ }
343
+ /** Throws for a mix taken off its transport. */
344
+ attached() {
345
+ if (this.dropped)
346
+ throw new Error(`blits: ${this.name ?? 'this mix'} was dropped from its transport`);
282
347
  }
283
348
  marks(from, to) {
284
349
  return listed(this, from, to).map(({ order: _, ...m }) => m);
@@ -316,7 +381,7 @@ export class Mixer {
316
381
  return true;
317
382
  }
318
383
  rebase() {
319
- this.rebasing = true;
384
+ this.own('rebase').rebase();
320
385
  }
321
386
  voices(tag) {
322
387
  const out = [];
@@ -341,25 +406,13 @@ export class Mixer {
341
406
  return this.cued.every((v) => this.still(v, this.now));
342
407
  }
343
408
  get rate() {
344
- return this.pace === null ? 1 : this.pace.rateAt(this.u);
409
+ return this.transport.rate;
345
410
  }
346
411
  set rate(r) {
347
- this.ramp(r, 0);
412
+ this.own('rate').ramp(r, 0);
348
413
  }
349
414
  ramp(rate, over) {
350
- if (!(rate >= 0 && rate < Number.POSITIVE_INFINITY))
351
- throw new RangeError(`blits: a mix's rate is a finite number, 0 or more, not ${rate}`);
352
- if (this.pace === null) {
353
- this.pace = new Pace(this.opts.history !== undefined);
354
- // Until now mix time was host time, so a pending voice's start is the host time it was given.
355
- for (const v of this.cued)
356
- if (v.state === 'pending' && !v.placing && v.owner === null && v.spec.start !== undefined)
357
- pin(this, v, v.start);
358
- }
359
- const history = this.opts.history;
360
- const reach = history === undefined ? Number.NEGATIVE_INFINITY : this.now - history.ms;
361
- this.pace.change(this.u, rate, over, reach);
362
- this.stir();
415
+ this.own('ramp').ramp(rate, over);
363
416
  }
364
417
  /** Whether a voice will change no pose from `now` on, short of a change made to it. */
365
418
  still(voice, now) {
@@ -380,6 +433,7 @@ export class Mixer {
380
433
  mute(opts) {
381
434
  for (const voice of this.cued)
382
435
  beginFade(this, voice, { over: opts?.over });
436
+ record(this, 'mute', () => this.mute(opts));
383
437
  }
384
438
  drop(subject) {
385
439
  this.frame = nextFrame();
@@ -397,9 +451,11 @@ export class Mixer {
397
451
  this.stir();
398
452
  // A record of it is only in a voice over every subject, one naming it, or one gone; a ramp out
399
453
  // of a voice that does not name it is in `parters`, and a patch's own state in `motions`.
454
+ const kept = releasing(this);
400
455
  const forget = (voice) => {
456
+ leave(this, voice, subject, voice.subjects.get(subject));
401
457
  voice.subjects.delete(subject);
402
- voice.motion?.release(subject);
458
+ voice.motion?.release(subject, ...kept);
403
459
  voice.parts?.delete(subject);
404
460
  voice.parted?.delete(subject);
405
461
  voice.blend?.of.reads.delete(subject);
@@ -410,8 +466,13 @@ export class Mixer {
410
466
  if (named !== undefined)
411
467
  for (const voice of [...named])
412
468
  forget(voice);
413
- for (const voice of this.gone)
469
+ const gone = goneIndex(this);
470
+ for (const voice of gone.general)
414
471
  forget(voice);
472
+ const goneNaming = gone.named.get(subject);
473
+ if (goneNaming !== undefined)
474
+ for (const voice of goneNaming)
475
+ forget(voice);
415
476
  if (this.owners !== null)
416
477
  for (const voice of this.owners)
417
478
  forget(voice);
@@ -427,10 +488,11 @@ export class Mixer {
427
488
  const strays = this.strays.get(subject);
428
489
  if (strays !== undefined)
429
490
  for (const motion of strays)
430
- motion.release(subject);
491
+ motion.release(subject, ...kept);
431
492
  this.strays.delete(subject);
432
493
  for (const motion of this.strayAll)
433
- motion.release(subject);
494
+ motion.release(subject, ...kept);
495
+ record(this, 'drop', () => this.drop(subject));
434
496
  }
435
497
  drain(tag) {
436
498
  const all = this.sent;
@@ -491,7 +553,7 @@ const methods = {
491
553
  owedBy,
492
554
  foldLoci,
493
555
  read,
494
- influence,
556
+ contribution,
495
557
  keyed,
496
558
  tick,
497
559
  apply,
@@ -514,6 +576,7 @@ const methods = {
514
576
  shareKept,
515
577
  parting,
516
578
  chain,
579
+ relink,
517
580
  linkable,
518
581
  aims,
519
582
  held,
@@ -525,6 +588,14 @@ const methods = {
525
588
  };
526
589
  for (const [name, value] of Object.entries(methods))
527
590
  Object.defineProperty(Mixer.prototype, name, { value, writable: true, configurable: true });
591
+ /** Refuses a mix joining a transport with a store unnamed, or under a name history still keys records by. */
592
+ function named(transport, name) {
593
+ if (name === undefined)
594
+ throw new Error('blits: a mix on a transport whose history has a store needs a name, which its records are keyed by');
595
+ const taken = [...transport.members, ...transport.dropped.map((d) => d.mix)];
596
+ if (taken.some((m) => m.name === name))
597
+ throw new Error(`blits: a mix named ${name} is already on this transport, or a seek could bring it back`);
598
+ }
528
599
  /**
529
600
  * The one that ships: every form on the CPU, probed per subject on demand.
530
601
  *