@msb235/blits 0.6.0 → 0.7.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 +152 -0
- package/README.md +31 -7
- package/dist/bare.js +7 -1
- package/dist/book.d.ts +7 -0
- package/dist/book.js +35 -4
- package/dist/chain.d.ts +2 -1
- package/dist/chain.js +7 -3
- package/dist/channels.d.ts +12 -19
- package/dist/channels.js +18 -118
- package/dist/clock.d.ts +9 -2
- package/dist/clock.js +10 -2
- package/dist/closed.d.ts +15 -0
- package/dist/closed.js +144 -0
- package/dist/color.d.ts +61 -0
- package/dist/color.js +229 -0
- package/dist/columns.js +2 -1
- package/dist/crowd.d.ts +1 -1
- package/dist/crowd.js +4 -1
- package/dist/cue.d.ts +2 -0
- package/dist/cue.js +80 -27
- package/dist/due.js +3 -0
- package/dist/fade.d.ts +13 -0
- package/dist/fade.js +57 -4
- package/dist/fill.d.ts +1 -1
- package/dist/fill.js +15 -8
- package/dist/fit.d.ts +144 -0
- package/dist/fit.js +181 -0
- package/dist/fold.d.ts +3 -3
- package/dist/fold.js +18 -11
- package/dist/gather.d.ts +6 -0
- package/dist/gather.js +18 -0
- package/dist/gone.d.ts +32 -0
- package/dist/gone.js +134 -0
- package/dist/handle.d.ts +27 -2
- package/dist/handle.js +60 -11
- package/dist/held.js +4 -0
- package/dist/history.d.ts +15 -2
- package/dist/history.js +56 -9
- package/dist/hosts.d.ts +2 -2
- package/dist/hosts.js +20 -6
- package/dist/index.d.ts +8 -2
- package/dist/index.js +5 -1
- package/dist/lane.d.ts +1 -1
- package/dist/lanes.d.ts +32 -9
- package/dist/lanes.js +53 -24
- package/dist/locus.d.ts +3 -3
- package/dist/locus.js +1 -1
- package/dist/marks.d.ts +3 -0
- package/dist/marks.js +80 -12
- package/dist/meet.d.ts +3 -2
- package/dist/meet.js +15 -3
- package/dist/mixer.d.ts +56 -36
- package/dist/mixer.js +158 -87
- package/dist/motion.d.ts +2 -199
- package/dist/motion.js +2 -652
- package/dist/motions.d.ts +266 -0
- package/dist/motions.js +724 -0
- package/dist/move.d.ts +1 -1
- package/dist/move.js +28 -8
- package/dist/owner.d.ts +1 -1
- package/dist/owner.js +3 -3
- package/dist/pace.d.ts +2 -0
- package/dist/pace.js +8 -0
- package/dist/pack.d.ts +31 -0
- package/dist/pack.js +65 -0
- package/dist/paging.d.ts +63 -0
- package/dist/paging.js +110 -0
- package/dist/patch.d.ts +3 -0
- package/dist/patch.js +2 -0
- package/dist/place.js +235 -43
- package/dist/project.d.ts +7 -4
- package/dist/project.js +117 -78
- package/dist/pull.js +4 -3
- package/dist/qualify.js +4 -2
- package/dist/revive.d.ts +58 -0
- package/dist/revive.js +158 -0
- package/dist/sample.d.ts +1 -1
- package/dist/sample.js +6 -1
- package/dist/scored.d.ts +57 -0
- package/dist/scored.js +196 -0
- package/dist/seek.d.ts +3 -0
- package/dist/seek.js +311 -0
- package/dist/spans.d.ts +29 -0
- package/dist/spans.js +165 -0
- package/dist/strays.d.ts +1 -1
- package/dist/strays.js +21 -0
- package/dist/tape.d.ts +21 -0
- package/dist/tape.js +65 -0
- package/dist/ticker.d.ts +28 -3
- package/dist/ticker.js +3 -0
- package/dist/transport.d.ts +125 -0
- package/dist/transport.js +283 -0
- package/dist/types.d.ts +404 -38
- package/dist/unpage.d.ts +8 -0
- package/dist/unpage.js +165 -0
- package/dist/voice.d.ts +75 -7
- package/dist/voice.js +119 -4
- package/dist/weigh.js +2 -2
- package/docs/schema.html +120 -31
- package/package.json +2 -1
package/dist/place.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { noted } from './history.js';
|
|
2
2
|
import { markOf } from './marks.js';
|
|
3
3
|
import { mixTime, ownerReading } from './owner.js';
|
|
4
|
+
import { holders, scored, scoredGone, scoreVersion, unhold } from './scored.js';
|
|
4
5
|
import { none } from './voice.js';
|
|
5
6
|
/** What a voice's own owner's clock reads now: the mix clock for a voice no owner holds. */
|
|
6
7
|
export function localNow(mix, voice) {
|
|
@@ -17,6 +18,7 @@ export function mixAt(mix, u) {
|
|
|
17
18
|
export function pin(mix, voice, u) {
|
|
18
19
|
mix.pins ??= new Map();
|
|
19
20
|
mix.pins.set(voice, u);
|
|
21
|
+
voice.pinned = u;
|
|
20
22
|
}
|
|
21
23
|
/** Puts each pending voice's start, given in host time, where the mix's rate now puts it. */
|
|
22
24
|
export function repin(mix) {
|
|
@@ -49,20 +51,23 @@ export function checkPlacement(mix, spec, anchor, owner) {
|
|
|
49
51
|
const name = spec.name;
|
|
50
52
|
if (name === undefined)
|
|
51
53
|
return;
|
|
52
|
-
// A voice is known by its owner, its score and its name
|
|
53
|
-
// asker's score under the asker's owner.
|
|
54
|
-
const key = (o, score, n) => `${o?.id ?? 0}\u0000${score ?? ''}\u0000${n}`;
|
|
55
|
-
const
|
|
56
|
-
if (
|
|
57
|
-
return
|
|
54
|
+
// A voice is known by its owner, its score and its name, and by its mix where it names no score;
|
|
55
|
+
// a bare name in an anchor is in the asker's score under the asker's owner.
|
|
56
|
+
const key = (o, score, n, slot) => `${score === undefined ? slot : ''}\u0000${o?.id ?? 0}\u0000${score ?? ''}\u0000${n}`;
|
|
57
|
+
const named = (a, score, o, slot) => {
|
|
58
|
+
if ('all' in a)
|
|
59
|
+
return a.all.flatMap((m) => named(m, score, o, slot));
|
|
60
|
+
if ('any' in a)
|
|
61
|
+
return a.any.flatMap((m) => named(m, score, o, slot));
|
|
58
62
|
const q = 'of' in a ? a.of : 'after' in a ? a.after : 'with' in a ? a.with : a.before;
|
|
59
63
|
if (typeof q === 'string')
|
|
60
|
-
return [key(o, score, q)];
|
|
61
|
-
return q.name === undefined ? [] : [key(o, q.score ?? score, q.name)];
|
|
62
|
-
}
|
|
63
|
-
const
|
|
64
|
+
return [key(o, score, q, slot)];
|
|
65
|
+
return q.name === undefined ? [] : [key(o, q.score ?? score, q.name, slot)];
|
|
66
|
+
};
|
|
67
|
+
const names = (p, score, o, slot) => [p.start, p.in, p.out, p.end].flatMap((a) => a === undefined || typeof a === 'number' ? [] : named(a, score, o, slot));
|
|
68
|
+
const self = key(owner, spec.score, name, mix.slot);
|
|
64
69
|
const seen = new Set();
|
|
65
|
-
const waits = names(anchor, spec.score, owner);
|
|
70
|
+
const waits = names(anchor, spec.score, owner, mix.slot);
|
|
66
71
|
while (waits.length > 0) {
|
|
67
72
|
const n = waits.pop();
|
|
68
73
|
if (n === self)
|
|
@@ -70,11 +75,12 @@ export function checkPlacement(mix, spec, anchor, owner) {
|
|
|
70
75
|
if (seen.has(n))
|
|
71
76
|
continue;
|
|
72
77
|
seen.add(n);
|
|
73
|
-
for (const
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
+
for (const m of mix.transport.members)
|
|
79
|
+
for (const v of m.cued)
|
|
80
|
+
if (v.spec.name !== undefined &&
|
|
81
|
+
key(v.owner, v.spec.score, v.spec.name, m.slot) === n &&
|
|
82
|
+
v.spec.anchor)
|
|
83
|
+
waits.push(...names(v.spec.anchor, v.spec.score, v.owner, m.slot));
|
|
78
84
|
}
|
|
79
85
|
}
|
|
80
86
|
/**
|
|
@@ -104,7 +110,7 @@ export function place(mix) {
|
|
|
104
110
|
moved = true;
|
|
105
111
|
}
|
|
106
112
|
}
|
|
107
|
-
if (voice.state !== 'fading') {
|
|
113
|
+
if (voice.state !== 'fading' && !voice.outSet) {
|
|
108
114
|
const by = anchor.out ?? anchor.end;
|
|
109
115
|
const t = by === undefined ? undefined : resolve(mix, by, voice);
|
|
110
116
|
if (t !== undefined) {
|
|
@@ -127,6 +133,32 @@ function resolve(mix, a, self) {
|
|
|
127
133
|
const o = self.owner;
|
|
128
134
|
if (typeof a === 'number')
|
|
129
135
|
return o === null ? mixAt(mix, a - mix.offset) : a;
|
|
136
|
+
if ('all' in a) {
|
|
137
|
+
let latest = Number.NEGATIVE_INFINITY;
|
|
138
|
+
for (const m of a.all) {
|
|
139
|
+
const t = member(mix, m, self);
|
|
140
|
+
if (t === undefined)
|
|
141
|
+
return undefined;
|
|
142
|
+
latest = Math.max(latest, t);
|
|
143
|
+
}
|
|
144
|
+
return a.all.length === 0 ? undefined : latest;
|
|
145
|
+
}
|
|
146
|
+
if ('any' in a) {
|
|
147
|
+
// A member still unknown may yet answer earlier than every known one, until one has passed.
|
|
148
|
+
const now = Number.isNaN(mix.now) ? Number.NEGATIVE_INFINITY : localNow(mix, self);
|
|
149
|
+
let earliest = Number.POSITIVE_INFINITY;
|
|
150
|
+
let unknown = false;
|
|
151
|
+
for (const m of a.any) {
|
|
152
|
+
const t = member(mix, m, self);
|
|
153
|
+
if (t === undefined)
|
|
154
|
+
unknown = true;
|
|
155
|
+
else
|
|
156
|
+
earliest = Math.min(earliest, t);
|
|
157
|
+
}
|
|
158
|
+
if (earliest === Number.POSITIVE_INFINITY)
|
|
159
|
+
return undefined;
|
|
160
|
+
return unknown && earliest > now ? undefined : earliest;
|
|
161
|
+
}
|
|
130
162
|
let query;
|
|
131
163
|
let mark;
|
|
132
164
|
let by = a.by ?? 0;
|
|
@@ -147,7 +179,7 @@ function resolve(mix, a, self) {
|
|
|
147
179
|
mark = 'start';
|
|
148
180
|
by = -by;
|
|
149
181
|
}
|
|
150
|
-
const t = timeOf(mix,
|
|
182
|
+
const t = timeOf(mix, query, mark, self);
|
|
151
183
|
if (t === undefined)
|
|
152
184
|
return undefined;
|
|
153
185
|
if (o === null)
|
|
@@ -155,42 +187,202 @@ function resolve(mix, a, self) {
|
|
|
155
187
|
const local = ownerReading(o, t);
|
|
156
188
|
return Number.isFinite(local) ? local + by : undefined;
|
|
157
189
|
}
|
|
190
|
+
/**
|
|
191
|
+
* What one member of a join answers, or the time it last gave once its target has left, as a lone
|
|
192
|
+
* anchor keeps its voice where its target last put it.
|
|
193
|
+
*/
|
|
194
|
+
function member(mix, a, self) {
|
|
195
|
+
const t = resolve(mix, a, self);
|
|
196
|
+
if (t === undefined)
|
|
197
|
+
return self.answers?.get(a);
|
|
198
|
+
if (!mix.projecting) {
|
|
199
|
+
self.answers ??= new Map();
|
|
200
|
+
self.answers.set(a, t);
|
|
201
|
+
}
|
|
202
|
+
return t;
|
|
203
|
+
}
|
|
204
|
+
/** Picks among the times a query meets, as its resolver says, in the order they are met. */
|
|
205
|
+
class Pick {
|
|
206
|
+
resolver;
|
|
207
|
+
now;
|
|
208
|
+
any = false;
|
|
209
|
+
firstOrder = Number.POSITIVE_INFINITY;
|
|
210
|
+
lastOrder = Number.NEGATIVE_INFINITY;
|
|
211
|
+
first;
|
|
212
|
+
last;
|
|
213
|
+
pick;
|
|
214
|
+
constructor(resolver, now) {
|
|
215
|
+
this.resolver = resolver;
|
|
216
|
+
this.now = now;
|
|
217
|
+
}
|
|
218
|
+
take(order, t) {
|
|
219
|
+
this.any = true;
|
|
220
|
+
if (order < this.firstOrder) {
|
|
221
|
+
this.firstOrder = order;
|
|
222
|
+
this.first = t;
|
|
223
|
+
}
|
|
224
|
+
if (order > this.lastOrder) {
|
|
225
|
+
this.lastOrder = order;
|
|
226
|
+
this.last = t;
|
|
227
|
+
}
|
|
228
|
+
if (t !== undefined)
|
|
229
|
+
this.consider(t);
|
|
230
|
+
}
|
|
231
|
+
/** Takes what `o` picked as though its times had been met here. */
|
|
232
|
+
merge(o) {
|
|
233
|
+
if (!o.any)
|
|
234
|
+
return;
|
|
235
|
+
this.any = true;
|
|
236
|
+
if (o.firstOrder < this.firstOrder) {
|
|
237
|
+
this.firstOrder = o.firstOrder;
|
|
238
|
+
this.first = o.first;
|
|
239
|
+
}
|
|
240
|
+
if (o.lastOrder > this.lastOrder) {
|
|
241
|
+
this.lastOrder = o.lastOrder;
|
|
242
|
+
this.last = o.last;
|
|
243
|
+
}
|
|
244
|
+
if (o.pick !== undefined)
|
|
245
|
+
this.consider(o.pick);
|
|
246
|
+
}
|
|
247
|
+
consider(t) {
|
|
248
|
+
const p = this.pick;
|
|
249
|
+
if (this.resolver === 'earliest'
|
|
250
|
+
? p === undefined || t < p
|
|
251
|
+
: this.resolver === 'latest'
|
|
252
|
+
? p === undefined || t > p
|
|
253
|
+
: this.resolver === 'next' && t >= this.now && (p === undefined || t < p))
|
|
254
|
+
this.pick = t;
|
|
255
|
+
}
|
|
256
|
+
answer() {
|
|
257
|
+
if (!this.any)
|
|
258
|
+
return undefined;
|
|
259
|
+
if (this.resolver === 'first')
|
|
260
|
+
return this.first;
|
|
261
|
+
if (this.resolver === 'last')
|
|
262
|
+
return this.last;
|
|
263
|
+
return this.pick;
|
|
264
|
+
}
|
|
265
|
+
}
|
|
158
266
|
/**
|
|
159
267
|
* The mix time a query answers: `mark` of the voice it picks, among those cued and those that have
|
|
160
268
|
* left, never the asker; or a mark the host announced on the score, which is the same time for
|
|
161
269
|
* any of the four. Undefined while what it picks has no such time. A query naming no score looks
|
|
162
270
|
* among the asker's siblings, the voices its owner holds.
|
|
163
271
|
*/
|
|
164
|
-
function timeOf(mix,
|
|
272
|
+
function timeOf(mix, key, mark, self) {
|
|
273
|
+
const q = typeof key === 'string' ? { name: key } : key;
|
|
165
274
|
const score = q.score ?? self.spec.score;
|
|
166
275
|
const anywhere = q.score !== undefined;
|
|
167
|
-
const
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
276
|
+
const resolver = q.resolver ?? 'last';
|
|
277
|
+
const now = Number.isNaN(mix.now) ? Number.NEGATIVE_INFINITY : mix.now;
|
|
278
|
+
const picked = new Pick(resolver, now);
|
|
279
|
+
const matches = (v) => v !== self &&
|
|
280
|
+
v.spec.score === score &&
|
|
281
|
+
(anywhere || v.owner === self.owner) &&
|
|
282
|
+
(q.name === undefined || v.spec.name === q.name) &&
|
|
283
|
+
(q.tag === undefined || (v.spec.tags ?? none).includes(q.tag)) &&
|
|
284
|
+
(q.writes === undefined || v.patch.writes.includes(q.writes));
|
|
285
|
+
// A query naming a score looks in every mix on the transport that holds a voice on it; one
|
|
286
|
+
// naming none, in its own.
|
|
287
|
+
const listed = anywhere && score !== undefined && !mix.projecting;
|
|
288
|
+
const mixes = !anywhere
|
|
289
|
+
? [mix]
|
|
290
|
+
: listed
|
|
291
|
+
? holders(mix, score)
|
|
292
|
+
: mix.transport.members;
|
|
293
|
+
for (let i = 0; i < mixes.length; i++) {
|
|
294
|
+
const m = mixes[i];
|
|
295
|
+
if (score === undefined || m.projecting || mix.projecting) {
|
|
296
|
+
for (const v of [...m.gone, ...m.cued])
|
|
297
|
+
if (matches(v))
|
|
298
|
+
picked.take(v.id, markOf(m, v, mark));
|
|
299
|
+
continue;
|
|
300
|
+
}
|
|
301
|
+
const cued = scored(m, score);
|
|
302
|
+
const list = scoredGone(m, score);
|
|
303
|
+
if (cued.size === 0 && list.length === 0) {
|
|
304
|
+
if (listed) {
|
|
305
|
+
unhold(m, score);
|
|
306
|
+
i--;
|
|
307
|
+
}
|
|
308
|
+
continue;
|
|
309
|
+
}
|
|
310
|
+
// `next` asks what is still ahead of now, which a running pick cannot keep.
|
|
311
|
+
if (resolver === 'next') {
|
|
312
|
+
for (const v of cued)
|
|
313
|
+
if (matches(v))
|
|
314
|
+
picked.take(v.id, markOf(m, v, mark));
|
|
315
|
+
for (const v of list)
|
|
316
|
+
if (matches(v))
|
|
317
|
+
picked.take(v.id, markOf(m, v, mark));
|
|
318
|
+
continue;
|
|
319
|
+
}
|
|
320
|
+
const fold = foldOf(m, list, key, mark, self, resolver);
|
|
321
|
+
const version = scoreVersion(m, score);
|
|
322
|
+
const reduced = m.reduced;
|
|
323
|
+
if (fold.live === null ||
|
|
324
|
+
fold.liveOf !== m.cued ||
|
|
325
|
+
fold.liveVersion !== version ||
|
|
326
|
+
fold.liveReduced !== reduced) {
|
|
327
|
+
const live = new Pick(resolver, Number.NEGATIVE_INFINITY);
|
|
328
|
+
let keeps = true;
|
|
329
|
+
for (const v of cued) {
|
|
330
|
+
if (!matches(v))
|
|
331
|
+
continue;
|
|
332
|
+
if (v.owner !== null || v.holding !== null || v.fitting !== null)
|
|
333
|
+
keeps = false;
|
|
334
|
+
live.take(v.id, markOf(m, v, mark));
|
|
335
|
+
}
|
|
336
|
+
fold.live = live;
|
|
337
|
+
fold.liveOf = keeps ? m.cued : null;
|
|
338
|
+
fold.liveVersion = version;
|
|
339
|
+
fold.liveReduced = reduced;
|
|
340
|
+
}
|
|
341
|
+
picked.merge(fold.live);
|
|
342
|
+
for (; fold.seen < list.length; fold.seen++) {
|
|
343
|
+
const v = list[fold.seen];
|
|
344
|
+
if (!matches(v))
|
|
345
|
+
continue;
|
|
346
|
+
if (v.owner === null)
|
|
347
|
+
fold.pick.take(v.id, markOf(m, v, mark));
|
|
348
|
+
else
|
|
349
|
+
fold.owned.push(v);
|
|
350
|
+
}
|
|
351
|
+
picked.merge(fold.pick);
|
|
352
|
+
for (const v of fold.owned)
|
|
353
|
+
picked.take(v.id, markOf(m, v, mark));
|
|
354
|
+
}
|
|
176
355
|
if (q.writes === undefined && (anywhere || self.owner === null))
|
|
177
356
|
for (const a of mix.announced)
|
|
178
357
|
if (a.score === score &&
|
|
358
|
+
(score !== undefined || a.slot === mix.slot) &&
|
|
179
359
|
(q.name === undefined || a.name === q.name) &&
|
|
180
360
|
(q.tag === undefined || a.tags.includes(q.tag)))
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
if (
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
361
|
+
picked.take(a.order, Number.isNaN(a.at) ? undefined : mixAt(mix, a.at));
|
|
362
|
+
return picked.answer();
|
|
363
|
+
}
|
|
364
|
+
/** `self`'s read of `m`'s gone voices for one query, started over where the list has changed under it. */
|
|
365
|
+
function foldOf(m, list, key, mark, self, resolver) {
|
|
366
|
+
self.folds ??= new Map();
|
|
367
|
+
let byMix = self.folds.get(key);
|
|
368
|
+
if (byMix === undefined) {
|
|
369
|
+
byMix = new Map();
|
|
370
|
+
self.folds.set(key, byMix);
|
|
371
|
+
}
|
|
372
|
+
let fold = byMix.get(m);
|
|
373
|
+
if (fold === undefined || fold.list !== list || fold.mark !== mark) {
|
|
374
|
+
fold = {
|
|
375
|
+
list,
|
|
376
|
+
mark,
|
|
377
|
+
seen: 0,
|
|
378
|
+
pick: new Pick(resolver, Number.NEGATIVE_INFINITY),
|
|
379
|
+
owned: [],
|
|
380
|
+
live: null,
|
|
381
|
+
liveOf: null,
|
|
382
|
+
liveVersion: -1,
|
|
383
|
+
liveReduced: false,
|
|
384
|
+
};
|
|
385
|
+
byMix.set(m, fold);
|
|
386
|
+
}
|
|
387
|
+
return fold;
|
|
196
388
|
}
|
package/dist/project.d.ts
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
|
-
import
|
|
2
|
-
import type {
|
|
3
|
-
/**
|
|
4
|
-
|
|
1
|
+
import { Transport } from './transport.js';
|
|
2
|
+
import type { TransportProjection } from './types.js';
|
|
3
|
+
/**
|
|
4
|
+
* Every mix on a transport read at mix time `t`, through copies of them on a transport of their
|
|
5
|
+
* own, moved together so an anchor across mixes answers as it would live.
|
|
6
|
+
*/
|
|
7
|
+
export declare function projectAll(transport: Transport, t: number): TransportProjection;
|
package/dist/project.js
CHANGED
|
@@ -1,85 +1,60 @@
|
|
|
1
1
|
import { ownBlends } from './blend.js';
|
|
2
2
|
import { index } from './chain.js';
|
|
3
|
-
import { copyHeld, last } from './history.js';
|
|
4
|
-
import {
|
|
3
|
+
import { copyHeld, last, leftAt } from './history.js';
|
|
4
|
+
import { Mixer } from './mixer.js';
|
|
5
5
|
import { move } from './move.js';
|
|
6
6
|
import { heldByInput, ownerReading, relink } from './owner.js';
|
|
7
7
|
import { pin } from './place.js';
|
|
8
8
|
import { reading } from './reading.js';
|
|
9
9
|
import { Store } from './store.js';
|
|
10
|
+
import { hostAt } from './tape.js';
|
|
11
|
+
import { Transport } from './transport.js';
|
|
12
|
+
import { cover } from './unpage.js';
|
|
10
13
|
import { unreached } from './unreached.js';
|
|
11
|
-
/**
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
if (Number.isNaN(mix.now) || t >= mix.now) {
|
|
24
|
-
c.now = mix.now;
|
|
25
|
-
c.pace = pace === null ? null : pace.until(Number.POSITIVE_INFINITY);
|
|
26
|
-
c.cued = mix.cued
|
|
27
|
-
.filter((v) => v.state !== 'done')
|
|
28
|
-
.map((v) => {
|
|
29
|
-
const copy = v.copy((subject) => carry(mix, v, subject));
|
|
30
|
-
const at = mix.pins?.get(v);
|
|
31
|
-
if (at !== undefined)
|
|
32
|
-
pin(c, copy, at);
|
|
33
|
-
return copy;
|
|
34
|
-
});
|
|
35
|
-
ownBlends(c.cued);
|
|
36
|
-
if (mix.owners !== null)
|
|
37
|
-
relink(c.cued);
|
|
38
|
-
c.announced = mix.announced.map((a) => ({ ...a }));
|
|
39
|
-
count(c);
|
|
40
|
-
move(c, t);
|
|
41
|
-
}
|
|
42
|
-
else {
|
|
43
|
-
const history = mix.opts.history;
|
|
14
|
+
/**
|
|
15
|
+
* Every mix on a transport read at mix time `t`, through copies of them on a transport of their
|
|
16
|
+
* own, moved together so an anchor across mixes answers as it would live.
|
|
17
|
+
*/
|
|
18
|
+
export function projectAll(transport, t) {
|
|
19
|
+
const ahead = Number.isNaN(transport.now) || t >= transport.now;
|
|
20
|
+
const tape = transport.tape;
|
|
21
|
+
const next = ahead && tape !== undefined ? tape.timestampAt(tape.undoDepth()) : undefined;
|
|
22
|
+
if (next !== undefined && next <= t)
|
|
23
|
+
throw new Error(`blits: the tape holds calls at ${next} that a read ahead to ${t} cannot play; seek there to see them`);
|
|
24
|
+
if (!ahead) {
|
|
25
|
+
const history = transport.history;
|
|
44
26
|
if (!history)
|
|
45
27
|
throw new Error('blits: reading back needs a mix made with history');
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
.
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
const copy = v.copy((subject) => recall(mix, v, subject, t), last(log, t, (e) => e.sync) ?? log[0]);
|
|
65
|
-
if (then !== undefined)
|
|
66
|
-
copy.setting.host = then;
|
|
67
|
-
return copy;
|
|
28
|
+
cover(transport, t);
|
|
29
|
+
}
|
|
30
|
+
const u = hostAt(transport, t);
|
|
31
|
+
const pace = transport.pace;
|
|
32
|
+
const c = new Transport(undefined, true);
|
|
33
|
+
c.offset = transport.offset;
|
|
34
|
+
c.u = u;
|
|
35
|
+
c.nextId = transport.nextId;
|
|
36
|
+
c.now = ahead ? transport.now : t;
|
|
37
|
+
c.pace = pace === null ? null : pace.until(ahead ? Number.POSITIVE_INFINITY : u);
|
|
38
|
+
c.announced = (ahead ? transport.announced : transport.announced.filter((a) => a.made < t)).map((a) => ({ ...a }));
|
|
39
|
+
const copies = new Map();
|
|
40
|
+
for (const mix of transport.members) {
|
|
41
|
+
const copy = new Mixer(mix.kit, {
|
|
42
|
+
...mix.opts,
|
|
43
|
+
history: undefined,
|
|
44
|
+
lanes: false,
|
|
45
|
+
transport: c,
|
|
68
46
|
});
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
copy
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
: copy.out && copy.out.at <= t
|
|
77
|
-
? 'fading'
|
|
78
|
-
: copy.freezesAfter && copy.elapsedAt(t) >= copy.span + copy.latest
|
|
79
|
-
? 'frozen'
|
|
80
|
-
: 'live';
|
|
81
|
-
count(c);
|
|
47
|
+
copy.slot = mix.slot;
|
|
48
|
+
copy.projecting = true;
|
|
49
|
+
copies.set(mix, copy);
|
|
50
|
+
if (ahead)
|
|
51
|
+
copyAhead(mix, copy);
|
|
52
|
+
else
|
|
53
|
+
copyBack(mix, copy, t);
|
|
82
54
|
}
|
|
55
|
+
if (ahead)
|
|
56
|
+
for (const copy of copies.values())
|
|
57
|
+
move(copy, t);
|
|
83
58
|
const read = (f) => {
|
|
84
59
|
reading.live = false;
|
|
85
60
|
try {
|
|
@@ -90,14 +65,78 @@ export function project(mix, c, timestamp) {
|
|
|
90
65
|
}
|
|
91
66
|
};
|
|
92
67
|
return {
|
|
93
|
-
timestamp,
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
68
|
+
timestamp: t,
|
|
69
|
+
of(mix) {
|
|
70
|
+
const copy = copies.get(mix);
|
|
71
|
+
if (copy === undefined)
|
|
72
|
+
throw new Error('blits: that mix is not on this transport');
|
|
73
|
+
return {
|
|
74
|
+
timestamp: t,
|
|
75
|
+
probe: (subject, out) => read(() => copy.fold(subject, out)),
|
|
76
|
+
assess: (subject) => read(() => {
|
|
77
|
+
copy.fold(subject);
|
|
78
|
+
return doubts(copy, subject);
|
|
79
|
+
}),
|
|
80
|
+
};
|
|
81
|
+
},
|
|
99
82
|
};
|
|
100
83
|
}
|
|
84
|
+
/** A mix's copy for a read ahead: its voices as they stand, each subject carried from the live one. */
|
|
85
|
+
function copyAhead(mix, c) {
|
|
86
|
+
c.pose = mix.pose;
|
|
87
|
+
c.wantsPose = mix.wantsPose;
|
|
88
|
+
c.reducedNow = mix.reducedNow;
|
|
89
|
+
c.cued = mix.cued
|
|
90
|
+
.filter((v) => v.state !== 'done')
|
|
91
|
+
.map((v) => {
|
|
92
|
+
const copy = v.copy((subject) => carry(mix, v, subject));
|
|
93
|
+
const at = mix.pins?.get(v);
|
|
94
|
+
if (at !== undefined)
|
|
95
|
+
pin(c, copy, at);
|
|
96
|
+
return copy;
|
|
97
|
+
});
|
|
98
|
+
ownBlends(c.cued);
|
|
99
|
+
if (mix.owners !== null)
|
|
100
|
+
relink(c.cued);
|
|
101
|
+
count(c);
|
|
102
|
+
}
|
|
103
|
+
/** A mix's copy for a read back to `t`: its voices then, from the controls and copies kept. */
|
|
104
|
+
function copyBack(mix, c, t) {
|
|
105
|
+
c.pose = mix.pose;
|
|
106
|
+
c.wantsPose = mix.wantsPose;
|
|
107
|
+
c.reducedNow = mix.reducedNow;
|
|
108
|
+
c.backward = true;
|
|
109
|
+
const was = last(mix.hostLog, t, true);
|
|
110
|
+
const host = mix.opts.host;
|
|
111
|
+
const then = was && typeof host === 'object' && host !== null
|
|
112
|
+
? Object.assign(Object.create(host), was.fields)
|
|
113
|
+
: undefined;
|
|
114
|
+
if (was)
|
|
115
|
+
c.hostThen = was;
|
|
116
|
+
c.cued = [...mix.cued, ...mix.gone]
|
|
117
|
+
.filter((v) => v.cuedAt <= t && v.doneAt > t)
|
|
118
|
+
.sort((a, b) => a.id - b.id)
|
|
119
|
+
.map((v) => {
|
|
120
|
+
const log = v.log;
|
|
121
|
+
const copy = v.copy((subject) => recall(mix, v, subject, t), last(log, t, (e) => e.sync) ?? log[0]);
|
|
122
|
+
if (then !== undefined)
|
|
123
|
+
copy.setting.host = then;
|
|
124
|
+
return copy;
|
|
125
|
+
});
|
|
126
|
+
ownBlends(c.cued);
|
|
127
|
+
if (mix.owners !== null)
|
|
128
|
+
relink(c.cued);
|
|
129
|
+
for (const copy of c.cued)
|
|
130
|
+
copy.state =
|
|
131
|
+
(copy.owner === null ? t : ownerReading(copy.owner, t)) < copy.start
|
|
132
|
+
? 'pending'
|
|
133
|
+
: copy.out && copy.out.at <= t
|
|
134
|
+
? 'fading'
|
|
135
|
+
: copy.freezesAfter && copy.elapsedAt(t) >= copy.span + copy.latest
|
|
136
|
+
? 'frozen'
|
|
137
|
+
: 'live';
|
|
138
|
+
count(c);
|
|
139
|
+
}
|
|
101
140
|
/** Recounts what the fold's shortcuts depend on, for a projection's freshly copied voices. */
|
|
102
141
|
function count(mix) {
|
|
103
142
|
mix.named = new Store();
|
|
@@ -132,7 +171,7 @@ function carry(mix, voice, subject) {
|
|
|
132
171
|
* voice's start with fresh state, which is exact for a voice stepped at a fixed interval.
|
|
133
172
|
*/
|
|
134
173
|
function recall(mix, voice, subject, t) {
|
|
135
|
-
const live = recordOf(mix, voice, subject);
|
|
174
|
+
const live = leftAt(voice.left, subject, t) ?? recordOf(mix, voice, subject);
|
|
136
175
|
if (live === undefined)
|
|
137
176
|
return undefined;
|
|
138
177
|
const snap = live.snaps && last(live.snaps, t, true);
|
|
@@ -192,7 +231,7 @@ function doubts(mix, subject) {
|
|
|
192
231
|
for (const slot of voice.slots)
|
|
193
232
|
out[mix.names[slot]] = 'held';
|
|
194
233
|
}
|
|
195
|
-
for (let held = mix.chain(subject
|
|
234
|
+
for (let held = mix.chain(subject); held !== null; held = held.next) {
|
|
196
235
|
const voice = held.voice;
|
|
197
236
|
if (voice === null || voice.state === 'done')
|
|
198
237
|
continue;
|
package/dist/pull.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { axesOf } from './channels.js';
|
|
1
2
|
import { copy } from './clone.js';
|
|
2
3
|
import { clampRun, flush, pullRun, writeLater } from './columns.js';
|
|
3
4
|
/** Writes a folded pose's value into subject `n`'s places in a column. */
|
|
@@ -128,14 +129,14 @@ function readAll(mix, list, columns, live, current, whole) {
|
|
|
128
129
|
const kept = heads[n];
|
|
129
130
|
const head = was[n] === subject && kept !== undefined && kept.version === version
|
|
130
131
|
? kept
|
|
131
|
-
: mix.chain(subject
|
|
132
|
+
: mix.chain(subject);
|
|
132
133
|
was[n] = subject;
|
|
133
134
|
heads[n] = head;
|
|
134
135
|
slot = head.slot;
|
|
135
136
|
slots[n] = slot;
|
|
136
137
|
}
|
|
137
138
|
}
|
|
138
|
-
const laned = live && lanes?.prepare(slot, subject,
|
|
139
|
+
const laned = live && lanes?.prepare(slot, subject, mix.version, heads[n] ?? null) === true;
|
|
139
140
|
if (laned && whole && lanes.whole && !lanes.owes(slot)) {
|
|
140
141
|
writeLater(lanes, slot, columns, n);
|
|
141
142
|
continue;
|
|
@@ -160,7 +161,7 @@ function columnsOf(mix, into) {
|
|
|
160
161
|
throw new Error(`blits: pull was handed ${key}, which the kit lacks`);
|
|
161
162
|
const channel = mix.channels[slot];
|
|
162
163
|
const rest = channel.rest;
|
|
163
|
-
const axes =
|
|
164
|
+
const axes = axesOf(channel);
|
|
164
165
|
const bounds = channel.bounds;
|
|
165
166
|
const at = new Float64Array(axes).fill(Number.NaN);
|
|
166
167
|
if (typeof rest === 'number')
|
package/dist/qualify.js
CHANGED
|
@@ -163,11 +163,13 @@ export function requalify(lanes, version) {
|
|
|
163
163
|
return;
|
|
164
164
|
const channel = host.channels[slot];
|
|
165
165
|
const n = numericOf(channel);
|
|
166
|
-
|
|
166
|
+
// A rest-less channel's values are NaN until some voice passes the band: absent from the pose.
|
|
167
|
+
const rest = (channel.rest ?? Number.NaN);
|
|
168
|
+
const scalar = typeof rest === 'number' && n.axes === 1;
|
|
167
169
|
const ch = {
|
|
168
170
|
name: host.names[slot],
|
|
169
171
|
op: n.op,
|
|
170
|
-
scalar
|
|
172
|
+
scalar,
|
|
171
173
|
rest: typeof rest === 'number' ? rest : rest[0],
|
|
172
174
|
axes: n.axes,
|
|
173
175
|
values: new Float64Array(lanes.cap * n.axes),
|