@msb235/blits 0.4.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 +262 -0
- package/README.md +2 -1
- 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 +121 -246
- package/dist/lanes.js +150 -1578
- 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 -196
- package/dist/mixer.js +242 -2220
- package/dist/motion.d.ts +66 -21
- package/dist/motion.js +325 -165
- 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 +217 -34
- 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 +118 -40
- 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,13 +14,11 @@ 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
|
-
export const noRevive = () => { };
|
|
19
|
-
export const noTouch = () => { };
|
|
20
17
|
const solved = { y: 0, dy: 0 };
|
|
21
18
|
/** Where every patch's `sample` leaves a value on its way to the mix; none outlives its call. */
|
|
22
19
|
let shared = { xs: new Float64Array(4), vs: new Float64Array(4) };
|
|
20
|
+
/** What a patch's `xs` and `vs` are before its first subject sets its axes. */
|
|
21
|
+
const unsized = new Float64Array(0);
|
|
23
22
|
/**
|
|
24
23
|
* A subject's flags: its value is a number; it has changes pending; it has older stretches; the
|
|
25
24
|
* stretch it is playing was found landed, which holds until another replaces it.
|
|
@@ -33,18 +32,29 @@ const LANDED = 8;
|
|
|
33
32
|
* a sample works them out once rather than once per axis.
|
|
34
33
|
*/
|
|
35
34
|
const timed = { e: 0, e2: 0, cos: 0, sin: 0 };
|
|
36
|
-
/**
|
|
37
|
-
const
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
const memo = { u: Number.NaN, ease: undefined, 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
|
+
};
|
|
45
43
|
/** `t` seconds after release, the terms every axis shares, into `timed`. */
|
|
46
44
|
function prepare(law, t) {
|
|
47
|
-
|
|
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) {
|
|
48
58
|
case UNDER: {
|
|
49
59
|
const zeta = law[2];
|
|
50
60
|
const w0 = law[3];
|
|
@@ -118,7 +128,11 @@ function solve(law, y0, v0, t) {
|
|
|
118
128
|
}
|
|
119
129
|
}
|
|
120
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);
|
|
121
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'}`);
|
|
122
136
|
/**
|
|
123
137
|
* A motion patch's subjects: the stretch each is playing, in one flat array by a number the patch
|
|
124
138
|
* gives it, with earlier stretches a read back may reach and changes not yet applied beside them.
|
|
@@ -131,47 +145,76 @@ const per = (p, subject) => typeof p === 'function' ? p(subject) : p;
|
|
|
131
145
|
*/
|
|
132
146
|
export class Motions {
|
|
133
147
|
shape;
|
|
134
|
-
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`. */
|
|
135
150
|
slots = new Store();
|
|
136
151
|
/** Axes per subject; -1 until the first subject sets it. */
|
|
137
152
|
n = -1;
|
|
138
153
|
cap = 0;
|
|
139
154
|
stride = 0;
|
|
140
|
-
|
|
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;
|
|
141
160
|
/**
|
|
142
161
|
* Where `sample` leaves a subject's position and velocity, per axis. Shared by every patch, so
|
|
143
162
|
* read it before the next sample.
|
|
144
163
|
*/
|
|
145
|
-
xs =
|
|
146
|
-
vs =
|
|
147
|
-
pending = new Map();
|
|
148
|
-
older = new Map();
|
|
164
|
+
xs = unsized;
|
|
165
|
+
vs = unsized;
|
|
149
166
|
/**
|
|
150
|
-
*
|
|
151
|
-
*
|
|
152
|
-
*
|
|
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.
|
|
153
170
|
*/
|
|
154
|
-
|
|
171
|
+
pending = null;
|
|
172
|
+
older = null;
|
|
155
173
|
/**
|
|
156
|
-
*
|
|
157
|
-
* the mix that cues the patch, and
|
|
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.
|
|
158
176
|
*/
|
|
159
|
-
|
|
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
|
+
}
|
|
160
200
|
constructor(shape) {
|
|
161
201
|
this.shape = shape;
|
|
162
|
-
this.runs = Float64Array.from(shape.law);
|
|
163
202
|
}
|
|
164
203
|
/** The patch's number for `subject`, made with its first stretch on first ask. */
|
|
165
204
|
slot(subject) {
|
|
166
|
-
const known = this.
|
|
205
|
+
const known = this.known(subject);
|
|
167
206
|
if (known !== undefined)
|
|
168
207
|
return known;
|
|
169
208
|
const x = this.shape.from(subject);
|
|
170
|
-
const v = this.shape.velocity(subject);
|
|
209
|
+
const v = this.shape.velocity(subject, x);
|
|
171
210
|
const to = this.shape.aim(x, v, null, subject);
|
|
172
211
|
const n = this.n < 0 ? x.length : this.n;
|
|
173
|
-
|
|
174
|
-
|
|
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);
|
|
175
218
|
if (this.n < 0) {
|
|
176
219
|
this.n = n;
|
|
177
220
|
this.stride = 3 + 3 * n;
|
|
@@ -181,32 +224,59 @@ export class Motions {
|
|
|
181
224
|
this.vs = shared.vs;
|
|
182
225
|
}
|
|
183
226
|
const s = this.numbers.take(subject);
|
|
184
|
-
|
|
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);
|
|
185
234
|
this.grow(s + 1);
|
|
186
|
-
this.runs[this.base(s) + 1] =
|
|
235
|
+
this.runs[this.base(s) + 1] = scalar ? SCALAR : 0;
|
|
187
236
|
this.write(s, { at: 0, ms: this.shape.ms?.(subject) ?? 0, x0: x, v0: v, to });
|
|
188
237
|
return s;
|
|
189
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
|
+
}
|
|
190
246
|
/** Forgets a subject, so a later ask starts it afresh from `from`. */
|
|
191
247
|
release(subject) {
|
|
192
|
-
const s = this.
|
|
248
|
+
const s = this.known(subject);
|
|
193
249
|
if (s === undefined)
|
|
194
250
|
return;
|
|
195
251
|
this.slots.delete(subject);
|
|
196
252
|
this.numbers.release(s);
|
|
197
253
|
}
|
|
198
254
|
/**
|
|
199
|
-
*
|
|
200
|
-
*
|
|
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.
|
|
201
258
|
*/
|
|
202
|
-
|
|
259
|
+
watcher = null;
|
|
260
|
+
watchId = -1;
|
|
203
261
|
/** The easing a tween's stretches follow; undefined for every other shape. */
|
|
204
262
|
get ease() {
|
|
205
263
|
return this.shape.ease;
|
|
206
264
|
}
|
|
265
|
+
/** The patch's kind and constants. */
|
|
266
|
+
spec() {
|
|
267
|
+
return this.shape.spec();
|
|
268
|
+
}
|
|
207
269
|
/** A copy of the patch's law, `[form, settle, k1, k2, k3]`. */
|
|
208
270
|
law() {
|
|
209
|
-
return this.runs.slice(0, HEAD);
|
|
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;
|
|
210
280
|
}
|
|
211
281
|
/**
|
|
212
282
|
* Copies subject `s`'s current stretch into `out` from `o` as its release time, its seconds, then
|
|
@@ -264,14 +334,22 @@ export class Motions {
|
|
|
264
334
|
}
|
|
265
335
|
this.peek(s, t, xo, vo);
|
|
266
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
|
+
}
|
|
267
345
|
/**
|
|
268
346
|
* A live `sample` at `t` for a subject with nothing pending and no earlier stretch kept, true
|
|
269
347
|
* then; false, writing nothing, for any other, which `quiet` and `sample` take instead.
|
|
270
348
|
*/
|
|
271
349
|
sampleBare(s, t, xo, vo) {
|
|
272
350
|
const runs = this.runs;
|
|
273
|
-
const b = this.
|
|
274
|
-
if (
|
|
351
|
+
const b = this.bareAt(s);
|
|
352
|
+
if (b < 0)
|
|
275
353
|
return false;
|
|
276
354
|
const x = b + 3;
|
|
277
355
|
const n = this.n;
|
|
@@ -316,13 +394,13 @@ export class Motions {
|
|
|
316
394
|
const b = this.base(s);
|
|
317
395
|
const flags = runs[b + 1];
|
|
318
396
|
if (flags & PENDING) {
|
|
319
|
-
const list = this.pending
|
|
397
|
+
const list = this.pending[s];
|
|
320
398
|
const first = list[0].at;
|
|
321
399
|
if (first === undefined || first <= t || list[list.length - 1].at === undefined)
|
|
322
400
|
return false;
|
|
323
401
|
}
|
|
324
402
|
if (flags & OLDER) {
|
|
325
|
-
const list = this.older
|
|
403
|
+
const list = this.older[s];
|
|
326
404
|
const next = list.length > 1 ? list[1].at : runs[b];
|
|
327
405
|
if (next <= reading.horizon)
|
|
328
406
|
return false;
|
|
@@ -331,7 +409,7 @@ export class Motions {
|
|
|
331
409
|
}
|
|
332
410
|
/** Position and velocity at `at`, default the latest frame; undefined until there is one. */
|
|
333
411
|
read(subject, at) {
|
|
334
|
-
const s = this.
|
|
412
|
+
const s = this.known(subject);
|
|
335
413
|
if (s === undefined)
|
|
336
414
|
return undefined;
|
|
337
415
|
const when = at ?? this.frame(subject);
|
|
@@ -339,12 +417,12 @@ export class Motions {
|
|
|
339
417
|
return undefined;
|
|
340
418
|
const x = new Float64Array(this.n);
|
|
341
419
|
const v = new Float64Array(this.n);
|
|
342
|
-
|
|
420
|
+
sloped(true);
|
|
343
421
|
try {
|
|
344
422
|
this.peek(s, when, x, v);
|
|
345
423
|
}
|
|
346
424
|
finally {
|
|
347
|
-
|
|
425
|
+
sloped(false);
|
|
348
426
|
}
|
|
349
427
|
return { x, v, s };
|
|
350
428
|
}
|
|
@@ -353,7 +431,7 @@ export class Motions {
|
|
|
353
431
|
* The queue keeps the changes with a time in time order, ahead of any still waiting for one.
|
|
354
432
|
*/
|
|
355
433
|
change(subject, c) {
|
|
356
|
-
this.revive(subject);
|
|
434
|
+
this.owner?.revive(this.ownerId, subject);
|
|
357
435
|
const s = this.slot(subject);
|
|
358
436
|
this.check(c.to, this.n);
|
|
359
437
|
this.check(c.v, this.n);
|
|
@@ -362,9 +440,11 @@ export class Motions {
|
|
|
362
440
|
if (!Number.isNaN(at))
|
|
363
441
|
c.at = at;
|
|
364
442
|
}
|
|
365
|
-
const list = this.pending
|
|
443
|
+
const list = this.pending?.[s];
|
|
366
444
|
if (list === undefined) {
|
|
367
|
-
this.pending
|
|
445
|
+
if (this.pending === null)
|
|
446
|
+
this.pending = [undefined];
|
|
447
|
+
this.pending[s] = [c];
|
|
368
448
|
this.flag(s, PENDING, true);
|
|
369
449
|
}
|
|
370
450
|
else if (c.at === undefined)
|
|
@@ -387,7 +467,7 @@ export class Motions {
|
|
|
387
467
|
/** Every change due by `t` applied to a copy of the stretch playing then, committing nothing. */
|
|
388
468
|
peek(s, t, xo, vo) {
|
|
389
469
|
let seg = this.playing(s, t);
|
|
390
|
-
const list = this.pending
|
|
470
|
+
const list = this.pending?.[s];
|
|
391
471
|
if (list !== undefined)
|
|
392
472
|
for (const change of list) {
|
|
393
473
|
const a = change.at;
|
|
@@ -403,14 +483,16 @@ export class Motions {
|
|
|
403
483
|
throw new Error('blits: a motion patch moves every subject on the same number of axes');
|
|
404
484
|
}
|
|
405
485
|
flag(s, bit, on) {
|
|
406
|
-
this.
|
|
486
|
+
this.watcher?.stretchChanged(this.watchId, s);
|
|
407
487
|
const i = this.base(s) + 1;
|
|
408
488
|
const flags = this.runs[i];
|
|
409
489
|
this.runs[i] = on ? flags | bit : flags & ~bit;
|
|
410
490
|
}
|
|
411
491
|
forget(s) {
|
|
412
|
-
this.pending
|
|
413
|
-
|
|
492
|
+
if (this.pending !== null)
|
|
493
|
+
this.pending[s] = undefined;
|
|
494
|
+
if (this.older !== null)
|
|
495
|
+
this.older[s] = undefined;
|
|
414
496
|
}
|
|
415
497
|
/** Where subject `s`'s run starts in `runs`. */
|
|
416
498
|
base(s) {
|
|
@@ -421,14 +503,14 @@ export class Motions {
|
|
|
421
503
|
return;
|
|
422
504
|
const cap = Math.max(size, this.cap * 2);
|
|
423
505
|
const runs = new Float64Array(HEAD + cap * this.stride);
|
|
424
|
-
runs.set(this.runs);
|
|
506
|
+
runs.set(this.cap === 0 ? this.shape.law : this.runs);
|
|
425
507
|
this.runs = runs;
|
|
426
508
|
this.cap = cap;
|
|
427
509
|
}
|
|
428
510
|
write(s, seg) {
|
|
429
511
|
const n = this.n;
|
|
430
512
|
const b = this.base(s);
|
|
431
|
-
this.
|
|
513
|
+
this.watcher?.stretchChanged(this.watchId, s);
|
|
432
514
|
this.runs[b] = seg.at;
|
|
433
515
|
this.runs[b + 1] = this.runs[b + 1] & ~LANDED;
|
|
434
516
|
this.runs[b + 2] = seg.ms;
|
|
@@ -452,7 +534,7 @@ export class Motions {
|
|
|
452
534
|
}
|
|
453
535
|
/** The stretch playing at voice time `t`: the latest released by then, else the first. */
|
|
454
536
|
playing(s, t) {
|
|
455
|
-
const list = this.older
|
|
537
|
+
const list = this.older?.[s];
|
|
456
538
|
if (list === undefined || t >= this.runs[this.base(s)])
|
|
457
539
|
return this.latest(s);
|
|
458
540
|
if (list[0].at > t)
|
|
@@ -481,7 +563,7 @@ export class Motions {
|
|
|
481
563
|
return { at, ms, x0: xs, v0: vs, to };
|
|
482
564
|
}
|
|
483
565
|
commit(s, t) {
|
|
484
|
-
const list = this.pending
|
|
566
|
+
const list = this.pending[s];
|
|
485
567
|
// The first read of a subject no frame had met: those waiting for a time take this one.
|
|
486
568
|
if (list[list.length - 1].at === undefined) {
|
|
487
569
|
let i = list.length;
|
|
@@ -501,27 +583,24 @@ export class Motions {
|
|
|
501
583
|
this.insert(s, this.applied(s, this.playing(s, a), change, a));
|
|
502
584
|
}
|
|
503
585
|
if (list.length === 0) {
|
|
504
|
-
this.pending
|
|
586
|
+
this.pending[s] = undefined;
|
|
505
587
|
this.flag(s, PENDING, false);
|
|
506
588
|
}
|
|
507
589
|
}
|
|
508
590
|
insert(s, seg) {
|
|
509
|
-
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
|
+
}
|
|
510
599
|
if (seg.at >= this.runs[this.base(s)]) {
|
|
511
|
-
if (list === undefined) {
|
|
512
|
-
list = [];
|
|
513
|
-
this.older.set(s, list);
|
|
514
|
-
this.flag(s, OLDER, true);
|
|
515
|
-
}
|
|
516
600
|
list.push(this.latest(s));
|
|
517
601
|
this.write(s, seg);
|
|
518
602
|
return;
|
|
519
603
|
}
|
|
520
|
-
if (list === undefined) {
|
|
521
|
-
list = [];
|
|
522
|
-
this.older.set(s, list);
|
|
523
|
-
this.flag(s, OLDER, true);
|
|
524
|
-
}
|
|
525
604
|
let i = list.length;
|
|
526
605
|
while (i > 0 && list[i - 1].at > seg.at)
|
|
527
606
|
i--;
|
|
@@ -529,7 +608,7 @@ export class Motions {
|
|
|
529
608
|
}
|
|
530
609
|
/** Lets go of stretches older than the one in force at `reading.horizon`. */
|
|
531
610
|
prune(s) {
|
|
532
|
-
const list = this.older
|
|
611
|
+
const list = this.older?.[s];
|
|
533
612
|
if (list === undefined)
|
|
534
613
|
return;
|
|
535
614
|
while (list.length > 0 &&
|
|
@@ -537,7 +616,7 @@ export class Motions {
|
|
|
537
616
|
reading.horizon)
|
|
538
617
|
list.shift();
|
|
539
618
|
if (list.length === 0) {
|
|
540
|
-
this.older
|
|
619
|
+
this.older[s] = undefined;
|
|
541
620
|
this.flag(s, OLDER, false);
|
|
542
621
|
}
|
|
543
622
|
}
|
|
@@ -557,7 +636,7 @@ export class Motions {
|
|
|
557
636
|
*/
|
|
558
637
|
export function closed(law, ease, n, at, ms, x0, x, v0, v, to, g, t, xo, vo) {
|
|
559
638
|
if (law[0] === EASED)
|
|
560
|
-
return eased(ease, n,
|
|
639
|
+
return eased(ease, n, progress(t, at, ms), ms, x0, x, to, g, xo, vo);
|
|
561
640
|
const dt = Math.max(0, t - at) / 1000;
|
|
562
641
|
const settle = law[1];
|
|
563
642
|
let still = settle > 0;
|
|
@@ -577,56 +656,178 @@ export function closed(law, ease, n, at, ms, x0, x, v0, v, to, g, t, xo, vo) {
|
|
|
577
656
|
}
|
|
578
657
|
return still;
|
|
579
658
|
}
|
|
580
|
-
/**
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
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);
|
|
674
|
+
}
|
|
675
|
+
this.writes = shared;
|
|
594
676
|
}
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
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) });
|
|
600
710
|
}
|
|
601
|
-
return left === 0;
|
|
602
711
|
}
|
|
603
|
-
const states = new WeakMap();
|
|
604
712
|
/** The state behind a `'motion'` patch, for an engine that reads it directly. */
|
|
605
713
|
export function motionOf(p) {
|
|
606
|
-
return
|
|
714
|
+
return p instanceof MotionPatch ? p.motions : undefined;
|
|
607
715
|
}
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
}
|
|
628
|
-
|
|
629
|
-
|
|
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
|
+
}
|
|
630
831
|
}
|
|
631
832
|
/**
|
|
632
833
|
* A damped spring toward a target, solved in closed form. `to(subject, target)` retargets one
|
|
@@ -652,20 +853,8 @@ export function spring(writes, opts) {
|
|
|
652
853
|
const s = Math.sqrt(zeta * zeta - 1);
|
|
653
854
|
law = [OVER, settle, -w0 * (zeta - s), -w0 * (zeta + s), 0];
|
|
654
855
|
}
|
|
655
|
-
const
|
|
656
|
-
|
|
657
|
-
from: (s) => axes(per(opts.from ?? opts.to, s)),
|
|
658
|
-
velocity: (s) => opts.velocity === undefined ? target(s).map(() => 0) : axes(per(opts.velocity, s)),
|
|
659
|
-
aim: (_x, _v, was, s) => was ?? target(s),
|
|
660
|
-
law,
|
|
661
|
-
scalar: (s) => typeof per(opts.to, s) === 'number',
|
|
662
|
-
ease: undefined,
|
|
663
|
-
ms: undefined,
|
|
664
|
-
});
|
|
665
|
-
return Object.assign(patch, {
|
|
666
|
-
push,
|
|
667
|
-
to: (subject, goal, at) => state.change(subject, { at, to: axes(goal) }),
|
|
668
|
-
});
|
|
856
|
+
const motion = { kind: 'spring', stiffness: k, damping: c, mass: m, settle };
|
|
857
|
+
return new SpringPatch(writes, new Motions(new SpringShape(law, motion, opts)));
|
|
669
858
|
}
|
|
670
859
|
/**
|
|
671
860
|
* Coasting under friction, a fling: a subject keeps its velocity, which falls by a factor of e every
|
|
@@ -678,18 +867,8 @@ export function glide(writes, opts) {
|
|
|
678
867
|
const ms = opts.ms ?? 325;
|
|
679
868
|
const tau = ms / 1000;
|
|
680
869
|
const settle = opts.settle ?? 1e-4;
|
|
681
|
-
const
|
|
682
|
-
|
|
683
|
-
velocity: (s) => opts.velocity === undefined
|
|
684
|
-
? axes(per(opts.from, s)).map(() => 0)
|
|
685
|
-
: axes(per(opts.velocity, s)),
|
|
686
|
-
aim: (x, v) => x.map((xi, i) => xi + v[i] * tau),
|
|
687
|
-
law: [COAST, settle, tau, 0, 0],
|
|
688
|
-
scalar: (s) => typeof per(opts.from, s) === 'number',
|
|
689
|
-
ease: undefined,
|
|
690
|
-
ms: undefined,
|
|
691
|
-
});
|
|
692
|
-
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));
|
|
693
872
|
}
|
|
694
873
|
/**
|
|
695
874
|
* From one value to another over `ms` along an easing, solved in closed form, so frame rate does not
|
|
@@ -701,27 +880,8 @@ export function glide(writes, opts) {
|
|
|
701
880
|
* @category patch
|
|
702
881
|
*/
|
|
703
882
|
export function tween(writes, opts) {
|
|
704
|
-
const
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
throw new Error('blits: a tween takes a positive ms');
|
|
709
|
-
return length;
|
|
710
|
-
};
|
|
711
|
-
if (fixed)
|
|
712
|
-
ms(undefined);
|
|
713
|
-
const ease = opts.ease ?? 'ease';
|
|
714
|
-
const target = (subject) => axes(per(opts.to, subject));
|
|
715
|
-
const { patch, state } = moving(writes, { kind: 'tween', ms: fixed ? opts.ms : undefined, ease }, {
|
|
716
|
-
from: (s) => axes(per(opts.from, s)),
|
|
717
|
-
velocity: (s) => target(s).map(() => 0),
|
|
718
|
-
aim: (_x, _v, was, s) => was ?? target(s),
|
|
719
|
-
law: [EASED, 0, 0, 0, 0],
|
|
720
|
-
scalar: (s) => typeof per(opts.to, s) === 'number',
|
|
721
|
-
ease: curve(ease),
|
|
722
|
-
ms,
|
|
723
|
-
});
|
|
724
|
-
return Object.assign(patch, {
|
|
725
|
-
to: (subject, goal, at) => state.change(subject, { at, to: axes(goal) }),
|
|
726
|
-
});
|
|
883
|
+
const shape = new TweenShape(opts);
|
|
884
|
+
if (typeof opts.ms === 'number')
|
|
885
|
+
shape.ms(undefined);
|
|
886
|
+
return new TweenPatch(writes, new Motions(shape));
|
|
727
887
|
}
|