@msb235/blits 0.6.0 → 0.7.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +152 -0
- package/README.md +31 -7
- package/dist/bare.js +7 -1
- package/dist/book.d.ts +7 -0
- package/dist/book.js +35 -4
- package/dist/chain.d.ts +2 -1
- package/dist/chain.js +7 -3
- package/dist/channels.d.ts +12 -19
- package/dist/channels.js +18 -118
- package/dist/clock.d.ts +9 -2
- package/dist/clock.js +10 -2
- package/dist/closed.d.ts +15 -0
- package/dist/closed.js +144 -0
- package/dist/color.d.ts +61 -0
- package/dist/color.js +229 -0
- package/dist/columns.js +2 -1
- package/dist/crowd.d.ts +1 -1
- package/dist/crowd.js +4 -1
- package/dist/cue.d.ts +2 -0
- package/dist/cue.js +80 -27
- package/dist/due.js +3 -0
- package/dist/fade.d.ts +13 -0
- package/dist/fade.js +57 -4
- package/dist/fill.d.ts +1 -1
- package/dist/fill.js +15 -8
- package/dist/fit.d.ts +144 -0
- package/dist/fit.js +181 -0
- package/dist/fold.d.ts +3 -3
- package/dist/fold.js +18 -11
- package/dist/gather.d.ts +6 -0
- package/dist/gather.js +18 -0
- package/dist/gone.d.ts +32 -0
- package/dist/gone.js +134 -0
- package/dist/handle.d.ts +27 -2
- package/dist/handle.js +60 -11
- package/dist/held.js +4 -0
- package/dist/history.d.ts +15 -2
- package/dist/history.js +56 -9
- package/dist/hosts.d.ts +2 -2
- package/dist/hosts.js +20 -6
- package/dist/index.d.ts +8 -2
- package/dist/index.js +5 -1
- package/dist/lane.d.ts +1 -1
- package/dist/lanes.d.ts +32 -9
- package/dist/lanes.js +53 -24
- package/dist/locus.d.ts +3 -3
- package/dist/locus.js +1 -1
- package/dist/marks.d.ts +3 -0
- package/dist/marks.js +80 -12
- package/dist/meet.d.ts +3 -2
- package/dist/meet.js +15 -3
- package/dist/mixer.d.ts +56 -36
- package/dist/mixer.js +158 -87
- package/dist/motion.d.ts +2 -199
- package/dist/motion.js +2 -652
- package/dist/motions.d.ts +266 -0
- package/dist/motions.js +724 -0
- package/dist/move.d.ts +1 -1
- package/dist/move.js +28 -8
- package/dist/owner.d.ts +1 -1
- package/dist/owner.js +3 -3
- package/dist/pace.d.ts +2 -0
- package/dist/pace.js +8 -0
- package/dist/pack.d.ts +31 -0
- package/dist/pack.js +65 -0
- package/dist/paging.d.ts +63 -0
- package/dist/paging.js +110 -0
- package/dist/patch.d.ts +3 -0
- package/dist/patch.js +2 -0
- package/dist/place.js +235 -43
- package/dist/project.d.ts +7 -4
- package/dist/project.js +117 -78
- package/dist/pull.js +4 -3
- package/dist/qualify.js +4 -2
- package/dist/revive.d.ts +58 -0
- package/dist/revive.js +158 -0
- package/dist/sample.d.ts +1 -1
- package/dist/sample.js +6 -1
- package/dist/scored.d.ts +57 -0
- package/dist/scored.js +196 -0
- package/dist/seek.d.ts +3 -0
- package/dist/seek.js +311 -0
- package/dist/spans.d.ts +29 -0
- package/dist/spans.js +165 -0
- package/dist/strays.d.ts +1 -1
- package/dist/strays.js +21 -0
- package/dist/tape.d.ts +21 -0
- package/dist/tape.js +65 -0
- package/dist/ticker.d.ts +28 -3
- package/dist/ticker.js +3 -0
- package/dist/transport.d.ts +125 -0
- package/dist/transport.js +283 -0
- package/dist/types.d.ts +404 -38
- package/dist/unpage.d.ts +8 -0
- package/dist/unpage.js +165 -0
- package/dist/voice.d.ts +75 -7
- package/dist/voice.js +119 -4
- package/dist/weigh.js +2 -2
- package/docs/schema.html +120 -31
- package/package.json +2 -1
package/dist/gather.js
CHANGED
|
@@ -171,6 +171,8 @@ export function foldInto(ch, slot, value, w) {
|
|
|
171
171
|
gatherInto(this, this.into, ch, slot, value, w);
|
|
172
172
|
return;
|
|
173
173
|
}
|
|
174
|
+
if (ch.op === 'last' && !this.gate(ch, w))
|
|
175
|
+
return;
|
|
174
176
|
const values = ch.values;
|
|
175
177
|
if (ch.scalar) {
|
|
176
178
|
values[slot] = foldNumber(ch.op, values[slot], value, w);
|
|
@@ -184,3 +186,19 @@ export function foldInto(ch, slot, value, w) {
|
|
|
184
186
|
values[base + a] = foldNumber(ch.op, values[base + a], v, w);
|
|
185
187
|
}
|
|
186
188
|
}
|
|
189
|
+
/**
|
|
190
|
+
* Whether a rest-less channel's contribution passes the band, for the record `one` is folding: the
|
|
191
|
+
* general path's `apply` decides it from the same band state on the same record, so a subject
|
|
192
|
+
* moving between the paths carries it.
|
|
193
|
+
*/
|
|
194
|
+
export function gate(ch, w) {
|
|
195
|
+
const lane = this.folding;
|
|
196
|
+
const rec = this.rec;
|
|
197
|
+
const i = lane.chans.indexOf(ch);
|
|
198
|
+
const bands = rec.bands ?? new Uint8Array(lane.chans.length);
|
|
199
|
+
rec.bands = bands;
|
|
200
|
+
const band = bands[i];
|
|
201
|
+
const on = this.host.passes(band === 0 ? undefined : band === 1, w);
|
|
202
|
+
bands[i] = on ? 1 : 2;
|
|
203
|
+
return on;
|
|
204
|
+
}
|
package/dist/gone.d.ts
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { Mixer } from './mixer.js';
|
|
2
|
+
import { Store } from './store.js';
|
|
3
|
+
import type { Voice } from './voice.js';
|
|
4
|
+
/**
|
|
5
|
+
* A mix's gone voices as `drop` and a freed slot look for them: those over every subject, and the
|
|
6
|
+
* rest by each subject they name. Kept in step with `gone`, which only grows by push and is
|
|
7
|
+
* otherwise replaced whole, so it catches up lazily and rebuilds only on a replacement it did not
|
|
8
|
+
* make itself. Under history a churning mix holds thousands, too many to walk per subject dropped.
|
|
9
|
+
*/
|
|
10
|
+
export declare class GoneIndex<I, O> {
|
|
11
|
+
/** The `gone` list it was built over, and how much of it. */
|
|
12
|
+
of: readonly Voice<I, O>[] | null;
|
|
13
|
+
count: number;
|
|
14
|
+
general: Voice<I, O>[];
|
|
15
|
+
named: Store<I, Voice<I, O>[]>;
|
|
16
|
+
/** The latest `doneAt` in `of`, and how far any voice finished before one listed ahead of it. */
|
|
17
|
+
latest: number;
|
|
18
|
+
slack: number;
|
|
19
|
+
}
|
|
20
|
+
/** The index caught up with `mix.gone`. */
|
|
21
|
+
export declare function goneIndex<I, O>(mix: Mixer<I, O>): GoneIndex<I, O>;
|
|
22
|
+
/**
|
|
23
|
+
* Keeps the gone voices `keep` says yes to, asking it once each, and takes the rest out of the
|
|
24
|
+
* index one by one. Leaves `gone` as it was when every one stays.
|
|
25
|
+
*/
|
|
26
|
+
export declare function keepGone<I, O>(mix: Mixer<I, O>, keep: (v: Voice<I, O>) => boolean): void;
|
|
27
|
+
/**
|
|
28
|
+
* Lets go of the gone voices that finished before `reach`. Voices go in about the order they
|
|
29
|
+
* finish, so only the front can hold one: past the point where a voice finished `slack` after
|
|
30
|
+
* `reach`, none behind it finished before `reach`.
|
|
31
|
+
*/
|
|
32
|
+
export declare function expireGone<I, O>(mix: Mixer<I, O>, reach: number): void;
|
package/dist/gone.js
ADDED
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { scoredCut } from './scored.js';
|
|
2
|
+
import { Store } from './store.js';
|
|
3
|
+
/**
|
|
4
|
+
* A mix's gone voices as `drop` and a freed slot look for them: those over every subject, and the
|
|
5
|
+
* rest by each subject they name. Kept in step with `gone`, which only grows by push and is
|
|
6
|
+
* otherwise replaced whole, so it catches up lazily and rebuilds only on a replacement it did not
|
|
7
|
+
* make itself. Under history a churning mix holds thousands, too many to walk per subject dropped.
|
|
8
|
+
*/
|
|
9
|
+
export class GoneIndex {
|
|
10
|
+
/** The `gone` list it was built over, and how much of it. */
|
|
11
|
+
of = null;
|
|
12
|
+
count = 0;
|
|
13
|
+
general = [];
|
|
14
|
+
named = new Store();
|
|
15
|
+
/** The latest `doneAt` in `of`, and how far any voice finished before one listed ahead of it. */
|
|
16
|
+
latest = Number.NEGATIVE_INFINITY;
|
|
17
|
+
slack = 0;
|
|
18
|
+
}
|
|
19
|
+
function add(ix, voice) {
|
|
20
|
+
if (voice.doneAt < ix.latest)
|
|
21
|
+
ix.slack = Math.max(ix.slack, ix.latest - voice.doneAt);
|
|
22
|
+
else
|
|
23
|
+
ix.latest = voice.doneAt;
|
|
24
|
+
if (voice.named === null) {
|
|
25
|
+
ix.general.push(voice);
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
for (const subject of voice.named) {
|
|
29
|
+
const list = ix.named.get(subject);
|
|
30
|
+
if (list === undefined)
|
|
31
|
+
ix.named.set(subject, [voice]);
|
|
32
|
+
else
|
|
33
|
+
list.push(voice);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
function remove(ix, out) {
|
|
37
|
+
let general = false;
|
|
38
|
+
for (const v of out) {
|
|
39
|
+
if (v.named === null) {
|
|
40
|
+
general = true;
|
|
41
|
+
continue;
|
|
42
|
+
}
|
|
43
|
+
for (const subject of v.named) {
|
|
44
|
+
const named = ix.named.get(subject);
|
|
45
|
+
if (named === undefined)
|
|
46
|
+
continue;
|
|
47
|
+
const at = named.indexOf(v);
|
|
48
|
+
if (at >= 0)
|
|
49
|
+
named.splice(at, 1);
|
|
50
|
+
if (named.length === 0)
|
|
51
|
+
ix.named.delete(subject);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
if (general) {
|
|
55
|
+
const leaving = new Set(out);
|
|
56
|
+
ix.general = ix.general.filter((v) => !leaving.has(v));
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
/** The index caught up with `mix.gone`. */
|
|
60
|
+
export function goneIndex(mix) {
|
|
61
|
+
const ix = mix.goneIx;
|
|
62
|
+
const list = mix.gone;
|
|
63
|
+
if (ix.of !== list || ix.count > list.length) {
|
|
64
|
+
ix.general = [];
|
|
65
|
+
ix.named = new Store();
|
|
66
|
+
ix.count = 0;
|
|
67
|
+
ix.of = list;
|
|
68
|
+
ix.latest = Number.NEGATIVE_INFINITY;
|
|
69
|
+
ix.slack = 0;
|
|
70
|
+
}
|
|
71
|
+
for (let i = ix.count; i < list.length; i++)
|
|
72
|
+
add(ix, list[i]);
|
|
73
|
+
ix.count = list.length;
|
|
74
|
+
return ix;
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Keeps the gone voices `keep` says yes to, asking it once each, and takes the rest out of the
|
|
78
|
+
* index one by one. Leaves `gone` as it was when every one stays.
|
|
79
|
+
*/
|
|
80
|
+
export function keepGone(mix, keep) {
|
|
81
|
+
const ix = goneIndex(mix);
|
|
82
|
+
const list = mix.gone;
|
|
83
|
+
let i = 0;
|
|
84
|
+
while (i < list.length && keep(list[i]))
|
|
85
|
+
i++;
|
|
86
|
+
if (i === list.length)
|
|
87
|
+
return;
|
|
88
|
+
const kept = list.slice(0, i);
|
|
89
|
+
const out = [list[i]];
|
|
90
|
+
for (i++; i < list.length; i++) {
|
|
91
|
+
const v = list[i];
|
|
92
|
+
(keep(v) ? kept : out).push(v);
|
|
93
|
+
}
|
|
94
|
+
remove(ix, out);
|
|
95
|
+
mix.gone = kept;
|
|
96
|
+
scoredCut(mix, list, out);
|
|
97
|
+
ix.of = kept;
|
|
98
|
+
ix.count = kept.length;
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Lets go of the gone voices that finished before `reach`. Voices go in about the order they
|
|
102
|
+
* finish, so only the front can hold one: past the point where a voice finished `slack` after
|
|
103
|
+
* `reach`, none behind it finished before `reach`.
|
|
104
|
+
*/
|
|
105
|
+
export function expireGone(mix, reach) {
|
|
106
|
+
const ix = goneIndex(mix);
|
|
107
|
+
if (!Number.isFinite(ix.slack)) {
|
|
108
|
+
keepGone(mix, (v) => v.doneAt >= reach);
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
const list = mix.gone;
|
|
112
|
+
const edge = reach + ix.slack;
|
|
113
|
+
let end = 0;
|
|
114
|
+
let any = false;
|
|
115
|
+
for (let seen = Number.NEGATIVE_INFINITY; end < list.length && seen < edge; end++) {
|
|
116
|
+
const done = list[end].doneAt;
|
|
117
|
+
if (done < reach)
|
|
118
|
+
any = true;
|
|
119
|
+
if (done > seen)
|
|
120
|
+
seen = done;
|
|
121
|
+
}
|
|
122
|
+
if (!any)
|
|
123
|
+
return;
|
|
124
|
+
const kept = [];
|
|
125
|
+
const out = [];
|
|
126
|
+
for (let i = 0; i < end; i++) {
|
|
127
|
+
const v = list[i];
|
|
128
|
+
(v.doneAt < reach ? out : kept).push(v);
|
|
129
|
+
}
|
|
130
|
+
list.splice(0, end, ...kept);
|
|
131
|
+
remove(ix, out);
|
|
132
|
+
scoredCut(mix, list, out);
|
|
133
|
+
ix.count = list.length;
|
|
134
|
+
}
|
package/dist/handle.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { FadeOptions, Handle } from './types.js';
|
|
1
|
+
import type { FadeOptions, FitResult, Handle } from './types.js';
|
|
2
2
|
import type { Voice } from './voice.js';
|
|
3
3
|
/** What a voice's handle asks of the mix that cued it: one per mix, shared by every handle. */
|
|
4
4
|
export interface HandleHost<I, O> {
|
|
@@ -9,17 +9,39 @@ export interface HandleHost<I, O> {
|
|
|
9
9
|
/** The voice's clock jumped: for an owner, so did every clock it holds. */
|
|
10
10
|
sought(voice: Voice<I, O>): void;
|
|
11
11
|
fade(voice: Voice<I, O>, opts: FadeOptions<I> | undefined): void;
|
|
12
|
+
rise(voice: Voice<I, O>, opts: {
|
|
13
|
+
over?: number;
|
|
14
|
+
} | undefined): void;
|
|
12
15
|
weightOf(voice: Voice<I, O>, subject: I): number;
|
|
16
|
+
/** Records a write the host made, which `again` makes once more where a seek replays it. */
|
|
17
|
+
record(label: string, again: () => void): void;
|
|
13
18
|
}
|
|
14
|
-
/**
|
|
19
|
+
/** What a handle answers for a voice a history store paged out, which left long ago. */
|
|
20
|
+
export interface Gone {
|
|
21
|
+
weight: number;
|
|
22
|
+
played: boolean;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* A voice's handle: the host's only way to control a voice once cued. A voice a history store
|
|
26
|
+
* pages out is let go of here too, so the handle answers as a gone voice's does, and a seek that
|
|
27
|
+
* revives it puts the revived voice behind the same handle.
|
|
28
|
+
*/
|
|
15
29
|
export declare class VoiceHandle<I, O> implements Handle<I> {
|
|
16
30
|
#private;
|
|
17
31
|
readonly id: number;
|
|
18
32
|
constructor(voice: Voice<I, O>, host: HandleHost<I, O>);
|
|
33
|
+
/** The voice behind a handle; null while a history store holds it. */
|
|
34
|
+
static voiceOf<I, O>(h: VoiceHandle<I, O>): Voice<I, O> | null;
|
|
35
|
+
/** Lets go of a voice a history store paged out. */
|
|
36
|
+
static page<I, O>(h: VoiceHandle<I, O>, gone: Gone): void;
|
|
37
|
+
/** Puts a voice revived from a history store behind the handle it had. */
|
|
38
|
+
static rehome<I, O>(h: VoiceHandle<I, O>, voice: Voice<I, O>): void;
|
|
19
39
|
get state(): Handle<I>['state'];
|
|
20
40
|
get owner(): Handle<I> | undefined;
|
|
21
41
|
get played(): Promise<boolean>;
|
|
22
42
|
get done(): Promise<void>;
|
|
43
|
+
/** How a span's children were last fitted; undefined for any voice but a span. */
|
|
44
|
+
get result(): FitResult | undefined;
|
|
23
45
|
get weight(): number;
|
|
24
46
|
set weight(w: number);
|
|
25
47
|
get rate(): number;
|
|
@@ -27,5 +49,8 @@ export declare class VoiceHandle<I, O> implements Handle<I> {
|
|
|
27
49
|
ramp(r: number, over: number): void;
|
|
28
50
|
seek(elapsed: number): void;
|
|
29
51
|
fade(opts?: FadeOptions<I>): void;
|
|
52
|
+
rise(opts?: {
|
|
53
|
+
over?: number;
|
|
54
|
+
}): void;
|
|
30
55
|
weightOf(subject: I): number;
|
|
31
56
|
}
|
package/dist/handle.js
CHANGED
|
@@ -1,56 +1,105 @@
|
|
|
1
1
|
import { retime } from './clock.js';
|
|
2
|
-
/**
|
|
2
|
+
/**
|
|
3
|
+
* A voice's handle: the host's only way to control a voice once cued. A voice a history store
|
|
4
|
+
* pages out is let go of here too, so the handle answers as a gone voice's does, and a seek that
|
|
5
|
+
* revives it puts the revived voice behind the same handle.
|
|
6
|
+
*/
|
|
3
7
|
export class VoiceHandle {
|
|
4
8
|
id;
|
|
5
9
|
#voice;
|
|
10
|
+
#gone = null;
|
|
6
11
|
#host;
|
|
7
12
|
constructor(voice, host) {
|
|
8
13
|
this.id = voice.id;
|
|
9
14
|
this.#voice = voice;
|
|
10
15
|
this.#host = host;
|
|
11
16
|
}
|
|
17
|
+
/** The voice behind a handle; null while a history store holds it. */
|
|
18
|
+
static voiceOf(h) {
|
|
19
|
+
return h.#voice;
|
|
20
|
+
}
|
|
21
|
+
/** Lets go of a voice a history store paged out. */
|
|
22
|
+
static page(h, gone) {
|
|
23
|
+
h.#voice = null;
|
|
24
|
+
h.#gone = gone;
|
|
25
|
+
}
|
|
26
|
+
/** Puts a voice revived from a history store behind the handle it had. */
|
|
27
|
+
static rehome(h, voice) {
|
|
28
|
+
h.#voice = voice;
|
|
29
|
+
h.#gone = null;
|
|
30
|
+
}
|
|
12
31
|
get state() {
|
|
13
|
-
return this.#voice
|
|
32
|
+
return this.#voice?.state ?? 'done';
|
|
14
33
|
}
|
|
15
34
|
get owner() {
|
|
16
|
-
return this.#voice
|
|
35
|
+
return this.#voice?.owner?.handle ?? undefined;
|
|
17
36
|
}
|
|
18
37
|
get played() {
|
|
19
|
-
return this.#voice.played;
|
|
38
|
+
return this.#voice?.played ?? Promise.resolve(this.#gone?.played ?? false);
|
|
20
39
|
}
|
|
21
40
|
get done() {
|
|
22
|
-
return this.#voice.
|
|
41
|
+
return this.#voice?.done ?? Promise.resolve();
|
|
42
|
+
}
|
|
43
|
+
/** How a span's children were last fitted; undefined for any voice but a span. */
|
|
44
|
+
get result() {
|
|
45
|
+
return this.#voice?.fitting?.report;
|
|
23
46
|
}
|
|
24
47
|
get weight() {
|
|
25
|
-
return this.#voice
|
|
48
|
+
return this.#voice?.weight ?? this.#gone?.weight ?? 0;
|
|
26
49
|
}
|
|
50
|
+
// A write to a paged voice changes nothing: it had left when the host made it, as it has on replay.
|
|
27
51
|
set weight(w) {
|
|
28
|
-
|
|
29
|
-
|
|
52
|
+
const voice = this.#voice;
|
|
53
|
+
if (voice === null)
|
|
54
|
+
return;
|
|
55
|
+
voice.weight = w;
|
|
56
|
+
this.#host.changed(voice);
|
|
57
|
+
this.#host.record('weight', () => {
|
|
58
|
+
this.weight = w;
|
|
59
|
+
});
|
|
30
60
|
}
|
|
31
61
|
get rate() {
|
|
32
|
-
|
|
62
|
+
const voice = this.#voice;
|
|
63
|
+
return voice === null ? 0 : voice.rateAt(this.#host.nowFor(voice));
|
|
33
64
|
}
|
|
34
65
|
set rate(r) {
|
|
35
66
|
this.ramp(r, 0);
|
|
36
67
|
}
|
|
37
68
|
ramp(r, over) {
|
|
38
69
|
const voice = this.#voice;
|
|
70
|
+
if (voice === null)
|
|
71
|
+
return;
|
|
39
72
|
retime(voice, this.#host.nowFor(voice), r, over);
|
|
40
73
|
this.#host.changed(voice);
|
|
74
|
+
this.#host.record('rate', () => this.ramp(r, over));
|
|
41
75
|
}
|
|
42
76
|
seek(elapsed) {
|
|
43
77
|
const voice = this.#voice;
|
|
78
|
+
if (voice === null)
|
|
79
|
+
return;
|
|
44
80
|
voice.rebase(this.#host.nowFor(voice));
|
|
45
81
|
voice.anchorElapsed = elapsed;
|
|
46
82
|
voice.seeks++;
|
|
47
83
|
this.#host.sought(voice);
|
|
48
84
|
this.#host.changed(voice);
|
|
85
|
+
this.#host.record('seek', () => this.seek(elapsed));
|
|
49
86
|
}
|
|
50
87
|
fade(opts) {
|
|
51
|
-
this.#
|
|
88
|
+
const voice = this.#voice;
|
|
89
|
+
if (voice === null)
|
|
90
|
+
return;
|
|
91
|
+
this.#host.fade(voice, opts);
|
|
92
|
+
this.#host.record('fade', () => this.fade(opts));
|
|
93
|
+
}
|
|
94
|
+
rise(opts) {
|
|
95
|
+
const voice = this.#voice;
|
|
96
|
+
if (voice === null)
|
|
97
|
+
return;
|
|
98
|
+
this.#host.rise(voice, opts);
|
|
99
|
+
this.#host.record('rise', () => this.rise(opts));
|
|
52
100
|
}
|
|
53
101
|
weightOf(subject) {
|
|
54
|
-
|
|
102
|
+
const voice = this.#voice;
|
|
103
|
+
return voice === null ? 0 : this.#host.weightOf(voice, subject);
|
|
55
104
|
}
|
|
56
105
|
}
|
package/dist/held.js
CHANGED
|
@@ -49,6 +49,10 @@ export function held(voice, subject, now, slot) {
|
|
|
49
49
|
voice.subjects.set(subject, none);
|
|
50
50
|
return none;
|
|
51
51
|
}
|
|
52
|
+
if (this.keys !== null) {
|
|
53
|
+
voice.keyed ??= new Set();
|
|
54
|
+
voice.keyed.add(this.keys.key(subject));
|
|
55
|
+
}
|
|
52
56
|
const delay = voice.spec.stagger ? voice.spec.stagger(subject) : 0;
|
|
53
57
|
const since = this.sinceOf(voice, delay);
|
|
54
58
|
held = {
|
package/dist/history.d.ts
CHANGED
|
@@ -17,6 +17,19 @@ export declare function copyHeld<I, O>(voice: Voice<I, O>, h: Subject<unknown>):
|
|
|
17
17
|
/** Under `history` with `inputs`, copies the host fields any voice reads, once a frame, when they changed. */
|
|
18
18
|
export declare function recordHost<I, O>(mix: Mixer<I, O>, now: number): void;
|
|
19
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;
|
|
20
|
+
export declare function record<I, O>(mix: Mixer<I, O>, voice: Voice<I, O>, subject: I, held: Subject<unknown>, value: number): void;
|
|
21
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;
|
|
22
|
+
export declare function remember<I, O>(mix: Mixer<I, O>, voice: Voice<I, O>, subject: I, held: Subject<unknown>): void;
|
|
23
|
+
/**
|
|
24
|
+
* Under history, keeps the record a subject had when it left a voice, faded out of it or dropped,
|
|
25
|
+
* for a seek or a read back to before then; without history, keeps nothing.
|
|
26
|
+
*/
|
|
27
|
+
export declare function leave<I, O>(mix: Mixer<I, O>, voice: Voice<I, O>, subject: I, held: Subject<unknown> | undefined): void;
|
|
28
|
+
/** The record a subject had at mix time `t` and left a voice with after it, if it left after `t`. */
|
|
29
|
+
export declare function leftAt<I>(left: readonly {
|
|
30
|
+
subject: I;
|
|
31
|
+
at: number;
|
|
32
|
+
held: Subject<unknown>;
|
|
33
|
+
}[] | null, subject: I, t: number): Subject<unknown> | undefined;
|
|
34
|
+
/** The mix time a motion patch keeps a released subject from, and how far back, under history. */
|
|
35
|
+
export declare function releasing<I, O>(mix: Mixer<I, O>): [at?: number, reach?: number];
|
package/dist/history.js
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import { elapsedWith } from './clock.js';
|
|
2
2
|
import { clone } from './clone.js';
|
|
3
3
|
import { schedule } from './due.js';
|
|
4
|
+
import { packHeld } from './pack.js';
|
|
5
|
+
import { pageOut } from './paging.js';
|
|
6
|
+
import { scoreTouched } from './scored.js';
|
|
4
7
|
import { none } from './voice.js';
|
|
5
8
|
/**
|
|
6
9
|
* The last entry taken before `t`, or at it where `inclusive` says so, as it does for a change a
|
|
@@ -50,6 +53,7 @@ function ownerThen(mix, owner, t) {
|
|
|
50
53
|
}
|
|
51
54
|
/** Records a change to a voice's controls under `history`, and lets go of what it no longer reaches. */
|
|
52
55
|
export function noted(mix, voice) {
|
|
56
|
+
scoreTouched(mix, voice, true);
|
|
53
57
|
mix.stir();
|
|
54
58
|
schedule(mix, voice);
|
|
55
59
|
const log = voice.log;
|
|
@@ -61,7 +65,15 @@ export function noted(mix, voice) {
|
|
|
61
65
|
while (drop + 1 < log.length && log[drop + 1].at <= reach)
|
|
62
66
|
drop++;
|
|
63
67
|
if (drop > 0)
|
|
64
|
-
log.splice(0, drop);
|
|
68
|
+
pageOut(mix, 'controls', voice.id, undefined, log.splice(0, drop), copied, reach);
|
|
69
|
+
}
|
|
70
|
+
const copied = (e) => ({ ...e });
|
|
71
|
+
/** Takes off the front of a list kept by time what a read back to `reach` no longer needs. */
|
|
72
|
+
function older(list, reach) {
|
|
73
|
+
let n = 0;
|
|
74
|
+
while (n + 1 < list.length && list[n + 1].at <= reach)
|
|
75
|
+
n++;
|
|
76
|
+
return n === 0 ? [] : list.splice(0, n);
|
|
65
77
|
}
|
|
66
78
|
/** A copy of a subject's record that shares nothing a read could change. */
|
|
67
79
|
export function copyHeld(voice, h) {
|
|
@@ -77,6 +89,7 @@ export function copyHeld(voice, h) {
|
|
|
77
89
|
bands: h.bands === null ? null : h.bands.slice(),
|
|
78
90
|
state: h.state === undefined ? undefined : patch.clone ? patch.clone(h.state) : clone(h.state),
|
|
79
91
|
kept,
|
|
92
|
+
unkept: h.unkept?.map(clone),
|
|
80
93
|
probed: Number.NaN,
|
|
81
94
|
delta: null,
|
|
82
95
|
phase: 0,
|
|
@@ -107,11 +120,10 @@ export function recordHost(mix, now) {
|
|
|
107
120
|
return;
|
|
108
121
|
log.push({ at: now, fields });
|
|
109
122
|
const reach = now - mix.opts.history.ms;
|
|
110
|
-
|
|
111
|
-
log.shift();
|
|
123
|
+
pageOut(mix, 'host', undefined, undefined, older(log, reach), (e) => e.fields, reach);
|
|
112
124
|
}
|
|
113
125
|
/** Under `history` with `inputs`, keeps what an input signal read, each time it changes. */
|
|
114
|
-
export function record(mix, held, value) {
|
|
126
|
+
export function record(mix, voice, subject, held, value) {
|
|
115
127
|
const history = mix.opts.history;
|
|
116
128
|
if (!history?.inputs || mix.projecting)
|
|
117
129
|
return;
|
|
@@ -125,11 +137,12 @@ export function record(mix, held, value) {
|
|
|
125
137
|
}
|
|
126
138
|
inputs.push({ at: mix.now, value });
|
|
127
139
|
const reach = mix.now - history.ms;
|
|
128
|
-
|
|
129
|
-
|
|
140
|
+
const out = older(inputs, reach);
|
|
141
|
+
if (out.length > 0)
|
|
142
|
+
pageOut(mix, 'input', voice.id, mix.keys?.key(subject), out, (e) => e.value, reach);
|
|
130
143
|
}
|
|
131
144
|
/** Under `history`, keeps a copy of a stateful voice's record for this subject every so often. */
|
|
132
|
-
export function remember(mix, voice, held) {
|
|
145
|
+
export function remember(mix, voice, subject, held) {
|
|
133
146
|
const history = mix.opts.history;
|
|
134
147
|
if (!history || mix.projecting)
|
|
135
148
|
return;
|
|
@@ -144,6 +157,40 @@ export function remember(mix, voice, held) {
|
|
|
144
157
|
return;
|
|
145
158
|
snaps.push({ at: mix.now, held: copyHeld(voice, held) });
|
|
146
159
|
const reach = mix.now - history.ms;
|
|
147
|
-
|
|
148
|
-
|
|
160
|
+
const out = older(snaps, reach);
|
|
161
|
+
if (out.length > 0)
|
|
162
|
+
pageOut(mix, 'snap', voice.id, mix.keys?.key(subject), out, (e) => packHeld(voice, e.held), reach);
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* Under history, keeps the record a subject had when it left a voice, faded out of it or dropped,
|
|
166
|
+
* for a seek or a read back to before then; without history, keeps nothing.
|
|
167
|
+
*/
|
|
168
|
+
export function leave(mix, voice, subject, held) {
|
|
169
|
+
const history = mix.opts.history;
|
|
170
|
+
if (history === undefined || held === undefined)
|
|
171
|
+
return;
|
|
172
|
+
const reach = mix.now - history.ms;
|
|
173
|
+
const left = [];
|
|
174
|
+
for (const e of voice.left ?? [])
|
|
175
|
+
if (e.at >= reach)
|
|
176
|
+
left.push(e);
|
|
177
|
+
else if (mix.keys !== null)
|
|
178
|
+
pageOut(mix, 'left', voice.id, mix.keys.key(e.subject), [e], (x) => packHeld(voice, x.held), reach);
|
|
179
|
+
left.push({ subject, at: Number.isNaN(mix.now) ? Number.NEGATIVE_INFINITY : mix.now, held });
|
|
180
|
+
voice.left = left;
|
|
181
|
+
}
|
|
182
|
+
/** The record a subject had at mix time `t` and left a voice with after it, if it left after `t`. */
|
|
183
|
+
export function leftAt(left, subject, t) {
|
|
184
|
+
let found;
|
|
185
|
+
for (const e of left ?? [])
|
|
186
|
+
if (e.at > t && Object.is(e.subject, subject) && (found === undefined || e.at < found.at))
|
|
187
|
+
found = e;
|
|
188
|
+
return found?.held;
|
|
189
|
+
}
|
|
190
|
+
/** The mix time a motion patch keeps a released subject from, and how far back, under history. */
|
|
191
|
+
export function releasing(mix) {
|
|
192
|
+
const history = mix.opts.history;
|
|
193
|
+
if (history === undefined || Number.isNaN(mix.now))
|
|
194
|
+
return [];
|
|
195
|
+
return [mix.now, mix.now - history.ms];
|
|
149
196
|
}
|
package/dist/hosts.d.ts
CHANGED
|
@@ -7,8 +7,8 @@ export declare function laneHost<I, O>(mix: Mixer<I, O>): LaneHost<I, O>;
|
|
|
7
7
|
/**
|
|
8
8
|
* Whether a voice's patch and spec can run on a lane, its channels aside. A voice whose patch has
|
|
9
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
|
|
11
|
-
*
|
|
10
|
+
* one that starts keeping partway through may advance that state once for one subject probed the
|
|
11
|
+
* frame before it starts and not on that frame, which declaring `state` avoids.
|
|
12
12
|
*/
|
|
13
13
|
export declare function fits<I, O>(mix: Mixer<I, O>, voice: Voice<I, O>): boolean;
|
|
14
14
|
/** A voice's patch kept state on a record: it is stateful from now on. */
|
package/dist/hosts.js
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import { Book } from './book.js';
|
|
2
|
+
import { numericOf } from './channels.js';
|
|
3
|
+
import { crowdable } from './crowd.js';
|
|
2
4
|
import { schedule } from './due.js';
|
|
3
|
-
import { beginFade, fadeSubject } from './fade.js';
|
|
5
|
+
import { beginFade, beginRise, fadeAt, fadeSubject } from './fade.js';
|
|
6
|
+
import { goneIndex } from './gone.js';
|
|
4
7
|
import { VoiceHandle } from './handle.js';
|
|
5
8
|
import { noted, remember } from './history.js';
|
|
6
9
|
import { hostOf, listed, markOf, readingAt } from './marks.js';
|
|
@@ -8,6 +11,7 @@ import { nextFrame } from './move.js';
|
|
|
8
11
|
import { descendants, ownWeight, signalled } from './owner.js';
|
|
9
12
|
import { localNow } from './place.js';
|
|
10
13
|
import { reading } from './reading.js';
|
|
14
|
+
import { record } from './tape.js';
|
|
11
15
|
import { unreach } from './unreached.js';
|
|
12
16
|
export function book(mix, opts) {
|
|
13
17
|
if (mix.projecting)
|
|
@@ -42,6 +46,7 @@ export function laneHost(mix) {
|
|
|
42
46
|
},
|
|
43
47
|
channels: mix.channels,
|
|
44
48
|
names: mix.names,
|
|
49
|
+
clock: mix,
|
|
45
50
|
fits: (voice) => fits(mix, voice),
|
|
46
51
|
meet: (voice, subject, head, slot) => {
|
|
47
52
|
for (let held = head; held !== null; held = held.next)
|
|
@@ -53,9 +58,10 @@ export function laneHost(mix) {
|
|
|
53
58
|
// Only a voice over every subject keeps bits, and one gone may still be read back.
|
|
54
59
|
for (const voice of mix.general)
|
|
55
60
|
unreach(voice, slot, false);
|
|
56
|
-
for (const voice of mix.
|
|
61
|
+
for (const voice of goneIndex(mix).general)
|
|
57
62
|
unreach(voice, slot, false);
|
|
58
63
|
},
|
|
64
|
+
passes: (was, w) => mix.passes(was, w),
|
|
59
65
|
naming: (subject) => (mix.naming === 0 ? undefined : mix.named.get(subject)),
|
|
60
66
|
slotOf: (subject) => mix.chains.get(subject)?.slot ?? -1,
|
|
61
67
|
envelope: (voice, since) => voice.owner === null
|
|
@@ -84,15 +90,15 @@ export function laneHost(mix) {
|
|
|
84
90
|
reading.horizon = mix.horizonFor(voice, delay, mix.now);
|
|
85
91
|
},
|
|
86
92
|
keeps: mix.opts.history !== undefined,
|
|
87
|
-
after: (voice, held) => remember(mix, voice, held),
|
|
93
|
+
after: (voice, subject, held) => remember(mix, voice, subject, held),
|
|
88
94
|
kept: (voice) => stateful(mix, voice),
|
|
89
95
|
};
|
|
90
96
|
}
|
|
91
97
|
/**
|
|
92
98
|
* Whether a voice's patch and spec can run on a lane, its channels aside. A voice whose patch has
|
|
93
99
|
* 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
|
|
95
|
-
*
|
|
100
|
+
* one that starts keeping partway through may advance that state once for one subject probed the
|
|
101
|
+
* frame before it starts and not on that frame, which declaring `state` avoids.
|
|
96
102
|
*/
|
|
97
103
|
export function fits(mix, voice) {
|
|
98
104
|
const spec = voice.spec;
|
|
@@ -112,6 +118,10 @@ export function fits(mix, voice) {
|
|
|
112
118
|
return false;
|
|
113
119
|
if (spec.from === 'current')
|
|
114
120
|
return false;
|
|
121
|
+
// A rest-less channel's band is read on the record `one` folds; crowds, motions and loci fold elsewhere.
|
|
122
|
+
if ((voice.motion !== undefined || spec.locus !== undefined || crowdable(voice)) &&
|
|
123
|
+
voice.slots.some((s) => numericOf(mix.channels[s])?.op === 'last'))
|
|
124
|
+
return false;
|
|
115
125
|
if (voice.out?.rest)
|
|
116
126
|
return false;
|
|
117
127
|
if (patch.state !== undefined || patch.step !== undefined)
|
|
@@ -159,9 +169,12 @@ function handleHost(mix) {
|
|
|
159
169
|
throw new Error('blits: an owner fades as a whole, not by subject or at rest');
|
|
160
170
|
if (opts !== undefined && 'subject' in opts)
|
|
161
171
|
fadeSubject(mix, voice, opts.subject, opts.over);
|
|
172
|
+
else if (typeof opts?.at === 'number')
|
|
173
|
+
fadeAt(mix, voice, opts.at, opts.over);
|
|
162
174
|
else
|
|
163
|
-
beginFade(mix, voice, opts ?? {});
|
|
175
|
+
beginFade(mix, voice, (opts ?? {}));
|
|
164
176
|
},
|
|
177
|
+
rise: (voice, opts) => beginRise(mix, voice, opts ?? {}),
|
|
165
178
|
weightOf: (voice, subject) => {
|
|
166
179
|
if (voice.state === 'done')
|
|
167
180
|
return 0;
|
|
@@ -174,5 +187,6 @@ function handleHost(mix) {
|
|
|
174
187
|
}
|
|
175
188
|
return voice.subjects.get(subject)?.weight ?? 0;
|
|
176
189
|
},
|
|
190
|
+
record: (label, again) => record(mix, label, again),
|
|
177
191
|
};
|
|
178
192
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,11 +1,17 @@
|
|
|
1
1
|
export type { NumberOptions } from './channels.js';
|
|
2
|
-
export {
|
|
2
|
+
export { kit, last, max, mul, sum, vec } from './channels.js';
|
|
3
|
+
export type { ColorOptions } from './color.js';
|
|
4
|
+
export { color, css, hex, mixHex, oklab, toHex } from './color.js';
|
|
5
|
+
export type { Claim, Fit, FitPlan, Order, SpanClaim, Strength } from './fit.js';
|
|
6
|
+
export { conclude, condense, lax, layout, overrun, pipe, plain, shed, } from './fit.js';
|
|
3
7
|
export { mix, mixer } from './mixer.js';
|
|
4
8
|
export type { Motion, Moving, PerSubject, Value } from './motion.js';
|
|
5
9
|
export { glide, spring, tween } from './motion.js';
|
|
10
|
+
export { HistoryMiss } from './paging.js';
|
|
6
11
|
export type { KeysOptions, PatchOptions } from './patch.js';
|
|
7
12
|
export { keys, patch } from './patch.js';
|
|
8
13
|
export { gate, lag, level, peak, slew } from './signals.js';
|
|
9
14
|
export type { Ticked, Ticker, TickerOptions } from './ticker.js';
|
|
10
15
|
export { ticker } from './ticker.js';
|
|
11
|
-
export
|
|
16
|
+
export { transport } from './transport.js';
|
|
17
|
+
export type { Anchor, BookedHit, Booker, BookOptions, Channel, Columns, Described, Doubt, Easing, Engine, FadeOptions, FadeSpec, FitResult, Handle, HistoryOptions, HistoryStore, Hit, Keyframe, Kit, Mark, Marked, Mix, MixOptions, MotionSpec, OwnerSpec, Paged, PagedStream, Patch, Placement, Projection, Query, Sent, Setting, Signal, SpanHandle, SpanHints, SpanSpec, Tape, TapeBranch, TapeMaker, TapeOp, Transport, TransportOptions, TransportProjection, VoiceSpec, } from './types.js';
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
|
-
export {
|
|
1
|
+
export { kit, last, max, mul, sum, vec } from './channels.js';
|
|
2
|
+
export { color, css, hex, mixHex, oklab, toHex } from './color.js';
|
|
3
|
+
export { conclude, condense, lax, layout, overrun, pipe, plain, shed, } from './fit.js';
|
|
2
4
|
export { mix, mixer } from './mixer.js';
|
|
3
5
|
export { glide, spring, tween } from './motion.js';
|
|
6
|
+
export { HistoryMiss } from './paging.js';
|
|
4
7
|
export { keys, patch } from './patch.js';
|
|
5
8
|
export { gate, lag, level, peak, slew } from './signals.js';
|
|
6
9
|
export { ticker } from './ticker.js';
|
|
10
|
+
export { transport } from './transport.js';
|
package/dist/lane.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { Numeric } from './channels.js';
|
|
2
|
-
import {
|
|
2
|
+
import type { Motions } from './motions.js';
|
|
3
3
|
import type { Scratch } from './patch.js';
|
|
4
4
|
import type { Subject, Voice } from './voice.js';
|
|
5
5
|
/** One laned channel: its arithmetic, and every numbered subject's folded value, `axes` per subject. */
|