@msb235/blits 0.10.0 → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +151 -0
- package/README.md +15 -4
- package/dist/blend.js +1 -5
- package/dist/book.d.ts +2 -0
- package/dist/book.js +27 -3
- package/dist/chain.d.ts +11 -0
- package/dist/chain.js +24 -2
- package/dist/clock.d.ts +14 -0
- package/dist/clock.js +9 -1
- package/dist/columns.d.ts +4 -5
- package/dist/columns.js +11 -5
- package/dist/crowd.d.ts +1 -1
- package/dist/crowd.js +4 -1
- package/dist/cue.js +52 -5
- package/dist/departed.d.ts +3 -2
- package/dist/departed.js +7 -4
- package/dist/due.d.ts +4 -0
- package/dist/due.js +16 -3
- package/dist/fade.d.ts +12 -5
- package/dist/fade.js +55 -17
- package/dist/fold.d.ts +1 -1
- package/dist/fold.js +53 -6
- package/dist/frames.d.ts +2 -0
- package/dist/frames.js +17 -0
- package/dist/handle.d.ts +5 -2
- package/dist/handle.js +20 -12
- package/dist/held.d.ts +8 -0
- package/dist/held.js +37 -6
- package/dist/history.d.ts +6 -0
- package/dist/history.js +15 -5
- package/dist/hosts.js +18 -6
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -1
- package/dist/keyfill.js +3 -1
- package/dist/lane.d.ts +1 -1
- package/dist/lane.js +3 -1
- package/dist/lanes.d.ts +8 -0
- package/dist/lanes.js +21 -4
- package/dist/lean.d.ts +15 -0
- package/dist/lean.js +23 -0
- package/dist/leavings.d.ts +6 -1
- package/dist/leavings.js +16 -2
- package/dist/marks.d.ts +7 -3
- package/dist/marks.js +102 -27
- package/dist/meet.js +1 -0
- package/dist/mixer.d.ts +21 -6
- package/dist/mixer.js +70 -29
- package/dist/motion.d.ts +4 -1
- package/dist/motion.js +16 -2
- package/dist/motions.d.ts +4 -0
- package/dist/motions.js +45 -11
- package/dist/move.js +28 -10
- package/dist/owner.d.ts +18 -2
- package/dist/owner.js +38 -2
- package/dist/pack.d.ts +1 -0
- package/dist/pack.js +3 -0
- package/dist/placed.d.ts +19 -0
- package/dist/placed.js +54 -0
- package/dist/project.d.ts +5 -2
- package/dist/project.js +236 -32
- package/dist/pull.js +37 -8
- package/dist/qualify.js +8 -2
- package/dist/reading.d.ts +6 -0
- package/dist/reading.js +6 -0
- package/dist/record.js +1 -0
- package/dist/restored.d.ts +7 -4
- package/dist/restored.js +13 -10
- package/dist/revive.d.ts +5 -3
- package/dist/revive.js +10 -3
- package/dist/rows.js +15 -5
- package/dist/seek.d.ts +7 -2
- package/dist/seek.js +90 -35
- package/dist/shared.d.ts +5 -0
- package/dist/shared.js +5 -0
- package/dist/signals.d.ts +10 -0
- package/dist/signals.js +17 -0
- package/dist/store.d.ts +11 -0
- package/dist/store.js +19 -0
- package/dist/strays.js +50 -2
- package/dist/tape.d.ts +29 -4
- package/dist/tape.js +49 -11
- package/dist/transport.d.ts +11 -2
- package/dist/transport.js +20 -9
- package/dist/types/mix.d.ts +39 -14
- package/dist/types/patch.d.ts +5 -1
- package/dist/types/place.d.ts +11 -4
- package/dist/types/transport.d.ts +12 -3
- package/dist/types/voice.d.ts +62 -4
- package/dist/unpage.d.ts +6 -1
- package/dist/unpage.js +34 -2
- package/dist/voice.d.ts +57 -11
- package/dist/voice.js +43 -3
- package/docs/schema.html +68 -36
- package/package.json +7 -2
package/dist/history.js
CHANGED
|
@@ -93,6 +93,11 @@ function older(list, reach) {
|
|
|
93
93
|
n++;
|
|
94
94
|
return n === 0 ? [] : list.splice(0, n);
|
|
95
95
|
}
|
|
96
|
+
/** A copy of the state a voice's patch keeps for a subject. */
|
|
97
|
+
export function copyState(voice, state) {
|
|
98
|
+
const patch = voice.patch;
|
|
99
|
+
return patch.clone ? patch.clone(state) : clone(state);
|
|
100
|
+
}
|
|
96
101
|
/** A copy of a subject's record that shares nothing a read could change. */
|
|
97
102
|
export function copyHeld(voice, h) {
|
|
98
103
|
let kept = null;
|
|
@@ -101,11 +106,10 @@ export function copyHeld(voice, h) {
|
|
|
101
106
|
for (const [owner, value] of h.kept)
|
|
102
107
|
kept.set(owner, clone(value));
|
|
103
108
|
}
|
|
104
|
-
const patch = voice.patch;
|
|
105
109
|
return {
|
|
106
110
|
...h,
|
|
107
111
|
bands: h.bands === null ? null : h.bands.slice(),
|
|
108
|
-
state: h.state === undefined ? undefined :
|
|
112
|
+
state: h.state === undefined ? undefined : copyState(voice, h.state),
|
|
109
113
|
kept,
|
|
110
114
|
unkept: h.unkept?.map(clone),
|
|
111
115
|
probed: Number.NaN,
|
|
@@ -160,6 +164,10 @@ export function record(mix, voice, subject, held, value) {
|
|
|
160
164
|
if (out.length > 0)
|
|
161
165
|
pageOut(mix, 'input', voice.id, mix.keys?.key(subject), out, (e) => e.value, reach);
|
|
162
166
|
}
|
|
167
|
+
/** How far apart, in mix ms, a record's copies are kept. */
|
|
168
|
+
export function apart(history) {
|
|
169
|
+
return history.every ?? 200;
|
|
170
|
+
}
|
|
163
171
|
/** Under `history`, keeps a copy of a stateful voice's record for this subject every so often. */
|
|
164
172
|
export function remember(mix, voice, subject, held) {
|
|
165
173
|
const history = mix.opts.history;
|
|
@@ -172,7 +180,7 @@ export function remember(mix, voice, subject, held) {
|
|
|
172
180
|
const snaps = held.snaps ?? [];
|
|
173
181
|
held.snaps = snaps;
|
|
174
182
|
const prev = snaps[snaps.length - 1];
|
|
175
|
-
if (prev !== undefined && mix.now - prev.at < (history
|
|
183
|
+
if (prev !== undefined && mix.now - prev.at < apart(history))
|
|
176
184
|
return;
|
|
177
185
|
snaps.push({ at: mix.now, seq: mix.transport.seq, held: copyHeld(voice, held) });
|
|
178
186
|
const reach = mix.now - history.ms;
|
|
@@ -194,13 +202,15 @@ export function leave(mix, voice, subject, held) {
|
|
|
194
202
|
const keys = mix.keys;
|
|
195
203
|
left.expire(reach, keys === null
|
|
196
204
|
? null
|
|
197
|
-
: (e) => pageOut(mix, 'left', voice.id, keys.key(e.subject), [e], (x) => [packHeld(voice, x.held), x.sync], reach));
|
|
205
|
+
: (e) => pageOut(mix, 'left', voice.id, keys.key(e.subject), [e], (x) => [packHeld(voice, x.held), x.sync, x.ramp, x.parted], reach));
|
|
198
206
|
left.push({
|
|
199
207
|
subject,
|
|
200
208
|
at: Number.isNaN(mix.now) ? Number.NEGATIVE_INFINITY : mix.now,
|
|
201
209
|
seq: mix.transport.seq,
|
|
202
|
-
sync: mix.syncing,
|
|
210
|
+
sync: mix.syncing || !mix.looked,
|
|
203
211
|
held,
|
|
212
|
+
ramp: voice.parts?.get(subject) ?? null,
|
|
213
|
+
parted: voice.parted?.get(subject) ?? null,
|
|
204
214
|
});
|
|
205
215
|
}
|
|
206
216
|
/**
|
package/dist/hosts.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Book } from './book.js';
|
|
2
2
|
import { numericOf } from './channels.js';
|
|
3
|
+
import { stepOf } from './clock.js';
|
|
3
4
|
import { crowdable } from './crowd.js';
|
|
4
5
|
import { schedule } from './due.js';
|
|
5
6
|
import { unshare, unsight, weightAt } from './everyone.js';
|
|
@@ -14,6 +15,7 @@ import { descendants, ownWeight, signalled } from './owner.js';
|
|
|
14
15
|
import { localNow } from './place.js';
|
|
15
16
|
import { reading } from './reading.js';
|
|
16
17
|
import { heard } from './signals.js';
|
|
18
|
+
import { cuedById } from './strays.js';
|
|
17
19
|
import { record } from './tape.js';
|
|
18
20
|
import { unreach } from './unreached.js';
|
|
19
21
|
export function book(mix, opts) {
|
|
@@ -161,8 +163,7 @@ function seekDoubt(mix, voice, rebuild) {
|
|
|
161
163
|
return 'stepped';
|
|
162
164
|
if (patch.step === undefined)
|
|
163
165
|
return 'exact';
|
|
164
|
-
const
|
|
165
|
-
const fixed = tick !== undefined && tick > 0 && !mix.reducedNow && mix.opts.maxDt === undefined;
|
|
166
|
+
const fixed = stepOf(mix, voice) !== undefined && mix.opts.maxDt === undefined;
|
|
166
167
|
return fixed ? 'exact' : 'stepped';
|
|
167
168
|
}
|
|
168
169
|
/** A voice's patch kept state on a record: it is stateful from now on. */
|
|
@@ -234,10 +235,21 @@ function handleHost(mix) {
|
|
|
234
235
|
if (w !== undefined)
|
|
235
236
|
return w;
|
|
236
237
|
}
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
238
|
+
const held = voice.sharing
|
|
239
|
+
? undefined
|
|
240
|
+
: voice.subjects.get(subject);
|
|
241
|
+
// A voice that stopped sharing makes a subject's record at its next read, from what it kept.
|
|
242
|
+
return held !== undefined ? held.weight : weightAt(voice, slot);
|
|
240
243
|
},
|
|
241
|
-
record: (label, again) => record(mix, label,
|
|
244
|
+
record: (h, label, again) => record(mix, label, (stage) => {
|
|
245
|
+
const to = stage.mix(mix);
|
|
246
|
+
if (to === mix)
|
|
247
|
+
again(h);
|
|
248
|
+
else if (to !== undefined) {
|
|
249
|
+
const voice = cuedById(to, h.id);
|
|
250
|
+
if (voice !== undefined)
|
|
251
|
+
again(handle(to, voice));
|
|
252
|
+
}
|
|
253
|
+
}),
|
|
242
254
|
};
|
|
243
255
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -12,11 +12,11 @@ export type { KeysOptions, PatchOptions } from './patch.js';
|
|
|
12
12
|
export { keys, patch } from './patch.js';
|
|
13
13
|
export type { AngleOptions } from './rotation.js';
|
|
14
14
|
export { angle, quat } from './rotation.js';
|
|
15
|
-
export { gate, input, lag, level, peak, slew } from './signals.js';
|
|
15
|
+
export { gate, input, lag, level, peak, share, slew } from './signals.js';
|
|
16
16
|
export { fold } from './stack.js';
|
|
17
17
|
export type { Ticked, Ticker, TickerOptions } from './ticker.js';
|
|
18
18
|
export { ticker } from './ticker.js';
|
|
19
19
|
export { transport } from './transport.js';
|
|
20
|
-
export type { Anchor, BookedHit, Booker, BookOptions, Channel, Columns, Described, Doubt, Easing, Engine, FadeOptions, FadeSpec, FitResult, Handle, HistoryOptions, HistoryStore, Hit, Keyframe, Kit, Mark, Marked, Mix, MixOptions, MotionSpec, OwnerSpec, Paged, PagedStream, Patch, Placement, Projection, Query, SeekOptions, Sent, Setting, Signal, SpanHandle, SpanHints, SpanSpec, Tape, TapeBranch, TapeMaker, TapeOp, Transport, TransportOptions, TransportProjection, VoiceSpec, } from './types.js';
|
|
20
|
+
export type { Anchor, BlendMember, BookedHit, Booker, BookOptions, Channel, Columns, Described, Doubt, Easing, Engine, FadeOptions, FadeSpec, FitResult, Handle, HistoryOptions, HistoryStore, Hit, Keyframe, Kit, Mark, Marked, MarkName, Mix, MixOptions, MotionSpec, OwnerSpec, Paged, PagedStream, Patch, Placement, Projection, Query, SeekOptions, Sent, Setting, Signal, SpanHandle, SpanHints, SpanSpec, Tape, TapeBranch, TapeMaker, TapeOp, Transport, TransportOptions, TransportProjection, VoiceSpec, } from './types.js';
|
|
21
21
|
export type { WaveOptions, WaveShape } from './wave.js';
|
|
22
22
|
export { wave, waveAt } from './wave.js';
|
package/dist/index.js
CHANGED
|
@@ -6,7 +6,7 @@ export { glide, spring, tween } from './motion.js';
|
|
|
6
6
|
export { HistoryMiss } from './paging.js';
|
|
7
7
|
export { keys, patch } from './patch.js';
|
|
8
8
|
export { angle, quat } from './rotation.js';
|
|
9
|
-
export { gate, input, lag, level, peak, slew } from './signals.js';
|
|
9
|
+
export { gate, input, lag, level, peak, share, slew } from './signals.js';
|
|
10
10
|
export { fold } from './stack.js';
|
|
11
11
|
export { ticker } from './ticker.js';
|
|
12
12
|
export { transport } from './transport.js';
|
package/dist/keyfill.js
CHANGED
|
@@ -23,6 +23,7 @@ export function runKeys(lanes, lane) {
|
|
|
23
23
|
const before = voice.freezesBefore;
|
|
24
24
|
const after = voice.freezesAfter;
|
|
25
25
|
const span = voice.span;
|
|
26
|
+
const hides = voice.hidesPast;
|
|
26
27
|
const flat = lane.flat;
|
|
27
28
|
const parts = voice.parts !== null;
|
|
28
29
|
const whole = clampWeight(voice.weight);
|
|
@@ -42,7 +43,8 @@ export function runKeys(lanes, lane) {
|
|
|
42
43
|
data[o + 3 /* Row.PROBED */] = data[o + 2 /* Row.WEIGHT */];
|
|
43
44
|
if (lately >= 0 && lanes.unread(slot))
|
|
44
45
|
continue;
|
|
45
|
-
const
|
|
46
|
+
const raw = elapsedNow - data[o + 0 /* Row.DELAY */];
|
|
47
|
+
const elapsed = raw > span && hides ? Number.NaN : frozenTime(raw, before, after, span);
|
|
46
48
|
if (!(elapsed >= 0)) {
|
|
47
49
|
data[o + 2 /* Row.WEIGHT */] = 0;
|
|
48
50
|
unband(records[p]);
|
package/dist/lane.d.ts
CHANGED
|
@@ -235,6 +235,6 @@ export declare class Lane<I, O> implements Positions<I, O> {
|
|
|
235
235
|
}
|
|
236
236
|
/**
|
|
237
237
|
* A subject's voice time once its voice's freezes apply: frozen at 0 before it starts and at the end
|
|
238
|
-
* of its passes after, and NaN where it shows nothing.
|
|
238
|
+
* of its passes after, and NaN where it shows nothing, as past its end running back.
|
|
239
239
|
*/
|
|
240
240
|
export declare function frozenAt<I, O>(voice: Voice<I, O>, elapsed: number): number;
|
package/dist/lane.js
CHANGED
|
@@ -185,8 +185,10 @@ export class Lane {
|
|
|
185
185
|
}
|
|
186
186
|
/**
|
|
187
187
|
* A subject's voice time once its voice's freezes apply: frozen at 0 before it starts and at the end
|
|
188
|
-
* of its passes after, and NaN where it shows nothing.
|
|
188
|
+
* of its passes after, and NaN where it shows nothing, as past its end running back.
|
|
189
189
|
*/
|
|
190
190
|
export function frozenAt(voice, elapsed) {
|
|
191
|
+
if (elapsed > voice.span && voice.hidesPast)
|
|
192
|
+
return Number.NaN;
|
|
191
193
|
return frozenTime(elapsed, voice.freezesBefore, voice.freezesAfter, voice.span);
|
|
192
194
|
}
|
package/dist/lanes.d.ts
CHANGED
|
@@ -7,6 +7,7 @@ import { type Lane, type Laned, type Locus } from './lane.js';
|
|
|
7
7
|
import type { Watcher } from './motions.js';
|
|
8
8
|
import { absent, Numbers } from './numbers.js';
|
|
9
9
|
import { Owed } from './owed.js';
|
|
10
|
+
import { Placed } from './placed.js';
|
|
10
11
|
import type { Channel } from './types.js';
|
|
11
12
|
import type { Subject, Voice } from './voice.js';
|
|
12
13
|
/** What a lane fill asks of the mix it belongs to. */
|
|
@@ -65,6 +66,13 @@ export declare class Lanes<I, O> implements Watcher {
|
|
|
65
66
|
/** The crowds of single-subject voices, one per list of channels, and which holds each voice. */
|
|
66
67
|
crowds: Crowd<I, O>[];
|
|
67
68
|
readonly crowdOf: Map<number, Crowd<I, O>>;
|
|
69
|
+
/** Which crowd voices' rows are placed on each subject. */
|
|
70
|
+
readonly placed: Placed;
|
|
71
|
+
/**
|
|
72
|
+
* By subject number, the mix's `relinks` when the subject's chain was last made stale by itself,
|
|
73
|
+
* so `pull` tells a head it remembers is stale without reading the head.
|
|
74
|
+
*/
|
|
75
|
+
unlinked: Float64Array<ArrayBuffer>;
|
|
68
76
|
/** Whether two crowds share a channel, so their rows must interleave in voice order. */
|
|
69
77
|
overlap: boolean;
|
|
70
78
|
/** The loci whose voices are on lanes, every member of each on one. */
|
package/dist/lanes.js
CHANGED
|
@@ -5,6 +5,7 @@ import { fold, foldDelta, foldInto, foldRun, gate, gather, unplain } from './gat
|
|
|
5
5
|
import { meet, reach } from './meet.js';
|
|
6
6
|
import { absent, Numbers } from './numbers.js';
|
|
7
7
|
import { Owed } from './owed.js';
|
|
8
|
+
import { Placed } from './placed.js';
|
|
8
9
|
import { requalify, retouch } from './qualify.js';
|
|
9
10
|
import { reading } from './reading.js';
|
|
10
11
|
import { reportedRow, unplace } from './rows.js';
|
|
@@ -23,6 +24,13 @@ export class Lanes {
|
|
|
23
24
|
/** The crowds of single-subject voices, one per list of channels, and which holds each voice. */
|
|
24
25
|
crowds = [];
|
|
25
26
|
crowdOf = new Map();
|
|
27
|
+
/** Which crowd voices' rows are placed on each subject. */
|
|
28
|
+
placed = new Placed();
|
|
29
|
+
/**
|
|
30
|
+
* By subject number, the mix's `relinks` when the subject's chain was last made stale by itself,
|
|
31
|
+
* so `pull` tells a head it remembers is stale without reading the head.
|
|
32
|
+
*/
|
|
33
|
+
unlinked = new Float64Array(0);
|
|
26
34
|
/** Whether two crowds share a channel, so their rows must interleave in voice order. */
|
|
27
35
|
overlap = false;
|
|
28
36
|
/** The loci whose voices are on lanes, every member of each on one. */
|
|
@@ -374,10 +382,15 @@ export class Lanes {
|
|
|
374
382
|
this.live--;
|
|
375
383
|
for (const lane of this.lanes)
|
|
376
384
|
lane.remove(slot);
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
385
|
+
const placed = this.placed;
|
|
386
|
+
for (let id = placed.any(slot); id !== 0; id = placed.any(slot)) {
|
|
387
|
+
const c = this.crowdOf.get(id);
|
|
388
|
+
const p = c?.rowOf.get(id);
|
|
389
|
+
if (c !== undefined && p !== undefined && c.list[p] === slot)
|
|
390
|
+
unplace(this, c, p);
|
|
391
|
+
else
|
|
392
|
+
placed.remove(slot, id);
|
|
393
|
+
}
|
|
381
394
|
if (slot < this.cap)
|
|
382
395
|
this.per[slot * 6 /* Per.SLOT */ + 0 /* Per.FILLED */] = this.fills;
|
|
383
396
|
this.subjects[slot] = undefined;
|
|
@@ -390,6 +403,10 @@ export class Lanes {
|
|
|
390
403
|
per.set(this.per);
|
|
391
404
|
this.per = per;
|
|
392
405
|
this.owed.grow(cap);
|
|
406
|
+
this.placed.grow(cap);
|
|
407
|
+
const unlinked = new Float64Array(cap);
|
|
408
|
+
unlinked.set(this.unlinked);
|
|
409
|
+
this.unlinked = unlinked;
|
|
393
410
|
for (const ch of this.laned) {
|
|
394
411
|
const values = new Float64Array(cap * ch.axes);
|
|
395
412
|
values.set(ch.values);
|
package/dist/lean.d.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { Engine, Kit, Mix, MixOptions } from './types.js';
|
|
2
|
+
export * from './index.js';
|
|
3
|
+
/**
|
|
4
|
+
* The stock engine without lanes: every voice folds subject by subject, to the same values, and a
|
|
5
|
+
* bundle that takes `mix` from here leaves the lanes' code out. `lanes: true` is refused.
|
|
6
|
+
*
|
|
7
|
+
* @category engine
|
|
8
|
+
*/
|
|
9
|
+
export declare const lean: Engine;
|
|
10
|
+
/**
|
|
11
|
+
* Makes a mix over a kit, on the engine the options name or the lean one.
|
|
12
|
+
*
|
|
13
|
+
* @category mix
|
|
14
|
+
*/
|
|
15
|
+
export declare function mix<I, O, H = unknown>(kit: Kit<O>, opts?: MixOptions<H>): Mix<I, O, H>;
|
package/dist/lean.js
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { Mixer } from './mixer.js';
|
|
2
|
+
export * from './index.js';
|
|
3
|
+
/**
|
|
4
|
+
* The stock engine without lanes: every voice folds subject by subject, to the same values, and a
|
|
5
|
+
* bundle that takes `mix` from here leaves the lanes' code out. `lanes: true` is refused.
|
|
6
|
+
*
|
|
7
|
+
* @category engine
|
|
8
|
+
*/
|
|
9
|
+
export const lean = {
|
|
10
|
+
name: 'lean',
|
|
11
|
+
runs: new Set(['fn', 'keys', 'motion']),
|
|
12
|
+
create(kit, opts) {
|
|
13
|
+
return new Mixer(kit, opts, null);
|
|
14
|
+
},
|
|
15
|
+
};
|
|
16
|
+
/**
|
|
17
|
+
* Makes a mix over a kit, on the engine the options name or the lean one.
|
|
18
|
+
*
|
|
19
|
+
* @category mix
|
|
20
|
+
*/
|
|
21
|
+
export function mix(kit, opts = {}) {
|
|
22
|
+
return (opts.engine ?? lean).create(kit, opts);
|
|
23
|
+
}
|
package/dist/leavings.d.ts
CHANGED
|
@@ -17,8 +17,13 @@ export declare class Leavings<I> {
|
|
|
17
17
|
* front: entries are in the order they left, and mix time does not run back along it.
|
|
18
18
|
*/
|
|
19
19
|
expire(reach: number, out: ((e: Left<I>) => void) | null): void;
|
|
20
|
-
/**
|
|
20
|
+
/**
|
|
21
|
+
* The record `subject` had at `cut` and left with after it, if it left after `cut`. None where it
|
|
22
|
+
* left saying only that it had faded out, after `cut`, of a voice that had not met it.
|
|
23
|
+
*/
|
|
21
24
|
at(subject: I, cut: Cut): Subject<unknown> | undefined;
|
|
25
|
+
/** Each subject's first leaving after `cut`, which holds what it had at `cut`. */
|
|
26
|
+
after(cut: Cut): Left<I>[];
|
|
22
27
|
/** Each time `subject` left, oldest first. */
|
|
23
28
|
of(subject: I): readonly Left<I>[];
|
|
24
29
|
/** Every entry, oldest first. */
|
package/dist/leavings.js
CHANGED
|
@@ -49,16 +49,30 @@ export class Leavings {
|
|
|
49
49
|
this.head = 0;
|
|
50
50
|
}
|
|
51
51
|
}
|
|
52
|
-
/**
|
|
52
|
+
/**
|
|
53
|
+
* The record `subject` had at `cut` and left with after it, if it left after `cut`. None where it
|
|
54
|
+
* left saying only that it had faded out, after `cut`, of a voice that had not met it.
|
|
55
|
+
*/
|
|
53
56
|
at(subject, cut) {
|
|
54
57
|
const own = this.by.get(subject);
|
|
55
58
|
if (own === undefined)
|
|
56
59
|
return undefined;
|
|
57
60
|
for (const e of own)
|
|
58
61
|
if (!within(cut, e.seq, e.sync))
|
|
59
|
-
return e.held;
|
|
62
|
+
return !e.held.reaches && e.parted !== null && e.parted.seq > cut.seq ? undefined : e.held;
|
|
60
63
|
return undefined;
|
|
61
64
|
}
|
|
65
|
+
/** Each subject's first leaving after `cut`, which holds what it had at `cut`. */
|
|
66
|
+
after(cut) {
|
|
67
|
+
const out = [];
|
|
68
|
+
const seen = new Set();
|
|
69
|
+
for (const e of this.all())
|
|
70
|
+
if (!within(cut, e.seq, e.sync) && !seen.has(e.subject)) {
|
|
71
|
+
seen.add(e.subject);
|
|
72
|
+
out.push(e);
|
|
73
|
+
}
|
|
74
|
+
return out;
|
|
75
|
+
}
|
|
62
76
|
/** Each time `subject` left, oldest first. */
|
|
63
77
|
of(subject) {
|
|
64
78
|
return this.by.get(subject) ?? [];
|
package/dist/marks.d.ts
CHANGED
|
@@ -1,8 +1,12 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type Listed } from './book.js';
|
|
2
2
|
import type { Mixer } from './mixer.js';
|
|
3
3
|
import type { Transport } from './transport.js';
|
|
4
|
-
import type {
|
|
4
|
+
import type { MarkName } from './types.js';
|
|
5
5
|
import { type Voice } from './voice.js';
|
|
6
|
+
/** A voice's marks, in the order `marks` lists those at one time. */
|
|
7
|
+
export declare const voiceMarks: readonly ["start", "in", "coast", "out", "end"];
|
|
8
|
+
/** Refuses marks placed inside a voice under one of its own five names, or that it could never reach. */
|
|
9
|
+
export declare function checkMarks(marks: Readonly<Record<string, number>>, duration: number): void;
|
|
6
10
|
/** The host timestamp the mix clock reads mix time `t` at; Infinity where it never will. */
|
|
7
11
|
export declare function hostOf<I, O>(mix: Mixer<I, O>, t: number): number;
|
|
8
12
|
/** What the mix clock reads at host time `u`, by the clock in force then. */
|
|
@@ -12,4 +16,4 @@ export declare function listed<I, O>(mix: Mixer<I, O>, from: number, to: number)
|
|
|
12
16
|
/** Every mark of every mix on a transport and every one announced on it, earliest first. */
|
|
13
17
|
export declare function listedAll(transport: Transport, from: number, to: number): Listed[];
|
|
14
18
|
/** When a voice reaches a mark, mix time, or undefined while nothing has fixed it. */
|
|
15
|
-
export declare function markOf<I, O>(mix: Mixer<I, O>, voice: Voice<I, O>, mark:
|
|
19
|
+
export declare function markOf<I, O>(mix: Mixer<I, O>, voice: Voice<I, O>, mark: MarkName): number | undefined;
|
package/dist/marks.js
CHANGED
|
@@ -1,5 +1,20 @@
|
|
|
1
|
+
import { inPass } from './book.js';
|
|
1
2
|
import { startOf } from './place.js';
|
|
2
3
|
import { none } from './voice.js';
|
|
4
|
+
/** A voice's marks, in the order `marks` lists those at one time. */
|
|
5
|
+
export const voiceMarks = ['start', 'in', 'coast', 'out', 'end'];
|
|
6
|
+
/** Refuses marks placed inside a voice under one of its own five names, or that it could never reach. */
|
|
7
|
+
export function checkMarks(marks, duration) {
|
|
8
|
+
for (const name of Object.keys(marks)) {
|
|
9
|
+
if (voiceMarks.includes(name))
|
|
10
|
+
throw new Error(`blits: ${name} is one of a voice's own marks; a mark placed inside it needs another name`);
|
|
11
|
+
const at = marks[name];
|
|
12
|
+
if (!inPass(at, duration))
|
|
13
|
+
throw new RangeError(duration > 0
|
|
14
|
+
? `blits: mark ${name} is voice ms into a pass, 0 up to ${duration}, not ${at}`
|
|
15
|
+
: `blits: mark ${name} is voice ms, 0 or more, not ${at}`);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
3
18
|
/** The host timestamp the mix clock reads mix time `t` at; Infinity where it never will. */
|
|
4
19
|
export function hostOf(mix, t) {
|
|
5
20
|
return (mix.pace === null ? t : mix.pace.timeOf(t)) + mix.offset;
|
|
@@ -10,7 +25,7 @@ export function readingAt(mix, u) {
|
|
|
10
25
|
}
|
|
11
26
|
/** Every mark between two host timestamps, earliest first, with the order that tells each apart. */
|
|
12
27
|
export function listed(mix, from, to) {
|
|
13
|
-
const out =
|
|
28
|
+
const out = marksOf(mix, from, to);
|
|
14
29
|
for (const a of announcedMarks(mix.transport, from, to))
|
|
15
30
|
if (a.score !== undefined || a.slot === mix.slot)
|
|
16
31
|
out.push(a.listed);
|
|
@@ -20,7 +35,7 @@ export function listed(mix, from, to) {
|
|
|
20
35
|
export function listedAll(transport, from, to) {
|
|
21
36
|
const out = [];
|
|
22
37
|
for (const m of transport.members)
|
|
23
|
-
out.push(...
|
|
38
|
+
out.push(...marksOf(m, from, to));
|
|
24
39
|
for (const a of announcedMarks(transport, from, to))
|
|
25
40
|
out.push(a.listed);
|
|
26
41
|
return sorted(out);
|
|
@@ -50,30 +65,36 @@ function announcedMarks(transport, from, to) {
|
|
|
50
65
|
});
|
|
51
66
|
return out;
|
|
52
67
|
}
|
|
53
|
-
function
|
|
68
|
+
function marksOf(mix, from, to) {
|
|
54
69
|
const lo = from - mix.offset;
|
|
55
70
|
const hi = to - mix.offset;
|
|
56
71
|
const out = [];
|
|
57
72
|
for (const voice of [...mix.cued, ...mix.gone]) {
|
|
58
|
-
for (const mark of
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
timestamp: t + mix.offset,
|
|
65
|
-
mark,
|
|
66
|
-
voice: voice.id,
|
|
67
|
-
score: voice.spec.score,
|
|
68
|
-
name: voice.spec.name,
|
|
69
|
-
tags: voice.spec.tags ?? none,
|
|
70
|
-
mix: mix.name,
|
|
71
|
-
order: voice.id,
|
|
72
|
-
});
|
|
73
|
-
}
|
|
73
|
+
for (const mark of voiceMarks)
|
|
74
|
+
listOne(mix, voice, mark, lo, hi, out);
|
|
75
|
+
const inner = voice.spec.marks;
|
|
76
|
+
if (inner !== undefined)
|
|
77
|
+
for (const mark of Object.keys(inner))
|
|
78
|
+
listOne(mix, voice, mark, lo, hi, out);
|
|
74
79
|
}
|
|
75
80
|
return out;
|
|
76
81
|
}
|
|
82
|
+
function listOne(mix, voice, mark, lo, hi, out) {
|
|
83
|
+
const m = markOf(mix, voice, mark);
|
|
84
|
+
const t = m === undefined ? m : hostOf(mix, m) - mix.offset;
|
|
85
|
+
if (t === undefined || t < lo || t > hi)
|
|
86
|
+
return;
|
|
87
|
+
out.push({
|
|
88
|
+
timestamp: t + mix.offset,
|
|
89
|
+
mark,
|
|
90
|
+
voice: voice.id,
|
|
91
|
+
score: voice.spec.score,
|
|
92
|
+
name: voice.spec.name,
|
|
93
|
+
tags: voice.spec.tags ?? none,
|
|
94
|
+
mix: mix.name,
|
|
95
|
+
order: voice.id,
|
|
96
|
+
});
|
|
97
|
+
}
|
|
77
98
|
/** When a voice reaches a mark, mix time, or undefined while nothing has fixed it. */
|
|
78
99
|
export function markOf(mix, voice, mark) {
|
|
79
100
|
if (!Number.isFinite(voice.start))
|
|
@@ -88,8 +109,12 @@ export function markOf(mix, voice, mark) {
|
|
|
88
109
|
return start + (voice.fade.in ?? 0);
|
|
89
110
|
if (mark === 'coast')
|
|
90
111
|
return coastOf(mix, voice, start);
|
|
112
|
+
if (mark !== 'out' && mark !== 'end')
|
|
113
|
+
return insideOf(mix, voice, start, mark);
|
|
91
114
|
const out = voice.out;
|
|
92
115
|
let outAt;
|
|
116
|
+
// An owner leaves with its last child, over no fade of its own.
|
|
117
|
+
let leaves = false;
|
|
93
118
|
if (out !== null)
|
|
94
119
|
outAt = out.at;
|
|
95
120
|
else if (Number.isFinite(voice.outAt))
|
|
@@ -99,6 +124,13 @@ export function markOf(mix, voice, mark) {
|
|
|
99
124
|
if (Number.isFinite(t))
|
|
100
125
|
outAt = Math.max(start, t);
|
|
101
126
|
}
|
|
127
|
+
else if (voice.holding !== null) {
|
|
128
|
+
const t = lastChild(mix, voice.holding.children, 'end');
|
|
129
|
+
if (t !== undefined) {
|
|
130
|
+
outAt = Math.max(start, t);
|
|
131
|
+
leaves = true;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
102
134
|
else if (!(voice.backs ? voice.freezesBefore : voice.freezesAfter)) {
|
|
103
135
|
const t = voice.endsAt();
|
|
104
136
|
if (Number.isFinite(t))
|
|
@@ -113,9 +145,38 @@ export function markOf(mix, voice, mark) {
|
|
|
113
145
|
return out.deadline === undefined ? undefined : out.at + out.deadline;
|
|
114
146
|
return out.at + out.over;
|
|
115
147
|
}
|
|
148
|
+
if (leaves)
|
|
149
|
+
return outAt;
|
|
116
150
|
const over = Number.isNaN(voice.outOver) ? (voice.fade.out ?? 0) : voice.outOver;
|
|
117
151
|
return outAt === undefined ? undefined : outAt + (mix.reduced ? 0 : over);
|
|
118
152
|
}
|
|
153
|
+
/** Voice ms within which a mark placed inside a voice counts as reached where the voice starts. */
|
|
154
|
+
const REACHED = 1e-9;
|
|
155
|
+
/**
|
|
156
|
+
* When a voice reaches a mark it placed inside itself, mix time: `at` into the first pass that
|
|
157
|
+
* reaches it from where the voice starts, while its clock runs forward and before it ends. None for
|
|
158
|
+
* a mark it does not place.
|
|
159
|
+
*/
|
|
160
|
+
function insideOf(mix, voice, start, name) {
|
|
161
|
+
const marks = voice.spec.marks;
|
|
162
|
+
if (marks === undefined || !Object.hasOwn(marks, name) || voice.backs)
|
|
163
|
+
return undefined;
|
|
164
|
+
const at = marks[name];
|
|
165
|
+
const from = voice.elapsedAt(start);
|
|
166
|
+
const d = voice.duration;
|
|
167
|
+
let pass = 0;
|
|
168
|
+
if (d > 0)
|
|
169
|
+
pass = Math.max(0, Math.ceil((from - at) / d - REACHED));
|
|
170
|
+
else if (at < from - REACHED)
|
|
171
|
+
return undefined;
|
|
172
|
+
if (pass >= voice.passes)
|
|
173
|
+
return undefined;
|
|
174
|
+
const t = voice.timeAt(pass * d + at);
|
|
175
|
+
if (!Number.isFinite(t))
|
|
176
|
+
return undefined;
|
|
177
|
+
const end = markOf(mix, voice, 'end');
|
|
178
|
+
return end !== undefined && t >= end ? undefined : Math.max(start, t);
|
|
179
|
+
}
|
|
119
180
|
/**
|
|
120
181
|
* When a voice's last pass ends, mix time: an owner's when its fit ends, or its last child's. None
|
|
121
182
|
* for a voice that never ends its passes, or that leaves or begins to fade before they end.
|
|
@@ -127,15 +188,15 @@ function coastOf(mix, voice, start) {
|
|
|
127
188
|
if (voice.fitting !== null)
|
|
128
189
|
t = voice.timeAt(voice.fitting.end);
|
|
129
190
|
else if (voice.holding !== null) {
|
|
130
|
-
const
|
|
131
|
-
if (
|
|
191
|
+
const h = voice.holding;
|
|
192
|
+
if (h.cutSeq !== Number.POSITIVE_INFINITY)
|
|
132
193
|
return undefined;
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
194
|
+
if (h.children.length === 0)
|
|
195
|
+
t = voice.state === 'done' ? h.coast : undefined;
|
|
196
|
+
else {
|
|
197
|
+
t = lastChild(mix, h.children, 'coast');
|
|
198
|
+
if (t !== undefined)
|
|
199
|
+
t = Math.max(t, h.coast);
|
|
139
200
|
}
|
|
140
201
|
}
|
|
141
202
|
else
|
|
@@ -146,3 +207,17 @@ function coastOf(mix, voice, start) {
|
|
|
146
207
|
const cut = voice.out !== null ? voice.out.at : voice.state === 'done' ? voice.doneAt : voice.outAt;
|
|
147
208
|
return cut < t ? undefined : t;
|
|
148
209
|
}
|
|
210
|
+
/** The latest of the children's `mark`; undefined for none, or while one has not fixed it. */
|
|
211
|
+
function lastChild(mix, children, mark) {
|
|
212
|
+
if (children.length === 0)
|
|
213
|
+
return undefined;
|
|
214
|
+
let t = Number.NEGATIVE_INFINITY;
|
|
215
|
+
for (const child of children) {
|
|
216
|
+
const c = markOf(mix, child, mark);
|
|
217
|
+
if (c === undefined)
|
|
218
|
+
return undefined;
|
|
219
|
+
if (c > t)
|
|
220
|
+
t = c;
|
|
221
|
+
}
|
|
222
|
+
return t;
|
|
223
|
+
}
|
package/dist/meet.js
CHANGED
|
@@ -110,6 +110,7 @@ function place(lanes, voice, slot, subject, from, head) {
|
|
|
110
110
|
return false;
|
|
111
111
|
const o = p * 8 /* Row.STRIDE */;
|
|
112
112
|
c.list[p] = slot;
|
|
113
|
+
lanes.placed.add(slot, voice.id);
|
|
113
114
|
c.data[o + 0 /* Row.DELAY */] = held.delay;
|
|
114
115
|
c.data[o + 1 /* Row.SINCE */] = held.shown;
|
|
115
116
|
c.data[o + 2 /* Row.WEIGHT */] = 0;
|
package/dist/mixer.d.ts
CHANGED
|
@@ -2,6 +2,7 @@ import { blended, laneRead, shareKept } from './blend.js';
|
|
|
2
2
|
import type { Book } from './book.js';
|
|
3
3
|
import { aims, chain, linkable, relink } from './chain.js';
|
|
4
4
|
import { type LerpInto } from './channels.js';
|
|
5
|
+
import type { Column } from './columns.js';
|
|
5
6
|
import { DepartedIndex } from './departed.js';
|
|
6
7
|
import type { Due } from './due.js';
|
|
7
8
|
import { parting } from './fade.js';
|
|
@@ -21,7 +22,7 @@ import { ScoreIndex } from './scored.js';
|
|
|
21
22
|
import { Fitting } from './spans.js';
|
|
22
23
|
import { Store } from './store.js';
|
|
23
24
|
import { type Announced, Transport } from './transport.js';
|
|
24
|
-
import type { Booker, BookOptions, Channel, Columns, Engine, Handle, Kit, Marked, Mix, MixOptions, OwnerSpec, Patch, Projection, Sent, Signal, SpanHandle, SpanSpec, Tape, VoiceSpec } from './types.js';
|
|
25
|
+
import type { BlendMember, Booker, BookOptions, Channel, Columns, Engine, Handle, Kit, Marked, Mix, MixOptions, OwnerSpec, Patch, Projection, Sent, Signal, SpanHandle, SpanSpec, Tape, VoiceSpec } from './types.js';
|
|
25
26
|
import { type Subject, type Voice } from './voice.js';
|
|
26
27
|
import { base, capped, fadeOf, horizonFor, ownedBy, ownerBase, prime, weigh, weighOwned } from './weigh.js';
|
|
27
28
|
/**
|
|
@@ -34,6 +35,8 @@ export declare const defaults: {
|
|
|
34
35
|
export declare class Mixer<I, O> implements Mix<I, O> {
|
|
35
36
|
readonly kit: Kit<O>;
|
|
36
37
|
readonly opts: MixOptions;
|
|
38
|
+
/** The lanes an entry was built with; null in one that leaves them out. */
|
|
39
|
+
readonly lanesOf: typeof Lanes | null;
|
|
37
40
|
cued: Voice<I, O>[];
|
|
38
41
|
/** Under `history`, voices that have left but that a read back may still reach. */
|
|
39
42
|
gone: Voice<I, O>[];
|
|
@@ -95,10 +98,15 @@ export declare class Mixer<I, O> implements Mix<I, O> {
|
|
|
95
98
|
/** The last array `pull` read, by position, with each subject's chain head, to skip the lookup. */
|
|
96
99
|
pulled: I[];
|
|
97
100
|
pulledHeads: (Subject<unknown> | undefined)[];
|
|
98
|
-
/**
|
|
101
|
+
/**
|
|
102
|
+
* Each remembered head's lane slot, -1 at a position with none, and the `version` and `relinks`
|
|
103
|
+
* they were all current at.
|
|
104
|
+
*/
|
|
99
105
|
pulledSlots: Int32Array<ArrayBuffer>;
|
|
100
106
|
pulledVersion: number;
|
|
101
107
|
pulledRelinks: number;
|
|
108
|
+
/** What `pull` knows of each channel it has written, by name; none until the first pull. */
|
|
109
|
+
pullColumns: Map<string, Column> | null;
|
|
102
110
|
/** Counts chains made stale one subject at a time, which `version` does not. */
|
|
103
111
|
relinks: number;
|
|
104
112
|
/** Read once a sync, so a `reduce` function is not called per voice per subject. */
|
|
@@ -187,7 +195,9 @@ export declare class Mixer<I, O> implements Mix<I, O> {
|
|
|
187
195
|
keys: Keys | null;
|
|
188
196
|
/** With a history store, the voices it paged out whose handles are still held; null without one. */
|
|
189
197
|
outs: Outs | null;
|
|
190
|
-
constructor(kit: Kit<O>, opts: MixOptions
|
|
198
|
+
constructor(kit: Kit<O>, opts: MixOptions,
|
|
199
|
+
/** The lanes an entry was built with; null in one that leaves them out. */
|
|
200
|
+
lanesOf: typeof Lanes | null);
|
|
191
201
|
/** Throws where the clock is a shared transport's, which only the transport moves. */
|
|
192
202
|
private own;
|
|
193
203
|
get now(): number;
|
|
@@ -203,6 +213,7 @@ export declare class Mixer<I, O> implements Mix<I, O> {
|
|
|
203
213
|
get nextId(): number;
|
|
204
214
|
set nextId(v: number);
|
|
205
215
|
get timestamp(): number;
|
|
216
|
+
get seq(): number;
|
|
206
217
|
get last(): number;
|
|
207
218
|
get tape(): Tape | undefined;
|
|
208
219
|
get replaying(): boolean;
|
|
@@ -212,16 +223,20 @@ export declare class Mixer<I, O> implements Mix<I, O> {
|
|
|
212
223
|
off: number;
|
|
213
224
|
};
|
|
214
225
|
cue(spec: VoiceSpec<I, O>): Handle<I>;
|
|
215
|
-
blend(
|
|
226
|
+
blend(members: readonly (Patch<I, O, unknown> | BlendMember<I, O>)[], by: Signal<I>, spec?: Omit<VoiceSpec<I, O>, 'patch' | 'weight' | 'locus'>): Handle<I>[];
|
|
216
227
|
owns(spec: OwnerSpec<I>): Handle<I>;
|
|
217
228
|
span(spec: SpanSpec<I>): SpanHandle<I>;
|
|
218
229
|
sync(timestamp: number): void;
|
|
219
230
|
book<E = unknown>(opts: BookOptions<E, I>): Booker;
|
|
220
231
|
probe(subject: I, out?: O): O;
|
|
221
232
|
pull(subjects: Iterable<I>, into: Columns<O>): void;
|
|
222
|
-
seek(
|
|
233
|
+
seek(to: number | {
|
|
234
|
+
readonly frame: number;
|
|
235
|
+
}): void;
|
|
223
236
|
prepare(time: number): Promise<void>;
|
|
224
|
-
project(
|
|
237
|
+
project(at: number | {
|
|
238
|
+
readonly frame: number;
|
|
239
|
+
}): Projection<I, O>;
|
|
225
240
|
announce(name: string, opts?: {
|
|
226
241
|
at?: number;
|
|
227
242
|
score?: string;
|