@msb235/blits 0.0.0-stage → 0.1.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.
@@ -0,0 +1,328 @@
1
+ /**
2
+ * One field of a delta, with its own arithmetic.
3
+ *
4
+ * @category channel
5
+ */
6
+ export interface Channel<V> {
7
+ /**
8
+ * Names this channel's arithmetic, so a patch written against one channel can be checked against
9
+ * another of the same name. Two channels of one kind must fold identically; the stock ones set it,
10
+ * as `'sum'` or `'vec(3, sum)'`. Absent, a channel matches only itself.
11
+ */
12
+ kind?: string;
13
+ /** Identity. Absent means the channel has none: it replaces rather than contributes. */
14
+ rest?: V;
15
+ /** Fold two influences into one. */
16
+ merge(a: V, b: V): V;
17
+ /** Fade toward `rest` by weight 0..1. Required when `rest` is set; absent otherwise. */
18
+ scale?(v: V, w: number): V;
19
+ /** Interpolate, for retargeting, for blending alternatives, and for folding a locus. */
20
+ lerp(a: V, b: V, u: number): V;
21
+ /**
22
+ * Optional: `merge(into, scale(v, w))`, written into `into` and returned, so a channel whose
23
+ * values are objects need not allocate per influence. The mix only hands it a value it made.
24
+ */
25
+ fold?(into: V, v: V, w: number): V;
26
+ }
27
+ /**
28
+ * The channel set for one kind of delta.
29
+ *
30
+ * @category channel
31
+ */
32
+ export type Kit<O> = {
33
+ readonly [K in keyof O]-?: Channel<NonNullable<O[K]>>;
34
+ };
35
+ /**
36
+ * A curve on 0..1, as data where it can be: CSS's names, a `cubic-bezier`, or `steps`, which a
37
+ * WAAPI or GPU engine can read. The function form runs on the CPU engine only.
38
+ *
39
+ * @category patch
40
+ */
41
+ export type Easing = ((u: number) => number) | 'linear' | 'ease' | 'ease-in' | 'ease-out' | 'ease-in-out' | {
42
+ readonly bezier: readonly [x1: number, y1: number, x2: number, y2: number];
43
+ } | {
44
+ readonly steps: number;
45
+ readonly jump?: 'start' | 'end';
46
+ };
47
+ /**
48
+ * One stop of a `keys` patch.
49
+ *
50
+ * @category patch
51
+ */
52
+ export interface Keyframe<O> {
53
+ /** Phase, 0..1. */
54
+ at: number;
55
+ delta: Partial<O>;
56
+ ease?: Easing;
57
+ }
58
+ /**
59
+ * What a patch may read and did not compute, for one subject this frame. The mix reuses the object,
60
+ * so it is valid only during the call it is handed to.
61
+ *
62
+ * @category state
63
+ */
64
+ export interface Setting<S = void> {
65
+ /**
66
+ * The mix clock at this frame: the host's timestamp less any time `rebase` took out. Identical
67
+ * for every probe in the frame.
68
+ */
69
+ timestamp: number;
70
+ /**
71
+ * The gap this subject is catching up by: milliseconds since it last advanced, which is the gap
72
+ * since the previous sync only for a subject sampled every frame. Infinity under reduced motion;
73
+ * no more than `maxDt` where the mix sets one. Inside a `step` under `stepMs`, it is `stepMs`, and
74
+ * `timestamp` is the end of that interval.
75
+ */
76
+ dt: number;
77
+ /** Milliseconds this voice has been playing, rate applied. */
78
+ elapsed: number;
79
+ /** Which pass this is, 0 first. */
80
+ pass: number;
81
+ /** This voice's weight for this subject this frame, after fades, signals and any locus. */
82
+ weight: number;
83
+ /** This subject's state, when the patch declares one. */
84
+ state: S;
85
+ /** Anything the host adds for its own patches. */
86
+ host: unknown;
87
+ /**
88
+ * The state `owner` keeps for this voice and this subject, made by `init` on first ask. The mix
89
+ * holds it, so a read at another time can copy it instead of moving it. A stateful signal keeps
90
+ * its state here and nowhere else.
91
+ */
92
+ keep<K>(owner: object, init: () => K): K;
93
+ /**
94
+ * Reports an event at `timestamp`, for this voice and this subject. The mix queues it until the
95
+ * host drains it, and never calls back. Meant for `step`, where under `stepMs` the timestamp is
96
+ * the interval the event happened in rather than the frame that sampled it.
97
+ */
98
+ send(event: unknown): void;
99
+ }
100
+ /**
101
+ * One event a patch sent, as `drain` hands it back.
102
+ *
103
+ * @category state
104
+ */
105
+ export interface Sent<I, E = unknown> {
106
+ /** The mix clock when it was sent. */
107
+ timestamp: number;
108
+ subject: I;
109
+ /** The id of the voice whose patch sent it, as its handle reports. */
110
+ voice: number;
111
+ /** That voice's tags. */
112
+ tags: readonly string[];
113
+ event: E;
114
+ }
115
+ /**
116
+ * A pure function of phase and a subject that returns a delta. Optionally stateful.
117
+ *
118
+ * @category patch
119
+ */
120
+ export interface Patch<I, O, S = void> {
121
+ /** Which authoring form built it. An engine declares which forms it runs. */
122
+ readonly form: 'fn' | 'keys';
123
+ /** Milliseconds one pass lasts. 0 is aperiodic: phase and pass stay 0. */
124
+ readonly period: number;
125
+ /** The channels this patch contributes to. Every key `at` sets, and no others. */
126
+ readonly writes: readonly (keyof O)[];
127
+ /**
128
+ * The channels this patch was written against. `cue` refuses a mix whose channel of the same name
129
+ * is of another kind, so a patch published in one package cannot fold by another's arithmetic.
130
+ */
131
+ readonly kit?: Partial<Kit<O>>;
132
+ /** The fields of `setting.host` this patch reads. `cue` refuses a mix whose host lacks one. */
133
+ readonly reads?: readonly string[];
134
+ /** `phase` is 0..1 across one period, wrapping. */
135
+ at(phase: number, subject: I, setting: Setting<S>): Partial<O>;
136
+ /** Per-subject state, created on the first frame this patch sees a subject. */
137
+ state?(subject: I): S;
138
+ /**
139
+ * Advances state once per subject per sampled frame, by the whole gap since it last advanced; or,
140
+ * where the mix sets `stepMs`, once per whole interval of that length.
141
+ */
142
+ step?(state: S, dt: number, subject: I, setting: Setting<S>): void;
143
+ /** Present when the patch was authored as keyframes, so an engine that reads data can. */
144
+ readonly keys?: readonly Keyframe<O>[];
145
+ }
146
+ /**
147
+ * A 0..1 scalar resolved per subject per frame from something outside the clock. `input` is set
148
+ * when it reads something the clock does not drive, such as a value the host writes; a signal
149
+ * built on one inherits it.
150
+ *
151
+ * @category signal
152
+ */
153
+ export type Signal<I> = ((subject: I, setting: Setting) => number) & {
154
+ readonly input?: boolean;
155
+ };
156
+ /**
157
+ * A voice's own ramps in and out, in ms, and the curve both take.
158
+ *
159
+ * @category blending
160
+ */
161
+ export interface FadeSpec {
162
+ in?: number;
163
+ out?: number;
164
+ ease?: Easing;
165
+ }
166
+ /**
167
+ * What `cue` takes: a patch, and the clock, weight and reach it plays with.
168
+ *
169
+ * @category voice
170
+ */
171
+ export interface VoiceSpec<I, O> {
172
+ patch: Patch<I, O, unknown>;
173
+ /**
174
+ * Which subjects this voice reaches. Default: all of them. The predicate is fixed at `cue`; it
175
+ * runs per subject the first time the mix sees that subject, and the answer is kept.
176
+ */
177
+ target?: (subject: I) => boolean;
178
+ /**
179
+ * When the voice starts, in ms on the host's clock, the one it passes `sync`: a rAF timestamp and
180
+ * `performance.now()` share an origin, `Date.now()` does not. Default: the next sync.
181
+ */
182
+ start?: number;
183
+ /** Playback rate. 1 is real time; 0 freezes. */
184
+ rate?: number;
185
+ /**
186
+ * true loops for good, false plays one pass, n plays n passes. A finite loop leaves when its
187
+ * passes are done for the latest-staggered subject it has seen — over `fade.out` where one is
188
+ * set, at once where none is.
189
+ */
190
+ loop?: boolean | number;
191
+ /**
192
+ * Per-subject delay in voice ms, klieg's stagger grammar reduced to the one thing the mix needs.
193
+ * Like `target`, it runs once per subject on first sight, and the answer is kept.
194
+ */
195
+ stagger?: (subject: I) => number;
196
+ /** Steady weight, or a signal read per subject per frame. Default 1. */
197
+ weight?: number | Signal<I>;
198
+ /** Ramp in and out, ms. Out applies on `fade()` and when a finite loop ends. */
199
+ fade?: FadeSpec;
200
+ /** Voices sharing a locus are alternatives: the mix folds them through each channel's `lerp`. */
201
+ locus?: string;
202
+ /** A `keys` voice whose first stop reads wherever this subject is now. */
203
+ from?: 'current';
204
+ /** Words a source attaches to this voice, so its events can be drained as a set. */
205
+ tags?: readonly string[];
206
+ }
207
+ /**
208
+ * How a voice leaves when it is faded.
209
+ *
210
+ * @category blending
211
+ */
212
+ export interface FadeOptions {
213
+ /** The ramp, ms. Defaults to the voice's own `fade.out`. */
214
+ over?: number;
215
+ /** Leave per subject at the first frame that subject's contribution is at rest. */
216
+ at?: 'rest';
217
+ /** Ms after which the voice leaves whether or not it rested. */
218
+ deadline?: number;
219
+ }
220
+ /**
221
+ * The live controls on one voice, returned by `cue`.
222
+ *
223
+ * @category voice
224
+ */
225
+ export interface Handle<I = unknown> {
226
+ readonly id: number;
227
+ readonly state: 'pending' | 'live' | 'fading' | 'done';
228
+ /** Live. Writes land on the next sync. */
229
+ weight: number;
230
+ rate: number;
231
+ /**
232
+ * Moves this voice's clock, forward or back. Phase is computed from the reading rather than
233
+ * accumulated into, so state is left where it is and never run forward to meet the new position.
234
+ */
235
+ seek(elapsed: number): void;
236
+ fade(opts?: FadeOptions): void;
237
+ /**
238
+ * The weight this voice gave `subject` the last frame that subject was probed: after its fades
239
+ * and its weight signal, before a locus folds it with its alternatives. 0 for a subject it does
240
+ * not reach, has not started on, has left at rest, or has never been probed for; 0 once done.
241
+ * Costs nothing until it is asked.
242
+ */
243
+ weightOf(subject: I): number;
244
+ /** Resolves when the voice has been removed from the mix, however that happened. */
245
+ readonly done: Promise<void>;
246
+ }
247
+ /**
248
+ * How a mix is built: its engine, reduced motion, the host, and its limits.
249
+ *
250
+ * @category mix
251
+ */
252
+ export interface MixOptions {
253
+ engine?: Engine;
254
+ /** Reduced motion: fades and warm-ups snap, `dt` reads Infinity. */
255
+ reduce?: boolean | (() => boolean);
256
+ /** Merged into every `setting.host`. */
257
+ host?: unknown;
258
+ /**
259
+ * The band a rest-less channel's influence switches on and off across. It has to clear the frame
260
+ * noise in a real signal; these are placeholders until the klieg port measures one.
261
+ */
262
+ band?: {
263
+ on: number;
264
+ off: number;
265
+ };
266
+ /**
267
+ * The most `dt` a `step` or signal is handed, however long the gap. Off by default, since a
268
+ * patch with a closed form wants the whole gap; an integrator wants this set.
269
+ */
270
+ maxDt?: number;
271
+ /**
272
+ * Run every `step` at this fixed interval, ms, instead of once by the frame's gap, so stateful
273
+ * patches play the same at any frame rate. Intervals count from when a subject's delay ran out,
274
+ * the remainder carrying to the next sample; `maxDt` then caps how many one sample may run.
275
+ * Signals and `at` still see the frame. Off by default.
276
+ */
277
+ stepMs?: number;
278
+ }
279
+ /**
280
+ * The live voices over one kit, folded into one pose per subject per frame.
281
+ *
282
+ * @category mix
283
+ */
284
+ export interface Mix<I, O> {
285
+ /** Cues a voice. Throws when the engine cannot run the patch's form, or the kit lacks a channel. */
286
+ cue(spec: VoiceSpec<I, O>): Handle<I>;
287
+ /** N voices whose weights split one signal, cued into one locus so they fold as alternatives. */
288
+ blend(patches: readonly Patch<I, O, unknown>[], by: Signal<I>, spec?: Omit<VoiceSpec<I, O>, 'patch' | 'weight' | 'locus'>): Handle<I>[];
289
+ /** The host reports the clock, once a frame. Nothing advances at the call. */
290
+ sync(timestamp: number): void;
291
+ /**
292
+ * The time between the last sync and the next one is not to count: a host calls it when a
293
+ * hidden tab comes back. Every voice, fade and subject resumes where it was left.
294
+ */
295
+ rebase(): void;
296
+ /** The merged pose for one subject at the synced frame. */
297
+ probe(subject: I, out?: O): O;
298
+ /** Every channel at rest for this subject this frame, so a host can skip the write. */
299
+ atRest(subject: I): boolean;
300
+ /** Anything still contributing, fading, or pending. */
301
+ readonly live: boolean;
302
+ /** Fades every voice out: over `over` when given, over each voice's own `fade.out` otherwise. */
303
+ mute(opts?: {
304
+ over?: number;
305
+ }): void;
306
+ /** Forgets per-subject state. */
307
+ drop(subject: I): void;
308
+ /**
309
+ * Every event patches have sent since the last drain, earliest first, in the order they were sent
310
+ * where two share a timestamp. A subject's events are made while it catches up, which is when it is
311
+ * probed, so one nobody probes has sent nothing yet: promptness is the host's, by probing. Under
312
+ * `stepMs` each carries the end of the interval it happened in, so what is sent and when does not
313
+ * depend on how the host spaces its probes. Time `rebase` took out sends nothing. Given a tag, it
314
+ * takes only the events of voices carrying it and leaves the rest for whoever drains them.
315
+ */
316
+ drain<E = unknown>(tag?: string): Sent<I, E>[];
317
+ }
318
+ /**
319
+ * The implementation behind a mix: which patch forms it runs, and how it makes one.
320
+ *
321
+ * @category engine
322
+ */
323
+ export interface Engine {
324
+ readonly name: string;
325
+ /** Which patch forms this engine can run. A voice it cannot run is refused at `cue`, by name. */
326
+ readonly runs: ReadonlySet<'fn' | 'keys'>;
327
+ create<I, O>(kit: Kit<O>, opts: MixOptions): Mix<I, O>;
328
+ }
package/dist/types.js ADDED
@@ -0,0 +1 @@
1
+ export {};