@msb235/blits 0.7.0 → 0.8.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 +196 -0
- package/README.md +22 -4
- package/dist/book.js +4 -4
- package/dist/chain.d.ts +1 -1
- package/dist/chain.js +57 -54
- package/dist/channels.d.ts +26 -5
- package/dist/channels.js +12 -8
- package/dist/clock.d.ts +8 -1
- package/dist/clock.js +14 -3
- package/dist/clone.d.ts +3 -0
- package/dist/clone.js +16 -1
- package/dist/closed.d.ts +1 -1
- package/dist/closed.js +3 -2
- package/dist/color.d.ts +0 -11
- package/dist/color.js +4 -24
- package/dist/columns.js +0 -1
- package/dist/copyout.d.ts +6 -0
- package/dist/copyout.js +38 -0
- package/dist/crowd.d.ts +7 -5
- package/dist/crowd.js +32 -18
- package/dist/cue.d.ts +3 -1
- package/dist/cue.js +33 -20
- package/dist/departed.d.ts +34 -0
- package/dist/departed.js +73 -0
- package/dist/easing.d.ts +5 -0
- package/dist/easing.js +31 -2
- package/dist/everyone.d.ts +56 -0
- package/dist/everyone.js +221 -0
- package/dist/fade.d.ts +8 -1
- package/dist/fade.js +36 -6
- package/dist/fill.d.ts +22 -13
- package/dist/fill.js +104 -41
- package/dist/fit.js +2 -1
- package/dist/fold.d.ts +37 -0
- package/dist/fold.js +213 -28
- package/dist/gather.d.ts +15 -9
- package/dist/gather.js +60 -25
- package/dist/gone.d.ts +32 -0
- package/dist/gone.js +144 -0
- package/dist/handle.d.ts +16 -5
- package/dist/handle.js +56 -6
- package/dist/held.d.ts +8 -5
- package/dist/held.js +56 -58
- package/dist/history.d.ts +17 -13
- package/dist/history.js +49 -28
- package/dist/hosts.js +57 -9
- package/dist/index.d.ts +9 -4
- package/dist/index.js +5 -2
- package/dist/keyfill.d.ts +7 -0
- package/dist/keyfill.js +124 -0
- package/dist/keyrows.d.ts +1 -0
- package/dist/keyrows.js +1 -1
- package/dist/lane.d.ts +32 -2
- package/dist/lane.js +5 -0
- package/dist/lanes.d.ts +32 -8
- package/dist/lanes.js +69 -37
- package/dist/leavings.d.ts +26 -0
- package/dist/leavings.js +70 -0
- package/dist/meet.js +14 -8
- package/dist/mixer.d.ts +43 -7
- package/dist/mixer.js +60 -11
- package/dist/motion.d.ts +2 -0
- package/dist/motion.js +4 -1
- package/dist/motions.d.ts +22 -15
- package/dist/motions.js +40 -34
- package/dist/move.js +31 -20
- package/dist/moved.d.ts +11 -0
- package/dist/moved.js +29 -0
- package/dist/origin.d.ts +19 -0
- package/dist/origin.js +68 -0
- package/dist/owed.d.ts +4 -4
- package/dist/owed.js +8 -8
- package/dist/owner.d.ts +1 -1
- package/dist/owner.js +2 -2
- package/dist/pace.d.ts +13 -5
- package/dist/pace.js +21 -8
- package/dist/pack.d.ts +3 -1
- package/dist/pack.js +15 -24
- package/dist/paging.d.ts +8 -2
- package/dist/paging.js +44 -4
- package/dist/patch.d.ts +14 -9
- package/dist/patch.js +83 -21
- package/dist/place.js +188 -30
- package/dist/project.d.ts +12 -1
- package/dist/project.js +101 -83
- package/dist/pull.d.ts +2 -0
- package/dist/pull.js +8 -0
- package/dist/qualify.js +10 -2
- package/dist/reading.d.ts +5 -0
- package/dist/reading.js +5 -0
- package/dist/record.d.ts +9 -0
- package/dist/record.js +42 -0
- package/dist/relink.d.ts +11 -5
- package/dist/relink.js +7 -5
- package/dist/restored.d.ts +41 -0
- package/dist/restored.js +91 -0
- package/dist/revive.d.ts +10 -3
- package/dist/revive.js +31 -23
- package/dist/roster.d.ts +11 -0
- package/dist/roster.js +46 -0
- package/dist/rotation.d.ts +29 -0
- package/dist/rotation.js +91 -0
- package/dist/rows.d.ts +5 -5
- package/dist/rows.js +5 -5
- package/dist/sample.d.ts +3 -2
- package/dist/sample.js +32 -15
- package/dist/scored.d.ts +57 -0
- package/dist/scored.js +196 -0
- package/dist/seek.js +97 -114
- package/dist/shared.d.ts +6 -0
- package/dist/shared.js +15 -0
- package/dist/signals.d.ts +24 -3
- package/dist/signals.js +50 -11
- package/dist/spans.js +7 -0
- package/dist/stack.d.ts +11 -0
- package/dist/stack.js +47 -0
- package/dist/standing.d.ts +10 -0
- package/dist/standing.js +79 -0
- package/dist/store.d.ts +3 -3
- package/dist/store.js +3 -3
- package/dist/strays.js +3 -0
- package/dist/tape.d.ts +8 -5
- package/dist/tape.js +21 -13
- package/dist/testing.d.ts +35 -0
- package/dist/testing.js +116 -0
- package/dist/touch.d.ts +6 -0
- package/dist/touch.js +28 -0
- package/dist/transport.d.ts +53 -6
- package/dist/transport.js +102 -20
- package/dist/turns.d.ts +18 -0
- package/dist/turns.js +72 -0
- package/dist/tweened.js +1 -1
- package/dist/types/channel.d.ts +46 -0
- package/dist/types/channel.js +1 -0
- package/dist/types/history.d.ts +139 -0
- package/dist/types/history.js +1 -0
- package/dist/types/mix.d.ts +336 -0
- package/dist/types/mix.js +1 -0
- package/dist/types/patch.d.ts +187 -0
- package/dist/types/patch.js +1 -0
- package/dist/types/place.d.ts +157 -0
- package/dist/types/place.js +1 -0
- package/dist/types/transport.d.ts +71 -0
- package/dist/types/transport.js +1 -0
- package/dist/types/voice.d.ts +323 -0
- package/dist/types/voice.js +1 -0
- package/dist/types.d.ts +7 -1162
- package/dist/unpage.d.ts +2 -2
- package/dist/unpage.js +55 -25
- package/dist/voice.d.ts +92 -22
- package/dist/voice.js +108 -43
- package/dist/wave.d.ts +42 -0
- package/dist/wave.js +62 -0
- package/dist/weigh.js +1 -1
- package/docs/schema.html +42 -36
- package/package.json +8 -3
package/dist/mixer.js
CHANGED
|
@@ -2,8 +2,10 @@ import { blended, blendOf, laneRead, shareKept } from './blend.js';
|
|
|
2
2
|
import { aims, chain, linkable, relink } from './chain.js';
|
|
3
3
|
import { lerpInto } from './channels.js';
|
|
4
4
|
import { cue } from './cue.js';
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
5
|
+
import { DepartedIndex } from './departed.js';
|
|
6
|
+
import { beginFade, leftWith, parting } from './fade.js';
|
|
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
|
+
import { GoneIndex, goneIndex } from './gone.js';
|
|
7
9
|
import { held, shownOf, sinceOf, unreachedOf } from './held.js';
|
|
8
10
|
import { leave, releasing } from './history.js';
|
|
9
11
|
import { book, laneHost } from './hosts.js';
|
|
@@ -17,13 +19,20 @@ import { projectAll } from './project.js';
|
|
|
17
19
|
import { keep, pull } from './pull.js';
|
|
18
20
|
import { Steps } from './relink.js';
|
|
19
21
|
import { Outs } from './revive.js';
|
|
22
|
+
import { ScoreIndex } from './scored.js';
|
|
20
23
|
import { seek } from './seek.js';
|
|
21
24
|
import { Fitting, refit } from './spans.js';
|
|
22
25
|
import { Store } from './store.js';
|
|
23
26
|
import { record } from './tape.js';
|
|
27
|
+
import { touch } from './touch.js';
|
|
24
28
|
import { announce, Transport } from './transport.js';
|
|
25
29
|
import { none } from './voice.js';
|
|
26
30
|
import { base, capped, fadeOf, horizonFor, ownedBy, ownerBase, prime, weigh, weighOwned, } from './weigh.js';
|
|
31
|
+
/**
|
|
32
|
+
* What a mix takes where its options say nothing. Not exported from the package: the suite turns
|
|
33
|
+
* `lanes` off to run every test on the general path, which is what makes each one an equivalence check.
|
|
34
|
+
*/
|
|
35
|
+
export const defaults = { lanes: true };
|
|
27
36
|
// biome-ignore lint/suspicious/noUnsafeDeclarationMerging: every member of `Methods` is installed below
|
|
28
37
|
export class Mixer {
|
|
29
38
|
kit;
|
|
@@ -31,12 +40,19 @@ export class Mixer {
|
|
|
31
40
|
cued = [];
|
|
32
41
|
/** Under `history`, voices that have left but that a read back may still reach. */
|
|
33
42
|
gone = [];
|
|
43
|
+
goneIx = new GoneIndex();
|
|
44
|
+
scored = new ScoreIndex();
|
|
34
45
|
/** Set on a projection's own mixer: it sends nothing, keeps no history, and reads without committing. */
|
|
35
46
|
projecting = false;
|
|
36
47
|
/** Set on a projection reading back: a subject it has nothing on starts from its voice's start. */
|
|
37
48
|
backward = false;
|
|
38
49
|
/** True during a sync, so a control change it makes is recorded as showing in that frame. */
|
|
39
50
|
moving = false;
|
|
51
|
+
/**
|
|
52
|
+
* Whether a probe or pull has read the mix since its clock last moved. A control change made
|
|
53
|
+
* before that shows in this frame live, so it is recorded as showing in it.
|
|
54
|
+
*/
|
|
55
|
+
looked = false;
|
|
40
56
|
/**
|
|
41
57
|
* Under `history` with `inputs`, the host fields patches read, copied each frame they changed. In
|
|
42
58
|
* a projection reading back, the copy in force then.
|
|
@@ -52,20 +68,23 @@ export class Mixer {
|
|
|
52
68
|
/** Taken off its transport, so it takes no more calls until a seek puts it back. */
|
|
53
69
|
dropped = false;
|
|
54
70
|
name;
|
|
55
|
-
/**
|
|
71
|
+
/**
|
|
72
|
+
* This mix's frame among every mix's: moved by a sync or seek, and by a control change, drop,
|
|
73
|
+
* or touch within one.
|
|
74
|
+
*/
|
|
56
75
|
frame = nextFrame();
|
|
57
76
|
/** Pending voices whose start the host gave, by its host time, kept where the rate puts it. */
|
|
58
77
|
pins = null;
|
|
59
78
|
wantsPose = false;
|
|
60
79
|
/** The pose `pull` folds a subject into where it cannot read straight from the lanes. */
|
|
61
80
|
scratch;
|
|
62
|
-
/** The last array `pull` read, by position, with each subject's chain head, to skip the lookup. */
|
|
63
81
|
/** Live voices by the earliest mix time `moveTo` would change each, a binary heap. */
|
|
64
82
|
due = [];
|
|
65
83
|
/** Voices retired since `moveTo` last pruned them, in the order they retired. */
|
|
66
84
|
retired = [];
|
|
67
85
|
/** Visit every voice each sync, as before the due queue; for tests that compare the two. */
|
|
68
86
|
walkAll = false;
|
|
87
|
+
/** The last array `pull` read, by position, with each subject's chain head, to skip the lookup. */
|
|
69
88
|
pulled = [];
|
|
70
89
|
pulledHeads = [];
|
|
71
90
|
/** Each remembered head's lane slot, and the `version` and `relinks` they were all current at. */
|
|
@@ -85,6 +104,9 @@ export class Mixer {
|
|
|
85
104
|
/** Records a change to the mix's voices or controls. */
|
|
86
105
|
stir() {
|
|
87
106
|
this.stirs++;
|
|
107
|
+
// A change a sync makes follows from the clock; one a host makes does not.
|
|
108
|
+
if (!this.moving)
|
|
109
|
+
this.transport.settle();
|
|
88
110
|
if (this.stirred)
|
|
89
111
|
return;
|
|
90
112
|
this.stirred = true;
|
|
@@ -109,8 +131,8 @@ export class Mixer {
|
|
|
109
131
|
}
|
|
110
132
|
/**
|
|
111
133
|
* Per subject, the first record of the chain through every live voice that reaches it, or a stub
|
|
112
|
-
* where none does. Relinked when `version` moves,
|
|
113
|
-
*
|
|
134
|
+
* where none does. Relinked when `version` moves, or when a voice naming the subject marks its
|
|
135
|
+
* head stale: see `changed` in `chain.ts`.
|
|
114
136
|
*/
|
|
115
137
|
chains = new Store();
|
|
116
138
|
version = 0;
|
|
@@ -119,6 +141,8 @@ export class Mixer {
|
|
|
119
141
|
named = new Store();
|
|
120
142
|
/** Voices a subject has been faded out of, which `drop` looks in besides those that reach it. */
|
|
121
143
|
parters = new Set();
|
|
144
|
+
/** What an anchor can still find of voices that left without history, or past its reach. */
|
|
145
|
+
departed = new DepartedIndex();
|
|
122
146
|
/**
|
|
123
147
|
* Motion patches that may hold state for a subject no voice still reaching it plays, which `drop`
|
|
124
148
|
* frees besides those voices' own: per subject, a patch a voice naming it left while another
|
|
@@ -132,6 +156,15 @@ export class Mixer {
|
|
|
132
156
|
naming = 0;
|
|
133
157
|
/** The voices in the list that name no subjects, in voice order. */
|
|
134
158
|
general = [];
|
|
159
|
+
/** Those of `general` that keep one record for every subject, in voice order: see `everyone.ts`. */
|
|
160
|
+
sharers = [];
|
|
161
|
+
/**
|
|
162
|
+
* Whether a voice stopped sharing this frame: a probe or pull under way then holds chains linked
|
|
163
|
+
* before it joined them, and takes each again.
|
|
164
|
+
*/
|
|
165
|
+
unshared = false;
|
|
166
|
+
/** 0 where a fold is a walk of the chain: see `detour` in `everyone.ts`. */
|
|
167
|
+
detours = 0;
|
|
135
168
|
/** How many voices in the list carry a locus; with none, a fold allocates nothing. */
|
|
136
169
|
loci = 0;
|
|
137
170
|
/** What every motion patch it plays asks for its voice's time, holding this mix weakly. */
|
|
@@ -192,7 +225,7 @@ export class Mixer {
|
|
|
192
225
|
this.names.forEach((k, i) => {
|
|
193
226
|
this.slotOf.set(k, i);
|
|
194
227
|
});
|
|
195
|
-
this.lanes = opts.lanes
|
|
228
|
+
this.lanes = (opts.lanes ?? defaults.lanes) ? new Lanes(laneHost(this)) : null;
|
|
196
229
|
this.name = opts.name;
|
|
197
230
|
const shared = opts.transport;
|
|
198
231
|
if (shared !== undefined) {
|
|
@@ -311,6 +344,7 @@ export class Mixer {
|
|
|
311
344
|
return book(this, opts);
|
|
312
345
|
}
|
|
313
346
|
probe(subject, out) {
|
|
347
|
+
this.looked = true;
|
|
314
348
|
const pose = this.fold(subject, out);
|
|
315
349
|
this.keep(subject, pose, out);
|
|
316
350
|
// Where the lanes answer `atRest` from their own values, there is nothing to keep.
|
|
@@ -319,6 +353,7 @@ export class Mixer {
|
|
|
319
353
|
return pose;
|
|
320
354
|
}
|
|
321
355
|
pull(subjects, into) {
|
|
356
|
+
this.looked = true;
|
|
322
357
|
pull(this, subjects, into);
|
|
323
358
|
}
|
|
324
359
|
seek(time) {
|
|
@@ -427,8 +462,10 @@ export class Mixer {
|
|
|
427
462
|
return motion !== undefined && voice.elapsedAt(now) >= voice.latest && motion.landed();
|
|
428
463
|
}
|
|
429
464
|
mute(opts) {
|
|
430
|
-
for (const voice of this.cued)
|
|
465
|
+
for (const voice of this.cued) {
|
|
466
|
+
voice.written = true;
|
|
431
467
|
beginFade(this, voice, { over: opts?.over });
|
|
468
|
+
}
|
|
432
469
|
record(this, 'mute', () => this.mute(opts));
|
|
433
470
|
}
|
|
434
471
|
drop(subject) {
|
|
@@ -449,7 +486,7 @@ export class Mixer {
|
|
|
449
486
|
// of a voice that does not name it is in `parters`, and a patch's own state in `motions`.
|
|
450
487
|
const kept = releasing(this);
|
|
451
488
|
const forget = (voice) => {
|
|
452
|
-
leave(this, voice, subject, voice
|
|
489
|
+
leave(this, voice, subject, leftWith(this, voice, subject));
|
|
453
490
|
voice.subjects.delete(subject);
|
|
454
491
|
voice.motion?.release(subject, ...kept);
|
|
455
492
|
voice.parts?.delete(subject);
|
|
@@ -462,8 +499,13 @@ export class Mixer {
|
|
|
462
499
|
if (named !== undefined)
|
|
463
500
|
for (const voice of [...named])
|
|
464
501
|
forget(voice);
|
|
465
|
-
|
|
502
|
+
const gone = goneIndex(this);
|
|
503
|
+
for (const voice of gone.general)
|
|
466
504
|
forget(voice);
|
|
505
|
+
const goneNaming = gone.named.get(subject);
|
|
506
|
+
if (goneNaming !== undefined)
|
|
507
|
+
for (const voice of goneNaming)
|
|
508
|
+
forget(voice);
|
|
467
509
|
if (this.owners !== null)
|
|
468
510
|
for (const voice of this.owners)
|
|
469
511
|
forget(voice);
|
|
@@ -485,6 +527,10 @@ export class Mixer {
|
|
|
485
527
|
motion.release(subject, ...kept);
|
|
486
528
|
record(this, 'drop', () => this.drop(subject));
|
|
487
529
|
}
|
|
530
|
+
touch(subject) {
|
|
531
|
+
this.attached();
|
|
532
|
+
touch(this, subject);
|
|
533
|
+
}
|
|
488
534
|
drain(tag) {
|
|
489
535
|
const all = this.sent;
|
|
490
536
|
if (all.length === 0)
|
|
@@ -509,7 +555,7 @@ export class Mixer {
|
|
|
509
555
|
* is first asked, when probes start writing it.
|
|
510
556
|
*/
|
|
511
557
|
restStamps = null;
|
|
512
|
-
/** What a probe's record of rest is good for: moved by a new frame, version or
|
|
558
|
+
/** What a probe's record of rest is good for: moved by a new frame, version, relink, or stir. */
|
|
513
559
|
restsEpoch = 0;
|
|
514
560
|
restsFrame = Number.NaN;
|
|
515
561
|
restsVersion = Number.NaN;
|
|
@@ -543,6 +589,9 @@ const methods = {
|
|
|
543
589
|
foldWith,
|
|
544
590
|
owedBy,
|
|
545
591
|
foldLoci,
|
|
592
|
+
foldDetour,
|
|
593
|
+
foldSharers,
|
|
594
|
+
shared,
|
|
546
595
|
read,
|
|
547
596
|
contribution,
|
|
548
597
|
keyed,
|
package/dist/motion.d.ts
CHANGED
|
@@ -33,6 +33,8 @@ export type Moving<I, O, V extends Value> = Patch<I, O, void> & {
|
|
|
33
33
|
* Where `subject` is and how fast it moves at voice time `at`, default the mix's latest frame,
|
|
34
34
|
* counting every change due by then. With no time given, undefined until a frame of its voice
|
|
35
35
|
* has met the subject, while the subject is still inside its stagger, and once that voice is gone.
|
|
36
|
+
* At any time, undefined for a subject the patch has never met or changed, or that the mix
|
|
37
|
+
* dropped.
|
|
36
38
|
*/
|
|
37
39
|
read(subject: I, at?: number): Motion<V> | undefined;
|
|
38
40
|
/**
|
package/dist/motion.js
CHANGED
|
@@ -26,7 +26,6 @@ class MotionPatch {
|
|
|
26
26
|
}
|
|
27
27
|
form = 'motion';
|
|
28
28
|
duration = 0;
|
|
29
|
-
period = 0;
|
|
30
29
|
get motion() {
|
|
31
30
|
return this.motions.spec();
|
|
32
31
|
}
|
|
@@ -191,6 +190,8 @@ export function spring(writes, opts) {
|
|
|
191
190
|
const k = opts.stiffness ?? 170;
|
|
192
191
|
const c = opts.damping ?? 26;
|
|
193
192
|
const m = opts.mass ?? 1;
|
|
193
|
+
if (!(k > 0) || !(m > 0) || !(c >= 0))
|
|
194
|
+
throw new RangeError('blits: a spring takes a positive stiffness and mass, and damping of 0 or more');
|
|
194
195
|
const settle = opts.settle ?? 1e-4;
|
|
195
196
|
const w0 = Math.sqrt(k / m);
|
|
196
197
|
const zeta = c / (2 * Math.sqrt(k * m));
|
|
@@ -215,6 +216,8 @@ export function spring(writes, opts) {
|
|
|
215
216
|
*/
|
|
216
217
|
export function glide(writes, opts) {
|
|
217
218
|
const ms = opts.ms ?? 325;
|
|
219
|
+
if (!(ms > 0))
|
|
220
|
+
throw new RangeError('blits: a glide takes a positive ms');
|
|
218
221
|
const tau = ms / 1000;
|
|
219
222
|
const settle = opts.settle ?? 1e-4;
|
|
220
223
|
const shape = new GlideShape([COAST, settle, tau, 0, 0], { kind: 'glide', ms, settle }, opts, tau);
|
package/dist/motions.d.ts
CHANGED
|
@@ -23,6 +23,8 @@ export interface Change {
|
|
|
23
23
|
v?: number[];
|
|
24
24
|
/** The mix clock when it was made, NaN where no mix was playing the patch. */
|
|
25
25
|
made?: number;
|
|
26
|
+
/** The frame it was made in, which a rewind partitions by; NaN where no mix was playing the patch. */
|
|
27
|
+
seq?: number;
|
|
26
28
|
}
|
|
27
29
|
/**
|
|
28
30
|
* The mix playing a patch, asked with its voice's id: an owner and an id, where a pair of closures
|
|
@@ -35,6 +37,8 @@ export interface MotionOwner {
|
|
|
35
37
|
revive(id: number, subject: unknown): void;
|
|
36
38
|
/** The mix clock at its latest frame; NaN before the first. */
|
|
37
39
|
now(): number;
|
|
40
|
+
/** The number of the frame the mix is playing. */
|
|
41
|
+
seq(): number;
|
|
38
42
|
/**
|
|
39
43
|
* The host made a change, already given its time, which a seek makes again on the patch its
|
|
40
44
|
* voice plays then: a voice a history store paged out comes back with a patch of its own.
|
|
@@ -43,9 +47,10 @@ export interface MotionOwner {
|
|
|
43
47
|
/** History leaving memory for a subject, which a mix with a history store pages out. */
|
|
44
48
|
page(id: number, stream: 'released' | 'stretch', subject: unknown, out: Paging[]): void;
|
|
45
49
|
}
|
|
46
|
-
/** A record of a motion patch's history leaving memory, at a mix time. */
|
|
50
|
+
/** A record of a motion patch's history leaving memory, at a mix time and frame. */
|
|
47
51
|
export interface Paging {
|
|
48
52
|
at: number;
|
|
53
|
+
seq: number;
|
|
49
54
|
data: unknown;
|
|
50
55
|
}
|
|
51
56
|
/** A subject's run as plain data: its flags, its stretches oldest first, and changes still to apply. */
|
|
@@ -75,7 +80,7 @@ export interface Shape<I> {
|
|
|
75
80
|
scalarEvery(): boolean | undefined;
|
|
76
81
|
/** A tween's easing, which its law cannot hold; undefined, but present, on every other shape. */
|
|
77
82
|
ease: Curve | undefined;
|
|
78
|
-
/** A tween's
|
|
83
|
+
/** A tween's ms for a stretch the subject starts now; undefined, but present, on the rest. */
|
|
79
84
|
ms: ((subject: I) => number) | undefined;
|
|
80
85
|
/** The patch's kind and constants, as `Patch.motion` reports them. */
|
|
81
86
|
spec(): MotionSpec;
|
|
@@ -86,7 +91,7 @@ export interface Shape<I> {
|
|
|
86
91
|
* The one copy, read by `at` and by a mix's lane alike.
|
|
87
92
|
*
|
|
88
93
|
* After the law, each subject's run of `stride` numbers is its release time, its flags, a tween's
|
|
89
|
-
*
|
|
94
|
+
* ms for the stretch, then `x0`, `v0` and `to` per axis. A live sample reads this one buffer and nothing else the patch owns: at
|
|
90
95
|
* 10k springs of one subject each, a buffer per field, a map lookup per sample and the law in an
|
|
91
96
|
* object of its own took a call from 130 ns to 490.
|
|
92
97
|
*/
|
|
@@ -95,6 +100,8 @@ export declare class Motions<I> {
|
|
|
95
100
|
private readonly numbers;
|
|
96
101
|
/** Numbers by subject once a second is numbered: till then the one subject is found by `numbers`. */
|
|
97
102
|
private readonly slots;
|
|
103
|
+
/** How many numbers have been given up, so a number kept elsewhere can tell it is stale. */
|
|
104
|
+
freed: number;
|
|
98
105
|
/** Axes per subject; -1 until the first subject sets it. */
|
|
99
106
|
n: number;
|
|
100
107
|
private cap;
|
|
@@ -149,11 +156,11 @@ export declare class Motions<I> {
|
|
|
149
156
|
*/
|
|
150
157
|
private released;
|
|
151
158
|
/**
|
|
152
|
-
* Forgets a subject, so a later ask starts it afresh from `from`. Given the mix time `at
|
|
153
|
-
* history, its run is kept for a rewind before then, until `reach` passes it.
|
|
159
|
+
* Forgets a subject, so a later ask starts it afresh from `from`. Given the mix time `at` and frame
|
|
160
|
+
* `seq`, under history, its run is kept for a rewind before then, until `reach` passes it.
|
|
154
161
|
*/
|
|
155
|
-
release(subject: I, at?: number, reach?: number): void;
|
|
156
|
-
/** Puts back each subject released after
|
|
162
|
+
release(subject: I, at?: number, reach?: number, seq?: number): void;
|
|
163
|
+
/** Puts back each subject released after frame `seq`, as it stood when released. */
|
|
157
164
|
private unrelease;
|
|
158
165
|
/** Subject `s`'s run as it stands. */
|
|
159
166
|
private runAt;
|
|
@@ -162,9 +169,9 @@ export declare class Motions<I> {
|
|
|
162
169
|
/** Puts a subject's run back as `pack` or a release left it, replacing any it has now. */
|
|
163
170
|
unpack(subject: I, run: Run): void;
|
|
164
171
|
/** A released run a history store gave back, for a rewind to before its release. */
|
|
165
|
-
reclaim(subject: I, at: number, run: Run): void;
|
|
172
|
+
reclaim(subject: I, at: number, seq: number, run: Run): void;
|
|
166
173
|
/**
|
|
167
|
-
* Stretches a history store gave back, each by the
|
|
174
|
+
* Stretches a history store gave back, each by the frame it was in force by: each life of the
|
|
168
175
|
* subject, ended by a release, takes those from its own, ahead of the earliest it still keeps.
|
|
169
176
|
*/
|
|
170
177
|
restretch(subject: I, back: readonly Paging[]): void;
|
|
@@ -184,7 +191,7 @@ export declare class Motions<I> {
|
|
|
184
191
|
/** Whether `law` holds this patch's law, number for number, as `Object.is` compares them. */
|
|
185
192
|
hasLaw(law: Float64Array): boolean;
|
|
186
193
|
/**
|
|
187
|
-
* Copies subject `s`'s current stretch into `out` from `o` as its release time, its
|
|
194
|
+
* Copies subject `s`'s current stretch into `out` from `o` as its release time, its ms, then
|
|
188
195
|
* `x0`, `v0` and `to` per axis; false, copying nothing, while it has a change pending or an
|
|
189
196
|
* earlier stretch kept, which only `sample` handles.
|
|
190
197
|
*/
|
|
@@ -200,7 +207,7 @@ export declare class Motions<I> {
|
|
|
200
207
|
*/
|
|
201
208
|
sample(s: number, t: number, xo: Float64Array, vo: Float64Array): void;
|
|
202
209
|
/**
|
|
203
|
-
* Where subject `s`'s run starts in `runs` (its release time, then flags,
|
|
210
|
+
* Where subject `s`'s run starts in `runs` (its release time, then flags, ms, `x0`, `v0`
|
|
204
211
|
* and `to`) while it has nothing pending and no earlier stretch kept; -1 for any other.
|
|
205
212
|
*/
|
|
206
213
|
bareAt(s: number): number;
|
|
@@ -238,10 +245,10 @@ export declare class Motions<I> {
|
|
|
238
245
|
private flag;
|
|
239
246
|
forget(s: number): void;
|
|
240
247
|
/**
|
|
241
|
-
* Takes back every change made after
|
|
248
|
+
* Takes back every change made after frame `seq`: each subject plays from its earliest stretch
|
|
242
249
|
* kept, with the changes made by then queued to be applied again.
|
|
243
250
|
*/
|
|
244
|
-
rewind(
|
|
251
|
+
rewind(seq: number): void;
|
|
245
252
|
private cut;
|
|
246
253
|
/** Where subject `s`'s run starts in `runs`. */
|
|
247
254
|
base(s: number): number;
|
|
@@ -255,8 +262,8 @@ export declare class Motions<I> {
|
|
|
255
262
|
private commit;
|
|
256
263
|
private insert;
|
|
257
264
|
/**
|
|
258
|
-
* The mix time a stretch was in force by, which a store keys it under: when the change
|
|
259
|
-
* ended it was made, which falls in the same life of the subject.
|
|
265
|
+
* The mix time and frame a stretch was in force by, which a store keys it under: when the change
|
|
266
|
+
* that ended it was made, which falls in the same life of the subject.
|
|
260
267
|
*/
|
|
261
268
|
private made;
|
|
262
269
|
/** Lets go of stretches older than the one in force at `reading.horizon`. */
|
package/dist/motions.js
CHANGED
|
@@ -43,7 +43,7 @@ const wrongKind = (channel, holdsNumber) => new Error(`blits: channel ${channel}
|
|
|
43
43
|
* The one copy, read by `at` and by a mix's lane alike.
|
|
44
44
|
*
|
|
45
45
|
* After the law, each subject's run of `stride` numbers is its release time, its flags, a tween's
|
|
46
|
-
*
|
|
46
|
+
* ms for the stretch, then `x0`, `v0` and `to` per axis. A live sample reads this one buffer and nothing else the patch owns: at
|
|
47
47
|
* 10k springs of one subject each, a buffer per field, a map lookup per sample and the law in an
|
|
48
48
|
* object of its own took a call from 130 ns to 490.
|
|
49
49
|
*/
|
|
@@ -52,6 +52,8 @@ export class Motions {
|
|
|
52
52
|
numbers = new Numbers(this);
|
|
53
53
|
/** Numbers by subject once a second is numbered: till then the one subject is found by `numbers`. */
|
|
54
54
|
slots = new Store();
|
|
55
|
+
/** How many numbers have been given up, so a number kept elsewhere can tell it is stale. */
|
|
56
|
+
freed = 0;
|
|
55
57
|
/** Axes per subject; -1 until the first subject sets it. */
|
|
56
58
|
n = -1;
|
|
57
59
|
cap = 0;
|
|
@@ -163,10 +165,10 @@ export class Motions {
|
|
|
163
165
|
*/
|
|
164
166
|
released = [];
|
|
165
167
|
/**
|
|
166
|
-
* Forgets a subject, so a later ask starts it afresh from `from`. Given the mix time `at
|
|
167
|
-
* history, its run is kept for a rewind before then, until `reach` passes it.
|
|
168
|
+
* Forgets a subject, so a later ask starts it afresh from `from`. Given the mix time `at` and frame
|
|
169
|
+
* `seq`, under history, its run is kept for a rewind before then, until `reach` passes it.
|
|
168
170
|
*/
|
|
169
|
-
release(subject, at, reach = Number.NEGATIVE_INFINITY) {
|
|
171
|
+
release(subject, at, reach = Number.NEGATIVE_INFINITY, seq = Number.NaN) {
|
|
170
172
|
const s = this.known(subject);
|
|
171
173
|
if (s === undefined)
|
|
172
174
|
return;
|
|
@@ -176,19 +178,21 @@ export class Motions {
|
|
|
176
178
|
if (e.at >= reach)
|
|
177
179
|
kept.push(e);
|
|
178
180
|
else
|
|
179
|
-
this.owner?.page(this.ownerId, 'released', e.subject, [
|
|
181
|
+
this.owner?.page(this.ownerId, 'released', e.subject, [
|
|
182
|
+
{ at: e.at, seq: e.seq, data: runOf(e) },
|
|
183
|
+
]);
|
|
180
184
|
this.released = kept;
|
|
181
|
-
this.released.push({ subject, at, ...this.runAt(s) });
|
|
185
|
+
this.released.push({ subject, at, seq, ...this.runAt(s) });
|
|
182
186
|
}
|
|
183
187
|
this.slots.delete(subject);
|
|
184
188
|
this.numbers.release(s);
|
|
185
189
|
}
|
|
186
|
-
/** Puts back each subject released after
|
|
187
|
-
unrelease(
|
|
188
|
-
const back = this.released.filter((e) => e.
|
|
190
|
+
/** Puts back each subject released after frame `seq`, as it stood when released. */
|
|
191
|
+
unrelease(seq) {
|
|
192
|
+
const back = this.released.filter((e) => e.seq > seq).sort((a, b) => a.seq - b.seq);
|
|
189
193
|
if (back.length === 0)
|
|
190
194
|
return;
|
|
191
|
-
this.released = this.released.filter((e) => e.
|
|
195
|
+
this.released = this.released.filter((e) => e.seq <= seq);
|
|
192
196
|
const done = new Set();
|
|
193
197
|
for (const e of back) {
|
|
194
198
|
if (done.has(e.subject))
|
|
@@ -239,24 +243,24 @@ export class Motions {
|
|
|
239
243
|
}
|
|
240
244
|
}
|
|
241
245
|
/** A released run a history store gave back, for a rewind to before its release. */
|
|
242
|
-
reclaim(subject, at, run) {
|
|
243
|
-
if (this.released.some((e) => e.
|
|
246
|
+
reclaim(subject, at, seq, run) {
|
|
247
|
+
if (this.released.some((e) => e.seq === seq && Object.is(e.subject, subject)))
|
|
244
248
|
return;
|
|
245
|
-
this.released.push({ subject, at, ...run });
|
|
249
|
+
this.released.push({ subject, at, seq, ...run });
|
|
246
250
|
}
|
|
247
251
|
/**
|
|
248
|
-
* Stretches a history store gave back, each by the
|
|
252
|
+
* Stretches a history store gave back, each by the frame it was in force by: each life of the
|
|
249
253
|
* subject, ended by a release, takes those from its own, ahead of the earliest it still keeps.
|
|
250
254
|
*/
|
|
251
255
|
restretch(subject, back) {
|
|
252
256
|
const lives = this.released
|
|
253
257
|
.filter((e) => Object.is(e.subject, subject))
|
|
254
|
-
.sort((x, y) => x.
|
|
258
|
+
.sort((x, y) => x.seq - y.seq);
|
|
255
259
|
let from = Number.NEGATIVE_INFINITY;
|
|
256
|
-
const within = (to) => back.filter((r) => r.
|
|
260
|
+
const within = (to) => back.filter((r) => r.seq > from && r.seq <= to).map((r) => r.data);
|
|
257
261
|
for (const e of lives) {
|
|
258
|
-
e.stretches = ahead(within(e.
|
|
259
|
-
from = e.
|
|
262
|
+
e.stretches = ahead(within(e.seq), e.stretches);
|
|
263
|
+
from = e.seq;
|
|
260
264
|
}
|
|
261
265
|
const s = this.known(subject);
|
|
262
266
|
if (s === undefined)
|
|
@@ -298,7 +302,7 @@ export class Motions {
|
|
|
298
302
|
return true;
|
|
299
303
|
}
|
|
300
304
|
/**
|
|
301
|
-
* Copies subject `s`'s current stretch into `out` from `o` as its release time, its
|
|
305
|
+
* Copies subject `s`'s current stretch into `out` from `o` as its release time, its ms, then
|
|
302
306
|
* `x0`, `v0` and `to` per axis; false, copying nothing, while it has a change pending or an
|
|
303
307
|
* earlier stretch kept, which only `sample` handles.
|
|
304
308
|
*/
|
|
@@ -354,7 +358,7 @@ export class Motions {
|
|
|
354
358
|
this.peek(s, t, xo, vo);
|
|
355
359
|
}
|
|
356
360
|
/**
|
|
357
|
-
* Where subject `s`'s run starts in `runs` (its release time, then flags,
|
|
361
|
+
* Where subject `s`'s run starts in `runs` (its release time, then flags, ms, `x0`, `v0`
|
|
358
362
|
* and `to`) while it has nothing pending and no earlier stretch kept; -1 for any other.
|
|
359
363
|
*/
|
|
360
364
|
bareAt(s) {
|
|
@@ -455,6 +459,7 @@ export class Motions {
|
|
|
455
459
|
this.check(c.to, this.n);
|
|
456
460
|
this.check(c.v, this.n);
|
|
457
461
|
c.made = this.owner === null ? Number.NaN : this.owner.now();
|
|
462
|
+
c.seq = this.owner === null ? Number.NaN : this.owner.seq();
|
|
458
463
|
if (c.at === undefined) {
|
|
459
464
|
const at = this.frame(subject);
|
|
460
465
|
if (!Number.isNaN(at))
|
|
@@ -511,6 +516,7 @@ export class Motions {
|
|
|
511
516
|
this.runs[i] = on ? flags | bit : flags & ~bit;
|
|
512
517
|
}
|
|
513
518
|
forget(s) {
|
|
519
|
+
this.freed++;
|
|
514
520
|
if (this.pending !== null)
|
|
515
521
|
this.pending[s] = undefined;
|
|
516
522
|
if (this.older !== null)
|
|
@@ -519,16 +525,16 @@ export class Motions {
|
|
|
519
525
|
this.causes[s] = undefined;
|
|
520
526
|
}
|
|
521
527
|
/**
|
|
522
|
-
* Takes back every change made after
|
|
528
|
+
* Takes back every change made after frame `seq`: each subject plays from its earliest stretch
|
|
523
529
|
* kept, with the changes made by then queued to be applied again.
|
|
524
530
|
*/
|
|
525
|
-
rewind(
|
|
526
|
-
this.unrelease(
|
|
531
|
+
rewind(seq) {
|
|
532
|
+
this.unrelease(seq);
|
|
527
533
|
for (let s = 0; s < this.numbers.size; s++)
|
|
528
534
|
if (this.numbers.subject(s) !== absent)
|
|
529
|
-
this.cut(s,
|
|
535
|
+
this.cut(s, seq);
|
|
530
536
|
}
|
|
531
|
-
cut(s,
|
|
537
|
+
cut(s, seq) {
|
|
532
538
|
const older = this.older?.[s];
|
|
533
539
|
const pending = this.pending?.[s];
|
|
534
540
|
const current = this.latest(s);
|
|
@@ -536,7 +542,7 @@ export class Motions {
|
|
|
536
542
|
const kept = [];
|
|
537
543
|
let cut = false;
|
|
538
544
|
for (const c of [...stretches.slice(1).map((seg) => seg.change), ...(pending ?? [])])
|
|
539
|
-
if (c === undefined || c.
|
|
545
|
+
if (c === undefined || c.seq === undefined || !(c.seq > seq)) {
|
|
540
546
|
if (c !== undefined)
|
|
541
547
|
kept.push(c);
|
|
542
548
|
}
|
|
@@ -684,12 +690,15 @@ export class Motions {
|
|
|
684
690
|
list.splice(i, 0, seg);
|
|
685
691
|
}
|
|
686
692
|
/**
|
|
687
|
-
* The mix time a stretch was in force by, which a store keys it under: when the change
|
|
688
|
-
* ended it was made, which falls in the same life of the subject.
|
|
693
|
+
* The mix time and frame a stretch was in force by, which a store keys it under: when the change
|
|
694
|
+
* that ended it was made, which falls in the same life of the subject.
|
|
689
695
|
*/
|
|
690
696
|
made(next) {
|
|
691
|
-
const
|
|
692
|
-
|
|
697
|
+
const c = next.change;
|
|
698
|
+
const made = c?.made;
|
|
699
|
+
if (made === undefined || Number.isNaN(made))
|
|
700
|
+
return { at: this.owner?.now() ?? Number.NaN, seq: this.owner?.seq() ?? Number.NaN };
|
|
701
|
+
return { at: made, seq: c?.seq ?? Number.NaN };
|
|
693
702
|
}
|
|
694
703
|
/** Lets go of stretches older than the one in force at `reading.horizon`. */
|
|
695
704
|
prune(s) {
|
|
@@ -706,10 +715,7 @@ export class Motions {
|
|
|
706
715
|
const out = list.splice(0, n);
|
|
707
716
|
const subject = this.numbers.subject(s);
|
|
708
717
|
if (this.owner !== null && subject !== absent)
|
|
709
|
-
this.owner.page(this.ownerId, 'stretch', subject, out.map((seg, i) => ({
|
|
710
|
-
at: this.made(out[i + 1] ?? list[0] ?? this.latest(s)),
|
|
711
|
-
data: seg,
|
|
712
|
-
})));
|
|
718
|
+
this.owner.page(this.ownerId, 'stretch', subject, out.map((seg, i) => ({ ...this.made(out[i + 1] ?? list[0] ?? this.latest(s)), data: seg })));
|
|
713
719
|
if (list.length === 0) {
|
|
714
720
|
this.older[s] = undefined;
|
|
715
721
|
this.flag(s, OLDER, false);
|
package/dist/move.js
CHANGED
|
@@ -1,10 +1,14 @@
|
|
|
1
|
-
import { changed
|
|
1
|
+
import { changed } from './chain.js';
|
|
2
2
|
import { popDue, schedule } from './due.js';
|
|
3
|
+
import { detour } from './everyone.js';
|
|
3
4
|
import { beginFade, part, retire } from './fade.js';
|
|
4
|
-
import {
|
|
5
|
+
import { expireGone, keepGone } from './gone.js';
|
|
6
|
+
import { reorigin } from './held.js';
|
|
5
7
|
import { ownerReading } from './owner.js';
|
|
6
8
|
import { mixAt, place, repin, startOf } from './place.js';
|
|
7
9
|
import { pageVoice } from './revive.js';
|
|
10
|
+
import { delist } from './roster.js';
|
|
11
|
+
import { scoredLeft, scoredRemove } from './scored.js';
|
|
8
12
|
import { lapse } from './spans.js';
|
|
9
13
|
import { unplay } from './strays.js';
|
|
10
14
|
/** Numbers every mix's frames apart, so a blend's read is never taken for another mix's or frame's. */
|
|
@@ -29,6 +33,11 @@ export function move(mix, now) {
|
|
|
29
33
|
}
|
|
30
34
|
function moveTo(mix, now) {
|
|
31
35
|
mix.now = now;
|
|
36
|
+
mix.looked = false;
|
|
37
|
+
if (mix.unshared) {
|
|
38
|
+
mix.unshared = false;
|
|
39
|
+
detour(mix);
|
|
40
|
+
}
|
|
32
41
|
mix.frame = nextFrame();
|
|
33
42
|
mix.reducedNow = mix.reduced;
|
|
34
43
|
for (const a of mix.announced)
|
|
@@ -50,18 +59,19 @@ function moveTo(mix, now) {
|
|
|
50
59
|
continue;
|
|
51
60
|
if (Number.isNaN(voice.opened))
|
|
52
61
|
voice.opened = now;
|
|
53
|
-
if ((voice.state === 'live' || voice.state === 'frozen') && voice.outAt <= now)
|
|
54
|
-
beginFade(mix, voice, { over: Number.isNaN(voice.outOver) ? undefined : voice.outOver }, Math.max(startOf(voice), voice.outAt));
|
|
55
62
|
if (voice.state === 'pending' &&
|
|
56
63
|
(voice.owner === null ? now : ownerReading(voice.owner, now)) >= voice.start) {
|
|
57
64
|
voice.state = 'live';
|
|
58
|
-
|
|
65
|
+
reorigin(mix, voice, now);
|
|
59
66
|
changed(mix, voice);
|
|
60
67
|
}
|
|
68
|
+
// After it starts, so a fade due by now on a voice that started since the last frame begins.
|
|
69
|
+
if ((voice.state === 'live' || voice.state === 'frozen') && voice.outAt <= now)
|
|
70
|
+
beginFade(mix, voice, { over: Number.isNaN(voice.outOver) ? undefined : voice.outOver }, Math.max(startOf(voice), voice.outAt));
|
|
61
71
|
if (voice.state !== 'pending' && Number.isFinite(voice.span)) {
|
|
62
72
|
const end = voice.span + voice.latest;
|
|
63
73
|
if (voice.elapsedAt(now) >= end) {
|
|
64
|
-
voice.play(true, now);
|
|
74
|
+
voice.play(true, now, mix.transport.seq);
|
|
65
75
|
// The fade starts when the last pass ended, not at the frame that noticed, so it plays
|
|
66
76
|
// the same at any frame rate and a read at another time can find it.
|
|
67
77
|
if (voice.state === 'live') {
|
|
@@ -152,8 +162,12 @@ function prune(mix) {
|
|
|
152
162
|
}
|
|
153
163
|
voices.length = kept;
|
|
154
164
|
}
|
|
155
|
-
if (pruned)
|
|
165
|
+
if (pruned) {
|
|
156
166
|
mix.general = mix.general.filter((v) => v.state !== 'done');
|
|
167
|
+
if (mix.sharers.length > 0)
|
|
168
|
+
mix.sharers = mix.sharers.filter((v) => v.state !== 'done');
|
|
169
|
+
detour(mix);
|
|
170
|
+
}
|
|
157
171
|
}
|
|
158
172
|
/** The voice's place in a list in id order, or -1 where it is not there. */
|
|
159
173
|
function indexOf(voices, voice) {
|
|
@@ -173,20 +187,18 @@ function indexOf(voices, voice) {
|
|
|
173
187
|
}
|
|
174
188
|
/** Lets go of a retired voice's place in the mix; whether it was among the voices naming no subject. */
|
|
175
189
|
function leave(mix, voice) {
|
|
176
|
-
|
|
190
|
+
scoredRemove(mix, [voice]);
|
|
191
|
+
if (mix.opts.history) {
|
|
177
192
|
mix.gone.push(voice);
|
|
178
|
-
|
|
193
|
+
scoredLeft(mix, voice);
|
|
194
|
+
}
|
|
195
|
+
else {
|
|
179
196
|
mix.parters.delete(voice);
|
|
197
|
+
mix.departed.add(mix, voice);
|
|
198
|
+
}
|
|
180
199
|
unplay(mix, voice);
|
|
181
200
|
changed(mix, voice);
|
|
182
|
-
|
|
183
|
-
unindex(mix, voice);
|
|
184
|
-
if (voice.holding !== null && mix.owners !== null)
|
|
185
|
-
mix.owners = mix.owners.filter((v) => v !== voice);
|
|
186
|
-
if (voice.spec.locus !== undefined)
|
|
187
|
-
mix.loci--;
|
|
188
|
-
if (voice.spec.anchor !== undefined)
|
|
189
|
-
mix.anchored--;
|
|
201
|
+
delist(mix, voice);
|
|
190
202
|
return voice.named === null;
|
|
191
203
|
}
|
|
192
204
|
/** Lets go of the voices that have left and that history no longer reaches. */
|
|
@@ -196,11 +208,10 @@ export function forget(mix) {
|
|
|
196
208
|
// With a store a voice that has left pages out where it can, and stays where it cannot.
|
|
197
209
|
if (mix.transport.pager !== null) {
|
|
198
210
|
const reach = mix.now - history.ms;
|
|
199
|
-
mix
|
|
211
|
+
keepGone(mix, (v) => v.doneAt >= reach || !pageVoice(mix, v, reach));
|
|
200
212
|
}
|
|
201
213
|
else {
|
|
202
|
-
|
|
203
|
-
mix.gone = mix.gone.filter((v) => v.doneAt >= reach);
|
|
214
|
+
expireGone(mix, mix.transport.keepsFrom());
|
|
204
215
|
}
|
|
205
216
|
}
|
|
206
217
|
}
|