@msb235/blits 0.5.0 → 0.7.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 (99) hide show
  1. package/CHANGELOG.md +163 -0
  2. package/README.md +32 -8
  3. package/dist/bare.js +7 -1
  4. package/dist/book.d.ts +8 -1
  5. package/dist/book.js +35 -4
  6. package/dist/chain.d.ts +4 -3
  7. package/dist/chain.js +10 -6
  8. package/dist/channels.d.ts +12 -19
  9. package/dist/channels.js +18 -118
  10. package/dist/clock.d.ts +12 -5
  11. package/dist/clock.js +13 -5
  12. package/dist/closed.d.ts +15 -0
  13. package/dist/closed.js +144 -0
  14. package/dist/color.d.ts +61 -0
  15. package/dist/color.js +229 -0
  16. package/dist/columns.js +2 -1
  17. package/dist/crowd.d.ts +4 -4
  18. package/dist/crowd.js +11 -8
  19. package/dist/cue.d.ts +2 -0
  20. package/dist/cue.js +78 -27
  21. package/dist/due.js +5 -2
  22. package/dist/fade.d.ts +13 -0
  23. package/dist/fade.js +55 -4
  24. package/dist/fill.d.ts +1 -1
  25. package/dist/fill.js +17 -10
  26. package/dist/fit.d.ts +144 -0
  27. package/dist/fit.js +181 -0
  28. package/dist/fold.d.ts +3 -3
  29. package/dist/fold.js +25 -18
  30. package/dist/gather.d.ts +6 -0
  31. package/dist/gather.js +18 -0
  32. package/dist/handle.d.ts +27 -2
  33. package/dist/handle.js +60 -11
  34. package/dist/held.js +7 -3
  35. package/dist/history.d.ts +15 -2
  36. package/dist/history.js +54 -9
  37. package/dist/hosts.d.ts +2 -2
  38. package/dist/hosts.js +19 -6
  39. package/dist/index.d.ts +10 -2
  40. package/dist/index.js +6 -1
  41. package/dist/lane.d.ts +4 -4
  42. package/dist/lane.js +5 -5
  43. package/dist/lanes.d.ts +32 -9
  44. package/dist/lanes.js +53 -24
  45. package/dist/locus.d.ts +3 -3
  46. package/dist/locus.js +1 -1
  47. package/dist/marks.d.ts +3 -0
  48. package/dist/marks.js +81 -13
  49. package/dist/meet.d.ts +3 -2
  50. package/dist/meet.js +15 -3
  51. package/dist/mixer.d.ts +55 -35
  52. package/dist/mixer.js +173 -83
  53. package/dist/motion.d.ts +2 -199
  54. package/dist/motion.js +2 -652
  55. package/dist/motions.d.ts +266 -0
  56. package/dist/motions.js +724 -0
  57. package/dist/move.d.ts +1 -1
  58. package/dist/move.js +27 -11
  59. package/dist/owner.d.ts +2 -2
  60. package/dist/owner.js +5 -5
  61. package/dist/pace.d.ts +2 -0
  62. package/dist/pace.js +8 -0
  63. package/dist/pack.d.ts +31 -0
  64. package/dist/pack.js +65 -0
  65. package/dist/paging.d.ts +63 -0
  66. package/dist/paging.js +110 -0
  67. package/dist/patch.d.ts +3 -0
  68. package/dist/patch.js +2 -0
  69. package/dist/place.js +74 -25
  70. package/dist/project.d.ts +7 -4
  71. package/dist/project.js +117 -78
  72. package/dist/pull.js +4 -3
  73. package/dist/qualify.js +4 -2
  74. package/dist/revive.d.ts +58 -0
  75. package/dist/revive.js +158 -0
  76. package/dist/sample.d.ts +1 -1
  77. package/dist/sample.js +6 -1
  78. package/dist/seek.d.ts +3 -0
  79. package/dist/seek.js +311 -0
  80. package/dist/signals.d.ts +3 -2
  81. package/dist/signals.js +12 -6
  82. package/dist/spans.d.ts +29 -0
  83. package/dist/spans.js +165 -0
  84. package/dist/strays.d.ts +1 -1
  85. package/dist/strays.js +21 -0
  86. package/dist/tape.d.ts +21 -0
  87. package/dist/tape.js +65 -0
  88. package/dist/ticker.d.ts +73 -0
  89. package/dist/ticker.js +115 -0
  90. package/dist/transport.d.ts +121 -0
  91. package/dist/transport.js +271 -0
  92. package/dist/types.d.ts +427 -50
  93. package/dist/unpage.d.ts +8 -0
  94. package/dist/unpage.js +163 -0
  95. package/dist/voice.d.ts +78 -12
  96. package/dist/voice.js +126 -12
  97. package/dist/weigh.js +2 -2
  98. package/docs/schema.html +140 -48
  99. package/package.json +2 -1
package/dist/cue.js CHANGED
@@ -1,15 +1,58 @@
1
1
  import { checkHits } from './book.js';
2
2
  import { changed, index } from './chain.js';
3
3
  import { schedule } from './due.js';
4
+ import { VoiceHandle } from './handle.js';
4
5
  import { started } from './held.js';
5
6
  import { handle } from './hosts.js';
6
7
  import { motionOf } from './motion.js';
7
- import { adopt, ownerReading } from './owner.js';
8
+ import { adopt, ownerPatch, ownerReading } from './owner.js';
8
9
  import { durationOf } from './patch.js';
9
10
  import { checkPlacement, localNow, mixAt, pin, place } from './place.js';
11
+ import { checkChild, refit } from './spans.js';
10
12
  import { motionOwner } from './strays.js';
13
+ import { record } from './tape.js';
11
14
  import { Voice } from './voice.js';
12
15
  export function cue(mix, spec) {
16
+ const patch = spec.patch;
17
+ playable(mix, spec);
18
+ if (spec.from === 'current' && patch.form !== 'keys')
19
+ throw new Error("blits: from: 'current' needs a keys patch");
20
+ if (spec.subjects !== undefined && spec.target !== undefined)
21
+ throw new Error('blits: a voice takes target or subjects, not both');
22
+ const anchor = spec.anchor;
23
+ const owner = spec.owner === undefined ? null : ownerOf(mix, spec.owner);
24
+ if (anchor)
25
+ checkPlacement(mix, spec, anchor, owner);
26
+ if (spec.hits !== undefined)
27
+ checkHits(spec.hits, durationOf(patch));
28
+ const anchored = anchor !== undefined && (anchor.start !== undefined || anchor.in !== undefined);
29
+ // A voice an owner holds is placed on the owner's clock: ms from when it starts.
30
+ const local = owner === null ? mix.now : ownerReading(owner, mix.now);
31
+ const start = anchored || owner?.fitting
32
+ ? Number.POSITIVE_INFINITY
33
+ : owner !== null
34
+ ? (spec.start ?? (local > 0 ? local : 0))
35
+ : spec.start !== undefined
36
+ ? mixAt(mix, spec.start - mix.offset)
37
+ : Number.isNaN(mix.now)
38
+ ? 0
39
+ : mix.now;
40
+ const voice = new Voice(mix.nextId++, spec, patch, spec.fade ?? {}, local, start, mix.slotOf, mix.channels, mix.opts.host, mix.send, owner);
41
+ if (patch === ownerPatch && mix.fitting !== null)
42
+ voice.fitting = mix.fitting;
43
+ if (owner?.fitting)
44
+ checkChild(spec, voice);
45
+ if (mix.pace !== null && owner === null && spec.start !== undefined && voice.state === 'pending')
46
+ voice.pinned = spec.start - mix.offset;
47
+ enter(mix, voice);
48
+ const h = handle(mix, voice);
49
+ voice.handle = h;
50
+ // By its handle, so a voice a history store paged out and a seek revived is the one cued again.
51
+ record(mix, 'cue', () => recue(mix, VoiceHandle.voiceOf(h) ?? lost(h)));
52
+ return h;
53
+ }
54
+ /** Refuses a spec whose patch the mix cannot play, or whose state its history cannot keep. */
55
+ export function playable(mix, spec) {
13
56
  const patch = spec.patch;
14
57
  for (const channel of patch.writes) {
15
58
  if (!(channel in mix.kit))
@@ -34,29 +77,30 @@ export function cue(mix, spec) {
34
77
  if (typeof host !== 'object' || host === null || !(field in host))
35
78
  throw new Error(`blits: the patch reads host.${field}, which this mix's host lacks`);
36
79
  }
37
- if (spec.from === 'current' && patch.form !== 'keys')
38
- throw new Error("blits: from: 'current' needs a keys patch");
39
- if (spec.subjects !== undefined && spec.target !== undefined)
40
- throw new Error('blits: a voice takes target or subjects, not both');
80
+ if (mix.transport.pager !== null &&
81
+ (patch.state !== undefined || patch.step !== undefined) &&
82
+ (patch.pack === undefined || patch.unpack === undefined))
83
+ throw new Error("blits: this mix's history has a store, which keeps a stateful patch's state as data; give the patch pack and unpack");
84
+ if (spec.as !== undefined && mix.transport.pager !== null && !mix.opts.history?.revive)
85
+ throw new Error('blits: a voice cued with as needs history.revive to rebuild it from');
86
+ }
87
+ /** A voice parked by a seek, cued again as the mix plays past its cue, on the handle it had. */
88
+ function recue(mix, voice) {
89
+ const owner = voice.owner;
90
+ if ((owner === null ? mix.now : ownerReading(owner, mix.now)) >= voice.start)
91
+ voice.state = 'live';
92
+ enter(mix, voice);
93
+ }
94
+ function lost(h) {
95
+ throw new Error(`blits: voice ${h.id} is paged out, and the seek that plays its cue again did not load it`);
96
+ }
97
+ /** Puts a voice just made, or parked by a seek, into the mix. */
98
+ function enter(mix, voice) {
99
+ const spec = voice.spec;
100
+ const owner = voice.owner;
41
101
  const anchor = spec.anchor;
42
- const owner = spec.owner === undefined ? null : ownerOf(mix, spec.owner);
43
- if (anchor)
44
- checkPlacement(mix, spec, anchor, owner);
45
- if (spec.hits !== undefined)
46
- checkHits(spec.hits, durationOf(patch));
47
102
  const anchored = anchor !== undefined && (anchor.start !== undefined || anchor.in !== undefined);
48
- // A voice an owner holds is placed on the owner's clock: ms from when it starts.
49
- const local = owner === null ? mix.now : ownerReading(owner, mix.now);
50
- const start = anchored
51
- ? Number.POSITIVE_INFINITY
52
- : owner !== null
53
- ? (spec.start ?? (local > 0 ? local : 0))
54
- : spec.start !== undefined
55
- ? mixAt(mix, spec.start - mix.offset)
56
- : Number.isNaN(mix.now)
57
- ? 0
58
- : mix.now;
59
- const voice = new Voice(mix.nextId++, spec, patch, spec.fade ?? {}, local, start, mix.slotOf, mix.channels, mix.opts.host, mix.send, owner);
103
+ const motion = voice.motion;
60
104
  if (owner !== null)
61
105
  adopt(owner, voice);
62
106
  if (voice.holding !== null) {
@@ -68,15 +112,21 @@ export function cue(mix, spec) {
68
112
  voice.opened = mix.now;
69
113
  }
70
114
  voice.placing = anchored;
71
- if (mix.pace !== null && owner === null && spec.start !== undefined && voice.state === 'pending')
72
- pin(mix, voice, spec.start - mix.offset);
115
+ // A start the host gave by its own clock, where the mix's rate puts it.
116
+ if (!Number.isNaN(voice.pinned) && voice.state === 'pending')
117
+ pin(mix, voice, voice.pinned);
73
118
  if (motion !== undefined) {
74
119
  mix.playing.set(motion, (mix.playing.get(motion) ?? 0) + 1);
75
120
  mix.owner ??= motionOwner(new WeakRef(mix));
76
121
  motion.owner = mix.owner;
77
122
  motion.ownerId = voice.id;
78
123
  }
79
- mix.cued.push(voice);
124
+ // In id order, which a voice cued again after later ones keeps by going in at its place.
125
+ const cued = mix.cued;
126
+ let i = cued.length;
127
+ while (i > 0 && cued[i - 1].id > voice.id)
128
+ i--;
129
+ cued.splice(i, 0, voice);
80
130
  index(mix, voice);
81
131
  changed(mix, voice);
82
132
  mix.stir();
@@ -95,14 +145,15 @@ export function cue(mix, spec) {
95
145
  changed(mix, voice);
96
146
  }
97
147
  }
148
+ if (owner?.fitting)
149
+ refit(mix, owner);
98
150
  // After placing, so the controls it starts with are where its anchors put it at the cue.
99
151
  if (mix.opts.history) {
100
152
  voice.log = [];
101
153
  voice.note(Number.NEGATIVE_INFINITY);
154
+ voice.first ??= voice.log[0];
102
155
  }
103
156
  schedule(mix, voice);
104
- voice.handle = handle(mix, voice);
105
- return voice.handle;
106
157
  }
107
158
  /** The owner a handle names, refused where it is not one of this mix's or has left. */
108
159
  function ownerOf(mix, handle) {
package/dist/due.js CHANGED
@@ -15,6 +15,9 @@ function dueOf(mix, voice) {
15
15
  if (voice.parts !== null)
16
16
  for (const r of voice.parts.values())
17
17
  due = Math.min(due, r.at + r.over);
18
+ const back = voice.back;
19
+ if (back !== null)
20
+ due = Math.min(due, back.at + (1 - back.from) * back.over);
18
21
  if (voice.state === 'pending')
19
22
  return Math.min(due, Number.isNaN(voice.start) ? due : voice.start);
20
23
  if (voice.state === 'fading') {
@@ -26,10 +29,10 @@ function dueOf(mix, voice) {
26
29
  due = Math.min(due, voice.outAt);
27
30
  if (Number.isFinite(voice.span)) {
28
31
  const end = voice.span + voice.latest;
29
- // A held voice goes live again once its clock is back before its end: by a seek, by a
32
+ // A frozen voice goes live again once its clock is back before its end: by a seek, by a
30
33
  // subject staggered later than any before, or by running backwards, which is checked each
31
34
  // frame while it can.
32
- if (voice.state === 'held') {
35
+ if (voice.state === 'frozen') {
33
36
  if (voice.rate <= 0 || voice.ramp !== null)
34
37
  return Number.NEGATIVE_INFINITY;
35
38
  if (!Number.isNaN(mix.now) && voice.elapsedAt(mix.now) < end)
package/dist/fade.d.ts CHANGED
@@ -16,4 +16,17 @@ export declare function beginFade<I, O>(mix: Mixer<I, O>, voice: Voice<I, O>, op
16
16
  at?: 'rest';
17
17
  deadline?: number;
18
18
  }, at?: number): void;
19
+ /**
20
+ * Turns a fading voice around: it climbs back from where its fade out had got to, up the same
21
+ * curve, at the whole curve per `over` ms. A fade waiting for rest is taken back at once. A voice
22
+ * not fading is left as it is.
23
+ */
24
+ export declare function beginRise<I, O>(mix: Mixer<I, O>, voice: Voice<I, O>, opts: {
25
+ over?: number;
26
+ }): void;
27
+ /**
28
+ * Fades a voice out from mix time `at`: ahead, it plays untouched until then and begins its fade
29
+ * exactly there; at or behind now, the fade began then and is partway.
30
+ */
31
+ export declare function fadeAt<I, O>(mix: Mixer<I, O>, voice: Voice<I, O>, at: number, over: number | undefined): void;
19
32
  export declare function retire<I, O>(mix: Mixer<I, O>, voice: Voice<I, O>, at?: number): void;
package/dist/fade.js CHANGED
@@ -1,9 +1,10 @@
1
1
  import { changed } from './chain.js';
2
2
  import { envelope } from './clock.js';
3
3
  import { schedule } from './due.js';
4
- import { noted } from './history.js';
4
+ import { leave, noted, releasing } from './history.js';
5
5
  import { orphan } from './owner.js';
6
6
  import { startOf } from './place.js';
7
+ import { lingers, refit } from './spans.js';
7
8
  import { unreach } from './unreached.js';
8
9
  /** Starts one subject's ramp out of a voice, or takes it out at once for a ramp of 0. */
9
10
  export function fadeSubject(mix, voice, subject, over) {
@@ -43,7 +44,7 @@ export function part(mix, voice, subject, at) {
43
44
  forgetIn(mix, voice, subject);
44
45
  const motion = voice.motion;
45
46
  if (motion !== undefined && asks(mix, motion, voice))
46
- motion.release(subject);
47
+ motion.release(subject, ...releasing(mix));
47
48
  }
48
49
  /** Brings a subject faded out of a voice back, to be met afresh on its next probe. */
49
50
  export function unpart(mix, voice, subject) {
@@ -63,6 +64,7 @@ function forgetIn(mix, voice, subject) {
63
64
  unreach(voice, head.slot, false);
64
65
  const held = voice.subjects.get(subject);
65
66
  if (held !== undefined) {
67
+ leave(mix, voice, subject, held);
66
68
  voice.subjects.delete(subject);
67
69
  if (held.reaches)
68
70
  voice.seen--;
@@ -102,6 +104,52 @@ export function beginFade(mix, voice, opts, at) {
102
104
  if (over === 0 && opts.at !== 'rest')
103
105
  retire(mix, voice, voice.out.at);
104
106
  }
107
+ /**
108
+ * Turns a fading voice around: it climbs back from where its fade out had got to, up the same
109
+ * curve, at the whole curve per `over` ms. A fade waiting for rest is taken back at once. A voice
110
+ * not fading is left as it is.
111
+ */
112
+ export function beginRise(mix, voice, opts) {
113
+ const out = voice.out;
114
+ if (voice.state !== 'fading' && voice.outSet) {
115
+ // A fade set ahead and not begun is taken back, and the weight never moved.
116
+ voice.outAt = Number.POSITIVE_INFINITY;
117
+ voice.outOver = Number.NaN;
118
+ voice.outSet = false;
119
+ noted(mix, voice);
120
+ return;
121
+ }
122
+ if (voice.state !== 'fading' || out === null)
123
+ return;
124
+ const at = Number.isNaN(mix.now) ? startOf(voice) : mix.now;
125
+ const over = mix.reduced ? 0 : (opts.over ?? voice.fade.in ?? voice.fade.out ?? 0);
126
+ const left = out.rest || out.over === 0 ? 1 : 1 - (at - out.at) / out.over;
127
+ const from = left < 0 ? 0 : left > 1 ? 1 : left;
128
+ voice.out = null;
129
+ voice.outAt = Number.POSITIVE_INFINITY;
130
+ voice.state = 'live';
131
+ voice.back = over > 0 && from < 1 ? { from, at, over } : null;
132
+ noted(mix, voice);
133
+ mix.lanes?.refill();
134
+ }
135
+ /**
136
+ * Fades a voice out from mix time `at`: ahead, it plays untouched until then and begins its fade
137
+ * exactly there; at or behind now, the fade began then and is partway.
138
+ */
139
+ export function fadeAt(mix, voice, at, over) {
140
+ if (voice.state === 'done' || voice.state === 'fading')
141
+ return;
142
+ const now = Number.isNaN(mix.now) ? startOf(voice) : mix.now;
143
+ if (at <= now) {
144
+ beginFade(mix, voice, { over }, Math.max(startOf(voice), at));
145
+ mix.stir();
146
+ return;
147
+ }
148
+ voice.outAt = at;
149
+ voice.outOver = over ?? Number.NaN;
150
+ voice.outSet = true;
151
+ noted(mix, voice);
152
+ }
105
153
  /** Removes a voice, recording that it left at `at`, default now. */
106
154
  /** Whether a motion patch still asks this voice of this mix for its time. */
107
155
  function asks(mix, motion, voice) {
@@ -118,7 +166,7 @@ export function retire(mix, voice, at) {
118
166
  voice.keepOn = null;
119
167
  mix.retired.push(voice);
120
168
  voice.doneAt = at ?? (Number.isNaN(mix.now) ? Number.NEGATIVE_INFINITY : mix.now);
121
- voice.play(false);
169
+ voice.play(false, voice.doneAt);
122
170
  voice.resolve();
123
171
  // An owner takes what it holds with it, and leaves with its last child.
124
172
  if (voice.holding !== null)
@@ -126,8 +174,11 @@ export function retire(mix, voice, at) {
126
174
  if (child.state !== 'done')
127
175
  retire(mix, child, voice.doneAt);
128
176
  if (voice.owner !== null) {
177
+ const span = voice.owner;
129
178
  const owner = orphan(voice);
130
- if (owner !== null)
179
+ if (owner !== null && !lingers(owner, voice.doneAt))
131
180
  retire(mix, owner, voice.doneAt);
181
+ else if (span.fitting !== null && span.state !== 'done')
182
+ refit(mix, span);
132
183
  }
133
184
  }
package/dist/fill.d.ts CHANGED
@@ -23,7 +23,7 @@ export declare function reset<I, O>(this: Lanes<I, O>, lane: Lane<I, O>): void;
23
23
  export declare function run<I, O>(this: Lanes<I, O>, lane: Lane<I, O>): void;
24
24
  /**
25
25
  * One subject's contribution from a lane's voice. True where the voice gave the subject a delta,
26
- * as the general path's `influence` does where it returns one, which is what makes a voice in a
26
+ * as the general path's `contribution` does where it returns one, which is what makes a voice in a
27
27
  * locus one of its members for the subject.
28
28
  */
29
29
  export declare function one<I, O>(this: Lanes<I, O>, lane: Lane<I, O>, p: number, slot: number, elapsedNow: number, duration: number, passes: number): boolean;
package/dist/fill.js CHANGED
@@ -3,7 +3,7 @@ import { passAt, phaseAt, silent, weighed } from './clock.js';
3
3
  import { flush } from './columns.js';
4
4
  import { crowdsUpTo, freshen } from './crowd.js';
5
5
  import { foldLocus, gatherLocus } from './gather.js';
6
- import { held } from './lane.js';
6
+ import { frozenAt } from './lane.js';
7
7
  import { reach } from './meet.js';
8
8
  import { absent } from './numbers.js';
9
9
  import { AT, NOTHING, readKeys, seg, segment, shifted } from './patch.js';
@@ -39,6 +39,7 @@ export function fillAll(lanes, now, version) {
39
39
  const decide = lanes.fresh;
40
40
  lanes.fresh = false;
41
41
  const share = lanes.live > 0 ? lanes.lastDistinct / lanes.live : 1;
42
+ lanes.lately = share < 1 ? lanes.lastFrom : -1;
42
43
  // Deciding which lanes and crowds go idle first, then running them, in voice order: a crowd's
43
44
  // rows are folded between the lanes on either side of their voices, as the general path
44
45
  // folds every voice in order.
@@ -95,11 +96,9 @@ export function fillAll(lanes, now, version) {
95
96
  }
96
97
  }
97
98
  // With every lane idle there was nothing to fill, and no subject reads a lane this frame.
98
- if (busy)
99
- for (let s = 0; s < size; s++)
100
- lanes.per[s * 6 /* Per.SLOT */ + 0 /* Per.FILLED */] = lanes.fills;
99
+ lanes.busyFill = busy ? lanes.fills : 0;
101
100
  for (const slot of lanes.late)
102
- lanes.per[slot * 6 /* Per.SLOT */ + 0 /* Per.FILLED */] = 0;
101
+ lanes.per[slot * 6 /* Per.SLOT */ + 0 /* Per.FILLED */] = lanes.fills;
103
102
  if (lanes.holding) {
104
103
  lanes.subjects.fill(undefined, 0, size);
105
104
  lanes.holding = false;
@@ -208,7 +207,11 @@ export function reset(lane) {
208
207
  lane.read = false;
209
208
  lane.weighed = false;
210
209
  // With no fade in or out the envelope is 1 for every subject, which is what it would return.
211
- lane.flat = !((voice.fade.in ?? 0) > 0) && voice.out === null && voice.owner === null;
210
+ lane.flat =
211
+ !((voice.fade.in ?? 0) > 0) &&
212
+ voice.out === null &&
213
+ voice.back === null &&
214
+ voice.owner === null;
212
215
  lane.fade = 1;
213
216
  }
214
217
  /** One voice's contribution to every subject it plays on. */
@@ -238,7 +241,7 @@ export function run(lane) {
238
241
  }
239
242
  /**
240
243
  * One subject's contribution from a lane's voice. True where the voice gave the subject a delta,
241
- * as the general path's `influence` does where it returns one, which is what makes a voice in a
244
+ * as the general path's `contribution` does where it returns one, which is what makes a voice in a
242
245
  * locus one of its members for the subject.
243
246
  */
244
247
  export function one(lane, p, slot, elapsedNow, duration, passes) {
@@ -249,8 +252,10 @@ export function one(lane, p, slot, elapsedNow, duration, passes) {
249
252
  // A probe read this subject from the last fill: keep the weight it read for `weightOf`.
250
253
  if (this.per[slot * 6 /* Per.SLOT */ + 5 /* Per.LANE_FILL */] === this.fills - 1)
251
254
  data[o + 3 /* Row.PROBED */] = data[o + 2 /* Row.WEIGHT */];
255
+ if (this.lately >= 0 && this.unread(slot))
256
+ return false;
252
257
  const delay = data[o + 0 /* Row.DELAY */];
253
- const elapsed = held(voice, elapsedNow - delay);
258
+ const elapsed = frozenAt(voice, elapsedNow - delay);
254
259
  if (!(elapsed >= 0)) {
255
260
  data[o + 2 /* Row.WEIGHT */] = 0;
256
261
  return false;
@@ -278,6 +283,8 @@ export function one(lane, p, slot, elapsedNow, duration, passes) {
278
283
  else
279
284
  w = weighed(voice.weight, lane.fade, this.parting(voice, slot));
280
285
  data[o + 2 /* Row.WEIGHT */] = w;
286
+ this.folding = lane;
287
+ this.rec = lane.records[p] ?? null;
281
288
  if (voice.built !== null) {
282
289
  if (!lane.read || !Object.is(elapsed, lane.readAt)) {
283
290
  this.readKeyed(voice, lane.phase, lane.delta, lane.scratch);
@@ -367,12 +374,12 @@ export function call(voice, chans, rec, slot, elapsed, phase, pass, delay, w) {
367
374
  else
368
375
  reading.horizon = Number.POSITIVE_INFINITY;
369
376
  const delta = voice.patch.at(phase, subject, voice.setting);
370
- // What `influence` leaves on the record, so a probe on the general path this frame reuses it.
377
+ // What `contribution` leaves on the record, so a probe on the general path this frame reuses it.
371
378
  rec.delta = delta;
372
379
  rec.probed = this.now;
373
380
  rec.seeks = voice.seeks;
374
381
  if (this.keeps)
375
- host.after(voice, rec);
382
+ host.after(voice, subject, rec);
376
383
  if (reading.kept !== kept && !voice.keeping)
377
384
  host.kept(voice);
378
385
  if (w > 0)
package/dist/fit.d.ts ADDED
@@ -0,0 +1,144 @@
1
+ /**
2
+ * How hard a claim on time holds when it meets another: the weaker gives way.
3
+ *
4
+ * @category score
5
+ */
6
+ export type Strength = 'weak' | 'strong' | 'required';
7
+ /**
8
+ * How a span lays its children out: each after the one before has ended, partway through it, or
9
+ * all at once.
10
+ *
11
+ * @category score
12
+ */
13
+ export type Order = 'queue' | 'stagger' | 'together';
14
+ /**
15
+ * One child of a span as a fit sees it: what is left of it, and the ways it may give way.
16
+ *
17
+ * @category score
18
+ */
19
+ export interface Claim {
20
+ /** Span ms left of it at its own rate. */
21
+ natural: number;
22
+ /** A playing child has started, and stays at the front. */
23
+ state: 'pending' | 'playing';
24
+ /** How many times faster it may play; 1 for no faster. */
25
+ faster: number;
26
+ /** How many times slower it may play; 1 for no slower. */
27
+ slower: number;
28
+ /** Whether it may run alongside the one before it where the span's order would not have it. */
29
+ overlap: boolean;
30
+ /** Whether it may jump to its end instead of playing. */
31
+ ballast: boolean;
32
+ priority: Strength;
33
+ }
34
+ /**
35
+ * The span as a fit sees it.
36
+ *
37
+ * @category score
38
+ */
39
+ export interface SpanClaim {
40
+ /** Span ms left of its budget; Infinity for a span with none. */
41
+ left: number;
42
+ /** Its whole budget. */
43
+ budget: number;
44
+ priority: Strength;
45
+ order: Order;
46
+ /** How far through the one before a staggered child starts, 0..1. */
47
+ share: number;
48
+ }
49
+ /**
50
+ * What a fit decides, per child in the order given: a rate over its own, and whether it is
51
+ * skipped. `share` is how far through the one before a child that may overlap starts, and `allow`
52
+ * the span ms the span may run past its budget.
53
+ *
54
+ * @category score
55
+ */
56
+ export interface FitPlan {
57
+ rate: number[];
58
+ skip: boolean[];
59
+ share: number;
60
+ allow: number;
61
+ }
62
+ /**
63
+ * A strategy for fitting a span's children into its budget: from the plan so far to a new one.
64
+ *
65
+ * @category score
66
+ */
67
+ export type Fit = (span: SpanClaim, kids: readonly Claim[], plan: FitPlan) => FitPlan;
68
+ /** How far through the one before a child starts under each order, before any fit. */
69
+ export declare function shareOf(order: Order, stagger: number): number;
70
+ /**
71
+ * The plan before any fit: every child at its own rate, played, in the span's order.
72
+ *
73
+ * @category score
74
+ */
75
+ export declare function plain(span: SpanClaim, kids: readonly Claim[]): FitPlan;
76
+ /**
77
+ * Where a plan puts each child, span ms from now, and how long they take together. A playing child
78
+ * is where it is, at 0; each pending one starts its share through the one before it.
79
+ *
80
+ * @category score
81
+ */
82
+ export declare function layout(span: SpanClaim, kids: readonly Claim[], plan: FitPlan): {
83
+ at: number[];
84
+ length: number;
85
+ };
86
+ /**
87
+ * Moves every child's rate by one factor toward the budget, before the span's fit: faster where
88
+ * they run past it, each no faster than its `faster` allows, and slower where they leave room in
89
+ * it, each no slower than its `slower` allows, so they fill it.
90
+ */
91
+ export declare function rescale(span: SpanClaim, kids: readonly Claim[], plan: FitPlan): FitPlan;
92
+ /**
93
+ * Starts children that may overlap sooner after the one before, toward all at once, until they
94
+ * fit.
95
+ *
96
+ * @category score
97
+ */
98
+ export declare function condense(): Fit;
99
+ /**
100
+ * Jumps children that may be skipped to their end, latest first, until they fit.
101
+ *
102
+ * @category score
103
+ */
104
+ export declare function shed(): Fit;
105
+ /**
106
+ * Jumps every child left to its end at once, whether or not it fits otherwise.
107
+ *
108
+ * @category score
109
+ */
110
+ export declare function conclude(): Fit;
111
+ /**
112
+ * Lets the span run past its budget: up to `cap` times it, or without limit.
113
+ *
114
+ * @category score
115
+ */
116
+ export declare function overrun(opts?: {
117
+ cap?: number;
118
+ }): Fit;
119
+ /**
120
+ * Tries each fit in turn, handing the plan on, and stops once the children fit.
121
+ *
122
+ * @category score
123
+ */
124
+ export declare function pipe(...fits_: readonly Fit[]): Fit;
125
+ /**
126
+ * Every way of giving way after the span's retiming, overrunning only once the rest are spent.
127
+ *
128
+ * @category score
129
+ */
130
+ export declare function lax(opts?: {
131
+ cap?: number;
132
+ }): Fit;
133
+ /** The fit a span takes without one of its own. */
134
+ export declare const defaultFit: Fit;
135
+ /**
136
+ * A span's fit settled: the plan, and what it had to do past the strategy, which starts from the
137
+ * children retimed toward the budget. Where the children still
138
+ * do not fit, a span weaker than `strong` lets them run long; otherwise `spill` either skips
139
+ * every child weaker than the span (`instant`), or lets them run long (`overrun`).
140
+ */
141
+ export declare function settle(span: SpanClaim, kids: readonly Claim[], fit: Fit, spill: 'instant' | 'overrun'): {
142
+ plan: FitPlan;
143
+ fell: boolean;
144
+ };