@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/mixer.js
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
|
+
import { cueArc } from './arcs.js';
|
|
1
2
|
import { blended, blendOf, laneRead, shareKept } from './blend.js';
|
|
2
|
-
import { aims, chain, linkable, relink } from './chain.js';
|
|
3
|
-
import { lerpInto } from './channels.js';
|
|
3
|
+
import { aims, chain, headOf, linkable, relink, unlink } from './chain.js';
|
|
4
|
+
import { lerpInto, scalarOp } from './channels.js';
|
|
4
5
|
import { cue } from './cue.js';
|
|
5
6
|
import { DepartedIndex } from './departed.js';
|
|
6
|
-
import { beginFade,
|
|
7
|
+
import { beginFade, leaveVoice, parting } from './fade.js';
|
|
7
8
|
import { apply, baseFor, clamp, contribution, fold, foldDetour, folded, foldLoci, foldSharers, foldWith, isRest, keyed, linked, near, owedBy, passes, read, shared, slopeFor, tick, } from './fold.js';
|
|
8
9
|
import { GoneIndex, goneIndex } from './gone.js';
|
|
9
10
|
import { held, shownOf, sinceOf, unreachedOf } from './held.js';
|
|
10
|
-
import {
|
|
11
|
+
import { releasing } from './history.js';
|
|
11
12
|
import { book, laneHost } from './hosts.js';
|
|
12
13
|
import { Lanes } from './lanes.js';
|
|
13
14
|
import { foldLocus } from './locus.js';
|
|
@@ -22,11 +23,12 @@ import { Outs } from './revive.js';
|
|
|
22
23
|
import { ScoreIndex } from './scored.js';
|
|
23
24
|
import { seek } from './seek.js';
|
|
24
25
|
import { Fitting, refit } from './spans.js';
|
|
25
|
-
import { Store } from './store.js';
|
|
26
|
-
import { record } from './tape.js';
|
|
26
|
+
import { Masked, Store } from './store.js';
|
|
27
|
+
import { record, unplayable } from './tape.js';
|
|
27
28
|
import { touch } from './touch.js';
|
|
28
29
|
import { announce, Transport } from './transport.js';
|
|
29
30
|
import { none } from './voice.js';
|
|
31
|
+
import { gave, historyFor, register } from './watch.js';
|
|
30
32
|
import { base, capped, fadeOf, horizonFor, ownedBy, ownerBase, prime, weigh, weighOwned, } from './weigh.js';
|
|
31
33
|
/**
|
|
32
34
|
* What a mix takes where its options say nothing. Not exported from the package: the suite turns
|
|
@@ -37,6 +39,7 @@ export const defaults = { lanes: true };
|
|
|
37
39
|
export class Mixer {
|
|
38
40
|
kit;
|
|
39
41
|
opts;
|
|
42
|
+
lanesOf;
|
|
40
43
|
cued = [];
|
|
41
44
|
/** Under `history`, voices that have left but that a read back may still reach. */
|
|
42
45
|
gone = [];
|
|
@@ -87,10 +90,15 @@ export class Mixer {
|
|
|
87
90
|
/** The last array `pull` read, by position, with each subject's chain head, to skip the lookup. */
|
|
88
91
|
pulled = [];
|
|
89
92
|
pulledHeads = [];
|
|
90
|
-
/**
|
|
93
|
+
/**
|
|
94
|
+
* Each remembered head's lane slot, -1 at a position with none, and the `version` and `relinks`
|
|
95
|
+
* they were all current at.
|
|
96
|
+
*/
|
|
91
97
|
pulledSlots = new Int32Array(0);
|
|
92
98
|
pulledVersion = Number.NaN;
|
|
93
99
|
pulledRelinks = -1;
|
|
100
|
+
/** What `pull` knows of each channel it has written, by name; none until the first pull. */
|
|
101
|
+
pullColumns = null;
|
|
94
102
|
/** Counts chains made stale one subject at a time, which `version` does not. */
|
|
95
103
|
relinks = 0;
|
|
96
104
|
/** Read once a sync, so a `reduce` function is not called per voice per subject. */
|
|
@@ -136,6 +144,8 @@ export class Mixer {
|
|
|
136
144
|
*/
|
|
137
145
|
chains = new Store();
|
|
138
146
|
version = 0;
|
|
147
|
+
/** What `prime` reads its clock, voice time and pass from: see `Primed`. */
|
|
148
|
+
primed = new Float64Array(3 /* Primed.SIZE */);
|
|
139
149
|
steps = new Steps();
|
|
140
150
|
/** Per subject, the voices whose `subjects` name it, in voice order. */
|
|
141
151
|
named = new Store();
|
|
@@ -199,6 +209,8 @@ export class Mixer {
|
|
|
199
209
|
bounded;
|
|
200
210
|
/** By slot, a stock array channel's in-place `lerp`. */
|
|
201
211
|
lerpsInto;
|
|
212
|
+
/** By slot, a stock scalar channel's arithmetic, which a fold applies without calling the channel. */
|
|
213
|
+
scalars;
|
|
202
214
|
/** What a fold with a locus in play gathers into, one per depth of folds under way. */
|
|
203
215
|
locusScratch = [];
|
|
204
216
|
locusDepth = 0;
|
|
@@ -209,23 +221,32 @@ export class Mixer {
|
|
|
209
221
|
bookers = null;
|
|
210
222
|
/** The fit a span being cued takes, which `cue` hands its voice. */
|
|
211
223
|
fitting = null;
|
|
224
|
+
/** What an arc being cued keeps, which `cue` hands its voice. */
|
|
225
|
+
arcing = null;
|
|
212
226
|
/** Every owner still in the mix, null until one is cued. */
|
|
213
227
|
owners = null;
|
|
214
228
|
/** With a history store, the key each subject is paged under; null without one. */
|
|
215
229
|
keys = null;
|
|
216
230
|
/** With a history store, the voices it paged out whose handles are still held; null without one. */
|
|
217
231
|
outs = null;
|
|
218
|
-
constructor(kit, opts
|
|
232
|
+
constructor(kit, opts,
|
|
233
|
+
/** The lanes an entry was built with; null in one that leaves them out. */
|
|
234
|
+
lanesOf) {
|
|
219
235
|
this.kit = kit;
|
|
220
236
|
this.opts = opts;
|
|
237
|
+
this.lanesOf = lanesOf;
|
|
221
238
|
this.names = Object.keys(kit);
|
|
222
239
|
this.channels = this.names.map((k) => kit[k]);
|
|
223
240
|
this.bounded = this.channels.flatMap((c, i) => (c.bounds ? [i] : []));
|
|
224
241
|
this.lerpsInto = this.channels.map((c) => lerpInto(c));
|
|
242
|
+
this.scalars = this.channels.map((c) => scalarOp(c));
|
|
225
243
|
this.names.forEach((k, i) => {
|
|
226
244
|
this.slotOf.set(k, i);
|
|
227
245
|
});
|
|
228
|
-
|
|
246
|
+
if (lanesOf === null && opts.lanes === true)
|
|
247
|
+
throw new Error("blits: '@msb235/blits/lean' has no lanes; take mix from '@msb235/blits'");
|
|
248
|
+
this.lanes =
|
|
249
|
+
lanesOf !== null && (opts.lanes ?? defaults.lanes) ? new lanesOf(laneHost(this)) : null;
|
|
229
250
|
this.name = opts.name;
|
|
230
251
|
const shared = opts.transport;
|
|
231
252
|
if (shared !== undefined) {
|
|
@@ -290,6 +311,9 @@ export class Mixer {
|
|
|
290
311
|
get timestamp() {
|
|
291
312
|
return this.transport.last;
|
|
292
313
|
}
|
|
314
|
+
get seq() {
|
|
315
|
+
return this.transport.seq;
|
|
316
|
+
}
|
|
293
317
|
get last() {
|
|
294
318
|
return this.transport.last;
|
|
295
319
|
}
|
|
@@ -308,16 +332,31 @@ export class Mixer {
|
|
|
308
332
|
}
|
|
309
333
|
cue(spec) {
|
|
310
334
|
this.attached();
|
|
311
|
-
const engine = this.opts.engine
|
|
312
|
-
if (!engine.runs.has(spec.patch.form))
|
|
335
|
+
const engine = this.opts.engine;
|
|
336
|
+
if (engine !== undefined && !engine.runs.has(spec.patch.form))
|
|
313
337
|
throw new Error(`blits: engine ${engine.name} does not run ${spec.patch.form} patches`);
|
|
314
338
|
return cue(this, spec);
|
|
315
339
|
}
|
|
316
|
-
blend(
|
|
340
|
+
blend(members, by, spec = {}) {
|
|
317
341
|
const locus = `blend:${this.nextId}`;
|
|
318
|
-
const of = blendOf(by,
|
|
319
|
-
return
|
|
320
|
-
const
|
|
342
|
+
const of = blendOf(by, members.length - 1);
|
|
343
|
+
return members.map((member, i) => {
|
|
344
|
+
const own = {
|
|
345
|
+
...spec,
|
|
346
|
+
patch: member,
|
|
347
|
+
weight: by,
|
|
348
|
+
locus,
|
|
349
|
+
};
|
|
350
|
+
if ('patch' in member) {
|
|
351
|
+
own.patch = member.patch;
|
|
352
|
+
if (member.name !== undefined)
|
|
353
|
+
own.name = member.name;
|
|
354
|
+
if (member.tags !== undefined)
|
|
355
|
+
own.tags = member.tags;
|
|
356
|
+
if (member.hits !== undefined)
|
|
357
|
+
own.hits = member.hits;
|
|
358
|
+
}
|
|
359
|
+
const handle = this.cue(own);
|
|
321
360
|
const voice = this.cued[this.cued.length - 1];
|
|
322
361
|
voice.blend = { of, i };
|
|
323
362
|
of.members.push(voice);
|
|
@@ -330,6 +369,8 @@ export class Mixer {
|
|
|
330
369
|
return this.cue({ ...spec, patch: ownerPatch });
|
|
331
370
|
}
|
|
332
371
|
span(spec) {
|
|
372
|
+
if ((spec.rate ?? 1) < 0)
|
|
373
|
+
throw new Error('blits: a span lays out its children forward from its start, so it is not cued backward');
|
|
333
374
|
this.fitting = new Fitting(spec);
|
|
334
375
|
try {
|
|
335
376
|
const handle = this.owns(spec);
|
|
@@ -340,6 +381,9 @@ export class Mixer {
|
|
|
340
381
|
this.fitting = null;
|
|
341
382
|
}
|
|
342
383
|
}
|
|
384
|
+
arc(spec) {
|
|
385
|
+
return cueArc(this, spec);
|
|
386
|
+
}
|
|
343
387
|
sync(timestamp) {
|
|
344
388
|
this.own('sync').sync(timestamp);
|
|
345
389
|
}
|
|
@@ -359,16 +403,16 @@ export class Mixer {
|
|
|
359
403
|
this.looked = true;
|
|
360
404
|
pull(this, subjects, into);
|
|
361
405
|
}
|
|
362
|
-
seek(
|
|
406
|
+
seek(to) {
|
|
363
407
|
if (this.projecting)
|
|
364
408
|
throw new Error('blits: a projection does not seek');
|
|
365
|
-
seek(this.own('seek'),
|
|
409
|
+
seek(this.own('seek'), to);
|
|
366
410
|
}
|
|
367
411
|
prepare(time) {
|
|
368
412
|
return this.own('prepare').prepare(time);
|
|
369
413
|
}
|
|
370
|
-
project(
|
|
371
|
-
return projectAll(this.transport,
|
|
414
|
+
project(at) {
|
|
415
|
+
return projectAll(this.transport, at).of(this);
|
|
372
416
|
}
|
|
373
417
|
announce(name, opts = {}) {
|
|
374
418
|
this.attached();
|
|
@@ -469,18 +513,21 @@ export class Mixer {
|
|
|
469
513
|
voice.written = true;
|
|
470
514
|
beginFade(this, voice, { over: opts?.over });
|
|
471
515
|
}
|
|
472
|
-
record(this, 'mute', () => this
|
|
516
|
+
record(this, 'mute', (stage) => stage.mix(this)?.mute(opts));
|
|
473
517
|
}
|
|
474
518
|
drop(subject) {
|
|
475
519
|
this.frame = nextFrame();
|
|
476
|
-
const head = this
|
|
520
|
+
const head = headOf(this, subject);
|
|
477
521
|
// Nothing that kept this head, such as `pull`'s remembered list, may take it as current again.
|
|
478
|
-
if (head !== undefined)
|
|
479
|
-
head
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
if (
|
|
483
|
-
this.lanes.release(
|
|
522
|
+
if (head !== undefined)
|
|
523
|
+
unlink(this, head);
|
|
524
|
+
// A number a seek carried over is released even where its chain is not linked since.
|
|
525
|
+
const slot = this.chains.get(subject)?.slot ?? -1;
|
|
526
|
+
if (slot >= 0 && this.lanes !== null)
|
|
527
|
+
this.lanes.release(slot);
|
|
528
|
+
// A read ahead's copy reads the live mix's poses, and so hides one where the mix deletes it.
|
|
529
|
+
if (this.projecting && !(this.pose instanceof Masked))
|
|
530
|
+
this.pose = new Masked(this.pose);
|
|
484
531
|
this.pose.delete(subject);
|
|
485
532
|
this.restStamps?.delete(subject);
|
|
486
533
|
this.chains.delete(subject);
|
|
@@ -488,32 +535,24 @@ export class Mixer {
|
|
|
488
535
|
// A record of it is only in a voice over every subject, one naming it, or one gone; a ramp out
|
|
489
536
|
// of a voice that does not name it is in `parters`, and a patch's own state in `motions`.
|
|
490
537
|
const kept = releasing(this);
|
|
491
|
-
const forget = (voice) => {
|
|
492
|
-
leave(this, voice, subject, leftWith(this, voice, subject));
|
|
493
|
-
voice.subjects.delete(subject);
|
|
494
|
-
voice.motion?.release(subject, ...kept);
|
|
495
|
-
voice.parts?.delete(subject);
|
|
496
|
-
voice.parted?.delete(subject);
|
|
497
|
-
voice.blend?.of.reads.delete(subject);
|
|
498
|
-
};
|
|
499
538
|
for (const voice of this.general)
|
|
500
|
-
|
|
539
|
+
this.forgetIn(voice, subject, kept);
|
|
501
540
|
const named = this.named.get(subject);
|
|
502
541
|
if (named !== undefined)
|
|
503
542
|
for (const voice of [...named])
|
|
504
|
-
|
|
543
|
+
this.forgetIn(voice, subject, kept);
|
|
505
544
|
const gone = goneIndex(this);
|
|
506
545
|
for (const voice of gone.general)
|
|
507
|
-
|
|
546
|
+
this.forgetIn(voice, subject, kept);
|
|
508
547
|
const goneNaming = gone.named.get(subject);
|
|
509
548
|
if (goneNaming !== undefined)
|
|
510
549
|
for (const voice of goneNaming)
|
|
511
|
-
|
|
550
|
+
this.forgetIn(voice, subject, kept);
|
|
512
551
|
if (this.owners !== null)
|
|
513
552
|
for (const voice of this.owners)
|
|
514
|
-
|
|
553
|
+
this.forgetIn(voice, subject, kept);
|
|
515
554
|
for (const voice of this.parters) {
|
|
516
|
-
|
|
555
|
+
this.forgetIn(voice, subject, kept);
|
|
517
556
|
if (voice.parts?.size === 0)
|
|
518
557
|
voice.parts = null;
|
|
519
558
|
if (voice.parted?.size === 0)
|
|
@@ -524,11 +563,31 @@ export class Mixer {
|
|
|
524
563
|
const strays = this.strays.get(subject);
|
|
525
564
|
if (strays !== undefined)
|
|
526
565
|
for (const motion of strays)
|
|
527
|
-
|
|
566
|
+
this.releaseIn(motion, subject, kept);
|
|
528
567
|
this.strays.delete(subject);
|
|
529
568
|
for (const motion of this.strayAll)
|
|
530
|
-
|
|
531
|
-
|
|
569
|
+
this.releaseIn(motion, subject, kept);
|
|
570
|
+
this.recordDrop(subject);
|
|
571
|
+
}
|
|
572
|
+
/** Lets `voice` go of a dropped subject. Methods rather than closures, so a drop makes neither. */
|
|
573
|
+
forgetIn(voice, subject, kept) {
|
|
574
|
+
leaveVoice(this, voice, subject);
|
|
575
|
+
voice.subjects.delete(subject);
|
|
576
|
+
if (voice.motion !== undefined)
|
|
577
|
+
this.releaseIn(voice.motion, subject, kept);
|
|
578
|
+
voice.parts?.delete(subject);
|
|
579
|
+
voice.parted?.delete(subject);
|
|
580
|
+
voice.blend?.of.reads.delete(subject);
|
|
581
|
+
}
|
|
582
|
+
// A motion patch's state is the live mix's alone: a read ahead cannot start a subject afresh in it.
|
|
583
|
+
releaseIn(motion, subject, kept) {
|
|
584
|
+
if (!this.projecting)
|
|
585
|
+
motion.release(subject, kept[0], kept[1], kept[2]);
|
|
586
|
+
else if (motion.holds(subject))
|
|
587
|
+
unplayable('a drop of a subject a motion patch holds');
|
|
588
|
+
}
|
|
589
|
+
recordDrop(subject) {
|
|
590
|
+
record(this, 'drop', (stage) => stage.mix(this)?.drop(subject));
|
|
532
591
|
}
|
|
533
592
|
touch(subject) {
|
|
534
593
|
this.attached();
|
|
@@ -639,6 +698,15 @@ function named(transport, name) {
|
|
|
639
698
|
if (taken.some((m) => m.name === name))
|
|
640
699
|
throw new Error(`blits: a mix named ${name} is already on this transport, or a seek could bring it back`);
|
|
641
700
|
}
|
|
701
|
+
/** Makes a mix the host asked for, with the registry's history where it names none, and lists it there. */
|
|
702
|
+
export function made(kit, opts, lanes) {
|
|
703
|
+
const { history, added } = historyFor(opts);
|
|
704
|
+
const m = new Mixer(kit, added ? { ...opts, history } : opts, lanes);
|
|
705
|
+
if (added)
|
|
706
|
+
gave(m.transport);
|
|
707
|
+
register(m);
|
|
708
|
+
return m;
|
|
709
|
+
}
|
|
642
710
|
/**
|
|
643
711
|
* The one that ships: every form on the CPU, probed per subject on demand.
|
|
644
712
|
*
|
|
@@ -648,7 +716,7 @@ export const mixer = {
|
|
|
648
716
|
name: 'mixer',
|
|
649
717
|
runs: new Set(['fn', 'keys', 'motion']),
|
|
650
718
|
create(kit, opts) {
|
|
651
|
-
return
|
|
719
|
+
return made(kit, opts, Lanes);
|
|
652
720
|
},
|
|
653
721
|
};
|
|
654
722
|
/**
|
package/dist/motion.d.ts
CHANGED
|
@@ -45,7 +45,10 @@ export type Moving<I, O, V extends Value> = Patch<I, O, void> & {
|
|
|
45
45
|
*/
|
|
46
46
|
push(subject: I, velocity: V, at?: number): void;
|
|
47
47
|
};
|
|
48
|
-
/**
|
|
48
|
+
/**
|
|
49
|
+
* The state behind a `'motion'` patch, for an engine that reads it directly; a patch another copy
|
|
50
|
+
* of the same release made counts.
|
|
51
|
+
*/
|
|
49
52
|
export declare function motionOf<I>(p: object): Motions<I> | undefined;
|
|
50
53
|
interface Common<I, V extends Value> {
|
|
51
54
|
/** Units per second each subject starts moving at. Default 0. */
|
package/dist/motion.js
CHANGED
|
@@ -1,11 +1,17 @@
|
|
|
1
1
|
import { COAST, CRITICAL, EASED, OVER, UNDER } from './closed.js';
|
|
2
2
|
import { curve } from './easing.js';
|
|
3
3
|
import { Motions } from './motions.js';
|
|
4
|
+
import { release } from './shared.js';
|
|
4
5
|
const axes = (v) => (typeof v === 'number' ? [v] : [...v]);
|
|
5
6
|
/** Zero on every axis `x` has: a motion's velocity where its options give none. */
|
|
6
7
|
const still = (x) => new Array(x.length).fill(0);
|
|
7
8
|
const per = (p, subject) => typeof p === 'function' ? p(subject) : p;
|
|
8
9
|
const scalarOf = (p) => typeof p === 'function' ? undefined : typeof p === 'number';
|
|
10
|
+
/**
|
|
11
|
+
* What marks a motion patch to every copy of blits of this release loaded in one realm, so a
|
|
12
|
+
* spring another copy made runs on this copy's lanes; it gives the patch's state.
|
|
13
|
+
*/
|
|
14
|
+
const MOTIONS = Symbol.for(`@msb235/blits/motions@${release}`);
|
|
9
15
|
/** One `writes` array per channel, shared by every motion patch that writes it. */
|
|
10
16
|
const writesOf = new Map();
|
|
11
17
|
/**
|
|
@@ -29,6 +35,9 @@ class MotionPatch {
|
|
|
29
35
|
get motion() {
|
|
30
36
|
return this.motions.spec();
|
|
31
37
|
}
|
|
38
|
+
get [MOTIONS]() {
|
|
39
|
+
return this.motions;
|
|
40
|
+
}
|
|
32
41
|
at(_phase, subject, setting) {
|
|
33
42
|
const state = this.motions;
|
|
34
43
|
const s = state.slot(subject);
|
|
@@ -58,9 +67,14 @@ class GlidePatch extends MotionPatch {
|
|
|
58
67
|
this.motions.change(subject, { at, v: axes(velocity) });
|
|
59
68
|
}
|
|
60
69
|
}
|
|
61
|
-
/**
|
|
70
|
+
/**
|
|
71
|
+
* The state behind a `'motion'` patch, for an engine that reads it directly; a patch another copy
|
|
72
|
+
* of the same release made counts.
|
|
73
|
+
*/
|
|
62
74
|
export function motionOf(p) {
|
|
63
|
-
return p instanceof MotionPatch
|
|
75
|
+
return p instanceof MotionPatch
|
|
76
|
+
? p.motions
|
|
77
|
+
: p[MOTIONS];
|
|
64
78
|
}
|
|
65
79
|
/**
|
|
66
80
|
* The shapes of the stock motions, each one object over the options it reads, copied out of them so
|
package/dist/motions.d.ts
CHANGED
|
@@ -167,6 +167,8 @@ export declare class Motions<I> {
|
|
|
167
167
|
private unrelease;
|
|
168
168
|
/** Subject `s`'s run as it stands. */
|
|
169
169
|
private runAt;
|
|
170
|
+
/** Whether the patch holds a run for `subject`. */
|
|
171
|
+
holds(subject: I): boolean;
|
|
170
172
|
/** A subject's run as plain data, for a voice leaving memory; undefined where it has none. */
|
|
171
173
|
pack(subject: I): Run | undefined;
|
|
172
174
|
/** Puts a subject's run back as `pack` or a release left it, replacing any it has now. */
|
|
@@ -244,6 +246,8 @@ export declare class Motions<I> {
|
|
|
244
246
|
private queue;
|
|
245
247
|
/** Every change due by `t` applied to a copy of the stretch playing then, committing nothing. */
|
|
246
248
|
private peek;
|
|
249
|
+
/** Subject `s`'s changes waiting, with those a read ahead's tape made filed among them by time. */
|
|
250
|
+
private dueTo;
|
|
247
251
|
private check;
|
|
248
252
|
private flag;
|
|
249
253
|
forget(s: number): void;
|
package/dist/motions.js
CHANGED
|
@@ -28,6 +28,17 @@ function ahead(segs, list) {
|
|
|
28
28
|
let shared = { xs: new Float64Array(4), vs: new Float64Array(4) };
|
|
29
29
|
/** What a patch's `xs` and `vs` are before its first subject sets its axes. */
|
|
30
30
|
const unsized = new Float64Array(0);
|
|
31
|
+
/** Where `applied` reads a stretch at the time a change lands; copied out before it returns. */
|
|
32
|
+
let landing = { x: new Float64Array(4), v: new Float64Array(4) };
|
|
33
|
+
/** Where `numbers` gathers what it copies, kept an array of doubles so every copy is one too. */
|
|
34
|
+
const gathered = [0.5];
|
|
35
|
+
/** `n` numbers of `from` from `at`, as an array of their own. */
|
|
36
|
+
function numbers(from, at, n) {
|
|
37
|
+
// `Array.from` walks a typed array by its iterator: about 500 B a call for three axes.
|
|
38
|
+
for (let i = 0; i < n; i++)
|
|
39
|
+
gathered[i] = from[at + i];
|
|
40
|
+
return gathered.slice(0, n);
|
|
41
|
+
}
|
|
31
42
|
/**
|
|
32
43
|
* A subject's flags: its value is a number; it has changes pending; it has older stretches; the
|
|
33
44
|
* stretch it is playing was found landed, which holds until another replaces it.
|
|
@@ -211,6 +222,10 @@ export class Motions {
|
|
|
211
222
|
pending: pending === undefined ? undefined : [...pending],
|
|
212
223
|
};
|
|
213
224
|
}
|
|
225
|
+
/** Whether the patch holds a run for `subject`. */
|
|
226
|
+
holds(subject) {
|
|
227
|
+
return this.known(subject) !== undefined;
|
|
228
|
+
}
|
|
214
229
|
/** A subject's run as plain data, for a voice leaving memory; undefined where it has none. */
|
|
215
230
|
pack(subject) {
|
|
216
231
|
const s = this.known(subject);
|
|
@@ -496,7 +511,7 @@ export class Motions {
|
|
|
496
511
|
/** Every change due by `t` applied to a copy of the stretch playing then, committing nothing. */
|
|
497
512
|
peek(s, t, xo, vo) {
|
|
498
513
|
let seg = this.playing(s, t);
|
|
499
|
-
const list = this.
|
|
514
|
+
const list = this.dueTo(s);
|
|
500
515
|
if (list !== undefined)
|
|
501
516
|
for (const change of list) {
|
|
502
517
|
const a = change.at;
|
|
@@ -507,6 +522,21 @@ export class Motions {
|
|
|
507
522
|
}
|
|
508
523
|
this.evaluateSegment(seg, t, xo, vo);
|
|
509
524
|
}
|
|
525
|
+
/** Subject `s`'s changes waiting, with those a read ahead's tape made filed among them by time. */
|
|
526
|
+
dueTo(s) {
|
|
527
|
+
const own = this.pending?.[s];
|
|
528
|
+
const ahead = reading.ahead?.get(this);
|
|
529
|
+
if (ahead === undefined)
|
|
530
|
+
return own;
|
|
531
|
+
const subject = this.numbers.subject(s);
|
|
532
|
+
const made = subject === absent ? undefined : ahead.get(subject);
|
|
533
|
+
if (made === undefined)
|
|
534
|
+
return own;
|
|
535
|
+
if (own === undefined)
|
|
536
|
+
return made;
|
|
537
|
+
// The patch's own were made first, and a stable sort keeps them first at one time.
|
|
538
|
+
return [...own.filter((c) => c.at !== undefined), ...made].sort((a, b) => a.at - b.at);
|
|
539
|
+
}
|
|
510
540
|
check(a, n) {
|
|
511
541
|
if (a !== undefined && a.length !== n)
|
|
512
542
|
throw new Error('blits: a motion patch moves every subject on the same number of axes');
|
|
@@ -611,9 +641,9 @@ export class Motions {
|
|
|
611
641
|
return {
|
|
612
642
|
at: this.runs[this.base(s)],
|
|
613
643
|
ms: this.runs[this.base(s) + 2],
|
|
614
|
-
x0:
|
|
615
|
-
v0:
|
|
616
|
-
to:
|
|
644
|
+
x0: numbers(this.runs, x, n),
|
|
645
|
+
v0: numbers(this.runs, x + n, n),
|
|
646
|
+
to: numbers(this.runs, x + 2 * n, n),
|
|
617
647
|
change: this.causes?.[s],
|
|
618
648
|
};
|
|
619
649
|
}
|
|
@@ -636,11 +666,13 @@ export class Motions {
|
|
|
636
666
|
return list[lo];
|
|
637
667
|
}
|
|
638
668
|
applied(s, seg, change, at) {
|
|
639
|
-
const
|
|
640
|
-
|
|
669
|
+
const n = this.n;
|
|
670
|
+
if (landing.x.length < n)
|
|
671
|
+
landing = { x: new Float64Array(n), v: new Float64Array(n) };
|
|
672
|
+
const { x, v } = landing;
|
|
641
673
|
this.evaluateSegment(seg, at, x, v);
|
|
642
|
-
const xs =
|
|
643
|
-
const vs = change.v ??
|
|
674
|
+
const xs = numbers(x, 0, n);
|
|
675
|
+
const vs = change.v ?? numbers(v, 0, n);
|
|
644
676
|
const subject = this.numbers.subject(s);
|
|
645
677
|
const known = subject === absent ? undefined : subject;
|
|
646
678
|
const to = this.shape.aim(xs, vs, change.to ?? seg.to, known);
|
|
@@ -695,8 +727,7 @@ export class Motions {
|
|
|
695
727
|
* The mix time and frame a stretch was in force by, which a store keys it under: when the change
|
|
696
728
|
* that ended it was made, which falls in the same life of the subject.
|
|
697
729
|
*/
|
|
698
|
-
made(
|
|
699
|
-
const c = next.change;
|
|
730
|
+
made(c) {
|
|
700
731
|
const made = c?.made;
|
|
701
732
|
if (made === undefined || Number.isNaN(made))
|
|
702
733
|
return { at: this.owner?.now() ?? Number.NaN, seq: this.owner?.seq() ?? Number.NaN };
|
|
@@ -717,7 +748,10 @@ export class Motions {
|
|
|
717
748
|
const out = list.splice(0, n);
|
|
718
749
|
const subject = this.numbers.subject(s);
|
|
719
750
|
if (this.owner !== null && subject !== absent)
|
|
720
|
-
this.owner.page(this.ownerId, 'stretch', subject, out.map((seg, i) =>
|
|
751
|
+
this.owner.page(this.ownerId, 'stretch', subject, out.map((seg, i) => {
|
|
752
|
+
const next = out[i + 1] ?? list[0];
|
|
753
|
+
return { ...this.made(next === undefined ? this.causes?.[s] : next.change), data: seg };
|
|
754
|
+
}));
|
|
721
755
|
if (list.length === 0) {
|
|
722
756
|
this.older[s] = undefined;
|
|
723
757
|
this.flag(s, OLDER, false);
|
package/dist/move.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
+
import { settleArcs } from './arcs.js';
|
|
1
2
|
import { changed } from './chain.js';
|
|
2
3
|
import { elapsedWith } from './clock.js';
|
|
3
|
-
import { popDue, schedule } from './due.js';
|
|
4
|
-
import { detour } from './everyone.js';
|
|
4
|
+
import { byId, popDue, schedule } from './due.js';
|
|
5
|
+
import { detour, outgrow } from './everyone.js';
|
|
5
6
|
import { beginFade, part, retire } from './fade.js';
|
|
6
7
|
import { expireGone, keepGone } from './gone.js';
|
|
7
8
|
import { begin } from './held.js';
|
|
@@ -66,7 +67,13 @@ function moveTo(mix, now) {
|
|
|
66
67
|
(voice.owner === null ? now : ownerReading(voice.owner, now)) >= voice.start)
|
|
67
68
|
begin(mix, voice, now);
|
|
68
69
|
// After it starts, so a fade due by now on a voice that started since the last frame begins.
|
|
69
|
-
|
|
70
|
+
// Not one set for after its passes run out, where a move across both would begin it first.
|
|
71
|
+
if ((voice.state === 'live' || voice.state === 'frozen') &&
|
|
72
|
+
voice.outAt <= now &&
|
|
73
|
+
!(voice.state === 'live' &&
|
|
74
|
+
!voice.freezesAfter &&
|
|
75
|
+
!voice.freezesBefore &&
|
|
76
|
+
voice.endsAt() < voice.outAt))
|
|
70
77
|
beginFade(mix, voice, { over: Number.isNaN(voice.outOver) ? undefined : voice.outOver }, Math.max(startOf(voice), voice.outAt));
|
|
71
78
|
if (voice.state !== 'pending') {
|
|
72
79
|
if (voice.turns)
|
|
@@ -123,8 +130,12 @@ function moveTo(mix, now) {
|
|
|
123
130
|
if (!mix.projecting)
|
|
124
131
|
schedule(mix, voice);
|
|
125
132
|
}
|
|
126
|
-
if (mix.owners !== null)
|
|
133
|
+
if (mix.owners !== null) {
|
|
127
134
|
lapse(mix, now);
|
|
135
|
+
settleArcs(mix, now);
|
|
136
|
+
}
|
|
137
|
+
if (mix.sharers.length > 0 && !mix.projecting)
|
|
138
|
+
outgrow(mix);
|
|
128
139
|
mix.stirred = false;
|
|
129
140
|
if (mix.retired.length > 0)
|
|
130
141
|
prune(mix);
|
|
@@ -155,11 +166,16 @@ function turn(mix, voice, now) {
|
|
|
155
166
|
voice.anchorNow = at;
|
|
156
167
|
noted(mix, voice);
|
|
157
168
|
}
|
|
158
|
-
/**
|
|
169
|
+
/**
|
|
170
|
+
* Past this many voices retiring in one frame, and more than one in `SPARSE` of the list, one pass
|
|
171
|
+
* over the list beats a search for each.
|
|
172
|
+
*/
|
|
159
173
|
const SEARCHED = 8;
|
|
174
|
+
const SPARSE = 32;
|
|
160
175
|
/**
|
|
161
|
-
* Takes the voices retired since the last prune out of the list, in list order. A few
|
|
162
|
-
* their id, which the list is in order of, so the voices still
|
|
176
|
+
* Takes the voices retired since the last prune out of the list, in list order. A few, or a small
|
|
177
|
+
* share of a long list, are found by their id, which the list is in order of, so the voices still
|
|
178
|
+
* in it are not read.
|
|
163
179
|
*/
|
|
164
180
|
function prune(mix) {
|
|
165
181
|
const retired = mix.retired;
|
|
@@ -167,9 +183,8 @@ function prune(mix) {
|
|
|
167
183
|
const voices = mix.cued;
|
|
168
184
|
let at = null;
|
|
169
185
|
// A projection's list holds copies of voices gone before it, which only a pass takes out.
|
|
170
|
-
if (retired.length <= SEARCHED && !mix.projecting) {
|
|
171
|
-
|
|
172
|
-
retired.sort((a, b) => a.id - b.id);
|
|
186
|
+
if ((retired.length <= SEARCHED || retired.length * SPARSE <= voices.length) && !mix.projecting) {
|
|
187
|
+
byId(retired);
|
|
173
188
|
at = [];
|
|
174
189
|
for (let k = 0; k < retired.length; k++) {
|
|
175
190
|
const voice = retired[k];
|
|
@@ -185,8 +200,16 @@ function prune(mix) {
|
|
|
185
200
|
if (at !== null) {
|
|
186
201
|
for (const voice of retired)
|
|
187
202
|
pruned = leave(mix, voice) || pruned;
|
|
188
|
-
|
|
189
|
-
|
|
203
|
+
// Closed up in one pass from the first to go: a splice for each moves the tail once a voice.
|
|
204
|
+
let kept = at[0];
|
|
205
|
+
let k = 0;
|
|
206
|
+
for (let i = kept; i < voices.length; i++) {
|
|
207
|
+
if (at[k] === i)
|
|
208
|
+
k++;
|
|
209
|
+
else
|
|
210
|
+
voices[kept++] = voices[i];
|
|
211
|
+
}
|
|
212
|
+
voices.length = kept;
|
|
190
213
|
}
|
|
191
214
|
else {
|
|
192
215
|
let kept = 0;
|
package/dist/numbers.d.ts
CHANGED
|
@@ -6,7 +6,7 @@ export declare const absent: unique symbol;
|
|
|
6
6
|
* them, so numbering one never keeps it alive.
|
|
7
7
|
*/
|
|
8
8
|
export declare class Numbers<I> {
|
|
9
|
-
private
|
|
9
|
+
private owner;
|
|
10
10
|
private next;
|
|
11
11
|
/** Numbers released for reuse; null until one is, as most patches never release one. */
|
|
12
12
|
private free;
|
|
@@ -26,6 +26,10 @@ export declare class Numbers<I> {
|
|
|
26
26
|
constructor(owner: {
|
|
27
27
|
forget(slot: number): void;
|
|
28
28
|
});
|
|
29
|
+
/** Hands the numbers to a new owner, which hears of every release from now on. */
|
|
30
|
+
adopt(owner: {
|
|
31
|
+
forget(slot: number): void;
|
|
32
|
+
}): void;
|
|
29
33
|
/** One past the highest number handed out, so a loop over 0..size meets every live one. */
|
|
30
34
|
get size(): number;
|
|
31
35
|
take(subject: I): number;
|
package/dist/numbers.js
CHANGED
|
@@ -29,6 +29,10 @@ export class Numbers {
|
|
|
29
29
|
constructor(owner) {
|
|
30
30
|
this.owner = owner;
|
|
31
31
|
}
|
|
32
|
+
/** Hands the numbers to a new owner, which hears of every release from now on. */
|
|
33
|
+
adopt(owner) {
|
|
34
|
+
this.owner = owner;
|
|
35
|
+
}
|
|
32
36
|
/** One past the highest number handed out, so a loop over 0..size meets every live one. */
|
|
33
37
|
get size() {
|
|
34
38
|
return this.next;
|
package/dist/owner.d.ts
CHANGED
|
@@ -9,6 +9,19 @@ export declare class Holding<V> {
|
|
|
9
9
|
/** How many it has held, and how many of those finished their passes. */
|
|
10
10
|
kids: number;
|
|
11
11
|
played: number;
|
|
12
|
+
/**
|
|
13
|
+
* When the children that left coast, as pairs of the frame one left in and the latest coast so
|
|
14
|
+
* far, mix time: a pair only where the latest grew, so a seek back cuts it to a prefix.
|
|
15
|
+
*/
|
|
16
|
+
coasts: number[];
|
|
17
|
+
/** The frame the first child to leave before its passes ended left in; Infinity for none. */
|
|
18
|
+
cutSeq: number;
|
|
19
|
+
/** The latest coast of a child that left; -Infinity for none. */
|
|
20
|
+
get coast(): number;
|
|
21
|
+
/** A child left in frame `seq`, coasting at `coast`, or before its passes ended for undefined. */
|
|
22
|
+
left(coast: number | undefined, seq: number): void;
|
|
23
|
+
/** Forgets the children that left after frame `seq`, for a seek or read back to it. */
|
|
24
|
+
back(seq: number): void;
|
|
12
25
|
}
|
|
13
26
|
/**
|
|
14
27
|
* What an owner's clock reads at mix time `now`, through every owner above it: -Infinity before it
|
|
@@ -41,5 +54,8 @@ export declare function heldByInput<I, O>(voice: Voice<I, O>): boolean;
|
|
|
41
54
|
export declare function ownWeight<I, O>(owner: Voice<I, O>, now: number, reduced: boolean, base: (owner: Voice<I, O>) => number): number;
|
|
42
55
|
/** What every owner above a voice multiplies into its weight. */
|
|
43
56
|
export declare function ownersWeight<I, O>(voice: Voice<I, O>, now: number, reduced: boolean, base: (owner: Voice<I, O>) => number): number;
|
|
44
|
-
/**
|
|
45
|
-
|
|
57
|
+
/**
|
|
58
|
+
* Points a projection's copies at each other's owners, each owner holding only copies; for a read
|
|
59
|
+
* back to frame `seq`, as its children that had left by then left it.
|
|
60
|
+
*/
|
|
61
|
+
export declare function relink<I, O>(copies: readonly Voice<I, O>[], seq?: number): void;
|