@msb235/blits 0.13.0 → 0.15.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (178) hide show
  1. package/CHANGELOG.md +183 -0
  2. package/README.md +20 -3
  3. package/dist/ahead.d.ts +26 -0
  4. package/dist/ahead.js +264 -0
  5. package/dist/around.d.ts +11 -0
  6. package/dist/around.js +32 -0
  7. package/dist/bare.js +15 -7
  8. package/dist/blank.d.ts +10 -0
  9. package/dist/blank.js +26 -0
  10. package/dist/blend.d.ts +2 -1
  11. package/dist/blend.js +1 -6
  12. package/dist/bound.d.ts +70 -0
  13. package/dist/bound.js +351 -0
  14. package/dist/chain.d.ts +2 -1
  15. package/dist/chain.js +7 -5
  16. package/dist/changes.d.ts +24 -0
  17. package/dist/changes.js +189 -0
  18. package/dist/channels.d.ts +39 -13
  19. package/dist/channels.js +14 -5
  20. package/dist/closed.d.ts +1 -0
  21. package/dist/closed.js +45 -1
  22. package/dist/color.d.ts +12 -10
  23. package/dist/color.js +25 -25
  24. package/dist/columns.js +7 -3
  25. package/dist/contribution.d.ts +30 -0
  26. package/dist/contribution.js +374 -0
  27. package/dist/copyout.d.ts +10 -0
  28. package/dist/copyout.js +69 -24
  29. package/dist/crowd.d.ts +8 -2
  30. package/dist/crowd.js +4 -5
  31. package/dist/cue.d.ts +8 -1
  32. package/dist/cue.js +47 -14
  33. package/dist/cut.d.ts +8 -0
  34. package/dist/cut.js +4 -0
  35. package/dist/drop.d.ts +9 -0
  36. package/dist/drop.js +84 -0
  37. package/dist/due.d.ts +24 -5
  38. package/dist/due.js +113 -33
  39. package/dist/easing.d.ts +4 -4
  40. package/dist/easing.js +3 -3
  41. package/dist/everyone.d.ts +3 -2
  42. package/dist/everyone.js +3 -3
  43. package/dist/fade.d.ts +2 -1
  44. package/dist/fade.js +2 -1
  45. package/dist/fill.d.ts +3 -2
  46. package/dist/fill.js +51 -19
  47. package/dist/fit.d.ts +44 -26
  48. package/dist/fit.js +10 -9
  49. package/dist/fling.d.ts +66 -0
  50. package/dist/fling.js +162 -0
  51. package/dist/fold.d.ts +4 -37
  52. package/dist/fold.js +5 -512
  53. package/dist/gather.d.ts +12 -7
  54. package/dist/gather.js +179 -44
  55. package/dist/gone.js +2 -2
  56. package/dist/grid.d.ts +47 -0
  57. package/dist/grid.js +247 -0
  58. package/dist/handle.d.ts +3 -0
  59. package/dist/handle.js +19 -8
  60. package/dist/held.d.ts +2 -1
  61. package/dist/history.d.ts +6 -5
  62. package/dist/history.js +9 -9
  63. package/dist/hosts.js +9 -15
  64. package/dist/index.d.ts +5 -4
  65. package/dist/keyfill.d.ts +1 -1
  66. package/dist/keyfill.js +5 -3
  67. package/dist/keyrows.d.ts +12 -0
  68. package/dist/keyrows.js +99 -12
  69. package/dist/lane.d.ts +36 -6
  70. package/dist/lane.js +32 -1
  71. package/dist/lanes.d.ts +23 -8
  72. package/dist/lanes.js +29 -8
  73. package/dist/leavings.d.ts +2 -2
  74. package/dist/leavings.js +1 -1
  75. package/dist/lives.d.ts +47 -0
  76. package/dist/lives.js +180 -0
  77. package/dist/meet.d.ts +2 -2
  78. package/dist/meet.js +90 -4
  79. package/dist/mixer.d.ts +61 -35
  80. package/dist/mixer.js +64 -169
  81. package/dist/motion.d.ts +19 -106
  82. package/dist/motion.js +60 -13
  83. package/dist/motions.d.ts +62 -150
  84. package/dist/motions.js +57 -369
  85. package/dist/move.js +24 -1
  86. package/dist/moved.d.ts +1 -1
  87. package/dist/moved.js +16 -7
  88. package/dist/named.d.ts +0 -1
  89. package/dist/named.js +20 -2
  90. package/dist/numbers.d.ts +2 -0
  91. package/dist/numbers.js +4 -0
  92. package/dist/owed.d.ts +2 -0
  93. package/dist/owed.js +4 -0
  94. package/dist/pack.d.ts +3 -1
  95. package/dist/pack.js +4 -3
  96. package/dist/patch.d.ts +7 -3
  97. package/dist/patch.js +6 -2
  98. package/dist/places.d.ts +53 -0
  99. package/dist/places.js +171 -0
  100. package/dist/plot.d.ts +12 -0
  101. package/dist/plot.js +52 -0
  102. package/dist/project.d.ts +4 -4
  103. package/dist/project.js +9 -211
  104. package/dist/pull.js +11 -8
  105. package/dist/qualify.js +84 -21
  106. package/dist/reading.d.ts +6 -0
  107. package/dist/reading.js +5 -0
  108. package/dist/record.d.ts +5 -1
  109. package/dist/record.js +4 -2
  110. package/dist/relink.d.ts +1 -1
  111. package/dist/restored.d.ts +2 -2
  112. package/dist/restored.js +1 -1
  113. package/dist/rests.d.ts +9 -0
  114. package/dist/rests.js +76 -0
  115. package/dist/revive.d.ts +3 -2
  116. package/dist/revive.js +2 -2
  117. package/dist/{locus.d.ts → role.d.ts} +10 -9
  118. package/dist/{locus.js → role.js} +3 -3
  119. package/dist/roles.d.ts +10 -0
  120. package/dist/roles.js +158 -0
  121. package/dist/roster.js +3 -3
  122. package/dist/rotation.d.ts +1 -1
  123. package/dist/rotation.js +1 -1
  124. package/dist/rows.js +1 -1
  125. package/dist/sample.d.ts +2 -2
  126. package/dist/sample.js +33 -7
  127. package/dist/seek.js +13 -5
  128. package/dist/setting.d.ts +27 -0
  129. package/dist/setting.js +55 -0
  130. package/dist/shared.d.ts +1 -1
  131. package/dist/shared.js +1 -1
  132. package/dist/signals.d.ts +64 -32
  133. package/dist/signals.js +20 -18
  134. package/dist/stack.d.ts +5 -5
  135. package/dist/stack.js +5 -5
  136. package/dist/standing.d.ts +2 -1
  137. package/dist/standing.js +3 -2
  138. package/dist/store.d.ts +9 -0
  139. package/dist/store.js +24 -0
  140. package/dist/strays.d.ts +2 -1
  141. package/dist/strays.js +21 -14
  142. package/dist/stretch.d.ts +112 -0
  143. package/dist/stretch.js +9 -0
  144. package/dist/subject.d.ts +142 -0
  145. package/dist/subject.js +1 -0
  146. package/dist/tape.d.ts +9 -5
  147. package/dist/tape.js +5 -1
  148. package/dist/then.d.ts +9 -0
  149. package/dist/then.js +57 -0
  150. package/dist/ticker.d.ts +4 -4
  151. package/dist/transport.d.ts +0 -8
  152. package/dist/transport.js +0 -4
  153. package/dist/turns.d.ts +4 -3
  154. package/dist/turns.js +4 -3
  155. package/dist/types/arc.d.ts +12 -11
  156. package/dist/types/channel.d.ts +12 -8
  157. package/dist/types/history.d.ts +35 -19
  158. package/dist/types/mix.d.ts +222 -161
  159. package/dist/types/motion.d.ts +151 -0
  160. package/dist/types/motion.js +1 -0
  161. package/dist/types/patch.d.ts +69 -34
  162. package/dist/types/place.d.ts +51 -36
  163. package/dist/types/transport.d.ts +20 -18
  164. package/dist/types/voice.d.ts +145 -72
  165. package/dist/types/watch.d.ts +88 -40
  166. package/dist/types.d.ts +1 -0
  167. package/dist/voice.d.ts +9 -141
  168. package/dist/voice.js +30 -79
  169. package/dist/warn.d.ts +2 -0
  170. package/dist/warn.js +1 -1
  171. package/dist/watch.d.ts +2 -2
  172. package/dist/watch.js +5 -3
  173. package/dist/wave.d.ts +4 -3
  174. package/dist/wave.js +3 -2
  175. package/dist/weigh.d.ts +7 -1
  176. package/dist/weigh.js +27 -0
  177. package/docs/schema.html +223 -37
  178. package/package.json +6 -2
package/dist/roles.js ADDED
@@ -0,0 +1,158 @@
1
+ import { first } from './clone.js';
2
+ import { open as openRecord } from './everyone.js';
3
+ import { merged, unband } from './fold.js';
4
+ import { roleScratch } from './role.js';
5
+ // V8 reads a local const as a constant, where it reads an import from its module on every call.
6
+ const open = openRecord;
7
+ /**
8
+ * Whether a fold's next voice is its chain's next record, `held`, and not the next voice sharing
9
+ * one record, `sharers[q]`: the two lists are each in voice order, and a fold takes them as one.
10
+ */
11
+ export function chainFirst(held, sharers, q) {
12
+ if (held === null)
13
+ return false;
14
+ if (q >= sharers.length || held.voice === null)
15
+ return true;
16
+ const next = sharers[q];
17
+ return next.id > held.voice.id;
18
+ }
19
+ /** `foldWith`'s voices with a role in play: each role folds at its first member's place. */
20
+ export function foldRoles(subject, pose, head, laned, dry, except, owed) {
21
+ const now = this.now;
22
+ // A patch reading the pose can fold again from inside this one, so each depth has its own.
23
+ let k = this.roleScratch[this.roleDepth];
24
+ if (k === undefined) {
25
+ k = roleScratch(this.channels.length);
26
+ this.roleScratch[this.roleDepth] = k;
27
+ }
28
+ this.roleDepth++;
29
+ try {
30
+ k.n = 0;
31
+ k.m = 0;
32
+ k.names.length = 0;
33
+ const sharers = this.sharers;
34
+ let q = 0;
35
+ let at = head;
36
+ while (at !== null || q < sharers.length) {
37
+ const mine = chainFirst(at, sharers, q);
38
+ let voice;
39
+ let rec;
40
+ if (mine) {
41
+ rec = at;
42
+ voice = rec.voice;
43
+ at = rec.next;
44
+ }
45
+ else {
46
+ voice = sharers[q++];
47
+ rec = voice.state === 'pending' ? null : voice.everyone;
48
+ }
49
+ if (voice === null || rec === null)
50
+ continue;
51
+ if (laned && voice.laned && !this.owedBy(owed, voice))
52
+ continue;
53
+ if (voice.id === except || voice.state === 'done')
54
+ continue;
55
+ const held = rec;
56
+ if (!mine)
57
+ open(voice, held, head.slot);
58
+ const delta = this.read(voice, subject, now, dry, held);
59
+ if (owed >= 0 && voice.laned)
60
+ this.lanes.paid(voice.id, owed, held.weight);
61
+ if (!mine)
62
+ q = this.shared(voice, head.slot, held, q);
63
+ if (delta === null)
64
+ continue;
65
+ const role = voice.spec.role;
66
+ let group = -1;
67
+ if (role !== undefined) {
68
+ group = k.names.indexOf(role);
69
+ const m = k.m++;
70
+ k.mVoices[m] = voice;
71
+ k.mDeltas[m] = delta;
72
+ k.mWeights[m] = this.w;
73
+ if (group >= 0) {
74
+ k.mGroups[m] = group;
75
+ continue;
76
+ }
77
+ group = k.names.length;
78
+ k.names.push(role);
79
+ k.mGroups[m] = group;
80
+ }
81
+ const n = k.n++;
82
+ k.voices[n] = voice;
83
+ k.helds[n] = held;
84
+ k.deltas[n] = delta;
85
+ k.weights[n] = role === undefined ? this.w : 0;
86
+ k.groups[n] = group;
87
+ }
88
+ let bands = head.roles;
89
+ if (bands === null) {
90
+ bands = new Map();
91
+ head.roles = bands;
92
+ }
93
+ for (let n = 0; n < k.n; n++) {
94
+ const group = k.groups[n];
95
+ if (group < 0) {
96
+ const weight = k.weights[n];
97
+ if (weight > 0)
98
+ this.apply(pose, k.voices[n], k.helds[n], k.deltas[n], weight);
99
+ else
100
+ unband(k.helds[n]);
101
+ continue;
102
+ }
103
+ const weight = this.foldRole(k, group);
104
+ const name = k.names[group];
105
+ if (!(weight > 0)) {
106
+ bands.get(name)?.fill(2);
107
+ continue;
108
+ }
109
+ let on = bands.get(name);
110
+ if (on === undefined) {
111
+ on = new Uint8Array(this.channels.length);
112
+ bands.set(name, on);
113
+ }
114
+ for (let t = 0; t < k.touched.length; t++) {
115
+ const slot = k.touched[t];
116
+ const value = k.values[slot];
117
+ k.values[slot] = undefined;
118
+ const key = this.names[slot];
119
+ const channel = this.channels[slot];
120
+ // A channel folds at the weight of the members that wrote it, not the whole role.
121
+ const taken = k.taken[slot];
122
+ const w = taken > 1 ? 1 : taken;
123
+ if (channel.rest !== undefined && channel.scale) {
124
+ // pose[key] is the copy of rest this fold made, so it is ours to write into.
125
+ pose[key] = channel.fold
126
+ ? channel.fold(pose[key], value, w)
127
+ : merged(channel, pose[key], channel.scale(value, w));
128
+ continue;
129
+ }
130
+ const band = on[slot];
131
+ const passes = this.passes(band === 0 ? undefined : band === 1, w);
132
+ on[slot] = passes ? 1 : 2;
133
+ if (passes)
134
+ pose[key] = pose[key] === undefined ? first(value) : merged(channel, pose[key], value);
135
+ }
136
+ }
137
+ return pose;
138
+ }
139
+ finally {
140
+ for (let n = 0; n < k.n; n++) {
141
+ k.voices[n] = null;
142
+ k.helds[n] = null;
143
+ k.deltas[n] = null;
144
+ }
145
+ for (let m = 0; m < k.m; m++) {
146
+ k.mVoices[m] = null;
147
+ k.mDeltas[m] = null;
148
+ }
149
+ // Left set only where a channel's lerp threw partway, which the next fold must not inherit.
150
+ for (let t = 0; t < k.touched.length; t++) {
151
+ const slot = k.touched[t];
152
+ k.met[slot] = 0;
153
+ k.values[slot] = undefined;
154
+ }
155
+ k.touched.length = 0;
156
+ this.roleDepth--;
157
+ }
158
+ }
package/dist/roster.js CHANGED
@@ -2,8 +2,8 @@ import { index, unindex } from './chain.js';
2
2
  import { detour } from './everyone.js';
3
3
  import { Store } from './store.js';
4
4
  function tally(mix, voice, by) {
5
- if (voice.spec.locus !== undefined)
6
- mix.loci += by;
5
+ if (voice.spec.role !== undefined)
6
+ mix.roleVoices += by;
7
7
  const e = voice.spec.earliest;
8
8
  if (voice.spec.anchor !== undefined || (e !== undefined && typeof e !== 'number'))
9
9
  mix.anchored += by;
@@ -36,7 +36,7 @@ export function recount(mix) {
36
36
  mix.naming = 0;
37
37
  mix.general = [];
38
38
  mix.sharers = [];
39
- mix.loci = 0;
39
+ mix.roleVoices = 0;
40
40
  mix.anchored = 0;
41
41
  for (const voice of mix.cued) {
42
42
  index(mix, voice);
@@ -10,7 +10,7 @@ export interface AngleOptions {
10
10
  turn?: number;
11
11
  }
12
12
  /**
13
- * A rotation about one axis, which adds as `sum` does and interpolates the short way round: from
13
+ * A rotation about one axis that interpolates the short way round. It adds as `sum` does, and from
14
14
  * 350 to 10 it passes 0, not 180. A weight below 1 fades it the short way to rest too. Its values
15
15
  * are angles, so two a whole number of turns apart are the same value, and a fold may answer with
16
16
  * either.
package/dist/rotation.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { doubles, vouch } from './channels.js';
2
2
  import { shortWay } from './turns.js';
3
3
  /**
4
- * A rotation about one axis, which adds as `sum` does and interpolates the short way round: from
4
+ * A rotation about one axis that interpolates the short way round. It adds as `sum` does, and from
5
5
  * 350 to 10 it passes 0, not 180. A weight below 1 fades it the short way to rest too. Its values
6
6
  * are angles, so two a whole number of turns apart are the same value, and a fold may answer with
7
7
  * either.
package/dist/rows.js CHANGED
@@ -25,7 +25,7 @@ export function unplace(lanes, c, p) {
25
25
  c.records[p] = undefined;
26
26
  c.deltas[p] = null;
27
27
  const f = p * c.stride + 0 /* Hot.FLAGS */;
28
- c.hot[f] = c.hot[f] & ~16 /* Flag.PLACED */;
28
+ c.hot[f] = c.hot[f] & ~(16 /* Flag.PLACED */ | 2048 /* Flag.READY */);
29
29
  const o = p * 8 /* Row.STRIDE */;
30
30
  c.data[o + 4 /* Row.MSLOT */] = -1;
31
31
  c.data[o + 5 /* Row.SAMPLED */] = -1;
package/dist/sample.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import { type Lane, type Positions } from './lane.js';
2
2
  import type { Lanes } from './lanes.js';
3
- import type { Motions } from './motions.js';
4
- import type { Subject } from './voice.js';
3
+ import { type Motions } from './motions.js';
4
+ import type { Subject } from './subject.js';
5
5
  /**
6
6
  * A motion voice's fill: what `one` and `move` do for each subject, in one loop for the common
7
7
  * case, a subject not yet probed this frame with nothing pending, and through `move` for the rest.
package/dist/sample.js CHANGED
@@ -1,6 +1,7 @@
1
1
  import { unbareLane } from './bare.js';
2
2
  import { foldNumber } from './channels.js';
3
3
  import { clampWeight, passAt, same, weighed } from './clock.js';
4
+ import { keepProbed } from './lane.js';
4
5
  import { absent } from './numbers.js';
5
6
  import { reading } from './reading.js';
6
7
  import { between, left, progress } from './tweened.js';
@@ -50,6 +51,11 @@ export function runMotion(lanes, lane, run) {
50
51
  }
51
52
  const own = ease !== undefined && !signal && run.watcher === lanes && run.watchId === voice.id;
52
53
  const folds = n === ch.axes && ch.plain;
54
+ // The weight a voice with a fade and no subject ramps gives every subject at one fade: worked
55
+ // out again for a zero, whose sign a hit by `===` would not keep.
56
+ let fadeOf = Number.NaN;
57
+ let baseOf = Number.NaN;
58
+ let faded = 0;
53
59
  for (let p = 0; p < list.length; p++) {
54
60
  // Its signal just made kept state: no further call this fill, the general path makes them.
55
61
  if (signal && voice.keeping)
@@ -57,8 +63,7 @@ export function runMotion(lanes, lane, run) {
57
63
  const slot = list[p];
58
64
  const o = p * 8 /* Row.STRIDE */;
59
65
  const q = slot * 6 /* Per.SLOT */;
60
- if (per[q + 5 /* Per.LANE_FILL */] === fills - 1)
61
- data[o + 3 /* Row.PROBED */] = data[o + 2 /* Row.WEIGHT */];
66
+ keepProbed(data, o, per, q, fills);
62
67
  if (lately >= 0 && lanes.unread(slot)) {
63
68
  unbareLane(lane, p);
64
69
  continue;
@@ -89,8 +94,20 @@ export function runMotion(lanes, lane, run) {
89
94
  }
90
95
  w = arg[0 /* Arg.WEIGHT */];
91
96
  }
92
- else
93
- w = flat && !parts ? whole : weighed(voice.weight, lane.fade, lanes.parting(voice, slot));
97
+ else if (parts)
98
+ w = weighed(voice.weight, lane.fade, lanes.parting(voice, slot));
99
+ else if (flat)
100
+ w = whole;
101
+ else {
102
+ const fade = lane.fade;
103
+ const base = voice.weight;
104
+ if (fade !== fadeOf || base !== baseOf || fade === 0 || base === 0) {
105
+ fadeOf = fade;
106
+ baseOf = base;
107
+ faded = weighed(base, fade, 1);
108
+ }
109
+ w = faded;
110
+ }
94
111
  data[o + 2 /* Row.WEIGHT */] = w;
95
112
  if (data[o + 7 /* Row.MET */] === 0) {
96
113
  if (Number.isNaN(records[p].probed)) {
@@ -101,7 +118,16 @@ export function runMotion(lanes, lane, run) {
101
118
  data[o + 7 /* Row.MET */] = 1;
102
119
  }
103
120
  const ms = data[o + 4 /* Row.MSLOT */];
104
- const b = ms < 0 ? -1 : run.bareAt(ms);
121
+ // `bareAt` and `scalar` written out: V8 leaves them and `base` out of line here.
122
+ let b = -1;
123
+ let scalar = false;
124
+ if (ms >= 0) {
125
+ b = 5 /* Law.HEAD */ + ms * run.stride;
126
+ const f = run.runs[b + 1];
127
+ if ((f & (2 /* RunFlag.PENDING */ | 4 /* RunFlag.OLDER */)) !== 0)
128
+ b = -1;
129
+ scalar = (f & 1 /* RunFlag.SCALAR */) !== 0;
130
+ }
105
131
  if (b < 0 ||
106
132
  (probed &&
107
133
  (per[q + 3 /* Per.LANE_PROBE */] > from ||
@@ -113,7 +139,7 @@ export function runMotion(lanes, lane, run) {
113
139
  continue;
114
140
  }
115
141
  const runs = run.runs;
116
- if (own && folds && run.scalar(ms) === ch.scalar) {
142
+ if (own && folds && scalar === ch.scalar) {
117
143
  if (data[o + 5 /* Row.SAMPLED */] !== -2 /* Sampled.BARE */) {
118
144
  data[o + 5 /* Row.SAMPLED */] = -2 /* Sampled.BARE */;
119
145
  lane.deltas[p] = null;
@@ -147,7 +173,7 @@ export function runMotion(lanes, lane, run) {
147
173
  data[o + 6 /* Row.SEEKS */] = seeks;
148
174
  if (w <= 0)
149
175
  continue;
150
- if (folds && run.scalar(ms) === ch.scalar) {
176
+ if (folds && scalar === ch.scalar) {
151
177
  const values = ch.values;
152
178
  const base = slot * n;
153
179
  for (let a = 0; a < n; a++)
package/dist/seek.js CHANGED
@@ -9,6 +9,7 @@ import { move, nextFrame } from './move.js';
9
9
  import { ownerReading } from './owner.js';
10
10
  import { place } from './place.js';
11
11
  import { unpull } from './pull.js';
12
+ import { isRested } from './record.js';
12
13
  import { Restored } from './restored.js';
13
14
  import { recount } from './roster.js';
14
15
  import { refitAll } from './spans.js';
@@ -33,8 +34,7 @@ function restore(mix, voice, live, at, every, mine) {
33
34
  const h = mine && live.reaches ? unread(live) : copyHeld(voice, live);
34
35
  if (h.stepped > t)
35
36
  h.stepped = t;
36
- // Whether it had rested by `t` is not kept; the next probe finds it again.
37
- h.rested = false;
37
+ restedBy(h, live, t);
38
38
  h.weight = 0;
39
39
  h.inputs = inputs;
40
40
  if (!(h.since <= t))
@@ -63,6 +63,7 @@ function restore(mix, voice, live, at, every, mine) {
63
63
  else
64
64
  h = copyHeld(voice, snap.held, true);
65
65
  h.weight = 0;
66
+ restedBy(h, live, t);
66
67
  if (snap.at < t)
67
68
  h.copied = true;
68
69
  const kept = i + 1;
@@ -108,6 +109,13 @@ function restore(mix, voice, live, at, every, mine) {
108
109
  }
109
110
  return h;
110
111
  }
112
+ /**
113
+ * Whether a record put back for mix time `t` had rested by then, from when the live one did: a
114
+ * subject may have rested and moved on since, so a probe at `t` would not find it at rest again.
115
+ */
116
+ function restedBy(h, live, t) {
117
+ h.restedAt = live.restedAt <= t ? live.restedAt : Number.NaN;
118
+ }
111
119
  /** Cuts a record's list of copies to its first `kept`, keeping those no other list holds as spares. */
112
120
  function trim(voice, snaps, kept) {
113
121
  while (snaps.length > kept) {
@@ -188,7 +196,7 @@ function restoreVoice(mix, voice, t, seq) {
188
196
  return h;
189
197
  if (h.reaches)
190
198
  voice.seen++;
191
- if (h.rested)
199
+ if (isRested(h))
192
200
  voice.restedCount++;
193
201
  if (!(h.since <= restored.top)) {
194
202
  voice.early ??= [];
@@ -350,7 +358,7 @@ function back(mix, t, seq, on) {
350
358
  if (voice.parts !== null || voice.parted !== null)
351
359
  mix.parters.add(voice);
352
360
  mix.relinks++;
353
- mix.pose = new Store();
361
+ mix.lastPoses = new Store();
354
362
  if (mix.restStamps !== null)
355
363
  mix.restStamps = new Store();
356
364
  unpull(mix);
@@ -393,7 +401,7 @@ function back(mix, t, seq, on) {
393
401
  mix.pins.set(voice, voice.pinned);
394
402
  }
395
403
  refitAll(mix);
396
- mix.due = [];
404
+ mix.due.clear();
397
405
  for (const voice of kept)
398
406
  schedule(mix, voice);
399
407
  mix.stir();
@@ -0,0 +1,27 @@
1
+ import type { Setting } from './types.js';
2
+ import type { Voice } from './voice.js';
3
+ /**
4
+ * A voice's `setting.keep`, one per voice: a setting is valid only during its call, so the state
5
+ * goes on the record of the subject being called for, `keepOn`, or on the voice before any is.
6
+ */
7
+ export declare function keeping<I, O>(voice: Voice<I, O>): Setting['keep'];
8
+ /**
9
+ * A voice's setting. `keep` is made on first read: most patches keep no state, and a closure and
10
+ * its context per voice cost a one-subject tween voice about 100 bytes.
11
+ */
12
+ export declare class VoiceSetting<I, O> implements Setting<unknown> {
13
+ host: unknown;
14
+ readonly send: (event: unknown) => void;
15
+ private readonly voice;
16
+ timestamp: number;
17
+ dt: number;
18
+ elapsed: number;
19
+ pass: number;
20
+ weight: number;
21
+ state: unknown;
22
+ /** The `Arg.MARK` a lane last filled `timestamp`, `elapsed` and `pass` under; 0 once anything else writes them. */
23
+ mark: number;
24
+ private kept;
25
+ constructor(host: unknown, send: (event: unknown) => void, voice: Voice<I, O>);
26
+ get keep(): Setting['keep'];
27
+ }
@@ -0,0 +1,55 @@
1
+ import { reading } from './reading.js';
2
+ /**
3
+ * A voice's `setting.keep`, one per voice: a setting is valid only during its call, so the state
4
+ * goes on the record of the subject being called for, `keepOn`, or on the voice before any is.
5
+ */
6
+ export function keeping(voice) {
7
+ return function keep(owner, init) {
8
+ const held = voice.keepOn;
9
+ let kept = held === null ? voice.ownKept : held.kept;
10
+ if (kept === null) {
11
+ kept = new Map();
12
+ if (held === null)
13
+ voice.ownKept = kept;
14
+ else
15
+ held.kept = kept;
16
+ }
17
+ if (kept.has(owner))
18
+ return kept.get(owner);
19
+ const unkept = held?.unkept;
20
+ const made = unkept === undefined ? init() : unkept.shift();
21
+ if (unkept?.length === 0 && held !== null)
22
+ held.unkept = undefined;
23
+ kept.set(owner, made);
24
+ reading.kept++;
25
+ return made;
26
+ };
27
+ }
28
+ /**
29
+ * A voice's setting. `keep` is made on first read: most patches keep no state, and a closure and
30
+ * its context per voice cost a one-subject tween voice about 100 bytes.
31
+ */
32
+ export class VoiceSetting {
33
+ host;
34
+ send;
35
+ voice;
36
+ timestamp = 0;
37
+ dt = 0;
38
+ elapsed = 0;
39
+ pass = 0;
40
+ weight = 0;
41
+ state = undefined;
42
+ /** The `Arg.MARK` a lane last filled `timestamp`, `elapsed` and `pass` under; 0 once anything else writes them. */
43
+ mark = 0;
44
+ kept = null;
45
+ constructor(host, send, voice) {
46
+ this.host = host;
47
+ this.send = send;
48
+ this.voice = voice;
49
+ }
50
+ get keep() {
51
+ if (this.kept === null)
52
+ this.kept = keeping(this.voice);
53
+ return this.kept;
54
+ }
55
+ }
package/dist/shared.d.ts CHANGED
@@ -2,7 +2,7 @@
2
2
  * The package version, for a fact whose shape follows the code too closely to version by hand:
3
3
  * copies of different releases keep apart. `test/copies.test.ts` holds it to `package.json`.
4
4
  */
5
- export declare const release = "0.13.0";
5
+ export declare const release = "0.15.0";
6
6
  /**
7
7
  * A WeakMap every copy of blits loaded in one realm shares, so a fact one copy records about a
8
8
  * patch or channel it made is read by another copy's mix. The name carries a version, bumped when
package/dist/shared.js CHANGED
@@ -2,7 +2,7 @@
2
2
  * The package version, for a fact whose shape follows the code too closely to version by hand:
3
3
  * copies of different releases keep apart. `test/copies.test.ts` holds it to `package.json`.
4
4
  */
5
- export const release = '0.13.0';
5
+ export const release = '0.15.0';
6
6
  /**
7
7
  * A WeakMap every copy of blits loaded in one realm shares, so a fact one copy records about a
8
8
  * patch or channel it made is read by another copy's mix. The name carries a version, bumped when
package/dist/signals.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { Setting, Signal } from './types.js';
1
+ import type { Band, Setting, Signal } from './types.js';
2
2
  /** Whether a change to an input signal reaches `reading.inputs`, so a lane fill can see it. */
3
3
  export declare const heard: (signal: object) => boolean;
4
4
  /**
@@ -10,15 +10,24 @@ export declare function peak<I, H = unknown>(...signals: readonly Signal<I, H>[]
10
10
  /** A blend member's share of its signal's read `k`: the members' stops sit evenly along 0..1. */
11
11
  export declare function shareOf(k: number, i: number, stops: number): number;
12
12
  /**
13
- * Member `i` of `n`'s share of `by`, as `mix.blend` weighs it: the hat of `by`'s read at the
14
- * member's stop, the stops spaced evenly from 0 to 1. Weights a voice outside a blend as one of
15
- * its members, such as one member played alone.
13
+ * Member `i` of `n`'s share of `by`, as `mix.blend` weighs it. It is the hat of `by`'s read at the
14
+ * member's stop, the stops spaced evenly from 0 to 1. Weights a voice outside a blend as one of its
15
+ * members, such as one member played alone.
16
16
  *
17
17
  * @category signal
18
18
  */
19
19
  export declare function share<I, H = unknown>(by: Signal<I, H>, i: number, n: number): Signal<I, H>;
20
20
  /**
21
- * Marks `read` as a signal driven from outside the clock, which a host's own signal has to be for
21
+ * The signal `input` returns, with `touch` to report a change.
22
+ *
23
+ * @category signal
24
+ */
25
+ export interface Input<I, H = unknown> extends Signal<I, H> {
26
+ /** Reports a change to what the signal reads. */
27
+ touch(): void;
28
+ }
29
+ /**
30
+ * Marks `read` as a signal driven from outside the clock. A host's own signal has to be one for
22
31
  * `assess` and a history's `inputs` to treat it as one. Two uses:
23
32
  *
24
33
  * `input(read)` is an input of the host's own: a pointer, a volume, a value in a store. The host
@@ -32,54 +41,77 @@ export declare function share<I, H = unknown>(by: Signal<I, H>, i: number, n: nu
32
41
  *
33
42
  * @category signal
34
43
  */
35
- export declare function input<I, H = unknown>(read: (subject: I, setting: Setting<void, H>) => number, of?: readonly Signal<I, H>[]): Signal<I, H> & {
36
- touch(): void;
37
- };
44
+ export declare function input<I, H = unknown>(read: (subject: I, setting: Setting<void, H>) => number, of?: readonly Signal<I, H>[]): Input<I, H>;
38
45
  /**
39
- * A signal the host writes: `input` over one number, with `set` reporting each change. klieg's
40
- * `level`.
46
+ * The signal `level` returns, with `set` to write it.
41
47
  *
42
48
  * @category signal
43
49
  */
44
- export declare function level<I, H = unknown>(initial?: number): Signal<I, H> & {
50
+ export interface Level<I, H = unknown> extends Signal<I, H> {
51
+ /** Writes the level, reporting the change; writing the value it holds already does nothing. */
45
52
  set(v: number): void;
46
- };
53
+ }
47
54
  /**
48
- * The package's linear decay primitive: follows its input at a rate limit, no faster than `riseMs` per
49
- * unit climbing or `fallMs` draining. The mix keeps its state per voice and subject, so two voices
50
- * handed the same slew each follow on their own. A subject starts on its input, or at `from`
51
- * unless `dt` is infinite.
55
+ * A signal the host writes. It is `input` over one number, with `set` reporting each change.
56
+ * klieg's `level`.
57
+ *
58
+ * @category signal
59
+ */
60
+ export declare function level<I, H = unknown>(initial?: number): Level<I, H>;
61
+ /**
62
+ * What `slew` takes.
52
63
  *
53
64
  * @category signal
54
65
  */
55
- export declare function slew<I, H = unknown>(of: Signal<I, H>, { riseMs, fallMs, from }: {
66
+ export interface SlewOptions {
67
+ /** Ms per unit climbing; 0 follows at once. Default 0. */
56
68
  riseMs?: number;
69
+ /** Ms per unit draining; 0 follows at once. Default 0. */
57
70
  fallMs?: number;
71
+ /** Where a subject starts, unless `dt` is infinite. Default: on its input. */
58
72
  from?: number;
59
- }): Signal<I, H>;
73
+ }
74
+ /**
75
+ * Follows its input at a rate limit, the package's linear decay primitive. No faster than `riseMs`
76
+ * per unit climbing or `fallMs` draining. The mix keeps its state per voice and subject, so two
77
+ * voices handed the same slew each follow on their own. A subject starts on its input, or at `from`
78
+ * unless `dt` is infinite.
79
+ *
80
+ * @category signal
81
+ */
82
+ export declare function slew<I, H = unknown>(of: Signal<I, H>, { riseMs, fallMs, from }: SlewOptions): Signal<I, H>;
60
83
  /**
61
- * The exponential counterpart to `slew`: closes the same fraction of the distance to its input in
62
- * every equal stretch of time, `1 − exp(−gap / ms)`, so it reads the same however the frames that
63
- * sample it are spaced while the input holds still. `riseMs` and `fallMs` are time constants: after
64
- * one, 63% of the way is covered. 0 follows at once. Within `floor` of its input it lands on it, so a
65
- * subject can reach rest; pass 0 to keep the curve exact. The mix keeps its state per voice and subject.
66
- * A subject starts on its input, or at `from` unless `dt` is infinite.
84
+ * What `lag` takes.
67
85
  *
68
86
  * @category signal
69
87
  */
70
- export declare function lag<I, H = unknown>(of: Signal<I, H>, { riseMs, fallMs, floor, from, }: {
88
+ export interface LagOptions {
89
+ /** The time constant climbing, ms; 0 follows at once. Default 0. */
71
90
  riseMs?: number;
91
+ /** The time constant draining, ms; 0 follows at once. Default 0. */
72
92
  fallMs?: number;
93
+ /** Within this of its input it lands on it; 0 keeps the curve exact. Default 1e-6. */
73
94
  floor?: number;
95
+ /** Where a subject starts, unless `dt` is infinite. Default: on its input. */
74
96
  from?: number;
75
- }): Signal<I, H>;
97
+ }
98
+ /**
99
+ * Closes the same fraction of the distance to its input in every equal stretch of time. The
100
+ * exponential counterpart to `slew`: the fraction is `1 − exp(−gap / ms)`, so it reads the same
101
+ * however the frames that sample it are spaced while the input holds still. `riseMs` and `fallMs`
102
+ * are time constants: after one, 63% of the way is covered. 0 follows at once. Within `floor` of
103
+ * its input it lands on it, so a subject can reach rest; pass 0 to keep the curve exact. The mix
104
+ * keeps its state per voice and subject. A subject starts on its input, or at `from` unless `dt` is
105
+ * infinite.
106
+ *
107
+ * @category signal
108
+ */
109
+ export declare function lag<I, H = unknown>(of: Signal<I, H>, { riseMs, fallMs, floor, from }: LagOptions): Signal<I, H>;
76
110
  /**
77
- * The other half of the pair: a band, not an edge. It drops at `off`, passes at `on`, and between
78
- * the two it holds whatever it last did, so an input resting on a threshold cannot chatter.
111
+ * Passes or drops its input across a band, not an edge. The other half of the pair. It drops at
112
+ * `off`, passes at `on`, and between the two it holds whatever it last did, so an input resting on
113
+ * a threshold cannot chatter.
79
114
  *
80
115
  * @category signal
81
116
  */
82
- export declare function gate<I, H = unknown>(of: Signal<I, H>, band: {
83
- on: number;
84
- off: number;
85
- } | number): Signal<I, H>;
117
+ export declare function gate<I, H = unknown>(of: Signal<I, H>, band: Band | number): Signal<I, H>;