@msb235/blits 0.8.1 → 0.10.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 +98 -0
- package/README.md +12 -2
- package/dist/book.d.ts +6 -5
- package/dist/book.js +20 -16
- 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.d.ts +1 -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/mixer.d.ts +2 -1
- package/dist/mixer.js +3 -0
- 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 +6 -3
- package/dist/types/patch.d.ts +7 -3
- package/dist/types/place.d.ts +18 -8
- 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 +98 -32
- package/package.json +2 -2
package/dist/gone.js
CHANGED
|
@@ -12,15 +12,16 @@ export class GoneIndex {
|
|
|
12
12
|
count = 0;
|
|
13
13
|
general = [];
|
|
14
14
|
named = new Store();
|
|
15
|
-
/** The latest `
|
|
15
|
+
/** The latest `reachedTo` in `of`, and how far any voice's falls before one listed ahead of it. */
|
|
16
16
|
latest = Number.NEGATIVE_INFINITY;
|
|
17
17
|
slack = 0;
|
|
18
18
|
}
|
|
19
19
|
function add(ix, voice) {
|
|
20
|
-
|
|
21
|
-
|
|
20
|
+
const to = voice.reachedTo;
|
|
21
|
+
if (to < ix.latest)
|
|
22
|
+
ix.slack = Math.max(ix.slack, ix.latest - to);
|
|
22
23
|
else
|
|
23
|
-
ix.latest =
|
|
24
|
+
ix.latest = to;
|
|
24
25
|
if (voice.named === null) {
|
|
25
26
|
ix.general.push(voice);
|
|
26
27
|
return;
|
|
@@ -107,14 +108,14 @@ export function keepGone(mix, keep) {
|
|
|
107
108
|
ix.count = kept.length;
|
|
108
109
|
}
|
|
109
110
|
/**
|
|
110
|
-
* Lets go of the gone voices
|
|
111
|
-
*
|
|
112
|
-
* `reach`, none behind it
|
|
111
|
+
* Lets go of the gone voices no seek or read back to `reach` finds playing: those that finished
|
|
112
|
+
* before it, in a frame before it. Voices go in about that order, so only the front can hold one:
|
|
113
|
+
* past the point where a voice is reached `slack` after `reach`, none behind it falls before.
|
|
113
114
|
*/
|
|
114
115
|
export function expireGone(mix, reach) {
|
|
115
116
|
const ix = goneIndex(mix);
|
|
116
117
|
if (!Number.isFinite(ix.slack)) {
|
|
117
|
-
keepGone(mix, (v) => v.
|
|
118
|
+
keepGone(mix, (v) => v.reachedTo >= reach);
|
|
118
119
|
return;
|
|
119
120
|
}
|
|
120
121
|
const list = mix.gone;
|
|
@@ -122,7 +123,7 @@ export function expireGone(mix, reach) {
|
|
|
122
123
|
let end = 0;
|
|
123
124
|
let any = false;
|
|
124
125
|
for (let seen = Number.NEGATIVE_INFINITY; end < list.length && seen < edge; end++) {
|
|
125
|
-
const done = list[end].
|
|
126
|
+
const done = list[end].reachedTo;
|
|
126
127
|
if (done < reach)
|
|
127
128
|
any = true;
|
|
128
129
|
if (done > seen)
|
|
@@ -134,7 +135,7 @@ export function expireGone(mix, reach) {
|
|
|
134
135
|
const out = [];
|
|
135
136
|
for (let i = 0; i < end; i++) {
|
|
136
137
|
const v = list[i];
|
|
137
|
-
(v.
|
|
138
|
+
(v.reachedTo < reach ? out : kept).push(v);
|
|
138
139
|
}
|
|
139
140
|
list.splice(0, end, ...kept);
|
|
140
141
|
remove(ix, out);
|
package/dist/handle.js
CHANGED
|
@@ -85,12 +85,17 @@ export class VoiceHandle {
|
|
|
85
85
|
if (voice === null)
|
|
86
86
|
return;
|
|
87
87
|
voice.written = true;
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
88
|
+
if (voice.state === 'pending') {
|
|
89
|
+
// Its clock is anchored at its start, wherever that comes, and its rate is taken from there.
|
|
90
|
+
const from = voice.ramp === null ? voice.rate : voice.ramp.from;
|
|
91
|
+
voice.ramp = over > 0 && r !== from ? { from, to: r, over } : null;
|
|
92
|
+
voice.rate = r;
|
|
93
|
+
}
|
|
94
|
+
else {
|
|
95
|
+
const now = this.#host.nowFor(voice);
|
|
96
|
+
leave(voice, now);
|
|
97
|
+
retime(voice, now, r, over);
|
|
98
|
+
}
|
|
94
99
|
this.#host.changed(voice);
|
|
95
100
|
this.#host.record('rate', () => this.ramp(r, over));
|
|
96
101
|
}
|
|
@@ -99,9 +104,12 @@ export class VoiceHandle {
|
|
|
99
104
|
if (voice === null)
|
|
100
105
|
return 'exact';
|
|
101
106
|
voice.written = true;
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
107
|
+
// A pending voice's clock is anchored at its start, so it starts there on time.
|
|
108
|
+
if (voice.state !== 'pending') {
|
|
109
|
+
const u = this.#host.nowFor(voice);
|
|
110
|
+
leave(voice, u);
|
|
111
|
+
voice.rebase(u);
|
|
112
|
+
}
|
|
105
113
|
voice.anchorElapsed = elapsed;
|
|
106
114
|
voice.seeks++;
|
|
107
115
|
const doubt = this.#host.sought(voice, opts?.state !== 'keep');
|
package/dist/held.d.ts
CHANGED
|
@@ -9,7 +9,17 @@ export declare function held<I, O>(this: Mixer<I, O>, voice: Voice<I, O>, subjec
|
|
|
9
9
|
* counts from its last seek that rebuilt state, as its records do.
|
|
10
10
|
*/
|
|
11
11
|
export declare function sinceOf<I, O>(this: Mixer<I, O>, voice: Voice<I, O>, delay: number): number;
|
|
12
|
+
/**
|
|
13
|
+
* The mix time a subject's fade in counts from: `since`, but no earlier than its voice starts,
|
|
14
|
+
* where a voice sought ahead of its start has a subject's delay run out before it; and from when
|
|
15
|
+
* the voice first showed, for one freezing before.
|
|
16
|
+
*/
|
|
12
17
|
export declare function shownOf<I, O>(this: Mixer<I, O>, voice: Voice<I, O>, since: number): number;
|
|
18
|
+
/**
|
|
19
|
+
* A pending voice starts at `now`. Starting anywhere but 0 it was sought there, so a record made
|
|
20
|
+
* while it waited is made again for where its clock is.
|
|
21
|
+
*/
|
|
22
|
+
export declare function begin<I, O>(mix: Mixer<I, O>, voice: Voice<I, O>, now: number): void;
|
|
13
23
|
/**
|
|
14
24
|
* A voice's start or clock moved, or it went live at `now`: the records whose `since` was still
|
|
15
25
|
* ahead count from where its clock now puts them.
|
package/dist/held.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
|
+
import { changed } from './chain.js';
|
|
1
2
|
import { schedule } from './due.js';
|
|
2
3
|
import { everyoneOf, inherit, sighted } from './everyone.js';
|
|
3
4
|
import { originOf } from './origin.js';
|
|
5
|
+
import { startOf } from './place.js';
|
|
4
6
|
import { record } from './record.js';
|
|
5
7
|
import { unreach, unreached } from './unreached.js';
|
|
6
8
|
/** The record a voice gives every subject it does not reach. */
|
|
@@ -82,6 +84,11 @@ export function held(voice, subject, slot) {
|
|
|
82
84
|
if (voice.state === 'frozen')
|
|
83
85
|
schedule(this, voice);
|
|
84
86
|
}
|
|
87
|
+
if (delay < voice.soonest) {
|
|
88
|
+
voice.soonest = delay;
|
|
89
|
+
// An earlier start moves where a voice running back ends.
|
|
90
|
+
schedule(this, voice);
|
|
91
|
+
}
|
|
85
92
|
return held;
|
|
86
93
|
}
|
|
87
94
|
/**
|
|
@@ -92,8 +99,28 @@ export function sinceOf(voice, delay) {
|
|
|
92
99
|
const p = voice.patch;
|
|
93
100
|
return originOf(voice, delay, p.state !== undefined || p.step !== undefined || voice.keeping);
|
|
94
101
|
}
|
|
102
|
+
/**
|
|
103
|
+
* The mix time a subject's fade in counts from: `since`, but no earlier than its voice starts,
|
|
104
|
+
* where a voice sought ahead of its start has a subject's delay run out before it; and from when
|
|
105
|
+
* the voice first showed, for one freezing before.
|
|
106
|
+
*/
|
|
95
107
|
export function shownOf(voice, since) {
|
|
96
|
-
|
|
108
|
+
const start = startOf(voice);
|
|
109
|
+
const from = since < start ? start : since;
|
|
110
|
+
return voice.freezesBefore && voice.opened < from ? voice.opened : from;
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* A pending voice starts at `now`. Starting anywhere but 0 it was sought there, so a record made
|
|
114
|
+
* while it waited is made again for where its clock is.
|
|
115
|
+
*/
|
|
116
|
+
export function begin(mix, voice, now) {
|
|
117
|
+
voice.state = 'live';
|
|
118
|
+
if (voice.anchorElapsed !== 0) {
|
|
119
|
+
voice.seeks++;
|
|
120
|
+
voice.rebuilds++;
|
|
121
|
+
}
|
|
122
|
+
reorigin(mix, voice, now);
|
|
123
|
+
changed(mix, voice);
|
|
97
124
|
}
|
|
98
125
|
/**
|
|
99
126
|
* A voice's start or clock moved, or it went live at `now`: the records whose `since` was still
|
package/dist/hosts.d.ts
CHANGED
|
@@ -2,7 +2,7 @@ import type { LaneHost } from './lanes.js';
|
|
|
2
2
|
import type { Mixer } from './mixer.js';
|
|
3
3
|
import type { Booker, BookOptions, Handle } from './types.js';
|
|
4
4
|
import type { Voice } from './voice.js';
|
|
5
|
-
export declare function book<I, O>(mix: Mixer<I, O>, opts: BookOptions): Booker;
|
|
5
|
+
export declare function book<I, O>(mix: Mixer<I, O>, opts: BookOptions<unknown, I>): Booker;
|
|
6
6
|
export declare function laneHost<I, O>(mix: Mixer<I, O>): LaneHost<I, O>;
|
|
7
7
|
/**
|
|
8
8
|
* Whether a voice's patch and spec can run on a lane, its channels aside. A voice whose patch has
|
package/dist/hosts.js
CHANGED
|
@@ -111,7 +111,11 @@ export function fits(mix, voice) {
|
|
|
111
111
|
const patch = voice.patch;
|
|
112
112
|
if (voice.keeping)
|
|
113
113
|
return false;
|
|
114
|
-
|
|
114
|
+
// Waiting to start, a voice shows the frame it will start on, which no fill reads its clock for.
|
|
115
|
+
if ((voice.freezesBefore || voice.state === 'fading') && voice.unstarted(mix.now))
|
|
116
|
+
return false;
|
|
117
|
+
// A fill takes a time before 0 as before a subject's start, which no time is for this voice.
|
|
118
|
+
if (voice.beginless || voice.out?.back === true)
|
|
115
119
|
return false;
|
|
116
120
|
// A fill weighs a voice's owners once for every subject, which a signal on one would not be.
|
|
117
121
|
if (voice.owner !== null && signalled(voice))
|
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/mixer.d.ts
CHANGED
|
@@ -202,6 +202,7 @@ export declare class Mixer<I, O> implements Mix<I, O> {
|
|
|
202
202
|
set announced(v: Announced[]);
|
|
203
203
|
get nextId(): number;
|
|
204
204
|
set nextId(v: number);
|
|
205
|
+
get timestamp(): number;
|
|
205
206
|
get last(): number;
|
|
206
207
|
get tape(): Tape | undefined;
|
|
207
208
|
get replaying(): boolean;
|
|
@@ -215,7 +216,7 @@ export declare class Mixer<I, O> implements Mix<I, O> {
|
|
|
215
216
|
owns(spec: OwnerSpec<I>): Handle<I>;
|
|
216
217
|
span(spec: SpanSpec<I>): SpanHandle<I>;
|
|
217
218
|
sync(timestamp: number): void;
|
|
218
|
-
book(opts: BookOptions): Booker;
|
|
219
|
+
book<E = unknown>(opts: BookOptions<E, I>): Booker;
|
|
219
220
|
probe(subject: I, out?: O): O;
|
|
220
221
|
pull(subjects: Iterable<I>, into: Columns<O>): void;
|
|
221
222
|
seek(time: number): void;
|
package/dist/mixer.js
CHANGED
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;
|