@msb235/blits 0.3.0 → 0.5.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 (102) hide show
  1. package/CHANGELOG.md +314 -0
  2. package/README.md +3 -2
  3. package/dist/bare.d.ts +29 -0
  4. package/dist/bare.js +105 -0
  5. package/dist/blend.d.ts +34 -0
  6. package/dist/blend.js +102 -0
  7. package/dist/book.d.ts +53 -0
  8. package/dist/book.js +218 -0
  9. package/dist/chain.d.ts +22 -0
  10. package/dist/chain.js +150 -0
  11. package/dist/channels.d.ts +2 -0
  12. package/dist/channels.js +3 -1
  13. package/dist/clock.d.ts +50 -3
  14. package/dist/clock.js +102 -9
  15. package/dist/clone.d.ts +7 -0
  16. package/dist/clone.js +57 -0
  17. package/dist/columns.d.ts +29 -0
  18. package/dist/columns.js +183 -0
  19. package/dist/crowd.d.ts +128 -0
  20. package/dist/crowd.js +354 -0
  21. package/dist/cue.d.ts +3 -0
  22. package/dist/cue.js +115 -0
  23. package/dist/due.d.ts +12 -0
  24. package/dist/due.js +93 -0
  25. package/dist/fade.d.ts +19 -0
  26. package/dist/fade.js +133 -0
  27. package/dist/fill.d.ts +42 -0
  28. package/dist/fill.js +381 -0
  29. package/dist/fold.d.ts +51 -0
  30. package/dist/fold.js +475 -0
  31. package/dist/gather.d.ts +24 -0
  32. package/dist/gather.js +186 -0
  33. package/dist/handle.d.ts +31 -0
  34. package/dist/handle.js +56 -0
  35. package/dist/held.d.ts +14 -0
  36. package/dist/held.js +117 -0
  37. package/dist/history.d.ts +22 -0
  38. package/dist/history.js +149 -0
  39. package/dist/hosts.d.ts +16 -0
  40. package/dist/hosts.js +178 -0
  41. package/dist/index.d.ts +1 -1
  42. package/dist/keyrows.d.ts +36 -0
  43. package/dist/keyrows.js +138 -0
  44. package/dist/lane.d.ts +210 -0
  45. package/dist/lane.js +187 -0
  46. package/dist/lanes.d.ts +143 -122
  47. package/dist/lanes.js +236 -742
  48. package/dist/locus.d.ts +40 -0
  49. package/dist/locus.js +89 -0
  50. package/dist/marks.d.ts +12 -0
  51. package/dist/marks.js +80 -0
  52. package/dist/meet.d.ts +16 -0
  53. package/dist/meet.js +113 -0
  54. package/dist/mixer.d.ts +244 -169
  55. package/dist/mixer.js +321 -1793
  56. package/dist/motion.d.ts +104 -19
  57. package/dist/motion.js +448 -180
  58. package/dist/move.d.ts +9 -0
  59. package/dist/move.js +190 -0
  60. package/dist/named.d.ts +12 -0
  61. package/dist/named.js +39 -0
  62. package/dist/numbers.d.ts +18 -6
  63. package/dist/numbers.js +59 -31
  64. package/dist/owed.d.ts +24 -0
  65. package/dist/owed.js +51 -0
  66. package/dist/owner.d.ts +43 -0
  67. package/dist/owner.js +117 -0
  68. package/dist/pace.d.ts +40 -0
  69. package/dist/pace.js +113 -0
  70. package/dist/patch.d.ts +51 -10
  71. package/dist/patch.js +140 -45
  72. package/dist/place.d.ts +20 -0
  73. package/dist/place.js +196 -0
  74. package/dist/project.d.ts +4 -0
  75. package/dist/project.js +233 -0
  76. package/dist/pull.d.ts +5 -0
  77. package/dist/pull.js +175 -0
  78. package/dist/qualify.d.ts +31 -0
  79. package/dist/qualify.js +345 -0
  80. package/dist/relink.d.ts +25 -0
  81. package/dist/relink.js +59 -0
  82. package/dist/rows.d.ts +33 -0
  83. package/dist/rows.js +282 -0
  84. package/dist/sample.d.ts +21 -0
  85. package/dist/sample.js +232 -0
  86. package/dist/signals.d.ts +12 -9
  87. package/dist/signals.js +8 -5
  88. package/dist/store.d.ts +10 -3
  89. package/dist/store.js +44 -9
  90. package/dist/strays.d.ts +21 -0
  91. package/dist/strays.js +84 -0
  92. package/dist/tweened.d.ts +11 -0
  93. package/dist/tweened.js +48 -0
  94. package/dist/types.d.ts +261 -42
  95. package/dist/unreached.d.ts +5 -0
  96. package/dist/unreached.js +24 -0
  97. package/dist/voice.d.ts +240 -0
  98. package/dist/voice.js +322 -0
  99. package/dist/weigh.d.ts +26 -0
  100. package/dist/weigh.js +89 -0
  101. package/docs/schema.html +151 -46
  102. package/package.json +5 -2
package/dist/fade.js ADDED
@@ -0,0 +1,133 @@
1
+ import { changed } from './chain.js';
2
+ import { envelope } from './clock.js';
3
+ import { schedule } from './due.js';
4
+ import { noted } from './history.js';
5
+ import { orphan } from './owner.js';
6
+ import { startOf } from './place.js';
7
+ import { unreach } from './unreached.js';
8
+ /** Starts one subject's ramp out of a voice, or takes it out at once for a ramp of 0. */
9
+ export function fadeSubject(mix, voice, subject, over) {
10
+ if (voice.state === 'done' || voice.parted?.has(subject) || voice.parts?.has(subject))
11
+ return;
12
+ mix.stir();
13
+ const ms = mix.reduced ? 0 : (over ?? voice.fade.out ?? 0);
14
+ const at = Number.isNaN(mix.now) ? startOf(voice) : mix.now;
15
+ if (ms === 0) {
16
+ part(mix, voice, subject, at);
17
+ return;
18
+ }
19
+ voice.parts ??= new Map();
20
+ voice.parts.set(subject, { at, over: ms });
21
+ mix.parters.add(voice);
22
+ schedule(mix, voice);
23
+ mix.lanes?.refill();
24
+ }
25
+ /** What a subject's own ramp out of a voice leaves of its weight this frame, 0..1. */
26
+ export function parting(voice, subject, now) {
27
+ const r = voice.parts?.get(subject);
28
+ if (r === undefined)
29
+ return 1;
30
+ return envelope(0, { at: r.at, over: r.over, rest: false }, voice.ease, this.reducedNow, now, 0);
31
+ }
32
+ /**
33
+ * Takes a subject out of one voice for good, as of mix time `at`: the voice forgets its record,
34
+ * its lane position and a motion patch's state for it, and reaches it no more.
35
+ */
36
+ export function part(mix, voice, subject, at) {
37
+ voice.parts?.delete(subject);
38
+ if (voice.parts?.size === 0)
39
+ voice.parts = null;
40
+ voice.parted ??= new Map();
41
+ voice.parted.set(subject, at);
42
+ mix.parters.add(voice);
43
+ forgetIn(mix, voice, subject);
44
+ const motion = voice.motion;
45
+ if (motion !== undefined && asks(mix, motion, voice))
46
+ motion.release(subject);
47
+ }
48
+ /** Brings a subject faded out of a voice back, to be met afresh on its next probe. */
49
+ export function unpart(mix, voice, subject) {
50
+ if (voice.parted?.delete(subject) !== true)
51
+ return;
52
+ if (voice.parted.size === 0)
53
+ voice.parted = null;
54
+ forgetIn(mix, voice, subject);
55
+ const slot = mix.chains.get(subject)?.slot ?? -1;
56
+ if (slot >= 0)
57
+ mix.lanes?.rejoin(slot);
58
+ }
59
+ /** Drops a voice's record of a subject and relinks the subject's chain without it. */
60
+ function forgetIn(mix, voice, subject) {
61
+ const head = mix.chains.get(subject);
62
+ if (head !== undefined && head.slot >= 0)
63
+ unreach(voice, head.slot, false);
64
+ const held = voice.subjects.get(subject);
65
+ if (held !== undefined) {
66
+ voice.subjects.delete(subject);
67
+ if (held.reaches)
68
+ voice.seen--;
69
+ if (held.rested)
70
+ voice.restedCount--;
71
+ if (voice.holder === held)
72
+ voice.holder = null;
73
+ }
74
+ if (head === undefined)
75
+ return;
76
+ head.version = Number.NaN;
77
+ mix.relinks++;
78
+ if (voice.laned && head.slot >= 0)
79
+ mix.lanes?.part(voice.id, head.slot);
80
+ mix.lanes?.refill();
81
+ }
82
+ export function beginFade(mix, voice, opts, at) {
83
+ if (voice.state === 'done' || voice.state === 'fading')
84
+ return;
85
+ const over = mix.reduced ? 0 : (opts.over ?? voice.fade.out ?? 0);
86
+ // A voice is linked into its subjects' chains once it plays, and a fading one plays.
87
+ if (voice.state === 'pending')
88
+ changed(mix, voice);
89
+ voice.state = 'fading';
90
+ voice.out = {
91
+ from: 1,
92
+ at: at ?? (Number.isNaN(mix.now) ? startOf(voice) : mix.now),
93
+ over,
94
+ rest: opts.at === 'rest',
95
+ deadline: opts.deadline,
96
+ };
97
+ noted(mix, voice);
98
+ if (opts.at === 'rest')
99
+ mix.lanes?.invalidate();
100
+ else
101
+ mix.lanes?.refill();
102
+ if (over === 0 && opts.at !== 'rest')
103
+ retire(mix, voice, voice.out.at);
104
+ }
105
+ /** Removes a voice, recording that it left at `at`, default now. */
106
+ /** Whether a motion patch still asks this voice of this mix for its time. */
107
+ function asks(mix, motion, voice) {
108
+ return motion.owner === mix.owner && motion.ownerId === voice.id;
109
+ }
110
+ export function retire(mix, voice, at) {
111
+ if (voice.holding === null)
112
+ mix.lanes?.touch(voice);
113
+ const motion = voice.motion;
114
+ if (motion !== undefined && asks(mix, motion, voice))
115
+ motion.owner = null;
116
+ voice.state = 'done';
117
+ // The record its setting last wrote to, which a retired voice no longer calls for.
118
+ voice.keepOn = null;
119
+ mix.retired.push(voice);
120
+ voice.doneAt = at ?? (Number.isNaN(mix.now) ? Number.NEGATIVE_INFINITY : mix.now);
121
+ voice.play(false);
122
+ voice.resolve();
123
+ // An owner takes what it holds with it, and leaves with its last child.
124
+ if (voice.holding !== null)
125
+ for (const child of [...voice.holding.children])
126
+ if (child.state !== 'done')
127
+ retire(mix, child, voice.doneAt);
128
+ if (voice.owner !== null) {
129
+ const owner = orphan(voice);
130
+ if (owner !== null)
131
+ retire(mix, owner, voice.doneAt);
132
+ }
133
+ }
package/dist/fill.d.ts ADDED
@@ -0,0 +1,42 @@
1
+ import { type Lane, type Laned, type Paced } from './lane.js';
2
+ import type { Lanes } from './lanes.js';
3
+ import { absent } from './numbers.js';
4
+ import { type Scratch } from './patch.js';
5
+ import type { Subject, Voice } from './voice.js';
6
+ export declare function fillAll<I, O>(lanes: Lanes<I, O>, now: number, version: number): void;
7
+ /** Leaves a lane's subjects to the general path until it wakes, keeping what `weightOf` reports. */
8
+ export declare function rest<I, O>(lanes: Lanes<I, O>, lane: Paced): void;
9
+ export declare function wake<I, O>(lanes: Lanes<I, O>, lane: Paced): void;
10
+ /**
11
+ * A voice with a signal weight: its weight for the subject at `slot` under the voice's fade `fade`,
12
+ * through the general path's own arithmetic; NaN where that path has not made the voice's first
13
+ * call for the subject, which it then makes this frame, so a signal that keeps state is first
14
+ * called where the general path would call it.
15
+ */
16
+ export declare function signalled<I, O>(this: Lanes<I, O>, voice: Voice<I, O>, slot: number, rec: Subject<unknown>, elapsed: number, pass: number, fade: number): number;
17
+ /** What a subject's own ramp out of a voice leaves of its weight: 1 with none. */
18
+ export declare function parting<I, O>(this: Lanes<I, O>, voice: Voice<I, O>, slot: number): number;
19
+ export declare function subjectAt<I, O>(this: Lanes<I, O>, slot: number): I | typeof absent;
20
+ /** What a lane remembers within one fill, cleared before the fill runs it. */
21
+ export declare function reset<I, O>(this: Lanes<I, O>, lane: Lane<I, O>): void;
22
+ /** One voice's contribution to every subject it plays on. */
23
+ export declare function run<I, O>(this: Lanes<I, O>, lane: Lane<I, O>): void;
24
+ /**
25
+ * One subject's contribution from a lane's voice. True where the voice gave the subject a delta,
26
+ * as the general path's `influence` does where it returns one, which is what makes a voice in a
27
+ * locus one of its members for the subject.
28
+ */
29
+ export declare function one<I, O>(this: Lanes<I, O>, lane: Lane<I, O>, p: number, slot: number, elapsedNow: number, duration: number, passes: number): boolean;
30
+ /**
31
+ * A keys voice's stops at `phase` folded straight into a subject's values, without a delta: what
32
+ * `readKeyed` then `foldDelta` give, through the same segment search and the stock channels' own
33
+ * lerp, which is all a laned keys voice can use.
34
+ */
35
+ export declare function foldKeys<I, O>(this: Lanes<I, O>, voice: Voice<I, O>, chans: readonly Laned[], phase: number, slot: number, w: number): void;
36
+ /** Reads a keys voice's stops at `phase` into `delta`, interpolating into the reader's `scratch`. */
37
+ export declare function readKeyed<I, O>(this: Lanes<I, O>, voice: Voice<I, O>, phase: number, delta: Record<string, unknown>, scratch: Scratch): void;
38
+ /**
39
+ * Calls a stateless fn voice's patch for a subject its general path has met, and folds the delta;
40
+ * false for a subject the host has let go of.
41
+ */
42
+ export declare function call<I, O>(this: Lanes<I, O>, voice: Voice<I, O>, chans: readonly Laned[], rec: Subject<unknown>, slot: number, elapsed: number, phase: number, pass: number, delay: number, w: number): boolean;
package/dist/fill.js ADDED
@@ -0,0 +1,381 @@
1
+ import { foldNumber, lerpNumber } from './channels.js';
2
+ import { passAt, phaseAt, silent, weighed } from './clock.js';
3
+ import { flush } from './columns.js';
4
+ import { crowdsUpTo, freshen } from './crowd.js';
5
+ import { foldLocus, gatherLocus } from './gather.js';
6
+ import { held } from './lane.js';
7
+ import { reach } from './meet.js';
8
+ import { absent } from './numbers.js';
9
+ import { AT, NOTHING, readKeys, seg, segment, shifted } from './patch.js';
10
+ import { reading } from './reading.js';
11
+ import { move, runMotion } from './sample.js';
12
+ /** The least prime at least `n`, and 1 below 2. */
13
+ function primeFrom(n) {
14
+ if (n < 2)
15
+ return 1;
16
+ for (let c = n;; c++) {
17
+ let prime = true;
18
+ for (let d = 2; d * d <= c; d++)
19
+ if (c % d === 0) {
20
+ prime = false;
21
+ break;
22
+ }
23
+ if (prime)
24
+ return c;
25
+ }
26
+ }
27
+ export function fillAll(lanes, now, version) {
28
+ flush(lanes);
29
+ const host = lanes.host;
30
+ lanes.filling = true;
31
+ try {
32
+ lanes.fills++;
33
+ lanes.owed.begin(lanes.fills);
34
+ lanes.now = now;
35
+ lanes.filledAt = now;
36
+ lanes.filledVersion = version;
37
+ const size = lanes.numbers.size;
38
+ lanes.keeps = host.keeps;
39
+ const decide = lanes.fresh;
40
+ lanes.fresh = false;
41
+ const share = lanes.live > 0 ? lanes.lastDistinct / lanes.live : 1;
42
+ // Deciding which lanes and crowds go idle first, then running them, in voice order: a crowd's
43
+ // rows are folded between the lanes on either side of their voices, as the general path
44
+ // folds every voice in order.
45
+ let busy = false;
46
+ for (const lane of lanes.lanes) {
47
+ if (decide) {
48
+ // A lane with few subjects, such as one naming its own, goes by the share of all probed;
49
+ // one with a single subject shares nothing a fill could save, as a solo lane does not.
50
+ const line = lane.line;
51
+ const n = lane.list.length;
52
+ if (n >= 64)
53
+ pace(lanes, lane);
54
+ else if (n === 1 && lane.motion === undefined) {
55
+ if (!lane.idle)
56
+ rest(lanes, lane);
57
+ }
58
+ else if (lane.idle ? share >= line.start : share < line.stop)
59
+ flip(lanes, lane);
60
+ }
61
+ if (!lane.idle)
62
+ busy = true;
63
+ }
64
+ for (const c of lanes.crowds) {
65
+ if (decide) {
66
+ if (c.size >= 64)
67
+ pace(lanes, c);
68
+ else if (c.idle ? share >= c.line.start : share < c.line.stop)
69
+ flip(lanes, c);
70
+ }
71
+ c.cursor = 0;
72
+ if (c.stale)
73
+ freshen(c);
74
+ if (!c.idle && c.size > 0)
75
+ busy = true;
76
+ }
77
+ if (busy) {
78
+ for (const ch of lanes.laned)
79
+ ch.values.fill(ch.rest, 0, size * ch.axes);
80
+ for (const g of lanes.loci)
81
+ gatherLocus(lanes, g);
82
+ for (const lane of lanes.lanes) {
83
+ if (lane.idle)
84
+ continue;
85
+ if (lanes.crowds.length > 0)
86
+ crowdsUpTo(lanes, lane.voice.id);
87
+ lanes.run(lane);
88
+ }
89
+ if (lanes.crowds.length > 0)
90
+ crowdsUpTo(lanes, Number.POSITIVE_INFINITY);
91
+ for (const c of lanes.crowds)
92
+ if (!c.idle) {
93
+ c.bareFill = lanes.fills;
94
+ c.bareNow = now;
95
+ }
96
+ }
97
+ // With every lane idle there was nothing to fill, and no subject reads a lane this frame.
98
+ if (busy)
99
+ for (let s = 0; s < size; s++)
100
+ lanes.per[s * 6 /* Per.SLOT */ + 0 /* Per.FILLED */] = lanes.fills;
101
+ for (const slot of lanes.late)
102
+ lanes.per[slot * 6 /* Per.SLOT */ + 0 /* Per.FILLED */] = 0;
103
+ if (lanes.holding) {
104
+ lanes.subjects.fill(undefined, 0, size);
105
+ lanes.holding = false;
106
+ }
107
+ lanes.moved = reading.moved;
108
+ }
109
+ finally {
110
+ lanes.filling = false;
111
+ lanes.late.length = 0;
112
+ }
113
+ }
114
+ /**
115
+ * Sets a lane idle or busy by the share of its subjects probed in the last frame that had a probe,
116
+ * read from about 256 of them a prime stride apart, from an offset that moves each frame, so a host
117
+ * probing every second or third subject is not read as probing all or none. Both paths give the
118
+ * same values, so this only picks the cheaper.
119
+ */
120
+ function pace(lanes, lane) {
121
+ const list = lane.list;
122
+ const line = lane.line;
123
+ const from = lanes.lastFrom;
124
+ const to = lanes.lastTo;
125
+ const step = primeFrom(Math.floor(list.length / 256));
126
+ let looked = 0;
127
+ let probed = 0;
128
+ for (let p = lanes.fills % step; p < list.length; p += step) {
129
+ const slot = list[p];
130
+ if (slot < 0)
131
+ continue;
132
+ const a = lanes.per[slot * 6 /* Per.SLOT */ + 3 /* Per.LANE_PROBE */];
133
+ const b = lanes.per[slot * 6 /* Per.SLOT */ + 4 /* Per.GENERAL_PROBE */];
134
+ if ((a > from && a <= to) || (b > from && b <= to))
135
+ probed++;
136
+ looked++;
137
+ }
138
+ const share = looked > 0 ? probed / looked : 1;
139
+ if (lane.idle ? share >= line.start : share < line.stop)
140
+ flip(lanes, lane);
141
+ }
142
+ function flip(lanes, lane) {
143
+ if (lane.idle)
144
+ wake(lanes, lane);
145
+ else
146
+ rest(lanes, lane);
147
+ }
148
+ /** Leaves a lane's subjects to the general path until it wakes, keeping what `weightOf` reports. */
149
+ export function rest(lanes, lane) {
150
+ lane.idle = true;
151
+ const list = lane.list;
152
+ const data = lane.data;
153
+ for (let p = 0; p < list.length; p++) {
154
+ const slot = list[p];
155
+ if (slot < 0)
156
+ continue;
157
+ reach(lanes, slot, 1);
158
+ // A probe read this subject from the last fill, which this lane's weight stays from.
159
+ if (lanes.per[slot * 6 /* Per.SLOT */ + 5 /* Per.LANE_FILL */] === lanes.fills - 1) {
160
+ const o = p * 8 /* Row.STRIDE */;
161
+ data[o + 3 /* Row.PROBED */] = data[o + 2 /* Row.WEIGHT */];
162
+ }
163
+ }
164
+ }
165
+ export function wake(lanes, lane) {
166
+ lane.idle = false;
167
+ for (const slot of lane.list)
168
+ if (slot >= 0)
169
+ reach(lanes, slot, -1);
170
+ }
171
+ /**
172
+ * A voice with a signal weight: its weight for the subject at `slot` under the voice's fade `fade`,
173
+ * through the general path's own arithmetic; NaN where that path has not made the voice's first
174
+ * call for the subject, which it then makes this frame, so a signal that keeps state is first
175
+ * called where the general path would call it.
176
+ */
177
+ export function signalled(voice, slot, rec, elapsed, pass, fade) {
178
+ if (Number.isNaN(rec.probed)) {
179
+ this.late.push(slot);
180
+ return Number.NaN;
181
+ }
182
+ const subject = this.subjectAt(slot);
183
+ if (subject === absent)
184
+ return 0;
185
+ const base = this.host.signal(voice, subject, rec, elapsed, pass, slot);
186
+ return weighed(base, fade, voice.parts === null ? 1 : this.host.parting(voice, subject));
187
+ }
188
+ /** What a subject's own ramp out of a voice leaves of its weight: 1 with none. */
189
+ export function parting(voice, slot) {
190
+ if (voice.parts === null)
191
+ return 1;
192
+ const subject = this.subjectAt(slot);
193
+ return subject === absent ? 1 : this.host.parting(voice, subject);
194
+ }
195
+ export function subjectAt(slot) {
196
+ let subject = this.subjects[slot];
197
+ if (subject === undefined) {
198
+ subject = this.numbers.subject(slot);
199
+ this.subjects[slot] = subject;
200
+ this.holding = true;
201
+ }
202
+ return subject;
203
+ }
204
+ /** What a lane remembers within one fill, cleared before the fill runs it. */
205
+ export function reset(lane) {
206
+ const voice = lane.voice;
207
+ lane.placed = false;
208
+ lane.read = false;
209
+ lane.weighed = false;
210
+ // With no fade in or out the envelope is 1 for every subject, which is what it would return.
211
+ lane.flat = !((voice.fade.in ?? 0) > 0) && voice.out === null && voice.owner === null;
212
+ lane.fade = 1;
213
+ }
214
+ /** One voice's contribution to every subject it plays on. */
215
+ export function run(lane) {
216
+ const voice = lane.voice;
217
+ if (voice.state === 'pending' || voice.state === 'done')
218
+ return;
219
+ if (lane.group !== null) {
220
+ foldLocus(this, lane, lane.group);
221
+ return;
222
+ }
223
+ this.reset(lane);
224
+ if (lane.motion !== undefined && !voice.keeping) {
225
+ runMotion(this, lane, lane.motion);
226
+ return;
227
+ }
228
+ const elapsed = voice.elapsedAt(this.now);
229
+ const duration = voice.duration;
230
+ const passes = voice.passes;
231
+ const list = lane.list;
232
+ for (let p = 0; p < list.length; p++) {
233
+ this.one(lane, p, list[p], elapsed, duration, passes);
234
+ // Its patch just made kept state: no further call this fill, the general path makes them.
235
+ if (voice.keeping)
236
+ return;
237
+ }
238
+ }
239
+ /**
240
+ * One subject's contribution from a lane's voice. True where the voice gave the subject a delta,
241
+ * as the general path's `influence` does where it returns one, which is what makes a voice in a
242
+ * locus one of its members for the subject.
243
+ */
244
+ export function one(lane, p, slot, elapsedNow, duration, passes) {
245
+ const host = this.host;
246
+ const voice = lane.voice;
247
+ const data = lane.data;
248
+ const o = p * 8 /* Row.STRIDE */;
249
+ // A probe read this subject from the last fill: keep the weight it read for `weightOf`.
250
+ if (this.per[slot * 6 /* Per.SLOT */ + 5 /* Per.LANE_FILL */] === this.fills - 1)
251
+ data[o + 3 /* Row.PROBED */] = data[o + 2 /* Row.WEIGHT */];
252
+ const delay = data[o + 0 /* Row.DELAY */];
253
+ const elapsed = held(voice, elapsedNow - delay);
254
+ if (!(elapsed >= 0)) {
255
+ data[o + 2 /* Row.WEIGHT */] = 0;
256
+ return false;
257
+ }
258
+ if (!lane.placed || !Object.is(elapsed, lane.placedAt)) {
259
+ lane.placed = true;
260
+ lane.placedAt = elapsed;
261
+ lane.phase = phaseAt(elapsed, duration, passes);
262
+ lane.pass = passAt(elapsed, duration, passes);
263
+ }
264
+ const since = data[o + 1 /* Row.SINCE */];
265
+ if (!lane.flat && (!lane.weighed || !Object.is(since, lane.weighedSince))) {
266
+ lane.fade = host.envelope(voice, since);
267
+ lane.weighed = true;
268
+ lane.weighedSince = since;
269
+ }
270
+ let w;
271
+ if (typeof voice.spec.weight === 'function') {
272
+ w = this.signalled(voice, slot, lane.records[p], elapsed, lane.pass, lane.fade);
273
+ if (Number.isNaN(w)) {
274
+ data[o + 2 /* Row.WEIGHT */] = 0;
275
+ return false;
276
+ }
277
+ }
278
+ else
279
+ w = weighed(voice.weight, lane.fade, this.parting(voice, slot));
280
+ data[o + 2 /* Row.WEIGHT */] = w;
281
+ if (voice.built !== null) {
282
+ if (!lane.read || !Object.is(elapsed, lane.readAt)) {
283
+ this.readKeyed(voice, lane.phase, lane.delta, lane.scratch);
284
+ lane.read = true;
285
+ lane.readAt = elapsed;
286
+ this.gather(lane, lane.delta);
287
+ }
288
+ if (w > 0)
289
+ this.fold(lane, slot, w);
290
+ return true;
291
+ }
292
+ const rec = lane.records[p];
293
+ // The general path makes a voice's first call for a subject, where it first sees it play.
294
+ if (Number.isNaN(rec.probed)) {
295
+ this.late.push(slot);
296
+ return false;
297
+ }
298
+ // A motion patch needs the subject only to number it, so the lookup waits until then.
299
+ if (lane.motion !== undefined) {
300
+ move(this, lane, lane.motion, p, slot, rec, elapsed, delay, w);
301
+ return true;
302
+ }
303
+ return this.call(voice, lane.chans, rec, slot, elapsed, lane.phase, lane.pass, delay, w);
304
+ }
305
+ /**
306
+ * A keys voice's stops at `phase` folded straight into a subject's values, without a delta: what
307
+ * `readKeyed` then `foldDelta` give, through the same segment search and the stock channels' own
308
+ * lerp, which is all a laned keys voice can use.
309
+ */
310
+ export function foldKeys(voice, chans, phase, slot, w) {
311
+ const built = voice.built;
312
+ const tracks = built.tracks;
313
+ for (let i = 0; i < tracks.length; i++) {
314
+ const track = tracks[i];
315
+ const ch = chans[i];
316
+ const found = segment(track, shifted(track, phase, built.duration), undefined);
317
+ if (found === NOTHING)
318
+ continue;
319
+ if (found === AT) {
320
+ this.foldInto(ch, slot, seg.a, w);
321
+ continue;
322
+ }
323
+ const a = seg.a;
324
+ const b = seg.b;
325
+ const u = seg.eased;
326
+ if (ch.scalar) {
327
+ this.foldInto(ch, slot, voice.lerps[i](a, b, u), w);
328
+ continue;
329
+ }
330
+ // `vec`'s lerp, reading each end past its length as the rest, then its fold.
331
+ const as = a;
332
+ const bs = b;
333
+ const values = ch.values;
334
+ const base = slot * ch.axes;
335
+ const rest = ch.rest;
336
+ for (let x = 0; x < ch.axes; x++) {
337
+ const v = lerpNumber(as[x] ?? rest, bs[x] ?? rest, u);
338
+ values[base + x] = foldNumber(ch.op, values[base + x], v, w);
339
+ }
340
+ }
341
+ }
342
+ /** Reads a keys voice's stops at `phase` into `delta`, interpolating into the reader's `scratch`. */
343
+ export function readKeyed(voice, phase, delta, scratch) {
344
+ readKeys(voice.built, phase, delta, undefined, voice.lerps, undefined, voice.intos, scratch);
345
+ }
346
+ /**
347
+ * Calls a stateless fn voice's patch for a subject its general path has met, and folds the delta;
348
+ * false for a subject the host has let go of.
349
+ */
350
+ export function call(voice, chans, rec, slot, elapsed, phase, pass, delay, w) {
351
+ const host = this.host;
352
+ const subject = this.subjectAt(slot);
353
+ if (subject === absent)
354
+ return false;
355
+ // Called already this frame, by the general path or a fill before a refill: reuse, as a probe does.
356
+ if (rec.probed === this.now && rec.delta !== null && rec.seeks === voice.seeks) {
357
+ if (w > 0)
358
+ this.foldDelta(chans, slot, rec.delta, w);
359
+ return true;
360
+ }
361
+ if (silent(voice, w))
362
+ return true;
363
+ const kept = reading.kept;
364
+ host.ready(voice, subject, rec, elapsed, pass, w);
365
+ if (this.keeps)
366
+ host.horizon(voice, delay);
367
+ else
368
+ reading.horizon = Number.POSITIVE_INFINITY;
369
+ const delta = voice.patch.at(phase, subject, voice.setting);
370
+ // What `influence` leaves on the record, so a probe on the general path this frame reuses it.
371
+ rec.delta = delta;
372
+ rec.probed = this.now;
373
+ rec.seeks = voice.seeks;
374
+ if (this.keeps)
375
+ host.after(voice, rec);
376
+ if (reading.kept !== kept && !voice.keeping)
377
+ host.kept(voice);
378
+ if (w > 0)
379
+ this.foldDelta(chans, slot, delta, w);
380
+ return true;
381
+ }
package/dist/fold.d.ts ADDED
@@ -0,0 +1,51 @@
1
+ import type { Mixer } from './mixer.js';
2
+ import type { Subject, Voice } from './voice.js';
3
+ export type Key<O> = keyof O & string;
4
+ export declare function near(a: unknown, b: unknown): boolean;
5
+ export type Values = Record<string, unknown>;
6
+ /**
7
+ * One live voice's delta for one subject this frame, through its record for the subject, or null
8
+ * when it does not reach. Its weight is left in `w`.
9
+ */
10
+ export declare function influence<I, O>(this: Mixer<I, O>, voice: Voice<I, O>, subject: I, now: number, held: Subject<unknown>): Record<string, unknown> | null;
11
+ /** Reads a keys voice's stops at `held.phase` into the subject's delta, through the voice's scratch. */
12
+ export declare function keyed<I, O>(this: Mixer<I, O>, voice: Voice<I, O>, subject: I, held: Subject<unknown>): Record<string, unknown>;
13
+ /**
14
+ * Runs `step` once per whole interval between where this subject last stopped and now, on a grid
15
+ * counted from when its delay ran out, and leaves the remainder for the next sample. Counting
16
+ * rather than adding keeps the grid where it is however many samples it is reached through.
17
+ */
18
+ export declare function tick<I, O>(this: Mixer<I, O>, voice: Voice<I, O>, subject: I, held: Subject<unknown>, tick: number, to: number): void;
19
+ /**
20
+ * Stop 0 for a retargeting voice: the subject's pose in the frame before this voice contributes.
21
+ * With no such frame on record — a voice cued before the subject was ever probed, or a host that
22
+ * samples into its own object — it is this frame's pose with every other voice folded in.
23
+ */
24
+ export declare function baseFor<I, O>(this: Mixer<I, O>, voice: Voice<I, O>, subject: I): Record<string, unknown>;
25
+ /**
26
+ * How fast each numeric channel this voice writes was moving, from the subject's last two probed
27
+ * poses, in units per ms. Undefined with fewer than two on record, which starts the voice at rest.
28
+ */
29
+ export declare function slopeFor<I, O>(this: Mixer<I, O>, voice: Voice<I, O>, subject: I): Record<string, unknown> | undefined;
30
+ export declare function isRest<I, O>(this: Mixer<I, O>, delta: Record<string, unknown>): boolean;
31
+ /** Whether a rest-less channel's influence is switched on, across a band rather than an edge. */
32
+ export declare function passes<I, O>(this: Mixer<I, O>, was: boolean | undefined, w: number): boolean;
33
+ /** Folds one voice's delta into the pose, its band state kept on the subject's record. */
34
+ export declare function apply<I, O>(this: Mixer<I, O>, pose: Record<string, unknown>, voice: Voice<I, O>, held: Subject<unknown>, delta: Record<string, unknown>, weight: number): void;
35
+ /** Clamps every bounded channel of a folded pose into its range, in place. */
36
+ export declare function clamp<I, O>(this: Mixer<I, O>, pose: Record<string, unknown>): Record<string, unknown>;
37
+ export declare function fold<I, O>(this: Mixer<I, O>, subject: I, out?: O, dry?: boolean, except?: number): O;
38
+ export declare function folded<I, O>(this: Mixer<I, O>, subject: I, out?: O, dry?: boolean, except?: number): O;
39
+ /**
40
+ * A subject's chain, linked for this frame, and in `linkedLaned` whether its lanes answer for the
41
+ * voices on them; a subject numbered since the frame's fill folds every voice, laned ones included.
42
+ */
43
+ export declare function linked<I, O>(this: Mixer<I, O>, subject: I): Subject<unknown> | null;
44
+ /** The fold after a subject's chain is linked and its lanes asked whether it reads from them. */
45
+ export declare function foldWith<I, O>(this: Mixer<I, O>, subject: I, out: O, head: Subject<unknown> | null, laned: boolean, dry: boolean, except?: number): O;
46
+ /** Whether the subject numbered `owed` reads laned `voice` from the general path this fill. */
47
+ export declare function owedBy<I, O>(this: Mixer<I, O>, owed: number, voice: Voice<I, O>): boolean;
48
+ /** `foldWith`'s voices with a locus in play: each locus folds at its first member's place. */
49
+ export declare function foldLoci<I, O>(this: Mixer<I, O>, subject: I, pose: Record<string, unknown>, head: Subject<unknown>, laned: boolean, dry: boolean, except: number | undefined, owed: number): O;
50
+ /** `influence`, or for a dry fold a subject already probed this frame, without advancing it. */
51
+ export declare function read<I, O>(this: Mixer<I, O>, voice: Voice<I, O>, subject: I, now: number, dry: boolean, held: Subject<unknown>): Record<string, unknown> | null;