@msb235/blits 0.15.1 → 0.16.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 +35 -0
- package/README.md +1 -1
- package/dist/ahead.js +5 -4
- package/dist/chain.js +2 -3
- 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/crowd.d.ts +0 -6
- package/dist/crowd.js +0 -11
- package/dist/cue.js +7 -4
- package/dist/everyone.d.ts +17 -6
- package/dist/everyone.js +17 -8
- package/dist/fade.d.ts +11 -0
- package/dist/fade.js +44 -8
- package/dist/fold.js +4 -7
- package/dist/held.js +3 -4
- package/dist/hosts.d.ts +7 -0
- package/dist/hosts.js +18 -5
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/lanes.d.ts +26 -1
- package/dist/lanes.js +7 -0
- package/dist/lch.d.ts +23 -0
- package/dist/lch.js +126 -0
- package/dist/mixer.d.ts +4 -1
- package/dist/mixer.js +3 -0
- package/dist/motions.d.ts +4 -1
- package/dist/motions.js +16 -2
- package/dist/move.js +15 -10
- package/dist/owner.d.ts +7 -2
- package/dist/owner.js +13 -3
- package/dist/project.js +3 -8
- package/dist/pull.d.ts +11 -0
- package/dist/pull.js +10 -6
- package/dist/qualify.js +1 -1
- package/dist/roles.js +1 -3
- package/dist/roster.d.ts +6 -0
- package/dist/roster.js +8 -1
- package/dist/sample.js +2 -1
- package/dist/seek.js +1 -13
- package/dist/shared.d.ts +1 -1
- package/dist/shared.js +1 -1
- package/dist/spans.d.ts +4 -1
- package/dist/spans.js +6 -3
- package/dist/touch.js +1 -2
- package/dist/turns.js +2 -1
- package/dist/types/voice.d.ts +3 -2
- package/dist/voice.d.ts +19 -0
- package/dist/voice.js +35 -1
- package/docs/schema.html +37 -11
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,41 @@ This package follows [semver](https://semver.org). Below 1.0.0, a breaking chang
|
|
|
4
4
|
version and everything else the patch. Each release lists its changes as **Breaking**, **Added** and
|
|
5
5
|
**Fixed**, and the release workflow refuses a tag with no section here.
|
|
6
6
|
|
|
7
|
+
## 0.16.0
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
- `ink()`, a color channel for colors that stand for a meaning (a status, a level, a team), beside
|
|
12
|
+
`color()` for colors as the real world shows them. It takes the same `oklab()` values. It travels
|
|
13
|
+
round the hue the short way, and overlapping voices blend round the hue too: the pose holds
|
|
14
|
+
`[Σw·L, Σw·a, Σw·b, Σw·C, Σw]` and shows the hue of `Σw·(a, b)` at the average chroma, so red and
|
|
15
|
+
blue at equal weight show a vivid purple rather than the grayed one `color()` shows. Near-opposite
|
|
16
|
+
hues swing round the wheel as their weights cross. `ink(last())` replaces, traveling round the
|
|
17
|
+
hue, and holds four numbers. `toHex` and `css` read a pose of either length. Both run on lanes.
|
|
18
|
+
|
|
19
|
+
### Changed
|
|
20
|
+
|
|
21
|
+
- A `tween`, `spring` or `glide` on an `ink`, or on the deprecated `color({ lerp: 'oklch' })`,
|
|
22
|
+
goes round the hue rather than straight across each OKLab axis through gray. A tween reads as the
|
|
23
|
+
channel's lerp along its easing; a spring and a glide are solved in lightness, chroma and hue
|
|
24
|
+
with coverage alongside, so velocity carries through a retarget and the path bends rather than
|
|
25
|
+
starting over. What the patch takes and reports stays premultiplied OKLab, four numbers. A glide
|
|
26
|
+
with `bounds` on such a channel is refused at `cue`. Lanes read it as the general path does, to
|
|
27
|
+
the bit; such a voice on one subject no longer joins a crowd. `color()` is unchanged.
|
|
28
|
+
|
|
29
|
+
- Under `history`, turning an owner back (a rate below 0) brings back the children that already
|
|
30
|
+
left, so playback reverses completely. A child an owner holds no longer leaves when its passes
|
|
31
|
+
run out: it fades as before, then stays with its owner, `live` and showing nothing, until the
|
|
32
|
+
owner leaves, and comes back, climbing over its `fade.out`, once its clock is back in its
|
|
33
|
+
passes. Its `done` now resolves when its owner leaves rather than at its own end. The owner
|
|
34
|
+
leaves when it did before, and without `history` nothing changes.
|
|
35
|
+
|
|
36
|
+
### Deprecated
|
|
37
|
+
|
|
38
|
+
- `color({ lerp })`: `lerp: 'oklch'` is `ink()`, which also blends overlapping voices round the
|
|
39
|
+
hue, and `lerp: 'oklab'` is `color()`. It keeps its old behavior for this release and warns once.
|
|
40
|
+
The playground loads compositions saved with it.
|
|
41
|
+
|
|
7
42
|
## 0.15.1
|
|
8
43
|
|
|
9
44
|
### Added
|
package/README.md
CHANGED
|
@@ -151,7 +151,7 @@ do yet.
|
|
|
151
151
|
|
|
152
152
|
## Status
|
|
153
153
|
|
|
154
|
-
v0.
|
|
154
|
+
v0.16.0, with five consumers. magicsmoke runs every fault on it, wod its wheel, tricks and
|
|
155
155
|
transitions, weasel its animator's tweens, sherpa its seams, and
|
|
156
156
|
[klieg](https://github.com/orochi235/klieg) composes all three of its systems — letter motion,
|
|
157
157
|
part effects, and the environment a sign is lit by — on a mix each.
|
package/dist/ahead.js
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { ownBlends } from './blend.js';
|
|
2
2
|
import { headOf } from './chain.js';
|
|
3
|
-
import { open, sighted, weightAt } from './everyone.js';
|
|
4
3
|
import { copyHeld, unread } from './history.js';
|
|
5
4
|
import { markOf, voiceMarks } from './marks.js';
|
|
6
5
|
import { Mixer } from './mixer.js';
|
|
@@ -39,6 +38,7 @@ export function playAhead(transport, c, copies, t, earlier) {
|
|
|
39
38
|
const on = new Mixer(copy.kit, { ...copy.opts, transport: next }, null);
|
|
40
39
|
on.slot = copy.slot;
|
|
41
40
|
on.projecting = true;
|
|
41
|
+
on.share = copy.share;
|
|
42
42
|
copyAhead(copy, on);
|
|
43
43
|
const stages = earlier.get(mix);
|
|
44
44
|
if (stages === undefined)
|
|
@@ -196,11 +196,12 @@ export function recordOf(mix, voice, subject) {
|
|
|
196
196
|
// the subject for the caller to copy from.
|
|
197
197
|
if (voice.sharing) {
|
|
198
198
|
const slot = headOf(mix, subject)?.slot ?? -1;
|
|
199
|
-
|
|
199
|
+
const share = mix.share;
|
|
200
|
+
if (!share.sighted(voice, slot))
|
|
200
201
|
return undefined;
|
|
201
202
|
const all = voice.everyone;
|
|
202
|
-
open(voice, all, slot);
|
|
203
|
-
all.weight = weightAt(voice, slot);
|
|
203
|
+
share.open(voice, all, slot);
|
|
204
|
+
all.weight = share.weightAt(voice, slot);
|
|
204
205
|
return all;
|
|
205
206
|
}
|
|
206
207
|
const held = voice.subjects.get(subject);
|
package/dist/chain.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { everyoneOf, sight } from './everyone.js';
|
|
2
1
|
import { record } from './record.js';
|
|
3
2
|
/** The first record of a subject no live voice reaches, so a probe of it still makes one lookup. */
|
|
4
3
|
function stub() {
|
|
@@ -98,10 +97,10 @@ export function linkable(voice, subject, slot, guess = null) {
|
|
|
98
97
|
if (state === 'done' || (state === 'pending' && !voice.freezesBefore))
|
|
99
98
|
return null;
|
|
100
99
|
// A voice sharing one record is in no chain: a fold takes it from `sharers` at its place.
|
|
101
|
-
if (voice.sharing && everyoneOf(this, voice) !== null) {
|
|
100
|
+
if (voice.sharing && this.share.everyoneOf(this, voice) !== null) {
|
|
102
101
|
// A projection numbers no subject, and keeps nothing by subject.
|
|
103
102
|
if (slot >= 0)
|
|
104
|
-
sight(voice, slot, this.lanes.cap);
|
|
103
|
+
this.share.sight(voice, slot, this.lanes.cap);
|
|
105
104
|
return null;
|
|
106
105
|
}
|
|
107
106
|
const held = this.held(voice, subject, slot, guess);
|
package/dist/color.d.ts
CHANGED
|
@@ -8,44 +8,66 @@ import type { Channel } from './types.js';
|
|
|
8
8
|
*/
|
|
9
9
|
export declare function mixHex(a: number, b: number, u: number): number;
|
|
10
10
|
/**
|
|
11
|
-
*
|
|
12
|
-
*
|
|
11
|
+
* What `color` took before `ink` existed: `lerp: 'oklch'` overlaps straight across OKLab and
|
|
12
|
+
* travels round the hue.
|
|
13
13
|
*
|
|
14
14
|
* @category channel
|
|
15
15
|
*/
|
|
16
16
|
export interface ColorOptions {
|
|
17
|
+
/** @deprecated `'oklch'` is `ink()`, which also blends overlapping voices round the hue; `'oklab'` is `color()`. */
|
|
17
18
|
lerp?: 'oklab' | 'oklch';
|
|
18
19
|
}
|
|
19
20
|
/**
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
*
|
|
21
|
+
* Whether `channel` travels round the hue, as `ink` does, so a motion patch cued on it moves round
|
|
22
|
+
* the hue too.
|
|
23
|
+
*/
|
|
24
|
+
export declare function roundsHue(channel: Channel<unknown>): boolean;
|
|
25
|
+
/**
|
|
26
|
+
* Color as the real world shows it, light, sky, smoke or paint: OKLab `[L, a, b, coverage]`, its
|
|
27
|
+
* merge rule given as `of`. `color()` averages: voices stack as a premultiplied sum, so a pose
|
|
28
|
+
* holds `[Σw·L, Σw·a, Σw·b, Σw]` and runs on lanes; `toHex` divides it out and lays it over what
|
|
29
|
+
* is under it. `color(last())` replaces, the last voice to pass winning, at the coverage it was
|
|
30
|
+
* authored with. It travels between two colors, in a motion or a role's handoff, straight across
|
|
31
|
+
* OKLab, so opposite hues cross through gray. For colors that stand for a meaning, use `ink`.
|
|
26
32
|
*
|
|
27
33
|
* @category channel
|
|
28
34
|
*/
|
|
29
35
|
export declare function color(opts?: ColorOptions): Channel<number[]>;
|
|
30
36
|
export declare function color(of: Channel<number> | Channel<unknown>, opts?: ColorOptions): Channel<number[]>;
|
|
31
37
|
/**
|
|
32
|
-
*
|
|
38
|
+
* Color standing for a meaning, a status, a level or a team, rather than for a physical
|
|
39
|
+
* phenomenon: choose it over `color` when the colors are symbols. People picture the midpoint
|
|
40
|
+
* between red and blue as purple, reached by turning the hue, not as the grayed mix light makes,
|
|
41
|
+
* and `ink` gives them that. It travels round the hue the short way, in a motion, between keys
|
|
42
|
+
* and in a role's handoff, and `ink()` blends overlapping voices round the hue too: each voice
|
|
43
|
+
* sums as `[w·L, w·a, w·b, w·C, w]`, C its chroma, so a pose shows lightness `ΣwL/Σw`, the hue of
|
|
44
|
+
* `Σw·(a, b)`, which a gray voice pulls on not at all, and chroma `ΣwC/Σw`, so two vivid colors
|
|
45
|
+
* stay vivid. Near-opposite hues overlapping swing round the wheel as their weights cross, with
|
|
46
|
+
* no tiebreak, and exactly opposite ones cancel to gray. A patch writes it the `oklab()` values a
|
|
47
|
+
* `color` takes, and `toHex` and `css` read either. `ink(last())` replaces, traveling round the
|
|
48
|
+
* hue, and holds the four numbers it was written, since it never sums.
|
|
49
|
+
*
|
|
50
|
+
* @category channel
|
|
51
|
+
*/
|
|
52
|
+
export declare function ink(of?: Channel<number> | Channel<unknown>): Channel<number[]>;
|
|
53
|
+
/**
|
|
54
|
+
* Converts 0xrrggbb to a `color` or `ink` value. The value is OKLab `[L, a, b]` at full coverage. Convert
|
|
33
55
|
* keyframe stops once when authoring them, and call it outside the hot path in an `fn` patch.
|
|
34
56
|
*
|
|
35
57
|
* @category channel
|
|
36
58
|
*/
|
|
37
59
|
export declare function oklab(rgb: number): number[];
|
|
38
60
|
/**
|
|
39
|
-
* A `color` pose as 0xrrggbb, laid over `under`. Its color is divided out of its
|
|
40
|
-
* coverage capped at 1 and laid over `under` in OKLab, then clipped to sRGB per
|
|
41
|
-
* coverage gives `under`.
|
|
61
|
+
* A `color` or `ink` pose as 0xrrggbb, laid over `under`. Its color is divided out of its
|
|
62
|
+
* coverage, the coverage capped at 1 and laid over `under` in OKLab, then clipped to sRGB per
|
|
63
|
+
* channel. No coverage gives `under`.
|
|
42
64
|
*
|
|
43
65
|
* @category channel
|
|
44
66
|
*/
|
|
45
67
|
export declare function toHex(pose: ArrayLike<number>, under?: number): number;
|
|
46
68
|
/**
|
|
47
|
-
* A `color` pose as a CSS `oklab()` color, its coverage as the alpha. The coverage is
|
|
48
|
-
* For a host that lets the browser lay it over what is under it.
|
|
69
|
+
* A `color` or `ink` pose as a CSS `oklab()` color, its coverage as the alpha. The coverage is
|
|
70
|
+
* capped at 1. For a host that lets the browser lay it over what is under it.
|
|
49
71
|
*
|
|
50
72
|
* @category channel
|
|
51
73
|
*/
|
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/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';
|
|
@@ -158,11 +158,14 @@ export function playable(mix, spec) {
|
|
|
158
158
|
const motion = motionOf(patch);
|
|
159
159
|
if (motion !== undefined) {
|
|
160
160
|
const channel = patch.writes[0];
|
|
161
|
-
const
|
|
161
|
+
const ch = mix.kit[channel];
|
|
162
|
+
const rest = ch.rest;
|
|
162
163
|
if (typeof rest === 'number')
|
|
163
164
|
motion.cuedOn(String(channel), true);
|
|
164
165
|
else if (Array.isArray(rest) || ArrayBuffer.isView(rest))
|
|
165
|
-
motion.cuedOn(String(channel), false);
|
|
166
|
+
motion.cuedOn(String(channel), false, roundsHue(ch));
|
|
167
|
+
else if (roundsHue(ch))
|
|
168
|
+
motion.cuedOn(String(channel), false, true);
|
|
166
169
|
}
|
|
167
170
|
if (patch.reads) {
|
|
168
171
|
const host = mix.opts.host;
|
|
@@ -250,7 +253,7 @@ function enter(mix, voice) {
|
|
|
250
253
|
i--;
|
|
251
254
|
cued.splice(i, 0, voice);
|
|
252
255
|
scoredAdd(mix, voice);
|
|
253
|
-
voice.sharing = shares(mix, voice);
|
|
256
|
+
voice.sharing = mix.share?.shares(mix, voice) === true;
|
|
254
257
|
enlist(mix, voice);
|
|
255
258
|
changed(mix, voice);
|
|
256
259
|
mix.stir();
|
package/dist/everyone.d.ts
CHANGED
|
@@ -65,11 +65,22 @@ export declare function unsight<I, O>(voice: Voice<I, O>, slot: number): void;
|
|
|
65
65
|
* positions take their own records now, since each held the one.
|
|
66
66
|
*/
|
|
67
67
|
export declare function unshare<I, O>(mix: Mixer<I, O>, voice: Voice<I, O>, kept?: boolean, keeper?: I): void;
|
|
68
|
-
/**
|
|
69
|
-
* Counts what makes a mix's fold more than a walk of each chain: a role, a voice sharing one
|
|
70
|
-
* record, a voice that stopped sharing this frame. Called wherever one of the three changes, so
|
|
71
|
-
* `foldWith` asks one number.
|
|
72
|
-
*/
|
|
73
|
-
export declare function detour<I, O>(mix: Mixer<I, O>): void;
|
|
74
68
|
/** Gives a record just made for a subject a sharing voice had seen what the voice kept for it. */
|
|
75
69
|
export declare function inherit<I, O>(voice: Voice<I, O>, held: Subject<unknown>, slot: number): void;
|
|
70
|
+
/** What the general path calls on a sharing voice, reached through `Lanes`, so a lean bundle leaves it out. */
|
|
71
|
+
export declare const share: {
|
|
72
|
+
shares: typeof shares;
|
|
73
|
+
outgrow: typeof outgrow;
|
|
74
|
+
everyoneOf: typeof everyoneOf;
|
|
75
|
+
sighted: typeof sighted;
|
|
76
|
+
sight: typeof sight;
|
|
77
|
+
weightAt: typeof weightAt;
|
|
78
|
+
weighAt: typeof weighAt;
|
|
79
|
+
open: typeof open;
|
|
80
|
+
shut: typeof shut;
|
|
81
|
+
restamp: typeof restamp;
|
|
82
|
+
unsight: typeof unsight;
|
|
83
|
+
unshare: typeof unshare;
|
|
84
|
+
inherit: typeof inherit;
|
|
85
|
+
};
|
|
86
|
+
export type Share = typeof share;
|