@msb235/blits 0.9.0 → 0.11.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 (95) hide show
  1. package/CHANGELOG.md +167 -0
  2. package/README.md +16 -5
  3. package/dist/blend.js +1 -5
  4. package/dist/book.d.ts +8 -5
  5. package/dist/book.js +47 -19
  6. package/dist/chain.d.ts +11 -0
  7. package/dist/chain.js +24 -2
  8. package/dist/clock.d.ts +14 -0
  9. package/dist/clock.js +9 -1
  10. package/dist/columns.d.ts +4 -5
  11. package/dist/columns.js +11 -5
  12. package/dist/crowd.d.ts +1 -1
  13. package/dist/crowd.js +4 -1
  14. package/dist/cue.js +52 -5
  15. package/dist/departed.d.ts +3 -2
  16. package/dist/departed.js +7 -4
  17. package/dist/due.d.ts +4 -0
  18. package/dist/due.js +16 -3
  19. package/dist/fade.d.ts +12 -5
  20. package/dist/fade.js +55 -17
  21. package/dist/fold.d.ts +1 -1
  22. package/dist/fold.js +53 -6
  23. package/dist/frames.d.ts +2 -0
  24. package/dist/frames.js +17 -0
  25. package/dist/handle.d.ts +5 -2
  26. package/dist/handle.js +20 -12
  27. package/dist/held.d.ts +8 -0
  28. package/dist/held.js +37 -6
  29. package/dist/history.d.ts +6 -0
  30. package/dist/history.js +15 -5
  31. package/dist/hosts.d.ts +1 -1
  32. package/dist/hosts.js +18 -6
  33. package/dist/index.d.ts +2 -2
  34. package/dist/index.js +1 -1
  35. package/dist/keyfill.js +3 -1
  36. package/dist/lane.d.ts +1 -1
  37. package/dist/lane.js +3 -1
  38. package/dist/lanes.d.ts +8 -0
  39. package/dist/lanes.js +21 -4
  40. package/dist/lean.d.ts +15 -0
  41. package/dist/lean.js +23 -0
  42. package/dist/leavings.d.ts +6 -1
  43. package/dist/leavings.js +16 -2
  44. package/dist/marks.d.ts +7 -3
  45. package/dist/marks.js +102 -27
  46. package/dist/meet.js +1 -0
  47. package/dist/mixer.d.ts +23 -7
  48. package/dist/mixer.js +73 -29
  49. package/dist/motion.d.ts +4 -1
  50. package/dist/motion.js +16 -2
  51. package/dist/motions.d.ts +4 -0
  52. package/dist/motions.js +45 -11
  53. package/dist/move.js +28 -10
  54. package/dist/owner.d.ts +18 -2
  55. package/dist/owner.js +38 -2
  56. package/dist/pack.d.ts +1 -0
  57. package/dist/pack.js +3 -0
  58. package/dist/placed.d.ts +19 -0
  59. package/dist/placed.js +54 -0
  60. package/dist/project.d.ts +5 -2
  61. package/dist/project.js +236 -32
  62. package/dist/pull.js +37 -8
  63. package/dist/qualify.js +8 -2
  64. package/dist/reading.d.ts +6 -0
  65. package/dist/reading.js +6 -0
  66. package/dist/record.js +1 -0
  67. package/dist/restored.d.ts +7 -4
  68. package/dist/restored.js +13 -10
  69. package/dist/revive.d.ts +5 -3
  70. package/dist/revive.js +10 -3
  71. package/dist/rows.js +15 -5
  72. package/dist/seek.d.ts +7 -2
  73. package/dist/seek.js +90 -35
  74. package/dist/shared.d.ts +5 -0
  75. package/dist/shared.js +5 -0
  76. package/dist/signals.d.ts +10 -0
  77. package/dist/signals.js +17 -0
  78. package/dist/store.d.ts +11 -0
  79. package/dist/store.js +19 -0
  80. package/dist/strays.js +50 -2
  81. package/dist/tape.d.ts +29 -4
  82. package/dist/tape.js +49 -11
  83. package/dist/transport.d.ts +11 -2
  84. package/dist/transport.js +20 -9
  85. package/dist/types/mix.d.ts +42 -15
  86. package/dist/types/patch.d.ts +5 -1
  87. package/dist/types/place.d.ts +25 -11
  88. package/dist/types/transport.d.ts +12 -3
  89. package/dist/types/voice.d.ts +62 -4
  90. package/dist/unpage.d.ts +6 -1
  91. package/dist/unpage.js +34 -2
  92. package/dist/voice.d.ts +57 -11
  93. package/dist/voice.js +43 -3
  94. package/docs/schema.html +75 -40
  95. package/package.json +7 -2
package/dist/motions.js CHANGED
@@ -28,6 +28,17 @@ function ahead(segs, list) {
28
28
  let shared = { xs: new Float64Array(4), vs: new Float64Array(4) };
29
29
  /** What a patch's `xs` and `vs` are before its first subject sets its axes. */
30
30
  const unsized = new Float64Array(0);
31
+ /** Where `applied` reads a stretch at the time a change lands; copied out before it returns. */
32
+ let landing = { x: new Float64Array(4), v: new Float64Array(4) };
33
+ /** Where `numbers` gathers what it copies, kept an array of doubles so every copy is one too. */
34
+ const gathered = [0.5];
35
+ /** `n` numbers of `from` from `at`, as an array of their own. */
36
+ function numbers(from, at, n) {
37
+ // `Array.from` walks a typed array by its iterator: about 500 B a call for three axes.
38
+ for (let i = 0; i < n; i++)
39
+ gathered[i] = from[at + i];
40
+ return gathered.slice(0, n);
41
+ }
31
42
  /**
32
43
  * A subject's flags: its value is a number; it has changes pending; it has older stretches; the
33
44
  * stretch it is playing was found landed, which holds until another replaces it.
@@ -211,6 +222,10 @@ export class Motions {
211
222
  pending: pending === undefined ? undefined : [...pending],
212
223
  };
213
224
  }
225
+ /** Whether the patch holds a run for `subject`. */
226
+ holds(subject) {
227
+ return this.known(subject) !== undefined;
228
+ }
214
229
  /** A subject's run as plain data, for a voice leaving memory; undefined where it has none. */
215
230
  pack(subject) {
216
231
  const s = this.known(subject);
@@ -496,7 +511,7 @@ export class Motions {
496
511
  /** Every change due by `t` applied to a copy of the stretch playing then, committing nothing. */
497
512
  peek(s, t, xo, vo) {
498
513
  let seg = this.playing(s, t);
499
- const list = this.pending?.[s];
514
+ const list = this.dueTo(s);
500
515
  if (list !== undefined)
501
516
  for (const change of list) {
502
517
  const a = change.at;
@@ -507,6 +522,21 @@ export class Motions {
507
522
  }
508
523
  this.evaluateSegment(seg, t, xo, vo);
509
524
  }
525
+ /** Subject `s`'s changes waiting, with those a read ahead's tape made filed among them by time. */
526
+ dueTo(s) {
527
+ const own = this.pending?.[s];
528
+ const ahead = reading.ahead?.get(this);
529
+ if (ahead === undefined)
530
+ return own;
531
+ const subject = this.numbers.subject(s);
532
+ const made = subject === absent ? undefined : ahead.get(subject);
533
+ if (made === undefined)
534
+ return own;
535
+ if (own === undefined)
536
+ return made;
537
+ // The patch's own were made first, and a stable sort keeps them first at one time.
538
+ return [...own.filter((c) => c.at !== undefined), ...made].sort((a, b) => a.at - b.at);
539
+ }
510
540
  check(a, n) {
511
541
  if (a !== undefined && a.length !== n)
512
542
  throw new Error('blits: a motion patch moves every subject on the same number of axes');
@@ -611,9 +641,9 @@ export class Motions {
611
641
  return {
612
642
  at: this.runs[this.base(s)],
613
643
  ms: this.runs[this.base(s) + 2],
614
- x0: Array.from(this.runs.subarray(x, x + n)),
615
- v0: Array.from(this.runs.subarray(x + n, x + 2 * n)),
616
- to: Array.from(this.runs.subarray(x + 2 * n, x + 3 * n)),
644
+ x0: numbers(this.runs, x, n),
645
+ v0: numbers(this.runs, x + n, n),
646
+ to: numbers(this.runs, x + 2 * n, n),
617
647
  change: this.causes?.[s],
618
648
  };
619
649
  }
@@ -636,11 +666,13 @@ export class Motions {
636
666
  return list[lo];
637
667
  }
638
668
  applied(s, seg, change, at) {
639
- const x = new Float64Array(this.n);
640
- const v = new Float64Array(this.n);
669
+ const n = this.n;
670
+ if (landing.x.length < n)
671
+ landing = { x: new Float64Array(n), v: new Float64Array(n) };
672
+ const { x, v } = landing;
641
673
  this.evaluateSegment(seg, at, x, v);
642
- const xs = Array.from(x);
643
- const vs = change.v ?? Array.from(v);
674
+ const xs = numbers(x, 0, n);
675
+ const vs = change.v ?? numbers(v, 0, n);
644
676
  const subject = this.numbers.subject(s);
645
677
  const known = subject === absent ? undefined : subject;
646
678
  const to = this.shape.aim(xs, vs, change.to ?? seg.to, known);
@@ -695,8 +727,7 @@ export class Motions {
695
727
  * The mix time and frame a stretch was in force by, which a store keys it under: when the change
696
728
  * that ended it was made, which falls in the same life of the subject.
697
729
  */
698
- made(next) {
699
- const c = next.change;
730
+ made(c) {
700
731
  const made = c?.made;
701
732
  if (made === undefined || Number.isNaN(made))
702
733
  return { at: this.owner?.now() ?? Number.NaN, seq: this.owner?.seq() ?? Number.NaN };
@@ -717,7 +748,10 @@ export class Motions {
717
748
  const out = list.splice(0, n);
718
749
  const subject = this.numbers.subject(s);
719
750
  if (this.owner !== null && subject !== absent)
720
- this.owner.page(this.ownerId, 'stretch', subject, out.map((seg, i) => ({ ...this.made(out[i + 1] ?? list[0] ?? this.latest(s)), data: seg })));
751
+ this.owner.page(this.ownerId, 'stretch', subject, out.map((seg, i) => {
752
+ const next = out[i + 1] ?? list[0];
753
+ return { ...this.made(next === undefined ? this.causes?.[s] : next.change), data: seg };
754
+ }));
721
755
  if (list.length === 0) {
722
756
  this.older[s] = undefined;
723
757
  this.flag(s, OLDER, false);
package/dist/move.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { changed } from './chain.js';
2
2
  import { elapsedWith } from './clock.js';
3
- import { popDue, schedule } from './due.js';
3
+ import { byId, popDue, schedule } from './due.js';
4
4
  import { detour } from './everyone.js';
5
5
  import { beginFade, part, retire } from './fade.js';
6
6
  import { expireGone, keepGone } from './gone.js';
@@ -66,7 +66,13 @@ function moveTo(mix, now) {
66
66
  (voice.owner === null ? now : ownerReading(voice.owner, now)) >= voice.start)
67
67
  begin(mix, voice, now);
68
68
  // After it starts, so a fade due by now on a voice that started since the last frame begins.
69
- if ((voice.state === 'live' || voice.state === 'frozen') && voice.outAt <= now)
69
+ // Not one set for after its passes run out, where a move across both would begin it first.
70
+ if ((voice.state === 'live' || voice.state === 'frozen') &&
71
+ voice.outAt <= now &&
72
+ !(voice.state === 'live' &&
73
+ !voice.freezesAfter &&
74
+ !voice.freezesBefore &&
75
+ voice.endsAt() < voice.outAt))
70
76
  beginFade(mix, voice, { over: Number.isNaN(voice.outOver) ? undefined : voice.outOver }, Math.max(startOf(voice), voice.outAt));
71
77
  if (voice.state !== 'pending') {
72
78
  if (voice.turns)
@@ -155,11 +161,16 @@ function turn(mix, voice, now) {
155
161
  voice.anchorNow = at;
156
162
  noted(mix, voice);
157
163
  }
158
- /** Past this many voices retiring in one frame, one pass over the list beats a search for each. */
164
+ /**
165
+ * Past this many voices retiring in one frame, and more than one in `SPARSE` of the list, one pass
166
+ * over the list beats a search for each.
167
+ */
159
168
  const SEARCHED = 8;
169
+ const SPARSE = 32;
160
170
  /**
161
- * Takes the voices retired since the last prune out of the list, in list order. A few are found by
162
- * their id, which the list is in order of, so the voices still in it are not read.
171
+ * Takes the voices retired since the last prune out of the list, in list order. A few, or a small
172
+ * share of a long list, are found by their id, which the list is in order of, so the voices still
173
+ * in it are not read.
163
174
  */
164
175
  function prune(mix) {
165
176
  const retired = mix.retired;
@@ -167,9 +178,8 @@ function prune(mix) {
167
178
  const voices = mix.cued;
168
179
  let at = null;
169
180
  // A projection's list holds copies of voices gone before it, which only a pass takes out.
170
- if (retired.length <= SEARCHED && !mix.projecting) {
171
- if (retired.length > 1)
172
- retired.sort((a, b) => a.id - b.id);
181
+ if ((retired.length <= SEARCHED || retired.length * SPARSE <= voices.length) && !mix.projecting) {
182
+ byId(retired);
173
183
  at = [];
174
184
  for (let k = 0; k < retired.length; k++) {
175
185
  const voice = retired[k];
@@ -185,8 +195,16 @@ function prune(mix) {
185
195
  if (at !== null) {
186
196
  for (const voice of retired)
187
197
  pruned = leave(mix, voice) || pruned;
188
- for (let k = at.length - 1; k >= 0; k--)
189
- voices.splice(at[k], 1);
198
+ // Closed up in one pass from the first to go: a splice for each moves the tail once a voice.
199
+ let kept = at[0];
200
+ let k = 0;
201
+ for (let i = kept; i < voices.length; i++) {
202
+ if (at[k] === i)
203
+ k++;
204
+ else
205
+ voices[kept++] = voices[i];
206
+ }
207
+ voices.length = kept;
190
208
  }
191
209
  else {
192
210
  let kept = 0;
package/dist/owner.d.ts CHANGED
@@ -9,6 +9,19 @@ export declare class Holding<V> {
9
9
  /** How many it has held, and how many of those finished their passes. */
10
10
  kids: number;
11
11
  played: number;
12
+ /**
13
+ * When the children that left coast, as pairs of the frame one left in and the latest coast so
14
+ * far, mix time: a pair only where the latest grew, so a seek back cuts it to a prefix.
15
+ */
16
+ coasts: number[];
17
+ /** The frame the first child to leave before its passes ended left in; Infinity for none. */
18
+ cutSeq: number;
19
+ /** The latest coast of a child that left; -Infinity for none. */
20
+ get coast(): number;
21
+ /** A child left in frame `seq`, coasting at `coast`, or before its passes ended for undefined. */
22
+ left(coast: number | undefined, seq: number): void;
23
+ /** Forgets the children that left after frame `seq`, for a seek or read back to it. */
24
+ back(seq: number): void;
12
25
  }
13
26
  /**
14
27
  * What an owner's clock reads at mix time `now`, through every owner above it: -Infinity before it
@@ -41,5 +54,8 @@ export declare function heldByInput<I, O>(voice: Voice<I, O>): boolean;
41
54
  export declare function ownWeight<I, O>(owner: Voice<I, O>, now: number, reduced: boolean, base: (owner: Voice<I, O>) => number): number;
42
55
  /** What every owner above a voice multiplies into its weight. */
43
56
  export declare function ownersWeight<I, O>(voice: Voice<I, O>, now: number, reduced: boolean, base: (owner: Voice<I, O>) => number): number;
44
- /** Points a projection's copies at each other's owners, each owner holding only copies. */
45
- export declare function relink<I, O>(copies: readonly Voice<I, O>[]): void;
57
+ /**
58
+ * Points a projection's copies at each other's owners, each owner holding only copies; for a read
59
+ * back to frame `seq`, as its children that had left by then left it.
60
+ */
61
+ export declare function relink<I, O>(copies: readonly Voice<I, O>[], seq?: number): void;
package/dist/owner.js CHANGED
@@ -13,6 +13,35 @@ export class Holding {
13
13
  /** How many it has held, and how many of those finished their passes. */
14
14
  kids = 0;
15
15
  played = 0;
16
+ /**
17
+ * When the children that left coast, as pairs of the frame one left in and the latest coast so
18
+ * far, mix time: a pair only where the latest grew, so a seek back cuts it to a prefix.
19
+ */
20
+ coasts = [];
21
+ /** The frame the first child to leave before its passes ended left in; Infinity for none. */
22
+ cutSeq = Number.POSITIVE_INFINITY;
23
+ /** The latest coast of a child that left; -Infinity for none. */
24
+ get coast() {
25
+ const n = this.coasts.length;
26
+ return n === 0 ? Number.NEGATIVE_INFINITY : this.coasts[n - 1];
27
+ }
28
+ /** A child left in frame `seq`, coasting at `coast`, or before its passes ended for undefined. */
29
+ left(coast, seq) {
30
+ if (coast === undefined) {
31
+ if (seq < this.cutSeq)
32
+ this.cutSeq = seq;
33
+ }
34
+ else if (coast > this.coast)
35
+ this.coasts.push(seq, coast);
36
+ }
37
+ /** Forgets the children that left after frame `seq`, for a seek or read back to it. */
38
+ back(seq) {
39
+ const c = this.coasts;
40
+ while (c.length > 0 && c[c.length - 2] > seq)
41
+ c.length -= 2;
42
+ if (this.cutSeq > seq)
43
+ this.cutSeq = Number.POSITIVE_INFINITY;
44
+ }
16
45
  }
17
46
  /**
18
47
  * What an owner's clock reads at mix time `now`, through every owner above it: -Infinity before it
@@ -103,8 +132,11 @@ export function ownersWeight(voice, now, reduced, base) {
103
132
  w *= ownWeight(o, now, reduced, base);
104
133
  return w;
105
134
  }
106
- /** Points a projection's copies at each other's owners, each owner holding only copies. */
107
- export function relink(copies) {
135
+ /**
136
+ * Points a projection's copies at each other's owners, each owner holding only copies; for a read
137
+ * back to frame `seq`, as its children that had left by then left it.
138
+ */
139
+ export function relink(copies, seq) {
108
140
  const byId = new Map();
109
141
  for (const v of copies) {
110
142
  byId.set(v.id, v);
@@ -112,6 +144,10 @@ export function relink(copies) {
112
144
  const h = new Holding();
113
145
  h.kids = v.holding.kids;
114
146
  h.played = v.holding.played;
147
+ h.coasts = v.holding.coasts.slice();
148
+ h.cutSeq = v.holding.cutSeq;
149
+ if (seq !== undefined)
150
+ h.back(seq);
115
151
  v.holding = h;
116
152
  }
117
153
  }
package/dist/pack.d.ts CHANGED
@@ -12,6 +12,7 @@ export interface PackedRecord {
12
12
  stepped: number;
13
13
  ticks: number;
14
14
  rebuilt: number;
15
+ copied?: true;
15
16
  /**
16
17
  * What `setting.keep` held, in the order its owners first kept it. An owner is code, which no
17
18
  * store keeps, and a voice revived from a descriptor has new ones; the same code keeps in the same
package/dist/pack.js CHANGED
@@ -18,6 +18,8 @@ export function packHeld(voice, h) {
18
18
  rebuilt: h.rebuilt,
19
19
  kept: kept.length === 0 ? null : kept.map(clone),
20
20
  };
21
+ if (h.copied)
22
+ p.copied = true;
21
23
  if (h.base !== undefined)
22
24
  p.base = clone(h.base);
23
25
  if (h.slope !== undefined)
@@ -42,6 +44,7 @@ export function unpackHeld(voice, p) {
42
44
  h.stepped = p.stepped;
43
45
  h.ticks = p.ticks;
44
46
  h.rebuilt = p.rebuilt;
47
+ h.copied = p.copied === true;
45
48
  if (p.kept !== null)
46
49
  h.unkept = p.kept;
47
50
  if (p.base !== undefined)
@@ -0,0 +1,19 @@
1
+ /**
2
+ * By subject number, the crowd voices whose rows are placed on it, so letting a number go reaches
3
+ * its rows without walking every crowd's. Kept by voice id, which a row keeps as a crowd closes up
4
+ * or is rebuilt around it.
5
+ */
6
+ export declare class Placed {
7
+ /** By subject number, one voice placed on it; 0, which is no voice's id, for none. */
8
+ private one;
9
+ /** The others, for a subject with several; null until some subject has two. */
10
+ private rest;
11
+ /** Makes room for subject numbers below `cap`. */
12
+ grow(cap: number): void;
13
+ /** Some voice placed on the subject at `slot`, 0 for none. */
14
+ any(slot: number): number;
15
+ /** Every voice placed on the subject at `slot`, in no order: for a test to read. */
16
+ all(slot: number): number[];
17
+ add(slot: number, id: number): void;
18
+ remove(slot: number, id: number): void;
19
+ }
package/dist/placed.js ADDED
@@ -0,0 +1,54 @@
1
+ /**
2
+ * By subject number, the crowd voices whose rows are placed on it, so letting a number go reaches
3
+ * its rows without walking every crowd's. Kept by voice id, which a row keeps as a crowd closes up
4
+ * or is rebuilt around it.
5
+ */
6
+ export class Placed {
7
+ /** By subject number, one voice placed on it; 0, which is no voice's id, for none. */
8
+ one = new Int32Array(0);
9
+ /** The others, for a subject with several; null until some subject has two. */
10
+ rest = null;
11
+ /** Makes room for subject numbers below `cap`. */
12
+ grow(cap) {
13
+ const one = new Int32Array(cap);
14
+ one.set(this.one);
15
+ this.one = one;
16
+ }
17
+ /** Some voice placed on the subject at `slot`, 0 for none. */
18
+ any(slot) {
19
+ return this.one[slot] | 0;
20
+ }
21
+ /** Every voice placed on the subject at `slot`, in no order: for a test to read. */
22
+ all(slot) {
23
+ const one = this.any(slot);
24
+ return one === 0 ? [] : [one, ...(this.rest?.get(slot) ?? [])];
25
+ }
26
+ add(slot, id) {
27
+ if (this.one[slot] === 0) {
28
+ this.one[slot] = id;
29
+ return;
30
+ }
31
+ this.rest ??= new Map();
32
+ const more = this.rest.get(slot);
33
+ if (more === undefined)
34
+ this.rest.set(slot, [id]);
35
+ else
36
+ more.push(id);
37
+ }
38
+ remove(slot, id) {
39
+ const more = this.rest?.get(slot);
40
+ if (this.one[slot] === id)
41
+ this.one[slot] = more === undefined ? 0 : more.pop();
42
+ else if (more !== undefined) {
43
+ const i = more.indexOf(id);
44
+ if (i < 0)
45
+ return;
46
+ more[i] = more[more.length - 1];
47
+ more.pop();
48
+ }
49
+ else
50
+ return;
51
+ if (more !== undefined && more.length === 0)
52
+ this.rest.delete(slot);
53
+ }
54
+ }
package/dist/project.d.ts CHANGED
@@ -4,9 +4,12 @@ import type { TransportProjection } from './types.js';
4
4
  import type { Subject, Voice } from './voice.js';
5
5
  /**
6
6
  * Every mix on a transport read at mix time `t`, through copies of them on a transport of their
7
- * own, moved together so an anchor across mixes answers as it would live.
7
+ * own, moved together so an anchor across mixes answers as it would live. Ahead, they stop at each
8
+ * known event on the way, and a subject asked about is read at each before `t`.
8
9
  */
9
- export declare function projectAll(transport: Transport, t: number): TransportProjection;
10
+ export declare function projectAll(transport: Transport, at: number | {
11
+ readonly frame: number;
12
+ }): TransportProjection;
10
13
  /** Sets each copied voice's state to what its clock and fade make it at `t`. */
11
14
  export declare function stateAt<I, O>(c: Mixer<I, O>, t: number): void;
12
15
  /** Gives a projection's copy what it reads of the live mix as it is, before its voices are copied. */