bireactive 0.3.0 → 0.3.2
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/README.md +14 -7
- package/dist/automerge/doc-cell.d.ts +20 -0
- package/dist/automerge/doc-cell.js +80 -0
- package/dist/automerge/index.d.ts +3 -0
- package/dist/automerge/index.js +12 -0
- package/dist/automerge/reconcile.d.ts +5 -0
- package/dist/automerge/reconcile.js +63 -0
- package/dist/core/_counts.d.ts +48 -0
- package/dist/core/_counts.js +51 -0
- package/dist/core/cell.d.ts +148 -112
- package/dist/core/cell.js +945 -768
- package/dist/core/debug.d.ts +25 -0
- package/dist/core/debug.js +121 -0
- package/dist/core/derived-geometry.js +4 -7
- package/dist/core/index.d.ts +9 -2
- package/dist/core/index.js +8 -1
- package/dist/core/lenses/aggregates.d.ts +42 -52
- package/dist/core/lenses/aggregates.js +225 -116
- package/dist/core/lenses/geometry.d.ts +22 -4
- package/dist/core/lenses/geometry.js +59 -27
- package/dist/core/lenses/index.d.ts +6 -6
- package/dist/core/lenses/index.js +6 -6
- package/dist/core/lenses/memory.js +4 -17
- package/dist/core/lenses/numerical.d.ts +100 -0
- package/dist/core/lenses/{typed-factor.js → numerical.js} +136 -34
- package/dist/core/lenses/point-cloud.d.ts +67 -0
- package/dist/core/lenses/{closed-form-policies.js → point-cloud.js} +226 -84
- package/dist/core/lenses/snap.d.ts +18 -0
- package/dist/core/lenses/snap.js +138 -0
- package/dist/core/lenses/text.d.ts +40 -0
- package/dist/core/lenses/text.js +202 -0
- package/dist/core/lifecycle.js +3 -6
- package/dist/core/linalg.js +5 -11
- package/dist/core/optic.d.ts +13 -0
- package/dist/core/optic.js +39 -0
- package/dist/core/optics.d.ts +10 -0
- package/dist/core/optics.js +26 -0
- package/dist/core/store.d.ts +9 -0
- package/dist/core/store.js +77 -0
- package/dist/core/traits.d.ts +4 -7
- package/dist/core/traits.js +8 -12
- package/dist/core/values/anchor.js +0 -4
- package/dist/core/values/arr.d.ts +110 -0
- package/dist/core/values/arr.js +336 -0
- package/dist/core/values/audio.d.ts +8 -9
- package/dist/core/values/audio.js +11 -28
- package/dist/core/values/bool.d.ts +11 -11
- package/dist/core/values/bool.js +12 -22
- package/dist/core/values/box.d.ts +15 -20
- package/dist/core/values/box.js +20 -33
- package/dist/core/values/canvas.d.ts +18 -25
- package/dist/core/values/canvas.js +32 -66
- package/dist/core/values/color.d.ts +5 -7
- package/dist/core/values/color.js +5 -11
- package/dist/core/values/field.d.ts +6 -7
- package/dist/core/values/field.js +10 -35
- package/dist/core/values/flags.d.ts +1 -2
- package/dist/core/values/flags.js +1 -17
- package/dist/core/values/gpu.d.ts +6 -10
- package/dist/core/values/gpu.js +8 -22
- package/dist/core/values/matrix.d.ts +2 -4
- package/dist/core/values/matrix.js +2 -12
- package/dist/core/values/num.d.ts +19 -28
- package/dist/core/values/num.js +23 -41
- package/dist/core/values/pose.d.ts +2 -4
- package/dist/core/values/pose.js +3 -12
- package/dist/core/values/range.d.ts +18 -26
- package/dist/core/values/range.js +22 -39
- package/dist/core/values/reg/ambiguity.d.ts +8 -0
- package/dist/core/values/reg/ambiguity.js +131 -0
- package/dist/core/values/reg/engine.d.ts +91 -0
- package/dist/core/values/reg/engine.js +373 -0
- package/dist/core/values/reg/nfa.d.ts +42 -0
- package/dist/core/values/reg/nfa.js +391 -0
- package/dist/core/values/reg/regex.d.ts +7 -0
- package/dist/core/values/reg/regex.js +318 -0
- package/dist/core/values/reg/types.d.ts +60 -0
- package/dist/core/values/reg/types.js +3 -0
- package/dist/core/values/reg.d.ts +250 -0
- package/dist/core/values/reg.js +649 -0
- package/dist/core/values/str.d.ts +16 -60
- package/dist/core/values/str.js +133 -315
- package/dist/core/values/template.js +1 -24
- package/dist/core/values/transform.d.ts +3 -5
- package/dist/core/values/transform.js +3 -12
- package/dist/core/values/tri.d.ts +9 -10
- package/dist/core/values/tri.js +9 -15
- package/dist/core/values/vec.d.ts +9 -24
- package/dist/core/values/vec.js +9 -64
- package/dist/formats/lens.js +6 -9
- package/dist/index.d.ts +0 -11
- package/dist/index.js +1 -11
- package/dist/jsx-dev-runtime.d.ts +2 -0
- package/dist/jsx-dev-runtime.js +5 -0
- package/dist/jsx-runtime.d.ts +54 -0
- package/dist/jsx-runtime.js +219 -0
- package/dist/schema/lens.js +5 -5
- package/dist/shapes/drag-behaviors.d.ts +56 -0
- package/dist/shapes/drag-behaviors.js +102 -0
- package/dist/shapes/drag-spec.d.ts +52 -0
- package/dist/shapes/drag-spec.js +112 -0
- package/dist/shapes/index.d.ts +3 -1
- package/dist/shapes/index.js +3 -1
- package/dist/shapes/interaction.d.ts +2 -3
- package/dist/shapes/interaction.js +77 -56
- package/dist/shapes/label.js +6 -0
- package/dist/shapes/layout.d.ts +47 -1
- package/dist/shapes/layout.js +59 -1
- package/package.json +22 -1
- package/dist/coll.d.ts +0 -74
- package/dist/coll.js +0 -210
- package/dist/core/lenses/closed-form-policies.d.ts +0 -57
- package/dist/core/lenses/decompositions.d.ts +0 -14
- package/dist/core/lenses/decompositions.js +0 -224
- package/dist/core/lenses/domain-aggregates.d.ts +0 -42
- package/dist/core/lenses/domain-aggregates.js +0 -245
- package/dist/core/lenses/typed-factor.d.ts +0 -40
|
@@ -0,0 +1,336 @@
|
|
|
1
|
+
import { batch, Cell, cell, derive, lazy, lens } from "../cell.js";
|
|
2
|
+
import { Num } from "./num.js";
|
|
3
|
+
/** Structural equality: same element cells (by reference) in the same order; a
|
|
4
|
+
* value change inside an element doesn't affect it. */
|
|
5
|
+
const sameCells = (a, b) => a === b || (a.length === b.length && a.every((c, i) => c === b[i]));
|
|
6
|
+
const clampInt = (v, lo, hi) => {
|
|
7
|
+
const r = Math.round(v);
|
|
8
|
+
return r < lo ? lo : r > hi ? hi : r;
|
|
9
|
+
};
|
|
10
|
+
const views = new WeakMap();
|
|
11
|
+
export class Arr extends Cell {
|
|
12
|
+
static traits = {
|
|
13
|
+
equals: sameCells,
|
|
14
|
+
};
|
|
15
|
+
constructor(items = []) {
|
|
16
|
+
super(items, { equals: sameCells });
|
|
17
|
+
}
|
|
18
|
+
/** Build a derived `Arr` over any `Read` source. Variance escape: `Cell<R>`
|
|
19
|
+
* isn't assignable to `Cell<unknown>` (contravariant `_equals`), so typed
|
|
20
|
+
* `Arr.derive` can't see element subtyping. */
|
|
21
|
+
static #view(parent, getter) {
|
|
22
|
+
// biome-ignore lint/suspicious/noExplicitAny: variance escape (see above)
|
|
23
|
+
return Arr.derive(parent, getter);
|
|
24
|
+
}
|
|
25
|
+
/** An `Arr` whose elements are derived from an external `source` and whose
|
|
26
|
+
* structural edits rewrite that source via `ops`. */
|
|
27
|
+
static fromSource(source, getter, ops) {
|
|
28
|
+
const view = Arr.#view(source, getter);
|
|
29
|
+
views.set(view, {
|
|
30
|
+
insert: ops.insert,
|
|
31
|
+
remove: ops.remove,
|
|
32
|
+
moveBefore: ops.moveBefore,
|
|
33
|
+
});
|
|
34
|
+
return view;
|
|
35
|
+
}
|
|
36
|
+
// ── reads ────────────────────────────────────────────────────────────
|
|
37
|
+
/** The element cells in order; tracked when read in an effect/derive. */
|
|
38
|
+
get cells() {
|
|
39
|
+
return this.value;
|
|
40
|
+
}
|
|
41
|
+
/** Snapshot of element values, `readonly T[]`; re-derives on any value or
|
|
42
|
+
* structural change. Memoized per instance. */
|
|
43
|
+
get values() {
|
|
44
|
+
return lazy(this, "values", () => derive(this, cs => cs.map(c => c.value)));
|
|
45
|
+
}
|
|
46
|
+
/** Element count as a reactive `Num` (structural changes only). */
|
|
47
|
+
get length() {
|
|
48
|
+
return lazy(this, "length", () => Num.derive(this, cs => cs.length));
|
|
49
|
+
}
|
|
50
|
+
/** This element's index as a writable `Num`: reading gives its position,
|
|
51
|
+
* writing reorders (splices the reference to the rounded, clamped target).
|
|
52
|
+
* Writable on a base `Arr`; read-only over a derived view. */
|
|
53
|
+
indexOf(e) {
|
|
54
|
+
return Num.lens(this, cs => cs.indexOf(e), (to, cs) => {
|
|
55
|
+
const from = cs.indexOf(e);
|
|
56
|
+
if (from < 0)
|
|
57
|
+
return cs;
|
|
58
|
+
const others = cs.filter(x => x !== e);
|
|
59
|
+
const t = clampInt(to, 0, others.length);
|
|
60
|
+
if (t === from)
|
|
61
|
+
return cs;
|
|
62
|
+
return [...others.slice(0, t), e, ...others.slice(t)];
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
// ── structural edits ────────────────────────────────────────────────
|
|
66
|
+
/** Append `v` (a value is wrapped in a fresh cell; a cell passes through).
|
|
67
|
+
* Returns the element cell. */
|
|
68
|
+
push(v) {
|
|
69
|
+
return this.insert(v);
|
|
70
|
+
}
|
|
71
|
+
/** Insert at `at` (default: end). On a view, delegates through the view's
|
|
72
|
+
* `insert` (a filter asserts its predicate; `map` has none, so it throws). */
|
|
73
|
+
insert(v, at) {
|
|
74
|
+
const info = views.get(this);
|
|
75
|
+
if (info) {
|
|
76
|
+
if (!info.insert)
|
|
77
|
+
throw new TypeError("Arr: this view does not support insert");
|
|
78
|
+
return info.insert(v, at);
|
|
79
|
+
}
|
|
80
|
+
const e = (v instanceof Cell ? v : cell(v));
|
|
81
|
+
const cur = this.peek();
|
|
82
|
+
const next = cur.slice();
|
|
83
|
+
if (at == null || at >= next.length)
|
|
84
|
+
next.push(e);
|
|
85
|
+
else
|
|
86
|
+
next.splice(Math.max(0, at), 0, e);
|
|
87
|
+
this._writeSource(next);
|
|
88
|
+
return e;
|
|
89
|
+
}
|
|
90
|
+
/** Remove element `e` (by reference). On a view, delegates through the
|
|
91
|
+
* view's `remove`. */
|
|
92
|
+
remove(e) {
|
|
93
|
+
const info = views.get(this);
|
|
94
|
+
if (info) {
|
|
95
|
+
if (!info.remove)
|
|
96
|
+
throw new TypeError("Arr: this view does not support remove");
|
|
97
|
+
info.remove(e);
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
const cur = this.peek();
|
|
101
|
+
const next = cur.filter(x => x !== e);
|
|
102
|
+
if (next.length !== cur.length)
|
|
103
|
+
this._writeSource(next);
|
|
104
|
+
}
|
|
105
|
+
/** Remove the element at index `i` in this (view's) order. */
|
|
106
|
+
removeAt(i) {
|
|
107
|
+
const e = this.peek()[i];
|
|
108
|
+
if (e)
|
|
109
|
+
this.remove(e);
|
|
110
|
+
}
|
|
111
|
+
/** Move `e` to index `to` (rounded, clamped) in this (view's) order. */
|
|
112
|
+
move(e, to) {
|
|
113
|
+
const cur = this.peek();
|
|
114
|
+
const others = cur.filter(x => x !== e);
|
|
115
|
+
const t = clampInt(to, 0, others.length);
|
|
116
|
+
this.moveBefore(e, t < others.length ? others[t] : null);
|
|
117
|
+
}
|
|
118
|
+
/** Splice `e` to just before `anchor` (or to the end when `anchor` is null).
|
|
119
|
+
* On a base `Arr` this rewrites the reference order; on a view it delegates
|
|
120
|
+
* through the view's `moveBefore`, where the shared `anchor` cell stays
|
|
121
|
+
* meaningful in the base order. No-op if `e` isn't present. */
|
|
122
|
+
moveBefore(e, anchor) {
|
|
123
|
+
const info = views.get(this);
|
|
124
|
+
if (info) {
|
|
125
|
+
if (!info.moveBefore)
|
|
126
|
+
throw new TypeError("Arr: this view does not support move");
|
|
127
|
+
info.moveBefore(e, anchor);
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
const cur = this.peek();
|
|
131
|
+
if (!cur.includes(e))
|
|
132
|
+
return;
|
|
133
|
+
const without = cur.filter(x => x !== e);
|
|
134
|
+
const ai = anchor == null ? -1 : without.indexOf(anchor);
|
|
135
|
+
const at = ai < 0 ? without.length : ai;
|
|
136
|
+
const next = [...without.slice(0, at), e, ...without.slice(at)];
|
|
137
|
+
if (!sameCells(next, cur))
|
|
138
|
+
this._writeSource(next);
|
|
139
|
+
}
|
|
140
|
+
/** Make `e` appear in this view: insert into the base if absent, then assert
|
|
141
|
+
* each view's constraint up the chain. */
|
|
142
|
+
assertContains(e) {
|
|
143
|
+
const info = views.get(this);
|
|
144
|
+
if (info) {
|
|
145
|
+
info.assertContains?.(e);
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
if (!this.peek().includes(e))
|
|
149
|
+
this.insert(e);
|
|
150
|
+
}
|
|
151
|
+
/** Empty the collection (a view removes only its visible elements). */
|
|
152
|
+
clear() {
|
|
153
|
+
if (views.get(this)) {
|
|
154
|
+
for (const e of [...this.peek()])
|
|
155
|
+
this.remove(e);
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
if (this.peek().length > 0)
|
|
159
|
+
this._writeSource([]);
|
|
160
|
+
}
|
|
161
|
+
// ── views ────────────────────────────────────────────────────────────
|
|
162
|
+
/** Subset whose elements pass `pred` (which reads element values, so the
|
|
163
|
+
* view re-derives reactively). Shares the element cells. `insert` adds to
|
|
164
|
+
* the parent and asserts the predicate. */
|
|
165
|
+
filter(pred) {
|
|
166
|
+
const base = this;
|
|
167
|
+
const view = Arr.#view(base, cs => cs.filter(c => pred(c)));
|
|
168
|
+
views.set(view, {
|
|
169
|
+
insert: (v, at) => {
|
|
170
|
+
const e = base.insert(v, at);
|
|
171
|
+
pred.assert?.(e);
|
|
172
|
+
return e;
|
|
173
|
+
},
|
|
174
|
+
remove: e => base.remove(e),
|
|
175
|
+
moveBefore: (e, anchor) => base.moveBefore(e, anchor),
|
|
176
|
+
assertContains: e => {
|
|
177
|
+
base.assertContains(e);
|
|
178
|
+
pred.assert?.(e);
|
|
179
|
+
},
|
|
180
|
+
});
|
|
181
|
+
return view;
|
|
182
|
+
}
|
|
183
|
+
/** Projection ordered by `key` (read off each element, so it re-sorts
|
|
184
|
+
* reactively). Shares the element cells; structural edits delegate to the
|
|
185
|
+
* base (a `move`/`insert` repositions in the base order, not the view's). */
|
|
186
|
+
sortBy(key) {
|
|
187
|
+
const base = this;
|
|
188
|
+
const view = Arr.#view(base, cs => [...cs].sort((a, b) => key(a) - key(b)));
|
|
189
|
+
views.set(view, {
|
|
190
|
+
insert: (v, at) => base.insert(v, at),
|
|
191
|
+
remove: e => base.remove(e),
|
|
192
|
+
moveBefore: (e, anchor) => base.moveBefore(e, anchor),
|
|
193
|
+
assertContains: e => base.assertContains(e),
|
|
194
|
+
});
|
|
195
|
+
return view;
|
|
196
|
+
}
|
|
197
|
+
/** Per-element map. `f` projects each element value; with the inverse `g`
|
|
198
|
+
* the mapped elements are writable lenses (editing one writes the source
|
|
199
|
+
* cell), else they're read-only. Element identity is stable: one lens per
|
|
200
|
+
* source cell, memoized. */
|
|
201
|
+
map(f, g) {
|
|
202
|
+
const fwd = new WeakMap();
|
|
203
|
+
const back = new WeakMap();
|
|
204
|
+
const lensOf = (c) => {
|
|
205
|
+
let m = fwd.get(c);
|
|
206
|
+
if (m === undefined) {
|
|
207
|
+
m = (g ? lens(c, f, (u, v) => g(u, v)) : derive(c, f));
|
|
208
|
+
fwd.set(c, m);
|
|
209
|
+
back.set(m, c);
|
|
210
|
+
}
|
|
211
|
+
return m;
|
|
212
|
+
};
|
|
213
|
+
const base = this;
|
|
214
|
+
const view = Arr.#view(base, cs => cs.map(lensOf));
|
|
215
|
+
views.set(view, {
|
|
216
|
+
remove: mc => {
|
|
217
|
+
const src = back.get(mc);
|
|
218
|
+
if (src)
|
|
219
|
+
base.remove(src);
|
|
220
|
+
},
|
|
221
|
+
moveBefore: (mc, anchor) => {
|
|
222
|
+
const src = back.get(mc);
|
|
223
|
+
const a = anchor ? (back.get(anchor) ?? null) : null;
|
|
224
|
+
if (src)
|
|
225
|
+
base.moveBefore(src, a);
|
|
226
|
+
},
|
|
227
|
+
assertContains: mc => {
|
|
228
|
+
const src = back.get(mc);
|
|
229
|
+
if (src)
|
|
230
|
+
base.assertContains(src);
|
|
231
|
+
},
|
|
232
|
+
});
|
|
233
|
+
return view;
|
|
234
|
+
}
|
|
235
|
+
/** Partition by a per-element key field into derived sub-`Arr`s, one writable
|
|
236
|
+
* filter view per bucket. `move(e, key, index)` writes the key field and
|
|
237
|
+
* splices the base so `e` lands at `index` in its new group. `order` seeds
|
|
238
|
+
* empty buckets and pins their order. */
|
|
239
|
+
groupBy(field, opts = {}) {
|
|
240
|
+
return new GroupArr(this, field, opts.order ?? []);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
/** The result of `Arr.groupBy`: derived buckets plus a grouped `move`/`insert`
|
|
244
|
+
* whose backward pass writes the group field and reorders the base. */
|
|
245
|
+
export class GroupArr {
|
|
246
|
+
#parent;
|
|
247
|
+
#field;
|
|
248
|
+
#order;
|
|
249
|
+
#subs = new Map();
|
|
250
|
+
/** The buckets in order; tracked when read in an effect/derive. */
|
|
251
|
+
groups;
|
|
252
|
+
constructor(parent, field, order) {
|
|
253
|
+
this.#parent = parent;
|
|
254
|
+
this.#field = field;
|
|
255
|
+
this.#order = order;
|
|
256
|
+
this.groups = derive(parent, cells => this.#bucket(cells));
|
|
257
|
+
}
|
|
258
|
+
#bucket(cells) {
|
|
259
|
+
const keys = [...this.#order];
|
|
260
|
+
const seen = new Set(keys);
|
|
261
|
+
for (const c of cells) {
|
|
262
|
+
const k = this.#field(c).value;
|
|
263
|
+
if (!seen.has(k)) {
|
|
264
|
+
seen.add(k);
|
|
265
|
+
keys.push(k);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
return keys.map(key => ({ key, items: this.#sub(key) }));
|
|
269
|
+
}
|
|
270
|
+
/** One memoized filter view per key — stable identity across re-buckets. */
|
|
271
|
+
#sub(key) {
|
|
272
|
+
let s = this.#subs.get(key);
|
|
273
|
+
if (s === undefined) {
|
|
274
|
+
const pred = c => Object.is(this.#field(c).value, key);
|
|
275
|
+
pred.assert = c => {
|
|
276
|
+
this.#field(c).value = key;
|
|
277
|
+
};
|
|
278
|
+
s = this.#parent.filter(pred);
|
|
279
|
+
this.#subs.set(key, s);
|
|
280
|
+
}
|
|
281
|
+
return s;
|
|
282
|
+
}
|
|
283
|
+
get value() {
|
|
284
|
+
return this.groups.value;
|
|
285
|
+
}
|
|
286
|
+
map(f) {
|
|
287
|
+
return derive(this.groups, gs => gs.map(f));
|
|
288
|
+
}
|
|
289
|
+
/** Place `e` in group `key` at `index` (within that group's order). Asserts
|
|
290
|
+
* every upstream filter, writes the group field, and splices the base — one
|
|
291
|
+
* batch, so every view re-flows once. Omit `index` to keep base order. */
|
|
292
|
+
move(e, key, index) {
|
|
293
|
+
batch(() => {
|
|
294
|
+
this.#parent.assertContains(e);
|
|
295
|
+
this.#field(e).value = key;
|
|
296
|
+
if (index != null) {
|
|
297
|
+
const members = this.#parent
|
|
298
|
+
.peek()
|
|
299
|
+
.filter(c => c !== e && Object.is(this.#field(c).peek(), key));
|
|
300
|
+
const t = index < 0 ? 0 : index > members.length ? members.length : index;
|
|
301
|
+
this.#parent.moveBefore(e, t < members.length ? members[t] : null);
|
|
302
|
+
}
|
|
303
|
+
});
|
|
304
|
+
}
|
|
305
|
+
/** Alias of `move`. */
|
|
306
|
+
insert(e, key, index) {
|
|
307
|
+
this.move(e, key, index);
|
|
308
|
+
}
|
|
309
|
+
/** Remove `e` from the source through the chain. */
|
|
310
|
+
remove(e) {
|
|
311
|
+
this.#parent.remove(e);
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
/** `field === value`, assertable by writing the field; for `filter` predicates. */
|
|
315
|
+
export function is(field, value) {
|
|
316
|
+
const p = ((c) => Object.is(field(c).value, value));
|
|
317
|
+
p.assert = c => {
|
|
318
|
+
field(c).value = value;
|
|
319
|
+
};
|
|
320
|
+
return p;
|
|
321
|
+
}
|
|
322
|
+
/** Conjunction of cell predicates; asserts every clause. */
|
|
323
|
+
export function allPass(...preds) {
|
|
324
|
+
const p = ((c) => preds.every(q => q(c)));
|
|
325
|
+
p.assert = c => {
|
|
326
|
+
for (const q of preds)
|
|
327
|
+
q.assert?.(c);
|
|
328
|
+
};
|
|
329
|
+
return p;
|
|
330
|
+
}
|
|
331
|
+
/** Writable `Arr<T>` from values and/or cells (values are wrapped in fresh
|
|
332
|
+
* cells; cells pass through by identity). */
|
|
333
|
+
export function arr(items = []) {
|
|
334
|
+
const cells = [...items].map(x => (x instanceof Cell ? x : cell(x)));
|
|
335
|
+
return new Arr(cells);
|
|
336
|
+
}
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import { Cell, type Init, type Val, type Writable } from "../cell.js";
|
|
2
2
|
import { Num } from "./num.js";
|
|
3
|
-
/** Clip header
|
|
3
|
+
/** Clip header; the graph compares `epoch`. One `pcm` buffer per channel. */
|
|
4
4
|
export interface AudioClip {
|
|
5
5
|
readonly pcm: readonly Float32Array[];
|
|
6
6
|
readonly sampleRate: number;
|
|
7
7
|
readonly epoch: number;
|
|
8
8
|
}
|
|
9
9
|
type V = AudioClip;
|
|
10
|
-
/** Stamp
|
|
10
|
+
/** Stamp buffers with a fresh epoch — the only way to mint a `Clip` value. */
|
|
11
11
|
export declare const stamp: (pcm: readonly Float32Array[], sampleRate: number) => V;
|
|
12
12
|
export declare const equals: (a: V, b: V) => boolean;
|
|
13
13
|
export declare class Audio extends Cell<V> {
|
|
@@ -16,18 +16,17 @@ export declare class Audio extends Cell<V> {
|
|
|
16
16
|
};
|
|
17
17
|
readonly _t: typeof Audio.traits;
|
|
18
18
|
constructor(v?: V);
|
|
19
|
-
/** Time-reverse every channel.
|
|
19
|
+
/** Time-reverse every channel. */
|
|
20
20
|
reverse(): this;
|
|
21
|
-
/** Scalar gain. Invertible while k ≠ 0
|
|
21
|
+
/** Scalar gain. Invertible while k ≠ 0. */
|
|
22
22
|
gain(k: Val<number>): this;
|
|
23
|
-
/** Peak-normalize to
|
|
24
|
-
*
|
|
25
|
-
* the backward pass restores it. The audio analog of str.lowercase(). */
|
|
23
|
+
/** Peak-normalize to `target` (default 1). The complement stores the original
|
|
24
|
+
* peak so a write-back restores it. */
|
|
26
25
|
normalize(target?: Val<number>): Writable<Audio>;
|
|
27
26
|
/** RMS loudness as a writable `Num`; writing rescales the clip to hit it. */
|
|
28
27
|
rms(): Writable<Num>;
|
|
29
28
|
}
|
|
30
|
-
/** Writable `Audio
|
|
31
|
-
*
|
|
29
|
+
/** Writable `Audio` from a `Clip` (new cell) or existing writable (passed
|
|
30
|
+
* through). */
|
|
32
31
|
export declare function audio(v: Init<Audio>): Writable<Audio>;
|
|
33
32
|
export {};
|
|
@@ -1,22 +1,7 @@
|
|
|
1
|
-
// audio.ts — reactive audio clip (handle-as-value), the sound twin of canvas.ts.
|
|
2
|
-
//
|
|
3
|
-
// A Clip is context-free PCM: one Float32Array per channel plus a sample rate.
|
|
4
|
-
// The graph transports only the tiny header {pcm, sampleRate, epoch} and compares
|
|
5
|
-
// the monotonic epoch, so propagation never scans a sample and nothing is copied
|
|
6
|
-
// across the bus (same handle-as-value rule as Canvas, minus the GL context — PCM
|
|
7
|
-
// needs no ambient AudioContext, that only appears at the playback sink).
|
|
8
|
-
//
|
|
9
|
-
// Tiers mirror the rest of values/:
|
|
10
|
-
// - pure isomorphisms (`reverse`) — one pass each way.
|
|
11
|
-
// - reactive-param invertible (`gain(k)`) — reads `Val<number>`.
|
|
12
|
-
// - complement projection (`normalize`) — the lossy view (peak-scaled) plus a
|
|
13
|
-
// complement (the original peak) recovered on write-back, the audio analog of
|
|
14
|
-
// str.lowercase()/canvas.grayscale().
|
|
15
|
-
// - cross-type lens (`rms` → Num).
|
|
16
1
|
import { Cell, reader } from "../cell.js";
|
|
17
2
|
import { Num } from "./num.js";
|
|
18
3
|
let EPOCH = 0;
|
|
19
|
-
/** Stamp
|
|
4
|
+
/** Stamp buffers with a fresh epoch — the only way to mint a `Clip` value. */
|
|
20
5
|
export const stamp = (pcm, sampleRate) => ({
|
|
21
6
|
pcm,
|
|
22
7
|
sampleRate,
|
|
@@ -55,7 +40,7 @@ export class Audio extends Cell {
|
|
|
55
40
|
constructor(v = { pcm: [], sampleRate: 44100, epoch: 0 }) {
|
|
56
41
|
super(v, { equals });
|
|
57
42
|
}
|
|
58
|
-
/** Time-reverse every channel.
|
|
43
|
+
/** Time-reverse every channel. */
|
|
59
44
|
reverse() {
|
|
60
45
|
const run = (v) => stamp(v.pcm.map(ch => {
|
|
61
46
|
const o = new Float32Array(ch.length);
|
|
@@ -65,27 +50,25 @@ export class Audio extends Cell {
|
|
|
65
50
|
}), v.sampleRate);
|
|
66
51
|
return this.lens(run, run);
|
|
67
52
|
}
|
|
68
|
-
/** Scalar gain. Invertible while k ≠ 0
|
|
53
|
+
/** Scalar gain. Invertible while k ≠ 0. */
|
|
69
54
|
gain(k) {
|
|
70
55
|
const kf = reader(k);
|
|
71
56
|
return this.lens(v => scaled(v, kf()), n => scaled(n, 1 / kf()));
|
|
72
57
|
}
|
|
73
|
-
/** Peak-normalize to
|
|
74
|
-
*
|
|
75
|
-
* the backward pass restores it. The audio analog of str.lowercase(). */
|
|
58
|
+
/** Peak-normalize to `target` (default 1). The complement stores the original
|
|
59
|
+
* peak so a write-back restores it. */
|
|
76
60
|
normalize(target = 1) {
|
|
77
61
|
const tf = reader(target);
|
|
78
62
|
const self = this;
|
|
79
|
-
return Audio.lens(
|
|
80
|
-
init:
|
|
81
|
-
|
|
82
|
-
fwd: ([s]) => {
|
|
63
|
+
return Audio.lens(self, {
|
|
64
|
+
init: s => peak(s),
|
|
65
|
+
fwd: s => {
|
|
83
66
|
const p = peak(s);
|
|
84
67
|
return p === 0 ? s : scaled(s, tf() / p);
|
|
85
68
|
},
|
|
86
69
|
bwd: (view, _src, c) => {
|
|
87
70
|
const t = tf();
|
|
88
|
-
return {
|
|
71
|
+
return { update: t === 0 ? view : scaled(view, c / t), complement: c };
|
|
89
72
|
},
|
|
90
73
|
});
|
|
91
74
|
}
|
|
@@ -98,8 +81,8 @@ export class Audio extends Cell {
|
|
|
98
81
|
});
|
|
99
82
|
}
|
|
100
83
|
}
|
|
101
|
-
/** Writable `Audio
|
|
102
|
-
*
|
|
84
|
+
/** Writable `Audio` from a `Clip` (new cell) or existing writable (passed
|
|
85
|
+
* through). */
|
|
103
86
|
export function audio(v) {
|
|
104
87
|
if (v instanceof Audio)
|
|
105
88
|
return v;
|
|
@@ -13,25 +13,25 @@ export declare class Bool extends Cell<V> {
|
|
|
13
13
|
};
|
|
14
14
|
readonly _t: typeof Bool.traits;
|
|
15
15
|
constructor(v?: V);
|
|
16
|
-
/** Logical negation.
|
|
16
|
+
/** Logical negation. */
|
|
17
17
|
not(): this;
|
|
18
|
-
/**
|
|
19
|
-
* `a ^ b = c ↔ a = c ^ b`. The F₂ analog of `Num#add`. */
|
|
18
|
+
/** True when exactly one side is true (XOR); its own inverse. */
|
|
20
19
|
xor(b: Val<V>): this;
|
|
21
|
-
/**
|
|
22
|
-
*
|
|
20
|
+
/** True when both sides are true. Read-only — a write can't be split
|
|
21
|
+
* between the two inputs. */
|
|
23
22
|
and(b: Val<V>): Bool;
|
|
23
|
+
/** True when either side is true. */
|
|
24
24
|
or(b: Val<V>): Bool;
|
|
25
|
-
/** `this
|
|
25
|
+
/** True unless `this` is true and `b` is false (`this → b`). */
|
|
26
26
|
implies(b: Val<V>): Bool;
|
|
27
|
-
/**
|
|
27
|
+
/** True when both sides match (XNOR). */
|
|
28
28
|
eq(b: Val<V>): Bool;
|
|
29
|
+
/** True unless both sides are true (NAND). */
|
|
29
30
|
nand(b: Val<V>): Bool;
|
|
31
|
+
/** True when both sides are false (NOR). */
|
|
30
32
|
nor(b: Val<V>): Bool;
|
|
31
33
|
}
|
|
32
|
-
/** Writable `Bool
|
|
33
|
-
*
|
|
34
|
-
* use `Bool.derive(...)` for reactive RO tracking, or `cell.value` to
|
|
35
|
-
* snapshot. */
|
|
34
|
+
/** Writable `Bool` from a literal (new cell) or existing writable (passed
|
|
35
|
+
* through). For read-only sources use `Bool.derive`. */
|
|
36
36
|
export declare function bool(v?: Init<Bool>): Writable<Bool>;
|
|
37
37
|
export {};
|
package/dist/core/values/bool.js
CHANGED
|
@@ -1,20 +1,10 @@
|
|
|
1
|
-
// bool.ts — reactive boolean.
|
|
2
|
-
//
|
|
3
|
-
// Invertibles ride the plain endo `.lens(fwd, bwd)`: `not()` (involution,
|
|
4
|
-
// `.not().not()` round-trips to identity) and `xor(b)` (its own inverse;
|
|
5
|
-
// `a ^ b = c ↔ a = c ^ b`). xor carries Bool's `linear` trait.
|
|
6
|
-
//
|
|
7
|
-
// `and` / `or` / `implies` / `eq` / `nand` / `nor` return bare RO `Bool`
|
|
8
|
-
// — lossy fan-ins whose write-back is ambiguous. Lift to writable via
|
|
9
|
-
// `Bool.lens([a, b], fwd, bwd)` with an explicit policy.
|
|
10
1
|
import { Cell, reader } from "../cell.js";
|
|
11
2
|
export const not = (a) => !a;
|
|
12
3
|
export const and = (a, b) => a && b;
|
|
13
4
|
export const or = (a, b) => a || b;
|
|
14
5
|
export const xor = (a, b) => a !== b;
|
|
15
6
|
export const equals = (a, b) => a === b;
|
|
16
|
-
// F₂-linear
|
|
17
|
-
// scale-by-integer collapses by parity (even k → false, odd k → a).
|
|
7
|
+
// F₂-linear: xor is both add and sub; scale collapses by parity.
|
|
18
8
|
const linearImpl = {
|
|
19
9
|
add: xor,
|
|
20
10
|
sub: xor,
|
|
@@ -25,49 +15,49 @@ export class Bool extends Cell {
|
|
|
25
15
|
constructor(v = false) {
|
|
26
16
|
super(v, { equals });
|
|
27
17
|
}
|
|
28
|
-
/** Logical negation.
|
|
18
|
+
/** Logical negation. */
|
|
29
19
|
not() {
|
|
30
20
|
return this.lens(not, not);
|
|
31
21
|
}
|
|
32
|
-
/**
|
|
33
|
-
* `a ^ b = c ↔ a = c ^ b`. The F₂ analog of `Num#add`. */
|
|
22
|
+
/** True when exactly one side is true (XOR); its own inverse. */
|
|
34
23
|
xor(b) {
|
|
35
24
|
const bf = reader(b);
|
|
36
25
|
return this.lens(v => v !== bf(), n => n !== bf());
|
|
37
26
|
}
|
|
38
|
-
/**
|
|
39
|
-
*
|
|
27
|
+
/** True when both sides are true. Read-only — a write can't be split
|
|
28
|
+
* between the two inputs. */
|
|
40
29
|
and(b) {
|
|
41
30
|
const bf = reader(b);
|
|
42
31
|
return Bool.derive(() => this.value && bf());
|
|
43
32
|
}
|
|
33
|
+
/** True when either side is true. */
|
|
44
34
|
or(b) {
|
|
45
35
|
const bf = reader(b);
|
|
46
36
|
return Bool.derive(() => this.value || bf());
|
|
47
37
|
}
|
|
48
|
-
/** `this
|
|
38
|
+
/** True unless `this` is true and `b` is false (`this → b`). */
|
|
49
39
|
implies(b) {
|
|
50
40
|
const bf = reader(b);
|
|
51
41
|
return Bool.derive(() => !this.value || bf());
|
|
52
42
|
}
|
|
53
|
-
/**
|
|
43
|
+
/** True when both sides match (XNOR). */
|
|
54
44
|
eq(b) {
|
|
55
45
|
const bf = reader(b);
|
|
56
46
|
return Bool.derive(() => this.value === bf());
|
|
57
47
|
}
|
|
48
|
+
/** True unless both sides are true (NAND). */
|
|
58
49
|
nand(b) {
|
|
59
50
|
const bf = reader(b);
|
|
60
51
|
return Bool.derive(() => !(this.value && bf()));
|
|
61
52
|
}
|
|
53
|
+
/** True when both sides are false (NOR). */
|
|
62
54
|
nor(b) {
|
|
63
55
|
const bf = reader(b);
|
|
64
56
|
return Bool.derive(() => !(this.value || bf()));
|
|
65
57
|
}
|
|
66
58
|
}
|
|
67
|
-
/** Writable `Bool
|
|
68
|
-
*
|
|
69
|
-
* use `Bool.derive(...)` for reactive RO tracking, or `cell.value` to
|
|
70
|
-
* snapshot. */
|
|
59
|
+
/** Writable `Bool` from a literal (new cell) or existing writable (passed
|
|
60
|
+
* through). For read-only sources use `Bool.derive`. */
|
|
71
61
|
export function bool(v = false) {
|
|
72
62
|
if (v instanceof Bool)
|
|
73
63
|
return v;
|
|
@@ -15,20 +15,19 @@ export declare const sub: (a: V, b: V) => V;
|
|
|
15
15
|
export declare const scale: (a: V, k: number) => V;
|
|
16
16
|
export declare const lerp: (a: V, b: V, t: number) => V;
|
|
17
17
|
export declare const equals: (a: V, b: V) => boolean;
|
|
18
|
-
/**
|
|
18
|
+
/** Euclidean distance over (x, y, w, h). */
|
|
19
19
|
export declare const metric: (a: V, b: V) => number;
|
|
20
20
|
export declare const expand: (b: V, n: number) => V;
|
|
21
21
|
export declare const contains: (b: V, p: Inner<Vec>) => boolean;
|
|
22
|
-
/** Closest point inside `b` to `p
|
|
23
|
-
*
|
|
22
|
+
/** Closest point inside `b` to `p`; already-inside is identity, outside snaps
|
|
23
|
+
* to the nearest boundary point. */
|
|
24
24
|
export declare const clampToBox: (p: Inner<Vec>, b: V) => Inner<Vec>;
|
|
25
|
-
/** Closest point strictly outside `b` to `p`, displaced past the nearest
|
|
26
|
-
*
|
|
27
|
-
* false-side bwd. */
|
|
25
|
+
/** Closest point strictly outside `b` to `p`, displaced past the nearest edge
|
|
26
|
+
* by `eps`; already-outside is identity. */
|
|
28
27
|
export declare const ejectFromBox: (p: Inner<Vec>, b: V, eps?: number) => Inner<Vec>;
|
|
29
28
|
/** Bounding box around a set of boxes. */
|
|
30
29
|
export declare function union(...bs: V[]): V;
|
|
31
|
-
/** Perimeter point on a
|
|
30
|
+
/** Perimeter point on a box facing `toward`. */
|
|
32
31
|
export declare function edgeFrom(b: V, toward: Inner<Vec>): Inner<Vec>;
|
|
33
32
|
export declare class Box extends Cell<V> {
|
|
34
33
|
static traits: {
|
|
@@ -45,32 +44,28 @@ export declare class Box extends Cell<V> {
|
|
|
45
44
|
scale(k: Val<number>): this;
|
|
46
45
|
expand(n: Val<number>): this;
|
|
47
46
|
lerp(b: Val<V>, t: Val<number>): Box;
|
|
48
|
-
/**
|
|
49
|
-
*
|
|
50
|
-
*
|
|
51
|
-
* nearest edge by `eps`. Literal / RO inputs yield a bare RO `Bool`. */
|
|
47
|
+
/** True when `p` is inside the box. A writable `Vec` yields a `Writable<Bool>`:
|
|
48
|
+
* flipping it clamps `p` to the nearest in-box point (`true`) or ejects it
|
|
49
|
+
* past the nearest edge (`false`). Literal/RO inputs yield a read-only `Bool`. */
|
|
52
50
|
contains<P extends Val<Inner<Vec>>>(p: P): P extends WritableBrand ? Writable<Bool> : Bool;
|
|
53
51
|
get x(): this extends WritableBrand ? Writable<Num> : Num;
|
|
54
52
|
get y(): this extends WritableBrand ? Writable<Num> : Num;
|
|
55
53
|
get w(): this extends WritableBrand ? Writable<Num> : Num;
|
|
56
54
|
get h(): this extends WritableBrand ? Writable<Num> : Num;
|
|
57
55
|
get area(): Num;
|
|
58
|
-
/** Vec at parametric (u, v)
|
|
59
|
-
*
|
|
60
|
-
* (`.center`, `.top`, …) for stable identity. */
|
|
56
|
+
/** Vec at parametric `(u, v)` in `[0,1]²`. Not memoised; use the named edge
|
|
57
|
+
* getters (`.center`, `.top`, …) for stable identity. */
|
|
61
58
|
at(u: number, v: number): Vec;
|
|
62
59
|
get center(): Vec;
|
|
63
60
|
get top(): Vec;
|
|
64
61
|
get bottom(): Vec;
|
|
65
62
|
get left(): Vec;
|
|
66
63
|
get right(): Vec;
|
|
67
|
-
/** Tween-builder
|
|
64
|
+
/** Tween-builder. */
|
|
68
65
|
to(this: Writable<Box>, target: V, dur: Val<number>, ease?: Easing): Tween<V>;
|
|
69
66
|
}
|
|
70
|
-
/** Writable `Box` at `(x, y, w, h)`. Each component is a literal
|
|
71
|
-
*
|
|
72
|
-
*
|
|
73
|
-
* `Box.derive(...)` for reactive RO tracking, or `cell.value` to
|
|
74
|
-
* snapshot. Lock a component with `Num.pin(c)`. */
|
|
67
|
+
/** Writable `Box` at `(x, y, w, h)`. Each component is a literal (new cell) or
|
|
68
|
+
* existing writable (passed through); for read-only sources use `Box.derive`.
|
|
69
|
+
* Lock a component with `Num.pin`. */
|
|
75
70
|
export declare function box(x?: Init<Num>, y?: Init<Num>, w?: Init<Num>, h?: Init<Num>): Writable<Box>;
|
|
76
71
|
export {};
|