@msb235/blits 0.7.0 → 0.8.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 +196 -0
- package/README.md +22 -4
- package/dist/book.js +4 -4
- package/dist/chain.d.ts +1 -1
- package/dist/chain.js +57 -54
- package/dist/channels.d.ts +26 -5
- package/dist/channels.js +12 -8
- package/dist/clock.d.ts +8 -1
- package/dist/clock.js +14 -3
- package/dist/clone.d.ts +3 -0
- package/dist/clone.js +16 -1
- package/dist/closed.d.ts +1 -1
- package/dist/closed.js +3 -2
- package/dist/color.d.ts +0 -11
- package/dist/color.js +4 -24
- package/dist/columns.js +0 -1
- package/dist/copyout.d.ts +6 -0
- package/dist/copyout.js +38 -0
- package/dist/crowd.d.ts +7 -5
- package/dist/crowd.js +32 -18
- package/dist/cue.d.ts +3 -1
- package/dist/cue.js +33 -20
- package/dist/departed.d.ts +34 -0
- package/dist/departed.js +73 -0
- package/dist/easing.d.ts +5 -0
- package/dist/easing.js +31 -2
- package/dist/everyone.d.ts +56 -0
- package/dist/everyone.js +221 -0
- package/dist/fade.d.ts +8 -1
- package/dist/fade.js +36 -6
- package/dist/fill.d.ts +22 -13
- package/dist/fill.js +104 -41
- package/dist/fit.js +2 -1
- package/dist/fold.d.ts +37 -0
- package/dist/fold.js +213 -28
- package/dist/gather.d.ts +15 -9
- package/dist/gather.js +60 -25
- package/dist/gone.d.ts +32 -0
- package/dist/gone.js +144 -0
- package/dist/handle.d.ts +16 -5
- package/dist/handle.js +56 -6
- package/dist/held.d.ts +8 -5
- package/dist/held.js +56 -58
- package/dist/history.d.ts +17 -13
- package/dist/history.js +49 -28
- package/dist/hosts.js +57 -9
- package/dist/index.d.ts +9 -4
- package/dist/index.js +5 -2
- package/dist/keyfill.d.ts +7 -0
- package/dist/keyfill.js +124 -0
- package/dist/keyrows.d.ts +1 -0
- package/dist/keyrows.js +1 -1
- package/dist/lane.d.ts +32 -2
- package/dist/lane.js +5 -0
- package/dist/lanes.d.ts +32 -8
- package/dist/lanes.js +69 -37
- package/dist/leavings.d.ts +26 -0
- package/dist/leavings.js +70 -0
- package/dist/meet.js +14 -8
- package/dist/mixer.d.ts +43 -7
- package/dist/mixer.js +60 -11
- package/dist/motion.d.ts +2 -0
- package/dist/motion.js +4 -1
- package/dist/motions.d.ts +22 -15
- package/dist/motions.js +40 -34
- package/dist/move.js +31 -20
- package/dist/moved.d.ts +11 -0
- package/dist/moved.js +29 -0
- package/dist/origin.d.ts +19 -0
- package/dist/origin.js +68 -0
- package/dist/owed.d.ts +4 -4
- package/dist/owed.js +8 -8
- package/dist/owner.d.ts +1 -1
- package/dist/owner.js +2 -2
- package/dist/pace.d.ts +13 -5
- package/dist/pace.js +21 -8
- package/dist/pack.d.ts +3 -1
- package/dist/pack.js +15 -24
- package/dist/paging.d.ts +8 -2
- package/dist/paging.js +44 -4
- package/dist/patch.d.ts +14 -9
- package/dist/patch.js +83 -21
- package/dist/place.js +188 -30
- package/dist/project.d.ts +12 -1
- package/dist/project.js +101 -83
- package/dist/pull.d.ts +2 -0
- package/dist/pull.js +8 -0
- package/dist/qualify.js +10 -2
- package/dist/reading.d.ts +5 -0
- package/dist/reading.js +5 -0
- package/dist/record.d.ts +9 -0
- package/dist/record.js +42 -0
- package/dist/relink.d.ts +11 -5
- package/dist/relink.js +7 -5
- package/dist/restored.d.ts +41 -0
- package/dist/restored.js +91 -0
- package/dist/revive.d.ts +10 -3
- package/dist/revive.js +31 -23
- package/dist/roster.d.ts +11 -0
- package/dist/roster.js +46 -0
- package/dist/rotation.d.ts +29 -0
- package/dist/rotation.js +91 -0
- package/dist/rows.d.ts +5 -5
- package/dist/rows.js +5 -5
- package/dist/sample.d.ts +3 -2
- package/dist/sample.js +32 -15
- package/dist/scored.d.ts +57 -0
- package/dist/scored.js +196 -0
- package/dist/seek.js +97 -114
- package/dist/shared.d.ts +6 -0
- package/dist/shared.js +15 -0
- package/dist/signals.d.ts +24 -3
- package/dist/signals.js +50 -11
- package/dist/spans.js +7 -0
- package/dist/stack.d.ts +11 -0
- package/dist/stack.js +47 -0
- package/dist/standing.d.ts +10 -0
- package/dist/standing.js +79 -0
- package/dist/store.d.ts +3 -3
- package/dist/store.js +3 -3
- package/dist/strays.js +3 -0
- package/dist/tape.d.ts +8 -5
- package/dist/tape.js +21 -13
- package/dist/testing.d.ts +35 -0
- package/dist/testing.js +116 -0
- package/dist/touch.d.ts +6 -0
- package/dist/touch.js +28 -0
- package/dist/transport.d.ts +53 -6
- package/dist/transport.js +102 -20
- package/dist/turns.d.ts +18 -0
- package/dist/turns.js +72 -0
- package/dist/tweened.js +1 -1
- package/dist/types/channel.d.ts +46 -0
- package/dist/types/channel.js +1 -0
- package/dist/types/history.d.ts +139 -0
- package/dist/types/history.js +1 -0
- package/dist/types/mix.d.ts +336 -0
- package/dist/types/mix.js +1 -0
- package/dist/types/patch.d.ts +187 -0
- package/dist/types/patch.js +1 -0
- package/dist/types/place.d.ts +157 -0
- package/dist/types/place.js +1 -0
- package/dist/types/transport.d.ts +71 -0
- package/dist/types/transport.js +1 -0
- package/dist/types/voice.d.ts +323 -0
- package/dist/types/voice.js +1 -0
- package/dist/types.d.ts +7 -1162
- package/dist/unpage.d.ts +2 -2
- package/dist/unpage.js +55 -25
- package/dist/voice.d.ts +92 -22
- package/dist/voice.js +108 -43
- package/dist/wave.d.ts +42 -0
- package/dist/wave.js +62 -0
- package/dist/weigh.js +1 -1
- package/docs/schema.html +42 -36
- package/package.json +8 -3
package/dist/closed.d.ts
CHANGED
|
@@ -6,7 +6,7 @@ export declare const OVER = 2;
|
|
|
6
6
|
export declare const COAST = 3;
|
|
7
7
|
export declare const EASED = 4;
|
|
8
8
|
/**
|
|
9
|
-
* A stretch
|
|
9
|
+
* A stretch at voice time `t`, released at `at` from `x0` moving at `v0` toward `to` (each read
|
|
10
10
|
* from its offset), into `xo` and `vo`: the one copy of the closed forms, which `law` (a patch's
|
|
11
11
|
* `[form, settle, k1, k2, k3]`, the head of a patch's buffer or a copy of it) picks between. A tween reads `ease` and
|
|
12
12
|
* `ms`, the stretch's length, dividing in milliseconds so a boundary lands exactly; every other
|
package/dist/closed.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { same } from './clock.js';
|
|
1
2
|
import { eased, progress } from './tweened.js';
|
|
2
3
|
/** The closed forms a motion patch's law picks between, by its first number. */
|
|
3
4
|
export const UNDER = 0;
|
|
@@ -26,7 +27,7 @@ function prepare(law, t) {
|
|
|
26
27
|
const k2 = law[3];
|
|
27
28
|
const k3 = law[4];
|
|
28
29
|
const p = prepared;
|
|
29
|
-
if (
|
|
30
|
+
if (same(t, p.t) && form === p.form && k1 === p.k1 && k2 === p.k2 && k3 === p.k3)
|
|
30
31
|
return;
|
|
31
32
|
p.form = form;
|
|
32
33
|
p.k1 = k1;
|
|
@@ -114,7 +115,7 @@ function solveGlide(law, y0) {
|
|
|
114
115
|
solved.dy = (-y0 / tau) * e;
|
|
115
116
|
}
|
|
116
117
|
/**
|
|
117
|
-
* A stretch
|
|
118
|
+
* A stretch at voice time `t`, released at `at` from `x0` moving at `v0` toward `to` (each read
|
|
118
119
|
* from its offset), into `xo` and `vo`: the one copy of the closed forms, which `law` (a patch's
|
|
119
120
|
* `[form, settle, k1, k2, k3]`, the head of a patch's buffer or a copy of it) picks between. A tween reads `ease` and
|
|
120
121
|
* `ms`, the stretch's length, dividing in milliseconds so a boundary lands exactly; every other
|
package/dist/color.d.ts
CHANGED
|
@@ -6,17 +6,6 @@ import type { Channel } from './types.js';
|
|
|
6
6
|
* @category channel
|
|
7
7
|
*/
|
|
8
8
|
export declare function mixHex(a: number, b: number, u: number): number;
|
|
9
|
-
/**
|
|
10
|
-
* Color, as 0xrrggbb, blended in OKLCH as `mixHex` does, or with `space: 'srgb'` channel by
|
|
11
|
-
* channel in sRGB, which keeps a port's arithmetic identical to a host that blended that way.
|
|
12
|
-
*
|
|
13
|
-
* @deprecated Use `color(last(), { lerp: 'oklch' })`, which replaces as this does, and `toHex` at
|
|
14
|
-
* write. A packed color has no arithmetic to stack with; `color()` averages instead.
|
|
15
|
-
* @category channel
|
|
16
|
-
*/
|
|
17
|
-
export declare function hex(opts?: {
|
|
18
|
-
space?: 'oklch' | 'srgb';
|
|
19
|
-
}): Channel<number>;
|
|
20
9
|
/**
|
|
21
10
|
* How a `color` channel interpolates two colors: straight across OKLab, or in OKLCH with hue the
|
|
22
11
|
* short way round, as `mixHex` does.
|
package/dist/color.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { lerpNumber, numericOf, sum, vec, vouch, widen } from './channels.js';
|
|
2
2
|
const mix = lerpNumber;
|
|
3
3
|
const LINEAR = Array.from({ length: 256 }, (_, c) => {
|
|
4
4
|
const v = c / 255;
|
|
@@ -107,28 +107,6 @@ export function mixHex(a, b, u) {
|
|
|
107
107
|
turn += 2 * Math.PI;
|
|
108
108
|
return fromOklch(mix(al, slotL[sb], u), mix(ac, bc, u), from + turn * u);
|
|
109
109
|
}
|
|
110
|
-
const clamp255 = (v) => (v < 0 ? 0 : v > 255 ? 255 : Math.round(v));
|
|
111
|
-
/** Each of red, green and blue on its own, as klieg's light channel blends. */
|
|
112
|
-
function mixSrgb(a, b, u) {
|
|
113
|
-
const ar = (a >> 16) & 0xff;
|
|
114
|
-
const ag = (a >> 8) & 0xff;
|
|
115
|
-
const ab = a & 0xff;
|
|
116
|
-
const br = (b >> 16) & 0xff;
|
|
117
|
-
const bg = (b >> 8) & 0xff;
|
|
118
|
-
const bb = b & 0xff;
|
|
119
|
-
return ((clamp255(mix(ar, br, u)) << 16) | (clamp255(mix(ag, bg, u)) << 8) | clamp255(mix(ab, bb, u)));
|
|
120
|
-
}
|
|
121
|
-
/**
|
|
122
|
-
* Color, as 0xrrggbb, blended in OKLCH as `mixHex` does, or with `space: 'srgb'` channel by
|
|
123
|
-
* channel in sRGB, which keeps a port's arithmetic identical to a host that blended that way.
|
|
124
|
-
*
|
|
125
|
-
* @deprecated Use `color(last(), { lerp: 'oklch' })`, which replaces as this does, and `toHex` at
|
|
126
|
-
* write. A packed color has no arithmetic to stack with; `color()` averages instead.
|
|
127
|
-
* @category channel
|
|
128
|
-
*/
|
|
129
|
-
export function hex(opts) {
|
|
130
|
-
return { ...last({ lerp: opts?.space === 'srgb' ? mixSrgb : mixHex }), kind: 'hex' };
|
|
131
|
-
}
|
|
132
110
|
/** Two OKLab colors with coverage, interpolated in OKLCH: color by `u`, coverage by `u` alongside. */
|
|
133
111
|
function lerpLch(x, y, u) {
|
|
134
112
|
const xw = x[3] ?? 0;
|
|
@@ -166,7 +144,9 @@ export function color(ofOrOpts, maybe) {
|
|
|
166
144
|
const opts = of === undefined ? ofOrOpts : maybe;
|
|
167
145
|
const round = opts?.lerp === 'oklch';
|
|
168
146
|
const space = round ? ', oklch' : '';
|
|
169
|
-
|
|
147
|
+
const stock = of === undefined ? undefined : numericOf(of);
|
|
148
|
+
if (of === undefined ||
|
|
149
|
+
(stock?.op === 'sum' && stock.turn === undefined && of.bounds === undefined)) {
|
|
170
150
|
const channel = vec(4, sum());
|
|
171
151
|
channel.kind = `color(sum${space})`;
|
|
172
152
|
// A lane interpolates a stock channel straight across, so a lerp of its own takes it off them.
|
package/dist/columns.js
CHANGED
|
@@ -103,7 +103,6 @@ function queue(lanes, columns, slot, row, count) {
|
|
|
103
103
|
lanes.queueCounts[k] = count;
|
|
104
104
|
lanes.queued = k + 1;
|
|
105
105
|
}
|
|
106
|
-
/** Makes room in the queue for `runs` runs. */
|
|
107
106
|
function room(lanes, runs) {
|
|
108
107
|
if (runs <= lanes.queueSlots.length)
|
|
109
108
|
return;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { Lanes } from './lanes.js';
|
|
2
|
+
/**
|
|
3
|
+
* Writes a subject's laned values into a pose, each array channel into the array the pose holds
|
|
4
|
+
* for it where that is the length a new one would be, as a host's `out` holds from its last probe.
|
|
5
|
+
*/
|
|
6
|
+
export declare function copy<I, O>(this: Lanes<I, O>, slot: number, pose: Record<string, unknown>): void;
|
package/dist/copyout.js
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Writes a subject's laned values into a pose, each array channel into the array the pose holds
|
|
3
|
+
* for it where that is the length a new one would be, as a host's `out` holds from its last probe.
|
|
4
|
+
*/
|
|
5
|
+
export function copy(slot, pose) {
|
|
6
|
+
const laned = this.laned;
|
|
7
|
+
for (let c = 0; c < laned.length; c++) {
|
|
8
|
+
const ch = laned[c];
|
|
9
|
+
const axes = ch.axes;
|
|
10
|
+
// Never `delete`: it drops a reused out object into dictionary mode for good.
|
|
11
|
+
if (ch.op === 'last' && Number.isNaN(ch.values[slot * axes])) {
|
|
12
|
+
if (pose[ch.name] !== undefined)
|
|
13
|
+
pose[ch.name] = undefined;
|
|
14
|
+
continue;
|
|
15
|
+
}
|
|
16
|
+
if (ch.scalar) {
|
|
17
|
+
pose[ch.name] = ch.values[slot];
|
|
18
|
+
continue;
|
|
19
|
+
}
|
|
20
|
+
// Made as the general path makes it, a copy of rest, so poses from either path share a shape.
|
|
21
|
+
const start = ch.start;
|
|
22
|
+
const base = slot * axes;
|
|
23
|
+
const held = pose[ch.name];
|
|
24
|
+
if (Array.isArray(held) &&
|
|
25
|
+
held !== start &&
|
|
26
|
+
held.length === (start.length > axes ? start.length : axes)) {
|
|
27
|
+
for (let a = 0; a < axes; a++)
|
|
28
|
+
held[a] = ch.values[base + a];
|
|
29
|
+
for (let a = axes; a < start.length; a++)
|
|
30
|
+
held[a] = start[a];
|
|
31
|
+
continue;
|
|
32
|
+
}
|
|
33
|
+
const arr = [...start];
|
|
34
|
+
for (let a = 0; a < axes; a++)
|
|
35
|
+
arr[a] = ch.values[base + a];
|
|
36
|
+
pose[ch.name] = arr;
|
|
37
|
+
}
|
|
38
|
+
}
|
package/dist/crowd.d.ts
CHANGED
|
@@ -57,7 +57,7 @@ export declare const enum Flag {
|
|
|
57
57
|
/** Whether a row's flags let `bareRows` take it. */
|
|
58
58
|
export declare function bare(f: number): boolean;
|
|
59
59
|
/**
|
|
60
|
-
* Every laned voice on
|
|
60
|
+
* Every laned voice on the same channels that reaches one subject, a row each, in voice order: the
|
|
61
61
|
* per-subject numbers a lane keeps, and a copy of what a fill reads of the voice and, for a motion
|
|
62
62
|
* voice, its patch, so a fill over 100k of them reads a few flat arrays instead of 100k voices,
|
|
63
63
|
* lanes and patch buffers. Each copy is written when its source changes: the voice's by
|
|
@@ -88,7 +88,7 @@ export declare class Crowd<I, O> implements Positions<I, O> {
|
|
|
88
88
|
};
|
|
89
89
|
/** Where the fill now under way has reached. */
|
|
90
90
|
cursor: number;
|
|
91
|
-
/** Rows whose voices have left, kept in place until the
|
|
91
|
+
/** Rows whose voices have left, kept in place until the crowd is compacted or rebuilt. */
|
|
92
92
|
dead: number;
|
|
93
93
|
/** Whether some row is `Flag.STALE`, for `freshen` to copy before the fill's loop. */
|
|
94
94
|
stale: boolean;
|
|
@@ -114,13 +114,15 @@ export declare class Crowd<I, O> implements Positions<I, O> {
|
|
|
114
114
|
kept(p: number, xs: Float64Array): void;
|
|
115
115
|
motionAt(p: number): Motions<I> | undefined;
|
|
116
116
|
get size(): number;
|
|
117
|
-
/** Makes room for `rows` rows. */
|
|
118
117
|
reserve(rows: number): void;
|
|
119
118
|
}
|
|
120
119
|
/** Runs every crowd's rows whose voices come before voice `id`, from where each crowd reached. */
|
|
121
120
|
export declare function crowdsUpTo<I, O>(lanes: Lanes<I, O>, id: number): void;
|
|
122
|
-
/**
|
|
123
|
-
|
|
121
|
+
/**
|
|
122
|
+
* A keys or fn row's contribution, as `one` makes a lane position's, at the voice time, delay and
|
|
123
|
+
* weight in `Arg`.
|
|
124
|
+
*/
|
|
125
|
+
export declare function row<I, O>(this: Lanes<I, O>, c: Crowd<I, O>, p: number, voice: Voice<I, O>, slot: number): void;
|
|
124
126
|
/**
|
|
125
127
|
* Copies stale motion rows' stretches as a fill begins: inside `runCrowd`, once voices came and
|
|
126
128
|
* went, TurboFan spent its inlining on the copy and stopped inlining every row's ease.
|
package/dist/crowd.js
CHANGED
|
@@ -22,7 +22,7 @@ export function bare(f) {
|
|
|
22
22
|
return (f & 2043 /* Bare.MASK */) === 1211 /* Bare.WANT */;
|
|
23
23
|
}
|
|
24
24
|
/**
|
|
25
|
-
* Every laned voice on
|
|
25
|
+
* Every laned voice on the same channels that reaches one subject, a row each, in voice order: the
|
|
26
26
|
* per-subject numbers a lane keeps, and a copy of what a fill reads of the voice and, for a motion
|
|
27
27
|
* voice, its patch, so a fill over 100k of them reads a few flat arrays instead of 100k voices,
|
|
28
28
|
* lanes and patch buffers. Each copy is written when its source changes: the voice's by
|
|
@@ -50,7 +50,7 @@ export class Crowd {
|
|
|
50
50
|
line = SPARSE.other;
|
|
51
51
|
/** Where the fill now under way has reached. */
|
|
52
52
|
cursor = 0;
|
|
53
|
-
/** Rows whose voices have left, kept in place until the
|
|
53
|
+
/** Rows whose voices have left, kept in place until the crowd is compacted or rebuilt. */
|
|
54
54
|
dead = 0;
|
|
55
55
|
/** Whether some row is `Flag.STALE`, for `freshen` to copy before the fill's loop. */
|
|
56
56
|
stale = false;
|
|
@@ -111,7 +111,6 @@ export class Crowd {
|
|
|
111
111
|
get size() {
|
|
112
112
|
return this.list.length;
|
|
113
113
|
}
|
|
114
|
-
/** Makes room for `rows` rows. */
|
|
115
114
|
reserve(rows) {
|
|
116
115
|
if (rows * 8 /* Row.STRIDE */ <= this.data.length)
|
|
117
116
|
return;
|
|
@@ -157,7 +156,7 @@ export function crowdsUpTo(lanes, id) {
|
|
|
157
156
|
runCrowd(lanes, first, b);
|
|
158
157
|
}
|
|
159
158
|
}
|
|
160
|
-
/** A crowd's rows from its cursor up to voice `id`, bare tweens in `bareRows
|
|
159
|
+
/** A crowd's rows from its cursor up to voice `id`, bare tweens in `bareRows`, others in `rows`. */
|
|
161
160
|
function runCrowd(lanes, c, id) {
|
|
162
161
|
const list = c.list;
|
|
163
162
|
const hot = c.hot;
|
|
@@ -171,7 +170,7 @@ function runCrowd(lanes, c, id) {
|
|
|
171
170
|
c.cursor = p;
|
|
172
171
|
}
|
|
173
172
|
/**
|
|
174
|
-
* A crowd's rows from `
|
|
173
|
+
* A crowd's rows from `first` up to voice `id`, until one `bareRows` takes after the first: what
|
|
175
174
|
* `runMotion` does for a lane's subjects, reading each row's voice and stretch from `hot`, and
|
|
176
175
|
* through `move` for whatever `hot` cannot answer, as `runMotion` does. Returns where it stopped.
|
|
177
176
|
*/
|
|
@@ -180,6 +179,7 @@ function rows(lanes, c, first, id) {
|
|
|
180
179
|
const hot = c.hot;
|
|
181
180
|
const H = c.stride;
|
|
182
181
|
const per = lanes.per;
|
|
182
|
+
const arg = lanes.arg;
|
|
183
183
|
const list = c.list;
|
|
184
184
|
const ch = c.chans[0];
|
|
185
185
|
const n = c.axes;
|
|
@@ -226,11 +226,16 @@ function rows(lanes, c, first, id) {
|
|
|
226
226
|
if (fast)
|
|
227
227
|
w = hot[h + 4 /* Hot.WEIGHT */];
|
|
228
228
|
else if (typeof voice.spec.weight === 'function') {
|
|
229
|
-
|
|
230
|
-
|
|
229
|
+
// The envelope first: it may call out, and `signalled` reads `arg` as it starts.
|
|
230
|
+
const fade = lanes.host.envelope(voice, data[o + 1 /* Row.SINCE */]);
|
|
231
|
+
arg[1 /* Arg.ELAPSED */] = elapsed;
|
|
232
|
+
arg[3 /* Arg.PASS */] = passAt(elapsed, voice.duration, voice.passes);
|
|
233
|
+
arg[5 /* Arg.FADE */] = fade;
|
|
234
|
+
if (!lanes.signalled(voice, slot, c.records[p])) {
|
|
231
235
|
data[o + 2 /* Row.WEIGHT */] = 0;
|
|
232
236
|
continue;
|
|
233
237
|
}
|
|
238
|
+
w = arg[0 /* Arg.WEIGHT */];
|
|
234
239
|
}
|
|
235
240
|
else
|
|
236
241
|
w = weighed(voice.weight, lanes.host.envelope(voice, data[o + 1 /* Row.SINCE */]), lanes.parting(voice, slot));
|
|
@@ -240,8 +245,11 @@ function rows(lanes, c, first, id) {
|
|
|
240
245
|
c.keys.fold(hot[h + 8 /* Hot.AT */], phaseAt(elapsed, hot[h + 10 /* Hot.X0 */], hot[h + 9 /* Hot.MS */]), c.chans, slot, w);
|
|
241
246
|
continue;
|
|
242
247
|
}
|
|
248
|
+
arg[1 /* Arg.ELAPSED */] = elapsed;
|
|
249
|
+
arg[4 /* Arg.DELAY */] = delay;
|
|
250
|
+
arg[0 /* Arg.WEIGHT */] = w;
|
|
243
251
|
if ((f & 128 /* Flag.MOTION */) === 0) {
|
|
244
|
-
lanes.row(c, p, voice, slot
|
|
252
|
+
lanes.row(c, p, voice, slot);
|
|
245
253
|
continue;
|
|
246
254
|
}
|
|
247
255
|
if (data[o + 7 /* Row.MET */] === 0) {
|
|
@@ -258,7 +266,7 @@ function rows(lanes, c, first, id) {
|
|
|
258
266
|
(per[q + 3 /* Per.LANE_PROBE */] > from ||
|
|
259
267
|
per[q + 4 /* Per.GENERAL_PROBE */] > from))) {
|
|
260
268
|
const run = c.motions[p];
|
|
261
|
-
move(lanes, c, run, p, slot, c.records[p]
|
|
269
|
+
move(lanes, c, run, p, slot, c.records[p]);
|
|
262
270
|
// The first sample numbered the subject in the patch: copy its stretch from the next fill.
|
|
263
271
|
if (ms < 0)
|
|
264
272
|
c.restale(p, hot[h + 0 /* Hot.FLAGS */]);
|
|
@@ -274,19 +282,24 @@ function rows(lanes, c, first, id) {
|
|
|
274
282
|
if (w <= 0)
|
|
275
283
|
continue;
|
|
276
284
|
if ((f & 32 /* Flag.FOLDS */) !== 0)
|
|
277
|
-
lanes.foldRun(ch, slot, xs
|
|
285
|
+
lanes.foldRun(ch, slot, xs);
|
|
278
286
|
else
|
|
279
|
-
lanes.foldInto(ch, slot, c.motions[p].value(ms, xs)
|
|
287
|
+
lanes.foldInto(ch, slot, c.motions[p].value(ms, xs));
|
|
280
288
|
}
|
|
281
289
|
return p;
|
|
282
290
|
}
|
|
283
|
-
/**
|
|
284
|
-
|
|
291
|
+
/**
|
|
292
|
+
* A keys or fn row's contribution, as `one` makes a lane position's, at the voice time, delay and
|
|
293
|
+
* weight in `Arg`.
|
|
294
|
+
*/
|
|
295
|
+
export function row(c, p, voice, slot) {
|
|
296
|
+
const arg = this.arg;
|
|
297
|
+
const elapsed = arg[1 /* Arg.ELAPSED */];
|
|
285
298
|
const duration = voice.duration;
|
|
286
|
-
|
|
299
|
+
arg[2 /* Arg.PHASE */] = phaseAt(elapsed, duration, voice.passes);
|
|
287
300
|
if (voice.built !== null) {
|
|
288
|
-
if (
|
|
289
|
-
this.foldKeys(voice, c.chans,
|
|
301
|
+
if (arg[0 /* Arg.WEIGHT */] > 0)
|
|
302
|
+
this.foldKeys(voice, c.chans, slot);
|
|
290
303
|
return;
|
|
291
304
|
}
|
|
292
305
|
const rec = c.records[p];
|
|
@@ -294,7 +307,8 @@ export function row(c, p, voice, slot, elapsed, delay, w) {
|
|
|
294
307
|
this.late.push(slot);
|
|
295
308
|
return;
|
|
296
309
|
}
|
|
297
|
-
|
|
310
|
+
arg[3 /* Arg.PASS */] = passAt(elapsed, duration, voice.passes);
|
|
311
|
+
this.call(voice, c.chans, rec, slot);
|
|
298
312
|
}
|
|
299
313
|
/** Copies what a fill reads of a crowd row's voice into `hot`; returns the row's flags. */
|
|
300
314
|
function copyVoice(c, p) {
|
|
@@ -349,7 +363,7 @@ function copyStretch(c, p, ms) {
|
|
|
349
363
|
const ch = c.chans[0];
|
|
350
364
|
if (run.n === c.axes && run.stretchInto(ms, c.hot, h + 8 /* Hot.AT */)) {
|
|
351
365
|
f |= 8 /* Flag.BARE */;
|
|
352
|
-
if (run.scalar(ms) === ch.scalar)
|
|
366
|
+
if (run.scalar(ms) === ch.scalar && ch.plain)
|
|
353
367
|
f |= 32 /* Flag.FOLDS */;
|
|
354
368
|
}
|
|
355
369
|
c.hot[h + 0 /* Hot.FLAGS */] = f;
|
package/dist/cue.d.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import type { Mixer } from './mixer.js';
|
|
2
|
-
import type { Handle, VoiceSpec } from './types.js';
|
|
2
|
+
import type { Handle, Kit, Patch, VoiceSpec } from './types.js';
|
|
3
3
|
export declare function cue<I, O>(mix: Mixer<I, O>, spec: VoiceSpec<I, O>): Handle<I>;
|
|
4
|
+
/** Refuses a patch writing a channel `kit` lacks, or holds as another kind than it was written for. */
|
|
5
|
+
export declare function fitsKit<I, O, S, H>(patch: Patch<I, O, S, H>, kit: Kit<O>): void;
|
|
4
6
|
/** Refuses a spec whose patch the mix cannot play, or whose state its history cannot keep. */
|
|
5
7
|
export declare function playable<I, O>(mix: Mixer<I, O>, spec: VoiceSpec<I, O>): void;
|
package/dist/cue.js
CHANGED
|
@@ -1,13 +1,16 @@
|
|
|
1
1
|
import { checkHits } from './book.js';
|
|
2
|
-
import { changed
|
|
2
|
+
import { changed } from './chain.js';
|
|
3
|
+
import { copyable } from './clone.js';
|
|
3
4
|
import { schedule } from './due.js';
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
5
|
+
import { shares } from './everyone.js';
|
|
6
|
+
import { finiteRate, plainWeight, VoiceHandle } from './handle.js';
|
|
7
|
+
import { reorigin } from './held.js';
|
|
6
8
|
import { handle } from './hosts.js';
|
|
7
9
|
import { motionOf } from './motion.js';
|
|
8
10
|
import { adopt, ownerPatch, ownerReading } from './owner.js';
|
|
9
|
-
import { durationOf } from './patch.js';
|
|
10
11
|
import { checkPlacement, localNow, mixAt, pin, place } from './place.js';
|
|
12
|
+
import { enlist } from './roster.js';
|
|
13
|
+
import { scoredAdd } from './scored.js';
|
|
11
14
|
import { checkChild, refit } from './spans.js';
|
|
12
15
|
import { motionOwner } from './strays.js';
|
|
13
16
|
import { record } from './tape.js';
|
|
@@ -24,7 +27,7 @@ export function cue(mix, spec) {
|
|
|
24
27
|
if (anchor)
|
|
25
28
|
checkPlacement(mix, spec, anchor, owner);
|
|
26
29
|
if (spec.hits !== undefined)
|
|
27
|
-
checkHits(spec.hits,
|
|
30
|
+
checkHits(spec.hits, patch.duration);
|
|
28
31
|
const anchored = anchor !== undefined && (anchor.start !== undefined || anchor.in !== undefined);
|
|
29
32
|
// A voice an owner holds is placed on the owner's clock: ms from when it starts.
|
|
30
33
|
const local = owner === null ? mix.now : ownerReading(owner, mix.now);
|
|
@@ -51,17 +54,31 @@ export function cue(mix, spec) {
|
|
|
51
54
|
record(mix, 'cue', () => recue(mix, VoiceHandle.voiceOf(h) ?? lost(h)));
|
|
52
55
|
return h;
|
|
53
56
|
}
|
|
54
|
-
/** Refuses a
|
|
55
|
-
export function
|
|
56
|
-
const patch = spec.patch;
|
|
57
|
+
/** Refuses a patch writing a channel `kit` lacks, or holds as another kind than it was written for. */
|
|
58
|
+
export function fitsKit(patch, kit) {
|
|
57
59
|
for (const channel of patch.writes) {
|
|
58
|
-
if (!(channel in
|
|
60
|
+
if (!(channel in kit))
|
|
59
61
|
throw new Error(`blits: kit has no channel ${String(channel)}, which this patch writes`);
|
|
60
62
|
const wanted = patch.kit?.[channel];
|
|
61
|
-
const here =
|
|
63
|
+
const here = kit[channel];
|
|
64
|
+
const rest = here.rest;
|
|
65
|
+
if (here.fold && !here.copy && typeof rest === 'object' && rest !== null && !copyable(rest))
|
|
66
|
+
throw new Error(`blits: channel ${String(channel)} folds into a copy of its rest, which the mix cannot copy; give the channel a copy`);
|
|
62
67
|
if (wanted && wanted !== here && (wanted.kind === undefined || wanted.kind !== here.kind))
|
|
63
68
|
throw new Error(`blits: channel ${String(channel)} is ${here.kind ?? 'a custom channel'} in this kit, but the patch was written for ${wanted.kind ?? 'a custom channel'}`);
|
|
64
69
|
}
|
|
70
|
+
}
|
|
71
|
+
/** Refuses a spec whose patch the mix cannot play, or whose state its history cannot keep. */
|
|
72
|
+
export function playable(mix, spec) {
|
|
73
|
+
const patch = spec.patch;
|
|
74
|
+
const loop = spec.loop;
|
|
75
|
+
if (typeof loop === 'number' && !(Number.isInteger(loop) && loop >= 1))
|
|
76
|
+
throw new RangeError(`blits: loop takes true, false or a whole number of passes, not ${loop}`);
|
|
77
|
+
if (spec.rate !== undefined)
|
|
78
|
+
finiteRate(spec.rate);
|
|
79
|
+
if (typeof spec.weight === 'number')
|
|
80
|
+
plainWeight(spec.weight);
|
|
81
|
+
fitsKit(patch, mix.kit);
|
|
65
82
|
const motion = motionOf(patch);
|
|
66
83
|
if (motion !== undefined) {
|
|
67
84
|
const channel = patch.writes[0];
|
|
@@ -103,12 +120,9 @@ function enter(mix, voice) {
|
|
|
103
120
|
const motion = voice.motion;
|
|
104
121
|
if (owner !== null)
|
|
105
122
|
adopt(owner, voice);
|
|
106
|
-
if (voice.holding !== null) {
|
|
107
|
-
mix.owners ??= [];
|
|
108
|
-
mix.owners.push(voice);
|
|
109
|
-
}
|
|
110
123
|
if (!Number.isNaN(mix.now)) {
|
|
111
124
|
voice.cuedAt = mix.now;
|
|
125
|
+
voice.cuedSeq = mix.transport.seq;
|
|
112
126
|
voice.opened = mix.now;
|
|
113
127
|
}
|
|
114
128
|
voice.placing = anchored;
|
|
@@ -127,21 +141,20 @@ function enter(mix, voice) {
|
|
|
127
141
|
while (i > 0 && cued[i - 1].id > voice.id)
|
|
128
142
|
i--;
|
|
129
143
|
cued.splice(i, 0, voice);
|
|
130
|
-
|
|
144
|
+
scoredAdd(mix, voice);
|
|
145
|
+
voice.sharing = shares(mix, voice);
|
|
146
|
+
enlist(mix, voice);
|
|
131
147
|
changed(mix, voice);
|
|
132
148
|
mix.stir();
|
|
133
|
-
if (spec.locus !== undefined)
|
|
134
|
-
mix.loci++;
|
|
135
149
|
if (spec.from === 'current')
|
|
136
150
|
mix.wantsPose = true;
|
|
137
151
|
if (anchor) {
|
|
138
|
-
mix.anchored++;
|
|
139
152
|
place(mix);
|
|
140
153
|
if (!Number.isNaN(mix.now) &&
|
|
141
154
|
voice.state === 'pending' &&
|
|
142
155
|
localNow(mix, voice) >= voice.start) {
|
|
143
156
|
voice.state = 'live';
|
|
144
|
-
|
|
157
|
+
reorigin(mix, voice, mix.now);
|
|
145
158
|
changed(mix, voice);
|
|
146
159
|
}
|
|
147
160
|
}
|
|
@@ -150,7 +163,7 @@ function enter(mix, voice) {
|
|
|
150
163
|
// After placing, so the controls it starts with are where its anchors put it at the cue.
|
|
151
164
|
if (mix.opts.history) {
|
|
152
165
|
voice.log = [];
|
|
153
|
-
voice.note(Number.NEGATIVE_INFINITY);
|
|
166
|
+
voice.note(Number.NEGATIVE_INFINITY, Number.NEGATIVE_INFINITY);
|
|
154
167
|
voice.first ??= voice.log[0];
|
|
155
168
|
}
|
|
156
169
|
schedule(mix, voice);
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { Mixer } from './mixer.js';
|
|
2
|
+
import type { Mark, Query } from './types.js';
|
|
3
|
+
import { type Voice } from './voice.js';
|
|
4
|
+
/**
|
|
5
|
+
* What a mix keeps of a voice it has forgotten, on leaving without history or past history's reach:
|
|
6
|
+
* what a query matches it by and the times of its marks, so an anchor that no voice the mix still
|
|
7
|
+
* knows answers can find it.
|
|
8
|
+
*/
|
|
9
|
+
export interface Departed {
|
|
10
|
+
readonly id: number;
|
|
11
|
+
readonly score: string | undefined;
|
|
12
|
+
/** Its owner's id, -1 for none. */
|
|
13
|
+
readonly owner: number;
|
|
14
|
+
readonly name: string | undefined;
|
|
15
|
+
readonly tags: readonly string[];
|
|
16
|
+
readonly writes: readonly unknown[];
|
|
17
|
+
readonly marks: Readonly<Record<Mark, number | undefined>>;
|
|
18
|
+
/** How many of the index's ends hold it. */
|
|
19
|
+
held: number;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* The departed voices a query could still pick: for each name, tag and channel within a score and
|
|
23
|
+
* owner, the first and the last to leave. It grows with those, never with how many voices left.
|
|
24
|
+
*/
|
|
25
|
+
export declare class DepartedIndex {
|
|
26
|
+
private readonly ends;
|
|
27
|
+
readonly all: Set<Departed>;
|
|
28
|
+
add<I, O>(mix: Mixer<I, O>, voice: Voice<I, O>): void;
|
|
29
|
+
/** Lets go of the voices an owner that has left held: nothing can be cued beside them again. */
|
|
30
|
+
dropOwner(owner: number): void;
|
|
31
|
+
private release;
|
|
32
|
+
}
|
|
33
|
+
/** Whether a departed voice answers `q`, asked by a voice of `owner` (-1 for none) on `score`. */
|
|
34
|
+
export declare function departedMatches(d: Departed, q: Query, score: string | undefined, anywhere: boolean, owner: number): boolean;
|
package/dist/departed.js
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { markOf } from './marks.js';
|
|
2
|
+
import { none } from './voice.js';
|
|
3
|
+
const MARKS = ['start', 'in', 'coast', 'out', 'end'];
|
|
4
|
+
/**
|
|
5
|
+
* The departed voices a query could still pick: for each name, tag and channel within a score and
|
|
6
|
+
* owner, the first and the last to leave. It grows with those, never with how many voices left.
|
|
7
|
+
*/
|
|
8
|
+
export class DepartedIndex {
|
|
9
|
+
ends = new Map();
|
|
10
|
+
all = new Set();
|
|
11
|
+
add(mix, voice) {
|
|
12
|
+
// Nothing can be cued beside a voice whose owner has gone, so nothing will ask for it.
|
|
13
|
+
if (voice.owner?.state === 'done')
|
|
14
|
+
return;
|
|
15
|
+
const marks = {};
|
|
16
|
+
for (const mark of MARKS)
|
|
17
|
+
marks[mark] = markOf(mix, voice, mark);
|
|
18
|
+
const d = {
|
|
19
|
+
id: voice.id,
|
|
20
|
+
score: voice.spec.score,
|
|
21
|
+
owner: voice.owner?.id ?? -1,
|
|
22
|
+
name: voice.spec.name,
|
|
23
|
+
tags: voice.spec.tags ?? none,
|
|
24
|
+
writes: voice.patch.writes,
|
|
25
|
+
marks,
|
|
26
|
+
held: 0,
|
|
27
|
+
};
|
|
28
|
+
const base = `${d.score ?? ''}\u0000${d.owner}\u0000`;
|
|
29
|
+
const keys = [`${base}w`];
|
|
30
|
+
if (d.name !== undefined)
|
|
31
|
+
keys.push(`${base}n\u0000${d.name}`);
|
|
32
|
+
for (const tag of d.tags)
|
|
33
|
+
keys.push(`${base}t\u0000${tag}`);
|
|
34
|
+
for (const ch of d.writes)
|
|
35
|
+
keys.push(`${base}c\u0000${String(ch)}`);
|
|
36
|
+
for (const key of keys) {
|
|
37
|
+
const end = this.ends.get(key);
|
|
38
|
+
if (end === undefined) {
|
|
39
|
+
this.ends.set(key, { first: d, last: d });
|
|
40
|
+
d.held += 2;
|
|
41
|
+
continue;
|
|
42
|
+
}
|
|
43
|
+
this.release(end.last);
|
|
44
|
+
end.last = d;
|
|
45
|
+
d.held++;
|
|
46
|
+
}
|
|
47
|
+
if (d.held > 0)
|
|
48
|
+
this.all.add(d);
|
|
49
|
+
if (voice.holding !== null)
|
|
50
|
+
this.dropOwner(voice.id);
|
|
51
|
+
}
|
|
52
|
+
/** Lets go of the voices an owner that has left held: nothing can be cued beside them again. */
|
|
53
|
+
dropOwner(owner) {
|
|
54
|
+
for (const [key, end] of this.ends)
|
|
55
|
+
if (end.first.owner === owner) {
|
|
56
|
+
this.ends.delete(key);
|
|
57
|
+
this.all.delete(end.first);
|
|
58
|
+
this.all.delete(end.last);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
release(d) {
|
|
62
|
+
if (--d.held === 0)
|
|
63
|
+
this.all.delete(d);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
/** Whether a departed voice answers `q`, asked by a voice of `owner` (-1 for none) on `score`. */
|
|
67
|
+
export function departedMatches(d, q, score, anywhere, owner) {
|
|
68
|
+
return (d.score === score &&
|
|
69
|
+
(anywhere || d.owner === owner) &&
|
|
70
|
+
(q.name === undefined || d.name === q.name) &&
|
|
71
|
+
(q.tag === undefined || d.tags.includes(q.tag)) &&
|
|
72
|
+
(q.writes === undefined || d.writes.includes(q.writes)));
|
|
73
|
+
}
|
package/dist/easing.d.ts
CHANGED
|
@@ -1,4 +1,9 @@
|
|
|
1
1
|
import type { Easing } from './types.js';
|
|
2
2
|
export type Curve = (u: number) => number;
|
|
3
|
+
/**
|
|
4
|
+
* How fast a curve leaves 0, for bending a retarget to the speed the subject had: Infinity where
|
|
5
|
+
* the curve jumps or leaves vertically, so no finite bend can match it.
|
|
6
|
+
*/
|
|
7
|
+
export declare function startSlope(c: Curve): number;
|
|
3
8
|
/** The function an easing names. Resolved once where a patch or voice is built, not per frame. */
|
|
4
9
|
export declare function curve(easing: Easing): Curve;
|
package/dist/easing.js
CHANGED
|
@@ -46,6 +46,31 @@ const named = {
|
|
|
46
46
|
'ease-in-out': bezier(0.42, 0, 0.58, 1),
|
|
47
47
|
};
|
|
48
48
|
const built = new WeakMap();
|
|
49
|
+
/** Each built curve's slope at 0, where its definition gives one: Infinity for a jump. */
|
|
50
|
+
const starts = new WeakMap([[linear, 1]]);
|
|
51
|
+
/** A bezier's slope at u = 0: dy/dx as t leaves 0, by the first control point that moves x. */
|
|
52
|
+
function bezierStart(x1, y1, x2, y2) {
|
|
53
|
+
if (x1 > 0)
|
|
54
|
+
return y1 / x1;
|
|
55
|
+
if (y1 > 0)
|
|
56
|
+
return Number.POSITIVE_INFINITY;
|
|
57
|
+
if (x2 > 0)
|
|
58
|
+
return y2 / x2;
|
|
59
|
+
return y2 > 0 ? Number.POSITIVE_INFINITY : 1;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* How fast a curve leaves 0, for bending a retarget to the speed the subject had: Infinity where
|
|
63
|
+
* the curve jumps or leaves vertically, so no finite bend can match it.
|
|
64
|
+
*/
|
|
65
|
+
export function startSlope(c) {
|
|
66
|
+
const known = starts.get(c);
|
|
67
|
+
if (known !== undefined)
|
|
68
|
+
return known;
|
|
69
|
+
const h = 1e-6;
|
|
70
|
+
const v = c(h);
|
|
71
|
+
// Past a thousandth of the way within a millionth of the time: a jump, not a slope.
|
|
72
|
+
return v > 1e-3 ? Number.POSITIVE_INFINITY : v / h;
|
|
73
|
+
}
|
|
49
74
|
/** The function an easing names. Resolved once where a patch or voice is built, not per frame. */
|
|
50
75
|
export function curve(easing) {
|
|
51
76
|
if (typeof easing === 'function')
|
|
@@ -60,15 +85,19 @@ export function curve(easing) {
|
|
|
60
85
|
if (held)
|
|
61
86
|
return held;
|
|
62
87
|
let made;
|
|
63
|
-
if ('bezier' in easing)
|
|
88
|
+
if ('bezier' in easing) {
|
|
64
89
|
made = bezier(...easing.bezier);
|
|
90
|
+
starts.set(made, bezierStart(...easing.bezier));
|
|
91
|
+
}
|
|
65
92
|
else {
|
|
66
93
|
const n = easing.steps;
|
|
67
94
|
const start = easing.jump === 'start';
|
|
68
95
|
made = (u) => {
|
|
69
96
|
const c = u <= 0 ? 0 : u >= 1 ? 1 : u;
|
|
70
|
-
|
|
97
|
+
// CSS: jump-start has already taken its first step at 0, and neither form passes n.
|
|
98
|
+
return (start ? Math.min(n, Math.floor(c * n) + 1) : Math.floor(c * n)) / n;
|
|
71
99
|
};
|
|
100
|
+
starts.set(made, start ? Number.POSITIVE_INFINITY : 0);
|
|
72
101
|
}
|
|
73
102
|
built.set(easing, made);
|
|
74
103
|
return made;
|