@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/voice.d.ts
ADDED
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
import type { Blend } from './blend.js';
|
|
2
|
+
import { type Clock } from './clock.js';
|
|
3
|
+
import { type Curve } from './easing.js';
|
|
4
|
+
import { type Motions } from './motion.js';
|
|
5
|
+
import { Named } from './named.js';
|
|
6
|
+
import { Holding } from './owner.js';
|
|
7
|
+
import { type Built, intosOf, type Scratch } from './patch.js';
|
|
8
|
+
import { Store } from './store.js';
|
|
9
|
+
import type { Channel, FadeSpec, Handle, Patch, Setting, VoiceSpec } from './types.js';
|
|
10
|
+
export declare const none: readonly string[];
|
|
11
|
+
/** Everything one voice holds for one subject, chained through the next voice that reaches it. */
|
|
12
|
+
export interface Subject<S> {
|
|
13
|
+
/** The voice's `target` answer, fixed on first sight. */
|
|
14
|
+
reaches: boolean;
|
|
15
|
+
/** The voice's `stagger` answer in voice ms, fixed on first sight. */
|
|
16
|
+
delay: number;
|
|
17
|
+
/** The mix timestamp this subject's delay ran out at, which its step grid counts from. */
|
|
18
|
+
since: number;
|
|
19
|
+
/** The mix timestamp its fade in counts from: `since`, or earlier for a voice holding before. */
|
|
20
|
+
shown: number;
|
|
21
|
+
/** The weight this voice gave this subject the last frame it was probed, 0 where it gave none. */
|
|
22
|
+
weight: number;
|
|
23
|
+
/** Left at rest during a handover, so it stops contributing. */
|
|
24
|
+
rested: boolean;
|
|
25
|
+
/**
|
|
26
|
+
* Per written channel, whether a rest-less influence is on: 0 unknown, 1 on, 2 off; null until
|
|
27
|
+
* a rest-less channel asks.
|
|
28
|
+
*/
|
|
29
|
+
bands: Uint8Array | null;
|
|
30
|
+
state: S;
|
|
31
|
+
/** The `now` this subject last caught up to, for this voice. */
|
|
32
|
+
stepped: number;
|
|
33
|
+
/** Under `stepMs`, how many intervals have run since `since`; `stepped` is where that lands. */
|
|
34
|
+
ticks: number;
|
|
35
|
+
/** The `now` this subject was last probed at, so twelve probes in a frame step once. */
|
|
36
|
+
probed: number;
|
|
37
|
+
/** The last delta computed this frame, handed back to a repeat probe unchanged. */
|
|
38
|
+
delta: Record<string, unknown> | null;
|
|
39
|
+
/** The phase `delta` was read at, to read it again once its voice's scratch has moved on. */
|
|
40
|
+
phase: number;
|
|
41
|
+
/** The voice's `seeks` when `delta` was read, so a seek later in the frame reads it again. */
|
|
42
|
+
seeks: number;
|
|
43
|
+
/** Stop 0 for a `from: 'current'` voice, taken the first frame this subject is seen. */
|
|
44
|
+
base?: Record<string, unknown>;
|
|
45
|
+
/** The pose's velocity per channel at that moment, units per ms, so the first segment leaves at it. */
|
|
46
|
+
slope?: Record<string, unknown>;
|
|
47
|
+
/** What `setting.keep` holds for this voice and subject, by owner; null until it holds any. */
|
|
48
|
+
kept: Map<object, unknown> | null;
|
|
49
|
+
/** Under `history`, copies of this record by the mix time they were taken, oldest first. */
|
|
50
|
+
snaps?: {
|
|
51
|
+
at: number;
|
|
52
|
+
held: Subject<S>;
|
|
53
|
+
}[];
|
|
54
|
+
/** In a projection: where this record started from, and whether nothing known could be. */
|
|
55
|
+
from?: number;
|
|
56
|
+
unknown?: boolean;
|
|
57
|
+
/** Under `history` with `inputs`, what an input weight signal read for this subject, when it changed. */
|
|
58
|
+
inputs?: {
|
|
59
|
+
at: number;
|
|
60
|
+
value: number;
|
|
61
|
+
}[];
|
|
62
|
+
/** In a projection reading back: that record, to read in place of the signal. */
|
|
63
|
+
replay?: {
|
|
64
|
+
at: number;
|
|
65
|
+
value: number;
|
|
66
|
+
}[];
|
|
67
|
+
/** The voice this is the record of; null on a subject's stub. */
|
|
68
|
+
voice: object | null;
|
|
69
|
+
/** The next voice's record for this subject, in voice order, among those that reach it. */
|
|
70
|
+
next: Subject<unknown> | null;
|
|
71
|
+
/**
|
|
72
|
+
* On the first record, which the mix keeps per subject so a probe makes one lookup: the `version`
|
|
73
|
+
* the chain was linked at, and per locus, by channel slot, whether a rest-less influence is on:
|
|
74
|
+
* 0 never decided, 1 on, 2 off.
|
|
75
|
+
*/
|
|
76
|
+
version: number;
|
|
77
|
+
loci: Map<string, Uint8Array> | null;
|
|
78
|
+
/** On the first record: the number lanes index this subject by, -1 without one. */
|
|
79
|
+
slot: number;
|
|
80
|
+
}
|
|
81
|
+
/** A voice's clock, weight and fade from one mix time on, kept under `history`. */
|
|
82
|
+
export interface Controls extends Clock {
|
|
83
|
+
at: number;
|
|
84
|
+
weight: number;
|
|
85
|
+
out: Ramp | null;
|
|
86
|
+
/** Where its anchors had placed it then: start, and the start of an anchored fade out. */
|
|
87
|
+
start: number;
|
|
88
|
+
outAt: number;
|
|
89
|
+
/** Made by a sync, so it shows in that frame; a host's change between frames shows from the next. */
|
|
90
|
+
sync: boolean;
|
|
91
|
+
}
|
|
92
|
+
export interface Ramp {
|
|
93
|
+
from: number;
|
|
94
|
+
at: number;
|
|
95
|
+
over: number;
|
|
96
|
+
rest: boolean;
|
|
97
|
+
deadline?: number;
|
|
98
|
+
}
|
|
99
|
+
export declare class Voice<I, O> {
|
|
100
|
+
readonly id: number;
|
|
101
|
+
readonly spec: VoiceSpec<I, O>;
|
|
102
|
+
readonly patch: Patch<I, O, unknown>;
|
|
103
|
+
readonly fade: FadeSpec;
|
|
104
|
+
start: number;
|
|
105
|
+
/** The owner whose clock it runs on, its start included; null for one on the mix clock. */
|
|
106
|
+
owner: Voice<I, O> | null;
|
|
107
|
+
state: 'pending' | 'live' | 'held' | 'fading' | 'done';
|
|
108
|
+
rate: number;
|
|
109
|
+
weight: number;
|
|
110
|
+
/**
|
|
111
|
+
* elapsed = anchorElapsed + the rate integrated from anchorNow, rebased on a rate change or a seek.
|
|
112
|
+
* Without a ramp the rate is constant; with one it moves linearly from `from` to `to` over `over`
|
|
113
|
+
* mix ms starting at the anchor, and holds `to` after.
|
|
114
|
+
*/
|
|
115
|
+
anchorNow: number;
|
|
116
|
+
anchorElapsed: number;
|
|
117
|
+
ramp: {
|
|
118
|
+
from: number;
|
|
119
|
+
to: number;
|
|
120
|
+
over: number;
|
|
121
|
+
} | null;
|
|
122
|
+
out: Ramp | null;
|
|
123
|
+
readonly subjects: Store<I, Subject<unknown>>;
|
|
124
|
+
/** The subjects its spec names, or null where it names none. */
|
|
125
|
+
readonly named: Named<I> | null;
|
|
126
|
+
/** Kit slot of each channel the patch writes, in `writes` order. */
|
|
127
|
+
readonly slots: number[];
|
|
128
|
+
/** The stops built ahead of time, for a `keys` patch. */
|
|
129
|
+
readonly built: Built | null;
|
|
130
|
+
/** The kit's `lerp` for each channel the patch writes, which keyed stops interpolate through. */
|
|
131
|
+
readonly lerps: Channel<unknown>['lerp'][];
|
|
132
|
+
/** Per keyed channel, the in-place form of the lerp its stops take, where it has one. */
|
|
133
|
+
readonly intos: ReturnType<typeof intosOf> | undefined;
|
|
134
|
+
/**
|
|
135
|
+
* The arrays keyed reads interpolate into: one set for the voice, not one per subject, since 10k
|
|
136
|
+
* subjects' worth of long-lived arrays costs more in cache misses than allocating them did.
|
|
137
|
+
*/
|
|
138
|
+
scratch: Scratch;
|
|
139
|
+
/** The record whose delta holds `scratch`'s arrays; any other's must be read again to be handed out. */
|
|
140
|
+
holder: Subject<unknown> | null;
|
|
141
|
+
/** Whether its channels run as lanes this frame, so the general fold passes it by. */
|
|
142
|
+
laned: boolean;
|
|
143
|
+
/** How many times it has been sought, so a delta read before a seek is not handed out after it. */
|
|
144
|
+
seeks: number;
|
|
145
|
+
/** Whether its patch has kept state on a record through `setting.keep`, which makes it stateful. */
|
|
146
|
+
keeping: boolean;
|
|
147
|
+
/** Subjects fading out of this voice alone, by the ramp each started; null while none are. */
|
|
148
|
+
parts: Map<I, {
|
|
149
|
+
at: number;
|
|
150
|
+
over: number;
|
|
151
|
+
}> | null;
|
|
152
|
+
/** Subjects faded out of this voice, by the mix time each left at; null while none have. */
|
|
153
|
+
parted: Map<I, number> | null;
|
|
154
|
+
readonly ease: Curve | undefined;
|
|
155
|
+
/** How many passes its `loop` plays, worked out once. */
|
|
156
|
+
readonly passes: number;
|
|
157
|
+
readonly duration: number;
|
|
158
|
+
/** Voice ms its passes last, Infinity for a patch with no passes or a loop for good. */
|
|
159
|
+
readonly span: number;
|
|
160
|
+
/** The state behind a motion patch, undefined for any other. */
|
|
161
|
+
readonly motion: Motions<I> | undefined;
|
|
162
|
+
/** Its `hold`, for a patch it applies to: motion holds its target already. */
|
|
163
|
+
readonly holdsBefore: boolean;
|
|
164
|
+
readonly holdsAfter: boolean;
|
|
165
|
+
/** The mix time it first showed: when it was cued, or the first sync after. */
|
|
166
|
+
opened: number;
|
|
167
|
+
/** Which of its entries in the mix's due queue is current; older ones are skipped when popped. */
|
|
168
|
+
dueToken: number;
|
|
169
|
+
/** Records made while it was pending, whose `since` its start may since have moved. */
|
|
170
|
+
early: Subject<unknown>[] | null;
|
|
171
|
+
/** Reused for every call this voice makes, so it is valid only during the call. */
|
|
172
|
+
readonly setting: Setting<unknown>;
|
|
173
|
+
/** Every subject this voice has been asked about, so a handover knows when it is finished. */
|
|
174
|
+
seen: number;
|
|
175
|
+
/** The longest stagger of any subject seen, so a finite loop waits for the last of them. */
|
|
176
|
+
latest: number;
|
|
177
|
+
restedCount: number;
|
|
178
|
+
/** The mix time it was cued at; -Infinity before the first sync. */
|
|
179
|
+
cuedAt: number;
|
|
180
|
+
/** The mix time it left at. */
|
|
181
|
+
doneAt: number;
|
|
182
|
+
/** Under `history`, its controls after each change, oldest first. */
|
|
183
|
+
log: Controls[] | null;
|
|
184
|
+
/** Where an anchored `out` or `end` puts its fade's start, mix time; Infinity until known. */
|
|
185
|
+
outAt: number;
|
|
186
|
+
/** Whether its start is still to be fixed by an anchor, so it waits pending. */
|
|
187
|
+
placing: boolean;
|
|
188
|
+
/** The record `setting.keep` writes to: the one its patch or signal is being called for. */
|
|
189
|
+
keepOn: Subject<unknown> | null;
|
|
190
|
+
/** What `setting.keep` holds when called before any record is. */
|
|
191
|
+
ownKept: Map<object, unknown> | null;
|
|
192
|
+
/** The blend it is a member of, and which. */
|
|
193
|
+
blend: {
|
|
194
|
+
of: Blend<I, O>;
|
|
195
|
+
i: number;
|
|
196
|
+
} | null;
|
|
197
|
+
/** The one record of every subject it does not reach. */
|
|
198
|
+
unreached: Subject<unknown> | null;
|
|
199
|
+
/** By subject number, a bit set where `unreached` stands for the subject; null until one is. */
|
|
200
|
+
unreachedBits: Uint32Array | null;
|
|
201
|
+
/** The handle `cue` returned, which `voices` hands back too. */
|
|
202
|
+
handle: Handle<I> | null;
|
|
203
|
+
/** For an owner, the voices it holds; null for any other voice. */
|
|
204
|
+
holding: Holding<Voice<I, O>> | null;
|
|
205
|
+
/**
|
|
206
|
+
* `done` and `played`, made when first asked for, already settled if the voice is: most hosts
|
|
207
|
+
* never await either, and a mix may hold tens of thousands of voices.
|
|
208
|
+
*/
|
|
209
|
+
private finished;
|
|
210
|
+
private playedAs;
|
|
211
|
+
private donePromise;
|
|
212
|
+
private doneSettle;
|
|
213
|
+
private playedPromise;
|
|
214
|
+
private playedSettle;
|
|
215
|
+
/** A projection's copy, which settles nothing. */
|
|
216
|
+
private quiet;
|
|
217
|
+
get done(): Promise<void>;
|
|
218
|
+
get played(): Promise<boolean>;
|
|
219
|
+
/** The voice has left: `done` resolves. */
|
|
220
|
+
resolve(): void;
|
|
221
|
+
/** Its finite loop ended (true) or it left first (false), whichever comes first: `played` resolves. */
|
|
222
|
+
play(played: boolean): void;
|
|
223
|
+
constructor(id: number, spec: VoiceSpec<I, O>, patch: Patch<I, O, unknown>, fade: FadeSpec, now: number, start: number, slotOf: Map<string, number>, channels: readonly Channel<unknown>[], host: unknown, send: (event: unknown) => void,
|
|
224
|
+
/** The owner whose clock it runs on, its start included; null for one on the mix clock. */
|
|
225
|
+
owner: Voice<I, O> | null);
|
|
226
|
+
/** What its clock reads at mix time `now`, through its owners' clocks. */
|
|
227
|
+
elapsedAt(now: number): number;
|
|
228
|
+
/** The mix time its clock reads `elapsed`, inverting `elapsedAt`; Infinity where it never will. */
|
|
229
|
+
timeAt(elapsed: number): number;
|
|
230
|
+
/** Records this voice's controls as they stand, from mix time `at`. */
|
|
231
|
+
note(at: number, sync?: boolean): void;
|
|
232
|
+
/**
|
|
233
|
+
* A copy for a projection: its own setting, which sends nothing, and its own per-subject records,
|
|
234
|
+
* filled from `fill`. With `controls` it takes those, as the voice stood at an earlier time.
|
|
235
|
+
*/
|
|
236
|
+
copy(fill: (subject: I) => Subject<unknown> | undefined, controls?: Controls): Voice<I, O>;
|
|
237
|
+
rateAt(now: number): number;
|
|
238
|
+
/** Moves the anchor to `now`, carrying what is left of a ramp. */
|
|
239
|
+
rebase(now: number): void;
|
|
240
|
+
}
|
package/dist/voice.js
ADDED
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
import { elapsedWith, passesOf, rateWith, rebaseWith, timeWith } from './clock.js';
|
|
2
|
+
import { curve } from './easing.js';
|
|
3
|
+
import { motionOf } from './motion.js';
|
|
4
|
+
import { Named } from './named.js';
|
|
5
|
+
import { childPlayed, Holding, mixTime, ownedElapsed, ownerPatch } from './owner.js';
|
|
6
|
+
import { builtOf, durationOf, intosOf } from './patch.js';
|
|
7
|
+
import { reading } from './reading.js';
|
|
8
|
+
import { Store } from './store.js';
|
|
9
|
+
export const none = Object.freeze([]);
|
|
10
|
+
/**
|
|
11
|
+
* A voice's `setting.keep`, one per voice: a setting is valid only during its call, so the state
|
|
12
|
+
* goes on the record of the subject being called for, `keepOn`, or on the voice before any is.
|
|
13
|
+
*/
|
|
14
|
+
function keeping(voice) {
|
|
15
|
+
return function keep(owner, init) {
|
|
16
|
+
const held = voice.keepOn;
|
|
17
|
+
let kept = held === null ? voice.ownKept : held.kept;
|
|
18
|
+
if (kept === null) {
|
|
19
|
+
kept = new Map();
|
|
20
|
+
if (held === null)
|
|
21
|
+
voice.ownKept = kept;
|
|
22
|
+
else
|
|
23
|
+
held.kept = kept;
|
|
24
|
+
}
|
|
25
|
+
if (kept.has(owner))
|
|
26
|
+
return kept.get(owner);
|
|
27
|
+
const made = init();
|
|
28
|
+
kept.set(owner, made);
|
|
29
|
+
reading.kept++;
|
|
30
|
+
return made;
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* A voice's setting. `keep` is made on first read: most patches keep no state, and a closure and
|
|
35
|
+
* its context per voice cost a one-subject tween voice about 100 bytes.
|
|
36
|
+
*/
|
|
37
|
+
class VoiceSetting {
|
|
38
|
+
host;
|
|
39
|
+
send;
|
|
40
|
+
voice;
|
|
41
|
+
timestamp = 0;
|
|
42
|
+
dt = 0;
|
|
43
|
+
elapsed = 0;
|
|
44
|
+
pass = 0;
|
|
45
|
+
weight = 0;
|
|
46
|
+
state = undefined;
|
|
47
|
+
kept = null;
|
|
48
|
+
constructor(host, send, voice) {
|
|
49
|
+
this.host = host;
|
|
50
|
+
this.send = send;
|
|
51
|
+
this.voice = voice;
|
|
52
|
+
}
|
|
53
|
+
get keep() {
|
|
54
|
+
if (this.kept === null)
|
|
55
|
+
this.kept = keeping(this.voice);
|
|
56
|
+
return this.kept;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
/** A store that fills a missing entry from `make` on first ask. */
|
|
60
|
+
class Filled extends Store {
|
|
61
|
+
make;
|
|
62
|
+
constructor(make) {
|
|
63
|
+
super();
|
|
64
|
+
this.make = make;
|
|
65
|
+
}
|
|
66
|
+
get(key) {
|
|
67
|
+
let v = super.get(key);
|
|
68
|
+
if (v === undefined) {
|
|
69
|
+
v = this.make(key);
|
|
70
|
+
if (v !== undefined)
|
|
71
|
+
super.set(key, v);
|
|
72
|
+
}
|
|
73
|
+
return v;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
const noSend = () => { };
|
|
77
|
+
export class Voice {
|
|
78
|
+
id;
|
|
79
|
+
spec;
|
|
80
|
+
patch;
|
|
81
|
+
fade;
|
|
82
|
+
start;
|
|
83
|
+
owner;
|
|
84
|
+
state = 'pending';
|
|
85
|
+
rate;
|
|
86
|
+
weight;
|
|
87
|
+
/**
|
|
88
|
+
* elapsed = anchorElapsed + the rate integrated from anchorNow, rebased on a rate change or a seek.
|
|
89
|
+
* Without a ramp the rate is constant; with one it moves linearly from `from` to `to` over `over`
|
|
90
|
+
* mix ms starting at the anchor, and holds `to` after.
|
|
91
|
+
*/
|
|
92
|
+
anchorNow;
|
|
93
|
+
anchorElapsed = 0;
|
|
94
|
+
ramp = null;
|
|
95
|
+
out = null;
|
|
96
|
+
subjects = new Store();
|
|
97
|
+
/** The subjects its spec names, or null where it names none. */
|
|
98
|
+
named;
|
|
99
|
+
/** Kit slot of each channel the patch writes, in `writes` order. */
|
|
100
|
+
slots;
|
|
101
|
+
/** The stops built ahead of time, for a `keys` patch. */
|
|
102
|
+
built;
|
|
103
|
+
/** The kit's `lerp` for each channel the patch writes, which keyed stops interpolate through. */
|
|
104
|
+
lerps;
|
|
105
|
+
/** Per keyed channel, the in-place form of the lerp its stops take, where it has one. */
|
|
106
|
+
intos;
|
|
107
|
+
/**
|
|
108
|
+
* The arrays keyed reads interpolate into: one set for the voice, not one per subject, since 10k
|
|
109
|
+
* subjects' worth of long-lived arrays costs more in cache misses than allocating them did.
|
|
110
|
+
*/
|
|
111
|
+
scratch = [];
|
|
112
|
+
/** The record whose delta holds `scratch`'s arrays; any other's must be read again to be handed out. */
|
|
113
|
+
holder = null;
|
|
114
|
+
/** Whether its channels run as lanes this frame, so the general fold passes it by. */
|
|
115
|
+
laned = false;
|
|
116
|
+
/** How many times it has been sought, so a delta read before a seek is not handed out after it. */
|
|
117
|
+
seeks = 0;
|
|
118
|
+
/** Whether its patch has kept state on a record through `setting.keep`, which makes it stateful. */
|
|
119
|
+
keeping = false;
|
|
120
|
+
/** Subjects fading out of this voice alone, by the ramp each started; null while none are. */
|
|
121
|
+
parts = null;
|
|
122
|
+
/** Subjects faded out of this voice, by the mix time each left at; null while none have. */
|
|
123
|
+
parted = null;
|
|
124
|
+
ease;
|
|
125
|
+
/** How many passes its `loop` plays, worked out once. */
|
|
126
|
+
passes;
|
|
127
|
+
duration;
|
|
128
|
+
/** Voice ms its passes last, Infinity for a patch with no passes or a loop for good. */
|
|
129
|
+
span;
|
|
130
|
+
/** The state behind a motion patch, undefined for any other. */
|
|
131
|
+
motion;
|
|
132
|
+
/** Its `hold`, for a patch it applies to: motion holds its target already. */
|
|
133
|
+
holdsBefore;
|
|
134
|
+
holdsAfter;
|
|
135
|
+
/** The mix time it first showed: when it was cued, or the first sync after. */
|
|
136
|
+
opened = Number.NaN;
|
|
137
|
+
/** Which of its entries in the mix's due queue is current; older ones are skipped when popped. */
|
|
138
|
+
dueToken = 0;
|
|
139
|
+
/** Records made while it was pending, whose `since` its start may since have moved. */
|
|
140
|
+
early = null;
|
|
141
|
+
/** Reused for every call this voice makes, so it is valid only during the call. */
|
|
142
|
+
setting;
|
|
143
|
+
/** Every subject this voice has been asked about, so a handover knows when it is finished. */
|
|
144
|
+
seen = 0;
|
|
145
|
+
/** The longest stagger of any subject seen, so a finite loop waits for the last of them. */
|
|
146
|
+
latest = 0;
|
|
147
|
+
restedCount = 0;
|
|
148
|
+
/** The mix time it was cued at; -Infinity before the first sync. */
|
|
149
|
+
cuedAt = Number.NEGATIVE_INFINITY;
|
|
150
|
+
/** The mix time it left at. */
|
|
151
|
+
doneAt = Number.POSITIVE_INFINITY;
|
|
152
|
+
/** Under `history`, its controls after each change, oldest first. */
|
|
153
|
+
log = null;
|
|
154
|
+
/** Where an anchored `out` or `end` puts its fade's start, mix time; Infinity until known. */
|
|
155
|
+
outAt = Number.POSITIVE_INFINITY;
|
|
156
|
+
/** Whether its start is still to be fixed by an anchor, so it waits pending. */
|
|
157
|
+
placing = false;
|
|
158
|
+
/** The record `setting.keep` writes to: the one its patch or signal is being called for. */
|
|
159
|
+
keepOn = null;
|
|
160
|
+
/** What `setting.keep` holds when called before any record is. */
|
|
161
|
+
ownKept = null;
|
|
162
|
+
/** The blend it is a member of, and which. */
|
|
163
|
+
blend = null;
|
|
164
|
+
/** The one record of every subject it does not reach. */
|
|
165
|
+
unreached = null;
|
|
166
|
+
/** By subject number, a bit set where `unreached` stands for the subject; null until one is. */
|
|
167
|
+
unreachedBits = null;
|
|
168
|
+
/** The handle `cue` returned, which `voices` hands back too. */
|
|
169
|
+
handle = null;
|
|
170
|
+
/** For an owner, the voices it holds; null for any other voice. */
|
|
171
|
+
holding;
|
|
172
|
+
/**
|
|
173
|
+
* `done` and `played`, made when first asked for, already settled if the voice is: most hosts
|
|
174
|
+
* never await either, and a mix may hold tens of thousands of voices.
|
|
175
|
+
*/
|
|
176
|
+
finished = false;
|
|
177
|
+
playedAs = undefined;
|
|
178
|
+
donePromise = null;
|
|
179
|
+
doneSettle = null;
|
|
180
|
+
playedPromise = null;
|
|
181
|
+
playedSettle = null;
|
|
182
|
+
/** A projection's copy, which settles nothing. */
|
|
183
|
+
quiet = false;
|
|
184
|
+
get done() {
|
|
185
|
+
if (this.donePromise === null)
|
|
186
|
+
this.donePromise = this.finished
|
|
187
|
+
? Promise.resolve()
|
|
188
|
+
: new Promise((r) => {
|
|
189
|
+
this.doneSettle = r;
|
|
190
|
+
});
|
|
191
|
+
return this.donePromise;
|
|
192
|
+
}
|
|
193
|
+
get played() {
|
|
194
|
+
if (this.playedPromise === null) {
|
|
195
|
+
const as = this.playedAs;
|
|
196
|
+
this.playedPromise =
|
|
197
|
+
as !== undefined
|
|
198
|
+
? Promise.resolve(as)
|
|
199
|
+
: new Promise((r) => {
|
|
200
|
+
this.playedSettle = r;
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
return this.playedPromise;
|
|
204
|
+
}
|
|
205
|
+
/** The voice has left: `done` resolves. */
|
|
206
|
+
resolve() {
|
|
207
|
+
if (this.quiet || this.finished)
|
|
208
|
+
return;
|
|
209
|
+
this.finished = true;
|
|
210
|
+
this.doneSettle?.();
|
|
211
|
+
}
|
|
212
|
+
/** Its finite loop ended (true) or it left first (false), whichever comes first: `played` resolves. */
|
|
213
|
+
play(played) {
|
|
214
|
+
if (this.quiet || this.playedAs !== undefined)
|
|
215
|
+
return;
|
|
216
|
+
this.playedAs = played;
|
|
217
|
+
this.playedSettle?.(played);
|
|
218
|
+
if (played && this.owner !== null)
|
|
219
|
+
childPlayed(this.owner);
|
|
220
|
+
}
|
|
221
|
+
constructor(id, spec, patch, fade, now, start, slotOf, channels, host, send,
|
|
222
|
+
/** The owner whose clock it runs on, its start included; null for one on the mix clock. */
|
|
223
|
+
owner) {
|
|
224
|
+
this.id = id;
|
|
225
|
+
this.spec = spec;
|
|
226
|
+
this.patch = patch;
|
|
227
|
+
this.fade = fade;
|
|
228
|
+
this.start = start;
|
|
229
|
+
this.owner = owner;
|
|
230
|
+
this.holding = patch === ownerPatch ? new Holding() : null;
|
|
231
|
+
this.slots = patch.writes.map((k) => slotOf.get(k));
|
|
232
|
+
this.named = spec.subjects ? new Named(spec.subjects) : null;
|
|
233
|
+
this.built = patch.form === 'keys' && patch.keys ? builtOf(patch) : null;
|
|
234
|
+
this.lerps = this.slots.map((slot) => channels[slot].lerp);
|
|
235
|
+
this.intos = this.built
|
|
236
|
+
? intosOf(this.built, this.slots.map((slot) => channels[slot]))
|
|
237
|
+
: undefined;
|
|
238
|
+
this.ease = fade.ease === undefined ? undefined : curve(fade.ease);
|
|
239
|
+
this.passes = passesOf(spec.loop);
|
|
240
|
+
this.duration = durationOf(patch);
|
|
241
|
+
this.span =
|
|
242
|
+
this.duration > 0 && Number.isFinite(this.passes)
|
|
243
|
+
? this.duration * this.passes
|
|
244
|
+
: Number.POSITIVE_INFINITY;
|
|
245
|
+
this.motion = motionOf(patch);
|
|
246
|
+
const holds = this.motion === undefined ? spec.hold : undefined;
|
|
247
|
+
// A voice with no hold of its own takes its owner's.
|
|
248
|
+
const inherits = holds === undefined && this.motion === undefined && owner !== null;
|
|
249
|
+
this.holdsBefore = inherits ? owner.holdsBefore : holds === 'before' || holds === 'both';
|
|
250
|
+
this.holdsAfter = inherits ? owner.holdsAfter : holds === 'after' || holds === 'both';
|
|
251
|
+
this.setting = new VoiceSetting(host, send, this);
|
|
252
|
+
this.rate = spec.rate ?? 1;
|
|
253
|
+
this.weight = typeof spec.weight === 'number' ? spec.weight : 1;
|
|
254
|
+
this.anchorNow = start;
|
|
255
|
+
if (now >= start)
|
|
256
|
+
this.state = 'live';
|
|
257
|
+
}
|
|
258
|
+
/** What its clock reads at mix time `now`, through its owners' clocks. */
|
|
259
|
+
elapsedAt(now) {
|
|
260
|
+
return this.owner === null ? elapsedWith(this, now) : ownedElapsed(this, this.owner, now);
|
|
261
|
+
}
|
|
262
|
+
/** The mix time its clock reads `elapsed`, inverting `elapsedAt`; Infinity where it never will. */
|
|
263
|
+
timeAt(elapsed) {
|
|
264
|
+
const t = timeWith(this, elapsed);
|
|
265
|
+
return this.owner === null ? t : mixTime(this.owner, t);
|
|
266
|
+
}
|
|
267
|
+
/** Records this voice's controls as they stand, from mix time `at`. */
|
|
268
|
+
note(at, sync = false) {
|
|
269
|
+
this.log?.push({
|
|
270
|
+
at,
|
|
271
|
+
sync,
|
|
272
|
+
anchorNow: this.anchorNow,
|
|
273
|
+
anchorElapsed: this.anchorElapsed,
|
|
274
|
+
rate: this.rate,
|
|
275
|
+
ramp: this.ramp,
|
|
276
|
+
weight: this.weight,
|
|
277
|
+
out: this.out,
|
|
278
|
+
start: this.start,
|
|
279
|
+
outAt: this.outAt,
|
|
280
|
+
});
|
|
281
|
+
}
|
|
282
|
+
/**
|
|
283
|
+
* A copy for a projection: its own setting, which sends nothing, and its own per-subject records,
|
|
284
|
+
* filled from `fill`. With `controls` it takes those, as the voice stood at an earlier time.
|
|
285
|
+
*/
|
|
286
|
+
copy(fill, controls) {
|
|
287
|
+
// A literal keeps the copy in fast mode, where `Object.assign` left it a dictionary that every
|
|
288
|
+
// read in a projection looked up by name.
|
|
289
|
+
const v = { __proto__: Voice.prototype, ...this };
|
|
290
|
+
const w = v;
|
|
291
|
+
w.subjects = new Filled(fill);
|
|
292
|
+
w.setting = { ...this.setting, keep: keeping(v), send: noSend };
|
|
293
|
+
v.keepOn = null;
|
|
294
|
+
v.ownKept = null;
|
|
295
|
+
v.scratch = [];
|
|
296
|
+
v.holder = null;
|
|
297
|
+
v.laned = false;
|
|
298
|
+
v.log = null;
|
|
299
|
+
v.early = null;
|
|
300
|
+
if (controls) {
|
|
301
|
+
v.anchorNow = controls.anchorNow;
|
|
302
|
+
v.anchorElapsed = controls.anchorElapsed;
|
|
303
|
+
v.rate = controls.rate;
|
|
304
|
+
v.ramp = controls.ramp;
|
|
305
|
+
v.weight = controls.weight;
|
|
306
|
+
v.out = controls.out;
|
|
307
|
+
v.start = controls.start;
|
|
308
|
+
v.outAt = controls.outAt;
|
|
309
|
+
}
|
|
310
|
+
v.quiet = true;
|
|
311
|
+
v.unreached = null;
|
|
312
|
+
v.unreachedBits = null;
|
|
313
|
+
return v;
|
|
314
|
+
}
|
|
315
|
+
rateAt(now) {
|
|
316
|
+
return rateWith(this, now);
|
|
317
|
+
}
|
|
318
|
+
/** Moves the anchor to `now`, carrying what is left of a ramp. */
|
|
319
|
+
rebase(now) {
|
|
320
|
+
rebaseWith(this, now);
|
|
321
|
+
}
|
|
322
|
+
}
|
package/dist/weigh.d.ts
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { Mixer } from './mixer.js';
|
|
2
|
+
import type { Subject, Voice } from './voice.js';
|
|
3
|
+
/** Fills a voice's setting for a call to its patch, the weight aside. */
|
|
4
|
+
export declare function prime<I, O>(this: Mixer<I, O>, voice: Voice<I, O>, held: Subject<unknown>, now: number, elapsed: number, pass: number): void;
|
|
5
|
+
/** A gap as a `dt`: Infinity under reduced motion, and no more than `maxDt`. */
|
|
6
|
+
export declare function capped<I, O>(this: Mixer<I, O>, gap: number): number;
|
|
7
|
+
/** The earliest voice time a read may still ask for, for a subject delayed `delay`. */
|
|
8
|
+
export declare function horizonFor<I, O>(this: Mixer<I, O>, voice: Voice<I, O>, delay: number, now: number): number;
|
|
9
|
+
/** The ramp a voice's own fade envelope applies this frame, 0..1. */
|
|
10
|
+
export declare function fadeOf<I, O>(this: Mixer<I, O>, voice: Voice<I, O>, now: number, since: number): number;
|
|
11
|
+
/** What every owner above a voice multiplies into its weight for a subject this frame. */
|
|
12
|
+
export declare function ownedBy<I, O>(this: Mixer<I, O>, voice: Voice<I, O>, subject: I): number;
|
|
13
|
+
/**
|
|
14
|
+
* An owner's signal read for a subject, once a frame: kept on the owner's record for it, which
|
|
15
|
+
* no fold links, in `phase`, with the frame it was read in `probed`.
|
|
16
|
+
*/
|
|
17
|
+
export declare function ownerBase<I, O>(this: Mixer<I, O>, owner: Voice<I, O>, subject: I): number;
|
|
18
|
+
/** The weight a voice gives a subject this frame, with the setting already filled in. */
|
|
19
|
+
export declare function weigh<I, O>(this: Mixer<I, O>, voice: Voice<I, O>, subject: I, now: number, held: Subject<unknown>): number;
|
|
20
|
+
/** `weigh` for a voice an owner holds, its owners' weight folded into its fade. */
|
|
21
|
+
export declare function weighOwned<I, O>(this: Mixer<I, O>, voice: Voice<I, O>, subject: I, now: number, held: Subject<unknown>): number;
|
|
22
|
+
/**
|
|
23
|
+
* A voice's weight for a subject before its fade and ramp: its signal's, or its own number.
|
|
24
|
+
* `slot` is the subject's number when lanes ask.
|
|
25
|
+
*/
|
|
26
|
+
export declare function base<I, O>(this: Mixer<I, O>, voice: Voice<I, O>, subject: I, now: number, held: Subject<unknown>, slot?: number): number;
|
package/dist/weigh.js
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { envelope, weighed } from './clock.js';
|
|
2
|
+
import { elapsedThen, last, record } from './history.js';
|
|
3
|
+
import { ownersWeight } from './owner.js';
|
|
4
|
+
import { reading } from './reading.js';
|
|
5
|
+
/** Fills a voice's setting for a call to its patch, the weight aside. */
|
|
6
|
+
export function prime(voice, held, now, elapsed, pass) {
|
|
7
|
+
const setting = voice.setting;
|
|
8
|
+
setting.timestamp = now;
|
|
9
|
+
setting.dt = this.capped(now - held.stepped);
|
|
10
|
+
setting.elapsed = elapsed;
|
|
11
|
+
setting.pass = pass;
|
|
12
|
+
setting.weight = 0;
|
|
13
|
+
setting.state = held.state;
|
|
14
|
+
voice.keepOn = held;
|
|
15
|
+
}
|
|
16
|
+
/** A gap as a `dt`: Infinity under reduced motion, and no more than `maxDt`. */
|
|
17
|
+
export function capped(gap) {
|
|
18
|
+
const cap = this.opts.maxDt;
|
|
19
|
+
return this.reducedNow ? Number.POSITIVE_INFINITY : cap !== undefined && gap > cap ? cap : gap;
|
|
20
|
+
}
|
|
21
|
+
/** The earliest voice time a read may still ask for, for a subject delayed `delay`. */
|
|
22
|
+
export function horizonFor(voice, delay, now) {
|
|
23
|
+
const history = this.opts.history;
|
|
24
|
+
return history === undefined
|
|
25
|
+
? Number.POSITIVE_INFINITY
|
|
26
|
+
: elapsedThen(this, voice, now - history.ms) - delay;
|
|
27
|
+
}
|
|
28
|
+
/** The ramp a voice's own fade envelope applies this frame, 0..1. */
|
|
29
|
+
export function fadeOf(voice, now, since) {
|
|
30
|
+
return envelope(voice.fade.in ?? 0, voice.out, voice.ease, this.reducedNow, now, since);
|
|
31
|
+
}
|
|
32
|
+
/** What every owner above a voice multiplies into its weight for a subject this frame. */
|
|
33
|
+
export function ownedBy(voice, subject) {
|
|
34
|
+
return ownersWeight(voice, this.now, this.reducedNow, (o) => this.ownerBase(o, subject));
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* An owner's signal read for a subject, once a frame: kept on the owner's record for it, which
|
|
38
|
+
* no fold links, in `phase`, with the frame it was read in `probed`.
|
|
39
|
+
*/
|
|
40
|
+
export function ownerBase(owner, subject) {
|
|
41
|
+
const now = this.now;
|
|
42
|
+
const held = this.held(owner, subject, now, -1);
|
|
43
|
+
if (held.probed === this.frame)
|
|
44
|
+
return held.phase;
|
|
45
|
+
const kept = reading.kept;
|
|
46
|
+
this.prime(owner, held, now, owner.elapsedAt(now), 0);
|
|
47
|
+
const base = this.base(owner, subject, now, held);
|
|
48
|
+
reading.kept = kept;
|
|
49
|
+
held.stepped = now;
|
|
50
|
+
held.probed = this.frame;
|
|
51
|
+
held.phase = base;
|
|
52
|
+
return base;
|
|
53
|
+
}
|
|
54
|
+
/** The weight a voice gives a subject this frame, with the setting already filled in. */
|
|
55
|
+
export function weigh(voice, subject, now, held) {
|
|
56
|
+
if (voice.owner !== null)
|
|
57
|
+
return this.weighOwned(voice, subject, now, held);
|
|
58
|
+
const base = this.base(voice, subject, now, held);
|
|
59
|
+
const fade = this.envelope(voice, now, held.shown);
|
|
60
|
+
return weighed(base, fade, voice.parts === null ? 1 : this.parting(voice, subject, now));
|
|
61
|
+
}
|
|
62
|
+
/** `weigh` for a voice an owner holds, its owners' weight folded into its fade. */
|
|
63
|
+
export function weighOwned(voice, subject, now, held) {
|
|
64
|
+
// Its owners first, since a signal on one takes over the setting `base` leaves for the patch.
|
|
65
|
+
const owned = this.ownedBy(voice, subject);
|
|
66
|
+
const base = this.base(voice, subject, now, held);
|
|
67
|
+
const fade = this.envelope(voice, now, held.shown) * owned;
|
|
68
|
+
return weighed(base, fade, voice.parts === null ? 1 : this.parting(voice, subject, now));
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* A voice's weight for a subject before its fade and ramp: its signal's, or its own number.
|
|
72
|
+
* `slot` is the subject's number when lanes ask.
|
|
73
|
+
*/
|
|
74
|
+
export function base(voice, subject, now, held, slot = -1) {
|
|
75
|
+
this.sending.voice = voice;
|
|
76
|
+
this.sending.subject = subject;
|
|
77
|
+
const signal = typeof voice.spec.weight === 'function' ? voice.spec.weight : null;
|
|
78
|
+
if (signal === null)
|
|
79
|
+
return voice.weight;
|
|
80
|
+
const was = held.replay && last(held.replay, now, true);
|
|
81
|
+
const base = was
|
|
82
|
+
? was.value
|
|
83
|
+
: voice.blend === null
|
|
84
|
+
? signal(subject, voice.setting)
|
|
85
|
+
: this.blended(voice, subject, held, slot);
|
|
86
|
+
if (signal.input && !was)
|
|
87
|
+
record(this, held, base);
|
|
88
|
+
return base;
|
|
89
|
+
}
|