@msb235/blits 0.4.0 → 0.6.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 +289 -0
- package/README.md +2 -1
- 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 +3 -1
- package/dist/index.js +1 -0
- 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 +121 -246
- package/dist/lanes.js +150 -1578
- 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 +248 -196
- package/dist/mixer.js +270 -2220
- package/dist/motion.d.ts +66 -21
- package/dist/motion.js +325 -165
- 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 +13 -9
- package/dist/signals.js +14 -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/ticker.d.ts +48 -0
- package/dist/ticker.js +112 -0
- package/dist/tweened.d.ts +11 -0
- package/dist/tweened.js +48 -0
- package/dist/types.d.ts +237 -43
- package/dist/unreached.d.ts +5 -0
- package/dist/unreached.js +24 -0
- package/dist/voice.d.ts +240 -0
- package/dist/voice.js +324 -0
- package/dist/weigh.d.ts +26 -0
- package/dist/weigh.js +89 -0
- package/docs/schema.html +137 -56
- package/package.json +5 -2
package/dist/cue.d.ts
ADDED
package/dist/cue.js
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { checkHits } from './book.js';
|
|
2
|
+
import { changed, index } from './chain.js';
|
|
3
|
+
import { schedule } from './due.js';
|
|
4
|
+
import { started } from './held.js';
|
|
5
|
+
import { handle } from './hosts.js';
|
|
6
|
+
import { motionOf } from './motion.js';
|
|
7
|
+
import { adopt, ownerReading } from './owner.js';
|
|
8
|
+
import { durationOf } from './patch.js';
|
|
9
|
+
import { checkPlacement, localNow, mixAt, pin, place } from './place.js';
|
|
10
|
+
import { motionOwner } from './strays.js';
|
|
11
|
+
import { Voice } from './voice.js';
|
|
12
|
+
export function cue(mix, spec) {
|
|
13
|
+
const patch = spec.patch;
|
|
14
|
+
for (const channel of patch.writes) {
|
|
15
|
+
if (!(channel in mix.kit))
|
|
16
|
+
throw new Error(`blits: kit has no channel ${String(channel)}, which this patch writes`);
|
|
17
|
+
const wanted = patch.kit?.[channel];
|
|
18
|
+
const here = mix.kit[channel];
|
|
19
|
+
if (wanted && wanted !== here && (wanted.kind === undefined || wanted.kind !== here.kind))
|
|
20
|
+
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'}`);
|
|
21
|
+
}
|
|
22
|
+
const motion = motionOf(patch);
|
|
23
|
+
if (motion !== undefined) {
|
|
24
|
+
const channel = patch.writes[0];
|
|
25
|
+
const rest = mix.kit[channel].rest;
|
|
26
|
+
if (typeof rest === 'number')
|
|
27
|
+
motion.cuedOn(String(channel), true);
|
|
28
|
+
else if (Array.isArray(rest) || ArrayBuffer.isView(rest))
|
|
29
|
+
motion.cuedOn(String(channel), false);
|
|
30
|
+
}
|
|
31
|
+
if (patch.reads) {
|
|
32
|
+
const host = mix.opts.host;
|
|
33
|
+
for (const field of patch.reads)
|
|
34
|
+
if (typeof host !== 'object' || host === null || !(field in host))
|
|
35
|
+
throw new Error(`blits: the patch reads host.${field}, which this mix's host lacks`);
|
|
36
|
+
}
|
|
37
|
+
if (spec.from === 'current' && patch.form !== 'keys')
|
|
38
|
+
throw new Error("blits: from: 'current' needs a keys patch");
|
|
39
|
+
if (spec.subjects !== undefined && spec.target !== undefined)
|
|
40
|
+
throw new Error('blits: a voice takes target or subjects, not both');
|
|
41
|
+
const anchor = spec.anchor;
|
|
42
|
+
const owner = spec.owner === undefined ? null : ownerOf(mix, spec.owner);
|
|
43
|
+
if (anchor)
|
|
44
|
+
checkPlacement(mix, spec, anchor, owner);
|
|
45
|
+
if (spec.hits !== undefined)
|
|
46
|
+
checkHits(spec.hits, durationOf(patch));
|
|
47
|
+
const anchored = anchor !== undefined && (anchor.start !== undefined || anchor.in !== undefined);
|
|
48
|
+
// A voice an owner holds is placed on the owner's clock: ms from when it starts.
|
|
49
|
+
const local = owner === null ? mix.now : ownerReading(owner, mix.now);
|
|
50
|
+
const start = anchored
|
|
51
|
+
? Number.POSITIVE_INFINITY
|
|
52
|
+
: owner !== null
|
|
53
|
+
? (spec.start ?? (local > 0 ? local : 0))
|
|
54
|
+
: spec.start !== undefined
|
|
55
|
+
? mixAt(mix, spec.start - mix.offset)
|
|
56
|
+
: Number.isNaN(mix.now)
|
|
57
|
+
? 0
|
|
58
|
+
: mix.now;
|
|
59
|
+
const voice = new Voice(mix.nextId++, spec, patch, spec.fade ?? {}, local, start, mix.slotOf, mix.channels, mix.opts.host, mix.send, owner);
|
|
60
|
+
if (owner !== null)
|
|
61
|
+
adopt(owner, voice);
|
|
62
|
+
if (voice.holding !== null) {
|
|
63
|
+
mix.owners ??= [];
|
|
64
|
+
mix.owners.push(voice);
|
|
65
|
+
}
|
|
66
|
+
if (!Number.isNaN(mix.now)) {
|
|
67
|
+
voice.cuedAt = mix.now;
|
|
68
|
+
voice.opened = mix.now;
|
|
69
|
+
}
|
|
70
|
+
voice.placing = anchored;
|
|
71
|
+
if (mix.pace !== null && owner === null && spec.start !== undefined && voice.state === 'pending')
|
|
72
|
+
pin(mix, voice, spec.start - mix.offset);
|
|
73
|
+
if (motion !== undefined) {
|
|
74
|
+
mix.playing.set(motion, (mix.playing.get(motion) ?? 0) + 1);
|
|
75
|
+
mix.owner ??= motionOwner(new WeakRef(mix));
|
|
76
|
+
motion.owner = mix.owner;
|
|
77
|
+
motion.ownerId = voice.id;
|
|
78
|
+
}
|
|
79
|
+
mix.cued.push(voice);
|
|
80
|
+
index(mix, voice);
|
|
81
|
+
changed(mix, voice);
|
|
82
|
+
mix.stir();
|
|
83
|
+
if (spec.locus !== undefined)
|
|
84
|
+
mix.loci++;
|
|
85
|
+
if (spec.from === 'current')
|
|
86
|
+
mix.wantsPose = true;
|
|
87
|
+
if (anchor) {
|
|
88
|
+
mix.anchored++;
|
|
89
|
+
place(mix);
|
|
90
|
+
if (!Number.isNaN(mix.now) &&
|
|
91
|
+
voice.state === 'pending' &&
|
|
92
|
+
localNow(mix, voice) >= voice.start) {
|
|
93
|
+
voice.state = 'live';
|
|
94
|
+
started(mix, voice);
|
|
95
|
+
changed(mix, voice);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
// After placing, so the controls it starts with are where its anchors put it at the cue.
|
|
99
|
+
if (mix.opts.history) {
|
|
100
|
+
voice.log = [];
|
|
101
|
+
voice.note(Number.NEGATIVE_INFINITY);
|
|
102
|
+
}
|
|
103
|
+
schedule(mix, voice);
|
|
104
|
+
voice.handle = handle(mix, voice);
|
|
105
|
+
return voice.handle;
|
|
106
|
+
}
|
|
107
|
+
/** The owner a handle names, refused where it is not one of this mix's or has left. */
|
|
108
|
+
function ownerOf(mix, handle) {
|
|
109
|
+
const found = mix.owners?.find((v) => v.handle === handle);
|
|
110
|
+
if (found === undefined)
|
|
111
|
+
throw new Error('blits: owner is not a handle mix.owns returned on this mix');
|
|
112
|
+
if (found.state === 'done')
|
|
113
|
+
throw new Error('blits: that owner has left the mix');
|
|
114
|
+
return found;
|
|
115
|
+
}
|
package/dist/due.d.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { Mixer } from './mixer.js';
|
|
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;
|
|
8
|
+
}
|
|
9
|
+
/** Files a voice in the due queue at its current due, replacing any entry it had. */
|
|
10
|
+
export declare function schedule<I, O>(mix: Mixer<I, O>, voice: Voice<I, O>): void;
|
|
11
|
+
/** Takes every current entry due by `now` off the queue, in cue order. */
|
|
12
|
+
export declare function popDue<I, O>(mix: Mixer<I, O>, now: number): Voice<I, O>[];
|
package/dist/due.js
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The earliest mix time `moveTo` would change a voice: its start, its anchored out, the end of
|
|
3
|
+
* its last pass, the end of its fade or of a subject's ramp out; -Infinity where it must be
|
|
4
|
+
* visited every frame, Infinity where nothing will happen until a handle or anchor changes it.
|
|
5
|
+
* Early is safe, since a visit that finds nothing due schedules again; late is not, so anything
|
|
6
|
+
* that can bring it earlier calls `schedule`.
|
|
7
|
+
*/
|
|
8
|
+
function dueOf(mix, voice) {
|
|
9
|
+
if (voice.state === 'done')
|
|
10
|
+
return Number.POSITIVE_INFINITY;
|
|
11
|
+
// An owner and the voices it holds run on clocks that move with it, so each is visited every frame.
|
|
12
|
+
if (Number.isNaN(voice.opened) || voice.owner !== null || voice.holding !== null)
|
|
13
|
+
return Number.NEGATIVE_INFINITY;
|
|
14
|
+
let due = Number.POSITIVE_INFINITY;
|
|
15
|
+
if (voice.parts !== null)
|
|
16
|
+
for (const r of voice.parts.values())
|
|
17
|
+
due = Math.min(due, r.at + r.over);
|
|
18
|
+
if (voice.state === 'pending')
|
|
19
|
+
return Math.min(due, Number.isNaN(voice.start) ? due : voice.start);
|
|
20
|
+
if (voice.state === 'fading') {
|
|
21
|
+
const out = voice.out;
|
|
22
|
+
if (out === null || out.rest)
|
|
23
|
+
return Number.NEGATIVE_INFINITY;
|
|
24
|
+
return Math.min(due, out.at + out.over);
|
|
25
|
+
}
|
|
26
|
+
due = Math.min(due, voice.outAt);
|
|
27
|
+
if (Number.isFinite(voice.span)) {
|
|
28
|
+
const end = voice.span + voice.latest;
|
|
29
|
+
// A frozen voice goes live again once its clock is back before its end: by a seek, by a
|
|
30
|
+
// subject staggered later than any before, or by running backwards, which is checked each
|
|
31
|
+
// frame while it can.
|
|
32
|
+
if (voice.state === 'frozen') {
|
|
33
|
+
if (voice.rate <= 0 || voice.ramp !== null)
|
|
34
|
+
return Number.NEGATIVE_INFINITY;
|
|
35
|
+
if (!Number.isNaN(mix.now) && voice.elapsedAt(mix.now) < end)
|
|
36
|
+
return Number.NEGATIVE_INFINITY;
|
|
37
|
+
}
|
|
38
|
+
if (voice.state === 'live')
|
|
39
|
+
due = Math.min(due, voice.timeAt(end));
|
|
40
|
+
}
|
|
41
|
+
return due;
|
|
42
|
+
}
|
|
43
|
+
/** Files a voice in the due queue at its current due, replacing any entry it had. */
|
|
44
|
+
export function schedule(mix, voice) {
|
|
45
|
+
if (mix.projecting)
|
|
46
|
+
return;
|
|
47
|
+
mix.lanes?.voiceChanged(voice.id);
|
|
48
|
+
const token = ++voice.dueToken;
|
|
49
|
+
const due = dueOf(mix, voice);
|
|
50
|
+
if (due === Number.POSITIVE_INFINITY)
|
|
51
|
+
return;
|
|
52
|
+
const heap = mix.due;
|
|
53
|
+
heap.push({ at: Number.isNaN(due) ? Number.NEGATIVE_INFINITY : due, voice, token });
|
|
54
|
+
let i = heap.length - 1;
|
|
55
|
+
while (i > 0) {
|
|
56
|
+
const up = (i - 1) >> 1;
|
|
57
|
+
if (heap[up].at <= heap[i].at)
|
|
58
|
+
break;
|
|
59
|
+
[heap[up], heap[i]] = [heap[i], heap[up]];
|
|
60
|
+
i = up;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
/** Takes every current entry due by `now` off the queue, in cue order. */
|
|
64
|
+
export function popDue(mix, now) {
|
|
65
|
+
const heap = mix.due;
|
|
66
|
+
const at = (k) => k < heap.length ? heap[k].at : Number.POSITIVE_INFINITY;
|
|
67
|
+
const out = [];
|
|
68
|
+
while (heap.length > 0 && heap[0].at <= now) {
|
|
69
|
+
const top = heap[0];
|
|
70
|
+
const last = heap.pop();
|
|
71
|
+
if (heap.length > 0) {
|
|
72
|
+
heap[0] = last;
|
|
73
|
+
let i = 0;
|
|
74
|
+
for (;;) {
|
|
75
|
+
const l = 2 * i + 1;
|
|
76
|
+
const r = l + 1;
|
|
77
|
+
const m0 = at(l) < at(i) ? l : i;
|
|
78
|
+
const m = at(r) < at(m0) ? r : m0;
|
|
79
|
+
if (m === i)
|
|
80
|
+
break;
|
|
81
|
+
[heap[m], heap[i]] = [heap[i], heap[m]];
|
|
82
|
+
i = m;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
if (top.token === top.voice.dueToken && top.voice.state !== 'done') {
|
|
86
|
+
top.voice.dueToken++;
|
|
87
|
+
out.push(top.voice);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
if (out.length > 1)
|
|
91
|
+
out.sort((a, b) => a.id - b.id);
|
|
92
|
+
return out;
|
|
93
|
+
}
|
package/dist/fade.d.ts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { Mixer } from './mixer.js';
|
|
2
|
+
import type { Voice } from './voice.js';
|
|
3
|
+
/** Starts one subject's ramp out of a voice, or takes it out at once for a ramp of 0. */
|
|
4
|
+
export declare function fadeSubject<I, O>(mix: Mixer<I, O>, voice: Voice<I, O>, subject: I, over?: number): void;
|
|
5
|
+
/** What a subject's own ramp out of a voice leaves of its weight this frame, 0..1. */
|
|
6
|
+
export declare function parting<I, O>(this: Mixer<I, O>, voice: Voice<I, O>, subject: I, now: number): number;
|
|
7
|
+
/**
|
|
8
|
+
* Takes a subject out of one voice for good, as of mix time `at`: the voice forgets its record,
|
|
9
|
+
* its lane position and a motion patch's state for it, and reaches it no more.
|
|
10
|
+
*/
|
|
11
|
+
export declare function part<I, O>(mix: Mixer<I, O>, voice: Voice<I, O>, subject: I, at: number): void;
|
|
12
|
+
/** Brings a subject faded out of a voice back, to be met afresh on its next probe. */
|
|
13
|
+
export declare function unpart<I, O>(mix: Mixer<I, O>, voice: Voice<I, O>, subject: I): void;
|
|
14
|
+
export declare function beginFade<I, O>(mix: Mixer<I, O>, voice: Voice<I, O>, opts: {
|
|
15
|
+
over?: number;
|
|
16
|
+
at?: 'rest';
|
|
17
|
+
deadline?: number;
|
|
18
|
+
}, at?: number): void;
|
|
19
|
+
export declare function retire<I, O>(mix: Mixer<I, O>, voice: Voice<I, O>, at?: number): void;
|
package/dist/fade.js
ADDED
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import { changed } from './chain.js';
|
|
2
|
+
import { envelope } from './clock.js';
|
|
3
|
+
import { schedule } from './due.js';
|
|
4
|
+
import { noted } from './history.js';
|
|
5
|
+
import { orphan } from './owner.js';
|
|
6
|
+
import { startOf } from './place.js';
|
|
7
|
+
import { unreach } from './unreached.js';
|
|
8
|
+
/** Starts one subject's ramp out of a voice, or takes it out at once for a ramp of 0. */
|
|
9
|
+
export function fadeSubject(mix, voice, subject, over) {
|
|
10
|
+
if (voice.state === 'done' || voice.parted?.has(subject) || voice.parts?.has(subject))
|
|
11
|
+
return;
|
|
12
|
+
mix.stir();
|
|
13
|
+
const ms = mix.reduced ? 0 : (over ?? voice.fade.out ?? 0);
|
|
14
|
+
const at = Number.isNaN(mix.now) ? startOf(voice) : mix.now;
|
|
15
|
+
if (ms === 0) {
|
|
16
|
+
part(mix, voice, subject, at);
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
voice.parts ??= new Map();
|
|
20
|
+
voice.parts.set(subject, { at, over: ms });
|
|
21
|
+
mix.parters.add(voice);
|
|
22
|
+
schedule(mix, voice);
|
|
23
|
+
mix.lanes?.refill();
|
|
24
|
+
}
|
|
25
|
+
/** What a subject's own ramp out of a voice leaves of its weight this frame, 0..1. */
|
|
26
|
+
export function parting(voice, subject, now) {
|
|
27
|
+
const r = voice.parts?.get(subject);
|
|
28
|
+
if (r === undefined)
|
|
29
|
+
return 1;
|
|
30
|
+
return envelope(0, { at: r.at, over: r.over, rest: false }, voice.ease, this.reducedNow, now, 0);
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Takes a subject out of one voice for good, as of mix time `at`: the voice forgets its record,
|
|
34
|
+
* its lane position and a motion patch's state for it, and reaches it no more.
|
|
35
|
+
*/
|
|
36
|
+
export function part(mix, voice, subject, at) {
|
|
37
|
+
voice.parts?.delete(subject);
|
|
38
|
+
if (voice.parts?.size === 0)
|
|
39
|
+
voice.parts = null;
|
|
40
|
+
voice.parted ??= new Map();
|
|
41
|
+
voice.parted.set(subject, at);
|
|
42
|
+
mix.parters.add(voice);
|
|
43
|
+
forgetIn(mix, voice, subject);
|
|
44
|
+
const motion = voice.motion;
|
|
45
|
+
if (motion !== undefined && asks(mix, motion, voice))
|
|
46
|
+
motion.release(subject);
|
|
47
|
+
}
|
|
48
|
+
/** Brings a subject faded out of a voice back, to be met afresh on its next probe. */
|
|
49
|
+
export function unpart(mix, voice, subject) {
|
|
50
|
+
if (voice.parted?.delete(subject) !== true)
|
|
51
|
+
return;
|
|
52
|
+
if (voice.parted.size === 0)
|
|
53
|
+
voice.parted = null;
|
|
54
|
+
forgetIn(mix, voice, subject);
|
|
55
|
+
const slot = mix.chains.get(subject)?.slot ?? -1;
|
|
56
|
+
if (slot >= 0)
|
|
57
|
+
mix.lanes?.rejoin(slot);
|
|
58
|
+
}
|
|
59
|
+
/** Drops a voice's record of a subject and relinks the subject's chain without it. */
|
|
60
|
+
function forgetIn(mix, voice, subject) {
|
|
61
|
+
const head = mix.chains.get(subject);
|
|
62
|
+
if (head !== undefined && head.slot >= 0)
|
|
63
|
+
unreach(voice, head.slot, false);
|
|
64
|
+
const held = voice.subjects.get(subject);
|
|
65
|
+
if (held !== undefined) {
|
|
66
|
+
voice.subjects.delete(subject);
|
|
67
|
+
if (held.reaches)
|
|
68
|
+
voice.seen--;
|
|
69
|
+
if (held.rested)
|
|
70
|
+
voice.restedCount--;
|
|
71
|
+
if (voice.holder === held)
|
|
72
|
+
voice.holder = null;
|
|
73
|
+
}
|
|
74
|
+
if (head === undefined)
|
|
75
|
+
return;
|
|
76
|
+
head.version = Number.NaN;
|
|
77
|
+
mix.relinks++;
|
|
78
|
+
if (voice.laned && head.slot >= 0)
|
|
79
|
+
mix.lanes?.part(voice.id, head.slot);
|
|
80
|
+
mix.lanes?.refill();
|
|
81
|
+
}
|
|
82
|
+
export function beginFade(mix, voice, opts, at) {
|
|
83
|
+
if (voice.state === 'done' || voice.state === 'fading')
|
|
84
|
+
return;
|
|
85
|
+
const over = mix.reduced ? 0 : (opts.over ?? voice.fade.out ?? 0);
|
|
86
|
+
// A voice is linked into its subjects' chains once it plays, and a fading one plays.
|
|
87
|
+
if (voice.state === 'pending')
|
|
88
|
+
changed(mix, voice);
|
|
89
|
+
voice.state = 'fading';
|
|
90
|
+
voice.out = {
|
|
91
|
+
from: 1,
|
|
92
|
+
at: at ?? (Number.isNaN(mix.now) ? startOf(voice) : mix.now),
|
|
93
|
+
over,
|
|
94
|
+
rest: opts.at === 'rest',
|
|
95
|
+
deadline: opts.deadline,
|
|
96
|
+
};
|
|
97
|
+
noted(mix, voice);
|
|
98
|
+
if (opts.at === 'rest')
|
|
99
|
+
mix.lanes?.invalidate();
|
|
100
|
+
else
|
|
101
|
+
mix.lanes?.refill();
|
|
102
|
+
if (over === 0 && opts.at !== 'rest')
|
|
103
|
+
retire(mix, voice, voice.out.at);
|
|
104
|
+
}
|
|
105
|
+
/** Removes a voice, recording that it left at `at`, default now. */
|
|
106
|
+
/** Whether a motion patch still asks this voice of this mix for its time. */
|
|
107
|
+
function asks(mix, motion, voice) {
|
|
108
|
+
return motion.owner === mix.owner && motion.ownerId === voice.id;
|
|
109
|
+
}
|
|
110
|
+
export function retire(mix, voice, at) {
|
|
111
|
+
if (voice.holding === null)
|
|
112
|
+
mix.lanes?.touch(voice);
|
|
113
|
+
const motion = voice.motion;
|
|
114
|
+
if (motion !== undefined && asks(mix, motion, voice))
|
|
115
|
+
motion.owner = null;
|
|
116
|
+
voice.state = 'done';
|
|
117
|
+
// The record its setting last wrote to, which a retired voice no longer calls for.
|
|
118
|
+
voice.keepOn = null;
|
|
119
|
+
mix.retired.push(voice);
|
|
120
|
+
voice.doneAt = at ?? (Number.isNaN(mix.now) ? Number.NEGATIVE_INFINITY : mix.now);
|
|
121
|
+
voice.play(false);
|
|
122
|
+
voice.resolve();
|
|
123
|
+
// An owner takes what it holds with it, and leaves with its last child.
|
|
124
|
+
if (voice.holding !== null)
|
|
125
|
+
for (const child of [...voice.holding.children])
|
|
126
|
+
if (child.state !== 'done')
|
|
127
|
+
retire(mix, child, voice.doneAt);
|
|
128
|
+
if (voice.owner !== null) {
|
|
129
|
+
const owner = orphan(voice);
|
|
130
|
+
if (owner !== null)
|
|
131
|
+
retire(mix, owner, voice.doneAt);
|
|
132
|
+
}
|
|
133
|
+
}
|
package/dist/fill.d.ts
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { type Lane, type Laned, type Paced } from './lane.js';
|
|
2
|
+
import type { Lanes } from './lanes.js';
|
|
3
|
+
import { absent } from './numbers.js';
|
|
4
|
+
import { type Scratch } from './patch.js';
|
|
5
|
+
import type { Subject, Voice } from './voice.js';
|
|
6
|
+
export declare function fillAll<I, O>(lanes: Lanes<I, O>, now: number, version: number): void;
|
|
7
|
+
/** Leaves a lane's subjects to the general path until it wakes, keeping what `weightOf` reports. */
|
|
8
|
+
export declare function rest<I, O>(lanes: Lanes<I, O>, lane: Paced): void;
|
|
9
|
+
export declare function wake<I, O>(lanes: Lanes<I, O>, lane: Paced): void;
|
|
10
|
+
/**
|
|
11
|
+
* A voice with a signal weight: its weight for the subject at `slot` under the voice's fade `fade`,
|
|
12
|
+
* through the general path's own arithmetic; NaN where that path has not made the voice's first
|
|
13
|
+
* call for the subject, which it then makes this frame, so a signal that keeps state is first
|
|
14
|
+
* called where the general path would call it.
|
|
15
|
+
*/
|
|
16
|
+
export declare function signalled<I, O>(this: Lanes<I, O>, voice: Voice<I, O>, slot: number, rec: Subject<unknown>, elapsed: number, pass: number, fade: number): number;
|
|
17
|
+
/** What a subject's own ramp out of a voice leaves of its weight: 1 with none. */
|
|
18
|
+
export declare function parting<I, O>(this: Lanes<I, O>, voice: Voice<I, O>, slot: number): number;
|
|
19
|
+
export declare function subjectAt<I, O>(this: Lanes<I, O>, slot: number): I | typeof absent;
|
|
20
|
+
/** What a lane remembers within one fill, cleared before the fill runs it. */
|
|
21
|
+
export declare function reset<I, O>(this: Lanes<I, O>, lane: Lane<I, O>): void;
|
|
22
|
+
/** One voice's contribution to every subject it plays on. */
|
|
23
|
+
export declare function run<I, O>(this: Lanes<I, O>, lane: Lane<I, O>): void;
|
|
24
|
+
/**
|
|
25
|
+
* One subject's contribution from a lane's voice. True where the voice gave the subject a delta,
|
|
26
|
+
* as the general path's `influence` does where it returns one, which is what makes a voice in a
|
|
27
|
+
* locus one of its members for the subject.
|
|
28
|
+
*/
|
|
29
|
+
export declare function one<I, O>(this: Lanes<I, O>, lane: Lane<I, O>, p: number, slot: number, elapsedNow: number, duration: number, passes: number): boolean;
|
|
30
|
+
/**
|
|
31
|
+
* A keys voice's stops at `phase` folded straight into a subject's values, without a delta: what
|
|
32
|
+
* `readKeyed` then `foldDelta` give, through the same segment search and the stock channels' own
|
|
33
|
+
* lerp, which is all a laned keys voice can use.
|
|
34
|
+
*/
|
|
35
|
+
export declare function foldKeys<I, O>(this: Lanes<I, O>, voice: Voice<I, O>, chans: readonly Laned[], phase: number, slot: number, w: number): void;
|
|
36
|
+
/** Reads a keys voice's stops at `phase` into `delta`, interpolating into the reader's `scratch`. */
|
|
37
|
+
export declare function readKeyed<I, O>(this: Lanes<I, O>, voice: Voice<I, O>, phase: number, delta: Record<string, unknown>, scratch: Scratch): void;
|
|
38
|
+
/**
|
|
39
|
+
* Calls a stateless fn voice's patch for a subject its general path has met, and folds the delta;
|
|
40
|
+
* false for a subject the host has let go of.
|
|
41
|
+
*/
|
|
42
|
+
export declare function call<I, O>(this: Lanes<I, O>, voice: Voice<I, O>, chans: readonly Laned[], rec: Subject<unknown>, slot: number, elapsed: number, phase: number, pass: number, delay: number, w: number): boolean;
|