@msb235/blits 0.14.0 → 0.15.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 +154 -0
- package/README.md +3 -3
- package/dist/ahead.d.ts +26 -0
- package/dist/ahead.js +264 -0
- package/dist/around.d.ts +11 -0
- package/dist/around.js +32 -0
- package/dist/bare.js +15 -7
- package/dist/blank.d.ts +10 -0
- package/dist/blank.js +26 -0
- package/dist/blend.d.ts +2 -1
- package/dist/blend.js +1 -6
- package/dist/bound.d.ts +70 -0
- package/dist/bound.js +351 -0
- package/dist/chain.d.ts +2 -1
- package/dist/chain.js +7 -5
- package/dist/changes.d.ts +24 -0
- package/dist/changes.js +189 -0
- package/dist/channels.d.ts +39 -13
- package/dist/channels.js +14 -5
- package/dist/color.d.ts +12 -10
- package/dist/color.js +25 -25
- package/dist/columns.js +7 -3
- package/dist/contribution.d.ts +30 -0
- package/dist/contribution.js +374 -0
- package/dist/copyout.d.ts +10 -0
- package/dist/copyout.js +69 -24
- package/dist/crowd.d.ts +8 -2
- package/dist/crowd.js +4 -5
- package/dist/cue.d.ts +8 -1
- package/dist/cue.js +39 -13
- package/dist/cut.d.ts +8 -0
- package/dist/cut.js +4 -0
- package/dist/drop.d.ts +9 -0
- package/dist/drop.js +84 -0
- package/dist/due.d.ts +24 -5
- package/dist/due.js +113 -33
- package/dist/easing.d.ts +4 -4
- package/dist/easing.js +3 -3
- package/dist/everyone.d.ts +3 -2
- package/dist/everyone.js +3 -3
- package/dist/fade.d.ts +2 -1
- package/dist/fade.js +2 -1
- package/dist/fill.d.ts +3 -2
- package/dist/fill.js +51 -19
- package/dist/fit.d.ts +44 -26
- package/dist/fit.js +10 -9
- package/dist/fold.d.ts +4 -37
- package/dist/fold.js +5 -513
- package/dist/gather.d.ts +12 -7
- package/dist/gather.js +179 -44
- package/dist/gone.js +2 -2
- package/dist/grid.d.ts +47 -0
- package/dist/grid.js +247 -0
- package/dist/handle.d.ts +2 -0
- package/dist/handle.js +14 -8
- package/dist/held.d.ts +2 -1
- package/dist/history.d.ts +6 -5
- package/dist/history.js +7 -9
- package/dist/hosts.js +9 -15
- package/dist/index.d.ts +5 -4
- package/dist/keyfill.d.ts +1 -1
- package/dist/keyfill.js +5 -3
- package/dist/keyrows.d.ts +12 -0
- package/dist/keyrows.js +99 -12
- package/dist/lane.d.ts +36 -6
- package/dist/lane.js +32 -1
- package/dist/lanes.d.ts +23 -8
- package/dist/lanes.js +29 -8
- package/dist/leavings.d.ts +2 -2
- package/dist/leavings.js +1 -1
- package/dist/lives.d.ts +47 -0
- package/dist/lives.js +180 -0
- package/dist/meet.d.ts +2 -2
- package/dist/meet.js +90 -4
- package/dist/mixer.d.ts +54 -36
- package/dist/mixer.js +53 -171
- package/dist/motion.d.ts +14 -118
- package/dist/motion.js +16 -8
- package/dist/motions.d.ts +62 -155
- package/dist/motions.js +56 -370
- package/dist/moved.d.ts +1 -1
- package/dist/moved.js +16 -7
- package/dist/named.d.ts +0 -1
- package/dist/named.js +20 -2
- package/dist/numbers.d.ts +2 -0
- package/dist/numbers.js +4 -0
- package/dist/owed.d.ts +2 -0
- package/dist/owed.js +4 -0
- package/dist/pack.d.ts +2 -1
- package/dist/pack.js +2 -3
- package/dist/patch.d.ts +7 -3
- package/dist/patch.js +6 -2
- package/dist/places.d.ts +53 -0
- package/dist/places.js +171 -0
- package/dist/plot.d.ts +12 -0
- package/dist/plot.js +52 -0
- package/dist/project.d.ts +4 -4
- package/dist/project.js +9 -212
- package/dist/pull.js +11 -8
- package/dist/qualify.js +84 -21
- package/dist/reading.d.ts +6 -0
- package/dist/reading.js +5 -0
- package/dist/record.d.ts +5 -1
- package/dist/record.js +3 -2
- package/dist/relink.d.ts +1 -1
- package/dist/restored.d.ts +2 -2
- package/dist/restored.js +1 -1
- package/dist/rests.d.ts +9 -0
- package/dist/rests.js +76 -0
- package/dist/revive.d.ts +3 -2
- package/dist/revive.js +2 -2
- package/dist/{locus.d.ts → role.d.ts} +10 -9
- package/dist/{locus.js → role.js} +3 -3
- package/dist/roles.d.ts +10 -0
- package/dist/roles.js +158 -0
- package/dist/roster.js +3 -3
- package/dist/rotation.d.ts +1 -1
- package/dist/rotation.js +1 -1
- package/dist/rows.js +1 -1
- package/dist/sample.d.ts +2 -2
- package/dist/sample.js +33 -7
- package/dist/seek.js +5 -5
- package/dist/setting.d.ts +27 -0
- package/dist/setting.js +55 -0
- package/dist/shared.d.ts +1 -1
- package/dist/shared.js +1 -1
- package/dist/signals.d.ts +64 -32
- package/dist/signals.js +20 -18
- package/dist/stack.d.ts +5 -5
- package/dist/stack.js +5 -5
- package/dist/standing.d.ts +2 -1
- package/dist/standing.js +3 -2
- package/dist/store.d.ts +9 -0
- package/dist/store.js +24 -0
- package/dist/strays.d.ts +2 -1
- package/dist/strays.js +20 -13
- package/dist/stretch.d.ts +112 -0
- package/dist/stretch.js +9 -0
- package/dist/subject.d.ts +142 -0
- package/dist/subject.js +1 -0
- package/dist/tape.d.ts +9 -5
- package/dist/tape.js +5 -1
- package/dist/then.d.ts +9 -0
- package/dist/then.js +57 -0
- package/dist/ticker.d.ts +4 -4
- package/dist/transport.d.ts +0 -8
- package/dist/transport.js +0 -4
- package/dist/turns.d.ts +4 -3
- package/dist/turns.js +4 -3
- package/dist/types/arc.d.ts +12 -11
- package/dist/types/channel.d.ts +12 -8
- package/dist/types/history.d.ts +35 -19
- package/dist/types/mix.d.ts +222 -163
- package/dist/types/motion.d.ts +151 -0
- package/dist/types/motion.js +1 -0
- package/dist/types/patch.d.ts +67 -34
- package/dist/types/place.d.ts +51 -36
- package/dist/types/transport.d.ts +20 -18
- package/dist/types/voice.d.ts +136 -70
- package/dist/types/watch.d.ts +88 -40
- package/dist/types.d.ts +1 -0
- package/dist/voice.d.ts +7 -143
- package/dist/voice.js +28 -79
- package/dist/warn.d.ts +2 -0
- package/dist/warn.js +1 -1
- package/dist/watch.d.ts +2 -2
- package/dist/watch.js +5 -3
- package/dist/wave.d.ts +4 -3
- package/dist/wave.js +3 -2
- package/dist/weigh.d.ts +7 -1
- package/dist/weigh.js +27 -0
- package/docs/schema.html +212 -34
- package/package.json +2 -2
package/dist/bound.d.ts
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { type Lane } from './lane.js';
|
|
2
|
+
import type { Lanes } from './lanes.js';
|
|
3
|
+
import type { Mixer } from './mixer.js';
|
|
4
|
+
import type { Circle, Finder, VoiceSpec } from './types.js';
|
|
5
|
+
import type { Voice } from './voice.js';
|
|
6
|
+
export declare class Bound<I, O> {
|
|
7
|
+
readonly mix: Mixer<I, O>;
|
|
8
|
+
readonly voice: Voice<I, O>;
|
|
9
|
+
readonly area: Circle | Finder;
|
|
10
|
+
/** The host's finder, or null for a circle. */
|
|
11
|
+
readonly finder: Finder | null;
|
|
12
|
+
/** By index, the `marks` stamp of the last time the general path found it within: see `marked`. */
|
|
13
|
+
hitAt: Float64Array<ArrayBuffer>;
|
|
14
|
+
marks: number;
|
|
15
|
+
/** The frame `hitAt` was last marked in. */
|
|
16
|
+
markedIn: number;
|
|
17
|
+
/** The indices a finder's check reads the signal for. */
|
|
18
|
+
missed: Int32Array<ArrayBuffer>;
|
|
19
|
+
/** Outside production, until it has warned: whether it checks a finder's indices. */
|
|
20
|
+
vets: boolean;
|
|
21
|
+
/** The frame `x`, `y` and `r` were read from `area` in. */
|
|
22
|
+
frame: number;
|
|
23
|
+
x: number;
|
|
24
|
+
y: number;
|
|
25
|
+
r: number;
|
|
26
|
+
r2: number;
|
|
27
|
+
/**
|
|
28
|
+
* Subject numbers whose lane weight or probed weight may not be 0: those the last fill found
|
|
29
|
+
* within, those it zeroed and a probe has yet to read 0 from, and those the general path paid.
|
|
30
|
+
*/
|
|
31
|
+
lit: number[];
|
|
32
|
+
spare: number[];
|
|
33
|
+
/**
|
|
34
|
+
* By subject number, the fill that last found it within (negated where that fill let it go), and
|
|
35
|
+
* the fill that last put it in `lit`.
|
|
36
|
+
*/
|
|
37
|
+
inAt: Float64Array<ArrayBuffer>;
|
|
38
|
+
litAt: Float64Array<ArrayBuffer>;
|
|
39
|
+
/** Outside production until it has warned: whether it checks the bound clips no signal. */
|
|
40
|
+
checks: boolean;
|
|
41
|
+
frames: number;
|
|
42
|
+
/** The `frames` count a finder's check last ran at. */
|
|
43
|
+
checkedAt: number;
|
|
44
|
+
constructor(mix: Mixer<I, O>, voice: Voice<I, O>, area: Circle | Finder);
|
|
45
|
+
}
|
|
46
|
+
/** Refuses a cue's `within` where the mix has handed out no indices, or on a voice with no signal. */
|
|
47
|
+
export declare function checkWithin<I, O>(mix: Mixer<I, O>, spec: VoiceSpec<I, O>): void;
|
|
48
|
+
/**
|
|
49
|
+
* Bounds a voice just made to its cue's `within`, or to the circle its `around` weight carries once
|
|
50
|
+
* the mix has indices. A carried circle is the signal's own edge, so it is not checked.
|
|
51
|
+
*/
|
|
52
|
+
export declare function bind<I, O>(mix: Mixer<I, O>, voice: Voice<I, O>): void;
|
|
53
|
+
/** Whether `subject` is within the voice's bound, on the general path. */
|
|
54
|
+
export declare function inBound<I, O>(mix: Mixer<I, O>, b: Bound<I, O>, subject: I): boolean;
|
|
55
|
+
/** Notes that the subject at `slot` may hold a weight other than 0 on the voice's lane. */
|
|
56
|
+
export declare function lit<I, O>(b: Bound<I, O>, slot: number, fills: number): void;
|
|
57
|
+
/**
|
|
58
|
+
* `run`'s loop for a bounded voice: `one` for the subjects within the bound, and for those that
|
|
59
|
+
* were lit and are not, their lane weight set to 0 as a read at weight 0 leaves it.
|
|
60
|
+
*/
|
|
61
|
+
export declare function runBounded<I, O>(lanes: Lanes<I, O>, lane: Lane<I, O>, b: Bound<I, O>): void;
|
|
62
|
+
/**
|
|
63
|
+
* For a role member's bound, before its role's loop: marks the subjects within it this fill, which
|
|
64
|
+
* `inside` then reads.
|
|
65
|
+
*/
|
|
66
|
+
export declare function meetBound<I, O>(lanes: Lanes<I, O>, b: Bound<I, O>): void;
|
|
67
|
+
/** Whether `meetBound` found the subject at `slot` within the bound this fill. */
|
|
68
|
+
export declare const inside: <I, O>(b: Bound<I, O>, slot: number, fills: number) => boolean;
|
|
69
|
+
/** A role member's subject outside its bound: its lane weight set to 0, as a read at weight 0 leaves it. */
|
|
70
|
+
export declare function leave<I, O>(lanes: Lanes<I, O>, lane: Lane<I, O>, p: number, slot: number): void;
|
package/dist/bound.js
ADDED
|
@@ -0,0 +1,351 @@
|
|
|
1
|
+
import { carried } from './around.js';
|
|
2
|
+
import { unband } from './fold.js';
|
|
3
|
+
import { keepProbed } from './lane.js';
|
|
4
|
+
import { production, warnOnce } from './warn.js';
|
|
5
|
+
/**
|
|
6
|
+
* A voice's signal declared 0 outside a bound (`within` on the cue): a circle around a point the host
|
|
7
|
+
* moves, over positions the host gives the mix (`plot`, `put`), or a finder the host answers with the
|
|
8
|
+
* indices `place` gave. Outside it the voice is not read at all, which is what lets a lane fill visit
|
|
9
|
+
* only the subjects within it.
|
|
10
|
+
*/
|
|
11
|
+
/** How many frames apart, outside production, a bound reads its signal just beyond the circle. */
|
|
12
|
+
const CHECK_EVERY = 8;
|
|
13
|
+
/** How many subjects beyond the circle each such check reads. */
|
|
14
|
+
const CHECK_SUBJECTS = 4;
|
|
15
|
+
export class Bound {
|
|
16
|
+
mix;
|
|
17
|
+
voice;
|
|
18
|
+
area;
|
|
19
|
+
/** The host's finder, or null for a circle. */
|
|
20
|
+
finder;
|
|
21
|
+
/** By index, the `marks` stamp of the last time the general path found it within: see `marked`. */
|
|
22
|
+
hitAt = new Float64Array(0);
|
|
23
|
+
marks = 0;
|
|
24
|
+
/** The frame `hitAt` was last marked in. */
|
|
25
|
+
markedIn = Number.NaN;
|
|
26
|
+
/** The indices a finder's check reads the signal for. */
|
|
27
|
+
missed = new Int32Array(CHECK_SUBJECTS);
|
|
28
|
+
/** Outside production, until it has warned: whether it checks a finder's indices. */
|
|
29
|
+
vets;
|
|
30
|
+
/** The frame `x`, `y` and `r` were read from `area` in. */
|
|
31
|
+
frame = Number.NaN;
|
|
32
|
+
x = 0;
|
|
33
|
+
y = 0;
|
|
34
|
+
r = 0;
|
|
35
|
+
r2 = 0;
|
|
36
|
+
/**
|
|
37
|
+
* Subject numbers whose lane weight or probed weight may not be 0: those the last fill found
|
|
38
|
+
* within, those it zeroed and a probe has yet to read 0 from, and those the general path paid.
|
|
39
|
+
*/
|
|
40
|
+
lit = [];
|
|
41
|
+
spare = [];
|
|
42
|
+
/**
|
|
43
|
+
* By subject number, the fill that last found it within (negated where that fill let it go), and
|
|
44
|
+
* the fill that last put it in `lit`.
|
|
45
|
+
*/
|
|
46
|
+
inAt = new Float64Array(0);
|
|
47
|
+
litAt = new Float64Array(0);
|
|
48
|
+
/** Outside production until it has warned: whether it checks the bound clips no signal. */
|
|
49
|
+
checks;
|
|
50
|
+
frames = 0;
|
|
51
|
+
/** The `frames` count a finder's check last ran at. */
|
|
52
|
+
checkedAt = -1;
|
|
53
|
+
constructor(mix, voice, area) {
|
|
54
|
+
this.mix = mix;
|
|
55
|
+
this.voice = voice;
|
|
56
|
+
this.area = area;
|
|
57
|
+
this.finder = isFinder(area) ? area : null;
|
|
58
|
+
this.checks = !production();
|
|
59
|
+
this.vets = this.checks && this.finder !== null;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
const isFinder = (area) => typeof area.find === 'function';
|
|
63
|
+
/** Refuses a cue's `within` where the mix has handed out no indices, or on a voice with no signal. */
|
|
64
|
+
export function checkWithin(mix, spec) {
|
|
65
|
+
const area = spec.within;
|
|
66
|
+
if (area === undefined)
|
|
67
|
+
return;
|
|
68
|
+
if (typeof area !== 'object' || area === null)
|
|
69
|
+
throw new TypeError('blits: within takes a circle, { x, y, r }, or a finder, { find(out) }');
|
|
70
|
+
if (mix.places === null)
|
|
71
|
+
throw new Error('blits: within needs indices: call mix.place or mix.plot first');
|
|
72
|
+
if (typeof spec.weight !== 'function')
|
|
73
|
+
throw new Error("blits: within needs a signal weight; for a hard-edged disk use weight: mix.around(circle, { steps: 1, jump: 'start' })");
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Bounds a voice just made to its cue's `within`, or to the circle its `around` weight carries once
|
|
77
|
+
* the mix has indices. A carried circle is the signal's own edge, so it is not checked.
|
|
78
|
+
*/
|
|
79
|
+
export function bind(mix, voice) {
|
|
80
|
+
const spec = voice.spec;
|
|
81
|
+
const own = carried(spec.weight);
|
|
82
|
+
const area = spec.within ?? (own !== undefined && mix.places !== null ? own : undefined);
|
|
83
|
+
if (area === undefined)
|
|
84
|
+
return;
|
|
85
|
+
voice.bound = new Bound(mix, voice, area);
|
|
86
|
+
if (area === own)
|
|
87
|
+
voice.bound.checks = false;
|
|
88
|
+
}
|
|
89
|
+
function snap(b, mix) {
|
|
90
|
+
if (b.frame === mix.frame)
|
|
91
|
+
return;
|
|
92
|
+
b.frame = mix.frame;
|
|
93
|
+
if (b.checks)
|
|
94
|
+
b.frames++;
|
|
95
|
+
if (b.finder === null) {
|
|
96
|
+
const a = b.area;
|
|
97
|
+
b.x = a.x;
|
|
98
|
+
b.y = a.y;
|
|
99
|
+
b.r = a.r;
|
|
100
|
+
b.r2 = a.r * a.r;
|
|
101
|
+
if (b.checks && b.frames % CHECK_EVERY === 0) {
|
|
102
|
+
const places = mix.places;
|
|
103
|
+
check(b, places.found, places.ring(b.x, b.y, b.r));
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Leaves in `places.found` the indices the bound finds this frame, and returns how many: the grid's
|
|
109
|
+
* for a circle, the host's for a finder. An index no subject holds reads as no lane number.
|
|
110
|
+
*/
|
|
111
|
+
function findAll(b) {
|
|
112
|
+
const places = b.mix.places;
|
|
113
|
+
snap(b, b.mix);
|
|
114
|
+
const finder = b.finder;
|
|
115
|
+
if (finder === null)
|
|
116
|
+
return places.query(b.x, b.y, b.r, b.r2);
|
|
117
|
+
if (places.found.length < places.n)
|
|
118
|
+
places.found = new Int32Array(Math.max(places.n, places.found.length * 2));
|
|
119
|
+
const out = places.found;
|
|
120
|
+
const count = finder.find(out);
|
|
121
|
+
if (!(Number.isInteger(count) && count >= 0 && count <= out.length))
|
|
122
|
+
throw new RangeError(`blits: within's find returned ${count}, not a count of the indices it wrote, 0 to ${out.length}`);
|
|
123
|
+
places.count = count;
|
|
124
|
+
if (b.vets)
|
|
125
|
+
vet(b, out, count, places.n);
|
|
126
|
+
// Once a frame, though a frame may find more than once.
|
|
127
|
+
if (b.checks && b.frames % CHECK_EVERY === 0 && b.checkedAt !== b.frames) {
|
|
128
|
+
b.checkedAt = b.frames;
|
|
129
|
+
check(b, b.missed, miss(b, places, count));
|
|
130
|
+
}
|
|
131
|
+
return count;
|
|
132
|
+
}
|
|
133
|
+
/** Warns once if a finder wrote an index `place` never handed out. */
|
|
134
|
+
function vet(b, out, count, n) {
|
|
135
|
+
for (let k = 0; k < count; k++) {
|
|
136
|
+
const i = out[k];
|
|
137
|
+
if (i >= 0 && i < n)
|
|
138
|
+
continue;
|
|
139
|
+
b.vets = false;
|
|
140
|
+
const voice = b.voice;
|
|
141
|
+
warnOnce(voice, `${voice.spec.name ?? `voice ${voice.id}`}'s within finder found index ${i}, which place never handed out: it is skipped`);
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Reads the signal, as at a first sight, for a few subjects just outside the bound, and warns once
|
|
147
|
+
* if any is above 0: the bound is cutting off part of the signal. Just beyond a circle's edge; for a
|
|
148
|
+
* finder, a few indices it did not find, taken in turn.
|
|
149
|
+
*/
|
|
150
|
+
function check(b, list, n) {
|
|
151
|
+
if (n === 0)
|
|
152
|
+
return;
|
|
153
|
+
const places = b.mix.places;
|
|
154
|
+
const voice = b.voice;
|
|
155
|
+
const signal = voice.spec.weight;
|
|
156
|
+
const now = b.mix.now;
|
|
157
|
+
const setting = {
|
|
158
|
+
timestamp: now,
|
|
159
|
+
dt: 0,
|
|
160
|
+
elapsed: voice.elapsedAt(now),
|
|
161
|
+
pass: voice.setting.pass,
|
|
162
|
+
weight: 0,
|
|
163
|
+
state: undefined,
|
|
164
|
+
host: voice.setting.host,
|
|
165
|
+
keep: (_owner, init) => init(),
|
|
166
|
+
send: () => { },
|
|
167
|
+
};
|
|
168
|
+
const step = Math.max(1, Math.floor(n / CHECK_SUBJECTS));
|
|
169
|
+
const from = (b.frames / CHECK_EVERY) % step;
|
|
170
|
+
const found = list.slice(0, n);
|
|
171
|
+
const named = voice.named;
|
|
172
|
+
for (let k = from; k < n; k += step) {
|
|
173
|
+
const subject = places.subjectAt(found[k]);
|
|
174
|
+
if (subject === undefined || (named !== null && !named.has(subject)))
|
|
175
|
+
continue;
|
|
176
|
+
const w = signal(subject, setting);
|
|
177
|
+
if (w > 0) {
|
|
178
|
+
b.checks = false;
|
|
179
|
+
warnOnce(voice, b.finder === null
|
|
180
|
+
? `${voice.spec.name ?? `voice ${voice.id}`}'s weight signal reads ${w} just outside its within circle (r ${b.r}), which cuts it off there: widen r to where the signal falls to 0`
|
|
181
|
+
: `${voice.spec.name ?? `voice ${voice.id}`}'s weight signal reads ${w} for a subject its within finder did not find, which cuts it off there: find every subject the signal is above 0 for`);
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* Marks the `count` indices a finder just found, and leaves in `missed` up to `CHECK_SUBJECTS` it did
|
|
188
|
+
* not, from where the last check left off; returns how many.
|
|
189
|
+
*/
|
|
190
|
+
function miss(b, places, count) {
|
|
191
|
+
const stamp = mark(b, places, count);
|
|
192
|
+
const n = places.n;
|
|
193
|
+
const hitAt = b.hitAt;
|
|
194
|
+
const from = n === 0 ? 0 : ((b.frames / CHECK_EVERY) * CHECK_SUBJECTS) % n;
|
|
195
|
+
let m = 0;
|
|
196
|
+
for (let k = 0; k < n && m < CHECK_SUBJECTS; k++) {
|
|
197
|
+
const i = (from + k) % n;
|
|
198
|
+
if (hitAt[i] !== stamp && places.subjectAt(i) !== undefined)
|
|
199
|
+
b.missed[m++] = i;
|
|
200
|
+
}
|
|
201
|
+
return m;
|
|
202
|
+
}
|
|
203
|
+
/** Marks in `hitAt` the first `count` of `places.found`, as this frame's, and returns their stamp. */
|
|
204
|
+
function mark(b, places, count) {
|
|
205
|
+
b.markedIn = b.mix.frame;
|
|
206
|
+
if (b.hitAt.length < places.n)
|
|
207
|
+
b.hitAt = new Float64Array(Math.max(places.n, b.hitAt.length * 2));
|
|
208
|
+
const stamp = ++b.marks;
|
|
209
|
+
const found = places.found;
|
|
210
|
+
const hitAt = b.hitAt;
|
|
211
|
+
for (let k = 0; k < count; k++) {
|
|
212
|
+
const i = found[k];
|
|
213
|
+
if (i >= 0 && i < hitAt.length)
|
|
214
|
+
hitAt[i] = stamp;
|
|
215
|
+
}
|
|
216
|
+
return stamp;
|
|
217
|
+
}
|
|
218
|
+
/** The stamp `hitAt` carries for the indices a finder finds this frame, marked once a frame. */
|
|
219
|
+
function marked(b) {
|
|
220
|
+
if (b.markedIn === b.mix.frame)
|
|
221
|
+
return b.marks;
|
|
222
|
+
const count = findAll(b);
|
|
223
|
+
// A check in `findAll` may have marked them already.
|
|
224
|
+
return b.markedIn === b.mix.frame ? b.marks : mark(b, b.mix.places, count);
|
|
225
|
+
}
|
|
226
|
+
/** Whether `subject` is within the voice's bound, on the general path. */
|
|
227
|
+
export function inBound(mix, b, subject) {
|
|
228
|
+
const places = mix.places;
|
|
229
|
+
if (places === null)
|
|
230
|
+
return false;
|
|
231
|
+
if (b.finder !== null) {
|
|
232
|
+
const i = places.at.get(subject);
|
|
233
|
+
if (i === undefined)
|
|
234
|
+
return false;
|
|
235
|
+
const stamp = marked(b);
|
|
236
|
+
return b.hitAt[i] === stamp;
|
|
237
|
+
}
|
|
238
|
+
if (mix.projecting) {
|
|
239
|
+
const a = b.area;
|
|
240
|
+
return places.inside(subject, a.x, a.y, a.r * a.r);
|
|
241
|
+
}
|
|
242
|
+
snap(b, mix);
|
|
243
|
+
return places.inside(subject, b.x, b.y, b.r2);
|
|
244
|
+
}
|
|
245
|
+
/** Notes that the subject at `slot` may hold a weight other than 0 on the voice's lane. */
|
|
246
|
+
export function lit(b, slot, fills) {
|
|
247
|
+
if (slot >= b.litAt.length)
|
|
248
|
+
grow(b, slot + 1);
|
|
249
|
+
if (b.litAt[slot] === fills)
|
|
250
|
+
return;
|
|
251
|
+
b.litAt[slot] = fills;
|
|
252
|
+
b.lit.push(slot);
|
|
253
|
+
}
|
|
254
|
+
function grow(b, cap) {
|
|
255
|
+
if (cap <= b.inAt.length)
|
|
256
|
+
return;
|
|
257
|
+
const size = Math.max(cap, b.inAt.length * 2, 64);
|
|
258
|
+
const inAt = new Float64Array(size);
|
|
259
|
+
inAt.set(b.inAt);
|
|
260
|
+
b.inAt = inAt;
|
|
261
|
+
const litAt = new Float64Array(size);
|
|
262
|
+
litAt.set(b.litAt);
|
|
263
|
+
b.litAt = litAt;
|
|
264
|
+
}
|
|
265
|
+
/**
|
|
266
|
+
* `run`'s loop for a bounded voice: `one` for the subjects within the bound, and for those that
|
|
267
|
+
* were lit and are not, their lane weight set to 0 as a read at weight 0 leaves it.
|
|
268
|
+
*/
|
|
269
|
+
export function runBounded(lanes, lane, b) {
|
|
270
|
+
const places = b.mix.places;
|
|
271
|
+
const n = findAll(b);
|
|
272
|
+
const fills = lanes.fills;
|
|
273
|
+
grow(b, lanes.cap);
|
|
274
|
+
const old = b.lit;
|
|
275
|
+
b.lit = b.spare;
|
|
276
|
+
b.lit.length = 0;
|
|
277
|
+
b.spare = old;
|
|
278
|
+
const voice = lane.voice;
|
|
279
|
+
const inAt = b.inAt;
|
|
280
|
+
const found = places.found;
|
|
281
|
+
const slots = places.slots;
|
|
282
|
+
for (let k = 0; k < n; k++) {
|
|
283
|
+
const slot = slots[found[k]] ?? -1;
|
|
284
|
+
if (slot < 0 || inAt[slot] === fills)
|
|
285
|
+
continue;
|
|
286
|
+
inAt[slot] = fills;
|
|
287
|
+
const p = lane.positionOf(slot);
|
|
288
|
+
if (p < 0)
|
|
289
|
+
continue;
|
|
290
|
+
lit(b, slot, fills);
|
|
291
|
+
places.cur = found[k];
|
|
292
|
+
try {
|
|
293
|
+
lanes.one(lane, p, slot);
|
|
294
|
+
}
|
|
295
|
+
finally {
|
|
296
|
+
places.cur = -1;
|
|
297
|
+
}
|
|
298
|
+
if (voice.keeping)
|
|
299
|
+
return;
|
|
300
|
+
}
|
|
301
|
+
const data = lane.data;
|
|
302
|
+
const per = lanes.per;
|
|
303
|
+
for (let k = 0; k < old.length; k++) {
|
|
304
|
+
const slot = old[k];
|
|
305
|
+
// Within, or already let go of this fill: a second `keepProbed` would copy the 0 just written.
|
|
306
|
+
if (inAt[slot] === fills || inAt[slot] === -fills)
|
|
307
|
+
continue;
|
|
308
|
+
inAt[slot] = -fills;
|
|
309
|
+
const p = lane.positionOf(slot);
|
|
310
|
+
if (p < 0)
|
|
311
|
+
continue;
|
|
312
|
+
const o = p * 8 /* Row.STRIDE */;
|
|
313
|
+
keepProbed(data, o, per, slot * 6 /* Per.SLOT */, fills);
|
|
314
|
+
if (data[o + 2 /* Row.WEIGHT */] !== 0) {
|
|
315
|
+
data[o + 2 /* Row.WEIGHT */] = 0;
|
|
316
|
+
unband(lane.records[p]);
|
|
317
|
+
}
|
|
318
|
+
if (data[o + 3 /* Row.PROBED */] !== 0)
|
|
319
|
+
lit(b, slot, fills);
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
/**
|
|
323
|
+
* For a role member's bound, before its role's loop: marks the subjects within it this fill, which
|
|
324
|
+
* `inside` then reads.
|
|
325
|
+
*/
|
|
326
|
+
export function meetBound(lanes, b) {
|
|
327
|
+
const places = b.mix.places;
|
|
328
|
+
const n = findAll(b);
|
|
329
|
+
const fills = lanes.fills;
|
|
330
|
+
grow(b, lanes.cap);
|
|
331
|
+
const inAt = b.inAt;
|
|
332
|
+
const found = places.found;
|
|
333
|
+
const slots = places.slots;
|
|
334
|
+
for (let k = 0; k < n; k++) {
|
|
335
|
+
const slot = slots[found[k]] ?? -1;
|
|
336
|
+
if (slot >= 0)
|
|
337
|
+
inAt[slot] = fills;
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
/** Whether `meetBound` found the subject at `slot` within the bound this fill. */
|
|
341
|
+
export const inside = (b, slot, fills) => b.inAt[slot] === fills;
|
|
342
|
+
/** A role member's subject outside its bound: its lane weight set to 0, as a read at weight 0 leaves it. */
|
|
343
|
+
export function leave(lanes, lane, p, slot) {
|
|
344
|
+
const data = lane.data;
|
|
345
|
+
const o = p * 8 /* Row.STRIDE */;
|
|
346
|
+
keepProbed(data, o, lanes.per, slot * 6 /* Per.SLOT */, lanes.fills);
|
|
347
|
+
if (data[o + 2 /* Row.WEIGHT */] !== 0) {
|
|
348
|
+
data[o + 2 /* Row.WEIGHT */] = 0;
|
|
349
|
+
unband(lane.records[p]);
|
|
350
|
+
}
|
|
351
|
+
}
|
package/dist/chain.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { Mixer } from './mixer.js';
|
|
2
|
-
import type { Subject
|
|
2
|
+
import type { Subject } from './subject.js';
|
|
3
|
+
import type { Voice } from './voice.js';
|
|
3
4
|
/**
|
|
4
5
|
* The first of this subject's records, linked through every live voice that reaches it. A voice
|
|
5
6
|
* is asked once, through the record it keeps for the subject anyway; a pending one is linked when
|
package/dist/chain.js
CHANGED
|
@@ -18,6 +18,8 @@ export function chain(subject) {
|
|
|
18
18
|
export function relink(subject, was) {
|
|
19
19
|
const lanes = this.lanes;
|
|
20
20
|
const slot = was !== undefined ? was.slot : lanes !== null ? lanes.number(subject) : -1;
|
|
21
|
+
if (was === undefined && slot >= 0)
|
|
22
|
+
this.places?.number(subject, slot);
|
|
21
23
|
// A chain a seek kept for its number alone links afresh, as a subject first seen does.
|
|
22
24
|
const sought = was !== undefined && slot >= 0 && lanes?.renew(slot) === true;
|
|
23
25
|
// Where only voices over every subject changed since, those alone are taken off or put on.
|
|
@@ -30,8 +32,8 @@ export function relink(subject, was) {
|
|
|
30
32
|
head.version = this.version;
|
|
31
33
|
if (was !== undefined && was !== head) {
|
|
32
34
|
if (!sought)
|
|
33
|
-
head.
|
|
34
|
-
was.
|
|
35
|
+
head.roles = was.roles;
|
|
36
|
+
was.roles = null;
|
|
35
37
|
was.slot = -1;
|
|
36
38
|
}
|
|
37
39
|
head.slot = slot;
|
|
@@ -143,7 +145,7 @@ export function changed(mix, voice) {
|
|
|
143
145
|
mix.steps.push(voice, ++mix.version);
|
|
144
146
|
return;
|
|
145
147
|
}
|
|
146
|
-
for (const subject of voice.named) {
|
|
148
|
+
for (const subject of voice.named.list) {
|
|
147
149
|
const head = headOf(mix, subject);
|
|
148
150
|
if (head !== undefined)
|
|
149
151
|
unlink(mix, head);
|
|
@@ -176,7 +178,7 @@ export function index(mix, voice) {
|
|
|
176
178
|
return;
|
|
177
179
|
}
|
|
178
180
|
mix.naming++;
|
|
179
|
-
for (const subject of voice.named) {
|
|
181
|
+
for (const subject of voice.named.list) {
|
|
180
182
|
const list = mix.named.get(subject);
|
|
181
183
|
if (list === undefined)
|
|
182
184
|
mix.named.set(subject, [voice]);
|
|
@@ -186,7 +188,7 @@ export function index(mix, voice) {
|
|
|
186
188
|
}
|
|
187
189
|
export function unindex(mix, voice) {
|
|
188
190
|
mix.naming--;
|
|
189
|
-
for (const subject of voice.named) {
|
|
191
|
+
for (const subject of voice.named.list) {
|
|
190
192
|
const list = mix.named.get(subject);
|
|
191
193
|
if (list === undefined)
|
|
192
194
|
continue;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { Motions } from './motions.js';
|
|
2
|
+
import { absent } from './numbers.js';
|
|
3
|
+
import { type Change, type Segment } from './stretch.js';
|
|
4
|
+
/**
|
|
5
|
+
* Queues a retarget, set or push, untimed at the latest frame, for `subject`'s next read past its time.
|
|
6
|
+
* The queue keeps the changes with a time in time order, ahead of any still waiting for one.
|
|
7
|
+
*/
|
|
8
|
+
export declare function change<I>(this: Motions<I>, subject: I, c: Change): void;
|
|
9
|
+
/** Files a change with a time after every one due by then, ahead of those still waiting for one. */
|
|
10
|
+
export declare function queue<I>(this: Motions<I>, list: Change[], c: Change, at: number): void;
|
|
11
|
+
/** Every change due by `t` applied to a copy of the stretch playing then, committing nothing. */
|
|
12
|
+
export declare function peek<I>(this: Motions<I>, s: number, t: number, xo: Float64Array, vo: Float64Array): void;
|
|
13
|
+
/** Subject `s`'s changes waiting, with those a read ahead's tape made filed among them by time. */
|
|
14
|
+
export declare function dueTo<I>(this: Motions<I>, s: number): readonly Change[] | undefined;
|
|
15
|
+
/**
|
|
16
|
+
* Takes back every change made after frame `seq`: each subject plays from its earliest stretch
|
|
17
|
+
* kept, with the changes made by then queued to be applied again.
|
|
18
|
+
*/
|
|
19
|
+
export declare function rewind<I>(this: Motions<I>, seq: number): void;
|
|
20
|
+
export declare function cut<I>(this: Motions<I>, s: number, seq: number): void;
|
|
21
|
+
/** The stretch `change` releases from `seg` at `at`, for `subject` or one no longer numbered. */
|
|
22
|
+
export declare function applied<I>(this: Motions<I>, subject: I | typeof absent, seg: Segment, change: Change, at: number): Segment;
|
|
23
|
+
export declare function commit<I>(this: Motions<I>, s: number, t: number): void;
|
|
24
|
+
export declare function insert<I>(this: Motions<I>, s: number, seg: Segment): void;
|
package/dist/changes.js
ADDED
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
import { absent } from './numbers.js';
|
|
2
|
+
import { reading } from './reading.js';
|
|
3
|
+
import { numbers } from './stretch.js';
|
|
4
|
+
const PENDING = 2 /* RunFlag.PENDING */;
|
|
5
|
+
const OLDER = 4 /* RunFlag.OLDER */;
|
|
6
|
+
/** Where `applied` reads a stretch at the time a change lands; copied out before it returns. */
|
|
7
|
+
let landing = { x: new Float64Array(4), v: new Float64Array(4) };
|
|
8
|
+
/**
|
|
9
|
+
* Queues a retarget, set or push, untimed at the latest frame, for `subject`'s next read past its time.
|
|
10
|
+
* The queue keeps the changes with a time in time order, ahead of any still waiting for one.
|
|
11
|
+
*/
|
|
12
|
+
export function change(subject, c) {
|
|
13
|
+
const back = this.owner === null ? Number.NaN : this.owner.revive(this.ownerId, subject);
|
|
14
|
+
const s = this.slot(subject);
|
|
15
|
+
this.check(c.to, this.n);
|
|
16
|
+
this.check(c.x, this.n);
|
|
17
|
+
this.check(c.v, this.n);
|
|
18
|
+
c.made = this.owner === null ? Number.NaN : this.owner.now();
|
|
19
|
+
c.seq = this.owner === null ? Number.NaN : this.owner.seq();
|
|
20
|
+
c.early = this.owner?.early() === true;
|
|
21
|
+
if (c.at === undefined) {
|
|
22
|
+
const at = this.frame(subject);
|
|
23
|
+
if (!Number.isNaN(at))
|
|
24
|
+
c.at = at;
|
|
25
|
+
else if (!Number.isNaN(back))
|
|
26
|
+
c.at = back;
|
|
27
|
+
}
|
|
28
|
+
const list = this.pending?.[s];
|
|
29
|
+
if (list === undefined) {
|
|
30
|
+
if (this.pending === null)
|
|
31
|
+
this.pending = [undefined];
|
|
32
|
+
this.pending[s] = [c];
|
|
33
|
+
this.flag(s, PENDING, true);
|
|
34
|
+
}
|
|
35
|
+
else if (c.at === undefined)
|
|
36
|
+
list.push(c);
|
|
37
|
+
else
|
|
38
|
+
this.queue(list, c, c.at);
|
|
39
|
+
reading.moved++;
|
|
40
|
+
const made = { at: c.at, to: c.to, x: c.x, v: c.v };
|
|
41
|
+
this.owner?.changed(this.ownerId, this, subject, made);
|
|
42
|
+
}
|
|
43
|
+
/** Files a change with a time after every one due by then, ahead of those still waiting for one. */
|
|
44
|
+
export function queue(list, c, at) {
|
|
45
|
+
let i = list.length;
|
|
46
|
+
while (i > 0) {
|
|
47
|
+
const before = list[i - 1].at;
|
|
48
|
+
if (before !== undefined && before <= at)
|
|
49
|
+
break;
|
|
50
|
+
i--;
|
|
51
|
+
}
|
|
52
|
+
list.splice(i, 0, c);
|
|
53
|
+
}
|
|
54
|
+
/** Every change due by `t` applied to a copy of the stretch playing then, committing nothing. */
|
|
55
|
+
export function peek(s, t, xo, vo) {
|
|
56
|
+
let seg = this.playing(s, t);
|
|
57
|
+
const list = this.dueTo(s);
|
|
58
|
+
if (list !== undefined)
|
|
59
|
+
for (const change of list) {
|
|
60
|
+
const a = change.at;
|
|
61
|
+
if (a === undefined || !(a <= t))
|
|
62
|
+
break;
|
|
63
|
+
if (a >= seg.at)
|
|
64
|
+
seg = this.applied(this.numbers.subject(s), seg, change, a);
|
|
65
|
+
}
|
|
66
|
+
this.evaluateSegment(seg, t, xo, vo);
|
|
67
|
+
}
|
|
68
|
+
/** Subject `s`'s changes waiting, with those a read ahead's tape made filed among them by time. */
|
|
69
|
+
export function dueTo(s) {
|
|
70
|
+
const own = this.pending?.[s];
|
|
71
|
+
const ahead = reading.ahead?.get(this);
|
|
72
|
+
if (ahead === undefined)
|
|
73
|
+
return own;
|
|
74
|
+
const subject = this.numbers.subject(s);
|
|
75
|
+
const made = subject === absent ? undefined : ahead.get(subject);
|
|
76
|
+
if (made === undefined)
|
|
77
|
+
return own;
|
|
78
|
+
if (own === undefined)
|
|
79
|
+
return made;
|
|
80
|
+
// The patch's own were made first, and a stable sort keeps them first at one time.
|
|
81
|
+
return [...own.filter((c) => c.at !== undefined), ...made].sort((a, b) => a.at - b.at);
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Takes back every change made after frame `seq`: each subject plays from its earliest stretch
|
|
85
|
+
* kept, with the changes made by then queued to be applied again.
|
|
86
|
+
*/
|
|
87
|
+
export function rewind(seq) {
|
|
88
|
+
this.unrelease(seq);
|
|
89
|
+
for (let s = 0; s < this.numbers.size; s++)
|
|
90
|
+
if (this.numbers.subject(s) !== absent)
|
|
91
|
+
this.cut(s, seq);
|
|
92
|
+
}
|
|
93
|
+
export function cut(s, seq) {
|
|
94
|
+
const older = this.older?.[s];
|
|
95
|
+
const pending = this.pending?.[s];
|
|
96
|
+
const current = this.latest(s);
|
|
97
|
+
const stretches = older === undefined ? [current] : [...older, current];
|
|
98
|
+
const kept = [];
|
|
99
|
+
let cut = false;
|
|
100
|
+
for (const c of [...stretches.slice(1).map((seg) => seg.change), ...(pending ?? [])])
|
|
101
|
+
if (c === undefined || c.seq === undefined || !(c.seq > seq)) {
|
|
102
|
+
if (c !== undefined)
|
|
103
|
+
kept.push(c);
|
|
104
|
+
}
|
|
105
|
+
else
|
|
106
|
+
cut = true;
|
|
107
|
+
if (!cut)
|
|
108
|
+
return;
|
|
109
|
+
if (older !== undefined) {
|
|
110
|
+
this.older[s] = undefined;
|
|
111
|
+
this.flag(s, OLDER, false);
|
|
112
|
+
}
|
|
113
|
+
this.write(s, stretches[0]);
|
|
114
|
+
// Applied again in the order they take effect; one never stamped waits for the next read.
|
|
115
|
+
const timed = kept
|
|
116
|
+
.filter((c) => c.at !== undefined)
|
|
117
|
+
.sort((a, b) => a.at - b.at);
|
|
118
|
+
const list = [...timed, ...kept.filter((c) => c.at === undefined)];
|
|
119
|
+
if (list.length === 0) {
|
|
120
|
+
if (pending !== undefined) {
|
|
121
|
+
this.pending[s] = undefined;
|
|
122
|
+
this.flag(s, PENDING, false);
|
|
123
|
+
}
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
if (this.pending === null)
|
|
127
|
+
this.pending = [undefined];
|
|
128
|
+
this.pending[s] = list;
|
|
129
|
+
this.flag(s, PENDING, true);
|
|
130
|
+
}
|
|
131
|
+
/** The stretch `change` releases from `seg` at `at`, for `subject` or one no longer numbered. */
|
|
132
|
+
export function applied(subject, seg, change, at) {
|
|
133
|
+
const n = this.n;
|
|
134
|
+
if (landing.x.length < n)
|
|
135
|
+
landing = { x: new Float64Array(n), v: new Float64Array(n) };
|
|
136
|
+
const { x, v } = landing;
|
|
137
|
+
this.evaluateSegment(seg, at, x, v);
|
|
138
|
+
const xs = change.x ?? numbers(x, 0, n);
|
|
139
|
+
const vs = change.v ?? numbers(v, 0, n);
|
|
140
|
+
const known = subject === absent ? undefined : subject;
|
|
141
|
+
const put = change.x !== undefined && change.v === undefined;
|
|
142
|
+
const to = this.shape.aim(xs, vs, change.to ?? seg.to, known, put);
|
|
143
|
+
const ms = subject === absent ? seg.ms : (this.shape.ms?.(known) ?? 0);
|
|
144
|
+
return { at, ms, x0: xs, v0: vs, to, change };
|
|
145
|
+
}
|
|
146
|
+
export function commit(s, t) {
|
|
147
|
+
const list = this.pending[s];
|
|
148
|
+
// The first read of a subject no frame had met: those waiting for a time take this one.
|
|
149
|
+
if (list[list.length - 1].at === undefined) {
|
|
150
|
+
let i = list.length;
|
|
151
|
+
while (i > 0 && list[i - 1].at === undefined)
|
|
152
|
+
i--;
|
|
153
|
+
for (const c of list.splice(i)) {
|
|
154
|
+
c.at = t;
|
|
155
|
+
this.queue(list, c, t);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
while (list.length > 0) {
|
|
159
|
+
const change = list[0];
|
|
160
|
+
const a = change.at;
|
|
161
|
+
if (a > t)
|
|
162
|
+
break;
|
|
163
|
+
list.shift();
|
|
164
|
+
this.insert(s, this.applied(this.numbers.subject(s), this.playing(s, a), change, a));
|
|
165
|
+
}
|
|
166
|
+
if (list.length === 0) {
|
|
167
|
+
this.pending[s] = undefined;
|
|
168
|
+
this.flag(s, PENDING, false);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
export function insert(s, seg) {
|
|
172
|
+
let list = this.older?.[s];
|
|
173
|
+
if (list === undefined) {
|
|
174
|
+
list = [];
|
|
175
|
+
if (this.older === null)
|
|
176
|
+
this.older = [undefined];
|
|
177
|
+
this.older[s] = list;
|
|
178
|
+
this.flag(s, OLDER, true);
|
|
179
|
+
}
|
|
180
|
+
if (seg.at >= this.runs[this.base(s)]) {
|
|
181
|
+
list.push(this.latest(s));
|
|
182
|
+
this.write(s, seg);
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
let i = list.length;
|
|
186
|
+
while (i > 0 && list[i - 1].at > seg.at)
|
|
187
|
+
i--;
|
|
188
|
+
list.splice(i, 0, seg);
|
|
189
|
+
}
|