@msb235/blits 0.10.0 → 0.12.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 +248 -0
- package/README.md +15 -4
- package/dist/arcs.d.ts +40 -0
- package/dist/arcs.js +239 -0
- package/dist/blend.js +1 -5
- package/dist/book.d.ts +2 -0
- package/dist/book.js +27 -3
- package/dist/chain.d.ts +17 -1
- package/dist/chain.js +57 -15
- package/dist/channels.d.ts +2 -0
- package/dist/channels.js +12 -1
- package/dist/clock.d.ts +14 -0
- package/dist/clock.js +9 -1
- package/dist/clone.js +16 -4
- 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 +63 -7
- package/dist/departed.d.ts +8 -3
- package/dist/departed.js +76 -29
- package/dist/due.d.ts +4 -0
- package/dist/due.js +16 -3
- package/dist/easing.d.ts +12 -1
- package/dist/easing.js +7 -1
- package/dist/everyone.d.ts +14 -1
- package/dist/everyone.js +27 -2
- package/dist/fade.d.ts +12 -5
- package/dist/fade.js +57 -19
- package/dist/fill.js +16 -9
- package/dist/fit.d.ts +8 -1
- package/dist/fit.js +7 -3
- package/dist/fold.d.ts +1 -1
- package/dist/fold.js +112 -9
- package/dist/frames.d.ts +2 -0
- package/dist/frames.js +17 -0
- package/dist/gather.js +38 -7
- package/dist/handle.d.ts +7 -2
- package/dist/handle.js +28 -12
- package/dist/held.d.ts +13 -2
- package/dist/held.js +43 -9
- package/dist/history.d.ts +25 -2
- package/dist/history.js +163 -35
- package/dist/hosts.js +45 -17
- package/dist/index.d.ts +5 -2
- package/dist/index.js +3 -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 +31 -7
- package/dist/lanes.js +57 -6
- 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 +105 -27
- package/dist/meet.js +1 -0
- package/dist/mixer.d.ts +35 -6
- package/dist/mixer.js +113 -45
- 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 +35 -12
- package/dist/numbers.d.ts +5 -1
- package/dist/numbers.js +4 -0
- package/dist/owner.d.ts +18 -2
- package/dist/owner.js +44 -3
- package/dist/pack.d.ts +1 -0
- package/dist/pack.js +3 -0
- package/dist/place.d.ts +8 -0
- package/dist/place.js +16 -8
- package/dist/placed.d.ts +19 -0
- package/dist/placed.js +54 -0
- package/dist/project.d.ts +5 -2
- package/dist/project.js +238 -34
- package/dist/pull.js +37 -8
- package/dist/qualify.d.ts +10 -0
- package/dist/qualify.js +60 -3
- package/dist/reading.d.ts +8 -1
- package/dist/reading.js +8 -1
- package/dist/record.d.ts +6 -0
- package/dist/record.js +2 -0
- package/dist/restored.d.ts +69 -17
- package/dist/restored.js +163 -50
- package/dist/revive.d.ts +6 -3
- package/dist/revive.js +11 -3
- package/dist/roster.js +2 -1
- package/dist/rows.js +15 -5
- package/dist/seek.d.ts +7 -2
- package/dist/seek.js +152 -43
- 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/spans.d.ts +5 -0
- package/dist/spans.js +75 -5
- package/dist/store.d.ts +13 -0
- package/dist/store.js +23 -0
- package/dist/strays.js +50 -2
- package/dist/tape.d.ts +29 -4
- package/dist/tape.js +49 -11
- package/dist/touch.js +3 -2
- package/dist/transport.d.ts +11 -2
- package/dist/transport.js +26 -10
- package/dist/types/arc.d.ts +55 -0
- package/dist/types/arc.js +1 -0
- package/dist/types/mix.d.ts +57 -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 +72 -5
- package/dist/types/watch.d.ts +126 -0
- package/dist/types/watch.js +1 -0
- package/dist/types.d.ts +2 -0
- package/dist/unpage.d.ts +6 -1
- package/dist/unpage.js +34 -2
- package/dist/voice.d.ts +93 -16
- package/dist/voice.js +60 -3
- package/dist/watch.d.ts +26 -0
- package/dist/watch.js +441 -0
- package/dist/weigh.d.ts +13 -2
- package/dist/weigh.js +10 -3
- package/docs/schema.html +106 -38
- package/package.json +10 -4
package/dist/history.js
CHANGED
|
@@ -9,14 +9,18 @@ import { within } from './transport.js';
|
|
|
9
9
|
import { none } from './voice.js';
|
|
10
10
|
/** The last entry taken before `t`, or at it where `inclusive` says so. */
|
|
11
11
|
export function last(list, t, inclusive) {
|
|
12
|
+
return list[lastIndex(list, t, inclusive)];
|
|
13
|
+
}
|
|
14
|
+
/** Where `last` finds its entry in the list, -1 where it finds none. */
|
|
15
|
+
export function lastIndex(list, t, inclusive) {
|
|
12
16
|
let lo = 0;
|
|
13
17
|
let hi = list.length - 1;
|
|
14
|
-
let found;
|
|
18
|
+
let found = -1;
|
|
15
19
|
while (lo <= hi) {
|
|
16
20
|
const mid = (lo + hi) >> 1;
|
|
17
21
|
const e = list[mid];
|
|
18
22
|
if (e.at < t || (e.at === t && (typeof inclusive === 'function' ? inclusive(e) : inclusive))) {
|
|
19
|
-
found =
|
|
23
|
+
found = mid;
|
|
20
24
|
lo = mid + 1;
|
|
21
25
|
}
|
|
22
26
|
else
|
|
@@ -86,41 +90,125 @@ export function noted(mix, voice) {
|
|
|
86
90
|
pageOut(mix, 'controls', voice.id, undefined, log.splice(0, drop), copied, reach);
|
|
87
91
|
}
|
|
88
92
|
const copied = (e) => ({ ...e });
|
|
93
|
+
const nothing = Object.freeze([]);
|
|
89
94
|
/** Takes off the front of a list kept by time what a read back to `reach` no longer needs. */
|
|
90
95
|
function older(list, reach) {
|
|
96
|
+
const n = past(list, reach);
|
|
97
|
+
return n === 0 ? nothing : list.splice(0, n);
|
|
98
|
+
}
|
|
99
|
+
/** How many entries at the front of a list kept by time a read back to `reach` no longer needs. */
|
|
100
|
+
function past(list, reach) {
|
|
91
101
|
let n = 0;
|
|
92
102
|
while (n + 1 < list.length && list[n + 1].at <= reach)
|
|
93
103
|
n++;
|
|
94
|
-
return n
|
|
95
|
-
}
|
|
96
|
-
/** A copy of a
|
|
97
|
-
export function
|
|
98
|
-
let kept = null;
|
|
99
|
-
if (h.kept !== null) {
|
|
100
|
-
kept = new Map();
|
|
101
|
-
for (const [owner, value] of h.kept)
|
|
102
|
-
kept.set(owner, clone(value));
|
|
103
|
-
}
|
|
104
|
+
return n;
|
|
105
|
+
}
|
|
106
|
+
/** A copy of the state a voice's patch keeps for a subject. */
|
|
107
|
+
export function copyState(voice, state) {
|
|
104
108
|
const patch = voice.patch;
|
|
105
|
-
return
|
|
106
|
-
|
|
109
|
+
return patch.clone ? patch.clone(state) : clone(state);
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* A copy of a subject's record that shares nothing a read could change; with `shared`, its state is
|
|
113
|
+
* the record's own, for a copy that takes one of its own before it steps.
|
|
114
|
+
*/
|
|
115
|
+
export function copyHeld(voice, h, shared = false) {
|
|
116
|
+
const kept = keptOf(h);
|
|
117
|
+
// Every field in `record`'s order, written out: a spread with fields after it costs a third more.
|
|
118
|
+
return unread({
|
|
119
|
+
reaches: h.reaches,
|
|
120
|
+
delay: h.delay,
|
|
121
|
+
since: h.since,
|
|
122
|
+
shown: h.shown,
|
|
123
|
+
weight: h.weight,
|
|
124
|
+
rested: h.rested,
|
|
107
125
|
bands: h.bands === null ? null : h.bands.slice(),
|
|
108
|
-
state: h.state === undefined
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
probed:
|
|
112
|
-
delta:
|
|
113
|
-
phase:
|
|
114
|
-
seeks:
|
|
126
|
+
state: h.state === undefined || shared ? h.state : copyState(voice, h.state),
|
|
127
|
+
stepped: h.stepped,
|
|
128
|
+
ticks: h.ticks,
|
|
129
|
+
probed: h.probed,
|
|
130
|
+
delta: h.delta,
|
|
131
|
+
phase: h.phase,
|
|
132
|
+
seeks: h.seeks,
|
|
133
|
+
rebuilt: h.rebuilt,
|
|
134
|
+
copied: h.copied,
|
|
115
135
|
base: h.base === undefined ? undefined : clone(h.base),
|
|
116
136
|
slope: h.slope === undefined ? undefined : clone(h.slope),
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
137
|
+
kept,
|
|
138
|
+
unkept: h.unkept?.map(clone),
|
|
139
|
+
snaps: h.snaps,
|
|
140
|
+
from: h.from,
|
|
141
|
+
unknown: h.unknown,
|
|
142
|
+
inputs: h.inputs,
|
|
143
|
+
replay: h.replay,
|
|
144
|
+
voice: h.voice,
|
|
145
|
+
next: h.next,
|
|
146
|
+
version: h.version,
|
|
147
|
+
loci: h.loci,
|
|
148
|
+
slot: h.slot,
|
|
149
|
+
run: h.run,
|
|
150
|
+
freed: h.freed,
|
|
151
|
+
// In no store, so stamped by none: a store trusts its stamp to say a record is still its own.
|
|
152
|
+
restored: null,
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
/** A copy of what a record keeps through `setting.keep`. */
|
|
156
|
+
function keptOf(h) {
|
|
157
|
+
if (h.kept === null)
|
|
158
|
+
return null;
|
|
159
|
+
const kept = new Map();
|
|
160
|
+
for (const [owner, value] of h.kept)
|
|
161
|
+
kept.set(owner, clone(value));
|
|
162
|
+
return kept;
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* As `copyHeld`, written over `into`, a record nothing else holds, so a copy made in its place
|
|
166
|
+
* leaves nothing new to collect. test/history.test.ts holds this to `copyHeld` field by field.
|
|
167
|
+
*/
|
|
168
|
+
export function copyHeldInto(voice, h, into, shared = false) {
|
|
169
|
+
into.reaches = h.reaches;
|
|
170
|
+
into.delay = h.delay;
|
|
171
|
+
into.since = h.since;
|
|
172
|
+
into.shown = h.shown;
|
|
173
|
+
into.weight = h.weight;
|
|
174
|
+
into.rested = h.rested;
|
|
175
|
+
into.bands = h.bands === null ? null : h.bands.slice();
|
|
176
|
+
into.state = h.state === undefined || shared ? h.state : copyState(voice, h.state);
|
|
177
|
+
into.stepped = h.stepped;
|
|
178
|
+
into.ticks = h.ticks;
|
|
179
|
+
into.rebuilt = h.rebuilt;
|
|
180
|
+
into.copied = h.copied;
|
|
181
|
+
into.base = h.base === undefined ? undefined : clone(h.base);
|
|
182
|
+
into.slope = h.slope === undefined ? undefined : clone(h.slope);
|
|
183
|
+
into.kept = keptOf(h);
|
|
184
|
+
into.unkept = h.unkept?.map(clone);
|
|
185
|
+
into.from = h.from;
|
|
186
|
+
into.unknown = h.unknown;
|
|
187
|
+
into.replay = h.replay;
|
|
188
|
+
into.voice = h.voice;
|
|
189
|
+
into.slot = h.slot;
|
|
190
|
+
into.run = h.run;
|
|
191
|
+
into.freed = h.freed;
|
|
192
|
+
into.restored = null;
|
|
193
|
+
// `probed`, `delta`, `phase`, `seeks`, `snaps`, `inputs`, `next`, `version` and `loci` as
|
|
194
|
+
// `unread` leaves them.
|
|
195
|
+
return unread(into);
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* Lets go of what a record holds from its last probe and its place in a chain, as a copy of it
|
|
199
|
+
* starts: a seek puts back a record nothing else holds this way, in place of a copy.
|
|
200
|
+
*/
|
|
201
|
+
export function unread(h) {
|
|
202
|
+
h.probed = Number.NaN;
|
|
203
|
+
h.delta = null;
|
|
204
|
+
h.phase = 0;
|
|
205
|
+
h.seeks = 0;
|
|
206
|
+
h.snaps = undefined;
|
|
207
|
+
h.inputs = undefined;
|
|
208
|
+
h.next = null;
|
|
209
|
+
h.version = Number.NaN;
|
|
210
|
+
h.loci = null;
|
|
211
|
+
return h;
|
|
124
212
|
}
|
|
125
213
|
/** Under `history` with `inputs`, copies the host fields any voice reads, once a frame, when they changed. */
|
|
126
214
|
export function recordHost(mix, now) {
|
|
@@ -160,6 +248,23 @@ export function record(mix, voice, subject, held, value) {
|
|
|
160
248
|
if (out.length > 0)
|
|
161
249
|
pageOut(mix, 'input', voice.id, mix.keys?.key(subject), out, (e) => e.value, reach);
|
|
162
250
|
}
|
|
251
|
+
/** How far apart, in mix ms, a record's copies are kept. */
|
|
252
|
+
export function apart(history) {
|
|
253
|
+
return history.every ?? 200;
|
|
254
|
+
}
|
|
255
|
+
/**
|
|
256
|
+
* A copy of a record to keep now, written over a spare the latest seek back cut, or one just let go
|
|
257
|
+
* of, where it has one.
|
|
258
|
+
*/
|
|
259
|
+
function snapOf(mix, voice, held, gone) {
|
|
260
|
+
const spare = voice.spares?.pop() ?? gone;
|
|
261
|
+
if (spare === undefined)
|
|
262
|
+
return { at: mix.now, seq: mix.transport.seq, held: copyHeld(voice, held) };
|
|
263
|
+
spare.at = mix.now;
|
|
264
|
+
spare.seq = mix.transport.seq;
|
|
265
|
+
copyHeldInto(voice, held, spare.held);
|
|
266
|
+
return spare;
|
|
267
|
+
}
|
|
163
268
|
/** Under `history`, keeps a copy of a stateful voice's record for this subject every so often. */
|
|
164
269
|
export function remember(mix, voice, subject, held) {
|
|
165
270
|
const history = mix.opts.history;
|
|
@@ -172,13 +277,34 @@ export function remember(mix, voice, subject, held) {
|
|
|
172
277
|
const snaps = held.snaps ?? [];
|
|
173
278
|
held.snaps = snaps;
|
|
174
279
|
const prev = snaps[snaps.length - 1];
|
|
175
|
-
if (prev !== undefined && mix.now - prev.at < (history
|
|
280
|
+
if (prev !== undefined && mix.now - prev.at < apart(history))
|
|
176
281
|
return;
|
|
177
|
-
|
|
282
|
+
// What falls out is the same before the push as after it, since the copy pushed is newer.
|
|
178
283
|
const reach = mix.now - history.ms;
|
|
179
|
-
const
|
|
180
|
-
|
|
181
|
-
|
|
284
|
+
const n = past(snaps, reach);
|
|
285
|
+
let gone;
|
|
286
|
+
if (n > 0) {
|
|
287
|
+
if (mix.transport.pager !== null)
|
|
288
|
+
pageOut(mix, 'snap', voice.id, mix.keys?.key(subject), snaps.slice(0, n), (e) => packHeld(voice, e.held), reach);
|
|
289
|
+
gone = reusable(snaps, n);
|
|
290
|
+
// `shift` moves the list's start where `splice` copies what it keeps and allocates what it cuts.
|
|
291
|
+
if (n === 1)
|
|
292
|
+
snaps.shift();
|
|
293
|
+
else
|
|
294
|
+
snaps.splice(0, n);
|
|
295
|
+
}
|
|
296
|
+
snaps.push(snapOf(mix, voice, held, gone));
|
|
297
|
+
}
|
|
298
|
+
/** The first of a list's first `n` copies no other record's list holds, made ready to be written over. */
|
|
299
|
+
function reusable(snaps, n) {
|
|
300
|
+
for (let i = 0; i < n; i++) {
|
|
301
|
+
const snap = snaps[i];
|
|
302
|
+
if (snap.shared)
|
|
303
|
+
continue;
|
|
304
|
+
snap.ahead = undefined;
|
|
305
|
+
return snap;
|
|
306
|
+
}
|
|
307
|
+
return undefined;
|
|
182
308
|
}
|
|
183
309
|
/**
|
|
184
310
|
* Under history, keeps the record a subject had when it left a voice, faded out of it or dropped,
|
|
@@ -194,13 +320,15 @@ export function leave(mix, voice, subject, held) {
|
|
|
194
320
|
const keys = mix.keys;
|
|
195
321
|
left.expire(reach, keys === null
|
|
196
322
|
? null
|
|
197
|
-
: (e) => pageOut(mix, 'left', voice.id, keys.key(e.subject), [e], (x) => [packHeld(voice, x.held), x.sync], reach));
|
|
323
|
+
: (e) => pageOut(mix, 'left', voice.id, keys.key(e.subject), [e], (x) => [packHeld(voice, x.held), x.sync, x.ramp, x.parted], reach));
|
|
198
324
|
left.push({
|
|
199
325
|
subject,
|
|
200
326
|
at: Number.isNaN(mix.now) ? Number.NEGATIVE_INFINITY : mix.now,
|
|
201
327
|
seq: mix.transport.seq,
|
|
202
|
-
sync: mix.syncing,
|
|
328
|
+
sync: mix.syncing || !mix.looked,
|
|
203
329
|
held,
|
|
330
|
+
ramp: voice.parts?.get(subject) ?? null,
|
|
331
|
+
parted: voice.parted?.get(subject) ?? null,
|
|
204
332
|
});
|
|
205
333
|
}
|
|
206
334
|
/**
|
package/dist/hosts.js
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
|
+
import { release } from './arcs.js';
|
|
1
2
|
import { Book } from './book.js';
|
|
3
|
+
import { headOf } from './chain.js';
|
|
2
4
|
import { numericOf } from './channels.js';
|
|
5
|
+
import { stepOf } from './clock.js';
|
|
3
6
|
import { crowdable } from './crowd.js';
|
|
4
7
|
import { schedule } from './due.js';
|
|
5
8
|
import { unshare, unsight, weightAt } from './everyone.js';
|
|
@@ -14,6 +17,7 @@ import { descendants, ownWeight, signalled } from './owner.js';
|
|
|
14
17
|
import { localNow } from './place.js';
|
|
15
18
|
import { reading } from './reading.js';
|
|
16
19
|
import { heard } from './signals.js';
|
|
20
|
+
import { cuedById } from './strays.js';
|
|
17
21
|
import { record } from './tape.js';
|
|
18
22
|
import { unreach } from './unreached.js';
|
|
19
23
|
export function book(mix, opts) {
|
|
@@ -69,31 +73,44 @@ export function laneHost(mix) {
|
|
|
69
73
|
},
|
|
70
74
|
passes: (was, w) => mix.passes(was, w),
|
|
71
75
|
naming: (subject) => (mix.naming === 0 ? undefined : mix.named.get(subject)),
|
|
72
|
-
slotOf: (subject) => mix
|
|
76
|
+
slotOf: (subject) => headOf(mix, subject)?.slot ?? -1,
|
|
73
77
|
envelope: (voice, since) => voice.owner === null
|
|
74
78
|
? mix.envelope(voice, mix.now, since)
|
|
75
79
|
: mix.envelope(voice, mix.now, since) * mix.ownedBy(voice, undefined),
|
|
76
80
|
parting: (voice, subject) => mix.parting(voice, subject, mix.now),
|
|
77
|
-
ready: (voice, subject, held
|
|
78
|
-
|
|
79
|
-
|
|
81
|
+
ready: (voice, subject, held) => {
|
|
82
|
+
const arg = mix.lanes.arg;
|
|
83
|
+
const primed = mix.primed;
|
|
84
|
+
primed[0 /* Primed.NOW */] = mix.now;
|
|
85
|
+
primed[1 /* Primed.ELAPSED */] = arg[1 /* Arg.ELAPSED */];
|
|
86
|
+
primed[2 /* Primed.PASS */] = arg[3 /* Arg.PASS */];
|
|
87
|
+
mix.prime(voice, held);
|
|
88
|
+
voice.setting.weight = arg[0 /* Arg.WEIGHT */];
|
|
80
89
|
mix.sending.voice = voice;
|
|
81
90
|
mix.sending.subject = subject;
|
|
82
91
|
},
|
|
83
|
-
signal: (voice, subject, held,
|
|
92
|
+
signal: (voice, subject, held, slot) => {
|
|
93
|
+
const arg = mix.lanes.arg;
|
|
84
94
|
// A blend member after the first takes the read already made, with no setting to fill.
|
|
85
95
|
const read = voice.blend === null ? Number.NaN : mix.laneRead(voice, slot);
|
|
86
|
-
if (!Number.isNaN(read))
|
|
87
|
-
|
|
88
|
-
|
|
96
|
+
if (!Number.isNaN(read)) {
|
|
97
|
+
arg[0 /* Arg.WEIGHT */] = read;
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
const primed = mix.primed;
|
|
101
|
+
primed[0 /* Primed.NOW */] = mix.now;
|
|
102
|
+
primed[1 /* Primed.ELAPSED */] = arg[1 /* Arg.ELAPSED */];
|
|
103
|
+
primed[2 /* Primed.PASS */] = arg[3 /* Arg.PASS */];
|
|
104
|
+
mix.prime(voice, held);
|
|
89
105
|
const kept = reading.kept;
|
|
90
106
|
const base = mix.base(voice, subject, mix.now, held, slot);
|
|
91
107
|
if (reading.kept !== kept && !voice.keeping)
|
|
92
108
|
stateful(mix, voice);
|
|
93
|
-
|
|
109
|
+
arg[0 /* Arg.WEIGHT */] = base;
|
|
94
110
|
},
|
|
95
111
|
horizon: (voice, delay) => {
|
|
96
|
-
|
|
112
|
+
if (voice.motion !== undefined)
|
|
113
|
+
reading.horizon = mix.horizonFor(voice, delay, mix.now);
|
|
97
114
|
},
|
|
98
115
|
keeps: mix.opts.history !== undefined,
|
|
99
116
|
after: (voice, subject, held) => remember(mix, voice, subject, held),
|
|
@@ -161,8 +178,7 @@ function seekDoubt(mix, voice, rebuild) {
|
|
|
161
178
|
return 'stepped';
|
|
162
179
|
if (patch.step === undefined)
|
|
163
180
|
return 'exact';
|
|
164
|
-
const
|
|
165
|
-
const fixed = tick !== undefined && tick > 0 && !mix.reducedNow && mix.opts.maxDt === undefined;
|
|
181
|
+
const fixed = stepOf(mix, voice) !== undefined && mix.opts.maxDt === undefined;
|
|
166
182
|
return fixed ? 'exact' : 'stepped';
|
|
167
183
|
}
|
|
168
184
|
/** A voice's patch kept state on a record: it is stateful from now on. */
|
|
@@ -223,21 +239,33 @@ function handleHost(mix) {
|
|
|
223
239
|
beginFade(mix, voice, (opts ?? {}));
|
|
224
240
|
},
|
|
225
241
|
rise: (voice, opts) => beginRise(mix, voice, opts ?? {}),
|
|
242
|
+
release: (voice) => release(mix, voice),
|
|
226
243
|
weightOf: (voice, subject) => {
|
|
227
244
|
if (voice.state === 'done')
|
|
228
245
|
return 0;
|
|
229
246
|
if (voice.holding !== null)
|
|
230
247
|
return ownWeight(voice, mix.now, mix.reducedNow, (o) => mix.ownerBase(o, subject));
|
|
231
|
-
const slot = mix.lanes === null ? -1 : (mix
|
|
248
|
+
const slot = mix.lanes === null ? -1 : (headOf(mix, subject)?.slot ?? -1);
|
|
232
249
|
if (voice.laned && mix.lanes !== null) {
|
|
233
250
|
const w = mix.lanes.weightOf(voice.id, slot);
|
|
234
251
|
if (w !== undefined)
|
|
235
252
|
return w;
|
|
236
253
|
}
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
254
|
+
const held = voice.sharing
|
|
255
|
+
? undefined
|
|
256
|
+
: voice.subjects.get(subject);
|
|
257
|
+
// A voice that stopped sharing makes a subject's record at its next read, from what it kept.
|
|
258
|
+
return held !== undefined ? held.weight : weightAt(voice, slot);
|
|
240
259
|
},
|
|
241
|
-
record: (label, again) => record(mix, label,
|
|
260
|
+
record: (h, label, again) => record(mix, label, (stage) => {
|
|
261
|
+
const to = stage.mix(mix);
|
|
262
|
+
if (to === mix)
|
|
263
|
+
again(h);
|
|
264
|
+
else if (to !== undefined) {
|
|
265
|
+
const voice = cuedById(to, h.id);
|
|
266
|
+
if (voice !== undefined)
|
|
267
|
+
again(handle(to, voice));
|
|
268
|
+
}
|
|
269
|
+
}),
|
|
242
270
|
};
|
|
243
271
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -2,6 +2,8 @@ export type { NumberOptions, Vec } from './channels.js';
|
|
|
2
2
|
export { kit, last, max, mul, sum, vec } from './channels.js';
|
|
3
3
|
export type { ColorOptions } from './color.js';
|
|
4
4
|
export { color, css, mixHex, oklab, toHex } from './color.js';
|
|
5
|
+
export type { Curve } from './easing.js';
|
|
6
|
+
export { curve } from './easing.js';
|
|
5
7
|
export type { Claim, Fit, FitPlan, Order, SpanClaim, Strength } from './fit.js';
|
|
6
8
|
export { conclude, condense, lax, layout, overrun, pipe, plain, shed, } from './fit.js';
|
|
7
9
|
export { mix, mixer } from './mixer.js';
|
|
@@ -12,11 +14,12 @@ export type { KeysOptions, PatchOptions } from './patch.js';
|
|
|
12
14
|
export { keys, patch } from './patch.js';
|
|
13
15
|
export type { AngleOptions } from './rotation.js';
|
|
14
16
|
export { angle, quat } from './rotation.js';
|
|
15
|
-
export { gate, input, lag, level, peak, slew } from './signals.js';
|
|
17
|
+
export { gate, input, lag, level, peak, share, slew } from './signals.js';
|
|
16
18
|
export { fold } from './stack.js';
|
|
17
19
|
export type { Ticked, Ticker, TickerOptions } from './ticker.js';
|
|
18
20
|
export { ticker } from './ticker.js';
|
|
19
21
|
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';
|
|
22
|
+
export type { Anchor, ArcHandle, ArcPart, ArcSpec, ArcVoice, BlendMember, BookedHit, Booker, BookOptions, Channel, Columns, Described, Doubt, Easing, Engine, FadeOptions, FadeSpec, FitResult, Handle, HistoryOptions, HistoryStore, Hit, Hold, Keyframe, Kit, Mark, Marked, MarkName, Mix, MixOptions, MotionSpec, OwnerSpec, Paged, PagedStream, Patch, Placement, Projection, Query, Registry, SeekOptions, Sent, Setting, Signal, SpanHandle, SpanHints, SpanSpec, Tape, TapeBranch, TapeMaker, TapeOp, Transport, TransportOptions, TransportProjection, VoiceSpec, Watched, WatchedFrame, } from './types.js';
|
|
23
|
+
export { watched } from './watch.js';
|
|
21
24
|
export type { WaveOptions, WaveShape } from './wave.js';
|
|
22
25
|
export { wave, waveAt } from './wave.js';
|
package/dist/index.js
CHANGED
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
export { kit, last, max, mul, sum, vec } from './channels.js';
|
|
2
2
|
export { color, css, mixHex, oklab, toHex } from './color.js';
|
|
3
|
+
export { curve } from './easing.js';
|
|
3
4
|
export { conclude, condense, lax, layout, overrun, pipe, plain, shed, } from './fit.js';
|
|
4
5
|
export { mix, mixer } from './mixer.js';
|
|
5
6
|
export { glide, spring, tween } from './motion.js';
|
|
6
7
|
export { HistoryMiss } from './paging.js';
|
|
7
8
|
export { keys, patch } from './patch.js';
|
|
8
9
|
export { angle, quat } from './rotation.js';
|
|
9
|
-
export { gate, input, lag, level, peak, slew } from './signals.js';
|
|
10
|
+
export { gate, input, lag, level, peak, share, slew } from './signals.js';
|
|
10
11
|
export { fold } from './stack.js';
|
|
11
12
|
export { ticker } from './ticker.js';
|
|
12
13
|
export { transport } from './transport.js';
|
|
14
|
+
export { watched } from './watch.js';
|
|
13
15
|
export { wave, waveAt } from './wave.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. */
|
|
@@ -32,14 +33,18 @@ export interface LaneHost<I, O> {
|
|
|
32
33
|
envelope(voice: Voice<I, O>, since: number): number;
|
|
33
34
|
/** What a subject's own ramp out of the voice leaves of its weight, 0..1. */
|
|
34
35
|
parting(voice: Voice<I, O>, subject: I): number;
|
|
35
|
-
/** Fills the voice's setting and the send target for a call to its patch. */
|
|
36
|
-
ready(voice: Voice<I, O>, subject: I, held: Subject<unknown>, elapsed: number, pass: number, weight: number): void;
|
|
37
36
|
/**
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
* stateful if the signal keeps state. `slot` is the subject's number.
|
|
37
|
+
* Fills the voice's setting and the send target for a call to its patch, at the voice time, pass
|
|
38
|
+
* and weight in `Arg.ELAPSED`, `Arg.PASS` and `Arg.WEIGHT`.
|
|
41
39
|
*/
|
|
42
|
-
|
|
40
|
+
ready(voice: Voice<I, O>, subject: I, held: Subject<unknown>): void;
|
|
41
|
+
/**
|
|
42
|
+
* A voice with a signal weight: the signal's value for a subject the general path has met, at the
|
|
43
|
+
* voice time and pass in `Arg.ELAPSED` and `Arg.PASS`, left in `Arg.WEIGHT` before the fade and
|
|
44
|
+
* ramp `weighed` applies, with the setting filled in for the call; makes the voice stateful if the
|
|
45
|
+
* signal keeps state. `slot` is the subject's number.
|
|
46
|
+
*/
|
|
47
|
+
signal(voice: Voice<I, O>, subject: I, held: Subject<unknown>, slot: number): void;
|
|
43
48
|
/** Whether a rest-less channel's contribution passes the band, as the general path decides it. */
|
|
44
49
|
passes(was: boolean | undefined, w: number): boolean;
|
|
45
50
|
/** Sets `reading.horizon` for the voice and a subject delayed `delay` voice ms. */
|
|
@@ -65,6 +70,13 @@ export declare class Lanes<I, O> implements Watcher {
|
|
|
65
70
|
/** The crowds of single-subject voices, one per list of channels, and which holds each voice. */
|
|
66
71
|
crowds: Crowd<I, O>[];
|
|
67
72
|
readonly crowdOf: Map<number, Crowd<I, O>>;
|
|
73
|
+
/** Which crowd voices' rows are placed on each subject. */
|
|
74
|
+
readonly placed: Placed;
|
|
75
|
+
/**
|
|
76
|
+
* By subject number, the mix's `relinks` when the subject's chain was last made stale by itself,
|
|
77
|
+
* so `pull` tells a head it remembers is stale without reading the head.
|
|
78
|
+
*/
|
|
79
|
+
unlinked: Float64Array<ArrayBuffer>;
|
|
68
80
|
/** Whether two crowds share a channel, so their rows must interleave in voice order. */
|
|
69
81
|
overlap: boolean;
|
|
70
82
|
/** The loci whose voices are on lanes, every member of each on one. */
|
|
@@ -168,7 +180,12 @@ export declare class Lanes<I, O> implements Watcher {
|
|
|
168
180
|
lately: number;
|
|
169
181
|
filledVersion: number;
|
|
170
182
|
qualifiedVersion: number;
|
|
171
|
-
|
|
183
|
+
/**
|
|
184
|
+
* With `from`, the lanes a seek replaces: their subject numbers carry over, each met as newly
|
|
185
|
+
* numbered when its chain is next linked (`renew`), so a seek makes no new weak reference per
|
|
186
|
+
* subject.
|
|
187
|
+
*/
|
|
188
|
+
constructor(host: LaneHost<I, O>, from?: Lanes<I, O>);
|
|
172
189
|
/** A change that may move a voice on or off its lane: qualify again, then fill again. */
|
|
173
190
|
invalidate(): void;
|
|
174
191
|
/** A change to a laned voice's weight or clock between probes: fill again. */
|
|
@@ -181,6 +198,13 @@ export declare class Lanes<I, O> implements Watcher {
|
|
|
181
198
|
/** Numbers a subject the mix sees for the first time. */
|
|
182
199
|
number(subject: I): number;
|
|
183
200
|
release(slot: number): void;
|
|
201
|
+
/** Whether `slot` is a number a seek carried over whose chain has not been linked since. */
|
|
202
|
+
sought(slot: number): boolean;
|
|
203
|
+
/**
|
|
204
|
+
* A chain is being linked for the subject at `slot`: one a seek carried over is from now on as
|
|
205
|
+
* `number` leaves a subject it numbers. Says whether it was one.
|
|
206
|
+
*/
|
|
207
|
+
renew(slot: number): boolean;
|
|
184
208
|
/** A voice faded a subject out of itself alone: its lane lets the subject's position go. */
|
|
185
209
|
part(id: number, slot: number): void;
|
|
186
210
|
/** A voice brought a subject back: its next probe meets every lane again, as it met them first. */
|
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. */
|
|
@@ -126,9 +134,27 @@ export class Lanes {
|
|
|
126
134
|
lately = -1;
|
|
127
135
|
filledVersion = Number.NaN;
|
|
128
136
|
qualifiedVersion = Number.NaN;
|
|
129
|
-
|
|
137
|
+
/**
|
|
138
|
+
* With `from`, the lanes a seek replaces: their subject numbers carry over, each met as newly
|
|
139
|
+
* numbered when its chain is next linked (`renew`), so a seek makes no new weak reference per
|
|
140
|
+
* subject.
|
|
141
|
+
*/
|
|
142
|
+
constructor(host, from) {
|
|
130
143
|
this.host = host;
|
|
131
|
-
|
|
144
|
+
if (from === undefined) {
|
|
145
|
+
this.numbers = new Numbers(this);
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
this.numbers = from.numbers;
|
|
149
|
+
this.numbers.adopt(this);
|
|
150
|
+
this.live = from.live;
|
|
151
|
+
const size = this.numbers.size;
|
|
152
|
+
this.grow(size);
|
|
153
|
+
const per = this.per;
|
|
154
|
+
for (let slot = 0; slot < size; slot++) {
|
|
155
|
+
per[slot * 6 /* Per.SLOT */ + 0 /* Per.FILLED */] = Number.NaN;
|
|
156
|
+
per[slot * 6 /* Per.SLOT */ + 1 /* Per.SEEN */] = -1;
|
|
157
|
+
}
|
|
132
158
|
}
|
|
133
159
|
/** A change that may move a voice on or off its lane: qualify again, then fill again. */
|
|
134
160
|
invalidate() {
|
|
@@ -165,6 +191,22 @@ export class Lanes {
|
|
|
165
191
|
release(slot) {
|
|
166
192
|
this.numbers.release(slot);
|
|
167
193
|
}
|
|
194
|
+
/** Whether `slot` is a number a seek carried over whose chain has not been linked since. */
|
|
195
|
+
sought(slot) {
|
|
196
|
+
return Number.isNaN(this.per[slot * 6 /* Per.SLOT */ + 0 /* Per.FILLED */]);
|
|
197
|
+
}
|
|
198
|
+
/**
|
|
199
|
+
* A chain is being linked for the subject at `slot`: one a seek carried over is from now on as
|
|
200
|
+
* `number` leaves a subject it numbers. Says whether it was one.
|
|
201
|
+
*/
|
|
202
|
+
renew(slot) {
|
|
203
|
+
if (!this.sought(slot))
|
|
204
|
+
return false;
|
|
205
|
+
this.per[slot * 6 /* Per.SLOT */ + 0 /* Per.FILLED */] = this.fills;
|
|
206
|
+
if (this.filling)
|
|
207
|
+
this.late.push(slot);
|
|
208
|
+
return true;
|
|
209
|
+
}
|
|
168
210
|
/** A voice faded a subject out of itself alone: its lane lets the subject's position go. */
|
|
169
211
|
part(id, slot) {
|
|
170
212
|
const lane = this.byId.get(id);
|
|
@@ -374,10 +416,15 @@ export class Lanes {
|
|
|
374
416
|
this.live--;
|
|
375
417
|
for (const lane of this.lanes)
|
|
376
418
|
lane.remove(slot);
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
419
|
+
const placed = this.placed;
|
|
420
|
+
for (let id = placed.any(slot); id !== 0; id = placed.any(slot)) {
|
|
421
|
+
const c = this.crowdOf.get(id);
|
|
422
|
+
const p = c?.rowOf.get(id);
|
|
423
|
+
if (c !== undefined && p !== undefined && c.list[p] === slot)
|
|
424
|
+
unplace(this, c, p);
|
|
425
|
+
else
|
|
426
|
+
placed.remove(slot, id);
|
|
427
|
+
}
|
|
381
428
|
if (slot < this.cap)
|
|
382
429
|
this.per[slot * 6 /* Per.SLOT */ + 0 /* Per.FILLED */] = this.fills;
|
|
383
430
|
this.subjects[slot] = undefined;
|
|
@@ -390,6 +437,10 @@ export class Lanes {
|
|
|
390
437
|
per.set(this.per);
|
|
391
438
|
this.per = per;
|
|
392
439
|
this.owed.grow(cap);
|
|
440
|
+
this.placed.grow(cap);
|
|
441
|
+
const unlinked = new Float64Array(cap);
|
|
442
|
+
unlinked.set(this.unlinked);
|
|
443
|
+
this.unlinked = unlinked;
|
|
393
444
|
for (const ch of this.laned) {
|
|
394
445
|
const values = new Float64Array(cap * ch.axes);
|
|
395
446
|
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>;
|