@msb235/blits 0.7.1 → 0.8.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 +193 -0
- package/README.md +22 -4
- package/dist/book.js +4 -4
- package/dist/chain.d.ts +1 -1
- package/dist/chain.js +57 -54
- package/dist/channels.d.ts +26 -5
- package/dist/channels.js +12 -8
- package/dist/clock.d.ts +8 -1
- package/dist/clock.js +14 -3
- package/dist/clone.d.ts +8 -0
- package/dist/clone.js +22 -0
- package/dist/closed.d.ts +1 -1
- package/dist/closed.js +3 -2
- package/dist/color.d.ts +0 -11
- package/dist/color.js +4 -24
- package/dist/columns.js +0 -1
- package/dist/copyout.d.ts +6 -0
- package/dist/copyout.js +38 -0
- package/dist/crowd.d.ts +7 -5
- package/dist/crowd.js +32 -18
- package/dist/cue.d.ts +3 -1
- package/dist/cue.js +31 -20
- package/dist/departed.d.ts +34 -0
- package/dist/departed.js +73 -0
- package/dist/easing.d.ts +5 -0
- package/dist/easing.js +31 -2
- package/dist/everyone.d.ts +56 -0
- package/dist/everyone.js +221 -0
- package/dist/fade.d.ts +8 -1
- package/dist/fade.js +34 -6
- package/dist/fill.d.ts +22 -13
- package/dist/fill.js +104 -41
- package/dist/fit.js +2 -1
- package/dist/fold.d.ts +37 -0
- package/dist/fold.js +216 -31
- package/dist/frames.d.ts +21 -0
- package/dist/frames.js +59 -0
- package/dist/gather.d.ts +15 -9
- package/dist/gather.js +60 -25
- package/dist/gone.js +10 -0
- package/dist/handle.d.ts +16 -5
- package/dist/handle.js +56 -6
- package/dist/held.d.ts +8 -5
- package/dist/held.js +56 -58
- package/dist/history.d.ts +17 -13
- package/dist/history.js +47 -28
- package/dist/hosts.js +55 -8
- package/dist/index.d.ts +9 -4
- package/dist/index.js +5 -2
- package/dist/keyfill.d.ts +7 -0
- package/dist/keyfill.js +124 -0
- package/dist/keyrows.d.ts +1 -0
- package/dist/keyrows.js +1 -1
- package/dist/lane.d.ts +32 -2
- package/dist/lane.js +5 -0
- package/dist/lanes.d.ts +32 -8
- package/dist/lanes.js +69 -37
- package/dist/leavings.d.ts +26 -0
- package/dist/leavings.js +70 -0
- package/dist/locus.js +2 -2
- package/dist/meet.js +14 -8
- package/dist/mixer.d.ts +39 -7
- package/dist/mixer.js +50 -10
- package/dist/motion.d.ts +2 -0
- package/dist/motion.js +4 -1
- package/dist/motions.d.ts +22 -15
- package/dist/motions.js +40 -34
- package/dist/move.js +23 -16
- package/dist/moved.d.ts +11 -0
- package/dist/moved.js +29 -0
- package/dist/origin.d.ts +19 -0
- package/dist/origin.js +68 -0
- package/dist/owed.d.ts +4 -4
- package/dist/owed.js +8 -8
- package/dist/owner.d.ts +1 -1
- package/dist/owner.js +2 -2
- package/dist/pace.d.ts +13 -5
- package/dist/pace.js +21 -8
- package/dist/pack.d.ts +3 -1
- package/dist/pack.js +15 -24
- package/dist/paging.d.ts +8 -2
- package/dist/paging.js +39 -4
- package/dist/patch.d.ts +14 -9
- package/dist/patch.js +83 -21
- package/dist/place.js +18 -3
- package/dist/project.d.ts +12 -1
- package/dist/project.js +101 -83
- package/dist/pull.d.ts +2 -0
- package/dist/pull.js +8 -0
- package/dist/qualify.js +10 -2
- package/dist/reading.d.ts +5 -0
- package/dist/reading.js +5 -0
- package/dist/record.d.ts +9 -0
- package/dist/record.js +42 -0
- package/dist/relink.d.ts +11 -5
- package/dist/relink.js +7 -5
- package/dist/restored.d.ts +41 -0
- package/dist/restored.js +91 -0
- package/dist/revive.d.ts +10 -3
- package/dist/revive.js +31 -23
- package/dist/roster.d.ts +11 -0
- package/dist/roster.js +46 -0
- package/dist/rotation.d.ts +29 -0
- package/dist/rotation.js +91 -0
- package/dist/rows.d.ts +5 -5
- package/dist/rows.js +5 -5
- package/dist/sample.d.ts +3 -2
- package/dist/sample.js +32 -15
- package/dist/seek.js +97 -114
- package/dist/shared.d.ts +6 -0
- package/dist/shared.js +15 -0
- package/dist/signals.d.ts +24 -3
- package/dist/signals.js +50 -11
- package/dist/spans.js +7 -0
- package/dist/stack.d.ts +11 -0
- package/dist/stack.js +47 -0
- package/dist/standing.d.ts +10 -0
- package/dist/standing.js +79 -0
- package/dist/store.d.ts +3 -3
- package/dist/store.js +3 -3
- package/dist/strays.js +3 -0
- package/dist/tape.d.ts +8 -5
- package/dist/tape.js +21 -13
- package/dist/testing.d.ts +35 -0
- package/dist/testing.js +116 -0
- package/dist/touch.d.ts +6 -0
- package/dist/touch.js +28 -0
- package/dist/transport.d.ts +45 -6
- package/dist/transport.js +64 -19
- package/dist/turns.d.ts +18 -0
- package/dist/turns.js +72 -0
- package/dist/tweened.js +1 -1
- package/dist/types/channel.d.ts +46 -0
- package/dist/types/channel.js +1 -0
- package/dist/types/history.d.ts +139 -0
- package/dist/types/history.js +1 -0
- package/dist/types/mix.d.ts +336 -0
- package/dist/types/mix.js +1 -0
- package/dist/types/patch.d.ts +187 -0
- package/dist/types/patch.js +1 -0
- package/dist/types/place.d.ts +157 -0
- package/dist/types/place.js +1 -0
- package/dist/types/transport.d.ts +71 -0
- package/dist/types/transport.js +1 -0
- package/dist/types/voice.d.ts +323 -0
- package/dist/types/voice.js +1 -0
- package/dist/types.d.ts +7 -1162
- package/dist/unpage.d.ts +2 -2
- package/dist/unpage.js +53 -25
- package/dist/voice.d.ts +90 -22
- package/dist/voice.js +105 -43
- package/dist/wave.d.ts +42 -0
- package/dist/wave.js +62 -0
- package/dist/weigh.js +1 -1
- package/docs/schema.html +42 -36
- package/package.json +8 -3
package/dist/record.js
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A subject's record for `voice` holding every field one can come to hold, in one order, so every
|
|
3
|
+
* record in a mix shares one hidden class: a record built elsewhere, or given `base`, `snaps` or
|
|
4
|
+
* `from` later, took a class of its own, and a fold walking a chain of several kinds reads each
|
|
5
|
+
* field the slow way. The caller sets what it knows; nothing here takes a number, which a call V8
|
|
6
|
+
* does not inline would box.
|
|
7
|
+
*/
|
|
8
|
+
export function record(voice, reaches, state) {
|
|
9
|
+
return {
|
|
10
|
+
reaches,
|
|
11
|
+
delay: 0,
|
|
12
|
+
since: 0,
|
|
13
|
+
shown: 0,
|
|
14
|
+
weight: 0,
|
|
15
|
+
rested: false,
|
|
16
|
+
bands: null,
|
|
17
|
+
state,
|
|
18
|
+
stepped: 0,
|
|
19
|
+
ticks: 0,
|
|
20
|
+
probed: Number.NaN,
|
|
21
|
+
delta: null,
|
|
22
|
+
phase: 0,
|
|
23
|
+
seeks: 0,
|
|
24
|
+
rebuilt: 0,
|
|
25
|
+
base: undefined,
|
|
26
|
+
slope: undefined,
|
|
27
|
+
kept: null,
|
|
28
|
+
unkept: undefined,
|
|
29
|
+
snaps: undefined,
|
|
30
|
+
from: undefined,
|
|
31
|
+
unknown: undefined,
|
|
32
|
+
inputs: undefined,
|
|
33
|
+
replay: undefined,
|
|
34
|
+
voice,
|
|
35
|
+
next: null,
|
|
36
|
+
version: Number.NaN,
|
|
37
|
+
loci: null,
|
|
38
|
+
slot: -1,
|
|
39
|
+
run: -1,
|
|
40
|
+
freed: -1,
|
|
41
|
+
};
|
|
42
|
+
}
|
package/dist/relink.d.ts
CHANGED
|
@@ -3,12 +3,16 @@ import type { Subject } from './voice.js';
|
|
|
3
3
|
interface Ordered {
|
|
4
4
|
readonly id: number;
|
|
5
5
|
}
|
|
6
|
+
/** What `patch` asks a mix: a voice's record for a subject where a full relink would link it. */
|
|
7
|
+
interface Linker<V, I> {
|
|
8
|
+
linkable(voice: V, subject: I, slot: number): Subject<unknown> | null;
|
|
9
|
+
}
|
|
6
10
|
/**
|
|
7
11
|
* The voices over every subject behind each step of a mix's `version`, so a chain linked a few
|
|
8
12
|
* steps back can be brought up to date by those voices alone; null for a step that changed
|
|
9
13
|
* anything else, after which every chain links afresh.
|
|
10
14
|
*/
|
|
11
|
-
export declare class Steps<V extends Ordered> {
|
|
15
|
+
export declare class Steps<V extends Ordered, I> {
|
|
12
16
|
private list;
|
|
13
17
|
/** The version before `list[0]`. */
|
|
14
18
|
private from;
|
|
@@ -16,10 +20,12 @@ export declare class Steps<V extends Ordered> {
|
|
|
16
20
|
push(voice: V | null, version: number): void;
|
|
17
21
|
/**
|
|
18
22
|
* A chain linked at `version`, its first record `head` (null where it holds none), with each voice
|
|
19
|
-
* stepped since taken off it or put on at its place in voice order by `
|
|
20
|
-
* voice's record where a full relink would link it and null where it would not. undefined
|
|
21
|
-
* the steps since do not say, so the chain must link afresh.
|
|
23
|
+
* stepped since taken off it or put on at its place in voice order by `mix.linkable`, which gives
|
|
24
|
+
* the voice's record where a full relink would link it and null where it would not. undefined
|
|
25
|
+
* where the steps since do not say, so the chain must link afresh. It takes the mix and the
|
|
26
|
+
* subject, not a function closed over them: a closure and its context a subject cost the `swap`
|
|
27
|
+
* row about 138 B each (bench/allocs.mjs, 2026-10-09).
|
|
22
28
|
*/
|
|
23
|
-
patch(head: Subject<unknown> | null, version: number,
|
|
29
|
+
patch(head: Subject<unknown> | null, version: number, mix: Linker<V, I>, subject: I, slot: number): Subject<unknown> | null | undefined;
|
|
24
30
|
}
|
|
25
31
|
export {};
|
package/dist/relink.js
CHANGED
|
@@ -21,11 +21,13 @@ export class Steps {
|
|
|
21
21
|
}
|
|
22
22
|
/**
|
|
23
23
|
* A chain linked at `version`, its first record `head` (null where it holds none), with each voice
|
|
24
|
-
* stepped since taken off it or put on at its place in voice order by `
|
|
25
|
-
* voice's record where a full relink would link it and null where it would not. undefined
|
|
26
|
-
* the steps since do not say, so the chain must link afresh.
|
|
24
|
+
* stepped since taken off it or put on at its place in voice order by `mix.linkable`, which gives
|
|
25
|
+
* the voice's record where a full relink would link it and null where it would not. undefined
|
|
26
|
+
* where the steps since do not say, so the chain must link afresh. It takes the mix and the
|
|
27
|
+
* subject, not a function closed over them: a closure and its context a subject cost the `swap`
|
|
28
|
+
* row about 138 B each (bench/allocs.mjs, 2026-10-09).
|
|
27
29
|
*/
|
|
28
|
-
patch(head, version,
|
|
30
|
+
patch(head, version, mix, subject, slot) {
|
|
29
31
|
if (!(version >= this.from))
|
|
30
32
|
return undefined;
|
|
31
33
|
const list = this.list;
|
|
@@ -40,7 +42,7 @@ export class Steps {
|
|
|
40
42
|
at = at.next;
|
|
41
43
|
}
|
|
42
44
|
const here = at !== null && at.voice === voice ? at : null;
|
|
43
|
-
const want =
|
|
45
|
+
const want = mix.linkable(voice, subject, slot);
|
|
44
46
|
if (want === here)
|
|
45
47
|
continue;
|
|
46
48
|
const after = here === null ? at : here.next;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type { Leavings } from './leavings.js';
|
|
2
|
+
import { Store } from './store.js';
|
|
3
|
+
import type { Cut } from './transport.js';
|
|
4
|
+
import type { Subject } from './voice.js';
|
|
5
|
+
/**
|
|
6
|
+
* A voice's records once a seek has gone back: each subject's is put back from the record it had,
|
|
7
|
+
* the first time it is asked for after a seek, since the subjects a store holds cannot be listed.
|
|
8
|
+
* It is one store however many seeks there are: by subject it keeps how many seeks its record has
|
|
9
|
+
* been put back for, and a record behind is put back to the earliest moment sought since, as a
|
|
10
|
+
* seek back cuts what came after it and a later seek ahead of that moment must not read past it.
|
|
11
|
+
*/
|
|
12
|
+
export declare class Restored<I> extends Store<I, Subject<unknown>> {
|
|
13
|
+
private readonly own;
|
|
14
|
+
private readonly make;
|
|
15
|
+
/** Seeks back so far. */
|
|
16
|
+
private n;
|
|
17
|
+
/** By subject, the `n` its record was last put back, written or deleted at; none reads as 0. */
|
|
18
|
+
private readonly at;
|
|
19
|
+
/**
|
|
20
|
+
* Each seek back sought to an earlier moment than every one after it, oldest first: the earliest
|
|
21
|
+
* moment sought since seek `n` is that of the first here at or after it.
|
|
22
|
+
*/
|
|
23
|
+
private readonly least;
|
|
24
|
+
/** By seek, the records subjects left with after its moment, and where it cut; null until one has any. */
|
|
25
|
+
private futures;
|
|
26
|
+
/** The latest seek's moment. */
|
|
27
|
+
private t;
|
|
28
|
+
/**
|
|
29
|
+
* `own` holds the records as they stood before the first seek. `make` puts a record back as it
|
|
30
|
+
* stood at `t`, undefined where it had none then; `top` is the latest seek's moment.
|
|
31
|
+
*/
|
|
32
|
+
constructor(own: Store<I, Subject<unknown>>, make: (live: Subject<unknown>, t: number, top: number) => Subject<unknown> | undefined);
|
|
33
|
+
/** A seek back to `t`: `left` has the records subjects left with after `cut`, null for none. */
|
|
34
|
+
back(t: number, cut: Cut, left: Leavings<I> | null): void;
|
|
35
|
+
/** The earliest moment sought by seek `from` or any after it. */
|
|
36
|
+
private since;
|
|
37
|
+
get(key: I): Subject<unknown> | undefined;
|
|
38
|
+
set(key: I, value: Subject<unknown>): void;
|
|
39
|
+
delete(key: I): void;
|
|
40
|
+
clear(): void;
|
|
41
|
+
}
|
package/dist/restored.js
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { Store } from './store.js';
|
|
2
|
+
/**
|
|
3
|
+
* A voice's records once a seek has gone back: each subject's is put back from the record it had,
|
|
4
|
+
* the first time it is asked for after a seek, since the subjects a store holds cannot be listed.
|
|
5
|
+
* It is one store however many seeks there are: by subject it keeps how many seeks its record has
|
|
6
|
+
* been put back for, and a record behind is put back to the earliest moment sought since, as a
|
|
7
|
+
* seek back cuts what came after it and a later seek ahead of that moment must not read past it.
|
|
8
|
+
*/
|
|
9
|
+
export class Restored extends Store {
|
|
10
|
+
own;
|
|
11
|
+
make;
|
|
12
|
+
/** Seeks back so far. */
|
|
13
|
+
n = 0;
|
|
14
|
+
/** By subject, the `n` its record was last put back, written or deleted at; none reads as 0. */
|
|
15
|
+
at = new Store();
|
|
16
|
+
/**
|
|
17
|
+
* Each seek back sought to an earlier moment than every one after it, oldest first: the earliest
|
|
18
|
+
* moment sought since seek `n` is that of the first here at or after it.
|
|
19
|
+
*/
|
|
20
|
+
least = [];
|
|
21
|
+
/** By seek, the records subjects left with after its moment, and where it cut; null until one has any. */
|
|
22
|
+
futures = null;
|
|
23
|
+
/** The latest seek's moment. */
|
|
24
|
+
t = Number.NaN;
|
|
25
|
+
/**
|
|
26
|
+
* `own` holds the records as they stood before the first seek. `make` puts a record back as it
|
|
27
|
+
* stood at `t`, undefined where it had none then; `top` is the latest seek's moment.
|
|
28
|
+
*/
|
|
29
|
+
constructor(own, make) {
|
|
30
|
+
super();
|
|
31
|
+
this.own = own;
|
|
32
|
+
this.make = make;
|
|
33
|
+
}
|
|
34
|
+
/** A seek back to `t`: `left` has the records subjects left with after `cut`, null for none. */
|
|
35
|
+
back(t, cut, left) {
|
|
36
|
+
const n = ++this.n;
|
|
37
|
+
const least = this.least;
|
|
38
|
+
while (least.length > 0 && least[least.length - 1].t >= t)
|
|
39
|
+
least.pop();
|
|
40
|
+
least.push({ n, t });
|
|
41
|
+
this.t = t;
|
|
42
|
+
if (left !== null) {
|
|
43
|
+
this.futures ??= new Map();
|
|
44
|
+
this.futures.set(n, { left, cut });
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
/** The earliest moment sought by seek `from` or any after it. */
|
|
48
|
+
since(from) {
|
|
49
|
+
const least = this.least;
|
|
50
|
+
let lo = 0;
|
|
51
|
+
let hi = least.length - 1;
|
|
52
|
+
while (lo < hi) {
|
|
53
|
+
const mid = (lo + hi) >> 1;
|
|
54
|
+
if (least[mid].n >= from)
|
|
55
|
+
hi = mid;
|
|
56
|
+
else
|
|
57
|
+
lo = mid + 1;
|
|
58
|
+
}
|
|
59
|
+
return least[lo].t;
|
|
60
|
+
}
|
|
61
|
+
get(key) {
|
|
62
|
+
const at = this.at.get(key) ?? 0;
|
|
63
|
+
if (at === this.n)
|
|
64
|
+
return this.own.get(key);
|
|
65
|
+
this.at.set(key, this.n);
|
|
66
|
+
// A subject that left after the moment the next seek sought had the record it left with,
|
|
67
|
+
// whatever it has now. Leaving is a write, so no later seek's leavings can hold it.
|
|
68
|
+
const future = this.futures?.get(at + 1);
|
|
69
|
+
const live = future?.left.at(key, future.cut) ?? this.own.get(key);
|
|
70
|
+
if (live === undefined)
|
|
71
|
+
return undefined;
|
|
72
|
+
const made = this.make(live, this.since(at + 1), this.t);
|
|
73
|
+
if (made === undefined)
|
|
74
|
+
this.own.delete(key);
|
|
75
|
+
else
|
|
76
|
+
this.own.set(key, made);
|
|
77
|
+
return made;
|
|
78
|
+
}
|
|
79
|
+
set(key, value) {
|
|
80
|
+
this.own.set(key, value);
|
|
81
|
+
this.at.set(key, this.n);
|
|
82
|
+
}
|
|
83
|
+
delete(key) {
|
|
84
|
+
this.own.delete(key);
|
|
85
|
+
this.at.set(key, this.n);
|
|
86
|
+
}
|
|
87
|
+
clear() {
|
|
88
|
+
this.own.clear();
|
|
89
|
+
this.at.clear();
|
|
90
|
+
}
|
|
91
|
+
}
|
package/dist/revive.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { Mixer } from './mixer.js';
|
|
2
2
|
import type { Run } from './motions.js';
|
|
3
|
+
import type { Past } from './origin.js';
|
|
3
4
|
import { type PackedRecord } from './pack.js';
|
|
4
5
|
import { type Key } from './paging.js';
|
|
5
6
|
import type { Described } from './types.js';
|
|
@@ -22,8 +23,13 @@ interface PackedVoice {
|
|
|
22
23
|
outAt: number;
|
|
23
24
|
outOver: number;
|
|
24
25
|
outSet: boolean;
|
|
26
|
+
rebuilds: number;
|
|
27
|
+
past: number;
|
|
28
|
+
clocks: Past[] | null;
|
|
25
29
|
cuedAt: number;
|
|
30
|
+
cuedSeq: number;
|
|
26
31
|
doneAt: number;
|
|
32
|
+
doneSeq: number;
|
|
27
33
|
opened: number;
|
|
28
34
|
pinned: number;
|
|
29
35
|
latest: number;
|
|
@@ -32,11 +38,12 @@ interface PackedVoice {
|
|
|
32
38
|
log: Controls[] | null;
|
|
33
39
|
played: boolean | undefined;
|
|
34
40
|
playedAt: number;
|
|
41
|
+
playedSeq: number;
|
|
35
42
|
records: [Key, PackedRecord][];
|
|
36
43
|
runs: [Key, Run][];
|
|
37
|
-
left: [Key, number, PackedRecord][];
|
|
38
|
-
parts: [Key, number, number][];
|
|
39
|
-
parted: [Key, number][];
|
|
44
|
+
left: [Key, number, number, PackedRecord, boolean][];
|
|
45
|
+
parts: [Key, number, number, number][];
|
|
46
|
+
parted: [Key, number, number][];
|
|
40
47
|
}
|
|
41
48
|
/**
|
|
42
49
|
* Voices a history store holds whose handles something still holds, by id: a seek that revives
|
package/dist/revive.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { playable } from './cue.js';
|
|
2
2
|
import { VoiceHandle } from './handle.js';
|
|
3
3
|
import { handle } from './hosts.js';
|
|
4
|
+
import { Leavings } from './leavings.js';
|
|
4
5
|
import { packHeld, unpackHeld } from './pack.js';
|
|
5
6
|
import { pageOut } from './paging.js';
|
|
6
7
|
import { Voice } from './voice.js';
|
|
@@ -47,7 +48,7 @@ export function pageVoice(mix, voice, reach) {
|
|
|
47
48
|
return false;
|
|
48
49
|
const keys = mix.keys;
|
|
49
50
|
const data = packVoice(voice, keys);
|
|
50
|
-
pageOut(mix, 'voice', voice.id, undefined, [{ at: voice.doneAt }], () => data, reach);
|
|
51
|
+
pageOut(mix, 'voice', voice.id, undefined, [{ at: voice.doneAt, seq: voice.doneSeq }], () => data, reach);
|
|
51
52
|
const h = voice.handle;
|
|
52
53
|
VoiceHandle.page(h, { weight: voice.weight, played: voice.settled.played === true });
|
|
53
54
|
mix.outs.add(voice.id, voice.doneAt, h);
|
|
@@ -81,8 +82,13 @@ function packVoice(voice, keys) {
|
|
|
81
82
|
outAt: voice.outAt,
|
|
82
83
|
outOver: voice.outOver,
|
|
83
84
|
outSet: voice.outSet,
|
|
85
|
+
rebuilds: voice.rebuilds,
|
|
86
|
+
past: voice.clocks?.length ?? 0,
|
|
87
|
+
clocks: voice.clocks,
|
|
84
88
|
cuedAt: voice.cuedAt,
|
|
89
|
+
cuedSeq: voice.cuedSeq,
|
|
85
90
|
doneAt: voice.doneAt,
|
|
91
|
+
doneSeq: voice.doneSeq,
|
|
86
92
|
opened: voice.opened,
|
|
87
93
|
pinned: voice.pinned,
|
|
88
94
|
latest: voice.latest,
|
|
@@ -91,11 +97,18 @@ function packVoice(voice, keys) {
|
|
|
91
97
|
log: voice.log,
|
|
92
98
|
played: settled.played,
|
|
93
99
|
playedAt: settled.at,
|
|
100
|
+
playedSeq: settled.seq,
|
|
94
101
|
records,
|
|
95
102
|
runs,
|
|
96
|
-
left: (voice.left ?? []).map((e) => [
|
|
97
|
-
|
|
98
|
-
|
|
103
|
+
left: (voice.left?.all() ?? []).map((e) => [
|
|
104
|
+
keys.key(e.subject),
|
|
105
|
+
e.at,
|
|
106
|
+
e.seq,
|
|
107
|
+
packHeld(voice, e.held),
|
|
108
|
+
e.sync,
|
|
109
|
+
]),
|
|
110
|
+
parts: [...(voice.parts ?? [])].map(([s, r]) => [keys.key(s), r.at, r.over, r.seq]),
|
|
111
|
+
parted: [...(voice.parted ?? [])].map(([s, p]) => [keys.key(s), p.at, p.seq]),
|
|
99
112
|
};
|
|
100
113
|
}
|
|
101
114
|
/** A voice a history store paged out, rebuilt from its descriptor and put back as it left. */
|
|
@@ -106,18 +119,12 @@ export function reviveVoice(mix, id, d) {
|
|
|
106
119
|
const spec = { ...revive(d.as), as: d.as };
|
|
107
120
|
playable(mix, spec);
|
|
108
121
|
const voice = new Voice(id, spec, spec.patch, spec.fade ?? {}, Number.NEGATIVE_INFINITY, d.start, mix.slotOf, mix.channels, mix.opts.host, mix.send, null);
|
|
109
|
-
voice.
|
|
110
|
-
voice.
|
|
111
|
-
voice.rate = d.rate;
|
|
112
|
-
voice.ramp = d.ramp;
|
|
113
|
-
voice.weight = d.weight;
|
|
114
|
-
voice.out = d.out;
|
|
115
|
-
voice.back = d.back;
|
|
116
|
-
voice.outAt = d.outAt;
|
|
117
|
-
voice.outOver = d.outOver;
|
|
118
|
-
voice.outSet = d.outSet;
|
|
122
|
+
voice.clocks = d.clocks;
|
|
123
|
+
voice.take(d);
|
|
119
124
|
voice.cuedAt = d.cuedAt;
|
|
125
|
+
voice.cuedSeq = d.cuedSeq;
|
|
120
126
|
voice.doneAt = d.doneAt;
|
|
127
|
+
voice.doneSeq = d.doneSeq;
|
|
121
128
|
voice.opened = d.opened;
|
|
122
129
|
voice.pinned = d.pinned;
|
|
123
130
|
voice.latest = d.latest;
|
|
@@ -126,7 +133,7 @@ export function reviveVoice(mix, id, d) {
|
|
|
126
133
|
voice.log = d.log;
|
|
127
134
|
voice.state = 'done';
|
|
128
135
|
if (d.played !== undefined)
|
|
129
|
-
voice.play(d.played, d.playedAt);
|
|
136
|
+
voice.play(d.played, d.playedAt, d.playedSeq);
|
|
130
137
|
voice.resolve();
|
|
131
138
|
const keys = mix.keys;
|
|
132
139
|
const subjectOf = (key) => keys.subject(key);
|
|
@@ -137,16 +144,17 @@ export function reviveVoice(mix, id, d) {
|
|
|
137
144
|
}
|
|
138
145
|
for (const [key, run] of d.runs)
|
|
139
146
|
voice.motion?.unpack(subjectOf(key), run);
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
147
|
+
voice.left = Leavings.from(d.left.map(([key, at, seq, p, sync]) => ({
|
|
148
|
+
subject: subjectOf(key),
|
|
149
|
+
at,
|
|
150
|
+
seq,
|
|
151
|
+
sync,
|
|
152
|
+
held: unpackHeld(voice, p),
|
|
153
|
+
})));
|
|
146
154
|
if (d.parts.length > 0)
|
|
147
|
-
voice.parts = new Map(d.parts.map(([key, at, over]) => [subjectOf(key), { at, over }]));
|
|
155
|
+
voice.parts = new Map(d.parts.map(([key, at, over, seq]) => [subjectOf(key), { at, over, seq }]));
|
|
148
156
|
if (d.parted.length > 0)
|
|
149
|
-
voice.parted = new Map(d.parted.map(([key, at]) => [subjectOf(key), at]));
|
|
157
|
+
voice.parted = new Map(d.parted.map(([key, at, seq]) => [subjectOf(key), { at, seq }]));
|
|
150
158
|
const h = mix.outs?.take(id);
|
|
151
159
|
if (h === undefined)
|
|
152
160
|
voice.handle = handle(mix, voice);
|
package/dist/roster.d.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { Mixer } from './mixer.js';
|
|
2
|
+
import type { Voice } from './voice.js';
|
|
3
|
+
/** Counts a voice just put in the list into everything a sync and a fold skip work by. */
|
|
4
|
+
export declare function enlist<I, O>(mix: Mixer<I, O>, voice: Voice<I, O>): void;
|
|
5
|
+
/**
|
|
6
|
+
* Counts a voice leaving the list back out. A voice naming no subject stays in `general` and
|
|
7
|
+
* `sharers`, which the caller filters once for every voice leaving together.
|
|
8
|
+
*/
|
|
9
|
+
export declare function delist<I, O>(mix: Mixer<I, O>, voice: Voice<I, O>): void;
|
|
10
|
+
/** Counts the list over from nothing, for a seek or a projection's copy. `owners` is the caller's. */
|
|
11
|
+
export declare function recount<I, O>(mix: Mixer<I, O>): void;
|
package/dist/roster.js
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { index, unindex } from './chain.js';
|
|
2
|
+
import { detour } from './everyone.js';
|
|
3
|
+
import { Store } from './store.js';
|
|
4
|
+
function tally(mix, voice, by) {
|
|
5
|
+
if (voice.spec.locus !== undefined)
|
|
6
|
+
mix.loci += by;
|
|
7
|
+
if (voice.spec.anchor !== undefined)
|
|
8
|
+
mix.anchored += by;
|
|
9
|
+
}
|
|
10
|
+
/** Counts a voice just put in the list into everything a sync and a fold skip work by. */
|
|
11
|
+
export function enlist(mix, voice) {
|
|
12
|
+
if (voice.holding !== null) {
|
|
13
|
+
mix.owners ??= [];
|
|
14
|
+
mix.owners.push(voice);
|
|
15
|
+
}
|
|
16
|
+
index(mix, voice);
|
|
17
|
+
tally(mix, voice, 1);
|
|
18
|
+
detour(mix);
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Counts a voice leaving the list back out. A voice naming no subject stays in `general` and
|
|
22
|
+
* `sharers`, which the caller filters once for every voice leaving together.
|
|
23
|
+
*/
|
|
24
|
+
export function delist(mix, voice) {
|
|
25
|
+
if (voice.named !== null)
|
|
26
|
+
unindex(mix, voice);
|
|
27
|
+
if (voice.holding !== null && mix.owners !== null)
|
|
28
|
+
mix.owners = mix.owners.filter((v) => v !== voice);
|
|
29
|
+
tally(mix, voice, -1);
|
|
30
|
+
detour(mix);
|
|
31
|
+
}
|
|
32
|
+
/** Counts the list over from nothing, for a seek or a projection's copy. `owners` is the caller's. */
|
|
33
|
+
export function recount(mix) {
|
|
34
|
+
mix.named = new Store();
|
|
35
|
+
mix.naming = 0;
|
|
36
|
+
mix.general = [];
|
|
37
|
+
mix.sharers = [];
|
|
38
|
+
mix.loci = 0;
|
|
39
|
+
mix.anchored = 0;
|
|
40
|
+
for (const voice of mix.cued) {
|
|
41
|
+
index(mix, voice);
|
|
42
|
+
tally(mix, voice, 1);
|
|
43
|
+
}
|
|
44
|
+
detour(mix);
|
|
45
|
+
mix.steps.push(null, ++mix.version);
|
|
46
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { type Vec } from './channels.js';
|
|
2
|
+
import type { Channel } from './types.js';
|
|
3
|
+
/**
|
|
4
|
+
* What `angle` takes.
|
|
5
|
+
*
|
|
6
|
+
* @category channel
|
|
7
|
+
*/
|
|
8
|
+
export interface AngleOptions {
|
|
9
|
+
/** One full turn in the channel's unit. Default 360, for degrees; `2 * Math.PI` for radians. */
|
|
10
|
+
turn?: number;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* A rotation about one axis, which adds as `sum` does and interpolates the short way round: from
|
|
14
|
+
* 350 to 10 it passes 0, not 180. A weight below 1 fades it the short way to rest too. Its values
|
|
15
|
+
* are angles, so two a whole number of turns apart are the same value, and a fold may answer with
|
|
16
|
+
* either.
|
|
17
|
+
*
|
|
18
|
+
* @category channel
|
|
19
|
+
*/
|
|
20
|
+
export declare function angle(opts?: AngleOptions): Channel<number>;
|
|
21
|
+
/**
|
|
22
|
+
* A rotation in space, as a unit quaternion `[x, y, z, w]`. Contributions compose by the Hamilton
|
|
23
|
+
* product in the order their voices were cued, which matters: rotations do not commute. A weight
|
|
24
|
+
* turns a contribution part of the way from no rotation, along the short arc, and `lerp` takes the
|
|
25
|
+
* short arc between two.
|
|
26
|
+
*
|
|
27
|
+
* @category channel
|
|
28
|
+
*/
|
|
29
|
+
export declare function quat(): Channel<Vec<4>>;
|
package/dist/rotation.js
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { doubles, vouch } from './channels.js';
|
|
2
|
+
import { shortWay } from './turns.js';
|
|
3
|
+
/**
|
|
4
|
+
* A rotation about one axis, which adds as `sum` does and interpolates the short way round: from
|
|
5
|
+
* 350 to 10 it passes 0, not 180. A weight below 1 fades it the short way to rest too. Its values
|
|
6
|
+
* are angles, so two a whole number of turns apart are the same value, and a fold may answer with
|
|
7
|
+
* either.
|
|
8
|
+
*
|
|
9
|
+
* @category channel
|
|
10
|
+
*/
|
|
11
|
+
export function angle(opts) {
|
|
12
|
+
const turn = opts?.turn ?? 360;
|
|
13
|
+
if (!(turn > 0))
|
|
14
|
+
throw new RangeError('blits: an angle takes a positive turn');
|
|
15
|
+
const channel = {
|
|
16
|
+
kind: `angle(${turn})`,
|
|
17
|
+
rest: 0,
|
|
18
|
+
merge: (a, b) => a + b,
|
|
19
|
+
scale: (v, w) => shortWay(v, turn) * w,
|
|
20
|
+
lerp: (a, b, u) => a + shortWay(b - a, turn) * u,
|
|
21
|
+
};
|
|
22
|
+
vouch(channel, { op: 'sum', axes: 1, turn });
|
|
23
|
+
return channel;
|
|
24
|
+
}
|
|
25
|
+
/** `a` then `b`, written into `out`, which may be either. */
|
|
26
|
+
function multiply(out, a, b) {
|
|
27
|
+
const ax = a[0];
|
|
28
|
+
const ay = a[1];
|
|
29
|
+
const az = a[2];
|
|
30
|
+
const aw = a[3];
|
|
31
|
+
const bx = b[0];
|
|
32
|
+
const by = b[1];
|
|
33
|
+
const bz = b[2];
|
|
34
|
+
const bw = b[3];
|
|
35
|
+
out[0] = aw * bx + ax * bw + ay * bz - az * by;
|
|
36
|
+
out[1] = aw * by - ax * bz + ay * bw + az * bx;
|
|
37
|
+
out[2] = aw * bz + ax * by - ay * bx + az * bw;
|
|
38
|
+
out[3] = aw * bw - ax * bx - ay * by - az * bz;
|
|
39
|
+
return out;
|
|
40
|
+
}
|
|
41
|
+
/** The short arc from `a` to `b` at `u`, a unit quaternion, written into `out`. */
|
|
42
|
+
function slerp(out, a, b, u) {
|
|
43
|
+
let dot = a[0] * b[0] + a[1] * b[1] + a[2] * b[2] + a[3] * b[3];
|
|
44
|
+
// `q` and `-q` are one rotation; the nearer of the two is the short way.
|
|
45
|
+
const sign = dot < 0 ? -1 : 1;
|
|
46
|
+
dot *= sign;
|
|
47
|
+
let p = 1 - u;
|
|
48
|
+
let q = u;
|
|
49
|
+
// Nearly parallel, the arc's sine is too small to divide by, and a straight line is as good.
|
|
50
|
+
if (dot < 1 - 1e-9) {
|
|
51
|
+
const theta = Math.acos(dot);
|
|
52
|
+
const s = Math.sin(theta);
|
|
53
|
+
p = Math.sin((1 - u) * theta) / s;
|
|
54
|
+
q = Math.sin(u * theta) / s;
|
|
55
|
+
}
|
|
56
|
+
q *= sign;
|
|
57
|
+
const x = p * a[0] + q * b[0];
|
|
58
|
+
const y = p * a[1] + q * b[1];
|
|
59
|
+
const z = p * a[2] + q * b[2];
|
|
60
|
+
const w = p * a[3] + q * b[3];
|
|
61
|
+
const n = Math.sqrt(x * x + y * y + z * z + w * w) || 1;
|
|
62
|
+
out[0] = x / n;
|
|
63
|
+
out[1] = y / n;
|
|
64
|
+
out[2] = z / n;
|
|
65
|
+
out[3] = w / n;
|
|
66
|
+
return out;
|
|
67
|
+
}
|
|
68
|
+
const IDENTITY = [0, 0, 0, 1];
|
|
69
|
+
const scratch = doubles(4, 0);
|
|
70
|
+
/**
|
|
71
|
+
* A rotation in space, as a unit quaternion `[x, y, z, w]`. Contributions compose by the Hamilton
|
|
72
|
+
* product in the order their voices were cued, which matters: rotations do not commute. A weight
|
|
73
|
+
* turns a contribution part of the way from no rotation, along the short arc, and `lerp` takes the
|
|
74
|
+
* short arc between two.
|
|
75
|
+
*
|
|
76
|
+
* @category channel
|
|
77
|
+
*/
|
|
78
|
+
export function quat() {
|
|
79
|
+
const rest = doubles(4, 0);
|
|
80
|
+
rest[3] = 1;
|
|
81
|
+
const channel = {
|
|
82
|
+
kind: 'quat',
|
|
83
|
+
rest: rest,
|
|
84
|
+
merge: (a, b) => multiply(doubles(4, 0), a, b),
|
|
85
|
+
scale: (v, w) => slerp(doubles(4, 0), IDENTITY, v, w),
|
|
86
|
+
fold: (into, v, w) => multiply(into, into, slerp(scratch, IDENTITY, v, w)),
|
|
87
|
+
lerp: (a, b, u) => slerp(doubles(4, 0), a, b, u),
|
|
88
|
+
};
|
|
89
|
+
vouch(channel, { op: 'own', axes: 4 });
|
|
90
|
+
return channel;
|
|
91
|
+
}
|
package/dist/rows.d.ts
CHANGED
|
@@ -15,19 +15,19 @@ export declare function sameCrowds<I, O>(lanes: Lanes<I, O>, members: readonly V
|
|
|
15
15
|
*/
|
|
16
16
|
export declare function regroup<I, O>(lanes: Lanes<I, O>, members: Voice<I, O>[]): void;
|
|
17
17
|
/**
|
|
18
|
-
* Gives a voice the next row of its
|
|
18
|
+
* Gives a voice the next row of its channels' crowd, made if there is none: a copy of the row it
|
|
19
19
|
* had in `from` at `at`, or a new one. `old` holds the crowds being replaced, whose idle state a
|
|
20
|
-
* new crowd on the same
|
|
20
|
+
* new crowd on the same channels keeps. Rows stay in voice order only because voices join in it.
|
|
21
21
|
*/
|
|
22
22
|
export declare function join<I, O>(lanes: Lanes<I, O>, v: Voice<I, O>, from: Crowd<I, O> | undefined, at: number | undefined, old: readonly Crowd<I, O>[]): void;
|
|
23
23
|
/** A crowd row takes up its voice as it now stands: opened once it starts, its fields copied again. */
|
|
24
24
|
export declare function retouchRow<I, O>(lanes: Lanes<I, O>, c: Crowd<I, O>, p: number, v: Voice<I, O>): void;
|
|
25
25
|
/**
|
|
26
|
-
* Compacts every crowd a quarter or more empty rows: each fill walks every row,
|
|
27
|
-
* crowd replacing a voice a frame carried as many empty rows as live ones.
|
|
26
|
+
* Compacts every crowd a quarter or more empty rows, past 64 of them: each fill walks every row,
|
|
27
|
+
* and at half a crowd replacing a voice a frame carried as many empty rows as live ones.
|
|
28
28
|
*/
|
|
29
29
|
export declare function compactSparse<I, O>(lanes: Lanes<I, O>): void;
|
|
30
|
-
/** A crowd voice left: its row stays, empty, until the
|
|
30
|
+
/** A crowd voice left: its row stays, empty, until the crowd is compacted or rebuilt. */
|
|
31
31
|
export declare function bury<I, O>(lanes: Lanes<I, O>, c: Crowd<I, O>, p: number): void;
|
|
32
32
|
/** A crowd row's weight as `reported` reads a lane position's. */
|
|
33
33
|
export declare function reportedRow<I, O>(lanes: Lanes<I, O>, c: Crowd<I, O>, p: number, slot: number): number | undefined;
|
package/dist/rows.js
CHANGED
|
@@ -75,9 +75,9 @@ export function regroup(lanes, members) {
|
|
|
75
75
|
}
|
|
76
76
|
}
|
|
77
77
|
/**
|
|
78
|
-
* Gives a voice the next row of its
|
|
78
|
+
* Gives a voice the next row of its channels' crowd, made if there is none: a copy of the row it
|
|
79
79
|
* had in `from` at `at`, or a new one. `old` holds the crowds being replaced, whose idle state a
|
|
80
|
-
* new crowd on the same
|
|
80
|
+
* new crowd on the same channels keeps. Rows stay in voice order only because voices join in it.
|
|
81
81
|
*/
|
|
82
82
|
export function join(lanes, v, from, at, old) {
|
|
83
83
|
const chans = v.slots.map((s) => lanes.bySlot[s]);
|
|
@@ -113,8 +113,8 @@ export function retouchRow(lanes, c, p, v) {
|
|
|
113
113
|
c.hot[h + 0 /* Hot.FLAGS */] = c.hot[h + 0 /* Hot.FLAGS */] | 64 /* Flag.VOICE */;
|
|
114
114
|
}
|
|
115
115
|
/**
|
|
116
|
-
* Compacts every crowd a quarter or more empty rows: each fill walks every row,
|
|
117
|
-
* crowd replacing a voice a frame carried as many empty rows as live ones.
|
|
116
|
+
* Compacts every crowd a quarter or more empty rows, past 64 of them: each fill walks every row,
|
|
117
|
+
* and at half a crowd replacing a voice a frame carried as many empty rows as live ones.
|
|
118
118
|
*/
|
|
119
119
|
export function compactSparse(lanes) {
|
|
120
120
|
for (const c of lanes.crowds)
|
|
@@ -154,7 +154,7 @@ function compact(c) {
|
|
|
154
154
|
c.laws.length = q;
|
|
155
155
|
c.dead = 0;
|
|
156
156
|
}
|
|
157
|
-
/** A crowd voice left: its row stays, empty, until the
|
|
157
|
+
/** A crowd voice left: its row stays, empty, until the crowd is compacted or rebuilt. */
|
|
158
158
|
export function bury(lanes, c, p) {
|
|
159
159
|
const v = c.voices[p];
|
|
160
160
|
leaveCrowd(lanes, c, p);
|
package/dist/sample.d.ts
CHANGED
|
@@ -11,9 +11,10 @@ export declare function runMotion<I, O>(lanes: Lanes<I, O>, lane: Lane<I, O>, ru
|
|
|
11
11
|
* A motion voice's value for a subject, sampled from the patch's state as its `at` would, but only
|
|
12
12
|
* where that sample changes nothing: one with a change to stamp, commit or let go of goes to the
|
|
13
13
|
* general path, which samples it if and when a probe asks, as it would with no lanes. A probe
|
|
14
|
-
* this frame already read keeps the value it read, as the general path's record would.
|
|
14
|
+
* this frame already read keeps the value it read, as the general path's record would. The
|
|
15
|
+
* subject's voice time, delay and weight are in `Arg`.
|
|
15
16
|
*/
|
|
16
|
-
export declare function move<I, O>(lanes: Lanes<I, O>, lane: Positions<I, O>, run: Motions<I>, p: number, slot: number, held: Subject<unknown
|
|
17
|
+
export declare function move<I, O>(lanes: Lanes<I, O>, lane: Positions<I, O>, run: Motions<I>, p: number, slot: number, held: Subject<unknown>): void;
|
|
17
18
|
/**
|
|
18
19
|
* A probe this frame read a motion voice's value for the subject from an earlier fill: write onto
|
|
19
20
|
* its record what the general path's sample would have, so a later read this frame reuses it.
|