@msb235/blits 0.14.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 (173) hide show
  1. package/CHANGELOG.md +154 -0
  2. package/README.md +3 -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/color.d.ts +12 -10
  21. package/dist/color.js +25 -25
  22. package/dist/columns.js +7 -3
  23. package/dist/contribution.d.ts +30 -0
  24. package/dist/contribution.js +374 -0
  25. package/dist/copyout.d.ts +10 -0
  26. package/dist/copyout.js +69 -24
  27. package/dist/crowd.d.ts +8 -2
  28. package/dist/crowd.js +4 -5
  29. package/dist/cue.d.ts +8 -1
  30. package/dist/cue.js +39 -13
  31. package/dist/cut.d.ts +8 -0
  32. package/dist/cut.js +4 -0
  33. package/dist/drop.d.ts +9 -0
  34. package/dist/drop.js +84 -0
  35. package/dist/due.d.ts +24 -5
  36. package/dist/due.js +113 -33
  37. package/dist/easing.d.ts +4 -4
  38. package/dist/easing.js +3 -3
  39. package/dist/everyone.d.ts +3 -2
  40. package/dist/everyone.js +3 -3
  41. package/dist/fade.d.ts +2 -1
  42. package/dist/fade.js +2 -1
  43. package/dist/fill.d.ts +3 -2
  44. package/dist/fill.js +51 -19
  45. package/dist/fit.d.ts +44 -26
  46. package/dist/fit.js +10 -9
  47. package/dist/fold.d.ts +4 -37
  48. package/dist/fold.js +5 -513
  49. package/dist/gather.d.ts +12 -7
  50. package/dist/gather.js +179 -44
  51. package/dist/gone.js +2 -2
  52. package/dist/grid.d.ts +47 -0
  53. package/dist/grid.js +247 -0
  54. package/dist/handle.d.ts +2 -0
  55. package/dist/handle.js +14 -8
  56. package/dist/held.d.ts +2 -1
  57. package/dist/history.d.ts +6 -5
  58. package/dist/history.js +7 -9
  59. package/dist/hosts.js +9 -15
  60. package/dist/index.d.ts +5 -4
  61. package/dist/keyfill.d.ts +1 -1
  62. package/dist/keyfill.js +5 -3
  63. package/dist/keyrows.d.ts +12 -0
  64. package/dist/keyrows.js +99 -12
  65. package/dist/lane.d.ts +36 -6
  66. package/dist/lane.js +32 -1
  67. package/dist/lanes.d.ts +23 -8
  68. package/dist/lanes.js +29 -8
  69. package/dist/leavings.d.ts +2 -2
  70. package/dist/leavings.js +1 -1
  71. package/dist/lives.d.ts +47 -0
  72. package/dist/lives.js +180 -0
  73. package/dist/meet.d.ts +2 -2
  74. package/dist/meet.js +90 -4
  75. package/dist/mixer.d.ts +54 -36
  76. package/dist/mixer.js +53 -171
  77. package/dist/motion.d.ts +14 -118
  78. package/dist/motion.js +16 -8
  79. package/dist/motions.d.ts +62 -155
  80. package/dist/motions.js +56 -370
  81. package/dist/moved.d.ts +1 -1
  82. package/dist/moved.js +16 -7
  83. package/dist/named.d.ts +0 -1
  84. package/dist/named.js +20 -2
  85. package/dist/numbers.d.ts +2 -0
  86. package/dist/numbers.js +4 -0
  87. package/dist/owed.d.ts +2 -0
  88. package/dist/owed.js +4 -0
  89. package/dist/pack.d.ts +2 -1
  90. package/dist/pack.js +2 -3
  91. package/dist/patch.d.ts +7 -3
  92. package/dist/patch.js +6 -2
  93. package/dist/places.d.ts +53 -0
  94. package/dist/places.js +171 -0
  95. package/dist/plot.d.ts +12 -0
  96. package/dist/plot.js +52 -0
  97. package/dist/project.d.ts +4 -4
  98. package/dist/project.js +9 -212
  99. package/dist/pull.js +11 -8
  100. package/dist/qualify.js +84 -21
  101. package/dist/reading.d.ts +6 -0
  102. package/dist/reading.js +5 -0
  103. package/dist/record.d.ts +5 -1
  104. package/dist/record.js +3 -2
  105. package/dist/relink.d.ts +1 -1
  106. package/dist/restored.d.ts +2 -2
  107. package/dist/restored.js +1 -1
  108. package/dist/rests.d.ts +9 -0
  109. package/dist/rests.js +76 -0
  110. package/dist/revive.d.ts +3 -2
  111. package/dist/revive.js +2 -2
  112. package/dist/{locus.d.ts → role.d.ts} +10 -9
  113. package/dist/{locus.js → role.js} +3 -3
  114. package/dist/roles.d.ts +10 -0
  115. package/dist/roles.js +158 -0
  116. package/dist/roster.js +3 -3
  117. package/dist/rotation.d.ts +1 -1
  118. package/dist/rotation.js +1 -1
  119. package/dist/rows.js +1 -1
  120. package/dist/sample.d.ts +2 -2
  121. package/dist/sample.js +33 -7
  122. package/dist/seek.js +5 -5
  123. package/dist/setting.d.ts +27 -0
  124. package/dist/setting.js +55 -0
  125. package/dist/shared.d.ts +1 -1
  126. package/dist/shared.js +1 -1
  127. package/dist/signals.d.ts +64 -32
  128. package/dist/signals.js +20 -18
  129. package/dist/stack.d.ts +5 -5
  130. package/dist/stack.js +5 -5
  131. package/dist/standing.d.ts +2 -1
  132. package/dist/standing.js +3 -2
  133. package/dist/store.d.ts +9 -0
  134. package/dist/store.js +24 -0
  135. package/dist/strays.d.ts +2 -1
  136. package/dist/strays.js +20 -13
  137. package/dist/stretch.d.ts +112 -0
  138. package/dist/stretch.js +9 -0
  139. package/dist/subject.d.ts +142 -0
  140. package/dist/subject.js +1 -0
  141. package/dist/tape.d.ts +9 -5
  142. package/dist/tape.js +5 -1
  143. package/dist/then.d.ts +9 -0
  144. package/dist/then.js +57 -0
  145. package/dist/ticker.d.ts +4 -4
  146. package/dist/transport.d.ts +0 -8
  147. package/dist/transport.js +0 -4
  148. package/dist/turns.d.ts +4 -3
  149. package/dist/turns.js +4 -3
  150. package/dist/types/arc.d.ts +12 -11
  151. package/dist/types/channel.d.ts +12 -8
  152. package/dist/types/history.d.ts +35 -19
  153. package/dist/types/mix.d.ts +222 -163
  154. package/dist/types/motion.d.ts +151 -0
  155. package/dist/types/motion.js +1 -0
  156. package/dist/types/patch.d.ts +67 -34
  157. package/dist/types/place.d.ts +51 -36
  158. package/dist/types/transport.d.ts +20 -18
  159. package/dist/types/voice.d.ts +136 -70
  160. package/dist/types/watch.d.ts +88 -40
  161. package/dist/types.d.ts +1 -0
  162. package/dist/voice.d.ts +7 -143
  163. package/dist/voice.js +28 -79
  164. package/dist/warn.d.ts +2 -0
  165. package/dist/warn.js +1 -1
  166. package/dist/watch.d.ts +2 -2
  167. package/dist/watch.js +5 -3
  168. package/dist/wave.d.ts +4 -3
  169. package/dist/wave.js +3 -2
  170. package/dist/weigh.d.ts +7 -1
  171. package/dist/weigh.js +27 -0
  172. package/docs/schema.html +212 -34
  173. package/package.json +2 -2
package/dist/cut.d.ts ADDED
@@ -0,0 +1,8 @@
1
+ /** Where history is cut for a seek or read: what frame `seq` made, and before it, is kept. */
2
+ export interface Cut {
3
+ seq: number;
4
+ /** For a read of what a frame showed: of that frame's own changes, only those made before it was read. */
5
+ strict: boolean;
6
+ }
7
+ /** Whether a change made in frame `seq` falls inside `cut`; `early` for one made before the frame was read. */
8
+ export declare function within(cut: Cut, seq: number, early?: boolean): boolean;
package/dist/cut.js ADDED
@@ -0,0 +1,4 @@
1
+ /** Whether a change made in frame `seq` falls inside `cut`; `early` for one made before the frame was read. */
2
+ export function within(cut, seq, early = false) {
3
+ return seq < cut.seq || (seq === cut.seq && (!cut.strict || early));
4
+ }
package/dist/drop.d.ts ADDED
@@ -0,0 +1,9 @@
1
+ import { releasing } from './history.js';
2
+ import type { Mixer } from './mixer.js';
3
+ import type { Motions } from './motions.js';
4
+ import type { Voice } from './voice.js';
5
+ export declare function drop<I, O>(this: Mixer<I, O>, subject: I): void;
6
+ /** Lets `voice` go of a dropped subject. Methods rather than closures, so a drop makes neither. */
7
+ export declare function forgetIn<I, O>(this: Mixer<I, O>, voice: Voice<I, O>, subject: I, kept: ReturnType<typeof releasing>): void;
8
+ export declare function releaseIn<I, O>(this: Mixer<I, O>, motion: Motions<I>, subject: I, kept: ReturnType<typeof releasing>): void;
9
+ export declare function recordDrop<I, O>(this: Mixer<I, O>, subject: I): void;
package/dist/drop.js ADDED
@@ -0,0 +1,84 @@
1
+ import { headOf, unlink } from './chain.js';
2
+ import { leaveVoice } from './fade.js';
3
+ import { goneIndex } from './gone.js';
4
+ import { releasing } from './history.js';
5
+ import { nextFrame } from './move.js';
6
+ import { Masked } from './store.js';
7
+ import { record, taping, unplayable } from './tape.js';
8
+ export function drop(subject) {
9
+ this.frame = nextFrame();
10
+ const head = headOf(this, subject);
11
+ // Nothing that kept this head, such as `pull`'s remembered list, may take it as current again.
12
+ if (head !== undefined)
13
+ unlink(this, head);
14
+ // A number a seek carried over is released even where its chain is not linked since.
15
+ const slot = this.chains.get(subject)?.slot ?? -1;
16
+ if (slot >= 0 && this.lanes !== null)
17
+ this.lanes.release(slot);
18
+ // A read ahead's copy reads the live mix's poses, and so hides one where the mix deletes it.
19
+ if (this.projecting && !(this.lastPoses instanceof Masked))
20
+ this.lastPoses = new Masked(this.lastPoses);
21
+ if (!this.projecting)
22
+ this.places?.drop(subject);
23
+ this.lastPoses.delete(subject);
24
+ this.restStamps?.delete(subject);
25
+ this.chains.delete(subject);
26
+ this.stir();
27
+ // A record of it is only in a voice over every subject, one naming it, or one gone; a ramp out
28
+ // of a voice that does not name it is in `parters`, and a patch's own state in `motions`.
29
+ const kept = releasing(this);
30
+ for (const voice of this.general)
31
+ this.forgetIn(voice, subject, kept);
32
+ const named = this.named.get(subject);
33
+ if (named !== undefined)
34
+ for (const voice of [...named])
35
+ this.forgetIn(voice, subject, kept);
36
+ const gone = goneIndex(this);
37
+ for (const voice of gone.general)
38
+ this.forgetIn(voice, subject, kept);
39
+ const goneNaming = gone.named.get(subject);
40
+ if (goneNaming !== undefined)
41
+ for (const voice of goneNaming)
42
+ this.forgetIn(voice, subject, kept);
43
+ if (this.owners !== null)
44
+ for (const voice of this.owners)
45
+ this.forgetIn(voice, subject, kept);
46
+ for (const voice of this.parters) {
47
+ this.forgetIn(voice, subject, kept);
48
+ if (voice.parts?.size === 0)
49
+ voice.parts = null;
50
+ if (voice.parted?.size === 0)
51
+ voice.parted = null;
52
+ if (voice.parts === null && voice.parted === null)
53
+ this.parters.delete(voice);
54
+ }
55
+ const strays = this.strays.get(subject);
56
+ if (strays !== undefined)
57
+ for (const motion of strays)
58
+ this.releaseIn(motion, subject, kept);
59
+ this.strays.delete(subject);
60
+ for (const motion of this.strayAll)
61
+ this.releaseIn(motion, subject, kept);
62
+ if (taping(this))
63
+ this.recordDrop(subject);
64
+ }
65
+ /** Lets `voice` go of a dropped subject. Methods rather than closures, so a drop makes neither. */
66
+ export function forgetIn(voice, subject, kept) {
67
+ leaveVoice(this, voice, subject);
68
+ voice.subjects.delete(subject);
69
+ if (voice.motion !== undefined)
70
+ this.releaseIn(voice.motion, subject, kept);
71
+ voice.parts?.delete(subject);
72
+ voice.parted?.delete(subject);
73
+ voice.blend?.of.reads.delete(subject);
74
+ }
75
+ // A motion patch's state is the live mix's alone: a read ahead cannot start a subject afresh in it.
76
+ export function releaseIn(motion, subject, kept) {
77
+ if (!this.projecting)
78
+ motion.release(subject, kept[0], kept[1], kept[2], kept[3]);
79
+ else if (motion.holds(subject))
80
+ unplayable('a drop of a subject a motion patch holds');
81
+ }
82
+ export function recordDrop(subject) {
83
+ record(this, 'drop', (stage) => stage.mix(this)?.drop(subject));
84
+ }
package/dist/due.d.ts CHANGED
@@ -1,10 +1,29 @@
1
1
  import type { Mixer } from './mixer.js';
2
2
  import type { Voice } from './voice.js';
3
- /** One entry in a mix's due queue; stale once its voice has been scheduled again. */
4
- export interface Due<I, O> {
5
- at: number;
6
- voice: Voice<I, O>;
7
- token: number;
3
+ /**
4
+ * Live voices by the earliest mix time `moveTo` would change each, a binary heap. The times sit in
5
+ * one flat array, so a sift reads no voice: at 40,000 voices a stop's sift to the top missed the
6
+ * cache at each of its levels. An entry is stale once its voice has been scheduled again.
7
+ */
8
+ export declare class DueQueue<I, O> {
9
+ size: number;
10
+ private at;
11
+ private tokens;
12
+ private voices;
13
+ /** The size past which stale entries are pruned, so a voice rescheduled often cannot grow it. */
14
+ private limit;
15
+ push(at: number, voice: Voice<I, O>, token: number): void;
16
+ /** The earliest entry's time, Infinity while empty. */
17
+ first(): number;
18
+ /** Takes off the earliest entry, giving its voice where it is current and undefined where stale. */
19
+ pop(): Voice<I, O> | undefined;
20
+ clear(): void;
21
+ private move;
22
+ /** Puts an entry in the hole at `i` and sifts it down to where it belongs. */
23
+ private sink;
24
+ private grow;
25
+ /** Drops the entries made stale by a later one and heapifies what is left. */
26
+ private prune;
8
27
  }
9
28
  /** Files a voice in the due queue at its current due, replacing any entry it had. */
10
29
  export declare function schedule<I, O>(mix: Mixer<I, O>, voice: Voice<I, O>): void;
package/dist/due.js CHANGED
@@ -1,3 +1,108 @@
1
+ /**
2
+ * Live voices by the earliest mix time `moveTo` would change each, a binary heap. The times sit in
3
+ * one flat array, so a sift reads no voice: at 40,000 voices a stop's sift to the top missed the
4
+ * cache at each of its levels. An entry is stale once its voice has been scheduled again.
5
+ */
6
+ export class DueQueue {
7
+ size = 0;
8
+ at = new Float64Array(64);
9
+ tokens = new Float64Array(64);
10
+ voices = [];
11
+ /** The size past which stale entries are pruned, so a voice rescheduled often cannot grow it. */
12
+ limit = 64;
13
+ push(at, voice, token) {
14
+ if (this.size >= this.limit)
15
+ this.prune();
16
+ if (this.size === this.at.length)
17
+ this.grow();
18
+ const ats = this.at;
19
+ let i = this.size++;
20
+ while (i > 0) {
21
+ const up = (i - 1) >> 1;
22
+ if (ats[up] <= at)
23
+ break;
24
+ this.move(up, i);
25
+ i = up;
26
+ }
27
+ ats[i] = at;
28
+ this.tokens[i] = token;
29
+ this.voices[i] = voice;
30
+ }
31
+ /** The earliest entry's time, Infinity while empty. */
32
+ first() {
33
+ return this.size > 0 ? this.at[0] : Number.POSITIVE_INFINITY;
34
+ }
35
+ /** Takes off the earliest entry, giving its voice where it is current and undefined where stale. */
36
+ pop() {
37
+ const voice = this.voices[0];
38
+ const token = this.tokens[0];
39
+ const last = --this.size;
40
+ if (last > 0)
41
+ this.sink(0, this.at[last], this.tokens[last], this.voices[last]);
42
+ this.voices[last] = undefined;
43
+ return token === voice.dueToken && voice.state !== 'done' ? voice : undefined;
44
+ }
45
+ clear() {
46
+ this.size = 0;
47
+ this.voices = [];
48
+ this.limit = 64;
49
+ }
50
+ move(from, to) {
51
+ this.at[to] = this.at[from];
52
+ this.tokens[to] = this.tokens[from];
53
+ this.voices[to] = this.voices[from];
54
+ }
55
+ /** Puts an entry in the hole at `i` and sifts it down to where it belongs. */
56
+ sink(i, at, token, voice) {
57
+ const ats = this.at;
58
+ const n = this.size;
59
+ for (;;) {
60
+ const l = 2 * i + 1;
61
+ const r = l + 1;
62
+ let m = i;
63
+ let least = at;
64
+ if (l < n && ats[l] < least) {
65
+ m = l;
66
+ least = ats[l];
67
+ }
68
+ if (r < n && ats[r] < least)
69
+ m = r;
70
+ if (m === i)
71
+ break;
72
+ this.move(m, i);
73
+ i = m;
74
+ }
75
+ ats[i] = at;
76
+ this.tokens[i] = token;
77
+ this.voices[i] = voice;
78
+ }
79
+ grow() {
80
+ const at = new Float64Array(this.at.length * 2);
81
+ at.set(this.at);
82
+ this.at = at;
83
+ const tokens = new Float64Array(this.tokens.length * 2);
84
+ tokens.set(this.tokens);
85
+ this.tokens = tokens;
86
+ }
87
+ /** Drops the entries made stale by a later one and heapifies what is left. */
88
+ prune() {
89
+ let n = 0;
90
+ for (let k = 0; k < this.size; k++) {
91
+ const voice = this.voices[k];
92
+ if (this.tokens[k] !== voice.dueToken)
93
+ continue;
94
+ if (n !== k)
95
+ this.move(k, n);
96
+ n++;
97
+ }
98
+ for (let k = n; k < this.size; k++)
99
+ this.voices[k] = undefined;
100
+ this.size = n;
101
+ for (let k = (n >> 1) - 1; k >= 0; k--)
102
+ this.sink(k, this.at[k], this.tokens[k], this.voices[k]);
103
+ this.limit = Math.max(64, 2 * n);
104
+ }
105
+ }
1
106
  /**
2
107
  * The earliest mix time `moveTo` would change a voice: its start, its anchored out, the end of
3
108
  * its last pass, the end of its fade or of a subject's ramp out; -Infinity where it must be
@@ -59,43 +164,18 @@ export function schedule(mix, voice) {
59
164
  const due = dueOf(mix, voice);
60
165
  if (due === Number.POSITIVE_INFINITY)
61
166
  return;
62
- const heap = mix.due;
63
- heap.push({ at: Number.isNaN(due) ? Number.NEGATIVE_INFINITY : due, voice, token });
64
- let i = heap.length - 1;
65
- while (i > 0) {
66
- const up = (i - 1) >> 1;
67
- if (heap[up].at <= heap[i].at)
68
- break;
69
- [heap[up], heap[i]] = [heap[i], heap[up]];
70
- i = up;
71
- }
167
+ mix.due.push(Number.isNaN(due) ? Number.NEGATIVE_INFINITY : due, voice, token);
72
168
  }
73
169
  /** Takes every current entry due by `now` off the queue, in cue order. */
74
170
  export function popDue(mix, now) {
75
- const heap = mix.due;
76
- const at = (k) => k < heap.length ? heap[k].at : Number.POSITIVE_INFINITY;
171
+ const queue = mix.due;
77
172
  const out = [];
78
- while (heap.length > 0 && heap[0].at <= now) {
79
- const top = heap[0];
80
- const last = heap.pop();
81
- if (heap.length > 0) {
82
- heap[0] = last;
83
- let i = 0;
84
- for (;;) {
85
- const l = 2 * i + 1;
86
- const r = l + 1;
87
- const m0 = at(l) < at(i) ? l : i;
88
- const m = at(r) < at(m0) ? r : m0;
89
- if (m === i)
90
- break;
91
- [heap[m], heap[i]] = [heap[i], heap[m]];
92
- i = m;
93
- }
94
- }
95
- if (top.token === top.voice.dueToken && top.voice.state !== 'done') {
96
- top.voice.dueToken++;
97
- out.push(top.voice);
98
- }
173
+ while (queue.first() <= now) {
174
+ const voice = queue.pop();
175
+ if (voice === undefined)
176
+ continue;
177
+ voice.dueToken++;
178
+ out.push(voice);
99
179
  }
100
180
  return byId(out);
101
181
  }
package/dist/easing.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import type { Easing } from './types.js';
2
2
  /**
3
- * A curve on 0..1: the function an easing stands for.
3
+ * The function an easing stands for, a curve on 0..1.
4
4
  *
5
5
  * @category patch
6
6
  */
@@ -11,9 +11,9 @@ export type Curve = (u: number) => number;
11
11
  */
12
12
  export declare function startSlope(c: Curve): number;
13
13
  /**
14
- * The function an easing names: a CSS name, a `bezier` or `steps`, or a function, which comes back
15
- * as it is. Call it once where a curve is built, not per frame; the same name or easing object
16
- * gives the same function each time.
14
+ * The function an easing names. The easing is a CSS name, a `bezier` or `steps`, or a function,
15
+ * which comes back as it is. Call it once where a curve is built, not per frame; the same name or
16
+ * easing object gives the same function each time.
17
17
  *
18
18
  * @category patch
19
19
  */
package/dist/easing.js CHANGED
@@ -72,9 +72,9 @@ export function startSlope(c) {
72
72
  return v > 1e-3 ? Number.POSITIVE_INFINITY : v / h;
73
73
  }
74
74
  /**
75
- * The function an easing names: a CSS name, a `bezier` or `steps`, or a function, which comes back
76
- * as it is. Call it once where a curve is built, not per frame; the same name or easing object
77
- * gives the same function each time.
75
+ * The function an easing names. The easing is a CSS name, a `bezier` or `steps`, or a function,
76
+ * which comes back as it is. Call it once where a curve is built, not per frame; the same name or
77
+ * easing object gives the same function each time.
78
78
  *
79
79
  * @category patch
80
80
  */
@@ -1,5 +1,6 @@
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
5
  * Whether a voice can keep one record for every subject, decided when it is cued. A record holds
5
6
  * what a voice knows of one subject; where the voice is over every subject with no stagger, keeps
@@ -60,7 +61,7 @@ export declare function unsight<I, O>(voice: Voice<I, O>, slot: number): void;
60
61
  */
61
62
  export declare function unshare<I, O>(mix: Mixer<I, O>, voice: Voice<I, O>, kept?: boolean, keeper?: I): void;
62
63
  /**
63
- * Counts what makes a mix's fold more than a walk of each chain: a locus, a voice sharing one
64
+ * Counts what makes a mix's fold more than a walk of each chain: a role, a voice sharing one
64
65
  * record, a voice that stopped sharing this frame. Called wherever one of the three changes, so
65
66
  * `foldWith` asks one number.
66
67
  */
package/dist/everyone.js CHANGED
@@ -22,7 +22,7 @@ export function shares(mix, voice) {
22
22
  voice.named === null &&
23
23
  spec.target === undefined &&
24
24
  spec.stagger === undefined &&
25
- spec.locus === undefined &&
25
+ spec.role === undefined &&
26
26
  spec.anchor === undefined &&
27
27
  spec.from !== 'current' &&
28
28
  typeof spec.weight !== 'function' &&
@@ -234,12 +234,12 @@ export function unshare(mix, voice, kept = false, keeper) {
234
234
  mix.lanes?.refill();
235
235
  }
236
236
  /**
237
- * Counts what makes a mix's fold more than a walk of each chain: a locus, a voice sharing one
237
+ * Counts what makes a mix's fold more than a walk of each chain: a role, a voice sharing one
238
238
  * record, a voice that stopped sharing this frame. Called wherever one of the three changes, so
239
239
  * `foldWith` asks one number.
240
240
  */
241
241
  export function detour(mix) {
242
- mix.detours = mix.loci + mix.sharers.length + (mix.unshared ? 1 : 0);
242
+ mix.detours = mix.roleVoices + mix.sharers.length + (mix.unshared ? 1 : 0);
243
243
  }
244
244
  /** Gives a record just made for a subject a sharing voice had seen what the voice kept for it. */
245
245
  export function inherit(voice, held, slot) {
package/dist/fade.d.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import type { Mixer } from './mixer.js';
2
- import type { Left, Parted, Parting, Voice } from './voice.js';
2
+ import type { Left, Parted, Parting } from './subject.js';
3
+ import type { Voice } from './voice.js';
3
4
  /** Starts one subject's ramp out of a voice, or takes it out at once for a ramp of 0. */
4
5
  export declare function fadeSubject<I, O>(mix: Mixer<I, O>, voice: Voice<I, O>, subject: I, over?: number): void;
5
6
  /** What a subject's own ramp out of a voice leaves of its weight this frame, 0..1. */
package/dist/fade.js CHANGED
@@ -6,6 +6,7 @@ import { leave, noted, releasing } from './history.js';
6
6
  import { markOf } from './marks.js';
7
7
  import { orphan, ownerReading } from './owner.js';
8
8
  import { startOf } from './place.js';
9
+ import { isRested } from './record.js';
9
10
  import { scoreTouched } from './scored.js';
10
11
  import { lingers, refit } from './spans.js';
11
12
  import { unreach } from './unreached.js';
@@ -111,7 +112,7 @@ function forgetIn(mix, voice, subject) {
111
112
  voice.subjects.delete(subject);
112
113
  if (held.reaches)
113
114
  voice.seen--;
114
- if (held.rested)
115
+ if (isRested(held))
115
116
  voice.restedCount--;
116
117
  if (voice.holder === held)
117
118
  voice.holder = null;
package/dist/fill.d.ts CHANGED
@@ -2,7 +2,8 @@ import { type Lane, type Laned, type Paced } from './lane.js';
2
2
  import type { Lanes } from './lanes.js';
3
3
  import { absent } from './numbers.js';
4
4
  import { type Scratch } from './patch.js';
5
- import type { Subject, Voice } from './voice.js';
5
+ import type { Subject } from './subject.js';
6
+ import type { Voice } from './voice.js';
6
7
  export declare function fillAll<I, O>(lanes: Lanes<I, O>, now: number, version: number): void;
7
8
  /** Leaves a lane's subjects to the general path until it wakes, keeping what `weightOf` reports. */
8
9
  export declare function rest<I, O>(lanes: Lanes<I, O>, lane: Paced): void;
@@ -31,7 +32,7 @@ export declare function runEveryone<I, O>(this: Lanes<I, O>, lane: Lane<I, O>):
31
32
  /**
32
33
  * One subject's contribution from a lane's voice. True where the voice gave the subject a delta,
33
34
  * as the general path's `contribution` does where it returns one, which is what makes a voice in a
34
- * locus one of its members for the subject.
35
+ * role one of its members for the subject.
35
36
  */
36
37
  export declare function one<I, O>(this: Lanes<I, O>, lane: Lane<I, O>, p: number, slot: number): boolean;
37
38
  /**
package/dist/fill.js CHANGED
@@ -1,11 +1,13 @@
1
+ import { runBounded } from './bound.js';
1
2
  import { foldNumber, lerpNumber } from './channels.js';
2
3
  import { passAt, phaseAt, same, silent, weighed } from './clock.js';
3
4
  import { flush } from './columns.js';
4
5
  import { crowdsUpTo, freshen } from './crowd.js';
5
6
  import { open as openRecord, shut as shutRecord } from './everyone.js';
6
7
  import { unband } from './fold.js';
7
- import { foldLocus, gatherLocus } from './gather.js';
8
+ import { foldRole, gatherRole, runRole } from './gather.js';
8
9
  import { runKeys } from './keyfill.js';
10
+ import { keepProbed } from './lane.js';
9
11
  import { reach } from './meet.js';
10
12
  import { absent } from './numbers.js';
11
13
  import { AT, NOTHING, readKeys, seg, segment, shifted } from './patch.js';
@@ -15,6 +17,8 @@ import { rested } from './turns.js';
15
17
  // V8 reads a local const as a constant, where it reads an import from its module on every call.
16
18
  const open = openRecord;
17
19
  const shut = shutRecord;
20
+ /** The last `Arg.MARK` handed out, by every mix's lanes, so no two placings share one. */
21
+ let marks = 0;
18
22
  /** The least prime at least `n`, and 1 below 2. */
19
23
  function primeFrom(n) {
20
24
  if (n < 2)
@@ -34,6 +38,7 @@ export function fillAll(lanes, now, version) {
34
38
  flush(lanes);
35
39
  const host = lanes.host;
36
40
  lanes.filling = true;
41
+ const lookups = lanes.lookups;
37
42
  try {
38
43
  lanes.fills++;
39
44
  lanes.owed.begin(lanes.fills);
@@ -84,17 +89,21 @@ export function fillAll(lanes, now, version) {
84
89
  if (busy) {
85
90
  for (const ch of lanes.laned)
86
91
  rested(ch, size);
87
- for (const g of lanes.loci)
88
- gatherLocus(lanes, g);
92
+ for (const g of lanes.roles)
93
+ gatherRole(lanes, g);
89
94
  for (const lane of lanes.lanes) {
90
95
  if (lane.idle)
91
96
  continue;
92
- if (lanes.crowds.length > 0)
97
+ if (lanes.crowds.length > 0) {
98
+ lanes.arg[6 /* Arg.MARK */] = Number.NaN;
93
99
  crowdsUpTo(lanes, lane.voice.id);
100
+ }
94
101
  lanes.run(lane);
95
102
  }
96
- if (lanes.crowds.length > 0)
103
+ if (lanes.crowds.length > 0) {
104
+ lanes.arg[6 /* Arg.MARK */] = Number.NaN;
97
105
  crowdsUpTo(lanes, Number.POSITIVE_INFINITY);
106
+ }
98
107
  for (const c of lanes.crowds)
99
108
  if (!c.idle) {
100
109
  c.bareFill = lanes.fills;
@@ -111,6 +120,7 @@ export function fillAll(lanes, now, version) {
111
120
  }
112
121
  lanes.moved = reading.moved;
113
122
  lanes.inputs = reading.inputs;
123
+ lanes.holds = lanes.lookups !== lookups;
114
124
  }
115
125
  finally {
116
126
  lanes.filling = false;
@@ -162,10 +172,7 @@ export function rest(lanes, lane) {
162
172
  continue;
163
173
  reach(lanes, slot, 1);
164
174
  // A probe read this subject from the last fill, which this lane's weight stays from.
165
- if (lanes.per[slot * 6 /* Per.SLOT */ + 5 /* Per.LANE_FILL */] === lanes.fills - 1) {
166
- const o = p * 8 /* Row.STRIDE */;
167
- data[o + 3 /* Row.PROBED */] = data[o + 2 /* Row.WEIGHT */];
168
- }
175
+ keepProbed(data, p * 8 /* Row.STRIDE */, lanes.per, slot * 6 /* Per.SLOT */, lanes.fills);
169
176
  }
170
177
  }
171
178
  export function wake(lanes, lane) {
@@ -205,6 +212,7 @@ export function parting(voice, slot) {
205
212
  return subject === absent ? 1 : this.host.parting(voice, subject);
206
213
  }
207
214
  export function subjectAt(slot) {
215
+ this.lookups++;
208
216
  let subject = this.subjects[slot];
209
217
  if (subject === undefined) {
210
218
  subject = this.numbers.subject(slot);
@@ -217,6 +225,7 @@ export function subjectAt(slot) {
217
225
  export function reset(lane) {
218
226
  const voice = lane.voice;
219
227
  lane.placed = false;
228
+ this.arg[6 /* Arg.MARK */] = Number.NaN;
220
229
  lane.read = false;
221
230
  lane.weighed = false;
222
231
  // With no fade in or out the envelope is 1 for every subject, which is what it would return.
@@ -232,11 +241,16 @@ export function run(lane) {
232
241
  const voice = lane.voice;
233
242
  if (voice.state === 'pending' || voice.state === 'done')
234
243
  return;
235
- if (lane.group !== null) {
236
- foldLocus(this, lane, lane.group);
244
+ const g = lane.group;
245
+ if (g !== null) {
246
+ if (!g.inline)
247
+ foldRole(this, lane, g);
248
+ else if (lane === g.members[0])
249
+ runRole(this, g);
237
250
  return;
238
251
  }
239
252
  this.reset(lane);
253
+ lane.ran = this.fills;
240
254
  if (lane.motion !== undefined && !voice.keeping) {
241
255
  runMotion(this, lane, lane.motion);
242
256
  return;
@@ -251,6 +265,10 @@ export function run(lane) {
251
265
  this.runEveryone(lane);
252
266
  return;
253
267
  }
268
+ if (voice.bound !== null) {
269
+ runBounded(this, lane, voice.bound);
270
+ return;
271
+ }
254
272
  for (let p = 0; p < list.length; p++) {
255
273
  this.one(lane, p, list[p]);
256
274
  // Its patch just made kept state: no further call this fill, the general path makes them.
@@ -281,7 +299,7 @@ export function runEveryone(lane) {
281
299
  /**
282
300
  * One subject's contribution from a lane's voice. True where the voice gave the subject a delta,
283
301
  * as the general path's `contribution` does where it returns one, which is what makes a voice in a
284
- * locus one of its members for the subject.
302
+ * role one of its members for the subject.
285
303
  */
286
304
  export function one(lane, p, slot) {
287
305
  const host = this.host;
@@ -290,9 +308,7 @@ export function one(lane, p, slot) {
290
308
  const arg = this.arg;
291
309
  const data = lane.data;
292
310
  const o = p * 8 /* Row.STRIDE */;
293
- // A probe read this subject from the last fill: keep the weight it read for `weightOf`.
294
- if (this.per[slot * 6 /* Per.SLOT */ + 5 /* Per.LANE_FILL */] === this.fills - 1)
295
- data[o + 3 /* Row.PROBED */] = data[o + 2 /* Row.WEIGHT */];
311
+ keepProbed(data, o, this.per, slot * 6 /* Per.SLOT */, this.fills);
296
312
  if (this.lately >= 0 && this.unread(slot))
297
313
  return false;
298
314
  const delay = data[o + 0 /* Row.DELAY */];
@@ -319,6 +335,8 @@ export function one(lane, p, slot) {
319
335
  lane.placedAt = elapsed;
320
336
  lane.phase = phaseAt(elapsed, voice.duration, voice.passes);
321
337
  lane.pass = passAt(elapsed, voice.duration, voice.passes);
338
+ arg[6 /* Arg.MARK */] = ++marks;
339
+ lane.mark = arg[6 /* Arg.MARK */];
322
340
  }
323
341
  const since = data[o + 1 /* Row.SINCE */];
324
342
  if (!lane.flat && (!lane.weighed || !same(since, lane.weighedSince))) {
@@ -352,6 +370,14 @@ export function one(lane, p, slot) {
352
370
  lane.readAt = elapsed;
353
371
  this.gather(lane, lane.delta);
354
372
  }
373
+ // A signal is handed the gap since the subject's last read, which this read is, as it is on
374
+ // the general path; the record's delta is not this read's, so a probe reads it again.
375
+ if (typeof voice.spec.weight === 'function') {
376
+ const rec = lane.records[p];
377
+ rec.probed = this.now;
378
+ rec.seeks = voice.seeks;
379
+ rec.delta = null;
380
+ }
355
381
  if (w > 0) {
356
382
  arg[0 /* Arg.WEIGHT */] = w;
357
383
  this.fold(lane, slot);
@@ -359,8 +385,9 @@ export function one(lane, p, slot) {
359
385
  return true;
360
386
  }
361
387
  const rec = lane.records[p];
362
- // The general path makes a voice's first call for a subject, where it first sees it play.
363
- if (Number.isNaN(rec.probed)) {
388
+ // The general path makes a voice's first call for a subject, where it first sees it play, or a
389
+ // probe meeting the voice makes it here, or the fill of a lane that took every subject.
390
+ if (Number.isNaN(rec.probed) && !this.meeting && !lane.every) {
364
391
  this.late.push(slot);
365
392
  return false;
366
393
  }
@@ -399,7 +426,7 @@ export function foldKeys(voice, chans, slot) {
399
426
  const a = seg.a;
400
427
  const b = seg.b;
401
428
  const u = seg.eased;
402
- if (ch.scalar || !ch.plain) {
429
+ if (ch.scalar || !ch.plain || !ch.straight) {
403
430
  this.foldInto(ch, slot, voice.lerps[i](a, b, u));
404
431
  continue;
405
432
  }
@@ -440,8 +467,13 @@ export function call(voice, chans, rec, slot) {
440
467
  this.foldDelta(chans, slot, rec.delta);
441
468
  return true;
442
469
  }
443
- if (silent(voice, w))
470
+ if (silent(voice, w)) {
471
+ // As `contribution` leaves a silent read, so the next one is handed the same `dt`.
472
+ rec.delta = null;
473
+ rec.probed = this.now;
474
+ rec.seeks = voice.seeks;
444
475
  return false;
476
+ }
445
477
  const kept = reading.kept;
446
478
  host.ready(voice, subject, rec);
447
479
  if (this.keeps)