@msb235/blits 0.7.1 → 0.8.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 +180 -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 +3 -0
- package/dist/clone.js +16 -1
- 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 +213 -28
- 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/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 +44 -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 +49 -6
- package/dist/transport.js +89 -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/seek.js
CHANGED
|
@@ -1,10 +1,14 @@
|
|
|
1
|
-
import { index } from './chain.js';
|
|
2
1
|
import { schedule } from './due.js';
|
|
3
|
-
import { copyHeld, last,
|
|
2
|
+
import { copyHeld, last, lastWithin } from './history.js';
|
|
4
3
|
import { laneHost } from './hosts.js';
|
|
5
4
|
import { Lanes } from './lanes.js';
|
|
6
|
-
import {
|
|
5
|
+
import { Leavings } from './leavings.js';
|
|
6
|
+
import { move, nextFrame } from './move.js';
|
|
7
7
|
import { ownerReading } from './owner.js';
|
|
8
|
+
import { place } from './place.js';
|
|
9
|
+
import { unpull } from './pull.js';
|
|
10
|
+
import { Restored } from './restored.js';
|
|
11
|
+
import { recount } from './roster.js';
|
|
8
12
|
import { refitAll } from './spans.js';
|
|
9
13
|
import { Store } from './store.js';
|
|
10
14
|
import { hostAt, replay } from './tape.js';
|
|
@@ -35,44 +39,6 @@ function restore(voice, live, t) {
|
|
|
35
39
|
h.inputs = inputs;
|
|
36
40
|
return h;
|
|
37
41
|
}
|
|
38
|
-
/**
|
|
39
|
-
* A voice's records after a seek back: each subject's is put back from the records it had, the first
|
|
40
|
-
* time it is asked for, since the subjects a store holds cannot be listed.
|
|
41
|
-
*/
|
|
42
|
-
class Restored extends Store {
|
|
43
|
-
was;
|
|
44
|
-
make;
|
|
45
|
-
left;
|
|
46
|
-
constructor(was, make, left) {
|
|
47
|
-
super();
|
|
48
|
-
this.was = was;
|
|
49
|
-
this.make = make;
|
|
50
|
-
this.left = left;
|
|
51
|
-
}
|
|
52
|
-
/** Subjects whose record has been put back or deleted, which `left` no longer answers for. */
|
|
53
|
-
settled = new Store();
|
|
54
|
-
get(key) {
|
|
55
|
-
const v = super.get(key);
|
|
56
|
-
if (v !== undefined)
|
|
57
|
-
return v;
|
|
58
|
-
// A subject that left after the moment sought had the record it left with, whatever it has now.
|
|
59
|
-
const kept = this.settled.get(key) === undefined ? this.left(key) : undefined;
|
|
60
|
-
this.settled.set(key, true);
|
|
61
|
-
const live = kept ?? this.was.get(key);
|
|
62
|
-
if (live === undefined)
|
|
63
|
-
return undefined;
|
|
64
|
-
this.was.delete(key);
|
|
65
|
-
const made = this.make(live);
|
|
66
|
-
if (made !== undefined)
|
|
67
|
-
super.set(key, made);
|
|
68
|
-
return made;
|
|
69
|
-
}
|
|
70
|
-
delete(key) {
|
|
71
|
-
super.delete(key);
|
|
72
|
-
this.was.delete(key);
|
|
73
|
-
this.settled.set(key, true);
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
42
|
/** Takes a voice cued after the moment sought out of the mix, until the tape cues it again. */
|
|
77
43
|
function park(mix, voice) {
|
|
78
44
|
const owner = voice.owner;
|
|
@@ -86,34 +52,27 @@ function park(mix, voice) {
|
|
|
86
52
|
motion.owner = null;
|
|
87
53
|
voice.park();
|
|
88
54
|
}
|
|
89
|
-
/** Puts a voice's controls and records back as they stood at mix time `t`. */
|
|
90
|
-
function restoreVoice(voice, t) {
|
|
55
|
+
/** Puts a voice's controls and records back as they stood at mix time `t`, the end of frame `seq`. */
|
|
56
|
+
function restoreVoice(mix, voice, t, seq) {
|
|
57
|
+
const cut = { seq, strict: false };
|
|
91
58
|
const log = voice.log;
|
|
92
|
-
const c =
|
|
59
|
+
const c = lastWithin(log, cut) ?? log[0];
|
|
93
60
|
log.length = log.indexOf(c) + 1;
|
|
94
|
-
voice.
|
|
95
|
-
voice.
|
|
96
|
-
voice.rate = c.rate;
|
|
97
|
-
voice.ramp = c.ramp;
|
|
98
|
-
voice.weight = c.weight;
|
|
99
|
-
voice.out = c.out;
|
|
100
|
-
voice.start = c.start;
|
|
101
|
-
voice.outAt = c.outAt;
|
|
102
|
-
voice.outOver = c.outOver;
|
|
103
|
-
voice.outSet = c.outSet;
|
|
104
|
-
if (voice.reopen(t) && voice.owner?.holding)
|
|
61
|
+
voice.take(c);
|
|
62
|
+
if (voice.reopen(seq) && voice.owner?.holding)
|
|
105
63
|
voice.owner.holding.played--;
|
|
106
64
|
voice.doneAt = Number.POSITIVE_INFINITY;
|
|
65
|
+
voice.doneSeq = Number.POSITIVE_INFINITY;
|
|
107
66
|
if (voice.parts !== null) {
|
|
108
67
|
for (const [subject, r] of voice.parts)
|
|
109
|
-
if (r.
|
|
68
|
+
if (r.seq > seq)
|
|
110
69
|
voice.parts.delete(subject);
|
|
111
70
|
if (voice.parts.size === 0)
|
|
112
71
|
voice.parts = null;
|
|
113
72
|
}
|
|
114
73
|
if (voice.parted !== null) {
|
|
115
|
-
for (const [subject,
|
|
116
|
-
if (
|
|
74
|
+
for (const [subject, p] of voice.parted)
|
|
75
|
+
if (p.seq > seq)
|
|
117
76
|
voice.parted.delete(subject);
|
|
118
77
|
if (voice.parted.size === 0)
|
|
119
78
|
voice.parted = null;
|
|
@@ -127,30 +86,43 @@ function restoreVoice(voice, t) {
|
|
|
127
86
|
voice.unreached = null;
|
|
128
87
|
voice.unreachedBits = null;
|
|
129
88
|
voice.seeks++;
|
|
130
|
-
const left = voice.left;
|
|
131
|
-
voice.left = left
|
|
132
|
-
const future = left
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
89
|
+
const left = voice.left?.all() ?? [];
|
|
90
|
+
voice.left = Leavings.from(left.filter((e) => e.seq <= seq));
|
|
91
|
+
const future = Leavings.from(left.filter((e) => e.seq > seq));
|
|
92
|
+
let records = voice.subjects;
|
|
93
|
+
if (!(records instanceof Restored)) {
|
|
94
|
+
records = new Restored(records, (live, least, top) => {
|
|
95
|
+
const h = restore(voice, live, least);
|
|
96
|
+
if (h === undefined)
|
|
97
|
+
return h;
|
|
98
|
+
if (h.reaches)
|
|
99
|
+
voice.seen++;
|
|
100
|
+
if (h.rested)
|
|
101
|
+
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
|
+
if (!(h.since <= top)) {
|
|
110
|
+
voice.early ??= [];
|
|
111
|
+
voice.early.push(h);
|
|
112
|
+
}
|
|
136
113
|
return h;
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
if (voice.state === 'pending') {
|
|
142
|
-
voice.early ??= [];
|
|
143
|
-
voice.early.push(h);
|
|
144
|
-
}
|
|
145
|
-
return h;
|
|
146
|
-
}, (key) => leftAt(future, key, t));
|
|
114
|
+
});
|
|
115
|
+
voice.subjects = records;
|
|
116
|
+
}
|
|
117
|
+
records.back(t, cut, future);
|
|
147
118
|
}
|
|
148
119
|
/** Each voice's state at mix time `t`, once every clock above it is back where it was. */
|
|
149
120
|
function stateAt(voice, t) {
|
|
121
|
+
// A fade begins at once, even one whose ramp starts later, at the voice's start.
|
|
122
|
+
if (voice.out !== null)
|
|
123
|
+
return 'fading';
|
|
150
124
|
if ((voice.owner === null ? t : ownerReading(voice.owner, t)) < voice.start)
|
|
151
125
|
return 'pending';
|
|
152
|
-
if (voice.out !== null && voice.out.at <= t)
|
|
153
|
-
return 'fading';
|
|
154
126
|
if (voice.freezesAfter && voice.elapsedAt(t) >= voice.span + voice.latest)
|
|
155
127
|
return 'frozen';
|
|
156
128
|
return 'live';
|
|
@@ -172,34 +144,43 @@ export function seek(transport, t) {
|
|
|
172
144
|
if (!(t >= transport.born))
|
|
173
145
|
throw new Error(`blits: ${t} is older than this mix's history reaches`);
|
|
174
146
|
const unpaged = cover(transport, t);
|
|
175
|
-
if (t >= transport.now)
|
|
176
|
-
replay(transport, () => t);
|
|
177
|
-
// Under the rate the recorded calls up to `t` set, ahead; behind, under the rate it had then.
|
|
178
|
-
const u = hostAt(transport, t);
|
|
179
|
-
if (!Number.isFinite(u))
|
|
180
|
-
throw new RangeError(`blits: the mix's rate never reaches ${t}`);
|
|
181
147
|
if (t >= transport.now) {
|
|
148
|
+
// Ahead, under the rate the recorded calls up to `t` set, past any frames rate 0 held at `t`.
|
|
149
|
+
const last = replay(transport, (at) => (transport.pace === null ? at : transport.pace.reading(at)) <= t);
|
|
150
|
+
const u = Number.isNaN(last) ? hostAt(transport, t) : Math.max(last, hostAt(transport, t));
|
|
151
|
+
if (!Number.isFinite(u))
|
|
152
|
+
throw new RangeError(`blits: the mix's rate never reaches ${t}`);
|
|
182
153
|
transport.u = u;
|
|
183
154
|
for (const m of transport.members)
|
|
184
155
|
move(m, t);
|
|
185
156
|
}
|
|
186
157
|
else {
|
|
187
|
-
const
|
|
188
|
-
if (
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
158
|
+
const frame = transport.frameAt(t);
|
|
159
|
+
if (frame === undefined)
|
|
160
|
+
throw new Error(`blits: ${t} is older than this mix's history reaches`);
|
|
161
|
+
// On the frame itself, its own host time: rate 0 may have held the clock there for several.
|
|
162
|
+
const u = frame.at === t ? frame.u : hostAt(transport, t);
|
|
163
|
+
const seq = frame.seq;
|
|
164
|
+
tape.goto(tape.depthAt(u));
|
|
165
|
+
transport.undrop(seq);
|
|
166
|
+
// What history kept after the frame is let go; the tape holds the calls that made it.
|
|
167
|
+
transport.announced = transport.announced.filter((a) => a.seq <= seq);
|
|
168
|
+
transport.frames = transport.frames.filter((f) => f.seq <= seq);
|
|
169
|
+
if (transport.pacedSeq > seq) {
|
|
170
|
+
transport.pace = null;
|
|
171
|
+
transport.pacedSeq = Number.POSITIVE_INFINITY;
|
|
172
|
+
}
|
|
173
|
+
else
|
|
174
|
+
transport.pace?.cut(u);
|
|
194
175
|
transport.u = u;
|
|
195
176
|
transport.now = t;
|
|
196
177
|
for (const m of transport.members)
|
|
197
|
-
back(m, t);
|
|
198
|
-
cut(transport,
|
|
178
|
+
back(m, t, seq, frame.at < t);
|
|
179
|
+
cut(transport, seq, unpaged);
|
|
199
180
|
}
|
|
181
|
+
transport.tick(t);
|
|
200
182
|
// The host's clock reads on from here: its next sync reads `t` plus its time since its last.
|
|
201
|
-
transport.offset = transport.last - u;
|
|
202
|
-
transport.u = u;
|
|
183
|
+
transport.offset = transport.last - transport.u;
|
|
203
184
|
transport.kept();
|
|
204
185
|
for (const m of transport.members) {
|
|
205
186
|
m.stir();
|
|
@@ -209,8 +190,11 @@ export function seek(transport, t) {
|
|
|
209
190
|
b.sought();
|
|
210
191
|
}
|
|
211
192
|
}
|
|
212
|
-
/**
|
|
213
|
-
|
|
193
|
+
/**
|
|
194
|
+
* Puts one mix back as it stood at mix time `t`, the end of frame `seq`, its transport already
|
|
195
|
+
* there; `on` past that frame's own time, where it moves on to `t`.
|
|
196
|
+
*/
|
|
197
|
+
function back(mix, t, seq, on) {
|
|
214
198
|
const all = [...mix.cued, ...mix.gone].sort((a, b) => a.id - b.id);
|
|
215
199
|
const kept = [];
|
|
216
200
|
const gone = [];
|
|
@@ -220,14 +204,14 @@ function back(mix, t) {
|
|
|
220
204
|
if (voice.motion !== undefined)
|
|
221
205
|
motions.add(voice.motion);
|
|
222
206
|
for (const voice of all)
|
|
223
|
-
if (voice.
|
|
207
|
+
if (voice.cuedSeq > seq)
|
|
224
208
|
park(mix, voice);
|
|
225
|
-
else if (voice.
|
|
209
|
+
else if (voice.doneSeq <= seq)
|
|
226
210
|
gone.push(voice);
|
|
227
211
|
else
|
|
228
212
|
kept.push(voice);
|
|
229
213
|
for (const voice of kept)
|
|
230
|
-
restoreVoice(voice, t);
|
|
214
|
+
restoreVoice(mix, voice, t, seq);
|
|
231
215
|
for (const voice of kept)
|
|
232
216
|
voice.state = stateAt(voice, t);
|
|
233
217
|
mix.cued = kept;
|
|
@@ -242,28 +226,17 @@ function back(mix, t) {
|
|
|
242
226
|
if (oldLanes !== null)
|
|
243
227
|
mix.lanes = new Lanes(laneHost(mix));
|
|
244
228
|
mix.chains = new Store();
|
|
245
|
-
mix.named = new Store();
|
|
246
|
-
mix.naming = 0;
|
|
247
|
-
mix.general = [];
|
|
248
|
-
for (const voice of kept)
|
|
249
|
-
index(mix, voice);
|
|
250
229
|
mix.owners = kept.some((v) => v.holding !== null) ? kept.filter((v) => v.holding !== null) : null;
|
|
251
|
-
mix
|
|
252
|
-
mix.anchored = kept.filter((v) => v.spec.anchor !== undefined).length;
|
|
230
|
+
recount(mix);
|
|
253
231
|
mix.parters.clear();
|
|
254
232
|
for (const voice of [...kept, ...gone])
|
|
255
233
|
if (voice.parts !== null || voice.parted !== null)
|
|
256
234
|
mix.parters.add(voice);
|
|
257
|
-
mix.steps.push(null, ++mix.version);
|
|
258
235
|
mix.relinks++;
|
|
259
236
|
mix.pose = new Store();
|
|
260
237
|
if (mix.restStamps !== null)
|
|
261
238
|
mix.restStamps = new Store();
|
|
262
|
-
mix
|
|
263
|
-
mix.pulledHeads = [];
|
|
264
|
-
mix.pulledSlots = new Int32Array(0);
|
|
265
|
-
mix.pulledVersion = Number.NaN;
|
|
266
|
-
mix.pulledRelinks = -1;
|
|
239
|
+
unpull(mix);
|
|
267
240
|
for (const voice of [...kept, ...gone]) {
|
|
268
241
|
voice.unreached = null;
|
|
269
242
|
voice.unreachedBits = null;
|
|
@@ -290,10 +263,10 @@ function back(mix, t) {
|
|
|
290
263
|
for (const motion of motions) {
|
|
291
264
|
if (oldLanes !== null && motion.watcher === oldLanes)
|
|
292
265
|
motion.watcher = null;
|
|
293
|
-
motion.rewind(
|
|
266
|
+
motion.rewind(seq);
|
|
294
267
|
}
|
|
295
268
|
// What history kept after `t` is let go; the tape holds the calls that made it.
|
|
296
|
-
mix.hostLog = mix.hostLog.filter((e) => e.
|
|
269
|
+
mix.hostLog = mix.hostLog.filter((e) => e.seq <= seq);
|
|
297
270
|
mix.sent = mix.sent.filter((e) => e.timestamp <= t);
|
|
298
271
|
mix.sentTo = t;
|
|
299
272
|
mix.pins = null;
|
|
@@ -307,5 +280,15 @@ function back(mix, t) {
|
|
|
307
280
|
for (const voice of kept)
|
|
308
281
|
schedule(mix, voice);
|
|
309
282
|
mix.stir();
|
|
310
|
-
move
|
|
283
|
+
// On the frame's own time its move is behind it: a second would land what the next frame does.
|
|
284
|
+
if (on)
|
|
285
|
+
move(mix, t);
|
|
286
|
+
else {
|
|
287
|
+
mix.frame = nextFrame();
|
|
288
|
+
mix.reducedNow = mix.reduced;
|
|
289
|
+
// A voice history let go of may have placed an anchor then: placed again from what it holds.
|
|
290
|
+
const ms = mix.opts.history.ms;
|
|
291
|
+
if (mix.anchored > 0 && mix.transport.forgotTo >= t - ms)
|
|
292
|
+
place(mix);
|
|
293
|
+
}
|
|
311
294
|
}
|
package/dist/shared.d.ts
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A WeakMap every copy of blits loaded in one realm shares, so a fact one copy records about a
|
|
3
|
+
* patch or channel it made is read by another copy's mix. The name carries a version, bumped when
|
|
4
|
+
* what is stored changes shape, so copies that disagree on the shape keep apart.
|
|
5
|
+
*/
|
|
6
|
+
export declare function shared<K extends object, V>(name: string): WeakMap<K, V>;
|
package/dist/shared.js
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A WeakMap every copy of blits loaded in one realm shares, so a fact one copy records about a
|
|
3
|
+
* patch or channel it made is read by another copy's mix. The name carries a version, bumped when
|
|
4
|
+
* what is stored changes shape, so copies that disagree on the shape keep apart.
|
|
5
|
+
*/
|
|
6
|
+
export function shared(name) {
|
|
7
|
+
const key = Symbol.for(`@msb235/blits/${name}`);
|
|
8
|
+
const g = globalThis;
|
|
9
|
+
let map = g[key];
|
|
10
|
+
if (map === undefined) {
|
|
11
|
+
map = new WeakMap();
|
|
12
|
+
g[key] = map;
|
|
13
|
+
}
|
|
14
|
+
return map;
|
|
15
|
+
}
|
package/dist/signals.d.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
import type { Signal } from './types.js';
|
|
1
|
+
import type { Setting, Signal } from './types.js';
|
|
2
|
+
/** Whether a change to an input signal reaches `reading.inputs`, so a lane fill can see it. */
|
|
3
|
+
export declare const heard: (signal: object) => boolean;
|
|
2
4
|
/**
|
|
3
5
|
* The loudest of several signals.
|
|
4
6
|
*
|
|
@@ -6,7 +8,26 @@ import type { Signal } from './types.js';
|
|
|
6
8
|
*/
|
|
7
9
|
export declare function peak<I, H = unknown>(...signals: readonly Signal<I, H>[]): Signal<I, H>;
|
|
8
10
|
/**
|
|
9
|
-
*
|
|
11
|
+
* Marks `read` as a signal driven from outside the clock, which a host's own signal has to be for
|
|
12
|
+
* `assess` and a history's `inputs` to treat it as one. Two uses:
|
|
13
|
+
*
|
|
14
|
+
* `input(read)` is an input of the host's own: a pointer, a volume, a value in a store. The host
|
|
15
|
+
* calls `touch()` after each change to what `read` reads, and a voice weighted by the signal, or by
|
|
16
|
+
* `peak`, `slew`, `lag` or `gate` over it, keeps its lane. A change nobody reports shows once the
|
|
17
|
+
* mix's clock next moves.
|
|
18
|
+
*
|
|
19
|
+
* `input(read, of)` is a signal built on others, as a wrapper that clamps one is: `of` lists every
|
|
20
|
+
* signal `read` calls, and the result is an input where any of them is, and reports its changes
|
|
21
|
+
* where they all do.
|
|
22
|
+
*
|
|
23
|
+
* @category signal
|
|
24
|
+
*/
|
|
25
|
+
export declare function input<I, H = unknown>(read: (subject: I, setting: Setting<void, H>) => number, of?: readonly Signal<I, H>[]): Signal<I, H> & {
|
|
26
|
+
touch(): void;
|
|
27
|
+
};
|
|
28
|
+
/**
|
|
29
|
+
* A signal the host writes: `input` over one number, with `set` reporting each change. klieg's
|
|
30
|
+
* `level`.
|
|
10
31
|
*
|
|
11
32
|
* @category signal
|
|
12
33
|
*/
|
|
@@ -14,7 +35,7 @@ export declare function level<I, H = unknown>(initial?: number): Signal<I, H> &
|
|
|
14
35
|
set(v: number): void;
|
|
15
36
|
};
|
|
16
37
|
/**
|
|
17
|
-
* The package's
|
|
38
|
+
* The package's linear decay primitive: follows its input at a rate limit, no faster than `riseMs` per
|
|
18
39
|
* unit climbing or `fallMs` draining. The mix keeps its state per voice and subject, so two voices
|
|
19
40
|
* handed the same slew each follow on their own. A subject starts on its input, or at `from`
|
|
20
41
|
* unless `dt` is infinite.
|
package/dist/signals.js
CHANGED
|
@@ -1,5 +1,16 @@
|
|
|
1
|
+
import { reading } from './reading.js';
|
|
2
|
+
/** Input signals whose every change bumps `reading.inputs`: `input`'s, and those built only on them. */
|
|
3
|
+
const told = new WeakSet();
|
|
4
|
+
/** Whether a change to an input signal reaches `reading.inputs`, so a lane fill can see it. */
|
|
5
|
+
export const heard = (signal) => told.has(signal);
|
|
1
6
|
const inputOf = (signals) => signals.some((s) => s.input);
|
|
2
|
-
const marked = (read,
|
|
7
|
+
const marked = (read, of) => {
|
|
8
|
+
if (!inputOf(of))
|
|
9
|
+
return read;
|
|
10
|
+
if (of.every((s) => !s.input || told.has(s)))
|
|
11
|
+
told.add(read);
|
|
12
|
+
return Object.assign(read, { input: true });
|
|
13
|
+
};
|
|
3
14
|
/**
|
|
4
15
|
* The loudest of several signals.
|
|
5
16
|
*
|
|
@@ -14,19 +25,46 @@ export function peak(...signals) {
|
|
|
14
25
|
out = v;
|
|
15
26
|
}
|
|
16
27
|
return out;
|
|
17
|
-
},
|
|
28
|
+
}, signals);
|
|
18
29
|
}
|
|
30
|
+
const touch = () => {
|
|
31
|
+
reading.inputs++;
|
|
32
|
+
};
|
|
19
33
|
/**
|
|
20
|
-
*
|
|
34
|
+
* Marks `read` as a signal driven from outside the clock, which a host's own signal has to be for
|
|
35
|
+
* `assess` and a history's `inputs` to treat it as one. Two uses:
|
|
36
|
+
*
|
|
37
|
+
* `input(read)` is an input of the host's own: a pointer, a volume, a value in a store. The host
|
|
38
|
+
* calls `touch()` after each change to what `read` reads, and a voice weighted by the signal, or by
|
|
39
|
+
* `peak`, `slew`, `lag` or `gate` over it, keeps its lane. A change nobody reports shows once the
|
|
40
|
+
* mix's clock next moves.
|
|
41
|
+
*
|
|
42
|
+
* `input(read, of)` is a signal built on others, as a wrapper that clamps one is: `of` lists every
|
|
43
|
+
* signal `read` calls, and the result is an input where any of them is, and reports its changes
|
|
44
|
+
* where they all do.
|
|
45
|
+
*
|
|
46
|
+
* @category signal
|
|
47
|
+
*/
|
|
48
|
+
export function input(read, of) {
|
|
49
|
+
if (of !== undefined)
|
|
50
|
+
return Object.assign(marked(read, of), { touch });
|
|
51
|
+
told.add(read);
|
|
52
|
+
return Object.assign(read, { input: true, touch });
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* A signal the host writes: `input` over one number, with `set` reporting each change. klieg's
|
|
56
|
+
* `level`.
|
|
21
57
|
*
|
|
22
58
|
* @category signal
|
|
23
59
|
*/
|
|
24
60
|
export function level(initial = 0) {
|
|
25
61
|
let value = initial;
|
|
26
|
-
return Object.assign(() => value, {
|
|
27
|
-
input: true,
|
|
62
|
+
return Object.assign(input(() => value), {
|
|
28
63
|
set(v) {
|
|
64
|
+
if (Object.is(v, value))
|
|
65
|
+
return;
|
|
29
66
|
value = v;
|
|
67
|
+
touch();
|
|
30
68
|
},
|
|
31
69
|
});
|
|
32
70
|
}
|
|
@@ -38,7 +76,7 @@ const firstSight = (target, from, setting) => from === undefined || !Number.isFi
|
|
|
38
76
|
? { value: target, seen: Number.NaN }
|
|
39
77
|
: { value: from, seen: setting.timestamp };
|
|
40
78
|
/**
|
|
41
|
-
* The package's
|
|
79
|
+
* The package's linear decay primitive: follows its input at a rate limit, no faster than `riseMs` per
|
|
42
80
|
* unit climbing or `fallMs` draining. The mix keeps its state per voice and subject, so two voices
|
|
43
81
|
* handed the same slew each follow on their own. A subject starts on its input, or at `from`
|
|
44
82
|
* unless `dt` is infinite.
|
|
@@ -56,7 +94,8 @@ export function slew(of, { riseMs = 0, fallMs = 0, from }) {
|
|
|
56
94
|
const gap = target - held.value;
|
|
57
95
|
const ms = gap > 0 ? riseMs : fallMs;
|
|
58
96
|
if (ms > 0) {
|
|
59
|
-
|
|
97
|
+
// No longer than `dt`, which `maxDt` caps, as every step is.
|
|
98
|
+
const step = Math.min(setting.timestamp - held.seen, setting.dt) / ms;
|
|
60
99
|
next = gap > 0 ? Math.min(target, held.value + step) : Math.max(target, held.value - step);
|
|
61
100
|
}
|
|
62
101
|
}
|
|
@@ -64,7 +103,7 @@ export function slew(of, { riseMs = 0, fallMs = 0, from }) {
|
|
|
64
103
|
held.seen = setting.timestamp;
|
|
65
104
|
return next;
|
|
66
105
|
};
|
|
67
|
-
return marked(read,
|
|
106
|
+
return marked(read, [of]);
|
|
68
107
|
}
|
|
69
108
|
/**
|
|
70
109
|
* The exponential counterpart to `slew`: closes the same fraction of the distance to its input in
|
|
@@ -86,7 +125,7 @@ export function lag(of, { riseMs = 0, fallMs = 0, floor = 1e-6, from, }) {
|
|
|
86
125
|
if (!Number.isNaN(held.seen) && Number.isFinite(setting.dt)) {
|
|
87
126
|
const ms = target > held.value ? riseMs : fallMs;
|
|
88
127
|
if (ms > 0) {
|
|
89
|
-
const gap = setting.timestamp - held.seen;
|
|
128
|
+
const gap = Math.min(setting.timestamp - held.seen, setting.dt);
|
|
90
129
|
next = held.value + (target - held.value) * (1 - Math.exp(-gap / ms));
|
|
91
130
|
if (Math.abs(target - next) <= floor)
|
|
92
131
|
next = target;
|
|
@@ -96,7 +135,7 @@ export function lag(of, { riseMs = 0, fallMs = 0, floor = 1e-6, from, }) {
|
|
|
96
135
|
held.seen = setting.timestamp;
|
|
97
136
|
return next;
|
|
98
137
|
};
|
|
99
|
-
return marked(read,
|
|
138
|
+
return marked(read, [of]);
|
|
100
139
|
}
|
|
101
140
|
/**
|
|
102
141
|
* The other half of the pair: a band, not an edge. It drops at `off`, passes at `on`, and between
|
|
@@ -116,5 +155,5 @@ export function gate(of, band) {
|
|
|
116
155
|
}
|
|
117
156
|
return held.on ? 1 : 0;
|
|
118
157
|
};
|
|
119
|
-
return marked(read,
|
|
158
|
+
return marked(read, [of]);
|
|
120
159
|
}
|
package/dist/spans.js
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { elapsedWith, retime } from './clock.js';
|
|
2
2
|
import { retire } from './fade.js';
|
|
3
3
|
import { defaultFit, layout, settle } from './fit.js';
|
|
4
|
+
import { reorigin } from './held.js';
|
|
4
5
|
import { noted } from './history.js';
|
|
5
6
|
import { nextFrame } from './move.js';
|
|
7
|
+
import { leave } from './origin.js';
|
|
6
8
|
import { ownerReading } from './owner.js';
|
|
7
9
|
/** What a span keeps beside its owner: its claim, its fit, and how the last fit came out. */
|
|
8
10
|
export class Fitting {
|
|
@@ -89,6 +91,7 @@ export function refit(mix, span) {
|
|
|
89
91
|
kids.forEach((c, i) => {
|
|
90
92
|
const rate = (c.spec.rate ?? 1) * plan.rate[i];
|
|
91
93
|
const skipped = plan.skip[i] && c.holding === null;
|
|
94
|
+
c.skipped = skipped;
|
|
92
95
|
let moved = false;
|
|
93
96
|
if (c.state === 'pending') {
|
|
94
97
|
const start = local + at[i];
|
|
@@ -99,23 +102,27 @@ export function refit(mix, span) {
|
|
|
99
102
|
c.anchorElapsed = elapsed;
|
|
100
103
|
c.rate = rate;
|
|
101
104
|
c.ramp = null;
|
|
105
|
+
c.clocks = null;
|
|
102
106
|
moved = true;
|
|
103
107
|
}
|
|
104
108
|
}
|
|
105
109
|
else {
|
|
106
110
|
if (skipped) {
|
|
111
|
+
leave(c, local);
|
|
107
112
|
c.rebase(local);
|
|
108
113
|
c.anchorElapsed = c.span;
|
|
109
114
|
c.seeks++;
|
|
110
115
|
moved = true;
|
|
111
116
|
}
|
|
112
117
|
if (c.rate !== rate || c.ramp !== null) {
|
|
118
|
+
leave(c, local);
|
|
113
119
|
retime(c, local, rate, 0);
|
|
114
120
|
moved = true;
|
|
115
121
|
}
|
|
116
122
|
}
|
|
117
123
|
if (moved) {
|
|
118
124
|
mix.frame = nextFrame();
|
|
125
|
+
reorigin(mix, c, mix.now);
|
|
119
126
|
noted(mix, c);
|
|
120
127
|
}
|
|
121
128
|
});
|
package/dist/stack.d.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { Kit } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Folds deltas into one pose by a kit's arithmetic, in order, with no mix and no clock: what a mix
|
|
4
|
+
* gives for voices contributing these deltas at these weights. Each channel starts at its rest, a
|
|
5
|
+
* weight (default 1, held to 0..1) fades a delta toward rest, and the pose is clamped to each
|
|
6
|
+
* channel's bounds. A channel with no rest takes the last delta whose weight is 0.6 or more, where
|
|
7
|
+
* a mix's default band first switches one on, and stays undefined where none is.
|
|
8
|
+
*
|
|
9
|
+
* @category channel
|
|
10
|
+
*/
|
|
11
|
+
export declare function fold<O>(kit: Kit<O>, deltas: readonly Partial<O>[], weights?: readonly number[]): O;
|
package/dist/stack.js
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { copy } from './clone.js';
|
|
2
|
+
import { bound, merged } from './fold.js';
|
|
3
|
+
/** The weight a mix's default band first switches a rest-less channel on at. */
|
|
4
|
+
const ON = 0.6;
|
|
5
|
+
/**
|
|
6
|
+
* Folds deltas into one pose by a kit's arithmetic, in order, with no mix and no clock: what a mix
|
|
7
|
+
* gives for voices contributing these deltas at these weights. Each channel starts at its rest, a
|
|
8
|
+
* weight (default 1, held to 0..1) fades a delta toward rest, and the pose is clamped to each
|
|
9
|
+
* channel's bounds. A channel with no rest takes the last delta whose weight is 0.6 or more, where
|
|
10
|
+
* a mix's default band first switches one on, and stays undefined where none is.
|
|
11
|
+
*
|
|
12
|
+
* @category channel
|
|
13
|
+
*/
|
|
14
|
+
export function fold(kit, deltas, weights) {
|
|
15
|
+
const pose = {};
|
|
16
|
+
const names = Object.keys(kit);
|
|
17
|
+
for (const key of names) {
|
|
18
|
+
const channel = kit[key];
|
|
19
|
+
const rest = channel.rest;
|
|
20
|
+
pose[key] = rest === undefined ? undefined : channel.copy ? channel.copy(rest) : copy(rest);
|
|
21
|
+
}
|
|
22
|
+
for (let i = 0; i < deltas.length; i++) {
|
|
23
|
+
const given = weights?.[i] ?? 1;
|
|
24
|
+
const w = given > 1 ? 1 : given;
|
|
25
|
+
if (!(w > 0))
|
|
26
|
+
continue;
|
|
27
|
+
const delta = deltas[i];
|
|
28
|
+
for (const key of names) {
|
|
29
|
+
const value = delta[key];
|
|
30
|
+
if (value === undefined)
|
|
31
|
+
continue;
|
|
32
|
+
const channel = kit[key];
|
|
33
|
+
if (channel.rest !== undefined && channel.scale)
|
|
34
|
+
pose[key] = channel.fold
|
|
35
|
+
? channel.fold(pose[key], value, w)
|
|
36
|
+
: merged(channel, pose[key], channel.scale(value, w));
|
|
37
|
+
else if (w >= ON)
|
|
38
|
+
pose[key] = pose[key] === undefined ? copy(value) : merged(channel, pose[key], value);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
for (const key of names) {
|
|
42
|
+
const channel = kit[key];
|
|
43
|
+
if (channel.bounds)
|
|
44
|
+
pose[key] = bound(channel, pose[key]);
|
|
45
|
+
}
|
|
46
|
+
return pose;
|
|
47
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { Mixer } from './mixer.js';
|
|
2
|
+
import type { Cut, Transport } from './transport.js';
|
|
3
|
+
/**
|
|
4
|
+
* Throws unless a read back to mix time `t` is exact with no history: every voice plays as it was
|
|
5
|
+
* cued and reads the same whenever it is asked, and no mix on the transport has changed since `t`.
|
|
6
|
+
* A mix keeps only how it stands now, which is how it has stood since its last change.
|
|
7
|
+
*/
|
|
8
|
+
export declare function stands(transport: Transport, t: number): void;
|
|
9
|
+
/** A mix's copy for a read back to `t` with no history: its voices as they stand, each at `t`. */
|
|
10
|
+
export declare function copyStanding<I, O>(mix: Mixer<I, O>, c: Mixer<I, O>, t: number, cut: Cut): void;
|