@msb235/blits 0.14.0 → 0.15.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 +154 -0
- package/README.md +3 -3
- package/dist/ahead.d.ts +26 -0
- package/dist/ahead.js +264 -0
- package/dist/around.d.ts +11 -0
- package/dist/around.js +32 -0
- package/dist/bare.js +15 -7
- package/dist/blank.d.ts +10 -0
- package/dist/blank.js +26 -0
- package/dist/blend.d.ts +2 -1
- package/dist/blend.js +1 -6
- package/dist/bound.d.ts +70 -0
- package/dist/bound.js +351 -0
- package/dist/chain.d.ts +2 -1
- package/dist/chain.js +7 -5
- package/dist/changes.d.ts +24 -0
- package/dist/changes.js +189 -0
- package/dist/channels.d.ts +39 -13
- package/dist/channels.js +14 -5
- package/dist/color.d.ts +12 -10
- package/dist/color.js +25 -25
- package/dist/columns.js +7 -3
- package/dist/contribution.d.ts +30 -0
- package/dist/contribution.js +374 -0
- package/dist/copyout.d.ts +10 -0
- package/dist/copyout.js +69 -24
- package/dist/crowd.d.ts +8 -2
- package/dist/crowd.js +4 -5
- package/dist/cue.d.ts +8 -1
- package/dist/cue.js +39 -13
- package/dist/cut.d.ts +8 -0
- package/dist/cut.js +4 -0
- package/dist/drop.d.ts +9 -0
- package/dist/drop.js +84 -0
- package/dist/due.d.ts +24 -5
- package/dist/due.js +113 -33
- package/dist/easing.d.ts +4 -4
- package/dist/easing.js +3 -3
- package/dist/everyone.d.ts +3 -2
- package/dist/everyone.js +3 -3
- package/dist/fade.d.ts +2 -1
- package/dist/fade.js +2 -1
- package/dist/fill.d.ts +3 -2
- package/dist/fill.js +51 -19
- package/dist/fit.d.ts +44 -26
- package/dist/fit.js +10 -9
- package/dist/fold.d.ts +4 -37
- package/dist/fold.js +5 -513
- package/dist/gather.d.ts +12 -7
- package/dist/gather.js +179 -44
- package/dist/gone.js +2 -2
- package/dist/grid.d.ts +47 -0
- package/dist/grid.js +247 -0
- package/dist/handle.d.ts +2 -0
- package/dist/handle.js +14 -8
- package/dist/held.d.ts +2 -1
- package/dist/history.d.ts +6 -5
- package/dist/history.js +7 -9
- package/dist/hosts.js +9 -15
- package/dist/index.d.ts +5 -4
- package/dist/keyfill.d.ts +1 -1
- package/dist/keyfill.js +5 -3
- package/dist/keyrows.d.ts +12 -0
- package/dist/keyrows.js +99 -12
- package/dist/lane.d.ts +36 -6
- package/dist/lane.js +32 -1
- package/dist/lanes.d.ts +23 -8
- package/dist/lanes.js +29 -8
- package/dist/leavings.d.ts +2 -2
- package/dist/leavings.js +1 -1
- package/dist/lives.d.ts +47 -0
- package/dist/lives.js +180 -0
- package/dist/meet.d.ts +2 -2
- package/dist/meet.js +90 -4
- package/dist/mixer.d.ts +54 -36
- package/dist/mixer.js +53 -171
- package/dist/motion.d.ts +14 -118
- package/dist/motion.js +16 -8
- package/dist/motions.d.ts +62 -155
- package/dist/motions.js +56 -370
- package/dist/moved.d.ts +1 -1
- package/dist/moved.js +16 -7
- package/dist/named.d.ts +0 -1
- package/dist/named.js +20 -2
- package/dist/numbers.d.ts +2 -0
- package/dist/numbers.js +4 -0
- package/dist/owed.d.ts +2 -0
- package/dist/owed.js +4 -0
- package/dist/pack.d.ts +2 -1
- package/dist/pack.js +2 -3
- package/dist/patch.d.ts +7 -3
- package/dist/patch.js +6 -2
- package/dist/places.d.ts +53 -0
- package/dist/places.js +171 -0
- package/dist/plot.d.ts +12 -0
- package/dist/plot.js +52 -0
- package/dist/project.d.ts +4 -4
- package/dist/project.js +9 -212
- package/dist/pull.js +11 -8
- package/dist/qualify.js +84 -21
- package/dist/reading.d.ts +6 -0
- package/dist/reading.js +5 -0
- package/dist/record.d.ts +5 -1
- package/dist/record.js +3 -2
- package/dist/relink.d.ts +1 -1
- package/dist/restored.d.ts +2 -2
- package/dist/restored.js +1 -1
- package/dist/rests.d.ts +9 -0
- package/dist/rests.js +76 -0
- package/dist/revive.d.ts +3 -2
- package/dist/revive.js +2 -2
- package/dist/{locus.d.ts → role.d.ts} +10 -9
- package/dist/{locus.js → role.js} +3 -3
- package/dist/roles.d.ts +10 -0
- package/dist/roles.js +158 -0
- package/dist/roster.js +3 -3
- package/dist/rotation.d.ts +1 -1
- package/dist/rotation.js +1 -1
- package/dist/rows.js +1 -1
- package/dist/sample.d.ts +2 -2
- package/dist/sample.js +33 -7
- package/dist/seek.js +5 -5
- package/dist/setting.d.ts +27 -0
- package/dist/setting.js +55 -0
- package/dist/shared.d.ts +1 -1
- package/dist/shared.js +1 -1
- package/dist/signals.d.ts +64 -32
- package/dist/signals.js +20 -18
- package/dist/stack.d.ts +5 -5
- package/dist/stack.js +5 -5
- package/dist/standing.d.ts +2 -1
- package/dist/standing.js +3 -2
- package/dist/store.d.ts +9 -0
- package/dist/store.js +24 -0
- package/dist/strays.d.ts +2 -1
- package/dist/strays.js +20 -13
- package/dist/stretch.d.ts +112 -0
- package/dist/stretch.js +9 -0
- package/dist/subject.d.ts +142 -0
- package/dist/subject.js +1 -0
- package/dist/tape.d.ts +9 -5
- package/dist/tape.js +5 -1
- package/dist/then.d.ts +9 -0
- package/dist/then.js +57 -0
- package/dist/ticker.d.ts +4 -4
- package/dist/transport.d.ts +0 -8
- package/dist/transport.js +0 -4
- package/dist/turns.d.ts +4 -3
- package/dist/turns.js +4 -3
- package/dist/types/arc.d.ts +12 -11
- package/dist/types/channel.d.ts +12 -8
- package/dist/types/history.d.ts +35 -19
- package/dist/types/mix.d.ts +222 -163
- package/dist/types/motion.d.ts +151 -0
- package/dist/types/motion.js +1 -0
- package/dist/types/patch.d.ts +67 -34
- package/dist/types/place.d.ts +51 -36
- package/dist/types/transport.d.ts +20 -18
- package/dist/types/voice.d.ts +136 -70
- package/dist/types/watch.d.ts +88 -40
- package/dist/types.d.ts +1 -0
- package/dist/voice.d.ts +7 -143
- package/dist/voice.js +28 -79
- package/dist/warn.d.ts +2 -0
- package/dist/warn.js +1 -1
- package/dist/watch.d.ts +2 -2
- package/dist/watch.js +5 -3
- package/dist/wave.d.ts +4 -3
- package/dist/wave.js +3 -2
- package/dist/weigh.d.ts +7 -1
- package/dist/weigh.js +27 -0
- package/docs/schema.html +212 -34
- package/package.json +2 -2
package/dist/fold.js
CHANGED
|
@@ -1,15 +1,6 @@
|
|
|
1
1
|
import { foldNumber } from './channels.js';
|
|
2
|
-
import { passAt, phaseAt, phaseBefore, silent, stepOf } from './clock.js';
|
|
3
2
|
import { copy as copyValue, first } from './clone.js';
|
|
4
3
|
import { open as openRecord, shut as shutRecord, weighAt } from './everyone.js';
|
|
5
|
-
import { waits } from './held.js';
|
|
6
|
-
import { apart, copyState, recordHost, remember } from './history.js';
|
|
7
|
-
import { stateful } from './hosts.js';
|
|
8
|
-
import { locusScratch } from './locus.js';
|
|
9
|
-
import { moved } from './moved.js';
|
|
10
|
-
import { readKeys } from './patch.js';
|
|
11
|
-
import { reading } from './reading.js';
|
|
12
|
-
import { checkWrites } from './voice.js';
|
|
13
4
|
// V8 reads a local const as a constant, where it reads an import from its module on every call.
|
|
14
5
|
const copy = copyValue;
|
|
15
6
|
const open = openRecord;
|
|
@@ -42,330 +33,6 @@ export function merged(channel, acc, b) {
|
|
|
42
33
|
acc[i] = r[i];
|
|
43
34
|
return acc;
|
|
44
35
|
}
|
|
45
|
-
/**
|
|
46
|
-
* A record after a seek that rebuilds: fresh state, stepped again from where the voice's clock now
|
|
47
|
-
* puts this subject's start, which under `stepMs` lands where a voice cued fresh would.
|
|
48
|
-
*/
|
|
49
|
-
function rebuild(mix, voice, subject, now, held) {
|
|
50
|
-
held.rebuilt = voice.rebuilds;
|
|
51
|
-
const patch = voice.patch;
|
|
52
|
-
if (patch.state === undefined && patch.step === undefined && held.kept === null)
|
|
53
|
-
return;
|
|
54
|
-
held.state = patch.state ? patch.state(subject) : undefined;
|
|
55
|
-
held.kept = null;
|
|
56
|
-
held.unkept = undefined;
|
|
57
|
-
held.since = mix.sinceOf(voice, held.delay);
|
|
58
|
-
held.shown = mix.shownOf(voice, held.since);
|
|
59
|
-
held.ticks = 0;
|
|
60
|
-
held.stepped = held.since < now ? held.since : now;
|
|
61
|
-
held.probed = Number.NaN;
|
|
62
|
-
}
|
|
63
|
-
/** Whether a seek since its last read moved a stateful record's origin, as one back before it does. */
|
|
64
|
-
function sentBack(mix, voice, held) {
|
|
65
|
-
const p = voice.patch;
|
|
66
|
-
if (p.state === undefined && p.step === undefined && !voice.keeping)
|
|
67
|
-
return false;
|
|
68
|
-
return mix.sinceOf(voice, held.delay) !== held.since;
|
|
69
|
-
}
|
|
70
|
-
/**
|
|
71
|
-
* One live voice's delta for one subject this frame, through its record for the subject, or null
|
|
72
|
-
* when it does not reach. Its weight is left in `w`.
|
|
73
|
-
*/
|
|
74
|
-
export function contribution(voice, subject, now, held) {
|
|
75
|
-
held.weight = 0;
|
|
76
|
-
if (!held.reaches)
|
|
77
|
-
return null;
|
|
78
|
-
if (voice.out?.rest && held.rested)
|
|
79
|
-
return null;
|
|
80
|
-
if (held.rebuilt !== voice.rebuilds)
|
|
81
|
-
rebuild(this, voice, subject, now, held);
|
|
82
|
-
else if (held.seeks !== voice.seeks && sentBack(this, voice, held)) {
|
|
83
|
-
// A seek sent it back before its start, so it starts afresh there, whatever state it kept.
|
|
84
|
-
rebuild(this, voice, subject, now, held);
|
|
85
|
-
waits(voice, held, now);
|
|
86
|
-
}
|
|
87
|
-
const raw = voice.rawAt(now, held.delay);
|
|
88
|
-
const early = voice.before(raw, now);
|
|
89
|
-
if (early && !voice.holdsFirst)
|
|
90
|
-
return null;
|
|
91
|
-
const elapsed = voice.shownAt(raw);
|
|
92
|
-
if (Number.isNaN(elapsed))
|
|
93
|
-
return null;
|
|
94
|
-
// A frozen subject's clock stands still at the edge it freezes at: -1 before, 1 after, 0 playing.
|
|
95
|
-
const still = early ? -1 : voice.freezesAfter && raw > voice.span ? 1 : 0;
|
|
96
|
-
const duration = voice.duration;
|
|
97
|
-
const phase = elapsed < 0 ? phaseBefore(elapsed, duration) : phaseAt(elapsed, duration, voice.passes);
|
|
98
|
-
const pass = passAt(elapsed, duration, voice.passes);
|
|
99
|
-
const primed = this.primed;
|
|
100
|
-
primed[0 /* Primed.NOW */] = now;
|
|
101
|
-
primed[1 /* Primed.ELAPSED */] = elapsed;
|
|
102
|
-
primed[2 /* Primed.PASS */] = pass;
|
|
103
|
-
this.prime(voice, held);
|
|
104
|
-
const setting = voice.setting;
|
|
105
|
-
// A signal that keeps state makes its voice stateful, as a patch that does: a lane would call
|
|
106
|
-
// it for subjects no probe asked about.
|
|
107
|
-
const keptAtWeigh = reading.kept;
|
|
108
|
-
const weight = this.weigh(voice, subject, now, held);
|
|
109
|
-
if (reading.kept !== keptAtWeigh && !voice.keeping)
|
|
110
|
-
stateful(this, voice);
|
|
111
|
-
setting.weight = weight;
|
|
112
|
-
held.weight = weight;
|
|
113
|
-
if (held.probed === now && held.delta && held.seeks === voice.seeks) {
|
|
114
|
-
if (voice.holder !== held && voice.scratch.length > 0)
|
|
115
|
-
this.keyed(voice, subject, held);
|
|
116
|
-
this.w = weight;
|
|
117
|
-
return held.delta;
|
|
118
|
-
}
|
|
119
|
-
const keptBefore = reading.kept;
|
|
120
|
-
const history = this.opts.history;
|
|
121
|
-
if (history?.inputs && voice.patch.reads !== undefined && !this.projecting)
|
|
122
|
-
recordHost(this, now);
|
|
123
|
-
// Only a motion reads it.
|
|
124
|
-
if (voice.motion !== undefined)
|
|
125
|
-
reading.horizon = this.horizonFor(voice, held.delay, now);
|
|
126
|
-
if (voice.patch.step && held.probed !== now) {
|
|
127
|
-
// Frozen after, it steps once more to where its last pass ended, and no further; run back
|
|
128
|
-
// past its start, to where it reached it. Before a start it has yet to reach, it does not step.
|
|
129
|
-
const to = still === 0
|
|
130
|
-
? now
|
|
131
|
-
: still === 1
|
|
132
|
-
? voice.timeAt(held.delay + voice.span)
|
|
133
|
-
: voice.ranBackTo(held.delay, now);
|
|
134
|
-
// Never probed, it may be a record a seek put back, sharing its copy's state until it steps.
|
|
135
|
-
const snaps = held.snaps;
|
|
136
|
-
const sought = Number.isNaN(held.probed) && snaps !== undefined;
|
|
137
|
-
if (!Number.isNaN(to)) {
|
|
138
|
-
if (held.stepped < held.since) {
|
|
139
|
-
held.stepped = held.since;
|
|
140
|
-
setting.dt = this.capped(now - held.since);
|
|
141
|
-
}
|
|
142
|
-
const tick = stepOf(this, voice);
|
|
143
|
-
if (tick !== undefined && sought)
|
|
144
|
-
ahead(this, voice, subject, held, snaps, tick, to);
|
|
145
|
-
if (sought)
|
|
146
|
-
own(voice, held);
|
|
147
|
-
if (tick !== undefined)
|
|
148
|
-
this.tick(voice, subject, held, tick, to);
|
|
149
|
-
else if (still === 0 ? now !== held.stepped : to > held.stepped) {
|
|
150
|
-
if (still !== 0)
|
|
151
|
-
setting.dt = this.capped(to - held.stepped);
|
|
152
|
-
voice.patch.step(held.state, setting.dt, subject, setting);
|
|
153
|
-
held.stepped = to;
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
else if (sought)
|
|
157
|
-
own(voice, held);
|
|
158
|
-
}
|
|
159
|
-
let delta = null;
|
|
160
|
-
// A keys voice in a locus reads at weight 0 too: having a delta makes it a member, and the
|
|
161
|
-
// locus folds at its first member's place.
|
|
162
|
-
if (silent(voice, weight) && (voice.built === null || voice.spec.locus === undefined)) {
|
|
163
|
-
// A `from: 'current'` voice takes its base on its first read, silent or not.
|
|
164
|
-
if (voice.built && voice.spec.from === 'current' && held.base === undefined) {
|
|
165
|
-
held.base = this.baseFor(voice, subject);
|
|
166
|
-
held.slope = this.slopeFor(voice, subject);
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
else if (voice.built) {
|
|
170
|
-
held.phase = phase;
|
|
171
|
-
delta = this.keyed(voice, subject, held);
|
|
172
|
-
}
|
|
173
|
-
else if (voice.motion !== undefined) {
|
|
174
|
-
delta = moved(voice.motion, voice.patch.writes[0], subject, setting, held);
|
|
175
|
-
}
|
|
176
|
-
else {
|
|
177
|
-
delta = voice.patch.at(phase, subject, setting);
|
|
178
|
-
if (voice.unchecked)
|
|
179
|
-
checkWrites(voice, delta);
|
|
180
|
-
}
|
|
181
|
-
held.delta = delta;
|
|
182
|
-
held.probed = now;
|
|
183
|
-
held.seeks = voice.seeks;
|
|
184
|
-
if (history !== undefined)
|
|
185
|
-
remember(this, voice, subject, held);
|
|
186
|
-
if (reading.kept !== keptBefore && !voice.keeping)
|
|
187
|
-
stateful(this, voice);
|
|
188
|
-
if (delta === null)
|
|
189
|
-
return null;
|
|
190
|
-
if (voice.out?.rest && this.isRest(delta)) {
|
|
191
|
-
held.weight = 0;
|
|
192
|
-
held.rested = true;
|
|
193
|
-
held.restedAt = now;
|
|
194
|
-
voice.restedCount++;
|
|
195
|
-
return null;
|
|
196
|
-
}
|
|
197
|
-
this.w = weight;
|
|
198
|
-
return delta;
|
|
199
|
-
}
|
|
200
|
-
/** Reads a keys voice's stops at `held.phase` into the subject's delta, through the voice's scratch. */
|
|
201
|
-
export function keyed(voice, subject, held) {
|
|
202
|
-
let base;
|
|
203
|
-
if (voice.spec.from === 'current') {
|
|
204
|
-
if (held.base === undefined) {
|
|
205
|
-
held.base = this.baseFor(voice, subject);
|
|
206
|
-
held.slope = this.slopeFor(voice, subject);
|
|
207
|
-
}
|
|
208
|
-
base = held.base;
|
|
209
|
-
}
|
|
210
|
-
const delta = readKeys(voice.built, held.phase, held.delta ?? {}, base, voice.lerps, held.slope, voice.intos, voice.scratch);
|
|
211
|
-
held.delta = delta;
|
|
212
|
-
if (voice.scratch.length > 0)
|
|
213
|
-
voice.holder = held;
|
|
214
|
-
return delta;
|
|
215
|
-
}
|
|
216
|
-
/** How many whole intervals of `tick` a subject's step grid holds by mix time `to`. */
|
|
217
|
-
function dueBy(held, tick, to) {
|
|
218
|
-
// The epsilon keeps an interval like 1000 / 120 from landing a hair short of a whole count.
|
|
219
|
-
return Math.floor((to - held.since) / tick + 1e-9);
|
|
220
|
-
}
|
|
221
|
-
/**
|
|
222
|
-
* The first step of a record a seek put back from a copy: takes up the furthest state stepped on
|
|
223
|
-
* from that copy that the steps due now run past, the one the record the seek replaced reached or
|
|
224
|
-
* the one the copy was last stepped ahead to, and otherwise keeps the copy's stepped ahead to the
|
|
225
|
-
* moment sought, so a mix sought back time after time does not step from the copy each time, nor a
|
|
226
|
-
* mix sought back and returned step again to where it was. Only where the same steps give the same
|
|
227
|
-
* state: a fixed interval, no `maxDt`, no host field read.
|
|
228
|
-
*/
|
|
229
|
-
function ahead(mix, voice, subject, held, snaps, tick, to) {
|
|
230
|
-
const from = snaps[snaps.length - 1];
|
|
231
|
-
const got = from?.ahead;
|
|
232
|
-
if (from === undefined || got === undefined || got.put !== held)
|
|
233
|
-
return;
|
|
234
|
-
got.put = null;
|
|
235
|
-
const reads = voice.patch.reads;
|
|
236
|
-
if (mix.opts.maxDt !== undefined || (reads !== undefined && reads.length > 0))
|
|
237
|
-
return;
|
|
238
|
-
// Stepped, rebuilt or placed anew since it was put back, its steps are no longer the copy's.
|
|
239
|
-
const was = from.held;
|
|
240
|
-
if (held.ticks !== was.ticks || held.since !== got.since || held.rebuilt !== was.rebuilt)
|
|
241
|
-
return;
|
|
242
|
-
const at = got.sought;
|
|
243
|
-
const due = dueBy(held, tick, to);
|
|
244
|
-
const usable = got.state !== undefined && got.ticks <= due;
|
|
245
|
-
if (got.left !== undefined && got.leftTicks <= due && !(usable && got.ticks > got.leftTicks)) {
|
|
246
|
-
// Taken, not copied: the next seek back keeps the state this record steps on to in its place.
|
|
247
|
-
held.state = got.left;
|
|
248
|
-
got.left = undefined;
|
|
249
|
-
voice.setting.state = held.state;
|
|
250
|
-
held.ticks = got.leftTicks;
|
|
251
|
-
return;
|
|
252
|
-
}
|
|
253
|
-
if (usable) {
|
|
254
|
-
held.state = copyState(voice, got.state);
|
|
255
|
-
voice.setting.state = held.state;
|
|
256
|
-
held.ticks = got.ticks;
|
|
257
|
-
}
|
|
258
|
-
const last = usable ? got.at : from.at;
|
|
259
|
-
if (!(at < to) || at - last < apart(mix.opts.history))
|
|
260
|
-
return;
|
|
261
|
-
own(voice, held);
|
|
262
|
-
mix.tick(voice, subject, held, tick, at);
|
|
263
|
-
got.state = copyState(voice, held.state);
|
|
264
|
-
got.ticks = held.ticks;
|
|
265
|
-
got.at = at;
|
|
266
|
-
}
|
|
267
|
-
/**
|
|
268
|
-
* A record a seek put back shares the state of the copy it was made from, which no read changes,
|
|
269
|
-
* until its first probe: before it steps, it takes a copy of its own.
|
|
270
|
-
*/
|
|
271
|
-
function own(voice, held) {
|
|
272
|
-
const snaps = held.snaps;
|
|
273
|
-
const from = snaps[snaps.length - 1];
|
|
274
|
-
if (from !== undefined && held.state !== undefined && held.state === from.held.state) {
|
|
275
|
-
held.state = copyState(voice, held.state);
|
|
276
|
-
voice.setting.state = held.state;
|
|
277
|
-
}
|
|
278
|
-
}
|
|
279
|
-
/**
|
|
280
|
-
* Runs `step` once per whole interval between where this subject last stopped and now, on a grid
|
|
281
|
-
* counted from when its delay ran out, and leaves the remainder for the next sample. Counting
|
|
282
|
-
* rather than adding keeps the grid where it is however many samples it is reached through.
|
|
283
|
-
*/
|
|
284
|
-
export function tick(voice, subject, held, tick, to) {
|
|
285
|
-
const now = this.now;
|
|
286
|
-
const setting = voice.setting;
|
|
287
|
-
const frameDt = setting.dt;
|
|
288
|
-
let n = held.ticks;
|
|
289
|
-
const due = dueBy(held, tick, to);
|
|
290
|
-
const cap = this.opts.maxDt;
|
|
291
|
-
if (cap !== undefined) {
|
|
292
|
-
const most = Math.floor(cap / tick);
|
|
293
|
-
if (due - n > most)
|
|
294
|
-
n = due - most;
|
|
295
|
-
}
|
|
296
|
-
const step = voice.patch.step;
|
|
297
|
-
setting.dt = tick;
|
|
298
|
-
for (n++; n <= due; n++) {
|
|
299
|
-
setting.timestamp = held.since + n * tick;
|
|
300
|
-
step(held.state, tick, subject, setting);
|
|
301
|
-
}
|
|
302
|
-
held.ticks = due > held.ticks ? due : held.ticks;
|
|
303
|
-
held.stepped = held.since + held.ticks * tick;
|
|
304
|
-
setting.timestamp = now;
|
|
305
|
-
setting.dt = frameDt;
|
|
306
|
-
}
|
|
307
|
-
/**
|
|
308
|
-
* Stop 0 for a retargeting voice: the subject's pose in the frame before this voice contributes.
|
|
309
|
-
* With no such frame on record — a voice cued before the subject was ever probed, or a host that
|
|
310
|
-
* samples into its own object — it is this frame's pose with every other voice folded in.
|
|
311
|
-
*/
|
|
312
|
-
export function baseFor(voice, subject) {
|
|
313
|
-
let prior = this.pose.get(subject)?.pose;
|
|
314
|
-
if (prior === undefined && !this.folding.has(voice.id)) {
|
|
315
|
-
this.folding.add(voice.id);
|
|
316
|
-
try {
|
|
317
|
-
prior = this.fold(subject, undefined, false, voice.id);
|
|
318
|
-
}
|
|
319
|
-
finally {
|
|
320
|
-
this.folding.delete(voice.id);
|
|
321
|
-
}
|
|
322
|
-
}
|
|
323
|
-
const base = {};
|
|
324
|
-
for (const key of voice.patch.writes) {
|
|
325
|
-
const channel = this.kit[key];
|
|
326
|
-
const value = prior?.[key];
|
|
327
|
-
const fallback = channel.rest;
|
|
328
|
-
if (value !== undefined)
|
|
329
|
-
base[key] = copy(value);
|
|
330
|
-
else if (fallback !== undefined)
|
|
331
|
-
base[key] = copy(fallback);
|
|
332
|
-
}
|
|
333
|
-
return base;
|
|
334
|
-
}
|
|
335
|
-
/**
|
|
336
|
-
* How fast each numeric channel this voice writes was moving, from the subject's last two probed
|
|
337
|
-
* poses, in units per ms. Undefined with fewer than two on record, which starts the voice at rest.
|
|
338
|
-
*/
|
|
339
|
-
export function slopeFor(voice, subject) {
|
|
340
|
-
const rec = this.pose.get(subject);
|
|
341
|
-
if (rec?.prev === undefined || !(rec.at > rec.prevAt))
|
|
342
|
-
return undefined;
|
|
343
|
-
const dt = rec.at - rec.prevAt;
|
|
344
|
-
const now = rec.pose;
|
|
345
|
-
const before = rec.prev;
|
|
346
|
-
const slope = {};
|
|
347
|
-
for (const key of voice.patch.writes) {
|
|
348
|
-
const a = before[key];
|
|
349
|
-
const b = now[key];
|
|
350
|
-
if (typeof a === 'number' && typeof b === 'number')
|
|
351
|
-
slope[key] = (b - a) / dt;
|
|
352
|
-
else if (Array.isArray(a) && Array.isArray(b) && a.length === b.length)
|
|
353
|
-
slope[key] = b.map((v, i) => (v - a[i]) / dt);
|
|
354
|
-
}
|
|
355
|
-
return slope;
|
|
356
|
-
}
|
|
357
|
-
export function isRest(delta) {
|
|
358
|
-
for (const key of Object.keys(delta)) {
|
|
359
|
-
if (delta[key] === undefined)
|
|
360
|
-
continue;
|
|
361
|
-
const channel = this.kit[key];
|
|
362
|
-
if (channel.rest === undefined)
|
|
363
|
-
return false;
|
|
364
|
-
if (!near(delta[key], channel.rest))
|
|
365
|
-
return false;
|
|
366
|
-
}
|
|
367
|
-
return true;
|
|
368
|
-
}
|
|
369
36
|
/**
|
|
370
37
|
* A record's band state once its voice is skipped at weight 0: off, as `passes` would have said, so
|
|
371
38
|
* a weight that comes back inside the band does not find it still on.
|
|
@@ -430,18 +97,6 @@ export function bound(channel, v) {
|
|
|
430
97
|
}
|
|
431
98
|
return v;
|
|
432
99
|
}
|
|
433
|
-
/**
|
|
434
|
-
* Whether a fold's next voice is its chain's next record, `held`, and not the next voice sharing
|
|
435
|
-
* one record, `sharers[q]`: the two lists are each in voice order, and a fold takes them as one.
|
|
436
|
-
*/
|
|
437
|
-
export function chainFirst(held, sharers, q) {
|
|
438
|
-
if (held === null)
|
|
439
|
-
return false;
|
|
440
|
-
if (q >= sharers.length || held.voice === null)
|
|
441
|
-
return true;
|
|
442
|
-
const next = sharers[q];
|
|
443
|
-
return next.id > held.voice.id;
|
|
444
|
-
}
|
|
445
100
|
/**
|
|
446
101
|
* After a fold read a sharing voice for the subject numbered `slot`: keeps the weight it gave and
|
|
447
102
|
* what the read left on its record, and gives the fold's place in `sharers`, which moves back where
|
|
@@ -469,7 +124,7 @@ export function fold(subject, out, dry = false, except) {
|
|
|
469
124
|
}
|
|
470
125
|
export function folded(subject, out, dry = false, except) {
|
|
471
126
|
const head = this.linked(subject);
|
|
472
|
-
return this.foldWith(subject, out ??
|
|
127
|
+
return this.foldWith(subject, out ?? this.pose(), head, this.linkedLaned, dry, except);
|
|
473
128
|
}
|
|
474
129
|
/**
|
|
475
130
|
* A subject's chain, linked for this frame, and in `linkedLaned` whether its lanes answer for the
|
|
@@ -555,15 +210,15 @@ export function foldWith(subject, out, head, laned, dry, except) {
|
|
|
555
210
|
return this.foldDetour(subject, pose, head, laned, dry, except, owed);
|
|
556
211
|
}
|
|
557
212
|
/**
|
|
558
|
-
* `foldWith`'s voices in a mix with a
|
|
213
|
+
* `foldWith`'s voices in a mix with a role or a voice sharing one record in it. The chain is taken
|
|
559
214
|
* again where a voice stopped sharing since it was linked, as a patch call in this probe's fill can
|
|
560
|
-
* make one do. With no
|
|
215
|
+
* make one do. With no role, the chain's records fold in voice order, and each voice sharing one
|
|
561
216
|
* record, which is in no chain, before the first chain record that comes after it.
|
|
562
217
|
*/
|
|
563
218
|
export function foldDetour(subject, pose, linkedHead, laned, dry, except, owed) {
|
|
564
219
|
const head = linkedHead.version === this.version ? linkedHead : this.chain(subject);
|
|
565
|
-
if (this.
|
|
566
|
-
return this.
|
|
220
|
+
if (this.roleVoices !== 0)
|
|
221
|
+
return this.foldRoles(subject, pose, head, laned, dry, except, owed);
|
|
567
222
|
const now = this.now;
|
|
568
223
|
const lanes = this.lanes;
|
|
569
224
|
const sharers = this.sharers;
|
|
@@ -625,166 +280,3 @@ export function foldSharers(pose, subject, head, q, before, laned, dry, except,
|
|
|
625
280
|
export function owedBy(owed, voice) {
|
|
626
281
|
return owed >= 0 && this.lanes.owesVoice(owed, voice.id);
|
|
627
282
|
}
|
|
628
|
-
/** `foldWith`'s voices with a locus in play: each locus folds at its first member's place. */
|
|
629
|
-
export function foldLoci(subject, pose, head, laned, dry, except, owed) {
|
|
630
|
-
const now = this.now;
|
|
631
|
-
// A patch reading the pose can fold again from inside this one, so each depth has its own.
|
|
632
|
-
let k = this.locusScratch[this.locusDepth];
|
|
633
|
-
if (k === undefined) {
|
|
634
|
-
k = locusScratch(this.channels.length);
|
|
635
|
-
this.locusScratch[this.locusDepth] = k;
|
|
636
|
-
}
|
|
637
|
-
this.locusDepth++;
|
|
638
|
-
try {
|
|
639
|
-
k.n = 0;
|
|
640
|
-
k.m = 0;
|
|
641
|
-
k.names.length = 0;
|
|
642
|
-
const sharers = this.sharers;
|
|
643
|
-
let q = 0;
|
|
644
|
-
let at = head;
|
|
645
|
-
while (at !== null || q < sharers.length) {
|
|
646
|
-
const mine = chainFirst(at, sharers, q);
|
|
647
|
-
let voice;
|
|
648
|
-
let rec;
|
|
649
|
-
if (mine) {
|
|
650
|
-
rec = at;
|
|
651
|
-
voice = rec.voice;
|
|
652
|
-
at = rec.next;
|
|
653
|
-
}
|
|
654
|
-
else {
|
|
655
|
-
voice = sharers[q++];
|
|
656
|
-
rec = voice.state === 'pending' ? null : voice.everyone;
|
|
657
|
-
}
|
|
658
|
-
if (voice === null || rec === null)
|
|
659
|
-
continue;
|
|
660
|
-
if (laned && voice.laned && !this.owedBy(owed, voice))
|
|
661
|
-
continue;
|
|
662
|
-
if (voice.id === except || voice.state === 'done')
|
|
663
|
-
continue;
|
|
664
|
-
const held = rec;
|
|
665
|
-
if (!mine)
|
|
666
|
-
open(voice, held, head.slot);
|
|
667
|
-
const delta = this.read(voice, subject, now, dry, held);
|
|
668
|
-
if (owed >= 0 && voice.laned)
|
|
669
|
-
this.lanes.paid(voice.id, owed, held.weight);
|
|
670
|
-
if (!mine)
|
|
671
|
-
q = this.shared(voice, head.slot, held, q);
|
|
672
|
-
if (delta === null)
|
|
673
|
-
continue;
|
|
674
|
-
const locus = voice.spec.locus;
|
|
675
|
-
let group = -1;
|
|
676
|
-
if (locus !== undefined) {
|
|
677
|
-
group = k.names.indexOf(locus);
|
|
678
|
-
const m = k.m++;
|
|
679
|
-
k.mVoices[m] = voice;
|
|
680
|
-
k.mDeltas[m] = delta;
|
|
681
|
-
k.mWeights[m] = this.w;
|
|
682
|
-
if (group >= 0) {
|
|
683
|
-
k.mGroups[m] = group;
|
|
684
|
-
continue;
|
|
685
|
-
}
|
|
686
|
-
group = k.names.length;
|
|
687
|
-
k.names.push(locus);
|
|
688
|
-
k.mGroups[m] = group;
|
|
689
|
-
}
|
|
690
|
-
const n = k.n++;
|
|
691
|
-
k.voices[n] = voice;
|
|
692
|
-
k.helds[n] = held;
|
|
693
|
-
k.deltas[n] = delta;
|
|
694
|
-
k.weights[n] = locus === undefined ? this.w : 0;
|
|
695
|
-
k.groups[n] = group;
|
|
696
|
-
}
|
|
697
|
-
let bands = head.loci;
|
|
698
|
-
if (bands === null) {
|
|
699
|
-
bands = new Map();
|
|
700
|
-
head.loci = bands;
|
|
701
|
-
}
|
|
702
|
-
for (let n = 0; n < k.n; n++) {
|
|
703
|
-
const group = k.groups[n];
|
|
704
|
-
if (group < 0) {
|
|
705
|
-
const weight = k.weights[n];
|
|
706
|
-
if (weight > 0)
|
|
707
|
-
this.apply(pose, k.voices[n], k.helds[n], k.deltas[n], weight);
|
|
708
|
-
else
|
|
709
|
-
unband(k.helds[n]);
|
|
710
|
-
continue;
|
|
711
|
-
}
|
|
712
|
-
const weight = this.foldLocus(k, group);
|
|
713
|
-
const name = k.names[group];
|
|
714
|
-
if (!(weight > 0)) {
|
|
715
|
-
bands.get(name)?.fill(2);
|
|
716
|
-
continue;
|
|
717
|
-
}
|
|
718
|
-
let on = bands.get(name);
|
|
719
|
-
if (on === undefined) {
|
|
720
|
-
on = new Uint8Array(this.channels.length);
|
|
721
|
-
bands.set(name, on);
|
|
722
|
-
}
|
|
723
|
-
for (let t = 0; t < k.touched.length; t++) {
|
|
724
|
-
const slot = k.touched[t];
|
|
725
|
-
const value = k.values[slot];
|
|
726
|
-
k.values[slot] = undefined;
|
|
727
|
-
const key = this.names[slot];
|
|
728
|
-
const channel = this.channels[slot];
|
|
729
|
-
// A channel folds at the weight of the members that wrote it, not the whole locus.
|
|
730
|
-
const taken = k.taken[slot];
|
|
731
|
-
const w = taken > 1 ? 1 : taken;
|
|
732
|
-
if (channel.rest !== undefined && channel.scale) {
|
|
733
|
-
// pose[key] is the copy of rest this fold made, so it is ours to write into.
|
|
734
|
-
pose[key] = channel.fold
|
|
735
|
-
? channel.fold(pose[key], value, w)
|
|
736
|
-
: merged(channel, pose[key], channel.scale(value, w));
|
|
737
|
-
continue;
|
|
738
|
-
}
|
|
739
|
-
const band = on[slot];
|
|
740
|
-
const passes = this.passes(band === 0 ? undefined : band === 1, w);
|
|
741
|
-
on[slot] = passes ? 1 : 2;
|
|
742
|
-
if (passes)
|
|
743
|
-
pose[key] = pose[key] === undefined ? first(value) : merged(channel, pose[key], value);
|
|
744
|
-
}
|
|
745
|
-
}
|
|
746
|
-
return pose;
|
|
747
|
-
}
|
|
748
|
-
finally {
|
|
749
|
-
for (let n = 0; n < k.n; n++) {
|
|
750
|
-
k.voices[n] = null;
|
|
751
|
-
k.helds[n] = null;
|
|
752
|
-
k.deltas[n] = null;
|
|
753
|
-
}
|
|
754
|
-
for (let m = 0; m < k.m; m++) {
|
|
755
|
-
k.mVoices[m] = null;
|
|
756
|
-
k.mDeltas[m] = null;
|
|
757
|
-
}
|
|
758
|
-
// Left set only where a channel's lerp threw partway, which the next fold must not inherit.
|
|
759
|
-
for (let t = 0; t < k.touched.length; t++) {
|
|
760
|
-
const slot = k.touched[t];
|
|
761
|
-
k.met[slot] = 0;
|
|
762
|
-
k.values[slot] = undefined;
|
|
763
|
-
}
|
|
764
|
-
k.touched.length = 0;
|
|
765
|
-
this.locusDepth--;
|
|
766
|
-
}
|
|
767
|
-
}
|
|
768
|
-
/** `contribution`, or for a dry fold a subject already probed this frame, without advancing it. */
|
|
769
|
-
export function read(voice, subject, now, dry, held) {
|
|
770
|
-
if (dry) {
|
|
771
|
-
if (held.reaches && held.probed === now && held.delta && held.seeks === voice.seeks) {
|
|
772
|
-
const setting = voice.setting;
|
|
773
|
-
// The setting the probe that read the delta had, but for `dt`: a dry read advances nothing.
|
|
774
|
-
const elapsed = voice.shownAt(voice.rawAt(now, held.delay));
|
|
775
|
-
setting.timestamp = now;
|
|
776
|
-
setting.dt = 0;
|
|
777
|
-
setting.elapsed = elapsed;
|
|
778
|
-
setting.pass = passAt(elapsed, voice.duration, voice.passes);
|
|
779
|
-
setting.weight = 0;
|
|
780
|
-
setting.state = held.state;
|
|
781
|
-
voice.keepOn = held;
|
|
782
|
-
this.w = this.weigh(voice, subject, now, held);
|
|
783
|
-
held.weight = this.w;
|
|
784
|
-
if (voice.holder !== held && voice.scratch.length > 0)
|
|
785
|
-
this.keyed(voice, subject, held);
|
|
786
|
-
return held.delta;
|
|
787
|
-
}
|
|
788
|
-
}
|
|
789
|
-
return this.contribution(voice, subject, now, held);
|
|
790
|
-
}
|
package/dist/gather.d.ts
CHANGED
|
@@ -1,14 +1,19 @@
|
|
|
1
|
-
import { type Lane, type Laned, type
|
|
1
|
+
import { type Lane, type Laned, type Role } from './lane.js';
|
|
2
2
|
import type { Lanes } from './lanes.js';
|
|
3
3
|
/**
|
|
4
|
-
* Every member of a
|
|
4
|
+
* Every member of a role, in voice order, gathered rather than folded: a member's delta for a
|
|
5
5
|
* subject is lerped into what the members before it left there by its share of the weight
|
|
6
|
-
* taken, as the general path's `
|
|
6
|
+
* taken, as the general path's `foldRole` does, so each subject's role can fold in at its
|
|
7
7
|
* first member's place in the order.
|
|
8
8
|
*/
|
|
9
|
-
export declare function
|
|
10
|
-
/** A
|
|
11
|
-
export declare function
|
|
9
|
+
export declare function gatherRole<I, O>(lanes: Lanes<I, O>, g: Role<I, O>): void;
|
|
10
|
+
/** A role member's turn in the order: each subject it was the first member of takes the role. */
|
|
11
|
+
export declare function foldRole<I, O>(lanes: Lanes<I, O>, lane: Lane<I, O>, g: Role<I, O>): void;
|
|
12
|
+
/**
|
|
13
|
+
* An inlined role at its first member's turn: each subject's members gathered in voice order, as
|
|
14
|
+
* `gatherRole` gathers them, then folded at once, with no pass of its own and no array of sums.
|
|
15
|
+
*/
|
|
16
|
+
export declare function runRole<I, O>(lanes: Lanes<I, O>, g: Role<I, O>): void;
|
|
12
17
|
/** For a keys voice: the delta's value for each channel it writes, read once per phase. */
|
|
13
18
|
export declare function gather<I, O>(this: Lanes<I, O>, lane: Lane<I, O>, delta: Record<string, unknown>): void;
|
|
14
19
|
/** Folds what `gather` read into a subject's laned values, at `Arg.WEIGHT`. */
|
|
@@ -21,7 +26,7 @@ export declare function fold<I, O>(this: Lanes<I, O>, lane: Lane<I, O>, slot: nu
|
|
|
21
26
|
export declare function foldDelta<I, O>(this: Lanes<I, O>, chans: readonly Laned[], slot: number, delta: Record<string, unknown>): void;
|
|
22
27
|
/** Folds a motion sample's axes, kept apart from `foldInto` so neither reads two array kinds. */
|
|
23
28
|
export declare function foldRun<I, O>(this: Lanes<I, O>, ch: Laned, slot: number, xs: Float64Array): void;
|
|
24
|
-
/** Folds one channel's value into a subject's laned values, or its
|
|
29
|
+
/** Folds one channel's value into a subject's laned values, or its role, at `Arg.WEIGHT`. */
|
|
25
30
|
export declare function foldInto<I, O>(this: Lanes<I, O>, ch: Laned, slot: number, value: unknown): void;
|
|
26
31
|
/**
|
|
27
32
|
* Whether a rest-less channel's contribution passes the band, for the record `one` is folding: the
|