@msb235/blits 0.3.0 → 0.5.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.
- package/CHANGELOG.md +314 -0
- package/README.md +3 -2
- package/dist/bare.d.ts +29 -0
- package/dist/bare.js +105 -0
- package/dist/blend.d.ts +34 -0
- package/dist/blend.js +102 -0
- package/dist/book.d.ts +53 -0
- package/dist/book.js +218 -0
- package/dist/chain.d.ts +22 -0
- package/dist/chain.js +150 -0
- package/dist/channels.d.ts +2 -0
- package/dist/channels.js +3 -1
- package/dist/clock.d.ts +50 -3
- package/dist/clock.js +102 -9
- package/dist/clone.d.ts +7 -0
- package/dist/clone.js +57 -0
- package/dist/columns.d.ts +29 -0
- package/dist/columns.js +183 -0
- package/dist/crowd.d.ts +128 -0
- package/dist/crowd.js +354 -0
- package/dist/cue.d.ts +3 -0
- package/dist/cue.js +115 -0
- package/dist/due.d.ts +12 -0
- package/dist/due.js +93 -0
- package/dist/fade.d.ts +19 -0
- package/dist/fade.js +133 -0
- package/dist/fill.d.ts +42 -0
- package/dist/fill.js +381 -0
- package/dist/fold.d.ts +51 -0
- package/dist/fold.js +475 -0
- package/dist/gather.d.ts +24 -0
- package/dist/gather.js +186 -0
- package/dist/handle.d.ts +31 -0
- package/dist/handle.js +56 -0
- package/dist/held.d.ts +14 -0
- package/dist/held.js +117 -0
- package/dist/history.d.ts +22 -0
- package/dist/history.js +149 -0
- package/dist/hosts.d.ts +16 -0
- package/dist/hosts.js +178 -0
- package/dist/index.d.ts +1 -1
- package/dist/keyrows.d.ts +36 -0
- package/dist/keyrows.js +138 -0
- package/dist/lane.d.ts +210 -0
- package/dist/lane.js +187 -0
- package/dist/lanes.d.ts +143 -122
- package/dist/lanes.js +236 -742
- package/dist/locus.d.ts +40 -0
- package/dist/locus.js +89 -0
- package/dist/marks.d.ts +12 -0
- package/dist/marks.js +80 -0
- package/dist/meet.d.ts +16 -0
- package/dist/meet.js +113 -0
- package/dist/mixer.d.ts +244 -169
- package/dist/mixer.js +321 -1793
- package/dist/motion.d.ts +104 -19
- package/dist/motion.js +448 -180
- package/dist/move.d.ts +9 -0
- package/dist/move.js +190 -0
- package/dist/named.d.ts +12 -0
- package/dist/named.js +39 -0
- package/dist/numbers.d.ts +18 -6
- package/dist/numbers.js +59 -31
- package/dist/owed.d.ts +24 -0
- package/dist/owed.js +51 -0
- package/dist/owner.d.ts +43 -0
- package/dist/owner.js +117 -0
- package/dist/pace.d.ts +40 -0
- package/dist/pace.js +113 -0
- package/dist/patch.d.ts +51 -10
- package/dist/patch.js +140 -45
- package/dist/place.d.ts +20 -0
- package/dist/place.js +196 -0
- package/dist/project.d.ts +4 -0
- package/dist/project.js +233 -0
- package/dist/pull.d.ts +5 -0
- package/dist/pull.js +175 -0
- package/dist/qualify.d.ts +31 -0
- package/dist/qualify.js +345 -0
- package/dist/relink.d.ts +25 -0
- package/dist/relink.js +59 -0
- package/dist/rows.d.ts +33 -0
- package/dist/rows.js +282 -0
- package/dist/sample.d.ts +21 -0
- package/dist/sample.js +232 -0
- package/dist/signals.d.ts +12 -9
- package/dist/signals.js +8 -5
- package/dist/store.d.ts +10 -3
- package/dist/store.js +44 -9
- package/dist/strays.d.ts +21 -0
- package/dist/strays.js +84 -0
- package/dist/tweened.d.ts +11 -0
- package/dist/tweened.js +48 -0
- package/dist/types.d.ts +261 -42
- package/dist/unreached.d.ts +5 -0
- package/dist/unreached.js +24 -0
- package/dist/voice.d.ts +240 -0
- package/dist/voice.js +322 -0
- package/dist/weigh.d.ts +26 -0
- package/dist/weigh.js +89 -0
- package/docs/schema.html +151 -46
- package/package.json +5 -2
package/dist/move.d.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { Mixer } from './mixer.js';
|
|
2
|
+
/** The next frame among every mix's. */
|
|
3
|
+
export declare function nextFrame(): number;
|
|
4
|
+
/** Whether a sync that leaves the mix clock where it was still has something to land. */
|
|
5
|
+
export declare function waits<I, O>(mix: Mixer<I, O>): boolean;
|
|
6
|
+
/** Moves the mix clock to `now`: voices start, finite loops end, and fades finish. */
|
|
7
|
+
export declare function move<I, O>(mix: Mixer<I, O>, now: number): void;
|
|
8
|
+
/** Lets go of the voices that have left and that history no longer reaches. */
|
|
9
|
+
export declare function forget<I, O>(mix: Mixer<I, O>, now: number): void;
|
package/dist/move.js
ADDED
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
import { changed, unindex } from './chain.js';
|
|
2
|
+
import { popDue, schedule } from './due.js';
|
|
3
|
+
import { beginFade, part, retire } from './fade.js';
|
|
4
|
+
import { started } from './held.js';
|
|
5
|
+
import { ownerReading } from './owner.js';
|
|
6
|
+
import { mixAt, place, repin, startOf } from './place.js';
|
|
7
|
+
import { unplay } from './strays.js';
|
|
8
|
+
/** Numbers every mix's frames apart, so a blend's read is never taken for another mix's or frame's. */
|
|
9
|
+
let frames = 0;
|
|
10
|
+
/** The next frame among every mix's. */
|
|
11
|
+
export function nextFrame() {
|
|
12
|
+
return ++frames;
|
|
13
|
+
}
|
|
14
|
+
/** Whether a sync that leaves the mix clock where it was still has something to land. */
|
|
15
|
+
export function waits(mix) {
|
|
16
|
+
return mix.stirred || mix.anchored > 0 || (mix.pins !== null && mix.pins.size > 0);
|
|
17
|
+
}
|
|
18
|
+
/** Moves the mix clock to `now`: voices start, finite loops end, and fades finish. */
|
|
19
|
+
export function move(mix, now) {
|
|
20
|
+
mix.moving = true;
|
|
21
|
+
try {
|
|
22
|
+
moveTo(mix, now);
|
|
23
|
+
}
|
|
24
|
+
finally {
|
|
25
|
+
mix.moving = false;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
function moveTo(mix, now) {
|
|
29
|
+
mix.now = now;
|
|
30
|
+
mix.frame = nextFrame();
|
|
31
|
+
mix.reducedNow = mix.reduced;
|
|
32
|
+
for (const a of mix.announced)
|
|
33
|
+
if (Number.isNaN(a.at))
|
|
34
|
+
a.at = mix.u;
|
|
35
|
+
if (mix.pins !== null && mix.pins.size > 0)
|
|
36
|
+
repin(mix);
|
|
37
|
+
if (mix.anchored > 0)
|
|
38
|
+
place(mix);
|
|
39
|
+
if (mix.announced.length > 0) {
|
|
40
|
+
// Kept while still ahead, or while history reaches it; anchors waiting on one were placed above.
|
|
41
|
+
const reach = now - (mix.opts.history?.ms ?? 0);
|
|
42
|
+
mix.announced = mix.announced.filter((a) => mixAt(mix, a.at) >= reach);
|
|
43
|
+
}
|
|
44
|
+
// A live mix visits only the voices due by now; a projection, which copies few, visits all.
|
|
45
|
+
const visit = mix.projecting || mix.walkAll ? mix.cued : popDue(mix, now);
|
|
46
|
+
for (const voice of visit) {
|
|
47
|
+
if (voice.state === 'done')
|
|
48
|
+
continue;
|
|
49
|
+
if (Number.isNaN(voice.opened))
|
|
50
|
+
voice.opened = now;
|
|
51
|
+
if ((voice.state === 'live' || voice.state === 'held') && voice.outAt <= now)
|
|
52
|
+
beginFade(mix, voice, {}, Math.max(startOf(voice), voice.outAt));
|
|
53
|
+
if (voice.state === 'pending' &&
|
|
54
|
+
(voice.owner === null ? now : ownerReading(voice.owner, now)) >= voice.start) {
|
|
55
|
+
voice.state = 'live';
|
|
56
|
+
started(mix, voice);
|
|
57
|
+
changed(mix, voice);
|
|
58
|
+
}
|
|
59
|
+
if (voice.state !== 'pending' && Number.isFinite(voice.span)) {
|
|
60
|
+
const end = voice.span + voice.latest;
|
|
61
|
+
if (voice.elapsedAt(now) >= end) {
|
|
62
|
+
voice.play(true);
|
|
63
|
+
// The fade starts when the last pass ended, not at the frame that noticed, so it plays
|
|
64
|
+
// the same at any frame rate and a read at another time can find it.
|
|
65
|
+
if (voice.state === 'live') {
|
|
66
|
+
if (voice.holdsAfter)
|
|
67
|
+
voice.state = 'held';
|
|
68
|
+
else
|
|
69
|
+
beginFade(mix, voice, {}, Math.max(startOf(voice), Math.min(now, voice.timeAt(end))));
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
else if (voice.state === 'held')
|
|
73
|
+
voice.state = 'live';
|
|
74
|
+
}
|
|
75
|
+
// A projection reads a finished ramp as weight 0, and leaves the live voice's subjects be.
|
|
76
|
+
if (voice.parts !== null && !mix.projecting)
|
|
77
|
+
for (const [subject, r] of voice.parts)
|
|
78
|
+
if (now - r.at >= r.over)
|
|
79
|
+
part(mix, voice, subject, r.at + r.over);
|
|
80
|
+
if (voice.state === 'fading' && voice.out) {
|
|
81
|
+
const { at, over, rest, deadline } = voice.out;
|
|
82
|
+
const spent = now - at;
|
|
83
|
+
if (rest) {
|
|
84
|
+
const out = deadline !== undefined && spent >= deadline;
|
|
85
|
+
const settled = voice.seen > 0 && voice.restedCount >= voice.seen;
|
|
86
|
+
if (out)
|
|
87
|
+
retire(mix, voice, at + deadline);
|
|
88
|
+
else if (settled)
|
|
89
|
+
retire(mix, voice);
|
|
90
|
+
}
|
|
91
|
+
else if (spent >= over)
|
|
92
|
+
retire(mix, voice, at + over);
|
|
93
|
+
}
|
|
94
|
+
if (!mix.projecting)
|
|
95
|
+
schedule(mix, voice);
|
|
96
|
+
}
|
|
97
|
+
mix.stirred = false;
|
|
98
|
+
if (mix.retired.length > 0)
|
|
99
|
+
prune(mix);
|
|
100
|
+
forget(mix, now);
|
|
101
|
+
}
|
|
102
|
+
/** Past this many voices retiring in one frame, one pass over the list beats a search for each. */
|
|
103
|
+
const SEARCHED = 8;
|
|
104
|
+
/**
|
|
105
|
+
* Takes the voices retired since the last prune out of the list, in list order. A few are found by
|
|
106
|
+
* their id, which the list is in order of, so the voices still in it are not read.
|
|
107
|
+
*/
|
|
108
|
+
function prune(mix) {
|
|
109
|
+
const retired = mix.retired;
|
|
110
|
+
mix.retired = [];
|
|
111
|
+
const voices = mix.cued;
|
|
112
|
+
let at = null;
|
|
113
|
+
// A projection's list holds copies of voices gone before it, which only a pass takes out.
|
|
114
|
+
if (retired.length <= SEARCHED && !mix.projecting) {
|
|
115
|
+
if (retired.length > 1)
|
|
116
|
+
retired.sort((a, b) => a.id - b.id);
|
|
117
|
+
at = [];
|
|
118
|
+
for (let k = 0; k < retired.length; k++) {
|
|
119
|
+
const voice = retired[k];
|
|
120
|
+
const i = voice === retired[k - 1] ? -1 : indexOf(voices, voice);
|
|
121
|
+
if (i < 0) {
|
|
122
|
+
at = null;
|
|
123
|
+
break;
|
|
124
|
+
}
|
|
125
|
+
at.push(i);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
let pruned = false;
|
|
129
|
+
if (at !== null) {
|
|
130
|
+
for (const voice of retired)
|
|
131
|
+
pruned = leave(mix, voice) || pruned;
|
|
132
|
+
for (let k = at.length - 1; k >= 0; k--)
|
|
133
|
+
voices.splice(at[k], 1);
|
|
134
|
+
}
|
|
135
|
+
else {
|
|
136
|
+
let kept = 0;
|
|
137
|
+
for (let i = 0; i < voices.length; i++) {
|
|
138
|
+
const voice = voices[i];
|
|
139
|
+
if (voice.state !== 'done')
|
|
140
|
+
voices[kept++] = voice;
|
|
141
|
+
else
|
|
142
|
+
pruned = leave(mix, voice) || pruned;
|
|
143
|
+
}
|
|
144
|
+
voices.length = kept;
|
|
145
|
+
}
|
|
146
|
+
if (pruned)
|
|
147
|
+
mix.general = mix.general.filter((v) => v.state !== 'done');
|
|
148
|
+
}
|
|
149
|
+
/** The voice's place in a list in id order, or -1 where it is not there. */
|
|
150
|
+
function indexOf(voices, voice) {
|
|
151
|
+
let lo = 0;
|
|
152
|
+
let hi = voices.length - 1;
|
|
153
|
+
while (lo <= hi) {
|
|
154
|
+
const mid = (lo + hi) >>> 1;
|
|
155
|
+
const id = voices[mid].id;
|
|
156
|
+
if (id === voice.id)
|
|
157
|
+
return voices[mid] === voice ? mid : -1;
|
|
158
|
+
if (id < voice.id)
|
|
159
|
+
lo = mid + 1;
|
|
160
|
+
else
|
|
161
|
+
hi = mid - 1;
|
|
162
|
+
}
|
|
163
|
+
return -1;
|
|
164
|
+
}
|
|
165
|
+
/** Lets go of a retired voice's place in the mix; whether it was among the voices naming no subject. */
|
|
166
|
+
function leave(mix, voice) {
|
|
167
|
+
if (mix.opts.history)
|
|
168
|
+
mix.gone.push(voice);
|
|
169
|
+
else
|
|
170
|
+
mix.parters.delete(voice);
|
|
171
|
+
unplay(mix, voice);
|
|
172
|
+
changed(mix, voice);
|
|
173
|
+
if (voice.named !== null)
|
|
174
|
+
unindex(mix, voice);
|
|
175
|
+
if (voice.holding !== null && mix.owners !== null)
|
|
176
|
+
mix.owners = mix.owners.filter((v) => v !== voice);
|
|
177
|
+
if (voice.spec.locus !== undefined)
|
|
178
|
+
mix.loci--;
|
|
179
|
+
if (voice.spec.anchor !== undefined)
|
|
180
|
+
mix.anchored--;
|
|
181
|
+
return voice.named === null;
|
|
182
|
+
}
|
|
183
|
+
/** Lets go of the voices that have left and that history no longer reaches. */
|
|
184
|
+
export function forget(mix, now) {
|
|
185
|
+
const history = mix.opts.history;
|
|
186
|
+
if (history && mix.gone.length > 0) {
|
|
187
|
+
const reach = now - history.ms;
|
|
188
|
+
mix.gone = mix.gone.filter((v) => v.doneAt >= reach);
|
|
189
|
+
}
|
|
190
|
+
}
|
package/dist/named.d.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The subjects a voice's `subjects` names, once each in the order first named. Most voices name
|
|
3
|
+
* one, which a `Set` per voice spends its smallest table on.
|
|
4
|
+
*/
|
|
5
|
+
export declare class Named<I> {
|
|
6
|
+
readonly list: readonly I[];
|
|
7
|
+
private readonly set;
|
|
8
|
+
constructor(subjects: Iterable<I>);
|
|
9
|
+
get size(): number;
|
|
10
|
+
has(subject: I): boolean;
|
|
11
|
+
[Symbol.iterator](): Iterator<I>;
|
|
12
|
+
}
|
package/dist/named.js
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/** Past this many, `has` looks in a set rather than down the list. */
|
|
2
|
+
const FEW = 8;
|
|
3
|
+
/**
|
|
4
|
+
* The subjects a voice's `subjects` names, once each in the order first named. Most voices name
|
|
5
|
+
* one, which a `Set` per voice spends its smallest table on.
|
|
6
|
+
*/
|
|
7
|
+
export class Named {
|
|
8
|
+
list;
|
|
9
|
+
set;
|
|
10
|
+
constructor(subjects) {
|
|
11
|
+
const list = [];
|
|
12
|
+
let set = null;
|
|
13
|
+
for (const s of subjects) {
|
|
14
|
+
if (set !== null) {
|
|
15
|
+
if (!set.has(s)) {
|
|
16
|
+
set.add(s);
|
|
17
|
+
list.push(s);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
else if (!list.includes(s)) {
|
|
21
|
+
list.push(s);
|
|
22
|
+
if (list.length > FEW)
|
|
23
|
+
set = new Set(list);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
// Exactly as long as it is: a list grown by push keeps room for 17.
|
|
27
|
+
this.list = list.slice();
|
|
28
|
+
this.set = set;
|
|
29
|
+
}
|
|
30
|
+
get size() {
|
|
31
|
+
return this.list.length;
|
|
32
|
+
}
|
|
33
|
+
has(subject) {
|
|
34
|
+
return this.set === null ? this.list.includes(subject) : this.set.has(subject);
|
|
35
|
+
}
|
|
36
|
+
[Symbol.iterator]() {
|
|
37
|
+
return this.list[Symbol.iterator]();
|
|
38
|
+
}
|
|
39
|
+
}
|
package/dist/numbers.d.ts
CHANGED
|
@@ -6,14 +6,26 @@ export declare const absent: unique symbol;
|
|
|
6
6
|
* them, so numbering one never keeps it alive.
|
|
7
7
|
*/
|
|
8
8
|
export declare class Numbers<I> {
|
|
9
|
-
private readonly
|
|
9
|
+
private readonly owner;
|
|
10
10
|
private next;
|
|
11
|
-
|
|
11
|
+
/** Numbers released for reuse; null until one is, as most patches never release one. */
|
|
12
|
+
private free;
|
|
13
|
+
/** By number: the subject, a weak reference to it, or undefined for a number not in use. */
|
|
12
14
|
private readonly refs;
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
15
|
+
/**
|
|
16
|
+
* Made at the second object numbered, when the first is registered too: a motion patch per voice
|
|
17
|
+
* holds a `Numbers`, mostly for one subject, whose collection frees nothing worth a registry.
|
|
18
|
+
* Each object's weak reference is also its token, so a number reused after its subject is
|
|
19
|
+
* released never hears of the old one's collection.
|
|
20
|
+
*/
|
|
21
|
+
private registry;
|
|
22
|
+
/** The one object numbered before there was a registry, and its number. */
|
|
23
|
+
private lone;
|
|
24
|
+
private loneSlot;
|
|
25
|
+
/** Takes the owner rather than a callback, which would cost a closure and its context per patch. */
|
|
26
|
+
constructor(owner: {
|
|
27
|
+
forget(slot: number): void;
|
|
28
|
+
});
|
|
17
29
|
/** One past the highest number handed out, so a loop over 0..size meets every live one. */
|
|
18
30
|
get size(): number;
|
|
19
31
|
take(subject: I): number;
|
package/dist/numbers.js
CHANGED
|
@@ -1,47 +1,73 @@
|
|
|
1
1
|
/** What a number's subject is once it has none: released, or let go by the host. */
|
|
2
2
|
export const absent = Symbol('absent');
|
|
3
|
+
/** What `refs` holds for a subject that is itself `undefined`, the unit subject of some mixes. */
|
|
4
|
+
const unit = Symbol('unit');
|
|
3
5
|
/**
|
|
4
6
|
* Hands each subject a small number to index flat arrays by, and takes it back when the subject is
|
|
5
7
|
* released or, for an object, when the host lets it go. Objects are held weakly, as `Store` holds
|
|
6
8
|
* them, so numbering one never keeps it alive.
|
|
7
9
|
*/
|
|
8
10
|
export class Numbers {
|
|
9
|
-
|
|
11
|
+
owner;
|
|
10
12
|
next = 0;
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
13
|
+
/** Numbers released for reuse; null until one is, as most patches never release one. */
|
|
14
|
+
free = null;
|
|
15
|
+
/** By number: the subject, a weak reference to it, or undefined for a number not in use. */
|
|
16
|
+
// Room for one: an empty array written at 0 reserves 17, and most patches number one subject.
|
|
17
|
+
refs = [undefined];
|
|
18
|
+
/**
|
|
19
|
+
* Made at the second object numbered, when the first is registered too: a motion patch per voice
|
|
20
|
+
* holds a `Numbers`, mostly for one subject, whose collection frees nothing worth a registry.
|
|
21
|
+
* Each object's weak reference is also its token, so a number reused after its subject is
|
|
22
|
+
* released never hears of the old one's collection.
|
|
23
|
+
*/
|
|
24
|
+
registry = null;
|
|
25
|
+
/** The one object numbered before there was a registry, and its number. */
|
|
26
|
+
lone = null;
|
|
27
|
+
loneSlot = -1;
|
|
28
|
+
/** Takes the owner rather than a callback, which would cost a closure and its context per patch. */
|
|
29
|
+
constructor(owner) {
|
|
30
|
+
this.owner = owner;
|
|
18
31
|
}
|
|
19
32
|
/** One past the highest number handed out, so a loop over 0..size meets every live one. */
|
|
20
33
|
get size() {
|
|
21
34
|
return this.next;
|
|
22
35
|
}
|
|
23
36
|
take(subject) {
|
|
24
|
-
const slot = this.free
|
|
25
|
-
|
|
26
|
-
const grown = new Uint8Array(Math.max(64, this.live.length * 2, slot + 1));
|
|
27
|
-
grown.set(this.live);
|
|
28
|
-
this.live = grown;
|
|
29
|
-
}
|
|
30
|
-
this.live[slot] = 1;
|
|
37
|
+
const slot = this.free?.pop() ?? this.next++;
|
|
38
|
+
let dead = -1;
|
|
31
39
|
if (typeof subject === 'object' && subject !== null) {
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
this.
|
|
35
|
-
|
|
40
|
+
const ref = new WeakRef(subject);
|
|
41
|
+
this.refs[slot] = ref;
|
|
42
|
+
if (this.registry === null) {
|
|
43
|
+
if (this.lone === null) {
|
|
44
|
+
this.lone = ref;
|
|
45
|
+
this.loneSlot = slot;
|
|
46
|
+
return slot;
|
|
47
|
+
}
|
|
48
|
+
this.registry = new FinalizationRegistry((slot) => this.release(slot));
|
|
49
|
+
const first = this.lone.deref();
|
|
50
|
+
if (first !== undefined)
|
|
51
|
+
this.registry.register(first, this.loneSlot, this.lone);
|
|
52
|
+
// Collected with no registry to hear of it: released here, once this subject is numbered.
|
|
53
|
+
else
|
|
54
|
+
dead = this.loneSlot;
|
|
55
|
+
this.lone = null;
|
|
56
|
+
}
|
|
57
|
+
this.registry.register(subject, slot, ref);
|
|
36
58
|
}
|
|
37
59
|
else
|
|
38
|
-
this.refs[slot] = subject;
|
|
60
|
+
this.refs[slot] = subject === undefined ? unit : subject;
|
|
61
|
+
if (dead >= 0)
|
|
62
|
+
this.release(dead);
|
|
39
63
|
return slot;
|
|
40
64
|
}
|
|
41
65
|
subject(slot) {
|
|
42
|
-
if (this.live[slot] !== 1)
|
|
43
|
-
return absent;
|
|
44
66
|
const ref = this.refs[slot];
|
|
67
|
+
if (ref === undefined)
|
|
68
|
+
return absent;
|
|
69
|
+
if (ref === unit)
|
|
70
|
+
return undefined;
|
|
45
71
|
if (ref instanceof WeakRef) {
|
|
46
72
|
const s = ref.deref();
|
|
47
73
|
return s === undefined ? absent : s;
|
|
@@ -49,16 +75,18 @@ export class Numbers {
|
|
|
49
75
|
return ref;
|
|
50
76
|
}
|
|
51
77
|
release(slot) {
|
|
52
|
-
|
|
78
|
+
const ref = this.refs[slot];
|
|
79
|
+
if (ref === undefined)
|
|
53
80
|
return;
|
|
54
|
-
this.
|
|
55
|
-
|
|
56
|
-
if (
|
|
57
|
-
this.registry
|
|
58
|
-
this.tokens[slot] = undefined;
|
|
59
|
-
}
|
|
81
|
+
if (ref === this.lone)
|
|
82
|
+
this.lone = null;
|
|
83
|
+
else if (ref instanceof WeakRef)
|
|
84
|
+
this.registry?.unregister(ref);
|
|
60
85
|
this.refs[slot] = undefined;
|
|
61
|
-
this.
|
|
62
|
-
this.free
|
|
86
|
+
this.owner.forget(slot);
|
|
87
|
+
if (this.free === null)
|
|
88
|
+
this.free = [slot];
|
|
89
|
+
else
|
|
90
|
+
this.free.push(slot);
|
|
63
91
|
}
|
|
64
92
|
}
|
package/dist/owed.d.ts
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* By subject number, the laned voices whose first meeting with the subject its probes fold on the
|
|
3
|
+
* general path this fill, after the lanes' values. Subjects meeting the same voices share one list.
|
|
4
|
+
*/
|
|
5
|
+
export declare class Owed {
|
|
6
|
+
/** By subject number, the fill it owes in, and which of `lists` it owes. */
|
|
7
|
+
private fill;
|
|
8
|
+
private at;
|
|
9
|
+
private readonly lists;
|
|
10
|
+
/** The fill now under way. */
|
|
11
|
+
private now;
|
|
12
|
+
/** A fill begins: nobody owes anything. */
|
|
13
|
+
begin(fill: number): void;
|
|
14
|
+
/** Makes room for subject numbers below `cap`. */
|
|
15
|
+
grow(cap: number): void;
|
|
16
|
+
/** Whether the subject at `slot` owes some laned voice this fill. */
|
|
17
|
+
owes(slot: number): boolean;
|
|
18
|
+
/** Whether the subject at `slot` owes voice `id` this fill. */
|
|
19
|
+
owesVoice(slot: number, id: number): boolean;
|
|
20
|
+
/** The subject at `slot` owes the voices `met`, which the caller may reuse after. */
|
|
21
|
+
owe(slot: number, met: readonly number[]): void;
|
|
22
|
+
/** Whether `met` is the last list a subject came to owe this fill. */
|
|
23
|
+
last(met: readonly number[]): boolean;
|
|
24
|
+
}
|
package/dist/owed.js
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* By subject number, the laned voices whose first meeting with the subject its probes fold on the
|
|
3
|
+
* general path this fill, after the lanes' values. Subjects meeting the same voices share one list.
|
|
4
|
+
*/
|
|
5
|
+
export class Owed {
|
|
6
|
+
/** By subject number, the fill it owes in, and which of `lists` it owes. */
|
|
7
|
+
fill = new Int32Array(0);
|
|
8
|
+
at = new Int32Array(0);
|
|
9
|
+
lists = [];
|
|
10
|
+
/** The fill now under way. */
|
|
11
|
+
now = 0;
|
|
12
|
+
/** A fill begins: nobody owes anything. */
|
|
13
|
+
begin(fill) {
|
|
14
|
+
this.now = fill;
|
|
15
|
+
this.lists.length = 0;
|
|
16
|
+
}
|
|
17
|
+
/** Makes room for subject numbers below `cap`. */
|
|
18
|
+
grow(cap) {
|
|
19
|
+
const fill = new Int32Array(cap);
|
|
20
|
+
fill.set(this.fill);
|
|
21
|
+
this.fill = fill;
|
|
22
|
+
const at = new Int32Array(cap);
|
|
23
|
+
at.set(this.at);
|
|
24
|
+
this.at = at;
|
|
25
|
+
}
|
|
26
|
+
/** Whether the subject at `slot` owes some laned voice this fill. */
|
|
27
|
+
owes(slot) {
|
|
28
|
+
return this.lists.length > 0 && this.fill[slot] === this.now;
|
|
29
|
+
}
|
|
30
|
+
/** Whether the subject at `slot` owes voice `id` this fill. */
|
|
31
|
+
owesVoice(slot, id) {
|
|
32
|
+
return this.lists[this.at[slot]].includes(id);
|
|
33
|
+
}
|
|
34
|
+
/** The subject at `slot` owes the voices `met`, which the caller may reuse after. */
|
|
35
|
+
owe(slot, met) {
|
|
36
|
+
if (!this.last(met))
|
|
37
|
+
this.lists.push(met.slice());
|
|
38
|
+
this.fill[slot] = this.now;
|
|
39
|
+
this.at[slot] = this.lists.length - 1;
|
|
40
|
+
}
|
|
41
|
+
/** Whether `met` is the last list a subject came to owe this fill. */
|
|
42
|
+
last(met) {
|
|
43
|
+
const last = this.lists[this.lists.length - 1];
|
|
44
|
+
if (last === undefined || last.length !== met.length)
|
|
45
|
+
return false;
|
|
46
|
+
for (let i = 0; i < met.length; i++)
|
|
47
|
+
if (last[i] !== met[i])
|
|
48
|
+
return false;
|
|
49
|
+
return true;
|
|
50
|
+
}
|
|
51
|
+
}
|
package/dist/owner.d.ts
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import type { Patch } from './types.js';
|
|
2
|
+
import type { Voice } from './voice.js';
|
|
3
|
+
/** What an owner is cued with in place of a patch: it writes nothing, so no fold ever meets it. */
|
|
4
|
+
export declare const ownerPatch: Patch<unknown, unknown, unknown>;
|
|
5
|
+
/** What an owner keeps of the voices it holds. */
|
|
6
|
+
export declare class Holding<V> {
|
|
7
|
+
/** Its children still in the mix, in cue order. */
|
|
8
|
+
children: V[];
|
|
9
|
+
/** How many it has held, and how many of those finished their passes. */
|
|
10
|
+
kids: number;
|
|
11
|
+
played: number;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* What an owner's clock reads at mix time `now`, through every owner above it: -Infinity before it
|
|
15
|
+
* starts, where its children show nothing.
|
|
16
|
+
*/
|
|
17
|
+
export declare function ownerReading<I, O>(owner: Voice<I, O>, now: number): number;
|
|
18
|
+
/** What a voice an owner holds reads at mix time `now`: -Infinity before the owner starts. */
|
|
19
|
+
export declare function ownedElapsed<I, O>(voice: Voice<I, O>, owner: Voice<I, O>, now: number): number;
|
|
20
|
+
/** The mix time an owner's clock reads `t`, by its controls now; Infinity where it never will. */
|
|
21
|
+
export declare function mixTime<I, O>(owner: Voice<I, O>, t: number): number;
|
|
22
|
+
/** Takes a child into its owner. */
|
|
23
|
+
export declare function adopt<I, O>(owner: Voice<I, O>, child: Voice<I, O>): void;
|
|
24
|
+
/** A child left: its owner, where that was its last child and it should leave too. */
|
|
25
|
+
export declare function orphan<I, O>(child: Voice<I, O>): Voice<I, O> | null;
|
|
26
|
+
/** A child finished its passes: its owner has played once every child it held has. */
|
|
27
|
+
export declare function childPlayed<I, O>(owner: Voice<I, O>): void;
|
|
28
|
+
/** Every voice an owner holds, its children's children included. */
|
|
29
|
+
export declare function descendants<I, O>(owner: Voice<I, O>, visit: (v: Voice<I, O>) => void): void;
|
|
30
|
+
/** Whether any owner above a voice reads a signal for its weight, which lanes cannot fill. */
|
|
31
|
+
export declare function signalled<I, O>(voice: Voice<I, O>): boolean;
|
|
32
|
+
/** Whether any owner above a voice weighs it by input from outside the clock. */
|
|
33
|
+
export declare function heldByInput<I, O>(voice: Voice<I, O>): boolean;
|
|
34
|
+
/**
|
|
35
|
+
* What one owner multiplies into its children's weight for a subject at mix time `now`: its weight,
|
|
36
|
+
* or its signal's read by `base`, times its own fade, held to 0..1. Its fade in counts from when it
|
|
37
|
+
* started, or from when it first showed where it holds before.
|
|
38
|
+
*/
|
|
39
|
+
export declare function ownWeight<I, O>(owner: Voice<I, O>, now: number, reduced: boolean, base: (owner: Voice<I, O>) => number): number;
|
|
40
|
+
/** What every owner above a voice multiplies into its weight. */
|
|
41
|
+
export declare function ownersWeight<I, O>(voice: Voice<I, O>, now: number, reduced: boolean, base: (owner: Voice<I, O>) => number): number;
|
|
42
|
+
/** Points a projection's copies at each other's owners, each owner holding only copies. */
|
|
43
|
+
export declare function relink<I, O>(copies: readonly Voice<I, O>[]): void;
|
package/dist/owner.js
ADDED
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { clampWeight, elapsedWith, envelope, timeWith } from './clock.js';
|
|
2
|
+
/** What an owner is cued with in place of a patch: it writes nothing, so no fold ever meets it. */
|
|
3
|
+
export const ownerPatch = Object.freeze({
|
|
4
|
+
form: 'fn',
|
|
5
|
+
duration: 0,
|
|
6
|
+
writes: [],
|
|
7
|
+
at: () => ({}),
|
|
8
|
+
});
|
|
9
|
+
/** What an owner keeps of the voices it holds. */
|
|
10
|
+
export class Holding {
|
|
11
|
+
/** Its children still in the mix, in cue order. */
|
|
12
|
+
children = [];
|
|
13
|
+
/** How many it has held, and how many of those finished their passes. */
|
|
14
|
+
kids = 0;
|
|
15
|
+
played = 0;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* What an owner's clock reads at mix time `now`, through every owner above it: -Infinity before it
|
|
19
|
+
* starts, where its children show nothing.
|
|
20
|
+
*/
|
|
21
|
+
export function ownerReading(owner, now) {
|
|
22
|
+
const t = owner.owner === null ? now : ownerReading(owner.owner, now);
|
|
23
|
+
return t >= owner.start ? elapsedWith(owner, t) : Number.NEGATIVE_INFINITY;
|
|
24
|
+
}
|
|
25
|
+
/** What a voice an owner holds reads at mix time `now`: -Infinity before the owner starts. */
|
|
26
|
+
export function ownedElapsed(voice, owner, now) {
|
|
27
|
+
const t = ownerReading(owner, now);
|
|
28
|
+
return t === Number.NEGATIVE_INFINITY ? t : elapsedWith(voice, t);
|
|
29
|
+
}
|
|
30
|
+
/** The mix time an owner's clock reads `t`, by its controls now; Infinity where it never will. */
|
|
31
|
+
export function mixTime(owner, t) {
|
|
32
|
+
const up = timeWith(owner, t);
|
|
33
|
+
return owner.owner === null ? up : mixTime(owner.owner, up);
|
|
34
|
+
}
|
|
35
|
+
/** Takes a child into its owner. */
|
|
36
|
+
export function adopt(owner, child) {
|
|
37
|
+
const h = owner.holding;
|
|
38
|
+
h.children.push(child);
|
|
39
|
+
h.kids++;
|
|
40
|
+
}
|
|
41
|
+
/** A child left: its owner, where that was its last child and it should leave too. */
|
|
42
|
+
export function orphan(child) {
|
|
43
|
+
const owner = child.owner;
|
|
44
|
+
const h = owner.holding;
|
|
45
|
+
const i = h.children.indexOf(child);
|
|
46
|
+
if (i >= 0)
|
|
47
|
+
h.children.splice(i, 1);
|
|
48
|
+
return h.children.length === 0 && owner.state !== 'done' ? owner : null;
|
|
49
|
+
}
|
|
50
|
+
/** A child finished its passes: its owner has played once every child it held has. */
|
|
51
|
+
export function childPlayed(owner) {
|
|
52
|
+
const h = owner.holding;
|
|
53
|
+
if (++h.played === h.kids)
|
|
54
|
+
owner.play(true);
|
|
55
|
+
}
|
|
56
|
+
/** Every voice an owner holds, its children's children included. */
|
|
57
|
+
export function descendants(owner, visit) {
|
|
58
|
+
for (const child of owner.holding.children) {
|
|
59
|
+
visit(child);
|
|
60
|
+
if (child.holding !== null)
|
|
61
|
+
descendants(child, visit);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
/** Whether any owner above a voice reads a signal for its weight, which lanes cannot fill. */
|
|
65
|
+
export function signalled(voice) {
|
|
66
|
+
for (let o = voice.owner; o !== null; o = o.owner)
|
|
67
|
+
if (typeof o.spec.weight === 'function')
|
|
68
|
+
return true;
|
|
69
|
+
return false;
|
|
70
|
+
}
|
|
71
|
+
/** Whether any owner above a voice weighs it by input from outside the clock. */
|
|
72
|
+
export function heldByInput(voice) {
|
|
73
|
+
for (let o = voice.owner; o !== null; o = o.owner) {
|
|
74
|
+
const w = o.spec.weight;
|
|
75
|
+
if (typeof w === 'function' && w.input)
|
|
76
|
+
return true;
|
|
77
|
+
}
|
|
78
|
+
return false;
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* What one owner multiplies into its children's weight for a subject at mix time `now`: its weight,
|
|
82
|
+
* or its signal's read by `base`, times its own fade, held to 0..1. Its fade in counts from when it
|
|
83
|
+
* started, or from when it first showed where it holds before.
|
|
84
|
+
*/
|
|
85
|
+
export function ownWeight(owner, now, reduced, base) {
|
|
86
|
+
const b = typeof owner.spec.weight === 'function' ? base(owner) : owner.weight;
|
|
87
|
+
const start = owner.owner === null ? owner.start : mixTime(owner.owner, owner.start);
|
|
88
|
+
const since = owner.holdsBefore && owner.opened < start ? owner.opened : start;
|
|
89
|
+
return clampWeight(b * envelope(owner.fade.in ?? 0, owner.out, owner.ease, reduced, now, since));
|
|
90
|
+
}
|
|
91
|
+
/** What every owner above a voice multiplies into its weight. */
|
|
92
|
+
export function ownersWeight(voice, now, reduced, base) {
|
|
93
|
+
let w = 1;
|
|
94
|
+
for (let o = voice.owner; o !== null; o = o.owner)
|
|
95
|
+
w *= ownWeight(o, now, reduced, base);
|
|
96
|
+
return w;
|
|
97
|
+
}
|
|
98
|
+
/** Points a projection's copies at each other's owners, each owner holding only copies. */
|
|
99
|
+
export function relink(copies) {
|
|
100
|
+
const byId = new Map();
|
|
101
|
+
for (const v of copies) {
|
|
102
|
+
byId.set(v.id, v);
|
|
103
|
+
if (v.holding !== null) {
|
|
104
|
+
const h = new Holding();
|
|
105
|
+
h.kids = v.holding.kids;
|
|
106
|
+
h.played = v.holding.played;
|
|
107
|
+
v.holding = h;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
for (const v of copies) {
|
|
111
|
+
const o = v.owner === null ? undefined : byId.get(v.owner.id);
|
|
112
|
+
if (o === undefined)
|
|
113
|
+
continue;
|
|
114
|
+
v.owner = o;
|
|
115
|
+
o.holding.children.push(v);
|
|
116
|
+
}
|
|
117
|
+
}
|