@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.
Files changed (52) hide show
  1. package/CHANGELOG.md +35 -0
  2. package/README.md +1 -1
  3. package/dist/ahead.js +5 -4
  4. package/dist/chain.js +2 -3
  5. package/dist/color.d.ts +36 -14
  6. package/dist/color.js +186 -58
  7. package/dist/columns.d.ts +1 -11
  8. package/dist/columns.js +1 -6
  9. package/dist/crowd.d.ts +0 -6
  10. package/dist/crowd.js +0 -11
  11. package/dist/cue.js +7 -4
  12. package/dist/everyone.d.ts +17 -6
  13. package/dist/everyone.js +17 -8
  14. package/dist/fade.d.ts +11 -0
  15. package/dist/fade.js +44 -8
  16. package/dist/fold.js +4 -7
  17. package/dist/held.js +3 -4
  18. package/dist/hosts.d.ts +7 -0
  19. package/dist/hosts.js +18 -5
  20. package/dist/index.d.ts +1 -1
  21. package/dist/index.js +1 -1
  22. package/dist/lanes.d.ts +26 -1
  23. package/dist/lanes.js +7 -0
  24. package/dist/lch.d.ts +23 -0
  25. package/dist/lch.js +126 -0
  26. package/dist/mixer.d.ts +4 -1
  27. package/dist/mixer.js +3 -0
  28. package/dist/motions.d.ts +4 -1
  29. package/dist/motions.js +16 -2
  30. package/dist/move.js +15 -10
  31. package/dist/owner.d.ts +7 -2
  32. package/dist/owner.js +13 -3
  33. package/dist/project.js +3 -8
  34. package/dist/pull.d.ts +11 -0
  35. package/dist/pull.js +10 -6
  36. package/dist/qualify.js +1 -1
  37. package/dist/roles.js +1 -3
  38. package/dist/roster.d.ts +6 -0
  39. package/dist/roster.js +8 -1
  40. package/dist/sample.js +2 -1
  41. package/dist/seek.js +1 -13
  42. package/dist/shared.d.ts +1 -1
  43. package/dist/shared.js +1 -1
  44. package/dist/spans.d.ts +4 -1
  45. package/dist/spans.js +6 -3
  46. package/dist/touch.js +1 -2
  47. package/dist/turns.js +2 -1
  48. package/dist/types/voice.d.ts +3 -2
  49. package/dist/voice.d.ts +19 -0
  50. package/dist/voice.js +35 -1
  51. package/docs/schema.html +37 -11
  52. package/package.json +1 -1
package/dist/everyone.js CHANGED
@@ -1,6 +1,7 @@
1
1
  import { headOf } from './chain.js';
2
2
  import { numericOf } from './channels.js';
3
3
  import { record } from './record.js';
4
+ import { detour } from './roster.js';
4
5
  /**
5
6
  * Whether a voice can keep one record for every subject, decided when it is cued. A record holds
6
7
  * what a voice knows of one subject; where the voice is over every subject with no stagger, keeps
@@ -263,17 +264,25 @@ export function unshare(mix, voice, kept = false, keeper) {
263
264
  detour(mix);
264
265
  mix.lanes?.refill();
265
266
  }
266
- /**
267
- * Counts what makes a mix's fold more than a walk of each chain: a role, a voice sharing one
268
- * record, a voice that stopped sharing this frame. Called wherever one of the three changes, so
269
- * `foldWith` asks one number.
270
- */
271
- export function detour(mix) {
272
- mix.detours = mix.roleVoices + mix.sharers.length + (mix.unshared ? 1 : 0);
273
- }
274
267
  /** Gives a record just made for a subject a sharing voice had seen what the voice kept for it. */
275
268
  export function inherit(voice, held, slot) {
276
269
  held.weight = weightAt(voice, slot);
277
270
  open(voice, held, slot);
278
271
  unsight(voice, slot);
279
272
  }
273
+ /** What the general path calls on a sharing voice, reached through `Lanes`, so a lean bundle leaves it out. */
274
+ export const share = {
275
+ shares,
276
+ outgrow,
277
+ everyoneOf,
278
+ sighted,
279
+ sight,
280
+ weightAt,
281
+ weighAt,
282
+ open,
283
+ shut,
284
+ restamp,
285
+ unsight,
286
+ unshare,
287
+ inherit,
288
+ };
package/dist/fade.d.ts CHANGED
@@ -30,7 +30,13 @@ export declare function beginFade<I, O>(mix: Mixer<I, O>, voice: Voice<I, O>, op
30
30
  at?: 'rest';
31
31
  deadline?: number;
32
32
  back?: boolean;
33
+ stays?: boolean;
33
34
  }, at?: number): void;
35
+ /**
36
+ * Keeps a voice faded at the end of its passes with its owner, live and quiet, rather than letting
37
+ * it leave; the owner leaves at `at` if it holds no child still playing.
38
+ */
39
+ export declare function stay<I, O>(mix: Mixer<I, O>, voice: Voice<I, O>, at: number): void;
34
40
  /**
35
41
  * Turns a fading voice around: it climbs back from where its fade out had got to, up the same
36
42
  * curve, at the whole curve per `over` ms. A fade waiting for rest is taken back at once. A voice
@@ -44,5 +50,10 @@ export declare function beginRise<I, O>(mix: Mixer<I, O>, voice: Voice<I, O>, op
44
50
  * exactly there; at or behind now, the fade began then and is partway.
45
51
  */
46
52
  export declare function fadeAt<I, O>(mix: Mixer<I, O>, voice: Voice<I, O>, at: number, over: number | undefined): void;
53
+ /**
54
+ * Brings back a voice kept quiet past its passes, its clock back in them by mix time `now`: it
55
+ * climbs from nothing over the fade it left over, from when its clock reached them.
56
+ */
57
+ export declare function rouse<I, O>(mix: Mixer<I, O>, voice: Voice<I, O>, now: number): void;
47
58
  /** Removes a voice, recording that it left at `at`, default now. */
48
59
  export declare function retire<I, O>(mix: Mixer<I, O>, voice: Voice<I, O>, at?: number): void;
package/dist/fade.js CHANGED
@@ -1,10 +1,9 @@
1
1
  import { changed, headOf, unlink } from './chain.js';
2
2
  import { envelope } from './clock.js';
3
3
  import { schedule } from './due.js';
4
- import { unshare, unsight } from './everyone.js';
5
4
  import { leave, noted, releasing } from './history.js';
6
5
  import { markOf } from './marks.js';
7
- import { orphan, ownerReading } from './owner.js';
6
+ import { orphan, ownerReading, plays } from './owner.js';
8
7
  import { startOf } from './place.js';
9
8
  import { isRested } from './record.js';
10
9
  import { scoreTouched } from './scored.js';
@@ -14,7 +13,7 @@ import { unreach } from './unreached.js';
14
13
  export function fadeSubject(mix, voice, subject, over) {
15
14
  if (voice.state === 'done' || voice.parted?.has(subject) || voice.parts?.has(subject))
16
15
  return;
17
- unshare(mix, voice);
16
+ mix.share?.unshare(mix, voice);
18
17
  mix.stir();
19
18
  const ms = mix.reduced ? 0 : (over ?? voice.fade.out ?? 0);
20
19
  const at = Number.isNaN(mix.now) ? startOf(voice) : mix.now;
@@ -104,7 +103,7 @@ function forgetIn(mix, voice, subject) {
104
103
  if (head !== undefined && head.slot >= 0) {
105
104
  unreach(voice, head.slot, false);
106
105
  // What the voice kept for the subject while it shared one record, which `weightOf` reads.
107
- unsight(voice, head.slot);
106
+ mix.share?.unsight(voice, head.slot);
108
107
  }
109
108
  const held = voice.subjects.get(subject);
110
109
  if (held !== undefined) {
@@ -135,7 +134,7 @@ export function beginFade(mix, voice, opts, at) {
135
134
  // A fade at rest marks each subject's record as it comes to rest, at its next read: one already
136
135
  // read this frame is read again, as after `touch`.
137
136
  if (opts.at === 'rest') {
138
- unshare(mix, voice);
137
+ mix.share?.unshare(mix, voice);
139
138
  voice.seeks++;
140
139
  }
141
140
  voice.state = 'fading';
@@ -148,14 +147,36 @@ export function beginFade(mix, voice, opts, at) {
148
147
  };
149
148
  if (opts.back)
150
149
  voice.out.back = true;
150
+ if (opts.stays)
151
+ voice.out.stays = true;
151
152
  noted(mix, voice);
152
153
  // A fade at rest, from a voice's start, or of a voice yet to start takes it off its lane.
153
154
  if (opts.at === 'rest' || opts.back || waiting)
154
155
  mix.lanes?.invalidate();
155
156
  else
156
157
  mix.lanes?.refill();
157
- if (over === 0 && opts.at !== 'rest')
158
- retire(mix, voice, voice.out.at);
158
+ if (over === 0 && opts.at !== 'rest') {
159
+ if (opts.stays)
160
+ stay(mix, voice, voice.out.at);
161
+ else
162
+ retire(mix, voice, voice.out.at);
163
+ }
164
+ }
165
+ /**
166
+ * Keeps a voice faded at the end of its passes with its owner, live and quiet, rather than letting
167
+ * it leave; the owner leaves at `at` if it holds no child still playing.
168
+ */
169
+ export function stay(mix, voice, at) {
170
+ voice.state = 'live';
171
+ mix.lanes?.refill();
172
+ const owner = voice.owner;
173
+ if (owner.state === 'done')
174
+ return;
175
+ const now = Number.isNaN(mix.now) ? Number.NEGATIVE_INFINITY : mix.now;
176
+ if (!plays(owner, now) && !lingers(owner, at))
177
+ retire(mix, owner, at);
178
+ else if (owner.fitting !== null && !voice.skipped)
179
+ refit(mix, owner, at);
159
180
  }
160
181
  /**
161
182
  * Turns a fading voice around: it climbs back from where its fade out had got to, up the same
@@ -211,6 +232,21 @@ export function fadeAt(mix, voice, at, over) {
211
232
  voice.outSet = true;
212
233
  noted(mix, voice);
213
234
  }
235
+ /**
236
+ * Brings back a voice kept quiet past its passes, its clock back in them by mix time `now`: it
237
+ * climbs from nothing over the fade it left over, from when its clock reached them.
238
+ */
239
+ export function rouse(mix, voice, now) {
240
+ const out = voice.out;
241
+ const edge = out.back === true ? voice.soonest : voice.span + voice.latest;
242
+ const reached = voice.reaches(edge);
243
+ const at = Math.max(out.at + out.over, reached <= now ? reached : now);
244
+ const over = mix.reduced ? 0 : out.over;
245
+ voice.out = null;
246
+ voice.back = over > 0 ? { from: 0, at, over } : null;
247
+ noted(mix, voice);
248
+ mix.lanes?.refill();
249
+ }
214
250
  /** Whether a motion patch still asks this voice of this mix for its time. */
215
251
  function asks(mix, motion, voice) {
216
252
  return motion.owner === mix.owner && motion.ownerId === voice.id;
@@ -248,7 +284,7 @@ export function retire(mix, voice, at) {
248
284
  if (voice.owner !== null) {
249
285
  const span = voice.owner;
250
286
  leaving(mix, voice);
251
- const owner = orphan(voice);
287
+ const owner = orphan(voice, now);
252
288
  if (owner !== null && !lingers(owner, voice.doneAt))
253
289
  retire(mix, owner, voice.doneAt);
254
290
  // A child the fit skipped had no time in it, so the rest of the fit stands: fitting again
package/dist/fold.js CHANGED
@@ -1,10 +1,7 @@
1
1
  import { foldNumber } from './channels.js';
2
2
  import { copy as copyValue, first } from './clone.js';
3
- import { open as openRecord, shut as shutRecord, weighAt } from './everyone.js';
4
3
  // V8 reads a local const as a constant, where it reads an import from its module on every call.
5
4
  const copy = copyValue;
6
- const open = openRecord;
7
- const shut = shutRecord;
8
5
  /** A record's band state, made when a rest-less channel first asks. */
9
6
  function bandsFor(held, n) {
10
7
  const bands = new Uint8Array(n);
@@ -107,9 +104,9 @@ export function shared(voice, slot, rec, q) {
107
104
  // A projection numbers no subject, and keeps nothing by subject.
108
105
  if (slot < 0)
109
106
  return q;
110
- const cap = this.lanes.cap;
111
- weighAt(voice, slot, rec.weight, cap);
112
- shut(voice, rec, slot, cap);
107
+ const lanes = this.lanes;
108
+ this.share.weighAt(voice, slot, rec.weight, lanes.cap);
109
+ this.share.shut(voice, rec, slot, lanes.cap);
113
110
  return q;
114
111
  }
115
112
  const sharers = this.sharers;
@@ -265,7 +262,7 @@ export function foldSharers(pose, subject, head, q, before, laned, dry, except,
265
262
  continue;
266
263
  if (voice.id === except || voice.state === 'done')
267
264
  continue;
268
- open(voice, rec, head.slot);
265
+ this.share.open(voice, rec, head.slot);
269
266
  const delta = this.read(voice, subject, now, dry, rec);
270
267
  if (owed >= 0 && voice.laned)
271
268
  this.lanes.paid(voice.id, owed, rec.weight);
package/dist/held.js CHANGED
@@ -2,7 +2,6 @@ import { changed } from './chain.js';
2
2
  import { stepOf } from './clock.js';
3
3
  import { delayOf } from './delay.js';
4
4
  import { schedule } from './due.js';
5
- import { everyoneOf, inherit, sighted } from './everyone.js';
6
5
  import { originOf } from './origin.js';
7
6
  import { startOf } from './place.js';
8
7
  import { record } from './record.js';
@@ -24,7 +23,7 @@ export function unreachedOf(voice, now) {
24
23
  */
25
24
  export function held(voice, subject, slot, guess = null) {
26
25
  if (voice.sharing) {
27
- const all = everyoneOf(this, voice);
26
+ const all = this.share.everyoneOf(this, voice);
28
27
  if (all !== null)
29
28
  return all;
30
29
  }
@@ -67,8 +66,8 @@ export function held(voice, subject, slot, guess = null) {
67
66
  voice.subjects.set(subject, held);
68
67
  waits(voice, held, now);
69
68
  // A subject a voice saw while it shared one record was counted then.
70
- if (sighted(voice, slot))
71
- inherit(voice, held, slot);
69
+ if (voice.sighted !== null && this.share?.sighted(voice, slot))
70
+ this.share.inherit(voice, held, slot);
72
71
  else
73
72
  voice.seen++;
74
73
  if (delay > voice.latest) {
package/dist/hosts.d.ts CHANGED
@@ -4,6 +4,13 @@ import type { Booker, BookOptions, Handle } from './types.js';
4
4
  import type { Voice } from './voice.js';
5
5
  export declare function book<I, O>(mix: Mixer<I, O>, opts: BookOptions<unknown, I>): Booker;
6
6
  export declare function laneHost<I, O>(mix: Mixer<I, O>): LaneHost<I, O>;
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. A motion going round
11
+ * an `ink` color's hue stays out, since a crowd solves its stretches straight across each axis.
12
+ */
13
+ export declare function crowdable<I, O>(v: Voice<I, O>): boolean;
7
14
  /**
8
15
  * Whether a voice's patch and spec can run on a lane, its channels aside. A voice whose patch has
9
16
  * kept state on a record through `setting.keep` is stateful from then on, and leaves its lane;
package/dist/hosts.js CHANGED
@@ -3,9 +3,7 @@ import { Book } from './book.js';
3
3
  import { headOf } from './chain.js';
4
4
  import { numericOf } from './channels.js';
5
5
  import { stepOf } from './clock.js';
6
- import { crowdable } from './crowd.js';
7
6
  import { schedule } from './due.js';
8
- import { unshare, unsight, weightAt } from './everyone.js';
9
7
  import { beginFade, beginRise, fadeAt, fadeSubject } from './fade.js';
10
8
  import { goneIndex } from './gone.js';
11
9
  import { VoiceHandle } from './handle.js';
@@ -67,7 +65,7 @@ export function laneHost(mix) {
67
65
  for (const voice of mix.general) {
68
66
  unreach(voice, slot, false);
69
67
  if (voice.sighted !== null)
70
- unsight(voice, slot);
68
+ mix.share?.unsight(voice, slot);
71
69
  }
72
70
  for (const voice of goneIndex(mix).general)
73
71
  unreach(voice, slot, false);
@@ -110,6 +108,19 @@ export function laneHost(mix) {
110
108
  kept: (voice) => stateful(mix, voice),
111
109
  };
112
110
  }
111
+ /**
112
+ * Whether a laned voice belongs in a crowd: one naming a single subject, which shares nothing
113
+ * across subjects for a lane to save. A motion voice writes one channel; a keys or fn voice may
114
+ * write several, and joins the crowd of voices writing those same channels. A motion going round
115
+ * an `ink` color's hue stays out, since a crowd solves its stretches straight across each axis.
116
+ */
117
+ export function crowdable(v) {
118
+ return (v.named !== null &&
119
+ v.named.size === 1 &&
120
+ v.spec.role === undefined &&
121
+ (v.slots.length === 1 || v.motion === undefined) &&
122
+ v.motion?.round !== true);
123
+ }
113
124
  /**
114
125
  * Whether a voice's patch and spec can run on a lane, its channels aside. A voice whose patch has
115
126
  * kept state on a record through `setting.keep` is stateful from then on, and leaves its lane;
@@ -181,7 +192,7 @@ export function stateful(mix, voice) {
181
192
  voice.keeping = true;
182
193
  // The call that kept state was for the subject last sent to, whose record the state is on.
183
194
  if (voice.sharing)
184
- unshare(mix, voice, true, mix.sending.subject);
195
+ mix.share?.unshare(mix, voice, true, mix.sending.subject);
185
196
  mix.lanes?.invalidate();
186
197
  }
187
198
  export function handle(mix, voice) {
@@ -248,7 +259,9 @@ function handleHost(mix) {
248
259
  ? undefined
249
260
  : voice.subjects.get(subject);
250
261
  // A voice that stopped sharing makes a subject's record at its next read, from what it kept.
251
- return held !== undefined ? held.weight : weightAt(voice, slot);
262
+ if (held !== undefined)
263
+ return held.weight;
264
+ return mix.share === null ? 0 : mix.share.weightAt(voice, slot);
252
265
  },
253
266
  taping: () => taping(mix),
254
267
  record: (h, label, again) => record(mix, label, (stage) => {
package/dist/index.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  export type { LastOptions, NumberOptions, NumberTuple, Vec } from './channels.js';
2
2
  export { kit, last, max, mul, sum, vec } from './channels.js';
3
3
  export type { ColorOptions } from './color.js';
4
- export { color, css, mixHex, oklab, toHex } from './color.js';
4
+ export { color, css, ink, mixHex, oklab, toHex } from './color.js';
5
5
  export type { Curve } from './easing.js';
6
6
  export { curve } from './easing.js';
7
7
  export type { Claim, Fit, FitPlan, Layout, Order, OverrunOptions, SpanClaim, Strength, } from './fit.js';
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  export { kit, last, max, mul, sum, vec } from './channels.js';
2
- export { color, css, mixHex, oklab, toHex } from './color.js';
2
+ export { color, css, ink, mixHex, oklab, toHex } from './color.js';
3
3
  export { curve } from './easing.js';
4
4
  export { conclude, condense, lax, layout, overrun, pipe, plain, shed, } from './fit.js';
5
5
  export { mix, mixer } from './mixer.js';
package/dist/lanes.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { Column } from './columns.js';
1
+ import { flush, pullRun, writeLater } from './columns.js';
2
2
  import { type Copier, copy } from './copyout.js';
3
3
  import { type Crowd, row } from './crowd.js';
4
4
  import { call, foldKeys, one, parting, readKeyed, reset, run, runEveryone, signalled, subjectAt } from './fill.js';
@@ -7,6 +7,8 @@ import { type Lane, type Laned, type Role } from './lane.js';
7
7
  import { absent, Numbers } from './numbers.js';
8
8
  import { Owed } from './owed.js';
9
9
  import { Placed } from './placed.js';
10
+ import type { Column } from './pull.js';
11
+ import { renumber } from './renumber.js';
10
12
  import { type Seat } from './seat.js';
11
13
  import type { Watcher } from './stretch.js';
12
14
  import type { Subject } from './subject.js';
@@ -64,6 +66,29 @@ export interface LaneHost<I, O> {
64
66
  */
65
67
  export declare class Lanes<I, O> implements Watcher {
66
68
  readonly host: LaneHost<I, O>;
69
+ /** What the general path calls on a sharing voice, given to a mix by its engine so a lean bundle leaves it out. */
70
+ static readonly share: {
71
+ shares: typeof import("./everyone.js").shares;
72
+ outgrow: typeof import("./everyone.js").outgrow;
73
+ everyoneOf: typeof import("./everyone.js").everyoneOf;
74
+ sighted: typeof import("./everyone.js").sighted;
75
+ sight: typeof import("./everyone.js").sight;
76
+ weightAt: typeof import("./everyone.js").weightAt;
77
+ weighAt: typeof import("./everyone.js").weighAt;
78
+ open: typeof import("./everyone.js").open;
79
+ shut: typeof import("./everyone.js").shut;
80
+ restamp: typeof import("./everyone.js").restamp;
81
+ unsight: typeof import("./everyone.js").unsight;
82
+ unshare: typeof import("./everyone.js").unshare;
83
+ inherit: typeof import("./everyone.js").inherit;
84
+ };
85
+ /** What `pull` calls on lanes alone. */
86
+ readonly pulls: {
87
+ pullRun: typeof pullRun;
88
+ writeLater: typeof writeLater;
89
+ flush: typeof flush;
90
+ renumber: typeof renumber;
91
+ };
67
92
  readonly numbers: Numbers<I>;
68
93
  lanes: Lane<I, O>[];
69
94
  /** The lanes over every subject that have started playing, by epoch. */
package/dist/lanes.js CHANGED
@@ -1,6 +1,8 @@
1
1
  import { lit } from './bound.js';
2
+ import { flush, pullRun, writeLater } from './columns.js';
2
3
  import { copy } from './copyout.js';
3
4
  import { row } from './crowd.js';
5
+ import { share } from './everyone.js';
4
6
  import { call, fillAll, foldKeys, one, parting, readKeyed, reset, run, runEveryone, signalled, subjectAt, } from './fill.js';
5
7
  import { fold, foldDelta, foldInto, foldRun, gate, gather, unplain } from './gather.js';
6
8
  import { meet, reach } from './meet.js';
@@ -9,6 +11,7 @@ import { Owed } from './owed.js';
9
11
  import { Placed } from './placed.js';
10
12
  import { requalify, retouch } from './qualify.js';
11
13
  import { reading } from './reading.js';
14
+ import { renumber } from './renumber.js';
12
15
  import { reportedRow, unplace } from './rows.js';
13
16
  import { seatOf } from './seat.js';
14
17
  /**
@@ -18,6 +21,10 @@ import { seatOf } from './seat.js';
18
21
  // biome-ignore lint/suspicious/noUnsafeDeclarationMerging: every member of `Methods` is installed below
19
22
  export class Lanes {
20
23
  host;
24
+ /** What the general path calls on a sharing voice, given to a mix by its engine so a lean bundle leaves it out. */
25
+ static share = share;
26
+ /** What `pull` calls on lanes alone. */
27
+ pulls = { pullRun, writeLater, flush, renumber };
21
28
  numbers;
22
29
  lanes = [];
23
30
  /** The lanes over every subject that have started playing, by epoch. */
package/dist/lch.d.ts ADDED
@@ -0,0 +1,23 @@
1
+ import type { Curve } from './easing.js';
2
+ /** Below this chroma a color is gray and its hue is noise, so it takes the other end's. */
3
+ export declare const GRAY = 0.0001;
4
+ /** Where `shortWay` leaves the hue a way starts from and how far it turns. */
5
+ export declare const HUES: number[];
6
+ /** The way from hue `ah` to `bh` the short way round into `HUES`, a gray end taking the other's. */
7
+ export declare function shortWay(ah: number, ac: number, bh: number, bc: number): void;
8
+ /**
9
+ * Two premultiplied OKLab colors, `[w·L, w·a, w·b, w]` read from `x` at `i` and `y` at `j`, into
10
+ * `out` as `[L, C, h, w]` each, `y`'s hue the short way round from `x`'s. A color with no coverage
11
+ * has none of its own to keep, so it takes the other end's.
12
+ */
13
+ export declare function lchPair(x: ArrayLike<number>, i: number, y: ArrayLike<number>, j: number, out: Float64Array): void;
14
+ /**
15
+ * `closed` for a stretch of a color traveling round the hue, as `ink` does: its ends and velocity,
16
+ * premultiplied OKLab as the patch keeps them, moved into `[L, C, h, w]`, solved there axis by
17
+ * axis, and the position and
18
+ * velocity moved back into `xo` and `vo`. A spring heads round the hue the short way from where
19
+ * the stretch starts, so a retarget bends the path rather than restarting it, and one that
20
+ * overshoots its target overshoots in hue too. A glide comes to rest `v·τ` along each of the four,
21
+ * so it coasts round the hue rather than off along a tangent.
22
+ */
23
+ export declare function closedLch(law: Float64Array, ease: Curve | undefined, at: number, ms: number, x0: ArrayLike<number>, x: number, v0: ArrayLike<number>, v: number, to: ArrayLike<number>, g: number, t: number, xo: Float64Array, vo: Float64Array): boolean;
package/dist/lch.js ADDED
@@ -0,0 +1,126 @@
1
+ import { COAST, closed } from './closed.js';
2
+ /** Below this chroma a color is gray and its hue is noise, so it takes the other end's. */
3
+ export const GRAY = 1e-4;
4
+ /** Where `shortWay` leaves the hue a way starts from and how far it turns. */
5
+ export const HUES = [0, 0];
6
+ /** The way from hue `ah` to `bh` the short way round into `HUES`, a gray end taking the other's. */
7
+ export function shortWay(ah, ac, bh, bc) {
8
+ const from = ac < GRAY ? bh : ah;
9
+ const to = bc < GRAY ? ah : bh;
10
+ let turn = to - from;
11
+ if (turn > Math.PI)
12
+ turn -= 2 * Math.PI;
13
+ else if (turn < -Math.PI)
14
+ turn += 2 * Math.PI;
15
+ HUES[0] = from;
16
+ HUES[1] = turn;
17
+ }
18
+ /**
19
+ * Two premultiplied OKLab colors, `[w·L, w·a, w·b, w]` read from `x` at `i` and `y` at `j`, into
20
+ * `out` as `[L, C, h, w]` each, `y`'s hue the short way round from `x`'s. A color with no coverage
21
+ * has none of its own to keep, so it takes the other end's.
22
+ */
23
+ export function lchPair(x, i, y, j, out) {
24
+ const xw = x[i + 3] ?? 0;
25
+ const yw = y[j + 3] ?? 0;
26
+ const ys = yw > 0 ? 1 / yw : 0;
27
+ const yL = (y[j] ?? 0) * ys;
28
+ const yA = (y[j + 1] ?? 0) * ys;
29
+ const yB = (y[j + 2] ?? 0) * ys;
30
+ const xs = xw > 0 ? 1 / xw : 0;
31
+ const xL = xw > 0 ? (x[i] ?? 0) * xs : yL;
32
+ const xA = xw > 0 ? (x[i + 1] ?? 0) * xs : yA;
33
+ const xB = xw > 0 ? (x[i + 2] ?? 0) * xs : yB;
34
+ const toL = yw > 0 ? yL : xL;
35
+ const toA = yw > 0 ? yA : xA;
36
+ const toB = yw > 0 ? yB : xB;
37
+ const xc = Math.sqrt(xA * xA + xB * xB);
38
+ const yc = Math.sqrt(toA * toA + toB * toB);
39
+ shortWay(Math.atan2(xB, xA), xc, Math.atan2(toB, toA), yc);
40
+ out[0] = xL;
41
+ out[1] = xc;
42
+ out[2] = HUES[0];
43
+ out[3] = xw;
44
+ out[4] = toL;
45
+ out[5] = yc;
46
+ out[6] = HUES[0] + HUES[1];
47
+ out[7] = yw;
48
+ }
49
+ const PAIR = new Float64Array(8);
50
+ const RATES = new Float64Array(4);
51
+ const REST = new Float64Array(4);
52
+ const XS = new Float64Array(4);
53
+ const VS = new Float64Array(4);
54
+ /**
55
+ * `closed` for a stretch of a color traveling round the hue, as `ink` does: its ends and velocity,
56
+ * premultiplied OKLab as the patch keeps them, moved into `[L, C, h, w]`, solved there axis by
57
+ * axis, and the position and
58
+ * velocity moved back into `xo` and `vo`. A spring heads round the hue the short way from where
59
+ * the stretch starts, so a retarget bends the path rather than restarting it, and one that
60
+ * overshoots its target overshoots in hue too. A glide comes to rest `v·τ` along each of the four,
61
+ * so it coasts round the hue rather than off along a tangent.
62
+ */
63
+ export function closedLch(law, ease, at, ms, x0, x, v0, v, to, g, t, xo, vo) {
64
+ const p = PAIR;
65
+ lchPair(x0, x, to, g, p);
66
+ const L = p[0];
67
+ const C = p[1];
68
+ const h = p[2];
69
+ const w = p[3];
70
+ // OKLab's rates of change at the start, its coverage's share divided out, turned onto L, C and h.
71
+ const dw = v0[v + 3];
72
+ let dL = 0;
73
+ let dC = 0;
74
+ let dh = 0;
75
+ if (w > 0) {
76
+ const cos = Math.cos(h);
77
+ const sin = Math.sin(h);
78
+ dL = (v0[v] - dw * L) / w;
79
+ const da = (v0[v + 1] - dw * C * cos) / w;
80
+ const db = (v0[v + 2] - dw * C * sin) / w;
81
+ dC = da * cos + db * sin;
82
+ dh = C < GRAY ? 0 : (db * cos - da * sin) / C;
83
+ }
84
+ RATES[0] = dL;
85
+ RATES[1] = dC;
86
+ RATES[2] = dh;
87
+ RATES[3] = dw;
88
+ let goal = p;
89
+ let o = 4;
90
+ if (law[0] === COAST) {
91
+ const tau = law[2];
92
+ for (let i = 0; i < 4; i++)
93
+ REST[i] = p[i] + RATES[i] * tau;
94
+ goal = REST;
95
+ o = 0;
96
+ }
97
+ const still = closed(law, ease, 4, at, ms, p, 0, RATES, 0, goal, o, t, XS, VS);
98
+ if (still && o === 4) {
99
+ for (let i = 0; i < 4; i++) {
100
+ xo[i] = to[g + i];
101
+ vo[i] = 0;
102
+ }
103
+ return true;
104
+ }
105
+ const nL = XS[0];
106
+ const nC = XS[1];
107
+ const nh = XS[2];
108
+ const nw = XS[3];
109
+ const cos = Math.cos(nh);
110
+ const sin = Math.sin(nh);
111
+ const a = nC * cos;
112
+ const b = nC * sin;
113
+ const rL = VS[0];
114
+ const rC = VS[1];
115
+ const rh = VS[2];
116
+ const rw = VS[3];
117
+ xo[0] = nL * nw;
118
+ xo[1] = a * nw;
119
+ xo[2] = b * nw;
120
+ xo[3] = nw;
121
+ vo[0] = rL * nw + nL * rw;
122
+ vo[1] = (rC * cos - nC * rh * sin) * nw + a * rw;
123
+ vo[2] = (rC * sin + nC * rh * cos) * nw + b * rw;
124
+ vo[3] = rw;
125
+ return still;
126
+ }
package/dist/mixer.d.ts CHANGED
@@ -4,11 +4,11 @@ import { blended, laneRead, shareKept } from './blend.js';
4
4
  import type { Book } from './book.js';
5
5
  import { aims, chain, linkable, relink } from './chain.js';
6
6
  import { type LerpInto } from './channels.js';
7
- import type { Column } from './columns.js';
8
7
  import { baseFor, contribution, isRest, keyed, read, slopeFor, tick } from './contribution.js';
9
8
  import { DepartedIndex } from './departed.js';
10
9
  import { drop, forgetIn, recordDrop, releaseIn } from './drop.js';
11
10
  import { DueQueue } from './due.js';
11
+ import type { Share } from './everyone.js';
12
12
  import { parting } from './fade.js';
13
13
  import { apply, clamp, fold, foldDetour, folded, foldSharers, foldWith, type Key, linked, owedBy, passes, shared } from './fold.js';
14
14
  import { GoneIndex } from './gone.js';
@@ -20,6 +20,7 @@ import type { Pace } from './pace.js';
20
20
  import { Keys } from './paging.js';
21
21
  import type { Places } from './places.js';
22
22
  import { moved, place, plot, put } from './plot.js';
23
+ import type { Column } from './pull.js';
23
24
  import { keep } from './pull.js';
24
25
  import { Steps } from './relink.js';
25
26
  import { atRest, restful, rests, restsKey, restsLaned, still } from './rests.js';
@@ -209,6 +210,8 @@ export declare class Mixer<I, O> implements Mix<I, O> {
209
210
  roleDepth: number;
210
211
  readonly slotOf: Map<string, number>;
211
212
  lanes: Lanes<I, O> | null;
213
+ /** The sharing hooks of the lanes this entry was built with, which a projection's copy keeps. */
214
+ share: Share | null;
212
215
  handles: HandleHost<I, O> | null;
213
216
  /** What `book` made, still booking; null while there is none. */
214
217
  bookers: Book<I, O>[] | null;
package/dist/mixer.js CHANGED
@@ -235,6 +235,8 @@ export class Mixer {
235
235
  roleDepth = 0;
236
236
  slotOf = new Map();
237
237
  lanes;
238
+ /** The sharing hooks of the lanes this entry was built with, which a projection's copy keeps. */
239
+ share;
238
240
  handles = null;
239
241
  /** What `book` made, still booking; null while there is none. */
240
242
  bookers = null;
@@ -266,6 +268,7 @@ export class Mixer {
266
268
  });
267
269
  if (lanesOf === null && opts.lanes === true)
268
270
  throw new Error("blits: '@msb235/blits/lean' has no lanes; take mix from '@msb235/blits'");
271
+ this.share = lanesOf?.share ?? null;
269
272
  this.lanes =
270
273
  lanesOf !== null && (opts.lanes ?? defaults.lanes) ? new lanesOf(laneHost(this)) : null;
271
274
  this.name = opts.name;
package/dist/motions.d.ts CHANGED
@@ -64,11 +64,14 @@ export declare class Motions<I> {
64
64
  /** Whether the channel it was cued on holds a number; undefined until it is cued. */
65
65
  private holdsNumber;
66
66
  private channel;
67
+ /** Whether the channel it was cued on travels round the hue, as `ink` does, and its stretches too. */
68
+ round: boolean;
67
69
  /**
68
70
  * Records what the channel the patch is cued on holds, refusing a value of the other kind: at once
69
71
  * where every subject's value is one kind, else on the first sample of a subject whose value is not.
72
+ * `round` for a channel that travels round the hue, as `ink` does.
70
73
  */
71
- cuedOn(channel: string, holdsNumber: boolean): void;
74
+ cuedOn(channel: string, holdsNumber: boolean, round?: boolean): void;
72
75
  frame(subject: I): number;
73
76
  constructor(shape: Shape<I>);
74
77
  /** The patch's number for `subject`, made with its first stretch on first ask. */