@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/mixer.js
CHANGED
|
@@ -1,22 +1,27 @@
|
|
|
1
1
|
import { blended, blendOf, laneRead, shareKept } from './blend.js';
|
|
2
|
-
import { aims, chain, linkable } from './chain.js';
|
|
2
|
+
import { aims, chain, linkable, relink } from './chain.js';
|
|
3
3
|
import { lerpInto } from './channels.js';
|
|
4
4
|
import { cue } from './cue.js';
|
|
5
5
|
import { beginFade, parting } from './fade.js';
|
|
6
|
-
import { apply, baseFor, clamp, fold, folded, foldLoci, foldWith,
|
|
6
|
+
import { apply, baseFor, clamp, contribution, fold, folded, foldLoci, foldWith, isRest, keyed, linked, near, owedBy, passes, read, slopeFor, tick, } from './fold.js';
|
|
7
7
|
import { held, shownOf, sinceOf, unreachedOf } from './held.js';
|
|
8
|
+
import { leave, releasing } from './history.js';
|
|
8
9
|
import { book, laneHost } from './hosts.js';
|
|
9
10
|
import { Lanes } from './lanes.js';
|
|
10
11
|
import { foldLocus } from './locus.js';
|
|
11
12
|
import { listed } from './marks.js';
|
|
12
|
-
import {
|
|
13
|
+
import { nextFrame } from './move.js';
|
|
13
14
|
import { ownerPatch } from './owner.js';
|
|
14
|
-
import {
|
|
15
|
-
import {
|
|
16
|
-
import { project } from './project.js';
|
|
15
|
+
import { Keys } from './paging.js';
|
|
16
|
+
import { projectAll } from './project.js';
|
|
17
17
|
import { keep, pull } from './pull.js';
|
|
18
18
|
import { Steps } from './relink.js';
|
|
19
|
+
import { Outs } from './revive.js';
|
|
20
|
+
import { seek } from './seek.js';
|
|
21
|
+
import { Fitting, refit } from './spans.js';
|
|
19
22
|
import { Store } from './store.js';
|
|
23
|
+
import { record } from './tape.js';
|
|
24
|
+
import { announce, Transport } from './transport.js';
|
|
20
25
|
import { none } from './voice.js';
|
|
21
26
|
import { base, capped, fadeOf, horizonFor, ownedBy, ownerBase, prime, weigh, weighOwned, } from './weigh.js';
|
|
22
27
|
// biome-ignore lint/suspicious/noUnsafeDeclarationMerging: every member of `Methods` is installed below
|
|
@@ -37,31 +42,20 @@ export class Mixer {
|
|
|
37
42
|
* a projection reading back, the copy in force then.
|
|
38
43
|
*/
|
|
39
44
|
hostLog = [];
|
|
40
|
-
/**
|
|
41
|
-
* Marks the host announced on a score: `at` in host time, so the mix's rate never moves one, and
|
|
42
|
-
* NaN until the next sync for one announced as now before any sync; `made` is the mix time it was
|
|
43
|
-
* announced, for a read back to know what was known.
|
|
44
|
-
*/
|
|
45
|
-
announced = [];
|
|
46
45
|
hostThen;
|
|
47
46
|
/** Each subject's last two poses and when they were probed, for `from: 'current'`. */
|
|
48
47
|
pose = new Store();
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
48
|
+
/** The clock this mix plays on, which also holds its tape and the marks announced on it. */
|
|
49
|
+
transport;
|
|
50
|
+
/** Its place among the mixes on its transport, in the order they joined. */
|
|
51
|
+
slot = -1;
|
|
52
|
+
/** Taken off its transport, so it takes no more calls until a seek puts it back. */
|
|
53
|
+
dropped = false;
|
|
54
|
+
name;
|
|
56
55
|
/** This mix's frame among every mix's: moved by a sync, and by a control change or drop within one. */
|
|
57
56
|
frame = nextFrame();
|
|
58
|
-
offset = 0;
|
|
59
|
-
/** The mix's own rate; null while it has never been set, when mix time is host time. */
|
|
60
|
-
pace = null;
|
|
61
57
|
/** Pending voices whose start the host gave, by its host time, kept where the rate puts it. */
|
|
62
58
|
pins = null;
|
|
63
|
-
last = Number.NaN;
|
|
64
|
-
rebasing = false;
|
|
65
59
|
wantsPose = false;
|
|
66
60
|
/** The pose `pull` folds a subject into where it cannot read straight from the lanes. */
|
|
67
61
|
scratch;
|
|
@@ -86,10 +80,32 @@ export class Mixer {
|
|
|
86
80
|
stirred = false;
|
|
87
81
|
/** Counts the changes `stir` records, which a frame's record of a probe is current only before. */
|
|
88
82
|
stirs = 0;
|
|
83
|
+
syncing = false;
|
|
84
|
+
stirFns = null;
|
|
89
85
|
/** Records a change to the mix's voices or controls. */
|
|
90
86
|
stir() {
|
|
91
|
-
this.stirred = true;
|
|
92
87
|
this.stirs++;
|
|
88
|
+
if (this.stirred)
|
|
89
|
+
return;
|
|
90
|
+
this.stirred = true;
|
|
91
|
+
if (this.syncing)
|
|
92
|
+
return;
|
|
93
|
+
const fns = this.stirFns;
|
|
94
|
+
if (fns !== null)
|
|
95
|
+
for (const fn of [...fns])
|
|
96
|
+
fn();
|
|
97
|
+
this.transport.woke();
|
|
98
|
+
}
|
|
99
|
+
onWake(fn) {
|
|
100
|
+
if (this.stirFns === null)
|
|
101
|
+
this.stirFns = [];
|
|
102
|
+
const fns = this.stirFns;
|
|
103
|
+
fns.push(fn);
|
|
104
|
+
return () => {
|
|
105
|
+
const i = fns.indexOf(fn);
|
|
106
|
+
if (i >= 0)
|
|
107
|
+
fns.splice(i, 1);
|
|
108
|
+
};
|
|
93
109
|
}
|
|
94
110
|
/**
|
|
95
111
|
* Per subject, the first record of the chain through every live voice that reaches it, or a stub
|
|
@@ -130,7 +146,7 @@ export class Mixer {
|
|
|
130
146
|
};
|
|
131
147
|
send = (event) => {
|
|
132
148
|
const { voice, subject } = this.sending;
|
|
133
|
-
if (voice === null || this.projecting)
|
|
149
|
+
if (voice === null || this.projecting || voice.setting.timestamp <= this.sentTo)
|
|
134
150
|
return;
|
|
135
151
|
this.sent.push({
|
|
136
152
|
timestamp: voice.setting.timestamp,
|
|
@@ -140,7 +156,9 @@ export class Mixer {
|
|
|
140
156
|
event,
|
|
141
157
|
});
|
|
142
158
|
};
|
|
143
|
-
/** The
|
|
159
|
+
/** The moment a seek went back to: what is stepped again up to it was sent the first time. */
|
|
160
|
+
sentTo = Number.NEGATIVE_INFINITY;
|
|
161
|
+
/** The weight `contribution` found besides the delta, read by the caller at once. */
|
|
144
162
|
w = 0;
|
|
145
163
|
names;
|
|
146
164
|
channels;
|
|
@@ -156,8 +174,14 @@ export class Mixer {
|
|
|
156
174
|
handles = null;
|
|
157
175
|
/** What `book` made, still booking; null while there is none. */
|
|
158
176
|
bookers = null;
|
|
177
|
+
/** The fit a span being cued takes, which `cue` hands its voice. */
|
|
178
|
+
fitting = null;
|
|
159
179
|
/** Every owner still in the mix, null until one is cued. */
|
|
160
180
|
owners = null;
|
|
181
|
+
/** With a history store, the key each subject is paged under; null without one. */
|
|
182
|
+
keys = null;
|
|
183
|
+
/** With a history store, the voices it paged out whose handles are still held; null without one. */
|
|
184
|
+
outs = null;
|
|
161
185
|
constructor(kit, opts) {
|
|
162
186
|
this.kit = kit;
|
|
163
187
|
this.opts = opts;
|
|
@@ -169,6 +193,75 @@ export class Mixer {
|
|
|
169
193
|
this.slotOf.set(k, i);
|
|
170
194
|
});
|
|
171
195
|
this.lanes = opts.lanes === false ? null : new Lanes(laneHost(this));
|
|
196
|
+
this.name = opts.name;
|
|
197
|
+
const shared = opts.transport;
|
|
198
|
+
if (shared !== undefined) {
|
|
199
|
+
if (opts.history !== undefined)
|
|
200
|
+
throw new Error('blits: a mix on a transport keeps the history the transport keeps, not its own');
|
|
201
|
+
this.opts = { ...opts, history: shared.history };
|
|
202
|
+
this.transport = shared;
|
|
203
|
+
}
|
|
204
|
+
else
|
|
205
|
+
this.transport = new Transport(opts.history, false);
|
|
206
|
+
if (this.transport.pager !== null) {
|
|
207
|
+
this.keys = new Keys(opts.keyOf);
|
|
208
|
+
this.outs = new Outs();
|
|
209
|
+
if (shared !== undefined)
|
|
210
|
+
named(shared, opts.name);
|
|
211
|
+
}
|
|
212
|
+
this.transport.join(this);
|
|
213
|
+
}
|
|
214
|
+
/** Throws where the clock is a shared transport's, which only the transport moves. */
|
|
215
|
+
own(what) {
|
|
216
|
+
if (this.transport.shared)
|
|
217
|
+
throw new Error(`blits: ${this.name ?? 'this mix'} plays on a transport, so ${what} goes to the transport`);
|
|
218
|
+
return this.transport;
|
|
219
|
+
}
|
|
220
|
+
// The transport's clock, read and written as the mix's own by every module that moves it.
|
|
221
|
+
get now() {
|
|
222
|
+
return this.transport.now;
|
|
223
|
+
}
|
|
224
|
+
set now(v) {
|
|
225
|
+
this.transport.now = v;
|
|
226
|
+
}
|
|
227
|
+
get u() {
|
|
228
|
+
return this.transport.u;
|
|
229
|
+
}
|
|
230
|
+
set u(v) {
|
|
231
|
+
this.transport.u = v;
|
|
232
|
+
}
|
|
233
|
+
get offset() {
|
|
234
|
+
return this.transport.offset;
|
|
235
|
+
}
|
|
236
|
+
set offset(v) {
|
|
237
|
+
this.transport.offset = v;
|
|
238
|
+
}
|
|
239
|
+
get pace() {
|
|
240
|
+
return this.transport.pace;
|
|
241
|
+
}
|
|
242
|
+
set pace(v) {
|
|
243
|
+
this.transport.pace = v;
|
|
244
|
+
}
|
|
245
|
+
get announced() {
|
|
246
|
+
return this.transport.announced;
|
|
247
|
+
}
|
|
248
|
+
set announced(v) {
|
|
249
|
+
this.transport.announced = v;
|
|
250
|
+
}
|
|
251
|
+
get nextId() {
|
|
252
|
+
return this.transport.nextId;
|
|
253
|
+
}
|
|
254
|
+
set nextId(v) {
|
|
255
|
+
this.transport.nextId = v;
|
|
256
|
+
}
|
|
257
|
+
get last() {
|
|
258
|
+
return this.transport.last;
|
|
259
|
+
}
|
|
260
|
+
get tape() {
|
|
261
|
+
return this.transport.tape;
|
|
262
|
+
}
|
|
263
|
+
get replaying() {
|
|
264
|
+
return this.transport.replaying;
|
|
172
265
|
}
|
|
173
266
|
get reduced() {
|
|
174
267
|
const r = this.opts.reduce;
|
|
@@ -178,6 +271,7 @@ export class Mixer {
|
|
|
178
271
|
return this.opts.band ?? { on: 0.6, off: 0.4 };
|
|
179
272
|
}
|
|
180
273
|
cue(spec) {
|
|
274
|
+
this.attached();
|
|
181
275
|
const engine = this.opts.engine ?? mixer;
|
|
182
276
|
if (!engine.runs.has(spec.patch.form))
|
|
183
277
|
throw new Error(`blits: engine ${engine.name} does not run ${spec.patch.form} patches`);
|
|
@@ -199,29 +293,19 @@ export class Mixer {
|
|
|
199
293
|
throw new Error('blits: an owner does not loop: a pass would have to restart its children');
|
|
200
294
|
return this.cue({ ...spec, patch: ownerPatch });
|
|
201
295
|
}
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
const pace = this.pace;
|
|
209
|
-
const now = pace === null ? u : pace.sync(u);
|
|
210
|
-
const later = u !== this.u;
|
|
211
|
-
const still = later && now === this.now;
|
|
212
|
-
this.u = u;
|
|
213
|
-
// Host time moving while the mix clock stands still lands what waits on host time or the host.
|
|
214
|
-
if (now !== this.now || (later && pace !== null && waits(this)))
|
|
215
|
-
move(this, now);
|
|
216
|
-
// It is a frame too, which asks every weight signal again, so one reading input follows it.
|
|
217
|
-
if (still) {
|
|
218
|
-
this.frame = nextFrame();
|
|
219
|
-
this.lanes?.refill();
|
|
296
|
+
span(spec) {
|
|
297
|
+
this.fitting = new Fitting(spec);
|
|
298
|
+
try {
|
|
299
|
+
const handle = this.owns(spec);
|
|
300
|
+
refit(this, this.owners?.find((v) => v.handle === handle));
|
|
301
|
+
return handle;
|
|
220
302
|
}
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
303
|
+
finally {
|
|
304
|
+
this.fitting = null;
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
sync(timestamp) {
|
|
308
|
+
this.own('sync').sync(timestamp);
|
|
225
309
|
}
|
|
226
310
|
book(opts) {
|
|
227
311
|
return book(this, opts);
|
|
@@ -237,20 +321,25 @@ export class Mixer {
|
|
|
237
321
|
pull(subjects, into) {
|
|
238
322
|
pull(this, subjects, into);
|
|
239
323
|
}
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
324
|
+
seek(time) {
|
|
325
|
+
if (this.projecting)
|
|
326
|
+
throw new Error('blits: a projection does not seek');
|
|
327
|
+
seek(this.own('seek'), time);
|
|
328
|
+
}
|
|
329
|
+
prepare(time) {
|
|
330
|
+
return this.own('prepare').prepare(time);
|
|
331
|
+
}
|
|
332
|
+
project(time) {
|
|
333
|
+
return projectAll(this.transport, time).of(this);
|
|
243
334
|
}
|
|
244
335
|
announce(name, opts = {}) {
|
|
245
|
-
|
|
246
|
-
this.
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
order: this.nextId++,
|
|
253
|
-
});
|
|
336
|
+
this.attached();
|
|
337
|
+
announce(this.transport, name, opts, this.slot, this.name);
|
|
338
|
+
}
|
|
339
|
+
/** Throws for a mix taken off its transport. */
|
|
340
|
+
attached() {
|
|
341
|
+
if (this.dropped)
|
|
342
|
+
throw new Error(`blits: ${this.name ?? 'this mix'} was dropped from its transport`);
|
|
254
343
|
}
|
|
255
344
|
marks(from, to) {
|
|
256
345
|
return listed(this, from, to).map(({ order: _, ...m }) => m);
|
|
@@ -288,7 +377,7 @@ export class Mixer {
|
|
|
288
377
|
return true;
|
|
289
378
|
}
|
|
290
379
|
rebase() {
|
|
291
|
-
this.
|
|
380
|
+
this.own('rebase').rebase();
|
|
292
381
|
}
|
|
293
382
|
voices(tag) {
|
|
294
383
|
const out = [];
|
|
@@ -313,38 +402,26 @@ export class Mixer {
|
|
|
313
402
|
return this.cued.every((v) => this.still(v, this.now));
|
|
314
403
|
}
|
|
315
404
|
get rate() {
|
|
316
|
-
return this.
|
|
405
|
+
return this.transport.rate;
|
|
317
406
|
}
|
|
318
407
|
set rate(r) {
|
|
319
|
-
this.ramp(r, 0);
|
|
408
|
+
this.own('rate').ramp(r, 0);
|
|
320
409
|
}
|
|
321
410
|
ramp(rate, over) {
|
|
322
|
-
|
|
323
|
-
throw new RangeError(`blits: a mix's rate is a finite number, 0 or more, not ${rate}`);
|
|
324
|
-
if (this.pace === null) {
|
|
325
|
-
this.pace = new Pace(this.opts.history !== undefined);
|
|
326
|
-
// Until now mix time was host time, so a pending voice's start is the host time it was given.
|
|
327
|
-
for (const v of this.cued)
|
|
328
|
-
if (v.state === 'pending' && !v.placing && v.owner === null && v.spec.start !== undefined)
|
|
329
|
-
pin(this, v, v.start);
|
|
330
|
-
}
|
|
331
|
-
const history = this.opts.history;
|
|
332
|
-
const reach = history === undefined ? Number.NEGATIVE_INFINITY : this.now - history.ms;
|
|
333
|
-
this.pace.change(this.u, rate, over, reach);
|
|
334
|
-
this.stir();
|
|
411
|
+
this.own('ramp').ramp(rate, over);
|
|
335
412
|
}
|
|
336
413
|
/** Whether a voice will change no pose from `now` on, short of a change made to it. */
|
|
337
414
|
still(voice, now) {
|
|
338
415
|
if (voice.state === 'done')
|
|
339
416
|
return true;
|
|
340
|
-
if (voice.state !== '
|
|
417
|
+
if (voice.state !== 'frozen' && voice.state !== 'live')
|
|
341
418
|
return false;
|
|
342
419
|
if (typeof voice.spec.weight === 'function' || voice.parts !== null)
|
|
343
420
|
return false;
|
|
344
421
|
const fadeIn = this.reducedNow ? 0 : (voice.fade.in ?? 0);
|
|
345
422
|
if (fadeIn > 0 && now - this.sinceOf(voice, voice.latest) < fadeIn)
|
|
346
423
|
return false;
|
|
347
|
-
if (voice.state === '
|
|
424
|
+
if (voice.state === 'frozen' || voice.holding !== null)
|
|
348
425
|
return true;
|
|
349
426
|
const motion = voice.motion;
|
|
350
427
|
return motion !== undefined && voice.elapsedAt(now) >= voice.latest && motion.landed();
|
|
@@ -352,6 +429,7 @@ export class Mixer {
|
|
|
352
429
|
mute(opts) {
|
|
353
430
|
for (const voice of this.cued)
|
|
354
431
|
beginFade(this, voice, { over: opts?.over });
|
|
432
|
+
record(this, 'mute', () => this.mute(opts));
|
|
355
433
|
}
|
|
356
434
|
drop(subject) {
|
|
357
435
|
this.frame = nextFrame();
|
|
@@ -369,9 +447,11 @@ export class Mixer {
|
|
|
369
447
|
this.stir();
|
|
370
448
|
// A record of it is only in a voice over every subject, one naming it, or one gone; a ramp out
|
|
371
449
|
// of a voice that does not name it is in `parters`, and a patch's own state in `motions`.
|
|
450
|
+
const kept = releasing(this);
|
|
372
451
|
const forget = (voice) => {
|
|
452
|
+
leave(this, voice, subject, voice.subjects.get(subject));
|
|
373
453
|
voice.subjects.delete(subject);
|
|
374
|
-
voice.motion?.release(subject);
|
|
454
|
+
voice.motion?.release(subject, ...kept);
|
|
375
455
|
voice.parts?.delete(subject);
|
|
376
456
|
voice.parted?.delete(subject);
|
|
377
457
|
voice.blend?.of.reads.delete(subject);
|
|
@@ -399,10 +479,11 @@ export class Mixer {
|
|
|
399
479
|
const strays = this.strays.get(subject);
|
|
400
480
|
if (strays !== undefined)
|
|
401
481
|
for (const motion of strays)
|
|
402
|
-
motion.release(subject);
|
|
482
|
+
motion.release(subject, ...kept);
|
|
403
483
|
this.strays.delete(subject);
|
|
404
484
|
for (const motion of this.strayAll)
|
|
405
|
-
motion.release(subject);
|
|
485
|
+
motion.release(subject, ...kept);
|
|
486
|
+
record(this, 'drop', () => this.drop(subject));
|
|
406
487
|
}
|
|
407
488
|
drain(tag) {
|
|
408
489
|
const all = this.sent;
|
|
@@ -463,7 +544,7 @@ const methods = {
|
|
|
463
544
|
owedBy,
|
|
464
545
|
foldLoci,
|
|
465
546
|
read,
|
|
466
|
-
|
|
547
|
+
contribution,
|
|
467
548
|
keyed,
|
|
468
549
|
tick,
|
|
469
550
|
apply,
|
|
@@ -486,6 +567,7 @@ const methods = {
|
|
|
486
567
|
shareKept,
|
|
487
568
|
parting,
|
|
488
569
|
chain,
|
|
570
|
+
relink,
|
|
489
571
|
linkable,
|
|
490
572
|
aims,
|
|
491
573
|
held,
|
|
@@ -497,6 +579,14 @@ const methods = {
|
|
|
497
579
|
};
|
|
498
580
|
for (const [name, value] of Object.entries(methods))
|
|
499
581
|
Object.defineProperty(Mixer.prototype, name, { value, writable: true, configurable: true });
|
|
582
|
+
/** Refuses a mix joining a transport with a store unnamed, or under a name history still keys records by. */
|
|
583
|
+
function named(transport, name) {
|
|
584
|
+
if (name === undefined)
|
|
585
|
+
throw new Error('blits: a mix on a transport whose history has a store needs a name, which its records are keyed by');
|
|
586
|
+
const taken = [...transport.members, ...transport.dropped.map((d) => d.mix)];
|
|
587
|
+
if (taken.some((m) => m.name === name))
|
|
588
|
+
throw new Error(`blits: a mix named ${name} is already on this transport, or a seek could bring it back`);
|
|
589
|
+
}
|
|
500
590
|
/**
|
|
501
591
|
* The one that ships: every form on the CPU, probed per subject on demand.
|
|
502
592
|
*
|
package/dist/motion.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import type { Easing,
|
|
1
|
+
import { Motions } from './motions.js';
|
|
2
|
+
import type { Easing, Patch } from './types.js';
|
|
3
3
|
/**
|
|
4
4
|
* A number, or one number per axis.
|
|
5
5
|
*
|
|
@@ -22,32 +22,6 @@ export interface Motion<V extends Value = number> {
|
|
|
22
22
|
/** Units per second. */
|
|
23
23
|
velocity: V;
|
|
24
24
|
}
|
|
25
|
-
interface Change {
|
|
26
|
-
/**
|
|
27
|
-
* Voice ms it takes effect at; absent, until the subject's next read stamps it with that read's
|
|
28
|
-
* time, for one made while no frame had met the subject.
|
|
29
|
-
*/
|
|
30
|
-
at?: number;
|
|
31
|
-
to?: number[];
|
|
32
|
-
v?: number[];
|
|
33
|
-
}
|
|
34
|
-
/**
|
|
35
|
-
* The mix playing a patch, asked with its voice's id: an owner and an id, where a pair of closures
|
|
36
|
-
* cost two functions, their contexts and a weak reference per voice.
|
|
37
|
-
*/
|
|
38
|
-
export interface MotionOwner {
|
|
39
|
-
/** The subject's voice time at the mix's latest frame; NaN where no frame of the voice met it. */
|
|
40
|
-
frame(id: number, subject: unknown): number;
|
|
41
|
-
/** Brings a subject the voice faded out back to it, so a change is not made for nothing. */
|
|
42
|
-
revive(id: number, subject: unknown): void;
|
|
43
|
-
}
|
|
44
|
-
/**
|
|
45
|
-
* What a mix keeping copies of a patch's stretches is told, with its id and the subject's number,
|
|
46
|
-
* before one changes.
|
|
47
|
-
*/
|
|
48
|
-
export interface Watcher {
|
|
49
|
-
stretchChanged(id: number, s: number): void;
|
|
50
|
-
}
|
|
51
25
|
/**
|
|
52
26
|
* A patch whose motion is a closed form per subject, so it lands in the same place at any frame
|
|
53
27
|
* rate, and whose changes mid-flight start from where the subject is and how fast it moves.
|
|
@@ -68,177 +42,6 @@ export type Moving<I, O, V extends Value> = Patch<I, O, void> & {
|
|
|
68
42
|
*/
|
|
69
43
|
push(subject: I, velocity: V, at?: number): void;
|
|
70
44
|
};
|
|
71
|
-
interface Shape<I> {
|
|
72
|
-
from: (subject: I) => number[];
|
|
73
|
-
/** How fast each axis starts moving, given where it starts: still unless the options say. */
|
|
74
|
-
velocity: (subject: I, x: number[]) => number[];
|
|
75
|
-
/** Where it heads when released from `x` at `v`, given where it was heading. */
|
|
76
|
-
aim: (x: number[], v: number[], was: number[] | null, subject: I) => number[];
|
|
77
|
-
/** `[form, settle, k1, k2, k3]`. */
|
|
78
|
-
law: readonly number[];
|
|
79
|
-
/** Whether a subject's value is a number rather than an array. */
|
|
80
|
-
scalar: (subject: I) => boolean;
|
|
81
|
-
/** Whether every subject's value is a number; undefined where that depends on the subject. */
|
|
82
|
-
scalarEvery(): boolean | undefined;
|
|
83
|
-
/** A tween's easing, which its law cannot hold; undefined, but present, on every other shape. */
|
|
84
|
-
ease: Curve | undefined;
|
|
85
|
-
/** A tween's seconds for a stretch the subject starts now; undefined, but present, on the rest. */
|
|
86
|
-
ms: ((subject: I) => number) | undefined;
|
|
87
|
-
/** The patch's kind and constants, as `Patch.motion` reports them. */
|
|
88
|
-
spec(): MotionSpec;
|
|
89
|
-
}
|
|
90
|
-
/**
|
|
91
|
-
* A motion patch's subjects: the stretch each is playing, in one flat array by a number the patch
|
|
92
|
-
* gives it, with earlier stretches a read back may reach and changes not yet applied beside them.
|
|
93
|
-
* The one copy, read by `at` and by a mix's lane alike.
|
|
94
|
-
*
|
|
95
|
-
* After the law, each subject's run of `stride` numbers is its release time, its flags, a tween's
|
|
96
|
-
* 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
|
|
97
|
-
* 10k springs of one subject each, a buffer per field, a map lookup per sample and the law in an
|
|
98
|
-
* object of its own took a call from 130 ns to 490.
|
|
99
|
-
*/
|
|
100
|
-
export declare class Motions<I> {
|
|
101
|
-
private readonly shape;
|
|
102
|
-
private readonly numbers;
|
|
103
|
-
/** Numbers by subject once a second is numbered: till then the one subject is found by `numbers`. */
|
|
104
|
-
private readonly slots;
|
|
105
|
-
/** Axes per subject; -1 until the first subject sets it. */
|
|
106
|
-
n: number;
|
|
107
|
-
private cap;
|
|
108
|
-
private stride;
|
|
109
|
-
/**
|
|
110
|
-
* The law, then each subject's run; empty until the first subject, the law read from `shape`.
|
|
111
|
-
* Read outside, at `bareAt`, and written only here.
|
|
112
|
-
*/
|
|
113
|
-
runs: Float64Array<ArrayBuffer>;
|
|
114
|
-
/**
|
|
115
|
-
* Where `sample` leaves a subject's position and velocity, per axis. Shared by every patch, so
|
|
116
|
-
* read it before the next sample.
|
|
117
|
-
*/
|
|
118
|
-
xs: Float64Array<ArrayBuffer>;
|
|
119
|
-
vs: Float64Array<ArrayBuffer>;
|
|
120
|
-
/**
|
|
121
|
-
* By subject number, changes not yet applied and earlier stretches a read back may reach: null
|
|
122
|
-
* until a subject first has one, since many patches are never changed. Each starts with room for
|
|
123
|
-
* one: an empty array written at 0 reserves 17, and most patches have one subject.
|
|
124
|
-
*/
|
|
125
|
-
private pending;
|
|
126
|
-
private older;
|
|
127
|
-
/**
|
|
128
|
-
* The mix playing the patch and its voice's id, which an untimed change and a `read` with no time
|
|
129
|
-
* ask for the latest frame. Set by the mix that cues the patch, and cleared when its voice retires.
|
|
130
|
-
*/
|
|
131
|
-
owner: MotionOwner | null;
|
|
132
|
-
ownerId: number;
|
|
133
|
-
/** Whether the channel it was cued on holds a number; undefined until it is cued. */
|
|
134
|
-
private holdsNumber;
|
|
135
|
-
private channel;
|
|
136
|
-
/**
|
|
137
|
-
* Records what the channel the patch is cued on holds, refusing a value of the other kind: at once
|
|
138
|
-
* where every subject's value is one kind, else on the first sample of a subject whose value is not.
|
|
139
|
-
*/
|
|
140
|
-
cuedOn(channel: string, holdsNumber: boolean): void;
|
|
141
|
-
private frame;
|
|
142
|
-
constructor(shape: Shape<I>);
|
|
143
|
-
/** The patch's number for `subject`, made with its first stretch on first ask. */
|
|
144
|
-
slot(subject: I): number;
|
|
145
|
-
/** The patch's number for `subject`, undefined where it has none. */
|
|
146
|
-
private known;
|
|
147
|
-
/** Forgets a subject, so a later ask starts it afresh from `from`. */
|
|
148
|
-
release(subject: I): void;
|
|
149
|
-
/**
|
|
150
|
-
* Told, with `watchId`, whenever a subject's stretch or what is pending for it changes, so a mix
|
|
151
|
-
* keeping a copy of it knows to read it again. Set by the mix, cleared when its voice retires. An
|
|
152
|
-
* owner and an id, where a callback cost a closure and its context per patch.
|
|
153
|
-
*/
|
|
154
|
-
watcher: Watcher | null;
|
|
155
|
-
watchId: number;
|
|
156
|
-
/** The easing a tween's stretches follow; undefined for every other shape. */
|
|
157
|
-
get ease(): Curve | undefined;
|
|
158
|
-
/** The patch's kind and constants. */
|
|
159
|
-
spec(): MotionSpec;
|
|
160
|
-
/** A copy of the patch's law, `[form, settle, k1, k2, k3]`. */
|
|
161
|
-
law(): Float64Array;
|
|
162
|
-
/** Whether `law` holds this patch's law, number for number, as `Object.is` compares them. */
|
|
163
|
-
hasLaw(law: Float64Array): boolean;
|
|
164
|
-
/**
|
|
165
|
-
* Copies subject `s`'s current stretch into `out` from `o` as its release time, its seconds, then
|
|
166
|
-
* `x0`, `v0` and `to` per axis; false, copying nothing, while it has a change pending or an
|
|
167
|
-
* earlier stretch kept, which only `sample` handles.
|
|
168
|
-
*/
|
|
169
|
-
stretchInto(s: number, out: Float64Array, o: number): boolean;
|
|
170
|
-
/** Whether subject `s`'s value is a number rather than an array. */
|
|
171
|
-
scalar(s: number): boolean;
|
|
172
|
-
/** The value `at` hands the mix: the first axis for a number, a fresh array otherwise. */
|
|
173
|
-
value(s: number, xs: Float64Array): number | number[];
|
|
174
|
-
/**
|
|
175
|
-
* Position and velocity at voice time `t` into `xo` and `vo`, a tween's velocity left at 0. A live
|
|
176
|
-
* read stamps every untimed change with `t` and commits every change due by then, in time order;
|
|
177
|
-
* a projection's read applies the timed ones to a copy and commits nothing.
|
|
178
|
-
*/
|
|
179
|
-
sample(s: number, t: number, xo: Float64Array, vo: Float64Array): void;
|
|
180
|
-
/**
|
|
181
|
-
* Where subject `s`'s run starts in `runs` (its release time, then flags, seconds, `x0`, `v0`
|
|
182
|
-
* and `to`) while it has nothing pending and no earlier stretch kept; -1 for any other.
|
|
183
|
-
*/
|
|
184
|
-
bareAt(s: number): number;
|
|
185
|
-
/**
|
|
186
|
-
* A live `sample` at `t` for a subject with nothing pending and no earlier stretch kept, true
|
|
187
|
-
* then; false, writing nothing, for any other, which `quiet` and `sample` take instead.
|
|
188
|
-
*/
|
|
189
|
-
sampleBare(s: number, t: number, xo: Float64Array, vo: Float64Array): boolean;
|
|
190
|
-
/**
|
|
191
|
-
* Whether every subject has landed on its target at its voice time at the latest frame, as a
|
|
192
|
-
* sample there would find; one with a change waiting has not. Commits nothing.
|
|
193
|
-
*/
|
|
194
|
-
landed(): boolean;
|
|
195
|
-
/**
|
|
196
|
-
* Whether a live `sample` at `t` would leave the subject's state as it is: nothing to stamp or
|
|
197
|
-
* commit by then, and nothing older than `reading.horizon` to let go of.
|
|
198
|
-
*/
|
|
199
|
-
quiet(s: number, t: number): boolean;
|
|
200
|
-
/** Position and velocity at `at`, default the latest frame; undefined until there is one. */
|
|
201
|
-
read(subject: I, at?: number): {
|
|
202
|
-
x: Float64Array;
|
|
203
|
-
v: Float64Array;
|
|
204
|
-
s: number;
|
|
205
|
-
} | undefined;
|
|
206
|
-
/**
|
|
207
|
-
* Queues a retarget or push, untimed at the latest frame, for `subject`'s next read past its time.
|
|
208
|
-
* The queue keeps the changes with a time in time order, ahead of any still waiting for one.
|
|
209
|
-
*/
|
|
210
|
-
change(subject: I, c: Change): void;
|
|
211
|
-
/** Files a change with a time after every one due by then, ahead of those still waiting for one. */
|
|
212
|
-
private queue;
|
|
213
|
-
/** Every change due by `t` applied to a copy of the stretch playing then, committing nothing. */
|
|
214
|
-
private peek;
|
|
215
|
-
private check;
|
|
216
|
-
private flag;
|
|
217
|
-
forget(s: number): void;
|
|
218
|
-
/** Where subject `s`'s run starts in `runs`. */
|
|
219
|
-
base(s: number): number;
|
|
220
|
-
private grow;
|
|
221
|
-
private write;
|
|
222
|
-
/** The stretch subject `s` is playing now, as an object. */
|
|
223
|
-
private latest;
|
|
224
|
-
/** The stretch playing at voice time `t`: the latest released by then, else the first. */
|
|
225
|
-
private playing;
|
|
226
|
-
private applied;
|
|
227
|
-
private commit;
|
|
228
|
-
private insert;
|
|
229
|
-
/** Lets go of stretches older than the one in force at `reading.horizon`. */
|
|
230
|
-
private prune;
|
|
231
|
-
private evaluateSegment;
|
|
232
|
-
private evaluate;
|
|
233
|
-
}
|
|
234
|
-
/**
|
|
235
|
-
* A stretch `t` voice ms into it, released at `at` from `x0` moving at `v0` toward `to` (each read
|
|
236
|
-
* from its offset), into `xo` and `vo`: the one copy of the closed forms, which `law` (a patch's
|
|
237
|
-
* `[form, settle, k1, k2, k3]`, the head of a patch's buffer or a copy of it) picks between. A tween reads `ease` and
|
|
238
|
-
* `ms`, the stretch's length, dividing in milliseconds so a boundary lands exactly; every other
|
|
239
|
-
* form ignores them.
|
|
240
|
-
*/
|
|
241
|
-
export declare function closed(law: Float64Array, ease: Curve | undefined, n: number, at: number, ms: number, x0: ArrayLike<number>, x: number, v0: ArrayLike<number>, v: number, to: ArrayLike<number>, g: number, t: number, xo: Float64Array, vo: Float64Array): boolean;
|
|
242
45
|
/** The state behind a `'motion'` patch, for an engine that reads it directly. */
|
|
243
46
|
export declare function motionOf<I>(p: object): Motions<I> | undefined;
|
|
244
47
|
interface Common<I, V extends Value> {
|