@msb235/blits 0.3.0 → 0.5.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 +314 -0
- package/README.md +3 -2
- package/dist/bare.d.ts +29 -0
- package/dist/bare.js +105 -0
- package/dist/blend.d.ts +34 -0
- package/dist/blend.js +102 -0
- package/dist/book.d.ts +53 -0
- package/dist/book.js +218 -0
- package/dist/chain.d.ts +22 -0
- package/dist/chain.js +150 -0
- package/dist/channels.d.ts +2 -0
- package/dist/channels.js +3 -1
- package/dist/clock.d.ts +50 -3
- package/dist/clock.js +102 -9
- package/dist/clone.d.ts +7 -0
- package/dist/clone.js +57 -0
- package/dist/columns.d.ts +29 -0
- package/dist/columns.js +183 -0
- package/dist/crowd.d.ts +128 -0
- package/dist/crowd.js +354 -0
- package/dist/cue.d.ts +3 -0
- package/dist/cue.js +115 -0
- package/dist/due.d.ts +12 -0
- package/dist/due.js +93 -0
- package/dist/fade.d.ts +19 -0
- package/dist/fade.js +133 -0
- package/dist/fill.d.ts +42 -0
- package/dist/fill.js +381 -0
- package/dist/fold.d.ts +51 -0
- package/dist/fold.js +475 -0
- package/dist/gather.d.ts +24 -0
- package/dist/gather.js +186 -0
- package/dist/handle.d.ts +31 -0
- package/dist/handle.js +56 -0
- package/dist/held.d.ts +14 -0
- package/dist/held.js +117 -0
- package/dist/history.d.ts +22 -0
- package/dist/history.js +149 -0
- package/dist/hosts.d.ts +16 -0
- package/dist/hosts.js +178 -0
- package/dist/index.d.ts +1 -1
- package/dist/keyrows.d.ts +36 -0
- package/dist/keyrows.js +138 -0
- package/dist/lane.d.ts +210 -0
- package/dist/lane.js +187 -0
- package/dist/lanes.d.ts +143 -122
- package/dist/lanes.js +236 -742
- package/dist/locus.d.ts +40 -0
- package/dist/locus.js +89 -0
- package/dist/marks.d.ts +12 -0
- package/dist/marks.js +80 -0
- package/dist/meet.d.ts +16 -0
- package/dist/meet.js +113 -0
- package/dist/mixer.d.ts +244 -169
- package/dist/mixer.js +321 -1793
- package/dist/motion.d.ts +104 -19
- package/dist/motion.js +448 -180
- package/dist/move.d.ts +9 -0
- package/dist/move.js +190 -0
- package/dist/named.d.ts +12 -0
- package/dist/named.js +39 -0
- package/dist/numbers.d.ts +18 -6
- package/dist/numbers.js +59 -31
- package/dist/owed.d.ts +24 -0
- package/dist/owed.js +51 -0
- package/dist/owner.d.ts +43 -0
- package/dist/owner.js +117 -0
- package/dist/pace.d.ts +40 -0
- package/dist/pace.js +113 -0
- package/dist/patch.d.ts +51 -10
- package/dist/patch.js +140 -45
- package/dist/place.d.ts +20 -0
- package/dist/place.js +196 -0
- package/dist/project.d.ts +4 -0
- package/dist/project.js +233 -0
- package/dist/pull.d.ts +5 -0
- package/dist/pull.js +175 -0
- package/dist/qualify.d.ts +31 -0
- package/dist/qualify.js +345 -0
- package/dist/relink.d.ts +25 -0
- package/dist/relink.js +59 -0
- package/dist/rows.d.ts +33 -0
- package/dist/rows.js +282 -0
- package/dist/sample.d.ts +21 -0
- package/dist/sample.js +232 -0
- package/dist/signals.d.ts +12 -9
- package/dist/signals.js +8 -5
- package/dist/store.d.ts +10 -3
- package/dist/store.js +44 -9
- package/dist/strays.d.ts +21 -0
- package/dist/strays.js +84 -0
- package/dist/tweened.d.ts +11 -0
- package/dist/tweened.js +48 -0
- package/dist/types.d.ts +261 -42
- package/dist/unreached.d.ts +5 -0
- package/dist/unreached.js +24 -0
- package/dist/voice.d.ts +240 -0
- package/dist/voice.js +322 -0
- package/dist/weigh.d.ts +26 -0
- package/dist/weigh.js +89 -0
- package/docs/schema.html +151 -46
- package/package.json +5 -2
package/dist/motion.js
CHANGED
|
@@ -2,6 +2,7 @@ import { curve } from './easing.js';
|
|
|
2
2
|
import { absent, Numbers } from './numbers.js';
|
|
3
3
|
import { reading } from './reading.js';
|
|
4
4
|
import { Store } from './store.js';
|
|
5
|
+
import { eased, progress, sloped } from './tweened.js';
|
|
5
6
|
const UNDER = 0;
|
|
6
7
|
const CRITICAL = 1;
|
|
7
8
|
const OVER = 2;
|
|
@@ -13,32 +14,47 @@ const EASED = 4;
|
|
|
13
14
|
* the buffer a sample already reads rather than in an object of their own.
|
|
14
15
|
*/
|
|
15
16
|
const HEAD = 5;
|
|
16
|
-
/** A motion patch's `frame` while no mix plays it: no subject has a latest frame. */
|
|
17
|
-
export const noFrame = () => Number.NaN;
|
|
18
17
|
const solved = { y: 0, dy: 0 };
|
|
19
18
|
/** Where every patch's `sample` leaves a value on its way to the mix; none outlives its call. */
|
|
20
19
|
let shared = { xs: new Float64Array(4), vs: new Float64Array(4) };
|
|
21
|
-
/**
|
|
20
|
+
/** What a patch's `xs` and `vs` are before its first subject sets its axes. */
|
|
21
|
+
const unsized = new Float64Array(0);
|
|
22
|
+
/**
|
|
23
|
+
* A subject's flags: its value is a number; it has changes pending; it has older stretches; the
|
|
24
|
+
* stretch it is playing was found landed, which holds until another replaces it.
|
|
25
|
+
*/
|
|
22
26
|
const SCALAR = 1;
|
|
23
27
|
const PENDING = 2;
|
|
24
28
|
const OLDER = 4;
|
|
29
|
+
const LANDED = 8;
|
|
25
30
|
/**
|
|
26
31
|
* The terms of a stretch that depend only on how long since its release, the same on every axis, so
|
|
27
32
|
* a sample works them out once rather than once per axis.
|
|
28
33
|
*/
|
|
29
34
|
const timed = { e: 0, e2: 0, cos: 0, sin: 0 };
|
|
30
|
-
/**
|
|
31
|
-
const
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
const memo = { dt: Number.NaN, ease: undefined, secs: 0, left: 0 };
|
|
35
|
+
/** The law and time `timed` was last worked out for: subjects released together share them. */
|
|
36
|
+
const prepared = {
|
|
37
|
+
form: Number.NaN,
|
|
38
|
+
k1: Number.NaN,
|
|
39
|
+
k2: Number.NaN,
|
|
40
|
+
k3: Number.NaN,
|
|
41
|
+
t: Number.NaN,
|
|
42
|
+
};
|
|
39
43
|
/** `t` seconds after release, the terms every axis shares, into `timed`. */
|
|
40
44
|
function prepare(law, t) {
|
|
41
|
-
|
|
45
|
+
const form = law[0];
|
|
46
|
+
const k1 = law[2];
|
|
47
|
+
const k2 = law[3];
|
|
48
|
+
const k3 = law[4];
|
|
49
|
+
const p = prepared;
|
|
50
|
+
if (Object.is(t, p.t) && form === p.form && k1 === p.k1 && k2 === p.k2 && k3 === p.k3)
|
|
51
|
+
return;
|
|
52
|
+
p.form = form;
|
|
53
|
+
p.k1 = k1;
|
|
54
|
+
p.k2 = k2;
|
|
55
|
+
p.k3 = k3;
|
|
56
|
+
p.t = t;
|
|
57
|
+
switch (form) {
|
|
42
58
|
case UNDER: {
|
|
43
59
|
const zeta = law[2];
|
|
44
60
|
const w0 = law[3];
|
|
@@ -112,78 +128,173 @@ function solve(law, y0, v0, t) {
|
|
|
112
128
|
}
|
|
113
129
|
}
|
|
114
130
|
const axes = (v) => (typeof v === 'number' ? [v] : [...v]);
|
|
131
|
+
/** Zero on every axis `x` has: a motion's velocity where its options give none. */
|
|
132
|
+
const still = (x) => new Array(x.length).fill(0);
|
|
115
133
|
const per = (p, subject) => typeof p === 'function' ? p(subject) : p;
|
|
134
|
+
const scalarOf = (p) => typeof p === 'function' ? undefined : typeof p === 'number';
|
|
135
|
+
const wrongKind = (channel, holdsNumber) => new Error(`blits: channel ${channel} holds ${holdsNumber ? 'a number' : 'an array'}, and this motion patch gives a subject ${holdsNumber ? 'an array' : 'a number'}`);
|
|
116
136
|
/**
|
|
117
137
|
* A motion patch's subjects: the stretch each is playing, in one flat array by a number the patch
|
|
118
138
|
* gives it, with earlier stretches a read back may reach and changes not yet applied beside them.
|
|
119
139
|
* The one copy, read by `at` and by a mix's lane alike.
|
|
120
140
|
*
|
|
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
|
|
141
|
+
* After the law, each subject's run of `stride` numbers is its release time, its flags, a tween's
|
|
142
|
+
* 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
143
|
* 10k springs of one subject each, a buffer per field, a map lookup per sample and the law in an
|
|
124
144
|
* object of its own took a call from 130 ns to 490.
|
|
125
145
|
*/
|
|
126
146
|
export class Motions {
|
|
127
147
|
shape;
|
|
128
|
-
numbers = new Numbers(
|
|
148
|
+
numbers = new Numbers(this);
|
|
149
|
+
/** Numbers by subject once a second is numbered: till then the one subject is found by `numbers`. */
|
|
129
150
|
slots = new Store();
|
|
130
151
|
/** Axes per subject; -1 until the first subject sets it. */
|
|
131
152
|
n = -1;
|
|
132
153
|
cap = 0;
|
|
133
154
|
stride = 0;
|
|
134
|
-
|
|
155
|
+
/**
|
|
156
|
+
* The law, then each subject's run; empty until the first subject, the law read from `shape`.
|
|
157
|
+
* Read outside, at `bareAt`, and written only here.
|
|
158
|
+
*/
|
|
159
|
+
runs = unsized;
|
|
135
160
|
/**
|
|
136
161
|
* Where `sample` leaves a subject's position and velocity, per axis. Shared by every patch, so
|
|
137
162
|
* read it before the next sample.
|
|
138
163
|
*/
|
|
139
|
-
xs =
|
|
140
|
-
vs =
|
|
141
|
-
pending = new Map();
|
|
142
|
-
older = new Map();
|
|
164
|
+
xs = unsized;
|
|
165
|
+
vs = unsized;
|
|
143
166
|
/**
|
|
144
|
-
*
|
|
145
|
-
*
|
|
146
|
-
*
|
|
167
|
+
* By subject number, changes not yet applied and earlier stretches a read back may reach: null
|
|
168
|
+
* until a subject first has one, since many patches are never changed. Each starts with room for
|
|
169
|
+
* one: an empty array written at 0 reserves 17, and most patches have one subject.
|
|
147
170
|
*/
|
|
148
|
-
|
|
171
|
+
pending = null;
|
|
172
|
+
older = null;
|
|
173
|
+
/**
|
|
174
|
+
* The mix playing the patch and its voice's id, which an untimed change and a `read` with no time
|
|
175
|
+
* ask for the latest frame. Set by the mix that cues the patch, and cleared when its voice retires.
|
|
176
|
+
*/
|
|
177
|
+
owner = null;
|
|
178
|
+
ownerId = -1;
|
|
179
|
+
/** Whether the channel it was cued on holds a number; undefined until it is cued. */
|
|
180
|
+
holdsNumber = undefined;
|
|
181
|
+
channel = '';
|
|
182
|
+
/**
|
|
183
|
+
* Records what the channel the patch is cued on holds, refusing a value of the other kind: at once
|
|
184
|
+
* where every subject's value is one kind, else on the first sample of a subject whose value is not.
|
|
185
|
+
*/
|
|
186
|
+
cuedOn(channel, holdsNumber) {
|
|
187
|
+
const every = this.shape.scalarEvery();
|
|
188
|
+
if (every !== undefined && every !== holdsNumber)
|
|
189
|
+
throw wrongKind(channel, holdsNumber);
|
|
190
|
+
for (let s = 0; s < this.numbers.size; s++)
|
|
191
|
+
if (this.numbers.subject(s) !== absent &&
|
|
192
|
+
((this.runs[this.base(s) + 1] & SCALAR) !== 0) !== holdsNumber)
|
|
193
|
+
throw wrongKind(channel, holdsNumber);
|
|
194
|
+
this.holdsNumber = holdsNumber;
|
|
195
|
+
this.channel = channel;
|
|
196
|
+
}
|
|
197
|
+
frame(subject) {
|
|
198
|
+
return this.owner === null ? Number.NaN : this.owner.frame(this.ownerId, subject);
|
|
199
|
+
}
|
|
149
200
|
constructor(shape) {
|
|
150
201
|
this.shape = shape;
|
|
151
|
-
this.runs = Float64Array.from(shape.law);
|
|
152
202
|
}
|
|
153
203
|
/** The patch's number for `subject`, made with its first stretch on first ask. */
|
|
154
204
|
slot(subject) {
|
|
155
|
-
const known = this.
|
|
205
|
+
const known = this.known(subject);
|
|
156
206
|
if (known !== undefined)
|
|
157
207
|
return known;
|
|
158
208
|
const x = this.shape.from(subject);
|
|
159
|
-
const v = this.shape.velocity(subject);
|
|
209
|
+
const v = this.shape.velocity(subject, x);
|
|
160
210
|
const to = this.shape.aim(x, v, null, subject);
|
|
161
211
|
const n = this.n < 0 ? x.length : this.n;
|
|
162
|
-
|
|
163
|
-
|
|
212
|
+
this.check(x, n);
|
|
213
|
+
this.check(v, n);
|
|
214
|
+
this.check(to, n);
|
|
215
|
+
const scalar = this.shape.scalar(subject);
|
|
216
|
+
if (this.holdsNumber !== undefined && scalar !== this.holdsNumber)
|
|
217
|
+
throw wrongKind(this.channel, this.holdsNumber);
|
|
164
218
|
if (this.n < 0) {
|
|
165
219
|
this.n = n;
|
|
166
|
-
this.stride =
|
|
220
|
+
this.stride = 3 + 3 * n;
|
|
167
221
|
if (shared.xs.length < n)
|
|
168
222
|
shared = { xs: new Float64Array(n), vs: new Float64Array(n) };
|
|
169
223
|
this.xs = shared.xs;
|
|
170
224
|
this.vs = shared.vs;
|
|
171
225
|
}
|
|
172
226
|
const s = this.numbers.take(subject);
|
|
173
|
-
|
|
227
|
+
if (s === 1) {
|
|
228
|
+
const first = this.numbers.subject(0);
|
|
229
|
+
if (first !== absent)
|
|
230
|
+
this.slots.set(first, 0);
|
|
231
|
+
}
|
|
232
|
+
if (this.numbers.size > 1)
|
|
233
|
+
this.slots.set(subject, s);
|
|
174
234
|
this.grow(s + 1);
|
|
175
|
-
this.runs[this.base(s) + 1] =
|
|
176
|
-
this.write(s, { at: 0, x0: x, v0: v, to });
|
|
235
|
+
this.runs[this.base(s) + 1] = scalar ? SCALAR : 0;
|
|
236
|
+
this.write(s, { at: 0, ms: this.shape.ms?.(subject) ?? 0, x0: x, v0: v, to });
|
|
177
237
|
return s;
|
|
178
238
|
}
|
|
239
|
+
/** The patch's number for `subject`, undefined where it has none. */
|
|
240
|
+
known(subject) {
|
|
241
|
+
if (this.numbers.size !== 1)
|
|
242
|
+
return this.slots.get(subject);
|
|
243
|
+
const only = this.numbers.subject(0);
|
|
244
|
+
return only !== absent && (only === subject || Object.is(only, subject)) ? 0 : undefined;
|
|
245
|
+
}
|
|
179
246
|
/** Forgets a subject, so a later ask starts it afresh from `from`. */
|
|
180
247
|
release(subject) {
|
|
181
|
-
const s = this.
|
|
248
|
+
const s = this.known(subject);
|
|
182
249
|
if (s === undefined)
|
|
183
250
|
return;
|
|
184
251
|
this.slots.delete(subject);
|
|
185
252
|
this.numbers.release(s);
|
|
186
253
|
}
|
|
254
|
+
/**
|
|
255
|
+
* Told, with `watchId`, whenever a subject's stretch or what is pending for it changes, so a mix
|
|
256
|
+
* keeping a copy of it knows to read it again. Set by the mix, cleared when its voice retires. An
|
|
257
|
+
* owner and an id, where a callback cost a closure and its context per patch.
|
|
258
|
+
*/
|
|
259
|
+
watcher = null;
|
|
260
|
+
watchId = -1;
|
|
261
|
+
/** The easing a tween's stretches follow; undefined for every other shape. */
|
|
262
|
+
get ease() {
|
|
263
|
+
return this.shape.ease;
|
|
264
|
+
}
|
|
265
|
+
/** The patch's kind and constants. */
|
|
266
|
+
spec() {
|
|
267
|
+
return this.shape.spec();
|
|
268
|
+
}
|
|
269
|
+
/** A copy of the patch's law, `[form, settle, k1, k2, k3]`. */
|
|
270
|
+
law() {
|
|
271
|
+
return this.cap === 0 ? Float64Array.from(this.shape.law) : this.runs.slice(0, HEAD);
|
|
272
|
+
}
|
|
273
|
+
/** Whether `law` holds this patch's law, number for number, as `Object.is` compares them. */
|
|
274
|
+
hasLaw(law) {
|
|
275
|
+
const own = this.cap === 0 ? this.shape.law : this.runs;
|
|
276
|
+
for (let i = 0; i < HEAD; i++)
|
|
277
|
+
if (!Object.is(law[i], own[i]))
|
|
278
|
+
return false;
|
|
279
|
+
return true;
|
|
280
|
+
}
|
|
281
|
+
/**
|
|
282
|
+
* Copies subject `s`'s current stretch into `out` from `o` as its release time, its seconds, then
|
|
283
|
+
* `x0`, `v0` and `to` per axis; false, copying nothing, while it has a change pending or an
|
|
284
|
+
* earlier stretch kept, which only `sample` handles.
|
|
285
|
+
*/
|
|
286
|
+
stretchInto(s, out, o) {
|
|
287
|
+
const runs = this.runs;
|
|
288
|
+
const b = this.base(s);
|
|
289
|
+
if ((runs[b + 1] & (PENDING | OLDER)) !== 0)
|
|
290
|
+
return false;
|
|
291
|
+
out[o] = runs[b];
|
|
292
|
+
out[o + 1] = runs[b + 2];
|
|
293
|
+
const n3 = 3 * this.n;
|
|
294
|
+
for (let i = 0; i < n3; i++)
|
|
295
|
+
out[o + 2 + i] = runs[b + 3 + i];
|
|
296
|
+
return true;
|
|
297
|
+
}
|
|
187
298
|
/** Whether subject `s`'s value is a number rather than an array. */
|
|
188
299
|
scalar(s) {
|
|
189
300
|
return (this.runs[this.base(s) + 1] & SCALAR) !== 0;
|
|
@@ -215,13 +326,65 @@ export class Motions {
|
|
|
215
326
|
if (t < at && runs[b + 1] & OLDER)
|
|
216
327
|
this.evaluateSegment(this.playing(s, t), t, xo, vo);
|
|
217
328
|
else {
|
|
218
|
-
const x = b +
|
|
219
|
-
|
|
329
|
+
const x = b + 3;
|
|
330
|
+
const ms = runs[b + 2];
|
|
331
|
+
this.evaluate(at, ms, runs, x, runs, x + this.n, runs, x + 2 * this.n, t, xo, vo);
|
|
220
332
|
}
|
|
221
333
|
return;
|
|
222
334
|
}
|
|
223
335
|
this.peek(s, t, xo, vo);
|
|
224
336
|
}
|
|
337
|
+
/**
|
|
338
|
+
* Where subject `s`'s run starts in `runs` (its release time, then flags, seconds, `x0`, `v0`
|
|
339
|
+
* and `to`) while it has nothing pending and no earlier stretch kept; -1 for any other.
|
|
340
|
+
*/
|
|
341
|
+
bareAt(s) {
|
|
342
|
+
const b = this.base(s);
|
|
343
|
+
return (this.runs[b + 1] & (PENDING | OLDER)) !== 0 ? -1 : b;
|
|
344
|
+
}
|
|
345
|
+
/**
|
|
346
|
+
* A live `sample` at `t` for a subject with nothing pending and no earlier stretch kept, true
|
|
347
|
+
* then; false, writing nothing, for any other, which `quiet` and `sample` take instead.
|
|
348
|
+
*/
|
|
349
|
+
sampleBare(s, t, xo, vo) {
|
|
350
|
+
const runs = this.runs;
|
|
351
|
+
const b = this.bareAt(s);
|
|
352
|
+
if (b < 0)
|
|
353
|
+
return false;
|
|
354
|
+
const x = b + 3;
|
|
355
|
+
const n = this.n;
|
|
356
|
+
this.evaluate(runs[b], runs[b + 2], runs, x, runs, x + n, runs, x + 2 * n, t, xo, vo);
|
|
357
|
+
return true;
|
|
358
|
+
}
|
|
359
|
+
/**
|
|
360
|
+
* Whether every subject has landed on its target at its voice time at the latest frame, as a
|
|
361
|
+
* sample there would find; one with a change waiting has not. Commits nothing.
|
|
362
|
+
*/
|
|
363
|
+
landed() {
|
|
364
|
+
const runs = this.runs;
|
|
365
|
+
const n = this.n;
|
|
366
|
+
for (let s = 0; s < this.numbers.size; s++) {
|
|
367
|
+
const b = this.base(s);
|
|
368
|
+
const flags = runs[b + 1];
|
|
369
|
+
if ((flags & (LANDED | PENDING)) === LANDED)
|
|
370
|
+
continue;
|
|
371
|
+
const subject = this.numbers.subject(s);
|
|
372
|
+
if (subject === absent)
|
|
373
|
+
continue;
|
|
374
|
+
if (flags & PENDING)
|
|
375
|
+
return false;
|
|
376
|
+
const at = runs[b];
|
|
377
|
+
const t = this.frame(subject);
|
|
378
|
+
if (!(t >= at))
|
|
379
|
+
return false;
|
|
380
|
+
const x = b + 3;
|
|
381
|
+
const ms = runs[b + 2];
|
|
382
|
+
if (!this.evaluate(at, ms, runs, x, runs, x + n, runs, x + 2 * n, t, this.xs, this.vs))
|
|
383
|
+
return false;
|
|
384
|
+
runs[b + 1] = flags | LANDED;
|
|
385
|
+
}
|
|
386
|
+
return true;
|
|
387
|
+
}
|
|
225
388
|
/**
|
|
226
389
|
* Whether a live `sample` at `t` would leave the subject's state as it is: nothing to stamp or
|
|
227
390
|
* commit by then, and nothing older than `reading.horizon` to let go of.
|
|
@@ -231,13 +394,13 @@ export class Motions {
|
|
|
231
394
|
const b = this.base(s);
|
|
232
395
|
const flags = runs[b + 1];
|
|
233
396
|
if (flags & PENDING) {
|
|
234
|
-
const list = this.pending
|
|
397
|
+
const list = this.pending[s];
|
|
235
398
|
const first = list[0].at;
|
|
236
399
|
if (first === undefined || first <= t || list[list.length - 1].at === undefined)
|
|
237
400
|
return false;
|
|
238
401
|
}
|
|
239
402
|
if (flags & OLDER) {
|
|
240
|
-
const list = this.older
|
|
403
|
+
const list = this.older[s];
|
|
241
404
|
const next = list.length > 1 ? list[1].at : runs[b];
|
|
242
405
|
if (next <= reading.horizon)
|
|
243
406
|
return false;
|
|
@@ -246,7 +409,7 @@ export class Motions {
|
|
|
246
409
|
}
|
|
247
410
|
/** Position and velocity at `at`, default the latest frame; undefined until there is one. */
|
|
248
411
|
read(subject, at) {
|
|
249
|
-
const s = this.
|
|
412
|
+
const s = this.known(subject);
|
|
250
413
|
if (s === undefined)
|
|
251
414
|
return undefined;
|
|
252
415
|
const when = at ?? this.frame(subject);
|
|
@@ -254,12 +417,12 @@ export class Motions {
|
|
|
254
417
|
return undefined;
|
|
255
418
|
const x = new Float64Array(this.n);
|
|
256
419
|
const v = new Float64Array(this.n);
|
|
257
|
-
|
|
420
|
+
sloped(true);
|
|
258
421
|
try {
|
|
259
422
|
this.peek(s, when, x, v);
|
|
260
423
|
}
|
|
261
424
|
finally {
|
|
262
|
-
|
|
425
|
+
sloped(false);
|
|
263
426
|
}
|
|
264
427
|
return { x, v, s };
|
|
265
428
|
}
|
|
@@ -268,6 +431,7 @@ export class Motions {
|
|
|
268
431
|
* The queue keeps the changes with a time in time order, ahead of any still waiting for one.
|
|
269
432
|
*/
|
|
270
433
|
change(subject, c) {
|
|
434
|
+
this.owner?.revive(this.ownerId, subject);
|
|
271
435
|
const s = this.slot(subject);
|
|
272
436
|
this.check(c.to, this.n);
|
|
273
437
|
this.check(c.v, this.n);
|
|
@@ -276,9 +440,11 @@ export class Motions {
|
|
|
276
440
|
if (!Number.isNaN(at))
|
|
277
441
|
c.at = at;
|
|
278
442
|
}
|
|
279
|
-
const list = this.pending
|
|
443
|
+
const list = this.pending?.[s];
|
|
280
444
|
if (list === undefined) {
|
|
281
|
-
this.pending
|
|
445
|
+
if (this.pending === null)
|
|
446
|
+
this.pending = [undefined];
|
|
447
|
+
this.pending[s] = [c];
|
|
282
448
|
this.flag(s, PENDING, true);
|
|
283
449
|
}
|
|
284
450
|
else if (c.at === undefined)
|
|
@@ -301,7 +467,7 @@ export class Motions {
|
|
|
301
467
|
/** Every change due by `t` applied to a copy of the stretch playing then, committing nothing. */
|
|
302
468
|
peek(s, t, xo, vo) {
|
|
303
469
|
let seg = this.playing(s, t);
|
|
304
|
-
const list = this.pending
|
|
470
|
+
const list = this.pending?.[s];
|
|
305
471
|
if (list !== undefined)
|
|
306
472
|
for (const change of list) {
|
|
307
473
|
const a = change.at;
|
|
@@ -317,13 +483,16 @@ export class Motions {
|
|
|
317
483
|
throw new Error('blits: a motion patch moves every subject on the same number of axes');
|
|
318
484
|
}
|
|
319
485
|
flag(s, bit, on) {
|
|
486
|
+
this.watcher?.stretchChanged(this.watchId, s);
|
|
320
487
|
const i = this.base(s) + 1;
|
|
321
488
|
const flags = this.runs[i];
|
|
322
489
|
this.runs[i] = on ? flags | bit : flags & ~bit;
|
|
323
490
|
}
|
|
324
491
|
forget(s) {
|
|
325
|
-
this.pending
|
|
326
|
-
|
|
492
|
+
if (this.pending !== null)
|
|
493
|
+
this.pending[s] = undefined;
|
|
494
|
+
if (this.older !== null)
|
|
495
|
+
this.older[s] = undefined;
|
|
327
496
|
}
|
|
328
497
|
/** Where subject `s`'s run starts in `runs`. */
|
|
329
498
|
base(s) {
|
|
@@ -334,26 +503,30 @@ export class Motions {
|
|
|
334
503
|
return;
|
|
335
504
|
const cap = Math.max(size, this.cap * 2);
|
|
336
505
|
const runs = new Float64Array(HEAD + cap * this.stride);
|
|
337
|
-
runs.set(this.runs);
|
|
506
|
+
runs.set(this.cap === 0 ? this.shape.law : this.runs);
|
|
338
507
|
this.runs = runs;
|
|
339
508
|
this.cap = cap;
|
|
340
509
|
}
|
|
341
510
|
write(s, seg) {
|
|
342
511
|
const n = this.n;
|
|
343
512
|
const b = this.base(s);
|
|
513
|
+
this.watcher?.stretchChanged(this.watchId, s);
|
|
344
514
|
this.runs[b] = seg.at;
|
|
515
|
+
this.runs[b + 1] = this.runs[b + 1] & ~LANDED;
|
|
516
|
+
this.runs[b + 2] = seg.ms;
|
|
345
517
|
for (let i = 0; i < n; i++) {
|
|
346
|
-
this.runs[b +
|
|
347
|
-
this.runs[b +
|
|
348
|
-
this.runs[b +
|
|
518
|
+
this.runs[b + 3 + i] = seg.x0[i];
|
|
519
|
+
this.runs[b + 3 + n + i] = seg.v0[i];
|
|
520
|
+
this.runs[b + 3 + 2 * n + i] = seg.to[i];
|
|
349
521
|
}
|
|
350
522
|
}
|
|
351
523
|
/** The stretch subject `s` is playing now, as an object. */
|
|
352
524
|
latest(s) {
|
|
353
525
|
const n = this.n;
|
|
354
|
-
const x = this.base(s) +
|
|
526
|
+
const x = this.base(s) + 3;
|
|
355
527
|
return {
|
|
356
528
|
at: this.runs[this.base(s)],
|
|
529
|
+
ms: this.runs[this.base(s) + 2],
|
|
357
530
|
x0: Array.from(this.runs.subarray(x, x + n)),
|
|
358
531
|
v0: Array.from(this.runs.subarray(x + n, x + 2 * n)),
|
|
359
532
|
to: Array.from(this.runs.subarray(x + 2 * n, x + 3 * n)),
|
|
@@ -361,7 +534,7 @@ export class Motions {
|
|
|
361
534
|
}
|
|
362
535
|
/** The stretch playing at voice time `t`: the latest released by then, else the first. */
|
|
363
536
|
playing(s, t) {
|
|
364
|
-
const list = this.older
|
|
537
|
+
const list = this.older?.[s];
|
|
365
538
|
if (list === undefined || t >= this.runs[this.base(s)])
|
|
366
539
|
return this.latest(s);
|
|
367
540
|
if (list[0].at > t)
|
|
@@ -384,11 +557,13 @@ export class Motions {
|
|
|
384
557
|
const xs = Array.from(x);
|
|
385
558
|
const vs = change.v ?? Array.from(v);
|
|
386
559
|
const subject = this.numbers.subject(s);
|
|
387
|
-
const
|
|
388
|
-
|
|
560
|
+
const known = subject === absent ? undefined : subject;
|
|
561
|
+
const to = this.shape.aim(xs, vs, change.to ?? seg.to, known);
|
|
562
|
+
const ms = subject === absent ? seg.ms : (this.shape.ms?.(known) ?? 0);
|
|
563
|
+
return { at, ms, x0: xs, v0: vs, to };
|
|
389
564
|
}
|
|
390
565
|
commit(s, t) {
|
|
391
|
-
const list = this.pending
|
|
566
|
+
const list = this.pending[s];
|
|
392
567
|
// The first read of a subject no frame had met: those waiting for a time take this one.
|
|
393
568
|
if (list[list.length - 1].at === undefined) {
|
|
394
569
|
let i = list.length;
|
|
@@ -408,27 +583,24 @@ export class Motions {
|
|
|
408
583
|
this.insert(s, this.applied(s, this.playing(s, a), change, a));
|
|
409
584
|
}
|
|
410
585
|
if (list.length === 0) {
|
|
411
|
-
this.pending
|
|
586
|
+
this.pending[s] = undefined;
|
|
412
587
|
this.flag(s, PENDING, false);
|
|
413
588
|
}
|
|
414
589
|
}
|
|
415
590
|
insert(s, seg) {
|
|
416
|
-
let list = this.older
|
|
591
|
+
let list = this.older?.[s];
|
|
592
|
+
if (list === undefined) {
|
|
593
|
+
list = [];
|
|
594
|
+
if (this.older === null)
|
|
595
|
+
this.older = [undefined];
|
|
596
|
+
this.older[s] = list;
|
|
597
|
+
this.flag(s, OLDER, true);
|
|
598
|
+
}
|
|
417
599
|
if (seg.at >= this.runs[this.base(s)]) {
|
|
418
|
-
if (list === undefined) {
|
|
419
|
-
list = [];
|
|
420
|
-
this.older.set(s, list);
|
|
421
|
-
this.flag(s, OLDER, true);
|
|
422
|
-
}
|
|
423
600
|
list.push(this.latest(s));
|
|
424
601
|
this.write(s, seg);
|
|
425
602
|
return;
|
|
426
603
|
}
|
|
427
|
-
if (list === undefined) {
|
|
428
|
-
list = [];
|
|
429
|
-
this.older.set(s, list);
|
|
430
|
-
this.flag(s, OLDER, true);
|
|
431
|
-
}
|
|
432
604
|
let i = list.length;
|
|
433
605
|
while (i > 0 && list[i - 1].at > seg.at)
|
|
434
606
|
i--;
|
|
@@ -436,7 +608,7 @@ export class Motions {
|
|
|
436
608
|
}
|
|
437
609
|
/** Lets go of stretches older than the one in force at `reading.horizon`. */
|
|
438
610
|
prune(s) {
|
|
439
|
-
const list = this.older
|
|
611
|
+
const list = this.older?.[s];
|
|
440
612
|
if (list === undefined)
|
|
441
613
|
return;
|
|
442
614
|
while (list.length > 0 &&
|
|
@@ -444,91 +616,218 @@ export class Motions {
|
|
|
444
616
|
reading.horizon)
|
|
445
617
|
list.shift();
|
|
446
618
|
if (list.length === 0) {
|
|
447
|
-
this.older
|
|
619
|
+
this.older[s] = undefined;
|
|
448
620
|
this.flag(s, OLDER, false);
|
|
449
621
|
}
|
|
450
622
|
}
|
|
451
623
|
evaluateSegment(seg, t, xo, vo) {
|
|
452
|
-
this.evaluate(seg.at, seg.x0, 0, seg.v0, 0, seg.to, 0, t, xo, vo);
|
|
624
|
+
this.evaluate(seg.at, seg.ms, seg.x0, 0, seg.v0, 0, seg.to, 0, t, xo, vo);
|
|
453
625
|
}
|
|
454
|
-
evaluate(at, x0, x, v0, v, to, g, t, xo, vo) {
|
|
455
|
-
|
|
456
|
-
const dt = Math.max(0, t - at) / 1000;
|
|
457
|
-
const law = this.runs;
|
|
458
|
-
if (law[0] === EASED) {
|
|
459
|
-
this.eased(dt, law[2], x0, x, to, g, xo, vo);
|
|
460
|
-
return;
|
|
461
|
-
}
|
|
462
|
-
const settle = law[1];
|
|
463
|
-
let still = settle > 0;
|
|
464
|
-
prepare(law, dt);
|
|
465
|
-
for (let i = 0; i < n; i++) {
|
|
466
|
-
const goal = to[g + i];
|
|
467
|
-
solve(law, x0[x + i] - goal, v0[v + i], dt);
|
|
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;
|
|
472
|
-
}
|
|
473
|
-
if (still)
|
|
474
|
-
for (let i = 0; i < n; i++) {
|
|
475
|
-
xo[i] = to[g + i];
|
|
476
|
-
vo[i] = 0;
|
|
477
|
-
}
|
|
626
|
+
evaluate(at, ms, x0, x, v0, v, to, g, t, xo, vo) {
|
|
627
|
+
return closed(this.runs, this.shape.ease, this.n, at, ms, x0, x, v0, v, to, g, t, xo, vo);
|
|
478
628
|
}
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
629
|
+
}
|
|
630
|
+
/**
|
|
631
|
+
* A stretch `t` voice ms into it, released at `at` from `x0` moving at `v0` toward `to` (each read
|
|
632
|
+
* from its offset), into `xo` and `vo`: the one copy of the closed forms, which `law` (a patch's
|
|
633
|
+
* `[form, settle, k1, k2, k3]`, the head of a patch's buffer or a copy of it) picks between. A tween reads `ease` and
|
|
634
|
+
* `ms`, the stretch's length, dividing in milliseconds so a boundary lands exactly; every other
|
|
635
|
+
* form ignores them.
|
|
636
|
+
*/
|
|
637
|
+
export function closed(law, ease, n, at, ms, x0, x, v0, v, to, g, t, xo, vo) {
|
|
638
|
+
if (law[0] === EASED)
|
|
639
|
+
return eased(ease, n, progress(t, at, ms), ms, x0, x, to, g, xo, vo);
|
|
640
|
+
const dt = Math.max(0, t - at) / 1000;
|
|
641
|
+
const settle = law[1];
|
|
642
|
+
let still = settle > 0;
|
|
643
|
+
prepare(law, dt);
|
|
644
|
+
for (let i = 0; i < n; i++) {
|
|
645
|
+
const goal = to[g + i];
|
|
646
|
+
solve(law, x0[x + i] - goal, v0[v + i], dt);
|
|
647
|
+
xo[i] = goal + solved.y;
|
|
648
|
+
vo[i] = solved.dy;
|
|
649
|
+
if (Math.abs(solved.y) > settle || Math.abs(solved.dy) > settle)
|
|
650
|
+
still = false;
|
|
651
|
+
}
|
|
652
|
+
if (still)
|
|
653
|
+
for (let i = 0; i < n; i++) {
|
|
654
|
+
xo[i] = to[g + i];
|
|
655
|
+
vo[i] = 0;
|
|
496
656
|
}
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
657
|
+
return still;
|
|
658
|
+
}
|
|
659
|
+
/** One `writes` array per channel, shared by every motion patch that writes it. */
|
|
660
|
+
const writesOf = new Map();
|
|
661
|
+
/**
|
|
662
|
+
* A `'motion'` patch: its methods on the prototype, where a closure each cost a function and its
|
|
663
|
+
* context per patch, and a mix may hold a patch per voice. Call them on the patch, not detached.
|
|
664
|
+
*/
|
|
665
|
+
class MotionPatch {
|
|
666
|
+
motions;
|
|
667
|
+
writes;
|
|
668
|
+
constructor(writes, motions) {
|
|
669
|
+
this.motions = motions;
|
|
670
|
+
let shared = writesOf.get(writes);
|
|
671
|
+
if (shared === undefined) {
|
|
672
|
+
shared = Object.freeze([writes]);
|
|
673
|
+
writesOf.set(writes, shared);
|
|
502
674
|
}
|
|
675
|
+
this.writes = shared;
|
|
676
|
+
}
|
|
677
|
+
form = 'motion';
|
|
678
|
+
duration = 0;
|
|
679
|
+
period = 0;
|
|
680
|
+
get motion() {
|
|
681
|
+
return this.motions.spec();
|
|
682
|
+
}
|
|
683
|
+
at(_phase, subject, setting) {
|
|
684
|
+
const state = this.motions;
|
|
685
|
+
const s = state.slot(subject);
|
|
686
|
+
state.sample(s, setting.elapsed, state.xs, state.vs);
|
|
687
|
+
return { [this.writes[0]]: state.value(s, state.xs) };
|
|
688
|
+
}
|
|
689
|
+
read(subject, at) {
|
|
690
|
+
const state = this.motions;
|
|
691
|
+
const r = state.read(subject, at);
|
|
692
|
+
if (r === undefined)
|
|
693
|
+
return undefined;
|
|
694
|
+
return { value: state.value(r.s, r.x), velocity: state.value(r.s, r.v) };
|
|
695
|
+
}
|
|
696
|
+
}
|
|
697
|
+
class TweenPatch extends MotionPatch {
|
|
698
|
+
to(subject, goal, at) {
|
|
699
|
+
this.motions.change(subject, { at, to: axes(goal) });
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
class SpringPatch extends TweenPatch {
|
|
703
|
+
push(subject, velocity, at) {
|
|
704
|
+
this.motions.change(subject, { at, v: axes(velocity) });
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
class GlidePatch extends MotionPatch {
|
|
708
|
+
push(subject, velocity, at) {
|
|
709
|
+
this.motions.change(subject, { at, v: axes(velocity) });
|
|
503
710
|
}
|
|
504
711
|
}
|
|
505
|
-
const states = new WeakMap();
|
|
506
712
|
/** The state behind a `'motion'` patch, for an engine that reads it directly. */
|
|
507
713
|
export function motionOf(p) {
|
|
508
|
-
return
|
|
714
|
+
return p instanceof MotionPatch ? p.motions : undefined;
|
|
509
715
|
}
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
}
|
|
530
|
-
|
|
531
|
-
|
|
716
|
+
/**
|
|
717
|
+
* The shapes of the stock motions, each one object over the options it reads, copied out of them so
|
|
718
|
+
* the caller's object is not kept: a closure per question cost a function and its context per
|
|
719
|
+
* patch, and a mix may hold a patch per voice.
|
|
720
|
+
*/
|
|
721
|
+
class SpringShape {
|
|
722
|
+
law;
|
|
723
|
+
motion;
|
|
724
|
+
ease = undefined;
|
|
725
|
+
ms = undefined;
|
|
726
|
+
goal;
|
|
727
|
+
start;
|
|
728
|
+
speed;
|
|
729
|
+
constructor(law, motion, opts) {
|
|
730
|
+
this.law = law;
|
|
731
|
+
this.motion = motion;
|
|
732
|
+
this.goal = opts.to;
|
|
733
|
+
this.start = opts.from ?? opts.to;
|
|
734
|
+
this.speed = opts.velocity;
|
|
735
|
+
}
|
|
736
|
+
spec() {
|
|
737
|
+
return this.motion;
|
|
738
|
+
}
|
|
739
|
+
from(s) {
|
|
740
|
+
return axes(per(this.start, s));
|
|
741
|
+
}
|
|
742
|
+
velocity(s, x) {
|
|
743
|
+
const v = this.speed;
|
|
744
|
+
return v === undefined ? still(x) : axes(per(v, s));
|
|
745
|
+
}
|
|
746
|
+
aim(_x, _v, was, s) {
|
|
747
|
+
return was ?? axes(per(this.goal, s));
|
|
748
|
+
}
|
|
749
|
+
scalar(s) {
|
|
750
|
+
return typeof per(this.goal, s) === 'number';
|
|
751
|
+
}
|
|
752
|
+
scalarEvery() {
|
|
753
|
+
return scalarOf(this.goal);
|
|
754
|
+
}
|
|
755
|
+
}
|
|
756
|
+
class GlideShape {
|
|
757
|
+
law;
|
|
758
|
+
motion;
|
|
759
|
+
tau;
|
|
760
|
+
ease = undefined;
|
|
761
|
+
ms = undefined;
|
|
762
|
+
start;
|
|
763
|
+
speed;
|
|
764
|
+
constructor(law, motion, opts, tau) {
|
|
765
|
+
this.law = law;
|
|
766
|
+
this.motion = motion;
|
|
767
|
+
this.tau = tau;
|
|
768
|
+
this.start = opts.from;
|
|
769
|
+
this.speed = opts.velocity;
|
|
770
|
+
}
|
|
771
|
+
spec() {
|
|
772
|
+
return this.motion;
|
|
773
|
+
}
|
|
774
|
+
from(s) {
|
|
775
|
+
return axes(per(this.start, s));
|
|
776
|
+
}
|
|
777
|
+
velocity(s, x) {
|
|
778
|
+
const v = this.speed;
|
|
779
|
+
return v === undefined ? still(x) : axes(per(v, s));
|
|
780
|
+
}
|
|
781
|
+
aim(x, v) {
|
|
782
|
+
return x.map((xi, i) => xi + v[i] * this.tau);
|
|
783
|
+
}
|
|
784
|
+
scalar(s) {
|
|
785
|
+
return typeof per(this.start, s) === 'number';
|
|
786
|
+
}
|
|
787
|
+
scalarEvery() {
|
|
788
|
+
return scalarOf(this.start);
|
|
789
|
+
}
|
|
790
|
+
}
|
|
791
|
+
const EASED_LAW = [EASED, 0, 0, 0, 0];
|
|
792
|
+
class TweenShape {
|
|
793
|
+
law = EASED_LAW;
|
|
794
|
+
ease;
|
|
795
|
+
easing;
|
|
796
|
+
start;
|
|
797
|
+
goal;
|
|
798
|
+
length;
|
|
799
|
+
constructor(opts) {
|
|
800
|
+
this.easing = opts.ease ?? 'ease';
|
|
801
|
+
this.ease = curve(this.easing);
|
|
802
|
+
this.start = opts.from;
|
|
803
|
+
this.goal = opts.to;
|
|
804
|
+
this.length = opts.ms;
|
|
805
|
+
}
|
|
806
|
+
spec() {
|
|
807
|
+
const ms = this.length;
|
|
808
|
+
return { kind: 'tween', ms: typeof ms === 'number' ? ms : undefined, ease: this.easing };
|
|
809
|
+
}
|
|
810
|
+
from(s) {
|
|
811
|
+
return axes(per(this.start, s));
|
|
812
|
+
}
|
|
813
|
+
velocity(_s, x) {
|
|
814
|
+
return still(x);
|
|
815
|
+
}
|
|
816
|
+
aim(_x, _v, was, s) {
|
|
817
|
+
return was ?? axes(per(this.goal, s));
|
|
818
|
+
}
|
|
819
|
+
scalar(s) {
|
|
820
|
+
return typeof per(this.goal, s) === 'number';
|
|
821
|
+
}
|
|
822
|
+
scalarEvery() {
|
|
823
|
+
return scalarOf(this.goal);
|
|
824
|
+
}
|
|
825
|
+
ms(s) {
|
|
826
|
+
const length = per(this.length, s);
|
|
827
|
+
if (!(length > 0))
|
|
828
|
+
throw new Error('blits: a tween takes a positive ms');
|
|
829
|
+
return length;
|
|
830
|
+
}
|
|
532
831
|
}
|
|
533
832
|
/**
|
|
534
833
|
* A damped spring toward a target, solved in closed form. `to(subject, target)` retargets one
|
|
@@ -554,19 +853,8 @@ export function spring(writes, opts) {
|
|
|
554
853
|
const s = Math.sqrt(zeta * zeta - 1);
|
|
555
854
|
law = [OVER, settle, -w0 * (zeta - s), -w0 * (zeta + s), 0];
|
|
556
855
|
}
|
|
557
|
-
const
|
|
558
|
-
|
|
559
|
-
from: (s) => axes(per(opts.from ?? opts.to, s)),
|
|
560
|
-
velocity: (s) => opts.velocity === undefined ? target(s).map(() => 0) : axes(per(opts.velocity, s)),
|
|
561
|
-
aim: (_x, _v, was, s) => was ?? target(s),
|
|
562
|
-
law,
|
|
563
|
-
scalar: (s) => typeof per(opts.to, s) === 'number',
|
|
564
|
-
ease: undefined,
|
|
565
|
-
});
|
|
566
|
-
return Object.assign(patch, {
|
|
567
|
-
push,
|
|
568
|
-
to: (subject, goal, at) => state.change(subject, { at, to: axes(goal) }),
|
|
569
|
-
});
|
|
856
|
+
const motion = { kind: 'spring', stiffness: k, damping: c, mass: m, settle };
|
|
857
|
+
return new SpringPatch(writes, new Motions(new SpringShape(law, motion, opts)));
|
|
570
858
|
}
|
|
571
859
|
/**
|
|
572
860
|
* Coasting under friction, a fling: a subject keeps its velocity, which falls by a factor of e every
|
|
@@ -579,17 +867,8 @@ export function glide(writes, opts) {
|
|
|
579
867
|
const ms = opts.ms ?? 325;
|
|
580
868
|
const tau = ms / 1000;
|
|
581
869
|
const settle = opts.settle ?? 1e-4;
|
|
582
|
-
const
|
|
583
|
-
|
|
584
|
-
velocity: (s) => opts.velocity === undefined
|
|
585
|
-
? axes(per(opts.from, s)).map(() => 0)
|
|
586
|
-
: axes(per(opts.velocity, s)),
|
|
587
|
-
aim: (x, v) => x.map((xi, i) => xi + v[i] * tau),
|
|
588
|
-
law: [COAST, settle, tau, 0, 0],
|
|
589
|
-
scalar: (s) => typeof per(opts.from, s) === 'number',
|
|
590
|
-
ease: undefined,
|
|
591
|
-
});
|
|
592
|
-
return Object.assign(patch, { push });
|
|
870
|
+
const shape = new GlideShape([COAST, settle, tau, 0, 0], { kind: 'glide', ms, settle }, opts, tau);
|
|
871
|
+
return new GlidePatch(writes, new Motions(shape));
|
|
593
872
|
}
|
|
594
873
|
/**
|
|
595
874
|
* From one value to another over `ms` along an easing, solved in closed form, so frame rate does not
|
|
@@ -601,19 +880,8 @@ export function glide(writes, opts) {
|
|
|
601
880
|
* @category patch
|
|
602
881
|
*/
|
|
603
882
|
export function tween(writes, opts) {
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
const { patch, state } = moving(writes, { kind: 'tween', ms: opts.ms, ease }, {
|
|
609
|
-
from: (s) => axes(per(opts.from, s)),
|
|
610
|
-
velocity: (s) => target(s).map(() => 0),
|
|
611
|
-
aim: (_x, _v, was, s) => was ?? target(s),
|
|
612
|
-
law: [EASED, 0, opts.ms / 1000, 0, 0],
|
|
613
|
-
scalar: (s) => typeof per(opts.to, s) === 'number',
|
|
614
|
-
ease: curve(ease),
|
|
615
|
-
});
|
|
616
|
-
return Object.assign(patch, {
|
|
617
|
-
to: (subject, goal, at) => state.change(subject, { at, to: axes(goal) }),
|
|
618
|
-
});
|
|
883
|
+
const shape = new TweenShape(opts);
|
|
884
|
+
if (typeof opts.ms === 'number')
|
|
885
|
+
shape.ms(undefined);
|
|
886
|
+
return new TweenPatch(writes, new Motions(shape));
|
|
619
887
|
}
|