@msb235/blits 0.2.0 → 0.2.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 +8 -0
- package/dist/channels.d.ts +8 -3
- package/dist/channels.js +12 -6
- package/dist/index.d.ts +1 -0
- package/dist/mixer.js +23 -0
- package/dist/types.d.ts +6 -0
- package/docs/schema.html +4 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,14 @@ 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.2.1
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
- Channel `bounds`: `sum`, `mul` and `max` take `{ bounds: [min, max] }`, and `vec` inherits its
|
|
12
|
+
axis channel's. The mix clamps the folded value to the range, so stacked voices and a retarget
|
|
13
|
+
that carries speed stop flat at the bound. Bounds are part of the channel's kind.
|
|
14
|
+
|
|
7
15
|
## 0.2.0
|
|
8
16
|
|
|
9
17
|
### Breaking
|
package/dist/channels.d.ts
CHANGED
|
@@ -1,22 +1,27 @@
|
|
|
1
1
|
import type { Channel, Kit } from './types.js';
|
|
2
|
+
/** What a stock numeric channel takes. */
|
|
3
|
+
export interface NumberOptions {
|
|
4
|
+
/** The range the value means anything in; the mix clamps to it. */
|
|
5
|
+
bounds?: readonly [min: number, max: number];
|
|
6
|
+
}
|
|
2
7
|
/**
|
|
3
8
|
* Position axes, rotation, crawl, yaw, pitch, offset.
|
|
4
9
|
*
|
|
5
10
|
* @category channel
|
|
6
11
|
*/
|
|
7
|
-
export declare function sum(): Channel<number>;
|
|
12
|
+
export declare function sum(opts?: NumberOptions): Channel<number>;
|
|
8
13
|
/**
|
|
9
14
|
* Gain, scale, opacity, hold.
|
|
10
15
|
*
|
|
11
16
|
* @category channel
|
|
12
17
|
*/
|
|
13
|
-
export declare function mul(): Channel<number>;
|
|
18
|
+
export declare function mul(opts?: NumberOptions): Channel<number>;
|
|
14
19
|
/**
|
|
15
20
|
* Dark, aperture.
|
|
16
21
|
*
|
|
17
22
|
* @category channel
|
|
18
23
|
*/
|
|
19
|
-
export declare function max(): Channel<number>;
|
|
24
|
+
export declare function max(opts?: NumberOptions): Channel<number>;
|
|
20
25
|
/**
|
|
21
26
|
* A channel with no rest: the last influence to pass wins, and a weight can only gate it. The
|
|
22
27
|
* default `lerp` steps at the midpoint, which is all a value with no arithmetic can promise; a
|
package/dist/channels.js
CHANGED
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
const mix = (a, b, u) => a + (b - a) * u;
|
|
2
|
+
/** Bounds change how a value folds at its edges, so they are part of the kind. */
|
|
3
|
+
const kindOf = (name, bounds) => bounds === undefined ? name : `${name}[${bounds[0]}, ${bounds[1]}]`;
|
|
2
4
|
/**
|
|
3
5
|
* Position axes, rotation, crawl, yaw, pitch, offset.
|
|
4
6
|
*
|
|
5
7
|
* @category channel
|
|
6
8
|
*/
|
|
7
|
-
export function sum() {
|
|
9
|
+
export function sum(opts) {
|
|
8
10
|
return {
|
|
9
|
-
kind: 'sum',
|
|
11
|
+
kind: kindOf('sum', opts?.bounds),
|
|
12
|
+
bounds: opts?.bounds,
|
|
10
13
|
rest: 0,
|
|
11
14
|
merge: (a, b) => a + b,
|
|
12
15
|
scale: (v, w) => v * w,
|
|
@@ -18,9 +21,10 @@ export function sum() {
|
|
|
18
21
|
*
|
|
19
22
|
* @category channel
|
|
20
23
|
*/
|
|
21
|
-
export function mul() {
|
|
24
|
+
export function mul(opts) {
|
|
22
25
|
return {
|
|
23
|
-
kind: 'mul',
|
|
26
|
+
kind: kindOf('mul', opts?.bounds),
|
|
27
|
+
bounds: opts?.bounds,
|
|
24
28
|
rest: 1,
|
|
25
29
|
merge: (a, b) => a * b,
|
|
26
30
|
scale: (v, w) => 1 + (v - 1) * w,
|
|
@@ -32,9 +36,10 @@ export function mul() {
|
|
|
32
36
|
*
|
|
33
37
|
* @category channel
|
|
34
38
|
*/
|
|
35
|
-
export function max() {
|
|
39
|
+
export function max(opts) {
|
|
36
40
|
return {
|
|
37
|
-
kind: 'max',
|
|
41
|
+
kind: kindOf('max', opts?.bounds),
|
|
42
|
+
bounds: opts?.bounds,
|
|
38
43
|
rest: 0,
|
|
39
44
|
merge: (a, b) => (a > b ? a : b),
|
|
40
45
|
scale: (v, w) => v * w,
|
|
@@ -67,6 +72,7 @@ export function vec(n, of) {
|
|
|
67
72
|
const fill = of.rest ?? 0;
|
|
68
73
|
return {
|
|
69
74
|
kind: of.kind === undefined ? undefined : `vec(${n}, ${of.kind})`,
|
|
75
|
+
bounds: of.bounds,
|
|
70
76
|
rest,
|
|
71
77
|
merge: (a, b) => {
|
|
72
78
|
const out = new Array(n);
|
package/dist/index.d.ts
CHANGED
package/dist/mixer.js
CHANGED
|
@@ -162,12 +162,15 @@ class Mixer {
|
|
|
162
162
|
h = null;
|
|
163
163
|
names;
|
|
164
164
|
channels;
|
|
165
|
+
/** Slots of the channels that declare bounds, clamped after every fold. */
|
|
166
|
+
bounded;
|
|
165
167
|
slotOf = new Map();
|
|
166
168
|
constructor(kit, opts) {
|
|
167
169
|
this.kit = kit;
|
|
168
170
|
this.opts = opts;
|
|
169
171
|
this.names = Object.keys(kit);
|
|
170
172
|
this.channels = this.names.map((k) => kit[k]);
|
|
173
|
+
this.bounded = this.channels.flatMap((c, i) => (c.bounds ? [i] : []));
|
|
171
174
|
this.names.forEach((k, i) => {
|
|
172
175
|
this.slotOf.set(k, i);
|
|
173
176
|
});
|
|
@@ -737,7 +740,27 @@ class Mixer {
|
|
|
737
740
|
pose[key] = pose[key] === undefined ? copy(value) : channel.merge(pose[key], value);
|
|
738
741
|
}
|
|
739
742
|
}
|
|
743
|
+
/** Clamps every bounded channel of a folded pose into its range, in place. */
|
|
744
|
+
clamp(pose) {
|
|
745
|
+
for (const slot of this.bounded) {
|
|
746
|
+
const [lo, hi] = this.channels[slot].bounds;
|
|
747
|
+
const key = this.names[slot];
|
|
748
|
+
const v = pose[key];
|
|
749
|
+
if (typeof v === 'number')
|
|
750
|
+
pose[key] = v < lo ? lo : v > hi ? hi : v;
|
|
751
|
+
else if (Array.isArray(v))
|
|
752
|
+
for (let i = 0; i < v.length; i++) {
|
|
753
|
+
const x = v[i];
|
|
754
|
+
v[i] = x < lo ? lo : x > hi ? hi : x;
|
|
755
|
+
}
|
|
756
|
+
}
|
|
757
|
+
return pose;
|
|
758
|
+
}
|
|
740
759
|
fold(subject, out, dry = false, except) {
|
|
760
|
+
const pose = this.folded(subject, out, dry, except);
|
|
761
|
+
return (this.bounded.length === 0 ? pose : this.clamp(pose));
|
|
762
|
+
}
|
|
763
|
+
folded(subject, out, dry = false, except) {
|
|
741
764
|
const now = this.now;
|
|
742
765
|
const pose = (out ?? {});
|
|
743
766
|
for (let i = 0; i < this.names.length; i++) {
|
package/dist/types.d.ts
CHANGED
|
@@ -10,6 +10,12 @@ export interface Channel<V> {
|
|
|
10
10
|
* as `'sum'` or `'vec(3, sum)'`. Absent, a channel matches only itself.
|
|
11
11
|
*/
|
|
12
12
|
kind?: string;
|
|
13
|
+
/**
|
|
14
|
+
* The range a numeric value means anything in, such as 0..1 for an opacity. The mix clamps the
|
|
15
|
+
* folded value to it, axis by axis for an array, so neither stacked voices nor a retarget that
|
|
16
|
+
* carries speed can push it out; overshoot stops flat at the bound.
|
|
17
|
+
*/
|
|
18
|
+
bounds?: readonly [min: number, max: number];
|
|
13
19
|
/** Identity. Absent means the channel has none: it replaces rather than contributes. */
|
|
14
20
|
rest?: V;
|
|
15
21
|
/** Fold two influences into one. */
|
package/docs/schema.html
CHANGED
|
@@ -248,6 +248,9 @@
|
|
|
248
248
|
<span class="cm">/** Names the arithmetic: 'sum', 'vec(3, sum)'. Two channels of one kind fold identically. Absent, a channel
|
|
249
249
|
matches only itself. */</span>
|
|
250
250
|
kind?: string;
|
|
251
|
+
<span class="cm">/** The range a numeric value means anything in. The mix clamps the folded value to it, so stacked voices and a
|
|
252
|
+
retarget that carries speed stop flat at the bound. Part of the kind: mul({ bounds: [0, 1] }) is 'mul[0, 1]'. */</span>
|
|
253
|
+
bounds?: readonly [min: number, max: number];
|
|
251
254
|
<span class="cm">/** Identity. Absent means the channel has none: `color` replaces rather than contributes. */</span>
|
|
252
255
|
rest?: V;
|
|
253
256
|
<span class="cm">/** Fold two influences into one. */</span>
|
|
@@ -569,7 +572,7 @@ export const mixer: Engine; <span class="cm">// the one that ships</span></cod
|
|
|
569
572
|
<tr><td>klieg motion</td><td><code>LetterInfo</code></td><td>position vec3 sum · rotation vec3 sum · scale mul · opacity mul</td><td>Three voices in locus <code>phase</code>, fades of <code>blendMs</code>.</td></tr>
|
|
570
573
|
<tr><td>klieg effects</td><td><code>PartInfo</code></td><td>gain mul · scale mul · dark max · crawl sum · position vec3 sum · rotation vec3 sum · light vec3 sum · color last</td><td>Light is premultiplied by amount before it reaches the channel, as <code>mergeOffsets</code> does.</td></tr>
|
|
571
574
|
<tr><td>klieg lighting</td><td>unit</td><td>yaw sum · pitch sum</td><td>One subject, the frame. A mix with no subject dimension is a mix over one subject.</td></tr>
|
|
572
|
-
<tr><td>wod transitions</td><td>wedge <code>Segment</code></td><td>hold mul · opacity mul · scale mul · offset sum · rotate sum · aperture max · offsetAngle last</td><td>Every voice is <code>keys</code> with <code>from: 'current'</code>; the voices map goes away.</td></tr>
|
|
575
|
+
<tr><td>wod transitions</td><td>wedge <code>Segment</code></td><td>hold mul[0, 1] · opacity mul[0, 1] · scale mul · offset sum · rotate sum · aperture max[0, 1] · offsetAngle last</td><td>Every voice is <code>keys</code> with <code>from: 'current'</code>; the voices map goes away.</td></tr>
|
|
573
576
|
<tr><td>sherpa seams</td><td>page</td><td>opacity mul · transform (host's) · clip max</td><td>Exit and enter as two voices; overlap is whether both are live at once.</td></tr>
|
|
574
577
|
<tr><td>magicsmoke faults</td><td>fault</td><td>none: the patch writes no channels</td><td>One voice per fault, on a mix per fault that magicsmoke keeps to itself. Its weight is the intensity, a number or any signal. Its state is the seeded discharge process, stepped under <code>stepMs: 5</code>, easing exponentially toward <code>setting.weight</code> and sending each discharge as an event. Stopping is <code>handle.fade()</code>; <code>blow</code> writes into the fault's own state through magicsmoke's handle, not as a second voice. The mix never sets <code>reduce</code>, which would step off the grid, and <code>cue</code> passes the last synced timestamp as <code>start</code>, the old process's clock origin. Jolt, whine and hum stay off blits: the browser already composites jolt, and whine and hum fade on the audio clock. magicsmoke imports <code>Signal</code>, <code>Handle</code>, <code>Setting</code>, <code>slew</code> and <code>level</code>, so those are surface to keep stable.</td></tr>
|
|
575
578
|
</table></div>
|
package/package.json
CHANGED