@msb235/blits 0.8.1 → 0.9.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 +82 -0
- package/README.md +12 -2
- package/dist/clock.d.ts +10 -0
- package/dist/clock.js +23 -0
- package/dist/cue.js +5 -8
- package/dist/due.js +20 -13
- package/dist/everyone.js +3 -0
- package/dist/fade.d.ts +1 -0
- package/dist/fade.js +10 -4
- package/dist/fill.js +5 -0
- package/dist/fold.js +28 -22
- package/dist/gone.d.ts +4 -4
- package/dist/gone.js +11 -10
- package/dist/handle.js +17 -9
- package/dist/held.d.ts +10 -0
- package/dist/held.js +28 -1
- package/dist/hosts.js +5 -1
- package/dist/keyfill.js +5 -1
- package/dist/locus.d.ts +4 -1
- package/dist/locus.js +12 -2
- package/dist/marks.js +4 -4
- package/dist/motion.d.ts +6 -3
- package/dist/motions.d.ts +5 -2
- package/dist/motions.js +3 -1
- package/dist/move.js +51 -14
- package/dist/origin.js +3 -2
- package/dist/owner.d.ts +2 -0
- package/dist/owner.js +10 -2
- package/dist/place.js +17 -3
- package/dist/project.d.ts +1 -1
- package/dist/project.js +11 -6
- package/dist/record.js +2 -1
- package/dist/revive.d.ts +2 -0
- package/dist/revive.js +2 -0
- package/dist/seek.js +2 -2
- package/dist/spans.d.ts +5 -4
- package/dist/spans.js +11 -10
- package/dist/strays.js +13 -5
- package/dist/transport.js +4 -2
- package/dist/types/mix.d.ts +3 -2
- package/dist/types/patch.d.ts +7 -3
- package/dist/types/place.d.ts +4 -1
- package/dist/types/voice.d.ts +22 -4
- package/dist/unpage.d.ts +1 -1
- package/dist/unpage.js +2 -3
- package/dist/voice.d.ts +52 -0
- package/dist/voice.js +103 -2
- package/dist/warn.d.ts +2 -0
- package/dist/warn.js +17 -0
- package/dist/weigh.js +5 -0
- package/docs/schema.html +92 -29
- package/package.json +2 -2
package/dist/keyfill.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { foldNumber } from './channels.js';
|
|
2
2
|
import { clampWeight, frozenTime, phaseAt, same, weighed } from './clock.js';
|
|
3
|
+
import { unband } from './fold.js';
|
|
3
4
|
import { shortWay } from './turns.js';
|
|
4
5
|
/**
|
|
5
6
|
* A keys voice's fill outside a locus, its weight a number: what `one` does for each subject, in
|
|
@@ -44,6 +45,7 @@ export function runKeys(lanes, lane) {
|
|
|
44
45
|
const elapsed = frozenTime(elapsedNow - data[o + 0 /* Row.DELAY */], before, after, span);
|
|
45
46
|
if (!(elapsed >= 0)) {
|
|
46
47
|
data[o + 2 /* Row.WEIGHT */] = 0;
|
|
48
|
+
unband(records[p]);
|
|
47
49
|
continue;
|
|
48
50
|
}
|
|
49
51
|
if (!flat) {
|
|
@@ -63,8 +65,10 @@ export function runKeys(lanes, lane) {
|
|
|
63
65
|
lanes.gather(lane, lane.delta);
|
|
64
66
|
flatten(chans, read, nums, kinds);
|
|
65
67
|
}
|
|
66
|
-
if (!(w > 0))
|
|
68
|
+
if (!(w > 0)) {
|
|
69
|
+
unband(records[p]);
|
|
67
70
|
continue;
|
|
71
|
+
}
|
|
68
72
|
let k = 0;
|
|
69
73
|
for (let i = 0; i < n; i++) {
|
|
70
74
|
const ch = chans[i];
|
package/dist/locus.d.ts
CHANGED
|
@@ -21,6 +21,8 @@ export interface LocusScratch<I, O> {
|
|
|
21
21
|
mGroups: number[];
|
|
22
22
|
values: unknown[];
|
|
23
23
|
taken: Float64Array;
|
|
24
|
+
/** Per slot of a stock `last` channel, the weight of the member its value came from. */
|
|
25
|
+
heaviest: Float64Array;
|
|
24
26
|
met: Uint8Array;
|
|
25
27
|
touched: number[];
|
|
26
28
|
owned: (number[] | undefined)[];
|
|
@@ -34,7 +36,8 @@ export declare function own<I, O>(k: LocusScratch<I, O>, slot: number, value: nu
|
|
|
34
36
|
/**
|
|
35
37
|
* Folds one locus's members into one contribution through each channel's own `lerp`: its value per
|
|
36
38
|
* channel goes in `k.values`, the channels in `k.touched` in the order first met, and its weight
|
|
37
|
-
* is returned. A channel
|
|
39
|
+
* is returned. A stock `last` channel has nothing to lerp, so it takes its heaviest member's value,
|
|
40
|
+
* the later cued of two as heavy. A channel folding by scale takes its value in an array of `k`'s own, lerped in
|
|
38
41
|
* place, since the fold only reads it.
|
|
39
42
|
*/
|
|
40
43
|
export declare function foldLocus<I, O>(this: Mixer<I, O>, k: LocusScratch<I, O>, group: number): number;
|
package/dist/locus.js
CHANGED
|
@@ -15,6 +15,7 @@ export function locusScratch(channels) {
|
|
|
15
15
|
mGroups: [],
|
|
16
16
|
values: new Array(channels).fill(undefined),
|
|
17
17
|
taken: new Float64Array(channels),
|
|
18
|
+
heaviest: new Float64Array(channels),
|
|
18
19
|
met: new Uint8Array(channels),
|
|
19
20
|
touched: [],
|
|
20
21
|
owned: new Array(channels).fill(undefined),
|
|
@@ -37,7 +38,8 @@ export function own(k, slot, value, rest) {
|
|
|
37
38
|
/**
|
|
38
39
|
* Folds one locus's members into one contribution through each channel's own `lerp`: its value per
|
|
39
40
|
* channel goes in `k.values`, the channels in `k.touched` in the order first met, and its weight
|
|
40
|
-
* is returned. A channel
|
|
41
|
+
* is returned. A stock `last` channel has nothing to lerp, so it takes its heaviest member's value,
|
|
42
|
+
* the later cued of two as heavy. A channel folding by scale takes its value in an array of `k`'s own, lerped in
|
|
41
43
|
* place, since the fold only reads it.
|
|
42
44
|
*/
|
|
43
45
|
export function foldLocus(k, group) {
|
|
@@ -67,6 +69,7 @@ export function foldLocus(k, group) {
|
|
|
67
69
|
k.met[slot] = 1;
|
|
68
70
|
k.touched.push(slot);
|
|
69
71
|
taken[slot] = weight;
|
|
72
|
+
k.heaviest[slot] = weight;
|
|
70
73
|
k.values[slot] =
|
|
71
74
|
this.lerpsInto[slot] !== undefined && channel.scale && Array.isArray(value)
|
|
72
75
|
? own(k, slot, value, channel.rest)
|
|
@@ -74,13 +77,20 @@ export function foldLocus(k, group) {
|
|
|
74
77
|
continue;
|
|
75
78
|
}
|
|
76
79
|
const total = taken[slot] + weight;
|
|
80
|
+
taken[slot] = total;
|
|
81
|
+
if (channel.kind === 'last') {
|
|
82
|
+
if (weight >= k.heaviest[slot]) {
|
|
83
|
+
k.heaviest[slot] = weight;
|
|
84
|
+
k.values[slot] = first(value);
|
|
85
|
+
}
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
77
88
|
const was = k.values[slot];
|
|
78
89
|
const lerp = this.lerpsInto[slot];
|
|
79
90
|
k.values[slot] =
|
|
80
91
|
lerp !== undefined && was === k.owned[slot]
|
|
81
92
|
? lerp(was, was, value, weight / total)
|
|
82
93
|
: channel.lerp(was, value, weight / total);
|
|
83
|
-
taken[slot] = total;
|
|
84
94
|
}
|
|
85
95
|
}
|
|
86
96
|
for (let t = 0; t < k.touched.length; t++)
|
package/dist/marks.js
CHANGED
|
@@ -99,8 +99,8 @@ export function markOf(mix, voice, mark) {
|
|
|
99
99
|
if (Number.isFinite(t))
|
|
100
100
|
outAt = Math.max(start, t);
|
|
101
101
|
}
|
|
102
|
-
else if (!voice.
|
|
103
|
-
const t = voice.
|
|
102
|
+
else if (!(voice.backs ? voice.freezesBefore : voice.freezesAfter)) {
|
|
103
|
+
const t = voice.endsAt();
|
|
104
104
|
if (Number.isFinite(t))
|
|
105
105
|
outAt = Math.max(start, t);
|
|
106
106
|
}
|
|
@@ -138,8 +138,8 @@ function coastOf(mix, voice, start) {
|
|
|
138
138
|
t = Math.max(t, c);
|
|
139
139
|
}
|
|
140
140
|
}
|
|
141
|
-
else
|
|
142
|
-
t = voice.
|
|
141
|
+
else
|
|
142
|
+
t = voice.endsAt();
|
|
143
143
|
if (t === undefined || !Number.isFinite(t))
|
|
144
144
|
return undefined;
|
|
145
145
|
t = Math.max(start, t);
|
package/dist/motion.d.ts
CHANGED
|
@@ -40,7 +40,8 @@ export type Moving<I, O, V extends Value> = Patch<I, O, void> & {
|
|
|
40
40
|
/**
|
|
41
41
|
* Sets `subject` moving at `velocity`, units per second, from voice time `at`, default the mix's
|
|
42
42
|
* latest frame; for a subject no frame of its voice has met yet, or still inside its stagger, its
|
|
43
|
-
* first read, after any change with a time due by then.
|
|
43
|
+
* first read, after any change with a time due by then. A subject faded out of the voice comes
|
|
44
|
+
* back at the latest frame too, however long it is before it is next read.
|
|
44
45
|
*/
|
|
45
46
|
push(subject: I, velocity: V, at?: number): void;
|
|
46
47
|
};
|
|
@@ -76,7 +77,8 @@ export declare function spring<I, O, V extends Value = number>(writes: keyof O,
|
|
|
76
77
|
/**
|
|
77
78
|
* Heads `subject` for `target` from voice time `at`, keeping its velocity: by default the mix's
|
|
78
79
|
* latest frame, or for a subject no frame of its voice has met yet, or still inside its stagger,
|
|
79
|
-
* its first read, after any change with a time due by then.
|
|
80
|
+
* its first read, after any change with a time due by then. A subject faded out of the voice
|
|
81
|
+
* comes back at the latest frame too, however long it is before it is next read.
|
|
80
82
|
*/
|
|
81
83
|
to(subject: I, target: V, at?: number): void;
|
|
82
84
|
};
|
|
@@ -115,7 +117,8 @@ export declare function tween<I, O, V extends Value = number>(writes: keyof O, o
|
|
|
115
117
|
/**
|
|
116
118
|
* Heads `subject` for `target` from voice time `at`, over a full `ms` from where it is: by default
|
|
117
119
|
* the mix's latest frame, or for a subject no frame of its voice has met yet, or still inside its
|
|
118
|
-
* stagger, its first read, after any change with a time due by then.
|
|
120
|
+
* stagger, its first read, after any change with a time due by then. A subject faded out of the
|
|
121
|
+
* voice comes back at the latest frame too, however long it is before it is next read.
|
|
119
122
|
*/
|
|
120
123
|
to(subject: I, target: V, at?: number): void;
|
|
121
124
|
};
|
package/dist/motions.d.ts
CHANGED
|
@@ -33,8 +33,11 @@ export interface Change {
|
|
|
33
33
|
export interface MotionOwner {
|
|
34
34
|
/** The subject's voice time at the mix's latest frame; NaN where no frame of the voice met it. */
|
|
35
35
|
frame(id: number, subject: unknown): number;
|
|
36
|
-
/**
|
|
37
|
-
|
|
36
|
+
/**
|
|
37
|
+
* Brings a subject the voice faded out back to it, so a change is not made for nothing: its
|
|
38
|
+
* voice time now, which a change bringing it back is made at; NaN for any other subject.
|
|
39
|
+
*/
|
|
40
|
+
revive(id: number, subject: unknown): number;
|
|
38
41
|
/** The mix clock at its latest frame; NaN before the first. */
|
|
39
42
|
now(): number;
|
|
40
43
|
/** The number of the frame the mix is playing. */
|
package/dist/motions.js
CHANGED
|
@@ -454,7 +454,7 @@ export class Motions {
|
|
|
454
454
|
* The queue keeps the changes with a time in time order, ahead of any still waiting for one.
|
|
455
455
|
*/
|
|
456
456
|
change(subject, c) {
|
|
457
|
-
this.owner
|
|
457
|
+
const back = this.owner === null ? Number.NaN : this.owner.revive(this.ownerId, subject);
|
|
458
458
|
const s = this.slot(subject);
|
|
459
459
|
this.check(c.to, this.n);
|
|
460
460
|
this.check(c.v, this.n);
|
|
@@ -464,6 +464,8 @@ export class Motions {
|
|
|
464
464
|
const at = this.frame(subject);
|
|
465
465
|
if (!Number.isNaN(at))
|
|
466
466
|
c.at = at;
|
|
467
|
+
else if (!Number.isNaN(back))
|
|
468
|
+
c.at = back;
|
|
467
469
|
}
|
|
468
470
|
const list = this.pending?.[s];
|
|
469
471
|
if (list === undefined) {
|
package/dist/move.js
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
import { changed } from './chain.js';
|
|
2
|
+
import { elapsedWith } from './clock.js';
|
|
2
3
|
import { popDue, schedule } from './due.js';
|
|
3
4
|
import { detour } from './everyone.js';
|
|
4
5
|
import { beginFade, part, retire } from './fade.js';
|
|
5
6
|
import { expireGone, keepGone } from './gone.js';
|
|
6
|
-
import {
|
|
7
|
+
import { begin } from './held.js';
|
|
8
|
+
import { noted } from './history.js';
|
|
9
|
+
import { leave as leaveClock } from './origin.js';
|
|
7
10
|
import { ownerReading } from './owner.js';
|
|
8
11
|
import { mixAt, place, repin, startOf } from './place.js';
|
|
9
12
|
import { pageVoice } from './revive.js';
|
|
@@ -60,28 +63,37 @@ function moveTo(mix, now) {
|
|
|
60
63
|
if (Number.isNaN(voice.opened))
|
|
61
64
|
voice.opened = now;
|
|
62
65
|
if (voice.state === 'pending' &&
|
|
63
|
-
(voice.owner === null ? now : ownerReading(voice.owner, now)) >= voice.start)
|
|
64
|
-
voice
|
|
65
|
-
reorigin(mix, voice, now);
|
|
66
|
-
changed(mix, voice);
|
|
67
|
-
}
|
|
66
|
+
(voice.owner === null ? now : ownerReading(voice.owner, now)) >= voice.start)
|
|
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
|
if ((voice.state === 'live' || voice.state === 'frozen') && voice.outAt <= now)
|
|
70
70
|
beginFade(mix, voice, { over: Number.isNaN(voice.outOver) ? undefined : voice.outOver }, Math.max(startOf(voice), voice.outAt));
|
|
71
|
-
if (voice.state !== 'pending'
|
|
72
|
-
|
|
73
|
-
|
|
71
|
+
if (voice.state !== 'pending') {
|
|
72
|
+
if (voice.turns)
|
|
73
|
+
turn(mix, voice, now);
|
|
74
|
+
// Its passes run out at its end running forward, and at its start running back. A clock
|
|
75
|
+
// standing still at or before its start is where it was: waiting, or frozen there.
|
|
76
|
+
const way = heading(voice, now);
|
|
77
|
+
const at = voice.elapsedAt(now);
|
|
78
|
+
const back = way < 0;
|
|
79
|
+
const atStart = !voice.beginless && at <= voice.soonest;
|
|
80
|
+
// An owner run back to its start stays for what it holds, and leaves with its last child.
|
|
81
|
+
const holds = voice.holding !== null && voice.holding.children.length > 0;
|
|
82
|
+
const over = back
|
|
83
|
+
? atStart && !holds
|
|
84
|
+
: Number.isFinite(voice.span) && at >= voice.span + voice.latest;
|
|
85
|
+
if (over) {
|
|
74
86
|
voice.play(true, now, mix.transport.seq);
|
|
75
|
-
// The fade starts when
|
|
87
|
+
// The fade starts when its passes ran out, not at the frame that noticed, so it plays
|
|
76
88
|
// the same at any frame rate and a read at another time can find it.
|
|
77
89
|
if (voice.state === 'live') {
|
|
78
|
-
if (voice.freezesAfter)
|
|
90
|
+
if (back ? voice.freezesBefore : voice.freezesAfter)
|
|
79
91
|
voice.state = 'frozen';
|
|
80
92
|
else
|
|
81
|
-
beginFade(mix, voice, {}, Math.max(startOf(voice), Math.min(now, voice.
|
|
93
|
+
beginFade(mix, voice, { back }, Math.max(startOf(voice), Math.min(now, voice.endsAt())));
|
|
82
94
|
}
|
|
83
95
|
}
|
|
84
|
-
else if (voice.state === 'frozen')
|
|
96
|
+
else if (voice.state === 'frozen' && !(atStart && way === 0))
|
|
85
97
|
voice.state = 'live';
|
|
86
98
|
}
|
|
87
99
|
// A projection reads a finished ramp as weight 0, and leaves the live voice's subjects be.
|
|
@@ -118,6 +130,31 @@ function moveTo(mix, now) {
|
|
|
118
130
|
prune(mix);
|
|
119
131
|
forget(mix);
|
|
120
132
|
}
|
|
133
|
+
/**
|
|
134
|
+
* Which way a voice's clock is running at mix time `now`, through every owner above it: above 0
|
|
135
|
+
* forward, below 0 back, 0 standing still.
|
|
136
|
+
*/
|
|
137
|
+
function heading(voice, now) {
|
|
138
|
+
let way = 1;
|
|
139
|
+
for (let v = voice; v !== null; v = v.owner)
|
|
140
|
+
way *= Math.sign(v.rateAt(v.owner === null ? now : ownerReading(v.owner, now)));
|
|
141
|
+
return way;
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* A ramp through 0 has turned a voice's clock around by `now`: the clock is anchored again where
|
|
145
|
+
* it turned, so it runs one way from there and the time it reads any position is one time.
|
|
146
|
+
*/
|
|
147
|
+
function turn(mix, voice, now) {
|
|
148
|
+
const r = voice.ramp;
|
|
149
|
+
const at = voice.anchorNow + (r.over * r.from) / (r.from - r.to);
|
|
150
|
+
if ((voice.owner === null ? now : ownerReading(voice.owner, now)) < at)
|
|
151
|
+
return;
|
|
152
|
+
leaveClock(voice, at);
|
|
153
|
+
voice.anchorElapsed = elapsedWith(voice, at);
|
|
154
|
+
voice.ramp = { from: 0, to: r.to, over: voice.anchorNow + r.over - at };
|
|
155
|
+
voice.anchorNow = at;
|
|
156
|
+
noted(mix, voice);
|
|
157
|
+
}
|
|
121
158
|
/** Past this many voices retiring in one frame, one pass over the list beats a search for each. */
|
|
122
159
|
const SEARCHED = 8;
|
|
123
160
|
/**
|
|
@@ -208,7 +245,7 @@ export function forget(mix) {
|
|
|
208
245
|
// With a store a voice that has left pages out where it can, and stays where it cannot.
|
|
209
246
|
if (mix.transport.pager !== null) {
|
|
210
247
|
const reach = mix.now - history.ms;
|
|
211
|
-
keepGone(mix, (v) => v.
|
|
248
|
+
keepGone(mix, (v) => v.reachedTo >= reach || !pageVoice(mix, v, reach));
|
|
212
249
|
}
|
|
213
250
|
else {
|
|
214
251
|
expireGone(mix, mix.transport.keepsFrom());
|
package/dist/origin.js
CHANGED
|
@@ -48,8 +48,9 @@ function fresh(voice) {
|
|
|
48
48
|
*/
|
|
49
49
|
export function originOf(voice, elapsed, rebuilt) {
|
|
50
50
|
const past = voice.clocks;
|
|
51
|
-
|
|
52
|
-
|
|
51
|
+
// A voice with no first pass shows every subject from its start, whatever its stagger.
|
|
52
|
+
let t = voice.beginless ? voice.start : Number.NaN;
|
|
53
|
+
if (past !== null && Number.isNaN(t)) {
|
|
53
54
|
for (let i = rebuilt ? fresh(voice) : 0; i < past.length; i++) {
|
|
54
55
|
const p = past[i];
|
|
55
56
|
const at = timeWith(p, elapsed);
|
package/dist/owner.d.ts
CHANGED
|
@@ -19,6 +19,8 @@ export declare function ownerReading<I, O>(owner: Voice<I, O>, now: number): num
|
|
|
19
19
|
export declare function ownedElapsed<I, O>(voice: Voice<I, O>, owner: Voice<I, O>, now: number): number;
|
|
20
20
|
/** The mix time an owner's clock reads `t`, by its controls now; Infinity where it never will. */
|
|
21
21
|
export declare function mixTime<I, O>(owner: Voice<I, O>, t: number): number;
|
|
22
|
+
/** Whether an odd number of the owners above a voice run back, which turns its clock around. */
|
|
23
|
+
export declare function turnedBack<I, O>(voice: Voice<I, O>): boolean;
|
|
22
24
|
/** Takes a child into its owner. */
|
|
23
25
|
export declare function adopt<I, O>(owner: Voice<I, O>, child: Voice<I, O>): void;
|
|
24
26
|
/** A child left: its owner, where that was its last child and it should leave too. */
|
package/dist/owner.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { clampWeight, elapsedWith, envelope, timeWith } from './clock.js';
|
|
1
|
+
import { backWith, clampWeight, elapsedWith, envelope, timeWith } from './clock.js';
|
|
2
2
|
/** What an owner is cued with in place of a patch: it writes nothing, so no fold ever meets it. */
|
|
3
3
|
export const ownerPatch = Object.freeze({
|
|
4
4
|
form: 'fn',
|
|
@@ -29,9 +29,17 @@ export function ownedElapsed(voice, owner, now) {
|
|
|
29
29
|
}
|
|
30
30
|
/** The mix time an owner's clock reads `t`, by its controls now; Infinity where it never will. */
|
|
31
31
|
export function mixTime(owner, t) {
|
|
32
|
-
const up = timeWith(owner, t);
|
|
32
|
+
const up = owner.backs ? backWith(owner, t) : timeWith(owner, t);
|
|
33
33
|
return owner.owner === null ? up : mixTime(owner.owner, up);
|
|
34
34
|
}
|
|
35
|
+
/** Whether an odd number of the owners above a voice run back, which turns its clock around. */
|
|
36
|
+
export function turnedBack(voice) {
|
|
37
|
+
let back = false;
|
|
38
|
+
for (let o = voice.owner; o !== null; o = o.owner)
|
|
39
|
+
if (o.backs)
|
|
40
|
+
back = !back;
|
|
41
|
+
return back;
|
|
42
|
+
}
|
|
35
43
|
/** Takes a child into its owner. */
|
|
36
44
|
export function adopt(owner, child) {
|
|
37
45
|
const h = owner.holding;
|
package/dist/place.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { same } from './clock.js';
|
|
1
2
|
import { departedMatches } from './departed.js';
|
|
2
3
|
import { reorigin } from './held.js';
|
|
3
4
|
import { noted } from './history.js';
|
|
@@ -5,6 +6,7 @@ import { markOf } from './marks.js';
|
|
|
5
6
|
import { mixTime, ownerReading } from './owner.js';
|
|
6
7
|
import { holders, scored, scoredGone, scoreVersion, unhold } from './scored.js';
|
|
7
8
|
import { none } from './voice.js';
|
|
9
|
+
import { warnOnce } from './warn.js';
|
|
8
10
|
/** What a voice's own owner's clock reads now: the mix clock for a voice no owner holds. */
|
|
9
11
|
export function localNow(mix, voice) {
|
|
10
12
|
return voice.owner === null ? mix.now : ownerReading(voice.owner, mix.now);
|
|
@@ -38,7 +40,6 @@ function startAt(mix, voice, start) {
|
|
|
38
40
|
return false;
|
|
39
41
|
voice.start = start;
|
|
40
42
|
voice.anchorNow = start;
|
|
41
|
-
voice.anchorElapsed = 0;
|
|
42
43
|
voice.clocks = null;
|
|
43
44
|
// A clock moved outright, as a seek moves it: what a record computed at this mix time is stale.
|
|
44
45
|
voice.seeks++;
|
|
@@ -121,9 +122,22 @@ export function place(mix) {
|
|
|
121
122
|
const t = by === undefined ? undefined : resolve(mix, by, voice);
|
|
122
123
|
if (t !== undefined) {
|
|
123
124
|
const m = voice.owner === null ? t : mixTime(voice.owner, t);
|
|
124
|
-
const
|
|
125
|
-
|
|
125
|
+
const start = startOf(voice);
|
|
126
|
+
let at = anchor.out !== undefined ? m : m - (voice.fade.out ?? 0);
|
|
127
|
+
let over = Number.NaN;
|
|
128
|
+
// A fade out begins no earlier than its voice starts, and one that has to end by `m`
|
|
129
|
+
// is shortened to; an end before the start leaves the voice an instant at its start.
|
|
130
|
+
if (at < start && Number.isFinite(start)) {
|
|
131
|
+
at = start;
|
|
132
|
+
if (anchor.end !== undefined) {
|
|
133
|
+
over = m > start ? m - start : 0;
|
|
134
|
+
if (m < start && !mix.projecting)
|
|
135
|
+
warnOnce(voice, `${voice.spec.name ?? `voice ${voice.id}`}'s placement ends before it starts, so it plays as an instant`);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
if (at !== voice.outAt || !same(over, voice.outOver)) {
|
|
126
139
|
voice.outAt = at;
|
|
140
|
+
voice.outOver = over;
|
|
127
141
|
noted(mix, voice);
|
|
128
142
|
moved = true;
|
|
129
143
|
}
|
package/dist/project.d.ts
CHANGED
|
@@ -15,4 +15,4 @@ export declare function mirror<I, O>(mix: Mixer<I, O>, c: Mixer<I, O>): void;
|
|
|
15
15
|
* A projection back starts each subject from the latest copy kept at or before `t`, else from its
|
|
16
16
|
* voice's start with fresh state, which is exact for a voice stepped at a fixed interval.
|
|
17
17
|
*/
|
|
18
|
-
export declare function recall<I, O>(mix: Mixer<I, O>, voice: Voice<I, O>, subject: I, t: number, cut: Cut): Subject<unknown> | undefined;
|
|
18
|
+
export declare function recall<I, O>(mix: Mixer<I, O>, voice: Voice<I, O>, subject: I, t: number, cut: Cut, c?: Mixer<I, O>, copy?: Voice<I, O>): Subject<unknown> | undefined;
|
package/dist/project.js
CHANGED
|
@@ -68,7 +68,9 @@ export function projectAll(transport, t) {
|
|
|
68
68
|
else
|
|
69
69
|
copyStanding(mix, copy, t, cut);
|
|
70
70
|
}
|
|
71
|
-
|
|
71
|
+
// A read at the mix's own time lands nothing, as a probe does not: what the host has done since
|
|
72
|
+
// the last sync shows at the next one.
|
|
73
|
+
if (ahead && t !== transport.now)
|
|
72
74
|
for (const copy of copies.values())
|
|
73
75
|
move(copy, t);
|
|
74
76
|
const read = (f) => {
|
|
@@ -131,7 +133,7 @@ function copyBack(mix, c, t, cut) {
|
|
|
131
133
|
.sort((a, b) => a.id - b.id)
|
|
132
134
|
.map((v) => {
|
|
133
135
|
const log = v.log;
|
|
134
|
-
const copy = v.copy((subject) => recall(mix, v, subject, t, cut), lastWithin(log, cut) ?? log[0]);
|
|
136
|
+
const copy = v.copy((subject) => recall(mix, v, subject, t, cut, c, copy), lastWithin(log, cut) ?? log[0]);
|
|
135
137
|
if (then !== undefined)
|
|
136
138
|
copy.setting.host = then;
|
|
137
139
|
return copy;
|
|
@@ -199,7 +201,7 @@ function carry(mix, voice, subject, c, copy) {
|
|
|
199
201
|
* A projection back starts each subject from the latest copy kept at or before `t`, else from its
|
|
200
202
|
* voice's start with fresh state, which is exact for a voice stepped at a fixed interval.
|
|
201
203
|
*/
|
|
202
|
-
export function recall(mix, voice, subject, t, cut) {
|
|
204
|
+
export function recall(mix, voice, subject, t, cut, c, copy) {
|
|
203
205
|
const live = voice.left?.at(subject, cut) ?? recordOf(mix, voice, subject);
|
|
204
206
|
if (live === undefined)
|
|
205
207
|
return undefined;
|
|
@@ -210,13 +212,16 @@ export function recall(mix, voice, subject, t, cut) {
|
|
|
210
212
|
h.replay = live.inputs;
|
|
211
213
|
return h;
|
|
212
214
|
}
|
|
213
|
-
|
|
215
|
+
// Its origin by the controls the voice had then: a seek or an anchor has moved a waiting
|
|
216
|
+
// voice's since.
|
|
217
|
+
const since = c === undefined || copy === undefined ? live.since : c.sinceOf(copy, live.delay);
|
|
218
|
+
const stepped = since < t ? since : t;
|
|
214
219
|
const h = record(voice, live.reaches, live.reaches && voice.patch.state
|
|
215
220
|
? voice.patch.state(subject)
|
|
216
221
|
: undefined);
|
|
217
222
|
h.delay = live.delay;
|
|
218
|
-
h.since =
|
|
219
|
-
h.shown = live.shown;
|
|
223
|
+
h.since = since;
|
|
224
|
+
h.shown = c === undefined || copy === undefined ? live.shown : c.shownOf(copy, since);
|
|
220
225
|
h.stepped = stepped;
|
|
221
226
|
h.rebuilt = voice.rebuilds;
|
|
222
227
|
h.from = stepped;
|
package/dist/record.js
CHANGED
|
@@ -15,7 +15,8 @@ export function record(voice, reaches, state) {
|
|
|
15
15
|
rested: false,
|
|
16
16
|
bands: null,
|
|
17
17
|
state,
|
|
18
|
-
|
|
18
|
+
// -0, not 0: a field first given a small integer is boxed on every later write of a double.
|
|
19
|
+
stepped: -0,
|
|
19
20
|
ticks: 0,
|
|
20
21
|
probed: Number.NaN,
|
|
21
22
|
delta: null,
|
package/dist/revive.d.ts
CHANGED
package/dist/revive.js
CHANGED
|
@@ -92,6 +92,7 @@ function packVoice(voice, keys) {
|
|
|
92
92
|
opened: voice.opened,
|
|
93
93
|
pinned: voice.pinned,
|
|
94
94
|
latest: voice.latest,
|
|
95
|
+
soonest: voice.soonest,
|
|
95
96
|
keeping: voice.keeping,
|
|
96
97
|
first: voice.first,
|
|
97
98
|
log: voice.log,
|
|
@@ -128,6 +129,7 @@ export function reviveVoice(mix, id, d) {
|
|
|
128
129
|
voice.opened = d.opened;
|
|
129
130
|
voice.pinned = d.pinned;
|
|
130
131
|
voice.latest = d.latest;
|
|
132
|
+
voice.soonest = d.soonest ?? 0;
|
|
131
133
|
voice.keeping = d.keeping;
|
|
132
134
|
voice.first = d.first;
|
|
133
135
|
voice.log = d.log;
|
package/dist/seek.js
CHANGED
|
@@ -143,7 +143,7 @@ export function seek(transport, t) {
|
|
|
143
143
|
throw new RangeError('blits: a mix seeks to a finite time');
|
|
144
144
|
if (!(t >= transport.born))
|
|
145
145
|
throw new Error(`blits: ${t} is older than this mix's history reaches`);
|
|
146
|
-
|
|
146
|
+
cover(transport, t);
|
|
147
147
|
if (t >= transport.now) {
|
|
148
148
|
// Ahead, under the rate the recorded calls up to `t` set, past any frames rate 0 held at `t`.
|
|
149
149
|
const last = replay(transport, (at) => (transport.pace === null ? at : transport.pace.reading(at)) <= t);
|
|
@@ -176,7 +176,7 @@ export function seek(transport, t) {
|
|
|
176
176
|
transport.now = t;
|
|
177
177
|
for (const m of transport.members)
|
|
178
178
|
back(m, t, seq, frame.at < t);
|
|
179
|
-
cut(transport, seq
|
|
179
|
+
cut(transport, seq);
|
|
180
180
|
}
|
|
181
181
|
transport.tick(t);
|
|
182
182
|
// The host's clock reads on from here: its next sync reads `t` plus its time since its last.
|
package/dist/spans.d.ts
CHANGED
|
@@ -16,11 +16,12 @@ export declare class Fitting {
|
|
|
16
16
|
/** Refuses a voice a span cannot fit: one it would not own the timing of, or one with no end. */
|
|
17
17
|
export declare function checkChild<I, O>(spec: VoiceSpec<I, O>, child: Voice<I, O>): void;
|
|
18
18
|
/**
|
|
19
|
-
* Lays out a span's children and fits them into what is left of its budget, from its clock
|
|
20
|
-
* Only what is still to come moves: a playing child keeps its place and
|
|
21
|
-
* pending one is placed. A span held by a span is fitted again by its own span
|
|
19
|
+
* Lays out a span's children and fits them into what is left of its budget, from its clock at mix
|
|
20
|
+
* time `from`, default now. Only what is still to come moves: a playing child keeps its place and
|
|
21
|
+
* may change rate, a pending one is placed. A span held by a span is fitted again by its own span
|
|
22
|
+
* after.
|
|
22
23
|
*/
|
|
23
|
-
export declare function refit<I, O>(mix: Mixer<I, O>, span: Voice<I, O
|
|
24
|
+
export declare function refit<I, O>(mix: Mixer<I, O>, span: Voice<I, O>, from?: number): void;
|
|
24
25
|
/** Whether a span whose last child just left at mix time `at` stays, its budget not yet out. */
|
|
25
26
|
export declare function lingers<I, O>(span: Voice<I, O>, at: number): boolean;
|
|
26
27
|
/** Lets the spans that are empty and out of budget leave, at the moment their budget ran out. */
|
package/dist/spans.js
CHANGED
|
@@ -46,11 +46,11 @@ export function checkChild(spec, child) {
|
|
|
46
46
|
else if (!Number.isFinite(child.span))
|
|
47
47
|
throw new Error('blits: a span cannot fit a voice that never ends');
|
|
48
48
|
}
|
|
49
|
-
/** A span's own clock at
|
|
50
|
-
function clockOf(
|
|
51
|
-
if (Number.isNaN(
|
|
49
|
+
/** A span's own clock at mix time `at`, from 0 while it has not started. */
|
|
50
|
+
function clockOf(span, at) {
|
|
51
|
+
if (Number.isNaN(at))
|
|
52
52
|
return 0;
|
|
53
|
-
const t = ownerReading(span,
|
|
53
|
+
const t = ownerReading(span, at);
|
|
54
54
|
return Number.isFinite(t) ? Math.max(0, t) : 0;
|
|
55
55
|
}
|
|
56
56
|
/** What a child's own clock reads at its span's `local`, 0 while it waits. */
|
|
@@ -63,15 +63,16 @@ function leftOf(child, local) {
|
|
|
63
63
|
return Math.max(0, length - elapsedIn(child, local));
|
|
64
64
|
}
|
|
65
65
|
/**
|
|
66
|
-
* Lays out a span's children and fits them into what is left of its budget, from its clock
|
|
67
|
-
* Only what is still to come moves: a playing child keeps its place and
|
|
68
|
-
* pending one is placed. A span held by a span is fitted again by its own span
|
|
66
|
+
* Lays out a span's children and fits them into what is left of its budget, from its clock at mix
|
|
67
|
+
* time `from`, default now. Only what is still to come moves: a playing child keeps its place and
|
|
68
|
+
* may change rate, a pending one is placed. A span held by a span is fitted again by its own span
|
|
69
|
+
* after.
|
|
69
70
|
*/
|
|
70
|
-
export function refit(mix, span) {
|
|
71
|
+
export function refit(mix, span, from = mix.now) {
|
|
71
72
|
const fitting = span.fitting;
|
|
72
73
|
if (mix.projecting || span.state === 'done')
|
|
73
74
|
return;
|
|
74
|
-
const local = clockOf(
|
|
75
|
+
const local = clockOf(span, from);
|
|
75
76
|
const kids = (span.holding?.children ?? []).filter((c) => c.state !== 'done' && c.state !== 'frozen' && c.state !== 'fading' && leftOf(c, local) > 0);
|
|
76
77
|
const claims = kids.map((c) => {
|
|
77
78
|
const s = c.spec;
|
|
@@ -143,7 +144,7 @@ export function refit(mix, span) {
|
|
|
143
144
|
fitting.end = end;
|
|
144
145
|
noted(mix, span);
|
|
145
146
|
if (span.owner?.fitting)
|
|
146
|
-
refit(mix, span.owner);
|
|
147
|
+
refit(mix, span.owner, from);
|
|
147
148
|
}
|
|
148
149
|
}
|
|
149
150
|
/** Whether a span whose last child just left at mix time `at` stays, its budget not yet out. */
|
package/dist/strays.js
CHANGED
|
@@ -64,14 +64,18 @@ export function motionOwner(mix) {
|
|
|
64
64
|
revive(id, subject) {
|
|
65
65
|
const m = mix.deref();
|
|
66
66
|
if (m === undefined)
|
|
67
|
-
return;
|
|
67
|
+
return Number.NaN;
|
|
68
68
|
m.stir();
|
|
69
69
|
const v = cuedById(m, id);
|
|
70
70
|
if (v === undefined)
|
|
71
|
-
return;
|
|
71
|
+
return Number.NaN;
|
|
72
72
|
if (v.motion !== undefined && v.named !== null && !v.named.has(subject))
|
|
73
73
|
stray(m, v.motion, subject);
|
|
74
|
+
if (v.parted?.has(subject) !== true)
|
|
75
|
+
return Number.NaN;
|
|
74
76
|
unpart(m, v, subject);
|
|
77
|
+
// Its record is gone until it is next read, so its stagger is asked here as it will be there.
|
|
78
|
+
return timeIn(m, v, v.spec.stagger ? v.spec.stagger(subject) : 0);
|
|
75
79
|
},
|
|
76
80
|
};
|
|
77
81
|
}
|
|
@@ -98,11 +102,15 @@ export function cuedById(mix, id) {
|
|
|
98
102
|
* subject's own time is still short of its stagger. A retired voice's patch no longer asks.
|
|
99
103
|
*/
|
|
100
104
|
export function frameOf(mix, voice, subject) {
|
|
101
|
-
if (Number.isNaN(mix.now) || voice.state === 'pending')
|
|
102
|
-
return Number.NaN;
|
|
103
105
|
const held = voice.subjects.get(subject);
|
|
104
106
|
if (held === undefined || !held.reaches)
|
|
105
107
|
return Number.NaN;
|
|
106
|
-
|
|
108
|
+
return timeIn(mix, voice, held.delay);
|
|
109
|
+
}
|
|
110
|
+
/** The voice time at the latest frame of a subject staggered by `delay`; NaN before it has any. */
|
|
111
|
+
function timeIn(mix, voice, delay) {
|
|
112
|
+
if (Number.isNaN(mix.now) || voice.state === 'pending')
|
|
113
|
+
return Number.NaN;
|
|
114
|
+
const t = voice.elapsedAt(mix.now) - delay;
|
|
107
115
|
return t < 0 ? Number.NaN : t;
|
|
108
116
|
}
|
package/dist/transport.js
CHANGED
|
@@ -7,7 +7,7 @@ import { pin } from './place.js';
|
|
|
7
7
|
import { projectAll } from './project.js';
|
|
8
8
|
import { scoredDrop, scoredJoin } from './scored.js';
|
|
9
9
|
import { seek } from './seek.js';
|
|
10
|
-
import { record, replay, tapeOf } from './tape.js';
|
|
10
|
+
import { hostAt, record, replay, tapeOf } from './tape.js';
|
|
11
11
|
/** How far the tape's reach moves before it is pruned again, ms of mix time. */
|
|
12
12
|
const TAPE_STEP_MS = 1000;
|
|
13
13
|
/** Whether a change made in frame `seq` falls inside `cut`; `early` for one made before the frame was read. */
|
|
@@ -216,7 +216,9 @@ export class Transport {
|
|
|
216
216
|
// In steps: the tape shifts every entry it keeps to let go of the oldest, and one a second
|
|
217
217
|
// behind reach is never played again.
|
|
218
218
|
if (this.tape !== undefined && history !== undefined && !(reach - this.taped < TAPE_STEP_MS)) {
|
|
219
|
-
this
|
|
219
|
+
const from = hostAt(this, reach);
|
|
220
|
+
if (from < Number.POSITIVE_INFINITY)
|
|
221
|
+
this.tape.prune(from);
|
|
220
222
|
this.taped = reach;
|
|
221
223
|
}
|
|
222
224
|
if (history !== undefined && this.dropped.length > 0)
|
package/dist/types/mix.d.ts
CHANGED
|
@@ -192,8 +192,9 @@ export interface Mix<I, O, H = unknown> {
|
|
|
192
192
|
*/
|
|
193
193
|
pull(subjects: Iterable<I>, into: Columns<O>): void;
|
|
194
194
|
/**
|
|
195
|
-
* Reads the mix at another mix time, without moving it.
|
|
196
|
-
*
|
|
195
|
+
* Reads the mix at another mix time, without moving it. At the mix's own time it reads what
|
|
196
|
+
* `probe` does: what the host has done since the last sync lands at the next. Ahead of the mix
|
|
197
|
+
* it plays what is cued forward, and throws for a time past a call the tape will play again after a `seek` back, which
|
|
197
198
|
* only a seek there makes; behind it, with `history`, it reads the last frame at or before `time`
|
|
198
199
|
* as that frame showed, and throws for a time older than the history reaches. On a transport it
|
|
199
200
|
* reads every mix on it together, so anchors across them answer.
|