@msb235/blits 0.4.0 → 0.6.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 (105) hide show
  1. package/CHANGELOG.md +289 -0
  2. package/README.md +2 -1
  3. package/dist/bare.d.ts +29 -0
  4. package/dist/bare.js +105 -0
  5. package/dist/blend.d.ts +34 -0
  6. package/dist/blend.js +102 -0
  7. package/dist/book.d.ts +53 -0
  8. package/dist/book.js +218 -0
  9. package/dist/chain.d.ts +22 -0
  10. package/dist/chain.js +150 -0
  11. package/dist/channels.d.ts +2 -0
  12. package/dist/channels.js +3 -1
  13. package/dist/clock.d.ts +50 -3
  14. package/dist/clock.js +102 -9
  15. package/dist/clone.d.ts +7 -0
  16. package/dist/clone.js +57 -0
  17. package/dist/columns.d.ts +29 -0
  18. package/dist/columns.js +183 -0
  19. package/dist/crowd.d.ts +128 -0
  20. package/dist/crowd.js +354 -0
  21. package/dist/cue.d.ts +3 -0
  22. package/dist/cue.js +115 -0
  23. package/dist/due.d.ts +12 -0
  24. package/dist/due.js +93 -0
  25. package/dist/fade.d.ts +19 -0
  26. package/dist/fade.js +133 -0
  27. package/dist/fill.d.ts +42 -0
  28. package/dist/fill.js +381 -0
  29. package/dist/fold.d.ts +51 -0
  30. package/dist/fold.js +475 -0
  31. package/dist/gather.d.ts +24 -0
  32. package/dist/gather.js +186 -0
  33. package/dist/handle.d.ts +31 -0
  34. package/dist/handle.js +56 -0
  35. package/dist/held.d.ts +14 -0
  36. package/dist/held.js +117 -0
  37. package/dist/history.d.ts +22 -0
  38. package/dist/history.js +149 -0
  39. package/dist/hosts.d.ts +16 -0
  40. package/dist/hosts.js +178 -0
  41. package/dist/index.d.ts +3 -1
  42. package/dist/index.js +1 -0
  43. package/dist/keyrows.d.ts +36 -0
  44. package/dist/keyrows.js +138 -0
  45. package/dist/lane.d.ts +210 -0
  46. package/dist/lane.js +187 -0
  47. package/dist/lanes.d.ts +121 -246
  48. package/dist/lanes.js +150 -1578
  49. package/dist/locus.d.ts +40 -0
  50. package/dist/locus.js +89 -0
  51. package/dist/marks.d.ts +12 -0
  52. package/dist/marks.js +80 -0
  53. package/dist/meet.d.ts +16 -0
  54. package/dist/meet.js +113 -0
  55. package/dist/mixer.d.ts +248 -196
  56. package/dist/mixer.js +270 -2220
  57. package/dist/motion.d.ts +66 -21
  58. package/dist/motion.js +325 -165
  59. package/dist/move.d.ts +9 -0
  60. package/dist/move.js +190 -0
  61. package/dist/named.d.ts +12 -0
  62. package/dist/named.js +39 -0
  63. package/dist/numbers.d.ts +18 -6
  64. package/dist/numbers.js +59 -31
  65. package/dist/owed.d.ts +24 -0
  66. package/dist/owed.js +51 -0
  67. package/dist/owner.d.ts +43 -0
  68. package/dist/owner.js +117 -0
  69. package/dist/pace.d.ts +40 -0
  70. package/dist/pace.js +113 -0
  71. package/dist/patch.d.ts +51 -10
  72. package/dist/patch.js +140 -45
  73. package/dist/place.d.ts +20 -0
  74. package/dist/place.js +196 -0
  75. package/dist/project.d.ts +4 -0
  76. package/dist/project.js +233 -0
  77. package/dist/pull.d.ts +5 -0
  78. package/dist/pull.js +175 -0
  79. package/dist/qualify.d.ts +31 -0
  80. package/dist/qualify.js +345 -0
  81. package/dist/relink.d.ts +25 -0
  82. package/dist/relink.js +59 -0
  83. package/dist/rows.d.ts +33 -0
  84. package/dist/rows.js +282 -0
  85. package/dist/sample.d.ts +21 -0
  86. package/dist/sample.js +232 -0
  87. package/dist/signals.d.ts +13 -9
  88. package/dist/signals.js +14 -5
  89. package/dist/store.d.ts +10 -3
  90. package/dist/store.js +44 -9
  91. package/dist/strays.d.ts +21 -0
  92. package/dist/strays.js +84 -0
  93. package/dist/ticker.d.ts +48 -0
  94. package/dist/ticker.js +112 -0
  95. package/dist/tweened.d.ts +11 -0
  96. package/dist/tweened.js +48 -0
  97. package/dist/types.d.ts +237 -43
  98. package/dist/unreached.d.ts +5 -0
  99. package/dist/unreached.js +24 -0
  100. package/dist/voice.d.ts +240 -0
  101. package/dist/voice.js +324 -0
  102. package/dist/weigh.d.ts +26 -0
  103. package/dist/weigh.js +89 -0
  104. package/docs/schema.html +137 -56
  105. package/package.json +5 -2
package/dist/types.d.ts CHANGED
@@ -67,10 +67,10 @@ export interface Keyframe<O> {
67
67
  *
68
68
  * @category state
69
69
  */
70
- export interface Setting<S = void> {
70
+ export interface Setting<S = void, H = unknown> {
71
71
  /**
72
- * The mix clock at this frame: the host's timestamp less any time `rebase` took out. Identical
73
- * for every probe in the frame.
72
+ * The mix clock at this frame: the host's timestamp less any time `rebase` took out, run at the
73
+ * mix's `rate`. Identical for every probe in the frame.
74
74
  */
75
75
  timestamp: number;
76
76
  /**
@@ -88,14 +88,15 @@ export interface Setting<S = void> {
88
88
  weight: number;
89
89
  /** This subject's state, when the patch declares one. */
90
90
  state: S;
91
- /** Anything the host adds for its own patches. */
92
- host: unknown;
91
+ /** Anything the host adds for its own patches: the mix's `host`, typed as its `H`. */
92
+ host: H;
93
93
  /**
94
94
  * The state `owner` keeps for this voice and this subject, made by `init` on first ask. The mix
95
95
  * holds it, so a read at another time can copy it instead of moving it. A stateful signal keeps
96
- * its state here and nowhere else. With lanes on, a patch that first calls it partway through
97
- * playing can have that state advanced once for one subject not probed on the frame it starts; a
98
- * patch that keeps state from its first call, or declares `state`, never does.
96
+ * its state here and nowhere else. With lanes on, a patch or weight signal that first calls it
97
+ * partway through playing can have that state advanced once for one subject not probed on the
98
+ * frame it starts; one that keeps state from its first call, or a patch that declares `state`,
99
+ * never does.
99
100
  */
100
101
  keep<K>(owner: object, init: () => K): K;
101
102
  /**
@@ -103,7 +104,7 @@ export interface Setting<S = void> {
103
104
  * host drains it, and never calls back. Meant for `step`, where under `stepMs` the timestamp is
104
105
  * the interval the event happened in rather than the frame that sampled it. Sent from a stateless
105
106
  * patch's `at` while it runs on a lane, it goes out for every subject the mix has met, probed that
106
- * frame or not.
107
+ * frame or not; `at` is not asked for a subject at weight 0, so nothing is sent from it there.
107
108
  */
108
109
  send(event: unknown): void;
109
110
  }
@@ -153,14 +154,16 @@ export type MotionSpec = {
153
154
  *
154
155
  * @category patch
155
156
  */
156
- export interface Patch<I, O, S = void> {
157
+ export interface Patch<I, O, S = void, H = unknown> {
157
158
  /**
158
159
  * Which authoring form built it: `'fn'` from `patch`, `'keys'` from `keys`, `'motion'` from
159
160
  * `spring`, `glide` or `tween`. An engine declares which forms it runs.
160
161
  */
161
162
  readonly form: 'fn' | 'keys' | 'motion';
162
- /** Milliseconds one pass lasts. 0 is aperiodic: phase and pass stay 0. */
163
- readonly period: number;
163
+ /** Milliseconds one pass lasts, whether or not the voice loops. 0 has no passes: phase and pass stay 0. */
164
+ readonly duration: number;
165
+ /** @deprecated Use `duration`. A patch that sets only `period` still plays. */
166
+ readonly period?: number;
164
167
  /** The channels this patch contributes to. Every key `at` sets, and no others. */
165
168
  readonly writes: readonly (keyof O)[];
166
169
  /**
@@ -170,15 +173,18 @@ export interface Patch<I, O, S = void> {
170
173
  readonly kit?: Partial<Kit<O>>;
171
174
  /** The fields of `setting.host` this patch reads. `cue` refuses a mix whose host lacks one. */
172
175
  readonly reads?: readonly string[];
173
- /** `phase` is 0..1 across one period, wrapping. */
174
- at(phase: number, subject: I, setting: Setting<S>): Partial<O>;
176
+ /**
177
+ * `phase` is 0..1 across one pass, wrapping. Not asked for a subject its voice gives no weight, unless the voice is fading to rest;
178
+ * `step` still runs.
179
+ */
180
+ at(phase: number, subject: I, setting: Setting<S, H>): Partial<O>;
175
181
  /** Per-subject state, created on the first frame this patch sees a subject. */
176
182
  state?(subject: I): S;
177
183
  /**
178
184
  * Advances state once per subject per sampled frame, by the whole gap since it last advanced; or,
179
185
  * where the mix sets `stepMs`, once per whole interval of that length.
180
186
  */
181
- step?(state: S, dt: number, subject: I, setting: Setting<S>): void;
187
+ step?(state: S, dt: number, subject: I, setting: Setting<S, H>): void;
182
188
  /** Present when the patch was authored as keyframes, so an engine that reads data can. */
183
189
  readonly keys?: readonly Keyframe<O>[];
184
190
  /** Present on a `'motion'` patch: its kind and constants, for an engine that reads data. */
@@ -197,7 +203,7 @@ export interface Patch<I, O, S = void> {
197
203
  *
198
204
  * @category signal
199
205
  */
200
- export type Signal<I> = ((subject: I, setting: Setting) => number) & {
206
+ export type Signal<I, H = unknown> = ((subject: I, setting: Setting<void, H>) => number) & {
201
207
  readonly input?: boolean;
202
208
  };
203
209
  /**
@@ -286,19 +292,91 @@ export interface Marked {
286
292
  name: string | undefined;
287
293
  tags: readonly string[];
288
294
  }
295
+ /**
296
+ * An event at a time on a voice's own clock, which `book` hands a host ahead of time. It plays once
297
+ * per pass, for the voice and not per subject, so `stagger` does not spread it.
298
+ *
299
+ * @category score
300
+ */
301
+ export interface Hit<E = unknown> {
302
+ /** Voice ms into each pass: 0 up to the patch's `duration`, or any time at all for a patch with none. */
303
+ at: number;
304
+ event: E;
305
+ }
306
+ /**
307
+ * One pass of one hit, as `book` takes it.
308
+ *
309
+ * @category score
310
+ */
311
+ export interface BookedHit<E = unknown> {
312
+ /** On the host's clock. */
313
+ timestamp: number;
314
+ voice: number;
315
+ /** Which of the voice's `hits`, by index. */
316
+ hit: number;
317
+ /** Which pass, 0 first. */
318
+ pass: number;
319
+ event: E;
320
+ score: string | undefined;
321
+ name: string | undefined;
322
+ tags: readonly string[];
323
+ }
324
+ /**
325
+ * What `book` takes: an outside clock, how far ahead to book on it, and what to do with each item.
326
+ *
327
+ * @category score
328
+ */
329
+ export interface BookOptions<E = unknown> {
330
+ /**
331
+ * The outside clock, in ms: `audio.currentTime * 1000`, `performance.now()`, or whatever the host
332
+ * schedules against. Read once a sync. The mix maps its host time onto it by an offset that folds
333
+ * in 5% of each frame's difference, and starts over from the clock's own reading on a jump past
334
+ * 50 ms, such as a suspended context or a hidden tab.
335
+ */
336
+ clock: () => number;
337
+ /** How far ahead to book, host ms. It has to cover the longest gap between two syncs. */
338
+ ahead: number;
339
+ /** How far past an item first seen late may be and still be taken, host ms. */
340
+ late: number;
341
+ /**
342
+ * Called once per item: a mark as `marks` lists it, or a hit. `when` is on the outside clock;
343
+ * `lateBy` is how many host ms past the item was when first seen, 0 for one booked ahead, which
344
+ * is then taken at the clock's now. Return a `stop` to hear about a booking whose time moved by
345
+ * more than 1 ms or that went away (a seek, a rate or ramp on the voice or the mix, a fade, a
346
+ * `rebase`, an anchor's target moving, the voice leaving); the item is then booked again, at its
347
+ * new time, at the same sync.
348
+ */
349
+ take(item: Marked | BookedHit<E>, when: number, lateBy: number): {
350
+ stop(): void;
351
+ } | undefined;
352
+ /** Book only the voices, and announced marks, carrying this tag. */
353
+ tag?: string;
354
+ /** Book only this score's voices and marks. */
355
+ score?: string;
356
+ }
357
+ /**
358
+ * A running booker, returned by `book`.
359
+ *
360
+ * @category score
361
+ */
362
+ export interface Booker {
363
+ /** Stops every booking still ahead, and books nothing more. */
364
+ stop(): void;
365
+ }
289
366
  /**
290
367
  * What `cue` takes: a patch, and the clock, weight and reach it plays with.
291
368
  *
292
369
  * @category voice
293
370
  */
294
- export interface VoiceSpec<I, O> {
295
- patch: Patch<I, O, unknown>;
371
+ export interface VoiceSpec<I, O, H = unknown> {
372
+ patch: Patch<I, O, unknown, H>;
296
373
  /**
297
374
  * Which subjects this voice reaches. Default: all of them. The predicate is fixed at `cue`; it
298
375
  * runs per subject the first time the mix sees that subject, and the answer is kept. Every
299
376
  * voice's predicate meets every subject, so voices that each reach a known few cost the square
300
- * of their number, in time and in memory (a record per voice per subject asked: about 800 MB for
301
- * 1,000 such voices); name those with `subjects` instead.
377
+ * of their number in time; name those with `subjects` instead. A no is kept as a bit by the
378
+ * number lanes give the subject, but with `lanes: false` as a map entry per voice per subject
379
+ * asked (about 37 MB for 1,000 such voices over 1,000 subjects).
302
380
  */
303
381
  target?: (subject: I) => boolean;
304
382
  /**
@@ -317,7 +395,7 @@ export interface VoiceSpec<I, O> {
317
395
  /**
318
396
  * true loops for good, false plays one pass, n plays n passes. A finite loop leaves when its
319
397
  * passes are done for the latest-staggered subject it has seen — over `fade.out` where one is
320
- * set, at once where none is — unless it holds after.
398
+ * set, at once where none is — unless it freezes after.
321
399
  */
322
400
  loop?: boolean | number;
323
401
  /**
@@ -328,22 +406,24 @@ export interface VoiceSpec<I, O> {
328
406
  /**
329
407
  * Show a subject the first frame before it starts — while the voice is pending and while the
330
408
  * subject waits out its `stagger` — the last frame of the last pass after a finite loop plays
331
- * out, or both. A held subject's clock stands still: `at` is asked for the edge frame and `step`
409
+ * out, or both. A frozen subject's clock stands still: `at` is asked for the edge frame and `step`
332
410
  * does not run, past one last step to the end of the last pass. `fade.in` counts from the first
333
- * frame the voice shows. A voice holding after stays, `held`, until `fade()` or an anchored `out`
334
- * takes it out, so every one cued must be faded or it stays in the mix for good. Does nothing to
335
- * a motion patch, which holds its target already. Default: neither.
411
+ * frame the voice shows. A voice freezing after stays, `frozen`, until `fade()` or an anchored
412
+ * `out` takes it out, so every one cued must be faded or it stays in the mix for good. Does
413
+ * nothing to a motion patch, which keeps its target already. Default: neither.
336
414
  */
415
+ freeze?: 'before' | 'after' | 'both';
416
+ /** @deprecated Use `freeze`. A voice that sets only `hold` still freezes; `freeze` wins over it. */
337
417
  hold?: 'before' | 'after' | 'both';
338
418
  /** Steady weight, or a signal read per subject per frame. Default 1. */
339
- weight?: number | Signal<I>;
419
+ weight?: number | Signal<I, H>;
340
420
  /** Ramp in and out, ms. Out applies on `fade()` and when a finite loop ends. */
341
421
  fade?: FadeSpec;
342
422
  /** Voices sharing a locus are alternatives: the mix folds them through each channel's `lerp`. */
343
423
  locus?: string;
344
424
  /** A `keys` voice whose first stop reads wherever this subject is now. */
345
425
  from?: 'current';
346
- /** Words a source attaches to this voice, so its events can be drained as a set. */
426
+ /** Words a source attaches to this voice, so its events and its handle can be taken as a set. */
347
427
  tags?: readonly string[];
348
428
  /** What other voices call this one by. A label blits never reads. */
349
429
  name?: string;
@@ -355,6 +435,54 @@ export interface VoiceSpec<I, O> {
355
435
  score?: string;
356
436
  /** Where it sits relative to the clock or to other voices, in place of `start`. */
357
437
  anchor?: Placement;
438
+ /**
439
+ * Events at times on this voice's clock, which `book` hands a host ahead of time. Patch-sent
440
+ * events are not known ahead, so they stay drain-only; these are the plan.
441
+ */
442
+ hits?: readonly Hit[];
443
+ /**
444
+ * The owner this voice plays under, a handle `owns` returned. Its `start` and `anchor` are then on
445
+ * the owner's clock, in ms from when the owner starts, and default to where that clock is now; a
446
+ * bare name in an anchor means a sibling, another voice the owner holds. Its clock runs on the
447
+ * owner's, so the owner's rate, ramp and seeks move it, and its weight is multiplied by the
448
+ * owner's weight and fade. Without a `freeze` of its own it takes the owner's.
449
+ */
450
+ owner?: Handle<I>;
451
+ }
452
+ /**
453
+ * What `owns` takes: an owner has no patch, and plays only through the voices it holds.
454
+ *
455
+ * @category voice
456
+ */
457
+ export interface OwnerSpec<I, H = unknown> {
458
+ /**
459
+ * When the owner starts, on its own owner's clock: the host's for an owner on the mix, as a
460
+ * voice's `start` is. Default: now.
461
+ */
462
+ start?: number;
463
+ /** Its clock's rate, multiplied into every child's own. Default 1. */
464
+ rate?: number;
465
+ /**
466
+ * Multiplied into every child's weight per subject, as the child's own fade is, before the child's
467
+ * locus folds it: a number, or a signal read per subject per frame. Default 1.
468
+ */
469
+ weight?: number | Signal<I, H>;
470
+ /** One ramp in and out over every child, multiplied into their weights as `weight` is. */
471
+ fade?: FadeSpec;
472
+ /** The freeze every child without one of its own takes. */
473
+ freeze?: 'before' | 'after' | 'both';
474
+ /** @deprecated Use `freeze`. An owner that sets only `hold` still freezes; `freeze` wins over it. */
475
+ hold?: 'before' | 'after' | 'both';
476
+ /** Words a source attaches to it, as a voice's. */
477
+ tags?: readonly string[];
478
+ /** What other voices call it by, among its siblings. */
479
+ name?: string;
480
+ /** The plan it belongs to, as a voice's. */
481
+ score?: string;
482
+ /** Where it sits relative to its own owner's clock or to other voices, in place of `start`. */
483
+ anchor?: Placement;
484
+ /** The owner it plays under, so owners nest. */
485
+ owner?: Handle<I>;
358
486
  }
359
487
  /**
360
488
  * How a voice leaves when it is faded: the whole voice, or one subject.
@@ -386,8 +514,10 @@ export type FadeOptions<I = unknown> = {
386
514
  */
387
515
  export interface Handle<I = unknown> {
388
516
  readonly id: number;
389
- /** `held`: a voice holding after has played every pass and shows its last frame until faded. */
390
- readonly state: 'pending' | 'live' | 'held' | 'fading' | 'done';
517
+ /** `frozen`: a voice freezing after has played every pass and shows its last frame until faded. */
518
+ readonly state: 'pending' | 'live' | 'frozen' | 'fading' | 'done';
519
+ /** The handle of the owner this voice plays under; undefined for one on the mix clock. */
520
+ readonly owner: Handle<I> | undefined;
391
521
  /** Live. Writes land on the next sync. */
392
522
  weight: number;
393
523
  /** Playback rate now. Setting it changes speed at once; `ramp` eases into a new one. */
@@ -407,7 +537,7 @@ export interface Handle<I = unknown> {
407
537
  /**
408
538
  * The weight this voice gave `subject` the last frame that subject was probed: after its fades
409
539
  * and its weight signal, before a locus folds it with its alternatives. 0 for a subject it does
410
- * not reach, has not started on and does not hold before, has left at rest, or has never been
540
+ * not reach, has not started on and does not freeze before, has left at rest, or has never been
411
541
  * probed for; 0 once done.
412
542
  * Costs nothing until it is asked.
413
543
  */
@@ -425,12 +555,12 @@ export interface Handle<I = unknown> {
425
555
  *
426
556
  * @category mix
427
557
  */
428
- export interface MixOptions {
558
+ export interface MixOptions<H = unknown> {
429
559
  engine?: Engine;
430
560
  /** Reduced motion: fades and warm-ups snap, `dt` reads Infinity. */
431
561
  reduce?: boolean | (() => boolean);
432
- /** Merged into every `setting.host`. */
433
- host?: unknown;
562
+ /** Merged into every `setting.host`. Its type is the mix's `H`, which every patch and signal it cues reads. */
563
+ host?: H;
434
564
  /**
435
565
  * The band a rest-less channel's influence switches on and off across. It has to clear the frame
436
566
  * noise in a real signal; these are placeholders until the klieg port measures one.
@@ -514,11 +644,26 @@ export type Columns<O> = {
514
644
  *
515
645
  * @category mix
516
646
  */
517
- export interface Mix<I, O> {
647
+ export interface Mix<I, O, H = unknown> {
518
648
  /** Cues a voice. Throws when the engine cannot run the patch's form, or the kit lacks a channel. */
519
- cue(spec: VoiceSpec<I, O>): Handle<I>;
520
- /** N voices whose weights split one signal, cued into one locus so they fold as alternatives. */
521
- blend(patches: readonly Patch<I, O, unknown>[], by: Signal<I>, spec?: Omit<VoiceSpec<I, O>, 'patch' | 'weight' | 'locus'>): Handle<I>[];
649
+ cue(spec: VoiceSpec<I, O, H>): Handle<I>;
650
+ /**
651
+ * N voices whose weights split one signal, cued into one locus so they fold as alternatives. The
652
+ * signal is read once per subject per frame, with the first member's setting, and every member
653
+ * takes its share of that one reading; a signal keeping state keeps it once, not per member.
654
+ */
655
+ blend(patches: readonly Patch<I, O, unknown, H>[], by: Signal<I, H>, spec?: Omit<VoiceSpec<I, O, H>, 'patch' | 'weight' | 'locus'>): Handle<I>[];
656
+ /**
657
+ * Cues an owner: a voice with no patch that holds the voices cued with `owner` set to its handle,
658
+ * and plays them on its own clock, so they can be placed, timed and faded as one. Its handle acts
659
+ * on all of them: `rate` and `ramp` multiply into theirs, `seek` moves their clocks with its own
660
+ * and leaves their state where it is, and `weight` and `fade()` multiply into their weights, by
661
+ * what its `weightOf` reports; it fades as a whole, not by subject or at rest. It is
662
+ * `played` once every voice it held has finished its passes, and leaves, `done`, with its last; a
663
+ * fade that ends takes the rest with it. One that never holds a voice stays until faded. Owners
664
+ * nest. Throws for a `loop`: a pass would have to restart its children.
665
+ */
666
+ owns(spec: OwnerSpec<I, H>): Handle<I>;
522
667
  /** The host reports the clock, once a frame. Nothing advances at the call. */
523
668
  sync(timestamp: number): void;
524
669
  /**
@@ -526,7 +671,30 @@ export interface Mix<I, O> {
526
671
  * hidden tab comes back. Every voice, fade and subject resumes where it was left.
527
672
  */
528
673
  rebase(): void;
529
- /** The merged pose for one subject at the synced frame. */
674
+ /**
675
+ * The mix's own playback rate, multiplied into every voice's: 1 is real time and 0 pauses the
676
+ * whole mix. A negative rate throws, since the mix only goes forward. Setting it changes speed at
677
+ * once; `ramp` eases into a new one. It scales mix time, which everything a voice owns runs on:
678
+ * its clock, its fades and a handle's ramp, `stagger`, an anchor's `by`, `stepMs`, `maxDt`,
679
+ * `history.ms`, every `dt`, and `setting.timestamp`. Timestamps on the host's clock name the same
680
+ * moment whatever the rate does: a `start`, an anchor given as a number, an announced mark, what
681
+ * `marks` reports and the time `project` reads, so a start ahead of a paused mix waits for the
682
+ * host's clock to reach it. A weight signal is still asked at every probe, so one reading host
683
+ * input follows it while the mix is paused.
684
+ */
685
+ rate: number;
686
+ /**
687
+ * Moves the mix's rate to `rate` linearly over `over` ms of the host's clock, as `Handle.ramp`
688
+ * moves a voice's. Mix time integrates the ramp and a voice's clock integrates mix time, so a ramp
689
+ * on both multiplies and every position stays continuous. A later `rate` write or `ramp` replaces
690
+ * it.
691
+ */
692
+ ramp(rate: number, over: number): void;
693
+ /**
694
+ * The merged pose for one subject at the synced frame, written into `out` when given. Without
695
+ * it, each call makes a new object, so a host reading every subject every frame passes `out` or
696
+ * reads by `pull`.
697
+ */
530
698
  probe(subject: I, out?: O): O;
531
699
  /**
532
700
  * Writes each subject's pose into arrays, one per channel, subject by subject in the order given:
@@ -544,16 +712,33 @@ export interface Mix<I, O> {
544
712
  * older than the history reaches.
545
713
  */
546
714
  project(timestamp: number): Projection<I, O>;
547
- /** Every channel at rest for this subject this frame, so a host can skip the write. */
715
+ /**
716
+ * Every channel at rest for this subject this frame, so a host can skip the write. After a probe
717
+ * of the subject this frame, it answers for the pose that probe gave, without folding again.
718
+ */
548
719
  atRest(subject: I): boolean;
720
+ /**
721
+ * A handle on every voice still in the mix, pending, live, frozen or fading, in the order they were
722
+ * cued; given a tag, only the voices whose `tags` carry it. Each is the handle `cue` returned for
723
+ * that voice, so a listed handle is `===` the cued one.
724
+ */
725
+ voices(tag?: string): Handle<I>[];
549
726
  /** Anything still contributing, fading, or pending. */
550
727
  readonly live: boolean;
551
728
  /**
552
- * Another frame would change no pose, so a host's frame loop may sleep: every voice is done, held
553
- * at a plain weight, or a motion whose every subject has landed on its target. A change made since
554
- * the last sync, a retarget included, makes it false until the next.
729
+ * Another frame would change no pose, so a host's frame loop may sleep: every voice is done, frozen
730
+ * at a plain weight, or a motion whose every subject has landed on its target. At rate 0 it is
731
+ * enough that every voice not done has started, at a plain weight, with no anchor. A change made
732
+ * since the last sync, a retarget or a rate included, makes it false until the next.
555
733
  */
556
734
  readonly inert: boolean;
735
+ /**
736
+ * Calls `fn` when a host call makes the mix need frames again after a sync: a cue, a fade, a
737
+ * handle's or the mix's rate, a seek, a `drop`, a motion retargeted. It fires once until the next
738
+ * sync clears the change, and never during `sync`. Returns a function that unsubscribes `fn`.
739
+ * A frame loop sleeping on `inert` subscribes here to wake.
740
+ */
741
+ onWake(fn: () => void): () => void;
557
742
  /** Fades every voice out: over `over` when given, over each voice's own `fade.out` otherwise. */
558
743
  mute(opts?: {
559
744
  over?: number;
@@ -588,6 +773,15 @@ export interface Mix<I, O> {
588
773
  * whoever drains them.
589
774
  */
590
775
  drain<E = unknown>(tag?: string): Sent<I, E>[];
776
+ /**
777
+ * Hands the host each mark and hit before it comes, against a clock of its own, so it can schedule
778
+ * the thing itself: a sound on an `AudioContext`, a MIDI message with a timestamp, a video loaded
779
+ * before the voice that shows it starts. Booking runs at each `sync`, `ahead` ms out, and a
780
+ * booking whose time moves is stopped and taken again. Nothing is booked twice: a mark is known by
781
+ * its voice and which mark, a hit by its voice, index and pass. `project` books nothing, and
782
+ * nothing about booking is replayed under `history`. A mix with no booker pays nothing for it.
783
+ */
784
+ book<E = unknown>(opts: BookOptions<E>): Booker;
591
785
  }
592
786
  /**
593
787
  * The implementation behind a mix: which patch forms it runs, and how it makes one.
@@ -598,5 +792,5 @@ export interface Engine {
598
792
  readonly name: string;
599
793
  /** Which patch forms this engine can run. A voice it cannot run is refused at `cue`, by name. */
600
794
  readonly runs: ReadonlySet<'fn' | 'keys' | 'motion'>;
601
- create<I, O>(kit: Kit<O>, opts: MixOptions): Mix<I, O>;
795
+ create<I, O, H = unknown>(kit: Kit<O>, opts: MixOptions<H>): Mix<I, O, H>;
602
796
  }
@@ -0,0 +1,5 @@
1
+ import type { Voice } from './voice.js';
2
+ /** Whether the voice's shared unreached record stands for the subject numbered `slot`. */
3
+ export declare function unreached<I, O>(voice: Voice<I, O>, slot: number): boolean;
4
+ /** Marks or clears the subject numbered `slot` as one the voice does not reach. */
5
+ export declare function unreach<I, O>(voice: Voice<I, O>, slot: number, on: boolean): void;
@@ -0,0 +1,24 @@
1
+ /** Whether the voice's shared unreached record stands for the subject numbered `slot`. */
2
+ export function unreached(voice, slot) {
3
+ const bits = voice.unreachedBits;
4
+ if (bits === null || slot < 0)
5
+ return false;
6
+ const word = slot >>> 5;
7
+ return word < bits.length && (bits[word] & (1 << (slot & 31))) !== 0;
8
+ }
9
+ /** Marks or clears the subject numbered `slot` as one the voice does not reach. */
10
+ export function unreach(voice, slot, on) {
11
+ let bits = voice.unreachedBits;
12
+ const word = slot >>> 5;
13
+ if (bits === null || word >= bits.length) {
14
+ if (!on)
15
+ return;
16
+ const grown = new Uint32Array(Math.max(word + 1, (bits?.length ?? 0) * 2, 2));
17
+ if (bits !== null)
18
+ grown.set(bits);
19
+ voice.unreachedBits = grown;
20
+ bits = grown;
21
+ }
22
+ const bit = 1 << (slot & 31);
23
+ bits[word] = on ? bits[word] | bit : bits[word] & ~bit;
24
+ }