@msb235/blits 0.7.1 → 0.8.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 (153) hide show
  1. package/CHANGELOG.md +180 -0
  2. package/README.md +22 -4
  3. package/dist/book.js +4 -4
  4. package/dist/chain.d.ts +1 -1
  5. package/dist/chain.js +57 -54
  6. package/dist/channels.d.ts +26 -5
  7. package/dist/channels.js +12 -8
  8. package/dist/clock.d.ts +8 -1
  9. package/dist/clock.js +14 -3
  10. package/dist/clone.d.ts +3 -0
  11. package/dist/clone.js +16 -1
  12. package/dist/closed.d.ts +1 -1
  13. package/dist/closed.js +3 -2
  14. package/dist/color.d.ts +0 -11
  15. package/dist/color.js +4 -24
  16. package/dist/columns.js +0 -1
  17. package/dist/copyout.d.ts +6 -0
  18. package/dist/copyout.js +38 -0
  19. package/dist/crowd.d.ts +7 -5
  20. package/dist/crowd.js +32 -18
  21. package/dist/cue.d.ts +3 -1
  22. package/dist/cue.js +31 -20
  23. package/dist/departed.d.ts +34 -0
  24. package/dist/departed.js +73 -0
  25. package/dist/easing.d.ts +5 -0
  26. package/dist/easing.js +31 -2
  27. package/dist/everyone.d.ts +56 -0
  28. package/dist/everyone.js +221 -0
  29. package/dist/fade.d.ts +8 -1
  30. package/dist/fade.js +34 -6
  31. package/dist/fill.d.ts +22 -13
  32. package/dist/fill.js +104 -41
  33. package/dist/fit.js +2 -1
  34. package/dist/fold.d.ts +37 -0
  35. package/dist/fold.js +213 -28
  36. package/dist/gather.d.ts +15 -9
  37. package/dist/gather.js +60 -25
  38. package/dist/gone.js +10 -0
  39. package/dist/handle.d.ts +16 -5
  40. package/dist/handle.js +56 -6
  41. package/dist/held.d.ts +8 -5
  42. package/dist/held.js +56 -58
  43. package/dist/history.d.ts +17 -13
  44. package/dist/history.js +47 -28
  45. package/dist/hosts.js +55 -8
  46. package/dist/index.d.ts +9 -4
  47. package/dist/index.js +5 -2
  48. package/dist/keyfill.d.ts +7 -0
  49. package/dist/keyfill.js +124 -0
  50. package/dist/keyrows.d.ts +1 -0
  51. package/dist/keyrows.js +1 -1
  52. package/dist/lane.d.ts +32 -2
  53. package/dist/lane.js +5 -0
  54. package/dist/lanes.d.ts +32 -8
  55. package/dist/lanes.js +69 -37
  56. package/dist/leavings.d.ts +26 -0
  57. package/dist/leavings.js +70 -0
  58. package/dist/meet.js +14 -8
  59. package/dist/mixer.d.ts +39 -7
  60. package/dist/mixer.js +50 -10
  61. package/dist/motion.d.ts +2 -0
  62. package/dist/motion.js +4 -1
  63. package/dist/motions.d.ts +22 -15
  64. package/dist/motions.js +40 -34
  65. package/dist/move.js +23 -16
  66. package/dist/moved.d.ts +11 -0
  67. package/dist/moved.js +29 -0
  68. package/dist/origin.d.ts +19 -0
  69. package/dist/origin.js +68 -0
  70. package/dist/owed.d.ts +4 -4
  71. package/dist/owed.js +8 -8
  72. package/dist/owner.d.ts +1 -1
  73. package/dist/owner.js +2 -2
  74. package/dist/pace.d.ts +13 -5
  75. package/dist/pace.js +21 -8
  76. package/dist/pack.d.ts +3 -1
  77. package/dist/pack.js +15 -24
  78. package/dist/paging.d.ts +8 -2
  79. package/dist/paging.js +44 -4
  80. package/dist/patch.d.ts +14 -9
  81. package/dist/patch.js +83 -21
  82. package/dist/place.js +18 -3
  83. package/dist/project.d.ts +12 -1
  84. package/dist/project.js +101 -83
  85. package/dist/pull.d.ts +2 -0
  86. package/dist/pull.js +8 -0
  87. package/dist/qualify.js +10 -2
  88. package/dist/reading.d.ts +5 -0
  89. package/dist/reading.js +5 -0
  90. package/dist/record.d.ts +9 -0
  91. package/dist/record.js +42 -0
  92. package/dist/relink.d.ts +11 -5
  93. package/dist/relink.js +7 -5
  94. package/dist/restored.d.ts +41 -0
  95. package/dist/restored.js +91 -0
  96. package/dist/revive.d.ts +10 -3
  97. package/dist/revive.js +31 -23
  98. package/dist/roster.d.ts +11 -0
  99. package/dist/roster.js +46 -0
  100. package/dist/rotation.d.ts +29 -0
  101. package/dist/rotation.js +91 -0
  102. package/dist/rows.d.ts +5 -5
  103. package/dist/rows.js +5 -5
  104. package/dist/sample.d.ts +3 -2
  105. package/dist/sample.js +32 -15
  106. package/dist/seek.js +97 -114
  107. package/dist/shared.d.ts +6 -0
  108. package/dist/shared.js +15 -0
  109. package/dist/signals.d.ts +24 -3
  110. package/dist/signals.js +50 -11
  111. package/dist/spans.js +7 -0
  112. package/dist/stack.d.ts +11 -0
  113. package/dist/stack.js +47 -0
  114. package/dist/standing.d.ts +10 -0
  115. package/dist/standing.js +79 -0
  116. package/dist/store.d.ts +3 -3
  117. package/dist/store.js +3 -3
  118. package/dist/strays.js +3 -0
  119. package/dist/tape.d.ts +8 -5
  120. package/dist/tape.js +21 -13
  121. package/dist/testing.d.ts +35 -0
  122. package/dist/testing.js +116 -0
  123. package/dist/touch.d.ts +6 -0
  124. package/dist/touch.js +28 -0
  125. package/dist/transport.d.ts +49 -6
  126. package/dist/transport.js +89 -19
  127. package/dist/turns.d.ts +18 -0
  128. package/dist/turns.js +72 -0
  129. package/dist/tweened.js +1 -1
  130. package/dist/types/channel.d.ts +46 -0
  131. package/dist/types/channel.js +1 -0
  132. package/dist/types/history.d.ts +139 -0
  133. package/dist/types/history.js +1 -0
  134. package/dist/types/mix.d.ts +336 -0
  135. package/dist/types/mix.js +1 -0
  136. package/dist/types/patch.d.ts +187 -0
  137. package/dist/types/patch.js +1 -0
  138. package/dist/types/place.d.ts +157 -0
  139. package/dist/types/place.js +1 -0
  140. package/dist/types/transport.d.ts +71 -0
  141. package/dist/types/transport.js +1 -0
  142. package/dist/types/voice.d.ts +323 -0
  143. package/dist/types/voice.js +1 -0
  144. package/dist/types.d.ts +7 -1162
  145. package/dist/unpage.d.ts +2 -2
  146. package/dist/unpage.js +53 -25
  147. package/dist/voice.d.ts +90 -22
  148. package/dist/voice.js +105 -43
  149. package/dist/wave.d.ts +42 -0
  150. package/dist/wave.js +62 -0
  151. package/dist/weigh.js +1 -1
  152. package/docs/schema.html +42 -36
  153. package/package.json +8 -3
package/dist/revive.js CHANGED
@@ -1,6 +1,7 @@
1
1
  import { playable } from './cue.js';
2
2
  import { VoiceHandle } from './handle.js';
3
3
  import { handle } from './hosts.js';
4
+ import { Leavings } from './leavings.js';
4
5
  import { packHeld, unpackHeld } from './pack.js';
5
6
  import { pageOut } from './paging.js';
6
7
  import { Voice } from './voice.js';
@@ -47,7 +48,7 @@ export function pageVoice(mix, voice, reach) {
47
48
  return false;
48
49
  const keys = mix.keys;
49
50
  const data = packVoice(voice, keys);
50
- pageOut(mix, 'voice', voice.id, undefined, [{ at: voice.doneAt }], () => data, reach);
51
+ pageOut(mix, 'voice', voice.id, undefined, [{ at: voice.doneAt, seq: voice.doneSeq }], () => data, reach);
51
52
  const h = voice.handle;
52
53
  VoiceHandle.page(h, { weight: voice.weight, played: voice.settled.played === true });
53
54
  mix.outs.add(voice.id, voice.doneAt, h);
@@ -81,8 +82,13 @@ function packVoice(voice, keys) {
81
82
  outAt: voice.outAt,
82
83
  outOver: voice.outOver,
83
84
  outSet: voice.outSet,
85
+ rebuilds: voice.rebuilds,
86
+ past: voice.clocks?.length ?? 0,
87
+ clocks: voice.clocks,
84
88
  cuedAt: voice.cuedAt,
89
+ cuedSeq: voice.cuedSeq,
85
90
  doneAt: voice.doneAt,
91
+ doneSeq: voice.doneSeq,
86
92
  opened: voice.opened,
87
93
  pinned: voice.pinned,
88
94
  latest: voice.latest,
@@ -91,11 +97,18 @@ function packVoice(voice, keys) {
91
97
  log: voice.log,
92
98
  played: settled.played,
93
99
  playedAt: settled.at,
100
+ playedSeq: settled.seq,
94
101
  records,
95
102
  runs,
96
- left: (voice.left ?? []).map((e) => [keys.key(e.subject), e.at, packHeld(voice, e.held)]),
97
- parts: [...(voice.parts ?? [])].map(([s, r]) => [keys.key(s), r.at, r.over]),
98
- parted: [...(voice.parted ?? [])].map(([s, at]) => [keys.key(s), at]),
103
+ left: (voice.left?.all() ?? []).map((e) => [
104
+ keys.key(e.subject),
105
+ e.at,
106
+ e.seq,
107
+ packHeld(voice, e.held),
108
+ e.sync,
109
+ ]),
110
+ parts: [...(voice.parts ?? [])].map(([s, r]) => [keys.key(s), r.at, r.over, r.seq]),
111
+ parted: [...(voice.parted ?? [])].map(([s, p]) => [keys.key(s), p.at, p.seq]),
99
112
  };
100
113
  }
101
114
  /** A voice a history store paged out, rebuilt from its descriptor and put back as it left. */
@@ -106,18 +119,12 @@ export function reviveVoice(mix, id, d) {
106
119
  const spec = { ...revive(d.as), as: d.as };
107
120
  playable(mix, spec);
108
121
  const voice = new Voice(id, spec, spec.patch, spec.fade ?? {}, Number.NEGATIVE_INFINITY, d.start, mix.slotOf, mix.channels, mix.opts.host, mix.send, null);
109
- voice.anchorNow = d.anchorNow;
110
- voice.anchorElapsed = d.anchorElapsed;
111
- voice.rate = d.rate;
112
- voice.ramp = d.ramp;
113
- voice.weight = d.weight;
114
- voice.out = d.out;
115
- voice.back = d.back;
116
- voice.outAt = d.outAt;
117
- voice.outOver = d.outOver;
118
- voice.outSet = d.outSet;
122
+ voice.clocks = d.clocks;
123
+ voice.take(d);
119
124
  voice.cuedAt = d.cuedAt;
125
+ voice.cuedSeq = d.cuedSeq;
120
126
  voice.doneAt = d.doneAt;
127
+ voice.doneSeq = d.doneSeq;
121
128
  voice.opened = d.opened;
122
129
  voice.pinned = d.pinned;
123
130
  voice.latest = d.latest;
@@ -126,7 +133,7 @@ export function reviveVoice(mix, id, d) {
126
133
  voice.log = d.log;
127
134
  voice.state = 'done';
128
135
  if (d.played !== undefined)
129
- voice.play(d.played, d.playedAt);
136
+ voice.play(d.played, d.playedAt, d.playedSeq);
130
137
  voice.resolve();
131
138
  const keys = mix.keys;
132
139
  const subjectOf = (key) => keys.subject(key);
@@ -137,16 +144,17 @@ export function reviveVoice(mix, id, d) {
137
144
  }
138
145
  for (const [key, run] of d.runs)
139
146
  voice.motion?.unpack(subjectOf(key), run);
140
- if (d.left.length > 0)
141
- voice.left = d.left.map(([key, at, p]) => ({
142
- subject: subjectOf(key),
143
- at,
144
- held: unpackHeld(voice, p),
145
- }));
147
+ voice.left = Leavings.from(d.left.map(([key, at, seq, p, sync]) => ({
148
+ subject: subjectOf(key),
149
+ at,
150
+ seq,
151
+ sync,
152
+ held: unpackHeld(voice, p),
153
+ })));
146
154
  if (d.parts.length > 0)
147
- voice.parts = new Map(d.parts.map(([key, at, over]) => [subjectOf(key), { at, over }]));
155
+ voice.parts = new Map(d.parts.map(([key, at, over, seq]) => [subjectOf(key), { at, over, seq }]));
148
156
  if (d.parted.length > 0)
149
- voice.parted = new Map(d.parted.map(([key, at]) => [subjectOf(key), at]));
157
+ voice.parted = new Map(d.parted.map(([key, at, seq]) => [subjectOf(key), { at, seq }]));
150
158
  const h = mix.outs?.take(id);
151
159
  if (h === undefined)
152
160
  voice.handle = handle(mix, voice);
@@ -0,0 +1,11 @@
1
+ import type { Mixer } from './mixer.js';
2
+ import type { Voice } from './voice.js';
3
+ /** Counts a voice just put in the list into everything a sync and a fold skip work by. */
4
+ export declare function enlist<I, O>(mix: Mixer<I, O>, voice: Voice<I, O>): void;
5
+ /**
6
+ * Counts a voice leaving the list back out. A voice naming no subject stays in `general` and
7
+ * `sharers`, which the caller filters once for every voice leaving together.
8
+ */
9
+ export declare function delist<I, O>(mix: Mixer<I, O>, voice: Voice<I, O>): void;
10
+ /** Counts the list over from nothing, for a seek or a projection's copy. `owners` is the caller's. */
11
+ export declare function recount<I, O>(mix: Mixer<I, O>): void;
package/dist/roster.js ADDED
@@ -0,0 +1,46 @@
1
+ import { index, unindex } from './chain.js';
2
+ import { detour } from './everyone.js';
3
+ import { Store } from './store.js';
4
+ function tally(mix, voice, by) {
5
+ if (voice.spec.locus !== undefined)
6
+ mix.loci += by;
7
+ if (voice.spec.anchor !== undefined)
8
+ mix.anchored += by;
9
+ }
10
+ /** Counts a voice just put in the list into everything a sync and a fold skip work by. */
11
+ export function enlist(mix, voice) {
12
+ if (voice.holding !== null) {
13
+ mix.owners ??= [];
14
+ mix.owners.push(voice);
15
+ }
16
+ index(mix, voice);
17
+ tally(mix, voice, 1);
18
+ detour(mix);
19
+ }
20
+ /**
21
+ * Counts a voice leaving the list back out. A voice naming no subject stays in `general` and
22
+ * `sharers`, which the caller filters once for every voice leaving together.
23
+ */
24
+ export function delist(mix, voice) {
25
+ if (voice.named !== null)
26
+ unindex(mix, voice);
27
+ if (voice.holding !== null && mix.owners !== null)
28
+ mix.owners = mix.owners.filter((v) => v !== voice);
29
+ tally(mix, voice, -1);
30
+ detour(mix);
31
+ }
32
+ /** Counts the list over from nothing, for a seek or a projection's copy. `owners` is the caller's. */
33
+ export function recount(mix) {
34
+ mix.named = new Store();
35
+ mix.naming = 0;
36
+ mix.general = [];
37
+ mix.sharers = [];
38
+ mix.loci = 0;
39
+ mix.anchored = 0;
40
+ for (const voice of mix.cued) {
41
+ index(mix, voice);
42
+ tally(mix, voice, 1);
43
+ }
44
+ detour(mix);
45
+ mix.steps.push(null, ++mix.version);
46
+ }
@@ -0,0 +1,29 @@
1
+ import { type Vec } from './channels.js';
2
+ import type { Channel } from './types.js';
3
+ /**
4
+ * What `angle` takes.
5
+ *
6
+ * @category channel
7
+ */
8
+ export interface AngleOptions {
9
+ /** One full turn in the channel's unit. Default 360, for degrees; `2 * Math.PI` for radians. */
10
+ turn?: number;
11
+ }
12
+ /**
13
+ * A rotation about one axis, which adds as `sum` does and interpolates the short way round: from
14
+ * 350 to 10 it passes 0, not 180. A weight below 1 fades it the short way to rest too. Its values
15
+ * are angles, so two a whole number of turns apart are the same value, and a fold may answer with
16
+ * either.
17
+ *
18
+ * @category channel
19
+ */
20
+ export declare function angle(opts?: AngleOptions): Channel<number>;
21
+ /**
22
+ * A rotation in space, as a unit quaternion `[x, y, z, w]`. Contributions compose by the Hamilton
23
+ * product in the order their voices were cued, which matters: rotations do not commute. A weight
24
+ * turns a contribution part of the way from no rotation, along the short arc, and `lerp` takes the
25
+ * short arc between two.
26
+ *
27
+ * @category channel
28
+ */
29
+ export declare function quat(): Channel<Vec<4>>;
@@ -0,0 +1,91 @@
1
+ import { doubles, vouch } from './channels.js';
2
+ import { shortWay } from './turns.js';
3
+ /**
4
+ * A rotation about one axis, which adds as `sum` does and interpolates the short way round: from
5
+ * 350 to 10 it passes 0, not 180. A weight below 1 fades it the short way to rest too. Its values
6
+ * are angles, so two a whole number of turns apart are the same value, and a fold may answer with
7
+ * either.
8
+ *
9
+ * @category channel
10
+ */
11
+ export function angle(opts) {
12
+ const turn = opts?.turn ?? 360;
13
+ if (!(turn > 0))
14
+ throw new RangeError('blits: an angle takes a positive turn');
15
+ const channel = {
16
+ kind: `angle(${turn})`,
17
+ rest: 0,
18
+ merge: (a, b) => a + b,
19
+ scale: (v, w) => shortWay(v, turn) * w,
20
+ lerp: (a, b, u) => a + shortWay(b - a, turn) * u,
21
+ };
22
+ vouch(channel, { op: 'sum', axes: 1, turn });
23
+ return channel;
24
+ }
25
+ /** `a` then `b`, written into `out`, which may be either. */
26
+ function multiply(out, a, b) {
27
+ const ax = a[0];
28
+ const ay = a[1];
29
+ const az = a[2];
30
+ const aw = a[3];
31
+ const bx = b[0];
32
+ const by = b[1];
33
+ const bz = b[2];
34
+ const bw = b[3];
35
+ out[0] = aw * bx + ax * bw + ay * bz - az * by;
36
+ out[1] = aw * by - ax * bz + ay * bw + az * bx;
37
+ out[2] = aw * bz + ax * by - ay * bx + az * bw;
38
+ out[3] = aw * bw - ax * bx - ay * by - az * bz;
39
+ return out;
40
+ }
41
+ /** The short arc from `a` to `b` at `u`, a unit quaternion, written into `out`. */
42
+ function slerp(out, a, b, u) {
43
+ let dot = a[0] * b[0] + a[1] * b[1] + a[2] * b[2] + a[3] * b[3];
44
+ // `q` and `-q` are one rotation; the nearer of the two is the short way.
45
+ const sign = dot < 0 ? -1 : 1;
46
+ dot *= sign;
47
+ let p = 1 - u;
48
+ let q = u;
49
+ // Nearly parallel, the arc's sine is too small to divide by, and a straight line is as good.
50
+ if (dot < 1 - 1e-9) {
51
+ const theta = Math.acos(dot);
52
+ const s = Math.sin(theta);
53
+ p = Math.sin((1 - u) * theta) / s;
54
+ q = Math.sin(u * theta) / s;
55
+ }
56
+ q *= sign;
57
+ const x = p * a[0] + q * b[0];
58
+ const y = p * a[1] + q * b[1];
59
+ const z = p * a[2] + q * b[2];
60
+ const w = p * a[3] + q * b[3];
61
+ const n = Math.sqrt(x * x + y * y + z * z + w * w) || 1;
62
+ out[0] = x / n;
63
+ out[1] = y / n;
64
+ out[2] = z / n;
65
+ out[3] = w / n;
66
+ return out;
67
+ }
68
+ const IDENTITY = [0, 0, 0, 1];
69
+ const scratch = doubles(4, 0);
70
+ /**
71
+ * A rotation in space, as a unit quaternion `[x, y, z, w]`. Contributions compose by the Hamilton
72
+ * product in the order their voices were cued, which matters: rotations do not commute. A weight
73
+ * turns a contribution part of the way from no rotation, along the short arc, and `lerp` takes the
74
+ * short arc between two.
75
+ *
76
+ * @category channel
77
+ */
78
+ export function quat() {
79
+ const rest = doubles(4, 0);
80
+ rest[3] = 1;
81
+ const channel = {
82
+ kind: 'quat',
83
+ rest: rest,
84
+ merge: (a, b) => multiply(doubles(4, 0), a, b),
85
+ scale: (v, w) => slerp(doubles(4, 0), IDENTITY, v, w),
86
+ fold: (into, v, w) => multiply(into, into, slerp(scratch, IDENTITY, v, w)),
87
+ lerp: (a, b, u) => slerp(doubles(4, 0), a, b, u),
88
+ };
89
+ vouch(channel, { op: 'own', axes: 4 });
90
+ return channel;
91
+ }
package/dist/rows.d.ts CHANGED
@@ -15,19 +15,19 @@ export declare function sameCrowds<I, O>(lanes: Lanes<I, O>, members: readonly V
15
15
  */
16
16
  export declare function regroup<I, O>(lanes: Lanes<I, O>, members: Voice<I, O>[]): void;
17
17
  /**
18
- * Gives a voice the next row of its channel's crowd, made if there is none: a copy of the row it
18
+ * Gives a voice the next row of its channels' crowd, made if there is none: a copy of the row it
19
19
  * had in `from` at `at`, or a new one. `old` holds the crowds being replaced, whose idle state a
20
- * new crowd on the same channel keeps. Rows stay in voice order only because voices join in it.
20
+ * new crowd on the same channels keeps. Rows stay in voice order only because voices join in it.
21
21
  */
22
22
  export declare function join<I, O>(lanes: Lanes<I, O>, v: Voice<I, O>, from: Crowd<I, O> | undefined, at: number | undefined, old: readonly Crowd<I, O>[]): void;
23
23
  /** A crowd row takes up its voice as it now stands: opened once it starts, its fields copied again. */
24
24
  export declare function retouchRow<I, O>(lanes: Lanes<I, O>, c: Crowd<I, O>, p: number, v: Voice<I, O>): void;
25
25
  /**
26
- * Compacts every crowd a quarter or more empty rows: each fill walks every row, and at half a
27
- * crowd replacing a voice a frame carried as many empty rows as live ones.
26
+ * Compacts every crowd a quarter or more empty rows, past 64 of them: each fill walks every row,
27
+ * and at half a crowd replacing a voice a frame carried as many empty rows as live ones.
28
28
  */
29
29
  export declare function compactSparse<I, O>(lanes: Lanes<I, O>): void;
30
- /** A crowd voice left: its row stays, empty, until the crowds are next rebuilt. */
30
+ /** A crowd voice left: its row stays, empty, until the crowd is compacted or rebuilt. */
31
31
  export declare function bury<I, O>(lanes: Lanes<I, O>, c: Crowd<I, O>, p: number): void;
32
32
  /** A crowd row's weight as `reported` reads a lane position's. */
33
33
  export declare function reportedRow<I, O>(lanes: Lanes<I, O>, c: Crowd<I, O>, p: number, slot: number): number | undefined;
package/dist/rows.js CHANGED
@@ -75,9 +75,9 @@ export function regroup(lanes, members) {
75
75
  }
76
76
  }
77
77
  /**
78
- * Gives a voice the next row of its channel's crowd, made if there is none: a copy of the row it
78
+ * Gives a voice the next row of its channels' crowd, made if there is none: a copy of the row it
79
79
  * had in `from` at `at`, or a new one. `old` holds the crowds being replaced, whose idle state a
80
- * new crowd on the same channel keeps. Rows stay in voice order only because voices join in it.
80
+ * new crowd on the same channels keeps. Rows stay in voice order only because voices join in it.
81
81
  */
82
82
  export function join(lanes, v, from, at, old) {
83
83
  const chans = v.slots.map((s) => lanes.bySlot[s]);
@@ -113,8 +113,8 @@ export function retouchRow(lanes, c, p, v) {
113
113
  c.hot[h + 0 /* Hot.FLAGS */] = c.hot[h + 0 /* Hot.FLAGS */] | 64 /* Flag.VOICE */;
114
114
  }
115
115
  /**
116
- * Compacts every crowd a quarter or more empty rows: each fill walks every row, and at half a
117
- * crowd replacing a voice a frame carried as many empty rows as live ones.
116
+ * Compacts every crowd a quarter or more empty rows, past 64 of them: each fill walks every row,
117
+ * and at half a crowd replacing a voice a frame carried as many empty rows as live ones.
118
118
  */
119
119
  export function compactSparse(lanes) {
120
120
  for (const c of lanes.crowds)
@@ -154,7 +154,7 @@ function compact(c) {
154
154
  c.laws.length = q;
155
155
  c.dead = 0;
156
156
  }
157
- /** A crowd voice left: its row stays, empty, until the crowds are next rebuilt. */
157
+ /** A crowd voice left: its row stays, empty, until the crowd is compacted or rebuilt. */
158
158
  export function bury(lanes, c, p) {
159
159
  const v = c.voices[p];
160
160
  leaveCrowd(lanes, c, p);
package/dist/sample.d.ts CHANGED
@@ -11,9 +11,10 @@ export declare function runMotion<I, O>(lanes: Lanes<I, O>, lane: Lane<I, O>, ru
11
11
  * A motion voice's value for a subject, sampled from the patch's state as its `at` would, but only
12
12
  * where that sample changes nothing: one with a change to stamp, commit or let go of goes to the
13
13
  * general path, which samples it if and when a probe asks, as it would with no lanes. A probe
14
- * this frame already read keeps the value it read, as the general path's record would.
14
+ * this frame already read keeps the value it read, as the general path's record would. The
15
+ * subject's voice time, delay and weight are in `Arg`.
15
16
  */
16
- export declare function move<I, O>(lanes: Lanes<I, O>, lane: Positions<I, O>, run: Motions<I>, p: number, slot: number, held: Subject<unknown>, elapsed: number, delay: number, w: number): void;
17
+ export declare function move<I, O>(lanes: Lanes<I, O>, lane: Positions<I, O>, run: Motions<I>, p: number, slot: number, held: Subject<unknown>): void;
17
18
  /**
18
19
  * A probe this frame read a motion voice's value for the subject from an earlier fill: write onto
19
20
  * its record what the general path's sample would have, so a later read this frame reuses it.
package/dist/sample.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { unbareLane } from './bare.js';
2
2
  import { foldNumber } from './channels.js';
3
- import { clampWeight, passAt, weighed } from './clock.js';
3
+ import { clampWeight, passAt, same, weighed } from './clock.js';
4
4
  import { absent } from './numbers.js';
5
5
  import { reading } from './reading.js';
6
6
  import { between, left, progress } from './tweened.js';
@@ -26,6 +26,7 @@ export function runMotion(lanes, lane, run) {
26
26
  // On a frame's first fill no probe has read a subject yet, so none needs `move` for that.
27
27
  const probed = lanes.probes !== from;
28
28
  const whole = clampWeight(voice.weight);
29
+ const arg = lanes.arg;
29
30
  const n = run.n;
30
31
  const op = ch.op;
31
32
  if (n > 0) {
@@ -48,7 +49,7 @@ export function runMotion(lanes, lane, run) {
48
49
  run.watchId = voice.id;
49
50
  }
50
51
  const own = ease !== undefined && !signal && run.watcher === lanes && run.watchId === voice.id;
51
- const folds = n === ch.axes;
52
+ const folds = n === ch.axes && ch.plain;
52
53
  for (let p = 0; p < list.length; p++) {
53
54
  // Its signal just made kept state: no further call this fill, the general path makes them.
54
55
  if (signal && voice.keeping)
@@ -64,14 +65,14 @@ export function runMotion(lanes, lane, run) {
64
65
  }
65
66
  const delay = data[o + 0 /* Row.DELAY */];
66
67
  const elapsed = elapsedNow - delay;
67
- if (elapsed < 0) {
68
+ if (!(elapsed >= 0)) {
68
69
  unbareLane(lane, p);
69
70
  data[o + 2 /* Row.WEIGHT */] = 0;
70
71
  continue;
71
72
  }
72
73
  if (!flat) {
73
74
  const since = data[o + 1 /* Row.SINCE */];
74
- if (!lane.weighed || !Object.is(since, lane.weighedSince)) {
75
+ if (!lane.weighed || !same(since, lane.weighedSince)) {
75
76
  lane.fade = lanes.host.envelope(voice, since);
76
77
  lane.weighed = true;
77
78
  lane.weighedSince = since;
@@ -79,11 +80,14 @@ export function runMotion(lanes, lane, run) {
79
80
  }
80
81
  let w;
81
82
  if (signal) {
82
- w = lanes.signalled(voice, slot, records[p], elapsed, passAt(elapsed, voice.duration, voice.passes), lane.fade);
83
- if (Number.isNaN(w)) {
83
+ arg[1 /* Arg.ELAPSED */] = elapsed;
84
+ arg[3 /* Arg.PASS */] = passAt(elapsed, voice.duration, voice.passes);
85
+ arg[5 /* Arg.FADE */] = lane.fade;
86
+ if (!lanes.signalled(voice, slot, records[p])) {
84
87
  data[o + 2 /* Row.WEIGHT */] = 0;
85
88
  continue;
86
89
  }
90
+ w = arg[0 /* Arg.WEIGHT */];
87
91
  }
88
92
  else
89
93
  w = flat && !parts ? whole : weighed(voice.weight, lane.fade, lanes.parting(voice, slot));
@@ -102,7 +106,10 @@ export function runMotion(lanes, lane, run) {
102
106
  (probed &&
103
107
  (per[q + 3 /* Per.LANE_PROBE */] > from ||
104
108
  per[q + 4 /* Per.GENERAL_PROBE */] > from))) {
105
- move(lanes, lane, run, p, slot, records[p], elapsed, delay, w);
109
+ arg[1 /* Arg.ELAPSED */] = elapsed;
110
+ arg[4 /* Arg.DELAY */] = delay;
111
+ arg[0 /* Arg.WEIGHT */] = w;
112
+ move(lanes, lane, run, p, slot, records[p]);
106
113
  continue;
107
114
  }
108
115
  const runs = run.runs;
@@ -123,7 +130,7 @@ export function runMotion(lanes, lane, run) {
123
130
  continue;
124
131
  }
125
132
  if (ease !== undefined) {
126
- // A tween's closed form, read straight from its run: release time, seconds, `x0`, `to`.
133
+ // A tween's closed form, read straight from its run: release time, length, `x0`, `to`.
127
134
  const share = left(ease, progress(elapsed, runs[b], runs[b + 2]));
128
135
  const x = b + 3;
129
136
  const g = x + 2 * n;
@@ -146,8 +153,10 @@ export function runMotion(lanes, lane, run) {
146
153
  for (let a = 0; a < n; a++)
147
154
  values[base + a] = foldNumber(op, values[base + a], xs[a], w);
148
155
  }
149
- else
150
- lanes.foldInto(ch, slot, run.value(ms, xs), w);
156
+ else {
157
+ arg[0 /* Arg.WEIGHT */] = w;
158
+ lanes.foldInto(ch, slot, run.value(ms, xs));
159
+ }
151
160
  }
152
161
  if (own) {
153
162
  lane.bareFill = fills;
@@ -159,14 +168,22 @@ export function runMotion(lanes, lane, run) {
159
168
  * A motion voice's value for a subject, sampled from the patch's state as its `at` would, but only
160
169
  * where that sample changes nothing: one with a change to stamp, commit or let go of goes to the
161
170
  * general path, which samples it if and when a probe asks, as it would with no lanes. A probe
162
- * this frame already read keeps the value it read, as the general path's record would.
171
+ * this frame already read keeps the value it read, as the general path's record would. The
172
+ * subject's voice time, delay and weight are in `Arg`.
163
173
  */
164
- export function move(lanes, lane, run, p, slot, held, elapsed, delay, w) {
174
+ export function move(lanes, lane, run, p, slot, held) {
175
+ const elapsed = lanes.arg[1 /* Arg.ELAPSED */];
176
+ const delay = lanes.arg[4 /* Arg.DELAY */];
177
+ const w = lanes.arg[0 /* Arg.WEIGHT */];
165
178
  const voice = lane.voiceAt(p);
166
179
  const data = lane.data;
167
180
  const o = p * 8 /* Row.STRIDE */;
168
181
  const ch = lane.chans[0];
169
182
  settle(lanes, lane, p, slot, held);
183
+ // The general path never samples a voice at weight 0, so the motion neither numbers the subject
184
+ // nor runs on for it, and `inert` reads the same.
185
+ if (!(w > 0))
186
+ return;
170
187
  const delta = held.delta;
171
188
  if (delta === null ||
172
189
  held.probed !== lanes.now ||
@@ -200,16 +217,16 @@ export function move(lanes, lane, run, p, slot, held, elapsed, delay, w) {
200
217
  return;
201
218
  // A sample of other axes than the channel's folds as the general path folds it.
202
219
  if (n === ch.axes && run.scalar(ms) === ch.scalar)
203
- lanes.foldRun(ch, slot, run.xs, w);
220
+ lanes.foldRun(ch, slot, run.xs);
204
221
  else
205
- lanes.foldInto(ch, slot, run.value(ms, run.xs), w);
222
+ lanes.foldInto(ch, slot, run.value(ms, run.xs));
206
223
  return;
207
224
  }
208
225
  lane.deltas[p] = delta;
209
226
  data[o + 5 /* Row.SAMPLED */] = lanes.fills;
210
227
  data[o + 6 /* Row.SEEKS */] = voice.seeks;
211
228
  if (w > 0)
212
- lanes.foldInto(ch, slot, delta[ch.name], w);
229
+ lanes.foldInto(ch, slot, delta[ch.name]);
213
230
  }
214
231
  /** The delta a motion voice's last sample for position `p` made, built from `samples`. */
215
232
  function sampled(lane, p) {