@msb235/blits 0.15.1 → 0.16.1
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 +184 -0
- package/README.md +1 -1
- package/dist/ahead.js +11 -13
- package/dist/chain.js +2 -3
- package/dist/clock.d.ts +11 -1
- package/dist/clock.js +28 -9
- package/dist/color.d.ts +36 -14
- package/dist/color.js +186 -58
- package/dist/columns.d.ts +1 -11
- package/dist/columns.js +1 -6
- package/dist/contribution.js +11 -4
- package/dist/crowd.d.ts +0 -6
- package/dist/crowd.js +0 -11
- package/dist/cue.js +19 -7
- package/dist/due.d.ts +18 -0
- package/dist/due.js +101 -2
- package/dist/everyone.d.ts +17 -6
- package/dist/everyone.js +17 -8
- package/dist/fade.d.ts +22 -1
- package/dist/fade.js +81 -12
- package/dist/fill.js +2 -1
- package/dist/fit.js +8 -0
- package/dist/fold.js +4 -7
- package/dist/gone.js +1 -0
- package/dist/handle.d.ts +1 -1
- package/dist/handle.js +3 -1
- package/dist/held.d.ts +2 -1
- package/dist/held.js +17 -10
- package/dist/hosts.d.ts +7 -0
- package/dist/hosts.js +23 -6
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/lane.d.ts +2 -0
- package/dist/lane.js +2 -0
- package/dist/lanes.d.ts +26 -1
- package/dist/lanes.js +9 -1
- package/dist/lch.d.ts +23 -0
- package/dist/lch.js +126 -0
- package/dist/meet.js +9 -0
- package/dist/mixer.d.ts +6 -1
- package/dist/mixer.js +5 -0
- package/dist/motion.js +6 -6
- package/dist/motions.d.ts +4 -1
- package/dist/motions.js +16 -2
- package/dist/move.js +311 -79
- package/dist/origin.d.ts +2 -0
- package/dist/origin.js +4 -0
- package/dist/owner.d.ts +7 -2
- package/dist/owner.js +17 -5
- package/dist/paging.d.ts +5 -1
- package/dist/paging.js +13 -3
- package/dist/patch.js +24 -4
- package/dist/place.js +67 -31
- package/dist/project.d.ts +5 -2
- package/dist/project.js +9 -11
- package/dist/pull.d.ts +11 -0
- package/dist/pull.js +10 -6
- package/dist/qualify.js +7 -2
- package/dist/rests.js +9 -1
- package/dist/revive.d.ts +11 -1
- package/dist/revive.js +7 -0
- package/dist/roles.js +6 -5
- package/dist/roster.d.ts +6 -0
- package/dist/roster.js +8 -1
- package/dist/rows.js +4 -1
- package/dist/sample.js +2 -1
- package/dist/seat.d.ts +5 -1
- package/dist/seat.js +5 -1
- package/dist/seek.js +6 -16
- package/dist/shared.d.ts +1 -1
- package/dist/shared.js +1 -1
- package/dist/spans.d.ts +13 -4
- package/dist/spans.js +125 -32
- package/dist/touch.js +1 -2
- package/dist/trace.js +7 -2
- package/dist/turns.js +2 -1
- package/dist/types/transport.d.ts +2 -1
- package/dist/types/voice.d.ts +3 -2
- package/dist/voice.d.ts +59 -2
- package/dist/voice.js +76 -2
- package/dist/weigh.js +1 -1
- package/docs/schema.html +38 -12
- package/package.json +3 -1
package/dist/color.js
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
import { lerpNumber, numericOf, sum, vec, vouch, widen } from './channels.js';
|
|
1
|
+
import { doubles, lerpNumber, numericOf, sum, vec, vouch, widen } from './channels.js';
|
|
2
|
+
import { HUES, lchPair, shortWay } from './lch.js';
|
|
3
|
+
import { warnOnce } from './warn.js';
|
|
2
4
|
const mix = lerpNumber;
|
|
3
5
|
const LINEAR = Array.from({ length: 256 }, (_, c) => {
|
|
4
6
|
const v = c / 255;
|
|
@@ -25,8 +27,6 @@ const toByte = (v) => {
|
|
|
25
27
|
const c = FIRST[(v * BINS) | 0];
|
|
26
28
|
return c < 255 && v >= ROUNDS_UP[c + 1] ? c + 1 : c;
|
|
27
29
|
};
|
|
28
|
-
/** Below this chroma a color is gray and its hue is noise, so the lerp takes the other end's. */
|
|
29
|
-
const GRAY = 1e-4;
|
|
30
30
|
/**
|
|
31
31
|
* Colors already turned into OKLCH, direct-mapped on a byte of their hash: a mix reads the same
|
|
32
32
|
* few keyframe colors every frame, and the table stays 256 colors however many it meets.
|
|
@@ -104,46 +104,107 @@ export function mixHex(a, b, u) {
|
|
|
104
104
|
}
|
|
105
105
|
/** The hue `u` of the way from `ah` to `bh` the short way round, a gray end taking the other's. */
|
|
106
106
|
function hueAt(ah, ac, bh, bc, u) {
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
let turn = to - from;
|
|
110
|
-
if (turn > Math.PI)
|
|
111
|
-
turn -= 2 * Math.PI;
|
|
112
|
-
else if (turn < -Math.PI)
|
|
113
|
-
turn += 2 * Math.PI;
|
|
114
|
-
return from + turn * u;
|
|
107
|
+
shortWay(ah, ac, bh, bc);
|
|
108
|
+
return HUES[0] + HUES[1] * u;
|
|
115
109
|
}
|
|
110
|
+
const LERP = {};
|
|
111
|
+
const PAIR = new Float64Array(8);
|
|
116
112
|
/** Two OKLab colors with coverage, interpolated in OKLCH: color by `u`, coverage by `u` alongside. */
|
|
117
113
|
function lerpLch(x, y, u) {
|
|
118
|
-
const
|
|
119
|
-
|
|
120
|
-
const w = mix(
|
|
121
|
-
|
|
122
|
-
const
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
const
|
|
133
|
-
const
|
|
134
|
-
const
|
|
135
|
-
const
|
|
136
|
-
const
|
|
137
|
-
|
|
114
|
+
const p = PAIR;
|
|
115
|
+
lchPair(x, 0, y, 0, p);
|
|
116
|
+
const w = mix(p[3], p[7], u);
|
|
117
|
+
const C = mix(p[1], p[5], u);
|
|
118
|
+
const h = p[2] + HUES[1] * u;
|
|
119
|
+
return [mix(p[0], p[4], u) * w, C * Math.cos(h) * w, C * Math.sin(h) * w, w];
|
|
120
|
+
}
|
|
121
|
+
/** Below this, an ink pose's summed `(a, b)` has canceled out and shows no hue. */
|
|
122
|
+
const CANCEL = 1e-9;
|
|
123
|
+
/**
|
|
124
|
+
* A color value, `oklab()`'s four numbers or ink's five, into `out` at `o` as `[L, C, h, w]`, its
|
|
125
|
+
* coverage divided out. Five numbers show their own chroma, the fourth, at the hue of their `(a, b)`.
|
|
126
|
+
*/
|
|
127
|
+
function lchOf(v, out, o) {
|
|
128
|
+
const five = v.length > 4;
|
|
129
|
+
const w = (five ? v[4] : v[3]) ?? 0;
|
|
130
|
+
const s = w > 0 ? 1 / w : 0;
|
|
131
|
+
const a = (v[1] ?? 0) * s;
|
|
132
|
+
const b = (v[2] ?? 0) * s;
|
|
133
|
+
const r = Math.sqrt(a * a + b * b);
|
|
134
|
+
out[o] = (v[0] ?? 0) * s;
|
|
135
|
+
out[o + 1] = !five ? r : r < CANCEL ? 0 : (v[3] ?? 0) * s;
|
|
136
|
+
out[o + 2] = Math.atan2(b, a);
|
|
137
|
+
out[o + 3] = w;
|
|
138
|
+
}
|
|
139
|
+
/** A color value as ink's five numbers, `[w·L, w·a, w·b, w·C, w]` times `k`, into `out`. */
|
|
140
|
+
function inked(v, k, out) {
|
|
141
|
+
const a = v[1] ?? 0;
|
|
142
|
+
const b = v[2] ?? 0;
|
|
143
|
+
const five = v.length > 4;
|
|
144
|
+
out[0] = (v[0] ?? 0) * k;
|
|
145
|
+
out[1] = a * k;
|
|
146
|
+
out[2] = b * k;
|
|
147
|
+
out[3] = (five ? (v[3] ?? 0) : Math.sqrt(a * a + b * b)) * k;
|
|
148
|
+
out[4] = ((five ? v[4] : v[3]) ?? 0) * k;
|
|
149
|
+
return out;
|
|
150
|
+
}
|
|
151
|
+
/** Two colors, four numbers or five, interpolated round the hue into ink's five. */
|
|
152
|
+
function lerpInk(x, y, u) {
|
|
153
|
+
if (u === 0)
|
|
154
|
+
return inked(x, 1, doubles(5, 0));
|
|
155
|
+
if (u === 1)
|
|
156
|
+
return inked(y, 1, doubles(5, 0));
|
|
157
|
+
const p = PAIR;
|
|
158
|
+
lchOf(x, p, 0);
|
|
159
|
+
lchOf(y, p, 4);
|
|
160
|
+
// A color with no coverage has none of its own to keep, so it takes the other end's.
|
|
161
|
+
const from = p[3] > 0 ? 0 : 4;
|
|
162
|
+
const to = p[7] > 0 ? 4 : 0;
|
|
163
|
+
const xc = p[from + 1];
|
|
164
|
+
const yc = p[to + 1];
|
|
165
|
+
shortWay(p[from + 2], xc, p[to + 2], yc);
|
|
166
|
+
const w = mix(p[3], p[7], u);
|
|
167
|
+
const Cw = mix(xc, yc, u) * w;
|
|
168
|
+
const h = HUES[0] + HUES[1] * u;
|
|
169
|
+
return [
|
|
170
|
+
mix(p[from], p[to], u) * w,
|
|
171
|
+
Math.cos(h) * Cw,
|
|
172
|
+
Math.sin(h) * Cw,
|
|
173
|
+
Cw,
|
|
174
|
+
w,
|
|
175
|
+
];
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* Whether `channel` travels round the hue, as `ink` does, so a motion patch cued on it moves round
|
|
179
|
+
* the hue too.
|
|
180
|
+
*/
|
|
181
|
+
export function roundsHue(channel) {
|
|
182
|
+
const lerp = channel.lerp;
|
|
183
|
+
return lerp === lerpLch || lerp === lerpInk;
|
|
184
|
+
}
|
|
185
|
+
/** Whether `of` asks a color channel to average: absent, or a stock `sum` with no turn or bounds. */
|
|
186
|
+
function averages(of) {
|
|
187
|
+
if (of === undefined)
|
|
188
|
+
return true;
|
|
189
|
+
const stock = numericOf(of);
|
|
190
|
+
return stock?.op === 'sum' && stock.turn === undefined && of.bounds === undefined;
|
|
191
|
+
}
|
|
192
|
+
const refuse = (name, of) => new Error(`${name} takes sum() to average or last() to replace, not ${of.kind ?? 'a channel of its own'}`);
|
|
193
|
+
/** A `last` color channel, which replaces and travels by `lerp`. */
|
|
194
|
+
function replacing(kind, lerp) {
|
|
195
|
+
const channel = { kind, merge: (_a, b) => b, lerp };
|
|
196
|
+
widen(channel, 4);
|
|
197
|
+
vouch(channel, { op: 'last', axes: 4 });
|
|
198
|
+
return channel;
|
|
138
199
|
}
|
|
139
200
|
export function color(ofOrOpts, maybe) {
|
|
140
201
|
const of = ofOrOpts !== undefined && 'merge' in ofOrOpts ? ofOrOpts : undefined;
|
|
141
202
|
const opts = of === undefined ? ofOrOpts : maybe;
|
|
203
|
+
if (opts?.lerp !== undefined)
|
|
204
|
+
warnOnce(LERP, "color({ lerp }) is deprecated: 'oklch' is ink(), which also blends overlapping voices round the hue, and 'oklab' is color()");
|
|
142
205
|
const round = opts?.lerp === 'oklch';
|
|
143
206
|
const space = round ? ', oklch' : '';
|
|
144
|
-
|
|
145
|
-
if (of === undefined ||
|
|
146
|
-
(stock?.op === 'sum' && stock.turn === undefined && of.bounds === undefined)) {
|
|
207
|
+
if (averages(of)) {
|
|
147
208
|
const channel = vec(4, sum());
|
|
148
209
|
channel.kind = `color(sum${space})`;
|
|
149
210
|
if (!round)
|
|
@@ -152,20 +213,59 @@ export function color(ofOrOpts, maybe) {
|
|
|
152
213
|
vouch(turning, { op: 'own', axes: 4, folds: 'sum' });
|
|
153
214
|
return turning;
|
|
154
215
|
}
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
216
|
+
const own = of;
|
|
217
|
+
if (own.kind === 'last')
|
|
218
|
+
return replacing(`color(last${space})`, round ? lerpLch : vec(4, sum()).lerp);
|
|
219
|
+
throw refuse('color', own);
|
|
220
|
+
}
|
|
221
|
+
/** Where `ink`'s merge and fold widen a contribution, which neither keeps. */
|
|
222
|
+
const WIDE = doubles(5, 0);
|
|
223
|
+
/**
|
|
224
|
+
* Color standing for a meaning, a status, a level or a team, rather than for a physical
|
|
225
|
+
* phenomenon: choose it over `color` when the colors are symbols. People picture the midpoint
|
|
226
|
+
* between red and blue as purple, reached by turning the hue, not as the grayed mix light makes,
|
|
227
|
+
* and `ink` gives them that. It travels round the hue the short way, in a motion, between keys
|
|
228
|
+
* and in a role's handoff, and `ink()` blends overlapping voices round the hue too: each voice
|
|
229
|
+
* sums as `[w·L, w·a, w·b, w·C, w]`, C its chroma, so a pose shows lightness `ΣwL/Σw`, the hue of
|
|
230
|
+
* `Σw·(a, b)`, which a gray voice pulls on not at all, and chroma `ΣwC/Σw`, so two vivid colors
|
|
231
|
+
* stay vivid. Near-opposite hues overlapping swing round the wheel as their weights cross, with
|
|
232
|
+
* no tiebreak, and exactly opposite ones cancel to gray. A patch writes it the `oklab()` values a
|
|
233
|
+
* `color` takes, and `toHex` and `css` read either. `ink(last())` replaces, traveling round the
|
|
234
|
+
* hue, and holds the four numbers it was written, since it never sums.
|
|
235
|
+
*
|
|
236
|
+
* @category channel
|
|
237
|
+
*/
|
|
238
|
+
export function ink(of) {
|
|
239
|
+
if (!averages(of)) {
|
|
240
|
+
const own = of;
|
|
241
|
+
if (own.kind === 'last')
|
|
242
|
+
return replacing('ink(last)', lerpLch);
|
|
243
|
+
throw refuse('ink', own);
|
|
164
244
|
}
|
|
165
|
-
|
|
245
|
+
const channel = {
|
|
246
|
+
kind: 'ink(sum)',
|
|
247
|
+
rest: doubles(5, 0),
|
|
248
|
+
merge: (x, y) => {
|
|
249
|
+
const out = inked(x, 1, doubles(5, 0));
|
|
250
|
+
const add = inked(y, 1, WIDE);
|
|
251
|
+
for (let i = 0; i < 5; i++)
|
|
252
|
+
out[i] = out[i] + add[i];
|
|
253
|
+
return out;
|
|
254
|
+
},
|
|
255
|
+
scale: (v, w) => inked(v, w, doubles(5, 0)),
|
|
256
|
+
fold: (into, v, w) => {
|
|
257
|
+
const add = inked(v, w, WIDE);
|
|
258
|
+
for (let i = 0; i < 5; i++)
|
|
259
|
+
into[i] = (into[i] ?? 0) + add[i];
|
|
260
|
+
return into;
|
|
261
|
+
},
|
|
262
|
+
lerp: lerpInk,
|
|
263
|
+
};
|
|
264
|
+
vouch(channel, { op: 'own', axes: 5 });
|
|
265
|
+
return channel;
|
|
166
266
|
}
|
|
167
267
|
/**
|
|
168
|
-
* Converts 0xrrggbb to a `color` value. The value is OKLab `[L, a, b]` at full coverage. Convert
|
|
268
|
+
* Converts 0xrrggbb to a `color` or `ink` value. The value is OKLab `[L, a, b]` at full coverage. Convert
|
|
169
269
|
* keyframe stops once when authoring them, and call it outside the hot path in an `fn` patch.
|
|
170
270
|
*
|
|
171
271
|
* @category channel
|
|
@@ -174,36 +274,64 @@ export function oklab(rgb) {
|
|
|
174
274
|
lab(rgb);
|
|
175
275
|
return [LAB[0], LAB[1], LAB[2], 1];
|
|
176
276
|
}
|
|
277
|
+
/** The color the last `shows` call read, `[L, a, b]` with its coverage divided out. */
|
|
278
|
+
const SHOWN = [0, 0, 0];
|
|
279
|
+
/**
|
|
280
|
+
* A `color` pose's or an `ink` pose's color into `SHOWN`, returning its coverage. Ink's shows the
|
|
281
|
+
* hue of its `(a, b)` at the chroma its fourth number holds, and gray where `(a, b)` cancels.
|
|
282
|
+
*/
|
|
283
|
+
function shows(pose) {
|
|
284
|
+
if (pose.length <= 4) {
|
|
285
|
+
const w = pose[3] ?? 0;
|
|
286
|
+
if (!(w > 0))
|
|
287
|
+
return w;
|
|
288
|
+
const s = 1 / w;
|
|
289
|
+
SHOWN[0] = (pose[0] ?? 0) * s;
|
|
290
|
+
SHOWN[1] = (pose[1] ?? 0) * s;
|
|
291
|
+
SHOWN[2] = (pose[2] ?? 0) * s;
|
|
292
|
+
return w;
|
|
293
|
+
}
|
|
294
|
+
const w = pose[4] ?? 0;
|
|
295
|
+
if (!(w > 0))
|
|
296
|
+
return w;
|
|
297
|
+
const s = 1 / w;
|
|
298
|
+
const a = (pose[1] ?? 0) * s;
|
|
299
|
+
const b = (pose[2] ?? 0) * s;
|
|
300
|
+
const r = Math.sqrt(a * a + b * b);
|
|
301
|
+
const k = r < CANCEL ? 0 : ((pose[3] ?? 0) * s) / r;
|
|
302
|
+
SHOWN[0] = (pose[0] ?? 0) * s;
|
|
303
|
+
SHOWN[1] = a * k;
|
|
304
|
+
SHOWN[2] = b * k;
|
|
305
|
+
return w;
|
|
306
|
+
}
|
|
177
307
|
/**
|
|
178
|
-
* A `color` pose as 0xrrggbb, laid over `under`. Its color is divided out of its
|
|
179
|
-
* coverage capped at 1 and laid over `under` in OKLab, then clipped to sRGB per
|
|
180
|
-
* coverage gives `under`.
|
|
308
|
+
* A `color` or `ink` pose as 0xrrggbb, laid over `under`. Its color is divided out of its
|
|
309
|
+
* coverage, the coverage capped at 1 and laid over `under` in OKLab, then clipped to sRGB per
|
|
310
|
+
* channel. No coverage gives `under`.
|
|
181
311
|
*
|
|
182
312
|
* @category channel
|
|
183
313
|
*/
|
|
184
314
|
export function toHex(pose, under = 0x000000) {
|
|
185
|
-
const w = pose
|
|
315
|
+
const w = shows(pose);
|
|
186
316
|
if (!(w > 0))
|
|
187
317
|
return under;
|
|
188
|
-
const
|
|
189
|
-
const
|
|
190
|
-
const
|
|
191
|
-
const B = (pose[2] ?? 0) * s;
|
|
318
|
+
const L = SHOWN[0];
|
|
319
|
+
const A = SHOWN[1];
|
|
320
|
+
const B = SHOWN[2];
|
|
192
321
|
if (w >= 1)
|
|
193
322
|
return fromLab(L, A, B);
|
|
194
323
|
lab(under);
|
|
195
324
|
return fromLab(mix(LAB[0], L, w), mix(LAB[1], A, w), mix(LAB[2], B, w));
|
|
196
325
|
}
|
|
197
326
|
/**
|
|
198
|
-
* A `color` pose as a CSS `oklab()` color, its coverage as the alpha. The coverage is
|
|
199
|
-
* For a host that lets the browser lay it over what is under it.
|
|
327
|
+
* A `color` or `ink` pose as a CSS `oklab()` color, its coverage as the alpha. The coverage is
|
|
328
|
+
* capped at 1. For a host that lets the browser lay it over what is under it.
|
|
200
329
|
*
|
|
201
330
|
* @category channel
|
|
202
331
|
*/
|
|
203
332
|
export function css(pose) {
|
|
204
|
-
const w = pose
|
|
333
|
+
const w = shows(pose);
|
|
205
334
|
if (!(w > 0))
|
|
206
335
|
return 'oklab(0 0 0 / 0)';
|
|
207
|
-
|
|
208
|
-
return `oklab(${(pose[0] ?? 0) * s} ${(pose[1] ?? 0) * s} ${(pose[2] ?? 0) * s} / ${w < 1 ? w : 1})`;
|
|
336
|
+
return `oklab(${SHOWN[0]} ${SHOWN[1]} ${SHOWN[2]} / ${w < 1 ? w : 1})`;
|
|
209
337
|
}
|
package/dist/columns.d.ts
CHANGED
|
@@ -1,15 +1,5 @@
|
|
|
1
1
|
import type { Lanes } from './lanes.js';
|
|
2
|
-
|
|
3
|
-
export interface Column {
|
|
4
|
-
key: string;
|
|
5
|
-
slot: number;
|
|
6
|
-
axes: number;
|
|
7
|
-
out: Float64Array;
|
|
8
|
-
/** The rest, clamped, or NaN for a channel with none. */
|
|
9
|
-
rest: Float64Array;
|
|
10
|
-
bounds: readonly [number, number] | undefined;
|
|
11
|
-
}
|
|
12
|
-
export declare function clampRun(out: Float64Array, at: number, n: number, [lo, hi]: readonly [number, number]): void;
|
|
2
|
+
import { type Column } from './pull.js';
|
|
13
3
|
/**
|
|
14
4
|
* For a `pull` of a list it read last time in the same order, with every voice on a lane, where
|
|
15
5
|
* each position's remembered chain head was current when the mix's `relinks` read `since`: what
|
package/dist/columns.js
CHANGED
|
@@ -1,9 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
for (let i = at; i < at + n; i++) {
|
|
3
|
-
const x = out[i];
|
|
4
|
-
out[i] = x < lo ? lo : x > hi ? hi : x;
|
|
5
|
-
}
|
|
6
|
-
}
|
|
1
|
+
import { clampRun } from './pull.js';
|
|
7
2
|
/**
|
|
8
3
|
* For a `pull` of a list it read last time in the same order, with every voice on a lane, where
|
|
9
4
|
* each position's remembered chain head was current when the mix's `relinks` read `since`: what
|
package/dist/contribution.js
CHANGED
|
@@ -41,6 +41,7 @@ function sentBack(mix, voice, held) {
|
|
|
41
41
|
function phaseOf(elapsed, duration, passes) {
|
|
42
42
|
return elapsed < 0 ? phaseBefore(elapsed, duration) : phaseAt(elapsed, duration, passes);
|
|
43
43
|
}
|
|
44
|
+
const NOTHING = Object.freeze({});
|
|
44
45
|
/**
|
|
45
46
|
* One live voice's delta for one subject this frame, through its record for the subject, or null
|
|
46
47
|
* when it does not reach. Its weight is left in `w`.
|
|
@@ -139,9 +140,11 @@ export function contribution(voice, subject, now, held) {
|
|
|
139
140
|
own(voice, held);
|
|
140
141
|
}
|
|
141
142
|
let delta = null;
|
|
142
|
-
// A
|
|
143
|
-
//
|
|
143
|
+
// A voice in a role is a member at weight 0 too, and the role folds at its first member's
|
|
144
|
+
// place. A keys member reads; any other is not asked, and contributes nothing.
|
|
145
|
+
let member = false;
|
|
144
146
|
if (silent(voice, weight) && (voice.built === null || voice.spec.role === undefined)) {
|
|
147
|
+
member = voice.spec.role !== undefined;
|
|
145
148
|
// A `from: 'current'` voice takes its base on its first read, silent or not.
|
|
146
149
|
if (voice.built && voice.spec.from === 'current' && held.base === undefined) {
|
|
147
150
|
held.base = this.baseFor(voice, subject);
|
|
@@ -168,8 +171,12 @@ export function contribution(voice, subject, now, held) {
|
|
|
168
171
|
remember(this, voice, subject, held);
|
|
169
172
|
if (reading.kept !== keptBefore && !voice.keeping)
|
|
170
173
|
stateful(this, voice);
|
|
171
|
-
if (delta === null)
|
|
172
|
-
|
|
174
|
+
if (delta === null) {
|
|
175
|
+
if (!member)
|
|
176
|
+
return null;
|
|
177
|
+
this.w = weight;
|
|
178
|
+
return NOTHING;
|
|
179
|
+
}
|
|
173
180
|
if (voice.out?.rest && this.isRest(delta)) {
|
|
174
181
|
held.weight = 0;
|
|
175
182
|
held.restedAt = now;
|
package/dist/crowd.d.ts
CHANGED
|
@@ -6,12 +6,6 @@ import type { Motions } from './motions.js';
|
|
|
6
6
|
import type { Scratch } from './patch.js';
|
|
7
7
|
import type { Subject } from './subject.js';
|
|
8
8
|
import type { Voice } from './voice.js';
|
|
9
|
-
/**
|
|
10
|
-
* Whether a laned voice belongs in a crowd: one naming a single subject, which shares nothing
|
|
11
|
-
* across subjects for a lane to save. A motion voice writes one channel; a keys or fn voice may
|
|
12
|
-
* write several, and joins the crowd of voices writing those same channels.
|
|
13
|
-
*/
|
|
14
|
-
export declare function crowdable<I, O>(v: Voice<I, O>): boolean;
|
|
15
9
|
/** The law of a row that has none, a keys or fn voice's. */
|
|
16
10
|
export declare const none: Float64Array<ArrayBuffer>;
|
|
17
11
|
/** Per row of a crowd's `hot`: what the fill reads of the row's voice and its current stretch. */
|
package/dist/crowd.js
CHANGED
|
@@ -4,17 +4,6 @@ import { closed } from './closed.js';
|
|
|
4
4
|
import { KeyRows } from './keyrows.js';
|
|
5
5
|
import { keepProbed, SPARSE } from './lane.js';
|
|
6
6
|
import { move } from './sample.js';
|
|
7
|
-
/**
|
|
8
|
-
* Whether a laned voice belongs in a crowd: one naming a single subject, which shares nothing
|
|
9
|
-
* across subjects for a lane to save. A motion voice writes one channel; a keys or fn voice may
|
|
10
|
-
* write several, and joins the crowd of voices writing those same channels.
|
|
11
|
-
*/
|
|
12
|
-
export function crowdable(v) {
|
|
13
|
-
return (v.named !== null &&
|
|
14
|
-
v.named.size === 1 &&
|
|
15
|
-
v.spec.role === undefined &&
|
|
16
|
-
(v.slots.length === 1 || v.motion === undefined));
|
|
17
|
-
}
|
|
18
7
|
/** The law of a row that has none, a keys or fn voice's. */
|
|
19
8
|
export const none = new Float64Array(0);
|
|
20
9
|
/** Whether a row's flags let `bareRows` take it. */
|
package/dist/cue.js
CHANGED
|
@@ -3,8 +3,8 @@ import { checkHits } from './book.js';
|
|
|
3
3
|
import { bind, checkWithin } from './bound.js';
|
|
4
4
|
import { changed } from './chain.js';
|
|
5
5
|
import { copyable } from './clone.js';
|
|
6
|
+
import { roundsHue } from './color.js';
|
|
6
7
|
import { schedule } from './due.js';
|
|
7
|
-
import { shares } from './everyone.js';
|
|
8
8
|
import { finiteRate, plainWeight, VoiceHandle } from './handle.js';
|
|
9
9
|
import { begin } from './held.js';
|
|
10
10
|
import { handle } from './hosts.js';
|
|
@@ -52,6 +52,7 @@ export function cue(mix, given) {
|
|
|
52
52
|
? Number.POSITIVE_INFINITY
|
|
53
53
|
: mix.now;
|
|
54
54
|
const voice = new Voice(mix.nextId++, spec, patch, spec.fade ?? noFade, local, start, mix.slotOf, mix.channels, mix.opts.host, mix.send, owner);
|
|
55
|
+
voice.offset = mix.offset;
|
|
55
56
|
if (patch === ownerPatch && mix.fitting !== null)
|
|
56
57
|
voice.fitting = mix.fitting;
|
|
57
58
|
if (patch === ownerPatch && mix.arcing !== null) {
|
|
@@ -62,7 +63,10 @@ export function cue(mix, given) {
|
|
|
62
63
|
checkChild(spec, voice);
|
|
63
64
|
if (mix.pace !== null && owner === null && spec.start !== undefined && voice.state === 'pending')
|
|
64
65
|
voice.pinned = spec.start - mix.offset;
|
|
65
|
-
if (start === Number.POSITIVE_INFINITY &&
|
|
66
|
+
if (start === Number.POSITIVE_INFINITY &&
|
|
67
|
+
!anchored &&
|
|
68
|
+
!owner?.fitting &&
|
|
69
|
+
Number.isNaN(voice.pinned)) {
|
|
66
70
|
voice.waking = true;
|
|
67
71
|
mix.waking = true;
|
|
68
72
|
}
|
|
@@ -158,11 +162,14 @@ export function playable(mix, spec) {
|
|
|
158
162
|
const motion = motionOf(patch);
|
|
159
163
|
if (motion !== undefined) {
|
|
160
164
|
const channel = patch.writes[0];
|
|
161
|
-
const
|
|
165
|
+
const ch = mix.kit[channel];
|
|
166
|
+
const rest = ch.rest;
|
|
162
167
|
if (typeof rest === 'number')
|
|
163
168
|
motion.cuedOn(String(channel), true);
|
|
164
169
|
else if (Array.isArray(rest) || ArrayBuffer.isView(rest))
|
|
165
|
-
motion.cuedOn(String(channel), false);
|
|
170
|
+
motion.cuedOn(String(channel), false, roundsHue(ch));
|
|
171
|
+
else if (roundsHue(ch))
|
|
172
|
+
motion.cuedOn(String(channel), false, true);
|
|
166
173
|
}
|
|
167
174
|
if (patch.reads) {
|
|
168
175
|
const host = mix.opts.host;
|
|
@@ -177,10 +184,13 @@ export function playable(mix, spec) {
|
|
|
177
184
|
if (spec.as !== undefined && mix.transport.pager !== null && !mix.opts.history?.revive)
|
|
178
185
|
throw new Error('blits: a voice cued with as needs history.revive to rebuild it from');
|
|
179
186
|
}
|
|
180
|
-
/**
|
|
187
|
+
/**
|
|
188
|
+
* A voice parked by a seek, cued again as the mix plays past its cue, on the handle it had. A span's
|
|
189
|
+
* child waits for the fit its cue makes again, as it did when first cued: its start is the last fit's.
|
|
190
|
+
*/
|
|
181
191
|
function recue(mix, voice) {
|
|
182
192
|
const owner = voice.owner;
|
|
183
|
-
if ((owner === null ? mix.now : ownerReading(owner, mix.now)) >= voice.start)
|
|
193
|
+
if (!owner?.fitting && (owner === null ? mix.now : ownerReading(owner, mix.now)) >= voice.start)
|
|
184
194
|
voice.state = 'live';
|
|
185
195
|
enter(mix, voice);
|
|
186
196
|
}
|
|
@@ -231,6 +241,8 @@ function enter(mix, voice) {
|
|
|
231
241
|
voice.cuedAt = mix.now;
|
|
232
242
|
voice.cuedSeq = mix.transport.seq;
|
|
233
243
|
voice.opened = mix.now;
|
|
244
|
+
if (voice.state !== 'pending')
|
|
245
|
+
voice.begunSeq = mix.transport.seq;
|
|
234
246
|
}
|
|
235
247
|
voice.placing = anchored;
|
|
236
248
|
// A start the host gave by its own clock, where the mix's rate puts it.
|
|
@@ -250,7 +262,7 @@ function enter(mix, voice) {
|
|
|
250
262
|
i--;
|
|
251
263
|
cued.splice(i, 0, voice);
|
|
252
264
|
scoredAdd(mix, voice);
|
|
253
|
-
voice.sharing = shares(mix, voice);
|
|
265
|
+
voice.sharing = mix.share?.shares(mix, voice) === true;
|
|
254
266
|
enlist(mix, voice);
|
|
255
267
|
changed(mix, voice);
|
|
256
268
|
mix.stir();
|
package/dist/due.d.ts
CHANGED
|
@@ -7,6 +7,17 @@ import type { Voice } from './voice.js';
|
|
|
7
7
|
*/
|
|
8
8
|
export declare class DueQueue<I, O> {
|
|
9
9
|
size: number;
|
|
10
|
+
/**
|
|
11
|
+
* The entries due every frame, -Infinity, kept out of the heap in the order they came: an owned
|
|
12
|
+
* voice is filed so each frame, and sifting each to the top and off again cost more than its visit.
|
|
13
|
+
*/
|
|
14
|
+
private every;
|
|
15
|
+
private everyTokens;
|
|
16
|
+
/** The list `takeEvery` last handed out, and its tokens: the next one fills it again. */
|
|
17
|
+
private spare;
|
|
18
|
+
private spareTokens;
|
|
19
|
+
/** Whether the list `takeEvery` last handed out was in id order. */
|
|
20
|
+
everySorted: boolean;
|
|
10
21
|
private at;
|
|
11
22
|
private tokens;
|
|
12
23
|
private voices;
|
|
@@ -15,6 +26,11 @@ export declare class DueQueue<I, O> {
|
|
|
15
26
|
push(at: number, voice: Voice<I, O>, token: number): void;
|
|
16
27
|
/** The earliest entry's time, Infinity while empty. */
|
|
17
28
|
first(): number;
|
|
29
|
+
/**
|
|
30
|
+
* The current entries due every frame, in the order they came, leaving none; `everySorted` says
|
|
31
|
+
* whether that was id order. The list is the queue's own, good until the next call.
|
|
32
|
+
*/
|
|
33
|
+
takeEvery(): Voice<I, O>[];
|
|
18
34
|
/** Takes off the earliest entry, giving its voice where it is current and undefined where stale. */
|
|
19
35
|
pop(): Voice<I, O> | undefined;
|
|
20
36
|
clear(): void;
|
|
@@ -22,6 +38,8 @@ export declare class DueQueue<I, O> {
|
|
|
22
38
|
/** Puts an entry in the hole at `i` and sifts it down to where it belongs. */
|
|
23
39
|
private sink;
|
|
24
40
|
private grow;
|
|
41
|
+
/** Drops the entries due every frame made stale by a later one. */
|
|
42
|
+
private pruneEvery;
|
|
25
43
|
/** Drops the entries made stale by a later one and heapifies what is left. */
|
|
26
44
|
private prune;
|
|
27
45
|
}
|
package/dist/due.js
CHANGED
|
@@ -5,12 +5,30 @@
|
|
|
5
5
|
*/
|
|
6
6
|
export class DueQueue {
|
|
7
7
|
size = 0;
|
|
8
|
+
/**
|
|
9
|
+
* The entries due every frame, -Infinity, kept out of the heap in the order they came: an owned
|
|
10
|
+
* voice is filed so each frame, and sifting each to the top and off again cost more than its visit.
|
|
11
|
+
*/
|
|
12
|
+
every = [];
|
|
13
|
+
everyTokens = [];
|
|
14
|
+
/** The list `takeEvery` last handed out, and its tokens: the next one fills it again. */
|
|
15
|
+
spare = [];
|
|
16
|
+
spareTokens = [];
|
|
17
|
+
/** Whether the list `takeEvery` last handed out was in id order. */
|
|
18
|
+
everySorted = true;
|
|
8
19
|
at = new Float64Array(64);
|
|
9
20
|
tokens = new Float64Array(64);
|
|
10
21
|
voices = [];
|
|
11
22
|
/** The size past which stale entries are pruned, so a voice rescheduled often cannot grow it. */
|
|
12
23
|
limit = 64;
|
|
13
24
|
push(at, voice, token) {
|
|
25
|
+
if (at === Number.NEGATIVE_INFINITY) {
|
|
26
|
+
if (this.every.length >= this.limit)
|
|
27
|
+
this.pruneEvery();
|
|
28
|
+
this.every.push(voice);
|
|
29
|
+
this.everyTokens.push(token);
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
14
32
|
if (this.size >= this.limit)
|
|
15
33
|
this.prune();
|
|
16
34
|
if (this.size === this.at.length)
|
|
@@ -30,8 +48,41 @@ export class DueQueue {
|
|
|
30
48
|
}
|
|
31
49
|
/** The earliest entry's time, Infinity while empty. */
|
|
32
50
|
first() {
|
|
51
|
+
if (this.every.length > 0)
|
|
52
|
+
return Number.NEGATIVE_INFINITY;
|
|
33
53
|
return this.size > 0 ? this.at[0] : Number.POSITIVE_INFINITY;
|
|
34
54
|
}
|
|
55
|
+
/**
|
|
56
|
+
* The current entries due every frame, in the order they came, leaving none; `everySorted` says
|
|
57
|
+
* whether that was id order. The list is the queue's own, good until the next call.
|
|
58
|
+
*/
|
|
59
|
+
takeEvery() {
|
|
60
|
+
const every = this.every;
|
|
61
|
+
const tokens = this.everyTokens;
|
|
62
|
+
let n = 0;
|
|
63
|
+
let sorted = true;
|
|
64
|
+
let last = Number.NEGATIVE_INFINITY;
|
|
65
|
+
for (let i = 0; i < every.length; i++) {
|
|
66
|
+
const voice = every[i];
|
|
67
|
+
if (tokens[i] !== voice.dueToken || voice.state === 'done')
|
|
68
|
+
continue;
|
|
69
|
+
// A voice filed twice keeps its last entry, so it is taken once.
|
|
70
|
+
voice.dueToken++;
|
|
71
|
+
if (voice.id < last)
|
|
72
|
+
sorted = false;
|
|
73
|
+
last = voice.id;
|
|
74
|
+
every[n++] = voice;
|
|
75
|
+
}
|
|
76
|
+
every.length = n;
|
|
77
|
+
tokens.length = 0;
|
|
78
|
+
this.spare.length = 0;
|
|
79
|
+
this.every = this.spare;
|
|
80
|
+
this.everyTokens = this.spareTokens;
|
|
81
|
+
this.spare = every;
|
|
82
|
+
this.spareTokens = tokens;
|
|
83
|
+
this.everySorted = sorted;
|
|
84
|
+
return every;
|
|
85
|
+
}
|
|
35
86
|
/** Takes off the earliest entry, giving its voice where it is current and undefined where stale. */
|
|
36
87
|
pop() {
|
|
37
88
|
const voice = this.voices[0];
|
|
@@ -44,6 +95,10 @@ export class DueQueue {
|
|
|
44
95
|
}
|
|
45
96
|
clear() {
|
|
46
97
|
this.size = 0;
|
|
98
|
+
this.every.length = 0;
|
|
99
|
+
this.everyTokens.length = 0;
|
|
100
|
+
this.spare.length = 0;
|
|
101
|
+
this.spareTokens.length = 0;
|
|
47
102
|
this.voices = [];
|
|
48
103
|
this.limit = 64;
|
|
49
104
|
}
|
|
@@ -84,6 +139,22 @@ export class DueQueue {
|
|
|
84
139
|
tokens.set(this.tokens);
|
|
85
140
|
this.tokens = tokens;
|
|
86
141
|
}
|
|
142
|
+
/** Drops the entries due every frame made stale by a later one. */
|
|
143
|
+
pruneEvery() {
|
|
144
|
+
const every = this.every;
|
|
145
|
+
const tokens = this.everyTokens;
|
|
146
|
+
let n = 0;
|
|
147
|
+
for (let i = 0; i < every.length; i++) {
|
|
148
|
+
const voice = every[i];
|
|
149
|
+
if (tokens[i] !== voice.dueToken)
|
|
150
|
+
continue;
|
|
151
|
+
every[n] = voice;
|
|
152
|
+
tokens[n++] = tokens[i];
|
|
153
|
+
}
|
|
154
|
+
every.length = n;
|
|
155
|
+
tokens.length = n;
|
|
156
|
+
this.limit = Math.max(this.limit, 2 * n);
|
|
157
|
+
}
|
|
87
158
|
/** Drops the entries made stale by a later one and heapifies what is left. */
|
|
88
159
|
prune() {
|
|
89
160
|
let n = 0;
|
|
@@ -169,7 +240,9 @@ export function schedule(mix, voice) {
|
|
|
169
240
|
/** Takes every current entry due by `now` off the queue, in cue order. */
|
|
170
241
|
export function popDue(mix, now) {
|
|
171
242
|
const queue = mix.due;
|
|
172
|
-
const out =
|
|
243
|
+
const out = queue.takeEvery();
|
|
244
|
+
const sorted = queue.everySorted;
|
|
245
|
+
const every = out.length;
|
|
173
246
|
while (queue.first() <= now) {
|
|
174
247
|
const voice = queue.pop();
|
|
175
248
|
if (voice === undefined)
|
|
@@ -177,7 +250,33 @@ export function popDue(mix, now) {
|
|
|
177
250
|
voice.dueToken++;
|
|
178
251
|
out.push(voice);
|
|
179
252
|
}
|
|
180
|
-
|
|
253
|
+
if (every === 0 || !sorted)
|
|
254
|
+
return byId(out);
|
|
255
|
+
return every === out.length ? out : merged(out, every);
|
|
256
|
+
}
|
|
257
|
+
/** `list` in id order, its first `n` already so and the rest few: those sorted, then merged in. */
|
|
258
|
+
function merged(list, n) {
|
|
259
|
+
const rest = byId(list.slice(n));
|
|
260
|
+
const out = [];
|
|
261
|
+
let i = 0;
|
|
262
|
+
let k = 0;
|
|
263
|
+
while (i < n && k < rest.length) {
|
|
264
|
+
const a = list[i];
|
|
265
|
+
const b = rest[k];
|
|
266
|
+
if (b.id < a.id) {
|
|
267
|
+
out.push(b);
|
|
268
|
+
k++;
|
|
269
|
+
}
|
|
270
|
+
else {
|
|
271
|
+
out.push(a);
|
|
272
|
+
i++;
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
while (i < n)
|
|
276
|
+
out.push(list[i++]);
|
|
277
|
+
while (k < rest.length)
|
|
278
|
+
out.push(rest[k++]);
|
|
279
|
+
return out;
|
|
181
280
|
}
|
|
182
281
|
/** Past this many voices the built-in sort, which makes working arrays however few it is given. */
|
|
183
282
|
const SHORT = 8;
|