@msb235/blits 0.8.1 → 0.10.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 +98 -0
- package/README.md +12 -2
- package/dist/book.d.ts +6 -5
- package/dist/book.js +20 -16
- package/dist/clock.d.ts +10 -0
- package/dist/clock.js +23 -0
- package/dist/cue.js +5 -8
- package/dist/due.js +20 -13
- package/dist/everyone.js +3 -0
- package/dist/fade.d.ts +1 -0
- package/dist/fade.js +10 -4
- package/dist/fill.js +5 -0
- package/dist/fold.js +28 -22
- package/dist/gone.d.ts +4 -4
- package/dist/gone.js +11 -10
- package/dist/handle.js +17 -9
- package/dist/held.d.ts +10 -0
- package/dist/held.js +28 -1
- package/dist/hosts.d.ts +1 -1
- package/dist/hosts.js +5 -1
- package/dist/keyfill.js +5 -1
- package/dist/locus.d.ts +4 -1
- package/dist/locus.js +12 -2
- package/dist/marks.js +4 -4
- package/dist/mixer.d.ts +2 -1
- package/dist/mixer.js +3 -0
- package/dist/motion.d.ts +6 -3
- package/dist/motions.d.ts +5 -2
- package/dist/motions.js +3 -1
- package/dist/move.js +51 -14
- package/dist/origin.js +3 -2
- package/dist/owner.d.ts +2 -0
- package/dist/owner.js +10 -2
- package/dist/place.js +17 -3
- package/dist/project.d.ts +1 -1
- package/dist/project.js +11 -6
- package/dist/record.js +2 -1
- package/dist/revive.d.ts +2 -0
- package/dist/revive.js +2 -0
- package/dist/seek.js +2 -2
- package/dist/spans.d.ts +5 -4
- package/dist/spans.js +11 -10
- package/dist/strays.js +13 -5
- package/dist/transport.js +4 -2
- package/dist/types/mix.d.ts +6 -3
- package/dist/types/patch.d.ts +7 -3
- package/dist/types/place.d.ts +18 -8
- package/dist/types/voice.d.ts +22 -4
- package/dist/unpage.d.ts +1 -1
- package/dist/unpage.js +2 -3
- package/dist/voice.d.ts +52 -0
- package/dist/voice.js +103 -2
- package/dist/warn.d.ts +2 -0
- package/dist/warn.js +17 -0
- package/dist/weigh.js +5 -0
- package/docs/schema.html +98 -32
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,104 @@ This package follows [semver](https://semver.org). Below 1.0.0, a breaking chang
|
|
|
4
4
|
version and everything else the patch. Each release lists its changes as **Breaking**, **Added** and
|
|
5
5
|
**Fixed**, and the release workflow refuses a tag with no section here.
|
|
6
6
|
|
|
7
|
+
## 0.10.0
|
|
8
|
+
|
|
9
|
+
### Breaking
|
|
10
|
+
|
|
11
|
+
- A booker is ended with `cancel()`, and `take` returns `{ cancel() }` to hear that its item moved
|
|
12
|
+
or went away. Both were `stop`: `mix.book(...).stop()` and `{ stop() }`.
|
|
13
|
+
|
|
14
|
+
### Added
|
|
15
|
+
|
|
16
|
+
- A booked hit carries `subjects`: the ones its voice names with `subjects`, or undefined for a
|
|
17
|
+
voice that names none. `BookedHit` and `BookOptions` take the subject type as a second
|
|
18
|
+
parameter.
|
|
19
|
+
- `at(timestamp)` on a booker: where a host time falls on the outside clock, by the offset the
|
|
20
|
+
booker keeps.
|
|
21
|
+
- `mix.timestamp`: the host's timestamp at the last sync.
|
|
22
|
+
|
|
23
|
+
## 0.9.0
|
|
24
|
+
|
|
25
|
+
### Breaking
|
|
26
|
+
|
|
27
|
+
- A stock `last()` channel in a locus takes its heaviest member's value, and the later cued of
|
|
28
|
+
two as heavy. It stepped through its members in cue order, so three voices at weights 0.3,
|
|
29
|
+
0.3, and 0.4 showed the second. Two members read as before.
|
|
30
|
+
- A placement whose `end` comes before its start is an instant: the voice starts and ends at its
|
|
31
|
+
start, shows nothing, and warns once outside production. Its `end` mark was reported ahead of
|
|
32
|
+
its `start`, so a voice anchored after it started before it did, and with a `fade.out` it
|
|
33
|
+
showed a whole fade from its start. An `end` that leaves less room than `fade.out` now
|
|
34
|
+
shortens the fade to finish when asked, where the fade ran its whole length past it, and an
|
|
35
|
+
`out` before the start is reported at the start, where its fade always began.
|
|
36
|
+
- A `to` or `push` that brings back a subject faded out of its voice is made at the frame of
|
|
37
|
+
the call, as one for a subject never faded is. It was made at the subject's next read, so how
|
|
38
|
+
far the motion had got depended on when the host next probed the subject.
|
|
39
|
+
- A handle `seek` on a pending voice holds: the voice starts on time, already at the sought
|
|
40
|
+
position, and one freezing before shows that frame while it waits. The seek was taken from
|
|
41
|
+
the moment of the call, and lost outright when an anchor or the mix's rate moved the start.
|
|
42
|
+
- A rate of 0, a negative rate, or a ramp set on a pending voice applies from its start, as a
|
|
43
|
+
rate above 0 always did. At rate 0 the voice starts on time and holds its first frame, where
|
|
44
|
+
it stopped its clock short of its start for good and never showed.
|
|
45
|
+
|
|
46
|
+
- A voice playing backward ends where it starts. When its clock runs back to 0, `played`
|
|
47
|
+
resolves and it leaves over `fade.out`, showing its first frame through the fade, or holds
|
|
48
|
+
that frame where it freezes before; its `coast`, `out`, and `end` marks are there. A voice at
|
|
49
|
+
a rate below 0 showed nothing once past its start and never left the mix. A stateful patch
|
|
50
|
+
steps once more to the moment its subject reached its start.
|
|
51
|
+
- A voice cued at a rate below 0 with no `seek` starts at its end, where it started at 0 and
|
|
52
|
+
was over at once.
|
|
53
|
+
- A fade in counts from no earlier than its voice's start. A subject whose delay had already run
|
|
54
|
+
out when the voice started (a negative `stagger`, or a seek made in the frame of the cue)
|
|
55
|
+
came in with its fade partway or done.
|
|
56
|
+
|
|
57
|
+
### Added
|
|
58
|
+
|
|
59
|
+
- `seek` on a cue: where in itself the voice starts, in voice ms.
|
|
60
|
+
- A voice with no end cued backward, a loop for good or a patch with no duration, plays from 0
|
|
61
|
+
into the pass before, with `setting.pass` and `setting.elapsed` below 0. It runs on the
|
|
62
|
+
general path.
|
|
63
|
+
- `handle.ramp` through 0 eases a voice into reverse, or out of it.
|
|
64
|
+
- `@msb235/blits-tone`, a package in `packages/tone` that drives Tone.js from a mix on the audio
|
|
65
|
+
clock. Its own changelog has what it does.
|
|
66
|
+
|
|
67
|
+
### Fixed
|
|
68
|
+
|
|
69
|
+
- `project(mix.now)` reads what `probe` does. It landed what the next sync would, so after a
|
|
70
|
+
handle `seek` past a voice's end it showed the voice gone while the mix still showed its last
|
|
71
|
+
frame, and after a fade it placed a voice anchored on the faded one a frame early.
|
|
72
|
+
- A read back of a voice that was pending counts each subject's origin from the controls the
|
|
73
|
+
voice had then, not from where a later seek, rate, or anchor has since put it.
|
|
74
|
+
|
|
75
|
+
- A seek back inside `history.ms` plays the host's calls again when the mix's rate is above 1.
|
|
76
|
+
The tape was let go of by mix time while its calls are stamped in host time, so at rate 2 a call
|
|
77
|
+
made 250 ms of host time before a seek back was already gone and never played again.
|
|
78
|
+
- A voice taken out with a time already older than history reaches, such as `fade({ at })` given
|
|
79
|
+
a moment long past, comes back for a seek or a read back to before that call. It was forgotten
|
|
80
|
+
at the next sync, so `seek` and `project` to the frame before the call found no voice.
|
|
81
|
+
- A `HistoryStore` is told of every seek back through `cut`, as its doc says. It was told only
|
|
82
|
+
when the seek read records from the store, so a mark announced with a past `at`, paged at once,
|
|
83
|
+
stayed in the store after a seek back to before it was announced.
|
|
84
|
+
- A `color(last())` channel on a lane switches off when its voice's weight falls to 0, as it does
|
|
85
|
+
off lanes. A lane left it on, so a weight coming back inside the band (0.4 to 0.6 by default)
|
|
86
|
+
showed the color where a mix with `lanes: false` showed none.
|
|
87
|
+
|
|
88
|
+
- A span's children play from where the one before each ended, not from the frame that noticed
|
|
89
|
+
it. Each child leaving fitted the rest again from the sync's time, so the next started up to a
|
|
90
|
+
frame late and the rest sped up to make room: 200 children of 10 ms in a 2000 ms span, fitted
|
|
91
|
+
with none skipped at cue, had 199 skipped once synced every 16 ms. A read ahead never fitted
|
|
92
|
+
again, so `project` showed a child retimed by `faster` up to a frame ahead of the mix.
|
|
93
|
+
|
|
94
|
+
- A voice whose owner runs backward leaves over its `fade.out` from where it reached its start,
|
|
95
|
+
as one turned back by its own handle does. An owner run back to its start left at once and
|
|
96
|
+
took every voice it held with it, so a child with a 100 ms `fade.out` vanished in one frame
|
|
97
|
+
and listed no `coast`, `out`, or `end` mark. An owner at its start now stays for what it
|
|
98
|
+
holds and leaves with its last child.
|
|
99
|
+
|
|
100
|
+
- `setting.dt` is the gap since the subject was last sampled for a patch with no `step`, and for
|
|
101
|
+
a weight signal, as its doc says. It was the time since the subject's origin, growing every
|
|
102
|
+
frame: a mix synced at 16, 48, and 64 handed such a patch 16, 48, and 64, and now hands it 16,
|
|
103
|
+
32, and 16.
|
|
104
|
+
|
|
7
105
|
## 0.8.1
|
|
8
106
|
|
|
9
107
|
### Fixed
|
package/README.md
CHANGED
|
@@ -116,9 +116,14 @@ state shared between subjects, a `step` that lands differently from the same sta
|
|
|
116
116
|
[three.quarks](https://github.com/Alchemist0823/three.quarks) particles. It is published separately
|
|
117
117
|
so the engine keeps no dependencies; its README says how it works.
|
|
118
118
|
|
|
119
|
+
`packages/tone` is `@msb235/blits-tone`, a driver that schedules a mix on the audio clock through
|
|
120
|
+
[Tone.js](https://tonejs.github.io): channels as parameter curves read ahead, and hits and marks at
|
|
121
|
+
their sample. It is published separately for the same reason, and its README says what it does not
|
|
122
|
+
do yet.
|
|
123
|
+
|
|
119
124
|
## Status
|
|
120
125
|
|
|
121
|
-
v0.
|
|
126
|
+
v0.10.0, with five consumers. magicsmoke runs every fault on it, wod its wheel, tricks and
|
|
122
127
|
transitions, weasel its animator's tweens, sherpa its seams, and
|
|
123
128
|
[klieg](https://github.com/orochi235/klieg) composes all three of its systems — letter motion,
|
|
124
129
|
part effects, and the environment a sign is lit by — on a mix each.
|
|
@@ -135,7 +140,12 @@ Zero runtime dependencies, ESM only, types included.
|
|
|
135
140
|
|
|
136
141
|
```
|
|
137
142
|
npm install
|
|
138
|
-
npm run check
|
|
143
|
+
npm run check # lint, typecheck, test
|
|
144
|
+
npm run test:general # the tests again with lanes off
|
|
139
145
|
```
|
|
140
146
|
|
|
147
|
+
A lane is a channel the mix computes for every subject at once, and the general path is the same
|
|
148
|
+
fold made subject by subject. `test:general` turns lanes off in every mix that does not set `lanes`
|
|
149
|
+
itself, so the whole suite runs on the general path.
|
|
150
|
+
|
|
141
151
|
MIT.
|
package/dist/book.d.ts
CHANGED
|
@@ -19,7 +19,7 @@ export interface BookHost<I, O> {
|
|
|
19
19
|
/** Mix time a voice is gone at, where something has fixed it. */
|
|
20
20
|
endOf(voice: Voice<I, O>): number | undefined;
|
|
21
21
|
marks(from: number, to: number): Listed[];
|
|
22
|
-
/** The booker
|
|
22
|
+
/** The booker canceled: forget it. */
|
|
23
23
|
unhook(booker: Book<I, O>): void;
|
|
24
24
|
}
|
|
25
25
|
/** Refuses hits a voice could never play. */
|
|
@@ -34,22 +34,23 @@ export declare class Book<I, O> implements Booker {
|
|
|
34
34
|
/** The mix clock at the previous sync, where a voice's clock change counts from. */
|
|
35
35
|
private prev;
|
|
36
36
|
private round;
|
|
37
|
-
private
|
|
37
|
+
private canceled;
|
|
38
38
|
/** The host time the mix last sought at: a mark at or before it was taken before the seek. */
|
|
39
39
|
private floor;
|
|
40
40
|
/** Bookings of marks, by `keyOf`, kept while listed so one already taken is not taken late. */
|
|
41
41
|
private readonly marked;
|
|
42
42
|
private readonly tracks;
|
|
43
43
|
private readonly due;
|
|
44
|
-
constructor(host: BookHost<I, O>, opts: BookOptions);
|
|
44
|
+
constructor(host: BookHost<I, O>, opts: BookOptions<unknown, I>);
|
|
45
45
|
/** Books what comes within `ahead` of this sync, and retracts what moved. */
|
|
46
46
|
sync(): void;
|
|
47
|
-
|
|
47
|
+
cancel(): void;
|
|
48
48
|
/**
|
|
49
|
-
* The mix was sought to `host.now`, back or ahead: what is booked ahead is
|
|
49
|
+
* The mix was sought to `host.now`, back or ahead: what is booked ahead is canceled, and
|
|
50
50
|
* everything after that moment is booked as the mix reaches it.
|
|
51
51
|
*/
|
|
52
52
|
sought(): void;
|
|
53
|
+
at(timestamp: number): number;
|
|
53
54
|
/** Whether a standing booking now falls at host time `time` within `RETIME_MS`. */
|
|
54
55
|
private stands;
|
|
55
56
|
private fits;
|
package/dist/book.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
const RESYNC_MS = 50;
|
|
3
3
|
/** The share of each frame's residual folded into the offset. */
|
|
4
4
|
const SLEW = 0.05;
|
|
5
|
-
/** A booking whose time moves by more than this is
|
|
5
|
+
/** A booking whose time moves by more than this is canceled and taken again. */
|
|
6
6
|
const RETIME_MS = 1;
|
|
7
7
|
const none = [];
|
|
8
8
|
/** Refuses hits a voice could never play. */
|
|
@@ -23,7 +23,7 @@ export class Book {
|
|
|
23
23
|
/** The mix clock at the previous sync, where a voice's clock change counts from. */
|
|
24
24
|
prev = Number.NaN;
|
|
25
25
|
round = 0;
|
|
26
|
-
|
|
26
|
+
canceled = false;
|
|
27
27
|
/** The host time the mix last sought at: a mark at or before it was taken before the seek. */
|
|
28
28
|
floor = Number.NEGATIVE_INFINITY;
|
|
29
29
|
/** Bookings of marks, by `keyOf`, kept while listed so one already taken is not taken late. */
|
|
@@ -36,7 +36,7 @@ export class Book {
|
|
|
36
36
|
}
|
|
37
37
|
/** Books what comes within `ahead` of this sync, and retracts what moved. */
|
|
38
38
|
sync() {
|
|
39
|
-
if (this.
|
|
39
|
+
if (this.canceled)
|
|
40
40
|
return;
|
|
41
41
|
const host = this.host;
|
|
42
42
|
const now = host.timestamp;
|
|
@@ -72,36 +72,36 @@ export class Book {
|
|
|
72
72
|
if (b.time <= now)
|
|
73
73
|
t.booked.delete(k);
|
|
74
74
|
}
|
|
75
|
-
|
|
76
|
-
if (this.
|
|
75
|
+
cancel() {
|
|
76
|
+
if (this.canceled)
|
|
77
77
|
return;
|
|
78
|
-
this.
|
|
78
|
+
this.canceled = true;
|
|
79
79
|
for (const b of this.marked.values())
|
|
80
80
|
if (b.time > this.last)
|
|
81
|
-
b.taken?.
|
|
81
|
+
b.taken?.cancel();
|
|
82
82
|
for (const t of this.tracks.values())
|
|
83
83
|
for (const b of t.booked.values())
|
|
84
84
|
if (b.time > this.last)
|
|
85
|
-
b.taken?.
|
|
85
|
+
b.taken?.cancel();
|
|
86
86
|
this.marked.clear();
|
|
87
87
|
this.tracks.clear();
|
|
88
88
|
this.host.unhook(this);
|
|
89
89
|
}
|
|
90
90
|
/**
|
|
91
|
-
* The mix was sought to `host.now`, back or ahead: what is booked ahead is
|
|
91
|
+
* The mix was sought to `host.now`, back or ahead: what is booked ahead is canceled, and
|
|
92
92
|
* everything after that moment is booked as the mix reaches it.
|
|
93
93
|
*/
|
|
94
94
|
sought() {
|
|
95
|
-
if (this.
|
|
95
|
+
if (this.canceled)
|
|
96
96
|
return;
|
|
97
97
|
const last = this.last;
|
|
98
98
|
for (const b of this.marked.values())
|
|
99
99
|
if (b.time > last)
|
|
100
|
-
b.taken?.
|
|
100
|
+
b.taken?.cancel();
|
|
101
101
|
for (const t of this.tracks.values())
|
|
102
102
|
for (const b of t.booked.values())
|
|
103
103
|
if (b.time > last)
|
|
104
|
-
b.taken?.
|
|
104
|
+
b.taken?.cancel();
|
|
105
105
|
this.marked.clear();
|
|
106
106
|
this.tracks.clear();
|
|
107
107
|
const host = this.host;
|
|
@@ -116,6 +116,9 @@ export class Book {
|
|
|
116
116
|
met: this.round,
|
|
117
117
|
});
|
|
118
118
|
}
|
|
119
|
+
at(timestamp) {
|
|
120
|
+
return this.est + (timestamp - this.last);
|
|
121
|
+
}
|
|
119
122
|
/** Whether a standing booking now falls at host time `time` within `RETIME_MS`. */
|
|
120
123
|
stands(b, time) {
|
|
121
124
|
if (time === undefined || !Number.isFinite(time))
|
|
@@ -139,7 +142,7 @@ export class Book {
|
|
|
139
142
|
if (b !== undefined) {
|
|
140
143
|
if (b.time <= now || this.stands(b, m.timestamp))
|
|
141
144
|
continue;
|
|
142
|
-
b.taken?.
|
|
145
|
+
b.taken?.cancel();
|
|
143
146
|
marked.delete(key);
|
|
144
147
|
}
|
|
145
148
|
const { order: _, ...item } = m;
|
|
@@ -148,7 +151,7 @@ export class Book {
|
|
|
148
151
|
for (const [key, b] of marked)
|
|
149
152
|
if (!seen.has(key)) {
|
|
150
153
|
if (b.time > now)
|
|
151
|
-
b.taken?.
|
|
154
|
+
b.taken?.cancel();
|
|
152
155
|
marked.delete(key);
|
|
153
156
|
}
|
|
154
157
|
}
|
|
@@ -184,7 +187,7 @@ export class Book {
|
|
|
184
187
|
const time = this.timeOfHit(voice, hits, Math.floor(key / n), key % n, end);
|
|
185
188
|
if (this.stands(b, time))
|
|
186
189
|
continue;
|
|
187
|
-
b.taken?.
|
|
190
|
+
b.taken?.cancel();
|
|
188
191
|
t.booked.delete(key);
|
|
189
192
|
moved = true;
|
|
190
193
|
}
|
|
@@ -217,6 +220,7 @@ export class Book {
|
|
|
217
220
|
score: voice.spec.score,
|
|
218
221
|
name: voice.spec.name,
|
|
219
222
|
tags: voice.spec.tags ?? none,
|
|
223
|
+
subjects: voice.named?.list,
|
|
220
224
|
};
|
|
221
225
|
this.due.push({ time, item, into: t.booked, key });
|
|
222
226
|
}
|
|
@@ -228,7 +232,7 @@ export class Book {
|
|
|
228
232
|
continue;
|
|
229
233
|
for (const b of t.booked.values())
|
|
230
234
|
if (b.time > now)
|
|
231
|
-
b.taken?.
|
|
235
|
+
b.taken?.cancel();
|
|
232
236
|
this.tracks.delete(voice);
|
|
233
237
|
}
|
|
234
238
|
}
|
package/dist/clock.d.ts
CHANGED
|
@@ -69,6 +69,16 @@ export interface Clock {
|
|
|
69
69
|
export declare function elapsedWith(c: Clock, now: number): number;
|
|
70
70
|
/** The time a clock reads `elapsed`, inverting `elapsedWith`; Infinity where it never will. */
|
|
71
71
|
export declare function timeWith(c: Clock, elapsed: number): number;
|
|
72
|
+
/**
|
|
73
|
+
* The time a clock running back reads `elapsed`: `timeWith` for the clock that reads the negative
|
|
74
|
+
* of this one, which runs forward where this runs back.
|
|
75
|
+
*/
|
|
76
|
+
export declare function backWith(c: Clock, elapsed: number): number;
|
|
77
|
+
/**
|
|
78
|
+
* The phase of a time before 0, for a loop with no first pass: where the pass before 0, and each
|
|
79
|
+
* one before that, would read it.
|
|
80
|
+
*/
|
|
81
|
+
export declare function phaseBefore(elapsed: number, duration: number): number;
|
|
72
82
|
/** A clock's rate at `now`. */
|
|
73
83
|
export declare function rateWith(c: Clock, now: number): number;
|
|
74
84
|
/** Moves a clock's anchor to `now`, carrying what is left of a ramp. */
|
package/dist/clock.js
CHANGED
|
@@ -125,6 +125,29 @@ export function timeWith(c, elapsed) {
|
|
|
125
125
|
return Number.POSITIVE_INFINITY;
|
|
126
126
|
return c.anchorNow + r.over + (elapsed - atEnd) / r.to;
|
|
127
127
|
}
|
|
128
|
+
/**
|
|
129
|
+
* The time a clock running back reads `elapsed`: `timeWith` for the clock that reads the negative
|
|
130
|
+
* of this one, which runs forward where this runs back.
|
|
131
|
+
*/
|
|
132
|
+
export function backWith(c, elapsed) {
|
|
133
|
+
const r = c.ramp;
|
|
134
|
+
return timeWith({
|
|
135
|
+
anchorNow: c.anchorNow,
|
|
136
|
+
anchorElapsed: -c.anchorElapsed,
|
|
137
|
+
rate: -c.rate,
|
|
138
|
+
ramp: r === null ? null : { from: -r.from, to: -r.to, over: r.over },
|
|
139
|
+
}, -elapsed);
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* The phase of a time before 0, for a loop with no first pass: where the pass before 0, and each
|
|
143
|
+
* one before that, would read it.
|
|
144
|
+
*/
|
|
145
|
+
export function phaseBefore(elapsed, duration) {
|
|
146
|
+
if (!(duration > 0))
|
|
147
|
+
return 0;
|
|
148
|
+
const into = elapsed % duration;
|
|
149
|
+
return (into < 0 ? into + duration : into) / duration;
|
|
150
|
+
}
|
|
128
151
|
/** A clock's rate at `now`. */
|
|
129
152
|
export function rateWith(c, now) {
|
|
130
153
|
const r = c.ramp;
|
package/dist/cue.js
CHANGED
|
@@ -4,7 +4,7 @@ import { copyable } from './clone.js';
|
|
|
4
4
|
import { schedule } from './due.js';
|
|
5
5
|
import { shares } from './everyone.js';
|
|
6
6
|
import { finiteRate, plainWeight, VoiceHandle } from './handle.js';
|
|
7
|
-
import {
|
|
7
|
+
import { begin } from './held.js';
|
|
8
8
|
import { handle } from './hosts.js';
|
|
9
9
|
import { motionOf } from './motion.js';
|
|
10
10
|
import { adopt, ownerPatch, ownerReading } from './owner.js';
|
|
@@ -76,6 +76,8 @@ export function playable(mix, spec) {
|
|
|
76
76
|
throw new RangeError(`blits: loop takes true, false or a whole number of passes, not ${loop}`);
|
|
77
77
|
if (spec.rate !== undefined)
|
|
78
78
|
finiteRate(spec.rate);
|
|
79
|
+
if (spec.seek !== undefined && !Number.isFinite(spec.seek))
|
|
80
|
+
throw new RangeError(`blits: a voice's seek is a finite number of ms, not ${spec.seek}`);
|
|
79
81
|
if (typeof spec.weight === 'number')
|
|
80
82
|
plainWeight(spec.weight);
|
|
81
83
|
fitsKit(patch, mix.kit);
|
|
@@ -150,13 +152,8 @@ function enter(mix, voice) {
|
|
|
150
152
|
mix.wantsPose = true;
|
|
151
153
|
if (anchor) {
|
|
152
154
|
place(mix);
|
|
153
|
-
if (!Number.isNaN(mix.now) &&
|
|
154
|
-
voice.
|
|
155
|
-
localNow(mix, voice) >= voice.start) {
|
|
156
|
-
voice.state = 'live';
|
|
157
|
-
reorigin(mix, voice, mix.now);
|
|
158
|
-
changed(mix, voice);
|
|
159
|
-
}
|
|
155
|
+
if (!Number.isNaN(mix.now) && voice.state === 'pending' && localNow(mix, voice) >= voice.start)
|
|
156
|
+
begin(mix, voice, mix.now);
|
|
160
157
|
}
|
|
161
158
|
if (owner?.fitting)
|
|
162
159
|
refit(mix, owner);
|
package/dist/due.js
CHANGED
|
@@ -20,6 +20,9 @@ function dueOf(mix, voice) {
|
|
|
20
20
|
due = Math.min(due, back.at + (1 - back.from) * back.over);
|
|
21
21
|
if (voice.state === 'pending')
|
|
22
22
|
return Math.min(due, Number.isNaN(voice.start) ? due : voice.start);
|
|
23
|
+
// A ramp through 0 turns the clock around, which is looked for each frame until it has.
|
|
24
|
+
if (voice.turns)
|
|
25
|
+
return Number.NEGATIVE_INFINITY;
|
|
23
26
|
if (voice.state === 'fading') {
|
|
24
27
|
const out = voice.out;
|
|
25
28
|
if (out === null || out.rest)
|
|
@@ -27,20 +30,24 @@ function dueOf(mix, voice) {
|
|
|
27
30
|
return Math.min(due, out.at + out.over);
|
|
28
31
|
}
|
|
29
32
|
due = Math.min(due, voice.outAt);
|
|
30
|
-
if (
|
|
31
|
-
const
|
|
32
|
-
//
|
|
33
|
-
//
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
if (voice.
|
|
42
|
-
|
|
33
|
+
if (voice.state === 'frozen') {
|
|
34
|
+
const at = Number.isNaN(mix.now) ? Number.NaN : voice.elapsedAt(mix.now);
|
|
35
|
+
// Frozen at its start, having run back to it, it stays while its rate is never above 0,
|
|
36
|
+
// until a handle or a subject staggered ahead of every other moves it.
|
|
37
|
+
const r = voice.ramp;
|
|
38
|
+
const never = r === null ? voice.rate <= 0 : r.from <= 0 && r.to <= 0;
|
|
39
|
+
if (never && !voice.beginless && at <= voice.soonest)
|
|
40
|
+
return due;
|
|
41
|
+
// Frozen at its end, it goes live again once its clock is back before it: by a seek, by a
|
|
42
|
+
// subject staggered later than any before, or by running back, which is checked each frame
|
|
43
|
+
// while it can.
|
|
44
|
+
if (voice.rate <= 0 || r !== null)
|
|
45
|
+
return Number.NEGATIVE_INFINITY;
|
|
46
|
+
if (at < voice.span + voice.latest)
|
|
47
|
+
return Number.NEGATIVE_INFINITY;
|
|
43
48
|
}
|
|
49
|
+
if (voice.state === 'live')
|
|
50
|
+
due = Math.min(due, voice.endsAt());
|
|
44
51
|
return due;
|
|
45
52
|
}
|
|
46
53
|
/** Files a voice in the due queue at its current due, replacing any entry it had. */
|
package/dist/everyone.js
CHANGED
|
@@ -32,6 +32,7 @@ export function shares(mix, voice) {
|
|
|
32
32
|
voice.holding === null &&
|
|
33
33
|
voice.blend === null &&
|
|
34
34
|
!voice.freezesBefore &&
|
|
35
|
+
!voice.beginless &&
|
|
35
36
|
voice.slots.every((s) => {
|
|
36
37
|
const channel = mix.channels[s];
|
|
37
38
|
return channel.rest !== undefined && numericOf(channel)?.op !== 'last';
|
|
@@ -113,6 +114,8 @@ export function open(voice, rec, slot) {
|
|
|
113
114
|
return;
|
|
114
115
|
const stamps = voice.stamps;
|
|
115
116
|
const o = slot * 2 /* Stamp.SIZE */;
|
|
117
|
+
// A subject never read counts its `dt` from the voice's origin, not another subject's last call.
|
|
118
|
+
rec.stepped = rec.since;
|
|
116
119
|
if (stamps === null || o >= stamps.length) {
|
|
117
120
|
rec.probed = Number.NaN;
|
|
118
121
|
rec.seeks = 0;
|
package/dist/fade.d.ts
CHANGED
|
@@ -21,6 +21,7 @@ export declare function beginFade<I, O>(mix: Mixer<I, O>, voice: Voice<I, O>, op
|
|
|
21
21
|
over?: number;
|
|
22
22
|
at?: 'rest';
|
|
23
23
|
deadline?: number;
|
|
24
|
+
back?: boolean;
|
|
24
25
|
}, at?: number): void;
|
|
25
26
|
/**
|
|
26
27
|
* Turns a fading voice around: it climbs back from where its fade out had got to, up the same
|
package/dist/fade.js
CHANGED
|
@@ -104,7 +104,8 @@ export function beginFade(mix, voice, opts, at) {
|
|
|
104
104
|
return;
|
|
105
105
|
const over = mix.reduced ? 0 : (opts.over ?? voice.fade.out ?? 0);
|
|
106
106
|
// A voice is linked into its subjects' chains once it plays, and a fading one plays.
|
|
107
|
-
|
|
107
|
+
const waiting = voice.state === 'pending';
|
|
108
|
+
if (waiting)
|
|
108
109
|
changed(mix, voice);
|
|
109
110
|
// A fade at rest marks each subject's record as it comes to rest, at its next read: one already
|
|
110
111
|
// read this frame is read again, as after `touch`.
|
|
@@ -120,8 +121,11 @@ export function beginFade(mix, voice, opts, at) {
|
|
|
120
121
|
rest: opts.at === 'rest',
|
|
121
122
|
deadline: opts.deadline,
|
|
122
123
|
};
|
|
124
|
+
if (opts.back)
|
|
125
|
+
voice.out.back = true;
|
|
123
126
|
noted(mix, voice);
|
|
124
|
-
|
|
127
|
+
// A fade at rest, from a voice's start, or of a voice yet to start takes it off its lane.
|
|
128
|
+
if (opts.at === 'rest' || opts.back || waiting)
|
|
125
129
|
mix.lanes?.invalidate();
|
|
126
130
|
else
|
|
127
131
|
mix.lanes?.refill();
|
|
@@ -190,7 +194,9 @@ export function retire(mix, voice, at) {
|
|
|
190
194
|
// The record its setting last wrote to, which a retired voice no longer calls for.
|
|
191
195
|
voice.keepOn = null;
|
|
192
196
|
mix.retired.push(voice);
|
|
193
|
-
|
|
197
|
+
const now = Number.isNaN(mix.now) ? Number.NEGATIVE_INFINITY : mix.now;
|
|
198
|
+
voice.doneAt = at ?? now;
|
|
199
|
+
voice.decidedAt = now;
|
|
194
200
|
voice.doneSeq = mix.transport.seq;
|
|
195
201
|
// The frames up to this one showed it, though its fade ended before: it leaves the list now.
|
|
196
202
|
mix.transport.settle();
|
|
@@ -209,6 +215,6 @@ export function retire(mix, voice, at) {
|
|
|
209
215
|
// A child the fit skipped had no time in it, so the rest of the fit stands: fitting again
|
|
210
216
|
// between siblings skipped together would play the ones still to leave.
|
|
211
217
|
else if (span.fitting !== null && span.state !== 'done' && !voice.skipped)
|
|
212
|
-
refit(mix, span);
|
|
218
|
+
refit(mix, span, voice.doneAt);
|
|
213
219
|
}
|
|
214
220
|
}
|
package/dist/fill.js
CHANGED
|
@@ -3,6 +3,7 @@ import { passAt, phaseAt, same, silent, weighed } from './clock.js';
|
|
|
3
3
|
import { flush } from './columns.js';
|
|
4
4
|
import { crowdsUpTo, freshen } from './crowd.js';
|
|
5
5
|
import { open as openRecord, shut as shutRecord } from './everyone.js';
|
|
6
|
+
import { unband } from './fold.js';
|
|
6
7
|
import { foldLocus, gatherLocus } from './gather.js';
|
|
7
8
|
import { runKeys } from './keyfill.js';
|
|
8
9
|
import { frozenAt } from './lane.js';
|
|
@@ -301,6 +302,7 @@ export function one(lane, p, slot) {
|
|
|
301
302
|
const elapsed = frozenAt(voice, elapsedNow - delay);
|
|
302
303
|
if (!(elapsed >= 0)) {
|
|
303
304
|
data[o + 2 /* Row.WEIGHT */] = 0;
|
|
305
|
+
unband(lane.records[p]);
|
|
304
306
|
return false;
|
|
305
307
|
}
|
|
306
308
|
if (!lane.placed || !same(elapsed, lane.placedAt)) {
|
|
@@ -322,6 +324,7 @@ export function one(lane, p, slot) {
|
|
|
322
324
|
arg[5 /* Arg.FADE */] = lane.fade;
|
|
323
325
|
if (!this.signalled(voice, slot, lane.records[p])) {
|
|
324
326
|
data[o + 2 /* Row.WEIGHT */] = 0;
|
|
327
|
+
unband(lane.records[p]);
|
|
325
328
|
return false;
|
|
326
329
|
}
|
|
327
330
|
w = arg[0 /* Arg.WEIGHT */];
|
|
@@ -329,6 +332,8 @@ export function one(lane, p, slot) {
|
|
|
329
332
|
else
|
|
330
333
|
w = weighed(voice.weight, lane.fade, this.parting(voice, slot));
|
|
331
334
|
data[o + 2 /* Row.WEIGHT */] = w;
|
|
335
|
+
if (!(w > 0))
|
|
336
|
+
unband(lane.records[p]);
|
|
332
337
|
this.folding = lane;
|
|
333
338
|
this.rec = lane.records[p] ?? null;
|
|
334
339
|
if (voice.built !== null) {
|
package/dist/fold.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { passAt, phaseAt, phaseBefore, silent } from './clock.js';
|
|
2
2
|
import { copy as copyValue, first } from './clone.js';
|
|
3
3
|
import { open as openRecord, shut as shutRecord, weighAt } from './everyone.js';
|
|
4
4
|
import { recordHost, remember } from './history.js';
|
|
@@ -69,16 +69,15 @@ export function contribution(voice, subject, now, held) {
|
|
|
69
69
|
return null;
|
|
70
70
|
if (held.rebuilt !== voice.rebuilds)
|
|
71
71
|
rebuild(this, voice, subject, now, held);
|
|
72
|
-
const raw = voice.
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
if (early && !voice.freezesBefore)
|
|
72
|
+
const raw = voice.rawAt(now, held.delay);
|
|
73
|
+
const early = voice.before(raw, now);
|
|
74
|
+
if (early && !voice.holdsFirst)
|
|
76
75
|
return null;
|
|
77
|
-
const elapsed =
|
|
76
|
+
const elapsed = voice.shownAt(raw);
|
|
78
77
|
// A frozen subject's clock stands still at the edge it freezes at: -1 before, 1 after, 0 playing.
|
|
79
78
|
const still = early ? -1 : voice.freezesAfter && raw > voice.span ? 1 : 0;
|
|
80
79
|
const duration = voice.duration;
|
|
81
|
-
const phase = phaseAt(elapsed, duration, voice.passes);
|
|
80
|
+
const phase = elapsed < 0 ? phaseBefore(elapsed, duration) : phaseAt(elapsed, duration, voice.passes);
|
|
82
81
|
const pass = passAt(elapsed, duration, voice.passes);
|
|
83
82
|
this.prime(voice, held, now, elapsed, pass);
|
|
84
83
|
const setting = voice.setting;
|
|
@@ -102,20 +101,27 @@ export function contribution(voice, subject, now, held) {
|
|
|
102
101
|
recordHost(this, now);
|
|
103
102
|
reading.horizon = this.horizonFor(voice, held.delay, now);
|
|
104
103
|
const tick = this.opts.stepMs;
|
|
105
|
-
if (voice.patch.step && held.probed !== now
|
|
106
|
-
// Frozen after, it steps once more to where its last pass ended, and no further
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
setting.dt = this.capped(
|
|
117
|
-
|
|
118
|
-
|
|
104
|
+
if (voice.patch.step && held.probed !== now) {
|
|
105
|
+
// Frozen after, it steps once more to where its last pass ended, and no further; run back
|
|
106
|
+
// past its start, to where it reached it. Before a start it has yet to reach, it does not step.
|
|
107
|
+
const to = still === 0
|
|
108
|
+
? now
|
|
109
|
+
: still === 1
|
|
110
|
+
? voice.timeAt(held.delay + voice.span)
|
|
111
|
+
: voice.ranBackTo(held.delay, now);
|
|
112
|
+
if (!Number.isNaN(to)) {
|
|
113
|
+
if (held.stepped < held.since) {
|
|
114
|
+
held.stepped = held.since;
|
|
115
|
+
setting.dt = this.capped(now - held.since);
|
|
116
|
+
}
|
|
117
|
+
if (tick !== undefined && tick > 0 && !this.reducedNow)
|
|
118
|
+
this.tick(voice, subject, held, tick, to);
|
|
119
|
+
else if (still === 0 ? now !== held.stepped : to > held.stepped) {
|
|
120
|
+
if (still !== 0)
|
|
121
|
+
setting.dt = this.capped(to - held.stepped);
|
|
122
|
+
voice.patch.step(held.state, setting.dt, subject, setting);
|
|
123
|
+
held.stepped = to;
|
|
124
|
+
}
|
|
119
125
|
}
|
|
120
126
|
}
|
|
121
127
|
let delta = null;
|
|
@@ -648,7 +654,7 @@ export function read(voice, subject, now, dry, held) {
|
|
|
648
654
|
if (held.reaches && held.probed === now && held.delta && held.seeks === voice.seeks) {
|
|
649
655
|
const setting = voice.setting;
|
|
650
656
|
// The setting the probe that read the delta had, but for `dt`: a dry read advances nothing.
|
|
651
|
-
const elapsed =
|
|
657
|
+
const elapsed = voice.shownAt(voice.rawAt(now, held.delay));
|
|
652
658
|
setting.timestamp = now;
|
|
653
659
|
setting.dt = 0;
|
|
654
660
|
setting.elapsed = elapsed;
|
package/dist/gone.d.ts
CHANGED
|
@@ -13,7 +13,7 @@ export declare class GoneIndex<I, O> {
|
|
|
13
13
|
count: number;
|
|
14
14
|
general: Voice<I, O>[];
|
|
15
15
|
named: Store<I, Voice<I, O>[]>;
|
|
16
|
-
/** The latest `
|
|
16
|
+
/** The latest `reachedTo` in `of`, and how far any voice's falls before one listed ahead of it. */
|
|
17
17
|
latest: number;
|
|
18
18
|
slack: number;
|
|
19
19
|
}
|
|
@@ -25,8 +25,8 @@ export declare function goneIndex<I, O>(mix: Mixer<I, O>): GoneIndex<I, O>;
|
|
|
25
25
|
*/
|
|
26
26
|
export declare function keepGone<I, O>(mix: Mixer<I, O>, keep: (v: Voice<I, O>) => boolean): void;
|
|
27
27
|
/**
|
|
28
|
-
* Lets go of the gone voices
|
|
29
|
-
*
|
|
30
|
-
* `reach`, none behind it
|
|
28
|
+
* Lets go of the gone voices no seek or read back to `reach` finds playing: those that finished
|
|
29
|
+
* before it, in a frame before it. Voices go in about that order, so only the front can hold one:
|
|
30
|
+
* past the point where a voice is reached `slack` after `reach`, none behind it falls before.
|
|
31
31
|
*/
|
|
32
32
|
export declare function expireGone<I, O>(mix: Mixer<I, O>, reach: number): void;
|