@msb235/blits 0.15.0 → 0.16.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 (80) hide show
  1. package/CHANGELOG.md +79 -0
  2. package/README.md +1 -1
  3. package/dist/ahead.js +5 -4
  4. package/dist/bare.js +22 -7
  5. package/dist/chain.js +2 -3
  6. package/dist/color.d.ts +36 -14
  7. package/dist/color.js +186 -58
  8. package/dist/columns.d.ts +3 -12
  9. package/dist/columns.js +56 -41
  10. package/dist/crowd.d.ts +8 -10
  11. package/dist/crowd.js +17 -18
  12. package/dist/cue.js +7 -4
  13. package/dist/delay.d.ts +3 -0
  14. package/dist/delay.js +7 -0
  15. package/dist/due.js +1 -1
  16. package/dist/easing.d.ts +2 -0
  17. package/dist/easing.js +4 -0
  18. package/dist/everyone.d.ts +22 -6
  19. package/dist/everyone.js +47 -8
  20. package/dist/fade.d.ts +11 -0
  21. package/dist/fade.js +44 -8
  22. package/dist/fill.js +1 -1
  23. package/dist/fold.js +4 -7
  24. package/dist/held.js +5 -5
  25. package/dist/hosts.d.ts +7 -0
  26. package/dist/hosts.js +18 -5
  27. package/dist/index.d.ts +2 -2
  28. package/dist/index.js +1 -1
  29. package/dist/lane.d.ts +2 -5
  30. package/dist/lane.js +14 -10
  31. package/dist/lanes.d.ts +33 -5
  32. package/dist/lanes.js +34 -28
  33. package/dist/lch.d.ts +23 -0
  34. package/dist/lch.js +126 -0
  35. package/dist/meet.js +5 -3
  36. package/dist/mixer.d.ts +6 -2
  37. package/dist/mixer.js +7 -0
  38. package/dist/motions.d.ts +4 -1
  39. package/dist/motions.js +16 -2
  40. package/dist/move.js +15 -10
  41. package/dist/named.d.ts +6 -1
  42. package/dist/named.js +29 -1
  43. package/dist/numbers.d.ts +5 -0
  44. package/dist/numbers.js +29 -0
  45. package/dist/owner.d.ts +7 -2
  46. package/dist/owner.js +13 -3
  47. package/dist/project.js +3 -8
  48. package/dist/pull.d.ts +11 -0
  49. package/dist/pull.js +18 -4
  50. package/dist/qualify.js +10 -11
  51. package/dist/renumber.d.ts +10 -0
  52. package/dist/renumber.js +75 -0
  53. package/dist/roles.js +1 -3
  54. package/dist/roster.d.ts +6 -0
  55. package/dist/roster.js +8 -1
  56. package/dist/rows.js +27 -19
  57. package/dist/sample.js +2 -1
  58. package/dist/seat.d.ts +17 -0
  59. package/dist/seat.js +25 -0
  60. package/dist/seek.js +1 -13
  61. package/dist/shared.d.ts +1 -1
  62. package/dist/shared.js +1 -1
  63. package/dist/spans.d.ts +4 -1
  64. package/dist/spans.js +6 -3
  65. package/dist/strays.js +3 -2
  66. package/dist/touch.js +1 -2
  67. package/dist/trace.d.ts +4 -0
  68. package/dist/trace.js +255 -0
  69. package/dist/turns.js +2 -1
  70. package/dist/types/mix.d.ts +15 -0
  71. package/dist/types/trace.d.ts +51 -0
  72. package/dist/types/trace.js +1 -0
  73. package/dist/types/voice.d.ts +13 -7
  74. package/dist/types.d.ts +1 -0
  75. package/dist/unreached.d.ts +2 -0
  76. package/dist/unreached.js +7 -0
  77. package/dist/voice.d.ts +22 -0
  78. package/dist/voice.js +44 -4
  79. package/docs/schema.html +48 -11
  80. package/package.json +1 -1
package/dist/columns.js CHANGED
@@ -1,9 +1,4 @@
1
- export function clampRun(out, at, n, [lo, hi]) {
2
- for (let i = at; i < at + n; i++) {
3
- const x = out[i];
4
- out[i] = x < lo ? lo : x > hi ? hi : x;
5
- }
6
- }
1
+ import { clampRun } from './pull.js';
7
2
  /**
8
3
  * For a `pull` of a list it read last time in the same order, with every voice on a lane, where
9
4
  * each position's remembered chain head was current when the mix's `relinks` read `since`: what
@@ -26,15 +21,8 @@ export function pullRun(lanes, slots, list, was, since, from, columns, now, vers
26
21
  // Nothing in `pullRun` makes a subject owe a voice, so one with nobody owing need not ask.
27
22
  const owing = lanes.owed.any();
28
23
  const current = since === Number.POSITIVE_INFINITY;
29
- if (columns !== lanes.queuedFor) {
30
- flush(lanes);
31
- lanes.queuedFor = columns;
32
- }
24
+ queueFor(lanes, columns, slots);
33
25
  room(lanes, lanes.queued + list.length - from);
34
- const qSlots = lanes.queueSlots;
35
- const qRows = lanes.queueRows;
36
- const qCounts = lanes.queueCounts;
37
- let k = lanes.queued;
38
26
  let n = from;
39
27
  // The run being gathered: its first slot and row, and how far it has reached.
40
28
  let first = -1;
@@ -60,23 +48,14 @@ export function pullRun(lanes, slots, list, was, since, from, columns, now, vers
60
48
  if (holds)
61
49
  subjects[slot] = subject;
62
50
  if (slot !== first + (n - start)) {
63
- if (first >= 0) {
64
- qSlots[k] = first;
65
- qRows[k] = start;
66
- qCounts[k] = n - start;
67
- k++;
68
- }
51
+ if (first >= 0)
52
+ add(lanes, first, start, n - start);
69
53
  first = slot;
70
54
  start = n;
71
55
  }
72
56
  }
73
- if (first >= 0) {
74
- qSlots[k] = first;
75
- qRows[k] = start;
76
- qCounts[k] = n - start;
77
- k++;
78
- }
79
- lanes.queued = k;
57
+ if (first >= 0)
58
+ add(lanes, first, start, n - start);
80
59
  if (n > from && holds)
81
60
  lanes.holding = true;
82
61
  return n;
@@ -85,24 +64,43 @@ export function pullRun(lanes, slots, list, was, since, from, columns, now, vers
85
64
  * Queues a subject's values for each column at subject `n`'s places, for a `pull` that reads
86
65
  * from the lanes while every voice is on one; `flush` writes what is queued, column by column. A
87
66
  * channel no lane holds is at rest. Anything queued is written before the lanes fill again.
67
+ * `slots` holds each row's lane slot, `slot` among them.
88
68
  */
89
- export function writeLater(lanes, slot, columns, n) {
90
- queue(lanes, columns, slot, n, 1);
69
+ export function writeLater(lanes, slot, columns, n, slots) {
70
+ queueFor(lanes, columns, slots);
71
+ add(lanes, slot, n, 1);
91
72
  }
92
- /** Queues `count` subjects from lane slot `slot` for rows from `row`, one apart in both. */
93
- function queue(lanes, columns, slot, row, count) {
94
- if (columns !== lanes.queuedFor) {
95
- flush(lanes);
96
- lanes.queuedFor = columns;
97
- }
73
+ /** Writes what is queued for other columns or another array of slots before queuing for these. */
74
+ function queueFor(lanes, columns, slots) {
75
+ if (columns === lanes.queuedFor && slots === lanes.queueFrom)
76
+ return;
77
+ flush(lanes);
78
+ lanes.queuedFor = columns;
79
+ lanes.queueFrom = slots;
80
+ }
81
+ /**
82
+ * The shortest run `flush` copies as a block from consecutive slots. A shorter one, as a list kept
83
+ * dense by swap-remove leaves its slots, joins the rows before it in one run whose slots `flush`
84
+ * reads row by row, so a run's cost is paid once a stretch of rows rather than once a few.
85
+ */
86
+ const BLOCK = 16;
87
+ /** Queues `count` rows from `row`, whose slots follow one apart from `slot`. */
88
+ function add(lanes, slot, row, count) {
98
89
  const k = lanes.queued;
99
90
  if (k > 0) {
100
91
  const last = k - 1;
101
92
  const had = lanes.queueCounts[last];
102
- if (slot === lanes.queueSlots[last] + had &&
103
- row === lanes.queueRows[last] + had) {
104
- lanes.queueCounts[last] = had + count;
105
- return;
93
+ if (row === lanes.queueRows[last] + had) {
94
+ const first = lanes.queueSlots[last];
95
+ if (first >= 0 && slot === first + had) {
96
+ lanes.queueCounts[last] = had + count;
97
+ return;
98
+ }
99
+ if (count < BLOCK && (first < 0 || had < BLOCK)) {
100
+ lanes.queueSlots[last] = -1;
101
+ lanes.queueCounts[last] = had + count;
102
+ return;
103
+ }
106
104
  }
107
105
  }
108
106
  room(lanes, k + 1);
@@ -173,16 +171,33 @@ function rests(lanes, c, runs) {
173
171
  }
174
172
  /** The longest run of values `copies` copies itself: `set` takes a view, an object a run. */
175
173
  const VIEWED = 256;
176
- /** Copies a laned channel's values for every queued run into its rows. */
174
+ /** Copies a laned channel's values for every queued run into its rows, a block or row by row. */
177
175
  function copies(lanes, c, values, runs) {
178
176
  const slots = lanes.queueSlots;
179
177
  const rows = lanes.queueRows;
180
178
  const counts = lanes.queueCounts;
181
179
  const out = c.out;
182
180
  const axes = c.axes;
181
+ const from = lanes.queueFrom;
183
182
  for (let r = 0; r < runs; r++) {
183
+ const first = slots[r];
184
+ if (first < 0) {
185
+ const row = rows[r];
186
+ const end = row + counts[r];
187
+ if (axes === 1)
188
+ for (let i = row; i < end; i++)
189
+ out[i] = values[from[i]];
190
+ else
191
+ for (let i = row; i < end; i++) {
192
+ const at = i * axes;
193
+ const base = from[i] * axes;
194
+ for (let a = 0; a < axes; a++)
195
+ out[at + a] = values[base + a];
196
+ }
197
+ continue;
198
+ }
184
199
  const at = rows[r] * axes;
185
- const base = slots[r] * axes;
200
+ const base = first * axes;
186
201
  const span = counts[r] * axes;
187
202
  if (span > VIEWED)
188
203
  out.set(values.subarray(base, base + span), at);
package/dist/crowd.d.ts CHANGED
@@ -6,12 +6,6 @@ import type { Motions } from './motions.js';
6
6
  import type { Scratch } from './patch.js';
7
7
  import type { Subject } from './subject.js';
8
8
  import type { Voice } from './voice.js';
9
- /**
10
- * Whether a laned voice belongs in a crowd: one naming a single subject, which shares nothing
11
- * across subjects for a lane to save. A motion voice writes one channel; a keys or fn voice may
12
- * write several, and joins the crowd of voices writing those same channels.
13
- */
14
- export declare function crowdable<I, O>(v: Voice<I, O>): boolean;
15
9
  /** The law of a row that has none, a keys or fn voice's. */
16
10
  export declare const none: Float64Array<ArrayBuffer>;
17
11
  /** Per row of a crowd's `hot`: what the fill reads of the row's voice and its current stretch. */
@@ -25,10 +19,15 @@ export declare const enum Hot {
25
19
  SEEKS = 5,
26
20
  EPOCH = 6,
27
21
  ID = 7,
28
- AT = 8,
29
- MS = 9,
22
+ /**
23
+ * A `Flag.READY` row's mix time from which its tween has landed, so a fill folds its `to` as it
24
+ * stands; Infinity until `bareRows` sees it land at a positive rate. Unread on any other row.
25
+ */
26
+ LANDED = 8,
27
+ AT = 9,
28
+ MS = 10,
30
29
  /** `x0`, `v0` and `to`, `axes` numbers each. */
31
- X0 = 10
30
+ X0 = 11
32
31
  }
33
32
  /** A crowd row's flags, at `Hot.FLAGS`. */
34
33
  export declare const enum Flag {
@@ -86,7 +85,6 @@ export declare class Crowd<I, O> implements Positions<I, O> {
86
85
  eases: (Curve | undefined)[];
87
86
  /** Each row's patch law, one array shared by every row whose law is the same. */
88
87
  laws: Float64Array[];
89
- readonly rowOf: Map<number, number>;
90
88
  idle: boolean;
91
89
  readonly line: {
92
90
  stop: number;
package/dist/crowd.js CHANGED
@@ -2,19 +2,8 @@ import { bareRows, clock, unbare } from './bare.js';
2
2
  import { clampWeight, passAt, phaseAt, weighed } from './clock.js';
3
3
  import { closed } from './closed.js';
4
4
  import { KeyRows } from './keyrows.js';
5
- import { frozenAt, keepProbed, SPARSE } from './lane.js';
5
+ import { keepProbed, SPARSE } from './lane.js';
6
6
  import { move } from './sample.js';
7
- /**
8
- * Whether a laned voice belongs in a crowd: one naming a single subject, which shares nothing
9
- * across subjects for a lane to save. A motion voice writes one channel; a keys or fn voice may
10
- * write several, and joins the crowd of voices writing those same channels.
11
- */
12
- export function crowdable(v) {
13
- return (v.named !== null &&
14
- v.named.size === 1 &&
15
- v.spec.role === undefined &&
16
- (v.slots.length === 1 || v.motion === undefined));
17
- }
18
7
  /** The law of a row that has none, a keys or fn voice's. */
19
8
  export const none = new Float64Array(0);
20
9
  /** Whether a row's flags let `bareRows` take it. */
@@ -45,7 +34,6 @@ export class Crowd {
45
34
  eases = [];
46
35
  /** Each row's patch law, one array shared by every row whose law is the same. */
47
36
  laws = [];
48
- rowOf = new Map();
49
37
  idle = false;
50
38
  line = SPARSE.other;
51
39
  /** Where the fill now under way has reached. */
@@ -69,7 +57,7 @@ export class Crowd {
69
57
  constructor(chans, axes) {
70
58
  this.chans = chans;
71
59
  this.axes = axes;
72
- this.stride = 10 /* Hot.X0 */ + 3 * axes;
60
+ this.stride = 11 /* Hot.X0 */ + 3 * axes;
73
61
  this.xs = new Float64Array(axes);
74
62
  this.vs = new Float64Array(axes);
75
63
  }
@@ -218,8 +206,19 @@ function rows(lanes, c, first, id) {
218
206
  const voice = c.voices[p];
219
207
  const elapsedNow = fast ? clock(hot, h, now) : voice.elapsedAt(now);
220
208
  let elapsed = elapsedNow - delay;
209
+ // Its freezes applied as `one` applies them, written out: a function for it stays out of line.
221
210
  if ((f & 256 /* Flag.FREEZES */) !== 0)
222
- elapsed = frozenAt(voice, elapsed);
211
+ elapsed = !(elapsed >= 0)
212
+ ? voice.freezesBefore
213
+ ? 0
214
+ : Number.NaN
215
+ : elapsed > voice.span
216
+ ? voice.hidesPast
217
+ ? Number.NaN
218
+ : voice.freezesAfter
219
+ ? voice.span
220
+ : elapsed
221
+ : elapsed;
223
222
  if (!(elapsed >= 0)) {
224
223
  data[o + 2 /* Row.WEIGHT */] = 0;
225
224
  continue;
@@ -244,7 +243,7 @@ function rows(lanes, c, first, id) {
244
243
  data[o + 2 /* Row.WEIGHT */] = w;
245
244
  if ((f & 512 /* Flag.FLAT */) !== 0) {
246
245
  if (w > 0)
247
- c.keys.fold(hot[h + 8 /* Hot.AT */], phaseAt(elapsed, hot[h + 10 /* Hot.X0 */], hot[h + 9 /* Hot.MS */]), c.chans, slot, w);
246
+ c.keys.fold(hot[h + 9 /* Hot.AT */], phaseAt(elapsed, hot[h + 11 /* Hot.X0 */], hot[h + 10 /* Hot.MS */]), c.chans, slot, w);
248
247
  continue;
249
248
  }
250
249
  arg[1 /* Arg.ELAPSED */] = elapsed;
@@ -274,7 +273,7 @@ function rows(lanes, c, first, id) {
274
273
  c.restale(p, hot[h + 0 /* Hot.FLAGS */]);
275
274
  continue;
276
275
  }
277
- closed(c.laws[p], c.eases[p], n, hot[h + 8 /* Hot.AT */], hot[h + 9 /* Hot.MS */], hot, h + 10 /* Hot.X0 */, hot, h + 10 /* Hot.X0 */ + n, hot, h + 10 /* Hot.X0 */ + 2 * n, elapsed, xs, vs);
276
+ closed(c.laws[p], c.eases[p], n, hot[h + 9 /* Hot.AT */], hot[h + 10 /* Hot.MS */], hot, h + 11 /* Hot.X0 */, hot, h + 11 /* Hot.X0 */ + n, hot, h + 11 /* Hot.X0 */ + 2 * n, elapsed, xs, vs);
278
277
  for (let a = 0; a < n; a++)
279
278
  samples[p * n + a] = xs[a];
280
279
  if (c.deltas[p] !== null)
@@ -371,7 +370,7 @@ function copyStretch(c, p, ms) {
371
370
  const h = p * c.stride;
372
371
  let f = c.hot[h + 0 /* Hot.FLAGS */] & ~(4 /* Flag.STALE */ | 8 /* Flag.BARE */ | 32 /* Flag.FOLDS */);
373
372
  const ch = c.chans[0];
374
- if (run.n === c.axes && run.stretchInto(ms, c.hot, h + 8 /* Hot.AT */)) {
373
+ if (run.n === c.axes && run.stretchInto(ms, c.hot, h + 9 /* Hot.AT */)) {
375
374
  f |= 8 /* Flag.BARE */;
376
375
  if (run.scalar(ms) === ch.scalar && ch.plain)
377
376
  f |= 32 /* Flag.FOLDS */;
package/dist/cue.js CHANGED
@@ -3,8 +3,8 @@ import { checkHits } from './book.js';
3
3
  import { bind, checkWithin } from './bound.js';
4
4
  import { changed } from './chain.js';
5
5
  import { copyable } from './clone.js';
6
+ import { roundsHue } from './color.js';
6
7
  import { schedule } from './due.js';
7
- import { shares } from './everyone.js';
8
8
  import { finiteRate, plainWeight, VoiceHandle } from './handle.js';
9
9
  import { begin } from './held.js';
10
10
  import { handle } from './hosts.js';
@@ -158,11 +158,14 @@ export function playable(mix, spec) {
158
158
  const motion = motionOf(patch);
159
159
  if (motion !== undefined) {
160
160
  const channel = patch.writes[0];
161
- const rest = mix.kit[channel].rest;
161
+ const ch = mix.kit[channel];
162
+ const rest = ch.rest;
162
163
  if (typeof rest === 'number')
163
164
  motion.cuedOn(String(channel), true);
164
165
  else if (Array.isArray(rest) || ArrayBuffer.isView(rest))
165
- motion.cuedOn(String(channel), false);
166
+ motion.cuedOn(String(channel), false, roundsHue(ch));
167
+ else if (roundsHue(ch))
168
+ motion.cuedOn(String(channel), false, true);
166
169
  }
167
170
  if (patch.reads) {
168
171
  const host = mix.opts.host;
@@ -250,7 +253,7 @@ function enter(mix, voice) {
250
253
  i--;
251
254
  cued.splice(i, 0, voice);
252
255
  scoredAdd(mix, voice);
253
- voice.sharing = shares(mix, voice);
256
+ voice.sharing = mix.share?.shares(mix, voice) === true;
254
257
  enlist(mix, voice);
255
258
  changed(mix, voice);
256
259
  mix.stir();
@@ -0,0 +1,3 @@
1
+ import type { Voice } from './voice.js';
2
+ /** A subject's `stagger` answer: as asked at cue for a voice naming its subjects, else asked now. */
3
+ export declare function delayOf<I, O>(voice: Voice<I, O>, subject: I): number;
package/dist/delay.js ADDED
@@ -0,0 +1,7 @@
1
+ /** A subject's `stagger` answer: as asked at cue for a voice naming its subjects, else asked now. */
2
+ export function delayOf(voice, subject) {
3
+ const known = voice.named?.delays?.get(subject);
4
+ if (known !== undefined)
5
+ return known;
6
+ return voice.spec.stagger ? voice.spec.stagger(subject) : 0;
7
+ }
package/dist/due.js CHANGED
@@ -159,7 +159,7 @@ function dueOf(mix, voice) {
159
159
  export function schedule(mix, voice) {
160
160
  if (mix.projecting)
161
161
  return;
162
- mix.lanes?.voiceChanged(voice.id);
162
+ mix.lanes?.voiceChanged(voice);
163
163
  const token = ++voice.dueToken;
164
164
  const due = dueOf(mix, voice);
165
165
  if (due === Number.POSITIVE_INFINITY)
package/dist/easing.d.ts CHANGED
@@ -5,6 +5,8 @@ import type { Easing } from './types.js';
5
5
  * @category patch
6
6
  */
7
7
  export type Curve = (u: number) => number;
8
+ /** Whether a segment eased by `c` runs in a straight line: no easing, or `linear`. */
9
+ export declare function straight(c: Curve | undefined): boolean;
8
10
  /**
9
11
  * How fast a curve leaves 0, for bending a retarget to the speed the subject had: Infinity where
10
12
  * the curve jumps or leaves vertically, so no finite bend can match it.
package/dist/easing.js CHANGED
@@ -1,4 +1,8 @@
1
1
  const linear = (u) => u;
2
+ /** Whether a segment eased by `c` runs in a straight line: no easing, or `linear`. */
3
+ export function straight(c) {
4
+ return c === undefined || c === linear;
5
+ }
2
6
  /** A CSS `cubic-bezier`: solves x(t) = u for t, then reads y(t). */
3
7
  function bezier(x1, y1, x2, y2) {
4
8
  const cx = 3 * x1;
@@ -51,6 +51,11 @@ export declare function open<I, O>(voice: Voice<I, O>, rec: Subject<unknown>, sl
51
51
  export declare function shut<I, O>(voice: Voice<I, O>, rec: Subject<unknown>, slot: number, cap: number): void;
52
52
  /** Makes stale what a sharing voice last read for the subject numbered `slot`, as `touch` asks. */
53
53
  export declare function restamp<I, O>(voice: Voice<I, O>, slot: number): void;
54
+ /**
55
+ * The subject numbered `from` is numbered `to` now, a number let go of: what the voice keeps for it
56
+ * moves along, and `from` is left as `unsight` leaves it.
57
+ */
58
+ export declare function resight<I, O>(voice: Voice<I, O>, from: number, to: number, cap: number): void;
54
59
  /** A subject's number was let go of: the next subject to take it is one the voice has not seen. */
55
60
  export declare function unsight<I, O>(voice: Voice<I, O>, slot: number): void;
56
61
  /**
@@ -60,11 +65,22 @@ export declare function unsight<I, O>(voice: Voice<I, O>, slot: number): void;
60
65
  * positions take their own records now, since each held the one.
61
66
  */
62
67
  export declare function unshare<I, O>(mix: Mixer<I, O>, voice: Voice<I, O>, kept?: boolean, keeper?: I): void;
63
- /**
64
- * Counts what makes a mix's fold more than a walk of each chain: a role, a voice sharing one
65
- * record, a voice that stopped sharing this frame. Called wherever one of the three changes, so
66
- * `foldWith` asks one number.
67
- */
68
- export declare function detour<I, O>(mix: Mixer<I, O>): void;
69
68
  /** Gives a record just made for a subject a sharing voice had seen what the voice kept for it. */
70
69
  export declare function inherit<I, O>(voice: Voice<I, O>, held: Subject<unknown>, slot: number): void;
70
+ /** What the general path calls on a sharing voice, reached through `Lanes`, so a lean bundle leaves it out. */
71
+ export declare const share: {
72
+ shares: typeof shares;
73
+ outgrow: typeof outgrow;
74
+ everyoneOf: typeof everyoneOf;
75
+ sighted: typeof sighted;
76
+ sight: typeof sight;
77
+ weightAt: typeof weightAt;
78
+ weighAt: typeof weighAt;
79
+ open: typeof open;
80
+ shut: typeof shut;
81
+ restamp: typeof restamp;
82
+ unsight: typeof unsight;
83
+ unshare: typeof unshare;
84
+ inherit: typeof inherit;
85
+ };
86
+ export type Share = typeof share;
package/dist/everyone.js CHANGED
@@ -1,6 +1,7 @@
1
1
  import { headOf } from './chain.js';
2
2
  import { numericOf } from './channels.js';
3
3
  import { record } from './record.js';
4
+ import { detour } from './roster.js';
4
5
  /**
5
6
  * Whether a voice can keep one record for every subject, decided when it is cued. A record holds
6
7
  * what a voice knows of one subject; where the voice is over every subject with no stagger, keeps
@@ -185,6 +186,36 @@ export function restamp(voice, slot) {
185
186
  if (voice.built !== null && voice.everyone !== null)
186
187
  voice.everyone.seeks = -1;
187
188
  }
189
+ /**
190
+ * The subject numbered `from` is numbered `to` now, a number let go of: what the voice keeps for it
191
+ * moves along, and `from` is left as `unsight` leaves it.
192
+ */
193
+ export function resight(voice, from, to, cap) {
194
+ if (sighted(voice, from)) {
195
+ const seen = voice.seen;
196
+ sight(voice, to, cap);
197
+ voice.seen = seen;
198
+ }
199
+ const w = weightAt(voice, from);
200
+ if (w !== 0 || (voice.weights !== null && to < voice.weights.length))
201
+ weighAt(voice, to, w, cap);
202
+ let stamps = voice.stamps;
203
+ const o = from * 2 /* Stamp.SIZE */;
204
+ if (stamps !== null && o < stamps.length) {
205
+ const t = to * 2 /* Stamp.SIZE */;
206
+ if (t >= stamps.length) {
207
+ const grown = new Float64Array(Math.max(to + 1, cap) * 2 /* Stamp.SIZE */).fill(Number.NaN);
208
+ grown.set(stamps);
209
+ voice.stamps = grown;
210
+ stamps = grown;
211
+ }
212
+ stamps[t + 0 /* Stamp.PROBED */] = stamps[o + 0 /* Stamp.PROBED */];
213
+ stamps[t + 1 /* Stamp.SEEKS */] = stamps[o + 1 /* Stamp.SEEKS */];
214
+ const deltas = voice.deltas;
215
+ deltas[to] = deltas[from] ?? null;
216
+ }
217
+ unsight(voice, from);
218
+ }
188
219
  /** A subject's number was let go of: the next subject to take it is one the voice has not seen. */
189
220
  export function unsight(voice, slot) {
190
221
  const bits = voice.sighted;
@@ -233,17 +264,25 @@ export function unshare(mix, voice, kept = false, keeper) {
233
264
  detour(mix);
234
265
  mix.lanes?.refill();
235
266
  }
236
- /**
237
- * Counts what makes a mix's fold more than a walk of each chain: a role, a voice sharing one
238
- * record, a voice that stopped sharing this frame. Called wherever one of the three changes, so
239
- * `foldWith` asks one number.
240
- */
241
- export function detour(mix) {
242
- mix.detours = mix.roleVoices + mix.sharers.length + (mix.unshared ? 1 : 0);
243
- }
244
267
  /** Gives a record just made for a subject a sharing voice had seen what the voice kept for it. */
245
268
  export function inherit(voice, held, slot) {
246
269
  held.weight = weightAt(voice, slot);
247
270
  open(voice, held, slot);
248
271
  unsight(voice, slot);
249
272
  }
273
+ /** What the general path calls on a sharing voice, reached through `Lanes`, so a lean bundle leaves it out. */
274
+ export const share = {
275
+ shares,
276
+ outgrow,
277
+ everyoneOf,
278
+ sighted,
279
+ sight,
280
+ weightAt,
281
+ weighAt,
282
+ open,
283
+ shut,
284
+ restamp,
285
+ unsight,
286
+ unshare,
287
+ inherit,
288
+ };
package/dist/fade.d.ts CHANGED
@@ -30,7 +30,13 @@ export declare function beginFade<I, O>(mix: Mixer<I, O>, voice: Voice<I, O>, op
30
30
  at?: 'rest';
31
31
  deadline?: number;
32
32
  back?: boolean;
33
+ stays?: boolean;
33
34
  }, at?: number): void;
35
+ /**
36
+ * Keeps a voice faded at the end of its passes with its owner, live and quiet, rather than letting
37
+ * it leave; the owner leaves at `at` if it holds no child still playing.
38
+ */
39
+ export declare function stay<I, O>(mix: Mixer<I, O>, voice: Voice<I, O>, at: number): void;
34
40
  /**
35
41
  * Turns a fading voice around: it climbs back from where its fade out had got to, up the same
36
42
  * curve, at the whole curve per `over` ms. A fade waiting for rest is taken back at once. A voice
@@ -44,5 +50,10 @@ export declare function beginRise<I, O>(mix: Mixer<I, O>, voice: Voice<I, O>, op
44
50
  * exactly there; at or behind now, the fade began then and is partway.
45
51
  */
46
52
  export declare function fadeAt<I, O>(mix: Mixer<I, O>, voice: Voice<I, O>, at: number, over: number | undefined): void;
53
+ /**
54
+ * Brings back a voice kept quiet past its passes, its clock back in them by mix time `now`: it
55
+ * climbs from nothing over the fade it left over, from when its clock reached them.
56
+ */
57
+ export declare function rouse<I, O>(mix: Mixer<I, O>, voice: Voice<I, O>, now: number): void;
47
58
  /** Removes a voice, recording that it left at `at`, default now. */
48
59
  export declare function retire<I, O>(mix: Mixer<I, O>, voice: Voice<I, O>, at?: number): void;
package/dist/fade.js CHANGED
@@ -1,10 +1,9 @@
1
1
  import { changed, headOf, unlink } from './chain.js';
2
2
  import { envelope } from './clock.js';
3
3
  import { schedule } from './due.js';
4
- import { unshare, unsight } from './everyone.js';
5
4
  import { leave, noted, releasing } from './history.js';
6
5
  import { markOf } from './marks.js';
7
- import { orphan, ownerReading } from './owner.js';
6
+ import { orphan, ownerReading, plays } from './owner.js';
8
7
  import { startOf } from './place.js';
9
8
  import { isRested } from './record.js';
10
9
  import { scoreTouched } from './scored.js';
@@ -14,7 +13,7 @@ import { unreach } from './unreached.js';
14
13
  export function fadeSubject(mix, voice, subject, over) {
15
14
  if (voice.state === 'done' || voice.parted?.has(subject) || voice.parts?.has(subject))
16
15
  return;
17
- unshare(mix, voice);
16
+ mix.share?.unshare(mix, voice);
18
17
  mix.stir();
19
18
  const ms = mix.reduced ? 0 : (over ?? voice.fade.out ?? 0);
20
19
  const at = Number.isNaN(mix.now) ? startOf(voice) : mix.now;
@@ -104,7 +103,7 @@ function forgetIn(mix, voice, subject) {
104
103
  if (head !== undefined && head.slot >= 0) {
105
104
  unreach(voice, head.slot, false);
106
105
  // What the voice kept for the subject while it shared one record, which `weightOf` reads.
107
- unsight(voice, head.slot);
106
+ mix.share?.unsight(voice, head.slot);
108
107
  }
109
108
  const held = voice.subjects.get(subject);
110
109
  if (held !== undefined) {
@@ -135,7 +134,7 @@ export function beginFade(mix, voice, opts, at) {
135
134
  // A fade at rest marks each subject's record as it comes to rest, at its next read: one already
136
135
  // read this frame is read again, as after `touch`.
137
136
  if (opts.at === 'rest') {
138
- unshare(mix, voice);
137
+ mix.share?.unshare(mix, voice);
139
138
  voice.seeks++;
140
139
  }
141
140
  voice.state = 'fading';
@@ -148,14 +147,36 @@ export function beginFade(mix, voice, opts, at) {
148
147
  };
149
148
  if (opts.back)
150
149
  voice.out.back = true;
150
+ if (opts.stays)
151
+ voice.out.stays = true;
151
152
  noted(mix, voice);
152
153
  // A fade at rest, from a voice's start, or of a voice yet to start takes it off its lane.
153
154
  if (opts.at === 'rest' || opts.back || waiting)
154
155
  mix.lanes?.invalidate();
155
156
  else
156
157
  mix.lanes?.refill();
157
- if (over === 0 && opts.at !== 'rest')
158
- retire(mix, voice, voice.out.at);
158
+ if (over === 0 && opts.at !== 'rest') {
159
+ if (opts.stays)
160
+ stay(mix, voice, voice.out.at);
161
+ else
162
+ retire(mix, voice, voice.out.at);
163
+ }
164
+ }
165
+ /**
166
+ * Keeps a voice faded at the end of its passes with its owner, live and quiet, rather than letting
167
+ * it leave; the owner leaves at `at` if it holds no child still playing.
168
+ */
169
+ export function stay(mix, voice, at) {
170
+ voice.state = 'live';
171
+ mix.lanes?.refill();
172
+ const owner = voice.owner;
173
+ if (owner.state === 'done')
174
+ return;
175
+ const now = Number.isNaN(mix.now) ? Number.NEGATIVE_INFINITY : mix.now;
176
+ if (!plays(owner, now) && !lingers(owner, at))
177
+ retire(mix, owner, at);
178
+ else if (owner.fitting !== null && !voice.skipped)
179
+ refit(mix, owner, at);
159
180
  }
160
181
  /**
161
182
  * Turns a fading voice around: it climbs back from where its fade out had got to, up the same
@@ -211,6 +232,21 @@ export function fadeAt(mix, voice, at, over) {
211
232
  voice.outSet = true;
212
233
  noted(mix, voice);
213
234
  }
235
+ /**
236
+ * Brings back a voice kept quiet past its passes, its clock back in them by mix time `now`: it
237
+ * climbs from nothing over the fade it left over, from when its clock reached them.
238
+ */
239
+ export function rouse(mix, voice, now) {
240
+ const out = voice.out;
241
+ const edge = out.back === true ? voice.soonest : voice.span + voice.latest;
242
+ const reached = voice.reaches(edge);
243
+ const at = Math.max(out.at + out.over, reached <= now ? reached : now);
244
+ const over = mix.reduced ? 0 : out.over;
245
+ voice.out = null;
246
+ voice.back = over > 0 ? { from: 0, at, over } : null;
247
+ noted(mix, voice);
248
+ mix.lanes?.refill();
249
+ }
214
250
  /** Whether a motion patch still asks this voice of this mix for its time. */
215
251
  function asks(mix, motion, voice) {
216
252
  return motion.owner === mix.owner && motion.ownerId === voice.id;
@@ -248,7 +284,7 @@ export function retire(mix, voice, at) {
248
284
  if (voice.owner !== null) {
249
285
  const span = voice.owner;
250
286
  leaving(mix, voice);
251
- const owner = orphan(voice);
287
+ const owner = orphan(voice, now);
252
288
  if (owner !== null && !lingers(owner, voice.doneAt))
253
289
  retire(mix, owner, voice.doneAt);
254
290
  // A child the fit skipped had no time in it, so the rest of the fit stands: fitting again
package/dist/fill.js CHANGED
@@ -313,7 +313,7 @@ export function one(lane, p, slot) {
313
313
  return false;
314
314
  const delay = data[o + 0 /* Row.DELAY */];
315
315
  const raw = elapsedNow - delay;
316
- // `frozenAt` written out: V8 leaves it out of line here, a call a subject.
316
+ // Its freezes applied, written out: V8 left a function for it out of line here, a call a subject.
317
317
  const elapsed = !(raw >= 0)
318
318
  ? voice.freezesBefore
319
319
  ? 0
package/dist/fold.js CHANGED
@@ -1,10 +1,7 @@
1
1
  import { foldNumber } from './channels.js';
2
2
  import { copy as copyValue, first } from './clone.js';
3
- import { open as openRecord, shut as shutRecord, weighAt } from './everyone.js';
4
3
  // V8 reads a local const as a constant, where it reads an import from its module on every call.
5
4
  const copy = copyValue;
6
- const open = openRecord;
7
- const shut = shutRecord;
8
5
  /** A record's band state, made when a rest-less channel first asks. */
9
6
  function bandsFor(held, n) {
10
7
  const bands = new Uint8Array(n);
@@ -107,9 +104,9 @@ export function shared(voice, slot, rec, q) {
107
104
  // A projection numbers no subject, and keeps nothing by subject.
108
105
  if (slot < 0)
109
106
  return q;
110
- const cap = this.lanes.cap;
111
- weighAt(voice, slot, rec.weight, cap);
112
- shut(voice, rec, slot, cap);
107
+ const lanes = this.lanes;
108
+ this.share.weighAt(voice, slot, rec.weight, lanes.cap);
109
+ this.share.shut(voice, rec, slot, lanes.cap);
113
110
  return q;
114
111
  }
115
112
  const sharers = this.sharers;
@@ -265,7 +262,7 @@ export function foldSharers(pose, subject, head, q, before, laned, dry, except,
265
262
  continue;
266
263
  if (voice.id === except || voice.state === 'done')
267
264
  continue;
268
- open(voice, rec, head.slot);
265
+ this.share.open(voice, rec, head.slot);
269
266
  const delta = this.read(voice, subject, now, dry, rec);
270
267
  if (owed >= 0 && voice.laned)
271
268
  this.lanes.paid(voice.id, owed, rec.weight);