@msb235/blits 0.2.1 → 0.4.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.
package/dist/types.d.ts CHANGED
@@ -93,13 +93,17 @@ export interface Setting<S = void> {
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.
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.
97
99
  */
98
100
  keep<K>(owner: object, init: () => K): K;
99
101
  /**
100
102
  * Reports an event at `timestamp`, for this voice and this subject. The mix queues it until the
101
103
  * host drains it, and never calls back. Meant for `step`, where under `stepMs` the timestamp is
102
- * the interval the event happened in rather than the frame that sampled it.
104
+ * the interval the event happened in rather than the frame that sampled it. Sent from a stateless
105
+ * 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.
103
107
  */
104
108
  send(event: unknown): void;
105
109
  }
@@ -118,14 +122,43 @@ export interface Sent<I, E = unknown> {
118
122
  tags: readonly string[];
119
123
  event: E;
120
124
  }
125
+ /**
126
+ * What a `'motion'` patch is made of, as data an engine can read: the kind of motion and its
127
+ * constants. Each subject's start, target and starting velocity stay on the patch, since they may
128
+ * be functions of the subject.
129
+ *
130
+ * @category patch
131
+ */
132
+ export type MotionSpec = {
133
+ readonly kind: 'spring';
134
+ /** Per second squared. */
135
+ readonly stiffness: number;
136
+ /** Per second. */
137
+ readonly damping: number;
138
+ readonly mass: number;
139
+ readonly settle: number;
140
+ } | {
141
+ readonly kind: 'glide';
142
+ /** The friction's time constant, ms. */
143
+ readonly ms: number;
144
+ readonly settle: number;
145
+ } | {
146
+ readonly kind: 'tween';
147
+ /** How long a stretch takes; undefined where it varies by subject, kept on the patch then. */
148
+ readonly ms: number | undefined;
149
+ readonly ease: Easing;
150
+ };
121
151
  /**
122
152
  * A pure function of phase and a subject that returns a delta. Optionally stateful.
123
153
  *
124
154
  * @category patch
125
155
  */
126
156
  export interface Patch<I, O, S = void> {
127
- /** Which authoring form built it. An engine declares which forms it runs. */
128
- readonly form: 'fn' | 'keys';
157
+ /**
158
+ * Which authoring form built it: `'fn'` from `patch`, `'keys'` from `keys`, `'motion'` from
159
+ * `spring`, `glide` or `tween`. An engine declares which forms it runs.
160
+ */
161
+ readonly form: 'fn' | 'keys' | 'motion';
129
162
  /** Milliseconds one pass lasts. 0 is aperiodic: phase and pass stay 0. */
130
163
  readonly period: number;
131
164
  /** The channels this patch contributes to. Every key `at` sets, and no others. */
@@ -148,6 +181,14 @@ export interface Patch<I, O, S = void> {
148
181
  step?(state: S, dt: number, subject: I, setting: Setting<S>): void;
149
182
  /** Present when the patch was authored as keyframes, so an engine that reads data can. */
150
183
  readonly keys?: readonly Keyframe<O>[];
184
+ /** Present on a `'motion'` patch: its kind and constants, for an engine that reads data. */
185
+ readonly motion?: MotionSpec;
186
+ /**
187
+ * A copy of `state` that shares nothing with it, for a read at another time. Default
188
+ * `structuredClone`, which is enough for plain data; a state holding a class instance or a
189
+ * function needs its own.
190
+ */
191
+ clone?(state: S): S;
151
192
  }
152
193
  /**
153
194
  * A 0..1 scalar resolved per subject per frame from something outside the clock. `input` is set
@@ -169,6 +210,82 @@ export interface FadeSpec {
169
210
  out?: number;
170
211
  ease?: Easing;
171
212
  }
213
+ /**
214
+ * The four times a voice is known by on the mix clock: when its fade in begins (`start`), when it is
215
+ * fully in (`in`), when its fade out begins (`out`), and when it is gone (`end`).
216
+ *
217
+ * @category score
218
+ */
219
+ export type Mark = 'start' | 'in' | 'out' | 'end';
220
+ /**
221
+ * Selects voices by what the plan knows of them, never by a channel's value, within the asking
222
+ * voice's own score unless it names another. Where several match,
223
+ * the resolver picks one: `last` cued (the default), `first` cued, `next` (the earliest whose mark
224
+ * is still to come), `earliest` or `latest` by the mark's time.
225
+ *
226
+ * @category score
227
+ */
228
+ export interface Query {
229
+ /** The score to look in. Default: the asking voice's own. */
230
+ score?: string;
231
+ name?: string;
232
+ tag?: string;
233
+ /** A channel the voice's patch writes. */
234
+ writes?: string;
235
+ resolver?: 'last' | 'first' | 'next' | 'earliest' | 'latest';
236
+ }
237
+ /**
238
+ * A time given by another voice: `mark` of the voice `of` selects, moved by `by` ms. `after` is
239
+ * sugar for that voice's `end`, `with` for its `start`, and `before` for its start less `by`. A
240
+ * string selects by name.
241
+ *
242
+ * @category score
243
+ */
244
+ export type Anchor = {
245
+ of: string | Query;
246
+ mark: Mark;
247
+ by?: number;
248
+ } | {
249
+ after: string | Query;
250
+ by?: number;
251
+ } | {
252
+ with: string | Query;
253
+ by?: number;
254
+ } | {
255
+ before: string | Query;
256
+ by?: number;
257
+ };
258
+ /**
259
+ * Where a voice sits on the mix clock: at most one of `start` and `in`, and at most one of `out`
260
+ * and `end`, each a timestamp on the host's clock or an anchor to another voice. A voice whose
261
+ * anchor has no answer yet waits pending; one whose anchored mark is already past starts partway
262
+ * through, as playback does from the middle of a region.
263
+ *
264
+ * @category score
265
+ */
266
+ export interface Placement {
267
+ start?: number | Anchor;
268
+ in?: number | Anchor;
269
+ out?: number | Anchor;
270
+ end?: number | Anchor;
271
+ }
272
+ /**
273
+ * One mark as `marks` lists it: one of a voice's four, or one the host announced on a score, which
274
+ * has a name and no voice.
275
+ *
276
+ * @category score
277
+ */
278
+ export interface Marked {
279
+ /** On the host's clock. */
280
+ timestamp: number;
281
+ /** Which of a voice's four this is; undefined for an announced mark. */
282
+ mark: Mark | undefined;
283
+ /** The voice it belongs to; undefined for an announced mark. */
284
+ voice: number | undefined;
285
+ score: string | undefined;
286
+ name: string | undefined;
287
+ tags: readonly string[];
288
+ }
172
289
  /**
173
290
  * What `cue` takes: a patch, and the clock, weight and reach it plays with.
174
291
  *
@@ -178,9 +295,18 @@ export interface VoiceSpec<I, O> {
178
295
  patch: Patch<I, O, unknown>;
179
296
  /**
180
297
  * Which subjects this voice reaches. Default: all of them. The predicate is fixed at `cue`; it
181
- * runs per subject the first time the mix sees that subject, and the answer is kept.
298
+ * runs per subject the first time the mix sees that subject, and the answer is kept. Every
299
+ * 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.
182
302
  */
183
303
  target?: (subject: I) => boolean;
304
+ /**
305
+ * The subjects this voice reaches, fixed at `cue` and matched by identity; a probe of any other
306
+ * never asks this voice. Takes the place of `target`, and a cue giving both is refused: a
307
+ * predicate over a fixed list is that list filtered, which the host can do before cueing.
308
+ */
309
+ subjects?: readonly I[];
184
310
  /**
185
311
  * When the voice starts, in ms on the host's clock, the one it passes `sync`: a rAF timestamp and
186
312
  * `performance.now()` share an origin, `Date.now()` does not. Default: the next sync.
@@ -191,7 +317,7 @@ export interface VoiceSpec<I, O> {
191
317
  /**
192
318
  * true loops for good, false plays one pass, n plays n passes. A finite loop leaves when its
193
319
  * passes are done for the latest-staggered subject it has seen — over `fade.out` where one is
194
- * set, at once where none is.
320
+ * set, at once where none is — unless it holds after.
195
321
  */
196
322
  loop?: boolean | number;
197
323
  /**
@@ -199,6 +325,16 @@ export interface VoiceSpec<I, O> {
199
325
  * Like `target`, it runs once per subject on first sight, and the answer is kept.
200
326
  */
201
327
  stagger?: (subject: I) => number;
328
+ /**
329
+ * Show a subject the first frame before it starts — while the voice is pending and while the
330
+ * 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`
332
+ * 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.
336
+ */
337
+ hold?: 'before' | 'after' | 'both';
202
338
  /** Steady weight, or a signal read per subject per frame. Default 1. */
203
339
  weight?: number | Signal<I>;
204
340
  /** Ramp in and out, ms. Out applies on `fade()` and when a finite loop ends. */
@@ -209,20 +345,40 @@ export interface VoiceSpec<I, O> {
209
345
  from?: 'current';
210
346
  /** Words a source attaches to this voice, so its events can be drained as a set. */
211
347
  tags?: readonly string[];
348
+ /** What other voices call this one by. A label blits never reads. */
349
+ name?: string;
350
+ /**
351
+ * The plan this voice belongs to. Each source keeps its own, so two sources can use one name
352
+ * without meeting: a bare name in an anchor means a voice in the same score, and a query names
353
+ * `score` to reach into another. Default: the mix's one unnamed score.
354
+ */
355
+ score?: string;
356
+ /** Where it sits relative to the clock or to other voices, in place of `start`. */
357
+ anchor?: Placement;
212
358
  }
213
359
  /**
214
- * How a voice leaves when it is faded.
360
+ * How a voice leaves when it is faded: the whole voice, or one subject.
215
361
  *
216
362
  * @category blending
217
363
  */
218
- export interface FadeOptions {
364
+ export type FadeOptions<I = unknown> = {
219
365
  /** The ramp, ms. Defaults to the voice's own `fade.out`. */
220
366
  over?: number;
221
367
  /** Leave per subject at the first frame that subject's contribution is at rest. */
222
368
  at?: 'rest';
223
369
  /** Ms after which the voice leaves whether or not it rested. */
224
370
  deadline?: number;
225
- }
371
+ subject?: never;
372
+ } | {
373
+ /**
374
+ * The one subject to fade out of this voice, which plays on for the rest. Once the ramp ends
375
+ * the voice forgets the subject and reaches it no more, until a motion patch's `to` brings it
376
+ * back, met afresh: `from`, `target` and `stagger` asked again.
377
+ */
378
+ subject: I;
379
+ /** The ramp, ms. Defaults to the voice's own `fade.out`; 0 takes the subject out at once. */
380
+ over?: number;
381
+ };
226
382
  /**
227
383
  * The live controls on one voice, returned by `cue`.
228
384
  *
@@ -230,7 +386,8 @@ export interface FadeOptions {
230
386
  */
231
387
  export interface Handle<I = unknown> {
232
388
  readonly id: number;
233
- readonly state: 'pending' | 'live' | 'fading' | 'done';
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';
234
391
  /** Live. Writes land on the next sync. */
235
392
  weight: number;
236
393
  /** Playback rate now. Setting it changes speed at once; `ramp` eases into a new one. */
@@ -246,16 +403,22 @@ export interface Handle<I = unknown> {
246
403
  * accumulated into, so state is left where it is and never run forward to meet the new position.
247
404
  */
248
405
  seek(elapsed: number): void;
249
- fade(opts?: FadeOptions): void;
406
+ fade(opts?: FadeOptions<I>): void;
250
407
  /**
251
408
  * The weight this voice gave `subject` the last frame that subject was probed: after its fades
252
409
  * and its weight signal, before a locus folds it with its alternatives. 0 for a subject it does
253
- * not reach, has not started on, has left at rest, or has never been probed for; 0 once done.
410
+ * not reach, has not started on and does not hold before, has left at rest, or has never been
411
+ * probed for; 0 once done.
254
412
  * Costs nothing until it is asked.
255
413
  */
256
414
  weightOf(subject: I): number;
257
415
  /** Resolves when the voice has been removed from the mix, however that happened. */
258
416
  readonly done: Promise<void>;
417
+ /**
418
+ * Resolves true when a finite loop's last pass ends, for the latest-staggered subject it has
419
+ * seen, and false if the voice leaves before that. Never rejects.
420
+ */
421
+ readonly played: Promise<boolean>;
259
422
  }
260
423
  /**
261
424
  * How a mix is built: its engine, reduced motion, the host, and its limits.
@@ -288,7 +451,64 @@ export interface MixOptions {
288
451
  * Signals and `at` still see the frame. Off by default.
289
452
  */
290
453
  stepMs?: number;
454
+ /**
455
+ * How far back `project` may read, in ms of mix time, and how often a stateful voice's state is
456
+ * kept per subject on the way, `every` ms (default 200). Within it the mix remembers what it cued,
457
+ * every change a handle made and when, the voices that have left, and those copies of state, so a
458
+ * read back restores the nearest copy and steps forward from it. Off by default, and a mix without
459
+ * it keeps nothing. With `inputs`, it also keeps what each input signal on a voice's weight read
460
+ * per subject, and the host fields patches `reads`, each time they changed, so a read back over a
461
+ * `level` or a pointer is known rather than held.
462
+ */
463
+ history?: {
464
+ ms: number;
465
+ every?: number;
466
+ inputs?: boolean;
467
+ };
468
+ /**
469
+ * Whether a channel may run as a lane: computed for every subject at once in flat arrays, when
470
+ * every voice writing it can run that way. On by default; the pose is the same either way, so
471
+ * turning it off is for ruling a lane out, or for comparing against. Two things differ: a
472
+ * stateless patch's `setting.send` from `at` sends for every subject a lane fills, probed or not,
473
+ * and a patch that first calls `setting.keep` partway through playing can advance that state once
474
+ * more for one unprobed subject (see `Setting.keep`).
475
+ */
476
+ lanes?: boolean;
291
477
  }
478
+ /**
479
+ * How sure a projection is of one channel: `exact` where only the clock and known changes drove
480
+ * it; `stepped` where state was advanced across a gap in one go, which is as good as the patch's
481
+ * step makes it; `held` where it depends on input from outside the clock, whose value at that time
482
+ * is not known, so the value it had is held.
483
+ *
484
+ * @category mix
485
+ */
486
+ export type Doubt = 'exact' | 'stepped' | 'held';
487
+ /**
488
+ * The mix read at another time: what `probe` would give at that timestamp, with nothing in the
489
+ * live mix moved. Valid until the mix is next synced or cued.
490
+ *
491
+ * @category mix
492
+ */
493
+ export interface Projection<I, O> {
494
+ /** The timestamp it reads at, on the host's clock. */
495
+ readonly timestamp: number;
496
+ /** The merged pose for one subject at this projection's timestamp. */
497
+ probe(subject: I, out?: O): O;
498
+ /** Per channel, how sure that pose is; the least sure voice that fed a channel decides. */
499
+ assess(subject: I): {
500
+ [K in keyof O]-?: Doubt;
501
+ };
502
+ }
503
+ /**
504
+ * Arrays for `pull` to write, one per channel it reads: any channel whose value is a number or an
505
+ * array of numbers.
506
+ *
507
+ * @category mix
508
+ */
509
+ export type Columns<O> = {
510
+ [K in keyof O as NonNullable<O[K]> extends number | readonly number[] ? K : never]?: Float64Array;
511
+ };
292
512
  /**
293
513
  * The live voices over one kit, folded into one pose per subject per frame.
294
514
  *
@@ -308,23 +528,64 @@ export interface Mix<I, O> {
308
528
  rebase(): void;
309
529
  /** The merged pose for one subject at the synced frame. */
310
530
  probe(subject: I, out?: O): O;
531
+ /**
532
+ * Writes each subject's pose into arrays, one per channel, subject by subject in the order given:
533
+ * what `probe(subject, out)` gives, without a pose object per subject. A channel of `n` numbers
534
+ * takes `n` places per subject, side by side; one with no value for a subject writes NaN there.
535
+ * Fastest while every voice runs on a lane, and when `subjects` is an array read again in the
536
+ * same order: the mix remembers the last array's subjects by position, holding them until the
537
+ * next `pull`, and skips looking up any still in its place. Throws when an array is too short for
538
+ * the subjects.
539
+ */
540
+ pull(subjects: Iterable<I>, into: Columns<O>): void;
541
+ /**
542
+ * Reads the mix at another timestamp, on the host's clock, without moving it. Ahead of the last
543
+ * sync it plays what is cued forward; behind it, it needs `history`, and throws for a timestamp
544
+ * older than the history reaches.
545
+ */
546
+ project(timestamp: number): Projection<I, O>;
311
547
  /** Every channel at rest for this subject this frame, so a host can skip the write. */
312
548
  atRest(subject: I): boolean;
313
549
  /** Anything still contributing, fading, or pending. */
314
550
  readonly live: boolean;
551
+ /**
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.
555
+ */
556
+ readonly inert: boolean;
315
557
  /** Fades every voice out: over `over` when given, over each voice's own `fade.out` otherwise. */
316
558
  mute(opts?: {
317
559
  over?: number;
318
560
  }): void;
319
- /** Forgets per-subject state. */
561
+ /** Forgets per-subject state, a motion patch's for the subject included. */
320
562
  drop(subject: I): void;
563
+ /**
564
+ * Puts a named mark on a score, for anchors to target as they target a voice's marks: a voice
565
+ * placed `{ with: 'reply' }` waits until the host announces `reply`. `at` is a timestamp on the
566
+ * host's clock, default now, and may lie ahead, so a read ahead sees it. A mark stays while it is
567
+ * ahead or while the mix's history reaches it.
568
+ */
569
+ announce(name: string, opts?: {
570
+ at?: number;
571
+ score?: string;
572
+ tags?: readonly string[];
573
+ }): void;
574
+ /**
575
+ * Every mark the plan knows between two timestamps on the host's clock, earliest first: when
576
+ * voices start, are fully in, begin to fade and are gone, and what the host announced. A mark
577
+ * nothing has fixed yet, such as the out of a voice that loops for good, is not listed.
578
+ */
579
+ marks(from: number, to: number): Marked[];
321
580
  /**
322
581
  * Every event patches have sent since the last drain, earliest first, in the order they were sent
323
582
  * where two share a timestamp. A subject's events are made while it catches up, which is when it is
324
- * probed, so one nobody probes has sent nothing yet: promptness is the host's, by probing. Under
325
- * `stepMs` each carries the end of the interval it happened in, so what is sent and when does not
326
- * depend on how the host spaces its probes. Time `rebase` took out sends nothing. Given a tag, it
327
- * takes only the events of voices carrying it and leaves the rest for whoever drains them.
583
+ * probed, so one nobody probes has sent nothing yet: promptness is the host's, by probing. A
584
+ * stateless patch's `at` on a lane is the exception: it runs for every subject the mix has met, at
585
+ * the frame's first probe. Under `stepMs` each carries the end of the interval it happened in, so
586
+ * what is sent and when does not depend on how the host spaces its probes. Time `rebase` took out
587
+ * sends nothing. Given a tag, it takes only the events of voices carrying it and leaves the rest for
588
+ * whoever drains them.
328
589
  */
329
590
  drain<E = unknown>(tag?: string): Sent<I, E>[];
330
591
  }
@@ -336,6 +597,6 @@ export interface Mix<I, O> {
336
597
  export interface Engine {
337
598
  readonly name: string;
338
599
  /** Which patch forms this engine can run. A voice it cannot run is refused at `cue`, by name. */
339
- readonly runs: ReadonlySet<'fn' | 'keys'>;
600
+ readonly runs: ReadonlySet<'fn' | 'keys' | 'motion'>;
340
601
  create<I, O>(kit: Kit<O>, opts: MixOptions): Mix<I, O>;
341
602
  }