@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
@@ -0,0 +1,374 @@
1
+ import { inBound } from './bound.js';
2
+ import { passAt, phaseAt, phaseBefore, silent, stepOf } from './clock.js';
3
+ import { copy as copyValue } from './clone.js';
4
+ import { near } from './fold.js';
5
+ import { waits } from './held.js';
6
+ import { apart, copyState, record, recordHost, remember } from './history.js';
7
+ import { stateful } from './hosts.js';
8
+ import { moved } from './moved.js';
9
+ import { readKeys } from './patch.js';
10
+ import { reading } from './reading.js';
11
+ import { isRested } from './record.js';
12
+ import { checkWrites } from './voice.js';
13
+ // V8 reads a local const as a constant, where it reads an import from its module on every call.
14
+ const copy = copyValue;
15
+ /**
16
+ * A record after a seek that rebuilds: fresh state, stepped again from where the voice's clock now
17
+ * puts this subject's start, which under `stepMs` lands where a voice cued fresh would.
18
+ */
19
+ function rebuild(mix, voice, subject, now, held) {
20
+ held.rebuilt = voice.rebuilds;
21
+ const patch = voice.patch;
22
+ if (patch.state === undefined && patch.step === undefined && held.kept === null)
23
+ return;
24
+ held.state = patch.state ? patch.state(subject) : undefined;
25
+ held.kept = null;
26
+ held.unkept = undefined;
27
+ held.since = mix.sinceOf(voice, held.delay);
28
+ held.shown = mix.shownOf(voice, held.since);
29
+ held.ticks = 0;
30
+ held.stepped = held.since < now ? held.since : now;
31
+ held.probed = Number.NaN;
32
+ }
33
+ /** Whether a seek since its last read moved a stateful record's origin, as one back before it does. */
34
+ function sentBack(mix, voice, held) {
35
+ const p = voice.patch;
36
+ if (p.state === undefined && p.step === undefined && !voice.keeping)
37
+ return false;
38
+ return mix.sinceOf(voice, held.delay) !== held.since;
39
+ }
40
+ /** The phase `elapsed` voice ms puts a voice at, before its start included. */
41
+ function phaseOf(elapsed, duration, passes) {
42
+ return elapsed < 0 ? phaseBefore(elapsed, duration) : phaseAt(elapsed, duration, passes);
43
+ }
44
+ /**
45
+ * One live voice's delta for one subject this frame, through its record for the subject, or null
46
+ * when it does not reach. Its weight is left in `w`.
47
+ */
48
+ export function contribution(voice, subject, now, held) {
49
+ held.weight = 0;
50
+ if (!held.reaches)
51
+ return null;
52
+ // Outside its bound the signal reads 0 unread. What a read at weight 0 still does is done there:
53
+ // a patch that steps steps, a `from: 'current'` voice takes its base, and an input is recorded.
54
+ // A read back of recorded inputs reads them, not the bound as it stands now.
55
+ const outside = voice.bound !== null && held.replay === undefined && !inBound(this, voice.bound, subject);
56
+ const recorded = outside && voice.spec.weight.input === true && !!this.opts.history?.inputs;
57
+ if (outside && !recorded && voice.patch.step === undefined && voice.spec.from !== 'current')
58
+ return null;
59
+ if (voice.out?.rest && isRested(held))
60
+ return null;
61
+ if (held.rebuilt !== voice.rebuilds)
62
+ rebuild(this, voice, subject, now, held);
63
+ else if (held.seeks !== voice.seeks && sentBack(this, voice, held)) {
64
+ // A seek sent it back before its start, so it starts afresh there, whatever state it kept.
65
+ rebuild(this, voice, subject, now, held);
66
+ waits(voice, held, now);
67
+ }
68
+ const raw = voice.rawAt(now, held.delay);
69
+ const early = voice.before(raw, now);
70
+ if (early && !voice.holdsFirst)
71
+ return null;
72
+ const elapsed = voice.shownAt(raw);
73
+ if (Number.isNaN(elapsed))
74
+ return null;
75
+ // A frozen subject's clock stands still at the edge it freezes at: -1 before, 1 after, 0 playing.
76
+ const still = early ? -1 : voice.freezesAfter && raw > voice.span ? 1 : 0;
77
+ const duration = voice.duration;
78
+ const pass = passAt(elapsed, duration, voice.passes);
79
+ const primed = this.primed;
80
+ primed[0 /* Primed.NOW */] = now;
81
+ primed[1 /* Primed.ELAPSED */] = elapsed;
82
+ primed[2 /* Primed.PASS */] = pass;
83
+ this.prime(voice, held);
84
+ const setting = voice.setting;
85
+ // A signal that keeps state makes its voice stateful, as a patch that does: a lane would call
86
+ // it for subjects no probe asked about.
87
+ const keptAtWeigh = reading.kept;
88
+ const weight = outside ? 0 : this.weigh(voice, subject, now, held);
89
+ if (recorded)
90
+ record(this, voice, subject, held, 0);
91
+ if (reading.kept !== keptAtWeigh && !voice.keeping)
92
+ stateful(this, voice);
93
+ setting.weight = weight;
94
+ held.weight = weight;
95
+ if (held.probed === now && held.delta && held.seeks === voice.seeks) {
96
+ if (voice.holder !== held && voice.scratch.length > 0)
97
+ this.keyed(voice, subject, held);
98
+ this.w = weight;
99
+ return held.delta;
100
+ }
101
+ const keptBefore = reading.kept;
102
+ const history = this.opts.history;
103
+ if (history?.inputs && voice.patch.reads !== undefined && !this.projecting)
104
+ recordHost(this, now);
105
+ // Only a motion reads it.
106
+ if (voice.motion !== undefined)
107
+ reading.horizon = this.horizonFor(voice, held.delay, now);
108
+ if (voice.patch.step && held.probed !== now) {
109
+ // Frozen after, it steps once more to where its last pass ended, and no further; run back
110
+ // past its start, to where it reached it. Before a start it has yet to reach, it does not step.
111
+ const to = still === 0
112
+ ? now
113
+ : still === 1
114
+ ? voice.timeAt(held.delay + voice.span)
115
+ : voice.ranBackTo(held.delay, now);
116
+ // Never probed, it may be a record a seek put back, sharing its copy's state until it steps.
117
+ const snaps = held.snaps;
118
+ const sought = Number.isNaN(held.probed) && snaps !== undefined;
119
+ if (!Number.isNaN(to)) {
120
+ if (held.stepped < held.since) {
121
+ held.stepped = held.since;
122
+ setting.dt = this.capped(now - held.since);
123
+ }
124
+ const tick = stepOf(this, voice);
125
+ if (tick !== undefined && sought)
126
+ ahead(this, voice, subject, held, snaps, tick, to);
127
+ if (sought)
128
+ own(voice, held);
129
+ if (tick !== undefined)
130
+ this.tick(voice, subject, held, tick, to);
131
+ else if (still === 0 ? now !== held.stepped : to > held.stepped) {
132
+ if (still !== 0)
133
+ setting.dt = this.capped(to - held.stepped);
134
+ voice.patch.step(held.state, setting.dt, subject, setting);
135
+ held.stepped = to;
136
+ }
137
+ }
138
+ else if (sought)
139
+ own(voice, held);
140
+ }
141
+ let delta = null;
142
+ // A keys voice in a role reads at weight 0 too: having a delta makes it a member, and the
143
+ // role folds at its first member's place.
144
+ if (silent(voice, weight) && (voice.built === null || voice.spec.role === undefined)) {
145
+ // A `from: 'current'` voice takes its base on its first read, silent or not.
146
+ if (voice.built && voice.spec.from === 'current' && held.base === undefined) {
147
+ held.base = this.baseFor(voice, subject);
148
+ held.slope = this.slopeFor(voice, subject);
149
+ }
150
+ }
151
+ else if (voice.built) {
152
+ held.phase = phaseOf(elapsed, duration, voice.passes);
153
+ delta = this.keyed(voice, subject, held);
154
+ }
155
+ else if (voice.motion !== undefined) {
156
+ delta = moved(voice.motion, voice.patch.writes[0], subject, setting, held);
157
+ }
158
+ else {
159
+ const phase = phaseOf(elapsed, duration, voice.passes);
160
+ delta = voice.patch.at(phase, subject, setting);
161
+ if (voice.unchecked)
162
+ checkWrites(voice, delta);
163
+ }
164
+ held.delta = delta;
165
+ held.probed = now;
166
+ held.seeks = voice.seeks;
167
+ if (history !== undefined)
168
+ remember(this, voice, subject, held);
169
+ if (reading.kept !== keptBefore && !voice.keeping)
170
+ stateful(this, voice);
171
+ if (delta === null)
172
+ return null;
173
+ if (voice.out?.rest && this.isRest(delta)) {
174
+ held.weight = 0;
175
+ held.restedAt = now;
176
+ voice.restedCount++;
177
+ return null;
178
+ }
179
+ this.w = weight;
180
+ return delta;
181
+ }
182
+ /** Reads a keys voice's stops at `held.phase` into the subject's delta, through the voice's scratch. */
183
+ export function keyed(voice, subject, held) {
184
+ let base;
185
+ if (voice.spec.from === 'current') {
186
+ if (held.base === undefined) {
187
+ held.base = this.baseFor(voice, subject);
188
+ held.slope = this.slopeFor(voice, subject);
189
+ }
190
+ base = held.base;
191
+ }
192
+ const delta = readKeys(voice.built, held.phase, held.delta ?? {}, base, voice.lerps, held.slope, voice.intos, voice.scratch);
193
+ held.delta = delta;
194
+ if (voice.scratch.length > 0)
195
+ voice.holder = held;
196
+ return delta;
197
+ }
198
+ /** How many whole intervals of `tick` a subject's step grid holds by mix time `to`. */
199
+ function dueBy(held, tick, to) {
200
+ // The epsilon keeps an interval like 1000 / 120 from landing a hair short of a whole count.
201
+ return Math.floor((to - held.since) / tick + 1e-9);
202
+ }
203
+ /**
204
+ * The first step of a record a seek put back from a copy: takes up the furthest state stepped on
205
+ * from that copy that the steps due now run past, the one the record the seek replaced reached or
206
+ * the one the copy was last stepped ahead to, and otherwise keeps the copy's stepped ahead to the
207
+ * moment sought, so a mix sought back time after time does not step from the copy each time, nor a
208
+ * mix sought back and returned step again to where it was. Only where the same steps give the same
209
+ * state: a fixed interval, no `maxDt`, no host field read.
210
+ */
211
+ function ahead(mix, voice, subject, held, snaps, tick, to) {
212
+ const from = snaps[snaps.length - 1];
213
+ const got = from?.ahead;
214
+ if (from === undefined || got === undefined || got.put !== held)
215
+ return;
216
+ got.put = null;
217
+ const reads = voice.patch.reads;
218
+ if (mix.opts.maxDt !== undefined || (reads !== undefined && reads.length > 0))
219
+ return;
220
+ // Stepped, rebuilt or placed anew since it was put back, its steps are no longer the copy's.
221
+ const was = from.held;
222
+ if (held.ticks !== was.ticks || held.since !== got.since || held.rebuilt !== was.rebuilt)
223
+ return;
224
+ const at = got.sought;
225
+ const due = dueBy(held, tick, to);
226
+ const usable = got.state !== undefined && got.ticks <= due;
227
+ if (got.left !== undefined && got.leftTicks <= due && !(usable && got.ticks > got.leftTicks)) {
228
+ // Taken, not copied: the next seek back keeps the state this record steps on to in its place.
229
+ held.state = got.left;
230
+ got.left = undefined;
231
+ voice.setting.state = held.state;
232
+ held.ticks = got.leftTicks;
233
+ return;
234
+ }
235
+ if (usable) {
236
+ held.state = copyState(voice, got.state);
237
+ voice.setting.state = held.state;
238
+ held.ticks = got.ticks;
239
+ }
240
+ const last = usable ? got.at : from.at;
241
+ if (!(at < to) || at - last < apart(mix.opts.history))
242
+ return;
243
+ own(voice, held);
244
+ mix.tick(voice, subject, held, tick, at);
245
+ got.state = copyState(voice, held.state);
246
+ got.ticks = held.ticks;
247
+ got.at = at;
248
+ }
249
+ /**
250
+ * A record a seek put back shares the state of the copy it was made from, which no read changes,
251
+ * until its first probe: before it steps, it takes a copy of its own.
252
+ */
253
+ function own(voice, held) {
254
+ const snaps = held.snaps;
255
+ const from = snaps[snaps.length - 1];
256
+ if (from !== undefined && held.state !== undefined && held.state === from.held.state) {
257
+ held.state = copyState(voice, held.state);
258
+ voice.setting.state = held.state;
259
+ }
260
+ }
261
+ /**
262
+ * Runs `step` once per whole interval between where this subject last stopped and now, on a grid
263
+ * counted from when its delay ran out, and leaves the remainder for the next sample. Counting
264
+ * rather than adding keeps the grid where it is however many samples it is reached through.
265
+ */
266
+ export function tick(voice, subject, held, tick, to) {
267
+ const now = this.now;
268
+ const setting = voice.setting;
269
+ const frameDt = setting.dt;
270
+ let n = held.ticks;
271
+ const due = dueBy(held, tick, to);
272
+ const cap = this.opts.maxDt;
273
+ if (cap !== undefined) {
274
+ const most = Math.floor(cap / tick);
275
+ if (due - n > most)
276
+ n = due - most;
277
+ }
278
+ const step = voice.patch.step;
279
+ setting.dt = tick;
280
+ for (n++; n <= due; n++) {
281
+ setting.timestamp = held.since + n * tick;
282
+ step(held.state, tick, subject, setting);
283
+ }
284
+ held.ticks = due > held.ticks ? due : held.ticks;
285
+ held.stepped = held.since + held.ticks * tick;
286
+ setting.timestamp = now;
287
+ setting.dt = frameDt;
288
+ }
289
+ /**
290
+ * Stop 0 for a retargeting voice: the subject's pose in the frame before this voice contributes.
291
+ * With no such frame on record — a voice cued before the subject was ever probed, or a host that
292
+ * samples into its own object — it is this frame's pose with every other voice folded in.
293
+ */
294
+ export function baseFor(voice, subject) {
295
+ let prior = this.lastPoses.get(subject)?.pose;
296
+ if (prior === undefined && !this.folding.has(voice.id)) {
297
+ this.folding.add(voice.id);
298
+ try {
299
+ prior = this.fold(subject, undefined, false, voice.id);
300
+ }
301
+ finally {
302
+ this.folding.delete(voice.id);
303
+ }
304
+ }
305
+ const base = {};
306
+ for (const key of voice.patch.writes) {
307
+ const channel = this.kit[key];
308
+ const value = prior?.[key];
309
+ const fallback = channel.rest;
310
+ if (value !== undefined)
311
+ base[key] = copy(value);
312
+ else if (fallback !== undefined)
313
+ base[key] = copy(fallback);
314
+ }
315
+ return base;
316
+ }
317
+ /**
318
+ * How fast each numeric channel this voice writes was moving, from the subject's last two probed
319
+ * poses, in units per ms. Undefined with fewer than two on record, which starts the voice at rest.
320
+ */
321
+ export function slopeFor(voice, subject) {
322
+ const rec = this.lastPoses.get(subject);
323
+ if (rec?.prev === undefined || !(rec.at > rec.prevAt))
324
+ return undefined;
325
+ const dt = rec.at - rec.prevAt;
326
+ const now = rec.pose;
327
+ const before = rec.prev;
328
+ const slope = {};
329
+ for (const key of voice.patch.writes) {
330
+ const a = before[key];
331
+ const b = now[key];
332
+ if (typeof a === 'number' && typeof b === 'number')
333
+ slope[key] = (b - a) / dt;
334
+ else if (Array.isArray(a) && Array.isArray(b) && a.length === b.length)
335
+ slope[key] = b.map((v, i) => (v - a[i]) / dt);
336
+ }
337
+ return slope;
338
+ }
339
+ export function isRest(delta) {
340
+ for (const key of Object.keys(delta)) {
341
+ if (delta[key] === undefined)
342
+ continue;
343
+ const channel = this.kit[key];
344
+ if (channel.rest === undefined)
345
+ return false;
346
+ if (!near(delta[key], channel.rest))
347
+ return false;
348
+ }
349
+ return true;
350
+ }
351
+ /** `contribution`, or for a dry fold a subject already probed this frame, without advancing it. */
352
+ export function read(voice, subject, now, dry, held) {
353
+ if (dry) {
354
+ if (held.reaches && held.probed === now && held.delta && held.seeks === voice.seeks) {
355
+ const setting = voice.setting;
356
+ // The setting the probe that read the delta had, but for `dt`: a dry read advances nothing.
357
+ const elapsed = voice.shownAt(voice.rawAt(now, held.delay));
358
+ setting.timestamp = now;
359
+ setting.dt = 0;
360
+ setting.elapsed = elapsed;
361
+ setting.pass = passAt(elapsed, voice.duration, voice.passes);
362
+ setting.weight = 0;
363
+ setting.state = held.state;
364
+ setting.mark = 0;
365
+ voice.keepOn = held;
366
+ this.w = this.weigh(voice, subject, now, held);
367
+ held.weight = this.w;
368
+ if (voice.holder !== held && voice.scratch.length > 0)
369
+ this.keyed(voice, subject, held);
370
+ return held.delta;
371
+ }
372
+ }
373
+ return this.contribution(voice, subject, now, held);
374
+ }
package/dist/copyout.d.ts CHANGED
@@ -1,6 +1,16 @@
1
+ import type { Laned } from './lane.js';
1
2
  import type { Lanes } from './lanes.js';
3
+ /** Writes a subject's laned values into a pose. */
4
+ export type Copier = (pose: Record<string, unknown>, slot: number) => void;
2
5
  /**
3
6
  * Writes a subject's laned values into a pose, each array channel into the array the pose holds
4
7
  * for it where that is the length a new one would be, as a host's `out` holds from its last probe.
5
8
  */
6
9
  export declare function copy<I, O>(this: Lanes<I, O>, slot: number, pose: Record<string, unknown>): void;
10
+ /** The array to hold an array channel's values for `slot`: `held` written over where it fits. */
11
+ export declare function filled(ch: Laned, slot: number, held: unknown): number[];
12
+ /**
13
+ * A copier for these channels, written out with each channel's name in place: a store under a name
14
+ * that varies boxes every number it writes. Where `Function` is refused, a loop over them.
15
+ */
16
+ export declare function copier(laned: readonly Laned[]): Copier;
package/dist/copyout.js CHANGED
@@ -3,36 +3,81 @@
3
3
  * for it where that is the length a new one would be, as a host's `out` holds from its last probe.
4
4
  */
5
5
  export function copy(slot, pose) {
6
- const laned = this.laned;
6
+ if (this.copier === null)
7
+ this.copier = copier(this.laned);
8
+ this.copier(pose, slot);
9
+ }
10
+ /** The array to hold an array channel's values for `slot`: `held` written over where it fits. */
11
+ export function filled(ch, slot, held) {
12
+ // Made as the general path makes it, a copy of rest, so poses from either path share a shape.
13
+ const start = ch.start;
14
+ const axes = ch.axes;
15
+ const base = slot * axes;
16
+ if (Array.isArray(held) &&
17
+ held !== start &&
18
+ held.length === (start.length > axes ? start.length : axes)) {
19
+ for (let a = 0; a < axes; a++)
20
+ held[a] = ch.values[base + a];
21
+ for (let a = axes; a < start.length; a++)
22
+ held[a] = start[a];
23
+ return held;
24
+ }
25
+ const arr = [...start];
26
+ for (let a = 0; a < axes; a++)
27
+ arr[a] = ch.values[base + a];
28
+ return arr;
29
+ }
30
+ /** Each set of laned channels' copier, made once from its source and bound to the channels. */
31
+ const made = new Map();
32
+ /** `Function` refused once: under a CSP each refusal fires a violation, so it is not asked again. */
33
+ let refused = false;
34
+ /**
35
+ * A copier for these channels, written out with each channel's name in place: a store under a name
36
+ * that varies boxes every number it writes. Where `Function` is refused, a loop over them.
37
+ */
38
+ export function copier(laned) {
39
+ const lines = [];
7
40
  for (let c = 0; c < laned.length; c++) {
8
41
  const ch = laned[c];
9
- const axes = ch.axes;
42
+ const key = `o[${JSON.stringify(ch.name)}]`;
43
+ const at = ch.scalar ? 's' : `s * ${ch.axes}`;
10
44
  // Never `delete`: it drops a reused out object into dictionary mode for good.
11
- if (ch.op === 'last' && Number.isNaN(ch.values[slot * axes])) {
45
+ const absent = `if (${key} !== undefined) ${key} = undefined;`;
46
+ const write = ch.scalar
47
+ ? `${key} = v;`
48
+ : `{ const h = ${key}, a = f(c${c}, s, h); if (a !== h) ${key} = a; }`;
49
+ lines.push(`{ const v = c${c}.values[${at}];`);
50
+ lines.push(ch.op === 'last' ? `if (v !== v) { ${absent} } else ${write} }` : `${write} }`);
51
+ }
52
+ const body = lines.join('\n');
53
+ let make = made.get(body);
54
+ if (make === undefined && !refused) {
55
+ const bind = laned.map((_, c) => `const c${c} = chans[${c}];`).join(' ');
56
+ try {
57
+ make = new Function('chans', 'f', `${bind}\nreturn function copyLaned(o, s) {\n${body}\n};`);
58
+ made.set(body, make);
59
+ }
60
+ catch {
61
+ refused = true;
62
+ }
63
+ }
64
+ return make === undefined ? (pose, slot) => loop(laned, pose, slot) : make(laned, filled);
65
+ }
66
+ function loop(laned, pose, slot) {
67
+ for (let c = 0; c < laned.length; c++) {
68
+ const ch = laned[c];
69
+ const v = ch.values[ch.scalar ? slot : slot * ch.axes];
70
+ if (ch.op === 'last' && Number.isNaN(v)) {
12
71
  if (pose[ch.name] !== undefined)
13
72
  pose[ch.name] = undefined;
14
- continue;
15
- }
16
- if (ch.scalar) {
17
- pose[ch.name] = ch.values[slot];
18
- continue;
19
73
  }
20
- // Made as the general path makes it, a copy of rest, so poses from either path share a shape.
21
- const start = ch.start;
22
- const base = slot * axes;
23
- const held = pose[ch.name];
24
- if (Array.isArray(held) &&
25
- held !== start &&
26
- held.length === (start.length > axes ? start.length : axes)) {
27
- for (let a = 0; a < axes; a++)
28
- held[a] = ch.values[base + a];
29
- for (let a = axes; a < start.length; a++)
30
- held[a] = start[a];
31
- continue;
74
+ else if (ch.scalar)
75
+ pose[ch.name] = v;
76
+ else {
77
+ const held = pose[ch.name];
78
+ const arr = filled(ch, slot, held);
79
+ if (arr !== held)
80
+ pose[ch.name] = arr;
32
81
  }
33
- const arr = [...start];
34
- for (let a = 0; a < axes; a++)
35
- arr[a] = ch.values[base + a];
36
- pose[ch.name] = arr;
37
82
  }
38
83
  }
package/dist/crowd.d.ts CHANGED
@@ -4,7 +4,8 @@ import { type Laned, type Positions } from './lane.js';
4
4
  import type { Lanes } from './lanes.js';
5
5
  import type { Motions } from './motions.js';
6
6
  import type { Scratch } from './patch.js';
7
- import type { Subject, Voice } from './voice.js';
7
+ import type { Subject } from './subject.js';
8
+ import type { Voice } from './voice.js';
8
9
  /**
9
10
  * Whether a laned voice belongs in a crowd: one naming a single subject, which shares nothing
10
11
  * across subjects for a lane to save. A motion voice writes one channel; a keys or fn voice may
@@ -52,7 +53,12 @@ export declare const enum Flag {
52
53
  /** A keys row whose stops `keys` holds, at `Hot.AT`, with its duration at `Hot.X0` and passes at `Hot.MS`. */
53
54
  FLAT = 512,
54
55
  /** The row's patch is a tween. */
55
- TWEEN = 1024
56
+ TWEEN = 1024,
57
+ /**
58
+ * `bareRows` found its stretch numbered, its subject met and its sample `Sampled.BARE`; cleared
59
+ * by `unbare` and `restale`, through which everything that undoes one of those goes.
60
+ */
61
+ READY = 2048
56
62
  }
57
63
  /** Whether a row's flags let `bareRows` take it. */
58
64
  export declare function bare(f: number): boolean;
package/dist/crowd.js CHANGED
@@ -2,7 +2,7 @@ import { bareRows, clock, unbare } from './bare.js';
2
2
  import { clampWeight, passAt, phaseAt, weighed } from './clock.js';
3
3
  import { closed } from './closed.js';
4
4
  import { KeyRows } from './keyrows.js';
5
- import { frozenAt, SPARSE } from './lane.js';
5
+ import { frozenAt, keepProbed, SPARSE } from './lane.js';
6
6
  import { move } from './sample.js';
7
7
  /**
8
8
  * Whether a laned voice belongs in a crowd: one naming a single subject, which shares nothing
@@ -12,7 +12,7 @@ import { move } from './sample.js';
12
12
  export function crowdable(v) {
13
13
  return (v.named !== null &&
14
14
  v.named.size === 1 &&
15
- v.spec.locus === undefined &&
15
+ v.spec.role === undefined &&
16
16
  (v.slots.length === 1 || v.motion === undefined));
17
17
  }
18
18
  /** The law of a row that has none, a keys or fn voice's. */
@@ -82,7 +82,7 @@ export class Crowd {
82
82
  numbered() { }
83
83
  /** Marks row `p` to copy its stretch from its patch again before the next fill reads it. */
84
84
  restale(p, flags) {
85
- this.hot[p * this.stride + 0 /* Hot.FLAGS */] = flags | 4 /* Flag.STALE */;
85
+ this.hot[p * this.stride + 0 /* Hot.FLAGS */] = (flags | 4 /* Flag.STALE */) & ~2048 /* Flag.READY */;
86
86
  this.stale = true;
87
87
  this.staleRows.push(p);
88
88
  }
@@ -210,8 +210,7 @@ function rows(lanes, c, first, id) {
210
210
  const slot = list[p];
211
211
  const o = p * 8 /* Row.STRIDE */;
212
212
  const q = slot * 6 /* Per.SLOT */;
213
- if (per[q + 5 /* Per.LANE_FILL */] === fills - 1)
214
- data[o + 3 /* Row.PROBED */] = data[o + 2 /* Row.WEIGHT */];
213
+ keepProbed(data, o, per, q, fills);
215
214
  if (lanes.lately >= 0 && lanes.unread(slot))
216
215
  continue;
217
216
  const delay = data[o + 0 /* Row.DELAY */];
package/dist/cue.d.ts CHANGED
@@ -1,7 +1,14 @@
1
1
  import type { Mixer } from './mixer.js';
2
- import type { Handle, Kit, Patch, VoiceSpec } from './types.js';
2
+ import type { Handle, Kit, MixOptions, Patch, VoiceSpec } from './types.js';
3
3
  export declare function cue<I, O>(mix: Mixer<I, O>, given: VoiceSpec<I, O>): Handle<I>;
4
4
  /** Refuses a patch writing a channel `kit` lacks, or holds as another kind than it was written for. */
5
5
  export declare function fitsKit<I, O, S, H>(patch: Patch<I, O, S, H>, kit: Kit<O>): void;
6
+ /**
7
+ * The spec with its `role` set from `locus`, the name `role` had before, or from a tag the mix
8
+ * declares a role, where it carries one.
9
+ */
10
+ export declare function roleOf<I, O>(mix: Mixer<I, O>, given: VoiceSpec<I, O>): VoiceSpec<I, O>;
11
+ /** The tags `opts` declares roles, from `roles` or `loci`, the name `roles` had before. */
12
+ export declare function roleTags(opts: MixOptions): readonly string[] | undefined;
6
13
  /** Refuses a spec whose patch the mix cannot play, or whose state its history cannot keep. */
7
14
  export declare function playable<I, O>(mix: Mixer<I, O>, spec: VoiceSpec<I, O>): void;