@msb235/blits 0.6.0 → 0.7.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 (100) hide show
  1. package/CHANGELOG.md +152 -0
  2. package/README.md +31 -7
  3. package/dist/bare.js +7 -1
  4. package/dist/book.d.ts +7 -0
  5. package/dist/book.js +35 -4
  6. package/dist/chain.d.ts +2 -1
  7. package/dist/chain.js +7 -3
  8. package/dist/channels.d.ts +12 -19
  9. package/dist/channels.js +18 -118
  10. package/dist/clock.d.ts +9 -2
  11. package/dist/clock.js +10 -2
  12. package/dist/closed.d.ts +15 -0
  13. package/dist/closed.js +144 -0
  14. package/dist/color.d.ts +61 -0
  15. package/dist/color.js +229 -0
  16. package/dist/columns.js +2 -1
  17. package/dist/crowd.d.ts +1 -1
  18. package/dist/crowd.js +4 -1
  19. package/dist/cue.d.ts +2 -0
  20. package/dist/cue.js +80 -27
  21. package/dist/due.js +3 -0
  22. package/dist/fade.d.ts +13 -0
  23. package/dist/fade.js +57 -4
  24. package/dist/fill.d.ts +1 -1
  25. package/dist/fill.js +15 -8
  26. package/dist/fit.d.ts +144 -0
  27. package/dist/fit.js +181 -0
  28. package/dist/fold.d.ts +3 -3
  29. package/dist/fold.js +18 -11
  30. package/dist/gather.d.ts +6 -0
  31. package/dist/gather.js +18 -0
  32. package/dist/gone.d.ts +32 -0
  33. package/dist/gone.js +134 -0
  34. package/dist/handle.d.ts +27 -2
  35. package/dist/handle.js +60 -11
  36. package/dist/held.js +4 -0
  37. package/dist/history.d.ts +15 -2
  38. package/dist/history.js +56 -9
  39. package/dist/hosts.d.ts +2 -2
  40. package/dist/hosts.js +20 -6
  41. package/dist/index.d.ts +8 -2
  42. package/dist/index.js +5 -1
  43. package/dist/lane.d.ts +1 -1
  44. package/dist/lanes.d.ts +32 -9
  45. package/dist/lanes.js +53 -24
  46. package/dist/locus.d.ts +3 -3
  47. package/dist/locus.js +1 -1
  48. package/dist/marks.d.ts +3 -0
  49. package/dist/marks.js +80 -12
  50. package/dist/meet.d.ts +3 -2
  51. package/dist/meet.js +15 -3
  52. package/dist/mixer.d.ts +56 -36
  53. package/dist/mixer.js +158 -87
  54. package/dist/motion.d.ts +2 -199
  55. package/dist/motion.js +2 -652
  56. package/dist/motions.d.ts +266 -0
  57. package/dist/motions.js +724 -0
  58. package/dist/move.d.ts +1 -1
  59. package/dist/move.js +28 -8
  60. package/dist/owner.d.ts +1 -1
  61. package/dist/owner.js +3 -3
  62. package/dist/pace.d.ts +2 -0
  63. package/dist/pace.js +8 -0
  64. package/dist/pack.d.ts +31 -0
  65. package/dist/pack.js +65 -0
  66. package/dist/paging.d.ts +63 -0
  67. package/dist/paging.js +110 -0
  68. package/dist/patch.d.ts +3 -0
  69. package/dist/patch.js +2 -0
  70. package/dist/place.js +235 -43
  71. package/dist/project.d.ts +7 -4
  72. package/dist/project.js +117 -78
  73. package/dist/pull.js +4 -3
  74. package/dist/qualify.js +4 -2
  75. package/dist/revive.d.ts +58 -0
  76. package/dist/revive.js +158 -0
  77. package/dist/sample.d.ts +1 -1
  78. package/dist/sample.js +6 -1
  79. package/dist/scored.d.ts +57 -0
  80. package/dist/scored.js +196 -0
  81. package/dist/seek.d.ts +3 -0
  82. package/dist/seek.js +311 -0
  83. package/dist/spans.d.ts +29 -0
  84. package/dist/spans.js +165 -0
  85. package/dist/strays.d.ts +1 -1
  86. package/dist/strays.js +21 -0
  87. package/dist/tape.d.ts +21 -0
  88. package/dist/tape.js +65 -0
  89. package/dist/ticker.d.ts +28 -3
  90. package/dist/ticker.js +3 -0
  91. package/dist/transport.d.ts +125 -0
  92. package/dist/transport.js +283 -0
  93. package/dist/types.d.ts +404 -38
  94. package/dist/unpage.d.ts +8 -0
  95. package/dist/unpage.js +165 -0
  96. package/dist/voice.d.ts +75 -7
  97. package/dist/voice.js +119 -4
  98. package/dist/weigh.js +2 -2
  99. package/docs/schema.html +120 -31
  100. package/package.json +2 -1
@@ -0,0 +1,58 @@
1
+ import type { Mixer } from './mixer.js';
2
+ import type { Run } from './motions.js';
3
+ import { type PackedRecord } from './pack.js';
4
+ import { type Key } from './paging.js';
5
+ import type { Described } from './types.js';
6
+ import { type Controls, type Ramp, type Rise, Voice } from './voice.js';
7
+ /** A voice that has left, as plain data: the descriptor it was cued with, its controls and its records. */
8
+ interface PackedVoice {
9
+ as: Described;
10
+ start: number;
11
+ anchorNow: number;
12
+ anchorElapsed: number;
13
+ rate: number;
14
+ ramp: {
15
+ from: number;
16
+ to: number;
17
+ over: number;
18
+ } | null;
19
+ weight: number;
20
+ out: Ramp | null;
21
+ back: Rise | null;
22
+ outAt: number;
23
+ outOver: number;
24
+ outSet: boolean;
25
+ cuedAt: number;
26
+ doneAt: number;
27
+ opened: number;
28
+ pinned: number;
29
+ latest: number;
30
+ keeping: boolean;
31
+ first: Controls | null;
32
+ log: Controls[] | null;
33
+ played: boolean | undefined;
34
+ playedAt: number;
35
+ records: [Key, PackedRecord][];
36
+ runs: [Key, Run][];
37
+ left: [Key, number, PackedRecord][];
38
+ parts: [Key, number, number][];
39
+ parted: [Key, number][];
40
+ }
41
+ /**
42
+ * Voices a history store holds whose handles something still holds, by id: a seek that revives
43
+ * one puts it behind its handle, and needs it back if it left after the moment sought.
44
+ */
45
+ export declare class Outs {
46
+ private readonly out;
47
+ private readonly gone;
48
+ add(id: number, doneAt: number, h: object): void;
49
+ /** Takes a revived voice's handle back, undefined where none is held. */
50
+ take(id: number): object | undefined;
51
+ /** The id of a voice that left after `t` and that `has` lacks; undefined for none. */
52
+ missing(t: number, has: (id: number) => boolean): number | undefined;
53
+ }
54
+ /** Pages out a voice that left before `reach`; false for one that has to stay in memory. */
55
+ export declare function pageVoice<I, O>(mix: Mixer<I, O>, voice: Voice<I, O>, reach: number): boolean;
56
+ /** A voice a history store paged out, rebuilt from its descriptor and put back as it left. */
57
+ export declare function reviveVoice<I, O>(mix: Mixer<I, O>, id: number, d: PackedVoice): Voice<I, O>;
58
+ export type { PackedVoice };
package/dist/revive.js ADDED
@@ -0,0 +1,158 @@
1
+ import { playable } from './cue.js';
2
+ import { VoiceHandle } from './handle.js';
3
+ import { handle } from './hosts.js';
4
+ import { packHeld, unpackHeld } from './pack.js';
5
+ import { pageOut } from './paging.js';
6
+ import { Voice } from './voice.js';
7
+ /**
8
+ * Voices a history store holds whose handles something still holds, by id: a seek that revives
9
+ * one puts it behind its handle, and needs it back if it left after the moment sought.
10
+ */
11
+ export class Outs {
12
+ out = new Map();
13
+ gone = new FinalizationRegistry((id) => {
14
+ if (this.out.get(id)?.handle.deref() === undefined)
15
+ this.out.delete(id);
16
+ });
17
+ add(id, doneAt, h) {
18
+ this.out.set(id, { doneAt, handle: new WeakRef(h) });
19
+ this.gone.register(h, id);
20
+ }
21
+ /** Takes a revived voice's handle back, undefined where none is held. */
22
+ take(id) {
23
+ const e = this.out.get(id);
24
+ this.out.delete(id);
25
+ return e?.handle.deref();
26
+ }
27
+ /** The id of a voice that left after `t` and that `has` lacks; undefined for none. */
28
+ missing(t, has) {
29
+ for (const [id, e] of this.out)
30
+ if (e.doneAt > t && !has(id))
31
+ return id;
32
+ return undefined;
33
+ }
34
+ }
35
+ /** Whether a voice can leave memory: it was cued with `as`, and nothing else in the mix holds it. */
36
+ function pageable(voice) {
37
+ return (voice.spec.as !== undefined &&
38
+ voice.owner === null &&
39
+ voice.holding === null &&
40
+ voice.blend === null &&
41
+ voice.answers === null &&
42
+ voice.fitting === null);
43
+ }
44
+ /** Pages out a voice that left before `reach`; false for one that has to stay in memory. */
45
+ export function pageVoice(mix, voice, reach) {
46
+ if (!pageable(voice))
47
+ return false;
48
+ const keys = mix.keys;
49
+ const data = packVoice(voice, keys);
50
+ pageOut(mix, 'voice', voice.id, undefined, [{ at: voice.doneAt }], () => data, reach);
51
+ const h = voice.handle;
52
+ VoiceHandle.page(h, { weight: voice.weight, played: voice.settled.played === true });
53
+ mix.outs.add(voice.id, voice.doneAt, h);
54
+ mix.parters.delete(voice);
55
+ return true;
56
+ }
57
+ function packVoice(voice, keys) {
58
+ const subjectOf = (key) => keys.subject(key);
59
+ const records = [];
60
+ const runs = [];
61
+ for (const key of voice.keyed ?? []) {
62
+ const subject = subjectOf(key);
63
+ const held = voice.subjects.get(subject);
64
+ if (held?.reaches)
65
+ records.push([key, packHeld(voice, held)]);
66
+ const run = voice.motion?.pack(subject);
67
+ if (run !== undefined)
68
+ runs.push([key, run]);
69
+ }
70
+ const settled = voice.settled;
71
+ return {
72
+ as: voice.spec.as,
73
+ start: voice.start,
74
+ anchorNow: voice.anchorNow,
75
+ anchorElapsed: voice.anchorElapsed,
76
+ rate: voice.rate,
77
+ ramp: voice.ramp,
78
+ weight: voice.weight,
79
+ out: voice.out,
80
+ back: voice.back,
81
+ outAt: voice.outAt,
82
+ outOver: voice.outOver,
83
+ outSet: voice.outSet,
84
+ cuedAt: voice.cuedAt,
85
+ doneAt: voice.doneAt,
86
+ opened: voice.opened,
87
+ pinned: voice.pinned,
88
+ latest: voice.latest,
89
+ keeping: voice.keeping,
90
+ first: voice.first,
91
+ log: voice.log,
92
+ played: settled.played,
93
+ playedAt: settled.at,
94
+ records,
95
+ runs,
96
+ left: (voice.left ?? []).map((e) => [keys.key(e.subject), e.at, packHeld(voice, e.held)]),
97
+ parts: [...(voice.parts ?? [])].map(([s, r]) => [keys.key(s), r.at, r.over]),
98
+ parted: [...(voice.parted ?? [])].map(([s, at]) => [keys.key(s), at]),
99
+ };
100
+ }
101
+ /** A voice a history store paged out, rebuilt from its descriptor and put back as it left. */
102
+ export function reviveVoice(mix, id, d) {
103
+ const revive = mix.opts.history?.revive;
104
+ if (revive === undefined)
105
+ throw new Error('blits: a paged voice needs history.revive');
106
+ const spec = { ...revive(d.as), as: d.as };
107
+ playable(mix, spec);
108
+ const voice = new Voice(id, spec, spec.patch, spec.fade ?? {}, Number.NEGATIVE_INFINITY, d.start, mix.slotOf, mix.channels, mix.opts.host, mix.send, null);
109
+ voice.anchorNow = d.anchorNow;
110
+ voice.anchorElapsed = d.anchorElapsed;
111
+ voice.rate = d.rate;
112
+ voice.ramp = d.ramp;
113
+ voice.weight = d.weight;
114
+ voice.out = d.out;
115
+ voice.back = d.back;
116
+ voice.outAt = d.outAt;
117
+ voice.outOver = d.outOver;
118
+ voice.outSet = d.outSet;
119
+ voice.cuedAt = d.cuedAt;
120
+ voice.doneAt = d.doneAt;
121
+ voice.opened = d.opened;
122
+ voice.pinned = d.pinned;
123
+ voice.latest = d.latest;
124
+ voice.keeping = d.keeping;
125
+ voice.first = d.first;
126
+ voice.log = d.log;
127
+ voice.state = 'done';
128
+ if (d.played !== undefined)
129
+ voice.play(d.played, d.playedAt);
130
+ voice.resolve();
131
+ const keys = mix.keys;
132
+ const subjectOf = (key) => keys.subject(key);
133
+ voice.keyed = new Set();
134
+ for (const [key, p] of d.records) {
135
+ voice.subjects.set(subjectOf(key), unpackHeld(voice, p));
136
+ voice.keyed.add(key);
137
+ }
138
+ for (const [key, run] of d.runs)
139
+ voice.motion?.unpack(subjectOf(key), run);
140
+ if (d.left.length > 0)
141
+ voice.left = d.left.map(([key, at, p]) => ({
142
+ subject: subjectOf(key),
143
+ at,
144
+ held: unpackHeld(voice, p),
145
+ }));
146
+ if (d.parts.length > 0)
147
+ voice.parts = new Map(d.parts.map(([key, at, over]) => [subjectOf(key), { at, over }]));
148
+ if (d.parted.length > 0)
149
+ voice.parted = new Map(d.parted.map(([key, at]) => [subjectOf(key), at]));
150
+ const h = mix.outs?.take(id);
151
+ if (h === undefined)
152
+ voice.handle = handle(mix, voice);
153
+ else {
154
+ VoiceHandle.rehome(h, voice);
155
+ voice.handle = h;
156
+ }
157
+ return voice;
158
+ }
package/dist/sample.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { type Lane, type Positions } from './lane.js';
2
2
  import type { Lanes } from './lanes.js';
3
- import type { Motions } from './motion.js';
3
+ import type { Motions } from './motions.js';
4
4
  import type { Subject } from './voice.js';
5
5
  /**
6
6
  * A motion voice's fill: what `one` and `move` do for each subject, in one loop for the common
package/dist/sample.js CHANGED
@@ -17,6 +17,7 @@ export function runMotion(lanes, lane, run) {
17
17
  const ch = lane.chans[0];
18
18
  const fills = lanes.fills;
19
19
  const from = lanes.frameProbes;
20
+ const lately = lanes.lately;
20
21
  const seeks = voice.seeks;
21
22
  const elapsedNow = voice.elapsedAt(lanes.now);
22
23
  const flat = lane.flat;
@@ -57,6 +58,10 @@ export function runMotion(lanes, lane, run) {
57
58
  const q = slot * 6 /* Per.SLOT */;
58
59
  if (per[q + 5 /* Per.LANE_FILL */] === fills - 1)
59
60
  data[o + 3 /* Row.PROBED */] = data[o + 2 /* Row.WEIGHT */];
61
+ if (lately >= 0 && lanes.unread(slot)) {
62
+ unbareLane(lane, p);
63
+ continue;
64
+ }
60
65
  const delay = data[o + 0 /* Row.DELAY */];
61
66
  const elapsed = elapsedNow - delay;
62
67
  if (elapsed < 0) {
@@ -106,9 +111,9 @@ export function runMotion(lanes, lane, run) {
106
111
  data[o + 5 /* Row.SAMPLED */] = -2 /* Sampled.BARE */;
107
112
  lane.deltas[p] = null;
108
113
  }
109
- const share = left(ease, progress(elapsed, runs[b], runs[b + 2]));
110
114
  if (w <= 0)
111
115
  continue;
116
+ const share = left(ease, progress(elapsed, runs[b], runs[b + 2]));
112
117
  const values = ch.values;
113
118
  const base = slot * n;
114
119
  const x = b + 3;
@@ -0,0 +1,57 @@
1
+ import type { Mixer } from './mixer.js';
2
+ import type { Voice } from './voice.js';
3
+ /**
4
+ * A mix's voices by the score they were cued on, for an anchor that asks for a score's marks: the
5
+ * cued ones as a set, the gone ones in the order they left, which an anchor reads once each. Built
6
+ * over the lists it saw: a seek or a copy that replaces either rebuilds that half on the next read.
7
+ * A score whose gone voices history lets go of gets a fresh list, so a read of the old one starts
8
+ * over.
9
+ */
10
+ export declare class ScoreIndex<I, O> {
11
+ cued: readonly Voice<I, O>[] | null;
12
+ by: Map<string, Set<Voice<I, O>>>;
13
+ gone: readonly Voice<I, O>[] | null;
14
+ goneBy: Map<string, Voice<I, O>[]>;
15
+ /** Bumped for a score each time a voice on it is cued, retimed, retired or leaves. */
16
+ versions: Map<string, number>;
17
+ /** The scores whose list on the transport names this mix. */
18
+ held: Set<string>;
19
+ }
20
+ type Member = Mixer<unknown, unknown>;
21
+ /**
22
+ * Per score, the mixes on a transport that may hold a voice on it, cued or gone, in the
23
+ * transport's order. Never short of one that does; one found holding none is let go on that read.
24
+ */
25
+ export type ScoreHolders = Map<string, Member[]>;
26
+ /** The mixes on `mix`'s transport that may hold a voice on `score`; the list is the transport's own. */
27
+ export declare function holders<I, O>(mix: Mixer<I, O>, score: string): readonly Mixer<I, O>[];
28
+ /** Takes `mix` off `score`'s list, having found it holds nothing there. */
29
+ export declare function unhold<I, O>(mix: Mixer<I, O>, score: string): void;
30
+ /** A mix joining its transport, with whatever voices it already has. */
31
+ export declare function scoredJoin<I, O>(mix: Mixer<I, O>): void;
32
+ /** A mix leaving its transport. */
33
+ export declare function scoredDrop<I, O>(mix: Mixer<I, O>): void;
34
+ /**
35
+ * A voice on its score was cued, retimed, retired or left: what was read of the score is stale. A
36
+ * host can still retime a gone voice through a handle it kept, so `retimed` on a done voice
37
+ * replaces its score's gone list too, which starts every read of it over.
38
+ */
39
+ export declare function scoreTouched<I, O>(mix: Mixer<I, O>, voice: Voice<I, O>, retimed?: boolean): void;
40
+ /** How many times `score` has been touched in `mix`. */
41
+ export declare function scoreVersion<I, O>(mix: Mixer<I, O>, score: string): number;
42
+ /** The voices of `mix` cued on `score` and not yet gone, in no order. */
43
+ export declare function scored<I, O>(mix: Mixer<I, O>, score: string): ReadonlySet<Voice<I, O>>;
44
+ /** The voices of `mix` cued on `score` that have gone, in the order they left. */
45
+ export declare function scoredGone<I, O>(mix: Mixer<I, O>, score: string): readonly Voice<I, O>[];
46
+ /** A voice just pushed onto `gone`. */
47
+ export declare function scoredLeft<I, O>(mix: Mixer<I, O>, voice: Voice<I, O>): void;
48
+ /**
49
+ * Gone voices history let go of, out of `was`; `mix.gone` is already what is left, whether that
50
+ * is `was` cut in place or a new list.
51
+ */
52
+ export declare function scoredCut<I, O>(mix: Mixer<I, O>, was: readonly Voice<I, O>[], out: readonly Voice<I, O>[]): void;
53
+ /** A voice just cued. */
54
+ export declare function scoredAdd<I, O>(mix: Mixer<I, O>, voice: Voice<I, O>): void;
55
+ /** Voices no longer cued. */
56
+ export declare function scoredRemove<I, O>(mix: Mixer<I, O>, voices: Iterable<Voice<I, O>>): void;
57
+ export {};
package/dist/scored.js ADDED
@@ -0,0 +1,196 @@
1
+ /**
2
+ * A mix's voices by the score they were cued on, for an anchor that asks for a score's marks: the
3
+ * cued ones as a set, the gone ones in the order they left, which an anchor reads once each. Built
4
+ * over the lists it saw: a seek or a copy that replaces either rebuilds that half on the next read.
5
+ * A score whose gone voices history lets go of gets a fresh list, so a read of the old one starts
6
+ * over.
7
+ */
8
+ export class ScoreIndex {
9
+ cued = null;
10
+ by = new Map();
11
+ gone = null;
12
+ goneBy = new Map();
13
+ /** Bumped for a score each time a voice on it is cued, retimed, retired or leaves. */
14
+ versions = new Map();
15
+ /** The scores whose list on the transport names this mix. */
16
+ held = new Set();
17
+ }
18
+ function hold(mix, score) {
19
+ const held = mix.scored.held;
20
+ // A dropped mix is off the transport's lists until a seek puts it back, and `scoredJoin` then.
21
+ if (held.has(score) || mix.dropped)
22
+ return;
23
+ held.add(score);
24
+ const by = mix.transport.holders;
25
+ const m = mix;
26
+ const list = by.get(score);
27
+ if (list === undefined) {
28
+ by.set(score, [m]);
29
+ return;
30
+ }
31
+ const i = list.findIndex((o) => o.slot > m.slot);
32
+ if (i < 0)
33
+ list.push(m);
34
+ else
35
+ list.splice(i, 0, m);
36
+ }
37
+ /** The mixes on `mix`'s transport that may hold a voice on `score`; the list is the transport's own. */
38
+ export function holders(mix, score) {
39
+ return (mix.transport.holders.get(score) ?? none);
40
+ }
41
+ /** Takes `mix` off `score`'s list, having found it holds nothing there. */
42
+ export function unhold(mix, score) {
43
+ mix.scored.held.delete(score);
44
+ const by = mix.transport.holders;
45
+ const list = by.get(score);
46
+ if (list === undefined)
47
+ return;
48
+ const i = list.indexOf(mix);
49
+ if (i >= 0)
50
+ list.splice(i, 1);
51
+ if (list.length === 0)
52
+ by.delete(score);
53
+ }
54
+ /** A mix joining its transport, with whatever voices it already has. */
55
+ export function scoredJoin(mix) {
56
+ for (const v of mix.cued)
57
+ if (v.spec.score !== undefined)
58
+ hold(mix, v.spec.score);
59
+ for (const v of mix.gone)
60
+ if (v.spec.score !== undefined)
61
+ hold(mix, v.spec.score);
62
+ }
63
+ /** A mix leaving its transport. */
64
+ export function scoredDrop(mix) {
65
+ for (const score of [...mix.scored.held])
66
+ unhold(mix, score);
67
+ }
68
+ /**
69
+ * A voice on its score was cued, retimed, retired or left: what was read of the score is stale. A
70
+ * host can still retime a gone voice through a handle it kept, so `retimed` on a done voice
71
+ * replaces its score's gone list too, which starts every read of it over.
72
+ */
73
+ export function scoreTouched(mix, voice, retimed = false) {
74
+ const score = voice.spec.score;
75
+ if (score === undefined)
76
+ return;
77
+ const ix = mix.scored;
78
+ ix.versions.set(score, (ix.versions.get(score) ?? 0) + 1);
79
+ if (!retimed || voice.state !== 'done')
80
+ return;
81
+ const gone = ix.goneBy.get(score);
82
+ if (gone !== undefined)
83
+ ix.goneBy.set(score, [...gone]);
84
+ }
85
+ /** How many times `score` has been touched in `mix`. */
86
+ export function scoreVersion(mix, score) {
87
+ return mix.scored.versions.get(score) ?? 0;
88
+ }
89
+ function put(by, voice) {
90
+ const score = voice.spec.score;
91
+ if (score === undefined)
92
+ return;
93
+ const set = by.get(score);
94
+ if (set === undefined)
95
+ by.set(score, new Set([voice]));
96
+ else
97
+ set.add(voice);
98
+ }
99
+ /** The voices of `mix` cued on `score` and not yet gone, in no order. */
100
+ export function scored(mix, score) {
101
+ const ix = mix.scored;
102
+ if (ix.cued !== mix.cued) {
103
+ ix.by = new Map();
104
+ for (const v of mix.cued)
105
+ put(ix.by, v);
106
+ ix.cued = mix.cued;
107
+ }
108
+ return ix.by.get(score) ?? noVoices;
109
+ }
110
+ const noVoices = new Set();
111
+ /** The voices of `mix` cued on `score` that have gone, in the order they left. */
112
+ export function scoredGone(mix, score) {
113
+ const ix = mix.scored;
114
+ if (ix.gone !== mix.gone) {
115
+ ix.goneBy = new Map();
116
+ for (const v of mix.gone)
117
+ goneAdd(ix.goneBy, v);
118
+ ix.gone = mix.gone;
119
+ }
120
+ return ix.goneBy.get(score) ?? none;
121
+ }
122
+ const none = [];
123
+ function goneAdd(by, voice) {
124
+ const score = voice.spec.score;
125
+ if (score === undefined)
126
+ return;
127
+ const list = by.get(score);
128
+ if (list === undefined)
129
+ by.set(score, [voice]);
130
+ else
131
+ list.push(voice);
132
+ }
133
+ /** A voice just pushed onto `gone`. */
134
+ export function scoredLeft(mix, voice) {
135
+ if (voice.spec.score !== undefined)
136
+ hold(mix, voice.spec.score);
137
+ const ix = mix.scored;
138
+ if (ix.gone === mix.gone)
139
+ goneAdd(ix.goneBy, voice);
140
+ }
141
+ /**
142
+ * Gone voices history let go of, out of `was`; `mix.gone` is already what is left, whether that
143
+ * is `was` cut in place or a new list.
144
+ */
145
+ export function scoredCut(mix, was, out) {
146
+ const ix = mix.scored;
147
+ if (ix.gone !== was)
148
+ return;
149
+ ix.gone = mix.gone;
150
+ const by = new Map();
151
+ for (const v of out) {
152
+ const score = v.spec.score;
153
+ if (score === undefined)
154
+ continue;
155
+ const set = by.get(score);
156
+ if (set === undefined)
157
+ by.set(score, new Set([v]));
158
+ else
159
+ set.add(v);
160
+ }
161
+ for (const [score, gone] of by) {
162
+ const left = (ix.goneBy.get(score) ?? []).filter((v) => !gone.has(v));
163
+ if (left.length === 0)
164
+ ix.goneBy.delete(score);
165
+ else
166
+ ix.goneBy.set(score, left);
167
+ }
168
+ }
169
+ /** A voice just cued. */
170
+ export function scoredAdd(mix, voice) {
171
+ scoreTouched(mix, voice);
172
+ if (voice.spec.score !== undefined)
173
+ hold(mix, voice.spec.score);
174
+ const ix = mix.scored;
175
+ if (ix.cued === mix.cued)
176
+ put(ix.by, voice);
177
+ }
178
+ /** Voices no longer cued. */
179
+ export function scoredRemove(mix, voices) {
180
+ for (const v of voices)
181
+ scoreTouched(mix, v);
182
+ const ix = mix.scored;
183
+ if (ix.cued !== mix.cued)
184
+ return;
185
+ for (const v of voices) {
186
+ const score = v.spec.score;
187
+ if (score === undefined)
188
+ continue;
189
+ const set = ix.by.get(score);
190
+ if (set === undefined)
191
+ continue;
192
+ set.delete(v);
193
+ if (set.size === 0)
194
+ ix.by.delete(score);
195
+ }
196
+ }
package/dist/seek.d.ts ADDED
@@ -0,0 +1,3 @@
1
+ import type { Transport } from './transport.js';
2
+ /** Moves every mix on the transport to mix time `t`, as it stood at the end of that frame. */
3
+ export declare function seek(transport: Transport, t: number): void;