@msb235/blits 0.14.0 → 0.15.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 +198 -0
- package/README.md +3 -3
- package/dist/ahead.d.ts +26 -0
- package/dist/ahead.js +264 -0
- package/dist/around.d.ts +11 -0
- package/dist/around.js +32 -0
- package/dist/bare.js +37 -14
- package/dist/blank.d.ts +10 -0
- package/dist/blank.js +26 -0
- package/dist/blend.d.ts +2 -1
- package/dist/blend.js +1 -6
- package/dist/bound.d.ts +70 -0
- package/dist/bound.js +351 -0
- package/dist/chain.d.ts +2 -1
- package/dist/chain.js +7 -5
- package/dist/changes.d.ts +24 -0
- package/dist/changes.js +189 -0
- package/dist/channels.d.ts +39 -13
- package/dist/channels.js +14 -5
- package/dist/color.d.ts +12 -10
- package/dist/color.js +25 -25
- package/dist/columns.d.ts +2 -1
- package/dist/columns.js +62 -38
- package/dist/contribution.d.ts +30 -0
- package/dist/contribution.js +374 -0
- package/dist/copyout.d.ts +10 -0
- package/dist/copyout.js +69 -24
- package/dist/crowd.d.ts +16 -6
- package/dist/crowd.js +20 -11
- package/dist/cue.d.ts +8 -1
- package/dist/cue.js +39 -13
- package/dist/cut.d.ts +8 -0
- package/dist/cut.js +4 -0
- package/dist/delay.d.ts +3 -0
- package/dist/delay.js +7 -0
- package/dist/drop.d.ts +9 -0
- package/dist/drop.js +84 -0
- package/dist/due.d.ts +24 -5
- package/dist/due.js +114 -34
- package/dist/easing.d.ts +6 -4
- package/dist/easing.js +7 -3
- package/dist/everyone.d.ts +8 -2
- package/dist/everyone.js +33 -3
- package/dist/fade.d.ts +2 -1
- package/dist/fade.js +2 -1
- package/dist/fill.d.ts +3 -2
- package/dist/fill.js +52 -20
- package/dist/fit.d.ts +44 -26
- package/dist/fit.js +10 -9
- package/dist/fold.d.ts +4 -37
- package/dist/fold.js +5 -513
- package/dist/gather.d.ts +12 -7
- package/dist/gather.js +179 -44
- package/dist/gone.js +2 -2
- package/dist/grid.d.ts +47 -0
- package/dist/grid.js +247 -0
- package/dist/handle.d.ts +2 -0
- package/dist/handle.js +14 -8
- package/dist/held.d.ts +2 -1
- package/dist/held.js +2 -1
- package/dist/history.d.ts +6 -5
- package/dist/history.js +7 -9
- package/dist/hosts.js +9 -15
- package/dist/index.d.ts +5 -4
- package/dist/keyfill.d.ts +1 -1
- package/dist/keyfill.js +5 -3
- package/dist/keyrows.d.ts +12 -0
- package/dist/keyrows.js +99 -12
- package/dist/lane.d.ts +38 -11
- package/dist/lane.js +46 -11
- package/dist/lanes.d.ts +30 -12
- package/dist/lanes.js +56 -36
- package/dist/leavings.d.ts +2 -2
- package/dist/leavings.js +1 -1
- package/dist/lives.d.ts +47 -0
- package/dist/lives.js +180 -0
- package/dist/meet.d.ts +2 -2
- package/dist/meet.js +95 -7
- package/dist/mixer.d.ts +56 -37
- package/dist/mixer.js +57 -171
- package/dist/motion.d.ts +14 -118
- package/dist/motion.js +16 -8
- package/dist/motions.d.ts +62 -155
- package/dist/motions.js +56 -370
- package/dist/moved.d.ts +1 -1
- package/dist/moved.js +16 -7
- package/dist/named.d.ts +6 -2
- package/dist/named.js +49 -3
- package/dist/numbers.d.ts +7 -0
- package/dist/numbers.js +33 -0
- package/dist/owed.d.ts +2 -0
- package/dist/owed.js +4 -0
- package/dist/pack.d.ts +2 -1
- package/dist/pack.js +2 -3
- package/dist/patch.d.ts +7 -3
- package/dist/patch.js +6 -2
- package/dist/places.d.ts +53 -0
- package/dist/places.js +171 -0
- package/dist/plot.d.ts +12 -0
- package/dist/plot.js +52 -0
- package/dist/project.d.ts +4 -4
- package/dist/project.js +9 -212
- package/dist/pull.js +22 -9
- package/dist/qualify.js +93 -31
- package/dist/reading.d.ts +6 -0
- package/dist/reading.js +5 -0
- package/dist/record.d.ts +5 -1
- package/dist/record.js +3 -2
- package/dist/relink.d.ts +1 -1
- package/dist/renumber.d.ts +10 -0
- package/dist/renumber.js +75 -0
- package/dist/restored.d.ts +2 -2
- package/dist/restored.js +1 -1
- package/dist/rests.d.ts +9 -0
- package/dist/rests.js +76 -0
- package/dist/revive.d.ts +3 -2
- package/dist/revive.js +2 -2
- package/dist/{locus.d.ts → role.d.ts} +10 -9
- package/dist/{locus.js → role.js} +3 -3
- package/dist/roles.d.ts +10 -0
- package/dist/roles.js +158 -0
- package/dist/roster.js +3 -3
- package/dist/rotation.d.ts +1 -1
- package/dist/rotation.js +1 -1
- package/dist/rows.js +28 -20
- package/dist/sample.d.ts +2 -2
- package/dist/sample.js +33 -7
- package/dist/seat.d.ts +17 -0
- package/dist/seat.js +25 -0
- package/dist/seek.js +5 -5
- package/dist/setting.d.ts +27 -0
- package/dist/setting.js +55 -0
- package/dist/shared.d.ts +1 -1
- package/dist/shared.js +1 -1
- package/dist/signals.d.ts +64 -32
- package/dist/signals.js +20 -18
- package/dist/stack.d.ts +5 -5
- package/dist/stack.js +5 -5
- package/dist/standing.d.ts +2 -1
- package/dist/standing.js +3 -2
- package/dist/store.d.ts +9 -0
- package/dist/store.js +24 -0
- package/dist/strays.d.ts +2 -1
- package/dist/strays.js +23 -15
- package/dist/stretch.d.ts +112 -0
- package/dist/stretch.js +9 -0
- package/dist/subject.d.ts +142 -0
- package/dist/subject.js +1 -0
- package/dist/tape.d.ts +9 -5
- package/dist/tape.js +5 -1
- package/dist/then.d.ts +9 -0
- package/dist/then.js +57 -0
- package/dist/ticker.d.ts +4 -4
- package/dist/trace.d.ts +4 -0
- package/dist/trace.js +255 -0
- package/dist/transport.d.ts +0 -8
- package/dist/transport.js +0 -4
- package/dist/turns.d.ts +4 -3
- package/dist/turns.js +4 -3
- package/dist/types/arc.d.ts +12 -11
- package/dist/types/channel.d.ts +12 -8
- package/dist/types/history.d.ts +35 -19
- package/dist/types/mix.d.ts +237 -163
- package/dist/types/motion.d.ts +151 -0
- package/dist/types/motion.js +1 -0
- package/dist/types/patch.d.ts +67 -34
- package/dist/types/place.d.ts +51 -36
- package/dist/types/trace.d.ts +51 -0
- package/dist/types/trace.js +1 -0
- package/dist/types/transport.d.ts +20 -18
- package/dist/types/voice.d.ts +144 -73
- package/dist/types/watch.d.ts +88 -40
- package/dist/types.d.ts +2 -0
- package/dist/unreached.d.ts +2 -0
- package/dist/unreached.js +7 -0
- package/dist/voice.d.ts +10 -143
- package/dist/voice.js +37 -82
- package/dist/warn.d.ts +2 -0
- package/dist/warn.js +1 -1
- package/dist/watch.d.ts +2 -2
- package/dist/watch.js +5 -3
- package/dist/wave.d.ts +4 -3
- package/dist/wave.js +3 -2
- package/dist/weigh.d.ts +7 -1
- package/dist/weigh.js +27 -0
- package/docs/schema.html +223 -34
- package/package.json +2 -2
package/dist/project.js
CHANGED
|
@@ -1,23 +1,20 @@
|
|
|
1
|
+
import { copyAhead, mirror, playAhead, recordOf } from './ahead.js';
|
|
1
2
|
import { ownBlends } from './blend.js';
|
|
2
|
-
import {
|
|
3
|
+
import { heldAt } from './chain.js';
|
|
3
4
|
import { stepOf } from './clock.js';
|
|
4
|
-
import {
|
|
5
|
+
import { within } from './cut.js';
|
|
5
6
|
import { fadesAt } from './fade.js';
|
|
6
7
|
import { meet, origin } from './held.js';
|
|
7
8
|
import { copyHeld, last, lastWithin } from './history.js';
|
|
8
|
-
import { markOf, voiceMarks } from './marks.js';
|
|
9
9
|
import { Mixer } from './mixer.js';
|
|
10
|
-
import { move } from './move.js';
|
|
11
10
|
import { heldByInput, ownerReading, relink } from './owner.js';
|
|
12
|
-
import { pin, place } from './place.js';
|
|
13
11
|
import { reading } from './reading.js';
|
|
14
12
|
import { record } from './record.js';
|
|
15
13
|
import { recount } from './roster.js';
|
|
16
14
|
import { copyStanding, stands } from './standing.js';
|
|
17
|
-
import { hostAhead, hostAt
|
|
18
|
-
import { Transport
|
|
15
|
+
import { hostAhead, hostAt } from './tape.js';
|
|
16
|
+
import { Transport } from './transport.js';
|
|
19
17
|
import { cover, coverFrame } from './unpage.js';
|
|
20
|
-
import { unreached } from './unreached.js';
|
|
21
18
|
/**
|
|
22
19
|
* Every mix on a transport read at mix time `t`, through copies of them on a transport of their
|
|
23
20
|
* own, moved together so an anchor across mixes answers as it would live. Ahead, they stop at each
|
|
@@ -75,6 +72,7 @@ export function projectAll(transport, at) {
|
|
|
75
72
|
}, null);
|
|
76
73
|
copy.slot = mix.slot;
|
|
77
74
|
copy.projecting = true;
|
|
75
|
+
copy.places = mix.places;
|
|
78
76
|
copies.set(mix, copy);
|
|
79
77
|
if (ahead)
|
|
80
78
|
copyAhead(mix, copy);
|
|
@@ -91,15 +89,18 @@ export function projectAll(transport, at) {
|
|
|
91
89
|
earlier = new Map();
|
|
92
90
|
stood = playAhead(transport, c, copies, t, earlier);
|
|
93
91
|
}
|
|
92
|
+
const back = !ahead && kept ? cut : null;
|
|
94
93
|
const read = (f) => {
|
|
95
94
|
reading.live = false;
|
|
96
95
|
reading.ahead = stood.ahead.size > 0 ? stood.ahead : null;
|
|
96
|
+
reading.back = back;
|
|
97
97
|
try {
|
|
98
98
|
return f();
|
|
99
99
|
}
|
|
100
100
|
finally {
|
|
101
101
|
reading.live = true;
|
|
102
102
|
reading.ahead = null;
|
|
103
|
+
reading.back = null;
|
|
103
104
|
}
|
|
104
105
|
};
|
|
105
106
|
return {
|
|
@@ -136,168 +137,6 @@ export function projectAll(transport, at) {
|
|
|
136
137
|
},
|
|
137
138
|
};
|
|
138
139
|
}
|
|
139
|
-
/**
|
|
140
|
-
* Moves a read ahead's copies to mix time `t`, making the calls the tape holds on the way as a seek
|
|
141
|
-
* there makes them on the mixes. While a voice could carry state across it, it stops at each known
|
|
142
|
-
* event: a call, and every mark of a voice or announced. At each stop the copies are kept for
|
|
143
|
-
* subjects to be read there and copied on, so `copies` ends holding those at `t`. Answers the
|
|
144
|
-
* transport they stand on, and puts the tape back where it stood.
|
|
145
|
-
*/
|
|
146
|
-
function playAhead(transport, c, copies, t, earlier) {
|
|
147
|
-
const tape = transport.tape;
|
|
148
|
-
const depth = tape?.undoDepth() ?? 0;
|
|
149
|
-
let played = false;
|
|
150
|
-
try {
|
|
151
|
-
for (;;) {
|
|
152
|
-
const e = nextEvent(tape, c, t);
|
|
153
|
-
if (!(e < t))
|
|
154
|
-
break;
|
|
155
|
-
played = playTo(transport, c, copies, e) || played;
|
|
156
|
-
if (!carries(c))
|
|
157
|
-
continue;
|
|
158
|
-
// Placed now, as the next move would place them, while a target that has just left is here.
|
|
159
|
-
for (const m of c.members)
|
|
160
|
-
if (m.anchored > 0)
|
|
161
|
-
place(m);
|
|
162
|
-
const next = restage(c);
|
|
163
|
-
for (const [mix, copy] of copies) {
|
|
164
|
-
const on = new Mixer(copy.kit, { ...copy.opts, transport: next }, null);
|
|
165
|
-
on.slot = copy.slot;
|
|
166
|
-
on.projecting = true;
|
|
167
|
-
copyAhead(copy, on);
|
|
168
|
-
const stages = earlier.get(mix);
|
|
169
|
-
if (stages === undefined)
|
|
170
|
-
earlier.set(mix, { at: [copy], met: new Set() });
|
|
171
|
-
else
|
|
172
|
-
stages.at.push(copy);
|
|
173
|
-
copies.set(mix, on);
|
|
174
|
-
}
|
|
175
|
-
c = next;
|
|
176
|
-
}
|
|
177
|
-
played = playTo(transport, c, copies, t) || played;
|
|
178
|
-
}
|
|
179
|
-
finally {
|
|
180
|
-
if (played)
|
|
181
|
-
tape?.goto(depth);
|
|
182
|
-
}
|
|
183
|
-
return c;
|
|
184
|
-
}
|
|
185
|
-
/**
|
|
186
|
-
* Whether a voice on a read ahead's copies could carry state from one known event to the next: a
|
|
187
|
-
* patch that steps, or a weight given by a signal, which may keep state. Where none could, a read
|
|
188
|
-
* crosses the events in one go, as fast as one with none.
|
|
189
|
-
*/
|
|
190
|
-
function carries(c) {
|
|
191
|
-
for (const m of c.members)
|
|
192
|
-
for (const v of m.cued)
|
|
193
|
-
if (v.state !== 'done' && (v.patch.step !== undefined || typeof v.spec.weight === 'function'))
|
|
194
|
-
return true;
|
|
195
|
-
return false;
|
|
196
|
-
}
|
|
197
|
-
/**
|
|
198
|
-
* The earliest known event a read ahead's copies have yet to reach before `t`, mix time: the next
|
|
199
|
-
* call the tape holds, and while a voice could carry state across one, a mark of a voice or one
|
|
200
|
-
* announced. Infinity where there is none.
|
|
201
|
-
*/
|
|
202
|
-
function nextEvent(tape, c, t) {
|
|
203
|
-
const now = c.now;
|
|
204
|
-
let e = Number.POSITIVE_INFINITY;
|
|
205
|
-
const call = tape?.timestampAt(tape.undoDepth());
|
|
206
|
-
if (call !== undefined)
|
|
207
|
-
e = c.pace === null ? call : c.pace.reading(call);
|
|
208
|
-
if (!carries(c))
|
|
209
|
-
return e < t ? e : Number.POSITIVE_INFINITY;
|
|
210
|
-
for (const m of c.members)
|
|
211
|
-
for (const v of m.cued) {
|
|
212
|
-
if (v.state === 'done')
|
|
213
|
-
continue;
|
|
214
|
-
for (const mark of voiceMarks) {
|
|
215
|
-
const at = markOf(m, v, mark);
|
|
216
|
-
if (at !== undefined && at > now && at < e)
|
|
217
|
-
e = at;
|
|
218
|
-
}
|
|
219
|
-
const inner = v.spec.marks;
|
|
220
|
-
if (inner !== undefined)
|
|
221
|
-
for (const mark of Object.keys(inner)) {
|
|
222
|
-
const at = markOf(m, v, mark);
|
|
223
|
-
if (at !== undefined && at > now && at < e)
|
|
224
|
-
e = at;
|
|
225
|
-
}
|
|
226
|
-
}
|
|
227
|
-
for (const a of c.announced) {
|
|
228
|
-
const at = c.pace === null ? a.at : c.pace.at(a.at, c.u);
|
|
229
|
-
if (at > now && at < e)
|
|
230
|
-
e = at;
|
|
231
|
-
}
|
|
232
|
-
return e < t ? e : Number.POSITIVE_INFINITY;
|
|
233
|
-
}
|
|
234
|
-
/**
|
|
235
|
-
* Makes the calls the tape holds up to mix time `to` on a read ahead's copies and moves them
|
|
236
|
-
* there. Answers whether it moved the tape.
|
|
237
|
-
*/
|
|
238
|
-
function playTo(transport, c, copies, to) {
|
|
239
|
-
const tape = transport.tape;
|
|
240
|
-
const due = (at) => (c.pace === null ? at : c.pace.reading(at)) <= to;
|
|
241
|
-
const next = tape?.timestampAt(tape.undoDepth());
|
|
242
|
-
c.u = hostAhead(c, to);
|
|
243
|
-
let played = false;
|
|
244
|
-
if (next !== undefined && due(next)) {
|
|
245
|
-
// A call may write what a copy shares with the voice it was copied from.
|
|
246
|
-
for (const copy of c.members)
|
|
247
|
-
for (const v of copy.cued) {
|
|
248
|
-
if (v.parts !== null)
|
|
249
|
-
v.parts = new Map(v.parts);
|
|
250
|
-
if (v.parted !== null)
|
|
251
|
-
v.parted = new Map(v.parted);
|
|
252
|
-
}
|
|
253
|
-
played = true;
|
|
254
|
-
const last = replay(transport, due, c, {
|
|
255
|
-
transport: () => c,
|
|
256
|
-
mix: (of) => copies.get(of),
|
|
257
|
-
});
|
|
258
|
-
c.u = Math.max(last, hostAhead(c, to));
|
|
259
|
-
}
|
|
260
|
-
// A call played at `to` itself moved the copies there before it, as a sync does.
|
|
261
|
-
if (!(c.now >= to))
|
|
262
|
-
for (const copy of c.members)
|
|
263
|
-
move(copy, to);
|
|
264
|
-
return played;
|
|
265
|
-
}
|
|
266
|
-
/** A transport for a read ahead's copies to go on from where those on `c` stand. */
|
|
267
|
-
function restage(c) {
|
|
268
|
-
const next = new Transport(undefined, true);
|
|
269
|
-
next.offset = c.offset;
|
|
270
|
-
next.u = c.u;
|
|
271
|
-
next.nextId = c.nextId;
|
|
272
|
-
next.now = c.now;
|
|
273
|
-
next.pace = c.pace === null ? null : c.pace.until(Number.POSITIVE_INFINITY);
|
|
274
|
-
next.announced = c.announced.map((a) => ({ ...a }));
|
|
275
|
-
// The retargets and pushes the tape made carry their times, so every stop shares one list.
|
|
276
|
-
next.ahead = c.ahead;
|
|
277
|
-
return next;
|
|
278
|
-
}
|
|
279
|
-
/**
|
|
280
|
-
* A mix's copy for a read ahead: its voices as they stand, each subject carried from the live one,
|
|
281
|
-
* or from the copy at the known event before.
|
|
282
|
-
*/
|
|
283
|
-
function copyAhead(mix, c) {
|
|
284
|
-
mirror(mix, c);
|
|
285
|
-
c.cued = mix.cued
|
|
286
|
-
.filter((v) => v.state !== 'done')
|
|
287
|
-
.map((v) => {
|
|
288
|
-
const copy = v.copy((subject) => carry(mix, v, subject, c, copy), undefined, true);
|
|
289
|
-
const at = mix.pins?.get(v);
|
|
290
|
-
if (at !== undefined)
|
|
291
|
-
pin(c, copy, at);
|
|
292
|
-
return copy;
|
|
293
|
-
});
|
|
294
|
-
c.waking = mix.waking;
|
|
295
|
-
ownBlends(c.cued);
|
|
296
|
-
// A copy keeps no list of owners, so the one before says only by its voices.
|
|
297
|
-
if (mix.owners !== null || (mix.projecting && c.cued.some((v) => v.holding !== null)))
|
|
298
|
-
relink(c.cued);
|
|
299
|
-
recount(c);
|
|
300
|
-
}
|
|
301
140
|
/** A mix's copy for a read back to `t`, cut at `cut`: its voices then, from the controls and copies kept. */
|
|
302
141
|
function copyBack(mix, c, t, cut) {
|
|
303
142
|
mirror(mix, c);
|
|
@@ -352,48 +191,6 @@ export function stateAt(c, t) {
|
|
|
352
191
|
? 'frozen'
|
|
353
192
|
: 'live';
|
|
354
193
|
}
|
|
355
|
-
/** Gives a projection's copy what it reads of the live mix as it is, before its voices are copied. */
|
|
356
|
-
export function mirror(mix, c) {
|
|
357
|
-
c.pose = mix.pose;
|
|
358
|
-
c.wantsPose = mix.wantsPose;
|
|
359
|
-
c.reducedNow = mix.reducedNow;
|
|
360
|
-
}
|
|
361
|
-
/** A voice's record of a subject, the one it shares among those it does not reach included. */
|
|
362
|
-
function recordOf(mix, voice, subject) {
|
|
363
|
-
// A voice sharing one record has that for every subject a chain has asked it about, pointed at
|
|
364
|
-
// the subject for the caller to copy from.
|
|
365
|
-
if (voice.sharing) {
|
|
366
|
-
const slot = headOf(mix, subject)?.slot ?? -1;
|
|
367
|
-
if (!sighted(voice, slot))
|
|
368
|
-
return undefined;
|
|
369
|
-
const all = voice.everyone;
|
|
370
|
-
open(voice, all, slot);
|
|
371
|
-
all.weight = weightAt(voice, slot);
|
|
372
|
-
return all;
|
|
373
|
-
}
|
|
374
|
-
const held = voice.subjects.get(subject);
|
|
375
|
-
if (held !== undefined || voice.unreachedBits === null)
|
|
376
|
-
return held;
|
|
377
|
-
return unreached(voice, headOf(mix, subject)?.slot ?? -1)
|
|
378
|
-
? voice.unreached
|
|
379
|
-
: undefined;
|
|
380
|
-
}
|
|
381
|
-
/**
|
|
382
|
-
* A projection ahead starts each subject from where the live mix holds it, its `since` worked out
|
|
383
|
-
* again where it was still ahead, since the copy's clock may have moved it.
|
|
384
|
-
*/
|
|
385
|
-
function carry(mix, voice, subject, c, copy) {
|
|
386
|
-
const live = recordOf(mix, voice, subject);
|
|
387
|
-
if (live === undefined)
|
|
388
|
-
return undefined;
|
|
389
|
-
const h = copyHeld(voice, live);
|
|
390
|
-
if (h.reaches && !(h.since <= mix.now)) {
|
|
391
|
-
h.since = c.sinceOf(copy, h.delay);
|
|
392
|
-
h.shown = c.shownOf(copy, h.since);
|
|
393
|
-
}
|
|
394
|
-
h.from = h.stepped;
|
|
395
|
-
return h;
|
|
396
|
-
}
|
|
397
194
|
/**
|
|
398
195
|
* A projection back starts each subject from the latest copy kept at or before `t`, else from its
|
|
399
196
|
* voice's start with fresh state, which is exact for a voice stepped at a fixed interval.
|
package/dist/pull.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { axesOf } from './channels.js';
|
|
2
2
|
import { copy } from './clone.js';
|
|
3
3
|
import { clampRun, flush, pullRun, writeLater } from './columns.js';
|
|
4
|
+
import { renumber } from './renumber.js';
|
|
4
5
|
/** What `pull` remembers at a position it has read no subject at. */
|
|
5
6
|
const UNREAD = Symbol('unread');
|
|
6
7
|
/** Writes a folded pose's value into subject `n`'s places in a column. */
|
|
@@ -18,12 +19,10 @@ function writeValue(c, v, n) {
|
|
|
18
19
|
}
|
|
19
20
|
/** After a probe, keeps the pose a retarget from `'current'` reads as the subject's last. */
|
|
20
21
|
export function keep(subject, pose, out) {
|
|
21
|
-
//
|
|
22
|
-
// object only pays for the copy once something
|
|
22
|
+
// A pose handed back without `out` is the host's to edit, so it is copied too. A host sampling
|
|
23
|
+
// into its own object only pays for the copy once something has asked to retarget from it.
|
|
23
24
|
let kept;
|
|
24
|
-
if (out === undefined)
|
|
25
|
-
kept = pose;
|
|
26
|
-
else if (this.wantsPose) {
|
|
25
|
+
if (out === undefined || this.wantsPose) {
|
|
27
26
|
kept = {};
|
|
28
27
|
for (const key of this.names) {
|
|
29
28
|
const v = pose[key];
|
|
@@ -32,9 +31,14 @@ export function keep(subject, pose, out) {
|
|
|
32
31
|
}
|
|
33
32
|
}
|
|
34
33
|
if (kept !== undefined) {
|
|
35
|
-
const rec = this.
|
|
34
|
+
const rec = this.lastPoses.get(subject);
|
|
36
35
|
if (rec === undefined)
|
|
37
|
-
this.
|
|
36
|
+
this.lastPoses.set(subject, {
|
|
37
|
+
pose: kept,
|
|
38
|
+
at: this.now,
|
|
39
|
+
prev: undefined,
|
|
40
|
+
prevAt: Number.NaN,
|
|
41
|
+
});
|
|
38
42
|
else if (rec.at === this.now)
|
|
39
43
|
rec.pose = kept;
|
|
40
44
|
else {
|
|
@@ -50,7 +54,7 @@ export function pull(mix, subjects, into) {
|
|
|
50
54
|
const lanes = mix.lanes;
|
|
51
55
|
const now = mix.now;
|
|
52
56
|
if (mix.scratch === undefined)
|
|
53
|
-
mix.scratch =
|
|
57
|
+
mix.scratch = mix.pose();
|
|
54
58
|
let room = Number.POSITIVE_INFINITY;
|
|
55
59
|
let tightest = '';
|
|
56
60
|
for (const c of columns) {
|
|
@@ -149,12 +153,21 @@ function readAll(mix, list, columns, live, current, since, whole) {
|
|
|
149
153
|
was[n] = subject;
|
|
150
154
|
heads[n] = head;
|
|
151
155
|
slot = head.slot;
|
|
156
|
+
// Numbered out of the list's order, as swap-remove leaves a subject: renumbered into it.
|
|
157
|
+
const to = n === 0 ? 0 : slots[n - 1] + 1;
|
|
158
|
+
if (runs && slot >= 0 && slot !== to && to >= 0) {
|
|
159
|
+
if (renumber(mix, subject, head, to)) {
|
|
160
|
+
for (let j = was.indexOf(subject); j >= 0; j = was.indexOf(subject, j + 1))
|
|
161
|
+
slots[j] = to;
|
|
162
|
+
slot = to;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
152
165
|
slots[n] = slot;
|
|
153
166
|
}
|
|
154
167
|
}
|
|
155
168
|
const laned = live && lanes?.prepare(slot, subject, mix.version, heads[n] ?? null) === true;
|
|
156
169
|
if (laned && whole && lanes.whole && !lanes.owes(slot)) {
|
|
157
|
-
writeLater(lanes, slot, columns, n);
|
|
170
|
+
writeLater(lanes, slot, columns, n, slots);
|
|
158
171
|
continue;
|
|
159
172
|
}
|
|
160
173
|
const head = live ? heads[n] : null;
|
package/dist/qualify.js
CHANGED
|
@@ -5,8 +5,10 @@ import { byId } from './due.js';
|
|
|
5
5
|
import { weighAt } from './everyone.js';
|
|
6
6
|
import { wake } from './fill.js';
|
|
7
7
|
import { Lane } from './lane.js';
|
|
8
|
+
import { reach } from './meet.js';
|
|
8
9
|
import { bury, compactSparse, join, regroup, retouchRow, sameCrowds } from './rows.js';
|
|
9
10
|
import { settle } from './sample.js';
|
|
11
|
+
import { seated, seatOf } from './seat.js';
|
|
10
12
|
/**
|
|
11
13
|
* Which channels run as lanes and which voices run on them: the fixed point where every voice
|
|
12
14
|
* writing a laned channel fits and writes only laned channels. Only channels some laned voice
|
|
@@ -57,21 +59,21 @@ export function requalify(lanes, version) {
|
|
|
57
59
|
keepCallsOff(present, fits, host.channels);
|
|
58
60
|
const candidates = () => present.map((v, i) => ({ id: v.id, fits: fits[i], slots: v.slots }));
|
|
59
61
|
let { channels, voices } = qualify(numeric, candidates());
|
|
60
|
-
// A
|
|
62
|
+
// A role folds its members together, so they run on lanes all together or not at all.
|
|
61
63
|
for (let split = true; split;) {
|
|
62
64
|
split = false;
|
|
63
|
-
const
|
|
65
|
+
const roles = new Map();
|
|
64
66
|
present.forEach((v, i) => {
|
|
65
|
-
const name = v.spec.
|
|
67
|
+
const name = v.spec.role;
|
|
66
68
|
if (name === undefined)
|
|
67
69
|
return;
|
|
68
|
-
const at =
|
|
70
|
+
const at = roles.get(name);
|
|
69
71
|
if (at === undefined)
|
|
70
|
-
|
|
72
|
+
roles.set(name, [i]);
|
|
71
73
|
else
|
|
72
74
|
at.push(i);
|
|
73
75
|
});
|
|
74
|
-
for (const at of
|
|
76
|
+
for (const at of roles.values()) {
|
|
75
77
|
const on = at.filter((i) => voices.has(present[i].id)).length;
|
|
76
78
|
if (on === 0 || on === at.length)
|
|
77
79
|
continue;
|
|
@@ -97,18 +99,18 @@ export function requalify(lanes, version) {
|
|
|
97
99
|
const lane = lanes.byId.get(v.id) ?? new Lane(v);
|
|
98
100
|
v.laned = true;
|
|
99
101
|
if (lane.epoch === 0 && v.state !== 'pending')
|
|
100
|
-
|
|
102
|
+
opened(lanes, lane);
|
|
101
103
|
kept.push(lane);
|
|
102
104
|
}
|
|
103
105
|
lanes.lanes = kept;
|
|
104
|
-
lanes.
|
|
105
|
-
const
|
|
106
|
+
lanes.roles = [];
|
|
107
|
+
const roles = new Map();
|
|
106
108
|
for (const lane of kept) {
|
|
107
109
|
lane.group = null;
|
|
108
|
-
const name = lane.voice.spec.
|
|
110
|
+
const name = lane.voice.spec.role;
|
|
109
111
|
if (name === undefined)
|
|
110
112
|
continue;
|
|
111
|
-
let g =
|
|
113
|
+
let g = roles.get(name);
|
|
112
114
|
if (g === undefined) {
|
|
113
115
|
g = {
|
|
114
116
|
members: [],
|
|
@@ -117,13 +119,17 @@ export function requalify(lanes, version) {
|
|
|
117
119
|
sum: new Float64Array(lanes.cap),
|
|
118
120
|
values: [],
|
|
119
121
|
taken: [],
|
|
122
|
+
contiguous: false,
|
|
123
|
+
inline: false,
|
|
120
124
|
};
|
|
121
|
-
|
|
122
|
-
lanes.
|
|
125
|
+
roles.set(name, g);
|
|
126
|
+
lanes.roles.push(g);
|
|
123
127
|
}
|
|
124
128
|
g.members.push(lane);
|
|
125
129
|
lane.group = g;
|
|
126
130
|
}
|
|
131
|
+
for (const [name, g] of roles)
|
|
132
|
+
g.contiguous = contiguous(present, voices, name, g.members);
|
|
127
133
|
lanes.dense = kept
|
|
128
134
|
.filter((l) => l.voice.named === null && l.epoch > 0)
|
|
129
135
|
.sort((a, b) => a.epoch - b.epoch);
|
|
@@ -145,22 +151,22 @@ export function requalify(lanes, version) {
|
|
|
145
151
|
for (const c of lanes.crowds)
|
|
146
152
|
for (let p = 0; p < c.size; p++) {
|
|
147
153
|
const v = c.voices[p];
|
|
148
|
-
if (
|
|
154
|
+
if (seated(lanes, v, c, p) && v.state === 'done')
|
|
149
155
|
bury(lanes, c, p);
|
|
150
156
|
}
|
|
151
157
|
// A crowd voice that started or began fading takes it up on its row, as `retouch` does.
|
|
152
158
|
for (const v of touched) {
|
|
153
159
|
if (v.state === 'done')
|
|
154
160
|
continue;
|
|
155
|
-
const
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
retouchRow(lanes, c, p, v);
|
|
161
|
+
const s = seatOf(lanes, v);
|
|
162
|
+
if (s !== null)
|
|
163
|
+
retouchRow(lanes, s.crowd, s.row, v);
|
|
159
164
|
}
|
|
160
165
|
compactSparse(lanes);
|
|
161
166
|
return;
|
|
162
167
|
}
|
|
163
168
|
lanes.laned = [];
|
|
169
|
+
lanes.copier = null;
|
|
164
170
|
lanes.bySlot = host.channels.map(() => undefined);
|
|
165
171
|
channels.forEach((on, slot) => {
|
|
166
172
|
if (!on)
|
|
@@ -170,10 +176,12 @@ export function requalify(lanes, version) {
|
|
|
170
176
|
// A rest-less channel's values are NaN until some voice passes the band: absent from the pose.
|
|
171
177
|
const rest = (channel.rest ?? Number.NaN);
|
|
172
178
|
const scalar = typeof rest === 'number' && n.axes === 1;
|
|
179
|
+
const op = n.folds ?? n.op;
|
|
173
180
|
const ch = {
|
|
174
181
|
name: host.names[slot],
|
|
175
|
-
op
|
|
176
|
-
plain:
|
|
182
|
+
op,
|
|
183
|
+
plain: op !== 'last' && op !== 'own' && n.turn === undefined,
|
|
184
|
+
straight: n.folds === undefined,
|
|
177
185
|
turn: n.turn ?? 0,
|
|
178
186
|
channel,
|
|
179
187
|
scalar,
|
|
@@ -263,7 +271,7 @@ export function open(lanes, v) {
|
|
|
263
271
|
lanes.wide = epoch;
|
|
264
272
|
return epoch;
|
|
265
273
|
}
|
|
266
|
-
for (const subject of v.named) {
|
|
274
|
+
for (const subject of v.named.list) {
|
|
267
275
|
const slot = lanes.host.slotOf(subject);
|
|
268
276
|
if (slot < 0 || slot >= lanes.cap)
|
|
269
277
|
continue;
|
|
@@ -298,14 +306,13 @@ export function retouch(lanes) {
|
|
|
298
306
|
const known = v.id <= lanes.known;
|
|
299
307
|
if (v.id > lanes.known)
|
|
300
308
|
lanes.known = v.id;
|
|
301
|
-
const
|
|
302
|
-
|
|
303
|
-
if (c !== undefined && p !== undefined) {
|
|
309
|
+
const s = seatOf(lanes, v);
|
|
310
|
+
if (s !== null) {
|
|
304
311
|
if (v.state === 'done') {
|
|
305
|
-
bury(lanes,
|
|
312
|
+
bury(lanes, s.crowd, s.row);
|
|
306
313
|
continue;
|
|
307
314
|
}
|
|
308
|
-
retouchRow(lanes,
|
|
315
|
+
retouchRow(lanes, s.crowd, s.row, v);
|
|
309
316
|
continue;
|
|
310
317
|
}
|
|
311
318
|
if (v.state === 'done' && !known)
|
|
@@ -318,8 +325,8 @@ export function retouch(lanes) {
|
|
|
318
325
|
}
|
|
319
326
|
if (v.state === 'done')
|
|
320
327
|
return qualify();
|
|
321
|
-
// A
|
|
322
|
-
if (v.spec.
|
|
328
|
+
// A role's members are on lanes together or not at all, so one joining may move the others.
|
|
329
|
+
if (v.spec.role !== undefined)
|
|
323
330
|
return qualify();
|
|
324
331
|
const laned = v.slots.some((slot) => lanes.bySlot[slot] !== undefined);
|
|
325
332
|
const fits = v.slots.length > 0 && lanes.host.fits(v);
|
|
@@ -341,6 +348,46 @@ export function retouch(lanes) {
|
|
|
341
348
|
lanes.subjects = [];
|
|
342
349
|
return true;
|
|
343
350
|
}
|
|
351
|
+
/** A lane's voice started playing: its epoch, and room up front where it reaches every subject. */
|
|
352
|
+
function opened(lanes, lane) {
|
|
353
|
+
const v = lane.voice;
|
|
354
|
+
lane.epoch = open(lanes, v);
|
|
355
|
+
if (v.named === null && v.spec.target === undefined) {
|
|
356
|
+
lane.reserve(lanes.live, lanes.cap);
|
|
357
|
+
if (placesAll(lanes, lane))
|
|
358
|
+
placeAll(lanes, lane);
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
/**
|
|
362
|
+
* Whether a lane just opened can take every numbered subject now, its first call or read for each
|
|
363
|
+
* made by the fill: a stateless fn or keys voice sharing one record, which every subject would meet
|
|
364
|
+
* at its next probe, in a host that probed every subject last frame. One by one, each probe would
|
|
365
|
+
* meet it and make its first call or read there (`foldNow` in `meet.ts`).
|
|
366
|
+
*/
|
|
367
|
+
function placesAll(lanes, lane) {
|
|
368
|
+
const v = lane.voice;
|
|
369
|
+
return (v.sharing &&
|
|
370
|
+
v.everyone !== null &&
|
|
371
|
+
lane.motion === undefined &&
|
|
372
|
+
!v.keeping &&
|
|
373
|
+
typeof v.spec.weight !== 'function' &&
|
|
374
|
+
lane.list.length === 0 &&
|
|
375
|
+
lanes.lastDistinct >= lanes.live);
|
|
376
|
+
}
|
|
377
|
+
/** Gives every numbered subject a position on a lane `placesAll` passed. */
|
|
378
|
+
function placeAll(lanes, lane) {
|
|
379
|
+
const rec = lane.voice.everyone;
|
|
380
|
+
const numbers = lanes.numbers;
|
|
381
|
+
const size = Math.min(numbers.size, lanes.cap);
|
|
382
|
+
for (let slot = 0; slot < size; slot++) {
|
|
383
|
+
if (!numbers.has(slot))
|
|
384
|
+
continue;
|
|
385
|
+
lane.add(slot, rec);
|
|
386
|
+
if (lane.idle)
|
|
387
|
+
reach(lanes, slot, 1);
|
|
388
|
+
}
|
|
389
|
+
lane.every = true;
|
|
390
|
+
}
|
|
344
391
|
/**
|
|
345
392
|
* A new voice that fits and writes only laned channels, and that no crowd takes, gets a lane at
|
|
346
393
|
* the end of the order, its id being past every other: what a qualify would give it, since such a
|
|
@@ -350,7 +397,7 @@ function enlane(lanes, v) {
|
|
|
350
397
|
const lane = new Lane(v);
|
|
351
398
|
v.laned = true;
|
|
352
399
|
if (v.state !== 'pending')
|
|
353
|
-
|
|
400
|
+
opened(lanes, lane);
|
|
354
401
|
lane.chans = v.slots.map((s) => lanes.bySlot[s]);
|
|
355
402
|
lane.values = lane.chans.map(() => undefined);
|
|
356
403
|
lanes.lanes.push(lane);
|
|
@@ -360,13 +407,13 @@ function enlane(lanes, v) {
|
|
|
360
407
|
}
|
|
361
408
|
/**
|
|
362
409
|
* A laned voice that started playing or finished, as a qualify would take it: a lane leaving
|
|
363
|
-
* cannot lane or unlane another channel. False for a
|
|
410
|
+
* cannot lane or unlane another channel. False for a role member, which a qualify must take.
|
|
364
411
|
*/
|
|
365
412
|
function relane(lanes, lane) {
|
|
366
413
|
const v = lane.voice;
|
|
367
414
|
if (v.state !== 'done') {
|
|
368
415
|
if (lane.epoch === 0 && v.state !== 'pending') {
|
|
369
|
-
|
|
416
|
+
opened(lanes, lane);
|
|
370
417
|
if (v.named === null)
|
|
371
418
|
lanes.dense.push(lane);
|
|
372
419
|
}
|
|
@@ -410,3 +457,18 @@ function leave(lanes, lane) {
|
|
|
410
457
|
}
|
|
411
458
|
lane.voice.laned = false;
|
|
412
459
|
}
|
|
460
|
+
/**
|
|
461
|
+
* Whether a role's members, in the order lanes run, have no laned or crowded voice of another
|
|
462
|
+
* role or none between them, so its every subject folds at its first member's turn.
|
|
463
|
+
*/
|
|
464
|
+
function contiguous(present, voices, name, members) {
|
|
465
|
+
for (let i = 1; i < members.length; i++)
|
|
466
|
+
if (members[i].voice.id <= members[i - 1].voice.id)
|
|
467
|
+
return false;
|
|
468
|
+
const lo = members[0].voice.id;
|
|
469
|
+
const hi = members[members.length - 1].voice.id;
|
|
470
|
+
for (const v of present)
|
|
471
|
+
if (v.id > lo && v.id < hi && voices.has(v.id) && v.spec.role !== name)
|
|
472
|
+
return false;
|
|
473
|
+
return true;
|
|
474
|
+
}
|
package/dist/reading.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { Cut } from './cut.js';
|
|
1
2
|
/**
|
|
2
3
|
* Internal: what kind of read the mix is making right now, for patches that keep state of their own
|
|
3
4
|
* outside the mix. Not exported from the package.
|
|
@@ -32,4 +33,9 @@ export declare const reading: {
|
|
|
32
33
|
* commits none. Null for every other read.
|
|
33
34
|
*/
|
|
34
35
|
ahead: Map<object, Map<unknown, unknown[]>> | null;
|
|
36
|
+
/**
|
|
37
|
+
* While a read back under history reads: the frame it reads, so a motion patch reads the run a
|
|
38
|
+
* subject had then and only the changes made by then. Null for every other read.
|
|
39
|
+
*/
|
|
40
|
+
back: Cut | null;
|
|
35
41
|
};
|
package/dist/reading.js
CHANGED
|
@@ -32,4 +32,9 @@ export const reading = {
|
|
|
32
32
|
* commits none. Null for every other read.
|
|
33
33
|
*/
|
|
34
34
|
ahead: null,
|
|
35
|
+
/**
|
|
36
|
+
* While a read back under history reads: the frame it reads, so a motion patch reads the run a
|
|
37
|
+
* subject had then and only the changes made by then. Null for every other read.
|
|
38
|
+
*/
|
|
39
|
+
back: null,
|
|
35
40
|
};
|
package/dist/record.d.ts
CHANGED
|
@@ -1,4 +1,8 @@
|
|
|
1
|
-
import type { Subject } from './
|
|
1
|
+
import type { Subject } from './subject.js';
|
|
2
|
+
/** Whether a record was left at rest during a handover. */
|
|
3
|
+
export declare const isRested: (h: {
|
|
4
|
+
restedAt: number;
|
|
5
|
+
}) => boolean;
|
|
2
6
|
/** A record with every field written out, optional ones too, as every record in a mix is built. */
|
|
3
7
|
export type WholeRecord = {
|
|
4
8
|
[K in Field]: Subject<unknown>[K];
|
package/dist/record.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
/** Whether a record was left at rest during a handover. */
|
|
2
|
+
export const isRested = (h) => !Number.isNaN(h.restedAt);
|
|
1
3
|
/**
|
|
2
4
|
* A subject's record for `voice` holding every field one can come to hold, in one order, so every
|
|
3
5
|
* record in a mix shares one hidden class: a record built elsewhere, or given `base`, `snaps` or
|
|
@@ -12,7 +14,6 @@ export function record(voice, reaches, state) {
|
|
|
12
14
|
since: 0,
|
|
13
15
|
shown: 0,
|
|
14
16
|
weight: 0,
|
|
15
|
-
rested: false,
|
|
16
17
|
restedAt: Number.NaN,
|
|
17
18
|
bands: null,
|
|
18
19
|
state,
|
|
@@ -37,7 +38,7 @@ export function record(voice, reaches, state) {
|
|
|
37
38
|
voice,
|
|
38
39
|
next: null,
|
|
39
40
|
version: Number.NaN,
|
|
40
|
-
|
|
41
|
+
roles: null,
|
|
41
42
|
slot: -1,
|
|
42
43
|
run: -1,
|
|
43
44
|
freed: -1,
|
package/dist/relink.d.ts
CHANGED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { Mixer } from './mixer.js';
|
|
2
|
+
import type { Subject } from './subject.js';
|
|
3
|
+
/**
|
|
4
|
+
* Gives a subject the free number `to` in place of its own, carrying everything kept under the old
|
|
5
|
+
* one, so `pull` can keep a list's numbers in its order: a list kept dense by swap-remove moves its
|
|
6
|
+
* last subject into the place a dropped one left, whose number the drop just freed. Says whether it
|
|
7
|
+
* did; it does not where some store it would have to carry is in use: while a fill runs, under
|
|
8
|
+
* history, in a read ahead, or with roles, a bound or anything owed on the lanes.
|
|
9
|
+
*/
|
|
10
|
+
export declare function renumber<I, O>(mix: Mixer<I, O>, subject: I, head: Subject<unknown>, to: number): boolean;
|