@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/fold.js
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { foldNumber } from './channels.js';
|
|
2
|
+
import { passAt, phaseAt, phaseBefore, silent, stepOf } from './clock.js';
|
|
2
3
|
import { copy as copyValue, first } from './clone.js';
|
|
3
4
|
import { open as openRecord, shut as shutRecord, weighAt } from './everyone.js';
|
|
4
|
-
import { recordHost, remember } from './history.js';
|
|
5
|
+
import { apart, copyState, recordHost, remember } from './history.js';
|
|
5
6
|
import { stateful } from './hosts.js';
|
|
6
7
|
import { locusScratch } from './locus.js';
|
|
7
8
|
import { moved } from './moved.js';
|
|
8
9
|
import { readKeys } from './patch.js';
|
|
9
10
|
import { reading } from './reading.js';
|
|
11
|
+
import { checkWrites } from './voice.js';
|
|
10
12
|
// V8 reads a local const as a constant, where it reads an import from its module on every call.
|
|
11
13
|
const copy = copyValue;
|
|
12
14
|
const open = openRecord;
|
|
@@ -74,12 +76,18 @@ export function contribution(voice, subject, now, held) {
|
|
|
74
76
|
if (early && !voice.holdsFirst)
|
|
75
77
|
return null;
|
|
76
78
|
const elapsed = voice.shownAt(raw);
|
|
79
|
+
if (Number.isNaN(elapsed))
|
|
80
|
+
return null;
|
|
77
81
|
// A frozen subject's clock stands still at the edge it freezes at: -1 before, 1 after, 0 playing.
|
|
78
82
|
const still = early ? -1 : voice.freezesAfter && raw > voice.span ? 1 : 0;
|
|
79
83
|
const duration = voice.duration;
|
|
80
84
|
const phase = elapsed < 0 ? phaseBefore(elapsed, duration) : phaseAt(elapsed, duration, voice.passes);
|
|
81
85
|
const pass = passAt(elapsed, duration, voice.passes);
|
|
82
|
-
|
|
86
|
+
const primed = this.primed;
|
|
87
|
+
primed[0 /* Primed.NOW */] = now;
|
|
88
|
+
primed[1 /* Primed.ELAPSED */] = elapsed;
|
|
89
|
+
primed[2 /* Primed.PASS */] = pass;
|
|
90
|
+
this.prime(voice, held);
|
|
83
91
|
const setting = voice.setting;
|
|
84
92
|
// A signal that keeps state makes its voice stateful, as a patch that does: a lane would call
|
|
85
93
|
// it for subjects no probe asked about.
|
|
@@ -99,8 +107,9 @@ export function contribution(voice, subject, now, held) {
|
|
|
99
107
|
const history = this.opts.history;
|
|
100
108
|
if (history?.inputs && voice.patch.reads !== undefined && !this.projecting)
|
|
101
109
|
recordHost(this, now);
|
|
102
|
-
|
|
103
|
-
|
|
110
|
+
// Only a motion reads it.
|
|
111
|
+
if (voice.motion !== undefined)
|
|
112
|
+
reading.horizon = this.horizonFor(voice, held.delay, now);
|
|
104
113
|
if (voice.patch.step && held.probed !== now) {
|
|
105
114
|
// Frozen after, it steps once more to where its last pass ended, and no further; run back
|
|
106
115
|
// past its start, to where it reached it. Before a start it has yet to reach, it does not step.
|
|
@@ -109,12 +118,20 @@ export function contribution(voice, subject, now, held) {
|
|
|
109
118
|
: still === 1
|
|
110
119
|
? voice.timeAt(held.delay + voice.span)
|
|
111
120
|
: voice.ranBackTo(held.delay, now);
|
|
121
|
+
// Never probed, it may be a record a seek put back, sharing its copy's state until it steps.
|
|
122
|
+
const snaps = held.snaps;
|
|
123
|
+
const sought = Number.isNaN(held.probed) && snaps !== undefined;
|
|
112
124
|
if (!Number.isNaN(to)) {
|
|
113
125
|
if (held.stepped < held.since) {
|
|
114
126
|
held.stepped = held.since;
|
|
115
127
|
setting.dt = this.capped(now - held.since);
|
|
116
128
|
}
|
|
117
|
-
|
|
129
|
+
const tick = stepOf(this, voice);
|
|
130
|
+
if (tick !== undefined && sought)
|
|
131
|
+
ahead(this, voice, subject, held, snaps, tick, to);
|
|
132
|
+
if (sought)
|
|
133
|
+
own(voice, held);
|
|
134
|
+
if (tick !== undefined)
|
|
118
135
|
this.tick(voice, subject, held, tick, to);
|
|
119
136
|
else if (still === 0 ? now !== held.stepped : to > held.stepped) {
|
|
120
137
|
if (still !== 0)
|
|
@@ -123,6 +140,8 @@ export function contribution(voice, subject, now, held) {
|
|
|
123
140
|
held.stepped = to;
|
|
124
141
|
}
|
|
125
142
|
}
|
|
143
|
+
else if (sought)
|
|
144
|
+
own(voice, held);
|
|
126
145
|
}
|
|
127
146
|
let delta = null;
|
|
128
147
|
// A keys voice in a locus reads at weight 0 too: having a delta makes it a member, and the
|
|
@@ -143,6 +162,8 @@ export function contribution(voice, subject, now, held) {
|
|
|
143
162
|
}
|
|
144
163
|
else {
|
|
145
164
|
delta = voice.patch.at(phase, subject, setting);
|
|
165
|
+
if (voice.unchecked)
|
|
166
|
+
checkWrites(voice, delta);
|
|
146
167
|
}
|
|
147
168
|
held.delta = delta;
|
|
148
169
|
held.probed = now;
|
|
@@ -178,6 +199,69 @@ export function keyed(voice, subject, held) {
|
|
|
178
199
|
voice.holder = held;
|
|
179
200
|
return delta;
|
|
180
201
|
}
|
|
202
|
+
/** How many whole intervals of `tick` a subject's step grid holds by mix time `to`. */
|
|
203
|
+
function dueBy(held, tick, to) {
|
|
204
|
+
// The epsilon keeps an interval like 1000 / 120 from landing a hair short of a whole count.
|
|
205
|
+
return Math.floor((to - held.since) / tick + 1e-9);
|
|
206
|
+
}
|
|
207
|
+
/**
|
|
208
|
+
* The first step of a record a seek put back from a copy: takes up the furthest state stepped on
|
|
209
|
+
* from that copy that the steps due now run past, the one the record the seek replaced reached or
|
|
210
|
+
* the one the copy was last stepped ahead to, and otherwise keeps the copy's stepped ahead to the
|
|
211
|
+
* moment sought, so a mix sought back time after time does not step from the copy each time, nor a
|
|
212
|
+
* mix sought back and returned step again to where it was. Only where the same steps give the same
|
|
213
|
+
* state: a fixed interval, no `maxDt`, no host field read.
|
|
214
|
+
*/
|
|
215
|
+
function ahead(mix, voice, subject, held, snaps, tick, to) {
|
|
216
|
+
const from = snaps[snaps.length - 1];
|
|
217
|
+
const got = from?.ahead;
|
|
218
|
+
if (from === undefined || got === undefined || got.put !== held)
|
|
219
|
+
return;
|
|
220
|
+
got.put = null;
|
|
221
|
+
const reads = voice.patch.reads;
|
|
222
|
+
if (mix.opts.maxDt !== undefined || (reads !== undefined && reads.length > 0))
|
|
223
|
+
return;
|
|
224
|
+
// Stepped, rebuilt or placed anew since it was put back, its steps are no longer the copy's.
|
|
225
|
+
const was = from.held;
|
|
226
|
+
if (held.ticks !== was.ticks || held.since !== got.since || held.rebuilt !== was.rebuilt)
|
|
227
|
+
return;
|
|
228
|
+
const at = got.sought;
|
|
229
|
+
const due = dueBy(held, tick, to);
|
|
230
|
+
const usable = got.state !== undefined && got.ticks <= due;
|
|
231
|
+
if (got.left !== undefined && got.leftTicks <= due && !(usable && got.ticks > got.leftTicks)) {
|
|
232
|
+
// Taken, not copied: the next seek back keeps the state this record steps on to in its place.
|
|
233
|
+
held.state = got.left;
|
|
234
|
+
got.left = undefined;
|
|
235
|
+
voice.setting.state = held.state;
|
|
236
|
+
held.ticks = got.leftTicks;
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
if (usable) {
|
|
240
|
+
held.state = copyState(voice, got.state);
|
|
241
|
+
voice.setting.state = held.state;
|
|
242
|
+
held.ticks = got.ticks;
|
|
243
|
+
}
|
|
244
|
+
const last = usable ? got.at : from.at;
|
|
245
|
+
if (!(at < to) || at - last < apart(mix.opts.history))
|
|
246
|
+
return;
|
|
247
|
+
own(voice, held);
|
|
248
|
+
mix.tick(voice, subject, held, tick, at);
|
|
249
|
+
got.state = copyState(voice, held.state);
|
|
250
|
+
got.ticks = held.ticks;
|
|
251
|
+
got.at = at;
|
|
252
|
+
}
|
|
253
|
+
/**
|
|
254
|
+
* A record a seek put back shares the state of the copy it was made from, which no read changes,
|
|
255
|
+
* until its first probe: before it steps, it takes a copy of its own.
|
|
256
|
+
*/
|
|
257
|
+
function own(voice, held) {
|
|
258
|
+
const snaps = held.snaps;
|
|
259
|
+
const from = snaps[snaps.length - 1];
|
|
260
|
+
if (from !== undefined && held.state !== undefined && held.state === from.held.state) {
|
|
261
|
+
held.state = copyState(voice, held.state);
|
|
262
|
+
voice.setting.state = held.state;
|
|
263
|
+
}
|
|
264
|
+
}
|
|
181
265
|
/**
|
|
182
266
|
* Runs `step` once per whole interval between where this subject last stopped and now, on a grid
|
|
183
267
|
* counted from when its delay ran out, and leaves the remainder for the next sample. Counting
|
|
@@ -188,8 +272,7 @@ export function tick(voice, subject, held, tick, to) {
|
|
|
188
272
|
const setting = voice.setting;
|
|
189
273
|
const frameDt = setting.dt;
|
|
190
274
|
let n = held.ticks;
|
|
191
|
-
|
|
192
|
-
const due = Math.floor((to - held.since) / tick + 1e-9);
|
|
275
|
+
const due = dueBy(held, tick, to);
|
|
193
276
|
const cap = this.opts.maxDt;
|
|
194
277
|
if (cap !== undefined) {
|
|
195
278
|
const most = Math.floor(cap / tick);
|
|
@@ -291,6 +374,12 @@ export function apply(pose, voice, held, delta, weight) {
|
|
|
291
374
|
const value = delta[key];
|
|
292
375
|
if (value === undefined)
|
|
293
376
|
continue;
|
|
377
|
+
const op = this.scalars[slot];
|
|
378
|
+
if (op !== undefined && typeof value === 'number') {
|
|
379
|
+
// Called through the channel, `scale` boxes the double it hands `merge`.
|
|
380
|
+
pose[key] = foldNumber(op, pose[key], value, weight);
|
|
381
|
+
continue;
|
|
382
|
+
}
|
|
294
383
|
const channel = this.channels[slot];
|
|
295
384
|
if (channel.rest !== undefined && channel.scale) {
|
|
296
385
|
// pose[key] is the copy of rest this fold made, so it is ours to write into.
|
|
@@ -374,8 +463,22 @@ export function folded(subject, out, dry = false, except) {
|
|
|
374
463
|
*/
|
|
375
464
|
export function linked(subject) {
|
|
376
465
|
const head = Number.isNaN(this.now) ? null : this.chain(subject);
|
|
466
|
+
const lanes = this.lanes;
|
|
467
|
+
// What `prepare` answers once this frame's qualify left nothing laned, without calling it.
|
|
468
|
+
if (head !== null &&
|
|
469
|
+
lanes !== null &&
|
|
470
|
+
lanes.laned.length === 0 &&
|
|
471
|
+
lanes.frameAt === this.now &&
|
|
472
|
+
lanes.qualifiedVersion === this.version &&
|
|
473
|
+
lanes.touched.length === 0 &&
|
|
474
|
+
!lanes.filling) {
|
|
475
|
+
if (head.slot >= 0)
|
|
476
|
+
lanes.per[head.slot * 6 /* Per.SLOT */ + 4 /* Per.GENERAL_PROBE */] = ++lanes.probes;
|
|
477
|
+
this.linkedLaned = false;
|
|
478
|
+
return head;
|
|
479
|
+
}
|
|
377
480
|
this.linkedLaned =
|
|
378
|
-
head !== null &&
|
|
481
|
+
head !== null && lanes?.prepare(head.slot, subject, this.version, head) === true;
|
|
379
482
|
return head;
|
|
380
483
|
}
|
|
381
484
|
/** The fold after a subject's chain is linked and its lanes asked whether it reads from them. */
|
package/dist/frames.d.ts
CHANGED
|
@@ -15,6 +15,8 @@ export declare class Frames {
|
|
|
15
15
|
* since rate 0 can hold the clock at `t` for many.
|
|
16
16
|
*/
|
|
17
17
|
at(t: number): Frame | undefined;
|
|
18
|
+
/** The frame numbered `seq`, if it is still held. */
|
|
19
|
+
numbered(seq: number): Frame | undefined;
|
|
18
20
|
/** Every frame held, oldest first. */
|
|
19
21
|
all(): Frame[];
|
|
20
22
|
set(frames: Frame[]): void;
|
package/dist/frames.js
CHANGED
|
@@ -48,6 +48,23 @@ export class Frames {
|
|
|
48
48
|
}
|
|
49
49
|
return found;
|
|
50
50
|
}
|
|
51
|
+
/** The frame numbered `seq`, if it is still held. */
|
|
52
|
+
numbered(seq) {
|
|
53
|
+
const list = this.list;
|
|
54
|
+
let lo = this.head;
|
|
55
|
+
let hi = list.length - 1;
|
|
56
|
+
while (lo <= hi) {
|
|
57
|
+
const mid = (lo + hi) >> 1;
|
|
58
|
+
const f = list[mid];
|
|
59
|
+
if (f.seq === seq)
|
|
60
|
+
return f;
|
|
61
|
+
if (f.seq < seq)
|
|
62
|
+
lo = mid + 1;
|
|
63
|
+
else
|
|
64
|
+
hi = mid - 1;
|
|
65
|
+
}
|
|
66
|
+
return undefined;
|
|
67
|
+
}
|
|
51
68
|
/** Every frame held, oldest first. */
|
|
52
69
|
all() {
|
|
53
70
|
return this.list.slice(this.head);
|
package/dist/gather.js
CHANGED
|
@@ -154,18 +154,49 @@ export function fold(lane, slot) {
|
|
|
154
154
|
*/
|
|
155
155
|
export function foldDelta(chans, slot, delta) {
|
|
156
156
|
const n = chans.length;
|
|
157
|
+
const arg = this.arg;
|
|
158
|
+
// A plain number is folded where it is read: passed to a `foldInto` V8 did not inline, it is
|
|
159
|
+
// boxed, 16 bytes a channel a subject, and whether V8 inlines it turns on the order its compiles
|
|
160
|
+
// ran (bench/allocs.mjs, 2026-10-20).
|
|
157
161
|
let ch = chans[0];
|
|
158
|
-
if (n > 0)
|
|
159
|
-
|
|
162
|
+
if (n > 0) {
|
|
163
|
+
const v = delta[ch.name];
|
|
164
|
+
if (typeof v === 'number' && ch.scalar && ch.plain && this.into === null) {
|
|
165
|
+
const values = ch.values;
|
|
166
|
+
values[slot] = foldNumber(ch.op, values[slot], v, arg[0 /* Arg.WEIGHT */]);
|
|
167
|
+
}
|
|
168
|
+
else
|
|
169
|
+
this.foldInto(ch, slot, v);
|
|
170
|
+
}
|
|
160
171
|
ch = chans[1];
|
|
161
|
-
if (n > 1)
|
|
162
|
-
|
|
172
|
+
if (n > 1) {
|
|
173
|
+
const v = delta[ch.name];
|
|
174
|
+
if (typeof v === 'number' && ch.scalar && ch.plain && this.into === null) {
|
|
175
|
+
const values = ch.values;
|
|
176
|
+
values[slot] = foldNumber(ch.op, values[slot], v, arg[0 /* Arg.WEIGHT */]);
|
|
177
|
+
}
|
|
178
|
+
else
|
|
179
|
+
this.foldInto(ch, slot, v);
|
|
180
|
+
}
|
|
163
181
|
ch = chans[2];
|
|
164
|
-
if (n > 2)
|
|
165
|
-
|
|
182
|
+
if (n > 2) {
|
|
183
|
+
const v = delta[ch.name];
|
|
184
|
+
if (typeof v === 'number' && ch.scalar && ch.plain && this.into === null) {
|
|
185
|
+
const values = ch.values;
|
|
186
|
+
values[slot] = foldNumber(ch.op, values[slot], v, arg[0 /* Arg.WEIGHT */]);
|
|
187
|
+
}
|
|
188
|
+
else
|
|
189
|
+
this.foldInto(ch, slot, v);
|
|
190
|
+
}
|
|
166
191
|
for (let i = 3; i < n; i++) {
|
|
167
192
|
ch = chans[i];
|
|
168
|
-
|
|
193
|
+
const v = delta[ch.name];
|
|
194
|
+
if (typeof v === 'number' && ch.scalar && ch.plain && this.into === null) {
|
|
195
|
+
const values = ch.values;
|
|
196
|
+
values[slot] = foldNumber(ch.op, values[slot], v, arg[0 /* Arg.WEIGHT */]);
|
|
197
|
+
}
|
|
198
|
+
else
|
|
199
|
+
this.foldInto(ch, slot, v);
|
|
169
200
|
}
|
|
170
201
|
}
|
|
171
202
|
/** Folds a motion sample's axes, kept apart from `foldInto` so neither reads two array kinds. */
|
package/dist/handle.d.ts
CHANGED
|
@@ -16,9 +16,13 @@ export interface HandleHost<I, O> {
|
|
|
16
16
|
rise(voice: Voice<I, O>, opts: {
|
|
17
17
|
over?: number;
|
|
18
18
|
} | undefined): void;
|
|
19
|
+
release(voice: Voice<I, O>): void;
|
|
19
20
|
weightOf(voice: Voice<I, O>, subject: I): number;
|
|
20
|
-
/**
|
|
21
|
-
|
|
21
|
+
/**
|
|
22
|
+
* Records a write the host made through `h`, which `again` makes once more where a seek replays
|
|
23
|
+
* it, on the handle it is given: `h`, or a read ahead's handle on its copy of the voice.
|
|
24
|
+
*/
|
|
25
|
+
record(h: VoiceHandle<I, O>, label: string, again: (h: VoiceHandle<I, O>) => void): void;
|
|
22
26
|
}
|
|
23
27
|
/** Refuses a voice rate no clock can run at. */
|
|
24
28
|
export declare function finiteRate(rate: number): void;
|
|
@@ -60,6 +64,7 @@ export declare class VoiceHandle<I, O> implements Handle<I> {
|
|
|
60
64
|
rise(opts?: {
|
|
61
65
|
over?: number;
|
|
62
66
|
}): void;
|
|
67
|
+
release(): void;
|
|
63
68
|
weightOf(subject: I): number;
|
|
64
69
|
to(subject: I, target: Value, at?: number): void;
|
|
65
70
|
push(subject: I, velocity: Value, at?: number): void;
|
package/dist/handle.js
CHANGED
|
@@ -59,17 +59,25 @@ export class VoiceHandle {
|
|
|
59
59
|
get weight() {
|
|
60
60
|
return this.#voice?.weight ?? this.#gone?.weight ?? 0;
|
|
61
61
|
}
|
|
62
|
-
|
|
62
|
+
/**
|
|
63
|
+
* The voice a write reaches: none while a history store holds it, or a seek back to before its
|
|
64
|
+
* cue has it waiting. It had left, or was yet to be cued, when the host made the write, as on
|
|
65
|
+
* replay, so the write changes nothing and the tape does not take it.
|
|
66
|
+
*/
|
|
67
|
+
get #cued() {
|
|
68
|
+
const voice = this.#voice;
|
|
69
|
+
return voice === null || voice.parked ? null : voice;
|
|
70
|
+
}
|
|
63
71
|
set weight(w) {
|
|
64
72
|
plainWeight(w);
|
|
65
|
-
const voice = this.#
|
|
73
|
+
const voice = this.#cued;
|
|
66
74
|
if (voice === null)
|
|
67
75
|
return;
|
|
68
76
|
voice.written = true;
|
|
69
77
|
voice.weight = w;
|
|
70
78
|
this.#host.changed(voice);
|
|
71
|
-
this.#host.record('weight', () => {
|
|
72
|
-
|
|
79
|
+
this.#host.record(this, 'weight', (h) => {
|
|
80
|
+
h.weight = w;
|
|
73
81
|
});
|
|
74
82
|
}
|
|
75
83
|
get rate() {
|
|
@@ -81,7 +89,7 @@ export class VoiceHandle {
|
|
|
81
89
|
}
|
|
82
90
|
ramp(r, over) {
|
|
83
91
|
finiteRate(r);
|
|
84
|
-
const voice = this.#
|
|
92
|
+
const voice = this.#cued;
|
|
85
93
|
if (voice === null)
|
|
86
94
|
return;
|
|
87
95
|
voice.written = true;
|
|
@@ -97,10 +105,10 @@ export class VoiceHandle {
|
|
|
97
105
|
retime(voice, now, r, over);
|
|
98
106
|
}
|
|
99
107
|
this.#host.changed(voice);
|
|
100
|
-
this.#host.record('rate', () =>
|
|
108
|
+
this.#host.record(this, 'rate', (h) => h.ramp(r, over));
|
|
101
109
|
}
|
|
102
110
|
seek(elapsed, opts) {
|
|
103
|
-
const voice = this.#
|
|
111
|
+
const voice = this.#cued;
|
|
104
112
|
if (voice === null)
|
|
105
113
|
return 'exact';
|
|
106
114
|
voice.written = true;
|
|
@@ -114,24 +122,32 @@ export class VoiceHandle {
|
|
|
114
122
|
voice.seeks++;
|
|
115
123
|
const doubt = this.#host.sought(voice, opts?.state !== 'keep');
|
|
116
124
|
this.#host.changed(voice);
|
|
117
|
-
this.#host.record('seek', () =>
|
|
125
|
+
this.#host.record(this, 'seek', (h) => h.seek(elapsed, opts));
|
|
118
126
|
return doubt;
|
|
119
127
|
}
|
|
120
128
|
fade(opts) {
|
|
121
|
-
const voice = this.#
|
|
129
|
+
const voice = this.#cued;
|
|
122
130
|
if (voice === null)
|
|
123
131
|
return;
|
|
124
132
|
voice.written = true;
|
|
125
133
|
this.#host.fade(voice, opts);
|
|
126
|
-
this.#host.record('fade', () =>
|
|
134
|
+
this.#host.record(this, 'fade', (h) => h.fade(opts));
|
|
127
135
|
}
|
|
128
136
|
rise(opts) {
|
|
129
|
-
const voice = this.#
|
|
137
|
+
const voice = this.#cued;
|
|
130
138
|
if (voice === null)
|
|
131
139
|
return;
|
|
132
140
|
voice.written = true;
|
|
133
141
|
this.#host.rise(voice, opts);
|
|
134
|
-
this.#host.record('rise', () =>
|
|
142
|
+
this.#host.record(this, 'rise', (h) => h.rise(opts));
|
|
143
|
+
}
|
|
144
|
+
release() {
|
|
145
|
+
const voice = this.#cued;
|
|
146
|
+
if (voice === null)
|
|
147
|
+
return;
|
|
148
|
+
voice.written = true;
|
|
149
|
+
this.#host.release(voice);
|
|
150
|
+
this.#host.record(this, 'release', (h) => h.release());
|
|
135
151
|
}
|
|
136
152
|
weightOf(subject) {
|
|
137
153
|
const voice = this.#voice;
|
package/dist/held.d.ts
CHANGED
|
@@ -2,8 +2,17 @@ import type { Mixer } from './mixer.js';
|
|
|
2
2
|
import type { Subject, Voice } from './voice.js';
|
|
3
3
|
/** The record a voice gives every subject it does not reach. */
|
|
4
4
|
export declare function unreachedOf<I, O>(this: Mixer<I, O>, voice: Voice<I, O>, now: number): Subject<unknown>;
|
|
5
|
-
/**
|
|
6
|
-
|
|
5
|
+
/**
|
|
6
|
+
* What this voice holds for this subject, made on first sight with `target` and `stagger` asked
|
|
7
|
+
* once; `guess` is a record it may be, from the subject's chain as last linked.
|
|
8
|
+
*/
|
|
9
|
+
export declare function held<I, O>(this: Mixer<I, O>, voice: Voice<I, O>, subject: I, slot: number, guess?: Subject<unknown> | null): Subject<unknown>;
|
|
10
|
+
/**
|
|
11
|
+
* Where a new record met at `now` starts stepping: from its `since`, or from `now` for a voice that
|
|
12
|
+
* catches up `fresh`, on the `stepMs` grid. True where that skipped steps, so the state depends on
|
|
13
|
+
* when the subject was first probed.
|
|
14
|
+
*/
|
|
15
|
+
export declare function meet<I, O>(mix: Mixer<I, O>, voice: Voice<I, O>, held: Subject<unknown>, now: number): boolean;
|
|
7
16
|
/**
|
|
8
17
|
* The mix time the voice clock first read `delay`, or will by its controls now. A stateful voice
|
|
9
18
|
* counts from its last seek that rebuilt state, as its records do.
|
|
@@ -20,6 +29,8 @@ export declare function shownOf<I, O>(this: Mixer<I, O>, voice: Voice<I, O>, sin
|
|
|
20
29
|
* while it waited is made again for where its clock is.
|
|
21
30
|
*/
|
|
22
31
|
export declare function begin<I, O>(mix: Mixer<I, O>, voice: Voice<I, O>, now: number): void;
|
|
32
|
+
/** Counts a record whose `since` was still ahead from where its voice's clock now puts it. */
|
|
33
|
+
export declare function origin<I, O>(mix: Mixer<I, O>, voice: Voice<I, O>, held: Subject<unknown>): void;
|
|
23
34
|
/**
|
|
24
35
|
* A voice's start or clock moved, or it went live at `now`: the records whose `since` was still
|
|
25
36
|
* ahead count from where its clock now puts them.
|
package/dist/held.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { changed } from './chain.js';
|
|
2
|
+
import { stepOf } from './clock.js';
|
|
2
3
|
import { schedule } from './due.js';
|
|
3
4
|
import { everyoneOf, inherit, sighted } from './everyone.js';
|
|
4
5
|
import { originOf } from './origin.js';
|
|
@@ -16,8 +17,11 @@ export function unreachedOf(voice, now) {
|
|
|
16
17
|
voice.unreached = none;
|
|
17
18
|
return none;
|
|
18
19
|
}
|
|
19
|
-
/**
|
|
20
|
-
|
|
20
|
+
/**
|
|
21
|
+
* What this voice holds for this subject, made on first sight with `target` and `stagger` asked
|
|
22
|
+
* once; `guess` is a record it may be, from the subject's chain as last linked.
|
|
23
|
+
*/
|
|
24
|
+
export function held(voice, subject, slot, guess = null) {
|
|
21
25
|
if (voice.sharing) {
|
|
22
26
|
const all = everyoneOf(this, voice);
|
|
23
27
|
if (all !== null)
|
|
@@ -25,7 +29,7 @@ export function held(voice, subject, slot) {
|
|
|
25
29
|
}
|
|
26
30
|
if (unreached(voice, slot))
|
|
27
31
|
return voice.unreached;
|
|
28
|
-
let held = voice.subjects.get(subject);
|
|
32
|
+
let held = (guess === null ? voice.subjects.get(subject) : voice.subjects.find(subject, guess));
|
|
29
33
|
if (held !== undefined)
|
|
30
34
|
return held;
|
|
31
35
|
const now = this.now;
|
|
@@ -53,11 +57,11 @@ export function held(voice, subject, slot) {
|
|
|
53
57
|
held.delay = delay;
|
|
54
58
|
held.since = since;
|
|
55
59
|
held.shown = shown;
|
|
56
|
-
|
|
60
|
+
const fresh = meet(this, voice, held, now);
|
|
57
61
|
held.rebuilt = voice.rebuilds;
|
|
58
62
|
if (this.projecting) {
|
|
59
63
|
held.from = held.stepped;
|
|
60
|
-
held.unknown = this.backward && voice.spec.from === 'current';
|
|
64
|
+
held.unknown = (this.backward && voice.spec.from === 'current') || fresh;
|
|
61
65
|
}
|
|
62
66
|
voice.subjects.set(subject, held);
|
|
63
67
|
if (!(since <= now)) {
|
|
@@ -91,6 +95,30 @@ export function held(voice, subject, slot) {
|
|
|
91
95
|
}
|
|
92
96
|
return held;
|
|
93
97
|
}
|
|
98
|
+
/**
|
|
99
|
+
* Where a new record met at `now` starts stepping: from its `since`, or from `now` for a voice that
|
|
100
|
+
* catches up `fresh`, on the `stepMs` grid. True where that skipped steps, so the state depends on
|
|
101
|
+
* when the subject was first probed.
|
|
102
|
+
*/
|
|
103
|
+
export function meet(mix, voice, held, now) {
|
|
104
|
+
const since = held.since;
|
|
105
|
+
if (!(since < now)) {
|
|
106
|
+
held.stepped = now;
|
|
107
|
+
return false;
|
|
108
|
+
}
|
|
109
|
+
if (voice.spec.catchUp !== 'fresh' || voice.patch.step === undefined) {
|
|
110
|
+
held.stepped = since;
|
|
111
|
+
return false;
|
|
112
|
+
}
|
|
113
|
+
const tick = stepOf(mix, voice);
|
|
114
|
+
if (tick === undefined)
|
|
115
|
+
held.stepped = now;
|
|
116
|
+
else {
|
|
117
|
+
held.ticks = Math.floor((now - since) / tick + 1e-9);
|
|
118
|
+
held.stepped = since + held.ticks * tick;
|
|
119
|
+
}
|
|
120
|
+
return held.stepped > since;
|
|
121
|
+
}
|
|
94
122
|
/**
|
|
95
123
|
* The mix time the voice clock first read `delay`, or will by its controls now. A stateful voice
|
|
96
124
|
* counts from its last seek that rebuilt state, as its records do.
|
|
@@ -118,10 +146,19 @@ export function begin(mix, voice, now) {
|
|
|
118
146
|
if (voice.anchorElapsed !== 0) {
|
|
119
147
|
voice.seeks++;
|
|
120
148
|
voice.rebuilds++;
|
|
149
|
+
// Kept with its controls, so a seek back to after this puts back the count its records hold.
|
|
150
|
+
voice.note(now, mix.transport.seq, true);
|
|
121
151
|
}
|
|
122
152
|
reorigin(mix, voice, now);
|
|
123
153
|
changed(mix, voice);
|
|
124
154
|
}
|
|
155
|
+
/** Counts a record whose `since` was still ahead from where its voice's clock now puts it. */
|
|
156
|
+
export function origin(mix, voice, held) {
|
|
157
|
+
held.since = mix.sinceOf(voice, held.delay);
|
|
158
|
+
held.shown = mix.shownOf(voice, held.since);
|
|
159
|
+
if (held.ticks === 0 && held.stepped > held.since)
|
|
160
|
+
held.stepped = held.since;
|
|
161
|
+
}
|
|
125
162
|
/**
|
|
126
163
|
* A voice's start or clock moved, or it went live at `now`: the records whose `since` was still
|
|
127
164
|
* ahead count from where its clock now puts them.
|
|
@@ -132,10 +169,7 @@ export function reorigin(mix, voice, now) {
|
|
|
132
169
|
return;
|
|
133
170
|
let kept = 0;
|
|
134
171
|
for (const held of early) {
|
|
135
|
-
|
|
136
|
-
held.shown = mix.shownOf(voice, held.since);
|
|
137
|
-
if (held.ticks === 0 && held.stepped > held.since)
|
|
138
|
-
held.stepped = held.since;
|
|
172
|
+
origin(mix, voice, held);
|
|
139
173
|
if (!(held.since <= now))
|
|
140
174
|
early[kept++] = held;
|
|
141
175
|
}
|
package/dist/history.d.ts
CHANGED
|
@@ -5,6 +5,10 @@ import { type Subject, type Voice } from './voice.js';
|
|
|
5
5
|
export declare function last<T extends {
|
|
6
6
|
at: number;
|
|
7
7
|
}>(list: readonly T[], t: number, inclusive: boolean | ((e: T) => boolean)): T | undefined;
|
|
8
|
+
/** Where `last` finds its entry in the list, -1 where it finds none. */
|
|
9
|
+
export declare function lastIndex<T extends {
|
|
10
|
+
at: number;
|
|
11
|
+
}>(list: readonly T[], t: number, inclusive: boolean | ((e: T) => boolean)): number;
|
|
8
12
|
/**
|
|
9
13
|
* The last entry of a list kept in the order it was made that falls inside `cut`, an entry made
|
|
10
14
|
* before its frame was read counting as `early`.
|
|
@@ -19,12 +23,31 @@ export declare function same(a: unknown, b: unknown): boolean;
|
|
|
19
23
|
export declare function elapsedThen<I, O>(mix: Mixer<I, O>, voice: Voice<I, O>, t: number): number;
|
|
20
24
|
/** Records a change to a voice's controls under `history`, and lets go of what it no longer reaches. */
|
|
21
25
|
export declare function noted<I, O>(mix: Mixer<I, O>, voice: Voice<I, O>): void;
|
|
22
|
-
/** A copy of a
|
|
23
|
-
export declare function
|
|
26
|
+
/** A copy of the state a voice's patch keeps for a subject. */
|
|
27
|
+
export declare function copyState<I, O>(voice: Voice<I, O>, state: unknown): unknown;
|
|
28
|
+
/**
|
|
29
|
+
* A copy of a subject's record that shares nothing a read could change; with `shared`, its state is
|
|
30
|
+
* the record's own, for a copy that takes one of its own before it steps.
|
|
31
|
+
*/
|
|
32
|
+
export declare function copyHeld<I, O>(voice: Voice<I, O>, h: Subject<unknown>, shared?: boolean): Subject<unknown>;
|
|
33
|
+
/**
|
|
34
|
+
* As `copyHeld`, written over `into`, a record nothing else holds, so a copy made in its place
|
|
35
|
+
* leaves nothing new to collect. test/history.test.ts holds this to `copyHeld` field by field.
|
|
36
|
+
*/
|
|
37
|
+
export declare function copyHeldInto<I, O>(voice: Voice<I, O>, h: Subject<unknown>, into: Subject<unknown>, shared?: boolean): Subject<unknown>;
|
|
38
|
+
/**
|
|
39
|
+
* Lets go of what a record holds from its last probe and its place in a chain, as a copy of it
|
|
40
|
+
* starts: a seek puts back a record nothing else holds this way, in place of a copy.
|
|
41
|
+
*/
|
|
42
|
+
export declare function unread(h: Subject<unknown>): Subject<unknown>;
|
|
24
43
|
/** Under `history` with `inputs`, copies the host fields any voice reads, once a frame, when they changed. */
|
|
25
44
|
export declare function recordHost<I, O>(mix: Mixer<I, O>, now: number): void;
|
|
26
45
|
/** Under `history` with `inputs`, keeps what an input signal read, each time it changes. */
|
|
27
46
|
export declare function record<I, O>(mix: Mixer<I, O>, voice: Voice<I, O>, subject: I, held: Subject<unknown>, value: number): void;
|
|
47
|
+
/** How far apart, in mix ms, a record's copies are kept. */
|
|
48
|
+
export declare function apart(history: {
|
|
49
|
+
readonly every?: number;
|
|
50
|
+
}): number;
|
|
28
51
|
/** Under `history`, keeps a copy of a stateful voice's record for this subject every so often. */
|
|
29
52
|
export declare function remember<I, O>(mix: Mixer<I, O>, voice: Voice<I, O>, subject: I, held: Subject<unknown>): void;
|
|
30
53
|
/**
|