@msb235/blits 0.9.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 +167 -0
- package/README.md +16 -5
- package/dist/blend.js +1 -5
- package/dist/book.d.ts +8 -5
- package/dist/book.js +47 -19
- 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.d.ts +1 -1
- 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 +23 -7
- package/dist/mixer.js +73 -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 +42 -15
- package/dist/types/patch.d.ts +5 -1
- package/dist/types/place.d.ts +25 -11
- 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 +75 -40
- package/package.json +7 -2
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;
|
|
@@ -202,6 +212,8 @@ export declare class Mixer<I, O> implements Mix<I, O> {
|
|
|
202
212
|
set announced(v: Announced[]);
|
|
203
213
|
get nextId(): number;
|
|
204
214
|
set nextId(v: number);
|
|
215
|
+
get timestamp(): number;
|
|
216
|
+
get seq(): number;
|
|
205
217
|
get last(): number;
|
|
206
218
|
get tape(): Tape | undefined;
|
|
207
219
|
get replaying(): boolean;
|
|
@@ -211,16 +223,20 @@ export declare class Mixer<I, O> implements Mix<I, O> {
|
|
|
211
223
|
off: number;
|
|
212
224
|
};
|
|
213
225
|
cue(spec: VoiceSpec<I, O>): Handle<I>;
|
|
214
|
-
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>[];
|
|
215
227
|
owns(spec: OwnerSpec<I>): Handle<I>;
|
|
216
228
|
span(spec: SpanSpec<I>): SpanHandle<I>;
|
|
217
229
|
sync(timestamp: number): void;
|
|
218
|
-
book(opts: BookOptions): Booker;
|
|
230
|
+
book<E = unknown>(opts: BookOptions<E, I>): Booker;
|
|
219
231
|
probe(subject: I, out?: O): O;
|
|
220
232
|
pull(subjects: Iterable<I>, into: Columns<O>): void;
|
|
221
|
-
seek(
|
|
233
|
+
seek(to: number | {
|
|
234
|
+
readonly frame: number;
|
|
235
|
+
}): void;
|
|
222
236
|
prepare(time: number): Promise<void>;
|
|
223
|
-
project(
|
|
237
|
+
project(at: number | {
|
|
238
|
+
readonly frame: number;
|
|
239
|
+
}): Projection<I, O>;
|
|
224
240
|
announce(name: string, opts?: {
|
|
225
241
|
at?: number;
|
|
226
242
|
score?: string;
|
package/dist/mixer.js
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import { blended, blendOf, laneRead, shareKept } from './blend.js';
|
|
2
|
-
import { aims, chain, linkable, relink } from './chain.js';
|
|
2
|
+
import { aims, chain, linkable, relink, unlink } from './chain.js';
|
|
3
3
|
import { lerpInto } from './channels.js';
|
|
4
4
|
import { cue } from './cue.js';
|
|
5
5
|
import { DepartedIndex } from './departed.js';
|
|
6
|
-
import { beginFade,
|
|
6
|
+
import { beginFade, leaveVoice, parting } from './fade.js';
|
|
7
7
|
import { apply, baseFor, clamp, contribution, fold, foldDetour, folded, foldLoci, foldSharers, foldWith, isRest, keyed, linked, near, owedBy, passes, read, shared, slopeFor, tick, } from './fold.js';
|
|
8
8
|
import { GoneIndex, goneIndex } from './gone.js';
|
|
9
9
|
import { held, shownOf, sinceOf, unreachedOf } from './held.js';
|
|
10
|
-
import {
|
|
10
|
+
import { releasing } from './history.js';
|
|
11
11
|
import { book, laneHost } from './hosts.js';
|
|
12
12
|
import { Lanes } from './lanes.js';
|
|
13
13
|
import { foldLocus } from './locus.js';
|
|
@@ -22,8 +22,8 @@ import { Outs } from './revive.js';
|
|
|
22
22
|
import { ScoreIndex } from './scored.js';
|
|
23
23
|
import { seek } from './seek.js';
|
|
24
24
|
import { Fitting, refit } from './spans.js';
|
|
25
|
-
import { Store } from './store.js';
|
|
26
|
-
import { record } from './tape.js';
|
|
25
|
+
import { Masked, Store } from './store.js';
|
|
26
|
+
import { record, unplayable } from './tape.js';
|
|
27
27
|
import { touch } from './touch.js';
|
|
28
28
|
import { announce, Transport } from './transport.js';
|
|
29
29
|
import { none } from './voice.js';
|
|
@@ -37,6 +37,7 @@ export const defaults = { lanes: true };
|
|
|
37
37
|
export class Mixer {
|
|
38
38
|
kit;
|
|
39
39
|
opts;
|
|
40
|
+
lanesOf;
|
|
40
41
|
cued = [];
|
|
41
42
|
/** Under `history`, voices that have left but that a read back may still reach. */
|
|
42
43
|
gone = [];
|
|
@@ -87,10 +88,15 @@ export class Mixer {
|
|
|
87
88
|
/** The last array `pull` read, by position, with each subject's chain head, to skip the lookup. */
|
|
88
89
|
pulled = [];
|
|
89
90
|
pulledHeads = [];
|
|
90
|
-
/**
|
|
91
|
+
/**
|
|
92
|
+
* Each remembered head's lane slot, -1 at a position with none, and the `version` and `relinks`
|
|
93
|
+
* they were all current at.
|
|
94
|
+
*/
|
|
91
95
|
pulledSlots = new Int32Array(0);
|
|
92
96
|
pulledVersion = Number.NaN;
|
|
93
97
|
pulledRelinks = -1;
|
|
98
|
+
/** What `pull` knows of each channel it has written, by name; none until the first pull. */
|
|
99
|
+
pullColumns = null;
|
|
94
100
|
/** Counts chains made stale one subject at a time, which `version` does not. */
|
|
95
101
|
relinks = 0;
|
|
96
102
|
/** Read once a sync, so a `reduce` function is not called per voice per subject. */
|
|
@@ -215,9 +221,12 @@ export class Mixer {
|
|
|
215
221
|
keys = null;
|
|
216
222
|
/** With a history store, the voices it paged out whose handles are still held; null without one. */
|
|
217
223
|
outs = null;
|
|
218
|
-
constructor(kit, opts
|
|
224
|
+
constructor(kit, opts,
|
|
225
|
+
/** The lanes an entry was built with; null in one that leaves them out. */
|
|
226
|
+
lanesOf) {
|
|
219
227
|
this.kit = kit;
|
|
220
228
|
this.opts = opts;
|
|
229
|
+
this.lanesOf = lanesOf;
|
|
221
230
|
this.names = Object.keys(kit);
|
|
222
231
|
this.channels = this.names.map((k) => kit[k]);
|
|
223
232
|
this.bounded = this.channels.flatMap((c, i) => (c.bounds ? [i] : []));
|
|
@@ -225,7 +234,10 @@ export class Mixer {
|
|
|
225
234
|
this.names.forEach((k, i) => {
|
|
226
235
|
this.slotOf.set(k, i);
|
|
227
236
|
});
|
|
228
|
-
|
|
237
|
+
if (lanesOf === null && opts.lanes === true)
|
|
238
|
+
throw new Error("blits: '@msb235/blits/lean' has no lanes; take mix from '@msb235/blits'");
|
|
239
|
+
this.lanes =
|
|
240
|
+
lanesOf !== null && (opts.lanes ?? defaults.lanes) ? new lanesOf(laneHost(this)) : null;
|
|
229
241
|
this.name = opts.name;
|
|
230
242
|
const shared = opts.transport;
|
|
231
243
|
if (shared !== undefined) {
|
|
@@ -287,6 +299,12 @@ export class Mixer {
|
|
|
287
299
|
set nextId(v) {
|
|
288
300
|
this.transport.nextId = v;
|
|
289
301
|
}
|
|
302
|
+
get timestamp() {
|
|
303
|
+
return this.transport.last;
|
|
304
|
+
}
|
|
305
|
+
get seq() {
|
|
306
|
+
return this.transport.seq;
|
|
307
|
+
}
|
|
290
308
|
get last() {
|
|
291
309
|
return this.transport.last;
|
|
292
310
|
}
|
|
@@ -305,16 +323,31 @@ export class Mixer {
|
|
|
305
323
|
}
|
|
306
324
|
cue(spec) {
|
|
307
325
|
this.attached();
|
|
308
|
-
const engine = this.opts.engine
|
|
309
|
-
if (!engine.runs.has(spec.patch.form))
|
|
326
|
+
const engine = this.opts.engine;
|
|
327
|
+
if (engine !== undefined && !engine.runs.has(spec.patch.form))
|
|
310
328
|
throw new Error(`blits: engine ${engine.name} does not run ${spec.patch.form} patches`);
|
|
311
329
|
return cue(this, spec);
|
|
312
330
|
}
|
|
313
|
-
blend(
|
|
331
|
+
blend(members, by, spec = {}) {
|
|
314
332
|
const locus = `blend:${this.nextId}`;
|
|
315
|
-
const of = blendOf(by,
|
|
316
|
-
return
|
|
317
|
-
const
|
|
333
|
+
const of = blendOf(by, members.length - 1);
|
|
334
|
+
return members.map((member, i) => {
|
|
335
|
+
const own = {
|
|
336
|
+
...spec,
|
|
337
|
+
patch: member,
|
|
338
|
+
weight: by,
|
|
339
|
+
locus,
|
|
340
|
+
};
|
|
341
|
+
if ('patch' in member) {
|
|
342
|
+
own.patch = member.patch;
|
|
343
|
+
if (member.name !== undefined)
|
|
344
|
+
own.name = member.name;
|
|
345
|
+
if (member.tags !== undefined)
|
|
346
|
+
own.tags = member.tags;
|
|
347
|
+
if (member.hits !== undefined)
|
|
348
|
+
own.hits = member.hits;
|
|
349
|
+
}
|
|
350
|
+
const handle = this.cue(own);
|
|
318
351
|
const voice = this.cued[this.cued.length - 1];
|
|
319
352
|
voice.blend = { of, i };
|
|
320
353
|
of.members.push(voice);
|
|
@@ -327,6 +360,8 @@ export class Mixer {
|
|
|
327
360
|
return this.cue({ ...spec, patch: ownerPatch });
|
|
328
361
|
}
|
|
329
362
|
span(spec) {
|
|
363
|
+
if ((spec.rate ?? 1) < 0)
|
|
364
|
+
throw new Error('blits: a span lays out its children forward from its start, so it is not cued backward');
|
|
330
365
|
this.fitting = new Fitting(spec);
|
|
331
366
|
try {
|
|
332
367
|
const handle = this.owns(spec);
|
|
@@ -356,16 +391,16 @@ export class Mixer {
|
|
|
356
391
|
this.looked = true;
|
|
357
392
|
pull(this, subjects, into);
|
|
358
393
|
}
|
|
359
|
-
seek(
|
|
394
|
+
seek(to) {
|
|
360
395
|
if (this.projecting)
|
|
361
396
|
throw new Error('blits: a projection does not seek');
|
|
362
|
-
seek(this.own('seek'),
|
|
397
|
+
seek(this.own('seek'), to);
|
|
363
398
|
}
|
|
364
399
|
prepare(time) {
|
|
365
400
|
return this.own('prepare').prepare(time);
|
|
366
401
|
}
|
|
367
|
-
project(
|
|
368
|
-
return projectAll(this.transport,
|
|
402
|
+
project(at) {
|
|
403
|
+
return projectAll(this.transport, at).of(this);
|
|
369
404
|
}
|
|
370
405
|
announce(name, opts = {}) {
|
|
371
406
|
this.attached();
|
|
@@ -466,18 +501,19 @@ export class Mixer {
|
|
|
466
501
|
voice.written = true;
|
|
467
502
|
beginFade(this, voice, { over: opts?.over });
|
|
468
503
|
}
|
|
469
|
-
record(this, 'mute', () => this
|
|
504
|
+
record(this, 'mute', (stage) => stage.mix(this)?.mute(opts));
|
|
470
505
|
}
|
|
471
506
|
drop(subject) {
|
|
472
507
|
this.frame = nextFrame();
|
|
473
508
|
const head = this.chains.get(subject);
|
|
474
509
|
// Nothing that kept this head, such as `pull`'s remembered list, may take it as current again.
|
|
475
|
-
if (head !== undefined)
|
|
476
|
-
head
|
|
477
|
-
this.relinks++;
|
|
478
|
-
}
|
|
510
|
+
if (head !== undefined)
|
|
511
|
+
unlink(this, head);
|
|
479
512
|
if (head !== undefined && head.slot >= 0 && this.lanes !== null)
|
|
480
513
|
this.lanes.release(head.slot);
|
|
514
|
+
// A read ahead's copy reads the live mix's poses, and so hides one where the mix deletes it.
|
|
515
|
+
if (this.projecting && !(this.pose instanceof Masked))
|
|
516
|
+
this.pose = new Masked(this.pose);
|
|
481
517
|
this.pose.delete(subject);
|
|
482
518
|
this.restStamps?.delete(subject);
|
|
483
519
|
this.chains.delete(subject);
|
|
@@ -485,10 +521,18 @@ export class Mixer {
|
|
|
485
521
|
// A record of it is only in a voice over every subject, one naming it, or one gone; a ramp out
|
|
486
522
|
// of a voice that does not name it is in `parters`, and a patch's own state in `motions`.
|
|
487
523
|
const kept = releasing(this);
|
|
524
|
+
// A motion patch's state is the live mix's alone: a read ahead cannot start a subject afresh in it.
|
|
525
|
+
const release = (motion) => {
|
|
526
|
+
if (!this.projecting)
|
|
527
|
+
motion.release(subject, ...kept);
|
|
528
|
+
else if (motion.holds(subject))
|
|
529
|
+
unplayable('a drop of a subject a motion patch holds');
|
|
530
|
+
};
|
|
488
531
|
const forget = (voice) => {
|
|
489
|
-
|
|
532
|
+
leaveVoice(this, voice, subject);
|
|
490
533
|
voice.subjects.delete(subject);
|
|
491
|
-
voice.motion
|
|
534
|
+
if (voice.motion !== undefined)
|
|
535
|
+
release(voice.motion);
|
|
492
536
|
voice.parts?.delete(subject);
|
|
493
537
|
voice.parted?.delete(subject);
|
|
494
538
|
voice.blend?.of.reads.delete(subject);
|
|
@@ -521,11 +565,11 @@ export class Mixer {
|
|
|
521
565
|
const strays = this.strays.get(subject);
|
|
522
566
|
if (strays !== undefined)
|
|
523
567
|
for (const motion of strays)
|
|
524
|
-
|
|
568
|
+
release(motion);
|
|
525
569
|
this.strays.delete(subject);
|
|
526
570
|
for (const motion of this.strayAll)
|
|
527
|
-
|
|
528
|
-
record(this, 'drop', () => this
|
|
571
|
+
release(motion);
|
|
572
|
+
record(this, 'drop', (stage) => stage.mix(this)?.drop(subject));
|
|
529
573
|
}
|
|
530
574
|
touch(subject) {
|
|
531
575
|
this.attached();
|
|
@@ -645,7 +689,7 @@ export const mixer = {
|
|
|
645
689
|
name: 'mixer',
|
|
646
690
|
runs: new Set(['fn', 'keys', 'motion']),
|
|
647
691
|
create(kit, opts) {
|
|
648
|
-
return new Mixer(kit, opts);
|
|
692
|
+
return new Mixer(kit, opts, Lanes);
|
|
649
693
|
},
|
|
650
694
|
};
|
|
651
695
|
/**
|
package/dist/motion.d.ts
CHANGED
|
@@ -45,7 +45,10 @@ export type Moving<I, O, V extends Value> = Patch<I, O, void> & {
|
|
|
45
45
|
*/
|
|
46
46
|
push(subject: I, velocity: V, at?: number): void;
|
|
47
47
|
};
|
|
48
|
-
/**
|
|
48
|
+
/**
|
|
49
|
+
* The state behind a `'motion'` patch, for an engine that reads it directly; a patch another copy
|
|
50
|
+
* of the same release made counts.
|
|
51
|
+
*/
|
|
49
52
|
export declare function motionOf<I>(p: object): Motions<I> | undefined;
|
|
50
53
|
interface Common<I, V extends Value> {
|
|
51
54
|
/** Units per second each subject starts moving at. Default 0. */
|
package/dist/motion.js
CHANGED
|
@@ -1,11 +1,17 @@
|
|
|
1
1
|
import { COAST, CRITICAL, EASED, OVER, UNDER } from './closed.js';
|
|
2
2
|
import { curve } from './easing.js';
|
|
3
3
|
import { Motions } from './motions.js';
|
|
4
|
+
import { release } from './shared.js';
|
|
4
5
|
const axes = (v) => (typeof v === 'number' ? [v] : [...v]);
|
|
5
6
|
/** Zero on every axis `x` has: a motion's velocity where its options give none. */
|
|
6
7
|
const still = (x) => new Array(x.length).fill(0);
|
|
7
8
|
const per = (p, subject) => typeof p === 'function' ? p(subject) : p;
|
|
8
9
|
const scalarOf = (p) => typeof p === 'function' ? undefined : typeof p === 'number';
|
|
10
|
+
/**
|
|
11
|
+
* What marks a motion patch to every copy of blits of this release loaded in one realm, so a
|
|
12
|
+
* spring another copy made runs on this copy's lanes; it gives the patch's state.
|
|
13
|
+
*/
|
|
14
|
+
const MOTIONS = Symbol.for(`@msb235/blits/motions@${release}`);
|
|
9
15
|
/** One `writes` array per channel, shared by every motion patch that writes it. */
|
|
10
16
|
const writesOf = new Map();
|
|
11
17
|
/**
|
|
@@ -29,6 +35,9 @@ class MotionPatch {
|
|
|
29
35
|
get motion() {
|
|
30
36
|
return this.motions.spec();
|
|
31
37
|
}
|
|
38
|
+
get [MOTIONS]() {
|
|
39
|
+
return this.motions;
|
|
40
|
+
}
|
|
32
41
|
at(_phase, subject, setting) {
|
|
33
42
|
const state = this.motions;
|
|
34
43
|
const s = state.slot(subject);
|
|
@@ -58,9 +67,14 @@ class GlidePatch extends MotionPatch {
|
|
|
58
67
|
this.motions.change(subject, { at, v: axes(velocity) });
|
|
59
68
|
}
|
|
60
69
|
}
|
|
61
|
-
/**
|
|
70
|
+
/**
|
|
71
|
+
* The state behind a `'motion'` patch, for an engine that reads it directly; a patch another copy
|
|
72
|
+
* of the same release made counts.
|
|
73
|
+
*/
|
|
62
74
|
export function motionOf(p) {
|
|
63
|
-
return p instanceof MotionPatch
|
|
75
|
+
return p instanceof MotionPatch
|
|
76
|
+
? p.motions
|
|
77
|
+
: p[MOTIONS];
|
|
64
78
|
}
|
|
65
79
|
/**
|
|
66
80
|
* The shapes of the stock motions, each one object over the options it reads, copied out of them so
|
package/dist/motions.d.ts
CHANGED
|
@@ -167,6 +167,8 @@ export declare class Motions<I> {
|
|
|
167
167
|
private unrelease;
|
|
168
168
|
/** Subject `s`'s run as it stands. */
|
|
169
169
|
private runAt;
|
|
170
|
+
/** Whether the patch holds a run for `subject`. */
|
|
171
|
+
holds(subject: I): boolean;
|
|
170
172
|
/** A subject's run as plain data, for a voice leaving memory; undefined where it has none. */
|
|
171
173
|
pack(subject: I): Run | undefined;
|
|
172
174
|
/** Puts a subject's run back as `pack` or a release left it, replacing any it has now. */
|
|
@@ -244,6 +246,8 @@ export declare class Motions<I> {
|
|
|
244
246
|
private queue;
|
|
245
247
|
/** Every change due by `t` applied to a copy of the stretch playing then, committing nothing. */
|
|
246
248
|
private peek;
|
|
249
|
+
/** Subject `s`'s changes waiting, with those a read ahead's tape made filed among them by time. */
|
|
250
|
+
private dueTo;
|
|
247
251
|
private check;
|
|
248
252
|
private flag;
|
|
249
253
|
forget(s: number): void;
|