@msb235/blits 0.13.0 → 0.15.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (178) hide show
  1. package/CHANGELOG.md +183 -0
  2. package/README.md +20 -3
  3. package/dist/ahead.d.ts +26 -0
  4. package/dist/ahead.js +264 -0
  5. package/dist/around.d.ts +11 -0
  6. package/dist/around.js +32 -0
  7. package/dist/bare.js +15 -7
  8. package/dist/blank.d.ts +10 -0
  9. package/dist/blank.js +26 -0
  10. package/dist/blend.d.ts +2 -1
  11. package/dist/blend.js +1 -6
  12. package/dist/bound.d.ts +70 -0
  13. package/dist/bound.js +351 -0
  14. package/dist/chain.d.ts +2 -1
  15. package/dist/chain.js +7 -5
  16. package/dist/changes.d.ts +24 -0
  17. package/dist/changes.js +189 -0
  18. package/dist/channels.d.ts +39 -13
  19. package/dist/channels.js +14 -5
  20. package/dist/closed.d.ts +1 -0
  21. package/dist/closed.js +45 -1
  22. package/dist/color.d.ts +12 -10
  23. package/dist/color.js +25 -25
  24. package/dist/columns.js +7 -3
  25. package/dist/contribution.d.ts +30 -0
  26. package/dist/contribution.js +374 -0
  27. package/dist/copyout.d.ts +10 -0
  28. package/dist/copyout.js +69 -24
  29. package/dist/crowd.d.ts +8 -2
  30. package/dist/crowd.js +4 -5
  31. package/dist/cue.d.ts +8 -1
  32. package/dist/cue.js +47 -14
  33. package/dist/cut.d.ts +8 -0
  34. package/dist/cut.js +4 -0
  35. package/dist/drop.d.ts +9 -0
  36. package/dist/drop.js +84 -0
  37. package/dist/due.d.ts +24 -5
  38. package/dist/due.js +113 -33
  39. package/dist/easing.d.ts +4 -4
  40. package/dist/easing.js +3 -3
  41. package/dist/everyone.d.ts +3 -2
  42. package/dist/everyone.js +3 -3
  43. package/dist/fade.d.ts +2 -1
  44. package/dist/fade.js +2 -1
  45. package/dist/fill.d.ts +3 -2
  46. package/dist/fill.js +51 -19
  47. package/dist/fit.d.ts +44 -26
  48. package/dist/fit.js +10 -9
  49. package/dist/fling.d.ts +66 -0
  50. package/dist/fling.js +162 -0
  51. package/dist/fold.d.ts +4 -37
  52. package/dist/fold.js +5 -512
  53. package/dist/gather.d.ts +12 -7
  54. package/dist/gather.js +179 -44
  55. package/dist/gone.js +2 -2
  56. package/dist/grid.d.ts +47 -0
  57. package/dist/grid.js +247 -0
  58. package/dist/handle.d.ts +3 -0
  59. package/dist/handle.js +19 -8
  60. package/dist/held.d.ts +2 -1
  61. package/dist/history.d.ts +6 -5
  62. package/dist/history.js +9 -9
  63. package/dist/hosts.js +9 -15
  64. package/dist/index.d.ts +5 -4
  65. package/dist/keyfill.d.ts +1 -1
  66. package/dist/keyfill.js +5 -3
  67. package/dist/keyrows.d.ts +12 -0
  68. package/dist/keyrows.js +99 -12
  69. package/dist/lane.d.ts +36 -6
  70. package/dist/lane.js +32 -1
  71. package/dist/lanes.d.ts +23 -8
  72. package/dist/lanes.js +29 -8
  73. package/dist/leavings.d.ts +2 -2
  74. package/dist/leavings.js +1 -1
  75. package/dist/lives.d.ts +47 -0
  76. package/dist/lives.js +180 -0
  77. package/dist/meet.d.ts +2 -2
  78. package/dist/meet.js +90 -4
  79. package/dist/mixer.d.ts +61 -35
  80. package/dist/mixer.js +64 -169
  81. package/dist/motion.d.ts +19 -106
  82. package/dist/motion.js +60 -13
  83. package/dist/motions.d.ts +62 -150
  84. package/dist/motions.js +57 -369
  85. package/dist/move.js +24 -1
  86. package/dist/moved.d.ts +1 -1
  87. package/dist/moved.js +16 -7
  88. package/dist/named.d.ts +0 -1
  89. package/dist/named.js +20 -2
  90. package/dist/numbers.d.ts +2 -0
  91. package/dist/numbers.js +4 -0
  92. package/dist/owed.d.ts +2 -0
  93. package/dist/owed.js +4 -0
  94. package/dist/pack.d.ts +3 -1
  95. package/dist/pack.js +4 -3
  96. package/dist/patch.d.ts +7 -3
  97. package/dist/patch.js +6 -2
  98. package/dist/places.d.ts +53 -0
  99. package/dist/places.js +171 -0
  100. package/dist/plot.d.ts +12 -0
  101. package/dist/plot.js +52 -0
  102. package/dist/project.d.ts +4 -4
  103. package/dist/project.js +9 -211
  104. package/dist/pull.js +11 -8
  105. package/dist/qualify.js +84 -21
  106. package/dist/reading.d.ts +6 -0
  107. package/dist/reading.js +5 -0
  108. package/dist/record.d.ts +5 -1
  109. package/dist/record.js +4 -2
  110. package/dist/relink.d.ts +1 -1
  111. package/dist/restored.d.ts +2 -2
  112. package/dist/restored.js +1 -1
  113. package/dist/rests.d.ts +9 -0
  114. package/dist/rests.js +76 -0
  115. package/dist/revive.d.ts +3 -2
  116. package/dist/revive.js +2 -2
  117. package/dist/{locus.d.ts → role.d.ts} +10 -9
  118. package/dist/{locus.js → role.js} +3 -3
  119. package/dist/roles.d.ts +10 -0
  120. package/dist/roles.js +158 -0
  121. package/dist/roster.js +3 -3
  122. package/dist/rotation.d.ts +1 -1
  123. package/dist/rotation.js +1 -1
  124. package/dist/rows.js +1 -1
  125. package/dist/sample.d.ts +2 -2
  126. package/dist/sample.js +33 -7
  127. package/dist/seek.js +13 -5
  128. package/dist/setting.d.ts +27 -0
  129. package/dist/setting.js +55 -0
  130. package/dist/shared.d.ts +1 -1
  131. package/dist/shared.js +1 -1
  132. package/dist/signals.d.ts +64 -32
  133. package/dist/signals.js +20 -18
  134. package/dist/stack.d.ts +5 -5
  135. package/dist/stack.js +5 -5
  136. package/dist/standing.d.ts +2 -1
  137. package/dist/standing.js +3 -2
  138. package/dist/store.d.ts +9 -0
  139. package/dist/store.js +24 -0
  140. package/dist/strays.d.ts +2 -1
  141. package/dist/strays.js +21 -14
  142. package/dist/stretch.d.ts +112 -0
  143. package/dist/stretch.js +9 -0
  144. package/dist/subject.d.ts +142 -0
  145. package/dist/subject.js +1 -0
  146. package/dist/tape.d.ts +9 -5
  147. package/dist/tape.js +5 -1
  148. package/dist/then.d.ts +9 -0
  149. package/dist/then.js +57 -0
  150. package/dist/ticker.d.ts +4 -4
  151. package/dist/transport.d.ts +0 -8
  152. package/dist/transport.js +0 -4
  153. package/dist/turns.d.ts +4 -3
  154. package/dist/turns.js +4 -3
  155. package/dist/types/arc.d.ts +12 -11
  156. package/dist/types/channel.d.ts +12 -8
  157. package/dist/types/history.d.ts +35 -19
  158. package/dist/types/mix.d.ts +222 -161
  159. package/dist/types/motion.d.ts +151 -0
  160. package/dist/types/motion.js +1 -0
  161. package/dist/types/patch.d.ts +69 -34
  162. package/dist/types/place.d.ts +51 -36
  163. package/dist/types/transport.d.ts +20 -18
  164. package/dist/types/voice.d.ts +145 -72
  165. package/dist/types/watch.d.ts +88 -40
  166. package/dist/types.d.ts +1 -0
  167. package/dist/voice.d.ts +9 -141
  168. package/dist/voice.js +30 -79
  169. package/dist/warn.d.ts +2 -0
  170. package/dist/warn.js +1 -1
  171. package/dist/watch.d.ts +2 -2
  172. package/dist/watch.js +5 -3
  173. package/dist/wave.d.ts +4 -3
  174. package/dist/wave.js +3 -2
  175. package/dist/weigh.d.ts +7 -1
  176. package/dist/weigh.js +27 -0
  177. package/docs/schema.html +223 -37
  178. package/package.json +6 -2
package/dist/pack.d.ts CHANGED
@@ -1,4 +1,5 @@
1
- import type { Subject, Voice } from './voice.js';
1
+ import type { Subject } from './subject.js';
2
+ import type { Voice } from './voice.js';
2
3
  /** A subject's record as plain data, for a history store. */
3
4
  export interface PackedRecord {
4
5
  reaches: boolean;
@@ -7,6 +8,7 @@ export interface PackedRecord {
7
8
  shown: number;
8
9
  weight: number;
9
10
  rested: boolean;
11
+ restedAt: number;
10
12
  bands: number[] | null;
11
13
  state: unknown;
12
14
  stepped: number;
package/dist/pack.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { clone } from './clone.js';
2
- import { record } from './record.js';
2
+ import { isRested, record } from './record.js';
3
3
  /** A voice's record of one subject as plain data, its patch's `pack` taking its state. */
4
4
  export function packHeld(voice, h) {
5
5
  const patch = voice.patch;
@@ -10,7 +10,8 @@ export function packHeld(voice, h) {
10
10
  since: h.since,
11
11
  shown: h.shown,
12
12
  weight: h.weight,
13
- rested: h.rested,
13
+ rested: isRested(h),
14
+ restedAt: h.restedAt,
14
15
  bands: h.bands === null ? null : Array.from(h.bands),
15
16
  state: h.state === undefined ? undefined : patch.pack ? patch.pack(h.state) : clone(h.state),
16
17
  stepped: h.stepped,
@@ -39,7 +40,7 @@ export function unpackHeld(voice, p) {
39
40
  h.since = p.since;
40
41
  h.shown = p.shown;
41
42
  h.weight = p.weight;
42
- h.rested = p.rested;
43
+ h.restedAt = p.restedAt;
43
44
  h.bands = bands;
44
45
  h.stepped = p.stepped;
45
46
  h.ticks = p.ticks;
package/dist/patch.d.ts CHANGED
@@ -15,12 +15,14 @@ export interface PatchOptions<I, O, S, H = unknown> {
15
15
  reads?: readonly string[];
16
16
  state?(subject: I): S;
17
17
  step?(state: S, dt: number, subject: I, setting: Setting<S, H>): void;
18
- /** `state` as plain data and back, for a history store: see `Patch.pack`. */
18
+ /** `state` as plain data and back, for a history store. See `Patch.pack`. */
19
19
  pack?(state: S): unknown;
20
20
  unpack?(data: unknown): S;
21
21
  }
22
22
  /**
23
- * The procedural form. Flicker, roving, a hash walk, a starter strike.
23
+ * A patch whose function computes its output from phase, subject and setting each frame.
24
+ *
25
+ * The procedural form, for flicker, roving, a hash walk, a starter strike.
24
26
  *
25
27
  * @category patch
26
28
  */
@@ -137,7 +139,9 @@ export declare function builtOf<I, O, S>(p: Patch<I, O, S>): Built;
137
139
  /** Evaluates a stop list into a fresh delta. */
138
140
  export declare function evalKeys<O>(stops: readonly Keyframe<O>[], writes: readonly (keyof O)[], phase: number, duration: number, opts: KeysOptions<O>, base?: Partial<O>): Partial<O>;
139
141
  /**
140
- * The declarative form. klieg's transition sugar and wod's keyframes.
142
+ * A patch that interpolates between keyframes over its duration.
143
+ *
144
+ * The declarative form, as klieg's transition sugar and wod's keyframes use.
141
145
  *
142
146
  * @category patch
143
147
  */
package/dist/patch.js CHANGED
@@ -1,7 +1,9 @@
1
1
  import { lerpInto, lerpNumber } from './channels.js';
2
2
  import { curve, startSlope } from './easing.js';
3
3
  /**
4
- * The procedural form. Flicker, roving, a hash walk, a starter strike.
4
+ * A patch whose function computes its output from phase, subject and setting each frame.
5
+ *
6
+ * The procedural form, for flicker, roving, a hash walk, a starter strike.
5
7
  *
6
8
  * @category patch
7
9
  */
@@ -326,7 +328,9 @@ export function evalKeys(stops, writes, phase, duration, opts, base) {
326
328
  return readKeys(build(stops, writes, duration, opts), phase, {}, base);
327
329
  }
328
330
  /**
329
- * The declarative form. klieg's transition sugar and wod's keyframes.
331
+ * A patch that interpolates between keyframes over its duration.
332
+ *
333
+ * The declarative form, as klieg's transition sugar and wod's keyframes use.
330
334
  *
331
335
  * @category patch
332
336
  */
@@ -0,0 +1,53 @@
1
+ import { Grid } from './grid.js';
2
+ import { Store } from './store.js';
3
+ /**
4
+ * The index the host gives each subject, by `place` or by its place in the list `plot` was given,
5
+ * and where the host says each one is, over `Grid`. Subjects are held as the rest of the mix holds
6
+ * them: an object weakly, anything else until `drop`. An index a subject leaves, by `drop` or by
7
+ * being collected, is handed out again.
8
+ */
9
+ export declare class Places<I> extends Grid {
10
+ private readonly slotOf;
11
+ private readonly keepRefs;
12
+ /** By subject, its index. */
13
+ at: Store<I, number>;
14
+ /** By index, its subject's lane number, -1 for one with none or a subject let go. */
15
+ slots: Int32Array<ArrayBuffer>;
16
+ /** By lane number, the index it was put in `slots` at, -1 for none. */
17
+ placeOf: Int32Array<ArrayBuffer>;
18
+ /** Outside production, by index, its subject, for the check that a bound does not clip a signal. */
19
+ refs: (WeakRef<object> | I | undefined)[] | null;
20
+ /** While a bounded fill reads the subject at an index, that index, so `around` need not look it up; else -1. */
21
+ cur: number;
22
+ /** Indices a subject left, for `place` to hand out again. */
23
+ free: number[];
24
+ /** Whether `xy` is the mix's own, grown as `place` hands out indices, rather than the array `plot` was given. */
25
+ owned: boolean;
26
+ /**
27
+ * By index, the token its object subject is registered under: a reused index never hears of an
28
+ * old subject's collection.
29
+ */
30
+ private tokens;
31
+ private registry;
32
+ constructor(slotOf: (subject: I) => number, keepRefs: boolean);
33
+ /** Takes the host's subjects and positions; `xy` is read in place, so a move needs only `moved`. */
34
+ set(list: readonly I[], xy: Float64Array, cell: number): void;
35
+ /** `subject`'s index: the one it has, or a new one, a freed index first. */
36
+ place(subject: I): number;
37
+ private hold;
38
+ /** Room for indices below `n`; the mix's own positions start as no position, NaN. */
39
+ private room;
40
+ /** The lanes numbered `subject`: its place, if it has one, reads the number from now on. */
41
+ number(subject: I, slot: number): void;
42
+ private numbered;
43
+ /** The lanes let lane number `slot` go. */
44
+ forgot(slot: number): void;
45
+ /** The host dropped `subject`: its index is freed. */
46
+ drop(subject: I): void;
47
+ /** Frees index `i`, its subject dropped or collected. */
48
+ private release;
49
+ /** The subject at index `i`, where it is still held; only outside production. */
50
+ subjectAt(i: number): I | undefined;
51
+ /** Whether `subject` is within `r` of (`cx`, `cy`), by the same test `query` makes. */
52
+ inside(subject: I, cx: number, cy: number, r2: number): boolean;
53
+ }
package/dist/places.js ADDED
@@ -0,0 +1,171 @@
1
+ import { Grid } from './grid.js';
2
+ import { Store } from './store.js';
3
+ /**
4
+ * The index the host gives each subject, by `place` or by its place in the list `plot` was given,
5
+ * and where the host says each one is, over `Grid`. Subjects are held as the rest of the mix holds
6
+ * them: an object weakly, anything else until `drop`. An index a subject leaves, by `drop` or by
7
+ * being collected, is handed out again.
8
+ */
9
+ export class Places extends Grid {
10
+ slotOf;
11
+ keepRefs;
12
+ /** By subject, its index. */
13
+ at = new Store();
14
+ /** By index, its subject's lane number, -1 for one with none or a subject let go. */
15
+ slots = new Int32Array(0);
16
+ /** By lane number, the index it was put in `slots` at, -1 for none. */
17
+ placeOf = new Int32Array(0);
18
+ /** Outside production, by index, its subject, for the check that a bound does not clip a signal. */
19
+ refs = null;
20
+ /** While a bounded fill reads the subject at an index, that index, so `around` need not look it up; else -1. */
21
+ cur = -1;
22
+ /** Indices a subject left, for `place` to hand out again. */
23
+ free = [];
24
+ /** Whether `xy` is the mix's own, grown as `place` hands out indices, rather than the array `plot` was given. */
25
+ owned = true;
26
+ /**
27
+ * By index, the token its object subject is registered under: a reused index never hears of an
28
+ * old subject's collection.
29
+ */
30
+ tokens = [];
31
+ registry = null;
32
+ constructor(slotOf, keepRefs) {
33
+ super();
34
+ this.slotOf = slotOf;
35
+ this.keepRefs = keepRefs;
36
+ this.refs = keepRefs ? [] : null;
37
+ }
38
+ /** Takes the host's subjects and positions; `xy` is read in place, so a move needs only `moved`. */
39
+ set(list, xy, cell) {
40
+ const n = list.length;
41
+ this.at = new Store();
42
+ for (let i = 0; i < n; i++)
43
+ this.at.set(list[i], i);
44
+ this.slots = new Int32Array(Math.max(n, 16)).fill(-1);
45
+ this.placeOf.fill(-1);
46
+ this.refs = this.keepRefs ? [] : null;
47
+ this.tokens = [];
48
+ this.free = [];
49
+ for (let i = 0; i < n; i++) {
50
+ const subject = list[i];
51
+ // A subject listed twice is at its last index.
52
+ if (this.at.get(subject) !== i)
53
+ continue;
54
+ this.hold(subject, i);
55
+ }
56
+ this.n = n;
57
+ this.xy = xy;
58
+ this.owned = false;
59
+ this.cell = cell;
60
+ this.dirty = true;
61
+ }
62
+ /** `subject`'s index: the one it has, or a new one, a freed index first. */
63
+ place(subject) {
64
+ const had = this.at.get(subject);
65
+ if (had !== undefined)
66
+ return had;
67
+ const i = this.free.pop() ?? this.n++;
68
+ this.room(i + 1);
69
+ this.at.set(subject, i);
70
+ this.hold(subject, i);
71
+ if (this.owned)
72
+ this.put(i, Number.NaN, Number.NaN);
73
+ return i;
74
+ }
75
+ hold(subject, i) {
76
+ const slot = this.slotOf(subject);
77
+ if (slot >= 0)
78
+ this.numbered(slot, i);
79
+ const object = typeof subject === 'object' && subject !== null;
80
+ if (this.refs !== null)
81
+ this.refs[i] = object ? new WeakRef(subject) : subject;
82
+ if (!object)
83
+ return;
84
+ const token = { i };
85
+ this.tokens[i] = token;
86
+ this.registry ??= new FinalizationRegistry((t) => {
87
+ if (this.tokens[t.i] === t)
88
+ this.release(t.i);
89
+ });
90
+ this.registry.register(subject, token, token);
91
+ }
92
+ /** Room for indices below `n`; the mix's own positions start as no position, NaN. */
93
+ room(n) {
94
+ if (this.slots.length < n) {
95
+ const slots = new Int32Array(Math.max(n, this.slots.length * 2)).fill(-1);
96
+ slots.set(this.slots);
97
+ this.slots = slots;
98
+ }
99
+ if (this.owned && this.xy.length < 2 * n) {
100
+ const xy = new Float64Array(Math.max(2 * n, this.xy.length * 2, 32)).fill(Number.NaN);
101
+ xy.set(this.xy);
102
+ this.xy = xy;
103
+ }
104
+ }
105
+ /** The lanes numbered `subject`: its place, if it has one, reads the number from now on. */
106
+ number(subject, slot) {
107
+ const i = this.at.get(subject);
108
+ if (i !== undefined)
109
+ this.numbered(slot, i);
110
+ }
111
+ numbered(slot, i) {
112
+ if (slot >= this.placeOf.length) {
113
+ const grown = new Int32Array(Math.max(slot + 1, this.placeOf.length * 2, 64)).fill(-1);
114
+ grown.set(this.placeOf);
115
+ this.placeOf = grown;
116
+ }
117
+ this.placeOf[slot] = i;
118
+ this.slots[i] = slot;
119
+ }
120
+ /** The lanes let lane number `slot` go. */
121
+ forgot(slot) {
122
+ if (slot >= this.placeOf.length)
123
+ return;
124
+ const i = this.placeOf[slot];
125
+ if (i < 0)
126
+ return;
127
+ this.placeOf[slot] = -1;
128
+ if (this.slots[i] === slot)
129
+ this.slots[i] = -1;
130
+ }
131
+ /** The host dropped `subject`: its index is freed. */
132
+ drop(subject) {
133
+ const i = this.at.get(subject);
134
+ if (i === undefined)
135
+ return;
136
+ this.at.delete(subject);
137
+ const token = this.tokens[i];
138
+ if (token !== undefined)
139
+ this.registry?.unregister(token);
140
+ this.release(i);
141
+ }
142
+ /** Frees index `i`, its subject dropped or collected. */
143
+ release(i) {
144
+ this.tokens[i] = undefined;
145
+ if (this.refs !== null)
146
+ this.refs[i] = undefined;
147
+ const slot = this.slots[i];
148
+ if (slot >= 0)
149
+ this.placeOf[slot] = -1;
150
+ this.slots[i] = -1;
151
+ // The host's own array is the host's: a reused index reads whatever it holds there.
152
+ if (this.owned)
153
+ this.put(i, Number.NaN, Number.NaN);
154
+ this.free.push(i);
155
+ }
156
+ /** The subject at index `i`, where it is still held; only outside production. */
157
+ subjectAt(i) {
158
+ const ref = this.refs?.[i];
159
+ const s = ref instanceof WeakRef ? ref.deref() : ref;
160
+ return s !== undefined && this.at.get(s) === i ? s : undefined;
161
+ }
162
+ /** Whether `subject` is within `r` of (`cx`, `cy`), by the same test `query` makes. */
163
+ inside(subject, cx, cy, r2) {
164
+ const i = this.at.get(subject);
165
+ if (i === undefined || 2 * i + 1 >= this.xy.length)
166
+ return false;
167
+ const dx = this.xy[2 * i] - cx;
168
+ const dy = this.xy[2 * i + 1] - cy;
169
+ return dx * dx + dy * dy <= r2;
170
+ }
171
+ }
package/dist/plot.d.ts ADDED
@@ -0,0 +1,12 @@
1
+ import type { Mixer } from './mixer.js';
2
+ /** `Mix.plot`: the mix's subjects and their positions, `x` then `y` side by side in `xy`, read in place. */
3
+ export declare function plot<I, O>(this: Mixer<I, O>, subjects: readonly I[], xy: Float64Array, cell?: number): void;
4
+ /** `Mix.place`: the subject's index, its own or a new one, which a `within` finder names it by. */
5
+ export declare function place<I, O>(this: Mixer<I, O>, subject: I): number;
6
+ /** `Mix.moved`: the host wrote new positions into the `xy` it gave `plot`. */
7
+ export declare function moved<I, O>(this: Mixer<I, O>): void;
8
+ /**
9
+ * `Mix.put`: moves one subject, writing into the `xy` `plot` gave or the mix's own. The grid is built
10
+ * again only once a sixteenth of the indices have left their cells.
11
+ */
12
+ export declare function put<I, O>(this: Mixer<I, O>, subject: I, x: number, y: number): void;
package/dist/plot.js ADDED
@@ -0,0 +1,52 @@
1
+ import { bind } from './bound.js';
2
+ import { Places } from './places.js';
3
+ import { production } from './warn.js';
4
+ /** `Mix.plot`: the mix's subjects and their positions, `x` then `y` side by side in `xy`, read in place. */
5
+ export function plot(subjects, xy, cell) {
6
+ if (xy.length < 2 * subjects.length)
7
+ throw new RangeError(`blits: plot takes two numbers a subject, x then y: ${subjects.length} subjects need ${2 * subjects.length}, not ${xy.length}`);
8
+ if (cell !== undefined && !(cell > 0 && cell < Number.POSITIVE_INFINITY))
9
+ throw new RangeError(`blits: plot's cell is a size above 0, not ${cell}`);
10
+ placesOf(this).set(subjects, xy, cell ?? Number.NaN);
11
+ this.moved();
12
+ }
13
+ /** `Mix.place`: the subject's index, its own or a new one, which a `within` finder names it by. */
14
+ export function place(subject) {
15
+ return placesOf(this).place(subject);
16
+ }
17
+ /**
18
+ * The mix's indices, made at the first `place` or `plot`. An `around` voice cued before then read 0
19
+ * everywhere, so it takes its circle with nothing to undo.
20
+ */
21
+ function placesOf(mix) {
22
+ if (mix.places !== null)
23
+ return mix.places;
24
+ const places = new Places((s) => mix.chains.get(s)?.slot ?? -1, !production());
25
+ mix.places = places;
26
+ for (const voice of mix.cued)
27
+ if (voice.bound === null)
28
+ bind(mix, voice);
29
+ return places;
30
+ }
31
+ /** `Mix.moved`: the host wrote new positions into the `xy` it gave `plot`. */
32
+ export function moved() {
33
+ if (this.places !== null)
34
+ this.places.dirty = true;
35
+ this.lanes?.refill();
36
+ this.stir();
37
+ }
38
+ /**
39
+ * `Mix.put`: moves one subject, writing into the `xy` `plot` gave or the mix's own. The grid is built
40
+ * again only once a sixteenth of the indices have left their cells.
41
+ */
42
+ export function put(subject, x, y) {
43
+ const places = this.places;
44
+ const i = places === null ? undefined : places.at.get(subject);
45
+ if (places === null || i === undefined)
46
+ throw new Error('blits: put takes a subject with an index: place or plot it first');
47
+ if (2 * i + 1 >= places.xy.length)
48
+ throw new RangeError(`blits: put's subject is at index ${i}, past the positions plot was given: plot again with room for it`);
49
+ places.put(i, x, y);
50
+ this.lanes?.refill();
51
+ this.stir();
52
+ }
package/dist/project.d.ts CHANGED
@@ -1,7 +1,9 @@
1
+ import { type Cut } from './cut.js';
1
2
  import { Mixer } from './mixer.js';
2
- import { type Cut, Transport } from './transport.js';
3
+ import type { Subject } from './subject.js';
4
+ import { Transport } from './transport.js';
3
5
  import type { TransportProjection } from './types.js';
4
- import type { Subject, Voice } from './voice.js';
6
+ import type { Voice } from './voice.js';
5
7
  /**
6
8
  * Every mix on a transport read at mix time `t`, through copies of them on a transport of their
7
9
  * own, moved together so an anchor across mixes answers as it would live. Ahead, they stop at each
@@ -12,8 +14,6 @@ export declare function projectAll(transport: Transport, at: number | {
12
14
  }): TransportProjection;
13
15
  /** Sets each copied voice's state to what its clock and fade make it at `t`. */
14
16
  export declare function stateAt<I, O>(c: Mixer<I, O>, t: number): void;
15
- /** Gives a projection's copy what it reads of the live mix as it is, before its voices are copied. */
16
- export declare function mirror<I, O>(mix: Mixer<I, O>, c: Mixer<I, O>): void;
17
17
  /**
18
18
  * A projection back starts each subject from the latest copy kept at or before `t`, else from its
19
19
  * voice's start with fresh state, which is exact for a voice stepped at a fixed interval.
package/dist/project.js CHANGED
@@ -1,23 +1,20 @@
1
+ import { copyAhead, mirror, playAhead, recordOf } from './ahead.js';
1
2
  import { ownBlends } from './blend.js';
2
- import { headOf, heldAt } from './chain.js';
3
+ import { heldAt } from './chain.js';
3
4
  import { stepOf } from './clock.js';
4
- import { open, sighted, weightAt } from './everyone.js';
5
+ import { within } from './cut.js';
5
6
  import { fadesAt } from './fade.js';
6
7
  import { meet, origin } from './held.js';
7
8
  import { copyHeld, last, lastWithin } from './history.js';
8
- import { markOf, voiceMarks } from './marks.js';
9
9
  import { Mixer } from './mixer.js';
10
- import { move } from './move.js';
11
10
  import { heldByInput, ownerReading, relink } from './owner.js';
12
- import { pin, place } from './place.js';
13
11
  import { reading } from './reading.js';
14
12
  import { record } from './record.js';
15
13
  import { recount } from './roster.js';
16
14
  import { copyStanding, stands } from './standing.js';
17
- import { hostAhead, hostAt, replay } from './tape.js';
18
- import { Transport, within } from './transport.js';
15
+ import { hostAhead, hostAt } from './tape.js';
16
+ import { Transport } from './transport.js';
19
17
  import { cover, coverFrame } from './unpage.js';
20
- import { unreached } from './unreached.js';
21
18
  /**
22
19
  * Every mix on a transport read at mix time `t`, through copies of them on a transport of their
23
20
  * own, moved together so an anchor across mixes answers as it would live. Ahead, they stop at each
@@ -75,6 +72,7 @@ export function projectAll(transport, at) {
75
72
  }, null);
76
73
  copy.slot = mix.slot;
77
74
  copy.projecting = true;
75
+ copy.places = mix.places;
78
76
  copies.set(mix, copy);
79
77
  if (ahead)
80
78
  copyAhead(mix, copy);
@@ -91,15 +89,18 @@ export function projectAll(transport, at) {
91
89
  earlier = new Map();
92
90
  stood = playAhead(transport, c, copies, t, earlier);
93
91
  }
92
+ const back = !ahead && kept ? cut : null;
94
93
  const read = (f) => {
95
94
  reading.live = false;
96
95
  reading.ahead = stood.ahead.size > 0 ? stood.ahead : null;
96
+ reading.back = back;
97
97
  try {
98
98
  return f();
99
99
  }
100
100
  finally {
101
101
  reading.live = true;
102
102
  reading.ahead = null;
103
+ reading.back = null;
103
104
  }
104
105
  };
105
106
  return {
@@ -136,167 +137,6 @@ export function projectAll(transport, at) {
136
137
  },
137
138
  };
138
139
  }
139
- /**
140
- * Moves a read ahead's copies to mix time `t`, making the calls the tape holds on the way as a seek
141
- * there makes them on the mixes. While a voice could carry state across it, it stops at each known
142
- * event: a call, and every mark of a voice or announced. At each stop the copies are kept for
143
- * subjects to be read there and copied on, so `copies` ends holding those at `t`. Answers the
144
- * transport they stand on, and puts the tape back where it stood.
145
- */
146
- function playAhead(transport, c, copies, t, earlier) {
147
- const tape = transport.tape;
148
- const depth = tape?.undoDepth() ?? 0;
149
- let played = false;
150
- try {
151
- for (;;) {
152
- const e = nextEvent(tape, c, t);
153
- if (!(e < t))
154
- break;
155
- played = playTo(transport, c, copies, e) || played;
156
- if (!carries(c))
157
- continue;
158
- // Placed now, as the next move would place them, while a target that has just left is here.
159
- for (const m of c.members)
160
- if (m.anchored > 0)
161
- place(m);
162
- const next = restage(c);
163
- for (const [mix, copy] of copies) {
164
- const on = new Mixer(copy.kit, { ...copy.opts, transport: next }, null);
165
- on.slot = copy.slot;
166
- on.projecting = true;
167
- copyAhead(copy, on);
168
- const stages = earlier.get(mix);
169
- if (stages === undefined)
170
- earlier.set(mix, { at: [copy], met: new Set() });
171
- else
172
- stages.at.push(copy);
173
- copies.set(mix, on);
174
- }
175
- c = next;
176
- }
177
- played = playTo(transport, c, copies, t) || played;
178
- }
179
- finally {
180
- if (played)
181
- tape?.goto(depth);
182
- }
183
- return c;
184
- }
185
- /**
186
- * Whether a voice on a read ahead's copies could carry state from one known event to the next: a
187
- * patch that steps, or a weight given by a signal, which may keep state. Where none could, a read
188
- * crosses the events in one go, as fast as one with none.
189
- */
190
- function carries(c) {
191
- for (const m of c.members)
192
- for (const v of m.cued)
193
- if (v.state !== 'done' && (v.patch.step !== undefined || typeof v.spec.weight === 'function'))
194
- return true;
195
- return false;
196
- }
197
- /**
198
- * The earliest known event a read ahead's copies have yet to reach before `t`, mix time: the next
199
- * call the tape holds, and while a voice could carry state across one, a mark of a voice or one
200
- * announced. Infinity where there is none.
201
- */
202
- function nextEvent(tape, c, t) {
203
- const now = c.now;
204
- let e = Number.POSITIVE_INFINITY;
205
- const call = tape?.timestampAt(tape.undoDepth());
206
- if (call !== undefined)
207
- e = c.pace === null ? call : c.pace.reading(call);
208
- if (!carries(c))
209
- return e < t ? e : Number.POSITIVE_INFINITY;
210
- for (const m of c.members)
211
- for (const v of m.cued) {
212
- if (v.state === 'done')
213
- continue;
214
- for (const mark of voiceMarks) {
215
- const at = markOf(m, v, mark);
216
- if (at !== undefined && at > now && at < e)
217
- e = at;
218
- }
219
- const inner = v.spec.marks;
220
- if (inner !== undefined)
221
- for (const mark of Object.keys(inner)) {
222
- const at = markOf(m, v, mark);
223
- if (at !== undefined && at > now && at < e)
224
- e = at;
225
- }
226
- }
227
- for (const a of c.announced) {
228
- const at = c.pace === null ? a.at : c.pace.at(a.at, c.u);
229
- if (at > now && at < e)
230
- e = at;
231
- }
232
- return e < t ? e : Number.POSITIVE_INFINITY;
233
- }
234
- /**
235
- * Makes the calls the tape holds up to mix time `to` on a read ahead's copies and moves them
236
- * there. Answers whether it moved the tape.
237
- */
238
- function playTo(transport, c, copies, to) {
239
- const tape = transport.tape;
240
- const due = (at) => (c.pace === null ? at : c.pace.reading(at)) <= to;
241
- const next = tape?.timestampAt(tape.undoDepth());
242
- c.u = hostAhead(c, to);
243
- let played = false;
244
- if (next !== undefined && due(next)) {
245
- // A call may write what a copy shares with the voice it was copied from.
246
- for (const copy of c.members)
247
- for (const v of copy.cued) {
248
- if (v.parts !== null)
249
- v.parts = new Map(v.parts);
250
- if (v.parted !== null)
251
- v.parted = new Map(v.parted);
252
- }
253
- played = true;
254
- const last = replay(transport, due, c, {
255
- transport: () => c,
256
- mix: (of) => copies.get(of),
257
- });
258
- c.u = Math.max(last, hostAhead(c, to));
259
- }
260
- // A call played at `to` itself moved the copies there before it, as a sync does.
261
- if (!(c.now >= to))
262
- for (const copy of c.members)
263
- move(copy, to);
264
- return played;
265
- }
266
- /** A transport for a read ahead's copies to go on from where those on `c` stand. */
267
- function restage(c) {
268
- const next = new Transport(undefined, true);
269
- next.offset = c.offset;
270
- next.u = c.u;
271
- next.nextId = c.nextId;
272
- next.now = c.now;
273
- next.pace = c.pace === null ? null : c.pace.until(Number.POSITIVE_INFINITY);
274
- next.announced = c.announced.map((a) => ({ ...a }));
275
- // The retargets and pushes the tape made carry their times, so every stop shares one list.
276
- next.ahead = c.ahead;
277
- return next;
278
- }
279
- /**
280
- * A mix's copy for a read ahead: its voices as they stand, each subject carried from the live one,
281
- * or from the copy at the known event before.
282
- */
283
- function copyAhead(mix, c) {
284
- mirror(mix, c);
285
- c.cued = mix.cued
286
- .filter((v) => v.state !== 'done')
287
- .map((v) => {
288
- const copy = v.copy((subject) => carry(mix, v, subject, c, copy), undefined, true);
289
- const at = mix.pins?.get(v);
290
- if (at !== undefined)
291
- pin(c, copy, at);
292
- return copy;
293
- });
294
- ownBlends(c.cued);
295
- // A copy keeps no list of owners, so the one before says only by its voices.
296
- if (mix.owners !== null || (mix.projecting && c.cued.some((v) => v.holding !== null)))
297
- relink(c.cued);
298
- recount(c);
299
- }
300
140
  /** A mix's copy for a read back to `t`, cut at `cut`: its voices then, from the controls and copies kept. */
301
141
  function copyBack(mix, c, t, cut) {
302
142
  mirror(mix, c);
@@ -351,48 +191,6 @@ export function stateAt(c, t) {
351
191
  ? 'frozen'
352
192
  : 'live';
353
193
  }
354
- /** Gives a projection's copy what it reads of the live mix as it is, before its voices are copied. */
355
- export function mirror(mix, c) {
356
- c.pose = mix.pose;
357
- c.wantsPose = mix.wantsPose;
358
- c.reducedNow = mix.reducedNow;
359
- }
360
- /** A voice's record of a subject, the one it shares among those it does not reach included. */
361
- function recordOf(mix, voice, subject) {
362
- // A voice sharing one record has that for every subject a chain has asked it about, pointed at
363
- // the subject for the caller to copy from.
364
- if (voice.sharing) {
365
- const slot = headOf(mix, subject)?.slot ?? -1;
366
- if (!sighted(voice, slot))
367
- return undefined;
368
- const all = voice.everyone;
369
- open(voice, all, slot);
370
- all.weight = weightAt(voice, slot);
371
- return all;
372
- }
373
- const held = voice.subjects.get(subject);
374
- if (held !== undefined || voice.unreachedBits === null)
375
- return held;
376
- return unreached(voice, headOf(mix, subject)?.slot ?? -1)
377
- ? voice.unreached
378
- : undefined;
379
- }
380
- /**
381
- * A projection ahead starts each subject from where the live mix holds it, its `since` worked out
382
- * again where it was still ahead, since the copy's clock may have moved it.
383
- */
384
- function carry(mix, voice, subject, c, copy) {
385
- const live = recordOf(mix, voice, subject);
386
- if (live === undefined)
387
- return undefined;
388
- const h = copyHeld(voice, live);
389
- if (h.reaches && !(h.since <= mix.now)) {
390
- h.since = c.sinceOf(copy, h.delay);
391
- h.shown = c.shownOf(copy, h.since);
392
- }
393
- h.from = h.stepped;
394
- return h;
395
- }
396
194
  /**
397
195
  * A projection back starts each subject from the latest copy kept at or before `t`, else from its
398
196
  * voice's start with fresh state, which is exact for a voice stepped at a fixed interval.