@msb235/blits 0.11.0 → 0.12.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 (78) hide show
  1. package/CHANGELOG.md +97 -0
  2. package/dist/arcs.d.ts +40 -0
  3. package/dist/arcs.js +239 -0
  4. package/dist/chain.d.ts +6 -1
  5. package/dist/chain.js +33 -13
  6. package/dist/channels.d.ts +2 -0
  7. package/dist/channels.js +12 -1
  8. package/dist/clone.js +16 -4
  9. package/dist/cue.js +11 -2
  10. package/dist/departed.d.ts +5 -1
  11. package/dist/departed.js +71 -27
  12. package/dist/easing.d.ts +12 -1
  13. package/dist/easing.js +7 -1
  14. package/dist/everyone.d.ts +14 -1
  15. package/dist/everyone.js +27 -2
  16. package/dist/fade.js +3 -3
  17. package/dist/fill.js +16 -9
  18. package/dist/fit.d.ts +8 -1
  19. package/dist/fit.js +7 -3
  20. package/dist/fold.js +69 -13
  21. package/dist/gather.js +38 -7
  22. package/dist/handle.d.ts +2 -0
  23. package/dist/handle.js +8 -0
  24. package/dist/held.d.ts +5 -2
  25. package/dist/held.js +6 -3
  26. package/dist/history.d.ts +19 -2
  27. package/dist/history.js +149 -31
  28. package/dist/hosts.js +27 -11
  29. package/dist/index.d.ts +4 -1
  30. package/dist/index.js +2 -0
  31. package/dist/lanes.d.ts +23 -7
  32. package/dist/lanes.js +36 -2
  33. package/dist/lean.js +2 -2
  34. package/dist/marks.js +3 -0
  35. package/dist/mixer.d.ts +15 -1
  36. package/dist/mixer.js +57 -30
  37. package/dist/move.js +7 -2
  38. package/dist/numbers.d.ts +5 -1
  39. package/dist/numbers.js +4 -0
  40. package/dist/owner.js +6 -1
  41. package/dist/place.d.ts +8 -0
  42. package/dist/place.js +16 -8
  43. package/dist/project.js +3 -3
  44. package/dist/qualify.d.ts +10 -0
  45. package/dist/qualify.js +52 -1
  46. package/dist/reading.d.ts +2 -1
  47. package/dist/reading.js +2 -1
  48. package/dist/record.d.ts +6 -0
  49. package/dist/record.js +1 -0
  50. package/dist/restored.d.ts +67 -18
  51. package/dist/restored.js +159 -49
  52. package/dist/revive.d.ts +1 -0
  53. package/dist/revive.js +1 -0
  54. package/dist/roster.js +2 -1
  55. package/dist/seek.js +72 -18
  56. package/dist/shared.d.ts +1 -1
  57. package/dist/shared.js +1 -1
  58. package/dist/spans.d.ts +5 -0
  59. package/dist/spans.js +75 -5
  60. package/dist/store.d.ts +2 -0
  61. package/dist/store.js +4 -0
  62. package/dist/touch.js +3 -2
  63. package/dist/transport.js +6 -1
  64. package/dist/types/arc.d.ts +55 -0
  65. package/dist/types/arc.js +1 -0
  66. package/dist/types/mix.d.ts +18 -0
  67. package/dist/types/voice.d.ts +10 -1
  68. package/dist/types/watch.d.ts +126 -0
  69. package/dist/types/watch.js +1 -0
  70. package/dist/types.d.ts +2 -0
  71. package/dist/voice.d.ts +37 -6
  72. package/dist/voice.js +17 -0
  73. package/dist/watch.d.ts +26 -0
  74. package/dist/watch.js +441 -0
  75. package/dist/weigh.d.ts +13 -2
  76. package/dist/weigh.js +10 -3
  77. package/docs/schema.html +39 -3
  78. package/package.json +5 -4
package/dist/held.js CHANGED
@@ -17,8 +17,11 @@ export function unreachedOf(voice, now) {
17
17
  voice.unreached = none;
18
18
  return none;
19
19
  }
20
- /** What this voice holds for this subject, made on first sight with `target` and `stagger` asked once. */
21
- export function held(voice, subject, slot) {
20
+ /**
21
+ * What this voice holds for this subject, made on first sight with `target` and `stagger` asked
22
+ * once; `guess` is a record it may be, from the subject's chain as last linked.
23
+ */
24
+ export function held(voice, subject, slot, guess = null) {
22
25
  if (voice.sharing) {
23
26
  const all = everyoneOf(this, voice);
24
27
  if (all !== null)
@@ -26,7 +29,7 @@ export function held(voice, subject, slot) {
26
29
  }
27
30
  if (unreached(voice, slot))
28
31
  return voice.unreached;
29
- let held = voice.subjects.get(subject);
32
+ let held = (guess === null ? voice.subjects.get(subject) : voice.subjects.find(subject, guess));
30
33
  if (held !== undefined)
31
34
  return held;
32
35
  const now = this.now;
package/dist/history.d.ts CHANGED
@@ -5,6 +5,10 @@ import { type Subject, type Voice } from './voice.js';
5
5
  export declare function last<T extends {
6
6
  at: number;
7
7
  }>(list: readonly T[], t: number, inclusive: boolean | ((e: T) => boolean)): T | undefined;
8
+ /** Where `last` finds its entry in the list, -1 where it finds none. */
9
+ export declare function lastIndex<T extends {
10
+ at: number;
11
+ }>(list: readonly T[], t: number, inclusive: boolean | ((e: T) => boolean)): number;
8
12
  /**
9
13
  * The last entry of a list kept in the order it was made that falls inside `cut`, an entry made
10
14
  * before its frame was read counting as `early`.
@@ -21,8 +25,21 @@ export declare function elapsedThen<I, O>(mix: Mixer<I, O>, voice: Voice<I, O>,
21
25
  export declare function noted<I, O>(mix: Mixer<I, O>, voice: Voice<I, O>): void;
22
26
  /** A copy of the state a voice's patch keeps for a subject. */
23
27
  export declare function copyState<I, O>(voice: Voice<I, O>, state: unknown): unknown;
24
- /** A copy of a subject's record that shares nothing a read could change. */
25
- export declare function copyHeld<I, O>(voice: Voice<I, O>, h: Subject<unknown>): Subject<unknown>;
28
+ /**
29
+ * A copy of a subject's record that shares nothing a read could change; with `shared`, its state is
30
+ * the record's own, for a copy that takes one of its own before it steps.
31
+ */
32
+ export declare function copyHeld<I, O>(voice: Voice<I, O>, h: Subject<unknown>, shared?: boolean): Subject<unknown>;
33
+ /**
34
+ * As `copyHeld`, written over `into`, a record nothing else holds, so a copy made in its place
35
+ * leaves nothing new to collect. test/history.test.ts holds this to `copyHeld` field by field.
36
+ */
37
+ export declare function copyHeldInto<I, O>(voice: Voice<I, O>, h: Subject<unknown>, into: Subject<unknown>, shared?: boolean): Subject<unknown>;
38
+ /**
39
+ * Lets go of what a record holds from its last probe and its place in a chain, as a copy of it
40
+ * starts: a seek puts back a record nothing else holds this way, in place of a copy.
41
+ */
42
+ export declare function unread(h: Subject<unknown>): Subject<unknown>;
26
43
  /** Under `history` with `inputs`, copies the host fields any voice reads, once a frame, when they changed. */
27
44
  export declare function recordHost<I, O>(mix: Mixer<I, O>, now: number): void;
28
45
  /** Under `history` with `inputs`, keeps what an input signal read, each time it changes. */
package/dist/history.js CHANGED
@@ -9,14 +9,18 @@ import { within } from './transport.js';
9
9
  import { none } from './voice.js';
10
10
  /** The last entry taken before `t`, or at it where `inclusive` says so. */
11
11
  export function last(list, t, inclusive) {
12
+ return list[lastIndex(list, t, inclusive)];
13
+ }
14
+ /** Where `last` finds its entry in the list, -1 where it finds none. */
15
+ export function lastIndex(list, t, inclusive) {
12
16
  let lo = 0;
13
17
  let hi = list.length - 1;
14
- let found;
18
+ let found = -1;
15
19
  while (lo <= hi) {
16
20
  const mid = (lo + hi) >> 1;
17
21
  const e = list[mid];
18
22
  if (e.at < t || (e.at === t && (typeof inclusive === 'function' ? inclusive(e) : inclusive))) {
19
- found = e;
23
+ found = mid;
20
24
  lo = mid + 1;
21
25
  }
22
26
  else
@@ -86,45 +90,125 @@ export function noted(mix, voice) {
86
90
  pageOut(mix, 'controls', voice.id, undefined, log.splice(0, drop), copied, reach);
87
91
  }
88
92
  const copied = (e) => ({ ...e });
93
+ const nothing = Object.freeze([]);
89
94
  /** Takes off the front of a list kept by time what a read back to `reach` no longer needs. */
90
95
  function older(list, reach) {
96
+ const n = past(list, reach);
97
+ return n === 0 ? nothing : list.splice(0, n);
98
+ }
99
+ /** How many entries at the front of a list kept by time a read back to `reach` no longer needs. */
100
+ function past(list, reach) {
91
101
  let n = 0;
92
102
  while (n + 1 < list.length && list[n + 1].at <= reach)
93
103
  n++;
94
- return n === 0 ? [] : list.splice(0, n);
104
+ return n;
95
105
  }
96
106
  /** A copy of the state a voice's patch keeps for a subject. */
97
107
  export function copyState(voice, state) {
98
108
  const patch = voice.patch;
99
109
  return patch.clone ? patch.clone(state) : clone(state);
100
110
  }
101
- /** A copy of a subject's record that shares nothing a read could change. */
102
- export function copyHeld(voice, h) {
103
- let kept = null;
104
- if (h.kept !== null) {
105
- kept = new Map();
106
- for (const [owner, value] of h.kept)
107
- kept.set(owner, clone(value));
108
- }
109
- return {
110
- ...h,
111
+ /**
112
+ * A copy of a subject's record that shares nothing a read could change; with `shared`, its state is
113
+ * the record's own, for a copy that takes one of its own before it steps.
114
+ */
115
+ export function copyHeld(voice, h, shared = false) {
116
+ const kept = keptOf(h);
117
+ // Every field in `record`'s order, written out: a spread with fields after it costs a third more.
118
+ return unread({
119
+ reaches: h.reaches,
120
+ delay: h.delay,
121
+ since: h.since,
122
+ shown: h.shown,
123
+ weight: h.weight,
124
+ rested: h.rested,
111
125
  bands: h.bands === null ? null : h.bands.slice(),
112
- state: h.state === undefined ? undefined : copyState(voice, h.state),
113
- kept,
114
- unkept: h.unkept?.map(clone),
115
- probed: Number.NaN,
116
- delta: null,
117
- phase: 0,
118
- seeks: 0,
126
+ state: h.state === undefined || shared ? h.state : copyState(voice, h.state),
127
+ stepped: h.stepped,
128
+ ticks: h.ticks,
129
+ probed: h.probed,
130
+ delta: h.delta,
131
+ phase: h.phase,
132
+ seeks: h.seeks,
133
+ rebuilt: h.rebuilt,
134
+ copied: h.copied,
119
135
  base: h.base === undefined ? undefined : clone(h.base),
120
136
  slope: h.slope === undefined ? undefined : clone(h.slope),
121
- snaps: undefined,
122
- inputs: undefined,
123
- next: null,
124
- version: Number.NaN,
125
- loci: null,
126
- slot: -1,
127
- };
137
+ kept,
138
+ unkept: h.unkept?.map(clone),
139
+ snaps: h.snaps,
140
+ from: h.from,
141
+ unknown: h.unknown,
142
+ inputs: h.inputs,
143
+ replay: h.replay,
144
+ voice: h.voice,
145
+ next: h.next,
146
+ version: h.version,
147
+ loci: h.loci,
148
+ slot: h.slot,
149
+ run: h.run,
150
+ freed: h.freed,
151
+ // In no store, so stamped by none: a store trusts its stamp to say a record is still its own.
152
+ restored: null,
153
+ });
154
+ }
155
+ /** A copy of what a record keeps through `setting.keep`. */
156
+ function keptOf(h) {
157
+ if (h.kept === null)
158
+ return null;
159
+ const kept = new Map();
160
+ for (const [owner, value] of h.kept)
161
+ kept.set(owner, clone(value));
162
+ return kept;
163
+ }
164
+ /**
165
+ * As `copyHeld`, written over `into`, a record nothing else holds, so a copy made in its place
166
+ * leaves nothing new to collect. test/history.test.ts holds this to `copyHeld` field by field.
167
+ */
168
+ export function copyHeldInto(voice, h, into, shared = false) {
169
+ into.reaches = h.reaches;
170
+ into.delay = h.delay;
171
+ into.since = h.since;
172
+ into.shown = h.shown;
173
+ into.weight = h.weight;
174
+ into.rested = h.rested;
175
+ into.bands = h.bands === null ? null : h.bands.slice();
176
+ into.state = h.state === undefined || shared ? h.state : copyState(voice, h.state);
177
+ into.stepped = h.stepped;
178
+ into.ticks = h.ticks;
179
+ into.rebuilt = h.rebuilt;
180
+ into.copied = h.copied;
181
+ into.base = h.base === undefined ? undefined : clone(h.base);
182
+ into.slope = h.slope === undefined ? undefined : clone(h.slope);
183
+ into.kept = keptOf(h);
184
+ into.unkept = h.unkept?.map(clone);
185
+ into.from = h.from;
186
+ into.unknown = h.unknown;
187
+ into.replay = h.replay;
188
+ into.voice = h.voice;
189
+ into.slot = h.slot;
190
+ into.run = h.run;
191
+ into.freed = h.freed;
192
+ into.restored = null;
193
+ // `probed`, `delta`, `phase`, `seeks`, `snaps`, `inputs`, `next`, `version` and `loci` as
194
+ // `unread` leaves them.
195
+ return unread(into);
196
+ }
197
+ /**
198
+ * Lets go of what a record holds from its last probe and its place in a chain, as a copy of it
199
+ * starts: a seek puts back a record nothing else holds this way, in place of a copy.
200
+ */
201
+ export function unread(h) {
202
+ h.probed = Number.NaN;
203
+ h.delta = null;
204
+ h.phase = 0;
205
+ h.seeks = 0;
206
+ h.snaps = undefined;
207
+ h.inputs = undefined;
208
+ h.next = null;
209
+ h.version = Number.NaN;
210
+ h.loci = null;
211
+ return h;
128
212
  }
129
213
  /** Under `history` with `inputs`, copies the host fields any voice reads, once a frame, when they changed. */
130
214
  export function recordHost(mix, now) {
@@ -168,6 +252,19 @@ export function record(mix, voice, subject, held, value) {
168
252
  export function apart(history) {
169
253
  return history.every ?? 200;
170
254
  }
255
+ /**
256
+ * A copy of a record to keep now, written over a spare the latest seek back cut, or one just let go
257
+ * of, where it has one.
258
+ */
259
+ function snapOf(mix, voice, held, gone) {
260
+ const spare = voice.spares?.pop() ?? gone;
261
+ if (spare === undefined)
262
+ return { at: mix.now, seq: mix.transport.seq, held: copyHeld(voice, held) };
263
+ spare.at = mix.now;
264
+ spare.seq = mix.transport.seq;
265
+ copyHeldInto(voice, held, spare.held);
266
+ return spare;
267
+ }
171
268
  /** Under `history`, keeps a copy of a stateful voice's record for this subject every so often. */
172
269
  export function remember(mix, voice, subject, held) {
173
270
  const history = mix.opts.history;
@@ -182,11 +279,32 @@ export function remember(mix, voice, subject, held) {
182
279
  const prev = snaps[snaps.length - 1];
183
280
  if (prev !== undefined && mix.now - prev.at < apart(history))
184
281
  return;
185
- snaps.push({ at: mix.now, seq: mix.transport.seq, held: copyHeld(voice, held) });
282
+ // What falls out is the same before the push as after it, since the copy pushed is newer.
186
283
  const reach = mix.now - history.ms;
187
- const out = older(snaps, reach);
188
- if (out.length > 0)
189
- pageOut(mix, 'snap', voice.id, mix.keys?.key(subject), out, (e) => packHeld(voice, e.held), reach);
284
+ const n = past(snaps, reach);
285
+ let gone;
286
+ if (n > 0) {
287
+ if (mix.transport.pager !== null)
288
+ pageOut(mix, 'snap', voice.id, mix.keys?.key(subject), snaps.slice(0, n), (e) => packHeld(voice, e.held), reach);
289
+ gone = reusable(snaps, n);
290
+ // `shift` moves the list's start where `splice` copies what it keeps and allocates what it cuts.
291
+ if (n === 1)
292
+ snaps.shift();
293
+ else
294
+ snaps.splice(0, n);
295
+ }
296
+ snaps.push(snapOf(mix, voice, held, gone));
297
+ }
298
+ /** The first of a list's first `n` copies no other record's list holds, made ready to be written over. */
299
+ function reusable(snaps, n) {
300
+ for (let i = 0; i < n; i++) {
301
+ const snap = snaps[i];
302
+ if (snap.shared)
303
+ continue;
304
+ snap.ahead = undefined;
305
+ return snap;
306
+ }
307
+ return undefined;
190
308
  }
191
309
  /**
192
310
  * Under history, keeps the record a subject had when it left a voice, faded out of it or dropped,
package/dist/hosts.js CHANGED
@@ -1,4 +1,6 @@
1
+ import { release } from './arcs.js';
1
2
  import { Book } from './book.js';
3
+ import { headOf } from './chain.js';
2
4
  import { numericOf } from './channels.js';
3
5
  import { stepOf } from './clock.js';
4
6
  import { crowdable } from './crowd.js';
@@ -71,31 +73,44 @@ export function laneHost(mix) {
71
73
  },
72
74
  passes: (was, w) => mix.passes(was, w),
73
75
  naming: (subject) => (mix.naming === 0 ? undefined : mix.named.get(subject)),
74
- slotOf: (subject) => mix.chains.get(subject)?.slot ?? -1,
76
+ slotOf: (subject) => headOf(mix, subject)?.slot ?? -1,
75
77
  envelope: (voice, since) => voice.owner === null
76
78
  ? mix.envelope(voice, mix.now, since)
77
79
  : mix.envelope(voice, mix.now, since) * mix.ownedBy(voice, undefined),
78
80
  parting: (voice, subject) => mix.parting(voice, subject, mix.now),
79
- ready: (voice, subject, held, elapsed, pass, weight) => {
80
- mix.prime(voice, held, mix.now, elapsed, pass);
81
- voice.setting.weight = weight;
81
+ ready: (voice, subject, held) => {
82
+ const arg = mix.lanes.arg;
83
+ const primed = mix.primed;
84
+ primed[0 /* Primed.NOW */] = mix.now;
85
+ primed[1 /* Primed.ELAPSED */] = arg[1 /* Arg.ELAPSED */];
86
+ primed[2 /* Primed.PASS */] = arg[3 /* Arg.PASS */];
87
+ mix.prime(voice, held);
88
+ voice.setting.weight = arg[0 /* Arg.WEIGHT */];
82
89
  mix.sending.voice = voice;
83
90
  mix.sending.subject = subject;
84
91
  },
85
- signal: (voice, subject, held, elapsed, pass, slot) => {
92
+ signal: (voice, subject, held, slot) => {
93
+ const arg = mix.lanes.arg;
86
94
  // A blend member after the first takes the read already made, with no setting to fill.
87
95
  const read = voice.blend === null ? Number.NaN : mix.laneRead(voice, slot);
88
- if (!Number.isNaN(read))
89
- return read;
90
- mix.prime(voice, held, mix.now, elapsed, pass);
96
+ if (!Number.isNaN(read)) {
97
+ arg[0 /* Arg.WEIGHT */] = read;
98
+ return;
99
+ }
100
+ const primed = mix.primed;
101
+ primed[0 /* Primed.NOW */] = mix.now;
102
+ primed[1 /* Primed.ELAPSED */] = arg[1 /* Arg.ELAPSED */];
103
+ primed[2 /* Primed.PASS */] = arg[3 /* Arg.PASS */];
104
+ mix.prime(voice, held);
91
105
  const kept = reading.kept;
92
106
  const base = mix.base(voice, subject, mix.now, held, slot);
93
107
  if (reading.kept !== kept && !voice.keeping)
94
108
  stateful(mix, voice);
95
- return base;
109
+ arg[0 /* Arg.WEIGHT */] = base;
96
110
  },
97
111
  horizon: (voice, delay) => {
98
- reading.horizon = mix.horizonFor(voice, delay, mix.now);
112
+ if (voice.motion !== undefined)
113
+ reading.horizon = mix.horizonFor(voice, delay, mix.now);
99
114
  },
100
115
  keeps: mix.opts.history !== undefined,
101
116
  after: (voice, subject, held) => remember(mix, voice, subject, held),
@@ -224,12 +239,13 @@ function handleHost(mix) {
224
239
  beginFade(mix, voice, (opts ?? {}));
225
240
  },
226
241
  rise: (voice, opts) => beginRise(mix, voice, opts ?? {}),
242
+ release: (voice) => release(mix, voice),
227
243
  weightOf: (voice, subject) => {
228
244
  if (voice.state === 'done')
229
245
  return 0;
230
246
  if (voice.holding !== null)
231
247
  return ownWeight(voice, mix.now, mix.reducedNow, (o) => mix.ownerBase(o, subject));
232
- const slot = mix.lanes === null ? -1 : (mix.chains.get(subject)?.slot ?? -1);
248
+ const slot = mix.lanes === null ? -1 : (headOf(mix, subject)?.slot ?? -1);
233
249
  if (voice.laned && mix.lanes !== null) {
234
250
  const w = mix.lanes.weightOf(voice.id, slot);
235
251
  if (w !== undefined)
package/dist/index.d.ts CHANGED
@@ -2,6 +2,8 @@ export type { NumberOptions, 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
4
  export { color, css, mixHex, oklab, toHex } from './color.js';
5
+ export type { Curve } from './easing.js';
6
+ export { curve } from './easing.js';
5
7
  export type { Claim, Fit, FitPlan, Order, SpanClaim, Strength } from './fit.js';
6
8
  export { conclude, condense, lax, layout, overrun, pipe, plain, shed, } from './fit.js';
7
9
  export { mix, mixer } from './mixer.js';
@@ -17,6 +19,7 @@ export { fold } from './stack.js';
17
19
  export type { Ticked, Ticker, TickerOptions } from './ticker.js';
18
20
  export { ticker } from './ticker.js';
19
21
  export { transport } from './transport.js';
20
- export type { Anchor, BlendMember, BookedHit, Booker, BookOptions, Channel, Columns, Described, Doubt, Easing, Engine, FadeOptions, FadeSpec, FitResult, Handle, HistoryOptions, HistoryStore, Hit, Keyframe, Kit, Mark, Marked, MarkName, Mix, MixOptions, MotionSpec, OwnerSpec, Paged, PagedStream, Patch, Placement, Projection, Query, SeekOptions, Sent, Setting, Signal, SpanHandle, SpanHints, SpanSpec, Tape, TapeBranch, TapeMaker, TapeOp, Transport, TransportOptions, TransportProjection, VoiceSpec, } from './types.js';
22
+ export type { Anchor, ArcHandle, ArcPart, ArcSpec, ArcVoice, BlendMember, BookedHit, Booker, BookOptions, Channel, Columns, Described, Doubt, Easing, Engine, FadeOptions, FadeSpec, FitResult, Handle, HistoryOptions, HistoryStore, Hit, Hold, Keyframe, Kit, Mark, Marked, MarkName, Mix, MixOptions, MotionSpec, OwnerSpec, Paged, PagedStream, Patch, Placement, Projection, Query, Registry, SeekOptions, Sent, Setting, Signal, SpanHandle, SpanHints, SpanSpec, Tape, TapeBranch, TapeMaker, TapeOp, Transport, TransportOptions, TransportProjection, VoiceSpec, Watched, WatchedFrame, } from './types.js';
23
+ export { watched } from './watch.js';
21
24
  export type { WaveOptions, WaveShape } from './wave.js';
22
25
  export { wave, waveAt } from './wave.js';
package/dist/index.js CHANGED
@@ -1,5 +1,6 @@
1
1
  export { kit, last, max, mul, sum, vec } from './channels.js';
2
2
  export { color, css, mixHex, oklab, toHex } from './color.js';
3
+ export { curve } from './easing.js';
3
4
  export { conclude, condense, lax, layout, overrun, pipe, plain, shed, } from './fit.js';
4
5
  export { mix, mixer } from './mixer.js';
5
6
  export { glide, spring, tween } from './motion.js';
@@ -10,4 +11,5 @@ export { gate, input, lag, level, peak, share, slew } from './signals.js';
10
11
  export { fold } from './stack.js';
11
12
  export { ticker } from './ticker.js';
12
13
  export { transport } from './transport.js';
14
+ export { watched } from './watch.js';
13
15
  export { wave, waveAt } from './wave.js';
package/dist/lanes.d.ts CHANGED
@@ -33,14 +33,18 @@ export interface LaneHost<I, O> {
33
33
  envelope(voice: Voice<I, O>, since: number): number;
34
34
  /** What a subject's own ramp out of the voice leaves of its weight, 0..1. */
35
35
  parting(voice: Voice<I, O>, subject: I): number;
36
- /** Fills the voice's setting and the send target for a call to its patch. */
37
- ready(voice: Voice<I, O>, subject: I, held: Subject<unknown>, elapsed: number, pass: number, weight: number): void;
38
36
  /**
39
- * A voice with a signal weight: the signal's value for a subject the general path has met, before
40
- * the fade and ramp `weighed` applies, with the setting filled in for the call; makes the voice
41
- * stateful if the signal keeps state. `slot` is the subject's number.
37
+ * Fills the voice's setting and the send target for a call to its patch, at the voice time, pass
38
+ * and weight in `Arg.ELAPSED`, `Arg.PASS` and `Arg.WEIGHT`.
42
39
  */
43
- signal(voice: Voice<I, O>, subject: I, held: Subject<unknown>, elapsed: number, pass: number, slot: number): number;
40
+ ready(voice: Voice<I, O>, subject: I, held: Subject<unknown>): void;
41
+ /**
42
+ * A voice with a signal weight: the signal's value for a subject the general path has met, at the
43
+ * voice time and pass in `Arg.ELAPSED` and `Arg.PASS`, left in `Arg.WEIGHT` before the fade and
44
+ * ramp `weighed` applies, with the setting filled in for the call; makes the voice stateful if the
45
+ * signal keeps state. `slot` is the subject's number.
46
+ */
47
+ signal(voice: Voice<I, O>, subject: I, held: Subject<unknown>, slot: number): void;
44
48
  /** Whether a rest-less channel's contribution passes the band, as the general path decides it. */
45
49
  passes(was: boolean | undefined, w: number): boolean;
46
50
  /** Sets `reading.horizon` for the voice and a subject delayed `delay` voice ms. */
@@ -176,7 +180,12 @@ export declare class Lanes<I, O> implements Watcher {
176
180
  lately: number;
177
181
  filledVersion: number;
178
182
  qualifiedVersion: number;
179
- constructor(host: LaneHost<I, O>);
183
+ /**
184
+ * With `from`, the lanes a seek replaces: their subject numbers carry over, each met as newly
185
+ * numbered when its chain is next linked (`renew`), so a seek makes no new weak reference per
186
+ * subject.
187
+ */
188
+ constructor(host: LaneHost<I, O>, from?: Lanes<I, O>);
180
189
  /** A change that may move a voice on or off its lane: qualify again, then fill again. */
181
190
  invalidate(): void;
182
191
  /** A change to a laned voice's weight or clock between probes: fill again. */
@@ -189,6 +198,13 @@ export declare class Lanes<I, O> implements Watcher {
189
198
  /** Numbers a subject the mix sees for the first time. */
190
199
  number(subject: I): number;
191
200
  release(slot: number): void;
201
+ /** Whether `slot` is a number a seek carried over whose chain has not been linked since. */
202
+ sought(slot: number): boolean;
203
+ /**
204
+ * A chain is being linked for the subject at `slot`: one a seek carried over is from now on as
205
+ * `number` leaves a subject it numbers. Says whether it was one.
206
+ */
207
+ renew(slot: number): boolean;
192
208
  /** A voice faded a subject out of itself alone: its lane lets the subject's position go. */
193
209
  part(id: number, slot: number): void;
194
210
  /** A voice brought a subject back: its next probe meets every lane again, as it met them first. */
package/dist/lanes.js CHANGED
@@ -134,9 +134,27 @@ export class Lanes {
134
134
  lately = -1;
135
135
  filledVersion = Number.NaN;
136
136
  qualifiedVersion = Number.NaN;
137
- constructor(host) {
137
+ /**
138
+ * With `from`, the lanes a seek replaces: their subject numbers carry over, each met as newly
139
+ * numbered when its chain is next linked (`renew`), so a seek makes no new weak reference per
140
+ * subject.
141
+ */
142
+ constructor(host, from) {
138
143
  this.host = host;
139
- this.numbers = new Numbers(this);
144
+ if (from === undefined) {
145
+ this.numbers = new Numbers(this);
146
+ return;
147
+ }
148
+ this.numbers = from.numbers;
149
+ this.numbers.adopt(this);
150
+ this.live = from.live;
151
+ const size = this.numbers.size;
152
+ this.grow(size);
153
+ const per = this.per;
154
+ for (let slot = 0; slot < size; slot++) {
155
+ per[slot * 6 /* Per.SLOT */ + 0 /* Per.FILLED */] = Number.NaN;
156
+ per[slot * 6 /* Per.SLOT */ + 1 /* Per.SEEN */] = -1;
157
+ }
140
158
  }
141
159
  /** A change that may move a voice on or off its lane: qualify again, then fill again. */
142
160
  invalidate() {
@@ -173,6 +191,22 @@ export class Lanes {
173
191
  release(slot) {
174
192
  this.numbers.release(slot);
175
193
  }
194
+ /** Whether `slot` is a number a seek carried over whose chain has not been linked since. */
195
+ sought(slot) {
196
+ return Number.isNaN(this.per[slot * 6 /* Per.SLOT */ + 0 /* Per.FILLED */]);
197
+ }
198
+ /**
199
+ * A chain is being linked for the subject at `slot`: one a seek carried over is from now on as
200
+ * `number` leaves a subject it numbers. Says whether it was one.
201
+ */
202
+ renew(slot) {
203
+ if (!this.sought(slot))
204
+ return false;
205
+ this.per[slot * 6 /* Per.SLOT */ + 0 /* Per.FILLED */] = this.fills;
206
+ if (this.filling)
207
+ this.late.push(slot);
208
+ return true;
209
+ }
176
210
  /** A voice faded a subject out of itself alone: its lane lets the subject's position go. */
177
211
  part(id, slot) {
178
212
  const lane = this.byId.get(id);
package/dist/lean.js CHANGED
@@ -1,4 +1,4 @@
1
- import { Mixer } from './mixer.js';
1
+ import { made } from './mixer.js';
2
2
  export * from './index.js';
3
3
  /**
4
4
  * The stock engine without lanes: every voice folds subject by subject, to the same values, and a
@@ -10,7 +10,7 @@ export const lean = {
10
10
  name: 'lean',
11
11
  runs: new Set(['fn', 'keys', 'motion']),
12
12
  create(kit, opts) {
13
- return new Mixer(kit, opts, null);
13
+ return made(kit, opts, null);
14
14
  },
15
15
  };
16
16
  /**
package/dist/marks.js CHANGED
@@ -1,3 +1,4 @@
1
+ import { arcMark } from './arcs.js';
1
2
  import { inPass } from './book.js';
2
3
  import { startOf } from './place.js';
3
4
  import { none } from './voice.js';
@@ -105,6 +106,8 @@ export function markOf(mix, voice, mark) {
105
106
  return undefined;
106
107
  if (mark === 'start')
107
108
  return start;
109
+ if (voice.arc !== null && (mark === 'in' || mark === 'coast' || mark === 'out' || mark === 'end'))
110
+ return arcMark(voice, start, mark);
108
111
  if (mark === 'in')
109
112
  return start + (voice.fade.in ?? 0);
110
113
  if (mark === 'coast')
package/dist/mixer.d.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import { type Arc } from './arcs.js';
1
2
  import { blended, laneRead, shareKept } from './blend.js';
2
3
  import type { Book } from './book.js';
3
4
  import { aims, chain, linkable, relink } from './chain.js';
@@ -22,7 +23,7 @@ import { ScoreIndex } from './scored.js';
22
23
  import { Fitting } from './spans.js';
23
24
  import { Store } from './store.js';
24
25
  import { type Announced, Transport } from './transport.js';
25
- import type { BlendMember, Booker, BookOptions, Channel, Columns, Engine, Handle, Kit, Marked, Mix, MixOptions, OwnerSpec, Patch, Projection, Sent, Signal, SpanHandle, SpanSpec, Tape, VoiceSpec } from './types.js';
26
+ import type { ArcHandle, ArcSpec, BlendMember, Booker, BookOptions, Channel, Columns, Engine, Handle, Kit, Marked, Mix, MixOptions, OwnerSpec, Patch, Projection, Sent, Signal, SpanHandle, SpanSpec, Tape, VoiceSpec } from './types.js';
26
27
  import { type Subject, type Voice } from './voice.js';
27
28
  import { base, capped, fadeOf, horizonFor, ownedBy, ownerBase, prime, weigh, weighOwned } from './weigh.js';
28
29
  /**
@@ -127,6 +128,8 @@ export declare class Mixer<I, O> implements Mix<I, O> {
127
128
  */
128
129
  chains: Store<I, Subject<unknown>>;
129
130
  version: number;
131
+ /** What `prime` reads its clock, voice time and pass from: see `Primed`. */
132
+ readonly primed: Float64Array<ArrayBuffer>;
130
133
  readonly steps: Steps<Voice<I, O>, I>;
131
134
  /** Per subject, the voices whose `subjects` name it, in voice order. */
132
135
  named: Store<I, Voice<I, O>[]>;
@@ -179,6 +182,8 @@ export declare class Mixer<I, O> implements Mix<I, O> {
179
182
  readonly bounded: number[];
180
183
  /** By slot, a stock array channel's in-place `lerp`. */
181
184
  readonly lerpsInto: (LerpInto | undefined)[];
185
+ /** By slot, a stock scalar channel's arithmetic, which a fold applies without calling the channel. */
186
+ readonly scalars: ('sum' | 'mul' | 'max' | undefined)[];
182
187
  /** What a fold with a locus in play gathers into, one per depth of folds under way. */
183
188
  readonly locusScratch: LocusScratch<I, O>[];
184
189
  locusDepth: number;
@@ -189,6 +194,8 @@ export declare class Mixer<I, O> implements Mix<I, O> {
189
194
  bookers: Book<I, O>[] | null;
190
195
  /** The fit a span being cued takes, which `cue` hands its voice. */
191
196
  fitting: Fitting | null;
197
+ /** What an arc being cued keeps, which `cue` hands its voice. */
198
+ arcing: Arc | null;
192
199
  /** Every owner still in the mix, null until one is cued. */
193
200
  owners: Voice<I, O>[] | null;
194
201
  /** With a history store, the key each subject is paged under; null without one. */
@@ -226,6 +233,7 @@ export declare class Mixer<I, O> implements Mix<I, O> {
226
233
  blend(members: readonly (Patch<I, O, unknown> | BlendMember<I, O>)[], by: Signal<I>, spec?: Omit<VoiceSpec<I, O>, 'patch' | 'weight' | 'locus'>): Handle<I>[];
227
234
  owns(spec: OwnerSpec<I>): Handle<I>;
228
235
  span(spec: SpanSpec<I>): SpanHandle<I>;
236
+ arc(spec: ArcSpec<I, O>): ArcHandle<I>;
229
237
  sync(timestamp: number): void;
230
238
  book<E = unknown>(opts: BookOptions<E, I>): Booker;
231
239
  probe(subject: I, out?: O): O;
@@ -260,6 +268,10 @@ export declare class Mixer<I, O> implements Mix<I, O> {
260
268
  over?: number;
261
269
  }): void;
262
270
  drop(subject: I): void;
271
+ /** Lets `voice` go of a dropped subject. Methods rather than closures, so a drop makes neither. */
272
+ private forgetIn;
273
+ private releaseIn;
274
+ private recordDrop;
263
275
  touch(subject?: I): void;
264
276
  drain<E = unknown>(tag?: string): Sent<I, E>[];
265
277
  /** Voices whose stop 0 is mid-computation, so a fold for one cannot re-enter itself. */
@@ -328,6 +340,8 @@ declare const methods: {
328
340
  type Methods = typeof methods;
329
341
  export interface Mixer<I, O> extends Methods {
330
342
  }
343
+ /** Makes a mix the host asked for, with the registry's history where it names none, and lists it there. */
344
+ export declare function made<I, O, H>(kit: Kit<O>, opts: MixOptions<H>, lanes: typeof Lanes | null): Mix<I, O, H>;
331
345
  /**
332
346
  * The one that ships: every form on the CPU, probed per subject on demand.
333
347
  *