@msb235/blits 0.13.0 → 0.15.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 (178) hide show
  1. package/CHANGELOG.md +183 -0
  2. package/README.md +20 -3
  3. package/dist/ahead.d.ts +26 -0
  4. package/dist/ahead.js +264 -0
  5. package/dist/around.d.ts +11 -0
  6. package/dist/around.js +32 -0
  7. package/dist/bare.js +15 -7
  8. package/dist/blank.d.ts +10 -0
  9. package/dist/blank.js +26 -0
  10. package/dist/blend.d.ts +2 -1
  11. package/dist/blend.js +1 -6
  12. package/dist/bound.d.ts +70 -0
  13. package/dist/bound.js +351 -0
  14. package/dist/chain.d.ts +2 -1
  15. package/dist/chain.js +7 -5
  16. package/dist/changes.d.ts +24 -0
  17. package/dist/changes.js +189 -0
  18. package/dist/channels.d.ts +39 -13
  19. package/dist/channels.js +14 -5
  20. package/dist/closed.d.ts +1 -0
  21. package/dist/closed.js +45 -1
  22. package/dist/color.d.ts +12 -10
  23. package/dist/color.js +25 -25
  24. package/dist/columns.js +7 -3
  25. package/dist/contribution.d.ts +30 -0
  26. package/dist/contribution.js +374 -0
  27. package/dist/copyout.d.ts +10 -0
  28. package/dist/copyout.js +69 -24
  29. package/dist/crowd.d.ts +8 -2
  30. package/dist/crowd.js +4 -5
  31. package/dist/cue.d.ts +8 -1
  32. package/dist/cue.js +47 -14
  33. package/dist/cut.d.ts +8 -0
  34. package/dist/cut.js +4 -0
  35. package/dist/drop.d.ts +9 -0
  36. package/dist/drop.js +84 -0
  37. package/dist/due.d.ts +24 -5
  38. package/dist/due.js +113 -33
  39. package/dist/easing.d.ts +4 -4
  40. package/dist/easing.js +3 -3
  41. package/dist/everyone.d.ts +3 -2
  42. package/dist/everyone.js +3 -3
  43. package/dist/fade.d.ts +2 -1
  44. package/dist/fade.js +2 -1
  45. package/dist/fill.d.ts +3 -2
  46. package/dist/fill.js +51 -19
  47. package/dist/fit.d.ts +44 -26
  48. package/dist/fit.js +10 -9
  49. package/dist/fling.d.ts +66 -0
  50. package/dist/fling.js +162 -0
  51. package/dist/fold.d.ts +4 -37
  52. package/dist/fold.js +5 -512
  53. package/dist/gather.d.ts +12 -7
  54. package/dist/gather.js +179 -44
  55. package/dist/gone.js +2 -2
  56. package/dist/grid.d.ts +47 -0
  57. package/dist/grid.js +247 -0
  58. package/dist/handle.d.ts +3 -0
  59. package/dist/handle.js +19 -8
  60. package/dist/held.d.ts +2 -1
  61. package/dist/history.d.ts +6 -5
  62. package/dist/history.js +9 -9
  63. package/dist/hosts.js +9 -15
  64. package/dist/index.d.ts +5 -4
  65. package/dist/keyfill.d.ts +1 -1
  66. package/dist/keyfill.js +5 -3
  67. package/dist/keyrows.d.ts +12 -0
  68. package/dist/keyrows.js +99 -12
  69. package/dist/lane.d.ts +36 -6
  70. package/dist/lane.js +32 -1
  71. package/dist/lanes.d.ts +23 -8
  72. package/dist/lanes.js +29 -8
  73. package/dist/leavings.d.ts +2 -2
  74. package/dist/leavings.js +1 -1
  75. package/dist/lives.d.ts +47 -0
  76. package/dist/lives.js +180 -0
  77. package/dist/meet.d.ts +2 -2
  78. package/dist/meet.js +90 -4
  79. package/dist/mixer.d.ts +61 -35
  80. package/dist/mixer.js +64 -169
  81. package/dist/motion.d.ts +19 -106
  82. package/dist/motion.js +60 -13
  83. package/dist/motions.d.ts +62 -150
  84. package/dist/motions.js +57 -369
  85. package/dist/move.js +24 -1
  86. package/dist/moved.d.ts +1 -1
  87. package/dist/moved.js +16 -7
  88. package/dist/named.d.ts +0 -1
  89. package/dist/named.js +20 -2
  90. package/dist/numbers.d.ts +2 -0
  91. package/dist/numbers.js +4 -0
  92. package/dist/owed.d.ts +2 -0
  93. package/dist/owed.js +4 -0
  94. package/dist/pack.d.ts +3 -1
  95. package/dist/pack.js +4 -3
  96. package/dist/patch.d.ts +7 -3
  97. package/dist/patch.js +6 -2
  98. package/dist/places.d.ts +53 -0
  99. package/dist/places.js +171 -0
  100. package/dist/plot.d.ts +12 -0
  101. package/dist/plot.js +52 -0
  102. package/dist/project.d.ts +4 -4
  103. package/dist/project.js +9 -211
  104. package/dist/pull.js +11 -8
  105. package/dist/qualify.js +84 -21
  106. package/dist/reading.d.ts +6 -0
  107. package/dist/reading.js +5 -0
  108. package/dist/record.d.ts +5 -1
  109. package/dist/record.js +4 -2
  110. package/dist/relink.d.ts +1 -1
  111. package/dist/restored.d.ts +2 -2
  112. package/dist/restored.js +1 -1
  113. package/dist/rests.d.ts +9 -0
  114. package/dist/rests.js +76 -0
  115. package/dist/revive.d.ts +3 -2
  116. package/dist/revive.js +2 -2
  117. package/dist/{locus.d.ts → role.d.ts} +10 -9
  118. package/dist/{locus.js → role.js} +3 -3
  119. package/dist/roles.d.ts +10 -0
  120. package/dist/roles.js +158 -0
  121. package/dist/roster.js +3 -3
  122. package/dist/rotation.d.ts +1 -1
  123. package/dist/rotation.js +1 -1
  124. package/dist/rows.js +1 -1
  125. package/dist/sample.d.ts +2 -2
  126. package/dist/sample.js +33 -7
  127. package/dist/seek.js +13 -5
  128. package/dist/setting.d.ts +27 -0
  129. package/dist/setting.js +55 -0
  130. package/dist/shared.d.ts +1 -1
  131. package/dist/shared.js +1 -1
  132. package/dist/signals.d.ts +64 -32
  133. package/dist/signals.js +20 -18
  134. package/dist/stack.d.ts +5 -5
  135. package/dist/stack.js +5 -5
  136. package/dist/standing.d.ts +2 -1
  137. package/dist/standing.js +3 -2
  138. package/dist/store.d.ts +9 -0
  139. package/dist/store.js +24 -0
  140. package/dist/strays.d.ts +2 -1
  141. package/dist/strays.js +21 -14
  142. package/dist/stretch.d.ts +112 -0
  143. package/dist/stretch.js +9 -0
  144. package/dist/subject.d.ts +142 -0
  145. package/dist/subject.js +1 -0
  146. package/dist/tape.d.ts +9 -5
  147. package/dist/tape.js +5 -1
  148. package/dist/then.d.ts +9 -0
  149. package/dist/then.js +57 -0
  150. package/dist/ticker.d.ts +4 -4
  151. package/dist/transport.d.ts +0 -8
  152. package/dist/transport.js +0 -4
  153. package/dist/turns.d.ts +4 -3
  154. package/dist/turns.js +4 -3
  155. package/dist/types/arc.d.ts +12 -11
  156. package/dist/types/channel.d.ts +12 -8
  157. package/dist/types/history.d.ts +35 -19
  158. package/dist/types/mix.d.ts +222 -161
  159. package/dist/types/motion.d.ts +151 -0
  160. package/dist/types/motion.js +1 -0
  161. package/dist/types/patch.d.ts +69 -34
  162. package/dist/types/place.d.ts +51 -36
  163. package/dist/types/transport.d.ts +20 -18
  164. package/dist/types/voice.d.ts +145 -72
  165. package/dist/types/watch.d.ts +88 -40
  166. package/dist/types.d.ts +1 -0
  167. package/dist/voice.d.ts +9 -141
  168. package/dist/voice.js +30 -79
  169. package/dist/warn.d.ts +2 -0
  170. package/dist/warn.js +1 -1
  171. package/dist/watch.d.ts +2 -2
  172. package/dist/watch.js +5 -3
  173. package/dist/wave.d.ts +4 -3
  174. package/dist/wave.js +3 -2
  175. package/dist/weigh.d.ts +7 -1
  176. package/dist/weigh.js +27 -0
  177. package/docs/schema.html +223 -37
  178. package/package.json +6 -2
package/dist/pull.js CHANGED
@@ -18,12 +18,10 @@ function writeValue(c, v, n) {
18
18
  }
19
19
  /** After a probe, keeps the pose a retarget from `'current'` reads as the subject's last. */
20
20
  export function keep(subject, pose, out) {
21
- // Keeping last frame's pose is free when the mix allocated it; a host sampling into its own
22
- // object only pays for the copy once something in the mix has asked to retarget from it.
21
+ // A pose handed back without `out` is the host's to edit, so it is copied too. A host sampling
22
+ // into its own object only pays for the copy once something has asked to retarget from it.
23
23
  let kept;
24
- if (out === undefined)
25
- kept = pose;
26
- else if (this.wantsPose) {
24
+ if (out === undefined || this.wantsPose) {
27
25
  kept = {};
28
26
  for (const key of this.names) {
29
27
  const v = pose[key];
@@ -32,9 +30,14 @@ export function keep(subject, pose, out) {
32
30
  }
33
31
  }
34
32
  if (kept !== undefined) {
35
- const rec = this.pose.get(subject);
33
+ const rec = this.lastPoses.get(subject);
36
34
  if (rec === undefined)
37
- this.pose.set(subject, { pose: kept, at: this.now, prev: undefined, prevAt: Number.NaN });
35
+ this.lastPoses.set(subject, {
36
+ pose: kept,
37
+ at: this.now,
38
+ prev: undefined,
39
+ prevAt: Number.NaN,
40
+ });
38
41
  else if (rec.at === this.now)
39
42
  rec.pose = kept;
40
43
  else {
@@ -50,7 +53,7 @@ export function pull(mix, subjects, into) {
50
53
  const lanes = mix.lanes;
51
54
  const now = mix.now;
52
55
  if (mix.scratch === undefined)
53
- mix.scratch = {};
56
+ mix.scratch = mix.pose();
54
57
  let room = Number.POSITIVE_INFINITY;
55
58
  let tightest = '';
56
59
  for (const c of columns) {
package/dist/qualify.js CHANGED
@@ -5,6 +5,7 @@ import { byId } from './due.js';
5
5
  import { weighAt } from './everyone.js';
6
6
  import { wake } from './fill.js';
7
7
  import { Lane } from './lane.js';
8
+ import { reach } from './meet.js';
8
9
  import { bury, compactSparse, join, regroup, retouchRow, sameCrowds } from './rows.js';
9
10
  import { settle } from './sample.js';
10
11
  /**
@@ -57,21 +58,21 @@ export function requalify(lanes, version) {
57
58
  keepCallsOff(present, fits, host.channels);
58
59
  const candidates = () => present.map((v, i) => ({ id: v.id, fits: fits[i], slots: v.slots }));
59
60
  let { channels, voices } = qualify(numeric, candidates());
60
- // A locus folds its members together, so they run on lanes all together or not at all.
61
+ // A role folds its members together, so they run on lanes all together or not at all.
61
62
  for (let split = true; split;) {
62
63
  split = false;
63
- const loci = new Map();
64
+ const roles = new Map();
64
65
  present.forEach((v, i) => {
65
- const name = v.spec.locus;
66
+ const name = v.spec.role;
66
67
  if (name === undefined)
67
68
  return;
68
- const at = loci.get(name);
69
+ const at = roles.get(name);
69
70
  if (at === undefined)
70
- loci.set(name, [i]);
71
+ roles.set(name, [i]);
71
72
  else
72
73
  at.push(i);
73
74
  });
74
- for (const at of loci.values()) {
75
+ for (const at of roles.values()) {
75
76
  const on = at.filter((i) => voices.has(present[i].id)).length;
76
77
  if (on === 0 || on === at.length)
77
78
  continue;
@@ -97,18 +98,18 @@ export function requalify(lanes, version) {
97
98
  const lane = lanes.byId.get(v.id) ?? new Lane(v);
98
99
  v.laned = true;
99
100
  if (lane.epoch === 0 && v.state !== 'pending')
100
- lane.epoch = open(lanes, v);
101
+ opened(lanes, lane);
101
102
  kept.push(lane);
102
103
  }
103
104
  lanes.lanes = kept;
104
- lanes.loci = [];
105
- const loci = new Map();
105
+ lanes.roles = [];
106
+ const roles = new Map();
106
107
  for (const lane of kept) {
107
108
  lane.group = null;
108
- const name = lane.voice.spec.locus;
109
+ const name = lane.voice.spec.role;
109
110
  if (name === undefined)
110
111
  continue;
111
- let g = loci.get(name);
112
+ let g = roles.get(name);
112
113
  if (g === undefined) {
113
114
  g = {
114
115
  members: [],
@@ -117,13 +118,17 @@ export function requalify(lanes, version) {
117
118
  sum: new Float64Array(lanes.cap),
118
119
  values: [],
119
120
  taken: [],
121
+ contiguous: false,
122
+ inline: false,
120
123
  };
121
- loci.set(name, g);
122
- lanes.loci.push(g);
124
+ roles.set(name, g);
125
+ lanes.roles.push(g);
123
126
  }
124
127
  g.members.push(lane);
125
128
  lane.group = g;
126
129
  }
130
+ for (const [name, g] of roles)
131
+ g.contiguous = contiguous(present, voices, name, g.members);
127
132
  lanes.dense = kept
128
133
  .filter((l) => l.voice.named === null && l.epoch > 0)
129
134
  .sort((a, b) => a.epoch - b.epoch);
@@ -161,6 +166,7 @@ export function requalify(lanes, version) {
161
166
  return;
162
167
  }
163
168
  lanes.laned = [];
169
+ lanes.copier = null;
164
170
  lanes.bySlot = host.channels.map(() => undefined);
165
171
  channels.forEach((on, slot) => {
166
172
  if (!on)
@@ -170,10 +176,12 @@ export function requalify(lanes, version) {
170
176
  // A rest-less channel's values are NaN until some voice passes the band: absent from the pose.
171
177
  const rest = (channel.rest ?? Number.NaN);
172
178
  const scalar = typeof rest === 'number' && n.axes === 1;
179
+ const op = n.folds ?? n.op;
173
180
  const ch = {
174
181
  name: host.names[slot],
175
- op: n.op,
176
- plain: n.op !== 'last' && n.op !== 'own' && n.turn === undefined,
182
+ op,
183
+ plain: op !== 'last' && op !== 'own' && n.turn === undefined,
184
+ straight: n.folds === undefined,
177
185
  turn: n.turn ?? 0,
178
186
  channel,
179
187
  scalar,
@@ -263,7 +271,7 @@ export function open(lanes, v) {
263
271
  lanes.wide = epoch;
264
272
  return epoch;
265
273
  }
266
- for (const subject of v.named) {
274
+ for (const subject of v.named.list) {
267
275
  const slot = lanes.host.slotOf(subject);
268
276
  if (slot < 0 || slot >= lanes.cap)
269
277
  continue;
@@ -318,8 +326,8 @@ export function retouch(lanes) {
318
326
  }
319
327
  if (v.state === 'done')
320
328
  return qualify();
321
- // A locus's members are on lanes together or not at all, so one joining may move the others.
322
- if (v.spec.locus !== undefined)
329
+ // A role's members are on lanes together or not at all, so one joining may move the others.
330
+ if (v.spec.role !== undefined)
323
331
  return qualify();
324
332
  const laned = v.slots.some((slot) => lanes.bySlot[slot] !== undefined);
325
333
  const fits = v.slots.length > 0 && lanes.host.fits(v);
@@ -341,6 +349,46 @@ export function retouch(lanes) {
341
349
  lanes.subjects = [];
342
350
  return true;
343
351
  }
352
+ /** A lane's voice started playing: its epoch, and room up front where it reaches every subject. */
353
+ function opened(lanes, lane) {
354
+ const v = lane.voice;
355
+ lane.epoch = open(lanes, v);
356
+ if (v.named === null && v.spec.target === undefined) {
357
+ lane.reserve(lanes.live, lanes.cap);
358
+ if (placesAll(lanes, lane))
359
+ placeAll(lanes, lane);
360
+ }
361
+ }
362
+ /**
363
+ * Whether a lane just opened can take every numbered subject now, its first call or read for each
364
+ * made by the fill: a stateless fn or keys voice sharing one record, which every subject would meet
365
+ * at its next probe, in a host that probed every subject last frame. One by one, each probe would
366
+ * meet it and make its first call or read there (`foldNow` in `meet.ts`).
367
+ */
368
+ function placesAll(lanes, lane) {
369
+ const v = lane.voice;
370
+ return (v.sharing &&
371
+ v.everyone !== null &&
372
+ lane.motion === undefined &&
373
+ !v.keeping &&
374
+ typeof v.spec.weight !== 'function' &&
375
+ lane.list.length === 0 &&
376
+ lanes.lastDistinct >= lanes.live);
377
+ }
378
+ /** Gives every numbered subject a position on a lane `placesAll` passed. */
379
+ function placeAll(lanes, lane) {
380
+ const rec = lane.voice.everyone;
381
+ const numbers = lanes.numbers;
382
+ const size = Math.min(numbers.size, lanes.cap);
383
+ for (let slot = 0; slot < size; slot++) {
384
+ if (!numbers.has(slot))
385
+ continue;
386
+ lane.add(slot, rec);
387
+ if (lane.idle)
388
+ reach(lanes, slot, 1);
389
+ }
390
+ lane.every = true;
391
+ }
344
392
  /**
345
393
  * A new voice that fits and writes only laned channels, and that no crowd takes, gets a lane at
346
394
  * the end of the order, its id being past every other: what a qualify would give it, since such a
@@ -350,7 +398,7 @@ function enlane(lanes, v) {
350
398
  const lane = new Lane(v);
351
399
  v.laned = true;
352
400
  if (v.state !== 'pending')
353
- lane.epoch = open(lanes, v);
401
+ opened(lanes, lane);
354
402
  lane.chans = v.slots.map((s) => lanes.bySlot[s]);
355
403
  lane.values = lane.chans.map(() => undefined);
356
404
  lanes.lanes.push(lane);
@@ -360,13 +408,13 @@ function enlane(lanes, v) {
360
408
  }
361
409
  /**
362
410
  * A laned voice that started playing or finished, as a qualify would take it: a lane leaving
363
- * cannot lane or unlane another channel. False for a locus member, which a qualify must take.
411
+ * cannot lane or unlane another channel. False for a role member, which a qualify must take.
364
412
  */
365
413
  function relane(lanes, lane) {
366
414
  const v = lane.voice;
367
415
  if (v.state !== 'done') {
368
416
  if (lane.epoch === 0 && v.state !== 'pending') {
369
- lane.epoch = open(lanes, v);
417
+ opened(lanes, lane);
370
418
  if (v.named === null)
371
419
  lanes.dense.push(lane);
372
420
  }
@@ -410,3 +458,18 @@ function leave(lanes, lane) {
410
458
  }
411
459
  lane.voice.laned = false;
412
460
  }
461
+ /**
462
+ * Whether a role's members, in the order lanes run, have no laned or crowded voice of another
463
+ * role or none between them, so its every subject folds at its first member's turn.
464
+ */
465
+ function contiguous(present, voices, name, members) {
466
+ for (let i = 1; i < members.length; i++)
467
+ if (members[i].voice.id <= members[i - 1].voice.id)
468
+ return false;
469
+ const lo = members[0].voice.id;
470
+ const hi = members[members.length - 1].voice.id;
471
+ for (const v of present)
472
+ if (v.id > lo && v.id < hi && voices.has(v.id) && v.spec.role !== name)
473
+ return false;
474
+ return true;
475
+ }
package/dist/reading.d.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import type { Cut } from './cut.js';
1
2
  /**
2
3
  * Internal: what kind of read the mix is making right now, for patches that keep state of their own
3
4
  * outside the mix. Not exported from the package.
@@ -32,4 +33,9 @@ export declare const reading: {
32
33
  * commits none. Null for every other read.
33
34
  */
34
35
  ahead: Map<object, Map<unknown, unknown[]>> | null;
36
+ /**
37
+ * While a read back under history reads: the frame it reads, so a motion patch reads the run a
38
+ * subject had then and only the changes made by then. Null for every other read.
39
+ */
40
+ back: Cut | null;
35
41
  };
package/dist/reading.js CHANGED
@@ -32,4 +32,9 @@ export const reading = {
32
32
  * commits none. Null for every other read.
33
33
  */
34
34
  ahead: null,
35
+ /**
36
+ * While a read back under history reads: the frame it reads, so a motion patch reads the run a
37
+ * subject had then and only the changes made by then. Null for every other read.
38
+ */
39
+ back: null,
35
40
  };
package/dist/record.d.ts CHANGED
@@ -1,4 +1,8 @@
1
- import type { Subject } from './voice.js';
1
+ import type { Subject } from './subject.js';
2
+ /** Whether a record was left at rest during a handover. */
3
+ export declare const isRested: (h: {
4
+ restedAt: number;
5
+ }) => boolean;
2
6
  /** A record with every field written out, optional ones too, as every record in a mix is built. */
3
7
  export type WholeRecord = {
4
8
  [K in Field]: Subject<unknown>[K];
package/dist/record.js CHANGED
@@ -1,3 +1,5 @@
1
+ /** Whether a record was left at rest during a handover. */
2
+ export const isRested = (h) => !Number.isNaN(h.restedAt);
1
3
  /**
2
4
  * A subject's record for `voice` holding every field one can come to hold, in one order, so every
3
5
  * record in a mix shares one hidden class: a record built elsewhere, or given `base`, `snaps` or
@@ -12,7 +14,7 @@ export function record(voice, reaches, state) {
12
14
  since: 0,
13
15
  shown: 0,
14
16
  weight: 0,
15
- rested: false,
17
+ restedAt: Number.NaN,
16
18
  bands: null,
17
19
  state,
18
20
  // -0, not 0: a field first given a small integer is boxed on every later write of a double.
@@ -36,7 +38,7 @@ export function record(voice, reaches, state) {
36
38
  voice,
37
39
  next: null,
38
40
  version: Number.NaN,
39
- loci: null,
41
+ roles: null,
40
42
  slot: -1,
41
43
  run: -1,
42
44
  freed: -1,
package/dist/relink.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { Subject } from './voice.js';
1
+ import type { Subject } from './subject.js';
2
2
  /** What `patch` needs of a voice: its place in voice order. */
3
3
  interface Ordered {
4
4
  readonly id: number;
@@ -1,7 +1,7 @@
1
+ import type { Cut } from './cut.js';
1
2
  import type { Leavings } from './leavings.js';
2
3
  import { Store } from './store.js';
3
- import type { Cut } from './transport.js';
4
- import type { Subject } from './voice.js';
4
+ import type { Subject } from './subject.js';
5
5
  /**
6
6
  * The seeks back after the one a record was last put back, written or deleted at, which every
7
7
  * record stamped then shares. The next seek fills it; a later seek to as early a moment joins it
package/dist/restored.js CHANGED
@@ -138,7 +138,7 @@ export class Restored extends Store {
138
138
  }
139
139
  /** `get`, given what `own` holds for `key`. */
140
140
  from(key, own) {
141
- const mark = own !== undefined && own.reaches;
141
+ const mark = own?.reaches;
142
142
  const at = (mark ? own.restored : this.at.get(key)) ?? this.first;
143
143
  const now = this.stamp;
144
144
  if (at === now)
@@ -0,0 +1,9 @@
1
+ import type { Mixer } from './mixer.js';
2
+ import type { Voice } from './voice.js';
3
+ export declare function atRest<I, O>(this: Mixer<I, O>, subject: I): boolean;
4
+ export declare function rests<I, O>(this: Mixer<I, O>, pose: O): boolean;
5
+ export declare function restful<I, O>(this: Mixer<I, O>): boolean;
6
+ /** Whether a voice will change no pose from `now` on, short of a change made to it. */
7
+ export declare function still<I, O>(this: Mixer<I, O>, voice: Voice<I, O>, now: number): boolean;
8
+ export declare function restsLaned<I, O>(this: Mixer<I, O>): boolean;
9
+ export declare function restsKey<I, O>(this: Mixer<I, O>): number;
package/dist/rests.js ADDED
@@ -0,0 +1,76 @@
1
+ import { near } from './fold.js';
2
+ import { Store } from './store.js';
3
+ export function atRest(subject) {
4
+ this.restStamps ??= new Store();
5
+ const head = this.linked(subject);
6
+ const laned = this.linkedLaned;
7
+ const lanes = this.lanes;
8
+ // Every voice on lanes and nothing to clamp: the pose a fold would make is the lanes' values.
9
+ if (laned && this.restsLaned()) {
10
+ const slot = head.slot;
11
+ if (!lanes.owes(slot))
12
+ return lanes.rests(slot);
13
+ }
14
+ // A probe this frame answers for the pose it gave the host.
15
+ const since = (this.restStamps.get(subject) ?? Number.NaN) - 2 * this.restsKey();
16
+ if (since >= 0)
17
+ return since === 1;
18
+ this.restScratch ??= this.pose();
19
+ const pose = this.foldWith(subject, this.restScratch, head, laned, true);
20
+ return this.rests((this.bounded.length === 0 ? pose : this.clamp(pose)));
21
+ }
22
+ export function rests(pose) {
23
+ for (let i = 0; i < this.names.length; i++) {
24
+ const channel = this.channels[i];
25
+ const value = pose[this.names[i]];
26
+ if (channel.rest === undefined) {
27
+ if (value !== undefined)
28
+ return false;
29
+ }
30
+ else if (!near(value, channel.rest))
31
+ return false;
32
+ }
33
+ return true;
34
+ }
35
+ export function restful() {
36
+ // Standing still, a voice changes no pose but by its weight signal, or by an anchor or start
37
+ // that host time may yet reach.
38
+ if (this.pace?.stopped(this.u))
39
+ return this.cued.every((v) => v.state === 'done' ||
40
+ (v.state !== 'pending' &&
41
+ typeof v.spec.weight !== 'function' &&
42
+ v.spec.anchor === undefined));
43
+ return this.cued.every((v) => this.still(v, this.now));
44
+ }
45
+ /** Whether a voice will change no pose from `now` on, short of a change made to it. */
46
+ export function still(voice, now) {
47
+ if (voice.state === 'done')
48
+ return true;
49
+ if (voice.state !== 'frozen' && voice.state !== 'live')
50
+ return false;
51
+ if (typeof voice.spec.weight === 'function' || voice.parts !== null)
52
+ return false;
53
+ const fadeIn = this.reducedNow ? 0 : (voice.fade.in ?? 0);
54
+ if (fadeIn > 0 && now - this.sinceOf(voice, voice.latest) < fadeIn)
55
+ return false;
56
+ if (voice.state === 'frozen' || voice.holding !== null)
57
+ return true;
58
+ const motion = voice.motion;
59
+ return motion !== undefined && voice.elapsedAt(now) >= voice.latest && motion.landed();
60
+ }
61
+ export function restsLaned() {
62
+ return this.lanes.whole && this.bounded.length === 0;
63
+ }
64
+ export function restsKey() {
65
+ if (this.frame !== this.restsFrame ||
66
+ this.version !== this.restsVersion ||
67
+ this.relinks !== this.restsRelinks ||
68
+ this.stirs !== this.restsStirs) {
69
+ this.restsEpoch++;
70
+ this.restsFrame = this.frame;
71
+ this.restsVersion = this.version;
72
+ this.restsRelinks = this.relinks;
73
+ this.restsStirs = this.stirs;
74
+ }
75
+ return this.restsEpoch;
76
+ }
package/dist/revive.d.ts CHANGED
@@ -1,10 +1,11 @@
1
1
  import type { Mixer } from './mixer.js';
2
- import type { Run } from './motions.js';
3
2
  import type { Past } from './origin.js';
4
3
  import { type PackedRecord } from './pack.js';
5
4
  import { type Key } from './paging.js';
5
+ import type { Run } from './stretch.js';
6
+ import type { Parted, Parting } from './subject.js';
6
7
  import type { Described } from './types.js';
7
- import { type Controls, type Parted, type Parting, type Ramp, type Rise, Voice } from './voice.js';
8
+ import { type Controls, type Ramp, type Rise, Voice } from './voice.js';
8
9
  /** A voice that has left, as plain data: the descriptor it was cued with, its controls and its records. */
9
10
  interface PackedVoice {
10
11
  as: Described;
package/dist/revive.js CHANGED
@@ -1,4 +1,4 @@
1
- import { playable } from './cue.js';
1
+ import { playable, roleOf } from './cue.js';
2
2
  import { VoiceHandle } from './handle.js';
3
3
  import { handle } from './hosts.js';
4
4
  import { Leavings } from './leavings.js';
@@ -120,7 +120,7 @@ export function reviveVoice(mix, id, d) {
120
120
  const revive = mix.opts.history?.revive;
121
121
  if (revive === undefined)
122
122
  throw new Error('blits: a paged voice needs history.revive');
123
- const spec = { ...revive(d.as), as: d.as };
123
+ const spec = roleOf(mix, { ...revive(d.as), as: d.as });
124
124
  playable(mix, spec);
125
125
  const voice = new Voice(id, spec, spec.patch, spec.fade ?? {}, Number.NEGATIVE_INFINITY, d.start, mix.slotOf, mix.channels, mix.opts.host, mix.send, null);
126
126
  voice.clocks = d.clocks;
@@ -1,11 +1,12 @@
1
1
  import type { Mixer } from './mixer.js';
2
- import type { Subject, Voice } from './voice.js';
2
+ import type { Subject } from './subject.js';
3
+ import type { Voice } from './voice.js';
3
4
  /**
4
- * What a fold with a locus in play gathers, kept from one fold to the next: the contributions in
5
- * voice order, each a voice's own or a locus's first member; every locus member; and per channel
6
- * slot, a locus's folding value, the weight taken into it and the array it owns.
5
+ * What a fold with a role in play gathers, kept from one fold to the next: the contributions in
6
+ * voice order, each a voice's own or a role's first member; every role member; and per channel
7
+ * slot, a role's folding value, the weight taken into it and the array it owns.
7
8
  */
8
- export interface LocusScratch<I, O> {
9
+ export interface RoleScratch<I, O> {
9
10
  n: number;
10
11
  voices: (Voice<I, O> | null)[];
11
12
  helds: (Subject<unknown> | null)[];
@@ -27,17 +28,17 @@ export interface LocusScratch<I, O> {
27
28
  touched: number[];
28
29
  owned: (number[] | undefined)[];
29
30
  }
30
- export declare function locusScratch<I, O>(channels: number): LocusScratch<I, O>;
31
+ export declare function roleScratch<I, O>(channels: number): RoleScratch<I, O>;
31
32
  /**
32
33
  * `value` copied into the array `k` owns for the slot, as long as the channel's rest, where a
33
34
  * shorter value leaves the rest's own fill: what the channel's fold and lerp read past its end.
34
35
  */
35
- export declare function own<I, O>(k: LocusScratch<I, O>, slot: number, value: number[], rest: number[]): number[];
36
+ export declare function own<I, O>(k: RoleScratch<I, O>, slot: number, value: number[], rest: number[]): number[];
36
37
  /**
37
- * Folds one locus's members into one contribution through each channel's own `lerp`: its value per
38
+ * Folds one role's members into one contribution through each channel's own `lerp`: its value per
38
39
  * channel goes in `k.values`, the channels in `k.touched` in the order first met, and its weight
39
40
  * is returned. A stock `last` channel has nothing to lerp, so it takes its heaviest member's value,
40
41
  * the later cued of two as heavy. A channel folding by scale takes its value in an array of `k`'s own, lerped in
41
42
  * place, since the fold only reads it.
42
43
  */
43
- export declare function foldLocus<I, O>(this: Mixer<I, O>, k: LocusScratch<I, O>, group: number): number;
44
+ export declare function foldRole<I, O>(this: Mixer<I, O>, k: RoleScratch<I, O>, group: number): number;
@@ -1,5 +1,5 @@
1
1
  import { first } from './clone.js';
2
- export function locusScratch(channels) {
2
+ export function roleScratch(channels) {
3
3
  return {
4
4
  n: 0,
5
5
  voices: [],
@@ -36,13 +36,13 @@ export function own(k, slot, value, rest) {
36
36
  return out;
37
37
  }
38
38
  /**
39
- * Folds one locus's members into one contribution through each channel's own `lerp`: its value per
39
+ * Folds one role's members into one contribution through each channel's own `lerp`: its value per
40
40
  * channel goes in `k.values`, the channels in `k.touched` in the order first met, and its weight
41
41
  * is returned. A stock `last` channel has nothing to lerp, so it takes its heaviest member's value,
42
42
  * the later cued of two as heavy. A channel folding by scale takes its value in an array of `k`'s own, lerped in
43
43
  * place, since the fold only reads it.
44
44
  */
45
- export function foldLocus(k, group) {
45
+ export function foldRole(k, group) {
46
46
  k.touched.length = 0;
47
47
  let sum = 0;
48
48
  for (let m = 0; m < k.m; m++)
@@ -0,0 +1,10 @@
1
+ import type { Mixer } from './mixer.js';
2
+ import type { Subject } from './subject.js';
3
+ import type { Voice } from './voice.js';
4
+ /**
5
+ * Whether a fold's next voice is its chain's next record, `held`, and not the next voice sharing
6
+ * one record, `sharers[q]`: the two lists are each in voice order, and a fold takes them as one.
7
+ */
8
+ export declare function chainFirst<I, O>(held: Subject<unknown> | null, sharers: readonly Voice<I, O>[], q: number): boolean;
9
+ /** `foldWith`'s voices with a role in play: each role folds at its first member's place. */
10
+ export declare function foldRoles<I, O>(this: Mixer<I, O>, subject: I, pose: Record<string, unknown>, head: Subject<unknown>, laned: boolean, dry: boolean, except: number | undefined, owed: number): O;