@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/move.js
CHANGED
|
@@ -2,18 +2,17 @@ import { settleArcs } from './arcs.js';
|
|
|
2
2
|
import { changed } from './chain.js';
|
|
3
3
|
import { elapsedWith } from './clock.js';
|
|
4
4
|
import { byId, popDue, schedule } from './due.js';
|
|
5
|
-
import {
|
|
6
|
-
import { beginFade, part, retire } from './fade.js';
|
|
5
|
+
import { beginFade, part, release, retire, rouse, stay } from './fade.js';
|
|
7
6
|
import { expireGone, keepGone } from './gone.js';
|
|
8
|
-
import { begin } from './held.js';
|
|
7
|
+
import { begin, reorigin } from './held.js';
|
|
9
8
|
import { noted } from './history.js';
|
|
10
|
-
import { leave as leaveClock } from './origin.js';
|
|
11
|
-
import { ownerReading } from './owner.js';
|
|
9
|
+
import { leave as leaveClock, startedAt } from './origin.js';
|
|
10
|
+
import { descendants, ownerReading, plays } from './owner.js';
|
|
12
11
|
import { mixAt, place, repin, startAt, startOf } from './place.js';
|
|
13
12
|
import { pageVoice } from './revive.js';
|
|
14
|
-
import { delist } from './roster.js';
|
|
13
|
+
import { delist, detour } from './roster.js';
|
|
15
14
|
import { scoredLeft, scoredRemove } from './scored.js';
|
|
16
|
-
import { lapse } from './spans.js';
|
|
15
|
+
import { lapse, lapses, unfit } from './spans.js';
|
|
17
16
|
import { unplay } from './strays.js';
|
|
18
17
|
/** Numbers every mix's frames apart, so a blend's read is never taken for another mix's or frame's. */
|
|
19
18
|
let frames = 0;
|
|
@@ -60,90 +59,323 @@ function moveTo(mix, now) {
|
|
|
60
59
|
}
|
|
61
60
|
// A live mix visits only the voices due by now; a projection, which copies few, visits all.
|
|
62
61
|
const visit = mix.projecting || mix.walkAll ? mix.cued : popDue(mix, now);
|
|
62
|
+
let held = null;
|
|
63
63
|
for (const voice of visit) {
|
|
64
64
|
if (voice.state === 'done')
|
|
65
65
|
continue;
|
|
66
|
-
if (
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
(voice.owner === null ? now : ownerReading(voice.owner, now)) >= voice.start)
|
|
70
|
-
begin(mix, voice, now);
|
|
71
|
-
// After it starts, so a fade due by now on a voice that started since the last frame begins.
|
|
72
|
-
// Not one set for after its passes run out, where a move across both would begin it first.
|
|
73
|
-
if ((voice.state === 'live' || voice.state === 'frozen') &&
|
|
74
|
-
voice.outAt <= now &&
|
|
75
|
-
!(voice.state === 'live' &&
|
|
76
|
-
!voice.freezesAfter &&
|
|
77
|
-
!voice.freezesBefore &&
|
|
78
|
-
voice.endsAt() < voice.outAt))
|
|
79
|
-
beginFade(mix, voice, { over: Number.isNaN(voice.outOver) ? undefined : voice.outOver }, Math.max(startOf(voice), voice.outAt));
|
|
80
|
-
if (voice.state !== 'pending') {
|
|
81
|
-
if (voice.turns)
|
|
82
|
-
turn(mix, voice, now);
|
|
83
|
-
// Its passes run out at its end running forward, and at its start running back. A clock
|
|
84
|
-
// standing still at or before its start is where it was: waiting, or frozen there.
|
|
85
|
-
const way = heading(voice, now);
|
|
86
|
-
const at = voice.elapsedAt(now);
|
|
87
|
-
const back = way < 0;
|
|
88
|
-
const atStart = !voice.beginless && at <= voice.soonest;
|
|
89
|
-
// An owner run back to its start stays for what it holds, and leaves with its last child.
|
|
90
|
-
const holds = voice.holding !== null && voice.holding.children.length > 0;
|
|
91
|
-
const over = back
|
|
92
|
-
? atStart && !holds
|
|
93
|
-
: Number.isFinite(voice.span) && at >= voice.span + voice.latest;
|
|
94
|
-
if (over) {
|
|
95
|
-
voice.play(true, now, mix.transport.seq);
|
|
96
|
-
// The fade starts when its passes ran out, not at the frame that noticed, so it plays
|
|
97
|
-
// the same at any frame rate and a read at another time can find it.
|
|
98
|
-
if (voice.state === 'live') {
|
|
99
|
-
if (back ? voice.freezesBefore : voice.freezesAfter)
|
|
100
|
-
voice.state = 'frozen';
|
|
101
|
-
else
|
|
102
|
-
beginFade(mix, voice, { back }, Math.max(startOf(voice), Math.min(now, voice.endsAt())));
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
else if (voice.state === 'frozen' && !(atStart && way === 0))
|
|
106
|
-
voice.state = 'live';
|
|
107
|
-
}
|
|
108
|
-
// A projection reads a finished ramp as weight 0, and leaves the live voice's subjects be.
|
|
109
|
-
if (voice.parts !== null && !mix.projecting)
|
|
110
|
-
for (const [subject, r] of voice.parts)
|
|
111
|
-
if (now - r.at >= r.over)
|
|
112
|
-
part(mix, voice, subject, r.at + r.over);
|
|
113
|
-
const back = voice.back;
|
|
114
|
-
if (back !== null && now >= back.at + (1 - back.from) * back.over && !mix.projecting) {
|
|
115
|
-
voice.back = null;
|
|
116
|
-
mix.lanes?.refill();
|
|
117
|
-
}
|
|
118
|
-
if (voice.state === 'fading' && voice.out) {
|
|
119
|
-
const { at, over, rest, deadline } = voice.out;
|
|
120
|
-
const spent = now - at;
|
|
121
|
-
if (rest) {
|
|
122
|
-
const out = deadline !== undefined && spent >= deadline;
|
|
123
|
-
const settled = voice.seen > 0 && voice.restedCount >= voice.seen;
|
|
124
|
-
if (out)
|
|
125
|
-
retire(mix, voice, at + deadline);
|
|
126
|
-
else if (settled)
|
|
127
|
-
retire(mix, voice);
|
|
128
|
-
}
|
|
129
|
-
else if (spent >= over)
|
|
130
|
-
retire(mix, voice, at + over);
|
|
66
|
+
if (visitOne(mix, voice, now, voice.owner !== null && voice.owner.fitting !== null)) {
|
|
67
|
+
held ??= [];
|
|
68
|
+
held.push(voice);
|
|
131
69
|
}
|
|
132
|
-
if (!mix.projecting)
|
|
133
|
-
schedule(mix, voice);
|
|
134
|
-
}
|
|
135
|
-
if (mix.owners !== null) {
|
|
136
|
-
lapse(mix, now);
|
|
137
|
-
settleArcs(mix, now);
|
|
138
70
|
}
|
|
71
|
+
const back = mix.leapt === null && held === null ? null : turnedBack(mix, held, now);
|
|
72
|
+
mix.leapt = null;
|
|
73
|
+
if (back === null)
|
|
74
|
+
settle(mix, held, now);
|
|
75
|
+
else
|
|
76
|
+
ahead(mix, back, held, now);
|
|
139
77
|
if (mix.sharers.length > 0 && !mix.projecting)
|
|
140
|
-
outgrow(mix);
|
|
78
|
+
mix.share?.outgrow(mix);
|
|
141
79
|
mix.stirred = false;
|
|
142
80
|
mix.asleep = false;
|
|
143
81
|
if (mix.retired.length > 0)
|
|
144
82
|
prune(mix);
|
|
145
83
|
forget(mix);
|
|
146
84
|
}
|
|
85
|
+
/** Takes the span children waiting their turn, then lets go of spans and arcs that are over. */
|
|
86
|
+
function settle(mix, held, now) {
|
|
87
|
+
if (held !== null)
|
|
88
|
+
inTurn(mix, held, now);
|
|
89
|
+
if (mix.owners !== null) {
|
|
90
|
+
lapse(mix, now);
|
|
91
|
+
settleArcs(mix, now);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* The owners sought forward since the last frame, or above the span children waiting their turn,
|
|
96
|
+
* whose clocks do not run forward at `now`, with those above them that do not either; null where
|
|
97
|
+
* there are none. An owner a span holds is left out, since its span's fits change its clock.
|
|
98
|
+
*/
|
|
99
|
+
function turnedBack(mix, held, now) {
|
|
100
|
+
let back = null;
|
|
101
|
+
if (mix.leapt !== null)
|
|
102
|
+
for (const o of mix.leapt)
|
|
103
|
+
back = up(o, now, back);
|
|
104
|
+
if (held !== null)
|
|
105
|
+
for (const v of held)
|
|
106
|
+
back = up(v.owner, now, back);
|
|
107
|
+
return back;
|
|
108
|
+
}
|
|
109
|
+
/** Adds to `back` the owners from `from` up that `turnedBack` takes. */
|
|
110
|
+
function up(from, now, back) {
|
|
111
|
+
for (let o = from; o !== null; o = o.owner) {
|
|
112
|
+
if (o.state === 'done' || o.owner?.fitting || back?.includes(o))
|
|
113
|
+
continue;
|
|
114
|
+
if (o.rateAt(o.owner === null ? now : ownerReading(o.owner, now)) > 0)
|
|
115
|
+
continue;
|
|
116
|
+
back ??= [];
|
|
117
|
+
back.push(o);
|
|
118
|
+
}
|
|
119
|
+
return back;
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Settles a frame in which a seek took owners whose clocks do not run forward ahead: the span
|
|
123
|
+
* children and spans it passed start and end in order, as played straight through, with those
|
|
124
|
+
* clocks run forward to where they read now, then back on their own.
|
|
125
|
+
*/
|
|
126
|
+
function ahead(mix, back, held, now) {
|
|
127
|
+
const kept = back.map((o) => {
|
|
128
|
+
const { anchorNow, anchorElapsed, rate, ramp, start } = o;
|
|
129
|
+
const at = o.owner === null ? now : ownerReading(o.owner, now);
|
|
130
|
+
o.anchorElapsed = elapsedWith(o, at);
|
|
131
|
+
o.anchorNow = at;
|
|
132
|
+
o.rate = 1;
|
|
133
|
+
o.ramp = null;
|
|
134
|
+
// Started where the clock run forward reads 0, so a time on it before now maps back to it.
|
|
135
|
+
o.start = at - o.anchorElapsed;
|
|
136
|
+
return { anchorNow, anchorElapsed, rate, ramp, start, logged: o.log?.length ?? 0 };
|
|
137
|
+
});
|
|
138
|
+
try {
|
|
139
|
+
// Anchors are placed on those clocks too, and a plain child placed behind them starts there:
|
|
140
|
+
// on a clock standing still, an owner's time maps to the seek's moment and back to where it
|
|
141
|
+
// was sought, so a later frame would place it again past it.
|
|
142
|
+
if (mix.anchored > 0) {
|
|
143
|
+
place(mix);
|
|
144
|
+
for (const o of back)
|
|
145
|
+
descendants(o, (v) => {
|
|
146
|
+
if (v.state === 'pending' && v.spec.anchor !== undefined && v.owner?.fitting === null)
|
|
147
|
+
visitOne(mix, v, now);
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
settle(mix, held, now);
|
|
151
|
+
}
|
|
152
|
+
finally {
|
|
153
|
+
back.forEach((o, i) => {
|
|
154
|
+
const { logged, ...clock } = kept[i];
|
|
155
|
+
Object.assign(o, clock);
|
|
156
|
+
const log = o.log;
|
|
157
|
+
if (log !== null)
|
|
158
|
+
for (let k = logged; k < log.length; k++)
|
|
159
|
+
Object.assign(log[k], clock);
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
// What was worked out in mix time from the clocks run forward is worked out again on their own.
|
|
163
|
+
mix.frame = nextFrame();
|
|
164
|
+
for (const o of back) {
|
|
165
|
+
if (o.state !== 'done')
|
|
166
|
+
schedule(mix, o);
|
|
167
|
+
descendants(o, (v) => {
|
|
168
|
+
if (v.state === 'done')
|
|
169
|
+
return;
|
|
170
|
+
reorigin(mix, v, now);
|
|
171
|
+
schedule(mix, v);
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
mix.lanes?.refill();
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* Starts, ends and fades one voice as of `now`; with `wait`, a span's child, it does none of that
|
|
178
|
+
* and answers true where the child `meets` now, to wait its turn.
|
|
179
|
+
*/
|
|
180
|
+
function visitOne(mix, voice, now, wait = false) {
|
|
181
|
+
if (Number.isNaN(voice.opened))
|
|
182
|
+
voice.opened = now;
|
|
183
|
+
if (voice.earlier !== null || voice.later !== null)
|
|
184
|
+
unfit(mix, voice, now);
|
|
185
|
+
const t = voice.owner === null ? now : ownerReading(voice.owner, now);
|
|
186
|
+
if (wait && meetsAt(voice, t, now))
|
|
187
|
+
return true;
|
|
188
|
+
if (voice.state === 'pending' && starts(voice, t, now))
|
|
189
|
+
begin(mix, voice, now);
|
|
190
|
+
// After it starts, so a fade due by now on a voice that started since the last frame begins.
|
|
191
|
+
// Not one set for after its passes run out, where a move across both would begin it first.
|
|
192
|
+
if ((voice.state === 'live' || voice.state === 'frozen') &&
|
|
193
|
+
voice.outAt <= now &&
|
|
194
|
+
!(voice.state === 'live' &&
|
|
195
|
+
!voice.freezesAfter &&
|
|
196
|
+
!voice.freezesBefore &&
|
|
197
|
+
voice.endsAt() < voice.outAt))
|
|
198
|
+
beginFade(mix, voice, { over: Number.isNaN(voice.outOver) ? undefined : voice.outOver }, Math.max(startedAt(voice), voice.outAt));
|
|
199
|
+
if (voice.state !== 'pending') {
|
|
200
|
+
if (voice.turns)
|
|
201
|
+
turn(mix, voice, now);
|
|
202
|
+
if (voice.state === 'live' && voice.out !== null && !voice.quietAt(now))
|
|
203
|
+
rouse(mix, voice, now);
|
|
204
|
+
// Its passes run out at its end running forward, and at its start running back. A clock
|
|
205
|
+
// standing still at or before its start is where it was: waiting, or frozen there.
|
|
206
|
+
const heads = Math.sign(voice.rateAt(t)) * (voice.owner === null ? 1 : heading(voice.owner, now));
|
|
207
|
+
const at = t === Number.NEGATIVE_INFINITY ? t : elapsedWith(voice, t);
|
|
208
|
+
const back = heads < 0;
|
|
209
|
+
const atStart = !voice.beginless && at <= voice.soonest;
|
|
210
|
+
// An owner run back to its start stays for what it holds, and leaves with its last child.
|
|
211
|
+
const holds = voice.holding !== null && plays(voice, now);
|
|
212
|
+
const over = back
|
|
213
|
+
? atStart && !holds
|
|
214
|
+
: Number.isFinite(voice.span) && at >= voice.span + voice.latest;
|
|
215
|
+
if (over) {
|
|
216
|
+
voice.play(true, now, mix.transport.seq);
|
|
217
|
+
// The fade starts when its passes ran out, not at the frame that noticed, so it plays
|
|
218
|
+
// the same at any frame rate and a read at another time can find it.
|
|
219
|
+
if (voice.state === 'live' && voice.out === null) {
|
|
220
|
+
if (back ? voice.freezesBefore : voice.freezesAfter)
|
|
221
|
+
voice.state = 'frozen';
|
|
222
|
+
else
|
|
223
|
+
beginFade(mix, voice, { back, stays: voice.owner !== null && mix.opts.history !== undefined }, Math.max(startOf(voice), Math.min(now, voice.endsAt())));
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
else if (voice.state === 'frozen' && !(atStart && heads === 0))
|
|
227
|
+
voice.state = 'live';
|
|
228
|
+
}
|
|
229
|
+
// A projection reads a finished ramp as weight 0, and leaves the live voice's subjects be.
|
|
230
|
+
if (voice.parts !== null && !mix.projecting)
|
|
231
|
+
for (const [subject, r] of voice.parts)
|
|
232
|
+
if (now - r.at >= r.over)
|
|
233
|
+
part(mix, voice, subject, r.at + r.over);
|
|
234
|
+
const back = voice.back;
|
|
235
|
+
if (back !== null && now >= back.at + (1 - back.from) * back.over && !mix.projecting) {
|
|
236
|
+
voice.back = null;
|
|
237
|
+
mix.lanes?.refill();
|
|
238
|
+
}
|
|
239
|
+
if (voice.state === 'fading' && voice.out) {
|
|
240
|
+
const { at, over, rest, deadline } = voice.out;
|
|
241
|
+
const spent = now - at;
|
|
242
|
+
if (rest) {
|
|
243
|
+
const out = deadline !== undefined && spent >= deadline;
|
|
244
|
+
const settled = voice.seen > 0 && voice.restedCount >= voice.seen;
|
|
245
|
+
if (out)
|
|
246
|
+
retire(mix, voice, at + deadline);
|
|
247
|
+
else if (settled)
|
|
248
|
+
retire(mix, voice);
|
|
249
|
+
}
|
|
250
|
+
else if (spent >= over) {
|
|
251
|
+
if (voice.out.stays)
|
|
252
|
+
stay(mix, voice, at + over);
|
|
253
|
+
else
|
|
254
|
+
retire(mix, voice, at + over);
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
if (!mix.projecting)
|
|
258
|
+
schedule(mix, voice);
|
|
259
|
+
return false;
|
|
260
|
+
}
|
|
261
|
+
/**
|
|
262
|
+
* Whether a pending voice starts with its owner's clock at `t`: at its very start only on a clock
|
|
263
|
+
* not running back, where it would leave as it starts, as one passed between frames never starts.
|
|
264
|
+
*/
|
|
265
|
+
function starts(voice, t, now) {
|
|
266
|
+
return (t > voice.start ||
|
|
267
|
+
(t === voice.start && (voice.owner === null || heading(voice.owner, now) >= 0)));
|
|
268
|
+
}
|
|
269
|
+
/**
|
|
270
|
+
* Whether a span's child starts, comes back, or reaches the end of its passes by `now`: a fit from
|
|
271
|
+
* an end earlier in the frame can move any of these, so they wait for the ends before them. One
|
|
272
|
+
* kept quiet at its start comes back as its clock passes it. Past its end, or its start, on a clock
|
|
273
|
+
* not running forward, a seek of an owner took it there going forward. Takes first the changes a
|
|
274
|
+
* fit made to it that its owner's clock has come back to, as `visitOne` would.
|
|
275
|
+
*/
|
|
276
|
+
function meets(mix, voice, now) {
|
|
277
|
+
if (voice.earlier !== null || voice.later !== null)
|
|
278
|
+
unfit(mix, voice, now);
|
|
279
|
+
return meetsAt(voice, ownerReading(voice.owner, now), now);
|
|
280
|
+
}
|
|
281
|
+
/** `meets`, given what its owner's clock reads at `now`, `t`. */
|
|
282
|
+
function meetsAt(voice, t, now) {
|
|
283
|
+
if (voice.state === 'pending')
|
|
284
|
+
return starts(voice, t, now);
|
|
285
|
+
if (voice.state !== 'live')
|
|
286
|
+
return false;
|
|
287
|
+
const out = voice.out;
|
|
288
|
+
if (voice.fitting !== null)
|
|
289
|
+
return out === null && lapses(voice, now);
|
|
290
|
+
if (!Number.isFinite(voice.span))
|
|
291
|
+
return false;
|
|
292
|
+
if (out !== null && !(out.stays === true && out.back === true))
|
|
293
|
+
return false;
|
|
294
|
+
const at = t === Number.NEGATIVE_INFINITY ? t : elapsedWith(voice, t);
|
|
295
|
+
const edge = out === null ? voice.span + voice.latest : voice.soonest;
|
|
296
|
+
return at > edge || (at === edge && heading(voice, now) > 0);
|
|
297
|
+
}
|
|
298
|
+
/**
|
|
299
|
+
* Visits span children that start, come back or end within the frame in the order those happen,
|
|
300
|
+
* one at a time, since a fit from each end can move the starts and ends after it. A start or a
|
|
301
|
+
* return only does that here; a child a fit moves to meet `now`, or whose span starts, joins them.
|
|
302
|
+
*/
|
|
303
|
+
function inTurn(mix, held, now) {
|
|
304
|
+
// A lone start of a child holding nothing moves nothing else: it starts and is visited.
|
|
305
|
+
const lone = held.length === 1 ? held[0] : null;
|
|
306
|
+
if (lone !== null && lone.state === 'pending' && lone.holding === null) {
|
|
307
|
+
begin(mix, lone, now);
|
|
308
|
+
if (!meets(mix, lone, now)) {
|
|
309
|
+
visitOne(mix, lone, now);
|
|
310
|
+
return;
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
// `held` is this frame's own list, so it is the one the children waiting their turn join.
|
|
314
|
+
const waiting = held;
|
|
315
|
+
const ended = [];
|
|
316
|
+
for (;;) {
|
|
317
|
+
let next;
|
|
318
|
+
let when = Number.POSITIVE_INFINITY;
|
|
319
|
+
for (let i = 0; i < waiting.length; i++) {
|
|
320
|
+
const v = waiting[i];
|
|
321
|
+
if (v.state === 'done' || ended.includes(v) || !meets(mix, v, now))
|
|
322
|
+
continue;
|
|
323
|
+
const t = v.state === 'pending'
|
|
324
|
+
? startOf(v)
|
|
325
|
+
: v.fitting !== null
|
|
326
|
+
? lapseOf(v)
|
|
327
|
+
: v.out !== null
|
|
328
|
+
? v.reaches(v.soonest)
|
|
329
|
+
: v.endsAt();
|
|
330
|
+
// At one time an end comes first, as a fit from it counts a start there as still to come.
|
|
331
|
+
if (next === undefined || t < when || (t === when && rank(v) < rank(next))) {
|
|
332
|
+
next = v;
|
|
333
|
+
when = t;
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
if (next === undefined)
|
|
337
|
+
break;
|
|
338
|
+
if (next.state === 'pending') {
|
|
339
|
+
begin(mix, next, now);
|
|
340
|
+
if (next.holding !== null)
|
|
341
|
+
join(mix, next, now, waiting, ended);
|
|
342
|
+
}
|
|
343
|
+
else if (next.out !== null)
|
|
344
|
+
rouse(mix, next, now);
|
|
345
|
+
else {
|
|
346
|
+
ended.push(next);
|
|
347
|
+
if (next.fitting !== null)
|
|
348
|
+
release(mix, next, when);
|
|
349
|
+
else
|
|
350
|
+
visitOne(mix, next, now);
|
|
351
|
+
for (let o = next.owner; o?.fitting; o = o.owner)
|
|
352
|
+
join(mix, o, now, waiting, ended);
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
byId(waiting);
|
|
356
|
+
for (let i = 0; i < waiting.length; i++) {
|
|
357
|
+
const v = waiting[i];
|
|
358
|
+
if (!ended.includes(v) && v.state !== 'done')
|
|
359
|
+
visitOne(mix, v, now);
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
/** When a span's budget ran out, mix time. */
|
|
363
|
+
function lapseOf(span) {
|
|
364
|
+
return span.timeAt(span.fitting.budget);
|
|
365
|
+
}
|
|
366
|
+
/** Adds to `waiting` the children of `o` that meet `now` and have not yet ended this frame. */
|
|
367
|
+
function join(mix, o, now, waiting, ended) {
|
|
368
|
+
const children = o.holding.children;
|
|
369
|
+
for (let i = 0; i < children.length; i++) {
|
|
370
|
+
const c = children[i];
|
|
371
|
+
if (c.state !== 'done' && !ended.includes(c) && !waiting.includes(c) && meets(mix, c, now))
|
|
372
|
+
waiting.push(c);
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
/** Which of two events at one time comes first: an end before a start or return, then cue order. */
|
|
376
|
+
function rank(v) {
|
|
377
|
+
return (v.state === 'pending' || v.out !== null ? 2 ** 40 : 0) + v.id;
|
|
378
|
+
}
|
|
147
379
|
/** Starts each voice cued while the mix slept at `now`, the first sync after its cue. */
|
|
148
380
|
function wake(mix, now) {
|
|
149
381
|
mix.waking = false;
|
package/dist/origin.d.ts
CHANGED
|
@@ -19,3 +19,5 @@ export declare function leave<I, O>(voice: Voice<I, O>, u: number, sought?: bool
|
|
|
19
19
|
* `rebuilt` starts from the last seek that rebuilt state, as a stateful record does.
|
|
20
20
|
*/
|
|
21
21
|
export declare function originOf<I, O>(voice: Voice<I, O>, elapsed: number, rebuilt: boolean): number;
|
|
22
|
+
/** The mix time a voice started, or will by its owners' controls now: where its clock read 0. */
|
|
23
|
+
export declare function startedAt<I, O>(voice: Voice<I, O>): number;
|
package/dist/origin.js
CHANGED
|
@@ -80,3 +80,7 @@ export function originOf(voice, elapsed, rebuilt) {
|
|
|
80
80
|
}
|
|
81
81
|
return voice.owner === null ? t : originOf(voice.owner, t, rebuilt);
|
|
82
82
|
}
|
|
83
|
+
/** The mix time a voice started, or will by its owners' controls now: where its clock read 0. */
|
|
84
|
+
export function startedAt(voice) {
|
|
85
|
+
return voice.owner === null ? voice.start : originOf(voice.owner, voice.start, false);
|
|
86
|
+
}
|
package/dist/owner.d.ts
CHANGED
|
@@ -36,8 +36,13 @@ export declare function mixTime<I, O>(owner: Voice<I, O>, t: number): number;
|
|
|
36
36
|
export declare function turnedBack<I, O>(voice: Voice<I, O>): boolean;
|
|
37
37
|
/** Takes a child into its owner. */
|
|
38
38
|
export declare function adopt<I, O>(owner: Voice<I, O>, child: Voice<I, O>): void;
|
|
39
|
-
/**
|
|
40
|
-
|
|
39
|
+
/**
|
|
40
|
+
* A child left at mix time `now`: its owner, where that was its last child still playing and it
|
|
41
|
+
* should leave too.
|
|
42
|
+
*/
|
|
43
|
+
export declare function orphan<I, O>(child: Voice<I, O>, now: number): Voice<I, O> | null;
|
|
44
|
+
/** Whether an owner holds a child at mix time `now` that is not kept quiet past its passes. */
|
|
45
|
+
export declare function plays<I, O>(owner: Voice<I, O>, now: number): boolean;
|
|
41
46
|
/** A child finished its passes: its owner has played once every child it held has. */
|
|
42
47
|
export declare function childPlayed<I, O>(owner: Voice<I, O>, at: number, seq: number): void;
|
|
43
48
|
/** Every voice an owner holds, its children's children included. */
|
package/dist/owner.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { arcShare } from './arcs.js';
|
|
2
2
|
import { backWith, clampWeight, elapsedWith, envelope, timeWith } from './clock.js';
|
|
3
|
+
import { startedAt } from './origin.js';
|
|
3
4
|
/** What an owner is cued with in place of a patch: it writes nothing, so no fold ever meets it. */
|
|
4
5
|
export const ownerPatch = Object.freeze({
|
|
5
6
|
form: 'fn',
|
|
@@ -76,14 +77,24 @@ export function adopt(owner, child) {
|
|
|
76
77
|
h.children.push(child);
|
|
77
78
|
h.kids++;
|
|
78
79
|
}
|
|
79
|
-
/**
|
|
80
|
-
|
|
80
|
+
/**
|
|
81
|
+
* A child left at mix time `now`: its owner, where that was its last child still playing and it
|
|
82
|
+
* should leave too.
|
|
83
|
+
*/
|
|
84
|
+
export function orphan(child, now) {
|
|
81
85
|
const owner = child.owner;
|
|
82
86
|
const h = owner.holding;
|
|
83
87
|
const i = h.children.indexOf(child);
|
|
84
88
|
if (i >= 0)
|
|
85
89
|
h.children.splice(i, 1);
|
|
86
|
-
return
|
|
90
|
+
return !plays(owner, now) && owner.state !== 'done' ? owner : null;
|
|
91
|
+
}
|
|
92
|
+
/** Whether an owner holds a child at mix time `now` that is not kept quiet past its passes. */
|
|
93
|
+
export function plays(owner, now) {
|
|
94
|
+
for (const child of owner.holding.children)
|
|
95
|
+
if (!child.quietAt(now))
|
|
96
|
+
return true;
|
|
97
|
+
return false;
|
|
87
98
|
}
|
|
88
99
|
/** A child finished its passes: its owner has played once every child it held has. */
|
|
89
100
|
export function childPlayed(owner, at, seq) {
|
|
@@ -122,9 +133,10 @@ export function heldByInput(voice) {
|
|
|
122
133
|
*/
|
|
123
134
|
export function ownWeight(owner, now, reduced, base) {
|
|
124
135
|
const b = typeof owner.spec.weight === 'function' ? base(owner) : owner.weight;
|
|
125
|
-
const start =
|
|
136
|
+
const start = startedAt(owner);
|
|
126
137
|
const since = owner.freezesBefore && owner.opened < start ? owner.opened : start;
|
|
127
|
-
return clampWeight(b *
|
|
138
|
+
return clampWeight(b *
|
|
139
|
+
envelope(owner.fade.in ?? 0, owner.out, owner.ease, reduced, now, since, owner.back, owner.stalls));
|
|
128
140
|
}
|
|
129
141
|
/** What every owner above a voice multiplies into its weight. */
|
|
130
142
|
export function ownersWeight(voice, now, reduced, base) {
|
package/dist/paging.d.ts
CHANGED
|
@@ -22,9 +22,13 @@ export declare class HistoryMiss extends Error {
|
|
|
22
22
|
/** A transport's history store: what left memory, and what `prepare` loaded back. */
|
|
23
23
|
export declare class Pager {
|
|
24
24
|
readonly store: HistoryStore;
|
|
25
|
-
/**
|
|
25
|
+
/**
|
|
26
|
+
* What the last `prepare` loaded at mix time `now`, and every record paged since, until a seek or
|
|
27
|
+
* read takes it, or memory's length of mix time passes.
|
|
28
|
+
*/
|
|
26
29
|
loaded: {
|
|
27
30
|
t: number;
|
|
31
|
+
now: number;
|
|
28
32
|
records: Paged[];
|
|
29
33
|
} | null;
|
|
30
34
|
/** The latest record paged, which a miss names. */
|
package/dist/paging.js
CHANGED
|
@@ -23,7 +23,10 @@ export class HistoryMiss extends Error {
|
|
|
23
23
|
/** A transport's history store: what left memory, and what `prepare` loaded back. */
|
|
24
24
|
export class Pager {
|
|
25
25
|
store;
|
|
26
|
-
/**
|
|
26
|
+
/**
|
|
27
|
+
* What the last `prepare` loaded at mix time `now`, and every record paged since, until a seek or
|
|
28
|
+
* read takes it, or memory's length of mix time passes.
|
|
29
|
+
*/
|
|
27
30
|
loaded = null;
|
|
28
31
|
/** The latest record paged, which a miss names. */
|
|
29
32
|
last = null;
|
|
@@ -94,7 +97,14 @@ export function pageOut(mix, stream, voice, subject, out, data, reach) {
|
|
|
94
97
|
export function page(transport, records, reach) {
|
|
95
98
|
const pager = transport.pager;
|
|
96
99
|
pager.store.page(records);
|
|
97
|
-
pager.loaded
|
|
100
|
+
const loaded = pager.loaded;
|
|
101
|
+
if (loaded !== null) {
|
|
102
|
+
// Left unread for longer than memory reaches, it is let go: the store still has it all.
|
|
103
|
+
if (transport.now - loaded.now > (transport.history?.ms ?? 0))
|
|
104
|
+
pager.loaded = null;
|
|
105
|
+
else
|
|
106
|
+
loaded.records.push(...records);
|
|
107
|
+
}
|
|
98
108
|
const last = records[records.length - 1];
|
|
99
109
|
pager.last = { mix: last.mix, stream: last.stream };
|
|
100
110
|
if (reach > transport.floor)
|
|
@@ -141,5 +151,5 @@ export async function prepare(transport, t) {
|
|
|
141
151
|
if (pager === null || transport.reaches(t))
|
|
142
152
|
return;
|
|
143
153
|
const records = await pager.store.load(t);
|
|
144
|
-
pager.loaded = { t, records: [...records] };
|
|
154
|
+
pager.loaded = { t, now: transport.now, records: [...records] };
|
|
145
155
|
}
|
package/dist/patch.js
CHANGED
|
@@ -262,13 +262,33 @@ slope, duration, into, reuse) {
|
|
|
262
262
|
return value + bend(slope, aValue, bValue);
|
|
263
263
|
if (Array.isArray(value) && Array.isArray(slope)) {
|
|
264
264
|
const out = value === lastRead.wrote ? value : new Array(value.length);
|
|
265
|
+
// A stop shorter than the channel reads its missing axes as the lerp does, at each end.
|
|
266
|
+
let ends;
|
|
267
|
+
const endsOf = () => {
|
|
268
|
+
const by = track.lerp ?? lerp;
|
|
269
|
+
const at = (t) => into !== undefined
|
|
270
|
+
? into(undefined, aValue, bValue, t)
|
|
271
|
+
: by
|
|
272
|
+
? by(aValue, bValue, t)
|
|
273
|
+
: interpolate(aValue, bValue, t);
|
|
274
|
+
ends = [at(0), at(1)];
|
|
275
|
+
return ends;
|
|
276
|
+
};
|
|
265
277
|
for (let i = 0; i < value.length; i++) {
|
|
266
278
|
const v = value[i];
|
|
267
279
|
const s = slope[i];
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
280
|
+
if (typeof v !== 'number' || typeof s !== 'number') {
|
|
281
|
+
out[i] = v;
|
|
282
|
+
continue;
|
|
283
|
+
}
|
|
284
|
+
let a = aValue[i];
|
|
285
|
+
let z = bValue[i];
|
|
286
|
+
if (typeof a !== 'number' || typeof z !== 'number') {
|
|
287
|
+
const [a0, z1] = ends ?? endsOf();
|
|
288
|
+
a = a0?.[i];
|
|
289
|
+
z = z1?.[i];
|
|
290
|
+
}
|
|
291
|
+
out[i] = typeof a === 'number' && typeof z === 'number' ? v + bend(s, a, z) : v;
|
|
272
292
|
}
|
|
273
293
|
return out;
|
|
274
294
|
}
|