@msb235/blits 0.7.0 → 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 (156) hide show
  1. package/CHANGELOG.md +196 -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 +33 -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 +36 -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.d.ts +32 -0
  39. package/dist/gone.js +144 -0
  40. package/dist/handle.d.ts +16 -5
  41. package/dist/handle.js +56 -6
  42. package/dist/held.d.ts +8 -5
  43. package/dist/held.js +56 -58
  44. package/dist/history.d.ts +17 -13
  45. package/dist/history.js +49 -28
  46. package/dist/hosts.js +57 -9
  47. package/dist/index.d.ts +9 -4
  48. package/dist/index.js +5 -2
  49. package/dist/keyfill.d.ts +7 -0
  50. package/dist/keyfill.js +124 -0
  51. package/dist/keyrows.d.ts +1 -0
  52. package/dist/keyrows.js +1 -1
  53. package/dist/lane.d.ts +32 -2
  54. package/dist/lane.js +5 -0
  55. package/dist/lanes.d.ts +32 -8
  56. package/dist/lanes.js +69 -37
  57. package/dist/leavings.d.ts +26 -0
  58. package/dist/leavings.js +70 -0
  59. package/dist/meet.js +14 -8
  60. package/dist/mixer.d.ts +43 -7
  61. package/dist/mixer.js +60 -11
  62. package/dist/motion.d.ts +2 -0
  63. package/dist/motion.js +4 -1
  64. package/dist/motions.d.ts +22 -15
  65. package/dist/motions.js +40 -34
  66. package/dist/move.js +31 -20
  67. package/dist/moved.d.ts +11 -0
  68. package/dist/moved.js +29 -0
  69. package/dist/origin.d.ts +19 -0
  70. package/dist/origin.js +68 -0
  71. package/dist/owed.d.ts +4 -4
  72. package/dist/owed.js +8 -8
  73. package/dist/owner.d.ts +1 -1
  74. package/dist/owner.js +2 -2
  75. package/dist/pace.d.ts +13 -5
  76. package/dist/pace.js +21 -8
  77. package/dist/pack.d.ts +3 -1
  78. package/dist/pack.js +15 -24
  79. package/dist/paging.d.ts +8 -2
  80. package/dist/paging.js +44 -4
  81. package/dist/patch.d.ts +14 -9
  82. package/dist/patch.js +83 -21
  83. package/dist/place.js +188 -30
  84. package/dist/project.d.ts +12 -1
  85. package/dist/project.js +101 -83
  86. package/dist/pull.d.ts +2 -0
  87. package/dist/pull.js +8 -0
  88. package/dist/qualify.js +10 -2
  89. package/dist/reading.d.ts +5 -0
  90. package/dist/reading.js +5 -0
  91. package/dist/record.d.ts +9 -0
  92. package/dist/record.js +42 -0
  93. package/dist/relink.d.ts +11 -5
  94. package/dist/relink.js +7 -5
  95. package/dist/restored.d.ts +41 -0
  96. package/dist/restored.js +91 -0
  97. package/dist/revive.d.ts +10 -3
  98. package/dist/revive.js +31 -23
  99. package/dist/roster.d.ts +11 -0
  100. package/dist/roster.js +46 -0
  101. package/dist/rotation.d.ts +29 -0
  102. package/dist/rotation.js +91 -0
  103. package/dist/rows.d.ts +5 -5
  104. package/dist/rows.js +5 -5
  105. package/dist/sample.d.ts +3 -2
  106. package/dist/sample.js +32 -15
  107. package/dist/scored.d.ts +57 -0
  108. package/dist/scored.js +196 -0
  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 +53 -6
  129. package/dist/transport.js +102 -20
  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 +55 -25
  150. package/dist/voice.d.ts +92 -22
  151. package/dist/voice.js +108 -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/scored.js ADDED
@@ -0,0 +1,196 @@
1
+ /**
2
+ * A mix's voices by the score they were cued on, for an anchor that asks for a score's marks: the
3
+ * cued ones as a set, the gone ones in the order they left, which an anchor reads once each. Built
4
+ * over the lists it saw: a seek or a copy that replaces either rebuilds that half on the next read.
5
+ * A score whose gone voices history lets go of gets a fresh list, so a read of the old one starts
6
+ * over.
7
+ */
8
+ export class ScoreIndex {
9
+ cued = null;
10
+ by = new Map();
11
+ gone = null;
12
+ goneBy = new Map();
13
+ /** Bumped for a score each time a voice on it is cued, retimed, retired or leaves. */
14
+ versions = new Map();
15
+ /** The scores whose list on the transport names this mix. */
16
+ held = new Set();
17
+ }
18
+ function hold(mix, score) {
19
+ const held = mix.scored.held;
20
+ // A dropped mix is off the transport's lists until a seek puts it back, and `scoredJoin` then.
21
+ if (held.has(score) || mix.dropped)
22
+ return;
23
+ held.add(score);
24
+ const by = mix.transport.holders;
25
+ const m = mix;
26
+ const list = by.get(score);
27
+ if (list === undefined) {
28
+ by.set(score, [m]);
29
+ return;
30
+ }
31
+ const i = list.findIndex((o) => o.slot > m.slot);
32
+ if (i < 0)
33
+ list.push(m);
34
+ else
35
+ list.splice(i, 0, m);
36
+ }
37
+ /** The mixes on `mix`'s transport that may hold a voice on `score`; the list is the transport's own. */
38
+ export function holders(mix, score) {
39
+ return (mix.transport.holders.get(score) ?? none);
40
+ }
41
+ /** Takes `mix` off `score`'s list, having found it holds nothing there. */
42
+ export function unhold(mix, score) {
43
+ mix.scored.held.delete(score);
44
+ const by = mix.transport.holders;
45
+ const list = by.get(score);
46
+ if (list === undefined)
47
+ return;
48
+ const i = list.indexOf(mix);
49
+ if (i >= 0)
50
+ list.splice(i, 1);
51
+ if (list.length === 0)
52
+ by.delete(score);
53
+ }
54
+ /** A mix joining its transport, with whatever voices it already has. */
55
+ export function scoredJoin(mix) {
56
+ for (const v of mix.cued)
57
+ if (v.spec.score !== undefined)
58
+ hold(mix, v.spec.score);
59
+ for (const v of mix.gone)
60
+ if (v.spec.score !== undefined)
61
+ hold(mix, v.spec.score);
62
+ }
63
+ /** A mix leaving its transport. */
64
+ export function scoredDrop(mix) {
65
+ for (const score of [...mix.scored.held])
66
+ unhold(mix, score);
67
+ }
68
+ /**
69
+ * A voice on its score was cued, retimed, retired or left: what was read of the score is stale. A
70
+ * host can still retime a gone voice through a handle it kept, so `retimed` on a done voice
71
+ * replaces its score's gone list too, which starts every read of it over.
72
+ */
73
+ export function scoreTouched(mix, voice, retimed = false) {
74
+ const score = voice.spec.score;
75
+ if (score === undefined)
76
+ return;
77
+ const ix = mix.scored;
78
+ ix.versions.set(score, (ix.versions.get(score) ?? 0) + 1);
79
+ if (!retimed || voice.state !== 'done')
80
+ return;
81
+ const gone = ix.goneBy.get(score);
82
+ if (gone !== undefined)
83
+ ix.goneBy.set(score, [...gone]);
84
+ }
85
+ /** How many times `score` has been touched in `mix`. */
86
+ export function scoreVersion(mix, score) {
87
+ return mix.scored.versions.get(score) ?? 0;
88
+ }
89
+ function put(by, voice) {
90
+ const score = voice.spec.score;
91
+ if (score === undefined)
92
+ return;
93
+ const set = by.get(score);
94
+ if (set === undefined)
95
+ by.set(score, new Set([voice]));
96
+ else
97
+ set.add(voice);
98
+ }
99
+ /** The voices of `mix` cued on `score` and not yet gone, in no order. */
100
+ export function scored(mix, score) {
101
+ const ix = mix.scored;
102
+ if (ix.cued !== mix.cued) {
103
+ ix.by = new Map();
104
+ for (const v of mix.cued)
105
+ put(ix.by, v);
106
+ ix.cued = mix.cued;
107
+ }
108
+ return ix.by.get(score) ?? noVoices;
109
+ }
110
+ const noVoices = new Set();
111
+ /** The voices of `mix` cued on `score` that have gone, in the order they left. */
112
+ export function scoredGone(mix, score) {
113
+ const ix = mix.scored;
114
+ if (ix.gone !== mix.gone) {
115
+ ix.goneBy = new Map();
116
+ for (const v of mix.gone)
117
+ goneAdd(ix.goneBy, v);
118
+ ix.gone = mix.gone;
119
+ }
120
+ return ix.goneBy.get(score) ?? none;
121
+ }
122
+ const none = [];
123
+ function goneAdd(by, voice) {
124
+ const score = voice.spec.score;
125
+ if (score === undefined)
126
+ return;
127
+ const list = by.get(score);
128
+ if (list === undefined)
129
+ by.set(score, [voice]);
130
+ else
131
+ list.push(voice);
132
+ }
133
+ /** A voice just pushed onto `gone`. */
134
+ export function scoredLeft(mix, voice) {
135
+ if (voice.spec.score !== undefined)
136
+ hold(mix, voice.spec.score);
137
+ const ix = mix.scored;
138
+ if (ix.gone === mix.gone)
139
+ goneAdd(ix.goneBy, voice);
140
+ }
141
+ /**
142
+ * Gone voices history let go of, out of `was`; `mix.gone` is already what is left, whether that
143
+ * is `was` cut in place or a new list.
144
+ */
145
+ export function scoredCut(mix, was, out) {
146
+ const ix = mix.scored;
147
+ if (ix.gone !== was)
148
+ return;
149
+ ix.gone = mix.gone;
150
+ const by = new Map();
151
+ for (const v of out) {
152
+ const score = v.spec.score;
153
+ if (score === undefined)
154
+ continue;
155
+ const set = by.get(score);
156
+ if (set === undefined)
157
+ by.set(score, new Set([v]));
158
+ else
159
+ set.add(v);
160
+ }
161
+ for (const [score, gone] of by) {
162
+ const left = (ix.goneBy.get(score) ?? []).filter((v) => !gone.has(v));
163
+ if (left.length === 0)
164
+ ix.goneBy.delete(score);
165
+ else
166
+ ix.goneBy.set(score, left);
167
+ }
168
+ }
169
+ /** A voice just cued. */
170
+ export function scoredAdd(mix, voice) {
171
+ scoreTouched(mix, voice);
172
+ if (voice.spec.score !== undefined)
173
+ hold(mix, voice.spec.score);
174
+ const ix = mix.scored;
175
+ if (ix.cued === mix.cued)
176
+ put(ix.by, voice);
177
+ }
178
+ /** Voices no longer cued. */
179
+ export function scoredRemove(mix, voices) {
180
+ for (const v of voices)
181
+ scoreTouched(mix, v);
182
+ const ix = mix.scored;
183
+ if (ix.cued !== mix.cued)
184
+ return;
185
+ for (const v of voices) {
186
+ const score = v.spec.score;
187
+ if (score === undefined)
188
+ continue;
189
+ const set = ix.by.get(score);
190
+ if (set === undefined)
191
+ continue;
192
+ set.delete(v);
193
+ if (set.size === 0)
194
+ ix.by.delete(score);
195
+ }
196
+ }
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.frameAt(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 = transport.frames.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.