@msb235/blits 0.12.1 → 0.14.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 +55 -0
- package/README.md +17 -0
- package/dist/channels.d.ts +5 -0
- package/dist/channels.js +2 -0
- package/dist/closed.d.ts +1 -0
- package/dist/closed.js +45 -1
- package/dist/crowd.d.ts +2 -0
- package/dist/crowd.js +9 -1
- package/dist/cue.d.ts +1 -1
- package/dist/cue.js +26 -2
- package/dist/fling.d.ts +66 -0
- package/dist/fling.js +162 -0
- package/dist/fold.js +14 -0
- package/dist/handle.d.ts +1 -0
- package/dist/handle.js +6 -1
- package/dist/held.d.ts +2 -0
- package/dist/held.js +21 -13
- package/dist/history.js +2 -0
- package/dist/mixer.d.ts +8 -0
- package/dist/mixer.js +15 -2
- package/dist/motion.d.ts +18 -1
- package/dist/motion.js +44 -5
- package/dist/motions.d.ts +8 -3
- package/dist/motions.js +7 -5
- package/dist/move.js +40 -10
- package/dist/origin.d.ts +3 -1
- package/dist/origin.js +16 -3
- package/dist/pack.d.ts +1 -0
- package/dist/pack.js +2 -0
- package/dist/project.js +1 -0
- package/dist/record.js +1 -0
- package/dist/rows.js +3 -1
- package/dist/seek.js +19 -3
- package/dist/shared.d.ts +1 -1
- package/dist/shared.js +1 -1
- package/dist/stack.d.ts +2 -2
- package/dist/stack.js +4 -5
- package/dist/strays.js +1 -1
- package/dist/transport.d.ts +5 -0
- package/dist/transport.js +2 -0
- package/dist/types/mix.d.ts +11 -4
- package/dist/types/patch.d.ts +2 -0
- package/dist/types/voice.d.ts +12 -4
- package/dist/voice.d.ts +4 -0
- package/dist/voice.js +2 -0
- package/docs/schema.html +15 -8
- package/package.json +5 -1
package/dist/mixer.d.ts
CHANGED
|
@@ -114,6 +114,13 @@ export declare class Mixer<I, O> implements Mix<I, O> {
|
|
|
114
114
|
reducedNow: boolean;
|
|
115
115
|
/** Something was changed since the last sync, so the next frame may differ from this one. */
|
|
116
116
|
stirred: boolean;
|
|
117
|
+
/**
|
|
118
|
+
* The host read `inert` as true since the last sync, so its frame loop may be asleep until a
|
|
119
|
+
* change wakes it; a host that never asks keeps no loop asleep on it.
|
|
120
|
+
*/
|
|
121
|
+
asleep: boolean;
|
|
122
|
+
/** Some voice cued while the mix slept waits for the next sync to set its start. */
|
|
123
|
+
waking: boolean;
|
|
117
124
|
/** Counts the changes `stir` records, which a frame's record of a probe is current only before. */
|
|
118
125
|
stirs: number;
|
|
119
126
|
syncing: boolean;
|
|
@@ -259,6 +266,7 @@ export declare class Mixer<I, O> implements Mix<I, O> {
|
|
|
259
266
|
voices(tag?: string): Handle<I>[];
|
|
260
267
|
get live(): boolean;
|
|
261
268
|
get inert(): boolean;
|
|
269
|
+
private restful;
|
|
262
270
|
get rate(): number;
|
|
263
271
|
set rate(r: number);
|
|
264
272
|
ramp(rate: number, over: number): void;
|
package/dist/mixer.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { cueArc } from './arcs.js';
|
|
2
2
|
import { blended, blendOf, laneRead, shareKept } from './blend.js';
|
|
3
3
|
import { aims, chain, headOf, linkable, relink, unlink } from './chain.js';
|
|
4
|
-
import { lerpInto, scalarOp } from './channels.js';
|
|
4
|
+
import { lerpInto, STOCK_BAND, scalarOp } from './channels.js';
|
|
5
5
|
import { cue } from './cue.js';
|
|
6
6
|
import { DepartedIndex } from './departed.js';
|
|
7
7
|
import { beginFade, leaveVoice, parting } from './fade.js';
|
|
@@ -105,6 +105,13 @@ export class Mixer {
|
|
|
105
105
|
reducedNow = false;
|
|
106
106
|
/** Something was changed since the last sync, so the next frame may differ from this one. */
|
|
107
107
|
stirred = false;
|
|
108
|
+
/**
|
|
109
|
+
* The host read `inert` as true since the last sync, so its frame loop may be asleep until a
|
|
110
|
+
* change wakes it; a host that never asks keeps no loop asleep on it.
|
|
111
|
+
*/
|
|
112
|
+
asleep = false;
|
|
113
|
+
/** Some voice cued while the mix slept waits for the next sync to set its start. */
|
|
114
|
+
waking = false;
|
|
108
115
|
/** Counts the changes `stir` records, which a frame's record of a probe is current only before. */
|
|
109
116
|
stirs = 0;
|
|
110
117
|
syncing = false;
|
|
@@ -328,7 +335,7 @@ export class Mixer {
|
|
|
328
335
|
return typeof r === 'function' ? r() : r === true;
|
|
329
336
|
}
|
|
330
337
|
get band() {
|
|
331
|
-
return this.opts.band ??
|
|
338
|
+
return this.opts.band ?? STOCK_BAND;
|
|
332
339
|
}
|
|
333
340
|
cue(spec) {
|
|
334
341
|
this.attached();
|
|
@@ -474,6 +481,12 @@ export class Mixer {
|
|
|
474
481
|
get inert() {
|
|
475
482
|
if (this.stirred)
|
|
476
483
|
return false;
|
|
484
|
+
const inert = this.restful();
|
|
485
|
+
if (inert && !Number.isNaN(this.now))
|
|
486
|
+
this.asleep = true;
|
|
487
|
+
return inert;
|
|
488
|
+
}
|
|
489
|
+
restful() {
|
|
477
490
|
// Standing still, a voice changes no pose but by its weight signal, or by an anchor or start
|
|
478
491
|
// that host time may yet reach.
|
|
479
492
|
if (this.pace?.stopped(this.u))
|
package/dist/motion.d.ts
CHANGED
|
@@ -44,6 +44,11 @@ export type Moving<I, O, V extends Value> = Patch<I, O, void> & {
|
|
|
44
44
|
* back at the latest frame too, however long it is before it is next read.
|
|
45
45
|
*/
|
|
46
46
|
push(subject: I, velocity: V, at?: number): void;
|
|
47
|
+
/**
|
|
48
|
+
* Puts `subject` at `value` from voice time `at`, keeping its velocity, with `at` defaulting as
|
|
49
|
+
* `push`'s does: a spring or tween heads for its target from there, a glide coasts on from there.
|
|
50
|
+
*/
|
|
51
|
+
set(subject: I, value: V, at?: number): void;
|
|
47
52
|
};
|
|
48
53
|
/**
|
|
49
54
|
* The state behind a `'motion'` patch, for an engine that reads it directly; a patch another copy
|
|
@@ -90,6 +95,11 @@ export declare function spring<I, O, V extends Value = number>(writes: keyof O,
|
|
|
90
95
|
* `ms`, and comes to rest `velocity · ms / 1000` past where it was released. `push` sets it moving
|
|
91
96
|
* again from wherever it is. Closed form, so frame rate does not change where it stops.
|
|
92
97
|
*
|
|
98
|
+
* With `bounds`, a coast that would stop past an edge springs back to it from where it crosses, at
|
|
99
|
+
* the speed it crosses at, on a critically damped spring of `stiffness`; one let go past an edge
|
|
100
|
+
* springs back from there, unless it coasts back inside. A `set` past an edge leaves the subject
|
|
101
|
+
* there, coasting on, until the next `push`, so a drag can hold it out.
|
|
102
|
+
*
|
|
93
103
|
* @category patch
|
|
94
104
|
*/
|
|
95
105
|
export declare function glide<I, O, V extends Value = number>(writes: keyof O, opts: Common<I, V> & {
|
|
@@ -97,7 +107,14 @@ export declare function glide<I, O, V extends Value = number>(writes: keyof O, o
|
|
|
97
107
|
from: PerSubject<I, V>;
|
|
98
108
|
/** The friction's time constant, ms. Default 325, a touch scroll's. */
|
|
99
109
|
ms?: number;
|
|
100
|
-
|
|
110
|
+
/** The least and greatest value a coast comes to rest at, per axis. Default none. */
|
|
111
|
+
bounds?: PerSubject<I, readonly [min: V, max: V]>;
|
|
112
|
+
/** The spring back's, per second squared, with `bounds`. Default 170, as a spring's. */
|
|
113
|
+
stiffness?: number;
|
|
114
|
+
}): Moving<I, O, V> & {
|
|
115
|
+
/** `subject`'s bounds, undefined for a glide without them. */
|
|
116
|
+
bounds(subject: I): readonly [min: V, max: V] | undefined;
|
|
117
|
+
};
|
|
101
118
|
/**
|
|
102
119
|
* From one value to another over `ms` along an easing, solved in closed form, so frame rate does not
|
|
103
120
|
* change where a subject is. `to(subject, target)` retargets one subject mid-flight: the new stretch
|
package/dist/motion.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { COAST, CRITICAL, EASED, OVER, UNDER } from './closed.js';
|
|
1
|
+
import { BOUND, COAST, CRITICAL, EASED, OVER, UNDER } from './closed.js';
|
|
2
2
|
import { curve } from './easing.js';
|
|
3
3
|
import { Motions } from './motions.js';
|
|
4
4
|
import { release } from './shared.js';
|
|
@@ -44,6 +44,9 @@ class MotionPatch {
|
|
|
44
44
|
state.sample(s, setting.elapsed, state.xs, state.vs);
|
|
45
45
|
return { [this.writes[0]]: state.value(s, state.xs) };
|
|
46
46
|
}
|
|
47
|
+
set(subject, value, at) {
|
|
48
|
+
this.motions.change(subject, { at, x: axes(value) });
|
|
49
|
+
}
|
|
47
50
|
read(subject, at) {
|
|
48
51
|
const state = this.motions;
|
|
49
52
|
const r = state.read(subject, at);
|
|
@@ -63,9 +66,18 @@ class SpringPatch extends TweenPatch {
|
|
|
63
66
|
}
|
|
64
67
|
}
|
|
65
68
|
class GlidePatch extends MotionPatch {
|
|
69
|
+
edges;
|
|
70
|
+
constructor(writes, motions, edges) {
|
|
71
|
+
super(writes, motions);
|
|
72
|
+
this.edges = edges;
|
|
73
|
+
}
|
|
66
74
|
push(subject, velocity, at) {
|
|
67
75
|
this.motions.change(subject, { at, v: axes(velocity) });
|
|
68
76
|
}
|
|
77
|
+
bounds(subject) {
|
|
78
|
+
const b = this.edges;
|
|
79
|
+
return b === undefined ? undefined : per(b, subject);
|
|
80
|
+
}
|
|
69
81
|
}
|
|
70
82
|
/**
|
|
71
83
|
* The state behind a `'motion'` patch, for an engine that reads it directly; a patch another copy
|
|
@@ -124,12 +136,14 @@ class GlideShape {
|
|
|
124
136
|
ms = undefined;
|
|
125
137
|
start;
|
|
126
138
|
speed;
|
|
139
|
+
edges;
|
|
127
140
|
constructor(law, motion, opts, tau) {
|
|
128
141
|
this.law = law;
|
|
129
142
|
this.motion = motion;
|
|
130
143
|
this.tau = tau;
|
|
131
144
|
this.start = opts.from;
|
|
132
145
|
this.speed = opts.velocity;
|
|
146
|
+
this.edges = opts.bounds;
|
|
133
147
|
}
|
|
134
148
|
spec() {
|
|
135
149
|
return this.motion;
|
|
@@ -141,8 +155,24 @@ class GlideShape {
|
|
|
141
155
|
const v = this.speed;
|
|
142
156
|
return v === undefined ? still(x) : axes(per(v, s));
|
|
143
157
|
}
|
|
144
|
-
aim(x, v) {
|
|
145
|
-
|
|
158
|
+
aim(x, v, _was, s, put) {
|
|
159
|
+
const rest = x.map((xi, i) => xi + v[i] * this.tau);
|
|
160
|
+
if (this.edges === undefined)
|
|
161
|
+
return rest;
|
|
162
|
+
const [lo, hi] = per(this.edges, s);
|
|
163
|
+
const min = axes(lo);
|
|
164
|
+
const max = axes(hi);
|
|
165
|
+
if (min.length !== rest.length || max.length !== rest.length)
|
|
166
|
+
throw new Error('blits: bounds on a glide have as many axes as its subjects');
|
|
167
|
+
return rest.map((r, i) => {
|
|
168
|
+
const a = min[i];
|
|
169
|
+
const b = max[i];
|
|
170
|
+
const xi = x[i];
|
|
171
|
+
// A set past an edge stays there: a drag holds it out, and only a push lets it spring back.
|
|
172
|
+
if (put && (xi < a || xi > b))
|
|
173
|
+
return r;
|
|
174
|
+
return r < a ? a : r > b ? b : r;
|
|
175
|
+
});
|
|
146
176
|
}
|
|
147
177
|
scalar(s) {
|
|
148
178
|
return typeof per(this.start, s) === 'number';
|
|
@@ -226,16 +256,25 @@ export function spring(writes, opts) {
|
|
|
226
256
|
* `ms`, and comes to rest `velocity · ms / 1000` past where it was released. `push` sets it moving
|
|
227
257
|
* again from wherever it is. Closed form, so frame rate does not change where it stops.
|
|
228
258
|
*
|
|
259
|
+
* With `bounds`, a coast that would stop past an edge springs back to it from where it crosses, at
|
|
260
|
+
* the speed it crosses at, on a critically damped spring of `stiffness`; one let go past an edge
|
|
261
|
+
* springs back from there, unless it coasts back inside. A `set` past an edge leaves the subject
|
|
262
|
+
* there, coasting on, until the next `push`, so a drag can hold it out.
|
|
263
|
+
*
|
|
229
264
|
* @category patch
|
|
230
265
|
*/
|
|
231
266
|
export function glide(writes, opts) {
|
|
232
267
|
const ms = opts.ms ?? 325;
|
|
233
268
|
if (!(ms > 0))
|
|
234
269
|
throw new RangeError('blits: a glide takes a positive ms');
|
|
270
|
+
const k = opts.stiffness ?? 170;
|
|
271
|
+
if (!(k > 0))
|
|
272
|
+
throw new RangeError('blits: a glide takes a positive stiffness');
|
|
235
273
|
const tau = ms / 1000;
|
|
236
274
|
const settle = opts.settle ?? 1e-4;
|
|
237
|
-
const
|
|
238
|
-
|
|
275
|
+
const bounded = opts.bounds !== undefined;
|
|
276
|
+
const shape = new GlideShape(bounded ? [BOUND, settle, tau, Math.sqrt(k), 0] : [COAST, settle, tau, 0, 0], bounded ? { kind: 'glide', ms, settle, stiffness: k } : { kind: 'glide', ms, settle }, opts, tau);
|
|
277
|
+
return new GlidePatch(writes, new Motions(shape), opts.bounds);
|
|
239
278
|
}
|
|
240
279
|
/**
|
|
241
280
|
* From one value to another over `ms` along an easing, solved in closed form, so frame rate does not
|
package/dist/motions.d.ts
CHANGED
|
@@ -20,6 +20,8 @@ export interface Change {
|
|
|
20
20
|
*/
|
|
21
21
|
at?: number;
|
|
22
22
|
to?: number[];
|
|
23
|
+
/** Where it moves from, in place of where its stretch has it then. */
|
|
24
|
+
x?: number[];
|
|
23
25
|
v?: number[];
|
|
24
26
|
/** The mix clock when it was made, NaN where no mix was playing the patch. */
|
|
25
27
|
made?: number;
|
|
@@ -73,8 +75,11 @@ export interface Shape<I> {
|
|
|
73
75
|
from: (subject: I) => number[];
|
|
74
76
|
/** How fast each axis starts moving, given where it starts: still unless the options say. */
|
|
75
77
|
velocity: (subject: I, x: number[]) => number[];
|
|
76
|
-
/**
|
|
77
|
-
|
|
78
|
+
/**
|
|
79
|
+
* Where it heads when released from `x` at `v`, given where it was heading; `put` for a stretch a
|
|
80
|
+
* `set` released.
|
|
81
|
+
*/
|
|
82
|
+
aim: (x: number[], v: number[], was: number[] | null, subject: I, put: boolean) => number[];
|
|
78
83
|
/** `[form, settle, k1, k2, k3]`. */
|
|
79
84
|
law: readonly number[];
|
|
80
85
|
/** Whether a subject's value is a number rather than an array. */
|
|
@@ -238,7 +243,7 @@ export declare class Motions<I> {
|
|
|
238
243
|
s: number;
|
|
239
244
|
} | undefined;
|
|
240
245
|
/**
|
|
241
|
-
* Queues a retarget or push, untimed at the latest frame, for `subject`'s next read past its time.
|
|
246
|
+
* Queues a retarget, set or push, untimed at the latest frame, for `subject`'s next read past its time.
|
|
242
247
|
* The queue keeps the changes with a time in time order, ahead of any still waiting for one.
|
|
243
248
|
*/
|
|
244
249
|
change(subject: I, c: Change): void;
|
package/dist/motions.js
CHANGED
|
@@ -126,7 +126,7 @@ export class Motions {
|
|
|
126
126
|
return known;
|
|
127
127
|
const x = this.shape.from(subject);
|
|
128
128
|
const v = this.shape.velocity(subject, x);
|
|
129
|
-
const to = this.shape.aim(x, v, null, subject);
|
|
129
|
+
const to = this.shape.aim(x, v, null, subject, false);
|
|
130
130
|
const n = this.n < 0 ? x.length : this.n;
|
|
131
131
|
this.check(x, n);
|
|
132
132
|
this.check(v, n);
|
|
@@ -465,13 +465,14 @@ export class Motions {
|
|
|
465
465
|
return { x, v, s };
|
|
466
466
|
}
|
|
467
467
|
/**
|
|
468
|
-
* Queues a retarget or push, untimed at the latest frame, for `subject`'s next read past its time.
|
|
468
|
+
* Queues a retarget, set or push, untimed at the latest frame, for `subject`'s next read past its time.
|
|
469
469
|
* The queue keeps the changes with a time in time order, ahead of any still waiting for one.
|
|
470
470
|
*/
|
|
471
471
|
change(subject, c) {
|
|
472
472
|
const back = this.owner === null ? Number.NaN : this.owner.revive(this.ownerId, subject);
|
|
473
473
|
const s = this.slot(subject);
|
|
474
474
|
this.check(c.to, this.n);
|
|
475
|
+
this.check(c.x, this.n);
|
|
475
476
|
this.check(c.v, this.n);
|
|
476
477
|
c.made = this.owner === null ? Number.NaN : this.owner.now();
|
|
477
478
|
c.seq = this.owner === null ? Number.NaN : this.owner.seq();
|
|
@@ -494,7 +495,7 @@ export class Motions {
|
|
|
494
495
|
else
|
|
495
496
|
this.queue(list, c, c.at);
|
|
496
497
|
reading.moved++;
|
|
497
|
-
const made = { at: c.at, to: c.to, v: c.v };
|
|
498
|
+
const made = { at: c.at, to: c.to, x: c.x, v: c.v };
|
|
498
499
|
this.owner?.changed(this.ownerId, this, subject, made);
|
|
499
500
|
}
|
|
500
501
|
/** Files a change with a time after every one due by then, ahead of those still waiting for one. */
|
|
@@ -671,11 +672,12 @@ export class Motions {
|
|
|
671
672
|
landing = { x: new Float64Array(n), v: new Float64Array(n) };
|
|
672
673
|
const { x, v } = landing;
|
|
673
674
|
this.evaluateSegment(seg, at, x, v);
|
|
674
|
-
const xs = numbers(x, 0, n);
|
|
675
|
+
const xs = change.x ?? numbers(x, 0, n);
|
|
675
676
|
const vs = change.v ?? numbers(v, 0, n);
|
|
676
677
|
const subject = this.numbers.subject(s);
|
|
677
678
|
const known = subject === absent ? undefined : subject;
|
|
678
|
-
const
|
|
679
|
+
const put = change.x !== undefined && change.v === undefined;
|
|
680
|
+
const to = this.shape.aim(xs, vs, change.to ?? seg.to, known, put);
|
|
679
681
|
const ms = subject === absent ? seg.ms : (this.shape.ms?.(known) ?? 0);
|
|
680
682
|
return { at, ms, x0: xs, v0: vs, to, change };
|
|
681
683
|
}
|
package/dist/move.js
CHANGED
|
@@ -9,7 +9,7 @@ import { begin } from './held.js';
|
|
|
9
9
|
import { noted } from './history.js';
|
|
10
10
|
import { leave as leaveClock } from './origin.js';
|
|
11
11
|
import { ownerReading } from './owner.js';
|
|
12
|
-
import { mixAt, place, repin, startOf } from './place.js';
|
|
12
|
+
import { mixAt, place, repin, startAt, startOf } from './place.js';
|
|
13
13
|
import { pageVoice } from './revive.js';
|
|
14
14
|
import { delist } from './roster.js';
|
|
15
15
|
import { scoredLeft, scoredRemove } from './scored.js';
|
|
@@ -47,6 +47,8 @@ function moveTo(mix, now) {
|
|
|
47
47
|
for (const a of mix.announced)
|
|
48
48
|
if (Number.isNaN(a.at))
|
|
49
49
|
a.at = mix.u;
|
|
50
|
+
if (mix.waking)
|
|
51
|
+
wake(mix, now);
|
|
50
52
|
if (mix.pins !== null && mix.pins.size > 0)
|
|
51
53
|
repin(mix);
|
|
52
54
|
if (mix.anchored > 0)
|
|
@@ -137,10 +139,31 @@ function moveTo(mix, now) {
|
|
|
137
139
|
if (mix.sharers.length > 0 && !mix.projecting)
|
|
138
140
|
outgrow(mix);
|
|
139
141
|
mix.stirred = false;
|
|
142
|
+
mix.asleep = false;
|
|
140
143
|
if (mix.retired.length > 0)
|
|
141
144
|
prune(mix);
|
|
142
145
|
forget(mix);
|
|
143
146
|
}
|
|
147
|
+
/** Starts each voice cued while the mix slept at `now`, the first sync after its cue. */
|
|
148
|
+
function wake(mix, now) {
|
|
149
|
+
mix.waking = false;
|
|
150
|
+
for (const voice of mix.cued) {
|
|
151
|
+
if (!voice.waking)
|
|
152
|
+
continue;
|
|
153
|
+
voice.waking = false;
|
|
154
|
+
if (voice.state !== 'pending')
|
|
155
|
+
continue;
|
|
156
|
+
startAt(mix, voice, now);
|
|
157
|
+
// Where a seek back before the cue puts it, so the tape's cue again starts it here.
|
|
158
|
+
const first = voice.first;
|
|
159
|
+
if (first !== null) {
|
|
160
|
+
first.start = now;
|
|
161
|
+
first.anchorNow = now;
|
|
162
|
+
}
|
|
163
|
+
if (!mix.projecting)
|
|
164
|
+
schedule(mix, voice);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
144
167
|
/**
|
|
145
168
|
* Which way a voice's clock is running at mix time `now`, through every owner above it: above 0
|
|
146
169
|
* forward, below 0 back, 0 standing still.
|
|
@@ -172,6 +195,8 @@ function turn(mix, voice, now) {
|
|
|
172
195
|
*/
|
|
173
196
|
const SEARCHED = 8;
|
|
174
197
|
const SPARSE = 32;
|
|
198
|
+
/** The most retired voices `prune` splices out one at a time, rather than closing up in one pass. */
|
|
199
|
+
const SPLICED = 4;
|
|
175
200
|
/**
|
|
176
201
|
* Takes the voices retired since the last prune out of the list, in list order. A few, or a small
|
|
177
202
|
* share of a long list, are found by their id, which the list is in order of, so the voices still
|
|
@@ -200,16 +225,21 @@ function prune(mix) {
|
|
|
200
225
|
if (at !== null) {
|
|
201
226
|
for (const voice of retired)
|
|
202
227
|
pruned = leave(mix, voice) || pruned;
|
|
203
|
-
//
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
228
|
+
// A splice moves the tail natively, several times faster than a loop, but once a voice.
|
|
229
|
+
if (at.length <= SPLICED)
|
|
230
|
+
for (let k = at.length - 1; k >= 0; k--)
|
|
231
|
+
voices.splice(at[k], 1);
|
|
232
|
+
else {
|
|
233
|
+
let kept = at[0];
|
|
234
|
+
let k = 0;
|
|
235
|
+
for (let i = kept; i < voices.length; i++) {
|
|
236
|
+
if (at[k] === i)
|
|
237
|
+
k++;
|
|
238
|
+
else
|
|
239
|
+
voices[kept++] = voices[i];
|
|
240
|
+
}
|
|
241
|
+
voices.length = kept;
|
|
211
242
|
}
|
|
212
|
-
voices.length = kept;
|
|
213
243
|
}
|
|
214
244
|
else {
|
|
215
245
|
let kept = 0;
|
package/dist/origin.d.ts
CHANGED
|
@@ -5,12 +5,14 @@ export interface Past extends Clock {
|
|
|
5
5
|
until: number;
|
|
6
6
|
/** The voice's `rebuilds` while it ran on this clock. */
|
|
7
7
|
rebuilds: number;
|
|
8
|
+
/** Whether the voice left it by a seek. */
|
|
9
|
+
sought: boolean;
|
|
8
10
|
}
|
|
9
11
|
/**
|
|
10
12
|
* Keeps the clock a voice is leaving at its local time `u`, ahead of a rate change, ramp or seek,
|
|
11
13
|
* where a subject's origin could still be found on it.
|
|
12
14
|
*/
|
|
13
|
-
export declare function leave<I, O>(voice: Voice<I, O>, u: number): void;
|
|
15
|
+
export declare function leave<I, O>(voice: Voice<I, O>, u: number, sought?: boolean): void;
|
|
14
16
|
/**
|
|
15
17
|
* The mix time a voice's clock first read `elapsed`, or will by its controls now, through its owners'
|
|
16
18
|
* clocks. A clock entered already past it, by a seek, answers where that clock would have read it.
|
package/dist/origin.js
CHANGED
|
@@ -5,7 +5,7 @@ const MOST = 256;
|
|
|
5
5
|
* Keeps the clock a voice is leaving at its local time `u`, ahead of a rate change, ramp or seek,
|
|
6
6
|
* where a subject's origin could still be found on it.
|
|
7
7
|
*/
|
|
8
|
-
export function leave(voice, u) {
|
|
8
|
+
export function leave(voice, u, sought = false) {
|
|
9
9
|
const past = voice.clocks;
|
|
10
10
|
if (past !== null && past.length >= MOST)
|
|
11
11
|
return;
|
|
@@ -14,7 +14,7 @@ export function leave(voice, u) {
|
|
|
14
14
|
if (past !== null && reached(past, 0) >= reach && reached(past, fresh(voice)) >= reach)
|
|
15
15
|
return;
|
|
16
16
|
const { anchorNow, anchorElapsed, rate, ramp, rebuilds } = voice;
|
|
17
|
-
const left = { anchorNow, anchorElapsed, rate, ramp, until: u, rebuilds };
|
|
17
|
+
const left = { anchorNow, anchorElapsed, rate, ramp, until: u, rebuilds, sought };
|
|
18
18
|
if (past === null)
|
|
19
19
|
voice.clocks = [left];
|
|
20
20
|
else
|
|
@@ -41,6 +41,18 @@ function fresh(voice) {
|
|
|
41
41
|
i++;
|
|
42
42
|
return i;
|
|
43
43
|
}
|
|
44
|
+
/**
|
|
45
|
+
* Where a walk for a stateful record at `elapsed` starts after a seek back before it: the clock that
|
|
46
|
+
* seek entered, since a subject sent back before its start starts afresh.
|
|
47
|
+
*/
|
|
48
|
+
function backFrom(voice, past, elapsed) {
|
|
49
|
+
for (let i = past.length - 1; i >= 0; i--) {
|
|
50
|
+
const p = past[i];
|
|
51
|
+
if (p.sought && (past[i + 1]?.anchorElapsed ?? voice.anchorElapsed) < elapsed)
|
|
52
|
+
return i + 1;
|
|
53
|
+
}
|
|
54
|
+
return 0;
|
|
55
|
+
}
|
|
44
56
|
/**
|
|
45
57
|
* The mix time a voice's clock first read `elapsed`, or will by its controls now, through its owners'
|
|
46
58
|
* clocks. A clock entered already past it, by a seek, answers where that clock would have read it.
|
|
@@ -51,7 +63,8 @@ export function originOf(voice, elapsed, rebuilt) {
|
|
|
51
63
|
// A voice with no first pass shows every subject from its start, whatever its stagger.
|
|
52
64
|
let t = voice.beginless ? voice.start : Number.NaN;
|
|
53
65
|
if (past !== null && Number.isNaN(t)) {
|
|
54
|
-
|
|
66
|
+
const from = rebuilt ? Math.max(fresh(voice), backFrom(voice, past, elapsed)) : 0;
|
|
67
|
+
for (let i = from; i < past.length; i++) {
|
|
55
68
|
const p = past[i];
|
|
56
69
|
const at = timeWith(p, elapsed);
|
|
57
70
|
if (at <= p.until) {
|
package/dist/pack.d.ts
CHANGED
package/dist/pack.js
CHANGED
|
@@ -11,6 +11,7 @@ export function packHeld(voice, h) {
|
|
|
11
11
|
shown: h.shown,
|
|
12
12
|
weight: h.weight,
|
|
13
13
|
rested: h.rested,
|
|
14
|
+
restedAt: h.restedAt,
|
|
14
15
|
bands: h.bands === null ? null : Array.from(h.bands),
|
|
15
16
|
state: h.state === undefined ? undefined : patch.pack ? patch.pack(h.state) : clone(h.state),
|
|
16
17
|
stepped: h.stepped,
|
|
@@ -40,6 +41,7 @@ export function unpackHeld(voice, p) {
|
|
|
40
41
|
h.shown = p.shown;
|
|
41
42
|
h.weight = p.weight;
|
|
42
43
|
h.rested = p.rested;
|
|
44
|
+
h.restedAt = p.restedAt;
|
|
43
45
|
h.bands = bands;
|
|
44
46
|
h.stepped = p.stepped;
|
|
45
47
|
h.ticks = p.ticks;
|
package/dist/project.js
CHANGED
|
@@ -291,6 +291,7 @@ function copyAhead(mix, c) {
|
|
|
291
291
|
pin(c, copy, at);
|
|
292
292
|
return copy;
|
|
293
293
|
});
|
|
294
|
+
c.waking = mix.waking;
|
|
294
295
|
ownBlends(c.cued);
|
|
295
296
|
// A copy keeps no list of owners, so the one before says only by its voices.
|
|
296
297
|
if (mix.owners !== null || (mix.projecting && c.cued.some((v) => v.holding !== null)))
|
package/dist/record.js
CHANGED
package/dist/rows.js
CHANGED
|
@@ -181,8 +181,10 @@ function copyRow(from, p, to, q, keys = from.keys) {
|
|
|
181
181
|
carry(from.data, p * 8 /* Row.STRIDE */, to.data, q * 8 /* Row.STRIDE */, 8 /* Row.STRIDE */);
|
|
182
182
|
carry(from.hot, p * from.stride, to.hot, q * to.stride, from.stride);
|
|
183
183
|
copyKeys(keys, to, q);
|
|
184
|
-
if ((from.hot[p * from.stride + 0 /* Hot.FLAGS */] & 4 /* Flag.STALE */) !== 0)
|
|
184
|
+
if ((from.hot[p * from.stride + 0 /* Hot.FLAGS */] & 4 /* Flag.STALE */) !== 0) {
|
|
185
185
|
to.stale = true;
|
|
186
|
+
to.staleRows.push(q);
|
|
187
|
+
}
|
|
186
188
|
carry(from.samples, p * from.axes, to.samples, q * to.axes, from.axes);
|
|
187
189
|
to.deltas[q] = from.deltas[p] ?? null;
|
|
188
190
|
to.records[q] = from.records[p];
|
package/dist/seek.js
CHANGED
|
@@ -33,8 +33,7 @@ function restore(mix, voice, live, at, every, mine) {
|
|
|
33
33
|
const h = mine && live.reaches ? unread(live) : copyHeld(voice, live);
|
|
34
34
|
if (h.stepped > t)
|
|
35
35
|
h.stepped = t;
|
|
36
|
-
|
|
37
|
-
h.rested = false;
|
|
36
|
+
restedBy(h, live, t);
|
|
38
37
|
h.weight = 0;
|
|
39
38
|
h.inputs = inputs;
|
|
40
39
|
if (!(h.since <= t))
|
|
@@ -63,6 +62,7 @@ function restore(mix, voice, live, at, every, mine) {
|
|
|
63
62
|
else
|
|
64
63
|
h = copyHeld(voice, snap.held, true);
|
|
65
64
|
h.weight = 0;
|
|
65
|
+
restedBy(h, live, t);
|
|
66
66
|
if (snap.at < t)
|
|
67
67
|
h.copied = true;
|
|
68
68
|
const kept = i + 1;
|
|
@@ -108,6 +108,14 @@ function restore(mix, voice, live, at, every, mine) {
|
|
|
108
108
|
}
|
|
109
109
|
return h;
|
|
110
110
|
}
|
|
111
|
+
/**
|
|
112
|
+
* Whether a record put back for mix time `t` had rested by then, from when the live one did: a
|
|
113
|
+
* subject may have rested and moved on since, so a probe at `t` would not find it at rest again.
|
|
114
|
+
*/
|
|
115
|
+
function restedBy(h, live, t) {
|
|
116
|
+
h.rested = live.restedAt <= t;
|
|
117
|
+
h.restedAt = h.rested ? live.restedAt : Number.NaN;
|
|
118
|
+
}
|
|
111
119
|
/** Cuts a record's list of copies to its first `kept`, keeping those no other list holds as spares. */
|
|
112
120
|
function trim(voice, snaps, kept) {
|
|
113
121
|
while (snaps.length > kept) {
|
|
@@ -252,7 +260,14 @@ export function seek(transport, to) {
|
|
|
252
260
|
if (frame === undefined) {
|
|
253
261
|
// Ahead, under the rate the recorded calls up to `t` set, past any frames rate 0 held at `t`.
|
|
254
262
|
const last = replay(transport, (at) => (transport.pace === null ? at : transport.pace.reading(at)) <= t);
|
|
255
|
-
const
|
|
263
|
+
const left = transport.left;
|
|
264
|
+
// Inverting the rate can land an ulp from where the host stood, which every later sync would carry.
|
|
265
|
+
const at = left !== null &&
|
|
266
|
+
left.t === t &&
|
|
267
|
+
left.u >= transport.u &&
|
|
268
|
+
(transport.pace === null ? left.u : transport.pace.reading(left.u)) === t
|
|
269
|
+
? left.u
|
|
270
|
+
: hostAhead(transport, t);
|
|
256
271
|
const u = Number.isNaN(last) ? at : Math.max(last, at);
|
|
257
272
|
if (!Number.isFinite(u))
|
|
258
273
|
throw new RangeError(`blits: the mix's rate never reaches ${t}`);
|
|
@@ -266,6 +281,7 @@ export function seek(transport, to) {
|
|
|
266
281
|
// On the frame itself, its own host time: rate 0 may have held the clock there for several.
|
|
267
282
|
const u = frame.at === t ? frame.u : hostAt(transport, t);
|
|
268
283
|
const seq = frame.seq;
|
|
284
|
+
transport.left = { t: transport.now, u: transport.u };
|
|
269
285
|
tape.goto(tape.depthAt(u));
|
|
270
286
|
transport.undrop(seq);
|
|
271
287
|
// What history kept after the frame is let go; the tape holds the calls that made it.
|
package/dist/shared.d.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* The package version, for a fact whose shape follows the code too closely to version by hand:
|
|
3
3
|
* copies of different releases keep apart. `test/copies.test.ts` holds it to `package.json`.
|
|
4
4
|
*/
|
|
5
|
-
export declare const release = "0.
|
|
5
|
+
export declare const release = "0.14.0";
|
|
6
6
|
/**
|
|
7
7
|
* A WeakMap every copy of blits loaded in one realm shares, so a fact one copy records about a
|
|
8
8
|
* patch or channel it made is read by another copy's mix. The name carries a version, bumped when
|
package/dist/shared.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* The package version, for a fact whose shape follows the code too closely to version by hand:
|
|
3
3
|
* copies of different releases keep apart. `test/copies.test.ts` holds it to `package.json`.
|
|
4
4
|
*/
|
|
5
|
-
export const release = '0.
|
|
5
|
+
export const release = '0.14.0';
|
|
6
6
|
/**
|
|
7
7
|
* A WeakMap every copy of blits loaded in one realm shares, so a fact one copy records about a
|
|
8
8
|
* patch or channel it made is read by another copy's mix. The name carries a version, bumped when
|
package/dist/stack.d.ts
CHANGED
|
@@ -3,8 +3,8 @@ import type { Kit } from './types.js';
|
|
|
3
3
|
* Folds deltas into one pose by a kit's arithmetic, in order, with no mix and no clock: what a mix
|
|
4
4
|
* gives for voices contributing these deltas at these weights. Each channel starts at its rest, a
|
|
5
5
|
* weight (default 1, held to 0..1) fades a delta toward rest, and the pose is clamped to each
|
|
6
|
-
* channel's bounds. A channel with no rest takes the last delta
|
|
7
|
-
*
|
|
6
|
+
* channel's bounds. A channel with no rest takes the last delta a mix's default band switches on, any
|
|
7
|
+
* weight above 0, and stays undefined where none is.
|
|
8
8
|
*
|
|
9
9
|
* @category channel
|
|
10
10
|
*/
|
package/dist/stack.js
CHANGED
|
@@ -1,13 +1,12 @@
|
|
|
1
|
+
import { STOCK_BAND } from './channels.js';
|
|
1
2
|
import { copy, first } from './clone.js';
|
|
2
3
|
import { bound, merged } from './fold.js';
|
|
3
|
-
/** The weight a mix's default band first switches a rest-less channel on at. */
|
|
4
|
-
const ON = 0.6;
|
|
5
4
|
/**
|
|
6
5
|
* Folds deltas into one pose by a kit's arithmetic, in order, with no mix and no clock: what a mix
|
|
7
6
|
* gives for voices contributing these deltas at these weights. Each channel starts at its rest, a
|
|
8
7
|
* weight (default 1, held to 0..1) fades a delta toward rest, and the pose is clamped to each
|
|
9
|
-
* channel's bounds. A channel with no rest takes the last delta
|
|
10
|
-
*
|
|
8
|
+
* channel's bounds. A channel with no rest takes the last delta a mix's default band switches on, any
|
|
9
|
+
* weight above 0, and stays undefined where none is.
|
|
11
10
|
*
|
|
12
11
|
* @category channel
|
|
13
12
|
*/
|
|
@@ -34,7 +33,7 @@ export function fold(kit, deltas, weights) {
|
|
|
34
33
|
pose[key] = channel.fold
|
|
35
34
|
? channel.fold(pose[key], value, w)
|
|
36
35
|
: merged(channel, pose[key], channel.scale(value, w));
|
|
37
|
-
else if (w >=
|
|
36
|
+
else if (w >= STOCK_BAND.on)
|
|
38
37
|
pose[key] = pose[key] === undefined ? first(value) : merged(channel, pose[key], value);
|
|
39
38
|
}
|
|
40
39
|
}
|
package/dist/strays.js
CHANGED
|
@@ -117,7 +117,7 @@ function changeAhead(to, id, motion, given, change) {
|
|
|
117
117
|
made.set(motion, bySubject);
|
|
118
118
|
}
|
|
119
119
|
const list = bySubject.get(subject);
|
|
120
|
-
const c = { at, to: change.to, v: change.v };
|
|
120
|
+
const c = { at, to: change.to, x: change.x, v: change.v };
|
|
121
121
|
if (list === undefined)
|
|
122
122
|
bySubject.set(subject, [c]);
|
|
123
123
|
else {
|
package/dist/transport.d.ts
CHANGED
|
@@ -73,6 +73,11 @@ export declare class Transport implements TransportApi {
|
|
|
73
73
|
/** Host time at the last sync: the host's timestamp less every gap `rebase` took out. */
|
|
74
74
|
u: number;
|
|
75
75
|
offset: number;
|
|
76
|
+
/** Where the last seek back left from, so a seek ahead to the same mix time lands on the same host time. */
|
|
77
|
+
left: {
|
|
78
|
+
t: number;
|
|
79
|
+
u: number;
|
|
80
|
+
} | null;
|
|
76
81
|
/** The mix time of the first sync. */
|
|
77
82
|
born: number;
|
|
78
83
|
/** The earliest mix time what history keeps in memory restores exactly. */
|
package/dist/transport.js
CHANGED
|
@@ -45,6 +45,8 @@ export class Transport {
|
|
|
45
45
|
/** Host time at the last sync: the host's timestamp less every gap `rebase` took out. */
|
|
46
46
|
u = Number.NaN;
|
|
47
47
|
offset = 0;
|
|
48
|
+
/** Where the last seek back left from, so a seek ahead to the same mix time lands on the same host time. */
|
|
49
|
+
left = null;
|
|
48
50
|
/** The mix time of the first sync. */
|
|
49
51
|
born = Number.NaN;
|
|
50
52
|
/** The earliest mix time what history keeps in memory restores exactly. */
|