@msb235/blits 0.9.0 → 0.11.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 (95) hide show
  1. package/CHANGELOG.md +167 -0
  2. package/README.md +16 -5
  3. package/dist/blend.js +1 -5
  4. package/dist/book.d.ts +8 -5
  5. package/dist/book.js +47 -19
  6. package/dist/chain.d.ts +11 -0
  7. package/dist/chain.js +24 -2
  8. package/dist/clock.d.ts +14 -0
  9. package/dist/clock.js +9 -1
  10. package/dist/columns.d.ts +4 -5
  11. package/dist/columns.js +11 -5
  12. package/dist/crowd.d.ts +1 -1
  13. package/dist/crowd.js +4 -1
  14. package/dist/cue.js +52 -5
  15. package/dist/departed.d.ts +3 -2
  16. package/dist/departed.js +7 -4
  17. package/dist/due.d.ts +4 -0
  18. package/dist/due.js +16 -3
  19. package/dist/fade.d.ts +12 -5
  20. package/dist/fade.js +55 -17
  21. package/dist/fold.d.ts +1 -1
  22. package/dist/fold.js +53 -6
  23. package/dist/frames.d.ts +2 -0
  24. package/dist/frames.js +17 -0
  25. package/dist/handle.d.ts +5 -2
  26. package/dist/handle.js +20 -12
  27. package/dist/held.d.ts +8 -0
  28. package/dist/held.js +37 -6
  29. package/dist/history.d.ts +6 -0
  30. package/dist/history.js +15 -5
  31. package/dist/hosts.d.ts +1 -1
  32. package/dist/hosts.js +18 -6
  33. package/dist/index.d.ts +2 -2
  34. package/dist/index.js +1 -1
  35. package/dist/keyfill.js +3 -1
  36. package/dist/lane.d.ts +1 -1
  37. package/dist/lane.js +3 -1
  38. package/dist/lanes.d.ts +8 -0
  39. package/dist/lanes.js +21 -4
  40. package/dist/lean.d.ts +15 -0
  41. package/dist/lean.js +23 -0
  42. package/dist/leavings.d.ts +6 -1
  43. package/dist/leavings.js +16 -2
  44. package/dist/marks.d.ts +7 -3
  45. package/dist/marks.js +102 -27
  46. package/dist/meet.js +1 -0
  47. package/dist/mixer.d.ts +23 -7
  48. package/dist/mixer.js +73 -29
  49. package/dist/motion.d.ts +4 -1
  50. package/dist/motion.js +16 -2
  51. package/dist/motions.d.ts +4 -0
  52. package/dist/motions.js +45 -11
  53. package/dist/move.js +28 -10
  54. package/dist/owner.d.ts +18 -2
  55. package/dist/owner.js +38 -2
  56. package/dist/pack.d.ts +1 -0
  57. package/dist/pack.js +3 -0
  58. package/dist/placed.d.ts +19 -0
  59. package/dist/placed.js +54 -0
  60. package/dist/project.d.ts +5 -2
  61. package/dist/project.js +236 -32
  62. package/dist/pull.js +37 -8
  63. package/dist/qualify.js +8 -2
  64. package/dist/reading.d.ts +6 -0
  65. package/dist/reading.js +6 -0
  66. package/dist/record.js +1 -0
  67. package/dist/restored.d.ts +7 -4
  68. package/dist/restored.js +13 -10
  69. package/dist/revive.d.ts +5 -3
  70. package/dist/revive.js +10 -3
  71. package/dist/rows.js +15 -5
  72. package/dist/seek.d.ts +7 -2
  73. package/dist/seek.js +90 -35
  74. package/dist/shared.d.ts +5 -0
  75. package/dist/shared.js +5 -0
  76. package/dist/signals.d.ts +10 -0
  77. package/dist/signals.js +17 -0
  78. package/dist/store.d.ts +11 -0
  79. package/dist/store.js +19 -0
  80. package/dist/strays.js +50 -2
  81. package/dist/tape.d.ts +29 -4
  82. package/dist/tape.js +49 -11
  83. package/dist/transport.d.ts +11 -2
  84. package/dist/transport.js +20 -9
  85. package/dist/types/mix.d.ts +42 -15
  86. package/dist/types/patch.d.ts +5 -1
  87. package/dist/types/place.d.ts +25 -11
  88. package/dist/types/transport.d.ts +12 -3
  89. package/dist/types/voice.d.ts +62 -4
  90. package/dist/unpage.d.ts +6 -1
  91. package/dist/unpage.js +34 -2
  92. package/dist/voice.d.ts +57 -11
  93. package/dist/voice.js +43 -3
  94. package/docs/schema.html +75 -40
  95. package/package.json +7 -2
package/dist/held.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import { changed } from './chain.js';
2
+ import { stepOf } from './clock.js';
2
3
  import { schedule } from './due.js';
3
4
  import { everyoneOf, inherit, sighted } from './everyone.js';
4
5
  import { originOf } from './origin.js';
@@ -53,11 +54,11 @@ export function held(voice, subject, slot) {
53
54
  held.delay = delay;
54
55
  held.since = since;
55
56
  held.shown = shown;
56
- held.stepped = since < now ? since : now;
57
+ const fresh = meet(this, voice, held, now);
57
58
  held.rebuilt = voice.rebuilds;
58
59
  if (this.projecting) {
59
60
  held.from = held.stepped;
60
- held.unknown = this.backward && voice.spec.from === 'current';
61
+ held.unknown = (this.backward && voice.spec.from === 'current') || fresh;
61
62
  }
62
63
  voice.subjects.set(subject, held);
63
64
  if (!(since <= now)) {
@@ -91,6 +92,30 @@ export function held(voice, subject, slot) {
91
92
  }
92
93
  return held;
93
94
  }
95
+ /**
96
+ * Where a new record met at `now` starts stepping: from its `since`, or from `now` for a voice that
97
+ * catches up `fresh`, on the `stepMs` grid. True where that skipped steps, so the state depends on
98
+ * when the subject was first probed.
99
+ */
100
+ export function meet(mix, voice, held, now) {
101
+ const since = held.since;
102
+ if (!(since < now)) {
103
+ held.stepped = now;
104
+ return false;
105
+ }
106
+ if (voice.spec.catchUp !== 'fresh' || voice.patch.step === undefined) {
107
+ held.stepped = since;
108
+ return false;
109
+ }
110
+ const tick = stepOf(mix, voice);
111
+ if (tick === undefined)
112
+ held.stepped = now;
113
+ else {
114
+ held.ticks = Math.floor((now - since) / tick + 1e-9);
115
+ held.stepped = since + held.ticks * tick;
116
+ }
117
+ return held.stepped > since;
118
+ }
94
119
  /**
95
120
  * The mix time the voice clock first read `delay`, or will by its controls now. A stateful voice
96
121
  * counts from its last seek that rebuilt state, as its records do.
@@ -118,10 +143,19 @@ export function begin(mix, voice, now) {
118
143
  if (voice.anchorElapsed !== 0) {
119
144
  voice.seeks++;
120
145
  voice.rebuilds++;
146
+ // Kept with its controls, so a seek back to after this puts back the count its records hold.
147
+ voice.note(now, mix.transport.seq, true);
121
148
  }
122
149
  reorigin(mix, voice, now);
123
150
  changed(mix, voice);
124
151
  }
152
+ /** Counts a record whose `since` was still ahead from where its voice's clock now puts it. */
153
+ export function origin(mix, voice, held) {
154
+ held.since = mix.sinceOf(voice, held.delay);
155
+ held.shown = mix.shownOf(voice, held.since);
156
+ if (held.ticks === 0 && held.stepped > held.since)
157
+ held.stepped = held.since;
158
+ }
125
159
  /**
126
160
  * A voice's start or clock moved, or it went live at `now`: the records whose `since` was still
127
161
  * ahead count from where its clock now puts them.
@@ -132,10 +166,7 @@ export function reorigin(mix, voice, now) {
132
166
  return;
133
167
  let kept = 0;
134
168
  for (const held of early) {
135
- held.since = mix.sinceOf(voice, held.delay);
136
- held.shown = mix.shownOf(voice, held.since);
137
- if (held.ticks === 0 && held.stepped > held.since)
138
- held.stepped = held.since;
169
+ origin(mix, voice, held);
139
170
  if (!(held.since <= now))
140
171
  early[kept++] = held;
141
172
  }
package/dist/history.d.ts CHANGED
@@ -19,12 +19,18 @@ export declare function same(a: unknown, b: unknown): boolean;
19
19
  export declare function elapsedThen<I, O>(mix: Mixer<I, O>, voice: Voice<I, O>, t: number): number;
20
20
  /** Records a change to a voice's controls under `history`, and lets go of what it no longer reaches. */
21
21
  export declare function noted<I, O>(mix: Mixer<I, O>, voice: Voice<I, O>): void;
22
+ /** A copy of the state a voice's patch keeps for a subject. */
23
+ export declare function copyState<I, O>(voice: Voice<I, O>, state: unknown): unknown;
22
24
  /** A copy of a subject's record that shares nothing a read could change. */
23
25
  export declare function copyHeld<I, O>(voice: Voice<I, O>, h: Subject<unknown>): Subject<unknown>;
24
26
  /** Under `history` with `inputs`, copies the host fields any voice reads, once a frame, when they changed. */
25
27
  export declare function recordHost<I, O>(mix: Mixer<I, O>, now: number): void;
26
28
  /** Under `history` with `inputs`, keeps what an input signal read, each time it changes. */
27
29
  export declare function record<I, O>(mix: Mixer<I, O>, voice: Voice<I, O>, subject: I, held: Subject<unknown>, value: number): void;
30
+ /** How far apart, in mix ms, a record's copies are kept. */
31
+ export declare function apart(history: {
32
+ readonly every?: number;
33
+ }): number;
28
34
  /** Under `history`, keeps a copy of a stateful voice's record for this subject every so often. */
29
35
  export declare function remember<I, O>(mix: Mixer<I, O>, voice: Voice<I, O>, subject: I, held: Subject<unknown>): void;
30
36
  /**
package/dist/history.js CHANGED
@@ -93,6 +93,11 @@ function older(list, reach) {
93
93
  n++;
94
94
  return n === 0 ? [] : list.splice(0, n);
95
95
  }
96
+ /** A copy of the state a voice's patch keeps for a subject. */
97
+ export function copyState(voice, state) {
98
+ const patch = voice.patch;
99
+ return patch.clone ? patch.clone(state) : clone(state);
100
+ }
96
101
  /** A copy of a subject's record that shares nothing a read could change. */
97
102
  export function copyHeld(voice, h) {
98
103
  let kept = null;
@@ -101,11 +106,10 @@ export function copyHeld(voice, h) {
101
106
  for (const [owner, value] of h.kept)
102
107
  kept.set(owner, clone(value));
103
108
  }
104
- const patch = voice.patch;
105
109
  return {
106
110
  ...h,
107
111
  bands: h.bands === null ? null : h.bands.slice(),
108
- state: h.state === undefined ? undefined : patch.clone ? patch.clone(h.state) : clone(h.state),
112
+ state: h.state === undefined ? undefined : copyState(voice, h.state),
109
113
  kept,
110
114
  unkept: h.unkept?.map(clone),
111
115
  probed: Number.NaN,
@@ -160,6 +164,10 @@ export function record(mix, voice, subject, held, value) {
160
164
  if (out.length > 0)
161
165
  pageOut(mix, 'input', voice.id, mix.keys?.key(subject), out, (e) => e.value, reach);
162
166
  }
167
+ /** How far apart, in mix ms, a record's copies are kept. */
168
+ export function apart(history) {
169
+ return history.every ?? 200;
170
+ }
163
171
  /** Under `history`, keeps a copy of a stateful voice's record for this subject every so often. */
164
172
  export function remember(mix, voice, subject, held) {
165
173
  const history = mix.opts.history;
@@ -172,7 +180,7 @@ export function remember(mix, voice, subject, held) {
172
180
  const snaps = held.snaps ?? [];
173
181
  held.snaps = snaps;
174
182
  const prev = snaps[snaps.length - 1];
175
- if (prev !== undefined && mix.now - prev.at < (history.every ?? 200))
183
+ if (prev !== undefined && mix.now - prev.at < apart(history))
176
184
  return;
177
185
  snaps.push({ at: mix.now, seq: mix.transport.seq, held: copyHeld(voice, held) });
178
186
  const reach = mix.now - history.ms;
@@ -194,13 +202,15 @@ export function leave(mix, voice, subject, held) {
194
202
  const keys = mix.keys;
195
203
  left.expire(reach, keys === null
196
204
  ? null
197
- : (e) => pageOut(mix, 'left', voice.id, keys.key(e.subject), [e], (x) => [packHeld(voice, x.held), x.sync], reach));
205
+ : (e) => pageOut(mix, 'left', voice.id, keys.key(e.subject), [e], (x) => [packHeld(voice, x.held), x.sync, x.ramp, x.parted], reach));
198
206
  left.push({
199
207
  subject,
200
208
  at: Number.isNaN(mix.now) ? Number.NEGATIVE_INFINITY : mix.now,
201
209
  seq: mix.transport.seq,
202
- sync: mix.syncing,
210
+ sync: mix.syncing || !mix.looked,
203
211
  held,
212
+ ramp: voice.parts?.get(subject) ?? null,
213
+ parted: voice.parted?.get(subject) ?? null,
204
214
  });
205
215
  }
206
216
  /**
package/dist/hosts.d.ts CHANGED
@@ -2,7 +2,7 @@ import type { LaneHost } from './lanes.js';
2
2
  import type { Mixer } from './mixer.js';
3
3
  import type { Booker, BookOptions, Handle } from './types.js';
4
4
  import type { Voice } from './voice.js';
5
- export declare function book<I, O>(mix: Mixer<I, O>, opts: BookOptions): Booker;
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
7
  /**
8
8
  * Whether a voice's patch and spec can run on a lane, its channels aside. A voice whose patch has
package/dist/hosts.js CHANGED
@@ -1,5 +1,6 @@
1
1
  import { Book } from './book.js';
2
2
  import { numericOf } from './channels.js';
3
+ import { stepOf } from './clock.js';
3
4
  import { crowdable } from './crowd.js';
4
5
  import { schedule } from './due.js';
5
6
  import { unshare, unsight, weightAt } from './everyone.js';
@@ -14,6 +15,7 @@ import { descendants, ownWeight, signalled } from './owner.js';
14
15
  import { localNow } from './place.js';
15
16
  import { reading } from './reading.js';
16
17
  import { heard } from './signals.js';
18
+ import { cuedById } from './strays.js';
17
19
  import { record } from './tape.js';
18
20
  import { unreach } from './unreached.js';
19
21
  export function book(mix, opts) {
@@ -161,8 +163,7 @@ function seekDoubt(mix, voice, rebuild) {
161
163
  return 'stepped';
162
164
  if (patch.step === undefined)
163
165
  return 'exact';
164
- const tick = mix.opts.stepMs;
165
- const fixed = tick !== undefined && tick > 0 && !mix.reducedNow && mix.opts.maxDt === undefined;
166
+ const fixed = stepOf(mix, voice) !== undefined && mix.opts.maxDt === undefined;
166
167
  return fixed ? 'exact' : 'stepped';
167
168
  }
168
169
  /** A voice's patch kept state on a record: it is stateful from now on. */
@@ -234,10 +235,21 @@ function handleHost(mix) {
234
235
  if (w !== undefined)
235
236
  return w;
236
237
  }
237
- if (voice.sharing)
238
- return weightAt(voice, slot);
239
- return voice.subjects.get(subject)?.weight ?? 0;
238
+ const held = voice.sharing
239
+ ? undefined
240
+ : voice.subjects.get(subject);
241
+ // A voice that stopped sharing makes a subject's record at its next read, from what it kept.
242
+ return held !== undefined ? held.weight : weightAt(voice, slot);
240
243
  },
241
- record: (label, again) => record(mix, label, again),
244
+ record: (h, label, again) => record(mix, label, (stage) => {
245
+ const to = stage.mix(mix);
246
+ if (to === mix)
247
+ again(h);
248
+ else if (to !== undefined) {
249
+ const voice = cuedById(to, h.id);
250
+ if (voice !== undefined)
251
+ again(handle(to, voice));
252
+ }
253
+ }),
242
254
  };
243
255
  }
package/dist/index.d.ts CHANGED
@@ -12,11 +12,11 @@ export type { KeysOptions, PatchOptions } from './patch.js';
12
12
  export { keys, patch } from './patch.js';
13
13
  export type { AngleOptions } from './rotation.js';
14
14
  export { angle, quat } from './rotation.js';
15
- export { gate, input, lag, level, peak, slew } from './signals.js';
15
+ export { gate, input, lag, level, peak, share, slew } from './signals.js';
16
16
  export { fold } from './stack.js';
17
17
  export type { Ticked, Ticker, TickerOptions } from './ticker.js';
18
18
  export { ticker } from './ticker.js';
19
19
  export { transport } from './transport.js';
20
- export type { Anchor, BookedHit, Booker, BookOptions, Channel, Columns, Described, Doubt, Easing, Engine, FadeOptions, FadeSpec, FitResult, Handle, HistoryOptions, HistoryStore, Hit, Keyframe, Kit, Mark, Marked, 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';
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';
21
21
  export type { WaveOptions, WaveShape } from './wave.js';
22
22
  export { wave, waveAt } from './wave.js';
package/dist/index.js CHANGED
@@ -6,7 +6,7 @@ export { glide, spring, tween } from './motion.js';
6
6
  export { HistoryMiss } from './paging.js';
7
7
  export { keys, patch } from './patch.js';
8
8
  export { angle, quat } from './rotation.js';
9
- export { gate, input, lag, level, peak, slew } from './signals.js';
9
+ export { gate, input, lag, level, peak, share, slew } from './signals.js';
10
10
  export { fold } from './stack.js';
11
11
  export { ticker } from './ticker.js';
12
12
  export { transport } from './transport.js';
package/dist/keyfill.js CHANGED
@@ -23,6 +23,7 @@ export function runKeys(lanes, lane) {
23
23
  const before = voice.freezesBefore;
24
24
  const after = voice.freezesAfter;
25
25
  const span = voice.span;
26
+ const hides = voice.hidesPast;
26
27
  const flat = lane.flat;
27
28
  const parts = voice.parts !== null;
28
29
  const whole = clampWeight(voice.weight);
@@ -42,7 +43,8 @@ export function runKeys(lanes, lane) {
42
43
  data[o + 3 /* Row.PROBED */] = data[o + 2 /* Row.WEIGHT */];
43
44
  if (lately >= 0 && lanes.unread(slot))
44
45
  continue;
45
- const elapsed = frozenTime(elapsedNow - data[o + 0 /* Row.DELAY */], before, after, span);
46
+ const raw = elapsedNow - data[o + 0 /* Row.DELAY */];
47
+ const elapsed = raw > span && hides ? Number.NaN : frozenTime(raw, before, after, span);
46
48
  if (!(elapsed >= 0)) {
47
49
  data[o + 2 /* Row.WEIGHT */] = 0;
48
50
  unband(records[p]);
package/dist/lane.d.ts CHANGED
@@ -235,6 +235,6 @@ export declare class Lane<I, O> implements Positions<I, O> {
235
235
  }
236
236
  /**
237
237
  * A subject's voice time once its voice's freezes apply: frozen at 0 before it starts and at the end
238
- * of its passes after, and NaN where it shows nothing.
238
+ * of its passes after, and NaN where it shows nothing, as past its end running back.
239
239
  */
240
240
  export declare function frozenAt<I, O>(voice: Voice<I, O>, elapsed: number): number;
package/dist/lane.js CHANGED
@@ -185,8 +185,10 @@ export class Lane {
185
185
  }
186
186
  /**
187
187
  * A subject's voice time once its voice's freezes apply: frozen at 0 before it starts and at the end
188
- * of its passes after, and NaN where it shows nothing.
188
+ * of its passes after, and NaN where it shows nothing, as past its end running back.
189
189
  */
190
190
  export function frozenAt(voice, elapsed) {
191
+ if (elapsed > voice.span && voice.hidesPast)
192
+ return Number.NaN;
191
193
  return frozenTime(elapsed, voice.freezesBefore, voice.freezesAfter, voice.span);
192
194
  }
package/dist/lanes.d.ts CHANGED
@@ -7,6 +7,7 @@ import { type Lane, type Laned, type Locus } from './lane.js';
7
7
  import type { Watcher } from './motions.js';
8
8
  import { absent, Numbers } from './numbers.js';
9
9
  import { Owed } from './owed.js';
10
+ import { Placed } from './placed.js';
10
11
  import type { Channel } from './types.js';
11
12
  import type { Subject, Voice } from './voice.js';
12
13
  /** What a lane fill asks of the mix it belongs to. */
@@ -65,6 +66,13 @@ export declare class Lanes<I, O> implements Watcher {
65
66
  /** The crowds of single-subject voices, one per list of channels, and which holds each voice. */
66
67
  crowds: Crowd<I, O>[];
67
68
  readonly crowdOf: Map<number, Crowd<I, O>>;
69
+ /** Which crowd voices' rows are placed on each subject. */
70
+ readonly placed: Placed;
71
+ /**
72
+ * By subject number, the mix's `relinks` when the subject's chain was last made stale by itself,
73
+ * so `pull` tells a head it remembers is stale without reading the head.
74
+ */
75
+ unlinked: Float64Array<ArrayBuffer>;
68
76
  /** Whether two crowds share a channel, so their rows must interleave in voice order. */
69
77
  overlap: boolean;
70
78
  /** The loci whose voices are on lanes, every member of each on one. */
package/dist/lanes.js CHANGED
@@ -5,6 +5,7 @@ import { fold, foldDelta, foldInto, foldRun, gate, gather, unplain } from './gat
5
5
  import { meet, reach } from './meet.js';
6
6
  import { absent, Numbers } from './numbers.js';
7
7
  import { Owed } from './owed.js';
8
+ import { Placed } from './placed.js';
8
9
  import { requalify, retouch } from './qualify.js';
9
10
  import { reading } from './reading.js';
10
11
  import { reportedRow, unplace } from './rows.js';
@@ -23,6 +24,13 @@ export class Lanes {
23
24
  /** The crowds of single-subject voices, one per list of channels, and which holds each voice. */
24
25
  crowds = [];
25
26
  crowdOf = new Map();
27
+ /** Which crowd voices' rows are placed on each subject. */
28
+ placed = new Placed();
29
+ /**
30
+ * By subject number, the mix's `relinks` when the subject's chain was last made stale by itself,
31
+ * so `pull` tells a head it remembers is stale without reading the head.
32
+ */
33
+ unlinked = new Float64Array(0);
26
34
  /** Whether two crowds share a channel, so their rows must interleave in voice order. */
27
35
  overlap = false;
28
36
  /** The loci whose voices are on lanes, every member of each on one. */
@@ -374,10 +382,15 @@ export class Lanes {
374
382
  this.live--;
375
383
  for (const lane of this.lanes)
376
384
  lane.remove(slot);
377
- for (const c of this.crowds)
378
- for (let p = 0; p < c.size; p++)
379
- if (c.list[p] === slot)
380
- unplace(this, c, p);
385
+ const placed = this.placed;
386
+ for (let id = placed.any(slot); id !== 0; id = placed.any(slot)) {
387
+ const c = this.crowdOf.get(id);
388
+ const p = c?.rowOf.get(id);
389
+ if (c !== undefined && p !== undefined && c.list[p] === slot)
390
+ unplace(this, c, p);
391
+ else
392
+ placed.remove(slot, id);
393
+ }
381
394
  if (slot < this.cap)
382
395
  this.per[slot * 6 /* Per.SLOT */ + 0 /* Per.FILLED */] = this.fills;
383
396
  this.subjects[slot] = undefined;
@@ -390,6 +403,10 @@ export class Lanes {
390
403
  per.set(this.per);
391
404
  this.per = per;
392
405
  this.owed.grow(cap);
406
+ this.placed.grow(cap);
407
+ const unlinked = new Float64Array(cap);
408
+ unlinked.set(this.unlinked);
409
+ this.unlinked = unlinked;
393
410
  for (const ch of this.laned) {
394
411
  const values = new Float64Array(cap * ch.axes);
395
412
  values.set(ch.values);
package/dist/lean.d.ts ADDED
@@ -0,0 +1,15 @@
1
+ import type { Engine, Kit, Mix, MixOptions } from './types.js';
2
+ export * from './index.js';
3
+ /**
4
+ * The stock engine without lanes: every voice folds subject by subject, to the same values, and a
5
+ * bundle that takes `mix` from here leaves the lanes' code out. `lanes: true` is refused.
6
+ *
7
+ * @category engine
8
+ */
9
+ export declare const lean: Engine;
10
+ /**
11
+ * Makes a mix over a kit, on the engine the options name or the lean one.
12
+ *
13
+ * @category mix
14
+ */
15
+ export declare function mix<I, O, H = unknown>(kit: Kit<O>, opts?: MixOptions<H>): Mix<I, O, H>;
package/dist/lean.js ADDED
@@ -0,0 +1,23 @@
1
+ import { Mixer } from './mixer.js';
2
+ export * from './index.js';
3
+ /**
4
+ * The stock engine without lanes: every voice folds subject by subject, to the same values, and a
5
+ * bundle that takes `mix` from here leaves the lanes' code out. `lanes: true` is refused.
6
+ *
7
+ * @category engine
8
+ */
9
+ export const lean = {
10
+ name: 'lean',
11
+ runs: new Set(['fn', 'keys', 'motion']),
12
+ create(kit, opts) {
13
+ return new Mixer(kit, opts, null);
14
+ },
15
+ };
16
+ /**
17
+ * Makes a mix over a kit, on the engine the options name or the lean one.
18
+ *
19
+ * @category mix
20
+ */
21
+ export function mix(kit, opts = {}) {
22
+ return (opts.engine ?? lean).create(kit, opts);
23
+ }
@@ -17,8 +17,13 @@ export declare class Leavings<I> {
17
17
  * front: entries are in the order they left, and mix time does not run back along it.
18
18
  */
19
19
  expire(reach: number, out: ((e: Left<I>) => void) | null): void;
20
- /** The record `subject` had at `cut` and left with after it, if it left after `cut`. */
20
+ /**
21
+ * The record `subject` had at `cut` and left with after it, if it left after `cut`. None where it
22
+ * left saying only that it had faded out, after `cut`, of a voice that had not met it.
23
+ */
21
24
  at(subject: I, cut: Cut): Subject<unknown> | undefined;
25
+ /** Each subject's first leaving after `cut`, which holds what it had at `cut`. */
26
+ after(cut: Cut): Left<I>[];
22
27
  /** Each time `subject` left, oldest first. */
23
28
  of(subject: I): readonly Left<I>[];
24
29
  /** Every entry, oldest first. */
package/dist/leavings.js CHANGED
@@ -49,16 +49,30 @@ export class Leavings {
49
49
  this.head = 0;
50
50
  }
51
51
  }
52
- /** The record `subject` had at `cut` and left with after it, if it left after `cut`. */
52
+ /**
53
+ * The record `subject` had at `cut` and left with after it, if it left after `cut`. None where it
54
+ * left saying only that it had faded out, after `cut`, of a voice that had not met it.
55
+ */
53
56
  at(subject, cut) {
54
57
  const own = this.by.get(subject);
55
58
  if (own === undefined)
56
59
  return undefined;
57
60
  for (const e of own)
58
61
  if (!within(cut, e.seq, e.sync))
59
- return e.held;
62
+ return !e.held.reaches && e.parted !== null && e.parted.seq > cut.seq ? undefined : e.held;
60
63
  return undefined;
61
64
  }
65
+ /** Each subject's first leaving after `cut`, which holds what it had at `cut`. */
66
+ after(cut) {
67
+ const out = [];
68
+ const seen = new Set();
69
+ for (const e of this.all())
70
+ if (!within(cut, e.seq, e.sync) && !seen.has(e.subject)) {
71
+ seen.add(e.subject);
72
+ out.push(e);
73
+ }
74
+ return out;
75
+ }
62
76
  /** Each time `subject` left, oldest first. */
63
77
  of(subject) {
64
78
  return this.by.get(subject) ?? [];
package/dist/marks.d.ts CHANGED
@@ -1,8 +1,12 @@
1
- import type { Listed } from './book.js';
1
+ import { type Listed } from './book.js';
2
2
  import type { Mixer } from './mixer.js';
3
3
  import type { Transport } from './transport.js';
4
- import type { Mark } from './types.js';
4
+ import type { MarkName } from './types.js';
5
5
  import { type Voice } from './voice.js';
6
+ /** A voice's marks, in the order `marks` lists those at one time. */
7
+ export declare const voiceMarks: readonly ["start", "in", "coast", "out", "end"];
8
+ /** Refuses marks placed inside a voice under one of its own five names, or that it could never reach. */
9
+ export declare function checkMarks(marks: Readonly<Record<string, number>>, duration: number): void;
6
10
  /** The host timestamp the mix clock reads mix time `t` at; Infinity where it never will. */
7
11
  export declare function hostOf<I, O>(mix: Mixer<I, O>, t: number): number;
8
12
  /** What the mix clock reads at host time `u`, by the clock in force then. */
@@ -12,4 +16,4 @@ export declare function listed<I, O>(mix: Mixer<I, O>, from: number, to: number)
12
16
  /** Every mark of every mix on a transport and every one announced on it, earliest first. */
13
17
  export declare function listedAll(transport: Transport, from: number, to: number): Listed[];
14
18
  /** When a voice reaches a mark, mix time, or undefined while nothing has fixed it. */
15
- export declare function markOf<I, O>(mix: Mixer<I, O>, voice: Voice<I, O>, mark: Mark): number | undefined;
19
+ export declare function markOf<I, O>(mix: Mixer<I, O>, voice: Voice<I, O>, mark: MarkName): number | undefined;