@msb235/blits 0.7.1 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +180 -0
- package/README.md +22 -4
- package/dist/book.js +4 -4
- package/dist/chain.d.ts +1 -1
- package/dist/chain.js +57 -54
- package/dist/channels.d.ts +26 -5
- package/dist/channels.js +12 -8
- package/dist/clock.d.ts +8 -1
- package/dist/clock.js +14 -3
- package/dist/clone.d.ts +3 -0
- package/dist/clone.js +16 -1
- package/dist/closed.d.ts +1 -1
- package/dist/closed.js +3 -2
- package/dist/color.d.ts +0 -11
- package/dist/color.js +4 -24
- package/dist/columns.js +0 -1
- package/dist/copyout.d.ts +6 -0
- package/dist/copyout.js +38 -0
- package/dist/crowd.d.ts +7 -5
- package/dist/crowd.js +32 -18
- package/dist/cue.d.ts +3 -1
- package/dist/cue.js +31 -20
- package/dist/departed.d.ts +34 -0
- package/dist/departed.js +73 -0
- package/dist/easing.d.ts +5 -0
- package/dist/easing.js +31 -2
- package/dist/everyone.d.ts +56 -0
- package/dist/everyone.js +221 -0
- package/dist/fade.d.ts +8 -1
- package/dist/fade.js +34 -6
- package/dist/fill.d.ts +22 -13
- package/dist/fill.js +104 -41
- package/dist/fit.js +2 -1
- package/dist/fold.d.ts +37 -0
- package/dist/fold.js +213 -28
- package/dist/gather.d.ts +15 -9
- package/dist/gather.js +60 -25
- package/dist/gone.js +10 -0
- package/dist/handle.d.ts +16 -5
- package/dist/handle.js +56 -6
- package/dist/held.d.ts +8 -5
- package/dist/held.js +56 -58
- package/dist/history.d.ts +17 -13
- package/dist/history.js +47 -28
- package/dist/hosts.js +55 -8
- package/dist/index.d.ts +9 -4
- package/dist/index.js +5 -2
- package/dist/keyfill.d.ts +7 -0
- package/dist/keyfill.js +124 -0
- package/dist/keyrows.d.ts +1 -0
- package/dist/keyrows.js +1 -1
- package/dist/lane.d.ts +32 -2
- package/dist/lane.js +5 -0
- package/dist/lanes.d.ts +32 -8
- package/dist/lanes.js +69 -37
- package/dist/leavings.d.ts +26 -0
- package/dist/leavings.js +70 -0
- package/dist/meet.js +14 -8
- package/dist/mixer.d.ts +39 -7
- package/dist/mixer.js +50 -10
- package/dist/motion.d.ts +2 -0
- package/dist/motion.js +4 -1
- package/dist/motions.d.ts +22 -15
- package/dist/motions.js +40 -34
- package/dist/move.js +23 -16
- package/dist/moved.d.ts +11 -0
- package/dist/moved.js +29 -0
- package/dist/origin.d.ts +19 -0
- package/dist/origin.js +68 -0
- package/dist/owed.d.ts +4 -4
- package/dist/owed.js +8 -8
- package/dist/owner.d.ts +1 -1
- package/dist/owner.js +2 -2
- package/dist/pace.d.ts +13 -5
- package/dist/pace.js +21 -8
- package/dist/pack.d.ts +3 -1
- package/dist/pack.js +15 -24
- package/dist/paging.d.ts +8 -2
- package/dist/paging.js +44 -4
- package/dist/patch.d.ts +14 -9
- package/dist/patch.js +83 -21
- package/dist/place.js +18 -3
- package/dist/project.d.ts +12 -1
- package/dist/project.js +101 -83
- package/dist/pull.d.ts +2 -0
- package/dist/pull.js +8 -0
- package/dist/qualify.js +10 -2
- package/dist/reading.d.ts +5 -0
- package/dist/reading.js +5 -0
- package/dist/record.d.ts +9 -0
- package/dist/record.js +42 -0
- package/dist/relink.d.ts +11 -5
- package/dist/relink.js +7 -5
- package/dist/restored.d.ts +41 -0
- package/dist/restored.js +91 -0
- package/dist/revive.d.ts +10 -3
- package/dist/revive.js +31 -23
- package/dist/roster.d.ts +11 -0
- package/dist/roster.js +46 -0
- package/dist/rotation.d.ts +29 -0
- package/dist/rotation.js +91 -0
- package/dist/rows.d.ts +5 -5
- package/dist/rows.js +5 -5
- package/dist/sample.d.ts +3 -2
- package/dist/sample.js +32 -15
- package/dist/seek.js +97 -114
- package/dist/shared.d.ts +6 -0
- package/dist/shared.js +15 -0
- package/dist/signals.d.ts +24 -3
- package/dist/signals.js +50 -11
- package/dist/spans.js +7 -0
- package/dist/stack.d.ts +11 -0
- package/dist/stack.js +47 -0
- package/dist/standing.d.ts +10 -0
- package/dist/standing.js +79 -0
- package/dist/store.d.ts +3 -3
- package/dist/store.js +3 -3
- package/dist/strays.js +3 -0
- package/dist/tape.d.ts +8 -5
- package/dist/tape.js +21 -13
- package/dist/testing.d.ts +35 -0
- package/dist/testing.js +116 -0
- package/dist/touch.d.ts +6 -0
- package/dist/touch.js +28 -0
- package/dist/transport.d.ts +49 -6
- package/dist/transport.js +89 -19
- package/dist/turns.d.ts +18 -0
- package/dist/turns.js +72 -0
- package/dist/tweened.js +1 -1
- package/dist/types/channel.d.ts +46 -0
- package/dist/types/channel.js +1 -0
- package/dist/types/history.d.ts +139 -0
- package/dist/types/history.js +1 -0
- package/dist/types/mix.d.ts +336 -0
- package/dist/types/mix.js +1 -0
- package/dist/types/patch.d.ts +187 -0
- package/dist/types/patch.js +1 -0
- package/dist/types/place.d.ts +157 -0
- package/dist/types/place.js +1 -0
- package/dist/types/transport.d.ts +71 -0
- package/dist/types/transport.js +1 -0
- package/dist/types/voice.d.ts +323 -0
- package/dist/types/voice.js +1 -0
- package/dist/types.d.ts +7 -1162
- package/dist/unpage.d.ts +2 -2
- package/dist/unpage.js +53 -25
- package/dist/voice.d.ts +90 -22
- package/dist/voice.js +105 -43
- package/dist/wave.d.ts +42 -0
- package/dist/wave.js +62 -0
- package/dist/weigh.js +1 -1
- package/docs/schema.html +42 -36
- package/package.json +8 -3
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,186 @@ 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.8.0
|
|
8
|
+
|
|
9
|
+
### Breaking
|
|
10
|
+
|
|
11
|
+
- `handle.seek(elapsed)` rebuilds each subject's state for the new position: fresh from the
|
|
12
|
+
patch's `state`, with what signals keep started over, and stepped from the voice's start to
|
|
13
|
+
`elapsed` at the next probe, on the `stepMs` grid where there is one. It left state where it was,
|
|
14
|
+
so a spring seeked back to 0 read about 111 where a fresh one reads 0. `seek(elapsed, { state:
|
|
15
|
+
'keep' })` keeps the old behavior. The same holds for an owner's seek and its children.
|
|
16
|
+
- A `keys` channel given a `delayBy` waits, then travels in the time left, landing with the other
|
|
17
|
+
channels at the end of the duration, as klieg's own `delayBy` does. It shifted the phase without
|
|
18
|
+
compressing it, so a channel waiting 200 ms of 1000 stopped 80% of the way there.
|
|
19
|
+
- In a `locus`, each channel folds at the summed weight of the members that write it, not the whole
|
|
20
|
+
locus's. A channel only one member writes fades with that member rather than holding at full
|
|
21
|
+
weight until it snaps to rest.
|
|
22
|
+
- `cue` refuses a `loop` that is not `true`, `false` or a whole number of passes; `spring` refuses
|
|
23
|
+
a stiffness or mass that is not positive, or a negative damping; `glide` refuses an `ms` that is
|
|
24
|
+
not positive. Each played wrongly: a fractional loop jumped at its end, and a spring with no
|
|
25
|
+
stiffness snapped to its target.
|
|
26
|
+
- `sync(NaN)` throws. It slipped past the check that the clock only goes forward.
|
|
27
|
+
- A voice refuses a rate that is not finite and a weight that is NaN, at `cue` and on its handle.
|
|
28
|
+
Both were accepted. A negative voice rate still is.
|
|
29
|
+
- `{ steps, jump: 'start' }` reads `1/n` at 0, as CSS does. It read 0.
|
|
30
|
+
- A history store's contract changed. `cut` takes the `seq` of the frame a seek went back to, not
|
|
31
|
+
a mix time, and every `Paged` record carries the `seq` it was made in; keep one record per key
|
|
32
|
+
and `seq`, since rate 0 lets several frames share a mix time. The transport pages its announced
|
|
33
|
+
marks, rate changes and frames too, as `mark`, `pace` and `frame` records with `mix: ''`, and a
|
|
34
|
+
`left` record's `data` is now `[record, sync]`. A store written for 0.7 forgets the wrong records
|
|
35
|
+
on a seek back.
|
|
36
|
+
- `vec(3, sum())` is typed `Channel<[number, number, number]>`, a tuple of the length it was given,
|
|
37
|
+
so a kit over a tuple pose needs no cast. A length that is not a literal still gives
|
|
38
|
+
`Channel<number[]>`. Types only: code that calls such a channel's `merge`, `scale` or `lerp`
|
|
39
|
+
itself with a `number[]` annotates the channel as `Channel<number[]>`.
|
|
40
|
+
- `probe(subject, out)` writes into the array `out` already holds for a `vec` channel, or a `color`
|
|
41
|
+
that averages in OKLab or runs on a lane, where that array is the channel's length. It put a new
|
|
42
|
+
array there on every probe. A host that kept `out.position` from one probe and compared it with
|
|
43
|
+
the next now holds the same array both times: copy what has to outlast the next probe. A probe
|
|
44
|
+
with no `out` still makes its own arrays.
|
|
45
|
+
- The deprecated names are removed. A voice's or an owner's `hold` is `freeze`, as since 0.6.0. A
|
|
46
|
+
patch's `period` is `duration`: a hand-made patch that set only `period` now has no duration.
|
|
47
|
+
`hex()` is gone, and `hex({ space: 'srgb' })` with it: `last<number>({ lerp: mixHex })` is the
|
|
48
|
+
same channel for a color kept as 0xrrggbb, and `color(last(), { lerp: 'oklch' })` with `toHex`
|
|
49
|
+
at write is the one that also runs with `color()`'s coverage. Nothing blends red, green and blue
|
|
50
|
+
each on its own any more; a host that wants that gives `last` a `lerp` of its own.
|
|
51
|
+
|
|
52
|
+
### Added
|
|
53
|
+
|
|
54
|
+
- `angle({ turn })`, a channel for a rotation about one axis: it adds as `sum` does, and `lerp` and
|
|
55
|
+
a weight below 1 go the short way round, so 350 to 10 passes 0. `turn` is one full turn in the
|
|
56
|
+
channel's unit, default 360. It runs on lanes.
|
|
57
|
+
- `quat()`, a channel for a rotation in space as a unit quaternion `[x, y, z, w]`: contributions
|
|
58
|
+
compose in cue order, and `lerp` and a weight take the short arc. It runs on lanes, through its
|
|
59
|
+
own `fold`.
|
|
60
|
+
- `Vec<N>`, the tuple of `N` numbers a `vec` channel holds.
|
|
61
|
+
- `fold(kit, deltas, weights?)` folds deltas into one pose by a kit's arithmetic with no mix and no
|
|
62
|
+
clock: what a mix gives for voices contributing those deltas at those weights, every weight 1
|
|
63
|
+
where none is given.
|
|
64
|
+
- `project(time)` reads an earlier time on a mix made without `history`, where how the mix stands
|
|
65
|
+
now says what it showed then: every voice plays as it was cued and reads by its clock alone (no
|
|
66
|
+
state, motion, anchor or owner, no write to its handle, no channel without a rest at a weight
|
|
67
|
+
that moves), the mix's rate was never set, and `time` is no earlier than the last cue, `drop`,
|
|
68
|
+
`touch`, `announce` or `rebase`, or the last frame a voice left in. Anywhere else it throws and
|
|
69
|
+
says which of those it met, where it threw for any time behind the mix. A host that cued its
|
|
70
|
+
voices once need not build a second mix to read an earlier frame.
|
|
71
|
+
- `handle.to`, `handle.push` and `handle.read` reach the `to`, `push` and `read` of the motion
|
|
72
|
+
patch a voice plays, so a host holding only the handle need not keep the patch or cast it. `to`
|
|
73
|
+
and `push` throw for a voice whose patch does not take them; `read` gives undefined.
|
|
74
|
+
- `mix.touch(subject?)` says that something a patch reads from outside the mix changed since the
|
|
75
|
+
last sync, so the next probe calls the patch again where it would have answered with what it
|
|
76
|
+
read this frame: for one subject, or for all. A host need not move the clock to show the change.
|
|
77
|
+
- `input(read)` makes a signal of the host's own that reads something outside the clock. The host
|
|
78
|
+
calls its `touch()` after each change, and a voice weighted by it keeps its lane, where one
|
|
79
|
+
weighted by a signal flagged `input` by hand never runs on one. `input(read, of)` marks a signal
|
|
80
|
+
built on others, an input where any of them is. `level` is `input` over one number.
|
|
81
|
+
|
|
82
|
+
- `@msb235/blits/testing`, for a library writing patches: `setting()` makes a `Setting` to call a
|
|
83
|
+
patch outside a mix, and `checkPatch(patch, { subject, kit })` lists what a mix cannot rely on.
|
|
84
|
+
- `handle.seek` answers how sure the state it leaves is, as a `Doubt`: `exact`, `stepped` or
|
|
85
|
+
`held`. `SeekOptions` is exported.
|
|
86
|
+
- `wave(duration, { shape, cycles, phase, depth, kit })` swings each numeric channel `depth` names
|
|
87
|
+
up to `depth` either side of its rest in `kit` (0 without one), as a sine, triangle, saw, or
|
|
88
|
+
square, `cycles` times a pass. It is an `fn` patch that keeps its options on `patch.wave` for an
|
|
89
|
+
engine that reads data; `waveAt` is the unit wave.
|
|
90
|
+
- A `keys` patch carries the options it was given as fields: `ease`, `easeBy`, `delayBy` and
|
|
91
|
+
`lerpBy`, beside `keys` and `kit`. They were kept in a table keyed by the patch object, so a copy
|
|
92
|
+
such as `{ ...p, duration: 2000 }` lost its easing; a copy now plays by the fields it carries,
|
|
93
|
+
and may change one.
|
|
94
|
+
- `Channel.copy`, for a channel whose `rest` is an object the mix cannot copy itself, such as a class
|
|
95
|
+
instance, and that has `fold`; `cue` refuses such a channel without one. The mix copies arrays,
|
|
96
|
+
typed arrays and plain objects itself.
|
|
97
|
+
- `npm run test:general` runs the suite with lanes off, so every test checks the general path
|
|
98
|
+
against the lanes.
|
|
99
|
+
|
|
100
|
+
### Fixed
|
|
101
|
+
|
|
102
|
+
- A handle `seek` made in the frame its voice was cued in put every subject's origin before the
|
|
103
|
+
voice began, so the fade in read as over at once, and under a fade out a subject first probed
|
|
104
|
+
after the seek read a weight the others did not. The origin is now the cue's frame, as it is for
|
|
105
|
+
a seek in any later frame.
|
|
106
|
+
- A rate above 0 set on a voice still pending applies from the voice's start. It was taken from
|
|
107
|
+
the moment it was set, so the voice's clock passed 0 before the start: the voice then began
|
|
108
|
+
partway in, and a subject it froze before played, and stepped its state, while the voice was
|
|
109
|
+
still pending.
|
|
110
|
+
- `fade({ at: 'rest' })` begun after a subject's probe in the same frame finds that subject at rest
|
|
111
|
+
at its next probe, where it stayed in the voice until the frame after.
|
|
112
|
+
- Seeking back many times no longer throws `Maximum call stack size exceeded`. Every `seek` to an
|
|
113
|
+
earlier time wrapped each voice's records in another layer, kept for the life of the mix, so a
|
|
114
|
+
scrubbed mix grew with every seek and a subject not probed since was found through all of them:
|
|
115
|
+
20,000 seeks overflowed the stack. A voice now keeps one store however often the mix seeks. Two
|
|
116
|
+
seeks back with no probe between could also read a wrong pose, as two fuzzer programs did.
|
|
117
|
+
- An anchor cued after its target had left waited for good, and kept the mix from coming to rest,
|
|
118
|
+
unless the mix's history still reached the target. A mix now keeps the marks of the first and last
|
|
119
|
+
voice to leave under each name, tag and channel, and an anchor no voice the mix still knows answers
|
|
120
|
+
reads those, placing its voice as it would have while the target was there. It holds that much and
|
|
121
|
+
no more, however many voices leave.
|
|
122
|
+
- A channel whose `rest` is an object or a typed array had it handed to `fold` by reference, so it
|
|
123
|
+
accumulated across frames and every subject's pose shared it.
|
|
124
|
+
- `fade()` during a fade-in made the weight rise before it fell: the fade-in kept climbing under
|
|
125
|
+
the fade-out. It now stops where the fade-out begins.
|
|
126
|
+
- A seek put a voice's controls back without its rise ramp, so a seek to before a later `rise()`
|
|
127
|
+
read the voice at 0.
|
|
128
|
+
- A `from: 'current'` retarget under an easing that jumps or leaves vertically at its start
|
|
129
|
+
(`steps` with `jump: 'start'`, `bezier [0,1,0,1]`) threw the value hundreds of thousands out. Such
|
|
130
|
+
an easing now takes no slope bend.
|
|
131
|
+
- A channel with no `rest` stayed switched on across a weight of 0, so a weight going 0.7, 0, 0.5
|
|
132
|
+
found it on at 0.5.
|
|
133
|
+
- `slew` and `lag` step by no more than `maxDt`, as `step` does. After a long gap a slew finished
|
|
134
|
+
at once.
|
|
135
|
+
- A `keys` patch reads a channel whose stops are all plain numbers, lerped straight across, by a
|
|
136
|
+
search and lerp of its own, giving the same bits: `patch.at` over 10,000 four-stop tracks takes
|
|
137
|
+
about 0.70 of the time it did.
|
|
138
|
+
- Two copies of blits loaded together, as a library pinning its own copy brings, read each other's
|
|
139
|
+
patches and channels: a `keys` patch one copy made kept its easing on the other's mix, which read
|
|
140
|
+
it linear, and the facts a stock channel carries for lanes are shared too.
|
|
141
|
+
- Under `history`, a read back to a frame in which the host changed a voice between the sync and
|
|
142
|
+
the first probe shows the change in that frame, as the live probe did. It showed from the next
|
|
143
|
+
frame.
|
|
144
|
+
- A subject's fade-in start and step origin came from its voice's rate and anchor as they stood at
|
|
145
|
+
the subject's first probe, so a rate change, ramp or handle seek before then moved them and the
|
|
146
|
+
numbers depended on when the host had looked. Every voice now keeps the clocks it ran on, and a
|
|
147
|
+
subject's origin is when its voice's clock first read the subject's delay. A stateful subject met
|
|
148
|
+
late steps from that origin, capped by `maxDt`, where it started stepping from first sight. A
|
|
149
|
+
read ahead no longer loses a stateful voice whose anchored start falls inside it.
|
|
150
|
+
- A seek or read back was keyed on mix time, which rate 0, a backdated `fade({ at })` and several
|
|
151
|
+
handle calls in one frame can all share, so it could restore the wrong moment or drop a voice
|
|
152
|
+
whose fade was backdated. The transport now numbers its frames, history entries carry the frame
|
|
153
|
+
they were made in, and a seek or read to mix time `t` lands on the last frame at or before `t`.
|
|
154
|
+
The tape stamps calls by host time, so frames rate 0 holds at one mix time stay apart.
|
|
155
|
+
- A frame that passed both a voice's start and the `fade({ at })` set for it began the fade a frame
|
|
156
|
+
late, so 16 ms and 32 ms frames disagreed.
|
|
157
|
+
- With a history store, the transport kept every announced mark, rate change and frame for good, and
|
|
158
|
+
a long run slowed quadratically: 20,000 frames took 19.8 s, against 0.17 s now.
|
|
159
|
+
- Lanes and the general path disagreed in three places. A `level` set between two probes in one
|
|
160
|
+
frame now reaches the next probe on lanes too; `inert` reads the same for a motion voice at weight
|
|
161
|
+
0, which lanes kept awake until it landed; and `project` reads a subject faded out of a voice as
|
|
162
|
+
out of it after a retarget or a `drop` brought it back, on both paths. A voice weighted by an input
|
|
163
|
+
signal not built on `level`, or whose patch reads host fields, never runs on a lane, since a
|
|
164
|
+
change to either between two probes would not reach it.
|
|
165
|
+
- A span that skipped several children at cue played the last of them in full on the next sync, and
|
|
166
|
+
then reported none skipped.
|
|
167
|
+
- A pose read NaN for a voice still waiting on a `start` the host gave, once the voice was set to
|
|
168
|
+
rate 0 while the mix's rate was 0. It reads as any voice before its start does.
|
|
169
|
+
- A frame allocates much less, which is most of what made the garbage collector run during one. A
|
|
170
|
+
probe into `out` reuses its arrays, a motion voice off lanes reuses its last delta, and a fill
|
|
171
|
+
no longer boxes a number per subject. `bench/frame.mjs`, 10,000 subjects probed into one `out`:
|
|
172
|
+
three `keys` voices went from 1.8 MB a frame to 0.16 MB and take 0.81 of the time; a `tween`
|
|
173
|
+
voice from 0.72 MB to 0.02 MB, 0.90 of the time; a `tween` voice with lanes off from 2.6 MB to
|
|
174
|
+
0.32 MB, 0.82 of the time.
|
|
175
|
+
- Cueing a voice over every subject costs much less. A voice with no `subjects`, `target`,
|
|
176
|
+
`stagger` or signal weight, whose patch keeps no state, kept a record for each subject probed;
|
|
177
|
+
it keeps one for them all, and a read ahead copies that one. `bench/frame.mjs`, 10,000 subjects
|
|
178
|
+
each under a `tween` voice of its own, with a voice over all of them replaced every frame: 9.3 MB
|
|
179
|
+
a frame to 2.7 MB, in 0.74 of the time. A read 500 ms ahead of 1,000 subjects under three voices,
|
|
180
|
+
made and probed every frame: 2.4 MB to 1.5 MB, in 0.80 of the time. A mix made with
|
|
181
|
+
`lanes: false`, or with `history`, keeps a record for each subject as before.
|
|
182
|
+
- Under `history`, dropping subjects and reading them back each took time that grew with the square
|
|
183
|
+
of how many had left: every drop went through every record its voice had kept, and so did every
|
|
184
|
+
read back. 8,000 subjects dropped in one frame took 173 ms and take 7; reading them back took 180
|
|
185
|
+
ms and takes 3 (`bench/drops.mjs`).
|
|
186
|
+
|
|
7
187
|
## 0.7.1
|
|
8
188
|
|
|
9
189
|
### Fixed
|
package/README.md
CHANGED
|
@@ -69,7 +69,7 @@ and weight), **mix** (the live voices over one kit), **signal** (a 0..1 scalar f
|
|
|
69
69
|
clock), **handle** (the live controls on one voice), **engine** (the implementation behind a mix).
|
|
70
70
|
|
|
71
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
|
|
72
|
+
done, last pass ended, fade-out begun, or end, or a named point the host announces), **anchor** (a time given by
|
|
73
73
|
another voice's mark), **span** (an owner fitting the voices it holds into a duration), **project**
|
|
74
74
|
(read the mix at another time without moving it), **seek** (move the mix to a time and play on).
|
|
75
75
|
|
|
@@ -89,8 +89,26 @@ npm run site # dev server on port 4880
|
|
|
89
89
|
npm run site:smoke # build, then load every page headless and fail on any error
|
|
90
90
|
```
|
|
91
91
|
|
|
92
|
-
`.github/workflows/site.yml` deploys it to GitHub Pages
|
|
93
|
-
|
|
92
|
+
`.github/workflows/site.yml` deploys it to GitHub Pages on every push to `main`, and by hand.
|
|
93
|
+
|
|
94
|
+
## Writing patches for blits in another library
|
|
95
|
+
|
|
96
|
+
A library that ships patches exports them with the channels they write, as a `Kit` of its own
|
|
97
|
+
for the host to spread into its kit, and checks them in its own tests against
|
|
98
|
+
`@msb235/blits/testing`:
|
|
99
|
+
|
|
100
|
+
```ts
|
|
101
|
+
import { checkPatch, setting } from '@msb235/blits/testing';
|
|
102
|
+
|
|
103
|
+
expect(checkPatch(glow, { subject, kit })).toEqual([]); // each string names a problem
|
|
104
|
+
glow.at(0.5, subject, setting({ weight: 0.5 })); // a Setting for calling a patch outside a mix
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
`checkPatch` reads the patch across its phase from fresh state and reports what a mix cannot rely
|
|
108
|
+
on: writing outside `writes`, a delta that changes between identical reads, a touched subject,
|
|
109
|
+
state shared between subjects, a `step` that lands differently from the same start, a `clone`,
|
|
110
|
+
`pack` or `unpack` that does not give back an equal state, a missing host field, and a kit that
|
|
111
|
+
`cue` would refuse. Two copies of blits loaded together read each other's patches and channels.
|
|
94
112
|
|
|
95
113
|
## Packages
|
|
96
114
|
|
|
@@ -100,7 +118,7 @@ so the engine keeps no dependencies; its README says how it works.
|
|
|
100
118
|
|
|
101
119
|
## Status
|
|
102
120
|
|
|
103
|
-
v0.
|
|
121
|
+
v0.8.0, with five consumers. magicsmoke runs every fault on it, wod its wheel, tricks and
|
|
104
122
|
transitions, weasel its animator's tweens, sherpa its seams, and
|
|
105
123
|
[klieg](https://github.com/orochi235/klieg) composes all three of its systems — letter motion,
|
|
106
124
|
part effects, and the environment a sign is lit by — on a mix each.
|
package/dist/book.js
CHANGED
|
@@ -111,7 +111,7 @@ export class Book {
|
|
|
111
111
|
if (voice.spec.hits !== undefined && voice.state !== 'done')
|
|
112
112
|
this.tracks.set(voice, {
|
|
113
113
|
seenTo: voice.elapsedAt(host.now),
|
|
114
|
-
|
|
114
|
+
jumps: voice.jumps,
|
|
115
115
|
booked: new Map(),
|
|
116
116
|
met: this.round,
|
|
117
117
|
});
|
|
@@ -166,13 +166,13 @@ export class Book {
|
|
|
166
166
|
continue;
|
|
167
167
|
let t = this.tracks.get(voice);
|
|
168
168
|
if (t === undefined) {
|
|
169
|
-
t = { seenTo: Number.NEGATIVE_INFINITY,
|
|
169
|
+
t = { seenTo: Number.NEGATIVE_INFINITY, jumps: voice.jumps, booked: new Map(), met: round };
|
|
170
170
|
this.tracks.set(voice, t);
|
|
171
171
|
}
|
|
172
172
|
t.met = round;
|
|
173
173
|
// A seek counts from where it put the clock: what it jumped over is not late.
|
|
174
|
-
if (t.
|
|
175
|
-
t.
|
|
174
|
+
if (t.jumps !== voice.jumps) {
|
|
175
|
+
t.jumps = voice.jumps;
|
|
176
176
|
t.seenTo = voice.elapsedAt(prev);
|
|
177
177
|
}
|
|
178
178
|
const end = host.endOf(voice);
|
package/dist/chain.d.ts
CHANGED
|
@@ -9,7 +9,7 @@ import type { Subject, Voice } from './voice.js';
|
|
|
9
9
|
export declare function chain<I, O>(this: Mixer<I, O>, subject: I): Subject<unknown>;
|
|
10
10
|
export declare function relink<I, O>(this: Mixer<I, O>, subject: I, was: Subject<unknown> | undefined): Subject<unknown>;
|
|
11
11
|
/** A voice's record for a subject where its chain links it: the voice plays or freezes, and reaches it. */
|
|
12
|
-
export declare function linkable<I, O>(this: Mixer<I, O>, voice: Voice<I, O>, subject: I,
|
|
12
|
+
export declare function linkable<I, O>(this: Mixer<I, O>, voice: Voice<I, O>, subject: I, slot: number): Subject<unknown> | null;
|
|
13
13
|
/** Whether a voice reaches a subject by what its spec says: the subjects it names, or its `target`. */
|
|
14
14
|
export declare function aims<I, O>(this: Mixer<I, O>, voice: Voice<I, O>, subject: I): boolean;
|
|
15
15
|
/**
|
package/dist/chain.js
CHANGED
|
@@ -1,27 +1,8 @@
|
|
|
1
|
+
import { everyoneOf, sight } from './everyone.js';
|
|
2
|
+
import { record } from './record.js';
|
|
1
3
|
/** The first record of a subject no live voice reaches, so a probe of it still makes one lookup. */
|
|
2
4
|
function stub() {
|
|
3
|
-
return
|
|
4
|
-
reaches: false,
|
|
5
|
-
delay: 0,
|
|
6
|
-
since: 0,
|
|
7
|
-
shown: 0,
|
|
8
|
-
weight: 0,
|
|
9
|
-
rested: false,
|
|
10
|
-
bands: null,
|
|
11
|
-
state: undefined,
|
|
12
|
-
stepped: 0,
|
|
13
|
-
ticks: 0,
|
|
14
|
-
probed: Number.NaN,
|
|
15
|
-
delta: null,
|
|
16
|
-
phase: 0,
|
|
17
|
-
seeks: 0,
|
|
18
|
-
kept: null,
|
|
19
|
-
voice: null,
|
|
20
|
-
next: null,
|
|
21
|
-
version: Number.NaN,
|
|
22
|
-
loci: null,
|
|
23
|
-
slot: -1,
|
|
24
|
-
};
|
|
5
|
+
return record(null, false, undefined);
|
|
25
6
|
}
|
|
26
7
|
/**
|
|
27
8
|
* The first of this subject's records, linked through every live voice that reaches it. A voice
|
|
@@ -33,40 +14,15 @@ export function chain(subject) {
|
|
|
33
14
|
const was = this.chains.get(subject);
|
|
34
15
|
return was !== undefined && was.version === this.version ? was : this.relink(subject, was);
|
|
35
16
|
}
|
|
36
|
-
// Apart from `chain
|
|
37
|
-
// `chain` stays small enough for V8 to inline where a probe links.
|
|
17
|
+
// Apart from `chain`, which stays small enough for V8 to inline where a probe links.
|
|
38
18
|
export function relink(subject, was) {
|
|
39
|
-
const now = this.now;
|
|
40
19
|
const slot = was !== undefined ? was.slot : this.lanes !== null ? this.lanes.number(subject) : -1;
|
|
41
20
|
// Where only voices over every subject changed since, those alone are taken off or put on.
|
|
42
21
|
let first = was === undefined
|
|
43
22
|
? undefined
|
|
44
|
-
: this.steps.patch(was.voice === null ? null : was, was.version,
|
|
45
|
-
if (first === undefined)
|
|
46
|
-
|
|
47
|
-
first = null;
|
|
48
|
-
const link = (voice) => {
|
|
49
|
-
const held = this.linkable(voice, subject, now, slot);
|
|
50
|
-
if (held === null)
|
|
51
|
-
return;
|
|
52
|
-
held.voice = voice;
|
|
53
|
-
held.next = null;
|
|
54
|
-
if (prev === null)
|
|
55
|
-
first = held;
|
|
56
|
-
else
|
|
57
|
-
prev.next = held;
|
|
58
|
-
prev = held;
|
|
59
|
-
};
|
|
60
|
-
const named = this.naming === 0 ? undefined : this.named.get(subject);
|
|
61
|
-
let j = 0;
|
|
62
|
-
for (const voice of this.general) {
|
|
63
|
-
while (named !== undefined && j < named.length && named[j].id < voice.id)
|
|
64
|
-
link(named[j++]);
|
|
65
|
-
link(voice);
|
|
66
|
-
}
|
|
67
|
-
while (named !== undefined && j < named.length)
|
|
68
|
-
link(named[j++]);
|
|
69
|
-
}
|
|
23
|
+
: this.steps.patch(was.voice === null ? null : was, was.version, this, subject, slot);
|
|
24
|
+
if (first === undefined)
|
|
25
|
+
first = linkAll(this, subject, slot);
|
|
70
26
|
const head = first ?? stub();
|
|
71
27
|
head.version = this.version;
|
|
72
28
|
if (was !== undefined && was !== head) {
|
|
@@ -81,19 +37,59 @@ export function relink(subject, was) {
|
|
|
81
37
|
this.chains.set(subject, head);
|
|
82
38
|
return head;
|
|
83
39
|
}
|
|
40
|
+
/** A subject's chain linked afresh through every voice that reaches it, in voice order. */
|
|
41
|
+
function linkAll(mix, subject, slot) {
|
|
42
|
+
let first = null;
|
|
43
|
+
let prev = null;
|
|
44
|
+
const named = mix.naming === 0 ? undefined : mix.named.get(subject);
|
|
45
|
+
let j = 0;
|
|
46
|
+
const general = mix.general;
|
|
47
|
+
for (let i = 0; i <= general.length; i++) {
|
|
48
|
+
const voice = i < general.length ? general[i] : null;
|
|
49
|
+
// The voices naming the subject that come before this one, then this one.
|
|
50
|
+
for (;;) {
|
|
51
|
+
const mine = named !== undefined && j < named.length ? named[j] : null;
|
|
52
|
+
const next = mine !== null && (voice === null || mine.id < voice.id) ? mine : voice;
|
|
53
|
+
if (next === null)
|
|
54
|
+
break;
|
|
55
|
+
if (next === mine)
|
|
56
|
+
j++;
|
|
57
|
+
const held = mix.linkable(next, subject, slot);
|
|
58
|
+
if (held !== null) {
|
|
59
|
+
held.voice = next;
|
|
60
|
+
held.next = null;
|
|
61
|
+
if (prev === null)
|
|
62
|
+
first = held;
|
|
63
|
+
else
|
|
64
|
+
prev.next = held;
|
|
65
|
+
prev = held;
|
|
66
|
+
}
|
|
67
|
+
if (next === voice)
|
|
68
|
+
break;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return first;
|
|
72
|
+
}
|
|
84
73
|
/** A voice's record for a subject where its chain links it: the voice plays or freezes, and reaches it. */
|
|
85
|
-
export function linkable(voice, subject,
|
|
74
|
+
export function linkable(voice, subject, slot) {
|
|
86
75
|
const state = voice.state;
|
|
87
76
|
if (state === 'done' || (state === 'pending' && !voice.freezesBefore))
|
|
88
77
|
return null;
|
|
89
|
-
|
|
78
|
+
// A voice sharing one record is in no chain: a fold takes it from `sharers` at its place.
|
|
79
|
+
if (voice.sharing && everyoneOf(this, voice) !== null) {
|
|
80
|
+
// A projection numbers no subject, and keeps nothing by subject.
|
|
81
|
+
if (slot >= 0)
|
|
82
|
+
sight(voice, slot, this.lanes.cap);
|
|
83
|
+
return null;
|
|
84
|
+
}
|
|
85
|
+
const held = this.held(voice, subject, slot);
|
|
90
86
|
return held.reaches ? held : null;
|
|
91
87
|
}
|
|
92
88
|
/** Whether a voice reaches a subject by what its spec says: the subjects it names, or its `target`. */
|
|
93
89
|
export function aims(voice, subject) {
|
|
94
90
|
// A read back to before a subject left still finds it reached.
|
|
95
91
|
const left = voice.parted?.get(subject);
|
|
96
|
-
if (left !== undefined && !(this.now < left))
|
|
92
|
+
if (left !== undefined && !(this.now < left.at))
|
|
97
93
|
return false;
|
|
98
94
|
if (voice.named !== null)
|
|
99
95
|
return voice.named.has(subject);
|
|
@@ -128,6 +124,13 @@ export function index(mix, voice) {
|
|
|
128
124
|
return;
|
|
129
125
|
if (voice.named === null) {
|
|
130
126
|
mix.general.push(voice);
|
|
127
|
+
if (voice.sharing) {
|
|
128
|
+
const sharers = mix.sharers;
|
|
129
|
+
let i = sharers.length;
|
|
130
|
+
while (i > 0 && sharers[i - 1].id > voice.id)
|
|
131
|
+
i--;
|
|
132
|
+
sharers.splice(i, 0, voice);
|
|
133
|
+
}
|
|
131
134
|
return;
|
|
132
135
|
}
|
|
133
136
|
mix.naming++;
|
package/dist/channels.d.ts
CHANGED
|
@@ -14,14 +14,17 @@ export declare function lerpInto(channel: Channel<unknown>): LerpInto | undefine
|
|
|
14
14
|
/**
|
|
15
15
|
* What a lane needs of a stock numeric channel beyond the channel: its arithmetic by name, its axes.
|
|
16
16
|
* `'last'` has no rest: the last contribution to pass the band replaces, and a weight only gates it.
|
|
17
|
+
* `'own'` folds its axes as one value, through the channel's own `fold`.
|
|
17
18
|
*/
|
|
18
19
|
export interface Numeric {
|
|
19
|
-
op: 'sum' | 'mul' | 'max' | 'last';
|
|
20
|
+
op: 'sum' | 'mul' | 'max' | 'last' | 'own';
|
|
20
21
|
axes: number;
|
|
22
|
+
/** An angle's full turn: each value is taken the short way round before it folds. */
|
|
23
|
+
turn?: number;
|
|
21
24
|
}
|
|
22
25
|
/**
|
|
23
|
-
* The stock arithmetic of a channel `sum`, `mul`, `max` or `vec` made,
|
|
24
|
-
* so a custom channel that claims a stock `kind` is not trusted with a lane.
|
|
26
|
+
* The stock arithmetic of a channel `sum`, `mul`, `max` or `vec` made, or `vouch` was given, keyed
|
|
27
|
+
* by the object itself, so a custom channel that claims a stock `kind` is not trusted with a lane.
|
|
25
28
|
*/
|
|
26
29
|
export declare function numericOf(channel: Channel<unknown>): Numeric | undefined;
|
|
27
30
|
/** Vouches for a stock channel made outside this module, as `color` makes its own. */
|
|
@@ -30,7 +33,10 @@ export declare function vouch(channel: Channel<unknown>, numeric: Numeric): void
|
|
|
30
33
|
export declare function widen(channel: Channel<unknown>, axes: number): void;
|
|
31
34
|
/** How many numbers a channel holds a subject: its rest's length, a recorded width, or one. */
|
|
32
35
|
export declare function axesOf(channel: Channel<unknown>): number;
|
|
33
|
-
/**
|
|
36
|
+
/**
|
|
37
|
+
* `merge(acc, scale(v, w))` for a stock numeric channel, in exactly its arithmetic. Not for an
|
|
38
|
+
* `'own'` channel, and an angle's `v` is taken the short way round first.
|
|
39
|
+
*/
|
|
34
40
|
export declare function foldNumber(op: Numeric['op'], acc: number, v: number, w: number): number;
|
|
35
41
|
/**
|
|
36
42
|
* What a stock numeric channel takes.
|
|
@@ -69,15 +75,30 @@ export declare function max(opts?: NumberOptions): Channel<number>;
|
|
|
69
75
|
export declare function last<V>(opts?: {
|
|
70
76
|
lerp?: (a: V, b: V, u: number) => V;
|
|
71
77
|
}): Channel<V>;
|
|
78
|
+
/**
|
|
79
|
+
* `n` copies of `v` in an array the engine stores as doubles from the start, so a copy of it, and
|
|
80
|
+
* every fraction written into that copy, keeps one element kind: a pose's arrays then look alike to
|
|
81
|
+
* every fold, whichever path made them.
|
|
82
|
+
*/
|
|
83
|
+
export declare function doubles(n: number, v: number): number[];
|
|
84
|
+
type Tuple<N extends number, T extends number[] = []> = T['length'] extends N ? T : Tuple<N, [...T, number]>;
|
|
85
|
+
/**
|
|
86
|
+
* `N` numbers: a tuple where `N` is a literal, as `Vec<3>` is `[number, number, number]`, and
|
|
87
|
+
* `number[]` where it is only known to be a number.
|
|
88
|
+
*
|
|
89
|
+
* @category channel
|
|
90
|
+
*/
|
|
91
|
+
export type Vec<N extends number> = number extends N ? number[] : Tuple<N>;
|
|
72
92
|
/**
|
|
73
93
|
* Vec3 position, premultiplied light: one channel's arithmetic applied down an axis list.
|
|
74
94
|
*
|
|
75
95
|
* @category channel
|
|
76
96
|
*/
|
|
77
|
-
export declare function vec(n:
|
|
97
|
+
export declare function vec<N extends number>(n: N, of: Channel<number>): Channel<Vec<N>>;
|
|
78
98
|
/**
|
|
79
99
|
* A kit is plain data; this only fixes the type.
|
|
80
100
|
*
|
|
81
101
|
* @category channel
|
|
82
102
|
*/
|
|
83
103
|
export declare function kit<O>(channels: Kit<O>): Kit<O>;
|
|
104
|
+
export {};
|
package/dist/channels.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
+
import { shared } from './shared.js';
|
|
1
2
|
/** The stock numeric channels' `lerp`, which `vec` applies per axis and lanes call directly. */
|
|
2
3
|
export const lerpNumber = (a, b, u) => a + (b - a) * u;
|
|
3
4
|
const mix = lerpNumber;
|
|
4
|
-
const inPlace =
|
|
5
|
+
const inPlace = shared('lerp-into@1');
|
|
5
6
|
/**
|
|
6
7
|
* The in-place form of a stock array channel's `lerp`. Keyed by the channel rather than its `lerp`,
|
|
7
8
|
* since only the stock fold and merge are known to copy the array rather than keep it.
|
|
@@ -15,10 +16,10 @@ const times = (v, w) => v * w;
|
|
|
15
16
|
const product = (a, b) => a * b;
|
|
16
17
|
const toward = (v, w) => 1 + (v - 1) * w;
|
|
17
18
|
const larger = (a, b) => (a > b ? a : b);
|
|
18
|
-
const numerics =
|
|
19
|
+
const numerics = shared('numeric@1');
|
|
19
20
|
/**
|
|
20
|
-
* The stock arithmetic of a channel `sum`, `mul`, `max` or `vec` made,
|
|
21
|
-
* so a custom channel that claims a stock `kind` is not trusted with a lane.
|
|
21
|
+
* The stock arithmetic of a channel `sum`, `mul`, `max` or `vec` made, or `vouch` was given, keyed
|
|
22
|
+
* by the object itself, so a custom channel that claims a stock `kind` is not trusted with a lane.
|
|
22
23
|
*/
|
|
23
24
|
export function numericOf(channel) {
|
|
24
25
|
return numerics.get(channel);
|
|
@@ -27,7 +28,7 @@ export function numericOf(channel) {
|
|
|
27
28
|
export function vouch(channel, numeric) {
|
|
28
29
|
numerics.set(channel, numeric);
|
|
29
30
|
}
|
|
30
|
-
const widths =
|
|
31
|
+
const widths = shared('width@1');
|
|
31
32
|
/** Records how many numbers a rest-less array channel holds a subject, which its rest cannot say. */
|
|
32
33
|
export function widen(channel, axes) {
|
|
33
34
|
widths.set(channel, axes);
|
|
@@ -37,7 +38,10 @@ export function axesOf(channel) {
|
|
|
37
38
|
const rest = channel.rest;
|
|
38
39
|
return Array.isArray(rest) ? rest.length : (widths.get(channel) ?? 1);
|
|
39
40
|
}
|
|
40
|
-
/**
|
|
41
|
+
/**
|
|
42
|
+
* `merge(acc, scale(v, w))` for a stock numeric channel, in exactly its arithmetic. Not for an
|
|
43
|
+
* `'own'` channel, and an angle's `v` is taken the short way round first.
|
|
44
|
+
*/
|
|
41
45
|
export function foldNumber(op, acc, v, w) {
|
|
42
46
|
if (op === 'sum')
|
|
43
47
|
return add(acc, times(v, w));
|
|
@@ -120,7 +124,7 @@ export function last(opts) {
|
|
|
120
124
|
* every fraction written into that copy, keeps one element kind: a pose's arrays then look alike to
|
|
121
125
|
* every fold, whichever path made them.
|
|
122
126
|
*/
|
|
123
|
-
function doubles(n, v) {
|
|
127
|
+
export function doubles(n, v) {
|
|
124
128
|
if (n === 0)
|
|
125
129
|
return [];
|
|
126
130
|
const a = [0.5];
|
|
@@ -174,7 +178,7 @@ export function vec(n, of) {
|
|
|
174
178
|
};
|
|
175
179
|
inPlace.set(channel, lerpTo);
|
|
176
180
|
const inner = numerics.get(of);
|
|
177
|
-
if (inner !== undefined && inner.axes === 1)
|
|
181
|
+
if (inner !== undefined && inner.axes === 1 && inner.turn === undefined)
|
|
178
182
|
numerics.set(channel, { ...inner, axes: n });
|
|
179
183
|
return channel;
|
|
180
184
|
}
|
package/dist/clock.d.ts
CHANGED
|
@@ -24,9 +24,16 @@ export declare function envelope(fadeIn: number, out: {
|
|
|
24
24
|
} | null): number;
|
|
25
25
|
/**
|
|
26
26
|
* A subject's voice time `raw` once the voice's freezes apply: 0 before it starts if it freezes
|
|
27
|
-
* before, else NaN, where it shows nothing; `span` once its passes end if it freezes after.
|
|
27
|
+
* before, else NaN, where it shows nothing; `span` once its passes end if it freezes after. A `raw`
|
|
28
|
+
* of NaN is before its start: a pending voice at rate 0 whose pinned start a mix at rate 0 puts at
|
|
29
|
+
* infinity reads its clock as infinity times 0.
|
|
28
30
|
*/
|
|
29
31
|
export declare function frozenTime(raw: number, before: boolean, after: boolean, span: number): number;
|
|
32
|
+
/**
|
|
33
|
+
* `Object.is` for two numbers, which V8 answers by boxing both where it does not inline the call: 32
|
|
34
|
+
* bytes a subject in a fill's loop (bench/allocs.mjs, 2026-10-09).
|
|
35
|
+
*/
|
|
36
|
+
export declare function same(a: number, b: number): boolean;
|
|
30
37
|
/** A weight held to 0..1. */
|
|
31
38
|
export declare function clampWeight(raw: number): number;
|
|
32
39
|
/**
|
package/dist/clock.js
CHANGED
|
@@ -28,7 +28,9 @@ export function passAt(elapsed, duration, passes) {
|
|
|
28
28
|
export function envelope(fadeIn, out, ease, reduced, now, since, back = null) {
|
|
29
29
|
let w = 1;
|
|
30
30
|
if (fadeIn > 0 && !reduced) {
|
|
31
|
-
|
|
31
|
+
// A fade in stops climbing where a fade out begins, or the two ramps multiply into a rise.
|
|
32
|
+
const end = out && !out.rest && out.at < now ? out.at : now;
|
|
33
|
+
const u = (end - since) / fadeIn;
|
|
32
34
|
if (u < 1)
|
|
33
35
|
w *= ease ? ease(Math.max(0, u)) : Math.max(0, u);
|
|
34
36
|
}
|
|
@@ -48,13 +50,22 @@ export function envelope(fadeIn, out, ease, reduced, now, since, back = null) {
|
|
|
48
50
|
}
|
|
49
51
|
/**
|
|
50
52
|
* A subject's voice time `raw` once the voice's freezes apply: 0 before it starts if it freezes
|
|
51
|
-
* before, else NaN, where it shows nothing; `span` once its passes end if it freezes after.
|
|
53
|
+
* before, else NaN, where it shows nothing; `span` once its passes end if it freezes after. A `raw`
|
|
54
|
+
* of NaN is before its start: a pending voice at rate 0 whose pinned start a mix at rate 0 puts at
|
|
55
|
+
* infinity reads its clock as infinity times 0.
|
|
52
56
|
*/
|
|
53
57
|
export function frozenTime(raw, before, after, span) {
|
|
54
|
-
if (raw
|
|
58
|
+
if (!(raw >= 0))
|
|
55
59
|
return before ? 0 : Number.NaN;
|
|
56
60
|
return after && raw > span ? span : raw;
|
|
57
61
|
}
|
|
62
|
+
/**
|
|
63
|
+
* `Object.is` for two numbers, which V8 answers by boxing both where it does not inline the call: 32
|
|
64
|
+
* bytes a subject in a fill's loop (bench/allocs.mjs, 2026-10-09).
|
|
65
|
+
*/
|
|
66
|
+
export function same(a, b) {
|
|
67
|
+
return a === b ? a !== 0 || 1 / a === 1 / b : Number.isNaN(a) && Number.isNaN(b);
|
|
68
|
+
}
|
|
58
69
|
/** A weight held to 0..1. */
|
|
59
70
|
export function clampWeight(raw) {
|
|
60
71
|
return raw < 0 ? 0 : raw > 1 ? 1 : raw;
|
package/dist/clone.d.ts
CHANGED
|
@@ -4,4 +4,7 @@
|
|
|
4
4
|
* anything it would refuse, goes to it. A read back copies a record per voice and subject.
|
|
5
5
|
*/
|
|
6
6
|
export declare function clone<T>(v: T): T;
|
|
7
|
+
/** A copy a fold may write into: arrays, typed arrays and plain objects copied, the rest as is. */
|
|
7
8
|
export declare function copy(v: unknown): unknown;
|
|
9
|
+
/** Whether `copy` can copy `v` faithfully, when it is an object. */
|
|
10
|
+
export declare function copyable(v: object): boolean;
|
package/dist/clone.js
CHANGED
|
@@ -52,6 +52,21 @@ function copied(v, depth) {
|
|
|
52
52
|
}
|
|
53
53
|
return out;
|
|
54
54
|
}
|
|
55
|
+
/** A copy a fold may write into: arrays, typed arrays and plain objects copied, the rest as is. */
|
|
55
56
|
export function copy(v) {
|
|
56
|
-
|
|
57
|
+
if (typeof v !== 'object' || v === null)
|
|
58
|
+
return v;
|
|
59
|
+
if (Array.isArray(v))
|
|
60
|
+
return [...v];
|
|
61
|
+
if (ArrayBuffer.isView(v))
|
|
62
|
+
return v.slice();
|
|
63
|
+
return plainObject(v) ? clone(v) : v;
|
|
64
|
+
}
|
|
65
|
+
/** Whether `copy` can copy `v` faithfully, when it is an object. */
|
|
66
|
+
export function copyable(v) {
|
|
67
|
+
return Array.isArray(v) || ArrayBuffer.isView(v) || plainObject(v);
|
|
68
|
+
}
|
|
69
|
+
function plainObject(v) {
|
|
70
|
+
const proto = Object.getPrototypeOf(v);
|
|
71
|
+
return proto === Object.prototype || proto === null;
|
|
57
72
|
}
|
package/dist/closed.d.ts
CHANGED
|
@@ -6,7 +6,7 @@ export declare const OVER = 2;
|
|
|
6
6
|
export declare const COAST = 3;
|
|
7
7
|
export declare const EASED = 4;
|
|
8
8
|
/**
|
|
9
|
-
* A stretch
|
|
9
|
+
* A stretch at voice time `t`, released at `at` from `x0` moving at `v0` toward `to` (each read
|
|
10
10
|
* from its offset), into `xo` and `vo`: the one copy of the closed forms, which `law` (a patch's
|
|
11
11
|
* `[form, settle, k1, k2, k3]`, the head of a patch's buffer or a copy of it) picks between. A tween reads `ease` and
|
|
12
12
|
* `ms`, the stretch's length, dividing in milliseconds so a boundary lands exactly; every other
|