@msb235/blits 0.10.0 → 0.12.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 (126) hide show
  1. package/CHANGELOG.md +248 -0
  2. package/README.md +15 -4
  3. package/dist/arcs.d.ts +40 -0
  4. package/dist/arcs.js +239 -0
  5. package/dist/blend.js +1 -5
  6. package/dist/book.d.ts +2 -0
  7. package/dist/book.js +27 -3
  8. package/dist/chain.d.ts +17 -1
  9. package/dist/chain.js +57 -15
  10. package/dist/channels.d.ts +2 -0
  11. package/dist/channels.js +12 -1
  12. package/dist/clock.d.ts +14 -0
  13. package/dist/clock.js +9 -1
  14. package/dist/clone.js +16 -4
  15. package/dist/columns.d.ts +4 -5
  16. package/dist/columns.js +11 -5
  17. package/dist/crowd.d.ts +1 -1
  18. package/dist/crowd.js +4 -1
  19. package/dist/cue.js +63 -7
  20. package/dist/departed.d.ts +8 -3
  21. package/dist/departed.js +76 -29
  22. package/dist/due.d.ts +4 -0
  23. package/dist/due.js +16 -3
  24. package/dist/easing.d.ts +12 -1
  25. package/dist/easing.js +7 -1
  26. package/dist/everyone.d.ts +14 -1
  27. package/dist/everyone.js +27 -2
  28. package/dist/fade.d.ts +12 -5
  29. package/dist/fade.js +57 -19
  30. package/dist/fill.js +16 -9
  31. package/dist/fit.d.ts +8 -1
  32. package/dist/fit.js +7 -3
  33. package/dist/fold.d.ts +1 -1
  34. package/dist/fold.js +112 -9
  35. package/dist/frames.d.ts +2 -0
  36. package/dist/frames.js +17 -0
  37. package/dist/gather.js +38 -7
  38. package/dist/handle.d.ts +7 -2
  39. package/dist/handle.js +28 -12
  40. package/dist/held.d.ts +13 -2
  41. package/dist/held.js +43 -9
  42. package/dist/history.d.ts +25 -2
  43. package/dist/history.js +163 -35
  44. package/dist/hosts.js +45 -17
  45. package/dist/index.d.ts +5 -2
  46. package/dist/index.js +3 -1
  47. package/dist/keyfill.js +3 -1
  48. package/dist/lane.d.ts +1 -1
  49. package/dist/lane.js +3 -1
  50. package/dist/lanes.d.ts +31 -7
  51. package/dist/lanes.js +57 -6
  52. package/dist/lean.d.ts +15 -0
  53. package/dist/lean.js +23 -0
  54. package/dist/leavings.d.ts +6 -1
  55. package/dist/leavings.js +16 -2
  56. package/dist/marks.d.ts +7 -3
  57. package/dist/marks.js +105 -27
  58. package/dist/meet.js +1 -0
  59. package/dist/mixer.d.ts +35 -6
  60. package/dist/mixer.js +113 -45
  61. package/dist/motion.d.ts +4 -1
  62. package/dist/motion.js +16 -2
  63. package/dist/motions.d.ts +4 -0
  64. package/dist/motions.js +45 -11
  65. package/dist/move.js +35 -12
  66. package/dist/numbers.d.ts +5 -1
  67. package/dist/numbers.js +4 -0
  68. package/dist/owner.d.ts +18 -2
  69. package/dist/owner.js +44 -3
  70. package/dist/pack.d.ts +1 -0
  71. package/dist/pack.js +3 -0
  72. package/dist/place.d.ts +8 -0
  73. package/dist/place.js +16 -8
  74. package/dist/placed.d.ts +19 -0
  75. package/dist/placed.js +54 -0
  76. package/dist/project.d.ts +5 -2
  77. package/dist/project.js +238 -34
  78. package/dist/pull.js +37 -8
  79. package/dist/qualify.d.ts +10 -0
  80. package/dist/qualify.js +60 -3
  81. package/dist/reading.d.ts +8 -1
  82. package/dist/reading.js +8 -1
  83. package/dist/record.d.ts +6 -0
  84. package/dist/record.js +2 -0
  85. package/dist/restored.d.ts +69 -17
  86. package/dist/restored.js +163 -50
  87. package/dist/revive.d.ts +6 -3
  88. package/dist/revive.js +11 -3
  89. package/dist/roster.js +2 -1
  90. package/dist/rows.js +15 -5
  91. package/dist/seek.d.ts +7 -2
  92. package/dist/seek.js +152 -43
  93. package/dist/shared.d.ts +5 -0
  94. package/dist/shared.js +5 -0
  95. package/dist/signals.d.ts +10 -0
  96. package/dist/signals.js +17 -0
  97. package/dist/spans.d.ts +5 -0
  98. package/dist/spans.js +75 -5
  99. package/dist/store.d.ts +13 -0
  100. package/dist/store.js +23 -0
  101. package/dist/strays.js +50 -2
  102. package/dist/tape.d.ts +29 -4
  103. package/dist/tape.js +49 -11
  104. package/dist/touch.js +3 -2
  105. package/dist/transport.d.ts +11 -2
  106. package/dist/transport.js +26 -10
  107. package/dist/types/arc.d.ts +55 -0
  108. package/dist/types/arc.js +1 -0
  109. package/dist/types/mix.d.ts +57 -14
  110. package/dist/types/patch.d.ts +5 -1
  111. package/dist/types/place.d.ts +11 -4
  112. package/dist/types/transport.d.ts +12 -3
  113. package/dist/types/voice.d.ts +72 -5
  114. package/dist/types/watch.d.ts +126 -0
  115. package/dist/types/watch.js +1 -0
  116. package/dist/types.d.ts +2 -0
  117. package/dist/unpage.d.ts +6 -1
  118. package/dist/unpage.js +34 -2
  119. package/dist/voice.d.ts +93 -16
  120. package/dist/voice.js +60 -3
  121. package/dist/watch.d.ts +26 -0
  122. package/dist/watch.js +441 -0
  123. package/dist/weigh.d.ts +13 -2
  124. package/dist/weigh.js +10 -3
  125. package/docs/schema.html +106 -38
  126. package/package.json +10 -4
package/dist/record.js CHANGED
@@ -23,6 +23,7 @@ export function record(voice, reaches, state) {
23
23
  phase: 0,
24
24
  seeks: 0,
25
25
  rebuilt: 0,
26
+ copied: false,
26
27
  base: undefined,
27
28
  slope: undefined,
28
29
  kept: null,
@@ -39,5 +40,6 @@ export function record(voice, reaches, state) {
39
40
  slot: -1,
40
41
  run: -1,
41
42
  freed: -1,
43
+ restored: null,
42
44
  };
43
45
  }
@@ -2,40 +2,92 @@ import type { Leavings } from './leavings.js';
2
2
  import { Store } from './store.js';
3
3
  import type { Cut } from './transport.js';
4
4
  import type { Subject } from './voice.js';
5
+ /**
6
+ * The seeks back after the one a record was last put back, written or deleted at, which every
7
+ * record stamped then shares. The next seek fills it; a later seek to as early a moment joins it
8
+ * to its own, so the root it reaches holds the earliest moment sought since. Only records and the
9
+ * stamps joined beneath it hold it, so it is collected with the last record that can read it.
10
+ */
11
+ /** A moment sought, the end of frame `seq`. */
12
+ export interface Moment {
13
+ readonly t: number;
14
+ readonly seq: number;
15
+ }
16
+ export interface Stamp extends Moment {
17
+ /** On a root: the earliest moment sought since, and the frame that ended. */
18
+ t: number;
19
+ seq: number;
20
+ /** The stamp this one was joined to, null on a root. */
21
+ up: Stamp | null;
22
+ /** A bound on how deep the stamps joined beneath it go, so joining the lower keeps paths short. */
23
+ rank: number;
24
+ /** The records subjects left with after this seek's moment, and where it cut; null for none. */
25
+ left: Leavings<unknown> | null;
26
+ cut: Cut | null;
27
+ /** The store that made it; null on `gone`. */
28
+ readonly of: object | null;
29
+ }
5
30
  /**
6
31
  * A voice's records once a seek has gone back: each subject's is put back from the record it had,
7
32
  * the first time it is asked for after a seek, since the subjects a store holds cannot be listed.
8
- * It is one store however many seeks there are: by subject it keeps how many seeks its record has
9
- * been put back for, and a record behind is put back to the earliest moment sought since, as a
10
- * seek back cuts what came after it and a later seek ahead of that moment must not read past it.
33
+ * It is one store however many seeks there are: by subject it keeps the stamp of the seek its
34
+ * record was last put back at, and a record behind is put back to the earliest moment sought
35
+ * since, as a seek back cuts what came after it and a later seek ahead of that moment must not
36
+ * read past it.
11
37
  */
12
38
  export declare class Restored<I> extends Store<I, Subject<unknown>> {
13
39
  private readonly own;
40
+ private readonly now;
14
41
  private readonly make;
15
- /** Seeks back so far. */
16
- private n;
17
- /** By subject, the `n` its record was last put back, written or deleted at; none reads as 0. */
42
+ /** What a record put back now is stamped with, filled by the next seek. */
43
+ private stamp;
44
+ /** What a record never stamped reads as: the first seek's. */
45
+ private readonly first;
46
+ /**
47
+ * By subject, the stamp its record was last put back, written or deleted at; none reads as
48
+ * `first`. A record that reaches its subject is its own, and holds it as `restored` instead,
49
+ * sparing a write here per subject a seek puts back; one that does not may stand for many.
50
+ */
18
51
  private readonly at;
19
52
  /**
20
- * Each seek back sought to an earlier moment than every one after it, oldest first: the earliest
21
- * moment sought since seek `n` is that of the first here at or after it.
53
+ * The roots, each moment earlier than the next, held weakly: one no record can reach is never
54
+ * read again, and those are cleared out once the list doubles.
55
+ */
56
+ private roots;
57
+ private kept;
58
+ /**
59
+ * Stamps made a batch ahead of their seeks: one made at its seek is young when every old record
60
+ * is written with it, and V8 notes each such write for its next scavenge.
22
61
  */
23
- private readonly least;
24
- /** By seek, the records subjects left with after its moment, and where it cut; null until one has any. */
25
- private futures;
62
+ private ready;
63
+ private aging;
26
64
  /** The latest seek's moment. */
27
- private t;
65
+ top: number;
28
66
  /**
29
- * `own` holds the records as they stood before the first seek. `make` puts a record back as it
30
- * stood at `t`, undefined where it had none then; `top` is the latest seek's moment.
67
+ * `own` holds the records as they stood before the first seek, and `now` gives the one a subject
68
+ * that has not left since and is not reached has there, as a seek to `at` takes it. `make` puts
69
+ * a record back as it stood at `at`, undefined where it had none then; `mine` says `live` is the
70
+ * record this store held, which nothing else does.
31
71
  */
32
- constructor(own: Store<I, Subject<unknown>>, make: (live: Subject<unknown>, t: number, top: number) => Subject<unknown> | undefined);
72
+ constructor(own: Store<I, Subject<unknown>>, now: (key: I, held: Subject<unknown> | undefined, at: Moment) => Subject<unknown> | undefined, make: (live: Subject<unknown>, at: Moment, mine: boolean) => Subject<unknown> | undefined);
33
73
  /** A seek back to `t`: `left` has the records subjects left with after `cut`, null for none. */
34
74
  back(t: number, cut: Cut, left: Leavings<I> | null): void;
35
- /** The earliest moment sought by seek `from` or any after it. */
36
- private since;
75
+ /** The next seek's stamp, from a batch made a batch of seeks ago, twice as many each time to 64. */
76
+ private fresh;
77
+ /**
78
+ * Whether a reaching record stamped by this store is still its record for the key it was found
79
+ * under: every record let go is stamped `gone`, copies are stamped with nothing, and this turns
80
+ * false if a stamped record is set again, which could put it under a second key.
81
+ */
82
+ private sure;
37
83
  get(key: I): Subject<unknown> | undefined;
84
+ /** A chain's record for a subject, found again without a lookup where this store stamped it. */
85
+ find(key: I, guess: Subject<unknown>): Subject<unknown> | undefined;
86
+ /** `get`, given what `own` holds for `key`. */
87
+ private from;
38
88
  set(key: I, value: Subject<unknown>): void;
39
89
  delete(key: I): void;
90
+ /** Lets go of the record held for `key`, stamping it `gone`. */
91
+ private shed;
40
92
  clear(): void;
41
93
  }
package/dist/restored.js CHANGED
@@ -1,90 +1,203 @@
1
1
  import { Store } from './store.js';
2
+ const stamp = (of) => ({
3
+ t: Number.NaN,
4
+ seq: 0,
5
+ up: null,
6
+ rank: 0,
7
+ left: null,
8
+ cut: null,
9
+ of,
10
+ });
11
+ /** What a reaching record a store has let go of is stamped with, so setting it again is seen. */
12
+ const gone = stamp(null);
13
+ /** The root `s` was joined to, halving the path there for the next read. */
14
+ function rootOf(s) {
15
+ let x = s;
16
+ let up = x.up;
17
+ while (up !== null) {
18
+ const next = up.up;
19
+ if (next === null)
20
+ return up;
21
+ x.up = next;
22
+ x = next;
23
+ up = x.up;
24
+ }
25
+ return x;
26
+ }
2
27
  /**
3
28
  * A voice's records once a seek has gone back: each subject's is put back from the record it had,
4
29
  * the first time it is asked for after a seek, since the subjects a store holds cannot be listed.
5
- * It is one store however many seeks there are: by subject it keeps how many seeks its record has
6
- * been put back for, and a record behind is put back to the earliest moment sought since, as a
7
- * seek back cuts what came after it and a later seek ahead of that moment must not read past it.
30
+ * It is one store however many seeks there are: by subject it keeps the stamp of the seek its
31
+ * record was last put back at, and a record behind is put back to the earliest moment sought
32
+ * since, as a seek back cuts what came after it and a later seek ahead of that moment must not
33
+ * read past it.
8
34
  */
9
35
  export class Restored extends Store {
10
36
  own;
37
+ now;
11
38
  make;
12
- /** Seeks back so far. */
13
- n = 0;
14
- /** By subject, the `n` its record was last put back, written or deleted at; none reads as 0. */
39
+ /** What a record put back now is stamped with, filled by the next seek. */
40
+ stamp = stamp(this);
41
+ /** What a record never stamped reads as: the first seek's. */
42
+ first = this.stamp;
43
+ /**
44
+ * By subject, the stamp its record was last put back, written or deleted at; none reads as
45
+ * `first`. A record that reaches its subject is its own, and holds it as `restored` instead,
46
+ * sparing a write here per subject a seek puts back; one that does not may stand for many.
47
+ */
15
48
  at = new Store();
16
49
  /**
17
- * Each seek back sought to an earlier moment than every one after it, oldest first: the earliest
18
- * moment sought since seek `n` is that of the first here at or after it.
50
+ * The roots, each moment earlier than the next, held weakly: one no record can reach is never
51
+ * read again, and those are cleared out once the list doubles.
52
+ */
53
+ roots = [];
54
+ kept = 0;
55
+ /**
56
+ * Stamps made a batch ahead of their seeks: one made at its seek is young when every old record
57
+ * is written with it, and V8 notes each such write for its next scavenge.
19
58
  */
20
- least = [];
21
- /** By seek, the records subjects left with after its moment, and where it cut; null until one has any. */
22
- futures = null;
59
+ ready = [];
60
+ aging = [];
23
61
  /** The latest seek's moment. */
24
- t = Number.NaN;
62
+ top = Number.NaN;
25
63
  /**
26
- * `own` holds the records as they stood before the first seek. `make` puts a record back as it
27
- * stood at `t`, undefined where it had none then; `top` is the latest seek's moment.
64
+ * `own` holds the records as they stood before the first seek, and `now` gives the one a subject
65
+ * that has not left since and is not reached has there, as a seek to `at` takes it. `make` puts
66
+ * a record back as it stood at `at`, undefined where it had none then; `mine` says `live` is the
67
+ * record this store held, which nothing else does.
28
68
  */
29
- constructor(own, make) {
69
+ constructor(own, now, make) {
30
70
  super();
31
71
  this.own = own;
72
+ this.now = now;
32
73
  this.make = make;
33
74
  }
34
75
  /** A seek back to `t`: `left` has the records subjects left with after `cut`, null for none. */
35
76
  back(t, cut, left) {
36
- const n = ++this.n;
37
- const least = this.least;
38
- while (least.length > 0 && least[least.length - 1].t >= t)
39
- least.pop();
40
- least.push({ n, t });
41
- this.t = t;
77
+ const s = this.stamp;
42
78
  if (left !== null) {
43
- this.futures ??= new Map();
44
- this.futures.set(n, { left, cut });
79
+ s.left = left;
80
+ s.cut = cut;
81
+ }
82
+ // Every root at or after `t` now reads `t`, so they and `s` are joined under one.
83
+ const roots = this.roots;
84
+ let root = s;
85
+ let ref = null;
86
+ while (roots.length > 0) {
87
+ const r = roots[roots.length - 1].deref();
88
+ if (r !== undefined && r.t < t)
89
+ break;
90
+ const w = roots.pop();
91
+ if (r === undefined)
92
+ continue;
93
+ if (r.rank > root.rank) {
94
+ root.up = r;
95
+ root = r;
96
+ ref = w;
97
+ }
98
+ else {
99
+ r.up = root;
100
+ if (r.rank === root.rank)
101
+ root.rank++;
102
+ }
103
+ }
104
+ root.t = t;
105
+ root.seq = cut.seq;
106
+ roots.push(ref ?? new WeakRef(root));
107
+ if (roots.length > 2 * this.kept + 16) {
108
+ this.roots = roots.filter((w) => w.deref() !== undefined);
109
+ this.kept = this.roots.length;
45
110
  }
111
+ this.stamp = this.fresh();
112
+ this.top = t;
46
113
  }
47
- /** The earliest moment sought by seek `from` or any after it. */
48
- since(from) {
49
- const least = this.least;
50
- let lo = 0;
51
- let hi = least.length - 1;
52
- while (lo < hi) {
53
- const mid = (lo + hi) >> 1;
54
- if (least[mid].n >= from)
55
- hi = mid;
56
- else
57
- lo = mid + 1;
114
+ /** The next seek's stamp, from a batch made a batch of seeks ago, twice as many each time to 64. */
115
+ fresh() {
116
+ if (this.ready.length === 0) {
117
+ const n = Math.min(64, Math.max(1, this.aging.length * 2));
118
+ this.ready = this.aging.reverse();
119
+ this.aging = [];
120
+ for (let i = 0; i < n; i++)
121
+ this.aging.push(stamp(this));
58
122
  }
59
- return least[lo].t;
123
+ return this.ready.pop() ?? stamp(this);
60
124
  }
125
+ /**
126
+ * Whether a reaching record stamped by this store is still its record for the key it was found
127
+ * under: every record let go is stamped `gone`, copies are stamped with nothing, and this turns
128
+ * false if a stamped record is set again, which could put it under a second key.
129
+ */
130
+ sure = true;
61
131
  get(key) {
62
- const at = this.at.get(key) ?? 0;
63
- if (at === this.n)
64
- return this.own.get(key);
65
- this.at.set(key, this.n);
132
+ return this.from(key, this.own.get(key));
133
+ }
134
+ /** A chain's record for a subject, found again without a lookup where this store stamped it. */
135
+ find(key, guess) {
136
+ const s = guess.restored;
137
+ return s !== null && s.of === this && this.sure ? this.from(key, guess) : this.get(key);
138
+ }
139
+ /** `get`, given what `own` holds for `key`. */
140
+ from(key, own) {
141
+ const mark = own !== undefined && own.reaches;
142
+ const at = (mark ? own.restored : this.at.get(key)) ?? this.first;
143
+ const now = this.stamp;
144
+ if (at === now)
145
+ return own;
146
+ if (mark)
147
+ own.restored = now;
148
+ else
149
+ this.at.set(key, now);
66
150
  // A subject that left after the moment the next seek sought had the record it left with,
67
151
  // whatever it has now. Leaving is a write, so no later seek's leavings can hold it.
68
- const future = this.futures?.get(at + 1);
69
- const live = future?.left.at(key, future.cut) ?? this.own.get(key);
70
- if (live === undefined)
71
- return undefined;
72
- const made = this.make(live, this.since(at + 1), this.t);
73
- if (made === undefined)
74
- this.own.delete(key);
75
- else
76
- this.own.set(key, made);
152
+ const root = rootOf(at);
153
+ const left = at.left?.at(key, at.cut);
154
+ const live = left ?? this.now(key, own, root);
155
+ const made = live && this.make(live, root, left === undefined);
156
+ // Looked up again, not taken from `own`: a call made while it was put back may have set it.
157
+ if (made === undefined) {
158
+ this.shed(key);
159
+ if (mark)
160
+ this.at.set(key, now);
161
+ }
162
+ else if (made !== own)
163
+ this.set(key, made);
164
+ else if (!made.reaches)
165
+ this.at.set(key, now);
77
166
  return made;
78
167
  }
79
168
  set(key, value) {
169
+ const old = this.own.get(key);
170
+ if (old === value) {
171
+ if (value.reaches)
172
+ value.restored = this.stamp;
173
+ else
174
+ this.at.set(key, this.stamp);
175
+ return;
176
+ }
177
+ if (old?.reaches)
178
+ old.restored = gone;
179
+ // Stamped already, it may be in a chain under another key.
180
+ if (value.reaches && value.restored !== null)
181
+ this.sure = false;
80
182
  this.own.set(key, value);
81
- this.at.set(key, this.n);
183
+ if (value.reaches)
184
+ value.restored = this.stamp;
185
+ else
186
+ this.at.set(key, this.stamp);
82
187
  }
83
188
  delete(key) {
189
+ this.shed(key);
190
+ this.at.set(key, this.stamp);
191
+ }
192
+ /** Lets go of the record held for `key`, stamping it `gone`. */
193
+ shed(key) {
194
+ const old = this.own.get(key);
195
+ if (old?.reaches)
196
+ old.restored = gone;
84
197
  this.own.delete(key);
85
- this.at.set(key, this.n);
86
198
  }
87
199
  clear() {
200
+ this.sure = false;
88
201
  this.own.clear();
89
202
  this.at.clear();
90
203
  }
package/dist/revive.d.ts CHANGED
@@ -4,7 +4,7 @@ import type { Past } from './origin.js';
4
4
  import { type PackedRecord } from './pack.js';
5
5
  import { type Key } from './paging.js';
6
6
  import type { Described } from './types.js';
7
- import { type Controls, type Ramp, type Rise, Voice } from './voice.js';
7
+ import { type Controls, type Parted, type Parting, type Ramp, type Rise, Voice } from './voice.js';
8
8
  /** A voice that has left, as plain data: the descriptor it was cued with, its controls and its records. */
9
9
  interface PackedVoice {
10
10
  as: Described;
@@ -24,6 +24,7 @@ interface PackedVoice {
24
24
  outOver: number;
25
25
  outSet: boolean;
26
26
  rebuilds: number;
27
+ hold: number;
27
28
  past: number;
28
29
  clocks: Past[] | null;
29
30
  cuedAt: number;
@@ -43,9 +44,11 @@ interface PackedVoice {
43
44
  playedSeq: number;
44
45
  records: [Key, PackedRecord][];
45
46
  runs: [Key, Run][];
46
- left: [Key, number, number, PackedRecord, boolean][];
47
+ /** The fades are absent from a store written before a leaving kept them. */
48
+ left: [Key, number, number, PackedRecord, boolean, (Parting | null)?, (Parted | null)?][];
47
49
  parts: [Key, number, number, number][];
48
- parted: [Key, number, number][];
50
+ /** With the ramp that took the subject out; absent from a store written before a voice kept it. */
51
+ parted: [Key, number, number, Parting?][];
49
52
  }
50
53
  /**
51
54
  * Voices a history store holds whose handles something still holds, by id: a seek that revives
package/dist/revive.js CHANGED
@@ -83,6 +83,7 @@ function packVoice(voice, keys) {
83
83
  outOver: voice.outOver,
84
84
  outSet: voice.outSet,
85
85
  rebuilds: voice.rebuilds,
86
+ hold: voice.hold,
86
87
  past: voice.clocks?.length ?? 0,
87
88
  clocks: voice.clocks,
88
89
  cuedAt: voice.cuedAt,
@@ -107,9 +108,11 @@ function packVoice(voice, keys) {
107
108
  e.seq,
108
109
  packHeld(voice, e.held),
109
110
  e.sync,
111
+ e.ramp,
112
+ e.parted,
110
113
  ]),
111
114
  parts: [...(voice.parts ?? [])].map(([s, r]) => [keys.key(s), r.at, r.over, r.seq]),
112
- parted: [...(voice.parted ?? [])].map(([s, p]) => [keys.key(s), p.at, p.seq]),
115
+ parted: [...(voice.parted ?? [])].map(([s, p]) => p.ramp === undefined ? [keys.key(s), p.at, p.seq] : [keys.key(s), p.at, p.seq, p.ramp]),
113
116
  };
114
117
  }
115
118
  /** A voice a history store paged out, rebuilt from its descriptor and put back as it left. */
@@ -146,17 +149,22 @@ export function reviveVoice(mix, id, d) {
146
149
  }
147
150
  for (const [key, run] of d.runs)
148
151
  voice.motion?.unpack(subjectOf(key), run);
149
- voice.left = Leavings.from(d.left.map(([key, at, seq, p, sync]) => ({
152
+ voice.left = Leavings.from(d.left.map(([key, at, seq, p, sync, ramp = null, parted = null]) => ({
150
153
  subject: subjectOf(key),
151
154
  at,
152
155
  seq,
153
156
  sync,
154
157
  held: unpackHeld(voice, p),
158
+ ramp,
159
+ parted,
155
160
  })));
156
161
  if (d.parts.length > 0)
157
162
  voice.parts = new Map(d.parts.map(([key, at, over, seq]) => [subjectOf(key), { at, over, seq }]));
158
163
  if (d.parted.length > 0)
159
- voice.parted = new Map(d.parted.map(([key, at, seq]) => [subjectOf(key), { at, seq }]));
164
+ voice.parted = new Map(d.parted.map(([key, at, seq, ramp]) => [
165
+ subjectOf(key),
166
+ { at, seq, ramp: ramp ?? undefined },
167
+ ]));
160
168
  const h = mix.outs?.take(id);
161
169
  if (h === undefined)
162
170
  voice.handle = handle(mix, voice);
package/dist/roster.js CHANGED
@@ -4,7 +4,8 @@ import { Store } from './store.js';
4
4
  function tally(mix, voice, by) {
5
5
  if (voice.spec.locus !== undefined)
6
6
  mix.loci += by;
7
- if (voice.spec.anchor !== undefined)
7
+ const e = voice.spec.earliest;
8
+ if (voice.spec.anchor !== undefined || (e !== undefined && typeof e !== 'number'))
8
9
  mix.anchored += by;
9
10
  }
10
11
  /** Counts a voice just put in the list into everything a sync and a fold skip work by. */
package/dist/rows.js CHANGED
@@ -16,8 +16,11 @@ function same(a, b) {
16
16
  /** Takes a crowd row off its subject, to be placed again if a probe meets it again. */
17
17
  export function unplace(lanes, c, p) {
18
18
  const slot = c.list[p];
19
- if (c.idle && slot >= 0)
20
- reach(lanes, slot, -1);
19
+ if (slot >= 0) {
20
+ if (c.idle)
21
+ reach(lanes, slot, -1);
22
+ lanes.placed.remove(slot, c.voices[p].id);
23
+ }
21
24
  c.list[p] = -1;
22
25
  c.records[p] = undefined;
23
26
  c.deltas[p] = null;
@@ -167,15 +170,20 @@ export function bury(lanes, c, p) {
167
170
  lanes.crowdOf.delete(v.id);
168
171
  c.dead++;
169
172
  }
173
+ /** Copies `n` numbers of a row; a view of them to hand `set` was three objects a row moved. */
174
+ function carry(from, a, to, b, n) {
175
+ for (let i = 0; i < n; i++)
176
+ to[b + i] = from[a + i];
177
+ }
170
178
  function copyRow(from, p, to, q, keys = from.keys) {
171
179
  from.fix(p);
172
180
  to.list[q] = from.list[p];
173
- to.data.set(from.data.subarray(p * 8 /* Row.STRIDE */, (p + 1) * 8 /* Row.STRIDE */), q * 8 /* Row.STRIDE */);
174
- to.hot.set(from.hot.subarray(p * from.stride, (p + 1) * from.stride), q * to.stride);
181
+ carry(from.data, p * 8 /* Row.STRIDE */, to.data, q * 8 /* Row.STRIDE */, 8 /* Row.STRIDE */);
182
+ carry(from.hot, p * from.stride, to.hot, q * to.stride, from.stride);
175
183
  copyKeys(keys, to, q);
176
184
  if ((from.hot[p * from.stride + 0 /* Hot.FLAGS */] & 4 /* Flag.STALE */) !== 0)
177
185
  to.stale = true;
178
- to.samples.set(from.samples.subarray(p * from.axes, (p + 1) * from.axes), q * to.axes);
186
+ carry(from.samples, p * from.axes, to.samples, q * to.axes, from.axes);
179
187
  to.deltas[q] = from.deltas[p] ?? null;
180
188
  to.records[q] = from.records[p];
181
189
  to.voices[q] = from.voices[p];
@@ -257,6 +265,8 @@ function leaveCrowd(lanes, c, p) {
257
265
  const slot = c.list[p];
258
266
  const rec = c.records[p];
259
267
  if (slot >= 0) {
268
+ // Its row is emptied or left behind with its crowd, so the subject has it placed no longer.
269
+ lanes.placed.remove(slot, c.voices[p].id);
260
270
  if (c.idle)
261
271
  reach(lanes, slot, -1);
262
272
  const w = reportedRow(lanes, c, p, slot);
package/dist/seek.d.ts CHANGED
@@ -1,3 +1,8 @@
1
1
  import type { Transport } from './transport.js';
2
- /** Moves every mix on the transport to mix time `t`, as it stood at the end of that frame. */
3
- export declare function seek(transport: Transport, t: number): void;
2
+ /**
3
+ * Moves every mix on the transport to mix time `to`, as it stood at the end of that frame, or to
4
+ * the end of the frame numbered `to.frame`.
5
+ */
6
+ export declare function seek(transport: Transport, to: number | {
7
+ readonly frame: number;
8
+ }): void;