@msb235/blits 0.3.0 → 0.5.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 +314 -0
- package/README.md +3 -2
- package/dist/bare.d.ts +29 -0
- package/dist/bare.js +105 -0
- package/dist/blend.d.ts +34 -0
- package/dist/blend.js +102 -0
- package/dist/book.d.ts +53 -0
- package/dist/book.js +218 -0
- package/dist/chain.d.ts +22 -0
- package/dist/chain.js +150 -0
- package/dist/channels.d.ts +2 -0
- package/dist/channels.js +3 -1
- package/dist/clock.d.ts +50 -3
- package/dist/clock.js +102 -9
- package/dist/clone.d.ts +7 -0
- package/dist/clone.js +57 -0
- package/dist/columns.d.ts +29 -0
- package/dist/columns.js +183 -0
- package/dist/crowd.d.ts +128 -0
- package/dist/crowd.js +354 -0
- package/dist/cue.d.ts +3 -0
- package/dist/cue.js +115 -0
- package/dist/due.d.ts +12 -0
- package/dist/due.js +93 -0
- package/dist/fade.d.ts +19 -0
- package/dist/fade.js +133 -0
- package/dist/fill.d.ts +42 -0
- package/dist/fill.js +381 -0
- package/dist/fold.d.ts +51 -0
- package/dist/fold.js +475 -0
- package/dist/gather.d.ts +24 -0
- package/dist/gather.js +186 -0
- package/dist/handle.d.ts +31 -0
- package/dist/handle.js +56 -0
- package/dist/held.d.ts +14 -0
- package/dist/held.js +117 -0
- package/dist/history.d.ts +22 -0
- package/dist/history.js +149 -0
- package/dist/hosts.d.ts +16 -0
- package/dist/hosts.js +178 -0
- package/dist/index.d.ts +1 -1
- package/dist/keyrows.d.ts +36 -0
- package/dist/keyrows.js +138 -0
- package/dist/lane.d.ts +210 -0
- package/dist/lane.js +187 -0
- package/dist/lanes.d.ts +143 -122
- package/dist/lanes.js +236 -742
- package/dist/locus.d.ts +40 -0
- package/dist/locus.js +89 -0
- package/dist/marks.d.ts +12 -0
- package/dist/marks.js +80 -0
- package/dist/meet.d.ts +16 -0
- package/dist/meet.js +113 -0
- package/dist/mixer.d.ts +244 -169
- package/dist/mixer.js +321 -1793
- package/dist/motion.d.ts +104 -19
- package/dist/motion.js +448 -180
- package/dist/move.d.ts +9 -0
- package/dist/move.js +190 -0
- package/dist/named.d.ts +12 -0
- package/dist/named.js +39 -0
- package/dist/numbers.d.ts +18 -6
- package/dist/numbers.js +59 -31
- package/dist/owed.d.ts +24 -0
- package/dist/owed.js +51 -0
- package/dist/owner.d.ts +43 -0
- package/dist/owner.js +117 -0
- package/dist/pace.d.ts +40 -0
- package/dist/pace.js +113 -0
- package/dist/patch.d.ts +51 -10
- package/dist/patch.js +140 -45
- package/dist/place.d.ts +20 -0
- package/dist/place.js +196 -0
- package/dist/project.d.ts +4 -0
- package/dist/project.js +233 -0
- package/dist/pull.d.ts +5 -0
- package/dist/pull.js +175 -0
- package/dist/qualify.d.ts +31 -0
- package/dist/qualify.js +345 -0
- package/dist/relink.d.ts +25 -0
- package/dist/relink.js +59 -0
- package/dist/rows.d.ts +33 -0
- package/dist/rows.js +282 -0
- package/dist/sample.d.ts +21 -0
- package/dist/sample.js +232 -0
- package/dist/signals.d.ts +12 -9
- package/dist/signals.js +8 -5
- package/dist/store.d.ts +10 -3
- package/dist/store.js +44 -9
- package/dist/strays.d.ts +21 -0
- package/dist/strays.js +84 -0
- package/dist/tweened.d.ts +11 -0
- package/dist/tweened.js +48 -0
- package/dist/types.d.ts +261 -42
- package/dist/unreached.d.ts +5 -0
- package/dist/unreached.js +24 -0
- package/dist/voice.d.ts +240 -0
- package/dist/voice.js +322 -0
- package/dist/weigh.d.ts +26 -0
- package/dist/weigh.js +89 -0
- package/docs/schema.html +151 -46
- package/package.json +5 -2
package/dist/crowd.js
ADDED
|
@@ -0,0 +1,354 @@
|
|
|
1
|
+
import { bareRows, clock, unbare } from './bare.js';
|
|
2
|
+
import { clampWeight, passAt, phaseAt, weighed } from './clock.js';
|
|
3
|
+
import { KeyRows } from './keyrows.js';
|
|
4
|
+
import { held, SPARSE } from './lane.js';
|
|
5
|
+
import { closed } from './motion.js';
|
|
6
|
+
import { move } from './sample.js';
|
|
7
|
+
/**
|
|
8
|
+
* Whether a laned voice belongs in a crowd: one naming a single subject, which shares nothing
|
|
9
|
+
* across subjects for a lane to save. A motion voice writes one channel; a keys or fn voice may
|
|
10
|
+
* write several, and joins the crowd of voices writing those same channels.
|
|
11
|
+
*/
|
|
12
|
+
export function crowdable(v) {
|
|
13
|
+
return (v.named !== null &&
|
|
14
|
+
v.named.size === 1 &&
|
|
15
|
+
v.spec.locus === undefined &&
|
|
16
|
+
(v.slots.length === 1 || v.motion === undefined));
|
|
17
|
+
}
|
|
18
|
+
/** The law of a row that has none, a keys or fn voice's. */
|
|
19
|
+
export const none = new Float64Array(0);
|
|
20
|
+
/** Whether a row's flags let `bareRows` take it. */
|
|
21
|
+
export function bare(f) {
|
|
22
|
+
return (f & 2043 /* Bare.MASK */) === 1211 /* Bare.WANT */;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Every laned voice on one channel that reaches a single subject, a row each, in voice order: the
|
|
26
|
+
* per-subject numbers a lane keeps, and a copy of what a fill reads of the voice and, for a motion
|
|
27
|
+
* voice, its patch, so a fill over 100k of them reads a few flat arrays instead of 100k voices,
|
|
28
|
+
* lanes and patch buffers. Each copy is written when its source changes: the voice's by
|
|
29
|
+
* `voiceChanged`, the stretch by `stretchChanged`.
|
|
30
|
+
*/
|
|
31
|
+
export class Crowd {
|
|
32
|
+
chans;
|
|
33
|
+
axes;
|
|
34
|
+
/** The subject number at each row. */
|
|
35
|
+
list = [];
|
|
36
|
+
/** `STRIDE` numbers per row, laid out as a lane's. */
|
|
37
|
+
data = new Float64Array(0);
|
|
38
|
+
hot = new Float64Array(0);
|
|
39
|
+
stride;
|
|
40
|
+
samples = new Float64Array(0);
|
|
41
|
+
deltas = [];
|
|
42
|
+
records = [];
|
|
43
|
+
voices = [];
|
|
44
|
+
motions = [];
|
|
45
|
+
eases = [];
|
|
46
|
+
/** Each row's patch law, one array shared by every row whose law is the same. */
|
|
47
|
+
laws = [];
|
|
48
|
+
rowOf = new Map();
|
|
49
|
+
idle = false;
|
|
50
|
+
line = SPARSE.other;
|
|
51
|
+
/** Where the fill now under way has reached. */
|
|
52
|
+
cursor = 0;
|
|
53
|
+
/** Rows whose voices have left, kept in place until the crowds are next rebuilt. */
|
|
54
|
+
dead = 0;
|
|
55
|
+
/** Whether some row is `Flag.STALE`, for `freshen` to copy before the fill's loop. */
|
|
56
|
+
stale = false;
|
|
57
|
+
xs;
|
|
58
|
+
vs;
|
|
59
|
+
/** What a keys row reads into, folded before the next row reads. */
|
|
60
|
+
delta = {};
|
|
61
|
+
/** The stops of its `Flag.FLAT` keys rows. */
|
|
62
|
+
keys = new KeyRows();
|
|
63
|
+
scratch = [];
|
|
64
|
+
/** The last fill that ran the crowd's rows, and its `now`, which a `Sampled.BARE` row was sampled at. */
|
|
65
|
+
bareFill = -1;
|
|
66
|
+
bareNow = Number.NaN;
|
|
67
|
+
constructor(chans, axes) {
|
|
68
|
+
this.chans = chans;
|
|
69
|
+
this.axes = axes;
|
|
70
|
+
this.stride = 10 /* Hot.X0 */ + 3 * axes;
|
|
71
|
+
this.xs = new Float64Array(axes);
|
|
72
|
+
this.vs = new Float64Array(axes);
|
|
73
|
+
}
|
|
74
|
+
voiceAt(p) {
|
|
75
|
+
return this.voices[p];
|
|
76
|
+
}
|
|
77
|
+
fix(p) {
|
|
78
|
+
unbare(this, p);
|
|
79
|
+
}
|
|
80
|
+
numbered() { }
|
|
81
|
+
/** Marks row `p` to copy its stretch from its patch again before the next fill reads it. */
|
|
82
|
+
restale(p, flags) {
|
|
83
|
+
this.hot[p * this.stride + 0 /* Hot.FLAGS */] = flags | 4 /* Flag.STALE */;
|
|
84
|
+
this.stale = true;
|
|
85
|
+
}
|
|
86
|
+
/** Samples of another width than the channel's, which `samples` cannot hold, by row. */
|
|
87
|
+
odd = null;
|
|
88
|
+
keep(p, xs, n) {
|
|
89
|
+
if (n === this.axes) {
|
|
90
|
+
for (let a = 0; a < n; a++)
|
|
91
|
+
this.samples[p * n + a] = xs[a];
|
|
92
|
+
this.odd?.delete(p);
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
this.odd ??= new Map();
|
|
96
|
+
this.odd.set(p, xs.slice(0, n));
|
|
97
|
+
}
|
|
98
|
+
kept(p, xs) {
|
|
99
|
+
const odd = this.odd?.get(p);
|
|
100
|
+
if (odd !== undefined) {
|
|
101
|
+
xs.set(odd);
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
const n = this.axes;
|
|
105
|
+
for (let a = 0; a < n; a++)
|
|
106
|
+
xs[a] = this.samples[p * n + a];
|
|
107
|
+
}
|
|
108
|
+
motionAt(p) {
|
|
109
|
+
return this.motions[p];
|
|
110
|
+
}
|
|
111
|
+
get size() {
|
|
112
|
+
return this.list.length;
|
|
113
|
+
}
|
|
114
|
+
/** Makes room for `rows` rows. */
|
|
115
|
+
reserve(rows) {
|
|
116
|
+
if (rows * 8 /* Row.STRIDE */ <= this.data.length)
|
|
117
|
+
return;
|
|
118
|
+
const cap = Math.max(rows, (this.data.length / 8 /* Row.STRIDE */) * 2, 16);
|
|
119
|
+
const data = new Float64Array(cap * 8 /* Row.STRIDE */);
|
|
120
|
+
data.set(this.data);
|
|
121
|
+
this.data = data;
|
|
122
|
+
const hot = new Float64Array(cap * this.stride);
|
|
123
|
+
hot.set(this.hot);
|
|
124
|
+
this.hot = hot;
|
|
125
|
+
const samples = new Float64Array(cap * this.axes);
|
|
126
|
+
samples.set(this.samples);
|
|
127
|
+
this.samples = samples;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
/** Runs every crowd's rows whose voices come before voice `id`, from where each crowd reached. */
|
|
131
|
+
export function crowdsUpTo(lanes, id) {
|
|
132
|
+
if (!lanes.overlap) {
|
|
133
|
+
for (const c of lanes.crowds)
|
|
134
|
+
if (!c.idle && c.cursor < c.size)
|
|
135
|
+
runCrowd(lanes, c, id);
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
// Crowds sharing a channel take turns in voice order, so a subject's rows fold as one crowd's do.
|
|
139
|
+
for (;;) {
|
|
140
|
+
let first;
|
|
141
|
+
let a = id;
|
|
142
|
+
let b = id;
|
|
143
|
+
for (const c of lanes.crowds) {
|
|
144
|
+
if (c.idle || c.cursor >= c.size)
|
|
145
|
+
continue;
|
|
146
|
+
const next = c.hot[c.cursor * c.stride + 7 /* Hot.ID */];
|
|
147
|
+
if (next < a) {
|
|
148
|
+
b = a;
|
|
149
|
+
a = next;
|
|
150
|
+
first = c;
|
|
151
|
+
}
|
|
152
|
+
else if (next < b)
|
|
153
|
+
b = next;
|
|
154
|
+
}
|
|
155
|
+
if (first === undefined)
|
|
156
|
+
return;
|
|
157
|
+
runCrowd(lanes, first, b);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
/** A crowd's rows from its cursor up to voice `id`, bare tweens in `bareRows` and the rest here. */
|
|
161
|
+
function runCrowd(lanes, c, id) {
|
|
162
|
+
const list = c.list;
|
|
163
|
+
const hot = c.hot;
|
|
164
|
+
let p = c.cursor;
|
|
165
|
+
for (;;) {
|
|
166
|
+
p = bareRows(lanes, c, p, id);
|
|
167
|
+
if (p >= list.length || hot[p * c.stride + 7 /* Hot.ID */] >= id)
|
|
168
|
+
break;
|
|
169
|
+
p = rows(lanes, c, p, id);
|
|
170
|
+
}
|
|
171
|
+
c.cursor = p;
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* A crowd's rows from `p` up to voice `id`, until one `bareRows` takes after the first: what
|
|
175
|
+
* `runMotion` does for a lane's subjects, reading each row's voice and stretch from `hot`, and
|
|
176
|
+
* through `move` for whatever `hot` cannot answer, as `runMotion` does. Returns where it stopped.
|
|
177
|
+
*/
|
|
178
|
+
function rows(lanes, c, first, id) {
|
|
179
|
+
const data = c.data;
|
|
180
|
+
const hot = c.hot;
|
|
181
|
+
const H = c.stride;
|
|
182
|
+
const per = lanes.per;
|
|
183
|
+
const list = c.list;
|
|
184
|
+
const ch = c.chans[0];
|
|
185
|
+
const n = c.axes;
|
|
186
|
+
const fills = lanes.fills;
|
|
187
|
+
const from = lanes.frameProbes;
|
|
188
|
+
const probed = lanes.probes !== from;
|
|
189
|
+
const now = lanes.now;
|
|
190
|
+
const xs = c.xs;
|
|
191
|
+
const vs = c.vs;
|
|
192
|
+
const samples = c.samples;
|
|
193
|
+
let p = first;
|
|
194
|
+
for (; p < list.length; p++) {
|
|
195
|
+
const h = p * H;
|
|
196
|
+
if (hot[h + 7 /* Hot.ID */] >= id)
|
|
197
|
+
break;
|
|
198
|
+
let f = hot[h + 0 /* Hot.FLAGS */];
|
|
199
|
+
if (p > first && bare(f))
|
|
200
|
+
break;
|
|
201
|
+
if (data[p * 8 /* Row.STRIDE */ + 5 /* Row.SAMPLED */] === -2 /* Sampled.BARE */)
|
|
202
|
+
unbare(c, p);
|
|
203
|
+
if ((f & 64 /* Flag.VOICE */) !== 0)
|
|
204
|
+
f = copyVoice(c, p);
|
|
205
|
+
if ((f & 1 /* Flag.PLAYING */) === 0 || (f & 16 /* Flag.PLACED */) === 0)
|
|
206
|
+
continue;
|
|
207
|
+
const slot = list[p];
|
|
208
|
+
const o = p * 8 /* Row.STRIDE */;
|
|
209
|
+
const q = slot * 6 /* Per.SLOT */;
|
|
210
|
+
if (per[q + 5 /* Per.LANE_FILL */] === fills - 1)
|
|
211
|
+
data[o + 3 /* Row.PROBED */] = data[o + 2 /* Row.WEIGHT */];
|
|
212
|
+
const delay = data[o + 0 /* Row.DELAY */];
|
|
213
|
+
const fast = (f & 2 /* Flag.FAST */) !== 0;
|
|
214
|
+
const voice = c.voices[p];
|
|
215
|
+
const elapsedNow = fast ? clock(hot, h, now) : voice.elapsedAt(now);
|
|
216
|
+
let elapsed = elapsedNow - delay;
|
|
217
|
+
if ((f & 256 /* Flag.HOLDS */) !== 0)
|
|
218
|
+
elapsed = held(voice, elapsed);
|
|
219
|
+
if (!(elapsed >= 0)) {
|
|
220
|
+
data[o + 2 /* Row.WEIGHT */] = 0;
|
|
221
|
+
continue;
|
|
222
|
+
}
|
|
223
|
+
let w;
|
|
224
|
+
if (fast)
|
|
225
|
+
w = hot[h + 4 /* Hot.WEIGHT */];
|
|
226
|
+
else if (typeof voice.spec.weight === 'function') {
|
|
227
|
+
w = lanes.signalled(voice, slot, c.records[p], elapsed, passAt(elapsed, voice.duration, voice.passes), lanes.host.envelope(voice, data[o + 1 /* Row.SINCE */]));
|
|
228
|
+
if (Number.isNaN(w)) {
|
|
229
|
+
data[o + 2 /* Row.WEIGHT */] = 0;
|
|
230
|
+
continue;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
else
|
|
234
|
+
w = weighed(voice.weight, lanes.host.envelope(voice, data[o + 1 /* Row.SINCE */]), lanes.parting(voice, slot));
|
|
235
|
+
data[o + 2 /* Row.WEIGHT */] = w;
|
|
236
|
+
if ((f & 512 /* Flag.FLAT */) !== 0) {
|
|
237
|
+
if (w > 0)
|
|
238
|
+
c.keys.fold(hot[h + 8 /* Hot.AT */], phaseAt(elapsed, hot[h + 10 /* Hot.X0 */], hot[h + 9 /* Hot.MS */]), c.chans, slot, w);
|
|
239
|
+
continue;
|
|
240
|
+
}
|
|
241
|
+
if ((f & 128 /* Flag.MOTION */) === 0) {
|
|
242
|
+
lanes.row(c, p, voice, slot, elapsed, delay, w);
|
|
243
|
+
continue;
|
|
244
|
+
}
|
|
245
|
+
if (data[o + 7 /* Row.MET */] === 0) {
|
|
246
|
+
if (Number.isNaN(c.records[p].probed)) {
|
|
247
|
+
lanes.late.push(slot);
|
|
248
|
+
continue;
|
|
249
|
+
}
|
|
250
|
+
data[o + 7 /* Row.MET */] = 1;
|
|
251
|
+
}
|
|
252
|
+
const ms = data[o + 4 /* Row.MSLOT */];
|
|
253
|
+
if (ms < 0 ||
|
|
254
|
+
(f & 8 /* Flag.BARE */) === 0 ||
|
|
255
|
+
(probed &&
|
|
256
|
+
(per[q + 3 /* Per.LANE_PROBE */] > from ||
|
|
257
|
+
per[q + 4 /* Per.GENERAL_PROBE */] > from))) {
|
|
258
|
+
const run = c.motions[p];
|
|
259
|
+
move(lanes, c, run, p, slot, c.records[p], elapsed, delay, w);
|
|
260
|
+
// The first sample numbered the subject in the patch: copy its stretch from the next fill.
|
|
261
|
+
if (ms < 0)
|
|
262
|
+
c.restale(p, hot[h + 0 /* Hot.FLAGS */]);
|
|
263
|
+
continue;
|
|
264
|
+
}
|
|
265
|
+
closed(c.laws[p], c.eases[p], n, hot[h + 8 /* Hot.AT */], hot[h + 9 /* Hot.MS */], hot, h + 10 /* Hot.X0 */, hot, h + 10 /* Hot.X0 */ + n, hot, h + 10 /* Hot.X0 */ + 2 * n, elapsed, xs, vs);
|
|
266
|
+
for (let a = 0; a < n; a++)
|
|
267
|
+
samples[p * n + a] = xs[a];
|
|
268
|
+
if (c.deltas[p] !== null)
|
|
269
|
+
c.deltas[p] = null;
|
|
270
|
+
data[o + 5 /* Row.SAMPLED */] = fills;
|
|
271
|
+
data[o + 6 /* Row.SEEKS */] = hot[h + 5 /* Hot.SEEKS */];
|
|
272
|
+
if (w <= 0)
|
|
273
|
+
continue;
|
|
274
|
+
if ((f & 32 /* Flag.FOLDS */) !== 0)
|
|
275
|
+
lanes.foldRun(ch, slot, xs, w);
|
|
276
|
+
else
|
|
277
|
+
lanes.foldInto(ch, slot, c.motions[p].value(ms, xs), w);
|
|
278
|
+
}
|
|
279
|
+
return p;
|
|
280
|
+
}
|
|
281
|
+
/** A keys or fn row's contribution, as `one` makes a lane position's. */
|
|
282
|
+
export function row(c, p, voice, slot, elapsed, delay, w) {
|
|
283
|
+
const duration = voice.duration;
|
|
284
|
+
const phase = phaseAt(elapsed, duration, voice.passes);
|
|
285
|
+
if (voice.built !== null) {
|
|
286
|
+
if (w > 0)
|
|
287
|
+
this.foldKeys(voice, c.chans, phase, slot, w);
|
|
288
|
+
return;
|
|
289
|
+
}
|
|
290
|
+
const rec = c.records[p];
|
|
291
|
+
if (Number.isNaN(rec.probed)) {
|
|
292
|
+
this.late.push(slot);
|
|
293
|
+
return;
|
|
294
|
+
}
|
|
295
|
+
this.call(voice, c.chans, rec, slot, elapsed, phase, passAt(elapsed, duration, voice.passes), delay, w);
|
|
296
|
+
}
|
|
297
|
+
/** Copies what a fill reads of a crowd row's voice into `hot`; returns the row's flags. */
|
|
298
|
+
function copyVoice(c, p) {
|
|
299
|
+
const v = c.voices[p];
|
|
300
|
+
const h = p * c.stride;
|
|
301
|
+
let f = c.hot[h + 0 /* Hot.FLAGS */] & ~(64 /* Flag.VOICE */ | 1 /* Flag.PLAYING */ | 2 /* Flag.FAST */ | 256 /* Flag.HOLDS */);
|
|
302
|
+
if (v.state === 'live' || v.state === 'held' || v.state === 'fading')
|
|
303
|
+
f |= 1 /* Flag.PLAYING */;
|
|
304
|
+
if (v.holdsBefore || v.holdsAfter)
|
|
305
|
+
f |= 256 /* Flag.HOLDS */;
|
|
306
|
+
if (v.ramp === null &&
|
|
307
|
+
v.owner === null &&
|
|
308
|
+
!((v.fade.in ?? 0) > 0) &&
|
|
309
|
+
v.out === null &&
|
|
310
|
+
v.parts === null &&
|
|
311
|
+
!v.keeping &&
|
|
312
|
+
typeof v.spec.weight !== 'function')
|
|
313
|
+
f |= 2 /* Flag.FAST */;
|
|
314
|
+
c.hot[h + 1 /* Hot.NOW */] = v.anchorNow;
|
|
315
|
+
c.hot[h + 2 /* Hot.ELAPSED */] = v.anchorElapsed;
|
|
316
|
+
c.hot[h + 3 /* Hot.RATE */] = v.rate;
|
|
317
|
+
c.hot[h + 4 /* Hot.WEIGHT */] = clampWeight(v.weight);
|
|
318
|
+
c.hot[h + 5 /* Hot.SEEKS */] = v.seeks;
|
|
319
|
+
c.hot[h + 0 /* Hot.FLAGS */] = f;
|
|
320
|
+
return f;
|
|
321
|
+
}
|
|
322
|
+
/**
|
|
323
|
+
* Copies stale motion rows' stretches as a fill begins: inside `runCrowd`, once voices came and
|
|
324
|
+
* went, TurboFan spent its inlining on the copy and stopped inlining every row's ease.
|
|
325
|
+
*/
|
|
326
|
+
export function freshen(c) {
|
|
327
|
+
c.stale = false;
|
|
328
|
+
const hot = c.hot;
|
|
329
|
+
const H = c.stride;
|
|
330
|
+
for (let p = 0; p < c.list.length; p++) {
|
|
331
|
+
const f = hot[p * H + 0 /* Hot.FLAGS */];
|
|
332
|
+
if ((f & 4 /* Flag.STALE */) === 0 || (f & 128 /* Flag.MOTION */) === 0)
|
|
333
|
+
continue;
|
|
334
|
+
// A row with no stretch yet is marked again once its first sample numbers it.
|
|
335
|
+
const ms = c.data[p * 8 /* Row.STRIDE */ + 4 /* Row.MSLOT */];
|
|
336
|
+
if (ms >= 0)
|
|
337
|
+
copyStretch(c, p, ms);
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
/** Copies a crowd row's current stretch from its patch into `hot`; returns the row's flags. */
|
|
341
|
+
function copyStretch(c, p, ms) {
|
|
342
|
+
unbare(c, p);
|
|
343
|
+
const run = c.motions[p];
|
|
344
|
+
const h = p * c.stride;
|
|
345
|
+
let f = c.hot[h + 0 /* Hot.FLAGS */] & ~(4 /* Flag.STALE */ | 8 /* Flag.BARE */ | 32 /* Flag.FOLDS */);
|
|
346
|
+
const ch = c.chans[0];
|
|
347
|
+
if (run.n === c.axes && run.stretchInto(ms, c.hot, h + 8 /* Hot.AT */)) {
|
|
348
|
+
f |= 8 /* Flag.BARE */;
|
|
349
|
+
if (run.scalar(ms) === ch.scalar)
|
|
350
|
+
f |= 32 /* Flag.FOLDS */;
|
|
351
|
+
}
|
|
352
|
+
c.hot[h + 0 /* Hot.FLAGS */] = f;
|
|
353
|
+
return f;
|
|
354
|
+
}
|
package/dist/cue.d.ts
ADDED
package/dist/cue.js
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { checkHits } from './book.js';
|
|
2
|
+
import { changed, index } from './chain.js';
|
|
3
|
+
import { schedule } from './due.js';
|
|
4
|
+
import { started } from './held.js';
|
|
5
|
+
import { handle } from './hosts.js';
|
|
6
|
+
import { motionOf } from './motion.js';
|
|
7
|
+
import { adopt, ownerReading } from './owner.js';
|
|
8
|
+
import { durationOf } from './patch.js';
|
|
9
|
+
import { checkPlacement, localNow, mixAt, pin, place } from './place.js';
|
|
10
|
+
import { motionOwner } from './strays.js';
|
|
11
|
+
import { Voice } from './voice.js';
|
|
12
|
+
export function cue(mix, spec) {
|
|
13
|
+
const patch = spec.patch;
|
|
14
|
+
for (const channel of patch.writes) {
|
|
15
|
+
if (!(channel in mix.kit))
|
|
16
|
+
throw new Error(`blits: kit has no channel ${String(channel)}, which this patch writes`);
|
|
17
|
+
const wanted = patch.kit?.[channel];
|
|
18
|
+
const here = mix.kit[channel];
|
|
19
|
+
if (wanted && wanted !== here && (wanted.kind === undefined || wanted.kind !== here.kind))
|
|
20
|
+
throw new Error(`blits: channel ${String(channel)} is ${here.kind ?? 'a custom channel'} in this kit, but the patch was written for ${wanted.kind ?? 'a custom channel'}`);
|
|
21
|
+
}
|
|
22
|
+
const motion = motionOf(patch);
|
|
23
|
+
if (motion !== undefined) {
|
|
24
|
+
const channel = patch.writes[0];
|
|
25
|
+
const rest = mix.kit[channel].rest;
|
|
26
|
+
if (typeof rest === 'number')
|
|
27
|
+
motion.cuedOn(String(channel), true);
|
|
28
|
+
else if (Array.isArray(rest) || ArrayBuffer.isView(rest))
|
|
29
|
+
motion.cuedOn(String(channel), false);
|
|
30
|
+
}
|
|
31
|
+
if (patch.reads) {
|
|
32
|
+
const host = mix.opts.host;
|
|
33
|
+
for (const field of patch.reads)
|
|
34
|
+
if (typeof host !== 'object' || host === null || !(field in host))
|
|
35
|
+
throw new Error(`blits: the patch reads host.${field}, which this mix's host lacks`);
|
|
36
|
+
}
|
|
37
|
+
if (spec.from === 'current' && patch.form !== 'keys')
|
|
38
|
+
throw new Error("blits: from: 'current' needs a keys patch");
|
|
39
|
+
if (spec.subjects !== undefined && spec.target !== undefined)
|
|
40
|
+
throw new Error('blits: a voice takes target or subjects, not both');
|
|
41
|
+
const anchor = spec.anchor;
|
|
42
|
+
const owner = spec.owner === undefined ? null : ownerOf(mix, spec.owner);
|
|
43
|
+
if (anchor)
|
|
44
|
+
checkPlacement(mix, spec, anchor, owner);
|
|
45
|
+
if (spec.hits !== undefined)
|
|
46
|
+
checkHits(spec.hits, durationOf(patch));
|
|
47
|
+
const anchored = anchor !== undefined && (anchor.start !== undefined || anchor.in !== undefined);
|
|
48
|
+
// A voice an owner holds is placed on the owner's clock: ms from when it starts.
|
|
49
|
+
const local = owner === null ? mix.now : ownerReading(owner, mix.now);
|
|
50
|
+
const start = anchored
|
|
51
|
+
? Number.POSITIVE_INFINITY
|
|
52
|
+
: owner !== null
|
|
53
|
+
? (spec.start ?? (local > 0 ? local : 0))
|
|
54
|
+
: spec.start !== undefined
|
|
55
|
+
? mixAt(mix, spec.start - mix.offset)
|
|
56
|
+
: Number.isNaN(mix.now)
|
|
57
|
+
? 0
|
|
58
|
+
: mix.now;
|
|
59
|
+
const voice = new Voice(mix.nextId++, spec, patch, spec.fade ?? {}, local, start, mix.slotOf, mix.channels, mix.opts.host, mix.send, owner);
|
|
60
|
+
if (owner !== null)
|
|
61
|
+
adopt(owner, voice);
|
|
62
|
+
if (voice.holding !== null) {
|
|
63
|
+
mix.owners ??= [];
|
|
64
|
+
mix.owners.push(voice);
|
|
65
|
+
}
|
|
66
|
+
if (!Number.isNaN(mix.now)) {
|
|
67
|
+
voice.cuedAt = mix.now;
|
|
68
|
+
voice.opened = mix.now;
|
|
69
|
+
}
|
|
70
|
+
voice.placing = anchored;
|
|
71
|
+
if (mix.pace !== null && owner === null && spec.start !== undefined && voice.state === 'pending')
|
|
72
|
+
pin(mix, voice, spec.start - mix.offset);
|
|
73
|
+
if (motion !== undefined) {
|
|
74
|
+
mix.playing.set(motion, (mix.playing.get(motion) ?? 0) + 1);
|
|
75
|
+
mix.owner ??= motionOwner(new WeakRef(mix));
|
|
76
|
+
motion.owner = mix.owner;
|
|
77
|
+
motion.ownerId = voice.id;
|
|
78
|
+
}
|
|
79
|
+
mix.cued.push(voice);
|
|
80
|
+
index(mix, voice);
|
|
81
|
+
changed(mix, voice);
|
|
82
|
+
mix.stir();
|
|
83
|
+
if (spec.locus !== undefined)
|
|
84
|
+
mix.loci++;
|
|
85
|
+
if (spec.from === 'current')
|
|
86
|
+
mix.wantsPose = true;
|
|
87
|
+
if (anchor) {
|
|
88
|
+
mix.anchored++;
|
|
89
|
+
place(mix);
|
|
90
|
+
if (!Number.isNaN(mix.now) &&
|
|
91
|
+
voice.state === 'pending' &&
|
|
92
|
+
localNow(mix, voice) >= voice.start) {
|
|
93
|
+
voice.state = 'live';
|
|
94
|
+
started(mix, voice);
|
|
95
|
+
changed(mix, voice);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
// After placing, so the controls it starts with are where its anchors put it at the cue.
|
|
99
|
+
if (mix.opts.history) {
|
|
100
|
+
voice.log = [];
|
|
101
|
+
voice.note(Number.NEGATIVE_INFINITY);
|
|
102
|
+
}
|
|
103
|
+
schedule(mix, voice);
|
|
104
|
+
voice.handle = handle(mix, voice);
|
|
105
|
+
return voice.handle;
|
|
106
|
+
}
|
|
107
|
+
/** The owner a handle names, refused where it is not one of this mix's or has left. */
|
|
108
|
+
function ownerOf(mix, handle) {
|
|
109
|
+
const found = mix.owners?.find((v) => v.handle === handle);
|
|
110
|
+
if (found === undefined)
|
|
111
|
+
throw new Error('blits: owner is not a handle mix.owns returned on this mix');
|
|
112
|
+
if (found.state === 'done')
|
|
113
|
+
throw new Error('blits: that owner has left the mix');
|
|
114
|
+
return found;
|
|
115
|
+
}
|
package/dist/due.d.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { Mixer } from './mixer.js';
|
|
2
|
+
import type { Voice } from './voice.js';
|
|
3
|
+
/** One entry in a mix's due queue; stale once its voice has been scheduled again. */
|
|
4
|
+
export interface Due<I, O> {
|
|
5
|
+
at: number;
|
|
6
|
+
voice: Voice<I, O>;
|
|
7
|
+
token: number;
|
|
8
|
+
}
|
|
9
|
+
/** Files a voice in the due queue at its current due, replacing any entry it had. */
|
|
10
|
+
export declare function schedule<I, O>(mix: Mixer<I, O>, voice: Voice<I, O>): void;
|
|
11
|
+
/** Takes every current entry due by `now` off the queue, in cue order. */
|
|
12
|
+
export declare function popDue<I, O>(mix: Mixer<I, O>, now: number): Voice<I, O>[];
|
package/dist/due.js
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The earliest mix time `moveTo` would change a voice: its start, its anchored out, the end of
|
|
3
|
+
* its last pass, the end of its fade or of a subject's ramp out; -Infinity where it must be
|
|
4
|
+
* visited every frame, Infinity where nothing will happen until a handle or anchor changes it.
|
|
5
|
+
* Early is safe, since a visit that finds nothing due schedules again; late is not, so anything
|
|
6
|
+
* that can bring it earlier calls `schedule`.
|
|
7
|
+
*/
|
|
8
|
+
function dueOf(mix, voice) {
|
|
9
|
+
if (voice.state === 'done')
|
|
10
|
+
return Number.POSITIVE_INFINITY;
|
|
11
|
+
// An owner and the voices it holds run on clocks that move with it, so each is visited every frame.
|
|
12
|
+
if (Number.isNaN(voice.opened) || voice.owner !== null || voice.holding !== null)
|
|
13
|
+
return Number.NEGATIVE_INFINITY;
|
|
14
|
+
let due = Number.POSITIVE_INFINITY;
|
|
15
|
+
if (voice.parts !== null)
|
|
16
|
+
for (const r of voice.parts.values())
|
|
17
|
+
due = Math.min(due, r.at + r.over);
|
|
18
|
+
if (voice.state === 'pending')
|
|
19
|
+
return Math.min(due, Number.isNaN(voice.start) ? due : voice.start);
|
|
20
|
+
if (voice.state === 'fading') {
|
|
21
|
+
const out = voice.out;
|
|
22
|
+
if (out === null || out.rest)
|
|
23
|
+
return Number.NEGATIVE_INFINITY;
|
|
24
|
+
return Math.min(due, out.at + out.over);
|
|
25
|
+
}
|
|
26
|
+
due = Math.min(due, voice.outAt);
|
|
27
|
+
if (Number.isFinite(voice.span)) {
|
|
28
|
+
const end = voice.span + voice.latest;
|
|
29
|
+
// A held voice goes live again once its clock is back before its end: by a seek, by a
|
|
30
|
+
// subject staggered later than any before, or by running backwards, which is checked each
|
|
31
|
+
// frame while it can.
|
|
32
|
+
if (voice.state === 'held') {
|
|
33
|
+
if (voice.rate <= 0 || voice.ramp !== null)
|
|
34
|
+
return Number.NEGATIVE_INFINITY;
|
|
35
|
+
if (!Number.isNaN(mix.now) && voice.elapsedAt(mix.now) < end)
|
|
36
|
+
return Number.NEGATIVE_INFINITY;
|
|
37
|
+
}
|
|
38
|
+
if (voice.state === 'live')
|
|
39
|
+
due = Math.min(due, voice.timeAt(end));
|
|
40
|
+
}
|
|
41
|
+
return due;
|
|
42
|
+
}
|
|
43
|
+
/** Files a voice in the due queue at its current due, replacing any entry it had. */
|
|
44
|
+
export function schedule(mix, voice) {
|
|
45
|
+
if (mix.projecting)
|
|
46
|
+
return;
|
|
47
|
+
mix.lanes?.voiceChanged(voice.id);
|
|
48
|
+
const token = ++voice.dueToken;
|
|
49
|
+
const due = dueOf(mix, voice);
|
|
50
|
+
if (due === Number.POSITIVE_INFINITY)
|
|
51
|
+
return;
|
|
52
|
+
const heap = mix.due;
|
|
53
|
+
heap.push({ at: Number.isNaN(due) ? Number.NEGATIVE_INFINITY : due, voice, token });
|
|
54
|
+
let i = heap.length - 1;
|
|
55
|
+
while (i > 0) {
|
|
56
|
+
const up = (i - 1) >> 1;
|
|
57
|
+
if (heap[up].at <= heap[i].at)
|
|
58
|
+
break;
|
|
59
|
+
[heap[up], heap[i]] = [heap[i], heap[up]];
|
|
60
|
+
i = up;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
/** Takes every current entry due by `now` off the queue, in cue order. */
|
|
64
|
+
export function popDue(mix, now) {
|
|
65
|
+
const heap = mix.due;
|
|
66
|
+
const at = (k) => k < heap.length ? heap[k].at : Number.POSITIVE_INFINITY;
|
|
67
|
+
const out = [];
|
|
68
|
+
while (heap.length > 0 && heap[0].at <= now) {
|
|
69
|
+
const top = heap[0];
|
|
70
|
+
const last = heap.pop();
|
|
71
|
+
if (heap.length > 0) {
|
|
72
|
+
heap[0] = last;
|
|
73
|
+
let i = 0;
|
|
74
|
+
for (;;) {
|
|
75
|
+
const l = 2 * i + 1;
|
|
76
|
+
const r = l + 1;
|
|
77
|
+
const m0 = at(l) < at(i) ? l : i;
|
|
78
|
+
const m = at(r) < at(m0) ? r : m0;
|
|
79
|
+
if (m === i)
|
|
80
|
+
break;
|
|
81
|
+
[heap[m], heap[i]] = [heap[i], heap[m]];
|
|
82
|
+
i = m;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
if (top.token === top.voice.dueToken && top.voice.state !== 'done') {
|
|
86
|
+
top.voice.dueToken++;
|
|
87
|
+
out.push(top.voice);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
if (out.length > 1)
|
|
91
|
+
out.sort((a, b) => a.id - b.id);
|
|
92
|
+
return out;
|
|
93
|
+
}
|
package/dist/fade.d.ts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { Mixer } from './mixer.js';
|
|
2
|
+
import type { Voice } from './voice.js';
|
|
3
|
+
/** Starts one subject's ramp out of a voice, or takes it out at once for a ramp of 0. */
|
|
4
|
+
export declare function fadeSubject<I, O>(mix: Mixer<I, O>, voice: Voice<I, O>, subject: I, over?: number): void;
|
|
5
|
+
/** What a subject's own ramp out of a voice leaves of its weight this frame, 0..1. */
|
|
6
|
+
export declare function parting<I, O>(this: Mixer<I, O>, voice: Voice<I, O>, subject: I, now: number): number;
|
|
7
|
+
/**
|
|
8
|
+
* Takes a subject out of one voice for good, as of mix time `at`: the voice forgets its record,
|
|
9
|
+
* its lane position and a motion patch's state for it, and reaches it no more.
|
|
10
|
+
*/
|
|
11
|
+
export declare function part<I, O>(mix: Mixer<I, O>, voice: Voice<I, O>, subject: I, at: number): void;
|
|
12
|
+
/** Brings a subject faded out of a voice back, to be met afresh on its next probe. */
|
|
13
|
+
export declare function unpart<I, O>(mix: Mixer<I, O>, voice: Voice<I, O>, subject: I): void;
|
|
14
|
+
export declare function beginFade<I, O>(mix: Mixer<I, O>, voice: Voice<I, O>, opts: {
|
|
15
|
+
over?: number;
|
|
16
|
+
at?: 'rest';
|
|
17
|
+
deadline?: number;
|
|
18
|
+
}, at?: number): void;
|
|
19
|
+
export declare function retire<I, O>(mix: Mixer<I, O>, voice: Voice<I, O>, at?: number): void;
|