@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/handle.d.ts
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { FadeOptions, Handle } from './types.js';
|
|
2
|
+
import type { Voice } from './voice.js';
|
|
3
|
+
/** What a voice's handle asks of the mix that cued it: one per mix, shared by every handle. */
|
|
4
|
+
export interface HandleHost<I, O> {
|
|
5
|
+
/** The mix clock, or the voice's start before the first sync. */
|
|
6
|
+
nowFor(voice: Voice<I, O>): number;
|
|
7
|
+
/** A handle write changed the voice: reschedule it, and refill the lanes. */
|
|
8
|
+
changed(voice: Voice<I, O>): void;
|
|
9
|
+
/** The voice's clock jumped: for an owner, so did every clock it holds. */
|
|
10
|
+
sought(voice: Voice<I, O>): void;
|
|
11
|
+
fade(voice: Voice<I, O>, opts: FadeOptions<I> | undefined): void;
|
|
12
|
+
weightOf(voice: Voice<I, O>, subject: I): number;
|
|
13
|
+
}
|
|
14
|
+
/** A voice's handle: the host's only way to control a voice once cued. */
|
|
15
|
+
export declare class VoiceHandle<I, O> implements Handle<I> {
|
|
16
|
+
#private;
|
|
17
|
+
readonly id: number;
|
|
18
|
+
constructor(voice: Voice<I, O>, host: HandleHost<I, O>);
|
|
19
|
+
get state(): Handle<I>['state'];
|
|
20
|
+
get owner(): Handle<I> | undefined;
|
|
21
|
+
get played(): Promise<boolean>;
|
|
22
|
+
get done(): Promise<void>;
|
|
23
|
+
get weight(): number;
|
|
24
|
+
set weight(w: number);
|
|
25
|
+
get rate(): number;
|
|
26
|
+
set rate(r: number);
|
|
27
|
+
ramp(r: number, over: number): void;
|
|
28
|
+
seek(elapsed: number): void;
|
|
29
|
+
fade(opts?: FadeOptions<I>): void;
|
|
30
|
+
weightOf(subject: I): number;
|
|
31
|
+
}
|
package/dist/handle.js
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { retime } from './clock.js';
|
|
2
|
+
/** A voice's handle: the host's only way to control a voice once cued. */
|
|
3
|
+
export class VoiceHandle {
|
|
4
|
+
id;
|
|
5
|
+
#voice;
|
|
6
|
+
#host;
|
|
7
|
+
constructor(voice, host) {
|
|
8
|
+
this.id = voice.id;
|
|
9
|
+
this.#voice = voice;
|
|
10
|
+
this.#host = host;
|
|
11
|
+
}
|
|
12
|
+
get state() {
|
|
13
|
+
return this.#voice.state;
|
|
14
|
+
}
|
|
15
|
+
get owner() {
|
|
16
|
+
return this.#voice.owner?.handle ?? undefined;
|
|
17
|
+
}
|
|
18
|
+
get played() {
|
|
19
|
+
return this.#voice.played;
|
|
20
|
+
}
|
|
21
|
+
get done() {
|
|
22
|
+
return this.#voice.done;
|
|
23
|
+
}
|
|
24
|
+
get weight() {
|
|
25
|
+
return this.#voice.weight;
|
|
26
|
+
}
|
|
27
|
+
set weight(w) {
|
|
28
|
+
this.#voice.weight = w;
|
|
29
|
+
this.#host.changed(this.#voice);
|
|
30
|
+
}
|
|
31
|
+
get rate() {
|
|
32
|
+
return this.#voice.rateAt(this.#host.nowFor(this.#voice));
|
|
33
|
+
}
|
|
34
|
+
set rate(r) {
|
|
35
|
+
this.ramp(r, 0);
|
|
36
|
+
}
|
|
37
|
+
ramp(r, over) {
|
|
38
|
+
const voice = this.#voice;
|
|
39
|
+
retime(voice, this.#host.nowFor(voice), r, over);
|
|
40
|
+
this.#host.changed(voice);
|
|
41
|
+
}
|
|
42
|
+
seek(elapsed) {
|
|
43
|
+
const voice = this.#voice;
|
|
44
|
+
voice.rebase(this.#host.nowFor(voice));
|
|
45
|
+
voice.anchorElapsed = elapsed;
|
|
46
|
+
voice.seeks++;
|
|
47
|
+
this.#host.sought(voice);
|
|
48
|
+
this.#host.changed(voice);
|
|
49
|
+
}
|
|
50
|
+
fade(opts) {
|
|
51
|
+
this.#host.fade(this.#voice, opts);
|
|
52
|
+
}
|
|
53
|
+
weightOf(subject) {
|
|
54
|
+
return this.#host.weightOf(this.#voice, subject);
|
|
55
|
+
}
|
|
56
|
+
}
|
package/dist/held.d.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { Mixer } from './mixer.js';
|
|
2
|
+
import type { Subject, Voice } from './voice.js';
|
|
3
|
+
/** The record a voice gives every subject it does not reach. */
|
|
4
|
+
export declare function unreachedOf<I, O>(this: Mixer<I, O>, voice: Voice<I, O>, now: number): Subject<unknown>;
|
|
5
|
+
/** What this voice holds for this subject, made on first sight with `target` and `stagger` asked once. */
|
|
6
|
+
export declare function held<I, O>(this: Mixer<I, O>, voice: Voice<I, O>, subject: I, now: number, slot: number): Subject<unknown>;
|
|
7
|
+
/**
|
|
8
|
+
* When the voice clock reads `delay`, from where it is anchored now; during a ramp this assumes
|
|
9
|
+
* the rate it is ramping to.
|
|
10
|
+
*/
|
|
11
|
+
export declare function sinceOf<I, O>(this: Mixer<I, O>, voice: Voice<I, O>, delay: number): number;
|
|
12
|
+
export declare function shownOf<I, O>(this: Mixer<I, O>, voice: Voice<I, O>, since: number): number;
|
|
13
|
+
/** A voice has gone live: the records it made while pending count from where it started. */
|
|
14
|
+
export declare function started<I, O>(mix: Mixer<I, O>, voice: Voice<I, O>): void;
|
package/dist/held.js
ADDED
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { schedule } from './due.js';
|
|
2
|
+
import { mixTime } from './owner.js';
|
|
3
|
+
import { unreach, unreached } from './unreached.js';
|
|
4
|
+
/** The record a voice gives every subject it does not reach. */
|
|
5
|
+
export function unreachedOf(voice, now) {
|
|
6
|
+
const since = this.sinceOf(voice, 0);
|
|
7
|
+
const none = {
|
|
8
|
+
reaches: false,
|
|
9
|
+
delay: 0,
|
|
10
|
+
since,
|
|
11
|
+
shown: this.shownOf(voice, since),
|
|
12
|
+
weight: 0,
|
|
13
|
+
rested: false,
|
|
14
|
+
bands: null,
|
|
15
|
+
state: undefined,
|
|
16
|
+
stepped: now,
|
|
17
|
+
ticks: 0,
|
|
18
|
+
probed: Number.NaN,
|
|
19
|
+
delta: null,
|
|
20
|
+
phase: 0,
|
|
21
|
+
seeks: 0,
|
|
22
|
+
kept: null,
|
|
23
|
+
voice,
|
|
24
|
+
next: null,
|
|
25
|
+
version: Number.NaN,
|
|
26
|
+
loci: null,
|
|
27
|
+
slot: -1,
|
|
28
|
+
};
|
|
29
|
+
voice.unreached = none;
|
|
30
|
+
return none;
|
|
31
|
+
}
|
|
32
|
+
/** What this voice holds for this subject, made on first sight with `target` and `stagger` asked once. */
|
|
33
|
+
export function held(voice, subject, now, slot) {
|
|
34
|
+
if (unreached(voice, slot))
|
|
35
|
+
return voice.unreached;
|
|
36
|
+
let held = voice.subjects.get(subject);
|
|
37
|
+
if (held !== undefined)
|
|
38
|
+
return held;
|
|
39
|
+
const reaches = this.aims(voice, subject);
|
|
40
|
+
if (!reaches) {
|
|
41
|
+
// Nothing is kept for a subject the voice does not reach, so one record stands for them all:
|
|
42
|
+
// a voice per subject reached by `target` held one per subject it was asked about. Where lanes
|
|
43
|
+
// number the subject, a bit by its number says so, where a map entry each grew with voices
|
|
44
|
+
// times subjects.
|
|
45
|
+
const none = voice.unreached ?? this.unreachedOf(voice, now);
|
|
46
|
+
if (slot >= 0 && voice.named === null)
|
|
47
|
+
unreach(voice, slot, true);
|
|
48
|
+
else
|
|
49
|
+
voice.subjects.set(subject, none);
|
|
50
|
+
return none;
|
|
51
|
+
}
|
|
52
|
+
const delay = voice.spec.stagger ? voice.spec.stagger(subject) : 0;
|
|
53
|
+
const since = this.sinceOf(voice, delay);
|
|
54
|
+
held = {
|
|
55
|
+
reaches: true,
|
|
56
|
+
delay,
|
|
57
|
+
since,
|
|
58
|
+
shown: this.shownOf(voice, since),
|
|
59
|
+
weight: 0,
|
|
60
|
+
rested: false,
|
|
61
|
+
bands: null,
|
|
62
|
+
state: voice.patch.state ? voice.patch.state(subject) : undefined,
|
|
63
|
+
stepped: this.backward && since < now ? since : now,
|
|
64
|
+
ticks: 0,
|
|
65
|
+
probed: Number.NaN,
|
|
66
|
+
delta: null,
|
|
67
|
+
phase: 0,
|
|
68
|
+
seeks: 0,
|
|
69
|
+
kept: null,
|
|
70
|
+
voice,
|
|
71
|
+
next: null,
|
|
72
|
+
version: Number.NaN,
|
|
73
|
+
loci: null,
|
|
74
|
+
slot: -1,
|
|
75
|
+
};
|
|
76
|
+
if (this.projecting) {
|
|
77
|
+
held.from = held.stepped;
|
|
78
|
+
held.unknown = this.backward && voice.spec.from === 'current';
|
|
79
|
+
}
|
|
80
|
+
voice.subjects.set(subject, held);
|
|
81
|
+
if (voice.state === 'pending') {
|
|
82
|
+
voice.early ??= [];
|
|
83
|
+
voice.early.push(held);
|
|
84
|
+
}
|
|
85
|
+
voice.seen++;
|
|
86
|
+
if (delay > voice.latest) {
|
|
87
|
+
voice.latest = delay;
|
|
88
|
+
// A later end can take a frozen voice live again.
|
|
89
|
+
if (voice.state === 'frozen')
|
|
90
|
+
schedule(this, voice);
|
|
91
|
+
}
|
|
92
|
+
return held;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* When the voice clock reads `delay`, from where it is anchored now; during a ramp this assumes
|
|
96
|
+
* the rate it is ramping to.
|
|
97
|
+
*/
|
|
98
|
+
export function sinceOf(voice, delay) {
|
|
99
|
+
const t = voice.rate > 0
|
|
100
|
+
? voice.anchorNow + (delay - voice.anchorElapsed) / voice.rate
|
|
101
|
+
: voice.start + delay;
|
|
102
|
+
return voice.owner === null ? t : mixTime(voice.owner, t);
|
|
103
|
+
}
|
|
104
|
+
export function shownOf(voice, since) {
|
|
105
|
+
return voice.freezesBefore && voice.opened < since ? voice.opened : since;
|
|
106
|
+
}
|
|
107
|
+
/** A voice has gone live: the records it made while pending count from where it started. */
|
|
108
|
+
export function started(mix, voice) {
|
|
109
|
+
const early = voice.early;
|
|
110
|
+
if (early === null)
|
|
111
|
+
return;
|
|
112
|
+
voice.early = null;
|
|
113
|
+
for (const held of early) {
|
|
114
|
+
held.since = mix.sinceOf(voice, held.delay);
|
|
115
|
+
held.shown = mix.shownOf(voice, held.since);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { Mixer } from './mixer.js';
|
|
2
|
+
import { type Subject, type Voice } from './voice.js';
|
|
3
|
+
/**
|
|
4
|
+
* The last entry taken before `t`, or at it where `inclusive` says so, as it does for a change a
|
|
5
|
+
* sync made. A host's change between frames is taken strictly: it shows from the next frame on, as
|
|
6
|
+
* it did live.
|
|
7
|
+
*/
|
|
8
|
+
export declare function last<T extends {
|
|
9
|
+
at: number;
|
|
10
|
+
}>(list: readonly T[], t: number, inclusive: boolean | ((e: T) => boolean)): T | undefined;
|
|
11
|
+
/** A voice's elapsed at an earlier mix time, by the controls it had then. */
|
|
12
|
+
export declare function elapsedThen<I, O>(mix: Mixer<I, O>, voice: Voice<I, O>, t: number): number;
|
|
13
|
+
/** Records a change to a voice's controls under `history`, and lets go of what it no longer reaches. */
|
|
14
|
+
export declare function noted<I, O>(mix: Mixer<I, O>, voice: Voice<I, O>): void;
|
|
15
|
+
/** A copy of a subject's record that shares nothing a read could change. */
|
|
16
|
+
export declare function copyHeld<I, O>(voice: Voice<I, O>, h: Subject<unknown>): Subject<unknown>;
|
|
17
|
+
/** Under `history` with `inputs`, copies the host fields any voice reads, once a frame, when they changed. */
|
|
18
|
+
export declare function recordHost<I, O>(mix: Mixer<I, O>, now: number): void;
|
|
19
|
+
/** Under `history` with `inputs`, keeps what an input signal read, each time it changes. */
|
|
20
|
+
export declare function record<I, O>(mix: Mixer<I, O>, held: Subject<unknown>, value: number): void;
|
|
21
|
+
/** Under `history`, keeps a copy of a stateful voice's record for this subject every so often. */
|
|
22
|
+
export declare function remember<I, O>(mix: Mixer<I, O>, voice: Voice<I, O>, held: Subject<unknown>): void;
|
package/dist/history.js
ADDED
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import { elapsedWith } from './clock.js';
|
|
2
|
+
import { clone } from './clone.js';
|
|
3
|
+
import { schedule } from './due.js';
|
|
4
|
+
import { none } from './voice.js';
|
|
5
|
+
/**
|
|
6
|
+
* The last entry taken before `t`, or at it where `inclusive` says so, as it does for a change a
|
|
7
|
+
* sync made. A host's change between frames is taken strictly: it shows from the next frame on, as
|
|
8
|
+
* it did live.
|
|
9
|
+
*/
|
|
10
|
+
export function last(list, t, inclusive) {
|
|
11
|
+
let lo = 0;
|
|
12
|
+
let hi = list.length - 1;
|
|
13
|
+
let found;
|
|
14
|
+
while (lo <= hi) {
|
|
15
|
+
const mid = (lo + hi) >> 1;
|
|
16
|
+
const e = list[mid];
|
|
17
|
+
if (e.at < t || (e.at === t && (typeof inclusive === 'function' ? inclusive(e) : inclusive))) {
|
|
18
|
+
found = e;
|
|
19
|
+
lo = mid + 1;
|
|
20
|
+
}
|
|
21
|
+
else
|
|
22
|
+
hi = mid - 1;
|
|
23
|
+
}
|
|
24
|
+
return found;
|
|
25
|
+
}
|
|
26
|
+
/** Deep equality over plain data, for telling whether a host field changed. */
|
|
27
|
+
function same(a, b) {
|
|
28
|
+
if (a === b)
|
|
29
|
+
return true;
|
|
30
|
+
if (typeof a !== 'object' || typeof b !== 'object' || a === null || b === null)
|
|
31
|
+
return false;
|
|
32
|
+
const ka = Object.keys(a);
|
|
33
|
+
const kb = Object.keys(b);
|
|
34
|
+
if (ka.length !== kb.length)
|
|
35
|
+
return false;
|
|
36
|
+
for (const k of ka)
|
|
37
|
+
if (!same(a[k], b[k]))
|
|
38
|
+
return false;
|
|
39
|
+
return true;
|
|
40
|
+
}
|
|
41
|
+
/** A voice's elapsed at an earlier mix time, by the controls it had then. */
|
|
42
|
+
export function elapsedThen(mix, voice, t) {
|
|
43
|
+
const log = voice.log;
|
|
44
|
+
const c = log === null ? undefined : last(log, t, true);
|
|
45
|
+
return elapsedWith(c ?? voice, voice.owner === null ? t : ownerThen(mix, voice.owner, t));
|
|
46
|
+
}
|
|
47
|
+
/** What an owner's clock read at an earlier mix time; apart, so `elapsedThen` stays small. */
|
|
48
|
+
function ownerThen(mix, owner, t) {
|
|
49
|
+
return elapsedThen(mix, owner, t);
|
|
50
|
+
}
|
|
51
|
+
/** Records a change to a voice's controls under `history`, and lets go of what it no longer reaches. */
|
|
52
|
+
export function noted(mix, voice) {
|
|
53
|
+
mix.stir();
|
|
54
|
+
schedule(mix, voice);
|
|
55
|
+
const log = voice.log;
|
|
56
|
+
if (log === null)
|
|
57
|
+
return;
|
|
58
|
+
voice.note(Number.isNaN(mix.now) ? Number.NEGATIVE_INFINITY : mix.now, mix.moving);
|
|
59
|
+
const reach = mix.now - mix.opts.history.ms;
|
|
60
|
+
let drop = 0;
|
|
61
|
+
while (drop + 1 < log.length && log[drop + 1].at <= reach)
|
|
62
|
+
drop++;
|
|
63
|
+
if (drop > 0)
|
|
64
|
+
log.splice(0, drop);
|
|
65
|
+
}
|
|
66
|
+
/** A copy of a subject's record that shares nothing a read could change. */
|
|
67
|
+
export function copyHeld(voice, h) {
|
|
68
|
+
let kept = null;
|
|
69
|
+
if (h.kept !== null) {
|
|
70
|
+
kept = new Map();
|
|
71
|
+
for (const [owner, value] of h.kept)
|
|
72
|
+
kept.set(owner, clone(value));
|
|
73
|
+
}
|
|
74
|
+
const patch = voice.patch;
|
|
75
|
+
return {
|
|
76
|
+
...h,
|
|
77
|
+
bands: h.bands === null ? null : h.bands.slice(),
|
|
78
|
+
state: h.state === undefined ? undefined : patch.clone ? patch.clone(h.state) : clone(h.state),
|
|
79
|
+
kept,
|
|
80
|
+
probed: Number.NaN,
|
|
81
|
+
delta: null,
|
|
82
|
+
phase: 0,
|
|
83
|
+
seeks: 0,
|
|
84
|
+
base: h.base === undefined ? undefined : clone(h.base),
|
|
85
|
+
slope: h.slope === undefined ? undefined : clone(h.slope),
|
|
86
|
+
snaps: undefined,
|
|
87
|
+
inputs: undefined,
|
|
88
|
+
next: null,
|
|
89
|
+
version: Number.NaN,
|
|
90
|
+
loci: null,
|
|
91
|
+
slot: -1,
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
/** Under `history` with `inputs`, copies the host fields any voice reads, once a frame, when they changed. */
|
|
95
|
+
export function recordHost(mix, now) {
|
|
96
|
+
const log = mix.hostLog;
|
|
97
|
+
const prev = log[log.length - 1];
|
|
98
|
+
if (prev !== undefined && prev.at === now)
|
|
99
|
+
return;
|
|
100
|
+
const host = mix.opts.host;
|
|
101
|
+
const fields = {};
|
|
102
|
+
for (const v of mix.cued)
|
|
103
|
+
for (const f of v.patch.reads ?? none)
|
|
104
|
+
if (!(f in fields))
|
|
105
|
+
fields[f] = clone(host[f]);
|
|
106
|
+
if (prev !== undefined && same(prev.fields, fields))
|
|
107
|
+
return;
|
|
108
|
+
log.push({ at: now, fields });
|
|
109
|
+
const reach = now - mix.opts.history.ms;
|
|
110
|
+
while (log.length > 1 && log[1].at <= reach)
|
|
111
|
+
log.shift();
|
|
112
|
+
}
|
|
113
|
+
/** Under `history` with `inputs`, keeps what an input signal read, each time it changes. */
|
|
114
|
+
export function record(mix, held, value) {
|
|
115
|
+
const history = mix.opts.history;
|
|
116
|
+
if (!history?.inputs || mix.projecting)
|
|
117
|
+
return;
|
|
118
|
+
const inputs = held.inputs ?? [];
|
|
119
|
+
held.inputs = inputs;
|
|
120
|
+
const prev = inputs[inputs.length - 1];
|
|
121
|
+
if (prev !== undefined && (prev.value === value || prev.at === mix.now)) {
|
|
122
|
+
if (prev.at === mix.now)
|
|
123
|
+
prev.value = value;
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
inputs.push({ at: mix.now, value });
|
|
127
|
+
const reach = mix.now - history.ms;
|
|
128
|
+
while (inputs.length > 1 && inputs[1].at <= reach)
|
|
129
|
+
inputs.shift();
|
|
130
|
+
}
|
|
131
|
+
/** Under `history`, keeps a copy of a stateful voice's record for this subject every so often. */
|
|
132
|
+
export function remember(mix, voice, held) {
|
|
133
|
+
const history = mix.opts.history;
|
|
134
|
+
if (!history || mix.projecting)
|
|
135
|
+
return;
|
|
136
|
+
if (voice.patch.step === undefined &&
|
|
137
|
+
(held.kept === null || held.kept.size === 0) &&
|
|
138
|
+
held.base === undefined)
|
|
139
|
+
return;
|
|
140
|
+
const snaps = held.snaps ?? [];
|
|
141
|
+
held.snaps = snaps;
|
|
142
|
+
const prev = snaps[snaps.length - 1];
|
|
143
|
+
if (prev !== undefined && mix.now - prev.at < (history.every ?? 200))
|
|
144
|
+
return;
|
|
145
|
+
snaps.push({ at: mix.now, held: copyHeld(voice, held) });
|
|
146
|
+
const reach = mix.now - history.ms;
|
|
147
|
+
while (snaps.length > 1 && snaps[1].at <= reach)
|
|
148
|
+
snaps.shift();
|
|
149
|
+
}
|
package/dist/hosts.d.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { LaneHost } from './lanes.js';
|
|
2
|
+
import type { Mixer } from './mixer.js';
|
|
3
|
+
import type { Booker, BookOptions, Handle } from './types.js';
|
|
4
|
+
import type { Voice } from './voice.js';
|
|
5
|
+
export declare function book<I, O>(mix: Mixer<I, O>, opts: BookOptions): Booker;
|
|
6
|
+
export declare function laneHost<I, O>(mix: Mixer<I, O>): LaneHost<I, O>;
|
|
7
|
+
/**
|
|
8
|
+
* Whether a voice's patch and spec can run on a lane, its channels aside. A voice whose patch has
|
|
9
|
+
* kept state on a record through `setting.keep` is stateful from then on, and leaves its lane;
|
|
10
|
+
* one that starts keeping partway through may advance that state once for one subject not
|
|
11
|
+
* probed on the frame it starts, which declaring `state` avoids.
|
|
12
|
+
*/
|
|
13
|
+
export declare function fits<I, O>(mix: Mixer<I, O>, voice: Voice<I, O>): boolean;
|
|
14
|
+
/** A voice's patch kept state on a record: it is stateful from now on. */
|
|
15
|
+
export declare function stateful<I, O>(mix: Mixer<I, O>, voice: Voice<I, O>): void;
|
|
16
|
+
export declare function handle<I, O>(mix: Mixer<I, O>, voice: Voice<I, O>): Handle<I>;
|
package/dist/hosts.js
ADDED
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
import { Book } from './book.js';
|
|
2
|
+
import { schedule } from './due.js';
|
|
3
|
+
import { beginFade, fadeSubject } from './fade.js';
|
|
4
|
+
import { VoiceHandle } from './handle.js';
|
|
5
|
+
import { noted, remember } from './history.js';
|
|
6
|
+
import { hostOf, listed, markOf, readingAt } from './marks.js';
|
|
7
|
+
import { nextFrame } from './move.js';
|
|
8
|
+
import { descendants, ownWeight, signalled } from './owner.js';
|
|
9
|
+
import { localNow } from './place.js';
|
|
10
|
+
import { reading } from './reading.js';
|
|
11
|
+
import { unreach } from './unreached.js';
|
|
12
|
+
export function book(mix, opts) {
|
|
13
|
+
if (mix.projecting)
|
|
14
|
+
throw new Error('blits: a projection books nothing');
|
|
15
|
+
const host = {
|
|
16
|
+
get voices() {
|
|
17
|
+
return mix.cued;
|
|
18
|
+
},
|
|
19
|
+
get timestamp() {
|
|
20
|
+
return mix.last;
|
|
21
|
+
},
|
|
22
|
+
get now() {
|
|
23
|
+
return mix.now;
|
|
24
|
+
},
|
|
25
|
+
hostOf: (t) => hostOf(mix, t),
|
|
26
|
+
readingAt: (timestamp) => readingAt(mix, timestamp - mix.offset),
|
|
27
|
+
endOf: (voice) => markOf(mix, voice, 'end'),
|
|
28
|
+
marks: (from, to) => listed(mix, from, to),
|
|
29
|
+
unhook: (b) => {
|
|
30
|
+
const left = (mix.bookers ?? []).filter((x) => x !== b);
|
|
31
|
+
mix.bookers = left.length === 0 ? null : left;
|
|
32
|
+
},
|
|
33
|
+
};
|
|
34
|
+
const b = new Book(host, opts);
|
|
35
|
+
mix.bookers = [...(mix.bookers ?? []), b];
|
|
36
|
+
return b;
|
|
37
|
+
}
|
|
38
|
+
export function laneHost(mix) {
|
|
39
|
+
return {
|
|
40
|
+
get voices() {
|
|
41
|
+
return mix.cued;
|
|
42
|
+
},
|
|
43
|
+
channels: mix.channels,
|
|
44
|
+
names: mix.names,
|
|
45
|
+
fits: (voice) => fits(mix, voice),
|
|
46
|
+
meet: (voice, subject, head, slot) => {
|
|
47
|
+
for (let held = head; held !== null; held = held.next)
|
|
48
|
+
if (held.voice === voice)
|
|
49
|
+
return held;
|
|
50
|
+
return mix.held(voice, subject, mix.now, slot);
|
|
51
|
+
},
|
|
52
|
+
forgot: (slot) => {
|
|
53
|
+
// Only a voice over every subject keeps bits, and one gone may still be read back.
|
|
54
|
+
for (const voice of mix.general)
|
|
55
|
+
unreach(voice, slot, false);
|
|
56
|
+
for (const voice of mix.gone)
|
|
57
|
+
unreach(voice, slot, false);
|
|
58
|
+
},
|
|
59
|
+
naming: (subject) => (mix.naming === 0 ? undefined : mix.named.get(subject)),
|
|
60
|
+
slotOf: (subject) => mix.chains.get(subject)?.slot ?? -1,
|
|
61
|
+
envelope: (voice, since) => voice.owner === null
|
|
62
|
+
? mix.envelope(voice, mix.now, since)
|
|
63
|
+
: mix.envelope(voice, mix.now, since) * mix.ownedBy(voice, undefined),
|
|
64
|
+
parting: (voice, subject) => mix.parting(voice, subject, mix.now),
|
|
65
|
+
ready: (voice, subject, held, elapsed, pass, weight) => {
|
|
66
|
+
mix.prime(voice, held, mix.now, elapsed, pass);
|
|
67
|
+
voice.setting.weight = weight;
|
|
68
|
+
mix.sending.voice = voice;
|
|
69
|
+
mix.sending.subject = subject;
|
|
70
|
+
},
|
|
71
|
+
signal: (voice, subject, held, elapsed, pass, slot) => {
|
|
72
|
+
// A blend member after the first takes the read already made, with no setting to fill.
|
|
73
|
+
const read = voice.blend === null ? Number.NaN : mix.laneRead(voice, slot);
|
|
74
|
+
if (!Number.isNaN(read))
|
|
75
|
+
return read;
|
|
76
|
+
mix.prime(voice, held, mix.now, elapsed, pass);
|
|
77
|
+
const kept = reading.kept;
|
|
78
|
+
const base = mix.base(voice, subject, mix.now, held, slot);
|
|
79
|
+
if (reading.kept !== kept && !voice.keeping)
|
|
80
|
+
stateful(mix, voice);
|
|
81
|
+
return base;
|
|
82
|
+
},
|
|
83
|
+
horizon: (voice, delay) => {
|
|
84
|
+
reading.horizon = mix.horizonFor(voice, delay, mix.now);
|
|
85
|
+
},
|
|
86
|
+
keeps: mix.opts.history !== undefined,
|
|
87
|
+
after: (voice, held) => remember(mix, voice, held),
|
|
88
|
+
kept: (voice) => stateful(mix, voice),
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Whether a voice's patch and spec can run on a lane, its channels aside. A voice whose patch has
|
|
93
|
+
* kept state on a record through `setting.keep` is stateful from then on, and leaves its lane;
|
|
94
|
+
* one that starts keeping partway through may advance that state once for one subject not
|
|
95
|
+
* probed on the frame it starts, which declaring `state` avoids.
|
|
96
|
+
*/
|
|
97
|
+
export function fits(mix, voice) {
|
|
98
|
+
const spec = voice.spec;
|
|
99
|
+
const patch = voice.patch;
|
|
100
|
+
if (voice.keeping)
|
|
101
|
+
return false;
|
|
102
|
+
if (voice.state === 'pending' && voice.freezesBefore)
|
|
103
|
+
return false;
|
|
104
|
+
// A fill weighs a voice's owners once for every subject, which a signal on one would not be.
|
|
105
|
+
if (voice.owner !== null && signalled(voice))
|
|
106
|
+
return false;
|
|
107
|
+
// A signal reading host input records it per probe under history, which a fill cannot.
|
|
108
|
+
if (typeof spec.weight === 'function' && spec.weight.input && mix.opts.history?.inputs)
|
|
109
|
+
return false;
|
|
110
|
+
// A locus on lanes gathers keys and fn members; a motion member keeps it on the general path.
|
|
111
|
+
if (spec.locus !== undefined && voice.motion !== undefined)
|
|
112
|
+
return false;
|
|
113
|
+
if (spec.from === 'current')
|
|
114
|
+
return false;
|
|
115
|
+
if (voice.out?.rest)
|
|
116
|
+
return false;
|
|
117
|
+
if (patch.state !== undefined || patch.step !== undefined)
|
|
118
|
+
return false;
|
|
119
|
+
if (mix.opts.history?.inputs && patch.reads !== undefined && patch.reads.length > 0)
|
|
120
|
+
return false;
|
|
121
|
+
const built = voice.built;
|
|
122
|
+
return (built === null ||
|
|
123
|
+
built.tracks.every((t, i) => t.lerp === undefined || t.lerp === voice.lerps[i]));
|
|
124
|
+
}
|
|
125
|
+
/** A voice's patch kept state on a record: it is stateful from now on. */
|
|
126
|
+
export function stateful(mix, voice) {
|
|
127
|
+
if (voice.keeping)
|
|
128
|
+
return;
|
|
129
|
+
voice.keeping = true;
|
|
130
|
+
mix.lanes?.invalidate();
|
|
131
|
+
}
|
|
132
|
+
export function handle(mix, voice) {
|
|
133
|
+
mix.handles ??= handleHost(mix);
|
|
134
|
+
return new VoiceHandle(voice, mix.handles);
|
|
135
|
+
}
|
|
136
|
+
function handleHost(mix) {
|
|
137
|
+
return {
|
|
138
|
+
nowFor: (voice) => {
|
|
139
|
+
if (Number.isNaN(mix.now))
|
|
140
|
+
return voice.start;
|
|
141
|
+
const local = localNow(mix, voice);
|
|
142
|
+
return Number.isFinite(local) ? local : voice.start;
|
|
143
|
+
},
|
|
144
|
+
changed: (voice) => {
|
|
145
|
+
mix.frame = nextFrame();
|
|
146
|
+
noted(mix, voice);
|
|
147
|
+
mix.lanes?.refill();
|
|
148
|
+
},
|
|
149
|
+
// A delta read before an owner's seek, or a hit booked, is not taken as standing after it.
|
|
150
|
+
sought: (voice) => {
|
|
151
|
+
if (voice.holding !== null)
|
|
152
|
+
descendants(voice, (v) => {
|
|
153
|
+
v.seeks++;
|
|
154
|
+
schedule(mix, v);
|
|
155
|
+
});
|
|
156
|
+
},
|
|
157
|
+
fade: (voice, opts) => {
|
|
158
|
+
if (voice.holding !== null && opts !== undefined && ('subject' in opts || opts.at === 'rest'))
|
|
159
|
+
throw new Error('blits: an owner fades as a whole, not by subject or at rest');
|
|
160
|
+
if (opts !== undefined && 'subject' in opts)
|
|
161
|
+
fadeSubject(mix, voice, opts.subject, opts.over);
|
|
162
|
+
else
|
|
163
|
+
beginFade(mix, voice, opts ?? {});
|
|
164
|
+
},
|
|
165
|
+
weightOf: (voice, subject) => {
|
|
166
|
+
if (voice.state === 'done')
|
|
167
|
+
return 0;
|
|
168
|
+
if (voice.holding !== null)
|
|
169
|
+
return ownWeight(voice, mix.now, mix.reducedNow, (o) => mix.ownerBase(o, subject));
|
|
170
|
+
if (voice.laned && mix.lanes !== null) {
|
|
171
|
+
const w = mix.lanes.weightOf(voice.id, mix.chains.get(subject)?.slot ?? -1);
|
|
172
|
+
if (w !== undefined)
|
|
173
|
+
return w;
|
|
174
|
+
}
|
|
175
|
+
return voice.subjects.get(subject)?.weight ?? 0;
|
|
176
|
+
},
|
|
177
|
+
};
|
|
178
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -6,4 +6,6 @@ export { glide, spring, tween } from './motion.js';
|
|
|
6
6
|
export type { KeysOptions, PatchOptions } from './patch.js';
|
|
7
7
|
export { keys, patch } from './patch.js';
|
|
8
8
|
export { gate, lag, level, peak, slew } from './signals.js';
|
|
9
|
-
export type {
|
|
9
|
+
export type { Ticked, Ticker, TickerOptions } from './ticker.js';
|
|
10
|
+
export { ticker } from './ticker.js';
|
|
11
|
+
export type { Anchor, BookedHit, Booker, BookOptions, Channel, Columns, Doubt, Easing, Engine, FadeOptions, FadeSpec, Handle, Hit, Keyframe, Kit, Mark, Marked, Mix, MixOptions, MotionSpec, OwnerSpec, Patch, Placement, Projection, Query, Sent, Setting, Signal, VoiceSpec, } from './types.js';
|
package/dist/index.js
CHANGED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { type Numeric } from './channels.js';
|
|
2
|
+
import type { Curve } from './easing.js';
|
|
3
|
+
import { type Built } from './patch.js';
|
|
4
|
+
/** What a keys row folds into: a laned channel's arithmetic, axes, rest and values. */
|
|
5
|
+
export interface KeyChannel {
|
|
6
|
+
scalar: boolean;
|
|
7
|
+
op: Numeric['op'];
|
|
8
|
+
rest: number;
|
|
9
|
+
axes: number;
|
|
10
|
+
values: Float64Array;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* The stops of a crowd's `keys` rows, copied into one array as a row joins, so a fill reads each
|
|
14
|
+
* row's stops in place rather than through its voice, its built tracks and every stop's own array.
|
|
15
|
+
* Per row: its header, then per track its stop count, its delay, and per stop its phase and its
|
|
16
|
+
* value on each of the channel's axes, a missing axis read as rest as `vec`'s lerp reads it. Each
|
|
17
|
+
* stop's easing is in `eases`.
|
|
18
|
+
*/
|
|
19
|
+
export declare class KeyRows {
|
|
20
|
+
data: Float64Array<ArrayBuffer>;
|
|
21
|
+
used: number;
|
|
22
|
+
eases: (Curve | undefined)[];
|
|
23
|
+
/**
|
|
24
|
+
* Copies a voice's stops for channels `chans` and returns the row's offset; -1 where a stop's
|
|
25
|
+
* value is not one a lane folds as a number per axis, so the row reads its voice's stops instead.
|
|
26
|
+
*/
|
|
27
|
+
add(built: Built, chans: readonly KeyChannel[]): number;
|
|
28
|
+
/** Copies another pool's row at `off` into this one, returning its offset here. */
|
|
29
|
+
copy(from: KeyRows, off: number): number;
|
|
30
|
+
/**
|
|
31
|
+
* Folds the row at `off` at `phase` into subject `slot`'s values at weight `w`: what `segment`
|
|
32
|
+
* and the stock lerp and fold give for each track, read from this pool.
|
|
33
|
+
*/
|
|
34
|
+
fold(off: number, phase: number, chans: readonly KeyChannel[], slot: number, w: number): void;
|
|
35
|
+
private reserve;
|
|
36
|
+
}
|