@msb235/blits 0.7.0 → 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 +196 -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 +33 -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 +36 -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.d.ts +32 -0
- package/dist/gone.js +144 -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 +49 -28
- package/dist/hosts.js +57 -9
- 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 +43 -7
- package/dist/mixer.js +60 -11
- 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 +31 -20
- 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 +188 -30
- 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/scored.d.ts +57 -0
- package/dist/scored.js +196 -0
- 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 +53 -6
- package/dist/transport.js +102 -20
- 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 +55 -25
- package/dist/voice.d.ts +92 -22
- package/dist/voice.js +108 -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/transport.d.ts
CHANGED
|
@@ -1,25 +1,43 @@
|
|
|
1
1
|
import type { Mixer } from './mixer.js';
|
|
2
2
|
import { Pace } from './pace.js';
|
|
3
3
|
import { Pager } from './paging.js';
|
|
4
|
+
import { type ScoreHolders } from './scored.js';
|
|
4
5
|
import type { Marked, Mix, MixOptions, Tape, Transport as TransportApi, TransportOptions, TransportProjection } from './types.js';
|
|
5
6
|
/**
|
|
6
7
|
* A mark the host announced on a score: `at` in host time, so the rate never moves one, and NaN
|
|
7
|
-
* until the next sync for one announced as now before any sync
|
|
8
|
-
* announced, for a read back to know what was known.
|
|
8
|
+
* until the next sync for one announced as now before any sync.
|
|
9
9
|
*/
|
|
10
10
|
export interface Announced {
|
|
11
11
|
name: string;
|
|
12
12
|
score: string | undefined;
|
|
13
13
|
tags: readonly string[];
|
|
14
14
|
at: number;
|
|
15
|
-
made: number;
|
|
16
15
|
order: number;
|
|
16
|
+
/** The frame it was announced in, for a read back to know what was known. */
|
|
17
|
+
seq: number;
|
|
17
18
|
/** The slot of the mix that announced it, -1 for the transport; only it sees an unnamed score. */
|
|
18
19
|
slot: number;
|
|
19
20
|
/** That mix's name, for `Marked.mix`. */
|
|
20
21
|
mix: string | undefined;
|
|
21
22
|
}
|
|
22
23
|
type Member = Mixer<unknown, unknown>;
|
|
24
|
+
/**
|
|
25
|
+
* One frame on the transport: its number, the mix time it stood at and the host time it was made
|
|
26
|
+
* at. Rate 0 holds mix time still across frames, so only the number tells two of them apart.
|
|
27
|
+
*/
|
|
28
|
+
export interface Frame {
|
|
29
|
+
seq: number;
|
|
30
|
+
at: number;
|
|
31
|
+
u: number;
|
|
32
|
+
}
|
|
33
|
+
/** Where history is cut for a seek or read: what frame `seq` made, and before it, is kept. */
|
|
34
|
+
export interface Cut {
|
|
35
|
+
seq: number;
|
|
36
|
+
/** For a read of what a frame showed: of that frame's own changes, only those made before it was read. */
|
|
37
|
+
strict: boolean;
|
|
38
|
+
}
|
|
39
|
+
/** Whether a change made in frame `seq` falls inside `cut`; `early` for one made before the frame was read. */
|
|
40
|
+
export declare function within(cut: Cut, seq: number, early?: boolean): boolean;
|
|
23
41
|
/**
|
|
24
42
|
* The clock its mixes play on: host time less what `rebase` took out, at the rate its `pace`
|
|
25
43
|
* sets, moved by `sync` and `seek`, and the tape of calls a seek plays again. A mix made alone
|
|
@@ -34,14 +52,23 @@ export declare class Transport implements TransportApi {
|
|
|
34
52
|
dropped: {
|
|
35
53
|
mix: Member;
|
|
36
54
|
at: number;
|
|
55
|
+
seq: number;
|
|
37
56
|
}[];
|
|
38
57
|
/** Numbers the mixes as they join, for their order and for whose unnamed score a mark is on. */
|
|
39
58
|
slots: number;
|
|
59
|
+
holders: ScoreHolders;
|
|
40
60
|
private wakeFns;
|
|
41
61
|
/** Whether it has woken since the last sync, so it wakes once. */
|
|
42
62
|
private woken;
|
|
43
63
|
/** The mix clock at the last sync or seek. */
|
|
44
64
|
now: number;
|
|
65
|
+
/**
|
|
66
|
+
* The number of the frame being played: each sync and seek begins one, and so does each host
|
|
67
|
+
* time the tape plays calls again at.
|
|
68
|
+
*/
|
|
69
|
+
seq: number;
|
|
70
|
+
/** Under history, the frames a seek or read back may reach, oldest first. */
|
|
71
|
+
frames: Frame[];
|
|
45
72
|
/** Host time at the last sync: the host's timestamp less every gap `rebase` took out. */
|
|
46
73
|
u: number;
|
|
47
74
|
offset: number;
|
|
@@ -51,8 +78,21 @@ export declare class Transport implements TransportApi {
|
|
|
51
78
|
floor: number;
|
|
52
79
|
last: number;
|
|
53
80
|
rebasing: boolean;
|
|
81
|
+
/** The reach the tape was last pruned to. */
|
|
82
|
+
taped: number;
|
|
54
83
|
/** The rate; null while it has never been set, when mix time is host time. */
|
|
55
84
|
pace: Pace | null;
|
|
85
|
+
/** The latest mix time a voice any mix on it let go of from `gone` had left at. */
|
|
86
|
+
forgotTo: number;
|
|
87
|
+
/**
|
|
88
|
+
* The latest mix time a host changed a mix on it, or a voice or a subject left one: without
|
|
89
|
+
* history, the earliest time a read back still finds the mixes as they stand now.
|
|
90
|
+
*/
|
|
91
|
+
settled: number;
|
|
92
|
+
/** Records a change or a leaving at the mix time now; before the first sync, nothing. */
|
|
93
|
+
settle(): void;
|
|
94
|
+
/** The frame the rate was first set in, which a seek or read back to before it finds unset. */
|
|
95
|
+
pacedSeq: number;
|
|
56
96
|
/** True while the tape makes a recorded call again, so it is not recorded twice. */
|
|
57
97
|
replaying: boolean;
|
|
58
98
|
announced: Announced[];
|
|
@@ -68,11 +108,11 @@ export declare class Transport implements TransportApi {
|
|
|
68
108
|
constructor(history: MixOptions['history'], shared: boolean);
|
|
69
109
|
/** Whether this is a projection's own transport, which records and seeks nothing. */
|
|
70
110
|
get projecting(): boolean;
|
|
71
|
-
/** Takes a mix on, after every mix that joined before it; `slot` keeps a
|
|
111
|
+
/** Takes a mix on, after every mix that joined before it; `slot` keeps one a seek puts back in place. */
|
|
72
112
|
join(mix: Member, slot?: number): void;
|
|
73
113
|
drop<I, O, H>(mix: Mix<I, O, H>): void;
|
|
74
|
-
/** A seek back to `
|
|
75
|
-
undrop(
|
|
114
|
+
/** A seek back to frame `seq`: every mix dropped after it is back on, as it stood then. */
|
|
115
|
+
undrop(seq: number): void;
|
|
76
116
|
seek(time: number): void;
|
|
77
117
|
prepare(time: number): Promise<void>;
|
|
78
118
|
announce(name: string, opts: {
|
|
@@ -89,6 +129,13 @@ export declare class Transport implements TransportApi {
|
|
|
89
129
|
woke(): void;
|
|
90
130
|
sync(timestamp: number): void;
|
|
91
131
|
private syncAt;
|
|
132
|
+
/** Begins a frame at mix time `at` and the host time the transport stands at. */
|
|
133
|
+
tick(at: number): void;
|
|
134
|
+
/**
|
|
135
|
+
* The frame a seek or read to mix time `t` lands on: the last one standing at or before `t`,
|
|
136
|
+
* since rate 0 can hold the clock at `t` for many.
|
|
137
|
+
*/
|
|
138
|
+
frameAt(t: number): Frame | undefined;
|
|
92
139
|
rebase(): void;
|
|
93
140
|
/**
|
|
94
141
|
* Moves `floor` past what the mix clock standing at `now` has let go of. With a store nothing is
|
package/dist/transport.js
CHANGED
|
@@ -1,11 +1,18 @@
|
|
|
1
1
|
import { listedAll } from './marks.js';
|
|
2
2
|
import { move, nextFrame, waits } from './move.js';
|
|
3
3
|
import { Pace } from './pace.js';
|
|
4
|
-
import { Pager, prepare } from './paging.js';
|
|
4
|
+
import { Pager, pageTransport, prepare } from './paging.js';
|
|
5
5
|
import { pin } from './place.js';
|
|
6
6
|
import { projectAll } from './project.js';
|
|
7
|
+
import { scoredDrop, scoredJoin } from './scored.js';
|
|
7
8
|
import { seek } from './seek.js';
|
|
8
9
|
import { record, replay, tapeOf } from './tape.js';
|
|
10
|
+
/** How far the tape's reach moves before it is pruned again, ms of mix time. */
|
|
11
|
+
const TAPE_STEP_MS = 1000;
|
|
12
|
+
/** Whether a change made in frame `seq` falls inside `cut`; `early` for one made before the frame was read. */
|
|
13
|
+
export function within(cut, seq, early = false) {
|
|
14
|
+
return seq < cut.seq || (seq === cut.seq && (!cut.strict || early));
|
|
15
|
+
}
|
|
9
16
|
/**
|
|
10
17
|
* The clock its mixes play on: host time less what `rebase` took out, at the rate its `pace`
|
|
11
18
|
* sets, moved by `sync` and `seek`, and the tape of calls a seek plays again. A mix made alone
|
|
@@ -20,11 +27,19 @@ export class Transport {
|
|
|
20
27
|
dropped = [];
|
|
21
28
|
/** Numbers the mixes as they join, for their order and for whose unnamed score a mark is on. */
|
|
22
29
|
slots = 0;
|
|
30
|
+
holders = new Map();
|
|
23
31
|
wakeFns = [];
|
|
24
32
|
/** Whether it has woken since the last sync, so it wakes once. */
|
|
25
33
|
woken = false;
|
|
26
34
|
/** The mix clock at the last sync or seek. */
|
|
27
35
|
now = Number.NaN;
|
|
36
|
+
/**
|
|
37
|
+
* The number of the frame being played: each sync and seek begins one, and so does each host
|
|
38
|
+
* time the tape plays calls again at.
|
|
39
|
+
*/
|
|
40
|
+
seq = 0;
|
|
41
|
+
/** Under history, the frames a seek or read back may reach, oldest first. */
|
|
42
|
+
frames = [];
|
|
28
43
|
/** Host time at the last sync: the host's timestamp less every gap `rebase` took out. */
|
|
29
44
|
u = Number.NaN;
|
|
30
45
|
offset = 0;
|
|
@@ -34,8 +49,24 @@ export class Transport {
|
|
|
34
49
|
floor = Number.NEGATIVE_INFINITY;
|
|
35
50
|
last = Number.NaN;
|
|
36
51
|
rebasing = false;
|
|
52
|
+
/** The reach the tape was last pruned to. */
|
|
53
|
+
taped = Number.NEGATIVE_INFINITY;
|
|
37
54
|
/** The rate; null while it has never been set, when mix time is host time. */
|
|
38
55
|
pace = null;
|
|
56
|
+
/** The latest mix time a voice any mix on it let go of from `gone` had left at. */
|
|
57
|
+
forgotTo = Number.NEGATIVE_INFINITY;
|
|
58
|
+
/**
|
|
59
|
+
* The latest mix time a host changed a mix on it, or a voice or a subject left one: without
|
|
60
|
+
* history, the earliest time a read back still finds the mixes as they stand now.
|
|
61
|
+
*/
|
|
62
|
+
settled = Number.NEGATIVE_INFINITY;
|
|
63
|
+
/** Records a change or a leaving at the mix time now; before the first sync, nothing. */
|
|
64
|
+
settle() {
|
|
65
|
+
if (this.now > this.settled)
|
|
66
|
+
this.settled = this.now;
|
|
67
|
+
}
|
|
68
|
+
/** The frame the rate was first set in, which a seek or read back to before it finds unset. */
|
|
69
|
+
pacedSeq = Number.POSITIVE_INFINITY;
|
|
39
70
|
/** True while the tape makes a recorded call again, so it is not recorded twice. */
|
|
40
71
|
replaying = false;
|
|
41
72
|
announced = [];
|
|
@@ -58,7 +89,7 @@ export class Transport {
|
|
|
58
89
|
get projecting() {
|
|
59
90
|
return this.members.some((m) => m.projecting);
|
|
60
91
|
}
|
|
61
|
-
/** Takes a mix on, after every mix that joined before it; `slot` keeps a
|
|
92
|
+
/** Takes a mix on, after every mix that joined before it; `slot` keeps one a seek puts back in place. */
|
|
62
93
|
join(mix, slot = this.slots++) {
|
|
63
94
|
mix.slot = slot;
|
|
64
95
|
const i = this.members.findIndex((m) => m.slot > slot);
|
|
@@ -66,6 +97,7 @@ export class Transport {
|
|
|
66
97
|
this.members.push(mix);
|
|
67
98
|
else
|
|
68
99
|
this.members.splice(i, 0, mix);
|
|
100
|
+
scoredJoin(mix);
|
|
69
101
|
}
|
|
70
102
|
drop(mix) {
|
|
71
103
|
const m = mix;
|
|
@@ -73,18 +105,19 @@ export class Transport {
|
|
|
73
105
|
if (i < 0)
|
|
74
106
|
throw new Error(`blits: ${m.name ?? 'that mix'} is not on this transport`);
|
|
75
107
|
this.members.splice(i, 1);
|
|
108
|
+
scoredDrop(m);
|
|
76
109
|
m.dropped = true;
|
|
77
110
|
if (this.history !== undefined) {
|
|
78
|
-
this.dropped.push({ mix: m, at: this.now });
|
|
111
|
+
this.dropped.push({ mix: m, at: this.now, seq: this.seq });
|
|
79
112
|
record(this, 'drop', () => this.drop(mix));
|
|
80
113
|
}
|
|
81
114
|
}
|
|
82
|
-
/** A seek back to `
|
|
83
|
-
undrop(
|
|
84
|
-
const back = this.dropped.filter((d) => d.
|
|
115
|
+
/** A seek back to frame `seq`: every mix dropped after it is back on, as it stood then. */
|
|
116
|
+
undrop(seq) {
|
|
117
|
+
const back = this.dropped.filter((d) => d.seq > seq);
|
|
85
118
|
if (back.length === 0)
|
|
86
119
|
return;
|
|
87
|
-
this.dropped = this.dropped.filter((d) => d.
|
|
120
|
+
this.dropped = this.dropped.filter((d) => d.seq <= seq);
|
|
88
121
|
for (const d of back) {
|
|
89
122
|
d.mix.dropped = false;
|
|
90
123
|
this.join(d.mix, d.mix.slot);
|
|
@@ -143,6 +176,8 @@ export class Transport {
|
|
|
143
176
|
}
|
|
144
177
|
}
|
|
145
178
|
syncAt(timestamp) {
|
|
179
|
+
if (Number.isNaN(timestamp))
|
|
180
|
+
throw new RangeError('blits: sync was given NaN; it takes the host clock as a number');
|
|
146
181
|
if (timestamp < this.last && !this.rebasing)
|
|
147
182
|
throw new RangeError(`blits: sync went back from ${this.last} to ${timestamp}; the host's clock only goes forward, and seek moves the mix`);
|
|
148
183
|
if (this.rebasing && !Number.isNaN(this.last))
|
|
@@ -159,24 +194,39 @@ export class Transport {
|
|
|
159
194
|
this.born = now;
|
|
160
195
|
// Calls the host made before, played again where a seek went back past them; one may set the
|
|
161
196
|
// rate, which moves where this sync lands.
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
197
|
+
// A call played again at this sync's own host time began its frame, moved there before it.
|
|
198
|
+
let begun = false;
|
|
199
|
+
if (this.tape !== undefined && later) {
|
|
200
|
+
begun = replay(this, (at) => at <= u) === u;
|
|
165
201
|
pace = this.pace;
|
|
166
|
-
now = reading();
|
|
202
|
+
now = pace === null ? u : pace.reading(u);
|
|
203
|
+
}
|
|
204
|
+
if (!begun) {
|
|
205
|
+
this.tick(now);
|
|
206
|
+
// Host time moving while the mix clock stands still lands what waits on host time or the host.
|
|
207
|
+
const moved = now !== this.now;
|
|
208
|
+
for (const m of this.members)
|
|
209
|
+
if (moved || (later && pace !== null && waits(m)))
|
|
210
|
+
move(m, now);
|
|
167
211
|
}
|
|
168
|
-
// Host time moving while the mix clock stands still lands what waits on host time or the host.
|
|
169
|
-
const moved = now !== this.now;
|
|
170
|
-
for (const m of this.members)
|
|
171
|
-
if (moved || (later && pace !== null && waits(m)))
|
|
172
|
-
move(m, now);
|
|
173
212
|
const history = this.history;
|
|
174
213
|
this.kept();
|
|
175
214
|
const reach = this.keepsFrom();
|
|
176
|
-
|
|
215
|
+
// In steps: the tape shifts every entry it keeps to let go of the oldest, and one a second
|
|
216
|
+
// behind reach is never played again.
|
|
217
|
+
if (this.tape !== undefined && history !== undefined && !(reach - this.taped < TAPE_STEP_MS)) {
|
|
177
218
|
this.tape.prune(reach);
|
|
219
|
+
this.taped = reach;
|
|
220
|
+
}
|
|
178
221
|
if (history !== undefined && this.dropped.length > 0)
|
|
179
222
|
this.dropped = this.dropped.filter((d) => d.at >= reach);
|
|
223
|
+
const frames = this.frames;
|
|
224
|
+
let n = 0;
|
|
225
|
+
while (n + 1 < frames.length && frames[n + 1].at <= reach)
|
|
226
|
+
n++;
|
|
227
|
+
if (n > 0)
|
|
228
|
+
frames.splice(0, n);
|
|
229
|
+
pageTransport(this);
|
|
180
230
|
this.woken = false;
|
|
181
231
|
// It is a frame too, which asks every weight signal again, so one reading input follows it.
|
|
182
232
|
if (still)
|
|
@@ -191,8 +241,38 @@ export class Transport {
|
|
|
191
241
|
b.sync();
|
|
192
242
|
}
|
|
193
243
|
}
|
|
244
|
+
/** Begins a frame at mix time `at` and the host time the transport stands at. */
|
|
245
|
+
tick(at) {
|
|
246
|
+
const seq = ++this.seq;
|
|
247
|
+
if (this.history !== undefined)
|
|
248
|
+
this.frames.push({ seq, at, u: this.u });
|
|
249
|
+
for (const m of this.members)
|
|
250
|
+
m.looked = false;
|
|
251
|
+
}
|
|
252
|
+
/**
|
|
253
|
+
* The frame a seek or read to mix time `t` lands on: the last one standing at or before `t`,
|
|
254
|
+
* since rate 0 can hold the clock at `t` for many.
|
|
255
|
+
*/
|
|
256
|
+
frameAt(t) {
|
|
257
|
+
const frames = this.frames;
|
|
258
|
+
let lo = 0;
|
|
259
|
+
let hi = frames.length - 1;
|
|
260
|
+
let found;
|
|
261
|
+
while (lo <= hi) {
|
|
262
|
+
const mid = (lo + hi) >> 1;
|
|
263
|
+
const f = frames[mid];
|
|
264
|
+
if (f.at <= t) {
|
|
265
|
+
found = f;
|
|
266
|
+
lo = mid + 1;
|
|
267
|
+
}
|
|
268
|
+
else
|
|
269
|
+
hi = mid - 1;
|
|
270
|
+
}
|
|
271
|
+
return found;
|
|
272
|
+
}
|
|
194
273
|
rebase() {
|
|
195
274
|
this.rebasing = true;
|
|
275
|
+
this.settle();
|
|
196
276
|
}
|
|
197
277
|
/**
|
|
198
278
|
* Moves `floor` past what the mix clock standing at `now` has let go of. With a store nothing is
|
|
@@ -230,6 +310,7 @@ export class Transport {
|
|
|
230
310
|
throw new RangeError(`blits: a mix's rate is a finite number, 0 or more, not ${rate}`);
|
|
231
311
|
if (this.pace === null) {
|
|
232
312
|
this.pace = new Pace(this.history !== undefined);
|
|
313
|
+
this.pacedSeq = this.seq;
|
|
233
314
|
// Until now mix time was host time, so a pending voice's start is the host time it was given.
|
|
234
315
|
for (const m of this.members)
|
|
235
316
|
for (const v of m.cued)
|
|
@@ -237,7 +318,7 @@ export class Transport {
|
|
|
237
318
|
pin(m, v, v.start);
|
|
238
319
|
}
|
|
239
320
|
const reach = this.history === undefined ? Number.NEGATIVE_INFINITY : this.keepsFrom();
|
|
240
|
-
this.pace.change(this.u, rate, over, reach);
|
|
321
|
+
this.pace.change(this.u, rate, over, reach, this.seq);
|
|
241
322
|
for (const m of this.members)
|
|
242
323
|
m.stir();
|
|
243
324
|
record(this, 'rate', () => this.ramp(rate, over));
|
|
@@ -251,14 +332,15 @@ export function announce(transport, name, opts, slot, mix) {
|
|
|
251
332
|
score: opts.score,
|
|
252
333
|
tags: opts.tags ?? [],
|
|
253
334
|
at,
|
|
254
|
-
made: Number.isNaN(transport.now) ? Number.NEGATIVE_INFINITY : transport.now,
|
|
255
335
|
order: transport.nextId++,
|
|
336
|
+
seq: transport.seq,
|
|
256
337
|
slot,
|
|
257
338
|
mix,
|
|
258
339
|
};
|
|
259
340
|
transport.announced.push(mark);
|
|
341
|
+
transport.settle();
|
|
260
342
|
record(transport, 'announce', () => {
|
|
261
|
-
transport.announced.push({ ...mark });
|
|
343
|
+
transport.announced.push({ ...mark, seq: transport.seq });
|
|
262
344
|
});
|
|
263
345
|
}
|
|
264
346
|
/**
|
package/dist/turns.d.ts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { Laned } from './lane.js';
|
|
2
|
+
/** `d` less whole turns, so within half a turn of 0: the short way round a circle of `turn`. */
|
|
3
|
+
export declare function shortWay(d: number, turn: number): number;
|
|
4
|
+
/**
|
|
5
|
+
* Folds a rotation's value into a subject's numbers at `base`, at weight `w`, in the channel's own
|
|
6
|
+
* arithmetic: an angle the short way round and then as a sum, anything else through the channel's
|
|
7
|
+
* `fold`, which takes its axes as one value.
|
|
8
|
+
*/
|
|
9
|
+
export declare function turnInto(ch: Laned, values: Float64Array, base: number, value: unknown, w: number): void;
|
|
10
|
+
/** `turnInto` for a value held as numbers at `from` in `source`, as a locus holds what it gathered. */
|
|
11
|
+
export declare function turnFrom(ch: Laned, values: Float64Array, base: number, source: Float64Array, from: number, w: number): void;
|
|
12
|
+
/**
|
|
13
|
+
* A locus member's rotation lerped by `u` into what the members before it left at `base`, through
|
|
14
|
+
* the channel's own `lerp`; the first member's value as it is, where `taken` is 0.
|
|
15
|
+
*/
|
|
16
|
+
export declare function turnToward(ch: Laned, values: Float64Array, base: number, value: unknown, taken: number, u: number): void;
|
|
17
|
+
/** Sets every numbered subject's values on a lane to the channel's rest, as a fill starts from. */
|
|
18
|
+
export declare function rested(ch: Laned, size: number): void;
|
package/dist/turns.js
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { doubles } from './channels.js';
|
|
2
|
+
/** `d` less whole turns, so within half a turn of 0: the short way round a circle of `turn`. */
|
|
3
|
+
export function shortWay(d, turn) {
|
|
4
|
+
return d - turn * Math.round(d / turn);
|
|
5
|
+
}
|
|
6
|
+
let held = doubles(4, 0);
|
|
7
|
+
let other = doubles(4, 0);
|
|
8
|
+
/** A subject's numbers on a lane as an array a channel's own functions take: `to`, or one as long. */
|
|
9
|
+
function lift(values, base, axes, to) {
|
|
10
|
+
const out = to.length === axes ? to : doubles(axes, 0);
|
|
11
|
+
for (let a = 0; a < axes; a++)
|
|
12
|
+
out[a] = values[base + a];
|
|
13
|
+
return out;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Folds a rotation's value into a subject's numbers at `base`, at weight `w`, in the channel's own
|
|
17
|
+
* arithmetic: an angle the short way round and then as a sum, anything else through the channel's
|
|
18
|
+
* `fold`, which takes its axes as one value.
|
|
19
|
+
*/
|
|
20
|
+
export function turnInto(ch, values, base, value, w) {
|
|
21
|
+
if (ch.turn > 0) {
|
|
22
|
+
values[base] = values[base] + shortWay(value, ch.turn) * w;
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
const axes = ch.axes;
|
|
26
|
+
held = lift(values, base, axes, held);
|
|
27
|
+
const out = ch.channel.fold(held, value, w);
|
|
28
|
+
for (let a = 0; a < axes; a++)
|
|
29
|
+
values[base + a] = out[a];
|
|
30
|
+
}
|
|
31
|
+
/** `turnInto` for a value held as numbers at `from` in `source`, as a locus holds what it gathered. */
|
|
32
|
+
export function turnFrom(ch, values, base, source, from, w) {
|
|
33
|
+
if (ch.turn > 0) {
|
|
34
|
+
turnInto(ch, values, base, source[from], w);
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
other = lift(source, from, ch.axes, other);
|
|
38
|
+
turnInto(ch, values, base, other, w);
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* A locus member's rotation lerped by `u` into what the members before it left at `base`, through
|
|
42
|
+
* the channel's own `lerp`; the first member's value as it is, where `taken` is 0.
|
|
43
|
+
*/
|
|
44
|
+
export function turnToward(ch, values, base, value, taken, u) {
|
|
45
|
+
if (ch.turn > 0) {
|
|
46
|
+
const was = values[base];
|
|
47
|
+
const v = value;
|
|
48
|
+
values[base] = taken === 0 ? v : was + shortWay(v - was, ch.turn) * u;
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
const axes = ch.axes;
|
|
52
|
+
let out = value;
|
|
53
|
+
if (taken !== 0) {
|
|
54
|
+
held = lift(values, base, axes, held);
|
|
55
|
+
out = ch.channel.lerp(held, value, u);
|
|
56
|
+
}
|
|
57
|
+
for (let a = 0; a < axes; a++)
|
|
58
|
+
values[base + a] = out[a];
|
|
59
|
+
}
|
|
60
|
+
/** Sets every numbered subject's values on a lane to the channel's rest, as a fill starts from. */
|
|
61
|
+
export function rested(ch, size) {
|
|
62
|
+
const values = ch.values;
|
|
63
|
+
const axes = ch.axes;
|
|
64
|
+
if (ch.op !== 'own') {
|
|
65
|
+
values.fill(ch.rest, 0, size * axes);
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
const start = ch.start;
|
|
69
|
+
for (let i = 0; i < size * axes; i += axes)
|
|
70
|
+
for (let a = 0; a < axes; a++)
|
|
71
|
+
values[i + a] = start[a];
|
|
72
|
+
}
|
package/dist/tweened.js
CHANGED
|
@@ -5,7 +5,7 @@ const SPAN = 1e-4;
|
|
|
5
5
|
* `read` asks for it; a mix and a retarget never use it.
|
|
6
6
|
*/
|
|
7
7
|
let slope = false;
|
|
8
|
-
/** The last tween sample's
|
|
8
|
+
/** The last tween sample's progress and curve, and the share of the way it had left to go. */
|
|
9
9
|
const memo = { u: Number.NaN, ease: undefined, left: 0 };
|
|
10
10
|
/** Turns the velocity `eased` works out on or off. */
|
|
11
11
|
export function sloped(on) {
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One field of a delta, with its own arithmetic.
|
|
3
|
+
*
|
|
4
|
+
* @category channel
|
|
5
|
+
*/
|
|
6
|
+
export interface Channel<V> {
|
|
7
|
+
/**
|
|
8
|
+
* Names this channel's arithmetic, so a patch written against one channel can be checked against
|
|
9
|
+
* another of the same name. Two channels of one kind must fold identically; the stock ones set it,
|
|
10
|
+
* as `'sum'` or `'vec(3, sum)'`. Absent, a channel matches only itself.
|
|
11
|
+
*/
|
|
12
|
+
kind?: string;
|
|
13
|
+
/**
|
|
14
|
+
* The range a numeric value means anything in, such as 0..1 for an opacity. The mix clamps the
|
|
15
|
+
* folded value to it, axis by axis for an array, so neither stacked voices nor a retarget that
|
|
16
|
+
* carries speed can push it out; overshoot stops flat at the bound.
|
|
17
|
+
*/
|
|
18
|
+
bounds?: readonly [min: number, max: number];
|
|
19
|
+
/** Identity. Absent means the channel has none: it replaces rather than contributes. */
|
|
20
|
+
rest?: V;
|
|
21
|
+
/** Fold two contributions into one. */
|
|
22
|
+
merge(a: V, b: V): V;
|
|
23
|
+
/** Fade toward `rest` by weight 0..1. Required when `rest` is set; absent otherwise. */
|
|
24
|
+
scale?(v: V, w: number): V;
|
|
25
|
+
/** Interpolate, for retargeting, for blending alternatives, and for folding a locus. */
|
|
26
|
+
lerp(a: V, b: V, u: number): V;
|
|
27
|
+
/**
|
|
28
|
+
* Optional: `merge(into, scale(v, w))`, written into `into` and returned, so a channel whose
|
|
29
|
+
* values are objects need not allocate per contribution. The mix only hands it a value it made.
|
|
30
|
+
*/
|
|
31
|
+
fold?(into: V, v: V, w: number): V;
|
|
32
|
+
/**
|
|
33
|
+
* Optional: a fresh copy of `v`, which the mix makes of `rest` for `fold` to write into. The mix
|
|
34
|
+
* copies arrays, typed arrays and plain objects itself; a channel whose `rest` is anything else,
|
|
35
|
+
* such as a class instance, and that has `fold`, needs this or `cue` refuses it.
|
|
36
|
+
*/
|
|
37
|
+
copy?(v: V): V;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* The channel set for one kind of delta.
|
|
41
|
+
*
|
|
42
|
+
* @category channel
|
|
43
|
+
*/
|
|
44
|
+
export type Kit<O> = {
|
|
45
|
+
readonly [K in keyof O]-?: Channel<NonNullable<O[K]>>;
|
|
46
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import type { VoiceSpec } from './voice.js';
|
|
2
|
+
/**
|
|
3
|
+
* What a mix or transport remembers, for `seek` and `project` to read back: see
|
|
4
|
+
* `MixOptions.history`.
|
|
5
|
+
*
|
|
6
|
+
* @category mix
|
|
7
|
+
*/
|
|
8
|
+
export interface HistoryOptions {
|
|
9
|
+
/** How much stays in memory, ms of mix time. With a `store`, the rest is paged out, not dropped. */
|
|
10
|
+
ms: number;
|
|
11
|
+
every?: number;
|
|
12
|
+
inputs?: boolean;
|
|
13
|
+
tape?: TapeMaker;
|
|
14
|
+
/**
|
|
15
|
+
* Where history older than `ms` goes instead of being dropped. A `seek` or `project` reaching
|
|
16
|
+
* past memory needs `prepare` first, or throws `HistoryMiss`. With a store, blits never prunes
|
|
17
|
+
* the tape: the host bounds it.
|
|
18
|
+
*/
|
|
19
|
+
store?: HistoryStore;
|
|
20
|
+
/** Rebuilds a cued voice's spec from the descriptor it was cued with, so the voice can be paged out. */
|
|
21
|
+
revive?(d: Described): VoiceSpec<any, any>;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* A voice as plain data: `kind` names what built it, and `data` survives `structuredClone`.
|
|
25
|
+
*
|
|
26
|
+
* @category mix
|
|
27
|
+
*/
|
|
28
|
+
export interface Described {
|
|
29
|
+
kind: string;
|
|
30
|
+
data: unknown;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Where a history `store` keeps what memory let go of. blits decides what is kept and when it is
|
|
34
|
+
* read back; the store decides where it lives.
|
|
35
|
+
*
|
|
36
|
+
* @category mix
|
|
37
|
+
*/
|
|
38
|
+
export interface HistoryStore {
|
|
39
|
+
/** Records leaving memory. One brought back by a seek may be paged again: keep one per key and `seq`. */
|
|
40
|
+
page(out: readonly Paged[]): void;
|
|
41
|
+
/**
|
|
42
|
+
* What a restore to mix time `t` needs, for every mix: from each stream, per voice and subject,
|
|
43
|
+
* the latest record at or before `t` and every record after it. Returning everything is fine.
|
|
44
|
+
*/
|
|
45
|
+
load(t: number): Promise<readonly Paged[]>;
|
|
46
|
+
/**
|
|
47
|
+
* A seek went back to frame `seq`: every record whose `seq` is greater is from a future the tape
|
|
48
|
+
* makes again. Forget them.
|
|
49
|
+
*/
|
|
50
|
+
cut(seq: number): void;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* What a history record holds: a voice's `controls`, a stateful record's `snap`, an `input`
|
|
54
|
+
* signal's reading, a subject's record when it `left` a voice, a motion subject's `released` run
|
|
55
|
+
* or older `stretch`, the `host` fields patches read, a whole `voice` that has left, or the
|
|
56
|
+
* transport's own: a `mark` announced on it, a change of its `pace`, and a `frame` it played.
|
|
57
|
+
*
|
|
58
|
+
* @category mix
|
|
59
|
+
*/
|
|
60
|
+
export type PagedStream = 'voice' | 'snap' | 'input' | 'left' | 'released' | 'stretch' | 'host' | 'controls' | 'mark' | 'pace' | 'frame';
|
|
61
|
+
/**
|
|
62
|
+
* One history record leaving memory, as plain data.
|
|
63
|
+
*
|
|
64
|
+
* @category mix
|
|
65
|
+
*/
|
|
66
|
+
export interface Paged {
|
|
67
|
+
/** The mix's `name`; '' for a mix made alone without one, and for the transport's own records. */
|
|
68
|
+
mix: string;
|
|
69
|
+
stream: PagedStream;
|
|
70
|
+
/** The voice's id, for every stream but `host` and the transport's own. */
|
|
71
|
+
voice?: number;
|
|
72
|
+
/** The subject's key, for a record of one subject; for a `mark`, the number it was announced as. */
|
|
73
|
+
subject?: string | number;
|
|
74
|
+
/** Mix time. */
|
|
75
|
+
at: number;
|
|
76
|
+
/**
|
|
77
|
+
* The transport's frame it was made in. Rate 0 holds mix time still across frames, and a fade
|
|
78
|
+
* given a time already passed is filed behind the frame that decided it, so a cut goes by this.
|
|
79
|
+
*/
|
|
80
|
+
seq: number;
|
|
81
|
+
data: unknown;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* One host call a mix recorded on its tape: `apply` makes it again, and `invert` does nothing,
|
|
85
|
+
* since a mix goes back by restoring what it kept rather than by undoing calls.
|
|
86
|
+
*
|
|
87
|
+
* @category mix
|
|
88
|
+
*/
|
|
89
|
+
export interface TapeOp {
|
|
90
|
+
apply(adapter: unknown): void;
|
|
91
|
+
invert(): TapeOp;
|
|
92
|
+
label?: string;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* One future a tape keeps at its current entry: the calls the host made after the mix last went
|
|
96
|
+
* back here, before it made new ones.
|
|
97
|
+
*
|
|
98
|
+
* @category mix
|
|
99
|
+
*/
|
|
100
|
+
export interface TapeBranch {
|
|
101
|
+
/** Its first entry's id, which `switchBranch` takes. */
|
|
102
|
+
id: number;
|
|
103
|
+
label: string;
|
|
104
|
+
timestamp: number;
|
|
105
|
+
/** How many entries run along it from here. */
|
|
106
|
+
length: number;
|
|
107
|
+
/** Whether the mix plays it on from here. */
|
|
108
|
+
current: boolean;
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Where a mix keeps the host's calls, by host time, for `seek`. weasel-history's `History`
|
|
112
|
+
* (`@weasel-js/history`) has this shape. A host reads `branches` and calls `switchBranch` to pick
|
|
113
|
+
* which recorded future plays on; moving it any other way is the mix's job.
|
|
114
|
+
*
|
|
115
|
+
* @category mix
|
|
116
|
+
*/
|
|
117
|
+
export interface Tape {
|
|
118
|
+
recordEntry(ops: TapeOp[], label: string): void;
|
|
119
|
+
undoDepth(): number;
|
|
120
|
+
timestampAt(i: number): number | undefined;
|
|
121
|
+
depthAt(t: number): number;
|
|
122
|
+
goto(n: number): void;
|
|
123
|
+
redo(): void;
|
|
124
|
+
prune(t: number): void;
|
|
125
|
+
branches(): readonly TapeBranch[];
|
|
126
|
+
switchBranch(id: number): void;
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Makes a mix's tape: weasel-history's `createHistory` is one. The mix passes the clock it stamps
|
|
130
|
+
* calls with, branching on, so a call made after going back keeps the old future as a branch, and
|
|
131
|
+
* coalescing off.
|
|
132
|
+
*
|
|
133
|
+
* @category mix
|
|
134
|
+
*/
|
|
135
|
+
export type TapeMaker = (adapter: unknown, opts: {
|
|
136
|
+
now: () => number;
|
|
137
|
+
branching: boolean;
|
|
138
|
+
coalesceWindowMs: number;
|
|
139
|
+
}) => Tape;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|