@msb235/blits 0.3.0 → 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/CHANGELOG.md +52 -0
- package/README.md +2 -2
- package/dist/lanes.d.ts +149 -3
- package/dist/lanes.js +1031 -109
- package/dist/mixer.d.ts +29 -2
- package/dist/mixer.js +588 -82
- package/dist/motion.d.ts +46 -6
- package/dist/motion.js +173 -65
- package/dist/types.d.ts +46 -10
- package/docs/schema.html +37 -10
- package/package.json +1 -1
package/dist/motion.d.ts
CHANGED
|
@@ -33,6 +33,8 @@ interface Change {
|
|
|
33
33
|
}
|
|
34
34
|
/** A motion patch's `frame` while no mix plays it: no subject has a latest frame. */
|
|
35
35
|
export declare const noFrame: () => number;
|
|
36
|
+
export declare const noRevive: () => void;
|
|
37
|
+
export declare const noTouch: () => void;
|
|
36
38
|
/**
|
|
37
39
|
* A patch whose motion is a closed form per subject, so it lands in the same place at any frame
|
|
38
40
|
* rate, and whose changes mid-flight start from where the subject is and how fast it moves.
|
|
@@ -64,14 +66,16 @@ interface Shape<I> {
|
|
|
64
66
|
scalar: (subject: I) => boolean;
|
|
65
67
|
/** A tween's easing, which its law cannot hold; undefined, but present, on every other shape. */
|
|
66
68
|
ease: Curve | undefined;
|
|
69
|
+
/** A tween's seconds for a stretch the subject starts now; undefined, but present, on the rest. */
|
|
70
|
+
ms: ((subject: I) => number) | undefined;
|
|
67
71
|
}
|
|
68
72
|
/**
|
|
69
73
|
* A motion patch's subjects: the stretch each is playing, in one flat array by a number the patch
|
|
70
74
|
* gives it, with earlier stretches a read back may reach and changes not yet applied beside them.
|
|
71
75
|
* The one copy, read by `at` and by a mix's lane alike.
|
|
72
76
|
*
|
|
73
|
-
* After the law, each subject's run of `stride` numbers is its release time, its flags,
|
|
74
|
-
* `v0` and `to` per axis. A live sample reads this one buffer and nothing else the patch owns: at
|
|
77
|
+
* After the law, each subject's run of `stride` numbers is its release time, its flags, a tween's
|
|
78
|
+
* seconds for the stretch, then `x0`, `v0` and `to` per axis. A live sample reads this one buffer and nothing else the patch owns: at
|
|
75
79
|
* 10k springs of one subject each, a buffer per field, a map lookup per sample and the law in an
|
|
76
80
|
* object of its own took a call from 130 ns to 490.
|
|
77
81
|
*/
|
|
@@ -98,11 +102,31 @@ export declare class Motions<I> {
|
|
|
98
102
|
* cues the patch, and put back when its voice retires.
|
|
99
103
|
*/
|
|
100
104
|
frame: (subject: I) => number;
|
|
105
|
+
/**
|
|
106
|
+
* Brings a subject its voice faded out back to it, so a change is not made for nothing. Set by
|
|
107
|
+
* the mix that cues the patch, and put back when its voice retires.
|
|
108
|
+
*/
|
|
109
|
+
revive: (subject: I) => void;
|
|
101
110
|
constructor(shape: Shape<I>);
|
|
102
111
|
/** The patch's number for `subject`, made with its first stretch on first ask. */
|
|
103
112
|
slot(subject: I): number;
|
|
104
113
|
/** Forgets a subject, so a later ask starts it afresh from `from`. */
|
|
105
114
|
release(subject: I): void;
|
|
115
|
+
/**
|
|
116
|
+
* Called with a subject's number whenever its stretch or what is pending for it changes, so a
|
|
117
|
+
* mix keeping a copy of it knows to read it again. Set by the mix, put back when its voice retires.
|
|
118
|
+
*/
|
|
119
|
+
touched: (s: number) => void;
|
|
120
|
+
/** The easing a tween's stretches follow; undefined for every other shape. */
|
|
121
|
+
get ease(): Curve | undefined;
|
|
122
|
+
/** A copy of the patch's law, `[form, settle, k1, k2, k3]`. */
|
|
123
|
+
law(): Float64Array;
|
|
124
|
+
/**
|
|
125
|
+
* Copies subject `s`'s current stretch into `out` from `o` as its release time, its seconds, then
|
|
126
|
+
* `x0`, `v0` and `to` per axis; false, copying nothing, while it has a change pending or an
|
|
127
|
+
* earlier stretch kept, which only `sample` handles.
|
|
128
|
+
*/
|
|
129
|
+
stretchInto(s: number, out: Float64Array, o: number): boolean;
|
|
106
130
|
/** Whether subject `s`'s value is a number rather than an array. */
|
|
107
131
|
scalar(s: number): boolean;
|
|
108
132
|
/** The value `at` hands the mix: the first axis for a number, a fresh array otherwise. */
|
|
@@ -113,6 +137,16 @@ export declare class Motions<I> {
|
|
|
113
137
|
* a projection's read applies the timed ones to a copy and commits nothing.
|
|
114
138
|
*/
|
|
115
139
|
sample(s: number, t: number, xo: Float64Array, vo: Float64Array): void;
|
|
140
|
+
/**
|
|
141
|
+
* A live `sample` at `t` for a subject with nothing pending and no earlier stretch kept, true
|
|
142
|
+
* then; false, writing nothing, for any other, which `quiet` and `sample` take instead.
|
|
143
|
+
*/
|
|
144
|
+
sampleBare(s: number, t: number, xo: Float64Array, vo: Float64Array): boolean;
|
|
145
|
+
/**
|
|
146
|
+
* Whether every subject has landed on its target at its voice time at the latest frame, as a
|
|
147
|
+
* sample there would find; one with a change waiting has not. Commits nothing.
|
|
148
|
+
*/
|
|
149
|
+
landed(): boolean;
|
|
116
150
|
/**
|
|
117
151
|
* Whether a live `sample` at `t` would leave the subject's state as it is: nothing to stamp or
|
|
118
152
|
* commit by then, and nothing older than `reading.horizon` to let go of.
|
|
@@ -151,9 +185,15 @@ export declare class Motions<I> {
|
|
|
151
185
|
private prune;
|
|
152
186
|
private evaluateSegment;
|
|
153
187
|
private evaluate;
|
|
154
|
-
/** A tween's stretch, `dt` seconds after release, kept out of `evaluate` so a spring's stays small. */
|
|
155
|
-
private eased;
|
|
156
188
|
}
|
|
189
|
+
/**
|
|
190
|
+
* A stretch `t` voice ms into it, released at `at` from `x0` moving at `v0` toward `to` (each read
|
|
191
|
+
* from its offset), into `xo` and `vo`: the one copy of the closed forms, which `law` (a patch's
|
|
192
|
+
* `[form, settle, k1, k2, k3]`, the head of a patch's buffer or a copy of it) picks between. A tween reads `ease` and
|
|
193
|
+
* `ms`, the stretch's length, dividing in milliseconds so a boundary lands exactly; every other
|
|
194
|
+
* form ignores them.
|
|
195
|
+
*/
|
|
196
|
+
export declare function closed(law: Float64Array, ease: Curve | undefined, n: number, at: number, ms: number, x0: ArrayLike<number>, x: number, v0: ArrayLike<number>, v: number, to: ArrayLike<number>, g: number, t: number, xo: Float64Array, vo: Float64Array): boolean;
|
|
157
197
|
/** The state behind a `'motion'` patch, for an engine that reads it directly. */
|
|
158
198
|
export declare function motionOf<I>(p: object): Motions<I> | undefined;
|
|
159
199
|
interface Common<I, V extends Value> {
|
|
@@ -217,8 +257,8 @@ export declare function tween<I, O, V extends Value = number>(writes: keyof O, o
|
|
|
217
257
|
from: PerSubject<I, V>;
|
|
218
258
|
/** Where each subject ends. */
|
|
219
259
|
to: PerSubject<I, V>;
|
|
220
|
-
/** How long a stretch takes, ms. */
|
|
221
|
-
ms: number
|
|
260
|
+
/** How long a stretch takes, ms: asked per subject each time one of its stretches starts. */
|
|
261
|
+
ms: PerSubject<I, number>;
|
|
222
262
|
/** The curve from `from` to `to`. Default `'ease'`, as CSS's. */
|
|
223
263
|
ease?: Easing;
|
|
224
264
|
}): Omit<Moving<I, O, V>, 'push'> & {
|
package/dist/motion.js
CHANGED
|
@@ -15,13 +15,19 @@ const EASED = 4;
|
|
|
15
15
|
const HEAD = 5;
|
|
16
16
|
/** A motion patch's `frame` while no mix plays it: no subject has a latest frame. */
|
|
17
17
|
export const noFrame = () => Number.NaN;
|
|
18
|
+
export const noRevive = () => { };
|
|
19
|
+
export const noTouch = () => { };
|
|
18
20
|
const solved = { y: 0, dy: 0 };
|
|
19
21
|
/** Where every patch's `sample` leaves a value on its way to the mix; none outlives its call. */
|
|
20
22
|
let shared = { xs: new Float64Array(4), vs: new Float64Array(4) };
|
|
21
|
-
/**
|
|
23
|
+
/**
|
|
24
|
+
* A subject's flags: its value is a number; it has changes pending; it has older stretches; the
|
|
25
|
+
* stretch it is playing was found landed, which holds until another replaces it.
|
|
26
|
+
*/
|
|
22
27
|
const SCALAR = 1;
|
|
23
28
|
const PENDING = 2;
|
|
24
29
|
const OLDER = 4;
|
|
30
|
+
const LANDED = 8;
|
|
25
31
|
/**
|
|
26
32
|
* The terms of a stretch that depend only on how long since its release, the same on every axis, so
|
|
27
33
|
* a sample works them out once rather than once per axis.
|
|
@@ -35,7 +41,7 @@ const SPAN = 1e-4;
|
|
|
35
41
|
*/
|
|
36
42
|
let slope = false;
|
|
37
43
|
/** The last tween sample's time, curve and length, and the share of the way it had left to go. */
|
|
38
|
-
const memo = {
|
|
44
|
+
const memo = { u: Number.NaN, ease: undefined, left: 0 };
|
|
39
45
|
/** `t` seconds after release, the terms every axis shares, into `timed`. */
|
|
40
46
|
function prepare(law, t) {
|
|
41
47
|
switch (law[0]) {
|
|
@@ -118,8 +124,8 @@ const per = (p, subject) => typeof p === 'function' ? p(subject) : p;
|
|
|
118
124
|
* gives it, with earlier stretches a read back may reach and changes not yet applied beside them.
|
|
119
125
|
* The one copy, read by `at` and by a mix's lane alike.
|
|
120
126
|
*
|
|
121
|
-
* After the law, each subject's run of `stride` numbers is its release time, its flags,
|
|
122
|
-
* `v0` and `to` per axis. A live sample reads this one buffer and nothing else the patch owns: at
|
|
127
|
+
* After the law, each subject's run of `stride` numbers is its release time, its flags, a tween's
|
|
128
|
+
* seconds for the stretch, then `x0`, `v0` and `to` per axis. A live sample reads this one buffer and nothing else the patch owns: at
|
|
123
129
|
* 10k springs of one subject each, a buffer per field, a map lookup per sample and the law in an
|
|
124
130
|
* object of its own took a call from 130 ns to 490.
|
|
125
131
|
*/
|
|
@@ -146,6 +152,11 @@ export class Motions {
|
|
|
146
152
|
* cues the patch, and put back when its voice retires.
|
|
147
153
|
*/
|
|
148
154
|
frame = noFrame;
|
|
155
|
+
/**
|
|
156
|
+
* Brings a subject its voice faded out back to it, so a change is not made for nothing. Set by
|
|
157
|
+
* the mix that cues the patch, and put back when its voice retires.
|
|
158
|
+
*/
|
|
159
|
+
revive = noRevive;
|
|
149
160
|
constructor(shape) {
|
|
150
161
|
this.shape = shape;
|
|
151
162
|
this.runs = Float64Array.from(shape.law);
|
|
@@ -163,7 +174,7 @@ export class Motions {
|
|
|
163
174
|
this.check(a, n);
|
|
164
175
|
if (this.n < 0) {
|
|
165
176
|
this.n = n;
|
|
166
|
-
this.stride =
|
|
177
|
+
this.stride = 3 + 3 * n;
|
|
167
178
|
if (shared.xs.length < n)
|
|
168
179
|
shared = { xs: new Float64Array(n), vs: new Float64Array(n) };
|
|
169
180
|
this.xs = shared.xs;
|
|
@@ -173,7 +184,7 @@ export class Motions {
|
|
|
173
184
|
this.slots.set(subject, s);
|
|
174
185
|
this.grow(s + 1);
|
|
175
186
|
this.runs[this.base(s) + 1] = this.shape.scalar(subject) ? SCALAR : 0;
|
|
176
|
-
this.write(s, { at: 0, x0: x, v0: v, to });
|
|
187
|
+
this.write(s, { at: 0, ms: this.shape.ms?.(subject) ?? 0, x0: x, v0: v, to });
|
|
177
188
|
return s;
|
|
178
189
|
}
|
|
179
190
|
/** Forgets a subject, so a later ask starts it afresh from `from`. */
|
|
@@ -184,6 +195,36 @@ export class Motions {
|
|
|
184
195
|
this.slots.delete(subject);
|
|
185
196
|
this.numbers.release(s);
|
|
186
197
|
}
|
|
198
|
+
/**
|
|
199
|
+
* Called with a subject's number whenever its stretch or what is pending for it changes, so a
|
|
200
|
+
* mix keeping a copy of it knows to read it again. Set by the mix, put back when its voice retires.
|
|
201
|
+
*/
|
|
202
|
+
touched = noTouch;
|
|
203
|
+
/** The easing a tween's stretches follow; undefined for every other shape. */
|
|
204
|
+
get ease() {
|
|
205
|
+
return this.shape.ease;
|
|
206
|
+
}
|
|
207
|
+
/** A copy of the patch's law, `[form, settle, k1, k2, k3]`. */
|
|
208
|
+
law() {
|
|
209
|
+
return this.runs.slice(0, HEAD);
|
|
210
|
+
}
|
|
211
|
+
/**
|
|
212
|
+
* Copies subject `s`'s current stretch into `out` from `o` as its release time, its seconds, then
|
|
213
|
+
* `x0`, `v0` and `to` per axis; false, copying nothing, while it has a change pending or an
|
|
214
|
+
* earlier stretch kept, which only `sample` handles.
|
|
215
|
+
*/
|
|
216
|
+
stretchInto(s, out, o) {
|
|
217
|
+
const runs = this.runs;
|
|
218
|
+
const b = this.base(s);
|
|
219
|
+
if ((runs[b + 1] & (PENDING | OLDER)) !== 0)
|
|
220
|
+
return false;
|
|
221
|
+
out[o] = runs[b];
|
|
222
|
+
out[o + 1] = runs[b + 2];
|
|
223
|
+
const n3 = 3 * this.n;
|
|
224
|
+
for (let i = 0; i < n3; i++)
|
|
225
|
+
out[o + 2 + i] = runs[b + 3 + i];
|
|
226
|
+
return true;
|
|
227
|
+
}
|
|
187
228
|
/** Whether subject `s`'s value is a number rather than an array. */
|
|
188
229
|
scalar(s) {
|
|
189
230
|
return (this.runs[this.base(s) + 1] & SCALAR) !== 0;
|
|
@@ -215,13 +256,57 @@ export class Motions {
|
|
|
215
256
|
if (t < at && runs[b + 1] & OLDER)
|
|
216
257
|
this.evaluateSegment(this.playing(s, t), t, xo, vo);
|
|
217
258
|
else {
|
|
218
|
-
const x = b +
|
|
219
|
-
|
|
259
|
+
const x = b + 3;
|
|
260
|
+
const ms = runs[b + 2];
|
|
261
|
+
this.evaluate(at, ms, runs, x, runs, x + this.n, runs, x + 2 * this.n, t, xo, vo);
|
|
220
262
|
}
|
|
221
263
|
return;
|
|
222
264
|
}
|
|
223
265
|
this.peek(s, t, xo, vo);
|
|
224
266
|
}
|
|
267
|
+
/**
|
|
268
|
+
* A live `sample` at `t` for a subject with nothing pending and no earlier stretch kept, true
|
|
269
|
+
* then; false, writing nothing, for any other, which `quiet` and `sample` take instead.
|
|
270
|
+
*/
|
|
271
|
+
sampleBare(s, t, xo, vo) {
|
|
272
|
+
const runs = this.runs;
|
|
273
|
+
const b = this.base(s);
|
|
274
|
+
if ((runs[b + 1] & (PENDING | OLDER)) !== 0)
|
|
275
|
+
return false;
|
|
276
|
+
const x = b + 3;
|
|
277
|
+
const n = this.n;
|
|
278
|
+
this.evaluate(runs[b], runs[b + 2], runs, x, runs, x + n, runs, x + 2 * n, t, xo, vo);
|
|
279
|
+
return true;
|
|
280
|
+
}
|
|
281
|
+
/**
|
|
282
|
+
* Whether every subject has landed on its target at its voice time at the latest frame, as a
|
|
283
|
+
* sample there would find; one with a change waiting has not. Commits nothing.
|
|
284
|
+
*/
|
|
285
|
+
landed() {
|
|
286
|
+
const runs = this.runs;
|
|
287
|
+
const n = this.n;
|
|
288
|
+
for (let s = 0; s < this.numbers.size; s++) {
|
|
289
|
+
const b = this.base(s);
|
|
290
|
+
const flags = runs[b + 1];
|
|
291
|
+
if ((flags & (LANDED | PENDING)) === LANDED)
|
|
292
|
+
continue;
|
|
293
|
+
const subject = this.numbers.subject(s);
|
|
294
|
+
if (subject === absent)
|
|
295
|
+
continue;
|
|
296
|
+
if (flags & PENDING)
|
|
297
|
+
return false;
|
|
298
|
+
const at = runs[b];
|
|
299
|
+
const t = this.frame(subject);
|
|
300
|
+
if (!(t >= at))
|
|
301
|
+
return false;
|
|
302
|
+
const x = b + 3;
|
|
303
|
+
const ms = runs[b + 2];
|
|
304
|
+
if (!this.evaluate(at, ms, runs, x, runs, x + n, runs, x + 2 * n, t, this.xs, this.vs))
|
|
305
|
+
return false;
|
|
306
|
+
runs[b + 1] = flags | LANDED;
|
|
307
|
+
}
|
|
308
|
+
return true;
|
|
309
|
+
}
|
|
225
310
|
/**
|
|
226
311
|
* Whether a live `sample` at `t` would leave the subject's state as it is: nothing to stamp or
|
|
227
312
|
* commit by then, and nothing older than `reading.horizon` to let go of.
|
|
@@ -268,6 +353,7 @@ export class Motions {
|
|
|
268
353
|
* The queue keeps the changes with a time in time order, ahead of any still waiting for one.
|
|
269
354
|
*/
|
|
270
355
|
change(subject, c) {
|
|
356
|
+
this.revive(subject);
|
|
271
357
|
const s = this.slot(subject);
|
|
272
358
|
this.check(c.to, this.n);
|
|
273
359
|
this.check(c.v, this.n);
|
|
@@ -317,6 +403,7 @@ export class Motions {
|
|
|
317
403
|
throw new Error('blits: a motion patch moves every subject on the same number of axes');
|
|
318
404
|
}
|
|
319
405
|
flag(s, bit, on) {
|
|
406
|
+
this.touched(s);
|
|
320
407
|
const i = this.base(s) + 1;
|
|
321
408
|
const flags = this.runs[i];
|
|
322
409
|
this.runs[i] = on ? flags | bit : flags & ~bit;
|
|
@@ -341,19 +428,23 @@ export class Motions {
|
|
|
341
428
|
write(s, seg) {
|
|
342
429
|
const n = this.n;
|
|
343
430
|
const b = this.base(s);
|
|
431
|
+
this.touched(s);
|
|
344
432
|
this.runs[b] = seg.at;
|
|
433
|
+
this.runs[b + 1] = this.runs[b + 1] & ~LANDED;
|
|
434
|
+
this.runs[b + 2] = seg.ms;
|
|
345
435
|
for (let i = 0; i < n; i++) {
|
|
346
|
-
this.runs[b +
|
|
347
|
-
this.runs[b +
|
|
348
|
-
this.runs[b +
|
|
436
|
+
this.runs[b + 3 + i] = seg.x0[i];
|
|
437
|
+
this.runs[b + 3 + n + i] = seg.v0[i];
|
|
438
|
+
this.runs[b + 3 + 2 * n + i] = seg.to[i];
|
|
349
439
|
}
|
|
350
440
|
}
|
|
351
441
|
/** The stretch subject `s` is playing now, as an object. */
|
|
352
442
|
latest(s) {
|
|
353
443
|
const n = this.n;
|
|
354
|
-
const x = this.base(s) +
|
|
444
|
+
const x = this.base(s) + 3;
|
|
355
445
|
return {
|
|
356
446
|
at: this.runs[this.base(s)],
|
|
447
|
+
ms: this.runs[this.base(s) + 2],
|
|
357
448
|
x0: Array.from(this.runs.subarray(x, x + n)),
|
|
358
449
|
v0: Array.from(this.runs.subarray(x + n, x + 2 * n)),
|
|
359
450
|
to: Array.from(this.runs.subarray(x + 2 * n, x + 3 * n)),
|
|
@@ -384,8 +475,10 @@ export class Motions {
|
|
|
384
475
|
const xs = Array.from(x);
|
|
385
476
|
const vs = change.v ?? Array.from(v);
|
|
386
477
|
const subject = this.numbers.subject(s);
|
|
387
|
-
const
|
|
388
|
-
|
|
478
|
+
const known = subject === absent ? undefined : subject;
|
|
479
|
+
const to = this.shape.aim(xs, vs, change.to ?? seg.to, known);
|
|
480
|
+
const ms = subject === absent ? seg.ms : (this.shape.ms?.(known) ?? 0);
|
|
481
|
+
return { at, ms, x0: xs, v0: vs, to };
|
|
389
482
|
}
|
|
390
483
|
commit(s, t) {
|
|
391
484
|
const list = this.pending.get(s);
|
|
@@ -449,58 +542,63 @@ export class Motions {
|
|
|
449
542
|
}
|
|
450
543
|
}
|
|
451
544
|
evaluateSegment(seg, t, xo, vo) {
|
|
452
|
-
this.evaluate(seg.at, seg.x0, 0, seg.v0, 0, seg.to, 0, t, xo, vo);
|
|
545
|
+
this.evaluate(seg.at, seg.ms, seg.x0, 0, seg.v0, 0, seg.to, 0, t, xo, vo);
|
|
453
546
|
}
|
|
454
|
-
evaluate(at, x0, x, v0, v, to, g, t, xo, vo) {
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
547
|
+
evaluate(at, ms, x0, x, v0, v, to, g, t, xo, vo) {
|
|
548
|
+
return closed(this.runs, this.shape.ease, this.n, at, ms, x0, x, v0, v, to, g, t, xo, vo);
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
/**
|
|
552
|
+
* A stretch `t` voice ms into it, released at `at` from `x0` moving at `v0` toward `to` (each read
|
|
553
|
+
* from its offset), into `xo` and `vo`: the one copy of the closed forms, which `law` (a patch's
|
|
554
|
+
* `[form, settle, k1, k2, k3]`, the head of a patch's buffer or a copy of it) picks between. A tween reads `ease` and
|
|
555
|
+
* `ms`, the stretch's length, dividing in milliseconds so a boundary lands exactly; every other
|
|
556
|
+
* form ignores them.
|
|
557
|
+
*/
|
|
558
|
+
export function closed(law, ease, n, at, ms, x0, x, v0, v, to, g, t, xo, vo) {
|
|
559
|
+
if (law[0] === EASED)
|
|
560
|
+
return eased(ease, n, Math.max(0, t - at) / ms, ms, x0, x, to, g, xo, vo);
|
|
561
|
+
const dt = Math.max(0, t - at) / 1000;
|
|
562
|
+
const settle = law[1];
|
|
563
|
+
let still = settle > 0;
|
|
564
|
+
prepare(law, dt);
|
|
565
|
+
for (let i = 0; i < n; i++) {
|
|
566
|
+
const goal = to[g + i];
|
|
567
|
+
solve(law, x0[x + i] - goal, v0[v + i], dt);
|
|
568
|
+
xo[i] = goal + solved.y;
|
|
569
|
+
vo[i] = solved.dy;
|
|
570
|
+
if (Math.abs(solved.y) > settle || Math.abs(solved.dy) > settle)
|
|
571
|
+
still = false;
|
|
572
|
+
}
|
|
573
|
+
if (still)
|
|
465
574
|
for (let i = 0; i < n; i++) {
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
xo[i] = goal + solved.y;
|
|
469
|
-
vo[i] = solved.dy;
|
|
470
|
-
if (Math.abs(solved.y) > settle || Math.abs(solved.dy) > settle)
|
|
471
|
-
still = false;
|
|
575
|
+
xo[i] = to[g + i];
|
|
576
|
+
vo[i] = 0;
|
|
472
577
|
}
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
578
|
+
return still;
|
|
579
|
+
}
|
|
580
|
+
/** A tween's stretch, `dt` seconds after release, kept out of `closed` so a spring's stays small. */
|
|
581
|
+
function eased(ease, n, u, ms, x0, x, to, g, xo, vo) {
|
|
582
|
+
// Subjects released together share `u`, so a frame reads a bezier once, not once each.
|
|
583
|
+
if (u !== memo.u || ease !== memo.ease) {
|
|
584
|
+
memo.u = u;
|
|
585
|
+
memo.ease = ease;
|
|
586
|
+
memo.left = u >= 1 ? 0 : 1 - ease(u);
|
|
478
587
|
}
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
const
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
let rate = 0;
|
|
492
|
-
if (slope && u < 1) {
|
|
493
|
-
const lo = Math.max(0, u - SPAN);
|
|
494
|
-
const hi = Math.min(1, u + SPAN);
|
|
495
|
-
rate = (ease(hi) - ease(lo)) / (hi - lo) / secs;
|
|
496
|
-
}
|
|
497
|
-
for (let i = 0; i < this.n; i++) {
|
|
498
|
-
const goal = to[g + i];
|
|
499
|
-
const gap = x0[x + i] - goal;
|
|
500
|
-
xo[i] = left === 0 ? goal : goal + gap * left;
|
|
501
|
-
vo[i] = -gap * rate;
|
|
502
|
-
}
|
|
588
|
+
const left = memo.left;
|
|
589
|
+
let rate = 0;
|
|
590
|
+
if (slope && u < 1) {
|
|
591
|
+
const lo = Math.max(0, u - SPAN);
|
|
592
|
+
const hi = Math.min(1, u + SPAN);
|
|
593
|
+
rate = ((ease(hi) - ease(lo)) / (hi - lo)) * (1000 / ms);
|
|
594
|
+
}
|
|
595
|
+
for (let i = 0; i < n; i++) {
|
|
596
|
+
const goal = to[g + i];
|
|
597
|
+
const gap = x0[x + i] - goal;
|
|
598
|
+
xo[i] = left === 0 ? goal : goal + gap * left;
|
|
599
|
+
vo[i] = -gap * rate;
|
|
503
600
|
}
|
|
601
|
+
return left === 0;
|
|
504
602
|
}
|
|
505
603
|
const states = new WeakMap();
|
|
506
604
|
/** The state behind a `'motion'` patch, for an engine that reads it directly. */
|
|
@@ -562,6 +660,7 @@ export function spring(writes, opts) {
|
|
|
562
660
|
law,
|
|
563
661
|
scalar: (s) => typeof per(opts.to, s) === 'number',
|
|
564
662
|
ease: undefined,
|
|
663
|
+
ms: undefined,
|
|
565
664
|
});
|
|
566
665
|
return Object.assign(patch, {
|
|
567
666
|
push,
|
|
@@ -588,6 +687,7 @@ export function glide(writes, opts) {
|
|
|
588
687
|
law: [COAST, settle, tau, 0, 0],
|
|
589
688
|
scalar: (s) => typeof per(opts.from, s) === 'number',
|
|
590
689
|
ease: undefined,
|
|
690
|
+
ms: undefined,
|
|
591
691
|
});
|
|
592
692
|
return Object.assign(patch, { push });
|
|
593
693
|
}
|
|
@@ -601,17 +701,25 @@ export function glide(writes, opts) {
|
|
|
601
701
|
* @category patch
|
|
602
702
|
*/
|
|
603
703
|
export function tween(writes, opts) {
|
|
604
|
-
|
|
605
|
-
|
|
704
|
+
const fixed = typeof opts.ms === 'number';
|
|
705
|
+
const ms = (subject) => {
|
|
706
|
+
const length = per(opts.ms, subject);
|
|
707
|
+
if (!(length > 0))
|
|
708
|
+
throw new Error('blits: a tween takes a positive ms');
|
|
709
|
+
return length;
|
|
710
|
+
};
|
|
711
|
+
if (fixed)
|
|
712
|
+
ms(undefined);
|
|
606
713
|
const ease = opts.ease ?? 'ease';
|
|
607
714
|
const target = (subject) => axes(per(opts.to, subject));
|
|
608
|
-
const { patch, state } = moving(writes, { kind: 'tween', ms: opts.ms, ease }, {
|
|
715
|
+
const { patch, state } = moving(writes, { kind: 'tween', ms: fixed ? opts.ms : undefined, ease }, {
|
|
609
716
|
from: (s) => axes(per(opts.from, s)),
|
|
610
717
|
velocity: (s) => target(s).map(() => 0),
|
|
611
718
|
aim: (_x, _v, was, s) => was ?? target(s),
|
|
612
|
-
law: [EASED, 0,
|
|
719
|
+
law: [EASED, 0, 0, 0, 0],
|
|
613
720
|
scalar: (s) => typeof per(opts.to, s) === 'number',
|
|
614
721
|
ease: curve(ease),
|
|
722
|
+
ms,
|
|
615
723
|
});
|
|
616
724
|
return Object.assign(patch, {
|
|
617
725
|
to: (subject, goal, at) => state.change(subject, { at, to: axes(goal) }),
|
package/dist/types.d.ts
CHANGED
|
@@ -144,8 +144,8 @@ export type MotionSpec = {
|
|
|
144
144
|
readonly settle: number;
|
|
145
145
|
} | {
|
|
146
146
|
readonly kind: 'tween';
|
|
147
|
-
/** How long a stretch takes. */
|
|
148
|
-
readonly ms: number;
|
|
147
|
+
/** How long a stretch takes; undefined where it varies by subject, kept on the patch then. */
|
|
148
|
+
readonly ms: number | undefined;
|
|
149
149
|
readonly ease: Easing;
|
|
150
150
|
};
|
|
151
151
|
/**
|
|
@@ -317,7 +317,7 @@ export interface VoiceSpec<I, O> {
|
|
|
317
317
|
/**
|
|
318
318
|
* true loops for good, false plays one pass, n plays n passes. A finite loop leaves when its
|
|
319
319
|
* passes are done for the latest-staggered subject it has seen — over `fade.out` where one is
|
|
320
|
-
* set, at once where none is.
|
|
320
|
+
* set, at once where none is — unless it holds after.
|
|
321
321
|
*/
|
|
322
322
|
loop?: boolean | number;
|
|
323
323
|
/**
|
|
@@ -325,6 +325,16 @@ export interface VoiceSpec<I, O> {
|
|
|
325
325
|
* Like `target`, it runs once per subject on first sight, and the answer is kept.
|
|
326
326
|
*/
|
|
327
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';
|
|
328
338
|
/** Steady weight, or a signal read per subject per frame. Default 1. */
|
|
329
339
|
weight?: number | Signal<I>;
|
|
330
340
|
/** Ramp in and out, ms. Out applies on `fade()` and when a finite loop ends. */
|
|
@@ -347,18 +357,28 @@ export interface VoiceSpec<I, O> {
|
|
|
347
357
|
anchor?: Placement;
|
|
348
358
|
}
|
|
349
359
|
/**
|
|
350
|
-
* How a voice leaves when it is faded.
|
|
360
|
+
* How a voice leaves when it is faded: the whole voice, or one subject.
|
|
351
361
|
*
|
|
352
362
|
* @category blending
|
|
353
363
|
*/
|
|
354
|
-
export
|
|
364
|
+
export type FadeOptions<I = unknown> = {
|
|
355
365
|
/** The ramp, ms. Defaults to the voice's own `fade.out`. */
|
|
356
366
|
over?: number;
|
|
357
367
|
/** Leave per subject at the first frame that subject's contribution is at rest. */
|
|
358
368
|
at?: 'rest';
|
|
359
369
|
/** Ms after which the voice leaves whether or not it rested. */
|
|
360
370
|
deadline?: number;
|
|
361
|
-
|
|
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
|
+
};
|
|
362
382
|
/**
|
|
363
383
|
* The live controls on one voice, returned by `cue`.
|
|
364
384
|
*
|
|
@@ -366,7 +386,8 @@ export interface FadeOptions {
|
|
|
366
386
|
*/
|
|
367
387
|
export interface Handle<I = unknown> {
|
|
368
388
|
readonly id: number;
|
|
369
|
-
|
|
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';
|
|
370
391
|
/** Live. Writes land on the next sync. */
|
|
371
392
|
weight: number;
|
|
372
393
|
/** Playback rate now. Setting it changes speed at once; `ramp` eases into a new one. */
|
|
@@ -382,16 +403,22 @@ export interface Handle<I = unknown> {
|
|
|
382
403
|
* accumulated into, so state is left where it is and never run forward to meet the new position.
|
|
383
404
|
*/
|
|
384
405
|
seek(elapsed: number): void;
|
|
385
|
-
fade(opts?: FadeOptions): void;
|
|
406
|
+
fade(opts?: FadeOptions<I>): void;
|
|
386
407
|
/**
|
|
387
408
|
* The weight this voice gave `subject` the last frame that subject was probed: after its fades
|
|
388
409
|
* and its weight signal, before a locus folds it with its alternatives. 0 for a subject it does
|
|
389
|
-
* not reach, has not started on, has left at rest, or has never been
|
|
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.
|
|
390
412
|
* Costs nothing until it is asked.
|
|
391
413
|
*/
|
|
392
414
|
weightOf(subject: I): number;
|
|
393
415
|
/** Resolves when the voice has been removed from the mix, however that happened. */
|
|
394
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>;
|
|
395
422
|
}
|
|
396
423
|
/**
|
|
397
424
|
* How a mix is built: its engine, reduced motion, the host, and its limits.
|
|
@@ -505,7 +532,10 @@ export interface Mix<I, O> {
|
|
|
505
532
|
* Writes each subject's pose into arrays, one per channel, subject by subject in the order given:
|
|
506
533
|
* what `probe(subject, out)` gives, without a pose object per subject. A channel of `n` numbers
|
|
507
534
|
* takes `n` places per subject, side by side; one with no value for a subject writes NaN there.
|
|
508
|
-
* Fastest while every voice runs on a lane
|
|
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.
|
|
509
539
|
*/
|
|
510
540
|
pull(subjects: Iterable<I>, into: Columns<O>): void;
|
|
511
541
|
/**
|
|
@@ -518,6 +548,12 @@ export interface Mix<I, O> {
|
|
|
518
548
|
atRest(subject: I): boolean;
|
|
519
549
|
/** Anything still contributing, fading, or pending. */
|
|
520
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;
|
|
521
557
|
/** Fades every voice out: over `over` when given, over each voice's own `fade.out` otherwise. */
|
|
522
558
|
mute(opts?: {
|
|
523
559
|
over?: number;
|