@msb235/blits 0.9.0 → 0.11.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 +167 -0
- package/README.md +16 -5
- package/dist/blend.js +1 -5
- package/dist/book.d.ts +8 -5
- package/dist/book.js +47 -19
- package/dist/chain.d.ts +11 -0
- package/dist/chain.js +24 -2
- package/dist/clock.d.ts +14 -0
- package/dist/clock.js +9 -1
- package/dist/columns.d.ts +4 -5
- package/dist/columns.js +11 -5
- package/dist/crowd.d.ts +1 -1
- package/dist/crowd.js +4 -1
- package/dist/cue.js +52 -5
- package/dist/departed.d.ts +3 -2
- package/dist/departed.js +7 -4
- package/dist/due.d.ts +4 -0
- package/dist/due.js +16 -3
- package/dist/fade.d.ts +12 -5
- package/dist/fade.js +55 -17
- package/dist/fold.d.ts +1 -1
- package/dist/fold.js +53 -6
- package/dist/frames.d.ts +2 -0
- package/dist/frames.js +17 -0
- package/dist/handle.d.ts +5 -2
- package/dist/handle.js +20 -12
- package/dist/held.d.ts +8 -0
- package/dist/held.js +37 -6
- package/dist/history.d.ts +6 -0
- package/dist/history.js +15 -5
- package/dist/hosts.d.ts +1 -1
- package/dist/hosts.js +18 -6
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -1
- package/dist/keyfill.js +3 -1
- package/dist/lane.d.ts +1 -1
- package/dist/lane.js +3 -1
- package/dist/lanes.d.ts +8 -0
- package/dist/lanes.js +21 -4
- package/dist/lean.d.ts +15 -0
- package/dist/lean.js +23 -0
- package/dist/leavings.d.ts +6 -1
- package/dist/leavings.js +16 -2
- package/dist/marks.d.ts +7 -3
- package/dist/marks.js +102 -27
- package/dist/meet.js +1 -0
- package/dist/mixer.d.ts +23 -7
- package/dist/mixer.js +73 -29
- package/dist/motion.d.ts +4 -1
- package/dist/motion.js +16 -2
- package/dist/motions.d.ts +4 -0
- package/dist/motions.js +45 -11
- package/dist/move.js +28 -10
- package/dist/owner.d.ts +18 -2
- package/dist/owner.js +38 -2
- package/dist/pack.d.ts +1 -0
- package/dist/pack.js +3 -0
- package/dist/placed.d.ts +19 -0
- package/dist/placed.js +54 -0
- package/dist/project.d.ts +5 -2
- package/dist/project.js +236 -32
- package/dist/pull.js +37 -8
- package/dist/qualify.js +8 -2
- package/dist/reading.d.ts +6 -0
- package/dist/reading.js +6 -0
- package/dist/record.js +1 -0
- package/dist/restored.d.ts +7 -4
- package/dist/restored.js +13 -10
- package/dist/revive.d.ts +5 -3
- package/dist/revive.js +10 -3
- package/dist/rows.js +15 -5
- package/dist/seek.d.ts +7 -2
- package/dist/seek.js +90 -35
- package/dist/shared.d.ts +5 -0
- package/dist/shared.js +5 -0
- package/dist/signals.d.ts +10 -0
- package/dist/signals.js +17 -0
- package/dist/store.d.ts +11 -0
- package/dist/store.js +19 -0
- package/dist/strays.js +50 -2
- package/dist/tape.d.ts +29 -4
- package/dist/tape.js +49 -11
- package/dist/transport.d.ts +11 -2
- package/dist/transport.js +20 -9
- package/dist/types/mix.d.ts +42 -15
- package/dist/types/patch.d.ts +5 -1
- package/dist/types/place.d.ts +25 -11
- package/dist/types/transport.d.ts +12 -3
- package/dist/types/voice.d.ts +62 -4
- package/dist/unpage.d.ts +6 -1
- package/dist/unpage.js +34 -2
- package/dist/voice.d.ts +57 -11
- package/dist/voice.js +43 -3
- package/docs/schema.html +75 -40
- package/package.json +7 -2
package/dist/revive.js
CHANGED
|
@@ -107,9 +107,11 @@ function packVoice(voice, keys) {
|
|
|
107
107
|
e.seq,
|
|
108
108
|
packHeld(voice, e.held),
|
|
109
109
|
e.sync,
|
|
110
|
+
e.ramp,
|
|
111
|
+
e.parted,
|
|
110
112
|
]),
|
|
111
113
|
parts: [...(voice.parts ?? [])].map(([s, r]) => [keys.key(s), r.at, r.over, r.seq]),
|
|
112
|
-
parted: [...(voice.parted ?? [])].map(([s, p]) => [keys.key(s), p.at, p.seq]),
|
|
114
|
+
parted: [...(voice.parted ?? [])].map(([s, p]) => p.ramp === undefined ? [keys.key(s), p.at, p.seq] : [keys.key(s), p.at, p.seq, p.ramp]),
|
|
113
115
|
};
|
|
114
116
|
}
|
|
115
117
|
/** A voice a history store paged out, rebuilt from its descriptor and put back as it left. */
|
|
@@ -146,17 +148,22 @@ export function reviveVoice(mix, id, d) {
|
|
|
146
148
|
}
|
|
147
149
|
for (const [key, run] of d.runs)
|
|
148
150
|
voice.motion?.unpack(subjectOf(key), run);
|
|
149
|
-
voice.left = Leavings.from(d.left.map(([key, at, seq, p, sync]) => ({
|
|
151
|
+
voice.left = Leavings.from(d.left.map(([key, at, seq, p, sync, ramp = null, parted = null]) => ({
|
|
150
152
|
subject: subjectOf(key),
|
|
151
153
|
at,
|
|
152
154
|
seq,
|
|
153
155
|
sync,
|
|
154
156
|
held: unpackHeld(voice, p),
|
|
157
|
+
ramp,
|
|
158
|
+
parted,
|
|
155
159
|
})));
|
|
156
160
|
if (d.parts.length > 0)
|
|
157
161
|
voice.parts = new Map(d.parts.map(([key, at, over, seq]) => [subjectOf(key), { at, over, seq }]));
|
|
158
162
|
if (d.parted.length > 0)
|
|
159
|
-
voice.parted = new Map(d.parted.map(([key, at, seq]) => [
|
|
163
|
+
voice.parted = new Map(d.parted.map(([key, at, seq, ramp]) => [
|
|
164
|
+
subjectOf(key),
|
|
165
|
+
{ at, seq, ramp: ramp ?? undefined },
|
|
166
|
+
]));
|
|
160
167
|
const h = mix.outs?.take(id);
|
|
161
168
|
if (h === undefined)
|
|
162
169
|
voice.handle = handle(mix, voice);
|
package/dist/rows.js
CHANGED
|
@@ -16,8 +16,11 @@ function same(a, b) {
|
|
|
16
16
|
/** Takes a crowd row off its subject, to be placed again if a probe meets it again. */
|
|
17
17
|
export function unplace(lanes, c, p) {
|
|
18
18
|
const slot = c.list[p];
|
|
19
|
-
if (
|
|
20
|
-
|
|
19
|
+
if (slot >= 0) {
|
|
20
|
+
if (c.idle)
|
|
21
|
+
reach(lanes, slot, -1);
|
|
22
|
+
lanes.placed.remove(slot, c.voices[p].id);
|
|
23
|
+
}
|
|
21
24
|
c.list[p] = -1;
|
|
22
25
|
c.records[p] = undefined;
|
|
23
26
|
c.deltas[p] = null;
|
|
@@ -167,15 +170,20 @@ export function bury(lanes, c, p) {
|
|
|
167
170
|
lanes.crowdOf.delete(v.id);
|
|
168
171
|
c.dead++;
|
|
169
172
|
}
|
|
173
|
+
/** Copies `n` numbers of a row; a view of them to hand `set` was three objects a row moved. */
|
|
174
|
+
function carry(from, a, to, b, n) {
|
|
175
|
+
for (let i = 0; i < n; i++)
|
|
176
|
+
to[b + i] = from[a + i];
|
|
177
|
+
}
|
|
170
178
|
function copyRow(from, p, to, q, keys = from.keys) {
|
|
171
179
|
from.fix(p);
|
|
172
180
|
to.list[q] = from.list[p];
|
|
173
|
-
|
|
174
|
-
|
|
181
|
+
carry(from.data, p * 8 /* Row.STRIDE */, to.data, q * 8 /* Row.STRIDE */, 8 /* Row.STRIDE */);
|
|
182
|
+
carry(from.hot, p * from.stride, to.hot, q * to.stride, from.stride);
|
|
175
183
|
copyKeys(keys, to, q);
|
|
176
184
|
if ((from.hot[p * from.stride + 0 /* Hot.FLAGS */] & 4 /* Flag.STALE */) !== 0)
|
|
177
185
|
to.stale = true;
|
|
178
|
-
|
|
186
|
+
carry(from.samples, p * from.axes, to.samples, q * to.axes, from.axes);
|
|
179
187
|
to.deltas[q] = from.deltas[p] ?? null;
|
|
180
188
|
to.records[q] = from.records[p];
|
|
181
189
|
to.voices[q] = from.voices[p];
|
|
@@ -257,6 +265,8 @@ function leaveCrowd(lanes, c, p) {
|
|
|
257
265
|
const slot = c.list[p];
|
|
258
266
|
const rec = c.records[p];
|
|
259
267
|
if (slot >= 0) {
|
|
268
|
+
// Its row is emptied or left behind with its crowd, so the subject has it placed no longer.
|
|
269
|
+
lanes.placed.remove(slot, c.voices[p].id);
|
|
260
270
|
if (c.idle)
|
|
261
271
|
reach(lanes, slot, -1);
|
|
262
272
|
const w = reportedRow(lanes, c, p, slot);
|
package/dist/seek.d.ts
CHANGED
|
@@ -1,3 +1,8 @@
|
|
|
1
1
|
import type { Transport } from './transport.js';
|
|
2
|
-
/**
|
|
3
|
-
|
|
2
|
+
/**
|
|
3
|
+
* Moves every mix on the transport to mix time `to`, as it stood at the end of that frame, or to
|
|
4
|
+
* the end of the frame numbered `to.frame`.
|
|
5
|
+
*/
|
|
6
|
+
export declare function seek(transport: Transport, to: number | {
|
|
7
|
+
readonly frame: number;
|
|
8
|
+
}): void;
|
package/dist/seek.js
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
|
+
import { heldAt } from './chain.js';
|
|
1
2
|
import { schedule } from './due.js';
|
|
2
|
-
import {
|
|
3
|
+
import { fadesAt } from './fade.js';
|
|
4
|
+
import { origin } from './held.js';
|
|
5
|
+
import { apart, copyHeld, last, lastWithin } from './history.js';
|
|
3
6
|
import { laneHost } from './hosts.js';
|
|
4
|
-
import { Lanes } from './lanes.js';
|
|
5
7
|
import { Leavings } from './leavings.js';
|
|
6
8
|
import { move, nextFrame } from './move.js';
|
|
7
9
|
import { ownerReading } from './owner.js';
|
|
@@ -11,14 +13,16 @@ import { Restored } from './restored.js';
|
|
|
11
13
|
import { recount } from './roster.js';
|
|
12
14
|
import { refitAll } from './spans.js';
|
|
13
15
|
import { Store } from './store.js';
|
|
14
|
-
import { hostAt, replay } from './tape.js';
|
|
15
|
-
import { cover, cut } from './unpage.js';
|
|
16
|
+
import { hostAhead, hostAt, replay } from './tape.js';
|
|
17
|
+
import { cover, coverFrame, cut } from './unpage.js';
|
|
16
18
|
/**
|
|
17
19
|
* A voice's record of one subject as it stood at mix time `t`, from the latest copy kept by then;
|
|
18
20
|
* undefined for a stateful record first made after `t`, which is met afresh. A stateless record
|
|
19
|
-
* holds nothing that moves with time, so it is copied whenever it was made.
|
|
21
|
+
* holds nothing that moves with time, so it is copied whenever it was made. The weight its last
|
|
22
|
+
* probe by `t` read is not kept, and a lane holds it apart from the record, so the record reads 0
|
|
23
|
+
* until the next probe.
|
|
20
24
|
*/
|
|
21
|
-
function restore(voice, live, t) {
|
|
25
|
+
function restore(mix, voice, live, t, every) {
|
|
22
26
|
const inputs = live.inputs?.filter((e) => e.at <= t);
|
|
23
27
|
const snaps = live.snaps;
|
|
24
28
|
if (snaps === undefined) {
|
|
@@ -28,15 +32,41 @@ function restore(voice, live, t) {
|
|
|
28
32
|
h.stepped = t;
|
|
29
33
|
// Whether it had rested by `t` is not kept; the next probe finds it again.
|
|
30
34
|
h.rested = false;
|
|
35
|
+
h.weight = 0;
|
|
31
36
|
h.inputs = inputs;
|
|
37
|
+
if (!(h.since <= t))
|
|
38
|
+
origin(mix, voice, h);
|
|
32
39
|
return h;
|
|
33
40
|
}
|
|
34
41
|
const snap = last(snaps, t, true);
|
|
35
42
|
if (snap === undefined)
|
|
36
43
|
return undefined;
|
|
37
44
|
const h = copyHeld(voice, snap.held);
|
|
45
|
+
h.weight = 0;
|
|
46
|
+
if (snap.at < t)
|
|
47
|
+
h.copied = true;
|
|
38
48
|
h.snaps = snaps.slice(0, snaps.indexOf(snap) + 1);
|
|
39
49
|
h.inputs = inputs;
|
|
50
|
+
// A start still ahead when the copy was kept may have moved before `t`, with no copy between.
|
|
51
|
+
if (!(h.since <= snap.at))
|
|
52
|
+
origin(mix, voice, h);
|
|
53
|
+
if (voice.patch.step !== undefined && (snap.ahead !== undefined || t - snap.at >= every)) {
|
|
54
|
+
const ahead = snap.ahead;
|
|
55
|
+
// Steps counted from a start that has moved since are not this record's.
|
|
56
|
+
if (ahead === undefined || ahead.since !== h.since)
|
|
57
|
+
snap.ahead = {
|
|
58
|
+
put: h,
|
|
59
|
+
sought: t,
|
|
60
|
+
since: h.since,
|
|
61
|
+
at: snap.at,
|
|
62
|
+
ticks: h.ticks,
|
|
63
|
+
state: undefined,
|
|
64
|
+
};
|
|
65
|
+
else {
|
|
66
|
+
ahead.put = h;
|
|
67
|
+
ahead.sought = t;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
40
70
|
return h;
|
|
41
71
|
}
|
|
42
72
|
/** Takes a voice cued after the moment sought out of the mix, until the tape cues it again. */
|
|
@@ -71,9 +101,15 @@ function restoreVoice(mix, voice, t, seq) {
|
|
|
71
101
|
voice.parts = null;
|
|
72
102
|
}
|
|
73
103
|
if (voice.parted !== null) {
|
|
74
|
-
for (const [subject, p] of voice.parted)
|
|
75
|
-
if (p.seq
|
|
76
|
-
|
|
104
|
+
for (const [subject, p] of voice.parted) {
|
|
105
|
+
if (p.seq <= seq)
|
|
106
|
+
continue;
|
|
107
|
+
voice.parted.delete(subject);
|
|
108
|
+
if (p.ramp === undefined || p.ramp.seq > seq)
|
|
109
|
+
continue;
|
|
110
|
+
voice.parts ??= new Map();
|
|
111
|
+
voice.parts.set(subject, p.ramp);
|
|
112
|
+
}
|
|
77
113
|
if (voice.parted.size === 0)
|
|
78
114
|
voice.parted = null;
|
|
79
115
|
}
|
|
@@ -87,25 +123,22 @@ function restoreVoice(mix, voice, t, seq) {
|
|
|
87
123
|
voice.unreachedBits = null;
|
|
88
124
|
voice.seeks++;
|
|
89
125
|
const left = voice.left?.all() ?? [];
|
|
126
|
+
if (voice.left !== null)
|
|
127
|
+
fadesAt(voice, voice.left.after(cut), seq);
|
|
90
128
|
voice.left = Leavings.from(left.filter((e) => e.seq <= seq));
|
|
91
129
|
const future = Leavings.from(left.filter((e) => e.seq > seq));
|
|
92
130
|
let records = voice.subjects;
|
|
93
131
|
if (!(records instanceof Restored)) {
|
|
94
|
-
|
|
95
|
-
|
|
132
|
+
const every = apart(mix.opts.history);
|
|
133
|
+
const now = (subject, held, least, seq) => heldAt(voice, subject, held, least, seq);
|
|
134
|
+
records = new Restored(records, now, (live, least, top) => {
|
|
135
|
+
const h = restore(mix, voice, live, least, every);
|
|
96
136
|
if (h === undefined)
|
|
97
137
|
return h;
|
|
98
138
|
if (h.reaches)
|
|
99
139
|
voice.seen++;
|
|
100
140
|
if (h.rested)
|
|
101
141
|
voice.restedCount++;
|
|
102
|
-
if (!(h.since <= least)) {
|
|
103
|
-
// Kept while its origin was still ahead: where the controls put back place it now.
|
|
104
|
-
h.since = mix.sinceOf(voice, h.delay);
|
|
105
|
-
h.shown = mix.shownOf(voice, h.since);
|
|
106
|
-
if (h.ticks === 0 && h.stepped > h.since)
|
|
107
|
-
h.stepped = h.since;
|
|
108
|
-
}
|
|
109
142
|
if (!(h.since <= top)) {
|
|
110
143
|
voice.early ??= [];
|
|
111
144
|
voice.early.push(h);
|
|
@@ -127,8 +160,11 @@ function stateAt(voice, t) {
|
|
|
127
160
|
return 'frozen';
|
|
128
161
|
return 'live';
|
|
129
162
|
}
|
|
130
|
-
/**
|
|
131
|
-
|
|
163
|
+
/**
|
|
164
|
+
* Moves every mix on the transport to mix time `to`, as it stood at the end of that frame, or to
|
|
165
|
+
* the end of the frame numbered `to.frame`.
|
|
166
|
+
*/
|
|
167
|
+
export function seek(transport, to) {
|
|
132
168
|
if (transport.projecting)
|
|
133
169
|
throw new Error('blits: a projection does not seek');
|
|
134
170
|
const history = transport.history;
|
|
@@ -139,25 +175,42 @@ export function seek(transport, t) {
|
|
|
139
175
|
throw new Error('blits: seeking needs history.tape, which keeps the calls a seek plays again');
|
|
140
176
|
if (Number.isNaN(transport.now))
|
|
141
177
|
throw new Error('blits: a mix that has never synced has nothing to seek');
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
if (
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
178
|
+
let t;
|
|
179
|
+
let frame;
|
|
180
|
+
if (typeof to === 'number') {
|
|
181
|
+
t = to;
|
|
182
|
+
if (t === Number.POSITIVE_INFINITY)
|
|
183
|
+
throw new RangeError('blits: a mix seeks to a finite time');
|
|
184
|
+
if (!(t >= transport.born))
|
|
185
|
+
throw new Error(`blits: ${t} is older than this mix's history reaches`);
|
|
186
|
+
cover(transport, t);
|
|
187
|
+
if (t < transport.now) {
|
|
188
|
+
frame = transport.frames.at(t);
|
|
189
|
+
if (frame === undefined)
|
|
190
|
+
throw new Error(`blits: ${t} is older than this mix's history reaches`);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
else {
|
|
194
|
+
const f = coverFrame(transport, to.frame);
|
|
195
|
+
t = f.at;
|
|
196
|
+
// The frame being played is where the mix stands; any other is behind it, whatever its time.
|
|
197
|
+
if (f.seq !== transport.seq)
|
|
198
|
+
frame = f;
|
|
199
|
+
}
|
|
200
|
+
if (frame === undefined) {
|
|
148
201
|
// Ahead, under the rate the recorded calls up to `t` set, past any frames rate 0 held at `t`.
|
|
149
202
|
const last = replay(transport, (at) => (transport.pace === null ? at : transport.pace.reading(at)) <= t);
|
|
150
|
-
const
|
|
203
|
+
const at = hostAhead(transport, t);
|
|
204
|
+
const u = Number.isNaN(last) ? at : Math.max(last, at);
|
|
151
205
|
if (!Number.isFinite(u))
|
|
152
206
|
throw new RangeError(`blits: the mix's rate never reaches ${t}`);
|
|
153
207
|
transport.u = u;
|
|
154
|
-
|
|
155
|
-
|
|
208
|
+
// A call played at `t` itself moved the mixes there before it, as a sync does.
|
|
209
|
+
if (Number.isNaN(last) || transport.now !== t)
|
|
210
|
+
for (const m of transport.members)
|
|
211
|
+
move(m, t);
|
|
156
212
|
}
|
|
157
213
|
else {
|
|
158
|
-
const frame = transport.frames.at(t);
|
|
159
|
-
if (frame === undefined)
|
|
160
|
-
throw new Error(`blits: ${t} is older than this mix's history reaches`);
|
|
161
214
|
// On the frame itself, its own host time: rate 0 may have held the clock there for several.
|
|
162
215
|
const u = frame.at === t ? frame.u : hostAt(transport, t);
|
|
163
216
|
const seq = frame.seq;
|
|
@@ -219,12 +272,14 @@ function back(mix, t, seq, on) {
|
|
|
219
272
|
mix.retired = [];
|
|
220
273
|
// Owners hold what is back; a voice that went live again counts its place in its owner anew.
|
|
221
274
|
for (const voice of kept)
|
|
222
|
-
if (voice.holding !== null)
|
|
275
|
+
if (voice.holding !== null) {
|
|
223
276
|
voice.holding.children = kept.filter((v) => v.owner === voice);
|
|
277
|
+
voice.holding.back(seq);
|
|
278
|
+
}
|
|
224
279
|
// Rebuilt from the voices that are back: the lanes, the chains and every count the fold reads.
|
|
225
280
|
const oldLanes = mix.lanes;
|
|
226
|
-
if (oldLanes !== null)
|
|
227
|
-
mix.lanes = new
|
|
281
|
+
if (oldLanes !== null && mix.lanesOf !== null)
|
|
282
|
+
mix.lanes = new mix.lanesOf(laneHost(mix));
|
|
228
283
|
mix.chains = new Store();
|
|
229
284
|
mix.owners = kept.some((v) => v.holding !== null) ? kept.filter((v) => v.holding !== null) : null;
|
|
230
285
|
recount(mix);
|
package/dist/shared.d.ts
CHANGED
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The package version, for a fact whose shape follows the code too closely to version by hand:
|
|
3
|
+
* copies of different releases keep apart. `test/copies.test.ts` holds it to `package.json`.
|
|
4
|
+
*/
|
|
5
|
+
export declare const release = "0.11.0";
|
|
1
6
|
/**
|
|
2
7
|
* A WeakMap every copy of blits loaded in one realm shares, so a fact one copy records about a
|
|
3
8
|
* patch or channel it made is read by another copy's mix. The name carries a version, bumped when
|
package/dist/shared.js
CHANGED
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The package version, for a fact whose shape follows the code too closely to version by hand:
|
|
3
|
+
* copies of different releases keep apart. `test/copies.test.ts` holds it to `package.json`.
|
|
4
|
+
*/
|
|
5
|
+
export const release = '0.11.0';
|
|
1
6
|
/**
|
|
2
7
|
* A WeakMap every copy of blits loaded in one realm shares, so a fact one copy records about a
|
|
3
8
|
* patch or channel it made is read by another copy's mix. The name carries a version, bumped when
|
package/dist/signals.d.ts
CHANGED
|
@@ -7,6 +7,16 @@ export declare const heard: (signal: object) => boolean;
|
|
|
7
7
|
* @category signal
|
|
8
8
|
*/
|
|
9
9
|
export declare function peak<I, H = unknown>(...signals: readonly Signal<I, H>[]): Signal<I, H>;
|
|
10
|
+
/** A blend member's share of its signal's read `k`: the members' stops sit evenly along 0..1. */
|
|
11
|
+
export declare function shareOf(k: number, i: number, stops: number): number;
|
|
12
|
+
/**
|
|
13
|
+
* Member `i` of `n`'s share of `by`, as `mix.blend` weighs it: the hat of `by`'s read at the
|
|
14
|
+
* member's stop, the stops spaced evenly from 0 to 1. Weights a voice outside a blend as one of
|
|
15
|
+
* its members, such as one member played alone.
|
|
16
|
+
*
|
|
17
|
+
* @category signal
|
|
18
|
+
*/
|
|
19
|
+
export declare function share<I, H = unknown>(by: Signal<I, H>, i: number, n: number): Signal<I, H>;
|
|
10
20
|
/**
|
|
11
21
|
* Marks `read` as a signal driven from outside the clock, which a host's own signal has to be for
|
|
12
22
|
* `assess` and a history's `inputs` to treat it as one. Two uses:
|
package/dist/signals.js
CHANGED
|
@@ -27,6 +27,23 @@ export function peak(...signals) {
|
|
|
27
27
|
return out;
|
|
28
28
|
}, signals);
|
|
29
29
|
}
|
|
30
|
+
/** A blend member's share of its signal's read `k`: the members' stops sit evenly along 0..1. */
|
|
31
|
+
export function shareOf(k, i, stops) {
|
|
32
|
+
const d = stops === 0 ? 0 : Math.abs(k * stops - i);
|
|
33
|
+
return d >= 1 ? 0 : 1 - d;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Member `i` of `n`'s share of `by`, as `mix.blend` weighs it: the hat of `by`'s read at the
|
|
37
|
+
* member's stop, the stops spaced evenly from 0 to 1. Weights a voice outside a blend as one of
|
|
38
|
+
* its members, such as one member played alone.
|
|
39
|
+
*
|
|
40
|
+
* @category signal
|
|
41
|
+
*/
|
|
42
|
+
export function share(by, i, n) {
|
|
43
|
+
if (!Number.isInteger(n) || n < 1 || !Number.isInteger(i) || i < 0 || i >= n)
|
|
44
|
+
throw new Error(`blits: share wants a member 0 to ${n - 1}, not ${i}`);
|
|
45
|
+
return marked((subject, setting) => shareOf(by(subject, setting), i, n - 1), [by]);
|
|
46
|
+
}
|
|
30
47
|
const touch = () => {
|
|
31
48
|
reading.inputs++;
|
|
32
49
|
};
|
package/dist/store.d.ts
CHANGED
|
@@ -20,3 +20,14 @@ export declare class Store<K, V> {
|
|
|
20
20
|
delete(key: K): void;
|
|
21
21
|
clear(): void;
|
|
22
22
|
}
|
|
23
|
+
/**
|
|
24
|
+
* A store over another, for a copy that must not write the one it reads: what is set or deleted
|
|
25
|
+
* here stays here, and a key deleted here is no longer read from the one under it.
|
|
26
|
+
*/
|
|
27
|
+
export declare class Masked<K, V> extends Store<K, V> {
|
|
28
|
+
private readonly under;
|
|
29
|
+
private readonly hidden;
|
|
30
|
+
constructor(under: Store<K, V>);
|
|
31
|
+
get(key: K): V | undefined;
|
|
32
|
+
delete(key: K): void;
|
|
33
|
+
}
|
package/dist/store.js
CHANGED
|
@@ -62,6 +62,25 @@ export class Store {
|
|
|
62
62
|
this.strong.clear();
|
|
63
63
|
}
|
|
64
64
|
}
|
|
65
|
+
/**
|
|
66
|
+
* A store over another, for a copy that must not write the one it reads: what is set or deleted
|
|
67
|
+
* here stays here, and a key deleted here is no longer read from the one under it.
|
|
68
|
+
*/
|
|
69
|
+
export class Masked extends Store {
|
|
70
|
+
under;
|
|
71
|
+
hidden = new Store();
|
|
72
|
+
constructor(under) {
|
|
73
|
+
super();
|
|
74
|
+
this.under = under;
|
|
75
|
+
}
|
|
76
|
+
get(key) {
|
|
77
|
+
return super.get(key) ?? (this.hidden.get(key) ? undefined : this.under.get(key));
|
|
78
|
+
}
|
|
79
|
+
delete(key) {
|
|
80
|
+
super.delete(key);
|
|
81
|
+
this.hidden.set(key, true);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
65
84
|
/** No key held inline. */
|
|
66
85
|
const none = Symbol('none');
|
|
67
86
|
/** A Map's key equality, SameValueZero: NaN finds NaN. */
|
package/dist/strays.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { unpart } from './fade.js';
|
|
2
2
|
import { pageOut } from './paging.js';
|
|
3
|
-
import { record } from './tape.js';
|
|
3
|
+
import { record, unplayable } from './tape.js';
|
|
4
4
|
/** Notes a motion patch that may hold state for a subject no voice playing it reaches. */
|
|
5
5
|
export function stray(mix, motion, subject) {
|
|
6
6
|
const list = mix.strays.get(subject);
|
|
@@ -49,7 +49,12 @@ export function motionOwner(mix) {
|
|
|
49
49
|
const m = mix.deref();
|
|
50
50
|
if (m === undefined)
|
|
51
51
|
return;
|
|
52
|
-
record(m, 'motion', () => {
|
|
52
|
+
record(m, 'motion', (stage) => {
|
|
53
|
+
const to = stage.mix(m);
|
|
54
|
+
if (to === undefined)
|
|
55
|
+
return;
|
|
56
|
+
if (to !== m)
|
|
57
|
+
return changeAhead(to, id, motion, subject, change);
|
|
53
58
|
const v = cuedById(m, id) ?? m.gone.find((g) => g.id === id);
|
|
54
59
|
(v?.motion ?? motion).change(subject, { ...change });
|
|
55
60
|
});
|
|
@@ -79,6 +84,49 @@ export function motionOwner(mix) {
|
|
|
79
84
|
},
|
|
80
85
|
};
|
|
81
86
|
}
|
|
87
|
+
/**
|
|
88
|
+
* A retarget or push the tape holds, made for a read ahead: kept on its transport for the patch
|
|
89
|
+
* to apply as it reads, since the patch's own state is the live mix's.
|
|
90
|
+
*/
|
|
91
|
+
function changeAhead(to, id, motion, given, change) {
|
|
92
|
+
const subject = given;
|
|
93
|
+
const v = cuedById(to, id);
|
|
94
|
+
let at = change.at;
|
|
95
|
+
if (v !== undefined) {
|
|
96
|
+
// As the patch asks its mix live: a subject faded out of the voice is back, and a change with
|
|
97
|
+
// no time takes the voice's at this frame, or the subject's own where it is yet to be met.
|
|
98
|
+
const parted = v.parted?.has(subject) === true;
|
|
99
|
+
if (parted)
|
|
100
|
+
unpart(to, v, subject);
|
|
101
|
+
if (at === undefined) {
|
|
102
|
+
const frame = frameOf(to, v, subject);
|
|
103
|
+
const back = parted ? timeIn(to, v, v.spec.stagger ? v.spec.stagger(subject) : 0) : frame;
|
|
104
|
+
if (!Number.isNaN(frame))
|
|
105
|
+
at = frame;
|
|
106
|
+
else if (!Number.isNaN(back))
|
|
107
|
+
at = back;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
if (at === undefined)
|
|
111
|
+
unplayable('a change to a motion made before its voice met the subject');
|
|
112
|
+
const stamped = at;
|
|
113
|
+
const made = to.transport.ahead;
|
|
114
|
+
let bySubject = made.get(motion);
|
|
115
|
+
if (bySubject === undefined) {
|
|
116
|
+
bySubject = new Map();
|
|
117
|
+
made.set(motion, bySubject);
|
|
118
|
+
}
|
|
119
|
+
const list = bySubject.get(subject);
|
|
120
|
+
const c = { at, to: change.to, v: change.v };
|
|
121
|
+
if (list === undefined)
|
|
122
|
+
bySubject.set(subject, [c]);
|
|
123
|
+
else {
|
|
124
|
+
let i = list.length;
|
|
125
|
+
while (i > 0 && list[i - 1].at > stamped)
|
|
126
|
+
i--;
|
|
127
|
+
list.splice(i, 0, c);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
82
130
|
/** The voice in the list with this id, which is in id order. */
|
|
83
131
|
export function cuedById(mix, id) {
|
|
84
132
|
const cued = mix.cued;
|
package/dist/tape.d.ts
CHANGED
|
@@ -1,24 +1,49 @@
|
|
|
1
|
+
import type { Mixer } from './mixer.js';
|
|
1
2
|
import type { Pace } from './pace.js';
|
|
2
3
|
import type { Transport } from './transport.js';
|
|
3
4
|
import type { Tape } from './types.js';
|
|
5
|
+
/**
|
|
6
|
+
* Where a recorded call is made again: on the mixes the host made it on, or, for a read ahead, on
|
|
7
|
+
* its copies of them.
|
|
8
|
+
*/
|
|
9
|
+
export interface Stage {
|
|
10
|
+
transport(of: Transport): Transport;
|
|
11
|
+
/** The mix standing for `of`; undefined where a read ahead has no copy of it. */
|
|
12
|
+
mix<I, O>(of: Mixer<I, O>): Mixer<I, O> | undefined;
|
|
13
|
+
}
|
|
14
|
+
/** Refuses a recorded call a read ahead's copies cannot take. */
|
|
15
|
+
export declare function unplayable(what: string): never;
|
|
4
16
|
/**
|
|
5
17
|
* The tape `history.tape` makes for a transport, stamping each call with the host time it was made
|
|
6
18
|
* at: rate 0 holds mix time still across frames, and host time tells them apart.
|
|
7
19
|
*/
|
|
8
20
|
export declare function tapeOf(transport: Transport): Tape | undefined;
|
|
9
|
-
/**
|
|
21
|
+
/**
|
|
22
|
+
* Records a host call, which `again` makes once more when the mix plays past it after a seek, or
|
|
23
|
+
* when a read ahead does, on the stage it is given.
|
|
24
|
+
*/
|
|
10
25
|
export declare function record(on: {
|
|
11
26
|
readonly tape: Tape | undefined;
|
|
12
27
|
readonly replaying: boolean;
|
|
13
28
|
readonly projecting: boolean;
|
|
14
|
-
}, label: string, again: () => void): void;
|
|
29
|
+
}, label: string, again: (stage: Stage) => void): void;
|
|
15
30
|
/** The host time at which the mix clock reads `t`, under the rate it has now. */
|
|
16
31
|
export declare function hostAt(clock: {
|
|
17
32
|
readonly pace: Pace | null;
|
|
18
33
|
}, t: number): number;
|
|
34
|
+
/**
|
|
35
|
+
* The host time a move ahead to mix time `t` lands at: never behind the clock's own, since rate 0
|
|
36
|
+
* reads `t` at every host time from the earliest `hostAt` gives, and a call stamped earlier than
|
|
37
|
+
* one already on the tape would cut it out of order.
|
|
38
|
+
*/
|
|
39
|
+
export declare function hostAhead(clock: {
|
|
40
|
+
readonly pace: Pace | null;
|
|
41
|
+
readonly u: number;
|
|
42
|
+
}, t: number): number;
|
|
19
43
|
/**
|
|
20
44
|
* Makes the recorded calls `due` by the host time each was made at again, each host time's in a
|
|
21
45
|
* frame of its own with every mix on the transport moved to where it stood then, as the host made
|
|
22
|
-
* them. Answers the host time of the last frame it played, NaN for none.
|
|
46
|
+
* them. Answers the host time of the last frame it played, NaN for none. Given `onto` and the
|
|
47
|
+
* stage of its copies, it makes them on those, and the caller puts the tape back.
|
|
23
48
|
*/
|
|
24
|
-
export declare function replay(transport: Transport, due: (u: number) => boolean): number;
|
|
49
|
+
export declare function replay(transport: Transport, due: (u: number) => boolean, onto?: Transport, on?: Stage): number;
|
package/dist/tape.js
CHANGED
|
@@ -1,4 +1,13 @@
|
|
|
1
1
|
import { move } from './move.js';
|
|
2
|
+
const live = { transport: (of) => of, mix: (of) => of };
|
|
3
|
+
/** The stage the tape is playing calls on; the live one outside a read ahead's replay. */
|
|
4
|
+
let stage = live;
|
|
5
|
+
/** What a call threw on a read ahead's stage, for `replay` to throw once the tape has the entry back. */
|
|
6
|
+
let failed;
|
|
7
|
+
/** Refuses a recorded call a read ahead's copies cannot take. */
|
|
8
|
+
export function unplayable(what) {
|
|
9
|
+
throw new Error(`blits: the tape holds ${what}, which a read ahead cannot play; seek there to see it`);
|
|
10
|
+
}
|
|
2
11
|
/** What a recorded call's `invert` gives: a mix goes back by restoring, never by undoing a call. */
|
|
3
12
|
const undone = { apply() { }, invert: () => undone };
|
|
4
13
|
/**
|
|
@@ -15,7 +24,10 @@ export function tapeOf(transport) {
|
|
|
15
24
|
coalesceWindowMs: 0,
|
|
16
25
|
});
|
|
17
26
|
}
|
|
18
|
-
/**
|
|
27
|
+
/**
|
|
28
|
+
* Records a host call, which `again` makes once more when the mix plays past it after a seek, or
|
|
29
|
+
* when a read ahead does, on the stage it is given.
|
|
30
|
+
*/
|
|
19
31
|
export function record(on, label, again) {
|
|
20
32
|
const tape = on.tape;
|
|
21
33
|
if (tape === undefined || on.replaying || on.projecting)
|
|
@@ -23,7 +35,16 @@ export function record(on, label, again) {
|
|
|
23
35
|
const op = {
|
|
24
36
|
label,
|
|
25
37
|
apply() {
|
|
26
|
-
|
|
38
|
+
if (stage === live)
|
|
39
|
+
again(stage);
|
|
40
|
+
// Thrown inside the tape's `redo`, it would leave the tape without the entry.
|
|
41
|
+
else
|
|
42
|
+
try {
|
|
43
|
+
again(stage);
|
|
44
|
+
}
|
|
45
|
+
catch (e) {
|
|
46
|
+
failed ??= { error: e };
|
|
47
|
+
}
|
|
27
48
|
},
|
|
28
49
|
invert: () => undone,
|
|
29
50
|
};
|
|
@@ -33,16 +54,26 @@ export function record(on, label, again) {
|
|
|
33
54
|
export function hostAt(clock, t) {
|
|
34
55
|
return clock.pace === null ? t : clock.pace.timeOf(t);
|
|
35
56
|
}
|
|
57
|
+
/**
|
|
58
|
+
* The host time a move ahead to mix time `t` lands at: never behind the clock's own, since rate 0
|
|
59
|
+
* reads `t` at every host time from the earliest `hostAt` gives, and a call stamped earlier than
|
|
60
|
+
* one already on the tape would cut it out of order.
|
|
61
|
+
*/
|
|
62
|
+
export function hostAhead(clock, t) {
|
|
63
|
+
const at = hostAt(clock, t);
|
|
64
|
+
return clock.u > at ? clock.u : at;
|
|
65
|
+
}
|
|
36
66
|
/**
|
|
37
67
|
* Makes the recorded calls `due` by the host time each was made at again, each host time's in a
|
|
38
68
|
* frame of its own with every mix on the transport moved to where it stood then, as the host made
|
|
39
|
-
* them. Answers the host time of the last frame it played, NaN for none.
|
|
69
|
+
* them. Answers the host time of the last frame it played, NaN for none. Given `onto` and the
|
|
70
|
+
* stage of its copies, it makes them on those, and the caller puts the tape back.
|
|
40
71
|
*/
|
|
41
|
-
export function replay(transport, due) {
|
|
72
|
+
export function replay(transport, due, onto = transport, on = live) {
|
|
42
73
|
const tape = transport.tape;
|
|
43
74
|
if (tape === undefined)
|
|
44
75
|
return Number.NaN;
|
|
45
|
-
const u =
|
|
76
|
+
const u = onto.u;
|
|
46
77
|
let last = Number.NaN;
|
|
47
78
|
try {
|
|
48
79
|
for (;;) {
|
|
@@ -51,23 +82,30 @@ export function replay(transport, due) {
|
|
|
51
82
|
return last;
|
|
52
83
|
if (at !== last) {
|
|
53
84
|
last = at;
|
|
54
|
-
|
|
55
|
-
const now =
|
|
56
|
-
if (now >
|
|
57
|
-
for (const m of
|
|
85
|
+
onto.u = at;
|
|
86
|
+
const now = onto.pace === null ? at : onto.pace.reading(at);
|
|
87
|
+
if (now > onto.now)
|
|
88
|
+
for (const m of onto.members)
|
|
58
89
|
move(m, now);
|
|
59
|
-
|
|
90
|
+
onto.tick(onto.now);
|
|
60
91
|
}
|
|
61
92
|
transport.replaying = true;
|
|
93
|
+
stage = on;
|
|
62
94
|
try {
|
|
63
95
|
tape.redo();
|
|
64
96
|
}
|
|
65
97
|
finally {
|
|
66
98
|
transport.replaying = false;
|
|
99
|
+
stage = live;
|
|
100
|
+
}
|
|
101
|
+
if (failed !== undefined) {
|
|
102
|
+
const { error } = failed;
|
|
103
|
+
failed = undefined;
|
|
104
|
+
throw error;
|
|
67
105
|
}
|
|
68
106
|
}
|
|
69
107
|
}
|
|
70
108
|
finally {
|
|
71
|
-
|
|
109
|
+
onto.u = u;
|
|
72
110
|
}
|
|
73
111
|
}
|