@msb235/blits 0.13.0 → 0.15.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +183 -0
- package/README.md +20 -3
- package/dist/ahead.d.ts +26 -0
- package/dist/ahead.js +264 -0
- package/dist/around.d.ts +11 -0
- package/dist/around.js +32 -0
- package/dist/bare.js +15 -7
- package/dist/blank.d.ts +10 -0
- package/dist/blank.js +26 -0
- package/dist/blend.d.ts +2 -1
- package/dist/blend.js +1 -6
- package/dist/bound.d.ts +70 -0
- package/dist/bound.js +351 -0
- package/dist/chain.d.ts +2 -1
- package/dist/chain.js +7 -5
- package/dist/changes.d.ts +24 -0
- package/dist/changes.js +189 -0
- package/dist/channels.d.ts +39 -13
- package/dist/channels.js +14 -5
- package/dist/closed.d.ts +1 -0
- package/dist/closed.js +45 -1
- package/dist/color.d.ts +12 -10
- package/dist/color.js +25 -25
- package/dist/columns.js +7 -3
- package/dist/contribution.d.ts +30 -0
- package/dist/contribution.js +374 -0
- package/dist/copyout.d.ts +10 -0
- package/dist/copyout.js +69 -24
- package/dist/crowd.d.ts +8 -2
- package/dist/crowd.js +4 -5
- package/dist/cue.d.ts +8 -1
- package/dist/cue.js +47 -14
- package/dist/cut.d.ts +8 -0
- package/dist/cut.js +4 -0
- package/dist/drop.d.ts +9 -0
- package/dist/drop.js +84 -0
- package/dist/due.d.ts +24 -5
- package/dist/due.js +113 -33
- package/dist/easing.d.ts +4 -4
- package/dist/easing.js +3 -3
- package/dist/everyone.d.ts +3 -2
- package/dist/everyone.js +3 -3
- package/dist/fade.d.ts +2 -1
- package/dist/fade.js +2 -1
- package/dist/fill.d.ts +3 -2
- package/dist/fill.js +51 -19
- package/dist/fit.d.ts +44 -26
- package/dist/fit.js +10 -9
- package/dist/fling.d.ts +66 -0
- package/dist/fling.js +162 -0
- package/dist/fold.d.ts +4 -37
- package/dist/fold.js +5 -512
- package/dist/gather.d.ts +12 -7
- package/dist/gather.js +179 -44
- package/dist/gone.js +2 -2
- package/dist/grid.d.ts +47 -0
- package/dist/grid.js +247 -0
- package/dist/handle.d.ts +3 -0
- package/dist/handle.js +19 -8
- package/dist/held.d.ts +2 -1
- package/dist/history.d.ts +6 -5
- package/dist/history.js +9 -9
- package/dist/hosts.js +9 -15
- package/dist/index.d.ts +5 -4
- package/dist/keyfill.d.ts +1 -1
- package/dist/keyfill.js +5 -3
- package/dist/keyrows.d.ts +12 -0
- package/dist/keyrows.js +99 -12
- package/dist/lane.d.ts +36 -6
- package/dist/lane.js +32 -1
- package/dist/lanes.d.ts +23 -8
- package/dist/lanes.js +29 -8
- package/dist/leavings.d.ts +2 -2
- package/dist/leavings.js +1 -1
- package/dist/lives.d.ts +47 -0
- package/dist/lives.js +180 -0
- package/dist/meet.d.ts +2 -2
- package/dist/meet.js +90 -4
- package/dist/mixer.d.ts +61 -35
- package/dist/mixer.js +64 -169
- package/dist/motion.d.ts +19 -106
- package/dist/motion.js +60 -13
- package/dist/motions.d.ts +62 -150
- package/dist/motions.js +57 -369
- package/dist/move.js +24 -1
- package/dist/moved.d.ts +1 -1
- package/dist/moved.js +16 -7
- package/dist/named.d.ts +0 -1
- package/dist/named.js +20 -2
- package/dist/numbers.d.ts +2 -0
- package/dist/numbers.js +4 -0
- package/dist/owed.d.ts +2 -0
- package/dist/owed.js +4 -0
- package/dist/pack.d.ts +3 -1
- package/dist/pack.js +4 -3
- package/dist/patch.d.ts +7 -3
- package/dist/patch.js +6 -2
- package/dist/places.d.ts +53 -0
- package/dist/places.js +171 -0
- package/dist/plot.d.ts +12 -0
- package/dist/plot.js +52 -0
- package/dist/project.d.ts +4 -4
- package/dist/project.js +9 -211
- package/dist/pull.js +11 -8
- package/dist/qualify.js +84 -21
- package/dist/reading.d.ts +6 -0
- package/dist/reading.js +5 -0
- package/dist/record.d.ts +5 -1
- package/dist/record.js +4 -2
- package/dist/relink.d.ts +1 -1
- package/dist/restored.d.ts +2 -2
- package/dist/restored.js +1 -1
- package/dist/rests.d.ts +9 -0
- package/dist/rests.js +76 -0
- package/dist/revive.d.ts +3 -2
- package/dist/revive.js +2 -2
- package/dist/{locus.d.ts → role.d.ts} +10 -9
- package/dist/{locus.js → role.js} +3 -3
- package/dist/roles.d.ts +10 -0
- package/dist/roles.js +158 -0
- package/dist/roster.js +3 -3
- package/dist/rotation.d.ts +1 -1
- package/dist/rotation.js +1 -1
- package/dist/rows.js +1 -1
- package/dist/sample.d.ts +2 -2
- package/dist/sample.js +33 -7
- package/dist/seek.js +13 -5
- package/dist/setting.d.ts +27 -0
- package/dist/setting.js +55 -0
- package/dist/shared.d.ts +1 -1
- package/dist/shared.js +1 -1
- package/dist/signals.d.ts +64 -32
- package/dist/signals.js +20 -18
- package/dist/stack.d.ts +5 -5
- package/dist/stack.js +5 -5
- package/dist/standing.d.ts +2 -1
- package/dist/standing.js +3 -2
- package/dist/store.d.ts +9 -0
- package/dist/store.js +24 -0
- package/dist/strays.d.ts +2 -1
- package/dist/strays.js +21 -14
- package/dist/stretch.d.ts +112 -0
- package/dist/stretch.js +9 -0
- package/dist/subject.d.ts +142 -0
- package/dist/subject.js +1 -0
- package/dist/tape.d.ts +9 -5
- package/dist/tape.js +5 -1
- package/dist/then.d.ts +9 -0
- package/dist/then.js +57 -0
- package/dist/ticker.d.ts +4 -4
- package/dist/transport.d.ts +0 -8
- package/dist/transport.js +0 -4
- package/dist/turns.d.ts +4 -3
- package/dist/turns.js +4 -3
- package/dist/types/arc.d.ts +12 -11
- package/dist/types/channel.d.ts +12 -8
- package/dist/types/history.d.ts +35 -19
- package/dist/types/mix.d.ts +222 -161
- package/dist/types/motion.d.ts +151 -0
- package/dist/types/motion.js +1 -0
- package/dist/types/patch.d.ts +69 -34
- package/dist/types/place.d.ts +51 -36
- package/dist/types/transport.d.ts +20 -18
- package/dist/types/voice.d.ts +145 -72
- package/dist/types/watch.d.ts +88 -40
- package/dist/types.d.ts +1 -0
- package/dist/voice.d.ts +9 -141
- package/dist/voice.js +30 -79
- package/dist/warn.d.ts +2 -0
- package/dist/warn.js +1 -1
- package/dist/watch.d.ts +2 -2
- package/dist/watch.js +5 -3
- package/dist/wave.d.ts +4 -3
- package/dist/wave.js +3 -2
- package/dist/weigh.d.ts +7 -1
- package/dist/weigh.js +27 -0
- package/docs/schema.html +223 -37
- package/package.json +6 -2
package/dist/changes.js
ADDED
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
import { absent } from './numbers.js';
|
|
2
|
+
import { reading } from './reading.js';
|
|
3
|
+
import { numbers } from './stretch.js';
|
|
4
|
+
const PENDING = 2 /* RunFlag.PENDING */;
|
|
5
|
+
const OLDER = 4 /* RunFlag.OLDER */;
|
|
6
|
+
/** Where `applied` reads a stretch at the time a change lands; copied out before it returns. */
|
|
7
|
+
let landing = { x: new Float64Array(4), v: new Float64Array(4) };
|
|
8
|
+
/**
|
|
9
|
+
* Queues a retarget, set or push, untimed at the latest frame, for `subject`'s next read past its time.
|
|
10
|
+
* The queue keeps the changes with a time in time order, ahead of any still waiting for one.
|
|
11
|
+
*/
|
|
12
|
+
export function change(subject, c) {
|
|
13
|
+
const back = this.owner === null ? Number.NaN : this.owner.revive(this.ownerId, subject);
|
|
14
|
+
const s = this.slot(subject);
|
|
15
|
+
this.check(c.to, this.n);
|
|
16
|
+
this.check(c.x, this.n);
|
|
17
|
+
this.check(c.v, this.n);
|
|
18
|
+
c.made = this.owner === null ? Number.NaN : this.owner.now();
|
|
19
|
+
c.seq = this.owner === null ? Number.NaN : this.owner.seq();
|
|
20
|
+
c.early = this.owner?.early() === true;
|
|
21
|
+
if (c.at === undefined) {
|
|
22
|
+
const at = this.frame(subject);
|
|
23
|
+
if (!Number.isNaN(at))
|
|
24
|
+
c.at = at;
|
|
25
|
+
else if (!Number.isNaN(back))
|
|
26
|
+
c.at = back;
|
|
27
|
+
}
|
|
28
|
+
const list = this.pending?.[s];
|
|
29
|
+
if (list === undefined) {
|
|
30
|
+
if (this.pending === null)
|
|
31
|
+
this.pending = [undefined];
|
|
32
|
+
this.pending[s] = [c];
|
|
33
|
+
this.flag(s, PENDING, true);
|
|
34
|
+
}
|
|
35
|
+
else if (c.at === undefined)
|
|
36
|
+
list.push(c);
|
|
37
|
+
else
|
|
38
|
+
this.queue(list, c, c.at);
|
|
39
|
+
reading.moved++;
|
|
40
|
+
const made = { at: c.at, to: c.to, x: c.x, v: c.v };
|
|
41
|
+
this.owner?.changed(this.ownerId, this, subject, made);
|
|
42
|
+
}
|
|
43
|
+
/** Files a change with a time after every one due by then, ahead of those still waiting for one. */
|
|
44
|
+
export function queue(list, c, at) {
|
|
45
|
+
let i = list.length;
|
|
46
|
+
while (i > 0) {
|
|
47
|
+
const before = list[i - 1].at;
|
|
48
|
+
if (before !== undefined && before <= at)
|
|
49
|
+
break;
|
|
50
|
+
i--;
|
|
51
|
+
}
|
|
52
|
+
list.splice(i, 0, c);
|
|
53
|
+
}
|
|
54
|
+
/** Every change due by `t` applied to a copy of the stretch playing then, committing nothing. */
|
|
55
|
+
export function peek(s, t, xo, vo) {
|
|
56
|
+
let seg = this.playing(s, t);
|
|
57
|
+
const list = this.dueTo(s);
|
|
58
|
+
if (list !== undefined)
|
|
59
|
+
for (const change of list) {
|
|
60
|
+
const a = change.at;
|
|
61
|
+
if (a === undefined || !(a <= t))
|
|
62
|
+
break;
|
|
63
|
+
if (a >= seg.at)
|
|
64
|
+
seg = this.applied(this.numbers.subject(s), seg, change, a);
|
|
65
|
+
}
|
|
66
|
+
this.evaluateSegment(seg, t, xo, vo);
|
|
67
|
+
}
|
|
68
|
+
/** Subject `s`'s changes waiting, with those a read ahead's tape made filed among them by time. */
|
|
69
|
+
export function dueTo(s) {
|
|
70
|
+
const own = this.pending?.[s];
|
|
71
|
+
const ahead = reading.ahead?.get(this);
|
|
72
|
+
if (ahead === undefined)
|
|
73
|
+
return own;
|
|
74
|
+
const subject = this.numbers.subject(s);
|
|
75
|
+
const made = subject === absent ? undefined : ahead.get(subject);
|
|
76
|
+
if (made === undefined)
|
|
77
|
+
return own;
|
|
78
|
+
if (own === undefined)
|
|
79
|
+
return made;
|
|
80
|
+
// The patch's own were made first, and a stable sort keeps them first at one time.
|
|
81
|
+
return [...own.filter((c) => c.at !== undefined), ...made].sort((a, b) => a.at - b.at);
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Takes back every change made after frame `seq`: each subject plays from its earliest stretch
|
|
85
|
+
* kept, with the changes made by then queued to be applied again.
|
|
86
|
+
*/
|
|
87
|
+
export function rewind(seq) {
|
|
88
|
+
this.unrelease(seq);
|
|
89
|
+
for (let s = 0; s < this.numbers.size; s++)
|
|
90
|
+
if (this.numbers.subject(s) !== absent)
|
|
91
|
+
this.cut(s, seq);
|
|
92
|
+
}
|
|
93
|
+
export function cut(s, seq) {
|
|
94
|
+
const older = this.older?.[s];
|
|
95
|
+
const pending = this.pending?.[s];
|
|
96
|
+
const current = this.latest(s);
|
|
97
|
+
const stretches = older === undefined ? [current] : [...older, current];
|
|
98
|
+
const kept = [];
|
|
99
|
+
let cut = false;
|
|
100
|
+
for (const c of [...stretches.slice(1).map((seg) => seg.change), ...(pending ?? [])])
|
|
101
|
+
if (c === undefined || c.seq === undefined || !(c.seq > seq)) {
|
|
102
|
+
if (c !== undefined)
|
|
103
|
+
kept.push(c);
|
|
104
|
+
}
|
|
105
|
+
else
|
|
106
|
+
cut = true;
|
|
107
|
+
if (!cut)
|
|
108
|
+
return;
|
|
109
|
+
if (older !== undefined) {
|
|
110
|
+
this.older[s] = undefined;
|
|
111
|
+
this.flag(s, OLDER, false);
|
|
112
|
+
}
|
|
113
|
+
this.write(s, stretches[0]);
|
|
114
|
+
// Applied again in the order they take effect; one never stamped waits for the next read.
|
|
115
|
+
const timed = kept
|
|
116
|
+
.filter((c) => c.at !== undefined)
|
|
117
|
+
.sort((a, b) => a.at - b.at);
|
|
118
|
+
const list = [...timed, ...kept.filter((c) => c.at === undefined)];
|
|
119
|
+
if (list.length === 0) {
|
|
120
|
+
if (pending !== undefined) {
|
|
121
|
+
this.pending[s] = undefined;
|
|
122
|
+
this.flag(s, PENDING, false);
|
|
123
|
+
}
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
if (this.pending === null)
|
|
127
|
+
this.pending = [undefined];
|
|
128
|
+
this.pending[s] = list;
|
|
129
|
+
this.flag(s, PENDING, true);
|
|
130
|
+
}
|
|
131
|
+
/** The stretch `change` releases from `seg` at `at`, for `subject` or one no longer numbered. */
|
|
132
|
+
export function applied(subject, seg, change, at) {
|
|
133
|
+
const n = this.n;
|
|
134
|
+
if (landing.x.length < n)
|
|
135
|
+
landing = { x: new Float64Array(n), v: new Float64Array(n) };
|
|
136
|
+
const { x, v } = landing;
|
|
137
|
+
this.evaluateSegment(seg, at, x, v);
|
|
138
|
+
const xs = change.x ?? numbers(x, 0, n);
|
|
139
|
+
const vs = change.v ?? numbers(v, 0, n);
|
|
140
|
+
const known = subject === absent ? undefined : subject;
|
|
141
|
+
const put = change.x !== undefined && change.v === undefined;
|
|
142
|
+
const to = this.shape.aim(xs, vs, change.to ?? seg.to, known, put);
|
|
143
|
+
const ms = subject === absent ? seg.ms : (this.shape.ms?.(known) ?? 0);
|
|
144
|
+
return { at, ms, x0: xs, v0: vs, to, change };
|
|
145
|
+
}
|
|
146
|
+
export function commit(s, t) {
|
|
147
|
+
const list = this.pending[s];
|
|
148
|
+
// The first read of a subject no frame had met: those waiting for a time take this one.
|
|
149
|
+
if (list[list.length - 1].at === undefined) {
|
|
150
|
+
let i = list.length;
|
|
151
|
+
while (i > 0 && list[i - 1].at === undefined)
|
|
152
|
+
i--;
|
|
153
|
+
for (const c of list.splice(i)) {
|
|
154
|
+
c.at = t;
|
|
155
|
+
this.queue(list, c, t);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
while (list.length > 0) {
|
|
159
|
+
const change = list[0];
|
|
160
|
+
const a = change.at;
|
|
161
|
+
if (a > t)
|
|
162
|
+
break;
|
|
163
|
+
list.shift();
|
|
164
|
+
this.insert(s, this.applied(this.numbers.subject(s), this.playing(s, a), change, a));
|
|
165
|
+
}
|
|
166
|
+
if (list.length === 0) {
|
|
167
|
+
this.pending[s] = undefined;
|
|
168
|
+
this.flag(s, PENDING, false);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
export function insert(s, seg) {
|
|
172
|
+
let list = this.older?.[s];
|
|
173
|
+
if (list === undefined) {
|
|
174
|
+
list = [];
|
|
175
|
+
if (this.older === null)
|
|
176
|
+
this.older = [undefined];
|
|
177
|
+
this.older[s] = list;
|
|
178
|
+
this.flag(s, OLDER, true);
|
|
179
|
+
}
|
|
180
|
+
if (seg.at >= this.runs[this.base(s)]) {
|
|
181
|
+
list.push(this.latest(s));
|
|
182
|
+
this.write(s, seg);
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
let i = list.length;
|
|
186
|
+
while (i > 0 && list[i - 1].at > seg.at)
|
|
187
|
+
i--;
|
|
188
|
+
list.splice(i, 0, seg);
|
|
189
|
+
}
|
package/dist/channels.d.ts
CHANGED
|
@@ -26,6 +26,11 @@ export interface Numeric {
|
|
|
26
26
|
axes: number;
|
|
27
27
|
/** An angle's full turn: each value is taken the short way round before it folds. */
|
|
28
28
|
turn?: number;
|
|
29
|
+
/**
|
|
30
|
+
* An `'own'` channel whose fold is this plain arithmetic, its lerp alone its own: a lane folds
|
|
31
|
+
* it as the op and lerps it through the channel. A copy that predates it folds it as `'own'`.
|
|
32
|
+
*/
|
|
33
|
+
folds?: 'sum';
|
|
29
34
|
}
|
|
30
35
|
/**
|
|
31
36
|
* The stock arithmetic of a channel `sum`, `mul`, `max` or `vec` made, or `vouch` was given, keyed
|
|
@@ -55,49 +60,71 @@ export interface NumberOptions {
|
|
|
55
60
|
bounds?: readonly [min: number, max: number];
|
|
56
61
|
}
|
|
57
62
|
/**
|
|
58
|
-
*
|
|
63
|
+
* A number whose contributions add, resting at 0.
|
|
64
|
+
*
|
|
65
|
+
* A weight scales a contribution toward 0, so two voices each moving `x` by 10 at half weight move it
|
|
66
|
+
* 10 in all. For position axes, rotation, crawl, yaw, pitch, offset.
|
|
59
67
|
*
|
|
60
68
|
* @category channel
|
|
61
69
|
*/
|
|
62
70
|
export declare function sum(opts?: NumberOptions): Channel<number>;
|
|
63
71
|
/**
|
|
64
|
-
*
|
|
72
|
+
* A number whose contributions multiply, resting at 1.
|
|
73
|
+
*
|
|
74
|
+
* A weight fades a contribution toward 1 rather than 0, so 0.5 at half weight multiplies by 0.75, and
|
|
75
|
+
* at weight 0 it leaves the value alone. For gain, scale, opacity, hold.
|
|
65
76
|
*
|
|
66
77
|
* @category channel
|
|
67
78
|
*/
|
|
68
79
|
export declare function mul(opts?: NumberOptions): Channel<number>;
|
|
69
80
|
/**
|
|
70
|
-
*
|
|
81
|
+
* A number where the largest contribution wins, resting at 0.
|
|
82
|
+
*
|
|
83
|
+
* A weight scales a contribution toward 0 before the comparison, so a fading voice gives way once
|
|
84
|
+
* another's value passes its scaled one. For dark, aperture.
|
|
71
85
|
*
|
|
72
86
|
* @category channel
|
|
73
87
|
*/
|
|
74
88
|
export declare function max(opts?: NumberOptions): Channel<number>;
|
|
75
89
|
/**
|
|
76
|
-
*
|
|
90
|
+
* What `last` takes.
|
|
91
|
+
*
|
|
92
|
+
* @category channel
|
|
93
|
+
*/
|
|
94
|
+
export interface LastOptions<V> {
|
|
95
|
+
/** How to interpolate between two values. Default: step at the midpoint. */
|
|
96
|
+
lerp?: (a: V, b: V, u: number) => V;
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* A channel with no rest, where the last contribution to pass wins. A weight can only gate it. The
|
|
77
100
|
* default `lerp` steps at the midpoint, which is all a value with no arithmetic can promise; a
|
|
78
101
|
* consumer with a real interpolation passes its own, as `hex` does.
|
|
79
102
|
*
|
|
80
103
|
* @category channel
|
|
81
104
|
*/
|
|
82
|
-
export declare function last<V>(opts?:
|
|
83
|
-
lerp?: (a: V, b: V, u: number) => V;
|
|
84
|
-
}): Channel<V>;
|
|
105
|
+
export declare function last<V>(opts?: LastOptions<V>): Channel<V>;
|
|
85
106
|
/**
|
|
86
107
|
* `n` copies of `v` in an array the engine stores as doubles from the start, so a copy of it, and
|
|
87
108
|
* every fraction written into that copy, keeps one element kind: a pose's arrays then look alike to
|
|
88
109
|
* every fold, whichever path made them.
|
|
89
110
|
*/
|
|
90
111
|
export declare function doubles(n: number, v: number): number[];
|
|
91
|
-
type Tuple<N extends number, T extends number[] = []> = T['length'] extends N ? T : Tuple<N, [...T, number]>;
|
|
92
112
|
/**
|
|
93
|
-
*
|
|
94
|
-
*
|
|
113
|
+
* A tuple of `N` numbers, as `NumberTuple<3>` is `[number, number, number]`. `T` is the tuple built
|
|
114
|
+
* so far; leave it out.
|
|
115
|
+
*
|
|
116
|
+
* @category channel
|
|
117
|
+
*/
|
|
118
|
+
export type NumberTuple<N extends number, T extends number[] = []> = T['length'] extends N ? T : NumberTuple<N, [...T, number]>;
|
|
119
|
+
/**
|
|
120
|
+
* `N` numbers, as a tuple where `N` is a literal. `Vec<3>` is `[number, number, number]`, and a
|
|
121
|
+
* `Vec` is `number[]` where `N` is only known to be a number.
|
|
95
122
|
*
|
|
96
123
|
* @category channel
|
|
97
124
|
*/
|
|
98
|
-
export type Vec<N extends number> = number extends N ? number[] :
|
|
125
|
+
export type Vec<N extends number> = number extends N ? number[] : NumberTuple<N>;
|
|
99
126
|
/**
|
|
100
|
-
*
|
|
127
|
+
* One channel's arithmetic applied down an axis list. For a Vec3 position, or premultiplied light.
|
|
101
128
|
*
|
|
102
129
|
* @category channel
|
|
103
130
|
*/
|
|
@@ -108,4 +135,3 @@ export declare function vec<N extends number>(n: N, of: Channel<number>): Channe
|
|
|
108
135
|
* @category channel
|
|
109
136
|
*/
|
|
110
137
|
export declare function kit<O>(channels: Kit<O>): Kit<O>;
|
|
111
|
-
export {};
|
package/dist/channels.js
CHANGED
|
@@ -67,7 +67,10 @@ export function scalarOp(channel) {
|
|
|
67
67
|
/** Bounds change how a value folds at its edges, so they are part of the kind. */
|
|
68
68
|
const kindOf = (name, bounds) => bounds === undefined ? name : `${name}[${bounds[0]}, ${bounds[1]}]`;
|
|
69
69
|
/**
|
|
70
|
-
*
|
|
70
|
+
* A number whose contributions add, resting at 0.
|
|
71
|
+
*
|
|
72
|
+
* A weight scales a contribution toward 0, so two voices each moving `x` by 10 at half weight move it
|
|
73
|
+
* 10 in all. For position axes, rotation, crawl, yaw, pitch, offset.
|
|
71
74
|
*
|
|
72
75
|
* @category channel
|
|
73
76
|
*/
|
|
@@ -84,7 +87,10 @@ export function sum(opts) {
|
|
|
84
87
|
return channel;
|
|
85
88
|
}
|
|
86
89
|
/**
|
|
87
|
-
*
|
|
90
|
+
* A number whose contributions multiply, resting at 1.
|
|
91
|
+
*
|
|
92
|
+
* A weight fades a contribution toward 1 rather than 0, so 0.5 at half weight multiplies by 0.75, and
|
|
93
|
+
* at weight 0 it leaves the value alone. For gain, scale, opacity, hold.
|
|
88
94
|
*
|
|
89
95
|
* @category channel
|
|
90
96
|
*/
|
|
@@ -101,7 +107,10 @@ export function mul(opts) {
|
|
|
101
107
|
return channel;
|
|
102
108
|
}
|
|
103
109
|
/**
|
|
104
|
-
*
|
|
110
|
+
* A number where the largest contribution wins, resting at 0.
|
|
111
|
+
*
|
|
112
|
+
* A weight scales a contribution toward 0 before the comparison, so a fading voice gives way once
|
|
113
|
+
* another's value passes its scaled one. For dark, aperture.
|
|
105
114
|
*
|
|
106
115
|
* @category channel
|
|
107
116
|
*/
|
|
@@ -118,7 +127,7 @@ export function max(opts) {
|
|
|
118
127
|
return channel;
|
|
119
128
|
}
|
|
120
129
|
/**
|
|
121
|
-
* A channel with no rest
|
|
130
|
+
* A channel with no rest, where the last contribution to pass wins. A weight can only gate it. The
|
|
122
131
|
* default `lerp` steps at the midpoint, which is all a value with no arithmetic can promise; a
|
|
123
132
|
* consumer with a real interpolation passes its own, as `hex` does.
|
|
124
133
|
*
|
|
@@ -148,7 +157,7 @@ export function doubles(n, v) {
|
|
|
148
157
|
return a;
|
|
149
158
|
}
|
|
150
159
|
/**
|
|
151
|
-
*
|
|
160
|
+
* One channel's arithmetic applied down an axis list. For a Vec3 position, or premultiplied light.
|
|
152
161
|
*
|
|
153
162
|
* @category channel
|
|
154
163
|
*/
|
package/dist/closed.d.ts
CHANGED
|
@@ -5,6 +5,7 @@ export declare const CRITICAL = 1;
|
|
|
5
5
|
export declare const OVER = 2;
|
|
6
6
|
export declare const COAST = 3;
|
|
7
7
|
export declare const EASED = 4;
|
|
8
|
+
export declare const BOUND = 5;
|
|
8
9
|
/**
|
|
9
10
|
* A stretch at voice time `t`, released at `at` from `x0` moving at `v0` toward `to` (each read
|
|
10
11
|
* from its offset), into `xo` and `vo`: the one copy of the closed forms, which `law` (a patch's
|
package/dist/closed.js
CHANGED
|
@@ -6,6 +6,7 @@ export const CRITICAL = 1;
|
|
|
6
6
|
export const OVER = 2;
|
|
7
7
|
export const COAST = 3;
|
|
8
8
|
export const EASED = 4;
|
|
9
|
+
export const BOUND = 5;
|
|
9
10
|
const solved = { y: 0, dy: 0 };
|
|
10
11
|
/**
|
|
11
12
|
* The terms of a stretch that depend only on how long since its release, the same on every axis, so
|
|
@@ -114,6 +115,46 @@ function solveGlide(law, y0) {
|
|
|
114
115
|
solved.y = y0 * e;
|
|
115
116
|
solved.dy = (-y0 / tau) * e;
|
|
116
117
|
}
|
|
118
|
+
/**
|
|
119
|
+
* A glide with bounds, heading for `goal`: the rest point itself where the coast stops inside them,
|
|
120
|
+
* else the edge it springs back to. Which one is read off the stretch, so no stretch carries more
|
|
121
|
+
* than an unbounded one: the coast's own rest, worked out as the glide's `aim` does, is `goal`
|
|
122
|
+
* exactly when it stops inside; past it on the side `x0` is on too, the subject was let go past the
|
|
123
|
+
* edge and springs back at once; else it coasts until it crosses the edge and springs from there,
|
|
124
|
+
* at the speed it crossed at. The spring is critically damped, its ω₀ the law's `k2`.
|
|
125
|
+
*/
|
|
126
|
+
function solveBound(law, x0, goal, v0, t) {
|
|
127
|
+
const tau = law[2];
|
|
128
|
+
const y0 = x0 - goal;
|
|
129
|
+
const over = x0 + v0 * tau - goal;
|
|
130
|
+
if (over === 0) {
|
|
131
|
+
solveGlide(law, y0);
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
const w0 = law[3];
|
|
135
|
+
if (y0 * over > 0) {
|
|
136
|
+
const e = Math.exp(-w0 * t);
|
|
137
|
+
const b = v0 + w0 * y0;
|
|
138
|
+
const y = e * (y0 + b * t);
|
|
139
|
+
solved.y = y;
|
|
140
|
+
solved.dy = e * b - w0 * y;
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
// e^(−t/τ) where the coast meets the edge: the fraction of its speed it still has there.
|
|
144
|
+
const left = 1 + y0 / (v0 * tau);
|
|
145
|
+
const cross = left > 0 ? -tau * Math.log(left) : Number.POSITIVE_INFINITY;
|
|
146
|
+
if (t < cross) {
|
|
147
|
+
const e = timed.e;
|
|
148
|
+
solved.y = y0 + v0 * tau * (1 - e);
|
|
149
|
+
solved.dy = v0 * e;
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
const s = t - cross;
|
|
153
|
+
const vc = v0 * left;
|
|
154
|
+
const e = Math.exp(-w0 * s);
|
|
155
|
+
solved.y = e * vc * s;
|
|
156
|
+
solved.dy = e * vc * (1 - w0 * s);
|
|
157
|
+
}
|
|
117
158
|
/**
|
|
118
159
|
* A stretch at voice time `t`, released at `at` from `x0` moving at `v0` toward `to` (each read
|
|
119
160
|
* from its offset), into `xo` and `vo`: the one copy of the closed forms, which `law` (a patch's
|
|
@@ -130,7 +171,10 @@ export function closed(law, ease, n, at, ms, x0, x, v0, v, to, g, t, xo, vo) {
|
|
|
130
171
|
prepare(law, dt);
|
|
131
172
|
for (let i = 0; i < n; i++) {
|
|
132
173
|
const goal = to[g + i];
|
|
133
|
-
|
|
174
|
+
if (law[0] === BOUND)
|
|
175
|
+
solveBound(law, x0[x + i], goal, v0[v + i], dt);
|
|
176
|
+
else
|
|
177
|
+
solve(law, x0[x + i] - goal, v0[v + i], dt);
|
|
134
178
|
xo[i] = goal + solved.y;
|
|
135
179
|
vo[i] = solved.dy;
|
|
136
180
|
if (Math.abs(solved.y) > settle || Math.abs(solved.dy) > settle)
|
package/dist/color.d.ts
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
import type { Channel } from './types.js';
|
|
2
2
|
/**
|
|
3
|
-
* OKLCH, hue the short way round
|
|
4
|
-
* instead of dipping through a muddy middle. A mix that leaves sRGB's
|
|
3
|
+
* Crossfades two hex colors in OKLCH, hue the short way round. A crossfade between hues then keeps
|
|
4
|
+
* its lightness and saturation instead of dipping through a muddy middle. A mix that leaves sRGB's
|
|
5
|
+
* gamut is clipped per channel.
|
|
5
6
|
*
|
|
6
7
|
* @category channel
|
|
7
8
|
*/
|
|
8
9
|
export declare function mixHex(a: number, b: number, u: number): number;
|
|
9
10
|
/**
|
|
10
|
-
* How a `color` channel interpolates two colors
|
|
11
|
+
* How a `color` channel interpolates two colors. Straight across OKLab, or in OKLCH with hue the
|
|
11
12
|
* short way round, as `mixHex` does.
|
|
12
13
|
*
|
|
13
14
|
* @category channel
|
|
@@ -21,29 +22,30 @@ export interface ColorOptions {
|
|
|
21
22
|
* `toHex` divides it out and lays it over what is under it. `color(last())` replaces, the last
|
|
22
23
|
* voice to pass winning, at the coverage it was authored with. `lerp` picks how two colors
|
|
23
24
|
* interpolate: OKLab, the default, crosses through gray between opposite hues, and `'oklch'`
|
|
24
|
-
* goes round the hue instead
|
|
25
|
+
* goes round the hue instead.
|
|
25
26
|
*
|
|
26
27
|
* @category channel
|
|
27
28
|
*/
|
|
28
29
|
export declare function color(opts?: ColorOptions): Channel<number[]>;
|
|
29
30
|
export declare function color(of: Channel<number> | Channel<unknown>, opts?: ColorOptions): Channel<number[]>;
|
|
30
31
|
/**
|
|
31
|
-
* 0xrrggbb
|
|
32
|
-
* when authoring them, and call it outside the hot path in an `fn` patch.
|
|
32
|
+
* Converts 0xrrggbb to a `color` value. The value is OKLab `[L, a, b]` at full coverage. Convert
|
|
33
|
+
* keyframe stops once when authoring them, and call it outside the hot path in an `fn` patch.
|
|
33
34
|
*
|
|
34
35
|
* @category channel
|
|
35
36
|
*/
|
|
36
37
|
export declare function oklab(rgb: number): number[];
|
|
37
38
|
/**
|
|
38
|
-
* A `color` pose as 0xrrggbb
|
|
39
|
-
* and laid over `under` in OKLab, then clipped to sRGB per channel. No
|
|
39
|
+
* A `color` pose as 0xrrggbb, laid over `under`. Its color is divided out of its coverage, the
|
|
40
|
+
* coverage capped at 1 and laid over `under` in OKLab, then clipped to sRGB per channel. No
|
|
41
|
+
* coverage gives `under`.
|
|
40
42
|
*
|
|
41
43
|
* @category channel
|
|
42
44
|
*/
|
|
43
45
|
export declare function toHex(pose: ArrayLike<number>, under?: number): number;
|
|
44
46
|
/**
|
|
45
|
-
* A `color` pose as a CSS `oklab()` color
|
|
46
|
-
* host that lets the browser lay it over what is under it.
|
|
47
|
+
* A `color` pose as a CSS `oklab()` color, its coverage as the alpha. The coverage is capped at 1.
|
|
48
|
+
* For a host that lets the browser lay it over what is under it.
|
|
47
49
|
*
|
|
48
50
|
* @category channel
|
|
49
51
|
*/
|
package/dist/color.js
CHANGED
|
@@ -82,8 +82,9 @@ function fromOklch(L, C, h) {
|
|
|
82
82
|
return fromLab(L, C * Math.cos(h), C * Math.sin(h));
|
|
83
83
|
}
|
|
84
84
|
/**
|
|
85
|
-
* OKLCH, hue the short way round
|
|
86
|
-
* instead of dipping through a muddy middle. A mix that leaves sRGB's
|
|
85
|
+
* Crossfades two hex colors in OKLCH, hue the short way round. A crossfade between hues then keeps
|
|
86
|
+
* its lightness and saturation instead of dipping through a muddy middle. A mix that leaves sRGB's
|
|
87
|
+
* gamut is clipped per channel.
|
|
87
88
|
*
|
|
88
89
|
* @category channel
|
|
89
90
|
*/
|
|
@@ -98,14 +99,19 @@ export function mixHex(a, b, u) {
|
|
|
98
99
|
const ah = slotH[sa];
|
|
99
100
|
const sb = oklch(b);
|
|
100
101
|
const bc = slotC[sb];
|
|
101
|
-
const
|
|
102
|
-
|
|
102
|
+
const h = hueAt(ah, ac, slotH[sb], bc, u);
|
|
103
|
+
return fromOklch(mix(al, slotL[sb], u), mix(ac, bc, u), h);
|
|
104
|
+
}
|
|
105
|
+
/** The hue `u` of the way from `ah` to `bh` the short way round, a gray end taking the other's. */
|
|
106
|
+
function hueAt(ah, ac, bh, bc, u) {
|
|
107
|
+
const from = ac < GRAY ? bh : ah;
|
|
108
|
+
const to = bc < GRAY ? ah : bh;
|
|
103
109
|
let turn = to - from;
|
|
104
110
|
if (turn > Math.PI)
|
|
105
111
|
turn -= 2 * Math.PI;
|
|
106
112
|
else if (turn < -Math.PI)
|
|
107
113
|
turn += 2 * Math.PI;
|
|
108
|
-
return
|
|
114
|
+
return from + turn * u;
|
|
109
115
|
}
|
|
110
116
|
/** Two OKLab colors with coverage, interpolated in OKLCH: color by `u`, coverage by `u` alongside. */
|
|
111
117
|
function lerpLch(x, y, u) {
|
|
@@ -126,17 +132,8 @@ function lerpLch(x, y, u) {
|
|
|
126
132
|
const toB = yw > 0 ? yB : xB;
|
|
127
133
|
const xc = Math.sqrt(xA * xA + xB * xB);
|
|
128
134
|
const yc = Math.sqrt(toA * toA + toB * toB);
|
|
129
|
-
const xh = Math.atan2(xB, xA);
|
|
130
|
-
const yh = Math.atan2(toB, toA);
|
|
131
|
-
const from = xc < GRAY ? yh : xh;
|
|
132
|
-
const to = yc < GRAY ? xh : yh;
|
|
133
|
-
let turn = to - from;
|
|
134
|
-
if (turn > Math.PI)
|
|
135
|
-
turn -= 2 * Math.PI;
|
|
136
|
-
else if (turn < -Math.PI)
|
|
137
|
-
turn += 2 * Math.PI;
|
|
138
135
|
const C = mix(xc, yc, u);
|
|
139
|
-
const h =
|
|
136
|
+
const h = hueAt(Math.atan2(xB, xA), xc, Math.atan2(toB, toA), yc, u);
|
|
140
137
|
return [mix(xL, toL, u) * w, C * Math.cos(h) * w, C * Math.sin(h) * w, w];
|
|
141
138
|
}
|
|
142
139
|
export function color(ofOrOpts, maybe) {
|
|
@@ -149,8 +146,11 @@ export function color(ofOrOpts, maybe) {
|
|
|
149
146
|
(stock?.op === 'sum' && stock.turn === undefined && of.bounds === undefined)) {
|
|
150
147
|
const channel = vec(4, sum());
|
|
151
148
|
channel.kind = `color(sum${space})`;
|
|
152
|
-
|
|
153
|
-
|
|
149
|
+
if (!round)
|
|
150
|
+
return channel;
|
|
151
|
+
const turning = { ...channel, lerp: lerpLch };
|
|
152
|
+
vouch(turning, { op: 'own', axes: 4, folds: 'sum' });
|
|
153
|
+
return turning;
|
|
154
154
|
}
|
|
155
155
|
if (of.kind === 'last') {
|
|
156
156
|
const channel = {
|
|
@@ -159,15 +159,14 @@ export function color(ofOrOpts, maybe) {
|
|
|
159
159
|
lerp: round ? lerpLch : vec(4, sum()).lerp,
|
|
160
160
|
};
|
|
161
161
|
widen(channel, 4);
|
|
162
|
-
|
|
163
|
-
vouch(channel, { op: 'last', axes: 4 });
|
|
162
|
+
vouch(channel, { op: 'last', axes: 4 });
|
|
164
163
|
return channel;
|
|
165
164
|
}
|
|
166
165
|
throw new Error(`color takes sum() to average or last() to replace, not ${of.kind ?? 'a channel of its own'}`);
|
|
167
166
|
}
|
|
168
167
|
/**
|
|
169
|
-
* 0xrrggbb
|
|
170
|
-
* when authoring them, and call it outside the hot path in an `fn` patch.
|
|
168
|
+
* Converts 0xrrggbb to a `color` value. The value is OKLab `[L, a, b]` at full coverage. Convert
|
|
169
|
+
* keyframe stops once when authoring them, and call it outside the hot path in an `fn` patch.
|
|
171
170
|
*
|
|
172
171
|
* @category channel
|
|
173
172
|
*/
|
|
@@ -176,8 +175,9 @@ export function oklab(rgb) {
|
|
|
176
175
|
return [LAB[0], LAB[1], LAB[2], 1];
|
|
177
176
|
}
|
|
178
177
|
/**
|
|
179
|
-
* A `color` pose as 0xrrggbb
|
|
180
|
-
* and laid over `under` in OKLab, then clipped to sRGB per channel. No
|
|
178
|
+
* A `color` pose as 0xrrggbb, laid over `under`. Its color is divided out of its coverage, the
|
|
179
|
+
* coverage capped at 1 and laid over `under` in OKLab, then clipped to sRGB per channel. No
|
|
180
|
+
* coverage gives `under`.
|
|
181
181
|
*
|
|
182
182
|
* @category channel
|
|
183
183
|
*/
|
|
@@ -195,8 +195,8 @@ export function toHex(pose, under = 0x000000) {
|
|
|
195
195
|
return fromLab(mix(LAB[0], L, w), mix(LAB[1], A, w), mix(LAB[2], B, w));
|
|
196
196
|
}
|
|
197
197
|
/**
|
|
198
|
-
* A `color` pose as a CSS `oklab()` color
|
|
199
|
-
* host that lets the browser lay it over what is under it.
|
|
198
|
+
* A `color` pose as a CSS `oklab()` color, its coverage as the alpha. The coverage is capped at 1.
|
|
199
|
+
* For a host that lets the browser lay it over what is under it.
|
|
200
200
|
*
|
|
201
201
|
* @category channel
|
|
202
202
|
*/
|
package/dist/columns.js
CHANGED
|
@@ -22,6 +22,9 @@ export function pullRun(lanes, slots, list, was, since, from, columns, now, vers
|
|
|
22
22
|
const frame = lanes.frameProbes;
|
|
23
23
|
const subjects = lanes.subjects;
|
|
24
24
|
const unlinked = lanes.unlinked;
|
|
25
|
+
const holds = lanes.holds;
|
|
26
|
+
// Nothing in `pullRun` makes a subject owe a voice, so one with nobody owing need not ask.
|
|
27
|
+
const owing = lanes.owed.any();
|
|
25
28
|
const current = since === Number.POSITIVE_INFINITY;
|
|
26
29
|
if (columns !== lanes.queuedFor) {
|
|
27
30
|
flush(lanes);
|
|
@@ -47,14 +50,15 @@ export function pullRun(lanes, slots, list, was, since, from, columns, now, vers
|
|
|
47
50
|
per[o + 0 /* Per.FILLED */] >= busy ||
|
|
48
51
|
per[o + 2 /* Per.IDLE */] !== 0 ||
|
|
49
52
|
per[o + 1 /* Per.SEEN */] < wide ||
|
|
50
|
-
lanes.owes(slot))
|
|
53
|
+
(owing && lanes.owes(slot)))
|
|
51
54
|
break;
|
|
52
55
|
if (!(per[o + 3 /* Per.LANE_PROBE */] > frame ||
|
|
53
56
|
per[o + 4 /* Per.GENERAL_PROBE */] > frame))
|
|
54
57
|
lanes.distinct++;
|
|
55
58
|
per[o + 3 /* Per.LANE_PROBE */] = ++lanes.probes;
|
|
56
59
|
per[o + 5 /* Per.LANE_FILL */] = fills;
|
|
57
|
-
|
|
60
|
+
if (holds)
|
|
61
|
+
subjects[slot] = subject;
|
|
58
62
|
if (slot !== first + (n - start)) {
|
|
59
63
|
if (first >= 0) {
|
|
60
64
|
qSlots[k] = first;
|
|
@@ -73,7 +77,7 @@ export function pullRun(lanes, slots, list, was, since, from, columns, now, vers
|
|
|
73
77
|
k++;
|
|
74
78
|
}
|
|
75
79
|
lanes.queued = k;
|
|
76
|
-
if (n > from)
|
|
80
|
+
if (n > from && holds)
|
|
77
81
|
lanes.holding = true;
|
|
78
82
|
return n;
|
|
79
83
|
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { Mixer } from './mixer.js';
|
|
2
|
+
import type { Subject } from './subject.js';
|
|
3
|
+
import { type Voice } from './voice.js';
|
|
4
|
+
/**
|
|
5
|
+
* One live voice's delta for one subject this frame, through its record for the subject, or null
|
|
6
|
+
* when it does not reach. Its weight is left in `w`.
|
|
7
|
+
*/
|
|
8
|
+
export declare function contribution<I, O>(this: Mixer<I, O>, voice: Voice<I, O>, subject: I, now: number, held: Subject<unknown>): Record<string, unknown> | null;
|
|
9
|
+
/** Reads a keys voice's stops at `held.phase` into the subject's delta, through the voice's scratch. */
|
|
10
|
+
export declare function keyed<I, O>(this: Mixer<I, O>, voice: Voice<I, O>, subject: I, held: Subject<unknown>): Record<string, unknown>;
|
|
11
|
+
/**
|
|
12
|
+
* Runs `step` once per whole interval between where this subject last stopped and now, on a grid
|
|
13
|
+
* counted from when its delay ran out, and leaves the remainder for the next sample. Counting
|
|
14
|
+
* rather than adding keeps the grid where it is however many samples it is reached through.
|
|
15
|
+
*/
|
|
16
|
+
export declare function tick<I, O>(this: Mixer<I, O>, voice: Voice<I, O>, subject: I, held: Subject<unknown>, tick: number, to: number): void;
|
|
17
|
+
/**
|
|
18
|
+
* Stop 0 for a retargeting voice: the subject's pose in the frame before this voice contributes.
|
|
19
|
+
* With no such frame on record — a voice cued before the subject was ever probed, or a host that
|
|
20
|
+
* samples into its own object — it is this frame's pose with every other voice folded in.
|
|
21
|
+
*/
|
|
22
|
+
export declare function baseFor<I, O>(this: Mixer<I, O>, voice: Voice<I, O>, subject: I): Record<string, unknown>;
|
|
23
|
+
/**
|
|
24
|
+
* How fast each numeric channel this voice writes was moving, from the subject's last two probed
|
|
25
|
+
* poses, in units per ms. Undefined with fewer than two on record, which starts the voice at rest.
|
|
26
|
+
*/
|
|
27
|
+
export declare function slopeFor<I, O>(this: Mixer<I, O>, voice: Voice<I, O>, subject: I): Record<string, unknown> | undefined;
|
|
28
|
+
export declare function isRest<I, O>(this: Mixer<I, O>, delta: Record<string, unknown>): boolean;
|
|
29
|
+
/** `contribution`, or for a dry fold a subject already probed this frame, without advancing it. */
|
|
30
|
+
export declare function read<I, O>(this: Mixer<I, O>, voice: Voice<I, O>, subject: I, now: number, dry: boolean, held: Subject<unknown>): Record<string, unknown> | null;
|