@msb235/blits 0.10.0 → 0.11.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 +151 -0
- package/README.md +15 -4
- package/dist/blend.js +1 -5
- package/dist/book.d.ts +2 -0
- package/dist/book.js +27 -3
- package/dist/chain.d.ts +11 -0
- package/dist/chain.js +24 -2
- package/dist/clock.d.ts +14 -0
- package/dist/clock.js +9 -1
- package/dist/columns.d.ts +4 -5
- package/dist/columns.js +11 -5
- package/dist/crowd.d.ts +1 -1
- package/dist/crowd.js +4 -1
- package/dist/cue.js +52 -5
- package/dist/departed.d.ts +3 -2
- package/dist/departed.js +7 -4
- package/dist/due.d.ts +4 -0
- package/dist/due.js +16 -3
- package/dist/fade.d.ts +12 -5
- package/dist/fade.js +55 -17
- package/dist/fold.d.ts +1 -1
- package/dist/fold.js +53 -6
- package/dist/frames.d.ts +2 -0
- package/dist/frames.js +17 -0
- package/dist/handle.d.ts +5 -2
- package/dist/handle.js +20 -12
- package/dist/held.d.ts +8 -0
- package/dist/held.js +37 -6
- package/dist/history.d.ts +6 -0
- package/dist/history.js +15 -5
- package/dist/hosts.js +18 -6
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -1
- package/dist/keyfill.js +3 -1
- package/dist/lane.d.ts +1 -1
- package/dist/lane.js +3 -1
- package/dist/lanes.d.ts +8 -0
- package/dist/lanes.js +21 -4
- package/dist/lean.d.ts +15 -0
- package/dist/lean.js +23 -0
- package/dist/leavings.d.ts +6 -1
- package/dist/leavings.js +16 -2
- package/dist/marks.d.ts +7 -3
- package/dist/marks.js +102 -27
- package/dist/meet.js +1 -0
- package/dist/mixer.d.ts +21 -6
- package/dist/mixer.js +70 -29
- package/dist/motion.d.ts +4 -1
- package/dist/motion.js +16 -2
- package/dist/motions.d.ts +4 -0
- package/dist/motions.js +45 -11
- package/dist/move.js +28 -10
- package/dist/owner.d.ts +18 -2
- package/dist/owner.js +38 -2
- package/dist/pack.d.ts +1 -0
- package/dist/pack.js +3 -0
- package/dist/placed.d.ts +19 -0
- package/dist/placed.js +54 -0
- package/dist/project.d.ts +5 -2
- package/dist/project.js +236 -32
- package/dist/pull.js +37 -8
- package/dist/qualify.js +8 -2
- package/dist/reading.d.ts +6 -0
- package/dist/reading.js +6 -0
- package/dist/record.js +1 -0
- package/dist/restored.d.ts +7 -4
- package/dist/restored.js +13 -10
- package/dist/revive.d.ts +5 -3
- package/dist/revive.js +10 -3
- package/dist/rows.js +15 -5
- package/dist/seek.d.ts +7 -2
- package/dist/seek.js +90 -35
- package/dist/shared.d.ts +5 -0
- package/dist/shared.js +5 -0
- package/dist/signals.d.ts +10 -0
- package/dist/signals.js +17 -0
- package/dist/store.d.ts +11 -0
- package/dist/store.js +19 -0
- package/dist/strays.js +50 -2
- package/dist/tape.d.ts +29 -4
- package/dist/tape.js +49 -11
- package/dist/transport.d.ts +11 -2
- package/dist/transport.js +20 -9
- package/dist/types/mix.d.ts +39 -14
- package/dist/types/patch.d.ts +5 -1
- package/dist/types/place.d.ts +11 -4
- package/dist/types/transport.d.ts +12 -3
- package/dist/types/voice.d.ts +62 -4
- package/dist/unpage.d.ts +6 -1
- package/dist/unpage.js +34 -2
- package/dist/voice.d.ts +57 -11
- package/dist/voice.js +43 -3
- package/docs/schema.html +68 -36
- package/package.json +7 -2
package/dist/mixer.js
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import { blended, blendOf, laneRead, shareKept } from './blend.js';
|
|
2
|
-
import { aims, chain, linkable, relink } from './chain.js';
|
|
2
|
+
import { aims, chain, linkable, relink, unlink } from './chain.js';
|
|
3
3
|
import { lerpInto } from './channels.js';
|
|
4
4
|
import { cue } from './cue.js';
|
|
5
5
|
import { DepartedIndex } from './departed.js';
|
|
6
|
-
import { beginFade,
|
|
6
|
+
import { beginFade, leaveVoice, parting } from './fade.js';
|
|
7
7
|
import { apply, baseFor, clamp, contribution, fold, foldDetour, folded, foldLoci, foldSharers, foldWith, isRest, keyed, linked, near, owedBy, passes, read, shared, slopeFor, tick, } from './fold.js';
|
|
8
8
|
import { GoneIndex, goneIndex } from './gone.js';
|
|
9
9
|
import { held, shownOf, sinceOf, unreachedOf } from './held.js';
|
|
10
|
-
import {
|
|
10
|
+
import { releasing } from './history.js';
|
|
11
11
|
import { book, laneHost } from './hosts.js';
|
|
12
12
|
import { Lanes } from './lanes.js';
|
|
13
13
|
import { foldLocus } from './locus.js';
|
|
@@ -22,8 +22,8 @@ import { Outs } from './revive.js';
|
|
|
22
22
|
import { ScoreIndex } from './scored.js';
|
|
23
23
|
import { seek } from './seek.js';
|
|
24
24
|
import { Fitting, refit } from './spans.js';
|
|
25
|
-
import { Store } from './store.js';
|
|
26
|
-
import { record } from './tape.js';
|
|
25
|
+
import { Masked, Store } from './store.js';
|
|
26
|
+
import { record, unplayable } from './tape.js';
|
|
27
27
|
import { touch } from './touch.js';
|
|
28
28
|
import { announce, Transport } from './transport.js';
|
|
29
29
|
import { none } from './voice.js';
|
|
@@ -37,6 +37,7 @@ export const defaults = { lanes: true };
|
|
|
37
37
|
export class Mixer {
|
|
38
38
|
kit;
|
|
39
39
|
opts;
|
|
40
|
+
lanesOf;
|
|
40
41
|
cued = [];
|
|
41
42
|
/** Under `history`, voices that have left but that a read back may still reach. */
|
|
42
43
|
gone = [];
|
|
@@ -87,10 +88,15 @@ export class Mixer {
|
|
|
87
88
|
/** The last array `pull` read, by position, with each subject's chain head, to skip the lookup. */
|
|
88
89
|
pulled = [];
|
|
89
90
|
pulledHeads = [];
|
|
90
|
-
/**
|
|
91
|
+
/**
|
|
92
|
+
* Each remembered head's lane slot, -1 at a position with none, and the `version` and `relinks`
|
|
93
|
+
* they were all current at.
|
|
94
|
+
*/
|
|
91
95
|
pulledSlots = new Int32Array(0);
|
|
92
96
|
pulledVersion = Number.NaN;
|
|
93
97
|
pulledRelinks = -1;
|
|
98
|
+
/** What `pull` knows of each channel it has written, by name; none until the first pull. */
|
|
99
|
+
pullColumns = null;
|
|
94
100
|
/** Counts chains made stale one subject at a time, which `version` does not. */
|
|
95
101
|
relinks = 0;
|
|
96
102
|
/** Read once a sync, so a `reduce` function is not called per voice per subject. */
|
|
@@ -215,9 +221,12 @@ export class Mixer {
|
|
|
215
221
|
keys = null;
|
|
216
222
|
/** With a history store, the voices it paged out whose handles are still held; null without one. */
|
|
217
223
|
outs = null;
|
|
218
|
-
constructor(kit, opts
|
|
224
|
+
constructor(kit, opts,
|
|
225
|
+
/** The lanes an entry was built with; null in one that leaves them out. */
|
|
226
|
+
lanesOf) {
|
|
219
227
|
this.kit = kit;
|
|
220
228
|
this.opts = opts;
|
|
229
|
+
this.lanesOf = lanesOf;
|
|
221
230
|
this.names = Object.keys(kit);
|
|
222
231
|
this.channels = this.names.map((k) => kit[k]);
|
|
223
232
|
this.bounded = this.channels.flatMap((c, i) => (c.bounds ? [i] : []));
|
|
@@ -225,7 +234,10 @@ export class Mixer {
|
|
|
225
234
|
this.names.forEach((k, i) => {
|
|
226
235
|
this.slotOf.set(k, i);
|
|
227
236
|
});
|
|
228
|
-
|
|
237
|
+
if (lanesOf === null && opts.lanes === true)
|
|
238
|
+
throw new Error("blits: '@msb235/blits/lean' has no lanes; take mix from '@msb235/blits'");
|
|
239
|
+
this.lanes =
|
|
240
|
+
lanesOf !== null && (opts.lanes ?? defaults.lanes) ? new lanesOf(laneHost(this)) : null;
|
|
229
241
|
this.name = opts.name;
|
|
230
242
|
const shared = opts.transport;
|
|
231
243
|
if (shared !== undefined) {
|
|
@@ -290,6 +302,9 @@ export class Mixer {
|
|
|
290
302
|
get timestamp() {
|
|
291
303
|
return this.transport.last;
|
|
292
304
|
}
|
|
305
|
+
get seq() {
|
|
306
|
+
return this.transport.seq;
|
|
307
|
+
}
|
|
293
308
|
get last() {
|
|
294
309
|
return this.transport.last;
|
|
295
310
|
}
|
|
@@ -308,16 +323,31 @@ export class Mixer {
|
|
|
308
323
|
}
|
|
309
324
|
cue(spec) {
|
|
310
325
|
this.attached();
|
|
311
|
-
const engine = this.opts.engine
|
|
312
|
-
if (!engine.runs.has(spec.patch.form))
|
|
326
|
+
const engine = this.opts.engine;
|
|
327
|
+
if (engine !== undefined && !engine.runs.has(spec.patch.form))
|
|
313
328
|
throw new Error(`blits: engine ${engine.name} does not run ${spec.patch.form} patches`);
|
|
314
329
|
return cue(this, spec);
|
|
315
330
|
}
|
|
316
|
-
blend(
|
|
331
|
+
blend(members, by, spec = {}) {
|
|
317
332
|
const locus = `blend:${this.nextId}`;
|
|
318
|
-
const of = blendOf(by,
|
|
319
|
-
return
|
|
320
|
-
const
|
|
333
|
+
const of = blendOf(by, members.length - 1);
|
|
334
|
+
return members.map((member, i) => {
|
|
335
|
+
const own = {
|
|
336
|
+
...spec,
|
|
337
|
+
patch: member,
|
|
338
|
+
weight: by,
|
|
339
|
+
locus,
|
|
340
|
+
};
|
|
341
|
+
if ('patch' in member) {
|
|
342
|
+
own.patch = member.patch;
|
|
343
|
+
if (member.name !== undefined)
|
|
344
|
+
own.name = member.name;
|
|
345
|
+
if (member.tags !== undefined)
|
|
346
|
+
own.tags = member.tags;
|
|
347
|
+
if (member.hits !== undefined)
|
|
348
|
+
own.hits = member.hits;
|
|
349
|
+
}
|
|
350
|
+
const handle = this.cue(own);
|
|
321
351
|
const voice = this.cued[this.cued.length - 1];
|
|
322
352
|
voice.blend = { of, i };
|
|
323
353
|
of.members.push(voice);
|
|
@@ -330,6 +360,8 @@ export class Mixer {
|
|
|
330
360
|
return this.cue({ ...spec, patch: ownerPatch });
|
|
331
361
|
}
|
|
332
362
|
span(spec) {
|
|
363
|
+
if ((spec.rate ?? 1) < 0)
|
|
364
|
+
throw new Error('blits: a span lays out its children forward from its start, so it is not cued backward');
|
|
333
365
|
this.fitting = new Fitting(spec);
|
|
334
366
|
try {
|
|
335
367
|
const handle = this.owns(spec);
|
|
@@ -359,16 +391,16 @@ export class Mixer {
|
|
|
359
391
|
this.looked = true;
|
|
360
392
|
pull(this, subjects, into);
|
|
361
393
|
}
|
|
362
|
-
seek(
|
|
394
|
+
seek(to) {
|
|
363
395
|
if (this.projecting)
|
|
364
396
|
throw new Error('blits: a projection does not seek');
|
|
365
|
-
seek(this.own('seek'),
|
|
397
|
+
seek(this.own('seek'), to);
|
|
366
398
|
}
|
|
367
399
|
prepare(time) {
|
|
368
400
|
return this.own('prepare').prepare(time);
|
|
369
401
|
}
|
|
370
|
-
project(
|
|
371
|
-
return projectAll(this.transport,
|
|
402
|
+
project(at) {
|
|
403
|
+
return projectAll(this.transport, at).of(this);
|
|
372
404
|
}
|
|
373
405
|
announce(name, opts = {}) {
|
|
374
406
|
this.attached();
|
|
@@ -469,18 +501,19 @@ export class Mixer {
|
|
|
469
501
|
voice.written = true;
|
|
470
502
|
beginFade(this, voice, { over: opts?.over });
|
|
471
503
|
}
|
|
472
|
-
record(this, 'mute', () => this
|
|
504
|
+
record(this, 'mute', (stage) => stage.mix(this)?.mute(opts));
|
|
473
505
|
}
|
|
474
506
|
drop(subject) {
|
|
475
507
|
this.frame = nextFrame();
|
|
476
508
|
const head = this.chains.get(subject);
|
|
477
509
|
// Nothing that kept this head, such as `pull`'s remembered list, may take it as current again.
|
|
478
|
-
if (head !== undefined)
|
|
479
|
-
head
|
|
480
|
-
this.relinks++;
|
|
481
|
-
}
|
|
510
|
+
if (head !== undefined)
|
|
511
|
+
unlink(this, head);
|
|
482
512
|
if (head !== undefined && head.slot >= 0 && this.lanes !== null)
|
|
483
513
|
this.lanes.release(head.slot);
|
|
514
|
+
// A read ahead's copy reads the live mix's poses, and so hides one where the mix deletes it.
|
|
515
|
+
if (this.projecting && !(this.pose instanceof Masked))
|
|
516
|
+
this.pose = new Masked(this.pose);
|
|
484
517
|
this.pose.delete(subject);
|
|
485
518
|
this.restStamps?.delete(subject);
|
|
486
519
|
this.chains.delete(subject);
|
|
@@ -488,10 +521,18 @@ export class Mixer {
|
|
|
488
521
|
// A record of it is only in a voice over every subject, one naming it, or one gone; a ramp out
|
|
489
522
|
// of a voice that does not name it is in `parters`, and a patch's own state in `motions`.
|
|
490
523
|
const kept = releasing(this);
|
|
524
|
+
// A motion patch's state is the live mix's alone: a read ahead cannot start a subject afresh in it.
|
|
525
|
+
const release = (motion) => {
|
|
526
|
+
if (!this.projecting)
|
|
527
|
+
motion.release(subject, ...kept);
|
|
528
|
+
else if (motion.holds(subject))
|
|
529
|
+
unplayable('a drop of a subject a motion patch holds');
|
|
530
|
+
};
|
|
491
531
|
const forget = (voice) => {
|
|
492
|
-
|
|
532
|
+
leaveVoice(this, voice, subject);
|
|
493
533
|
voice.subjects.delete(subject);
|
|
494
|
-
voice.motion
|
|
534
|
+
if (voice.motion !== undefined)
|
|
535
|
+
release(voice.motion);
|
|
495
536
|
voice.parts?.delete(subject);
|
|
496
537
|
voice.parted?.delete(subject);
|
|
497
538
|
voice.blend?.of.reads.delete(subject);
|
|
@@ -524,11 +565,11 @@ export class Mixer {
|
|
|
524
565
|
const strays = this.strays.get(subject);
|
|
525
566
|
if (strays !== undefined)
|
|
526
567
|
for (const motion of strays)
|
|
527
|
-
|
|
568
|
+
release(motion);
|
|
528
569
|
this.strays.delete(subject);
|
|
529
570
|
for (const motion of this.strayAll)
|
|
530
|
-
|
|
531
|
-
record(this, 'drop', () => this
|
|
571
|
+
release(motion);
|
|
572
|
+
record(this, 'drop', (stage) => stage.mix(this)?.drop(subject));
|
|
532
573
|
}
|
|
533
574
|
touch(subject) {
|
|
534
575
|
this.attached();
|
|
@@ -648,7 +689,7 @@ export const mixer = {
|
|
|
648
689
|
name: 'mixer',
|
|
649
690
|
runs: new Set(['fn', 'keys', 'motion']),
|
|
650
691
|
create(kit, opts) {
|
|
651
|
-
return new Mixer(kit, opts);
|
|
692
|
+
return new Mixer(kit, opts, Lanes);
|
|
652
693
|
},
|
|
653
694
|
};
|
|
654
695
|
/**
|
package/dist/motion.d.ts
CHANGED
|
@@ -45,7 +45,10 @@ export type Moving<I, O, V extends Value> = Patch<I, O, void> & {
|
|
|
45
45
|
*/
|
|
46
46
|
push(subject: I, velocity: V, at?: number): void;
|
|
47
47
|
};
|
|
48
|
-
/**
|
|
48
|
+
/**
|
|
49
|
+
* The state behind a `'motion'` patch, for an engine that reads it directly; a patch another copy
|
|
50
|
+
* of the same release made counts.
|
|
51
|
+
*/
|
|
49
52
|
export declare function motionOf<I>(p: object): Motions<I> | undefined;
|
|
50
53
|
interface Common<I, V extends Value> {
|
|
51
54
|
/** Units per second each subject starts moving at. Default 0. */
|
package/dist/motion.js
CHANGED
|
@@ -1,11 +1,17 @@
|
|
|
1
1
|
import { COAST, CRITICAL, EASED, OVER, UNDER } from './closed.js';
|
|
2
2
|
import { curve } from './easing.js';
|
|
3
3
|
import { Motions } from './motions.js';
|
|
4
|
+
import { release } from './shared.js';
|
|
4
5
|
const axes = (v) => (typeof v === 'number' ? [v] : [...v]);
|
|
5
6
|
/** Zero on every axis `x` has: a motion's velocity where its options give none. */
|
|
6
7
|
const still = (x) => new Array(x.length).fill(0);
|
|
7
8
|
const per = (p, subject) => typeof p === 'function' ? p(subject) : p;
|
|
8
9
|
const scalarOf = (p) => typeof p === 'function' ? undefined : typeof p === 'number';
|
|
10
|
+
/**
|
|
11
|
+
* What marks a motion patch to every copy of blits of this release loaded in one realm, so a
|
|
12
|
+
* spring another copy made runs on this copy's lanes; it gives the patch's state.
|
|
13
|
+
*/
|
|
14
|
+
const MOTIONS = Symbol.for(`@msb235/blits/motions@${release}`);
|
|
9
15
|
/** One `writes` array per channel, shared by every motion patch that writes it. */
|
|
10
16
|
const writesOf = new Map();
|
|
11
17
|
/**
|
|
@@ -29,6 +35,9 @@ class MotionPatch {
|
|
|
29
35
|
get motion() {
|
|
30
36
|
return this.motions.spec();
|
|
31
37
|
}
|
|
38
|
+
get [MOTIONS]() {
|
|
39
|
+
return this.motions;
|
|
40
|
+
}
|
|
32
41
|
at(_phase, subject, setting) {
|
|
33
42
|
const state = this.motions;
|
|
34
43
|
const s = state.slot(subject);
|
|
@@ -58,9 +67,14 @@ class GlidePatch extends MotionPatch {
|
|
|
58
67
|
this.motions.change(subject, { at, v: axes(velocity) });
|
|
59
68
|
}
|
|
60
69
|
}
|
|
61
|
-
/**
|
|
70
|
+
/**
|
|
71
|
+
* The state behind a `'motion'` patch, for an engine that reads it directly; a patch another copy
|
|
72
|
+
* of the same release made counts.
|
|
73
|
+
*/
|
|
62
74
|
export function motionOf(p) {
|
|
63
|
-
return p instanceof MotionPatch
|
|
75
|
+
return p instanceof MotionPatch
|
|
76
|
+
? p.motions
|
|
77
|
+
: p[MOTIONS];
|
|
64
78
|
}
|
|
65
79
|
/**
|
|
66
80
|
* The shapes of the stock motions, each one object over the options it reads, copied out of them so
|
package/dist/motions.d.ts
CHANGED
|
@@ -167,6 +167,8 @@ export declare class Motions<I> {
|
|
|
167
167
|
private unrelease;
|
|
168
168
|
/** Subject `s`'s run as it stands. */
|
|
169
169
|
private runAt;
|
|
170
|
+
/** Whether the patch holds a run for `subject`. */
|
|
171
|
+
holds(subject: I): boolean;
|
|
170
172
|
/** A subject's run as plain data, for a voice leaving memory; undefined where it has none. */
|
|
171
173
|
pack(subject: I): Run | undefined;
|
|
172
174
|
/** Puts a subject's run back as `pack` or a release left it, replacing any it has now. */
|
|
@@ -244,6 +246,8 @@ export declare class Motions<I> {
|
|
|
244
246
|
private queue;
|
|
245
247
|
/** Every change due by `t` applied to a copy of the stretch playing then, committing nothing. */
|
|
246
248
|
private peek;
|
|
249
|
+
/** Subject `s`'s changes waiting, with those a read ahead's tape made filed among them by time. */
|
|
250
|
+
private dueTo;
|
|
247
251
|
private check;
|
|
248
252
|
private flag;
|
|
249
253
|
forget(s: number): void;
|
package/dist/motions.js
CHANGED
|
@@ -28,6 +28,17 @@ function ahead(segs, list) {
|
|
|
28
28
|
let shared = { xs: new Float64Array(4), vs: new Float64Array(4) };
|
|
29
29
|
/** What a patch's `xs` and `vs` are before its first subject sets its axes. */
|
|
30
30
|
const unsized = new Float64Array(0);
|
|
31
|
+
/** Where `applied` reads a stretch at the time a change lands; copied out before it returns. */
|
|
32
|
+
let landing = { x: new Float64Array(4), v: new Float64Array(4) };
|
|
33
|
+
/** Where `numbers` gathers what it copies, kept an array of doubles so every copy is one too. */
|
|
34
|
+
const gathered = [0.5];
|
|
35
|
+
/** `n` numbers of `from` from `at`, as an array of their own. */
|
|
36
|
+
function numbers(from, at, n) {
|
|
37
|
+
// `Array.from` walks a typed array by its iterator: about 500 B a call for three axes.
|
|
38
|
+
for (let i = 0; i < n; i++)
|
|
39
|
+
gathered[i] = from[at + i];
|
|
40
|
+
return gathered.slice(0, n);
|
|
41
|
+
}
|
|
31
42
|
/**
|
|
32
43
|
* A subject's flags: its value is a number; it has changes pending; it has older stretches; the
|
|
33
44
|
* stretch it is playing was found landed, which holds until another replaces it.
|
|
@@ -211,6 +222,10 @@ export class Motions {
|
|
|
211
222
|
pending: pending === undefined ? undefined : [...pending],
|
|
212
223
|
};
|
|
213
224
|
}
|
|
225
|
+
/** Whether the patch holds a run for `subject`. */
|
|
226
|
+
holds(subject) {
|
|
227
|
+
return this.known(subject) !== undefined;
|
|
228
|
+
}
|
|
214
229
|
/** A subject's run as plain data, for a voice leaving memory; undefined where it has none. */
|
|
215
230
|
pack(subject) {
|
|
216
231
|
const s = this.known(subject);
|
|
@@ -496,7 +511,7 @@ export class Motions {
|
|
|
496
511
|
/** Every change due by `t` applied to a copy of the stretch playing then, committing nothing. */
|
|
497
512
|
peek(s, t, xo, vo) {
|
|
498
513
|
let seg = this.playing(s, t);
|
|
499
|
-
const list = this.
|
|
514
|
+
const list = this.dueTo(s);
|
|
500
515
|
if (list !== undefined)
|
|
501
516
|
for (const change of list) {
|
|
502
517
|
const a = change.at;
|
|
@@ -507,6 +522,21 @@ export class Motions {
|
|
|
507
522
|
}
|
|
508
523
|
this.evaluateSegment(seg, t, xo, vo);
|
|
509
524
|
}
|
|
525
|
+
/** Subject `s`'s changes waiting, with those a read ahead's tape made filed among them by time. */
|
|
526
|
+
dueTo(s) {
|
|
527
|
+
const own = this.pending?.[s];
|
|
528
|
+
const ahead = reading.ahead?.get(this);
|
|
529
|
+
if (ahead === undefined)
|
|
530
|
+
return own;
|
|
531
|
+
const subject = this.numbers.subject(s);
|
|
532
|
+
const made = subject === absent ? undefined : ahead.get(subject);
|
|
533
|
+
if (made === undefined)
|
|
534
|
+
return own;
|
|
535
|
+
if (own === undefined)
|
|
536
|
+
return made;
|
|
537
|
+
// The patch's own were made first, and a stable sort keeps them first at one time.
|
|
538
|
+
return [...own.filter((c) => c.at !== undefined), ...made].sort((a, b) => a.at - b.at);
|
|
539
|
+
}
|
|
510
540
|
check(a, n) {
|
|
511
541
|
if (a !== undefined && a.length !== n)
|
|
512
542
|
throw new Error('blits: a motion patch moves every subject on the same number of axes');
|
|
@@ -611,9 +641,9 @@ export class Motions {
|
|
|
611
641
|
return {
|
|
612
642
|
at: this.runs[this.base(s)],
|
|
613
643
|
ms: this.runs[this.base(s) + 2],
|
|
614
|
-
x0:
|
|
615
|
-
v0:
|
|
616
|
-
to:
|
|
644
|
+
x0: numbers(this.runs, x, n),
|
|
645
|
+
v0: numbers(this.runs, x + n, n),
|
|
646
|
+
to: numbers(this.runs, x + 2 * n, n),
|
|
617
647
|
change: this.causes?.[s],
|
|
618
648
|
};
|
|
619
649
|
}
|
|
@@ -636,11 +666,13 @@ export class Motions {
|
|
|
636
666
|
return list[lo];
|
|
637
667
|
}
|
|
638
668
|
applied(s, seg, change, at) {
|
|
639
|
-
const
|
|
640
|
-
|
|
669
|
+
const n = this.n;
|
|
670
|
+
if (landing.x.length < n)
|
|
671
|
+
landing = { x: new Float64Array(n), v: new Float64Array(n) };
|
|
672
|
+
const { x, v } = landing;
|
|
641
673
|
this.evaluateSegment(seg, at, x, v);
|
|
642
|
-
const xs =
|
|
643
|
-
const vs = change.v ??
|
|
674
|
+
const xs = numbers(x, 0, n);
|
|
675
|
+
const vs = change.v ?? numbers(v, 0, n);
|
|
644
676
|
const subject = this.numbers.subject(s);
|
|
645
677
|
const known = subject === absent ? undefined : subject;
|
|
646
678
|
const to = this.shape.aim(xs, vs, change.to ?? seg.to, known);
|
|
@@ -695,8 +727,7 @@ export class Motions {
|
|
|
695
727
|
* The mix time and frame a stretch was in force by, which a store keys it under: when the change
|
|
696
728
|
* that ended it was made, which falls in the same life of the subject.
|
|
697
729
|
*/
|
|
698
|
-
made(
|
|
699
|
-
const c = next.change;
|
|
730
|
+
made(c) {
|
|
700
731
|
const made = c?.made;
|
|
701
732
|
if (made === undefined || Number.isNaN(made))
|
|
702
733
|
return { at: this.owner?.now() ?? Number.NaN, seq: this.owner?.seq() ?? Number.NaN };
|
|
@@ -717,7 +748,10 @@ export class Motions {
|
|
|
717
748
|
const out = list.splice(0, n);
|
|
718
749
|
const subject = this.numbers.subject(s);
|
|
719
750
|
if (this.owner !== null && subject !== absent)
|
|
720
|
-
this.owner.page(this.ownerId, 'stretch', subject, out.map((seg, i) =>
|
|
751
|
+
this.owner.page(this.ownerId, 'stretch', subject, out.map((seg, i) => {
|
|
752
|
+
const next = out[i + 1] ?? list[0];
|
|
753
|
+
return { ...this.made(next === undefined ? this.causes?.[s] : next.change), data: seg };
|
|
754
|
+
}));
|
|
721
755
|
if (list.length === 0) {
|
|
722
756
|
this.older[s] = undefined;
|
|
723
757
|
this.flag(s, OLDER, false);
|
package/dist/move.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { changed } from './chain.js';
|
|
2
2
|
import { elapsedWith } from './clock.js';
|
|
3
|
-
import { popDue, schedule } from './due.js';
|
|
3
|
+
import { byId, popDue, schedule } from './due.js';
|
|
4
4
|
import { detour } from './everyone.js';
|
|
5
5
|
import { beginFade, part, retire } from './fade.js';
|
|
6
6
|
import { expireGone, keepGone } from './gone.js';
|
|
@@ -66,7 +66,13 @@ function moveTo(mix, now) {
|
|
|
66
66
|
(voice.owner === null ? now : ownerReading(voice.owner, now)) >= voice.start)
|
|
67
67
|
begin(mix, voice, now);
|
|
68
68
|
// After it starts, so a fade due by now on a voice that started since the last frame begins.
|
|
69
|
-
|
|
69
|
+
// Not one set for after its passes run out, where a move across both would begin it first.
|
|
70
|
+
if ((voice.state === 'live' || voice.state === 'frozen') &&
|
|
71
|
+
voice.outAt <= now &&
|
|
72
|
+
!(voice.state === 'live' &&
|
|
73
|
+
!voice.freezesAfter &&
|
|
74
|
+
!voice.freezesBefore &&
|
|
75
|
+
voice.endsAt() < voice.outAt))
|
|
70
76
|
beginFade(mix, voice, { over: Number.isNaN(voice.outOver) ? undefined : voice.outOver }, Math.max(startOf(voice), voice.outAt));
|
|
71
77
|
if (voice.state !== 'pending') {
|
|
72
78
|
if (voice.turns)
|
|
@@ -155,11 +161,16 @@ function turn(mix, voice, now) {
|
|
|
155
161
|
voice.anchorNow = at;
|
|
156
162
|
noted(mix, voice);
|
|
157
163
|
}
|
|
158
|
-
/**
|
|
164
|
+
/**
|
|
165
|
+
* Past this many voices retiring in one frame, and more than one in `SPARSE` of the list, one pass
|
|
166
|
+
* over the list beats a search for each.
|
|
167
|
+
*/
|
|
159
168
|
const SEARCHED = 8;
|
|
169
|
+
const SPARSE = 32;
|
|
160
170
|
/**
|
|
161
|
-
* Takes the voices retired since the last prune out of the list, in list order. A few
|
|
162
|
-
* their id, which the list is in order of, so the voices still
|
|
171
|
+
* Takes the voices retired since the last prune out of the list, in list order. A few, or a small
|
|
172
|
+
* share of a long list, are found by their id, which the list is in order of, so the voices still
|
|
173
|
+
* in it are not read.
|
|
163
174
|
*/
|
|
164
175
|
function prune(mix) {
|
|
165
176
|
const retired = mix.retired;
|
|
@@ -167,9 +178,8 @@ function prune(mix) {
|
|
|
167
178
|
const voices = mix.cued;
|
|
168
179
|
let at = null;
|
|
169
180
|
// A projection's list holds copies of voices gone before it, which only a pass takes out.
|
|
170
|
-
if (retired.length <= SEARCHED && !mix.projecting) {
|
|
171
|
-
|
|
172
|
-
retired.sort((a, b) => a.id - b.id);
|
|
181
|
+
if ((retired.length <= SEARCHED || retired.length * SPARSE <= voices.length) && !mix.projecting) {
|
|
182
|
+
byId(retired);
|
|
173
183
|
at = [];
|
|
174
184
|
for (let k = 0; k < retired.length; k++) {
|
|
175
185
|
const voice = retired[k];
|
|
@@ -185,8 +195,16 @@ function prune(mix) {
|
|
|
185
195
|
if (at !== null) {
|
|
186
196
|
for (const voice of retired)
|
|
187
197
|
pruned = leave(mix, voice) || pruned;
|
|
188
|
-
|
|
189
|
-
|
|
198
|
+
// Closed up in one pass from the first to go: a splice for each moves the tail once a voice.
|
|
199
|
+
let kept = at[0];
|
|
200
|
+
let k = 0;
|
|
201
|
+
for (let i = kept; i < voices.length; i++) {
|
|
202
|
+
if (at[k] === i)
|
|
203
|
+
k++;
|
|
204
|
+
else
|
|
205
|
+
voices[kept++] = voices[i];
|
|
206
|
+
}
|
|
207
|
+
voices.length = kept;
|
|
190
208
|
}
|
|
191
209
|
else {
|
|
192
210
|
let kept = 0;
|
package/dist/owner.d.ts
CHANGED
|
@@ -9,6 +9,19 @@ export declare class Holding<V> {
|
|
|
9
9
|
/** How many it has held, and how many of those finished their passes. */
|
|
10
10
|
kids: number;
|
|
11
11
|
played: number;
|
|
12
|
+
/**
|
|
13
|
+
* When the children that left coast, as pairs of the frame one left in and the latest coast so
|
|
14
|
+
* far, mix time: a pair only where the latest grew, so a seek back cuts it to a prefix.
|
|
15
|
+
*/
|
|
16
|
+
coasts: number[];
|
|
17
|
+
/** The frame the first child to leave before its passes ended left in; Infinity for none. */
|
|
18
|
+
cutSeq: number;
|
|
19
|
+
/** The latest coast of a child that left; -Infinity for none. */
|
|
20
|
+
get coast(): number;
|
|
21
|
+
/** A child left in frame `seq`, coasting at `coast`, or before its passes ended for undefined. */
|
|
22
|
+
left(coast: number | undefined, seq: number): void;
|
|
23
|
+
/** Forgets the children that left after frame `seq`, for a seek or read back to it. */
|
|
24
|
+
back(seq: number): void;
|
|
12
25
|
}
|
|
13
26
|
/**
|
|
14
27
|
* What an owner's clock reads at mix time `now`, through every owner above it: -Infinity before it
|
|
@@ -41,5 +54,8 @@ export declare function heldByInput<I, O>(voice: Voice<I, O>): boolean;
|
|
|
41
54
|
export declare function ownWeight<I, O>(owner: Voice<I, O>, now: number, reduced: boolean, base: (owner: Voice<I, O>) => number): number;
|
|
42
55
|
/** What every owner above a voice multiplies into its weight. */
|
|
43
56
|
export declare function ownersWeight<I, O>(voice: Voice<I, O>, now: number, reduced: boolean, base: (owner: Voice<I, O>) => number): number;
|
|
44
|
-
/**
|
|
45
|
-
|
|
57
|
+
/**
|
|
58
|
+
* Points a projection's copies at each other's owners, each owner holding only copies; for a read
|
|
59
|
+
* back to frame `seq`, as its children that had left by then left it.
|
|
60
|
+
*/
|
|
61
|
+
export declare function relink<I, O>(copies: readonly Voice<I, O>[], seq?: number): void;
|
package/dist/owner.js
CHANGED
|
@@ -13,6 +13,35 @@ export class Holding {
|
|
|
13
13
|
/** How many it has held, and how many of those finished their passes. */
|
|
14
14
|
kids = 0;
|
|
15
15
|
played = 0;
|
|
16
|
+
/**
|
|
17
|
+
* When the children that left coast, as pairs of the frame one left in and the latest coast so
|
|
18
|
+
* far, mix time: a pair only where the latest grew, so a seek back cuts it to a prefix.
|
|
19
|
+
*/
|
|
20
|
+
coasts = [];
|
|
21
|
+
/** The frame the first child to leave before its passes ended left in; Infinity for none. */
|
|
22
|
+
cutSeq = Number.POSITIVE_INFINITY;
|
|
23
|
+
/** The latest coast of a child that left; -Infinity for none. */
|
|
24
|
+
get coast() {
|
|
25
|
+
const n = this.coasts.length;
|
|
26
|
+
return n === 0 ? Number.NEGATIVE_INFINITY : this.coasts[n - 1];
|
|
27
|
+
}
|
|
28
|
+
/** A child left in frame `seq`, coasting at `coast`, or before its passes ended for undefined. */
|
|
29
|
+
left(coast, seq) {
|
|
30
|
+
if (coast === undefined) {
|
|
31
|
+
if (seq < this.cutSeq)
|
|
32
|
+
this.cutSeq = seq;
|
|
33
|
+
}
|
|
34
|
+
else if (coast > this.coast)
|
|
35
|
+
this.coasts.push(seq, coast);
|
|
36
|
+
}
|
|
37
|
+
/** Forgets the children that left after frame `seq`, for a seek or read back to it. */
|
|
38
|
+
back(seq) {
|
|
39
|
+
const c = this.coasts;
|
|
40
|
+
while (c.length > 0 && c[c.length - 2] > seq)
|
|
41
|
+
c.length -= 2;
|
|
42
|
+
if (this.cutSeq > seq)
|
|
43
|
+
this.cutSeq = Number.POSITIVE_INFINITY;
|
|
44
|
+
}
|
|
16
45
|
}
|
|
17
46
|
/**
|
|
18
47
|
* What an owner's clock reads at mix time `now`, through every owner above it: -Infinity before it
|
|
@@ -103,8 +132,11 @@ export function ownersWeight(voice, now, reduced, base) {
|
|
|
103
132
|
w *= ownWeight(o, now, reduced, base);
|
|
104
133
|
return w;
|
|
105
134
|
}
|
|
106
|
-
/**
|
|
107
|
-
|
|
135
|
+
/**
|
|
136
|
+
* Points a projection's copies at each other's owners, each owner holding only copies; for a read
|
|
137
|
+
* back to frame `seq`, as its children that had left by then left it.
|
|
138
|
+
*/
|
|
139
|
+
export function relink(copies, seq) {
|
|
108
140
|
const byId = new Map();
|
|
109
141
|
for (const v of copies) {
|
|
110
142
|
byId.set(v.id, v);
|
|
@@ -112,6 +144,10 @@ export function relink(copies) {
|
|
|
112
144
|
const h = new Holding();
|
|
113
145
|
h.kids = v.holding.kids;
|
|
114
146
|
h.played = v.holding.played;
|
|
147
|
+
h.coasts = v.holding.coasts.slice();
|
|
148
|
+
h.cutSeq = v.holding.cutSeq;
|
|
149
|
+
if (seq !== undefined)
|
|
150
|
+
h.back(seq);
|
|
115
151
|
v.holding = h;
|
|
116
152
|
}
|
|
117
153
|
}
|
package/dist/pack.d.ts
CHANGED
|
@@ -12,6 +12,7 @@ export interface PackedRecord {
|
|
|
12
12
|
stepped: number;
|
|
13
13
|
ticks: number;
|
|
14
14
|
rebuilt: number;
|
|
15
|
+
copied?: true;
|
|
15
16
|
/**
|
|
16
17
|
* What `setting.keep` held, in the order its owners first kept it. An owner is code, which no
|
|
17
18
|
* store keeps, and a voice revived from a descriptor has new ones; the same code keeps in the same
|
package/dist/pack.js
CHANGED
|
@@ -18,6 +18,8 @@ export function packHeld(voice, h) {
|
|
|
18
18
|
rebuilt: h.rebuilt,
|
|
19
19
|
kept: kept.length === 0 ? null : kept.map(clone),
|
|
20
20
|
};
|
|
21
|
+
if (h.copied)
|
|
22
|
+
p.copied = true;
|
|
21
23
|
if (h.base !== undefined)
|
|
22
24
|
p.base = clone(h.base);
|
|
23
25
|
if (h.slope !== undefined)
|
|
@@ -42,6 +44,7 @@ export function unpackHeld(voice, p) {
|
|
|
42
44
|
h.stepped = p.stepped;
|
|
43
45
|
h.ticks = p.ticks;
|
|
44
46
|
h.rebuilt = p.rebuilt;
|
|
47
|
+
h.copied = p.copied === true;
|
|
45
48
|
if (p.kept !== null)
|
|
46
49
|
h.unkept = p.kept;
|
|
47
50
|
if (p.base !== undefined)
|
package/dist/placed.d.ts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* By subject number, the crowd voices whose rows are placed on it, so letting a number go reaches
|
|
3
|
+
* its rows without walking every crowd's. Kept by voice id, which a row keeps as a crowd closes up
|
|
4
|
+
* or is rebuilt around it.
|
|
5
|
+
*/
|
|
6
|
+
export declare class Placed {
|
|
7
|
+
/** By subject number, one voice placed on it; 0, which is no voice's id, for none. */
|
|
8
|
+
private one;
|
|
9
|
+
/** The others, for a subject with several; null until some subject has two. */
|
|
10
|
+
private rest;
|
|
11
|
+
/** Makes room for subject numbers below `cap`. */
|
|
12
|
+
grow(cap: number): void;
|
|
13
|
+
/** Some voice placed on the subject at `slot`, 0 for none. */
|
|
14
|
+
any(slot: number): number;
|
|
15
|
+
/** Every voice placed on the subject at `slot`, in no order: for a test to read. */
|
|
16
|
+
all(slot: number): number[];
|
|
17
|
+
add(slot: number, id: number): void;
|
|
18
|
+
remove(slot: number, id: number): void;
|
|
19
|
+
}
|