@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
@@ -0,0 +1,138 @@
1
+ import { foldNumber, lerpNumber } from './channels.js';
2
+ import { fraction, locate, shift } from './patch.js';
3
+ /** A row's header: its length in `data`, its offset in `eases`, its duration, its stop count. */
4
+ const HEAD = 4;
5
+ /**
6
+ * The stops of a crowd's `keys` rows, copied into one array as a row joins, so a fill reads each
7
+ * row's stops in place rather than through its voice, its built tracks and every stop's own array.
8
+ * Per row: its header, then per track its stop count, its delay, and per stop its phase and its
9
+ * value on each of the channel's axes, a missing axis read as rest as `vec`'s lerp reads it. Each
10
+ * stop's easing is in `eases`.
11
+ */
12
+ export class KeyRows {
13
+ data = new Float64Array(64);
14
+ used = 0;
15
+ eases = [];
16
+ /**
17
+ * Copies a voice's stops for channels `chans` and returns the row's offset; -1 where a stop's
18
+ * value is not one a lane folds as a number per axis, so the row reads its voice's stops instead.
19
+ */
20
+ add(built, chans) {
21
+ let len = HEAD;
22
+ let stops = 0;
23
+ for (let i = 0; i < built.tracks.length; i++) {
24
+ const track = built.tracks[i];
25
+ const ch = chans[i];
26
+ if (track === undefined || ch === undefined)
27
+ return -1;
28
+ for (const pt of track.all)
29
+ if (!flat(pt.value, ch))
30
+ return -1;
31
+ len += 2 + track.all.length * (1 + ch.axes);
32
+ stops += track.all.length;
33
+ }
34
+ const off = this.reserve(len);
35
+ const d = this.data;
36
+ d[off] = len;
37
+ d[off + 1] = this.eases.length;
38
+ d[off + 2] = built.duration;
39
+ d[off + 3] = stops;
40
+ let o = off + HEAD;
41
+ for (let i = 0; i < built.tracks.length; i++) {
42
+ const track = built.tracks[i];
43
+ const ch = chans[i];
44
+ d[o++] = track.all.length;
45
+ d[o++] = track.delay;
46
+ for (const pt of track.all) {
47
+ d[o++] = pt.at;
48
+ if (ch.scalar)
49
+ d[o++] = pt.value;
50
+ else {
51
+ const arr = pt.value;
52
+ for (let a = 0; a < ch.axes; a++)
53
+ d[o++] = arr[a] ?? ch.rest;
54
+ }
55
+ this.eases.push(pt.ease);
56
+ }
57
+ }
58
+ return off;
59
+ }
60
+ /** Copies another pool's row at `off` into this one, returning its offset here. */
61
+ copy(from, off) {
62
+ const len = from.data[off];
63
+ const at = this.reserve(len);
64
+ this.data.set(from.data.subarray(off, off + len), at);
65
+ const e = from.data[off + 1];
66
+ const stops = from.data[off + 3];
67
+ this.data[at + 1] = this.eases.length;
68
+ for (let s = 0; s < stops; s++)
69
+ this.eases.push(from.eases[e + s]);
70
+ return at;
71
+ }
72
+ /**
73
+ * Folds the row at `off` at `phase` into subject `slot`'s values at weight `w`: what `segment`
74
+ * and the stock lerp and fold give for each track, read from this pool.
75
+ */
76
+ fold(off, phase, chans, slot, w) {
77
+ const d = this.data;
78
+ const duration = d[off + 2];
79
+ let e = d[off + 1];
80
+ let o = off + HEAD;
81
+ const end = off + d[off];
82
+ for (let i = 0; o < end; i++) {
83
+ const ch = chans[i];
84
+ const axes = ch.axes;
85
+ const stride = 1 + axes;
86
+ const n = d[o];
87
+ const ph = shift(d[o + 1], phase, duration);
88
+ const first = o + 2;
89
+ o = first + n * stride;
90
+ const k = locate(d, first, stride, n, ph);
91
+ const stops = e;
92
+ e += n;
93
+ if (k === -2)
94
+ continue;
95
+ const values = ch.values;
96
+ const base = slot * axes;
97
+ if (k === -1 || k === n) {
98
+ const at = first + (k === -1 ? 0 : n - 1) * stride + 1;
99
+ for (let a = 0; a < axes; a++)
100
+ values[base + a] = foldNumber(ch.op, values[base + a], d[at + a], w);
101
+ continue;
102
+ }
103
+ const b = first + k * stride;
104
+ const before = b - stride;
105
+ const u = fraction(ph, d[before], d[b]);
106
+ const ease = this.eases[stops + k];
107
+ const eased = ease ? ease(u) : u;
108
+ for (let a = 0; a < axes; a++) {
109
+ const v = lerpNumber(d[before + 1 + a], d[b + 1 + a], eased);
110
+ values[base + a] = foldNumber(ch.op, values[base + a], v, w);
111
+ }
112
+ }
113
+ }
114
+ reserve(len) {
115
+ const at = this.used;
116
+ if (at + len > this.data.length) {
117
+ const data = new Float64Array(Math.max(at + len, this.data.length * 2));
118
+ data.set(this.data.subarray(0, at));
119
+ this.data = data;
120
+ }
121
+ this.used = at + len;
122
+ return at;
123
+ }
124
+ }
125
+ /** Whether a stop's value folds on a lane as numbers: a number on a number, an array on an array. */
126
+ function flat(value, ch) {
127
+ if (ch.scalar)
128
+ return typeof value === 'number';
129
+ if (!(Array.isArray(value) || ArrayBuffer.isView(value)))
130
+ return false;
131
+ const arr = value;
132
+ for (let a = 0; a < ch.axes; a++) {
133
+ const x = arr[a];
134
+ if (x !== undefined && typeof x !== 'number')
135
+ return false;
136
+ }
137
+ return true;
138
+ }
package/dist/lane.d.ts ADDED
@@ -0,0 +1,210 @@
1
+ import type { Numeric } from './channels.js';
2
+ import { type Motions } from './motion.js';
3
+ import type { Scratch } from './patch.js';
4
+ import type { Subject, Voice } from './voice.js';
5
+ /** One laned channel: its arithmetic, and every numbered subject's folded value, `axes` per subject. */
6
+ export interface Laned {
7
+ name: string;
8
+ /** Its value is a number, its rest a number; otherwise an array, which `vec(1)` is too. */
9
+ scalar: boolean;
10
+ op: Numeric['op'];
11
+ rest: number;
12
+ axes: number;
13
+ values: Float64Array;
14
+ /** An array channel's rest, which a copy starts from as the general path's does. */
15
+ start: readonly number[];
16
+ /** Its place among the laned channels. */
17
+ index: number;
18
+ }
19
+ /**
20
+ * The laned voices sharing one locus, in voice order, and what a fill gathers from them for each
21
+ * subject before folding it in at the subject's first member: their summed weight, and per laned
22
+ * channel the members' values lerped by share of weight and the weight taken into each.
23
+ */
24
+ export interface Locus<I, O> {
25
+ readonly members: Lane<I, O>[];
26
+ /** By subject number: the fill that last met it, the member it met first then, the summed weight. */
27
+ met: Float64Array;
28
+ first: Float64Array;
29
+ sum: Float64Array;
30
+ /** By laned channel, then subject number (times axes for `values`). */
31
+ values: (Float64Array | undefined)[];
32
+ taken: (Float64Array | undefined)[];
33
+ }
34
+ /**
35
+ * Where each of a lane position's or crowd row's `STRIDE` numbers sits. The layouts are const
36
+ * enums so tsc writes each member in as a number: V8 reads a const imported from another module on
37
+ * every use, which cost the fills 10-20%.
38
+ */
39
+ export declare const enum Row {
40
+ STRIDE = 8,
41
+ DELAY = 0,
42
+ SINCE = 1,
43
+ /** The weight the last fill gave the subject. */
44
+ WEIGHT = 2,
45
+ /** The weight a fill gave it the last time a probe read it from the lane. */
46
+ PROBED = 3,
47
+ MSLOT = 4,
48
+ /** For a motion voice, the last fill that sampled the subject, and the voice's seeks then. */
49
+ SAMPLED = 5,
50
+ SEEKS = 6,
51
+ /** 1 once the general path has made the voice's first call for the subject, which never undoes. */
52
+ MET = 7
53
+ }
54
+ /** What `begin` found: the general path, or lanes filled. */
55
+ export declare const enum Begin {
56
+ GENERAL = 0,
57
+ READY = 1
58
+ }
59
+ /** Where each of a subject's `SLOT` numbers in `Lanes.per` sits. */
60
+ export declare const enum Per {
61
+ SLOT = 6,
62
+ FILLED = 0,
63
+ /**
64
+ * The latest epoch a probe has checked the subject against; or, as `-1 - e`, that a voice naming
65
+ * it started after epoch `e`, so its next probe checks it against everything since `e`.
66
+ */
67
+ SEEN = 1,
68
+ IDLE = 2,
69
+ LANE_PROBE = 3,
70
+ GENERAL_PROBE = 4,
71
+ LANE_FILL = 5
72
+ }
73
+ /**
74
+ * Below what share of its subjects probed last frame a lane stops filling, and above what share it
75
+ * fills again; between the two it keeps doing what it did, so a host near the line does not flip
76
+ * every frame. Measured at 10k and 1k subjects (2026-10-02, one local machine): filling breaks even
77
+ * with probing through the general path at about 19% probed for keys at both sizes, 71-77% for a
78
+ * spring, and 63% (1k) to about 100% (10k) for a stateless fn.
79
+ */
80
+ export declare const SPARSE: {
81
+ keys: {
82
+ stop: number;
83
+ start: number;
84
+ };
85
+ other: {
86
+ stop: number;
87
+ start: number;
88
+ };
89
+ };
90
+ /** What pacing reads and sets: a lane's or a crowd's subjects, idle state and per-position numbers. */
91
+ export interface Paced {
92
+ list: number[];
93
+ idle: boolean;
94
+ readonly line: {
95
+ stop: number;
96
+ start: number;
97
+ };
98
+ data: Float64Array;
99
+ }
100
+ /**
101
+ * Where a motion voice's per-subject numbers live, by position: a lane of one voice, or the crowd
102
+ * of single-subject motion voices, whose positions each belong to a voice of their own.
103
+ */
104
+ export interface Positions<I, O> {
105
+ /** `STRIDE` numbers per position. */
106
+ data: Float64Array;
107
+ /** Each position's last delta, built from its kept sample only when asked. */
108
+ deltas: (Record<string, unknown> | null)[];
109
+ /** Keeps a position's sample, `n` numbers from `xs`. */
110
+ keep(p: number, xs: Float64Array, n: number): void;
111
+ /** Reads a position's kept sample back into `xs`. */
112
+ kept(p: number, xs: Float64Array): void;
113
+ chans: Laned[];
114
+ voiceAt(p: number): Voice<I, O>;
115
+ /** Makes position `p`'s sample and its stamps hold what its last fill gave it. */
116
+ fix(p: number): void;
117
+ /** Position `p`'s subject has number `ms` in its voice's motion patch. */
118
+ numbered(p: number, ms: number): void;
119
+ motionAt(p: number): Motions<I> | undefined;
120
+ }
121
+ /**
122
+ * What one laned voice keeps per subject it reaches, by position in `list`. A subject gets a
123
+ * position when a probe of it first finds the voice playing, which is when the general path first
124
+ * sees it too.
125
+ */
126
+ export declare class Lane<I, O> implements Positions<I, O> {
127
+ readonly voice: Voice<I, O>;
128
+ /** The subject number at each position. */
129
+ readonly list: number[];
130
+ /** By subject number, its position plus one, 0 where it has none. */
131
+ private at;
132
+ /** Whether too few of its subjects were probed lately to fill it: theirs take the general path. */
133
+ idle: boolean;
134
+ /** The lines it goes idle and busy at, by its patch form. */
135
+ readonly line: {
136
+ stop: number;
137
+ start: number;
138
+ };
139
+ /**
140
+ * When the voice started playing on its lane, in the order lanes did; 0 while it waits. A subject
141
+ * checked against every lane up to some epoch has met this one if this one's is no later.
142
+ */
143
+ epoch: number;
144
+ /**
145
+ * Per position, `STRIDE` numbers side by side, so one subject's are one read from memory: its
146
+ * delay, its `since`, its weight at the last fill, its weight at the last fill a probe read, a
147
+ * motion patch's own number for it (-1 until asked), and the fill and seeks of its last sample.
148
+ */
149
+ data: Float64Array<ArrayBuffer>;
150
+ records: (Subject<unknown> | undefined)[];
151
+ /**
152
+ * For a motion voice, per position, the delta its last sample made; null where that sample was
153
+ * kept only in `samples`, until a probe asks for it.
154
+ */
155
+ deltas: (Record<string, unknown> | null)[];
156
+ /** For a motion voice, per position, its last sample's value on each of `axes` axes. */
157
+ samples: Float64Array<ArrayBuffer>;
158
+ axes: number;
159
+ /** What a keys read writes into, reused across subjects. */
160
+ readonly delta: Record<string, unknown>;
161
+ /** The arrays keyed reads interpolate into: the lane's own, so a probe mid-fill cannot move them. */
162
+ readonly scratch: Scratch;
163
+ /** The laned channel of each channel the voice writes, in `writes` order. */
164
+ chans: Laned[];
165
+ /** The value read for each of `chans`, for the subject being folded. */
166
+ values: unknown[];
167
+ /**
168
+ * Within one fill, the last elapsed placed and the last `since` weighed, with what they gave, so
169
+ * subjects sharing a delay or a start share the arithmetic: the same inputs give the same bits.
170
+ */
171
+ placed: boolean;
172
+ placedAt: number;
173
+ phase: number;
174
+ pass: number;
175
+ read: boolean;
176
+ readAt: number;
177
+ weighed: boolean;
178
+ weighedSince: number;
179
+ fade: number;
180
+ /** Whether the voice has no fade in or out this fill, so its envelope is 1 for every subject. */
181
+ flat: boolean;
182
+ /** A motion patch's state, which the lane samples in place of calling the patch. */
183
+ readonly motion: Motions<I> | undefined;
184
+ /** By the motion patch's number for a subject, its position plus one, 0 where it has none. */
185
+ private byMotion;
186
+ /** The last fill `runMotion` ran, and its voice time and seeks, which a `Sampled.BARE` position was sampled at. */
187
+ bareFill: number;
188
+ bareElapsed: number;
189
+ bareSeeks: number;
190
+ /** The locus its voice shares, gathered before the voices are folded in order. */
191
+ group: Locus<I, O> | null;
192
+ voiceAt(): Voice<I, O>;
193
+ fix(p: number): void;
194
+ numbered(p: number, ms: number): void;
195
+ positionOfMotion(ms: number): number;
196
+ keep(p: number, xs: Float64Array, n: number): void;
197
+ kept(p: number, xs: Float64Array): void;
198
+ motionAt(): Motions<I> | undefined;
199
+ constructor(voice: Voice<I, O>);
200
+ positionOf(slot: number): number;
201
+ /** Gives a subject the voice reaches a position, from what its record fixed on first sight. */
202
+ add(slot: number, held: Subject<unknown>): void;
203
+ /** A subject lost its number: the last position moves into its place. */
204
+ remove(slot: number): void;
205
+ }
206
+ /**
207
+ * A subject's voice time once its voice's holds apply: held at 0 before it starts and at the end of
208
+ * its passes after, and NaN where it shows nothing.
209
+ */
210
+ export declare function held<I, O>(voice: Voice<I, O>, elapsed: number): number;
package/dist/lane.js ADDED
@@ -0,0 +1,187 @@
1
+ import { unbareLane } from './bare.js';
2
+ import { heldTime } from './clock.js';
3
+ import { motionOf } from './motion.js';
4
+ /**
5
+ * Below what share of its subjects probed last frame a lane stops filling, and above what share it
6
+ * fills again; between the two it keeps doing what it did, so a host near the line does not flip
7
+ * every frame. Measured at 10k and 1k subjects (2026-10-02, one local machine): filling breaks even
8
+ * with probing through the general path at about 19% probed for keys at both sizes, 71-77% for a
9
+ * spring, and 63% (1k) to about 100% (10k) for a stateless fn.
10
+ */
11
+ export const SPARSE = { keys: { stop: 0.15, start: 0.25 }, other: { stop: 0.6, start: 0.8 } };
12
+ /**
13
+ * What one laned voice keeps per subject it reaches, by position in `list`. A subject gets a
14
+ * position when a probe of it first finds the voice playing, which is when the general path first
15
+ * sees it too.
16
+ */
17
+ export class Lane {
18
+ voice;
19
+ /** The subject number at each position. */
20
+ list = [];
21
+ /** By subject number, its position plus one, 0 where it has none. */
22
+ at = new Int32Array(0);
23
+ /** Whether too few of its subjects were probed lately to fill it: theirs take the general path. */
24
+ idle = false;
25
+ /** The lines it goes idle and busy at, by its patch form. */
26
+ line;
27
+ /**
28
+ * When the voice started playing on its lane, in the order lanes did; 0 while it waits. A subject
29
+ * checked against every lane up to some epoch has met this one if this one's is no later.
30
+ */
31
+ epoch = 0;
32
+ /**
33
+ * Per position, `STRIDE` numbers side by side, so one subject's are one read from memory: its
34
+ * delay, its `since`, its weight at the last fill, its weight at the last fill a probe read, a
35
+ * motion patch's own number for it (-1 until asked), and the fill and seeks of its last sample.
36
+ */
37
+ data = new Float64Array(0);
38
+ records = [];
39
+ /**
40
+ * For a motion voice, per position, the delta its last sample made; null where that sample was
41
+ * kept only in `samples`, until a probe asks for it.
42
+ */
43
+ deltas = [];
44
+ /** For a motion voice, per position, its last sample's value on each of `axes` axes. */
45
+ samples = new Float64Array(0);
46
+ axes = 0;
47
+ /** What a keys read writes into, reused across subjects. */
48
+ delta = {};
49
+ /** The arrays keyed reads interpolate into: the lane's own, so a probe mid-fill cannot move them. */
50
+ scratch = [];
51
+ /** The laned channel of each channel the voice writes, in `writes` order. */
52
+ chans = [];
53
+ /** The value read for each of `chans`, for the subject being folded. */
54
+ values = [];
55
+ /**
56
+ * Within one fill, the last elapsed placed and the last `since` weighed, with what they gave, so
57
+ * subjects sharing a delay or a start share the arithmetic: the same inputs give the same bits.
58
+ */
59
+ placed = false;
60
+ placedAt = 0;
61
+ phase = 0;
62
+ pass = 0;
63
+ read = false;
64
+ readAt = 0;
65
+ weighed = false;
66
+ weighedSince = 0;
67
+ fade = 1;
68
+ /** Whether the voice has no fade in or out this fill, so its envelope is 1 for every subject. */
69
+ flat = false;
70
+ /** A motion patch's state, which the lane samples in place of calling the patch. */
71
+ motion;
72
+ /** By the motion patch's number for a subject, its position plus one, 0 where it has none. */
73
+ byMotion = new Int32Array(0);
74
+ /** The last fill `runMotion` ran, and its voice time and seeks, which a `Sampled.BARE` position was sampled at. */
75
+ bareFill = -1;
76
+ bareElapsed = Number.NaN;
77
+ bareSeeks = Number.NaN;
78
+ /** The locus its voice shares, gathered before the voices are folded in order. */
79
+ group = null;
80
+ voiceAt() {
81
+ return this.voice;
82
+ }
83
+ fix(p) {
84
+ unbareLane(this, p);
85
+ }
86
+ numbered(p, ms) {
87
+ if (ms >= this.byMotion.length) {
88
+ const at = new Int32Array(Math.max(ms + 1, this.byMotion.length * 2, 64));
89
+ at.set(this.byMotion);
90
+ this.byMotion = at;
91
+ }
92
+ this.byMotion[ms] = p + 1;
93
+ }
94
+ positionOfMotion(ms) {
95
+ return ms < this.byMotion.length ? this.byMotion[ms] - 1 : -1;
96
+ }
97
+ keep(p, xs, n) {
98
+ if (this.axes !== n) {
99
+ this.axes = n;
100
+ this.samples = new Float64Array(0);
101
+ }
102
+ if ((p + 1) * n > this.samples.length) {
103
+ const grown = new Float64Array(Math.max(p + 1, (this.samples.length / n) * 2, 4) * n);
104
+ grown.set(this.samples);
105
+ this.samples = grown;
106
+ }
107
+ for (let a = 0; a < n; a++)
108
+ this.samples[p * n + a] = xs[a];
109
+ }
110
+ kept(p, xs) {
111
+ const n = this.axes;
112
+ for (let a = 0; a < n; a++)
113
+ xs[a] = this.samples[p * n + a];
114
+ }
115
+ motionAt() {
116
+ return this.motion;
117
+ }
118
+ constructor(voice) {
119
+ this.voice = voice;
120
+ this.motion = motionOf(voice.patch);
121
+ this.line = voice.built !== null ? SPARSE.keys : SPARSE.other;
122
+ }
123
+ positionOf(slot) {
124
+ return slot < this.at.length ? this.at[slot] - 1 : -1;
125
+ }
126
+ /** Gives a subject the voice reaches a position, from what its record fixed on first sight. */
127
+ add(slot, held) {
128
+ const p = this.list.length;
129
+ this.list.push(slot);
130
+ if (slot >= this.at.length) {
131
+ const at = new Int32Array(Math.max(slot + 1, this.at.length * 2, 64));
132
+ at.set(this.at);
133
+ this.at = at;
134
+ }
135
+ this.at[slot] = p + 1;
136
+ if ((p + 1) * 8 /* Row.STRIDE */ > this.data.length) {
137
+ const data = new Float64Array(Math.max(p + 1, (this.data.length / 8 /* Row.STRIDE */) * 2, 4) * 8 /* Row.STRIDE */);
138
+ data.set(this.data);
139
+ this.data = data;
140
+ }
141
+ const o = p * 8 /* Row.STRIDE */;
142
+ this.data[o + 0 /* Row.DELAY */] = held.delay;
143
+ this.data[o + 1 /* Row.SINCE */] = held.shown;
144
+ this.data[o + 2 /* Row.WEIGHT */] = 0;
145
+ this.data[o + 3 /* Row.PROBED */] = 0;
146
+ this.data[o + 4 /* Row.MSLOT */] = -1;
147
+ this.data[o + 5 /* Row.SAMPLED */] = -1;
148
+ this.data[o + 7 /* Row.MET */] = 0;
149
+ this.records[p] = held;
150
+ this.deltas[p] = null;
151
+ }
152
+ /** A subject lost its number: the last position moves into its place. */
153
+ remove(slot) {
154
+ const p = this.positionOf(slot);
155
+ if (p < 0)
156
+ return;
157
+ const last = this.list.length - 1;
158
+ const ms = this.data[p * 8 /* Row.STRIDE */ + 4 /* Row.MSLOT */];
159
+ if (ms >= 0)
160
+ this.byMotion[ms] = 0;
161
+ if (p !== last) {
162
+ const number = this.data[last * 8 /* Row.STRIDE */ + 4 /* Row.MSLOT */];
163
+ if (number >= 0)
164
+ this.byMotion[number] = p + 1;
165
+ const moved = this.list[last];
166
+ this.list[p] = moved;
167
+ this.at[moved] = p + 1;
168
+ this.data.copyWithin(p * 8 /* Row.STRIDE */, last * 8 /* Row.STRIDE */, (last + 1) * 8 /* Row.STRIDE */);
169
+ this.records[p] = this.records[last];
170
+ this.deltas[p] = this.deltas[last] ?? null;
171
+ const n = this.axes;
172
+ if (n > 0)
173
+ this.samples.copyWithin(p * n, last * n, (last + 1) * n);
174
+ }
175
+ this.list.pop();
176
+ this.at[slot] = 0;
177
+ this.records[last] = undefined;
178
+ this.deltas[last] = null;
179
+ }
180
+ }
181
+ /**
182
+ * A subject's voice time once its voice's holds apply: held at 0 before it starts and at the end of
183
+ * its passes after, and NaN where it shows nothing.
184
+ */
185
+ export function held(voice, elapsed) {
186
+ return heldTime(elapsed, voice.holdsBefore, voice.holdsAfter, voice.span);
187
+ }