@msb235/blits 0.6.0 → 0.7.1
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 +152 -0
- package/README.md +31 -7
- package/dist/bare.js +7 -1
- package/dist/book.d.ts +7 -0
- package/dist/book.js +35 -4
- package/dist/chain.d.ts +2 -1
- package/dist/chain.js +7 -3
- package/dist/channels.d.ts +12 -19
- package/dist/channels.js +18 -118
- package/dist/clock.d.ts +9 -2
- package/dist/clock.js +10 -2
- package/dist/closed.d.ts +15 -0
- package/dist/closed.js +144 -0
- package/dist/color.d.ts +61 -0
- package/dist/color.js +229 -0
- package/dist/columns.js +2 -1
- package/dist/crowd.d.ts +1 -1
- package/dist/crowd.js +4 -1
- package/dist/cue.d.ts +2 -0
- package/dist/cue.js +80 -27
- package/dist/due.js +3 -0
- package/dist/fade.d.ts +13 -0
- package/dist/fade.js +57 -4
- package/dist/fill.d.ts +1 -1
- package/dist/fill.js +15 -8
- package/dist/fit.d.ts +144 -0
- package/dist/fit.js +181 -0
- package/dist/fold.d.ts +3 -3
- package/dist/fold.js +18 -11
- package/dist/gather.d.ts +6 -0
- package/dist/gather.js +18 -0
- package/dist/gone.d.ts +32 -0
- package/dist/gone.js +134 -0
- package/dist/handle.d.ts +27 -2
- package/dist/handle.js +60 -11
- package/dist/held.js +4 -0
- package/dist/history.d.ts +15 -2
- package/dist/history.js +56 -9
- package/dist/hosts.d.ts +2 -2
- package/dist/hosts.js +20 -6
- package/dist/index.d.ts +8 -2
- package/dist/index.js +5 -1
- package/dist/lane.d.ts +1 -1
- package/dist/lanes.d.ts +32 -9
- package/dist/lanes.js +53 -24
- package/dist/locus.d.ts +3 -3
- package/dist/locus.js +1 -1
- package/dist/marks.d.ts +3 -0
- package/dist/marks.js +80 -12
- package/dist/meet.d.ts +3 -2
- package/dist/meet.js +15 -3
- package/dist/mixer.d.ts +56 -36
- package/dist/mixer.js +158 -87
- package/dist/motion.d.ts +2 -199
- package/dist/motion.js +2 -652
- package/dist/motions.d.ts +266 -0
- package/dist/motions.js +724 -0
- package/dist/move.d.ts +1 -1
- package/dist/move.js +28 -8
- package/dist/owner.d.ts +1 -1
- package/dist/owner.js +3 -3
- package/dist/pace.d.ts +2 -0
- package/dist/pace.js +8 -0
- package/dist/pack.d.ts +31 -0
- package/dist/pack.js +65 -0
- package/dist/paging.d.ts +63 -0
- package/dist/paging.js +110 -0
- package/dist/patch.d.ts +3 -0
- package/dist/patch.js +2 -0
- package/dist/place.js +235 -43
- package/dist/project.d.ts +7 -4
- package/dist/project.js +117 -78
- package/dist/pull.js +4 -3
- package/dist/qualify.js +4 -2
- package/dist/revive.d.ts +58 -0
- package/dist/revive.js +158 -0
- package/dist/sample.d.ts +1 -1
- package/dist/sample.js +6 -1
- package/dist/scored.d.ts +57 -0
- package/dist/scored.js +196 -0
- package/dist/seek.d.ts +3 -0
- package/dist/seek.js +311 -0
- package/dist/spans.d.ts +29 -0
- package/dist/spans.js +165 -0
- package/dist/strays.d.ts +1 -1
- package/dist/strays.js +21 -0
- package/dist/tape.d.ts +21 -0
- package/dist/tape.js +65 -0
- package/dist/ticker.d.ts +28 -3
- package/dist/ticker.js +3 -0
- package/dist/transport.d.ts +125 -0
- package/dist/transport.js +283 -0
- package/dist/types.d.ts +404 -38
- package/dist/unpage.d.ts +8 -0
- package/dist/unpage.js +165 -0
- package/dist/voice.d.ts +75 -7
- package/dist/voice.js +119 -4
- package/dist/weigh.js +2 -2
- package/docs/schema.html +120 -31
- package/package.json +2 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,158 @@ 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.7.1
|
|
8
|
+
|
|
9
|
+
### Fixed
|
|
10
|
+
|
|
11
|
+
- Under `history`, a mix whose subjects come and go no longer slows as the voices that have left
|
|
12
|
+
pile up. `drop` and a freed slot visit only the voices that left that can hold the subject,
|
|
13
|
+
history lets go of them from the front of the list, and the tape is pruned a second at a time
|
|
14
|
+
rather than every sync. A 200-subject roster replacing 4 subjects a frame climbed to about 1.2 ms
|
|
15
|
+
a frame as 14,000 voices that had left built up; it now holds at about 0.065 ms.
|
|
16
|
+
- An anchor naming a score reads only the voices on that score, and only in the mixes on the
|
|
17
|
+
transport that hold one; it reads each voice that has left once rather than every frame, and
|
|
18
|
+
picks in one pass instead of sorting. astv's busy stage, whose rail waits on the latest `coast`
|
|
19
|
+
of its motion score, takes about 0.45 ms a frame in astv's motion bench, against 4.3 ms on 0.7.0.
|
|
20
|
+
With 200 mixes on scores of their own beside it, a frame's sync takes about 14 µs, against 21
|
|
21
|
+
before the transport listed the mixes holding each score (Node, medians of six alternated runs).
|
|
22
|
+
|
|
23
|
+
## 0.7.0
|
|
24
|
+
|
|
25
|
+
### Breaking
|
|
26
|
+
|
|
27
|
+
- With lanes on, a stateless patch's `send` from `at` sends only for subjects probed this frame
|
|
28
|
+
or in the last frame with a probe, where it sent for every subject the lane had met.
|
|
29
|
+
- A `keys` patch's stops are not read for a subject its voice gives no weight, as `at` already
|
|
30
|
+
was not, unless the voice is fading to rest or is in a locus. A `lerp` or `lerpBy` that counted
|
|
31
|
+
its calls no longer sees those frames. A `from: 'current'` voice still takes its base on its
|
|
32
|
+
first read.
|
|
33
|
+
- `sync` throws for a timestamp earlier than the last sync's, short of a `rebase`. It used to take
|
|
34
|
+
one and half work: stateless voices moved back while a stateful patch without `stepMs` was handed
|
|
35
|
+
a negative `dt`. A host moving the mix calls `seek`.
|
|
36
|
+
- `project` takes mix time, not the host's timestamp. The two differ only on a mix with its own
|
|
37
|
+
rate or one that was rebased, where `project(mix.now - 300)` reads 300 ms of mix time back.
|
|
38
|
+
- `Mix` has `seek`, `now` and `tape`, which an engine of its own has to provide.
|
|
39
|
+
- `Mix` has `span`, which an engine of its own has to provide.
|
|
40
|
+
- `Mark` has a fifth value, `coast`, so a `switch` over it that checks every case needs one more.
|
|
41
|
+
- `project` ahead of the mix after a `seek` back throws for a time past a call the tape will make
|
|
42
|
+
again, which it used to leave out; a seek there makes it.
|
|
43
|
+
- An engine of its own is handed `MixOptions.transport` and `name`, and a `Mix` that can share a
|
|
44
|
+
transport has to keep its clock there.
|
|
45
|
+
- `Mix` and `Transport` have `prepare`, which an engine of its own has to provide.
|
|
46
|
+
|
|
47
|
+
### Added
|
|
48
|
+
|
|
49
|
+
- `transport(opts)` makes one clock several mixes share, each over its own kit, by passing it as
|
|
50
|
+
`MixOptions.transport` with a `name`. `sync`, `rebase`, `rate`, `ramp` and `seek` on the
|
|
51
|
+
transport move every mix on it, and a mix's own throw. A query naming a `score` looks in every
|
|
52
|
+
mix on the transport, so an anchor in one mix waits on a voice in another; one naming no score
|
|
53
|
+
stays in its own mix. The transport has `announce` (on a named score), `marks`, `project(time)`
|
|
54
|
+
with `.of(mix)`, `live`, `inert`, `onWake` and `drop(mix)`. One tape records every mix's calls
|
|
55
|
+
and a seek replays them in the order they were made; under history a seek back before a `drop`
|
|
56
|
+
puts the mix back. `Marked.mix` names the mix a mark came from, and voice ids are unique across a
|
|
57
|
+
transport. A mix made alone has a transport of its own, so nothing changes for it.
|
|
58
|
+
- `fade({ at, over })` with `at` a mix time begins the fade exactly there: ahead, the voice plays
|
|
59
|
+
untouched until then with its `out` and `end` fixed from the call, and `rise` before then takes it
|
|
60
|
+
back with nothing changed; at or behind now, the fade began then and is partway. It wins over an
|
|
61
|
+
anchored `out`.
|
|
62
|
+
- A voice **coasts** when its last pass ends, for its latest-staggered subject: the moment
|
|
63
|
+
`played` resolves true. `coast` is a mark like the other four, listed by `marks`, booked by
|
|
64
|
+
`book`, read ahead by `project`, and waited on with `{ of, mark: 'coast' }`. It falls with `out`
|
|
65
|
+
on a voice that does not freeze, and where a frozen voice starts showing its last frame, which
|
|
66
|
+
has no `out` until it is faded. An owner coasts with its last child, a span where its fit ends. A
|
|
67
|
+
voice that loops for good, or fades before its last pass ends, never coasts.
|
|
68
|
+
- `color()`, a channel holding color as OKLab `[L, a, b, coverage]` with its merge rule as a
|
|
69
|
+
parameter. `color()` averages: voices stack as a premultiplied sum, so it runs on lanes, and the
|
|
70
|
+
pose carries the summed weight as coverage. `color(last())` replaces. `{ lerp: 'oklch' }`
|
|
71
|
+
interpolates round the hue instead of across OKLab, off lanes. `oklab(0xrrggbb)` makes a value;
|
|
72
|
+
`toHex(pose, under)` lays a pose over the subject's own color and `css(pose)` writes an
|
|
73
|
+
`oklab()` string with the coverage as alpha. `color(last())` runs on lanes too, the last voice
|
|
74
|
+
past the band winning, unless a voice writing it is a motion, sits in a locus or names one
|
|
75
|
+
subject. `hex` still works, but it is deprecated for
|
|
76
|
+
`color(last(), { lerp: 'oklch' })`, which replaces and interpolates as it does, and a later
|
|
77
|
+
release removes it.
|
|
78
|
+
- `mix.span(spec)` cues an owner that lays out the voices it holds in an `order` (`queue`,
|
|
79
|
+
`stagger`, `together`) and fits them into its `duration`, again whenever one joins or leaves.
|
|
80
|
+
Each child says how it may give way with `faster`, `slower`, `overlap`, `ballast` and `priority`. The
|
|
81
|
+
span first retimes its children by one factor toward the budget, faster where they run past it
|
|
82
|
+
and slower where they leave room, each as far as its hints allow; then its `fit` decides the
|
|
83
|
+
rest: `condense`, `shed`, `conclude` and `overrun` are exported, `pipe` runs several in turn,
|
|
84
|
+
and `lax` is every one with `overrun` last.
|
|
85
|
+
Where nothing fits, `spill: 'instant'` jumps the weaker children to their end at once and
|
|
86
|
+
`'overrun'` lets the span run long. A span with a budget stays until it has passed, so late
|
|
87
|
+
children can join, and `handle.result` says how the last fit came out.
|
|
88
|
+
- `ticker().wake()` asks for a frame. A mix changed before `stop` and not synced since fires
|
|
89
|
+
`onWake` no more, so nothing else starts the loop again; `add` asks for a frame too, as it
|
|
90
|
+
always did, now documented.
|
|
91
|
+
- An anchor can wait on several: `{ all: [...] }` answers the latest of them and `{ any: [...] }`
|
|
92
|
+
the earliest, and both nest.
|
|
93
|
+
- `handle.rise({ over })` turns a fade out around: the voice climbs back from where its fade had
|
|
94
|
+
got to, up the same curve, and plays on as if never faded, so a host can take back a leave
|
|
95
|
+
without the jump a fresh fade in from 0 makes. It is recorded, so history and `seek` replay it.
|
|
96
|
+
A fade waiting for rest is taken back at once; a voice not fading is left as it is.
|
|
97
|
+
- `mix.seek(time)` moves the live mix to a mix time and plays on from there, under `history` with
|
|
98
|
+
a `tape`. `history.tape` takes weasel-history's `createHistory` (`@weasel-js/history`), or
|
|
99
|
+
anything of the `Tape` shape, and the mix records every call the host makes on it, its handles
|
|
100
|
+
and its spring and glide patches. Back, the mix restores itself as it stood at the end of that
|
|
101
|
+
frame: voices that left since return on the handles the host holds, with `done` and `played`
|
|
102
|
+
starting over, and voices cued since wait `pending` on theirs. Forward, by a sync or a later
|
|
103
|
+
seek, the recorded calls play again at the mix times they were made. A call made while recorded
|
|
104
|
+
calls lie ahead starts a branch, and the tape keeps the old future: `mix.tape.branches()` lists
|
|
105
|
+
them and `switchBranch` picks one. State steps once from the nearest copy history kept, exact
|
|
106
|
+
under `stepMs`. Playing past a time again sends its events and books its marks and hits again.
|
|
107
|
+
The host keeps passing its own clock.
|
|
108
|
+
- `mix.now` reads the mix clock, which `seek` and `project` take.
|
|
109
|
+
- `history.store` pages out what `history.ms` would drop, as plain data, so `ms` bounds memory
|
|
110
|
+
rather than how far back a seek reaches. `store.page` takes records leaving memory,
|
|
111
|
+
`store.load(t)` gives back what a restore to `t` needs, and `store.cut(t)` forgets what a seek
|
|
112
|
+
back left behind. `transport.prepare(t)` (and `mix.prepare` for a mix alone) loads before a seek
|
|
113
|
+
or read past memory; without it, it throws `HistoryMiss` before anything moves. Object subjects
|
|
114
|
+
need `MixOptions.keyOf`, a patch with `state` or `step` needs `pack` and `unpack` (which `patch`
|
|
115
|
+
now takes), and every mix on a transport with a store needs a unique `name`. A voice cued with
|
|
116
|
+
`as: { kind, data }` leaves memory once it has left and history no longer reaches its end, and
|
|
117
|
+
`history.revive` builds its spec again when a seek needs it, behind the handle the host holds.
|
|
118
|
+
With a store, blits never prunes the tape; the host bounds it. Without one, nothing changes.
|
|
119
|
+
|
|
120
|
+
### Fixed
|
|
121
|
+
|
|
122
|
+
- A lane fills only the subjects probed this frame or in the last frame with a probe, not every
|
|
123
|
+
subject it has met; one probed in neither takes the general path if it is probed before the next
|
|
124
|
+
fill. Three `keys` voices over 10k subjects, every subject probed once and then 40% each frame
|
|
125
|
+
(`view`), take 0.561 ms a frame against 0.878. No other row moves past its run-to-run spread:
|
|
126
|
+
`signal` and `named` at 10k read 1.06 and 1.09 here and 0.96 and 0.91 in a run against the
|
|
127
|
+
build before (teitou, medians of six alternated runs, each in its own process).
|
|
128
|
+
- A second `seek` or `project` back no longer reaches into time the first one's history had
|
|
129
|
+
already let go of, where it read wrong values: after seeking back to 1600 under `ms: 500` from
|
|
130
|
+
2000, a seek to 1200 read 0 where the mix had shown 90. It throws as a read older than history
|
|
131
|
+
does.
|
|
132
|
+
- Under `history`, a subject faded out of a voice or dropped after the moment a `seek` or a
|
|
133
|
+
`project` reads back to comes back with the record it had, and with a motion patch's state for
|
|
134
|
+
it. It used to come back as never seen, its state started afresh.
|
|
135
|
+
- `project` across a `rebase` reads the moment the host saw: it converted a host timestamp with
|
|
136
|
+
the offset in force now, so a 1000 ms rise synced at 0 and 200, rebased and synced at 10200 read
|
|
137
|
+
0 at 200, where the host had seen 20. Taking mix time, it reads 20.
|
|
138
|
+
|
|
139
|
+
- A probe and `atRest` no longer allocate on the heap to find a subject's voices. Each passed the
|
|
140
|
+
mix's clock, a fractional number, to a function too large for V8 to inline, which stores such a
|
|
141
|
+
number on the heap to pass it; and the function that links a subject's voices made a closure
|
|
142
|
+
context on every call, though only relinking uses one. Collections over 5000 frames of 10k
|
|
143
|
+
subjects fall from 666 to 375 with a probe and `atRest` of each (`probed`), 279 to 121 with
|
|
144
|
+
`atRest` alone (`rest`), 428 to 285 with a voice per subject (`named`), and 741 to 524 with three
|
|
145
|
+
`fn` voices over every subject (`fn`). A frame takes 0.857 ms against 0.885 for `rest`, 1.263
|
|
146
|
+
against 1.325 for `named`, and 2.074 against 2.156 for `fn`; `probed`, at 1.507 against 1.504,
|
|
147
|
+
and the rows read by `pull` are unchanged (teitou, medians of twelve alternated runs).
|
|
148
|
+
- A fill no longer stamps every subject: the lanes keep which fill ran a lane, and a subject is
|
|
149
|
+
stamped only when it is left to the general path. A spring is solved by a function per kind of
|
|
150
|
+
spring, small enough for V8 to inline where it samples, where one function over all four left
|
|
151
|
+
spring fills running at one of two speeds. At 10k subjects, a frame takes 0.438 ms against 0.473
|
|
152
|
+
with a spring voice read by `pull` (`spring^`), 0.411 against 0.463 with a spring voice per
|
|
153
|
+
subject (`springs^`), 0.937 against 0.996 and 0.884 against 0.935 for the same by `probe`,
|
|
154
|
+
0.306 against 0.319 with one subject replaced each frame (`turnover^`), 0.226 against 0.233 with
|
|
155
|
+
one voice replaced each frame (`churn^`), and 0.167 against 0.170 with a tween voice per subject
|
|
156
|
+
(`tweens^`); a probe and `atRest` of every subject (`probed`) is unchanged, 1.497 against 1.504
|
|
157
|
+
(teitou, medians of eight alternated runs, twelve for `probed`).
|
|
158
|
+
|
|
7
159
|
## 0.6.0
|
|
8
160
|
|
|
9
161
|
### Breaking
|
package/README.md
CHANGED
|
@@ -1,22 +1,24 @@
|
|
|
1
1
|
# blits
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
effect.
|
|
3
|
+
Effects over time, at once or in order. Each effect runs on its own clock with its own state and
|
|
4
|
+
weight. Effects that overlap are folded by a mix into one value per subject per frame, by rules
|
|
5
|
+
that belong to the channel, not the effect. Effects that follow one another are placed on a score,
|
|
6
|
+
at a time or against another effect's start or end, and the mix can be read ahead, read back, and
|
|
7
|
+
moved to any moment, as a scrubber does.
|
|
6
8
|
|
|
7
9
|
It is the thing klieg does three times over — motion, effects, lighting — and wod does once for
|
|
8
10
|
transitions, extracted so those repos, sherpa and magicsmoke can share one vocabulary and one
|
|
9
11
|
engine.
|
|
10
12
|
|
|
11
13
|
```ts
|
|
12
|
-
import {
|
|
14
|
+
import { color, kit, max, mix, mul, patch, sum, vec } from '@msb235/blits';
|
|
13
15
|
|
|
14
16
|
// What a part of a sign can move on. The arithmetic lives here and nowhere else.
|
|
15
17
|
const PART = kit<PartPose>({
|
|
16
18
|
gain: mul(),
|
|
17
19
|
dark: max(),
|
|
18
20
|
position: vec(3, sum()),
|
|
19
|
-
color:
|
|
21
|
+
color: color(),
|
|
20
22
|
});
|
|
21
23
|
|
|
22
24
|
const flicker = patch<Part, PartPose>(
|
|
@@ -39,16 +41,38 @@ function frame(timestamp: number) {
|
|
|
39
41
|
document.addEventListener('visibilitychange', () => document.hidden || m.rebase());
|
|
40
42
|
|
|
41
43
|
handle.fade({ over: 500 }); // the voice ramps out and leaves
|
|
44
|
+
handle.rise({ over: 500 }); // or, before it is gone, climbs back from where it had got to
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
In order, on the same mix:
|
|
48
|
+
|
|
49
|
+
```ts
|
|
50
|
+
// Place a voice at a time on the host's clock, or against another voice's marks.
|
|
51
|
+
m.cue({ patch: typeIn, name: 'title', start: 1000, loop: false });
|
|
52
|
+
m.cue({ patch: tint, loop: false, anchor: { start: { after: 'title', by: 200 } } });
|
|
53
|
+
|
|
54
|
+
// A span fits the voices it holds into a budget, one after another by default.
|
|
55
|
+
const intro = m.span({ duration: 2000 });
|
|
56
|
+
for (const line of lines) m.cue({ patch: typeIn, loop: false, owner: intro });
|
|
57
|
+
|
|
58
|
+
// Read the mix half a second ahead without moving it, or move it: a scrubber.
|
|
59
|
+
const next = m.project(m.now + 500).probe(part);
|
|
60
|
+
m.seek(1200); // back as well as forward, given `history` with a tape
|
|
42
61
|
```
|
|
43
62
|
|
|
44
63
|
## The words
|
|
45
64
|
|
|
46
|
-
|
|
65
|
+
These carry the rest: **delta** (a partial record of channel values), **channel** (one field
|
|
47
66
|
with its own arithmetic), **kit** (the channel set for one kind of delta), **subject** (what is
|
|
48
67
|
driven), **patch** (a pure function of phase and a subject), **voice** (a patch with its own clock
|
|
49
68
|
and weight), **mix** (the live voices over one kit), **signal** (a 0..1 scalar from outside the
|
|
50
69
|
clock), **handle** (the live controls on one voice), **engine** (the implementation behind a mix).
|
|
51
70
|
|
|
71
|
+
For time: **score** (the plan a source's voices are placed on), **mark** (a voice's start, fade-in
|
|
72
|
+
done, fade-out begun, or end, or a named point the host announces), **anchor** (a time given by
|
|
73
|
+
another voice's mark), **span** (an owner fitting the voices it holds into a duration), **project**
|
|
74
|
+
(read the mix at another time without moving it), **seek** (move the mix to a time and play on).
|
|
75
|
+
|
|
52
76
|
The design — the full vocabulary, the channel table, the time model, blending, the engine seam, and
|
|
53
77
|
what is still open — is `docs/schema.html`, which ships in the package: open it from
|
|
54
78
|
`node_modules/@msb235/blits/`. The naming work behind it is
|
|
@@ -76,7 +100,7 @@ so the engine keeps no dependencies; its README says how it works.
|
|
|
76
100
|
|
|
77
101
|
## Status
|
|
78
102
|
|
|
79
|
-
v0.
|
|
103
|
+
v0.7.1, with five consumers. magicsmoke runs every fault on it, wod its wheel, tricks and
|
|
80
104
|
transitions, weasel its animator's tweens, sherpa its seams, and
|
|
81
105
|
[klieg](https://github.com/orochi235/klieg) composes all three of its systems — letter motion,
|
|
82
106
|
part effects, and the environment a sign is lit by — on a mix each.
|
package/dist/bare.js
CHANGED
|
@@ -23,6 +23,7 @@ export function bareRows(lanes, c, p, id) {
|
|
|
23
23
|
const fills = lanes.fills;
|
|
24
24
|
const from = lanes.frameProbes;
|
|
25
25
|
const probed = lanes.probes !== from;
|
|
26
|
+
const lately = lanes.lately;
|
|
26
27
|
const now = lanes.now;
|
|
27
28
|
const op = ch.op;
|
|
28
29
|
for (; p < list.length; p++) {
|
|
@@ -37,6 +38,11 @@ export function bareRows(lanes, c, p, id) {
|
|
|
37
38
|
if (probed &&
|
|
38
39
|
(per[q + 3 /* Per.LANE_PROBE */] > from || per[q + 4 /* Per.GENERAL_PROBE */] > from))
|
|
39
40
|
break;
|
|
41
|
+
// Left to the general path, as `rows` leaves it.
|
|
42
|
+
if (lately >= 0 &&
|
|
43
|
+
!(per[q + 3 /* Per.LANE_PROBE */] > lately ||
|
|
44
|
+
per[q + 4 /* Per.GENERAL_PROBE */] > lately))
|
|
45
|
+
break;
|
|
40
46
|
const elapsed = clock(hot, h, now) - data[o + 0 /* Row.DELAY */];
|
|
41
47
|
if (!(elapsed >= 0))
|
|
42
48
|
break;
|
|
@@ -48,9 +54,9 @@ export function bareRows(lanes, c, p, id) {
|
|
|
48
54
|
data[o + 5 /* Row.SAMPLED */] = -2 /* Sampled.BARE */;
|
|
49
55
|
c.deltas[p] = null;
|
|
50
56
|
}
|
|
51
|
-
const share = left(c.eases[p], progress(elapsed, hot[h + 8 /* Hot.AT */], hot[h + 9 /* Hot.MS */]));
|
|
52
57
|
if (w <= 0)
|
|
53
58
|
continue;
|
|
59
|
+
const share = left(c.eases[p], progress(elapsed, hot[h + 8 /* Hot.AT */], hot[h + 9 /* Hot.MS */]));
|
|
54
60
|
const values = ch.values;
|
|
55
61
|
const base = slot * n;
|
|
56
62
|
const x = h + 10 /* Hot.X0 */;
|
package/dist/book.d.ts
CHANGED
|
@@ -35,6 +35,8 @@ export declare class Book<I, O> implements Booker {
|
|
|
35
35
|
private prev;
|
|
36
36
|
private round;
|
|
37
37
|
private stopped;
|
|
38
|
+
/** The host time the mix last sought at: a mark at or before it was taken before the seek. */
|
|
39
|
+
private floor;
|
|
38
40
|
/** Bookings of marks, by `keyOf`, kept while listed so one already taken is not taken late. */
|
|
39
41
|
private readonly marked;
|
|
40
42
|
private readonly tracks;
|
|
@@ -43,6 +45,11 @@ export declare class Book<I, O> implements Booker {
|
|
|
43
45
|
/** Books what comes within `ahead` of this sync, and retracts what moved. */
|
|
44
46
|
sync(): void;
|
|
45
47
|
stop(): void;
|
|
48
|
+
/**
|
|
49
|
+
* The mix was sought to `host.now`, back or ahead: what is booked ahead is stopped, and
|
|
50
|
+
* everything after that moment is booked as the mix reaches it.
|
|
51
|
+
*/
|
|
52
|
+
sought(): void;
|
|
46
53
|
/** Whether a standing booking now falls at host time `time` within `RETIME_MS`. */
|
|
47
54
|
private stands;
|
|
48
55
|
private fits;
|
package/dist/book.js
CHANGED
|
@@ -24,6 +24,8 @@ export class Book {
|
|
|
24
24
|
prev = Number.NaN;
|
|
25
25
|
round = 0;
|
|
26
26
|
stopped = false;
|
|
27
|
+
/** The host time the mix last sought at: a mark at or before it was taken before the seek. */
|
|
28
|
+
floor = Number.NEGATIVE_INFINITY;
|
|
27
29
|
/** Bookings of marks, by `keyOf`, kept while listed so one already taken is not taken late. */
|
|
28
30
|
marked = new Map();
|
|
29
31
|
tracks = new Map();
|
|
@@ -85,6 +87,35 @@ export class Book {
|
|
|
85
87
|
this.tracks.clear();
|
|
86
88
|
this.host.unhook(this);
|
|
87
89
|
}
|
|
90
|
+
/**
|
|
91
|
+
* The mix was sought to `host.now`, back or ahead: what is booked ahead is stopped, and
|
|
92
|
+
* everything after that moment is booked as the mix reaches it.
|
|
93
|
+
*/
|
|
94
|
+
sought() {
|
|
95
|
+
if (this.stopped)
|
|
96
|
+
return;
|
|
97
|
+
const last = this.last;
|
|
98
|
+
for (const b of this.marked.values())
|
|
99
|
+
if (b.time > last)
|
|
100
|
+
b.taken?.stop();
|
|
101
|
+
for (const t of this.tracks.values())
|
|
102
|
+
for (const b of t.booked.values())
|
|
103
|
+
if (b.time > last)
|
|
104
|
+
b.taken?.stop();
|
|
105
|
+
this.marked.clear();
|
|
106
|
+
this.tracks.clear();
|
|
107
|
+
const host = this.host;
|
|
108
|
+
this.floor = host.timestamp;
|
|
109
|
+
this.prev = host.now;
|
|
110
|
+
for (const voice of host.voices)
|
|
111
|
+
if (voice.spec.hits !== undefined && voice.state !== 'done')
|
|
112
|
+
this.tracks.set(voice, {
|
|
113
|
+
seenTo: voice.elapsedAt(host.now),
|
|
114
|
+
seeks: voice.seeks,
|
|
115
|
+
booked: new Map(),
|
|
116
|
+
met: this.round,
|
|
117
|
+
});
|
|
118
|
+
}
|
|
88
119
|
/** Whether a standing booking now falls at host time `time` within `RETIME_MS`. */
|
|
89
120
|
stands(b, time) {
|
|
90
121
|
if (time === undefined || !Number.isFinite(time))
|
|
@@ -100,7 +131,7 @@ export class Book {
|
|
|
100
131
|
const marked = this.marked;
|
|
101
132
|
const seen = new Set();
|
|
102
133
|
for (const m of listed) {
|
|
103
|
-
if (!this.fits(m.score, m.tags))
|
|
134
|
+
if (!this.fits(m.score, m.tags) || m.timestamp <= this.floor)
|
|
104
135
|
continue;
|
|
105
136
|
const key = keyOf(m);
|
|
106
137
|
seen.add(key);
|
|
@@ -211,8 +242,8 @@ export class Book {
|
|
|
211
242
|
return this.host.hostOf(t);
|
|
212
243
|
}
|
|
213
244
|
}
|
|
214
|
-
const markIndex = { start: 1, in: 2,
|
|
215
|
-
/** A mark's identity: its voice and which of
|
|
245
|
+
const markIndex = { start: 1, in: 2, coast: 3, out: 4, end: 5 };
|
|
246
|
+
/** A mark's identity: its voice and which of its marks, or the announcement. */
|
|
216
247
|
function keyOf(m) {
|
|
217
|
-
return m.order *
|
|
248
|
+
return m.order * 6 + (m.mark === undefined ? 0 : markIndex[m.mark]);
|
|
218
249
|
}
|
package/dist/chain.d.ts
CHANGED
|
@@ -6,7 +6,8 @@ import type { Subject, Voice } from './voice.js';
|
|
|
6
6
|
* it goes live, since `target` is asked on first sight, and sight only comes once a voice plays
|
|
7
7
|
* or freezes before.
|
|
8
8
|
*/
|
|
9
|
-
export declare function chain<I, O>(this: Mixer<I, O>, subject: I
|
|
9
|
+
export declare function chain<I, O>(this: Mixer<I, O>, subject: I): Subject<unknown>;
|
|
10
|
+
export declare function relink<I, O>(this: Mixer<I, O>, subject: I, was: Subject<unknown> | undefined): Subject<unknown>;
|
|
10
11
|
/** A voice's record for a subject where its chain links it: the voice plays or freezes, and reaches it. */
|
|
11
12
|
export declare function linkable<I, O>(this: Mixer<I, O>, voice: Voice<I, O>, subject: I, now: number, slot: number): Subject<unknown> | null;
|
|
12
13
|
/** Whether a voice reaches a subject by what its spec says: the subjects it names, or its `target`. */
|
package/dist/chain.js
CHANGED
|
@@ -29,10 +29,14 @@ function stub() {
|
|
|
29
29
|
* it goes live, since `target` is asked on first sight, and sight only comes once a voice plays
|
|
30
30
|
* or freezes before.
|
|
31
31
|
*/
|
|
32
|
-
export function chain(subject
|
|
32
|
+
export function chain(subject) {
|
|
33
33
|
const was = this.chains.get(subject);
|
|
34
|
-
|
|
35
|
-
|
|
34
|
+
return was !== undefined && was.version === this.version ? was : this.relink(subject, was);
|
|
35
|
+
}
|
|
36
|
+
// Apart from `chain` so that its closures' context is made only when a chain is relinked, and
|
|
37
|
+
// `chain` stays small enough for V8 to inline where a probe links.
|
|
38
|
+
export function relink(subject, was) {
|
|
39
|
+
const now = this.now;
|
|
36
40
|
const slot = was !== undefined ? was.slot : this.lanes !== null ? this.lanes.number(subject) : -1;
|
|
37
41
|
// Where only voices over every subject changed since, those alone are taken off or put on.
|
|
38
42
|
let first = was === undefined
|
package/dist/channels.d.ts
CHANGED
|
@@ -11,9 +11,12 @@ export type LerpInto = (out: unknown[] | undefined, a: unknown, b: unknown, u: n
|
|
|
11
11
|
* since only the stock fold and merge are known to copy the array rather than keep it.
|
|
12
12
|
*/
|
|
13
13
|
export declare function lerpInto(channel: Channel<unknown>): LerpInto | undefined;
|
|
14
|
-
/**
|
|
14
|
+
/**
|
|
15
|
+
* What a lane needs of a stock numeric channel beyond the channel: its arithmetic by name, its axes.
|
|
16
|
+
* `'last'` has no rest: the last contribution to pass the band replaces, and a weight only gates it.
|
|
17
|
+
*/
|
|
15
18
|
export interface Numeric {
|
|
16
|
-
op: 'sum' | 'mul' | 'max';
|
|
19
|
+
op: 'sum' | 'mul' | 'max' | 'last';
|
|
17
20
|
axes: number;
|
|
18
21
|
}
|
|
19
22
|
/**
|
|
@@ -21,6 +24,12 @@ export interface Numeric {
|
|
|
21
24
|
* so a custom channel that claims a stock `kind` is not trusted with a lane.
|
|
22
25
|
*/
|
|
23
26
|
export declare function numericOf(channel: Channel<unknown>): Numeric | undefined;
|
|
27
|
+
/** Vouches for a stock channel made outside this module, as `color` makes its own. */
|
|
28
|
+
export declare function vouch(channel: Channel<unknown>, numeric: Numeric): void;
|
|
29
|
+
/** Records how many numbers a rest-less array channel holds a subject, which its rest cannot say. */
|
|
30
|
+
export declare function widen(channel: Channel<unknown>, axes: number): void;
|
|
31
|
+
/** How many numbers a channel holds a subject: its rest's length, a recorded width, or one. */
|
|
32
|
+
export declare function axesOf(channel: Channel<unknown>): number;
|
|
24
33
|
/** `merge(acc, scale(v, w))` for a stock numeric channel, in exactly its arithmetic. */
|
|
25
34
|
export declare function foldNumber(op: Numeric['op'], acc: number, v: number, w: number): number;
|
|
26
35
|
/**
|
|
@@ -51,7 +60,7 @@ export declare function mul(opts?: NumberOptions): Channel<number>;
|
|
|
51
60
|
*/
|
|
52
61
|
export declare function max(opts?: NumberOptions): Channel<number>;
|
|
53
62
|
/**
|
|
54
|
-
* A channel with no rest: the last
|
|
63
|
+
* A channel with no rest: the last contribution to pass wins, and a weight can only gate it. The
|
|
55
64
|
* default `lerp` steps at the midpoint, which is all a value with no arithmetic can promise; a
|
|
56
65
|
* consumer with a real interpolation passes its own, as `hex` does.
|
|
57
66
|
*
|
|
@@ -66,22 +75,6 @@ export declare function last<V>(opts?: {
|
|
|
66
75
|
* @category channel
|
|
67
76
|
*/
|
|
68
77
|
export declare function vec(n: number, of: Channel<number>): Channel<number[]>;
|
|
69
|
-
/**
|
|
70
|
-
* OKLCH, hue the short way round, so a crossfade between hues keeps its lightness and saturation
|
|
71
|
-
* instead of dipping through a muddy middle. A mix that leaves sRGB's gamut is clipped per channel.
|
|
72
|
-
*
|
|
73
|
-
* @category channel
|
|
74
|
-
*/
|
|
75
|
-
export declare function mixHex(a: number, b: number, u: number): number;
|
|
76
|
-
/**
|
|
77
|
-
* Color, as 0xrrggbb, blended in OKLCH as `mixHex` does, or with `space: 'srgb'` channel by
|
|
78
|
-
* channel in sRGB, which keeps a port's arithmetic identical to a host that blended that way.
|
|
79
|
-
*
|
|
80
|
-
* @category channel
|
|
81
|
-
*/
|
|
82
|
-
export declare function hex(opts?: {
|
|
83
|
-
space?: 'oklch' | 'srgb';
|
|
84
|
-
}): Channel<number>;
|
|
85
78
|
/**
|
|
86
79
|
* A kit is plain data; this only fixes the type.
|
|
87
80
|
*
|
package/dist/channels.js
CHANGED
|
@@ -23,13 +23,29 @@ const numerics = new WeakMap();
|
|
|
23
23
|
export function numericOf(channel) {
|
|
24
24
|
return numerics.get(channel);
|
|
25
25
|
}
|
|
26
|
+
/** Vouches for a stock channel made outside this module, as `color` makes its own. */
|
|
27
|
+
export function vouch(channel, numeric) {
|
|
28
|
+
numerics.set(channel, numeric);
|
|
29
|
+
}
|
|
30
|
+
const widths = new WeakMap();
|
|
31
|
+
/** Records how many numbers a rest-less array channel holds a subject, which its rest cannot say. */
|
|
32
|
+
export function widen(channel, axes) {
|
|
33
|
+
widths.set(channel, axes);
|
|
34
|
+
}
|
|
35
|
+
/** How many numbers a channel holds a subject: its rest's length, a recorded width, or one. */
|
|
36
|
+
export function axesOf(channel) {
|
|
37
|
+
const rest = channel.rest;
|
|
38
|
+
return Array.isArray(rest) ? rest.length : (widths.get(channel) ?? 1);
|
|
39
|
+
}
|
|
26
40
|
/** `merge(acc, scale(v, w))` for a stock numeric channel, in exactly its arithmetic. */
|
|
27
41
|
export function foldNumber(op, acc, v, w) {
|
|
28
42
|
if (op === 'sum')
|
|
29
43
|
return add(acc, times(v, w));
|
|
30
44
|
if (op === 'mul')
|
|
31
45
|
return product(acc, toward(v, w));
|
|
32
|
-
|
|
46
|
+
if (op === 'max')
|
|
47
|
+
return larger(acc, times(v, w));
|
|
48
|
+
return v;
|
|
33
49
|
}
|
|
34
50
|
/** Bounds change how a value folds at its edges, so they are part of the kind. */
|
|
35
51
|
const kindOf = (name, bounds) => bounds === undefined ? name : `${name}[${bounds[0]}, ${bounds[1]}]`;
|
|
@@ -85,7 +101,7 @@ export function max(opts) {
|
|
|
85
101
|
return channel;
|
|
86
102
|
}
|
|
87
103
|
/**
|
|
88
|
-
* A channel with no rest: the last
|
|
104
|
+
* A channel with no rest: the last contribution to pass wins, and a weight can only gate it. The
|
|
89
105
|
* default `lerp` steps at the midpoint, which is all a value with no arithmetic can promise; a
|
|
90
106
|
* consumer with a real interpolation passes its own, as `hex` does.
|
|
91
107
|
*
|
|
@@ -162,122 +178,6 @@ export function vec(n, of) {
|
|
|
162
178
|
numerics.set(channel, { ...inner, axes: n });
|
|
163
179
|
return channel;
|
|
164
180
|
}
|
|
165
|
-
const LINEAR = Array.from({ length: 256 }, (_, c) => {
|
|
166
|
-
const v = c / 255;
|
|
167
|
-
return v <= 0.04045 ? v / 12.92 : ((v + 0.055) / 1.055) ** 2.4;
|
|
168
|
-
});
|
|
169
|
-
/** The linear value halfway between byte c - 1 and c: at or above it, a value rounds to c. */
|
|
170
|
-
const ROUNDS_UP = LINEAR.map((_, c) => {
|
|
171
|
-
const v = (c - 0.5) / 255;
|
|
172
|
-
return v <= 0.04045 ? v / 12.92 : ((v + 0.055) / 1.055) ** 2.4;
|
|
173
|
-
});
|
|
174
|
-
/** The byte each 1/4096 of linear light starts at; a bin holds at most one rounding point. */
|
|
175
|
-
const BINS = 4096;
|
|
176
|
-
const FIRST = Array.from({ length: BINS + 1 }, (_, i) => {
|
|
177
|
-
let c = 0;
|
|
178
|
-
while (c < 255 && i / BINS >= ROUNDS_UP[c + 1])
|
|
179
|
-
c++;
|
|
180
|
-
return c;
|
|
181
|
-
});
|
|
182
|
-
const toByte = (v) => {
|
|
183
|
-
if (v <= 0)
|
|
184
|
-
return 0;
|
|
185
|
-
if (v >= 1)
|
|
186
|
-
return 255;
|
|
187
|
-
const c = FIRST[(v * BINS) | 0];
|
|
188
|
-
return c < 255 && v >= ROUNDS_UP[c + 1] ? c + 1 : c;
|
|
189
|
-
};
|
|
190
|
-
/** Below this chroma a color is gray and its hue is noise, so the lerp takes the other end's. */
|
|
191
|
-
const GRAY = 1e-4;
|
|
192
|
-
/**
|
|
193
|
-
* Colors already turned into OKLCH, direct-mapped on a byte of their hash: a mix reads the same
|
|
194
|
-
* few keyframe colors every frame, and the table stays 256 colors however many it meets.
|
|
195
|
-
*/
|
|
196
|
-
const SLOTS = 256;
|
|
197
|
-
const slotColor = new Int32Array(SLOTS).fill(-1);
|
|
198
|
-
const slotL = new Float64Array(SLOTS);
|
|
199
|
-
const slotC = new Float64Array(SLOTS);
|
|
200
|
-
const slotH = new Float64Array(SLOTS);
|
|
201
|
-
/** 0xrrggbb to its slot, filled with [L, C, h], h in radians; Björn Ottosson's OKLab matrices. */
|
|
202
|
-
function oklch(rgb) {
|
|
203
|
-
const slot = Math.imul(rgb, 0x9e3779b1) >>> 24;
|
|
204
|
-
if (slotColor[slot] === rgb)
|
|
205
|
-
return slot;
|
|
206
|
-
const r = LINEAR[(rgb >> 16) & 0xff];
|
|
207
|
-
const g = LINEAR[(rgb >> 8) & 0xff];
|
|
208
|
-
const b = LINEAR[rgb & 0xff];
|
|
209
|
-
const l = Math.cbrt(0.4122214708 * r + 0.5363325363 * g + 0.0514459929 * b);
|
|
210
|
-
const m = Math.cbrt(0.2119034982 * r + 0.6806995451 * g + 0.1073969566 * b);
|
|
211
|
-
const s = Math.cbrt(0.0883024619 * r + 0.2817188376 * g + 0.6299787005 * b);
|
|
212
|
-
const A = 1.9779984951 * l - 2.428592205 * m + 0.4505937099 * s;
|
|
213
|
-
const B = 0.0259040371 * l + 0.7827717662 * m - 0.808675766 * s;
|
|
214
|
-
slotColor[slot] = rgb;
|
|
215
|
-
slotL[slot] = 0.2104542553 * l + 0.793617785 * m - 0.0040720468 * s;
|
|
216
|
-
slotC[slot] = Math.sqrt(A * A + B * B);
|
|
217
|
-
slotH[slot] = Math.atan2(B, A);
|
|
218
|
-
return slot;
|
|
219
|
-
}
|
|
220
|
-
/** [L, C, h] to 0xrrggbb, clipping each channel to sRGB's gamut. */
|
|
221
|
-
function fromOklch(L, C, h) {
|
|
222
|
-
const A = C * Math.cos(h);
|
|
223
|
-
const B = C * Math.sin(h);
|
|
224
|
-
const l_ = L + 0.3963377774 * A + 0.2158037573 * B;
|
|
225
|
-
const m_ = L - 0.1055613458 * A - 0.0638541728 * B;
|
|
226
|
-
const s_ = L - 0.0894841775 * A - 1.291485548 * B;
|
|
227
|
-
const l = l_ * l_ * l_;
|
|
228
|
-
const m = m_ * m_ * m_;
|
|
229
|
-
const s = s_ * s_ * s_;
|
|
230
|
-
const r = 4.0767416621 * l - 3.3077115913 * m + 0.2309699292 * s;
|
|
231
|
-
const g = -1.2684380046 * l + 2.6097574011 * m - 0.3413193965 * s;
|
|
232
|
-
const b = -0.0041960863 * l - 0.7034186147 * m + 1.707614701 * s;
|
|
233
|
-
return (toByte(r) << 16) | (toByte(g) << 8) | toByte(b);
|
|
234
|
-
}
|
|
235
|
-
/**
|
|
236
|
-
* OKLCH, hue the short way round, so a crossfade between hues keeps its lightness and saturation
|
|
237
|
-
* instead of dipping through a muddy middle. A mix that leaves sRGB's gamut is clipped per channel.
|
|
238
|
-
*
|
|
239
|
-
* @category channel
|
|
240
|
-
*/
|
|
241
|
-
export function mixHex(a, b, u) {
|
|
242
|
-
if (u === 0 || a === b)
|
|
243
|
-
return a;
|
|
244
|
-
if (u === 1)
|
|
245
|
-
return b;
|
|
246
|
-
const sa = oklch(a);
|
|
247
|
-
const al = slotL[sa];
|
|
248
|
-
const ac = slotC[sa];
|
|
249
|
-
const ah = slotH[sa];
|
|
250
|
-
const sb = oklch(b);
|
|
251
|
-
const bc = slotC[sb];
|
|
252
|
-
const from = ac < GRAY ? slotH[sb] : ah;
|
|
253
|
-
const to = bc < GRAY ? ah : slotH[sb];
|
|
254
|
-
let turn = to - from;
|
|
255
|
-
if (turn > Math.PI)
|
|
256
|
-
turn -= 2 * Math.PI;
|
|
257
|
-
else if (turn < -Math.PI)
|
|
258
|
-
turn += 2 * Math.PI;
|
|
259
|
-
return fromOklch(mix(al, slotL[sb], u), mix(ac, bc, u), from + turn * u);
|
|
260
|
-
}
|
|
261
|
-
const clamp255 = (v) => (v < 0 ? 0 : v > 255 ? 255 : Math.round(v));
|
|
262
|
-
/** Each of red, green and blue on its own, as klieg's light channel blends. */
|
|
263
|
-
function mixSrgb(a, b, u) {
|
|
264
|
-
const ar = (a >> 16) & 0xff;
|
|
265
|
-
const ag = (a >> 8) & 0xff;
|
|
266
|
-
const ab = a & 0xff;
|
|
267
|
-
const br = (b >> 16) & 0xff;
|
|
268
|
-
const bg = (b >> 8) & 0xff;
|
|
269
|
-
const bb = b & 0xff;
|
|
270
|
-
return ((clamp255(mix(ar, br, u)) << 16) | (clamp255(mix(ag, bg, u)) << 8) | clamp255(mix(ab, bb, u)));
|
|
271
|
-
}
|
|
272
|
-
/**
|
|
273
|
-
* Color, as 0xrrggbb, blended in OKLCH as `mixHex` does, or with `space: 'srgb'` channel by
|
|
274
|
-
* channel in sRGB, which keeps a port's arithmetic identical to a host that blended that way.
|
|
275
|
-
*
|
|
276
|
-
* @category channel
|
|
277
|
-
*/
|
|
278
|
-
export function hex(opts) {
|
|
279
|
-
return { ...last({ lerp: opts?.space === 'srgb' ? mixSrgb : mixHex }), kind: 'hex' };
|
|
280
|
-
}
|
|
281
181
|
/**
|
|
282
182
|
* A kit is plain data; this only fixes the type.
|
|
283
183
|
*
|
package/dist/clock.d.ts
CHANGED
|
@@ -9,12 +9,19 @@ export declare function passesOf(loop: boolean | number | undefined): number;
|
|
|
9
9
|
export declare function phaseAt(elapsed: number, duration: number, passes: number): number;
|
|
10
10
|
/** The pass `elapsed` voice ms falls in, held at the last once they are done. */
|
|
11
11
|
export declare function passAt(elapsed: number, duration: number, passes: number): number;
|
|
12
|
-
/**
|
|
12
|
+
/**
|
|
13
|
+
* A voice's own fade this frame, 0..1: in from `since` over `fadeIn`, out along its ramp, and back
|
|
14
|
+
* up the same curve where a fade out was turned around.
|
|
15
|
+
*/
|
|
13
16
|
export declare function envelope(fadeIn: number, out: {
|
|
14
17
|
at: number;
|
|
15
18
|
over: number;
|
|
16
19
|
rest: boolean;
|
|
17
|
-
} | null, ease: Curve | undefined, reduced: boolean, now: number, since: number
|
|
20
|
+
} | null, ease: Curve | undefined, reduced: boolean, now: number, since: number, back?: {
|
|
21
|
+
from: number;
|
|
22
|
+
at: number;
|
|
23
|
+
over: number;
|
|
24
|
+
} | null): number;
|
|
18
25
|
/**
|
|
19
26
|
* A subject's voice time `raw` once the voice's freezes apply: 0 before it starts if it freezes
|
|
20
27
|
* before, else NaN, where it shows nothing; `span` once its passes end if it freezes after.
|
package/dist/clock.js
CHANGED
|
@@ -21,14 +21,22 @@ export function passAt(elapsed, duration, passes) {
|
|
|
21
21
|
return 0;
|
|
22
22
|
return over(elapsed, duration, passes) ? passes - 1 : Math.floor(elapsed / duration);
|
|
23
23
|
}
|
|
24
|
-
/**
|
|
25
|
-
|
|
24
|
+
/**
|
|
25
|
+
* A voice's own fade this frame, 0..1: in from `since` over `fadeIn`, out along its ramp, and back
|
|
26
|
+
* up the same curve where a fade out was turned around.
|
|
27
|
+
*/
|
|
28
|
+
export function envelope(fadeIn, out, ease, reduced, now, since, back = null) {
|
|
26
29
|
let w = 1;
|
|
27
30
|
if (fadeIn > 0 && !reduced) {
|
|
28
31
|
const u = (now - since) / fadeIn;
|
|
29
32
|
if (u < 1)
|
|
30
33
|
w *= ease ? ease(Math.max(0, u)) : Math.max(0, u);
|
|
31
34
|
}
|
|
35
|
+
if (back && !reduced) {
|
|
36
|
+
const u = back.from + (now - back.at) / back.over;
|
|
37
|
+
if (u < 1)
|
|
38
|
+
w *= ease ? ease(Math.max(0, u)) : Math.max(0, u);
|
|
39
|
+
}
|
|
32
40
|
if (out && !out.rest) {
|
|
33
41
|
if (reduced || out.over === 0)
|
|
34
42
|
return 0;
|