@msb235/blits 0.14.0 → 0.15.1

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 (187) hide show
  1. package/CHANGELOG.md +198 -0
  2. package/README.md +3 -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 +37 -14
  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/color.d.ts +12 -10
  21. package/dist/color.js +25 -25
  22. package/dist/columns.d.ts +2 -1
  23. package/dist/columns.js +62 -38
  24. package/dist/contribution.d.ts +30 -0
  25. package/dist/contribution.js +374 -0
  26. package/dist/copyout.d.ts +10 -0
  27. package/dist/copyout.js +69 -24
  28. package/dist/crowd.d.ts +16 -6
  29. package/dist/crowd.js +20 -11
  30. package/dist/cue.d.ts +8 -1
  31. package/dist/cue.js +39 -13
  32. package/dist/cut.d.ts +8 -0
  33. package/dist/cut.js +4 -0
  34. package/dist/delay.d.ts +3 -0
  35. package/dist/delay.js +7 -0
  36. package/dist/drop.d.ts +9 -0
  37. package/dist/drop.js +84 -0
  38. package/dist/due.d.ts +24 -5
  39. package/dist/due.js +114 -34
  40. package/dist/easing.d.ts +6 -4
  41. package/dist/easing.js +7 -3
  42. package/dist/everyone.d.ts +8 -2
  43. package/dist/everyone.js +33 -3
  44. package/dist/fade.d.ts +2 -1
  45. package/dist/fade.js +2 -1
  46. package/dist/fill.d.ts +3 -2
  47. package/dist/fill.js +52 -20
  48. package/dist/fit.d.ts +44 -26
  49. package/dist/fit.js +10 -9
  50. package/dist/fold.d.ts +4 -37
  51. package/dist/fold.js +5 -513
  52. package/dist/gather.d.ts +12 -7
  53. package/dist/gather.js +179 -44
  54. package/dist/gone.js +2 -2
  55. package/dist/grid.d.ts +47 -0
  56. package/dist/grid.js +247 -0
  57. package/dist/handle.d.ts +2 -0
  58. package/dist/handle.js +14 -8
  59. package/dist/held.d.ts +2 -1
  60. package/dist/held.js +2 -1
  61. package/dist/history.d.ts +6 -5
  62. package/dist/history.js +7 -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 +38 -11
  70. package/dist/lane.js +46 -11
  71. package/dist/lanes.d.ts +30 -12
  72. package/dist/lanes.js +56 -36
  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 +95 -7
  79. package/dist/mixer.d.ts +56 -37
  80. package/dist/mixer.js +57 -171
  81. package/dist/motion.d.ts +14 -118
  82. package/dist/motion.js +16 -8
  83. package/dist/motions.d.ts +62 -155
  84. package/dist/motions.js +56 -370
  85. package/dist/moved.d.ts +1 -1
  86. package/dist/moved.js +16 -7
  87. package/dist/named.d.ts +6 -2
  88. package/dist/named.js +49 -3
  89. package/dist/numbers.d.ts +7 -0
  90. package/dist/numbers.js +33 -0
  91. package/dist/owed.d.ts +2 -0
  92. package/dist/owed.js +4 -0
  93. package/dist/pack.d.ts +2 -1
  94. package/dist/pack.js +2 -3
  95. package/dist/patch.d.ts +7 -3
  96. package/dist/patch.js +6 -2
  97. package/dist/places.d.ts +53 -0
  98. package/dist/places.js +171 -0
  99. package/dist/plot.d.ts +12 -0
  100. package/dist/plot.js +52 -0
  101. package/dist/project.d.ts +4 -4
  102. package/dist/project.js +9 -212
  103. package/dist/pull.js +22 -9
  104. package/dist/qualify.js +93 -31
  105. package/dist/reading.d.ts +6 -0
  106. package/dist/reading.js +5 -0
  107. package/dist/record.d.ts +5 -1
  108. package/dist/record.js +3 -2
  109. package/dist/relink.d.ts +1 -1
  110. package/dist/renumber.d.ts +10 -0
  111. package/dist/renumber.js +75 -0
  112. package/dist/restored.d.ts +2 -2
  113. package/dist/restored.js +1 -1
  114. package/dist/rests.d.ts +9 -0
  115. package/dist/rests.js +76 -0
  116. package/dist/revive.d.ts +3 -2
  117. package/dist/revive.js +2 -2
  118. package/dist/{locus.d.ts → role.d.ts} +10 -9
  119. package/dist/{locus.js → role.js} +3 -3
  120. package/dist/roles.d.ts +10 -0
  121. package/dist/roles.js +158 -0
  122. package/dist/roster.js +3 -3
  123. package/dist/rotation.d.ts +1 -1
  124. package/dist/rotation.js +1 -1
  125. package/dist/rows.js +28 -20
  126. package/dist/sample.d.ts +2 -2
  127. package/dist/sample.js +33 -7
  128. package/dist/seat.d.ts +17 -0
  129. package/dist/seat.js +25 -0
  130. package/dist/seek.js +5 -5
  131. package/dist/setting.d.ts +27 -0
  132. package/dist/setting.js +55 -0
  133. package/dist/shared.d.ts +1 -1
  134. package/dist/shared.js +1 -1
  135. package/dist/signals.d.ts +64 -32
  136. package/dist/signals.js +20 -18
  137. package/dist/stack.d.ts +5 -5
  138. package/dist/stack.js +5 -5
  139. package/dist/standing.d.ts +2 -1
  140. package/dist/standing.js +3 -2
  141. package/dist/store.d.ts +9 -0
  142. package/dist/store.js +24 -0
  143. package/dist/strays.d.ts +2 -1
  144. package/dist/strays.js +23 -15
  145. package/dist/stretch.d.ts +112 -0
  146. package/dist/stretch.js +9 -0
  147. package/dist/subject.d.ts +142 -0
  148. package/dist/subject.js +1 -0
  149. package/dist/tape.d.ts +9 -5
  150. package/dist/tape.js +5 -1
  151. package/dist/then.d.ts +9 -0
  152. package/dist/then.js +57 -0
  153. package/dist/ticker.d.ts +4 -4
  154. package/dist/trace.d.ts +4 -0
  155. package/dist/trace.js +255 -0
  156. package/dist/transport.d.ts +0 -8
  157. package/dist/transport.js +0 -4
  158. package/dist/turns.d.ts +4 -3
  159. package/dist/turns.js +4 -3
  160. package/dist/types/arc.d.ts +12 -11
  161. package/dist/types/channel.d.ts +12 -8
  162. package/dist/types/history.d.ts +35 -19
  163. package/dist/types/mix.d.ts +237 -163
  164. package/dist/types/motion.d.ts +151 -0
  165. package/dist/types/motion.js +1 -0
  166. package/dist/types/patch.d.ts +67 -34
  167. package/dist/types/place.d.ts +51 -36
  168. package/dist/types/trace.d.ts +51 -0
  169. package/dist/types/trace.js +1 -0
  170. package/dist/types/transport.d.ts +20 -18
  171. package/dist/types/voice.d.ts +144 -73
  172. package/dist/types/watch.d.ts +88 -40
  173. package/dist/types.d.ts +2 -0
  174. package/dist/unreached.d.ts +2 -0
  175. package/dist/unreached.js +7 -0
  176. package/dist/voice.d.ts +10 -143
  177. package/dist/voice.js +37 -82
  178. package/dist/warn.d.ts +2 -0
  179. package/dist/warn.js +1 -1
  180. package/dist/watch.d.ts +2 -2
  181. package/dist/watch.js +5 -3
  182. package/dist/wave.d.ts +4 -3
  183. package/dist/wave.js +3 -2
  184. package/dist/weigh.d.ts +7 -1
  185. package/dist/weigh.js +27 -0
  186. package/docs/schema.html +223 -34
  187. package/package.json +2 -2
package/dist/cue.d.ts CHANGED
@@ -1,7 +1,14 @@
1
1
  import type { Mixer } from './mixer.js';
2
- import type { Handle, Kit, Patch, VoiceSpec } from './types.js';
2
+ import type { Handle, Kit, MixOptions, Patch, VoiceSpec } from './types.js';
3
3
  export declare function cue<I, O>(mix: Mixer<I, O>, given: VoiceSpec<I, O>): Handle<I>;
4
4
  /** Refuses a patch writing a channel `kit` lacks, or holds as another kind than it was written for. */
5
5
  export declare function fitsKit<I, O, S, H>(patch: Patch<I, O, S, H>, kit: Kit<O>): void;
6
+ /**
7
+ * The spec with its `role` set from `locus`, the name `role` had before, or from a tag the mix
8
+ * declares a role, where it carries one.
9
+ */
10
+ export declare function roleOf<I, O>(mix: Mixer<I, O>, given: VoiceSpec<I, O>): VoiceSpec<I, O>;
11
+ /** The tags `opts` declares roles, from `roles` or `loci`, the name `roles` had before. */
12
+ export declare function roleTags(opts: MixOptions): readonly string[] | undefined;
6
13
  /** Refuses a spec whose patch the mix cannot play, or whose state its history cannot keep. */
7
14
  export declare function playable<I, O>(mix: Mixer<I, O>, spec: VoiceSpec<I, O>): void;
package/dist/cue.js CHANGED
@@ -1,5 +1,6 @@
1
1
  import { blendOf } from './blend.js';
2
2
  import { checkHits } from './book.js';
3
+ import { bind, checkWithin } from './bound.js';
3
4
  import { changed } from './chain.js';
4
5
  import { copyable } from './clone.js';
5
6
  import { schedule } from './due.js';
@@ -15,10 +16,11 @@ import { enlist } from './roster.js';
15
16
  import { scoredAdd } from './scored.js';
16
17
  import { checkChild, refit } from './spans.js';
17
18
  import { cuedById, motionOwner } from './strays.js';
18
- import { record, unplayable } from './tape.js';
19
+ import { record, taping, unplayable } from './tape.js';
19
20
  import { Voice } from './voice.js';
21
+ import { warnOnce } from './warn.js';
20
22
  export function cue(mix, given) {
21
- const spec = locusOf(mix, given);
23
+ const spec = roleOf(mix, given);
22
24
  const patch = spec.patch;
23
25
  playable(mix, spec);
24
26
  if (spec.from === 'current' && patch.form !== 'keys')
@@ -33,6 +35,7 @@ export function cue(mix, given) {
33
35
  checkHits(spec.hits, patch.duration);
34
36
  if (spec.marks !== undefined)
35
37
  checkMarks(spec.marks, patch.duration);
38
+ checkWithin(mix, spec);
36
39
  const anchored = anchor !== undefined && (anchor.start !== undefined || anchor.in !== undefined);
37
40
  // A voice an owner holds is placed on the owner's clock: ms from when it starts.
38
41
  const local = owner === null ? mix.now : ownerReading(owner, mix.now);
@@ -63,10 +66,12 @@ export function cue(mix, given) {
63
66
  voice.waking = true;
64
67
  mix.waking = true;
65
68
  }
69
+ bind(mix, voice);
66
70
  enter(mix, voice);
67
71
  const h = handle(mix, voice);
68
72
  voice.handle = h;
69
- recordCue(mix, h);
73
+ if (taping(mix))
74
+ recordCue(mix, h);
70
75
  return h;
71
76
  }
72
77
  const noFade = Object.freeze({});
@@ -96,21 +101,42 @@ export function fitsKit(patch, kit) {
96
101
  throw new Error(`blits: channel ${String(channel)} is ${here.kind ?? 'a custom channel'} in this kit, but the patch was written for ${wanted.kind ?? 'a custom channel'}`);
97
102
  }
98
103
  }
99
- /** The spec with its `locus` set from a tag the mix declares one, where it carries one. */
100
- function locusOf(mix, spec) {
101
- const loci = mix.opts.loci;
104
+ /**
105
+ * The spec with its `role` set from `locus`, the name `role` had before, or from a tag the mix
106
+ * declares a role, where it carries one.
107
+ */
108
+ export function roleOf(mix, given) {
109
+ let spec = given;
110
+ if (spec.locus !== undefined) {
111
+ if (spec.role !== undefined && spec.role !== spec.locus)
112
+ throw new Error(`blits: a voice plays one role, not both role ${spec.role} and locus ${spec.locus}; locus is role's old name`);
113
+ warnOnce(mix, 'locus is renamed role; cue the voice with role');
114
+ const { locus, ...rest } = spec;
115
+ spec = { ...rest, role: locus };
116
+ }
117
+ const roles = mix.roleTags;
102
118
  const tags = spec.tags;
103
- if (loci === undefined || tags === undefined)
119
+ if (roles === undefined || tags === undefined)
104
120
  return spec;
105
- let locus = spec.locus;
121
+ let role = spec.role;
106
122
  for (const tag of tags) {
107
- if (!loci.includes(tag) || tag === locus)
123
+ if (!roles.includes(tag) || tag === role)
108
124
  continue;
109
- if (locus !== undefined)
110
- throw new Error(`blits: a voice folds in one locus, not both ${locus} and ${tag}`);
111
- locus = tag;
125
+ if (role !== undefined)
126
+ throw new Error(`blits: a voice plays one role, not both ${role} and ${tag}`);
127
+ role = tag;
112
128
  }
113
- return locus === spec.locus ? spec : { ...spec, locus };
129
+ return role === spec.role ? spec : { ...spec, role };
130
+ }
131
+ /** The tags `opts` declares roles, from `roles` or `loci`, the name `roles` had before. */
132
+ export function roleTags(opts) {
133
+ const { roles, loci } = opts;
134
+ if (loci === undefined)
135
+ return roles;
136
+ if (roles !== undefined && (roles.length !== loci.length || roles.some((r, i) => r !== loci[i])))
137
+ throw new Error("blits: a mix takes roles or loci, not both; loci is roles' old name");
138
+ warnOnce(opts, 'the mix option loci is renamed roles; make the mix with roles');
139
+ return loci;
114
140
  }
115
141
  /** Refuses a spec whose patch the mix cannot play, or whose state its history cannot keep. */
116
142
  export function playable(mix, spec) {
package/dist/cut.d.ts ADDED
@@ -0,0 +1,8 @@
1
+ /** Where history is cut for a seek or read: what frame `seq` made, and before it, is kept. */
2
+ export interface Cut {
3
+ seq: number;
4
+ /** For a read of what a frame showed: of that frame's own changes, only those made before it was read. */
5
+ strict: boolean;
6
+ }
7
+ /** Whether a change made in frame `seq` falls inside `cut`; `early` for one made before the frame was read. */
8
+ export declare function within(cut: Cut, seq: number, early?: boolean): boolean;
package/dist/cut.js ADDED
@@ -0,0 +1,4 @@
1
+ /** Whether a change made in frame `seq` falls inside `cut`; `early` for one made before the frame was read. */
2
+ export function within(cut, seq, early = false) {
3
+ return seq < cut.seq || (seq === cut.seq && (!cut.strict || early));
4
+ }
@@ -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/drop.d.ts ADDED
@@ -0,0 +1,9 @@
1
+ import { releasing } from './history.js';
2
+ import type { Mixer } from './mixer.js';
3
+ import type { Motions } from './motions.js';
4
+ import type { Voice } from './voice.js';
5
+ export declare function drop<I, O>(this: Mixer<I, O>, subject: I): void;
6
+ /** Lets `voice` go of a dropped subject. Methods rather than closures, so a drop makes neither. */
7
+ export declare function forgetIn<I, O>(this: Mixer<I, O>, voice: Voice<I, O>, subject: I, kept: ReturnType<typeof releasing>): void;
8
+ export declare function releaseIn<I, O>(this: Mixer<I, O>, motion: Motions<I>, subject: I, kept: ReturnType<typeof releasing>): void;
9
+ export declare function recordDrop<I, O>(this: Mixer<I, O>, subject: I): void;
package/dist/drop.js ADDED
@@ -0,0 +1,84 @@
1
+ import { headOf, unlink } from './chain.js';
2
+ import { leaveVoice } from './fade.js';
3
+ import { goneIndex } from './gone.js';
4
+ import { releasing } from './history.js';
5
+ import { nextFrame } from './move.js';
6
+ import { Masked } from './store.js';
7
+ import { record, taping, unplayable } from './tape.js';
8
+ export function drop(subject) {
9
+ this.frame = nextFrame();
10
+ const head = headOf(this, subject);
11
+ // Nothing that kept this head, such as `pull`'s remembered list, may take it as current again.
12
+ if (head !== undefined)
13
+ unlink(this, head);
14
+ // A number a seek carried over is released even where its chain is not linked since.
15
+ const slot = this.chains.get(subject)?.slot ?? -1;
16
+ if (slot >= 0 && this.lanes !== null)
17
+ this.lanes.release(slot);
18
+ // A read ahead's copy reads the live mix's poses, and so hides one where the mix deletes it.
19
+ if (this.projecting && !(this.lastPoses instanceof Masked))
20
+ this.lastPoses = new Masked(this.lastPoses);
21
+ if (!this.projecting)
22
+ this.places?.drop(subject);
23
+ this.lastPoses.delete(subject);
24
+ this.restStamps?.delete(subject);
25
+ this.chains.delete(subject);
26
+ this.stir();
27
+ // A record of it is only in a voice over every subject, one naming it, or one gone; a ramp out
28
+ // of a voice that does not name it is in `parters`, and a patch's own state in `motions`.
29
+ const kept = releasing(this);
30
+ for (const voice of this.general)
31
+ this.forgetIn(voice, subject, kept);
32
+ const named = this.named.get(subject);
33
+ if (named !== undefined)
34
+ for (const voice of [...named])
35
+ this.forgetIn(voice, subject, kept);
36
+ const gone = goneIndex(this);
37
+ for (const voice of gone.general)
38
+ this.forgetIn(voice, subject, kept);
39
+ const goneNaming = gone.named.get(subject);
40
+ if (goneNaming !== undefined)
41
+ for (const voice of goneNaming)
42
+ this.forgetIn(voice, subject, kept);
43
+ if (this.owners !== null)
44
+ for (const voice of this.owners)
45
+ this.forgetIn(voice, subject, kept);
46
+ for (const voice of this.parters) {
47
+ this.forgetIn(voice, subject, kept);
48
+ if (voice.parts?.size === 0)
49
+ voice.parts = null;
50
+ if (voice.parted?.size === 0)
51
+ voice.parted = null;
52
+ if (voice.parts === null && voice.parted === null)
53
+ this.parters.delete(voice);
54
+ }
55
+ const strays = this.strays.get(subject);
56
+ if (strays !== undefined)
57
+ for (const motion of strays)
58
+ this.releaseIn(motion, subject, kept);
59
+ this.strays.delete(subject);
60
+ for (const motion of this.strayAll)
61
+ this.releaseIn(motion, subject, kept);
62
+ if (taping(this))
63
+ this.recordDrop(subject);
64
+ }
65
+ /** Lets `voice` go of a dropped subject. Methods rather than closures, so a drop makes neither. */
66
+ export function forgetIn(voice, subject, kept) {
67
+ leaveVoice(this, voice, subject);
68
+ voice.subjects.delete(subject);
69
+ if (voice.motion !== undefined)
70
+ this.releaseIn(voice.motion, subject, kept);
71
+ voice.parts?.delete(subject);
72
+ voice.parted?.delete(subject);
73
+ voice.blend?.of.reads.delete(subject);
74
+ }
75
+ // A motion patch's state is the live mix's alone: a read ahead cannot start a subject afresh in it.
76
+ export function releaseIn(motion, subject, kept) {
77
+ if (!this.projecting)
78
+ motion.release(subject, kept[0], kept[1], kept[2], kept[3]);
79
+ else if (motion.holds(subject))
80
+ unplayable('a drop of a subject a motion patch holds');
81
+ }
82
+ export function recordDrop(subject) {
83
+ record(this, 'drop', (stage) => stage.mix(this)?.drop(subject));
84
+ }
package/dist/due.d.ts CHANGED
@@ -1,10 +1,29 @@
1
1
  import type { Mixer } from './mixer.js';
2
2
  import type { Voice } from './voice.js';
3
- /** One entry in a mix's due queue; stale once its voice has been scheduled again. */
4
- export interface Due<I, O> {
5
- at: number;
6
- voice: Voice<I, O>;
7
- token: number;
3
+ /**
4
+ * Live voices by the earliest mix time `moveTo` would change each, a binary heap. The times sit in
5
+ * one flat array, so a sift reads no voice: at 40,000 voices a stop's sift to the top missed the
6
+ * cache at each of its levels. An entry is stale once its voice has been scheduled again.
7
+ */
8
+ export declare class DueQueue<I, O> {
9
+ size: number;
10
+ private at;
11
+ private tokens;
12
+ private voices;
13
+ /** The size past which stale entries are pruned, so a voice rescheduled often cannot grow it. */
14
+ private limit;
15
+ push(at: number, voice: Voice<I, O>, token: number): void;
16
+ /** The earliest entry's time, Infinity while empty. */
17
+ first(): number;
18
+ /** Takes off the earliest entry, giving its voice where it is current and undefined where stale. */
19
+ pop(): Voice<I, O> | undefined;
20
+ clear(): void;
21
+ private move;
22
+ /** Puts an entry in the hole at `i` and sifts it down to where it belongs. */
23
+ private sink;
24
+ private grow;
25
+ /** Drops the entries made stale by a later one and heapifies what is left. */
26
+ private prune;
8
27
  }
9
28
  /** Files a voice in the due queue at its current due, replacing any entry it had. */
10
29
  export declare function schedule<I, O>(mix: Mixer<I, O>, voice: Voice<I, O>): void;
package/dist/due.js CHANGED
@@ -1,3 +1,108 @@
1
+ /**
2
+ * Live voices by the earliest mix time `moveTo` would change each, a binary heap. The times sit in
3
+ * one flat array, so a sift reads no voice: at 40,000 voices a stop's sift to the top missed the
4
+ * cache at each of its levels. An entry is stale once its voice has been scheduled again.
5
+ */
6
+ export class DueQueue {
7
+ size = 0;
8
+ at = new Float64Array(64);
9
+ tokens = new Float64Array(64);
10
+ voices = [];
11
+ /** The size past which stale entries are pruned, so a voice rescheduled often cannot grow it. */
12
+ limit = 64;
13
+ push(at, voice, token) {
14
+ if (this.size >= this.limit)
15
+ this.prune();
16
+ if (this.size === this.at.length)
17
+ this.grow();
18
+ const ats = this.at;
19
+ let i = this.size++;
20
+ while (i > 0) {
21
+ const up = (i - 1) >> 1;
22
+ if (ats[up] <= at)
23
+ break;
24
+ this.move(up, i);
25
+ i = up;
26
+ }
27
+ ats[i] = at;
28
+ this.tokens[i] = token;
29
+ this.voices[i] = voice;
30
+ }
31
+ /** The earliest entry's time, Infinity while empty. */
32
+ first() {
33
+ return this.size > 0 ? this.at[0] : Number.POSITIVE_INFINITY;
34
+ }
35
+ /** Takes off the earliest entry, giving its voice where it is current and undefined where stale. */
36
+ pop() {
37
+ const voice = this.voices[0];
38
+ const token = this.tokens[0];
39
+ const last = --this.size;
40
+ if (last > 0)
41
+ this.sink(0, this.at[last], this.tokens[last], this.voices[last]);
42
+ this.voices[last] = undefined;
43
+ return token === voice.dueToken && voice.state !== 'done' ? voice : undefined;
44
+ }
45
+ clear() {
46
+ this.size = 0;
47
+ this.voices = [];
48
+ this.limit = 64;
49
+ }
50
+ move(from, to) {
51
+ this.at[to] = this.at[from];
52
+ this.tokens[to] = this.tokens[from];
53
+ this.voices[to] = this.voices[from];
54
+ }
55
+ /** Puts an entry in the hole at `i` and sifts it down to where it belongs. */
56
+ sink(i, at, token, voice) {
57
+ const ats = this.at;
58
+ const n = this.size;
59
+ for (;;) {
60
+ const l = 2 * i + 1;
61
+ const r = l + 1;
62
+ let m = i;
63
+ let least = at;
64
+ if (l < n && ats[l] < least) {
65
+ m = l;
66
+ least = ats[l];
67
+ }
68
+ if (r < n && ats[r] < least)
69
+ m = r;
70
+ if (m === i)
71
+ break;
72
+ this.move(m, i);
73
+ i = m;
74
+ }
75
+ ats[i] = at;
76
+ this.tokens[i] = token;
77
+ this.voices[i] = voice;
78
+ }
79
+ grow() {
80
+ const at = new Float64Array(this.at.length * 2);
81
+ at.set(this.at);
82
+ this.at = at;
83
+ const tokens = new Float64Array(this.tokens.length * 2);
84
+ tokens.set(this.tokens);
85
+ this.tokens = tokens;
86
+ }
87
+ /** Drops the entries made stale by a later one and heapifies what is left. */
88
+ prune() {
89
+ let n = 0;
90
+ for (let k = 0; k < this.size; k++) {
91
+ const voice = this.voices[k];
92
+ if (this.tokens[k] !== voice.dueToken)
93
+ continue;
94
+ if (n !== k)
95
+ this.move(k, n);
96
+ n++;
97
+ }
98
+ for (let k = n; k < this.size; k++)
99
+ this.voices[k] = undefined;
100
+ this.size = n;
101
+ for (let k = (n >> 1) - 1; k >= 0; k--)
102
+ this.sink(k, this.at[k], this.tokens[k], this.voices[k]);
103
+ this.limit = Math.max(64, 2 * n);
104
+ }
105
+ }
1
106
  /**
2
107
  * The earliest mix time `moveTo` would change a voice: its start, its anchored out, the end of
3
108
  * its last pass, the end of its fade or of a subject's ramp out; -Infinity where it must be
@@ -54,48 +159,23 @@ function dueOf(mix, voice) {
54
159
  export function schedule(mix, voice) {
55
160
  if (mix.projecting)
56
161
  return;
57
- mix.lanes?.voiceChanged(voice.id);
162
+ mix.lanes?.voiceChanged(voice);
58
163
  const token = ++voice.dueToken;
59
164
  const due = dueOf(mix, voice);
60
165
  if (due === Number.POSITIVE_INFINITY)
61
166
  return;
62
- const heap = mix.due;
63
- heap.push({ at: Number.isNaN(due) ? Number.NEGATIVE_INFINITY : due, voice, token });
64
- let i = heap.length - 1;
65
- while (i > 0) {
66
- const up = (i - 1) >> 1;
67
- if (heap[up].at <= heap[i].at)
68
- break;
69
- [heap[up], heap[i]] = [heap[i], heap[up]];
70
- i = up;
71
- }
167
+ mix.due.push(Number.isNaN(due) ? Number.NEGATIVE_INFINITY : due, voice, token);
72
168
  }
73
169
  /** Takes every current entry due by `now` off the queue, in cue order. */
74
170
  export function popDue(mix, now) {
75
- const heap = mix.due;
76
- const at = (k) => k < heap.length ? heap[k].at : Number.POSITIVE_INFINITY;
171
+ const queue = mix.due;
77
172
  const out = [];
78
- while (heap.length > 0 && heap[0].at <= now) {
79
- const top = heap[0];
80
- const last = heap.pop();
81
- if (heap.length > 0) {
82
- heap[0] = last;
83
- let i = 0;
84
- for (;;) {
85
- const l = 2 * i + 1;
86
- const r = l + 1;
87
- const m0 = at(l) < at(i) ? l : i;
88
- const m = at(r) < at(m0) ? r : m0;
89
- if (m === i)
90
- break;
91
- [heap[m], heap[i]] = [heap[i], heap[m]];
92
- i = m;
93
- }
94
- }
95
- if (top.token === top.voice.dueToken && top.voice.state !== 'done') {
96
- top.voice.dueToken++;
97
- out.push(top.voice);
98
- }
173
+ while (queue.first() <= now) {
174
+ const voice = queue.pop();
175
+ if (voice === undefined)
176
+ continue;
177
+ voice.dueToken++;
178
+ out.push(voice);
99
179
  }
100
180
  return byId(out);
101
181
  }
package/dist/easing.d.ts CHANGED
@@ -1,19 +1,21 @@
1
1
  import type { Easing } from './types.js';
2
2
  /**
3
- * A curve on 0..1: the function an easing stands for.
3
+ * The function an easing stands for, a curve on 0..1.
4
4
  *
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.
11
13
  */
12
14
  export declare function startSlope(c: Curve): number;
13
15
  /**
14
- * The function an easing names: a CSS name, a `bezier` or `steps`, or a function, which comes back
15
- * as it is. Call it once where a curve is built, not per frame; the same name or easing object
16
- * gives the same function each time.
16
+ * The function an easing names. The easing is a CSS name, a `bezier` or `steps`, or a function,
17
+ * which comes back as it is. Call it once where a curve is built, not per frame; the same name or
18
+ * easing object gives the same function each time.
17
19
  *
18
20
  * @category patch
19
21
  */
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;
@@ -72,9 +76,9 @@ export function startSlope(c) {
72
76
  return v > 1e-3 ? Number.POSITIVE_INFINITY : v / h;
73
77
  }
74
78
  /**
75
- * The function an easing names: a CSS name, a `bezier` or `steps`, or a function, which comes back
76
- * as it is. Call it once where a curve is built, not per frame; the same name or easing object
77
- * gives the same function each time.
79
+ * The function an easing names. The easing is a CSS name, a `bezier` or `steps`, or a function,
80
+ * which comes back as it is. Call it once where a curve is built, not per frame; the same name or
81
+ * easing object gives the same function each time.
78
82
  *
79
83
  * @category patch
80
84
  */
@@ -1,5 +1,6 @@
1
1
  import type { Mixer } from './mixer.js';
2
- import type { Subject, Voice } from './voice.js';
2
+ import type { Subject } from './subject.js';
3
+ import type { Voice } from './voice.js';
3
4
  /**
4
5
  * Whether a voice can keep one record for every subject, decided when it is cued. A record holds
5
6
  * what a voice knows of one subject; where the voice is over every subject with no stagger, keeps
@@ -50,6 +51,11 @@ export declare function open<I, O>(voice: Voice<I, O>, rec: Subject<unknown>, sl
50
51
  export declare function shut<I, O>(voice: Voice<I, O>, rec: Subject<unknown>, slot: number, cap: number): void;
51
52
  /** Makes stale what a sharing voice last read for the subject numbered `slot`, as `touch` asks. */
52
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;
53
59
  /** A subject's number was let go of: the next subject to take it is one the voice has not seen. */
54
60
  export declare function unsight<I, O>(voice: Voice<I, O>, slot: number): void;
55
61
  /**
@@ -60,7 +66,7 @@ export declare function unsight<I, O>(voice: Voice<I, O>, slot: number): void;
60
66
  */
61
67
  export declare function unshare<I, O>(mix: Mixer<I, O>, voice: Voice<I, O>, kept?: boolean, keeper?: I): void;
62
68
  /**
63
- * Counts what makes a mix's fold more than a walk of each chain: a locus, a voice sharing one
69
+ * Counts what makes a mix's fold more than a walk of each chain: a role, a voice sharing one
64
70
  * record, a voice that stopped sharing this frame. Called wherever one of the three changes, so
65
71
  * `foldWith` asks one number.
66
72
  */
package/dist/everyone.js CHANGED
@@ -22,7 +22,7 @@ export function shares(mix, voice) {
22
22
  voice.named === null &&
23
23
  spec.target === undefined &&
24
24
  spec.stagger === undefined &&
25
- spec.locus === undefined &&
25
+ spec.role === undefined &&
26
26
  spec.anchor === undefined &&
27
27
  spec.from !== 'current' &&
28
28
  typeof spec.weight !== 'function' &&
@@ -185,6 +185,36 @@ export function restamp(voice, slot) {
185
185
  if (voice.built !== null && voice.everyone !== null)
186
186
  voice.everyone.seeks = -1;
187
187
  }
188
+ /**
189
+ * The subject numbered `from` is numbered `to` now, a number let go of: what the voice keeps for it
190
+ * moves along, and `from` is left as `unsight` leaves it.
191
+ */
192
+ export function resight(voice, from, to, cap) {
193
+ if (sighted(voice, from)) {
194
+ const seen = voice.seen;
195
+ sight(voice, to, cap);
196
+ voice.seen = seen;
197
+ }
198
+ const w = weightAt(voice, from);
199
+ if (w !== 0 || (voice.weights !== null && to < voice.weights.length))
200
+ weighAt(voice, to, w, cap);
201
+ let stamps = voice.stamps;
202
+ const o = from * 2 /* Stamp.SIZE */;
203
+ if (stamps !== null && o < stamps.length) {
204
+ const t = to * 2 /* Stamp.SIZE */;
205
+ if (t >= stamps.length) {
206
+ const grown = new Float64Array(Math.max(to + 1, cap) * 2 /* Stamp.SIZE */).fill(Number.NaN);
207
+ grown.set(stamps);
208
+ voice.stamps = grown;
209
+ stamps = grown;
210
+ }
211
+ stamps[t + 0 /* Stamp.PROBED */] = stamps[o + 0 /* Stamp.PROBED */];
212
+ stamps[t + 1 /* Stamp.SEEKS */] = stamps[o + 1 /* Stamp.SEEKS */];
213
+ const deltas = voice.deltas;
214
+ deltas[to] = deltas[from] ?? null;
215
+ }
216
+ unsight(voice, from);
217
+ }
188
218
  /** A subject's number was let go of: the next subject to take it is one the voice has not seen. */
189
219
  export function unsight(voice, slot) {
190
220
  const bits = voice.sighted;
@@ -234,12 +264,12 @@ export function unshare(mix, voice, kept = false, keeper) {
234
264
  mix.lanes?.refill();
235
265
  }
236
266
  /**
237
- * Counts what makes a mix's fold more than a walk of each chain: a locus, a voice sharing one
267
+ * Counts what makes a mix's fold more than a walk of each chain: a role, a voice sharing one
238
268
  * record, a voice that stopped sharing this frame. Called wherever one of the three changes, so
239
269
  * `foldWith` asks one number.
240
270
  */
241
271
  export function detour(mix) {
242
- mix.detours = mix.loci + mix.sharers.length + (mix.unshared ? 1 : 0);
272
+ mix.detours = mix.roleVoices + mix.sharers.length + (mix.unshared ? 1 : 0);
243
273
  }
244
274
  /** Gives a record just made for a subject a sharing voice had seen what the voice kept for it. */
245
275
  export function inherit(voice, held, slot) {
package/dist/fade.d.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import type { Mixer } from './mixer.js';
2
- import type { Left, Parted, Parting, Voice } from './voice.js';
2
+ import type { Left, Parted, Parting } from './subject.js';
3
+ import type { Voice } from './voice.js';
3
4
  /** Starts one subject's ramp out of a voice, or takes it out at once for a ramp of 0. */
4
5
  export declare function fadeSubject<I, O>(mix: Mixer<I, O>, voice: Voice<I, O>, subject: I, over?: number): void;
5
6
  /** What a subject's own ramp out of a voice leaves of its weight this frame, 0..1. */
package/dist/fade.js CHANGED
@@ -6,6 +6,7 @@ import { leave, noted, releasing } from './history.js';
6
6
  import { markOf } from './marks.js';
7
7
  import { orphan, ownerReading } from './owner.js';
8
8
  import { startOf } from './place.js';
9
+ import { isRested } from './record.js';
9
10
  import { scoreTouched } from './scored.js';
10
11
  import { lingers, refit } from './spans.js';
11
12
  import { unreach } from './unreached.js';
@@ -111,7 +112,7 @@ function forgetIn(mix, voice, subject) {
111
112
  voice.subjects.delete(subject);
112
113
  if (held.reaches)
113
114
  voice.seen--;
114
- if (held.rested)
115
+ if (isRested(held))
115
116
  voice.restedCount--;
116
117
  if (voice.holder === held)
117
118
  voice.holder = null;
package/dist/fill.d.ts CHANGED
@@ -2,7 +2,8 @@ import { type Lane, type Laned, type Paced } from './lane.js';
2
2
  import type { Lanes } from './lanes.js';
3
3
  import { absent } from './numbers.js';
4
4
  import { type Scratch } from './patch.js';
5
- import type { Subject, Voice } from './voice.js';
5
+ import type { Subject } from './subject.js';
6
+ import type { Voice } from './voice.js';
6
7
  export declare function fillAll<I, O>(lanes: Lanes<I, O>, now: number, version: number): void;
7
8
  /** Leaves a lane's subjects to the general path until it wakes, keeping what `weightOf` reports. */
8
9
  export declare function rest<I, O>(lanes: Lanes<I, O>, lane: Paced): void;
@@ -31,7 +32,7 @@ export declare function runEveryone<I, O>(this: Lanes<I, O>, lane: Lane<I, O>):
31
32
  /**
32
33
  * One subject's contribution from a lane's voice. True where the voice gave the subject a delta,
33
34
  * as the general path's `contribution` does where it returns one, which is what makes a voice in a
34
- * locus one of its members for the subject.
35
+ * role one of its members for the subject.
35
36
  */
36
37
  export declare function one<I, O>(this: Lanes<I, O>, lane: Lane<I, O>, p: number, slot: number): boolean;
37
38
  /**