@msb235/blits 0.5.0 → 0.7.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 +163 -0
- package/README.md +32 -8
- package/dist/bare.js +7 -1
- package/dist/book.d.ts +8 -1
- package/dist/book.js +35 -4
- package/dist/chain.d.ts +4 -3
- package/dist/chain.js +10 -6
- package/dist/channels.d.ts +12 -19
- package/dist/channels.js +18 -118
- package/dist/clock.d.ts +12 -5
- package/dist/clock.js +13 -5
- package/dist/closed.d.ts +15 -0
- package/dist/closed.js +144 -0
- package/dist/color.d.ts +61 -0
- package/dist/color.js +229 -0
- package/dist/columns.js +2 -1
- package/dist/crowd.d.ts +4 -4
- package/dist/crowd.js +11 -8
- package/dist/cue.d.ts +2 -0
- package/dist/cue.js +78 -27
- package/dist/due.js +5 -2
- package/dist/fade.d.ts +13 -0
- package/dist/fade.js +55 -4
- package/dist/fill.d.ts +1 -1
- package/dist/fill.js +17 -10
- package/dist/fit.d.ts +144 -0
- package/dist/fit.js +181 -0
- package/dist/fold.d.ts +3 -3
- package/dist/fold.js +25 -18
- package/dist/gather.d.ts +6 -0
- package/dist/gather.js +18 -0
- package/dist/handle.d.ts +27 -2
- package/dist/handle.js +60 -11
- package/dist/held.js +7 -3
- package/dist/history.d.ts +15 -2
- package/dist/history.js +54 -9
- package/dist/hosts.d.ts +2 -2
- package/dist/hosts.js +19 -6
- package/dist/index.d.ts +10 -2
- package/dist/index.js +6 -1
- package/dist/lane.d.ts +4 -4
- package/dist/lane.js +5 -5
- package/dist/lanes.d.ts +32 -9
- package/dist/lanes.js +53 -24
- package/dist/locus.d.ts +3 -3
- package/dist/locus.js +1 -1
- package/dist/marks.d.ts +3 -0
- package/dist/marks.js +81 -13
- package/dist/meet.d.ts +3 -2
- package/dist/meet.js +15 -3
- package/dist/mixer.d.ts +55 -35
- package/dist/mixer.js +173 -83
- package/dist/motion.d.ts +2 -199
- package/dist/motion.js +2 -652
- package/dist/motions.d.ts +266 -0
- package/dist/motions.js +724 -0
- package/dist/move.d.ts +1 -1
- package/dist/move.js +27 -11
- package/dist/owner.d.ts +2 -2
- package/dist/owner.js +5 -5
- package/dist/pace.d.ts +2 -0
- package/dist/pace.js +8 -0
- package/dist/pack.d.ts +31 -0
- package/dist/pack.js +65 -0
- package/dist/paging.d.ts +63 -0
- package/dist/paging.js +110 -0
- package/dist/patch.d.ts +3 -0
- package/dist/patch.js +2 -0
- package/dist/place.js +74 -25
- package/dist/project.d.ts +7 -4
- package/dist/project.js +117 -78
- package/dist/pull.js +4 -3
- package/dist/qualify.js +4 -2
- package/dist/revive.d.ts +58 -0
- package/dist/revive.js +158 -0
- package/dist/sample.d.ts +1 -1
- package/dist/sample.js +6 -1
- package/dist/seek.d.ts +3 -0
- package/dist/seek.js +311 -0
- package/dist/signals.d.ts +3 -2
- package/dist/signals.js +12 -6
- package/dist/spans.d.ts +29 -0
- package/dist/spans.js +165 -0
- package/dist/strays.d.ts +1 -1
- package/dist/strays.js +21 -0
- package/dist/tape.d.ts +21 -0
- package/dist/tape.js +65 -0
- package/dist/ticker.d.ts +73 -0
- package/dist/ticker.js +115 -0
- package/dist/transport.d.ts +121 -0
- package/dist/transport.js +271 -0
- package/dist/types.d.ts +427 -50
- package/dist/unpage.d.ts +8 -0
- package/dist/unpage.js +163 -0
- package/dist/voice.d.ts +78 -12
- package/dist/voice.js +126 -12
- package/dist/weigh.js +2 -2
- package/docs/schema.html +140 -48
- package/package.json +2 -1
package/dist/motion.js
CHANGED
|
@@ -1,661 +1,11 @@
|
|
|
1
|
+
import { COAST, CRITICAL, EASED, OVER, UNDER } from './closed.js';
|
|
1
2
|
import { curve } from './easing.js';
|
|
2
|
-
import {
|
|
3
|
-
import { reading } from './reading.js';
|
|
4
|
-
import { Store } from './store.js';
|
|
5
|
-
import { eased, progress, sloped } from './tweened.js';
|
|
6
|
-
const UNDER = 0;
|
|
7
|
-
const CRITICAL = 1;
|
|
8
|
-
const OVER = 2;
|
|
9
|
-
const COAST = 3;
|
|
10
|
-
const EASED = 4;
|
|
11
|
-
/**
|
|
12
|
-
* A patch's law, `[form, settle, k1, k2, k3]`: which closed form its stretches follow, its `settle`,
|
|
13
|
-
* and up to three constants that form reads. It heads the patch's `runs`, so `solve` finds them in
|
|
14
|
-
* the buffer a sample already reads rather than in an object of their own.
|
|
15
|
-
*/
|
|
16
|
-
const HEAD = 5;
|
|
17
|
-
const solved = { y: 0, dy: 0 };
|
|
18
|
-
/** Where every patch's `sample` leaves a value on its way to the mix; none outlives its call. */
|
|
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);
|
|
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
|
-
*/
|
|
26
|
-
const SCALAR = 1;
|
|
27
|
-
const PENDING = 2;
|
|
28
|
-
const OLDER = 4;
|
|
29
|
-
const LANDED = 8;
|
|
30
|
-
/**
|
|
31
|
-
* The terms of a stretch that depend only on how long since its release, the same on every axis, so
|
|
32
|
-
* a sample works them out once rather than once per axis.
|
|
33
|
-
*/
|
|
34
|
-
const timed = { e: 0, e2: 0, cos: 0, sin: 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
|
-
};
|
|
43
|
-
/** `t` seconds after release, the terms every axis shares, into `timed`. */
|
|
44
|
-
function prepare(law, t) {
|
|
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) {
|
|
58
|
-
case UNDER: {
|
|
59
|
-
const zeta = law[2];
|
|
60
|
-
const w0 = law[3];
|
|
61
|
-
const wd = law[4];
|
|
62
|
-
timed.e = Math.exp(-zeta * w0 * t);
|
|
63
|
-
timed.cos = Math.cos(wd * t);
|
|
64
|
-
timed.sin = Math.sin(wd * t);
|
|
65
|
-
return;
|
|
66
|
-
}
|
|
67
|
-
case CRITICAL: {
|
|
68
|
-
const w0 = law[3];
|
|
69
|
-
timed.e = Math.exp(-w0 * t);
|
|
70
|
-
return;
|
|
71
|
-
}
|
|
72
|
-
case OVER: {
|
|
73
|
-
timed.e = Math.exp(law[2] * t);
|
|
74
|
-
timed.e2 = Math.exp(law[3] * t);
|
|
75
|
-
return;
|
|
76
|
-
}
|
|
77
|
-
default:
|
|
78
|
-
timed.e = Math.exp(-t / law[2]);
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
/**
|
|
82
|
-
* One axis, `t` seconds after release, `y0` from where it heads and moving at `v0`, into `solved`,
|
|
83
|
-
* with `timed` prepared for `t`.
|
|
84
|
-
*/
|
|
85
|
-
function solve(law, y0, v0, t) {
|
|
86
|
-
switch (law[0]) {
|
|
87
|
-
case UNDER: {
|
|
88
|
-
const zeta = law[2];
|
|
89
|
-
const w0 = law[3];
|
|
90
|
-
const wd = law[4];
|
|
91
|
-
const e = timed.e;
|
|
92
|
-
const b = (v0 + zeta * w0 * y0) / wd;
|
|
93
|
-
const cos = timed.cos;
|
|
94
|
-
const sin = timed.sin;
|
|
95
|
-
const y = e * (y0 * cos + b * sin);
|
|
96
|
-
solved.y = y;
|
|
97
|
-
solved.dy = -zeta * w0 * y + e * wd * (b * cos - y0 * sin);
|
|
98
|
-
return;
|
|
99
|
-
}
|
|
100
|
-
case CRITICAL: {
|
|
101
|
-
const w0 = law[3];
|
|
102
|
-
const e = timed.e;
|
|
103
|
-
const b = v0 + w0 * y0;
|
|
104
|
-
const y = e * (y0 + b * t);
|
|
105
|
-
solved.y = y;
|
|
106
|
-
solved.dy = e * b - w0 * y;
|
|
107
|
-
return;
|
|
108
|
-
}
|
|
109
|
-
case OVER: {
|
|
110
|
-
const r1 = law[2];
|
|
111
|
-
const r2 = law[3];
|
|
112
|
-
const a = (v0 - r2 * y0) / (r1 - r2);
|
|
113
|
-
const b = y0 - a;
|
|
114
|
-
const e1 = timed.e;
|
|
115
|
-
const e2 = timed.e2;
|
|
116
|
-
solved.y = a * e1 + b * e2;
|
|
117
|
-
solved.dy = a * r1 * e1 + b * r2 * e2;
|
|
118
|
-
return;
|
|
119
|
-
}
|
|
120
|
-
default: {
|
|
121
|
-
// Released at x moving at v, a glide comes to rest at x + v·τ; y is the distance still to go,
|
|
122
|
-
// which friction closes as e^(−t/τ), and its derivative at release is v again.
|
|
123
|
-
const tau = law[2];
|
|
124
|
-
const e = timed.e;
|
|
125
|
-
solved.y = y0 * e;
|
|
126
|
-
solved.dy = (-y0 / tau) * e;
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
}
|
|
3
|
+
import { Motions } from './motions.js';
|
|
130
4
|
const axes = (v) => (typeof v === 'number' ? [v] : [...v]);
|
|
131
5
|
/** Zero on every axis `x` has: a motion's velocity where its options give none. */
|
|
132
6
|
const still = (x) => new Array(x.length).fill(0);
|
|
133
7
|
const per = (p, subject) => typeof p === 'function' ? p(subject) : p;
|
|
134
8
|
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'}`);
|
|
136
|
-
/**
|
|
137
|
-
* A motion patch's subjects: the stretch each is playing, in one flat array by a number the patch
|
|
138
|
-
* gives it, with earlier stretches a read back may reach and changes not yet applied beside them.
|
|
139
|
-
* The one copy, read by `at` and by a mix's lane alike.
|
|
140
|
-
*
|
|
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
|
|
143
|
-
* 10k springs of one subject each, a buffer per field, a map lookup per sample and the law in an
|
|
144
|
-
* object of its own took a call from 130 ns to 490.
|
|
145
|
-
*/
|
|
146
|
-
export class Motions {
|
|
147
|
-
shape;
|
|
148
|
-
numbers = new Numbers(this);
|
|
149
|
-
/** Numbers by subject once a second is numbered: till then the one subject is found by `numbers`. */
|
|
150
|
-
slots = new Store();
|
|
151
|
-
/** Axes per subject; -1 until the first subject sets it. */
|
|
152
|
-
n = -1;
|
|
153
|
-
cap = 0;
|
|
154
|
-
stride = 0;
|
|
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;
|
|
160
|
-
/**
|
|
161
|
-
* Where `sample` leaves a subject's position and velocity, per axis. Shared by every patch, so
|
|
162
|
-
* read it before the next sample.
|
|
163
|
-
*/
|
|
164
|
-
xs = unsized;
|
|
165
|
-
vs = unsized;
|
|
166
|
-
/**
|
|
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.
|
|
170
|
-
*/
|
|
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
|
-
}
|
|
200
|
-
constructor(shape) {
|
|
201
|
-
this.shape = shape;
|
|
202
|
-
}
|
|
203
|
-
/** The patch's number for `subject`, made with its first stretch on first ask. */
|
|
204
|
-
slot(subject) {
|
|
205
|
-
const known = this.known(subject);
|
|
206
|
-
if (known !== undefined)
|
|
207
|
-
return known;
|
|
208
|
-
const x = this.shape.from(subject);
|
|
209
|
-
const v = this.shape.velocity(subject, x);
|
|
210
|
-
const to = this.shape.aim(x, v, null, subject);
|
|
211
|
-
const n = this.n < 0 ? x.length : this.n;
|
|
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);
|
|
218
|
-
if (this.n < 0) {
|
|
219
|
-
this.n = n;
|
|
220
|
-
this.stride = 3 + 3 * n;
|
|
221
|
-
if (shared.xs.length < n)
|
|
222
|
-
shared = { xs: new Float64Array(n), vs: new Float64Array(n) };
|
|
223
|
-
this.xs = shared.xs;
|
|
224
|
-
this.vs = shared.vs;
|
|
225
|
-
}
|
|
226
|
-
const s = this.numbers.take(subject);
|
|
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);
|
|
234
|
-
this.grow(s + 1);
|
|
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 });
|
|
237
|
-
return s;
|
|
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
|
-
}
|
|
246
|
-
/** Forgets a subject, so a later ask starts it afresh from `from`. */
|
|
247
|
-
release(subject) {
|
|
248
|
-
const s = this.known(subject);
|
|
249
|
-
if (s === undefined)
|
|
250
|
-
return;
|
|
251
|
-
this.slots.delete(subject);
|
|
252
|
-
this.numbers.release(s);
|
|
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
|
-
}
|
|
298
|
-
/** Whether subject `s`'s value is a number rather than an array. */
|
|
299
|
-
scalar(s) {
|
|
300
|
-
return (this.runs[this.base(s) + 1] & SCALAR) !== 0;
|
|
301
|
-
}
|
|
302
|
-
/** The value `at` hands the mix: the first axis for a number, a fresh array otherwise. */
|
|
303
|
-
value(s, xs) {
|
|
304
|
-
if ((this.runs[this.base(s) + 1] & SCALAR) !== 0)
|
|
305
|
-
return xs[0];
|
|
306
|
-
const n = this.n;
|
|
307
|
-
const out = new Array(n);
|
|
308
|
-
for (let i = 0; i < n; i++)
|
|
309
|
-
out[i] = xs[i];
|
|
310
|
-
return out;
|
|
311
|
-
}
|
|
312
|
-
/**
|
|
313
|
-
* Position and velocity at voice time `t` into `xo` and `vo`, a tween's velocity left at 0. A live
|
|
314
|
-
* read stamps every untimed change with `t` and commits every change due by then, in time order;
|
|
315
|
-
* a projection's read applies the timed ones to a copy and commits nothing.
|
|
316
|
-
*/
|
|
317
|
-
sample(s, t, xo, vo) {
|
|
318
|
-
if (reading.live) {
|
|
319
|
-
const runs = this.runs;
|
|
320
|
-
const b = this.base(s);
|
|
321
|
-
if (runs[b + 1] & PENDING)
|
|
322
|
-
this.commit(s, t);
|
|
323
|
-
if (runs[b + 1] & OLDER)
|
|
324
|
-
this.prune(s);
|
|
325
|
-
const at = runs[b];
|
|
326
|
-
if (t < at && runs[b + 1] & OLDER)
|
|
327
|
-
this.evaluateSegment(this.playing(s, t), t, xo, vo);
|
|
328
|
-
else {
|
|
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);
|
|
332
|
-
}
|
|
333
|
-
return;
|
|
334
|
-
}
|
|
335
|
-
this.peek(s, t, xo, vo);
|
|
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
|
-
}
|
|
388
|
-
/**
|
|
389
|
-
* Whether a live `sample` at `t` would leave the subject's state as it is: nothing to stamp or
|
|
390
|
-
* commit by then, and nothing older than `reading.horizon` to let go of.
|
|
391
|
-
*/
|
|
392
|
-
quiet(s, t) {
|
|
393
|
-
const runs = this.runs;
|
|
394
|
-
const b = this.base(s);
|
|
395
|
-
const flags = runs[b + 1];
|
|
396
|
-
if (flags & PENDING) {
|
|
397
|
-
const list = this.pending[s];
|
|
398
|
-
const first = list[0].at;
|
|
399
|
-
if (first === undefined || first <= t || list[list.length - 1].at === undefined)
|
|
400
|
-
return false;
|
|
401
|
-
}
|
|
402
|
-
if (flags & OLDER) {
|
|
403
|
-
const list = this.older[s];
|
|
404
|
-
const next = list.length > 1 ? list[1].at : runs[b];
|
|
405
|
-
if (next <= reading.horizon)
|
|
406
|
-
return false;
|
|
407
|
-
}
|
|
408
|
-
return true;
|
|
409
|
-
}
|
|
410
|
-
/** Position and velocity at `at`, default the latest frame; undefined until there is one. */
|
|
411
|
-
read(subject, at) {
|
|
412
|
-
const s = this.known(subject);
|
|
413
|
-
if (s === undefined)
|
|
414
|
-
return undefined;
|
|
415
|
-
const when = at ?? this.frame(subject);
|
|
416
|
-
if (Number.isNaN(when))
|
|
417
|
-
return undefined;
|
|
418
|
-
const x = new Float64Array(this.n);
|
|
419
|
-
const v = new Float64Array(this.n);
|
|
420
|
-
sloped(true);
|
|
421
|
-
try {
|
|
422
|
-
this.peek(s, when, x, v);
|
|
423
|
-
}
|
|
424
|
-
finally {
|
|
425
|
-
sloped(false);
|
|
426
|
-
}
|
|
427
|
-
return { x, v, s };
|
|
428
|
-
}
|
|
429
|
-
/**
|
|
430
|
-
* Queues a retarget or push, untimed at the latest frame, for `subject`'s next read past its time.
|
|
431
|
-
* The queue keeps the changes with a time in time order, ahead of any still waiting for one.
|
|
432
|
-
*/
|
|
433
|
-
change(subject, c) {
|
|
434
|
-
this.owner?.revive(this.ownerId, subject);
|
|
435
|
-
const s = this.slot(subject);
|
|
436
|
-
this.check(c.to, this.n);
|
|
437
|
-
this.check(c.v, this.n);
|
|
438
|
-
if (c.at === undefined) {
|
|
439
|
-
const at = this.frame(subject);
|
|
440
|
-
if (!Number.isNaN(at))
|
|
441
|
-
c.at = at;
|
|
442
|
-
}
|
|
443
|
-
const list = this.pending?.[s];
|
|
444
|
-
if (list === undefined) {
|
|
445
|
-
if (this.pending === null)
|
|
446
|
-
this.pending = [undefined];
|
|
447
|
-
this.pending[s] = [c];
|
|
448
|
-
this.flag(s, PENDING, true);
|
|
449
|
-
}
|
|
450
|
-
else if (c.at === undefined)
|
|
451
|
-
list.push(c);
|
|
452
|
-
else
|
|
453
|
-
this.queue(list, c, c.at);
|
|
454
|
-
reading.moved++;
|
|
455
|
-
}
|
|
456
|
-
/** Files a change with a time after every one due by then, ahead of those still waiting for one. */
|
|
457
|
-
queue(list, c, at) {
|
|
458
|
-
let i = list.length;
|
|
459
|
-
while (i > 0) {
|
|
460
|
-
const before = list[i - 1].at;
|
|
461
|
-
if (before !== undefined && before <= at)
|
|
462
|
-
break;
|
|
463
|
-
i--;
|
|
464
|
-
}
|
|
465
|
-
list.splice(i, 0, c);
|
|
466
|
-
}
|
|
467
|
-
/** Every change due by `t` applied to a copy of the stretch playing then, committing nothing. */
|
|
468
|
-
peek(s, t, xo, vo) {
|
|
469
|
-
let seg = this.playing(s, t);
|
|
470
|
-
const list = this.pending?.[s];
|
|
471
|
-
if (list !== undefined)
|
|
472
|
-
for (const change of list) {
|
|
473
|
-
const a = change.at;
|
|
474
|
-
if (a === undefined || !(a <= t))
|
|
475
|
-
break;
|
|
476
|
-
if (a >= seg.at)
|
|
477
|
-
seg = this.applied(s, seg, change, a);
|
|
478
|
-
}
|
|
479
|
-
this.evaluateSegment(seg, t, xo, vo);
|
|
480
|
-
}
|
|
481
|
-
check(a, n) {
|
|
482
|
-
if (a !== undefined && a.length !== n)
|
|
483
|
-
throw new Error('blits: a motion patch moves every subject on the same number of axes');
|
|
484
|
-
}
|
|
485
|
-
flag(s, bit, on) {
|
|
486
|
-
this.watcher?.stretchChanged(this.watchId, s);
|
|
487
|
-
const i = this.base(s) + 1;
|
|
488
|
-
const flags = this.runs[i];
|
|
489
|
-
this.runs[i] = on ? flags | bit : flags & ~bit;
|
|
490
|
-
}
|
|
491
|
-
forget(s) {
|
|
492
|
-
if (this.pending !== null)
|
|
493
|
-
this.pending[s] = undefined;
|
|
494
|
-
if (this.older !== null)
|
|
495
|
-
this.older[s] = undefined;
|
|
496
|
-
}
|
|
497
|
-
/** Where subject `s`'s run starts in `runs`. */
|
|
498
|
-
base(s) {
|
|
499
|
-
return HEAD + s * this.stride;
|
|
500
|
-
}
|
|
501
|
-
grow(size) {
|
|
502
|
-
if (size <= this.cap)
|
|
503
|
-
return;
|
|
504
|
-
const cap = Math.max(size, this.cap * 2);
|
|
505
|
-
const runs = new Float64Array(HEAD + cap * this.stride);
|
|
506
|
-
runs.set(this.cap === 0 ? this.shape.law : this.runs);
|
|
507
|
-
this.runs = runs;
|
|
508
|
-
this.cap = cap;
|
|
509
|
-
}
|
|
510
|
-
write(s, seg) {
|
|
511
|
-
const n = this.n;
|
|
512
|
-
const b = this.base(s);
|
|
513
|
-
this.watcher?.stretchChanged(this.watchId, s);
|
|
514
|
-
this.runs[b] = seg.at;
|
|
515
|
-
this.runs[b + 1] = this.runs[b + 1] & ~LANDED;
|
|
516
|
-
this.runs[b + 2] = seg.ms;
|
|
517
|
-
for (let i = 0; i < n; i++) {
|
|
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];
|
|
521
|
-
}
|
|
522
|
-
}
|
|
523
|
-
/** The stretch subject `s` is playing now, as an object. */
|
|
524
|
-
latest(s) {
|
|
525
|
-
const n = this.n;
|
|
526
|
-
const x = this.base(s) + 3;
|
|
527
|
-
return {
|
|
528
|
-
at: this.runs[this.base(s)],
|
|
529
|
-
ms: this.runs[this.base(s) + 2],
|
|
530
|
-
x0: Array.from(this.runs.subarray(x, x + n)),
|
|
531
|
-
v0: Array.from(this.runs.subarray(x + n, x + 2 * n)),
|
|
532
|
-
to: Array.from(this.runs.subarray(x + 2 * n, x + 3 * n)),
|
|
533
|
-
};
|
|
534
|
-
}
|
|
535
|
-
/** The stretch playing at voice time `t`: the latest released by then, else the first. */
|
|
536
|
-
playing(s, t) {
|
|
537
|
-
const list = this.older?.[s];
|
|
538
|
-
if (list === undefined || t >= this.runs[this.base(s)])
|
|
539
|
-
return this.latest(s);
|
|
540
|
-
if (list[0].at > t)
|
|
541
|
-
return list[0];
|
|
542
|
-
let lo = 0;
|
|
543
|
-
let hi = list.length - 1;
|
|
544
|
-
while (lo < hi) {
|
|
545
|
-
const mid = (lo + hi + 1) >> 1;
|
|
546
|
-
if (list[mid].at <= t)
|
|
547
|
-
lo = mid;
|
|
548
|
-
else
|
|
549
|
-
hi = mid - 1;
|
|
550
|
-
}
|
|
551
|
-
return list[lo];
|
|
552
|
-
}
|
|
553
|
-
applied(s, seg, change, at) {
|
|
554
|
-
const x = new Float64Array(this.n);
|
|
555
|
-
const v = new Float64Array(this.n);
|
|
556
|
-
this.evaluateSegment(seg, at, x, v);
|
|
557
|
-
const xs = Array.from(x);
|
|
558
|
-
const vs = change.v ?? Array.from(v);
|
|
559
|
-
const subject = this.numbers.subject(s);
|
|
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 };
|
|
564
|
-
}
|
|
565
|
-
commit(s, t) {
|
|
566
|
-
const list = this.pending[s];
|
|
567
|
-
// The first read of a subject no frame had met: those waiting for a time take this one.
|
|
568
|
-
if (list[list.length - 1].at === undefined) {
|
|
569
|
-
let i = list.length;
|
|
570
|
-
while (i > 0 && list[i - 1].at === undefined)
|
|
571
|
-
i--;
|
|
572
|
-
for (const c of list.splice(i)) {
|
|
573
|
-
c.at = t;
|
|
574
|
-
this.queue(list, c, t);
|
|
575
|
-
}
|
|
576
|
-
}
|
|
577
|
-
while (list.length > 0) {
|
|
578
|
-
const change = list[0];
|
|
579
|
-
const a = change.at;
|
|
580
|
-
if (a > t)
|
|
581
|
-
break;
|
|
582
|
-
list.shift();
|
|
583
|
-
this.insert(s, this.applied(s, this.playing(s, a), change, a));
|
|
584
|
-
}
|
|
585
|
-
if (list.length === 0) {
|
|
586
|
-
this.pending[s] = undefined;
|
|
587
|
-
this.flag(s, PENDING, false);
|
|
588
|
-
}
|
|
589
|
-
}
|
|
590
|
-
insert(s, seg) {
|
|
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
|
-
}
|
|
599
|
-
if (seg.at >= this.runs[this.base(s)]) {
|
|
600
|
-
list.push(this.latest(s));
|
|
601
|
-
this.write(s, seg);
|
|
602
|
-
return;
|
|
603
|
-
}
|
|
604
|
-
let i = list.length;
|
|
605
|
-
while (i > 0 && list[i - 1].at > seg.at)
|
|
606
|
-
i--;
|
|
607
|
-
list.splice(i, 0, seg);
|
|
608
|
-
}
|
|
609
|
-
/** Lets go of stretches older than the one in force at `reading.horizon`. */
|
|
610
|
-
prune(s) {
|
|
611
|
-
const list = this.older?.[s];
|
|
612
|
-
if (list === undefined)
|
|
613
|
-
return;
|
|
614
|
-
while (list.length > 0 &&
|
|
615
|
-
(list.length > 1 ? list[1].at : this.runs[this.base(s)]) <=
|
|
616
|
-
reading.horizon)
|
|
617
|
-
list.shift();
|
|
618
|
-
if (list.length === 0) {
|
|
619
|
-
this.older[s] = undefined;
|
|
620
|
-
this.flag(s, OLDER, false);
|
|
621
|
-
}
|
|
622
|
-
}
|
|
623
|
-
evaluateSegment(seg, t, xo, vo) {
|
|
624
|
-
this.evaluate(seg.at, seg.ms, seg.x0, 0, seg.v0, 0, seg.to, 0, t, xo, vo);
|
|
625
|
-
}
|
|
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);
|
|
628
|
-
}
|
|
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;
|
|
656
|
-
}
|
|
657
|
-
return still;
|
|
658
|
-
}
|
|
659
9
|
/** One `writes` array per channel, shared by every motion patch that writes it. */
|
|
660
10
|
const writesOf = new Map();
|
|
661
11
|
/**
|