@msb235/blits 0.7.1 → 0.8.1

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 (156) hide show
  1. package/CHANGELOG.md +193 -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 +8 -0
  11. package/dist/clone.js +22 -0
  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 +216 -31
  36. package/dist/frames.d.ts +21 -0
  37. package/dist/frames.js +59 -0
  38. package/dist/gather.d.ts +15 -9
  39. package/dist/gather.js +60 -25
  40. package/dist/gone.js +10 -0
  41. package/dist/handle.d.ts +16 -5
  42. package/dist/handle.js +56 -6
  43. package/dist/held.d.ts +8 -5
  44. package/dist/held.js +56 -58
  45. package/dist/history.d.ts +17 -13
  46. package/dist/history.js +47 -28
  47. package/dist/hosts.js +55 -8
  48. package/dist/index.d.ts +9 -4
  49. package/dist/index.js +5 -2
  50. package/dist/keyfill.d.ts +7 -0
  51. package/dist/keyfill.js +124 -0
  52. package/dist/keyrows.d.ts +1 -0
  53. package/dist/keyrows.js +1 -1
  54. package/dist/lane.d.ts +32 -2
  55. package/dist/lane.js +5 -0
  56. package/dist/lanes.d.ts +32 -8
  57. package/dist/lanes.js +69 -37
  58. package/dist/leavings.d.ts +26 -0
  59. package/dist/leavings.js +70 -0
  60. package/dist/locus.js +2 -2
  61. package/dist/meet.js +14 -8
  62. package/dist/mixer.d.ts +39 -7
  63. package/dist/mixer.js +50 -10
  64. package/dist/motion.d.ts +2 -0
  65. package/dist/motion.js +4 -1
  66. package/dist/motions.d.ts +22 -15
  67. package/dist/motions.js +40 -34
  68. package/dist/move.js +23 -16
  69. package/dist/moved.d.ts +11 -0
  70. package/dist/moved.js +29 -0
  71. package/dist/origin.d.ts +19 -0
  72. package/dist/origin.js +68 -0
  73. package/dist/owed.d.ts +4 -4
  74. package/dist/owed.js +8 -8
  75. package/dist/owner.d.ts +1 -1
  76. package/dist/owner.js +2 -2
  77. package/dist/pace.d.ts +13 -5
  78. package/dist/pace.js +21 -8
  79. package/dist/pack.d.ts +3 -1
  80. package/dist/pack.js +15 -24
  81. package/dist/paging.d.ts +8 -2
  82. package/dist/paging.js +39 -4
  83. package/dist/patch.d.ts +14 -9
  84. package/dist/patch.js +83 -21
  85. package/dist/place.js +18 -3
  86. package/dist/project.d.ts +12 -1
  87. package/dist/project.js +101 -83
  88. package/dist/pull.d.ts +2 -0
  89. package/dist/pull.js +8 -0
  90. package/dist/qualify.js +10 -2
  91. package/dist/reading.d.ts +5 -0
  92. package/dist/reading.js +5 -0
  93. package/dist/record.d.ts +9 -0
  94. package/dist/record.js +42 -0
  95. package/dist/relink.d.ts +11 -5
  96. package/dist/relink.js +7 -5
  97. package/dist/restored.d.ts +41 -0
  98. package/dist/restored.js +91 -0
  99. package/dist/revive.d.ts +10 -3
  100. package/dist/revive.js +31 -23
  101. package/dist/roster.d.ts +11 -0
  102. package/dist/roster.js +46 -0
  103. package/dist/rotation.d.ts +29 -0
  104. package/dist/rotation.js +91 -0
  105. package/dist/rows.d.ts +5 -5
  106. package/dist/rows.js +5 -5
  107. package/dist/sample.d.ts +3 -2
  108. package/dist/sample.js +32 -15
  109. package/dist/seek.js +97 -114
  110. package/dist/shared.d.ts +6 -0
  111. package/dist/shared.js +15 -0
  112. package/dist/signals.d.ts +24 -3
  113. package/dist/signals.js +50 -11
  114. package/dist/spans.js +7 -0
  115. package/dist/stack.d.ts +11 -0
  116. package/dist/stack.js +47 -0
  117. package/dist/standing.d.ts +10 -0
  118. package/dist/standing.js +79 -0
  119. package/dist/store.d.ts +3 -3
  120. package/dist/store.js +3 -3
  121. package/dist/strays.js +3 -0
  122. package/dist/tape.d.ts +8 -5
  123. package/dist/tape.js +21 -13
  124. package/dist/testing.d.ts +35 -0
  125. package/dist/testing.js +116 -0
  126. package/dist/touch.d.ts +6 -0
  127. package/dist/touch.js +28 -0
  128. package/dist/transport.d.ts +45 -6
  129. package/dist/transport.js +64 -19
  130. package/dist/turns.d.ts +18 -0
  131. package/dist/turns.js +72 -0
  132. package/dist/tweened.js +1 -1
  133. package/dist/types/channel.d.ts +46 -0
  134. package/dist/types/channel.js +1 -0
  135. package/dist/types/history.d.ts +139 -0
  136. package/dist/types/history.js +1 -0
  137. package/dist/types/mix.d.ts +336 -0
  138. package/dist/types/mix.js +1 -0
  139. package/dist/types/patch.d.ts +187 -0
  140. package/dist/types/patch.js +1 -0
  141. package/dist/types/place.d.ts +157 -0
  142. package/dist/types/place.js +1 -0
  143. package/dist/types/transport.d.ts +71 -0
  144. package/dist/types/transport.js +1 -0
  145. package/dist/types/voice.d.ts +323 -0
  146. package/dist/types/voice.js +1 -0
  147. package/dist/types.d.ts +7 -1162
  148. package/dist/unpage.d.ts +2 -2
  149. package/dist/unpage.js +53 -25
  150. package/dist/voice.d.ts +90 -22
  151. package/dist/voice.js +105 -43
  152. package/dist/wave.d.ts +42 -0
  153. package/dist/wave.js +62 -0
  154. package/dist/weigh.js +1 -1
  155. package/docs/schema.html +42 -36
  156. package/package.json +8 -3
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) {
package/dist/seek.js CHANGED
@@ -1,10 +1,14 @@
1
- import { index } from './chain.js';
2
1
  import { schedule } from './due.js';
3
- import { copyHeld, last, leftAt } from './history.js';
2
+ import { copyHeld, last, lastWithin } from './history.js';
4
3
  import { laneHost } from './hosts.js';
5
4
  import { Lanes } from './lanes.js';
6
- import { move } from './move.js';
5
+ import { Leavings } from './leavings.js';
6
+ import { move, nextFrame } from './move.js';
7
7
  import { ownerReading } from './owner.js';
8
+ import { place } from './place.js';
9
+ import { unpull } from './pull.js';
10
+ import { Restored } from './restored.js';
11
+ import { recount } from './roster.js';
8
12
  import { refitAll } from './spans.js';
9
13
  import { Store } from './store.js';
10
14
  import { hostAt, replay } from './tape.js';
@@ -35,44 +39,6 @@ function restore(voice, live, t) {
35
39
  h.inputs = inputs;
36
40
  return h;
37
41
  }
38
- /**
39
- * A voice's records after a seek back: each subject's is put back from the records it had, the first
40
- * time it is asked for, since the subjects a store holds cannot be listed.
41
- */
42
- class Restored extends Store {
43
- was;
44
- make;
45
- left;
46
- constructor(was, make, left) {
47
- super();
48
- this.was = was;
49
- this.make = make;
50
- this.left = left;
51
- }
52
- /** Subjects whose record has been put back or deleted, which `left` no longer answers for. */
53
- settled = new Store();
54
- get(key) {
55
- const v = super.get(key);
56
- if (v !== undefined)
57
- return v;
58
- // A subject that left after the moment sought had the record it left with, whatever it has now.
59
- const kept = this.settled.get(key) === undefined ? this.left(key) : undefined;
60
- this.settled.set(key, true);
61
- const live = kept ?? this.was.get(key);
62
- if (live === undefined)
63
- return undefined;
64
- this.was.delete(key);
65
- const made = this.make(live);
66
- if (made !== undefined)
67
- super.set(key, made);
68
- return made;
69
- }
70
- delete(key) {
71
- super.delete(key);
72
- this.was.delete(key);
73
- this.settled.set(key, true);
74
- }
75
- }
76
42
  /** Takes a voice cued after the moment sought out of the mix, until the tape cues it again. */
77
43
  function park(mix, voice) {
78
44
  const owner = voice.owner;
@@ -86,34 +52,27 @@ function park(mix, voice) {
86
52
  motion.owner = null;
87
53
  voice.park();
88
54
  }
89
- /** Puts a voice's controls and records back as they stood at mix time `t`. */
90
- function restoreVoice(voice, t) {
55
+ /** Puts a voice's controls and records back as they stood at mix time `t`, the end of frame `seq`. */
56
+ function restoreVoice(mix, voice, t, seq) {
57
+ const cut = { seq, strict: false };
91
58
  const log = voice.log;
92
- const c = last(log, t, true) ?? log[0];
59
+ const c = lastWithin(log, cut) ?? log[0];
93
60
  log.length = log.indexOf(c) + 1;
94
- voice.anchorNow = c.anchorNow;
95
- voice.anchorElapsed = c.anchorElapsed;
96
- voice.rate = c.rate;
97
- voice.ramp = c.ramp;
98
- voice.weight = c.weight;
99
- voice.out = c.out;
100
- voice.start = c.start;
101
- voice.outAt = c.outAt;
102
- voice.outOver = c.outOver;
103
- voice.outSet = c.outSet;
104
- if (voice.reopen(t) && voice.owner?.holding)
61
+ voice.take(c);
62
+ if (voice.reopen(seq) && voice.owner?.holding)
105
63
  voice.owner.holding.played--;
106
64
  voice.doneAt = Number.POSITIVE_INFINITY;
65
+ voice.doneSeq = Number.POSITIVE_INFINITY;
107
66
  if (voice.parts !== null) {
108
67
  for (const [subject, r] of voice.parts)
109
- if (r.at > t)
68
+ if (r.seq > seq)
110
69
  voice.parts.delete(subject);
111
70
  if (voice.parts.size === 0)
112
71
  voice.parts = null;
113
72
  }
114
73
  if (voice.parted !== null) {
115
- for (const [subject, at] of voice.parted)
116
- if (at > t)
74
+ for (const [subject, p] of voice.parted)
75
+ if (p.seq > seq)
117
76
  voice.parted.delete(subject);
118
77
  if (voice.parted.size === 0)
119
78
  voice.parted = null;
@@ -127,30 +86,43 @@ function restoreVoice(voice, t) {
127
86
  voice.unreached = null;
128
87
  voice.unreachedBits = null;
129
88
  voice.seeks++;
130
- const left = voice.left;
131
- voice.left = left === null ? null : left.filter((e) => e.at <= t);
132
- const future = left === null ? [] : left.filter((e) => e.at > t);
133
- voice.subjects = new Restored(voice.subjects, (live) => {
134
- const h = restore(voice, live, t);
135
- if (h === undefined)
89
+ const left = voice.left?.all() ?? [];
90
+ voice.left = Leavings.from(left.filter((e) => e.seq <= seq));
91
+ const future = Leavings.from(left.filter((e) => e.seq > seq));
92
+ let records = voice.subjects;
93
+ if (!(records instanceof Restored)) {
94
+ records = new Restored(records, (live, least, top) => {
95
+ const h = restore(voice, live, least);
96
+ if (h === undefined)
97
+ return h;
98
+ if (h.reaches)
99
+ voice.seen++;
100
+ if (h.rested)
101
+ voice.restedCount++;
102
+ if (!(h.since <= least)) {
103
+ // Kept while its origin was still ahead: where the controls put back place it now.
104
+ h.since = mix.sinceOf(voice, h.delay);
105
+ h.shown = mix.shownOf(voice, h.since);
106
+ if (h.ticks === 0 && h.stepped > h.since)
107
+ h.stepped = h.since;
108
+ }
109
+ if (!(h.since <= top)) {
110
+ voice.early ??= [];
111
+ voice.early.push(h);
112
+ }
136
113
  return h;
137
- if (h.reaches)
138
- voice.seen++;
139
- if (h.rested)
140
- voice.restedCount++;
141
- if (voice.state === 'pending') {
142
- voice.early ??= [];
143
- voice.early.push(h);
144
- }
145
- return h;
146
- }, (key) => leftAt(future, key, t));
114
+ });
115
+ voice.subjects = records;
116
+ }
117
+ records.back(t, cut, future);
147
118
  }
148
119
  /** Each voice's state at mix time `t`, once every clock above it is back where it was. */
149
120
  function stateAt(voice, t) {
121
+ // A fade begins at once, even one whose ramp starts later, at the voice's start.
122
+ if (voice.out !== null)
123
+ return 'fading';
150
124
  if ((voice.owner === null ? t : ownerReading(voice.owner, t)) < voice.start)
151
125
  return 'pending';
152
- if (voice.out !== null && voice.out.at <= t)
153
- return 'fading';
154
126
  if (voice.freezesAfter && voice.elapsedAt(t) >= voice.span + voice.latest)
155
127
  return 'frozen';
156
128
  return 'live';
@@ -172,34 +144,43 @@ export function seek(transport, t) {
172
144
  if (!(t >= transport.born))
173
145
  throw new Error(`blits: ${t} is older than this mix's history reaches`);
174
146
  const unpaged = cover(transport, t);
175
- if (t >= transport.now)
176
- replay(transport, () => t);
177
- // Under the rate the recorded calls up to `t` set, ahead; behind, under the rate it had then.
178
- const u = hostAt(transport, t);
179
- if (!Number.isFinite(u))
180
- throw new RangeError(`blits: the mix's rate never reaches ${t}`);
181
147
  if (t >= transport.now) {
148
+ // Ahead, under the rate the recorded calls up to `t` set, past any frames rate 0 held at `t`.
149
+ const last = replay(transport, (at) => (transport.pace === null ? at : transport.pace.reading(at)) <= t);
150
+ const u = Number.isNaN(last) ? hostAt(transport, t) : Math.max(last, hostAt(transport, t));
151
+ if (!Number.isFinite(u))
152
+ throw new RangeError(`blits: the mix's rate never reaches ${t}`);
182
153
  transport.u = u;
183
154
  for (const m of transport.members)
184
155
  move(m, t);
185
156
  }
186
157
  else {
187
- const n = tape.depthAt(t);
188
- if (n < tape.undoDepth())
189
- tape.goto(n);
190
- transport.undrop(t);
191
- // What history kept after `t` is let go; the tape holds the calls that made it.
192
- transport.announced = transport.announced.filter((a) => a.made <= t);
193
- transport.pace?.cut(u);
158
+ const frame = transport.frames.at(t);
159
+ if (frame === undefined)
160
+ throw new Error(`blits: ${t} is older than this mix's history reaches`);
161
+ // On the frame itself, its own host time: rate 0 may have held the clock there for several.
162
+ const u = frame.at === t ? frame.u : hostAt(transport, t);
163
+ const seq = frame.seq;
164
+ tape.goto(tape.depthAt(u));
165
+ transport.undrop(seq);
166
+ // What history kept after the frame is let go; the tape holds the calls that made it.
167
+ transport.announced = transport.announced.filter((a) => a.seq <= seq);
168
+ transport.frames.set(transport.frames.all().filter((f) => f.seq <= seq));
169
+ if (transport.pacedSeq > seq) {
170
+ transport.pace = null;
171
+ transport.pacedSeq = Number.POSITIVE_INFINITY;
172
+ }
173
+ else
174
+ transport.pace?.cut(u);
194
175
  transport.u = u;
195
176
  transport.now = t;
196
177
  for (const m of transport.members)
197
- back(m, t);
198
- cut(transport, t, unpaged);
178
+ back(m, t, seq, frame.at < t);
179
+ cut(transport, seq, unpaged);
199
180
  }
181
+ transport.tick(t);
200
182
  // The host's clock reads on from here: its next sync reads `t` plus its time since its last.
201
- transport.offset = transport.last - u;
202
- transport.u = u;
183
+ transport.offset = transport.last - transport.u;
203
184
  transport.kept();
204
185
  for (const m of transport.members) {
205
186
  m.stir();
@@ -209,8 +190,11 @@ export function seek(transport, t) {
209
190
  b.sought();
210
191
  }
211
192
  }
212
- /** Puts one mix back as it stood at the end of frame `t`, its transport already there. */
213
- function back(mix, t) {
193
+ /**
194
+ * Puts one mix back as it stood at mix time `t`, the end of frame `seq`, its transport already
195
+ * there; `on` past that frame's own time, where it moves on to `t`.
196
+ */
197
+ function back(mix, t, seq, on) {
214
198
  const all = [...mix.cued, ...mix.gone].sort((a, b) => a.id - b.id);
215
199
  const kept = [];
216
200
  const gone = [];
@@ -220,14 +204,14 @@ function back(mix, t) {
220
204
  if (voice.motion !== undefined)
221
205
  motions.add(voice.motion);
222
206
  for (const voice of all)
223
- if (voice.cuedAt > t)
207
+ if (voice.cuedSeq > seq)
224
208
  park(mix, voice);
225
- else if (voice.doneAt <= t)
209
+ else if (voice.doneSeq <= seq)
226
210
  gone.push(voice);
227
211
  else
228
212
  kept.push(voice);
229
213
  for (const voice of kept)
230
- restoreVoice(voice, t);
214
+ restoreVoice(mix, voice, t, seq);
231
215
  for (const voice of kept)
232
216
  voice.state = stateAt(voice, t);
233
217
  mix.cued = kept;
@@ -242,28 +226,17 @@ function back(mix, t) {
242
226
  if (oldLanes !== null)
243
227
  mix.lanes = new Lanes(laneHost(mix));
244
228
  mix.chains = new Store();
245
- mix.named = new Store();
246
- mix.naming = 0;
247
- mix.general = [];
248
- for (const voice of kept)
249
- index(mix, voice);
250
229
  mix.owners = kept.some((v) => v.holding !== null) ? kept.filter((v) => v.holding !== null) : null;
251
- mix.loci = kept.filter((v) => v.spec.locus !== undefined).length;
252
- mix.anchored = kept.filter((v) => v.spec.anchor !== undefined).length;
230
+ recount(mix);
253
231
  mix.parters.clear();
254
232
  for (const voice of [...kept, ...gone])
255
233
  if (voice.parts !== null || voice.parted !== null)
256
234
  mix.parters.add(voice);
257
- mix.steps.push(null, ++mix.version);
258
235
  mix.relinks++;
259
236
  mix.pose = new Store();
260
237
  if (mix.restStamps !== null)
261
238
  mix.restStamps = new Store();
262
- mix.pulled = [];
263
- mix.pulledHeads = [];
264
- mix.pulledSlots = new Int32Array(0);
265
- mix.pulledVersion = Number.NaN;
266
- mix.pulledRelinks = -1;
239
+ unpull(mix);
267
240
  for (const voice of [...kept, ...gone]) {
268
241
  voice.unreached = null;
269
242
  voice.unreachedBits = null;
@@ -290,10 +263,10 @@ function back(mix, t) {
290
263
  for (const motion of motions) {
291
264
  if (oldLanes !== null && motion.watcher === oldLanes)
292
265
  motion.watcher = null;
293
- motion.rewind(t);
266
+ motion.rewind(seq);
294
267
  }
295
268
  // What history kept after `t` is let go; the tape holds the calls that made it.
296
- mix.hostLog = mix.hostLog.filter((e) => e.at <= t);
269
+ mix.hostLog = mix.hostLog.filter((e) => e.seq <= seq);
297
270
  mix.sent = mix.sent.filter((e) => e.timestamp <= t);
298
271
  mix.sentTo = t;
299
272
  mix.pins = null;
@@ -307,5 +280,15 @@ function back(mix, t) {
307
280
  for (const voice of kept)
308
281
  schedule(mix, voice);
309
282
  mix.stir();
310
- move(mix, t);
283
+ // On the frame's own time its move is behind it: a second would land what the next frame does.
284
+ if (on)
285
+ move(mix, t);
286
+ else {
287
+ mix.frame = nextFrame();
288
+ mix.reducedNow = mix.reduced;
289
+ // A voice history let go of may have placed an anchor then: placed again from what it holds.
290
+ const ms = mix.opts.history.ms;
291
+ if (mix.anchored > 0 && mix.transport.forgotTo >= t - ms)
292
+ place(mix);
293
+ }
311
294
  }
@@ -0,0 +1,6 @@
1
+ /**
2
+ * A WeakMap every copy of blits loaded in one realm shares, so a fact one copy records about a
3
+ * patch or channel it made is read by another copy's mix. The name carries a version, bumped when
4
+ * what is stored changes shape, so copies that disagree on the shape keep apart.
5
+ */
6
+ export declare function shared<K extends object, V>(name: string): WeakMap<K, V>;
package/dist/shared.js ADDED
@@ -0,0 +1,15 @@
1
+ /**
2
+ * A WeakMap every copy of blits loaded in one realm shares, so a fact one copy records about a
3
+ * patch or channel it made is read by another copy's mix. The name carries a version, bumped when
4
+ * what is stored changes shape, so copies that disagree on the shape keep apart.
5
+ */
6
+ export function shared(name) {
7
+ const key = Symbol.for(`@msb235/blits/${name}`);
8
+ const g = globalThis;
9
+ let map = g[key];
10
+ if (map === undefined) {
11
+ map = new WeakMap();
12
+ g[key] = map;
13
+ }
14
+ return map;
15
+ }
package/dist/signals.d.ts CHANGED
@@ -1,4 +1,6 @@
1
- import type { Signal } from './types.js';
1
+ import type { Setting, Signal } from './types.js';
2
+ /** Whether a change to an input signal reaches `reading.inputs`, so a lane fill can see it. */
3
+ export declare const heard: (signal: object) => boolean;
2
4
  /**
3
5
  * The loudest of several signals.
4
6
  *
@@ -6,7 +8,26 @@ import type { Signal } from './types.js';
6
8
  */
7
9
  export declare function peak<I, H = unknown>(...signals: readonly Signal<I, H>[]): Signal<I, H>;
8
10
  /**
9
- * A signal the host writes. klieg's `level`.
11
+ * Marks `read` as a signal driven from outside the clock, which a host's own signal has to be for
12
+ * `assess` and a history's `inputs` to treat it as one. Two uses:
13
+ *
14
+ * `input(read)` is an input of the host's own: a pointer, a volume, a value in a store. The host
15
+ * calls `touch()` after each change to what `read` reads, and a voice weighted by the signal, or by
16
+ * `peak`, `slew`, `lag` or `gate` over it, keeps its lane. A change nobody reports shows once the
17
+ * mix's clock next moves.
18
+ *
19
+ * `input(read, of)` is a signal built on others, as a wrapper that clamps one is: `of` lists every
20
+ * signal `read` calls, and the result is an input where any of them is, and reports its changes
21
+ * where they all do.
22
+ *
23
+ * @category signal
24
+ */
25
+ export declare function input<I, H = unknown>(read: (subject: I, setting: Setting<void, H>) => number, of?: readonly Signal<I, H>[]): Signal<I, H> & {
26
+ touch(): void;
27
+ };
28
+ /**
29
+ * A signal the host writes: `input` over one number, with `set` reporting each change. klieg's
30
+ * `level`.
10
31
  *
11
32
  * @category signal
12
33
  */
@@ -14,7 +35,7 @@ export declare function level<I, H = unknown>(initial?: number): Signal<I, H> &
14
35
  set(v: number): void;
15
36
  };
16
37
  /**
17
- * The package's one decay primitive: follows its input at a rate limit, no faster than `riseMs` per
38
+ * The package's linear decay primitive: follows its input at a rate limit, no faster than `riseMs` per
18
39
  * unit climbing or `fallMs` draining. The mix keeps its state per voice and subject, so two voices
19
40
  * handed the same slew each follow on their own. A subject starts on its input, or at `from`
20
41
  * unless `dt` is infinite.
package/dist/signals.js CHANGED
@@ -1,5 +1,16 @@
1
+ import { reading } from './reading.js';
2
+ /** Input signals whose every change bumps `reading.inputs`: `input`'s, and those built only on them. */
3
+ const told = new WeakSet();
4
+ /** Whether a change to an input signal reaches `reading.inputs`, so a lane fill can see it. */
5
+ export const heard = (signal) => told.has(signal);
1
6
  const inputOf = (signals) => signals.some((s) => s.input);
2
- const marked = (read, input) => (input ? Object.assign(read, { input: true }) : read);
7
+ const marked = (read, of) => {
8
+ if (!inputOf(of))
9
+ return read;
10
+ if (of.every((s) => !s.input || told.has(s)))
11
+ told.add(read);
12
+ return Object.assign(read, { input: true });
13
+ };
3
14
  /**
4
15
  * The loudest of several signals.
5
16
  *
@@ -14,19 +25,46 @@ export function peak(...signals) {
14
25
  out = v;
15
26
  }
16
27
  return out;
17
- }, inputOf(signals));
28
+ }, signals);
18
29
  }
30
+ const touch = () => {
31
+ reading.inputs++;
32
+ };
19
33
  /**
20
- * A signal the host writes. klieg's `level`.
34
+ * Marks `read` as a signal driven from outside the clock, which a host's own signal has to be for
35
+ * `assess` and a history's `inputs` to treat it as one. Two uses:
36
+ *
37
+ * `input(read)` is an input of the host's own: a pointer, a volume, a value in a store. The host
38
+ * calls `touch()` after each change to what `read` reads, and a voice weighted by the signal, or by
39
+ * `peak`, `slew`, `lag` or `gate` over it, keeps its lane. A change nobody reports shows once the
40
+ * mix's clock next moves.
41
+ *
42
+ * `input(read, of)` is a signal built on others, as a wrapper that clamps one is: `of` lists every
43
+ * signal `read` calls, and the result is an input where any of them is, and reports its changes
44
+ * where they all do.
45
+ *
46
+ * @category signal
47
+ */
48
+ export function input(read, of) {
49
+ if (of !== undefined)
50
+ return Object.assign(marked(read, of), { touch });
51
+ told.add(read);
52
+ return Object.assign(read, { input: true, touch });
53
+ }
54
+ /**
55
+ * A signal the host writes: `input` over one number, with `set` reporting each change. klieg's
56
+ * `level`.
21
57
  *
22
58
  * @category signal
23
59
  */
24
60
  export function level(initial = 0) {
25
61
  let value = initial;
26
- return Object.assign(() => value, {
27
- input: true,
62
+ return Object.assign(input(() => value), {
28
63
  set(v) {
64
+ if (Object.is(v, value))
65
+ return;
29
66
  value = v;
67
+ touch();
30
68
  },
31
69
  });
32
70
  }
@@ -38,7 +76,7 @@ const firstSight = (target, from, setting) => from === undefined || !Number.isFi
38
76
  ? { value: target, seen: Number.NaN }
39
77
  : { value: from, seen: setting.timestamp };
40
78
  /**
41
- * The package's one decay primitive: follows its input at a rate limit, no faster than `riseMs` per
79
+ * The package's linear decay primitive: follows its input at a rate limit, no faster than `riseMs` per
42
80
  * unit climbing or `fallMs` draining. The mix keeps its state per voice and subject, so two voices
43
81
  * handed the same slew each follow on their own. A subject starts on its input, or at `from`
44
82
  * unless `dt` is infinite.
@@ -56,7 +94,8 @@ export function slew(of, { riseMs = 0, fallMs = 0, from }) {
56
94
  const gap = target - held.value;
57
95
  const ms = gap > 0 ? riseMs : fallMs;
58
96
  if (ms > 0) {
59
- const step = (setting.timestamp - held.seen) / ms;
97
+ // No longer than `dt`, which `maxDt` caps, as every step is.
98
+ const step = Math.min(setting.timestamp - held.seen, setting.dt) / ms;
60
99
  next = gap > 0 ? Math.min(target, held.value + step) : Math.max(target, held.value - step);
61
100
  }
62
101
  }
@@ -64,7 +103,7 @@ export function slew(of, { riseMs = 0, fallMs = 0, from }) {
64
103
  held.seen = setting.timestamp;
65
104
  return next;
66
105
  };
67
- return marked(read, inputOf([of]));
106
+ return marked(read, [of]);
68
107
  }
69
108
  /**
70
109
  * The exponential counterpart to `slew`: closes the same fraction of the distance to its input in
@@ -86,7 +125,7 @@ export function lag(of, { riseMs = 0, fallMs = 0, floor = 1e-6, from, }) {
86
125
  if (!Number.isNaN(held.seen) && Number.isFinite(setting.dt)) {
87
126
  const ms = target > held.value ? riseMs : fallMs;
88
127
  if (ms > 0) {
89
- const gap = setting.timestamp - held.seen;
128
+ const gap = Math.min(setting.timestamp - held.seen, setting.dt);
90
129
  next = held.value + (target - held.value) * (1 - Math.exp(-gap / ms));
91
130
  if (Math.abs(target - next) <= floor)
92
131
  next = target;
@@ -96,7 +135,7 @@ export function lag(of, { riseMs = 0, fallMs = 0, floor = 1e-6, from, }) {
96
135
  held.seen = setting.timestamp;
97
136
  return next;
98
137
  };
99
- return marked(read, inputOf([of]));
138
+ return marked(read, [of]);
100
139
  }
101
140
  /**
102
141
  * The other half of the pair: a band, not an edge. It drops at `off`, passes at `on`, and between
@@ -116,5 +155,5 @@ export function gate(of, band) {
116
155
  }
117
156
  return held.on ? 1 : 0;
118
157
  };
119
- return marked(read, inputOf([of]));
158
+ return marked(read, [of]);
120
159
  }