@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/blend.d.ts CHANGED
@@ -1,7 +1,8 @@
1
1
  import type { Mixer } from './mixer.js';
2
2
  import { Store } from './store.js';
3
+ import type { Subject } from './subject.js';
3
4
  import type { Signal } from './types.js';
4
- import type { Subject, Voice } from './voice.js';
5
+ import type { Voice } from './voice.js';
5
6
  /** A `mix.blend`'s one signal, read once for all its members. */
6
7
  export interface Blend<I, O> {
7
8
  by: Signal<I>;
package/dist/blend.js CHANGED
@@ -42,12 +42,7 @@ export function blended(voice, subject, held, slot) {
42
42
  if (cell?.frame === frame)
43
43
  return shareOf(cell.value, i, of.stops);
44
44
  }
45
- else if (slot < of.frames.length) {
46
- const read = this.laneRead(voice, slot);
47
- if (!Number.isNaN(read))
48
- return read;
49
- }
50
- else {
45
+ else if (slot >= of.frames.length) {
51
46
  const size = Math.max(slot + 1, of.frames.length * 2, 64);
52
47
  const frames = new Float64Array(size);
53
48
  const values = new Float64Array(size);
@@ -0,0 +1,70 @@
1
+ import { type Lane } from './lane.js';
2
+ import type { Lanes } from './lanes.js';
3
+ import type { Mixer } from './mixer.js';
4
+ import type { Circle, Finder, VoiceSpec } from './types.js';
5
+ import type { Voice } from './voice.js';
6
+ export declare class Bound<I, O> {
7
+ readonly mix: Mixer<I, O>;
8
+ readonly voice: Voice<I, O>;
9
+ readonly area: Circle | Finder;
10
+ /** The host's finder, or null for a circle. */
11
+ readonly finder: Finder | null;
12
+ /** By index, the `marks` stamp of the last time the general path found it within: see `marked`. */
13
+ hitAt: Float64Array<ArrayBuffer>;
14
+ marks: number;
15
+ /** The frame `hitAt` was last marked in. */
16
+ markedIn: number;
17
+ /** The indices a finder's check reads the signal for. */
18
+ missed: Int32Array<ArrayBuffer>;
19
+ /** Outside production, until it has warned: whether it checks a finder's indices. */
20
+ vets: boolean;
21
+ /** The frame `x`, `y` and `r` were read from `area` in. */
22
+ frame: number;
23
+ x: number;
24
+ y: number;
25
+ r: number;
26
+ r2: number;
27
+ /**
28
+ * Subject numbers whose lane weight or probed weight may not be 0: those the last fill found
29
+ * within, those it zeroed and a probe has yet to read 0 from, and those the general path paid.
30
+ */
31
+ lit: number[];
32
+ spare: number[];
33
+ /**
34
+ * By subject number, the fill that last found it within (negated where that fill let it go), and
35
+ * the fill that last put it in `lit`.
36
+ */
37
+ inAt: Float64Array<ArrayBuffer>;
38
+ litAt: Float64Array<ArrayBuffer>;
39
+ /** Outside production until it has warned: whether it checks the bound clips no signal. */
40
+ checks: boolean;
41
+ frames: number;
42
+ /** The `frames` count a finder's check last ran at. */
43
+ checkedAt: number;
44
+ constructor(mix: Mixer<I, O>, voice: Voice<I, O>, area: Circle | Finder);
45
+ }
46
+ /** Refuses a cue's `within` where the mix has handed out no indices, or on a voice with no signal. */
47
+ export declare function checkWithin<I, O>(mix: Mixer<I, O>, spec: VoiceSpec<I, O>): void;
48
+ /**
49
+ * Bounds a voice just made to its cue's `within`, or to the circle its `around` weight carries once
50
+ * the mix has indices. A carried circle is the signal's own edge, so it is not checked.
51
+ */
52
+ export declare function bind<I, O>(mix: Mixer<I, O>, voice: Voice<I, O>): void;
53
+ /** Whether `subject` is within the voice's bound, on the general path. */
54
+ export declare function inBound<I, O>(mix: Mixer<I, O>, b: Bound<I, O>, subject: I): boolean;
55
+ /** Notes that the subject at `slot` may hold a weight other than 0 on the voice's lane. */
56
+ export declare function lit<I, O>(b: Bound<I, O>, slot: number, fills: number): void;
57
+ /**
58
+ * `run`'s loop for a bounded voice: `one` for the subjects within the bound, and for those that
59
+ * were lit and are not, their lane weight set to 0 as a read at weight 0 leaves it.
60
+ */
61
+ export declare function runBounded<I, O>(lanes: Lanes<I, O>, lane: Lane<I, O>, b: Bound<I, O>): void;
62
+ /**
63
+ * For a role member's bound, before its role's loop: marks the subjects within it this fill, which
64
+ * `inside` then reads.
65
+ */
66
+ export declare function meetBound<I, O>(lanes: Lanes<I, O>, b: Bound<I, O>): void;
67
+ /** Whether `meetBound` found the subject at `slot` within the bound this fill. */
68
+ export declare const inside: <I, O>(b: Bound<I, O>, slot: number, fills: number) => boolean;
69
+ /** A role member's subject outside its bound: its lane weight set to 0, as a read at weight 0 leaves it. */
70
+ export declare function leave<I, O>(lanes: Lanes<I, O>, lane: Lane<I, O>, p: number, slot: number): void;
package/dist/bound.js ADDED
@@ -0,0 +1,351 @@
1
+ import { carried } from './around.js';
2
+ import { unband } from './fold.js';
3
+ import { keepProbed } from './lane.js';
4
+ import { production, warnOnce } from './warn.js';
5
+ /**
6
+ * A voice's signal declared 0 outside a bound (`within` on the cue): a circle around a point the host
7
+ * moves, over positions the host gives the mix (`plot`, `put`), or a finder the host answers with the
8
+ * indices `place` gave. Outside it the voice is not read at all, which is what lets a lane fill visit
9
+ * only the subjects within it.
10
+ */
11
+ /** How many frames apart, outside production, a bound reads its signal just beyond the circle. */
12
+ const CHECK_EVERY = 8;
13
+ /** How many subjects beyond the circle each such check reads. */
14
+ const CHECK_SUBJECTS = 4;
15
+ export class Bound {
16
+ mix;
17
+ voice;
18
+ area;
19
+ /** The host's finder, or null for a circle. */
20
+ finder;
21
+ /** By index, the `marks` stamp of the last time the general path found it within: see `marked`. */
22
+ hitAt = new Float64Array(0);
23
+ marks = 0;
24
+ /** The frame `hitAt` was last marked in. */
25
+ markedIn = Number.NaN;
26
+ /** The indices a finder's check reads the signal for. */
27
+ missed = new Int32Array(CHECK_SUBJECTS);
28
+ /** Outside production, until it has warned: whether it checks a finder's indices. */
29
+ vets;
30
+ /** The frame `x`, `y` and `r` were read from `area` in. */
31
+ frame = Number.NaN;
32
+ x = 0;
33
+ y = 0;
34
+ r = 0;
35
+ r2 = 0;
36
+ /**
37
+ * Subject numbers whose lane weight or probed weight may not be 0: those the last fill found
38
+ * within, those it zeroed and a probe has yet to read 0 from, and those the general path paid.
39
+ */
40
+ lit = [];
41
+ spare = [];
42
+ /**
43
+ * By subject number, the fill that last found it within (negated where that fill let it go), and
44
+ * the fill that last put it in `lit`.
45
+ */
46
+ inAt = new Float64Array(0);
47
+ litAt = new Float64Array(0);
48
+ /** Outside production until it has warned: whether it checks the bound clips no signal. */
49
+ checks;
50
+ frames = 0;
51
+ /** The `frames` count a finder's check last ran at. */
52
+ checkedAt = -1;
53
+ constructor(mix, voice, area) {
54
+ this.mix = mix;
55
+ this.voice = voice;
56
+ this.area = area;
57
+ this.finder = isFinder(area) ? area : null;
58
+ this.checks = !production();
59
+ this.vets = this.checks && this.finder !== null;
60
+ }
61
+ }
62
+ const isFinder = (area) => typeof area.find === 'function';
63
+ /** Refuses a cue's `within` where the mix has handed out no indices, or on a voice with no signal. */
64
+ export function checkWithin(mix, spec) {
65
+ const area = spec.within;
66
+ if (area === undefined)
67
+ return;
68
+ if (typeof area !== 'object' || area === null)
69
+ throw new TypeError('blits: within takes a circle, { x, y, r }, or a finder, { find(out) }');
70
+ if (mix.places === null)
71
+ throw new Error('blits: within needs indices: call mix.place or mix.plot first');
72
+ if (typeof spec.weight !== 'function')
73
+ throw new Error("blits: within needs a signal weight; for a hard-edged disk use weight: mix.around(circle, { steps: 1, jump: 'start' })");
74
+ }
75
+ /**
76
+ * Bounds a voice just made to its cue's `within`, or to the circle its `around` weight carries once
77
+ * the mix has indices. A carried circle is the signal's own edge, so it is not checked.
78
+ */
79
+ export function bind(mix, voice) {
80
+ const spec = voice.spec;
81
+ const own = carried(spec.weight);
82
+ const area = spec.within ?? (own !== undefined && mix.places !== null ? own : undefined);
83
+ if (area === undefined)
84
+ return;
85
+ voice.bound = new Bound(mix, voice, area);
86
+ if (area === own)
87
+ voice.bound.checks = false;
88
+ }
89
+ function snap(b, mix) {
90
+ if (b.frame === mix.frame)
91
+ return;
92
+ b.frame = mix.frame;
93
+ if (b.checks)
94
+ b.frames++;
95
+ if (b.finder === null) {
96
+ const a = b.area;
97
+ b.x = a.x;
98
+ b.y = a.y;
99
+ b.r = a.r;
100
+ b.r2 = a.r * a.r;
101
+ if (b.checks && b.frames % CHECK_EVERY === 0) {
102
+ const places = mix.places;
103
+ check(b, places.found, places.ring(b.x, b.y, b.r));
104
+ }
105
+ }
106
+ }
107
+ /**
108
+ * Leaves in `places.found` the indices the bound finds this frame, and returns how many: the grid's
109
+ * for a circle, the host's for a finder. An index no subject holds reads as no lane number.
110
+ */
111
+ function findAll(b) {
112
+ const places = b.mix.places;
113
+ snap(b, b.mix);
114
+ const finder = b.finder;
115
+ if (finder === null)
116
+ return places.query(b.x, b.y, b.r, b.r2);
117
+ if (places.found.length < places.n)
118
+ places.found = new Int32Array(Math.max(places.n, places.found.length * 2));
119
+ const out = places.found;
120
+ const count = finder.find(out);
121
+ if (!(Number.isInteger(count) && count >= 0 && count <= out.length))
122
+ throw new RangeError(`blits: within's find returned ${count}, not a count of the indices it wrote, 0 to ${out.length}`);
123
+ places.count = count;
124
+ if (b.vets)
125
+ vet(b, out, count, places.n);
126
+ // Once a frame, though a frame may find more than once.
127
+ if (b.checks && b.frames % CHECK_EVERY === 0 && b.checkedAt !== b.frames) {
128
+ b.checkedAt = b.frames;
129
+ check(b, b.missed, miss(b, places, count));
130
+ }
131
+ return count;
132
+ }
133
+ /** Warns once if a finder wrote an index `place` never handed out. */
134
+ function vet(b, out, count, n) {
135
+ for (let k = 0; k < count; k++) {
136
+ const i = out[k];
137
+ if (i >= 0 && i < n)
138
+ continue;
139
+ b.vets = false;
140
+ const voice = b.voice;
141
+ warnOnce(voice, `${voice.spec.name ?? `voice ${voice.id}`}'s within finder found index ${i}, which place never handed out: it is skipped`);
142
+ return;
143
+ }
144
+ }
145
+ /**
146
+ * Reads the signal, as at a first sight, for a few subjects just outside the bound, and warns once
147
+ * if any is above 0: the bound is cutting off part of the signal. Just beyond a circle's edge; for a
148
+ * finder, a few indices it did not find, taken in turn.
149
+ */
150
+ function check(b, list, n) {
151
+ if (n === 0)
152
+ return;
153
+ const places = b.mix.places;
154
+ const voice = b.voice;
155
+ const signal = voice.spec.weight;
156
+ const now = b.mix.now;
157
+ const setting = {
158
+ timestamp: now,
159
+ dt: 0,
160
+ elapsed: voice.elapsedAt(now),
161
+ pass: voice.setting.pass,
162
+ weight: 0,
163
+ state: undefined,
164
+ host: voice.setting.host,
165
+ keep: (_owner, init) => init(),
166
+ send: () => { },
167
+ };
168
+ const step = Math.max(1, Math.floor(n / CHECK_SUBJECTS));
169
+ const from = (b.frames / CHECK_EVERY) % step;
170
+ const found = list.slice(0, n);
171
+ const named = voice.named;
172
+ for (let k = from; k < n; k += step) {
173
+ const subject = places.subjectAt(found[k]);
174
+ if (subject === undefined || (named !== null && !named.has(subject)))
175
+ continue;
176
+ const w = signal(subject, setting);
177
+ if (w > 0) {
178
+ b.checks = false;
179
+ warnOnce(voice, b.finder === null
180
+ ? `${voice.spec.name ?? `voice ${voice.id}`}'s weight signal reads ${w} just outside its within circle (r ${b.r}), which cuts it off there: widen r to where the signal falls to 0`
181
+ : `${voice.spec.name ?? `voice ${voice.id}`}'s weight signal reads ${w} for a subject its within finder did not find, which cuts it off there: find every subject the signal is above 0 for`);
182
+ return;
183
+ }
184
+ }
185
+ }
186
+ /**
187
+ * Marks the `count` indices a finder just found, and leaves in `missed` up to `CHECK_SUBJECTS` it did
188
+ * not, from where the last check left off; returns how many.
189
+ */
190
+ function miss(b, places, count) {
191
+ const stamp = mark(b, places, count);
192
+ const n = places.n;
193
+ const hitAt = b.hitAt;
194
+ const from = n === 0 ? 0 : ((b.frames / CHECK_EVERY) * CHECK_SUBJECTS) % n;
195
+ let m = 0;
196
+ for (let k = 0; k < n && m < CHECK_SUBJECTS; k++) {
197
+ const i = (from + k) % n;
198
+ if (hitAt[i] !== stamp && places.subjectAt(i) !== undefined)
199
+ b.missed[m++] = i;
200
+ }
201
+ return m;
202
+ }
203
+ /** Marks in `hitAt` the first `count` of `places.found`, as this frame's, and returns their stamp. */
204
+ function mark(b, places, count) {
205
+ b.markedIn = b.mix.frame;
206
+ if (b.hitAt.length < places.n)
207
+ b.hitAt = new Float64Array(Math.max(places.n, b.hitAt.length * 2));
208
+ const stamp = ++b.marks;
209
+ const found = places.found;
210
+ const hitAt = b.hitAt;
211
+ for (let k = 0; k < count; k++) {
212
+ const i = found[k];
213
+ if (i >= 0 && i < hitAt.length)
214
+ hitAt[i] = stamp;
215
+ }
216
+ return stamp;
217
+ }
218
+ /** The stamp `hitAt` carries for the indices a finder finds this frame, marked once a frame. */
219
+ function marked(b) {
220
+ if (b.markedIn === b.mix.frame)
221
+ return b.marks;
222
+ const count = findAll(b);
223
+ // A check in `findAll` may have marked them already.
224
+ return b.markedIn === b.mix.frame ? b.marks : mark(b, b.mix.places, count);
225
+ }
226
+ /** Whether `subject` is within the voice's bound, on the general path. */
227
+ export function inBound(mix, b, subject) {
228
+ const places = mix.places;
229
+ if (places === null)
230
+ return false;
231
+ if (b.finder !== null) {
232
+ const i = places.at.get(subject);
233
+ if (i === undefined)
234
+ return false;
235
+ const stamp = marked(b);
236
+ return b.hitAt[i] === stamp;
237
+ }
238
+ if (mix.projecting) {
239
+ const a = b.area;
240
+ return places.inside(subject, a.x, a.y, a.r * a.r);
241
+ }
242
+ snap(b, mix);
243
+ return places.inside(subject, b.x, b.y, b.r2);
244
+ }
245
+ /** Notes that the subject at `slot` may hold a weight other than 0 on the voice's lane. */
246
+ export function lit(b, slot, fills) {
247
+ if (slot >= b.litAt.length)
248
+ grow(b, slot + 1);
249
+ if (b.litAt[slot] === fills)
250
+ return;
251
+ b.litAt[slot] = fills;
252
+ b.lit.push(slot);
253
+ }
254
+ function grow(b, cap) {
255
+ if (cap <= b.inAt.length)
256
+ return;
257
+ const size = Math.max(cap, b.inAt.length * 2, 64);
258
+ const inAt = new Float64Array(size);
259
+ inAt.set(b.inAt);
260
+ b.inAt = inAt;
261
+ const litAt = new Float64Array(size);
262
+ litAt.set(b.litAt);
263
+ b.litAt = litAt;
264
+ }
265
+ /**
266
+ * `run`'s loop for a bounded voice: `one` for the subjects within the bound, and for those that
267
+ * were lit and are not, their lane weight set to 0 as a read at weight 0 leaves it.
268
+ */
269
+ export function runBounded(lanes, lane, b) {
270
+ const places = b.mix.places;
271
+ const n = findAll(b);
272
+ const fills = lanes.fills;
273
+ grow(b, lanes.cap);
274
+ const old = b.lit;
275
+ b.lit = b.spare;
276
+ b.lit.length = 0;
277
+ b.spare = old;
278
+ const voice = lane.voice;
279
+ const inAt = b.inAt;
280
+ const found = places.found;
281
+ const slots = places.slots;
282
+ for (let k = 0; k < n; k++) {
283
+ const slot = slots[found[k]] ?? -1;
284
+ if (slot < 0 || inAt[slot] === fills)
285
+ continue;
286
+ inAt[slot] = fills;
287
+ const p = lane.positionOf(slot);
288
+ if (p < 0)
289
+ continue;
290
+ lit(b, slot, fills);
291
+ places.cur = found[k];
292
+ try {
293
+ lanes.one(lane, p, slot);
294
+ }
295
+ finally {
296
+ places.cur = -1;
297
+ }
298
+ if (voice.keeping)
299
+ return;
300
+ }
301
+ const data = lane.data;
302
+ const per = lanes.per;
303
+ for (let k = 0; k < old.length; k++) {
304
+ const slot = old[k];
305
+ // Within, or already let go of this fill: a second `keepProbed` would copy the 0 just written.
306
+ if (inAt[slot] === fills || inAt[slot] === -fills)
307
+ continue;
308
+ inAt[slot] = -fills;
309
+ const p = lane.positionOf(slot);
310
+ if (p < 0)
311
+ continue;
312
+ const o = p * 8 /* Row.STRIDE */;
313
+ keepProbed(data, o, per, slot * 6 /* Per.SLOT */, fills);
314
+ if (data[o + 2 /* Row.WEIGHT */] !== 0) {
315
+ data[o + 2 /* Row.WEIGHT */] = 0;
316
+ unband(lane.records[p]);
317
+ }
318
+ if (data[o + 3 /* Row.PROBED */] !== 0)
319
+ lit(b, slot, fills);
320
+ }
321
+ }
322
+ /**
323
+ * For a role member's bound, before its role's loop: marks the subjects within it this fill, which
324
+ * `inside` then reads.
325
+ */
326
+ export function meetBound(lanes, b) {
327
+ const places = b.mix.places;
328
+ const n = findAll(b);
329
+ const fills = lanes.fills;
330
+ grow(b, lanes.cap);
331
+ const inAt = b.inAt;
332
+ const found = places.found;
333
+ const slots = places.slots;
334
+ for (let k = 0; k < n; k++) {
335
+ const slot = slots[found[k]] ?? -1;
336
+ if (slot >= 0)
337
+ inAt[slot] = fills;
338
+ }
339
+ }
340
+ /** Whether `meetBound` found the subject at `slot` within the bound this fill. */
341
+ export const inside = (b, slot, fills) => b.inAt[slot] === fills;
342
+ /** A role member's subject outside its bound: its lane weight set to 0, as a read at weight 0 leaves it. */
343
+ export function leave(lanes, lane, p, slot) {
344
+ const data = lane.data;
345
+ const o = p * 8 /* Row.STRIDE */;
346
+ keepProbed(data, o, lanes.per, slot * 6 /* Per.SLOT */, lanes.fills);
347
+ if (data[o + 2 /* Row.WEIGHT */] !== 0) {
348
+ data[o + 2 /* Row.WEIGHT */] = 0;
349
+ unband(lane.records[p]);
350
+ }
351
+ }
package/dist/chain.d.ts CHANGED
@@ -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
  * The first of this subject's records, linked through every live voice that reaches it. A voice
5
6
  * is asked once, through the record it keeps for the subject anyway; a pending one is linked when
package/dist/chain.js CHANGED
@@ -18,6 +18,8 @@ export function chain(subject) {
18
18
  export function relink(subject, was) {
19
19
  const lanes = this.lanes;
20
20
  const slot = was !== undefined ? was.slot : lanes !== null ? lanes.number(subject) : -1;
21
+ if (was === undefined && slot >= 0)
22
+ this.places?.number(subject, slot);
21
23
  // A chain a seek kept for its number alone links afresh, as a subject first seen does.
22
24
  const sought = was !== undefined && slot >= 0 && lanes?.renew(slot) === true;
23
25
  // Where only voices over every subject changed since, those alone are taken off or put on.
@@ -30,8 +32,8 @@ export function relink(subject, was) {
30
32
  head.version = this.version;
31
33
  if (was !== undefined && was !== head) {
32
34
  if (!sought)
33
- head.loci = was.loci;
34
- was.loci = null;
35
+ head.roles = was.roles;
36
+ was.roles = null;
35
37
  was.slot = -1;
36
38
  }
37
39
  head.slot = slot;
@@ -143,7 +145,7 @@ export function changed(mix, voice) {
143
145
  mix.steps.push(voice, ++mix.version);
144
146
  return;
145
147
  }
146
- for (const subject of voice.named) {
148
+ for (const subject of voice.named.list) {
147
149
  const head = headOf(mix, subject);
148
150
  if (head !== undefined)
149
151
  unlink(mix, head);
@@ -176,7 +178,7 @@ export function index(mix, voice) {
176
178
  return;
177
179
  }
178
180
  mix.naming++;
179
- for (const subject of voice.named) {
181
+ for (const subject of voice.named.list) {
180
182
  const list = mix.named.get(subject);
181
183
  if (list === undefined)
182
184
  mix.named.set(subject, [voice]);
@@ -186,7 +188,7 @@ export function index(mix, voice) {
186
188
  }
187
189
  export function unindex(mix, voice) {
188
190
  mix.naming--;
189
- for (const subject of voice.named) {
191
+ for (const subject of voice.named.list) {
190
192
  const list = mix.named.get(subject);
191
193
  if (list === undefined)
192
194
  continue;
@@ -0,0 +1,24 @@
1
+ import type { Motions } from './motions.js';
2
+ import { absent } from './numbers.js';
3
+ import { type Change, type Segment } from './stretch.js';
4
+ /**
5
+ * Queues a retarget, set or push, untimed at the latest frame, for `subject`'s next read past its time.
6
+ * The queue keeps the changes with a time in time order, ahead of any still waiting for one.
7
+ */
8
+ export declare function change<I>(this: Motions<I>, subject: I, c: Change): void;
9
+ /** Files a change with a time after every one due by then, ahead of those still waiting for one. */
10
+ export declare function queue<I>(this: Motions<I>, list: Change[], c: Change, at: number): void;
11
+ /** Every change due by `t` applied to a copy of the stretch playing then, committing nothing. */
12
+ export declare function peek<I>(this: Motions<I>, s: number, t: number, xo: Float64Array, vo: Float64Array): void;
13
+ /** Subject `s`'s changes waiting, with those a read ahead's tape made filed among them by time. */
14
+ export declare function dueTo<I>(this: Motions<I>, s: number): readonly Change[] | undefined;
15
+ /**
16
+ * Takes back every change made after frame `seq`: each subject plays from its earliest stretch
17
+ * kept, with the changes made by then queued to be applied again.
18
+ */
19
+ export declare function rewind<I>(this: Motions<I>, seq: number): void;
20
+ export declare function cut<I>(this: Motions<I>, s: number, seq: number): void;
21
+ /** The stretch `change` releases from `seg` at `at`, for `subject` or one no longer numbered. */
22
+ export declare function applied<I>(this: Motions<I>, subject: I | typeof absent, seg: Segment, change: Change, at: number): Segment;
23
+ export declare function commit<I>(this: Motions<I>, s: number, t: number): void;
24
+ export declare function insert<I>(this: Motions<I>, s: number, seg: Segment): void;