@msb235/blits 0.15.1 → 0.16.1
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 +184 -0
- package/README.md +1 -1
- package/dist/ahead.js +11 -13
- package/dist/chain.js +2 -3
- package/dist/clock.d.ts +11 -1
- package/dist/clock.js +28 -9
- package/dist/color.d.ts +36 -14
- package/dist/color.js +186 -58
- package/dist/columns.d.ts +1 -11
- package/dist/columns.js +1 -6
- package/dist/contribution.js +11 -4
- package/dist/crowd.d.ts +0 -6
- package/dist/crowd.js +0 -11
- package/dist/cue.js +19 -7
- package/dist/due.d.ts +18 -0
- package/dist/due.js +101 -2
- package/dist/everyone.d.ts +17 -6
- package/dist/everyone.js +17 -8
- package/dist/fade.d.ts +22 -1
- package/dist/fade.js +81 -12
- package/dist/fill.js +2 -1
- package/dist/fit.js +8 -0
- package/dist/fold.js +4 -7
- package/dist/gone.js +1 -0
- package/dist/handle.d.ts +1 -1
- package/dist/handle.js +3 -1
- package/dist/held.d.ts +2 -1
- package/dist/held.js +17 -10
- package/dist/hosts.d.ts +7 -0
- package/dist/hosts.js +23 -6
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/lane.d.ts +2 -0
- package/dist/lane.js +2 -0
- package/dist/lanes.d.ts +26 -1
- package/dist/lanes.js +9 -1
- package/dist/lch.d.ts +23 -0
- package/dist/lch.js +126 -0
- package/dist/meet.js +9 -0
- package/dist/mixer.d.ts +6 -1
- package/dist/mixer.js +5 -0
- package/dist/motion.js +6 -6
- package/dist/motions.d.ts +4 -1
- package/dist/motions.js +16 -2
- package/dist/move.js +311 -79
- package/dist/origin.d.ts +2 -0
- package/dist/origin.js +4 -0
- package/dist/owner.d.ts +7 -2
- package/dist/owner.js +17 -5
- package/dist/paging.d.ts +5 -1
- package/dist/paging.js +13 -3
- package/dist/patch.js +24 -4
- package/dist/place.js +67 -31
- package/dist/project.d.ts +5 -2
- package/dist/project.js +9 -11
- package/dist/pull.d.ts +11 -0
- package/dist/pull.js +10 -6
- package/dist/qualify.js +7 -2
- package/dist/rests.js +9 -1
- package/dist/revive.d.ts +11 -1
- package/dist/revive.js +7 -0
- package/dist/roles.js +6 -5
- package/dist/roster.d.ts +6 -0
- package/dist/roster.js +8 -1
- package/dist/rows.js +4 -1
- package/dist/sample.js +2 -1
- package/dist/seat.d.ts +5 -1
- package/dist/seat.js +5 -1
- package/dist/seek.js +6 -16
- package/dist/shared.d.ts +1 -1
- package/dist/shared.js +1 -1
- package/dist/spans.d.ts +13 -4
- package/dist/spans.js +125 -32
- package/dist/touch.js +1 -2
- package/dist/trace.js +7 -2
- package/dist/turns.js +2 -1
- package/dist/types/transport.d.ts +2 -1
- package/dist/types/voice.d.ts +3 -2
- package/dist/voice.d.ts +59 -2
- package/dist/voice.js +76 -2
- package/dist/weigh.js +1 -1
- package/docs/schema.html +38 -12
- package/package.json +3 -1
package/dist/spans.js
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { arcEnd } from './arcs.js';
|
|
2
2
|
import { elapsedWith, retime } from './clock.js';
|
|
3
|
-
import {
|
|
3
|
+
import { release } from './fade.js';
|
|
4
4
|
import { defaultFit, layout, settle } from './fit.js';
|
|
5
5
|
import { reorigin } from './held.js';
|
|
6
6
|
import { noted } from './history.js';
|
|
7
7
|
import { nextFrame } from './move.js';
|
|
8
8
|
import { leave } from './origin.js';
|
|
9
|
-
import { ownerReading } from './owner.js';
|
|
9
|
+
import { ownerReading, plays } from './owner.js';
|
|
10
10
|
import { resolve } from './place.js';
|
|
11
11
|
/** What a span keeps beside its owner: its claim, its fit, and how the last fit came out. */
|
|
12
12
|
export class Fitting {
|
|
@@ -52,40 +52,115 @@ export function checkChild(spec, child) {
|
|
|
52
52
|
else if (!Number.isFinite(child.span))
|
|
53
53
|
throw new Error('blits: a span cannot fit a voice that never ends');
|
|
54
54
|
}
|
|
55
|
+
/**
|
|
56
|
+
* Whether a span's clock went back over a child at span ms `local`: it holds one kept quiet that
|
|
57
|
+
* ran back to its start, or that starts after `local`, which only a clock gone back leaves quiet.
|
|
58
|
+
* Its layout up to there was made going forward, and stands until the clock passes it again.
|
|
59
|
+
*/
|
|
60
|
+
function ranBackOver(span, local) {
|
|
61
|
+
for (const child of span.holding.children)
|
|
62
|
+
if (child.out?.stays === true && (child.out.back === true || child.start > local))
|
|
63
|
+
return true;
|
|
64
|
+
return false;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Puts a child whose owner's clock ran back to before a fit changed its rate, or skipped it, back
|
|
68
|
+
* on the clock it ran on before, so it reads, run back over, what it read going forward; and once
|
|
69
|
+
* the owner's clock reaches that change again, takes the change again, as the layout stands.
|
|
70
|
+
*/
|
|
71
|
+
export function unfit(mix, child, now) {
|
|
72
|
+
const t = ownerReading(child.owner, now);
|
|
73
|
+
let moved = false;
|
|
74
|
+
for (let e = child.earlier; e !== null && t < child.anchorNow; e = child.earlier) {
|
|
75
|
+
child.later = { ...standing(child), prev: child.later };
|
|
76
|
+
take(child, e);
|
|
77
|
+
child.earlier = e.prev;
|
|
78
|
+
moved = true;
|
|
79
|
+
}
|
|
80
|
+
for (let e = child.later; e !== null && t >= e.anchorNow; e = child.later) {
|
|
81
|
+
child.earlier = { ...standing(child), prev: child.earlier };
|
|
82
|
+
take(child, e);
|
|
83
|
+
child.later = e.prev;
|
|
84
|
+
moved = true;
|
|
85
|
+
}
|
|
86
|
+
if (!moved)
|
|
87
|
+
return;
|
|
88
|
+
mix.frame = nextFrame();
|
|
89
|
+
reorigin(mix, child, now);
|
|
90
|
+
noted(mix, child);
|
|
91
|
+
mix.lanes?.refill();
|
|
92
|
+
}
|
|
93
|
+
/** A child's clock as it stands, and whether a fit skipped it. */
|
|
94
|
+
function standing(c) {
|
|
95
|
+
const { anchorNow, anchorElapsed, rate, ramp, skipped } = c;
|
|
96
|
+
return { anchorNow, anchorElapsed, rate, ramp, skipped };
|
|
97
|
+
}
|
|
98
|
+
/** Puts a child on a clock it had, which jumps where a fit skipped it on one and not the other. */
|
|
99
|
+
function take(c, e) {
|
|
100
|
+
c.anchorNow = e.anchorNow;
|
|
101
|
+
c.anchorElapsed = e.anchorElapsed;
|
|
102
|
+
c.rate = e.rate;
|
|
103
|
+
c.ramp = e.ramp;
|
|
104
|
+
if (c.skipped !== e.skipped)
|
|
105
|
+
c.seeks++;
|
|
106
|
+
c.skipped = e.skipped;
|
|
107
|
+
}
|
|
55
108
|
/** A span's own clock at mix time `at`, from 0 while it has not started. */
|
|
56
109
|
function clockOf(span, at) {
|
|
57
110
|
if (Number.isNaN(at))
|
|
58
111
|
return 0;
|
|
59
|
-
const t =
|
|
112
|
+
const t = readingAt(span, at);
|
|
60
113
|
return Number.isFinite(t) ? Math.max(0, t) : 0;
|
|
61
114
|
}
|
|
115
|
+
/**
|
|
116
|
+
* What a voice's clock reads at mix time `at` through its owners' clocks as they stand, inverting
|
|
117
|
+
* `mixTime`: -Infinity where one yet to start has not reached its start. Unlike `ownerReading`, an
|
|
118
|
+
* owner that has started reads on its clock before its start, where a seek of the owner put a time
|
|
119
|
+
* its children left at.
|
|
120
|
+
*/
|
|
121
|
+
function readingAt(voice, at) {
|
|
122
|
+
const t = voice.owner === null ? at : readingAt(voice.owner, at);
|
|
123
|
+
if (t === Number.NEGATIVE_INFINITY || (voice.state === 'pending' && !(t >= voice.start)))
|
|
124
|
+
return Number.NEGATIVE_INFINITY;
|
|
125
|
+
return elapsedWith(voice, t);
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Whether a child has started, or would have by span ms `by` had a frame come between: a fit from a
|
|
129
|
+
* moment between frames keeps a child whose start that moment had passed where it is.
|
|
130
|
+
*/
|
|
131
|
+
function begun(child, by) {
|
|
132
|
+
return child.state !== 'pending' || child.start < by;
|
|
133
|
+
}
|
|
62
134
|
/** What a child's own clock reads at its span's `local`, 0 while it waits. */
|
|
63
|
-
function elapsedIn(child, local) {
|
|
64
|
-
return child
|
|
135
|
+
function elapsedIn(child, local, by) {
|
|
136
|
+
return begun(child, by) ? Math.max(0, elapsedWith(child, local)) : 0;
|
|
65
137
|
}
|
|
66
138
|
/** How much of a child is left, voice ms at its own clock: what its span still has to fit. */
|
|
67
|
-
function leftOf(child, local) {
|
|
139
|
+
function leftOf(child, local, by) {
|
|
68
140
|
const length = child.fitting !== null ? child.fitting.end : child.arc !== null ? arcEnd(child) : child.span;
|
|
69
|
-
return Math.max(0, length - elapsedIn(child, local));
|
|
141
|
+
return Math.max(0, length - elapsedIn(child, local, by));
|
|
70
142
|
}
|
|
71
143
|
/**
|
|
72
144
|
* Lays out a span's children and fits them into what is left of its budget, from its clock at mix
|
|
73
145
|
* time `from`, default now. Only what is still to come moves: a playing child keeps its place and
|
|
74
146
|
* may change rate, a pending one is placed. A span held by a span is fitted again by its own span
|
|
75
|
-
* after.
|
|
147
|
+
* after. A span its clock ran back over keeps the layout it made going forward.
|
|
76
148
|
*/
|
|
77
149
|
export function refit(mix, span, from = mix.now) {
|
|
78
150
|
const fitting = span.fitting;
|
|
79
151
|
if (mix.projecting || span.state === 'done')
|
|
80
152
|
return;
|
|
81
153
|
const local = clockOf(span, from);
|
|
82
|
-
|
|
154
|
+
if (ranBackOver(span, local))
|
|
155
|
+
return;
|
|
156
|
+
const by = from < mix.now ? local : Number.NEGATIVE_INFINITY;
|
|
157
|
+
const { kids, floors, waiting } = lineUp(mix, span, local, by);
|
|
83
158
|
const claims = kids.map((c, i) => {
|
|
84
159
|
const s = c.spec;
|
|
85
160
|
return {
|
|
86
161
|
floor: floors[i],
|
|
87
|
-
natural: leftOf(c, local) / (s.rate ?? 1),
|
|
88
|
-
state: c
|
|
162
|
+
natural: leftOf(c, local, by) / (s.rate ?? 1),
|
|
163
|
+
state: begun(c, by) ? 'playing' : 'pending',
|
|
89
164
|
faster: Math.max(1, s.faster ?? 1),
|
|
90
165
|
slower: Math.max(1, s.slower ?? 1),
|
|
91
166
|
overlap: s.overlap === true,
|
|
@@ -99,9 +174,10 @@ export function refit(mix, span, from = mix.now) {
|
|
|
99
174
|
kids.forEach((c, i) => {
|
|
100
175
|
const rate = (c.spec.rate ?? 1) * plan.rate[i];
|
|
101
176
|
const skipped = plan.skip[i] && c.holding === null;
|
|
177
|
+
const wasSkipped = c.skipped;
|
|
102
178
|
c.skipped = skipped;
|
|
103
179
|
let moved = false;
|
|
104
|
-
if (c
|
|
180
|
+
if (!begun(c, by)) {
|
|
105
181
|
const start = local + at[i];
|
|
106
182
|
const elapsed = skipped ? c.span : 0;
|
|
107
183
|
if (c.start !== start || c.rate !== rate || c.anchorElapsed !== elapsed || c.ramp !== null) {
|
|
@@ -111,10 +187,16 @@ export function refit(mix, span, from = mix.now) {
|
|
|
111
187
|
c.rate = rate;
|
|
112
188
|
c.ramp = null;
|
|
113
189
|
c.clocks = null;
|
|
190
|
+
c.earlier = null;
|
|
191
|
+
c.later = null;
|
|
114
192
|
moved = true;
|
|
115
193
|
}
|
|
116
194
|
}
|
|
117
195
|
else {
|
|
196
|
+
if (skipped || c.rate !== rate || c.ramp !== null) {
|
|
197
|
+
c.earlier = { ...standing(c), skipped: wasSkipped, prev: c.earlier };
|
|
198
|
+
c.later = null;
|
|
199
|
+
}
|
|
118
200
|
if (skipped) {
|
|
119
201
|
leave(c, local);
|
|
120
202
|
c.rebase(local);
|
|
@@ -141,6 +223,8 @@ export function refit(mix, span, from = mix.now) {
|
|
|
141
223
|
c.anchorNow = Number.POSITIVE_INFINITY;
|
|
142
224
|
c.anchorElapsed = 0;
|
|
143
225
|
c.clocks = null;
|
|
226
|
+
c.earlier = null;
|
|
227
|
+
c.later = null;
|
|
144
228
|
mix.frame = nextFrame();
|
|
145
229
|
reorigin(mix, c, mix.now);
|
|
146
230
|
noted(mix, c);
|
|
@@ -151,19 +235,26 @@ export function refit(mix, span, from = mix.now) {
|
|
|
151
235
|
const over = Number.isFinite(fitting.budget) ? Math.max(0, reach - fitting.budget) : 0;
|
|
152
236
|
const within = over < 1e-6;
|
|
153
237
|
const end = !Number.isFinite(fitting.budget) ? reach : within ? fitting.budget : reach;
|
|
154
|
-
fitting.report
|
|
238
|
+
const was = fitting.report;
|
|
239
|
+
const report = {
|
|
155
240
|
budget: fitting.budget,
|
|
156
241
|
length: reach,
|
|
157
242
|
over: within ? 0 : over,
|
|
158
243
|
skipped: plan.skip.filter((s, i) => s && kids[i]?.holding === null).length,
|
|
159
244
|
fell,
|
|
160
245
|
};
|
|
246
|
+
fitting.report = report;
|
|
161
247
|
if (end !== fitting.end) {
|
|
162
248
|
fitting.end = end;
|
|
163
249
|
noted(mix, span);
|
|
164
250
|
if (span.owner?.fitting)
|
|
165
251
|
refit(mix, span.owner, from);
|
|
166
252
|
}
|
|
253
|
+
else if (report.length !== was.length ||
|
|
254
|
+
report.over !== was.over ||
|
|
255
|
+
report.skipped !== was.skipped ||
|
|
256
|
+
report.fell !== was.fell)
|
|
257
|
+
noted(mix, span);
|
|
167
258
|
}
|
|
168
259
|
/**
|
|
169
260
|
* A span's children still to fit, in the order it lays them out: those playing first, as cued, then
|
|
@@ -171,13 +262,14 @@ export function refit(mix, span, from = mix.now) {
|
|
|
171
262
|
* from `local`, and before `local` only in the frame it was cued. One whose anchor has no answer yet waits unplaced, and under `queue` or `stagger`
|
|
172
263
|
* so does every child after it, since each is laid out from the one before.
|
|
173
264
|
*/
|
|
174
|
-
function lineUp(mix, span, local) {
|
|
175
|
-
|
|
176
|
-
const
|
|
265
|
+
function lineUp(mix, span, local, by) {
|
|
266
|
+
// A child fading, or kept quiet past its passes, has nothing left to fit.
|
|
267
|
+
const live = (span.holding?.children ?? []).filter((c) => c.state !== 'done' && c.state !== 'frozen' && c.out === null && leftOf(c, local, by) > 0);
|
|
268
|
+
const kids = live.filter((c) => begun(c, by));
|
|
177
269
|
const floors = kids.map(() => undefined);
|
|
178
270
|
const waiting = [];
|
|
179
271
|
const pending = live
|
|
180
|
-
.filter((c) => c
|
|
272
|
+
.filter((c) => !begun(c, by))
|
|
181
273
|
.sort((a, b) => (a.spec.rank ?? 0) - (b.spec.rank ?? 0));
|
|
182
274
|
const chained = span.fitting.claim.order !== 'together';
|
|
183
275
|
for (const c of pending) {
|
|
@@ -222,22 +314,23 @@ export function lingers(span, at) {
|
|
|
222
314
|
const f = span.fitting;
|
|
223
315
|
return f !== null && Number.isFinite(f.budget) && ownerReading(span, at) < f.budget;
|
|
224
316
|
}
|
|
225
|
-
/**
|
|
317
|
+
/**
|
|
318
|
+
* Lets the spans out of budget that hold no child still playing leave, at the moment their budget
|
|
319
|
+
* ran out, or, under history, stay quiet with an owner holding them.
|
|
320
|
+
*/
|
|
226
321
|
export function lapse(mix, now) {
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
if (
|
|
232
|
-
|
|
233
|
-
if (ownerReading(span, now) >= f.budget)
|
|
234
|
-
retire(mix, span, span.timeAt(f.budget));
|
|
322
|
+
const owners = mix.owners ?? [];
|
|
323
|
+
// Those held first, cued after what holds them, so a span whose last span lapses goes with it.
|
|
324
|
+
for (let i = owners.length - 1; i >= 0; i--) {
|
|
325
|
+
const span = owners[i];
|
|
326
|
+
if (span.out?.stays !== true && lapses(span, now))
|
|
327
|
+
release(mix, span, span.timeAt(span.fitting.budget));
|
|
235
328
|
}
|
|
236
329
|
}
|
|
237
|
-
/**
|
|
238
|
-
export function
|
|
239
|
-
const
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
330
|
+
/** Whether a span's budget is out by `now` with no child of it still playing. */
|
|
331
|
+
export function lapses(span, now) {
|
|
332
|
+
const f = span.fitting;
|
|
333
|
+
if (f === null || span.state === 'done' || !Number.isFinite(f.budget))
|
|
334
|
+
return false;
|
|
335
|
+
return !plays(span, now) && ownerReading(span, now) >= f.budget;
|
|
243
336
|
}
|
package/dist/touch.js
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { headOf } from './chain.js';
|
|
2
|
-
import { restamp } from './everyone.js';
|
|
3
2
|
import { nextFrame } from './move.js';
|
|
4
3
|
/**
|
|
5
4
|
* What `Mix.touch` does: makes stale every delta read this frame, for one subject or for all, so
|
|
@@ -22,7 +21,7 @@ export function touch(mix, subject) {
|
|
|
22
21
|
held.seeks = -1;
|
|
23
22
|
const slot = headOf(mix, subject)?.slot ?? -1;
|
|
24
23
|
for (const voice of mix.sharers)
|
|
25
|
-
restamp(voice, slot);
|
|
24
|
+
mix.share?.restamp(voice, slot);
|
|
26
25
|
}
|
|
27
26
|
mix.lanes?.refill();
|
|
28
27
|
mix.stir();
|
package/dist/trace.js
CHANGED
|
@@ -151,6 +151,8 @@ function shapeOf(mix, voice, subject, from, to, plan) {
|
|
|
151
151
|
return false;
|
|
152
152
|
if (voice.ramp !== null || !(voice.rate > 0) || voice.out !== null || voice.back !== null)
|
|
153
153
|
return false;
|
|
154
|
+
if (voice.stalls !== null)
|
|
155
|
+
return false;
|
|
154
156
|
if (voice.parts !== null || voice.parted !== null || voice.left !== null)
|
|
155
157
|
return false;
|
|
156
158
|
if (Number.isFinite(voice.outAt) || voice.state === 'fading')
|
|
@@ -190,9 +192,12 @@ function shapeOf(mix, voice, subject, from, to, plan) {
|
|
|
190
192
|
at(ends);
|
|
191
193
|
if (!voice.freezesAfter && Number.isFinite(ends)) {
|
|
192
194
|
const over = reduced ? 0 : Number.isNaN(voice.outOver) ? (voice.fade.out ?? 0) : voice.outOver;
|
|
195
|
+
// Ended before its start, as a seek past its end leaves it, it fades from its start.
|
|
196
|
+
const fades = Math.max(voice.start, ends);
|
|
197
|
+
at(fades);
|
|
193
198
|
if (over > 0) {
|
|
194
|
-
bent.push([
|
|
195
|
-
at(
|
|
199
|
+
bent.push([fades, fades + over]);
|
|
200
|
+
at(fades + over);
|
|
196
201
|
}
|
|
197
202
|
// It leaves when its latest subject ends, which a subject first met later can move.
|
|
198
203
|
if (spec.stagger !== undefined)
|
package/dist/turns.js
CHANGED
|
@@ -51,7 +51,8 @@ export function turnToward(ch, values, base, value, taken, u) {
|
|
|
51
51
|
}
|
|
52
52
|
const axes = ch.axes;
|
|
53
53
|
let out = value;
|
|
54
|
-
|
|
54
|
+
// A first value of other axes than the lane's, as an ink contribution is, widens through the lerp.
|
|
55
|
+
if (taken !== 0 || out.length !== axes) {
|
|
55
56
|
held = lift(values, base, axes, held);
|
|
56
57
|
out = ch.channel.lerp(held, value, u);
|
|
57
58
|
}
|
|
@@ -45,7 +45,8 @@ export interface Transport {
|
|
|
45
45
|
/**
|
|
46
46
|
* Loads from the history `store` what a seek or projection to `time` will need. That is, what
|
|
47
47
|
* `seek(time)` and `project(time)` need where it reaches past memory. Without a store, or within
|
|
48
|
-
* memory, it loads nothing.
|
|
48
|
+
* memory, it loads nothing. What it loads is kept until a seek or read takes it, or until
|
|
49
|
+
* `history.ms` of mix time passes, after which the time needs `prepare` again.
|
|
49
50
|
*/
|
|
50
51
|
prepare(time: number): Promise<void>;
|
|
51
52
|
readonly now: number;
|
package/dist/types/voice.d.ts
CHANGED
|
@@ -249,8 +249,9 @@ export interface OwnerSpec<I, H = unknown> extends SpanHints {
|
|
|
249
249
|
/**
|
|
250
250
|
* Its clock's rate, multiplied into every child's own. Default 1. Below 0 its clock runs back,
|
|
251
251
|
* and so does every child's: a child plays backward to its start and leaves there, as a voice cued
|
|
252
|
-
* backward does, and the owner leaves with its last child.
|
|
253
|
-
*
|
|
252
|
+
* backward does, and the owner leaves with its last child. Under `history` a child that already
|
|
253
|
+
* left runs back too: a child leaves only when its owner does. A span is not cued backward, since
|
|
254
|
+
* it lays out its children forward from its start; its handle's `rate` turns it once it plays.
|
|
254
255
|
*/
|
|
255
256
|
rate?: number;
|
|
256
257
|
/**
|
package/dist/voice.d.ts
CHANGED
|
@@ -2,6 +2,7 @@ import type { Arc } from './arcs.js';
|
|
|
2
2
|
import type { Blend } from './blend.js';
|
|
3
3
|
import type { Bound } from './bound.js';
|
|
4
4
|
import { type Clock } from './clock.js';
|
|
5
|
+
import { type Cut } from './cut.js';
|
|
5
6
|
import { type Curve } from './easing.js';
|
|
6
7
|
import type { Leavings } from './leavings.js';
|
|
7
8
|
import type { Motions } from './motions.js';
|
|
@@ -11,10 +12,10 @@ import { Holding } from './owner.js';
|
|
|
11
12
|
import { type Built, intosOf, type Scratch } from './patch.js';
|
|
12
13
|
import type { Seat } from './seat.js';
|
|
13
14
|
import { VoiceSetting } from './setting.js';
|
|
14
|
-
import type
|
|
15
|
+
import { type Fitting } from './spans.js';
|
|
15
16
|
import { Store } from './store.js';
|
|
16
17
|
import type { Parted, Parting, Snap, Subject } from './subject.js';
|
|
17
|
-
import type { Anchor, Channel, FadeSpec, Handle, Patch, VoiceSpec } from './types.js';
|
|
18
|
+
import type { Anchor, Channel, FadeSpec, FitResult, Handle, Patch, VoiceSpec } from './types.js';
|
|
18
19
|
export declare const none: readonly string[];
|
|
19
20
|
/** A voice's clock, weight and fade from one mix time on, kept under `history`. */
|
|
20
21
|
export interface Controls extends Clock {
|
|
@@ -24,6 +25,8 @@ export interface Controls extends Clock {
|
|
|
24
25
|
weight: number;
|
|
25
26
|
out: Ramp | null;
|
|
26
27
|
back: Rise | null;
|
|
28
|
+
/** Absent from a store written before a voice kept them. */
|
|
29
|
+
stalls?: readonly number[] | null;
|
|
27
30
|
/** Where its anchors had placed it then: start, and the start of an anchored fade out. */
|
|
28
31
|
start: number;
|
|
29
32
|
outAt: number;
|
|
@@ -35,9 +38,27 @@ export interface Controls extends Clock {
|
|
|
35
38
|
hold: number;
|
|
36
39
|
/** How many of the voice's past clocks it had then. */
|
|
37
40
|
past: number;
|
|
41
|
+
/** The clocks a fit replaced; absent from a store written before a voice kept them. */
|
|
42
|
+
earlier?: Earlier | null;
|
|
43
|
+
/** The clocks its owner ran back before; absent from a store written before a voice kept them. */
|
|
44
|
+
later?: Earlier | null;
|
|
45
|
+
/** A span's end and how its fit came out; absent for any other voice. */
|
|
46
|
+
fitted?: {
|
|
47
|
+
end: number;
|
|
48
|
+
report: FitResult;
|
|
49
|
+
} | null;
|
|
38
50
|
/** Shows in its own frame: made by a sync, or before anything read the mix that frame. */
|
|
39
51
|
sync: boolean;
|
|
40
52
|
}
|
|
53
|
+
/**
|
|
54
|
+
* A clock a fit replaced, which its voice runs on again where its owner's clock runs back to
|
|
55
|
+
* before the one that replaced it, with whether the fit had skipped it; `prev` is the one before.
|
|
56
|
+
* In `later`, one its owner ran back before, taken again where the owner's clock reaches it.
|
|
57
|
+
*/
|
|
58
|
+
export interface Earlier extends Clock {
|
|
59
|
+
readonly skipped: boolean;
|
|
60
|
+
readonly prev: Earlier | null;
|
|
61
|
+
}
|
|
41
62
|
export interface Ramp {
|
|
42
63
|
from: number;
|
|
43
64
|
at: number;
|
|
@@ -46,6 +67,11 @@ export interface Ramp {
|
|
|
46
67
|
deadline?: number;
|
|
47
68
|
/** Begun where the voice ran back to its start, whose first frame it holds through the fade. */
|
|
48
69
|
back?: boolean;
|
|
70
|
+
/**
|
|
71
|
+
* Begun where the passes of a voice an owner holds under history ran out: faded, it stays live
|
|
72
|
+
* and quiet with its owner, which brings it back by taking its clock back into its passes.
|
|
73
|
+
*/
|
|
74
|
+
stays?: boolean;
|
|
49
75
|
}
|
|
50
76
|
/**
|
|
51
77
|
* A climb back from a fade out that was turned around: from `from`, the place along the fade's
|
|
@@ -82,6 +108,11 @@ export declare class Voice<I, O> {
|
|
|
82
108
|
out: Ramp | null;
|
|
83
109
|
/** A fade out turned around, still climbing back; null once back in, or never turned. */
|
|
84
110
|
back: Rise | null;
|
|
111
|
+
/**
|
|
112
|
+
* Where its fade in stood still under a fade out later turned around, as pairs of mix times,
|
|
113
|
+
* from and to; null where it never did. Replaced, never changed, so a noted copy stays good.
|
|
114
|
+
*/
|
|
115
|
+
stalls: readonly number[] | null;
|
|
85
116
|
subjects: Store<I, Subject<unknown>>;
|
|
86
117
|
/** The subjects its spec names, or null where it names none. */
|
|
87
118
|
readonly named: Named<I> | null;
|
|
@@ -165,6 +196,10 @@ export declare class Voice<I, O> {
|
|
|
165
196
|
early: Subject<unknown>[] | null;
|
|
166
197
|
/** The clocks it ran on before its current one, where a subject's origin may lie; null for none. */
|
|
167
198
|
clocks: Past[] | null;
|
|
199
|
+
/** The clocks a fit replaced, latest first; null for none. */
|
|
200
|
+
earlier: Earlier | null;
|
|
201
|
+
/** The clocks its owner's clock ran back before, which it takes again going forward, soonest first. */
|
|
202
|
+
later: Earlier | null;
|
|
168
203
|
/** Reused for every call this voice makes, so it is valid only during the call. */
|
|
169
204
|
readonly setting: VoiceSetting<I, O>;
|
|
170
205
|
/** Every subject this voice has been asked about, so a handover knows when it is finished. */
|
|
@@ -180,6 +215,11 @@ export declare class Voice<I, O> {
|
|
|
180
215
|
cuedAt: number;
|
|
181
216
|
/** The frame it was cued in. */
|
|
182
217
|
cuedSeq: number;
|
|
218
|
+
/**
|
|
219
|
+
* The frame it last went live in, Infinity while pending: a seek of an owner back before its
|
|
220
|
+
* start leaves it live, which a read back by its clock alone would take for still to start.
|
|
221
|
+
*/
|
|
222
|
+
begunSeq: number;
|
|
183
223
|
/** The mix time it left at, which a fade or seek given a time already passed puts behind now. */
|
|
184
224
|
doneAt: number;
|
|
185
225
|
/** The frame its leaving was decided in, which a seek or read back partitions by. */
|
|
@@ -200,6 +240,8 @@ export declare class Voice<I, O> {
|
|
|
200
240
|
outSet: boolean;
|
|
201
241
|
/** The host time a pinned start was given at, NaN for none: a seek back pins it again. */
|
|
202
242
|
pinned: number;
|
|
243
|
+
/** The mix's offset when it was cued: a timestamp in its anchor converts at this one. */
|
|
244
|
+
offset: number;
|
|
203
245
|
/** Cued with no start while its mix slept, so the next sync sets its start. */
|
|
204
246
|
waking: boolean;
|
|
205
247
|
/** Whether its start is still to be fixed by an anchor, so it waits pending. */
|
|
@@ -326,6 +368,21 @@ export declare class Voice<I, O> {
|
|
|
326
368
|
* turn it around.
|
|
327
369
|
*/
|
|
328
370
|
endsAt(): number;
|
|
371
|
+
/** The mix time its clock reads `elapsed`, by its controls now, through every owner above it. */
|
|
372
|
+
reaches(elapsed: number): number;
|
|
373
|
+
/**
|
|
374
|
+
* Its state at mix time `t`, from its clock and fade, once every clock above it is back where it
|
|
375
|
+
* was: a fade begins at once, even one whose ramp starts later, at the voice's start, and one
|
|
376
|
+
* from where an owner under history keeps it ends with it quiet and live. With `cut`, one that
|
|
377
|
+
* had started by then is live, though an owner's seek has put its clock back before its start.
|
|
378
|
+
*/
|
|
379
|
+
stateAt(t: number, cut?: Cut): Voice<I, O>['state'];
|
|
380
|
+
/**
|
|
381
|
+
* Whether it is kept quiet past its passes at mix time `now`: its passes ran out while an owner
|
|
382
|
+
* held it under history, and its clock has not come back into them. At their very edge it is
|
|
383
|
+
* quiet unless its clock runs into them.
|
|
384
|
+
*/
|
|
385
|
+
quietAt(now: number): boolean;
|
|
329
386
|
/**
|
|
330
387
|
* The mix time its clock ran back to `elapsed` by `now`, by its controls now: where a subject
|
|
331
388
|
* `elapsed` into it reached its start. NaN where it has not, and for a voice an owner holds.
|
package/dist/voice.js
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import { backWith, elapsedWith, frozenTime, passesOf, rateWith, rebaseWith, timeWith, } from './clock.js';
|
|
2
|
+
import { within } from './cut.js';
|
|
2
3
|
import { curve } from './easing.js';
|
|
3
4
|
import { motionOf } from './motion.js';
|
|
4
5
|
import { Named } from './named.js';
|
|
5
|
-
import { childPlayed, Holding, mixTime, ownedElapsed, ownerPatch, ownerReading, turnedBack, } from './owner.js';
|
|
6
|
+
import { childPlayed, Holding, mixTime, ownedElapsed, ownerPatch, ownerReading, plays, turnedBack, } from './owner.js';
|
|
6
7
|
import { builtOf, intosOf } from './patch.js';
|
|
7
8
|
import { keeping, VoiceSetting } from './setting.js';
|
|
9
|
+
import { lingers } from './spans.js';
|
|
8
10
|
import { Filled, Store } from './store.js';
|
|
9
11
|
import { warnOnce } from './warn.js';
|
|
10
12
|
export const none = Object.freeze([]);
|
|
@@ -52,6 +54,11 @@ export class Voice {
|
|
|
52
54
|
out = null;
|
|
53
55
|
/** A fade out turned around, still climbing back; null once back in, or never turned. */
|
|
54
56
|
back = null;
|
|
57
|
+
/**
|
|
58
|
+
* Where its fade in stood still under a fade out later turned around, as pairs of mix times,
|
|
59
|
+
* from and to; null where it never did. Replaced, never changed, so a noted copy stays good.
|
|
60
|
+
*/
|
|
61
|
+
stalls = null;
|
|
55
62
|
subjects = new Store();
|
|
56
63
|
/** The subjects its spec names, or null where it names none. */
|
|
57
64
|
named;
|
|
@@ -137,6 +144,10 @@ export class Voice {
|
|
|
137
144
|
early = null;
|
|
138
145
|
/** The clocks it ran on before its current one, where a subject's origin may lie; null for none. */
|
|
139
146
|
clocks = null;
|
|
147
|
+
/** The clocks a fit replaced, latest first; null for none. */
|
|
148
|
+
earlier = null;
|
|
149
|
+
/** The clocks its owner's clock ran back before, which it takes again going forward, soonest first. */
|
|
150
|
+
later = null;
|
|
140
151
|
/** Reused for every call this voice makes, so it is valid only during the call. */
|
|
141
152
|
setting;
|
|
142
153
|
/** Every subject this voice has been asked about, so a handover knows when it is finished. */
|
|
@@ -152,6 +163,11 @@ export class Voice {
|
|
|
152
163
|
cuedAt = Number.NEGATIVE_INFINITY;
|
|
153
164
|
/** The frame it was cued in. */
|
|
154
165
|
cuedSeq = Number.NEGATIVE_INFINITY;
|
|
166
|
+
/**
|
|
167
|
+
* The frame it last went live in, Infinity while pending: a seek of an owner back before its
|
|
168
|
+
* start leaves it live, which a read back by its clock alone would take for still to start.
|
|
169
|
+
*/
|
|
170
|
+
begunSeq = Number.POSITIVE_INFINITY;
|
|
155
171
|
/** The mix time it left at, which a fade or seek given a time already passed puts behind now. */
|
|
156
172
|
doneAt = Number.POSITIVE_INFINITY;
|
|
157
173
|
/** The frame its leaving was decided in, which a seek or read back partitions by. */
|
|
@@ -174,6 +190,8 @@ export class Voice {
|
|
|
174
190
|
outSet = false;
|
|
175
191
|
/** The host time a pinned start was given at, NaN for none: a seek back pins it again. */
|
|
176
192
|
pinned = Number.NaN;
|
|
193
|
+
/** The mix's offset when it was cued: a timestamp in its anchor converts at this one. */
|
|
194
|
+
offset = 0;
|
|
177
195
|
/** Cued with no start while its mix slept, so the next sync sets its start. */
|
|
178
196
|
waking = false;
|
|
179
197
|
/** Whether its start is still to be fixed by an anchor, so it waits pending. */
|
|
@@ -327,12 +345,20 @@ export class Voice {
|
|
|
327
345
|
this.weight = c.weight;
|
|
328
346
|
this.out = c.out;
|
|
329
347
|
this.back = c.back;
|
|
348
|
+
this.stalls = c.stalls ?? null;
|
|
330
349
|
this.start = c.start;
|
|
331
350
|
this.outAt = c.outAt;
|
|
332
351
|
this.outOver = c.outOver;
|
|
333
352
|
this.outSet = c.outSet;
|
|
334
353
|
this.rebuilds = c.rebuilds;
|
|
335
354
|
this.hold = c.hold;
|
|
355
|
+
this.earlier = c.earlier ?? null;
|
|
356
|
+
this.later = c.later ?? null;
|
|
357
|
+
const fitted = c.fitted;
|
|
358
|
+
if (fitted && this.fitting !== null) {
|
|
359
|
+
this.fitting.end = fitted.end;
|
|
360
|
+
this.fitting.report = fitted.report;
|
|
361
|
+
}
|
|
336
362
|
const clocks = this.clocks;
|
|
337
363
|
if (clocks !== null && clocks.length !== c.past)
|
|
338
364
|
this.clocks = c.past === 0 ? null : clocks.slice(0, c.past);
|
|
@@ -365,6 +391,7 @@ export class Voice {
|
|
|
365
391
|
this.restedCount = 0;
|
|
366
392
|
this.cuedAt = Number.NEGATIVE_INFINITY;
|
|
367
393
|
this.cuedSeq = Number.NEGATIVE_INFINITY;
|
|
394
|
+
this.begunSeq = Number.POSITIVE_INFINITY;
|
|
368
395
|
this.doneAt = Number.POSITIVE_INFINITY;
|
|
369
396
|
this.doneSeq = Number.POSITIVE_INFINITY;
|
|
370
397
|
this.placing = false;
|
|
@@ -472,9 +499,52 @@ export class Voice {
|
|
|
472
499
|
return Number.POSITIVE_INFINITY;
|
|
473
500
|
to = this.span + this.latest;
|
|
474
501
|
}
|
|
475
|
-
|
|
502
|
+
return this.reaches(to);
|
|
503
|
+
}
|
|
504
|
+
/** The mix time its clock reads `elapsed`, by its controls now, through every owner above it. */
|
|
505
|
+
reaches(elapsed) {
|
|
506
|
+
const t = this.backs ? backWith(this, elapsed) : timeWith(this, elapsed);
|
|
476
507
|
return this.owner === null ? t : mixTime(this.owner, t);
|
|
477
508
|
}
|
|
509
|
+
/**
|
|
510
|
+
* Its state at mix time `t`, from its clock and fade, once every clock above it is back where it
|
|
511
|
+
* was: a fade begins at once, even one whose ramp starts later, at the voice's start, and one
|
|
512
|
+
* from where an owner under history keeps it ends with it quiet and live. With `cut`, one that
|
|
513
|
+
* had started by then is live, though an owner's seek has put its clock back before its start.
|
|
514
|
+
*/
|
|
515
|
+
stateAt(t, cut) {
|
|
516
|
+
const out = this.out;
|
|
517
|
+
if (out !== null)
|
|
518
|
+
return out.stays && t >= out.at + out.over ? 'live' : 'fading';
|
|
519
|
+
if ((this.owner === null ? t : ownerReading(this.owner, t)) < this.start &&
|
|
520
|
+
!(cut !== undefined && within(cut, this.begunSeq, true)))
|
|
521
|
+
return 'pending';
|
|
522
|
+
if (this.freezesAfter && this.elapsedAt(t) >= this.span + this.latest)
|
|
523
|
+
return 'frozen';
|
|
524
|
+
return 'live';
|
|
525
|
+
}
|
|
526
|
+
/**
|
|
527
|
+
* Whether it is kept quiet past its passes at mix time `now`: its passes ran out while an owner
|
|
528
|
+
* held it under history, and its clock has not come back into them. At their very edge it is
|
|
529
|
+
* quiet unless its clock runs into them.
|
|
530
|
+
*/
|
|
531
|
+
quietAt(now) {
|
|
532
|
+
const out = this.out;
|
|
533
|
+
if (this.state !== 'live' || out?.stays !== true)
|
|
534
|
+
return false;
|
|
535
|
+
// An owner kept quiet while its children were, rather than run back to its start.
|
|
536
|
+
if (this.holding !== null && out.back !== true)
|
|
537
|
+
return !plays(this, now) && !lingers(this, now);
|
|
538
|
+
// A fit that skipped it put it at its end at its start, so before its start it plays nothing.
|
|
539
|
+
if (this.skipped && ownerReading(this.owner, now) < this.start)
|
|
540
|
+
return true;
|
|
541
|
+
const at = this.elapsedAt(now);
|
|
542
|
+
const end = this.span + this.latest;
|
|
543
|
+
const back = this.backs !== (this.owner !== null && turnedBack(this));
|
|
544
|
+
if (at > end || (at === end && !back))
|
|
545
|
+
return true;
|
|
546
|
+
return !this.beginless && (at < this.soonest || (at === this.soonest && back));
|
|
547
|
+
}
|
|
478
548
|
/**
|
|
479
549
|
* The mix time its clock ran back to `elapsed` by `now`, by its controls now: where a subject
|
|
480
550
|
* `elapsed` into it reached its start. NaN where it has not, and for a voice an owner holds.
|
|
@@ -545,6 +615,7 @@ export class Voice {
|
|
|
545
615
|
weight: this.weight,
|
|
546
616
|
out: this.out,
|
|
547
617
|
back: this.back,
|
|
618
|
+
stalls: this.stalls,
|
|
548
619
|
start: this.start,
|
|
549
620
|
outAt: this.outAt,
|
|
550
621
|
outOver: this.outOver,
|
|
@@ -552,6 +623,9 @@ export class Voice {
|
|
|
552
623
|
rebuilds: this.rebuilds,
|
|
553
624
|
hold: this.hold,
|
|
554
625
|
past: this.clocks?.length ?? 0,
|
|
626
|
+
earlier: this.earlier,
|
|
627
|
+
later: this.later,
|
|
628
|
+
fitted: this.fitting === null ? null : { end: this.fitting.end, report: this.fitting.report },
|
|
555
629
|
});
|
|
556
630
|
}
|
|
557
631
|
/**
|
package/dist/weigh.js
CHANGED
|
@@ -60,7 +60,7 @@ export function horizonFor(voice, delay, now) {
|
|
|
60
60
|
}
|
|
61
61
|
/** The ramp a voice's own fade envelope applies this frame, 0..1. */
|
|
62
62
|
export function fadeOf(voice, now, since) {
|
|
63
|
-
return envelope(voice.fade.in ?? 0, voice.out, voice.ease, this.reducedNow, now, since, voice.back);
|
|
63
|
+
return envelope(voice.fade.in ?? 0, voice.out, voice.ease, this.reducedNow, now, since, voice.back, voice.stalls);
|
|
64
64
|
}
|
|
65
65
|
/** What every owner above a voice multiplies into its weight for a subject this frame. */
|
|
66
66
|
export function ownedBy(voice, subject) {
|