@msb235/blits 0.3.0 → 0.5.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 +314 -0
- package/README.md +3 -2
- package/dist/bare.d.ts +29 -0
- package/dist/bare.js +105 -0
- package/dist/blend.d.ts +34 -0
- package/dist/blend.js +102 -0
- package/dist/book.d.ts +53 -0
- package/dist/book.js +218 -0
- package/dist/chain.d.ts +22 -0
- package/dist/chain.js +150 -0
- package/dist/channels.d.ts +2 -0
- package/dist/channels.js +3 -1
- package/dist/clock.d.ts +50 -3
- package/dist/clock.js +102 -9
- package/dist/clone.d.ts +7 -0
- package/dist/clone.js +57 -0
- package/dist/columns.d.ts +29 -0
- package/dist/columns.js +183 -0
- package/dist/crowd.d.ts +128 -0
- package/dist/crowd.js +354 -0
- package/dist/cue.d.ts +3 -0
- package/dist/cue.js +115 -0
- package/dist/due.d.ts +12 -0
- package/dist/due.js +93 -0
- package/dist/fade.d.ts +19 -0
- package/dist/fade.js +133 -0
- package/dist/fill.d.ts +42 -0
- package/dist/fill.js +381 -0
- package/dist/fold.d.ts +51 -0
- package/dist/fold.js +475 -0
- package/dist/gather.d.ts +24 -0
- package/dist/gather.js +186 -0
- package/dist/handle.d.ts +31 -0
- package/dist/handle.js +56 -0
- package/dist/held.d.ts +14 -0
- package/dist/held.js +117 -0
- package/dist/history.d.ts +22 -0
- package/dist/history.js +149 -0
- package/dist/hosts.d.ts +16 -0
- package/dist/hosts.js +178 -0
- package/dist/index.d.ts +1 -1
- package/dist/keyrows.d.ts +36 -0
- package/dist/keyrows.js +138 -0
- package/dist/lane.d.ts +210 -0
- package/dist/lane.js +187 -0
- package/dist/lanes.d.ts +143 -122
- package/dist/lanes.js +236 -742
- package/dist/locus.d.ts +40 -0
- package/dist/locus.js +89 -0
- package/dist/marks.d.ts +12 -0
- package/dist/marks.js +80 -0
- package/dist/meet.d.ts +16 -0
- package/dist/meet.js +113 -0
- package/dist/mixer.d.ts +244 -169
- package/dist/mixer.js +321 -1793
- package/dist/motion.d.ts +104 -19
- package/dist/motion.js +448 -180
- package/dist/move.d.ts +9 -0
- package/dist/move.js +190 -0
- package/dist/named.d.ts +12 -0
- package/dist/named.js +39 -0
- package/dist/numbers.d.ts +18 -6
- package/dist/numbers.js +59 -31
- package/dist/owed.d.ts +24 -0
- package/dist/owed.js +51 -0
- package/dist/owner.d.ts +43 -0
- package/dist/owner.js +117 -0
- package/dist/pace.d.ts +40 -0
- package/dist/pace.js +113 -0
- package/dist/patch.d.ts +51 -10
- package/dist/patch.js +140 -45
- package/dist/place.d.ts +20 -0
- package/dist/place.js +196 -0
- package/dist/project.d.ts +4 -0
- package/dist/project.js +233 -0
- package/dist/pull.d.ts +5 -0
- package/dist/pull.js +175 -0
- package/dist/qualify.d.ts +31 -0
- package/dist/qualify.js +345 -0
- package/dist/relink.d.ts +25 -0
- package/dist/relink.js +59 -0
- package/dist/rows.d.ts +33 -0
- package/dist/rows.js +282 -0
- package/dist/sample.d.ts +21 -0
- package/dist/sample.js +232 -0
- package/dist/signals.d.ts +12 -9
- package/dist/signals.js +8 -5
- package/dist/store.d.ts +10 -3
- package/dist/store.js +44 -9
- package/dist/strays.d.ts +21 -0
- package/dist/strays.js +84 -0
- package/dist/tweened.d.ts +11 -0
- package/dist/tweened.js +48 -0
- package/dist/types.d.ts +261 -42
- package/dist/unreached.d.ts +5 -0
- package/dist/unreached.js +24 -0
- package/dist/voice.d.ts +240 -0
- package/dist/voice.js +322 -0
- package/dist/weigh.d.ts +26 -0
- package/dist/weigh.js +89 -0
- package/docs/schema.html +151 -46
- package/package.json +5 -2
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,320 @@ 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.5.0
|
|
8
|
+
|
|
9
|
+
### Breaking
|
|
10
|
+
|
|
11
|
+
- A patch is not asked for a subject its voice gives no weight: `at` is skipped at weight 0, unless
|
|
12
|
+
the voice is fading to rest, while `step` still runs. A blend over 8 stops now runs the 2 around
|
|
13
|
+
its signal per subject rather than all 8. A patch that counted its calls or sent events from `at`
|
|
14
|
+
at weight 0 no longer sees those frames.
|
|
15
|
+
- A patch's length is `duration`, not `period`: a pass is one run through a patch whether or not
|
|
16
|
+
it loops, and only a looping voice has a period. `period` is still set on every patch `patch`,
|
|
17
|
+
`keys`, `spring`, `glide` and `tween` build, and a patch that sets only `period` still plays,
|
|
18
|
+
but it is deprecated and a later release removes it.
|
|
19
|
+
|
|
20
|
+
- A spring, glide or tween's `to`, `push`, `read` and `at` are methods of its class, so call them on
|
|
21
|
+
the patch, as `glide.to(id, 1)`: one taken off it, as `const { to } = glide`, has no patch to act
|
|
22
|
+
on. Its `motion` is a getter on the class rather than its own property.
|
|
23
|
+
- `mix.blend` reads its signal once per subject per frame, with the first member's setting, and
|
|
24
|
+
every member takes its share of that one reading; it read it once per member. A host counting
|
|
25
|
+
calls sees a third as many from a three-member blend, and a signal reading `setting.elapsed` or
|
|
26
|
+
`setting.pass` sees the first member's even where a member was sought or retimed on its own. A
|
|
27
|
+
signal keeping state, as `slew`, `lag` and `gate` do, keeps one state per subject for the whole
|
|
28
|
+
blend, which carries on when the member that made it leaves; each member kept its own, and those
|
|
29
|
+
could part ways where members left the lanes in different frames.
|
|
30
|
+
- `keys` copies each stop's array when it is made, so editing an array after passing it to `keys`
|
|
31
|
+
changes nothing; it showed on the general path and on a lane, and a crowd row now keeps its own
|
|
32
|
+
copy of the numbers. Make a new patch to change its stops.
|
|
33
|
+
|
|
34
|
+
- A spring, glide or tween refuses a value of the wrong kind for its channel: an array on a channel
|
|
35
|
+
holding a number, or a number on one holding an array. A value every subject shares is refused at
|
|
36
|
+
`cue`, and one given per subject on that subject's first sample. A one-element array on a number
|
|
37
|
+
channel used to play as its number, through a fold about 5× slower (1.2 ms a frame at 10k subjects
|
|
38
|
+
against 0.24, on teitou); a number on an array channel played as the channel's rest.
|
|
39
|
+
|
|
40
|
+
- `atRest` after a probe of the subject in the same frame answers for the pose that probe gave,
|
|
41
|
+
rather than folding the subject again: a weight reading host input that moved between the two no
|
|
42
|
+
longer changes the answer. With lanes off, a probe then `atRest` of 10k subjects under two voices
|
|
43
|
+
(`probed-`) takes 2.64–2.71 ms a frame against 3.80–3.87 (teitou, three alternated runs); a mix
|
|
44
|
+
that never asks `atRest` reads as before.
|
|
45
|
+
|
|
46
|
+
### Added
|
|
47
|
+
|
|
48
|
+
- A mix's host has a type: `mix<I, O, H>` types its `host` option, and every patch, signal and
|
|
49
|
+
setting it plays reads `setting.host` as `H`, without a cast. `Setting`, `Patch`, `PatchOptions`,
|
|
50
|
+
`Signal`, `VoiceSpec`, `MixOptions` and `Mix` take `H` as their last type parameter, `unknown` by
|
|
51
|
+
default, and the stock signals carry it through.
|
|
52
|
+
- `slew` and `lag` take `from`: where a subject starts the first time they see it, in place of on
|
|
53
|
+
their input, so a follower can climb from rest.
|
|
54
|
+
- `mix.voices(tag?)`: a handle on every voice still in the mix, pending, live, held or fading, in
|
|
55
|
+
cue order, or only those whose `tags` carry `tag`. A host no longer has to keep every handle `cue`
|
|
56
|
+
returned to know what is playing. Each handle listed is the one `cue` returned for that voice, so
|
|
57
|
+
it compares `===` with it.
|
|
58
|
+
- A rate for the whole mix, `mix.rate` and `mix.ramp(rate, over)`, shaped like a handle's and
|
|
59
|
+
multiplied into every voice's; 0 pauses the mix. It runs mix time, so everything a voice owns
|
|
60
|
+
slows with it: its clock, its fades, `stagger`, an anchor's `by`, `stepMs` and every `dt`. A
|
|
61
|
+
timestamp the host gives or reads stays on the host's clock: a `start`, an anchor given as a
|
|
62
|
+
number, an announced mark, `marks` and `project`. A paused mix with nothing left to land is
|
|
63
|
+
`inert`, and `history` keeps every rate change, so `project` reads back through them as it does
|
|
64
|
+
through a handle's. A mix whose rate is never set reads the same bits as before, at the same
|
|
65
|
+
cost: every row timed (`fn`, `keys`, `named`, `spring`, `tweens^`, `churn^`, `ahead`, `back`)
|
|
66
|
+
overlaps `9015b0b`'s range over three to five alternated runs on studio.
|
|
67
|
+
- Booking ahead against an outside clock, for a host that schedules on one: sound on an
|
|
68
|
+
`AudioContext`, MIDI sent with a timestamp, a video loaded before the voice that shows it.
|
|
69
|
+
`mix.book({ clock, ahead, late, take, tag?, score? })` hands `take` every mark `marks` lists and
|
|
70
|
+
every hit, `ahead` ms before it comes, with its time on that clock. A hit is a new `VoiceSpec`
|
|
71
|
+
field, `hits: [{ at, event }]`: events at times on the voice's clock, once per pass, for the
|
|
72
|
+
voice rather than per subject. A booking whose time moves by more than 1 ms or that goes away (a
|
|
73
|
+
seek, a rate on the voice or the mix, a fade, a `rebase`, an anchor's target moving, the voice
|
|
74
|
+
leaving) has the `stop` that `take` returned called and is booked again; an item first seen past
|
|
75
|
+
is taken at once with `lateBy`, up to `late`. `book` returns a `stop()`. `project` books nothing.
|
|
76
|
+
A mix with no booker costs what it did: every row timed (`fn`, `keys`, `signal`, `blend`,
|
|
77
|
+
`named`, `spring`, `tweens`, `churn`, `weasel`, `sparse`) overlaps `c6de97e`'s range over three
|
|
78
|
+
alternated runs on msb-uai, each row in a process of its own.
|
|
79
|
+
- Owners, for placing, timing and fading voices as one: `mix.owns(spec)` cues a voice with no
|
|
80
|
+
patch, and a voice cued with `owner: handle` plays under it. A child's `start` and anchors count
|
|
81
|
+
on the owner's clock, in ms from its start, and a bare name in its anchors means a sibling. The
|
|
82
|
+
owner's `rate`, `ramp` and `seek` move every child's clock with its own, leaving their state where
|
|
83
|
+
it is; its `weight`, a number or a signal, and its `fade` multiply into each child's weight per
|
|
84
|
+
subject; a child with no `hold` takes the owner's. The owner is `played` once every child has
|
|
85
|
+
finished its passes and leaves with its last child, and its fade ending takes them all. Owners
|
|
86
|
+
nest, `handle.owner` names a child's owner, and `marks`, hits, `history` and `project` all read
|
|
87
|
+
through the chain of owner clocks. An owner refuses `loop`. A mix with no owner costs what it
|
|
88
|
+
did: every row timed (`fn`, `keys`, `signal`, `blend`, `named`, `spring`, `tweens^`, `churn^`,
|
|
89
|
+
`weasel^`, `swap`, `sparse`) overlaps `995cd97`'s range over four alternated runs on teitou, each
|
|
90
|
+
row in a process of its own.
|
|
91
|
+
|
|
92
|
+
### Fixed
|
|
93
|
+
|
|
94
|
+
- `pull` queues the subjects it reads from the lanes as runs, a first subject and a count, rather
|
|
95
|
+
than one by one, and writes each run into its arrays as a block. Its loop over the subjects no
|
|
96
|
+
longer shares a function with its tail: compiled while the first long list was read, `pull`
|
|
97
|
+
deoptimized at its tail on every frame a voice came or went. At 10k subjects, a frame takes
|
|
98
|
+
0.167–0.180 ms against 0.228–0.235 with a tween voice per subject (`tweens^`), 0.218–0.227
|
|
99
|
+
against 0.269–0.278 over one voice (`weasel^`), 0.218–0.234 against 0.291–0.307 with one voice
|
|
100
|
+
replaced each frame (`churn^`), and 0.308–0.314 against 0.330–0.344 with one subject replaced
|
|
101
|
+
each frame (`turnover^`) (teitou, eight alternated runs).
|
|
102
|
+
|
|
103
|
+
- A fill samples a tween on a lane or in a crowd and folds it in one loop, calling its closed form
|
|
104
|
+
and the channel's fold directly, and keeps no copy of the sample: one is worked out again from
|
|
105
|
+
the tween's stretch when a probe later in the frame needs it. The steady frame no longer
|
|
106
|
+
allocates 16 B a subject (8 B over one voice). At 10k subjects read by `pull`, a frame takes
|
|
107
|
+
0.225–0.234 ms against 0.321–0.327 with a tween voice per subject (`tweens^`), 0.269–0.280
|
|
108
|
+
against 0.350–0.357 with one voice over all of them (`weasel^`), 0.295–0.308 against
|
|
109
|
+
0.373–0.404 with one voice replaced each frame (`churn^`), and 0.324–0.337 against 0.397–0.468
|
|
110
|
+
with one subject replaced each frame (`turnover^`) (teitou, eight alternated runs).
|
|
111
|
+
|
|
112
|
+
- A voice retiring no longer has the mix read every voice it holds to take it out of the list: up
|
|
113
|
+
to 8 a frame are found by their id. At 10k voices with one leaving each frame (`turnover^`), a
|
|
114
|
+
frame takes 0.407–0.416 ms against 0.427–0.436 (teitou, three alternated runs).
|
|
115
|
+
|
|
116
|
+
- A subject meeting a voice over every subject on a lane does less to meet it. A lane finds a
|
|
117
|
+
subject's place by its number in an array rather than a Map, the voice's record comes from the
|
|
118
|
+
chain the probe just linked rather than a second lookup, the voices naming the subject are looked
|
|
119
|
+
up only when one has started on it since, and the subjects that fold the voice on the general path
|
|
120
|
+
this frame share one list of it rather than a copy each in a Map. With 10k tween voices of one
|
|
121
|
+
subject each and a voice over all of them replaced every frame (`swap`), a frame takes
|
|
122
|
+
4.66–5.13 ms on teitou, from 6.22–6.43.
|
|
123
|
+
- A voice over every subject coming, starting or going relinks each subject's chain by that voice
|
|
124
|
+
alone, taking it off or putting it on at its place in voice order, where every chain was linked
|
|
125
|
+
afresh through every voice reaching the subject. The same `swap` frame takes 4.24–4.47 ms on
|
|
126
|
+
teitou, from 4.81–4.93; a voice per subject (`named`) is unchanged.
|
|
127
|
+
- A subject owing the same voices as the one before it in a frame takes that answer rather than
|
|
128
|
+
working out again whether it may fold them after the lanes, and a retired voice leaving its lane
|
|
129
|
+
writes no weights back to its records, which nothing reads once it is done. The same `swap`
|
|
130
|
+
frame takes 3.55–3.70 ms on teitou, from 3.77–4.09.
|
|
131
|
+
- A projection that retires a copy of a motion voice leaves the live patch asking the live voice
|
|
132
|
+
for its time. The copy carried the voice's hooks, and retiring it cut the patch off from the
|
|
133
|
+
mix: an untimed `to` or `push` after such a projection took its time from the subject's next read
|
|
134
|
+
rather than the latest frame. A motion patch now asks its mix by voice id, where each voice made
|
|
135
|
+
a weak reference and two closures of its own: a one-subject tween voice holds about 250 B less
|
|
136
|
+
(`bench/heapwho.mjs 5000 tweens` 3747 B from 4004, `weasel` 3448 from 3704).
|
|
137
|
+
- A spring, glide or tween playing one object subject finds its number for it through the weak
|
|
138
|
+
reference it numbered it by, rather than a WeakMap of its own: a one-subject tween voice over an
|
|
139
|
+
object holds 3610 B, from 3747 (`bench/heapwho.mjs 5000 tweens`). Starting them takes no less
|
|
140
|
+
time.
|
|
141
|
+
- A spring, glide or tween makes its buffer when it numbers its first subject, rather than one for
|
|
142
|
+
its law when it is made and another then, and works out a still start without asking `to` again:
|
|
143
|
+
making and cueing 10k one-subject tween voices takes 2.5–2.8 ms on teitou, from 5.1–6.8, and
|
|
144
|
+
their first frame 15.7–16.9 ms, from 19.0–21.1 (`bench/start.mjs`).
|
|
145
|
+
- A voice over every subject keeps the subjects its `target` turned away as a bit by the number
|
|
146
|
+
lanes give each, where it kept a map entry for each: memory no longer grows with voices times
|
|
147
|
+
subjects. 1000 voices each picking one of 1000 subjects by `target` hold 4.6 KB a voice after
|
|
148
|
+
their first frame, from 37.1, and over 3000 subjects 4.2 KB, from 135; that first frame takes
|
|
149
|
+
35–38 ms on teitou, from 72–79, and later frames are unchanged (`bench/memory.mjs`, row `own`).
|
|
150
|
+
With `lanes: false` the answers are kept in a map as before.
|
|
151
|
+
- A crowd's `keys` rows read their stops from one array the crowd keeps, copied as each row
|
|
152
|
+
joins, rather than through each row's voice, tracks and stop arrays: 10k `keys` voices of one
|
|
153
|
+
subject each read by `pull` take 0.34–0.36 ms a frame on teitou, from 0.66–1.02, and by probe
|
|
154
|
+
0.81–0.83, from 1.00–1.37. A row whose stops are not plain numbers on its channel's axes reads
|
|
155
|
+
them as before.
|
|
156
|
+
- A `keys` patch keeps its stops' phases in a plain array made with it, and those of the stops after
|
|
157
|
+
phase 0 only once a `from: 'current'` voice first reads them, where it made a typed array of
|
|
158
|
+
each as it was built: making and cueing 10k one-subject `keys` voices takes 10.9–11.9 ms on
|
|
159
|
+
teitou, from 13.9–14.8, with their first frame and later frames unchanged.
|
|
160
|
+
- A projection copies each voice into an object V8 keeps in fast mode, where the copy was a
|
|
161
|
+
dictionary that every read of it looked up by name: a projection made and probed every frame
|
|
162
|
+
takes 0.57–0.58 ms ahead and 0.74–0.87 back on teitou, from 0.81–0.84 and 0.95–1.09 (`ahead` and
|
|
163
|
+
`back`, 1000 subjects under three voices, four alternated runs).
|
|
164
|
+
- A pose never holds a patch's own array. Where a channel's `merge` returned its second argument,
|
|
165
|
+
as `last()` does, the mix put that array into the pose as it was: with two voices on a keyed
|
|
166
|
+
array channel, a host editing its pose edited the stop, and every later frame read the edit. The
|
|
167
|
+
mix now copies such a value, into the array the pose already holds where one fits.
|
|
168
|
+
- A voice holds less while it plays. A store keeps its first id inline rather than in a Map, a
|
|
169
|
+
voice's small arrays are sized to what they hold, `setting.keep` is made the first time a patch
|
|
170
|
+
reads it, and a spring, glide or tween keeps its methods on its class rather than in closures of
|
|
171
|
+
its own: a one-subject tween voice keyed by a number, cued and retargeted as weasel's animator
|
|
172
|
+
does, holds 3.7 KB, from 5.3
|
|
173
|
+
(`bench/heapwho.mjs 5000 weasel`, which breaks a voice's bytes down by the field that holds them).
|
|
174
|
+
- A crowd voice that starts or begins fading in the frame the lanes are qualified again plays on
|
|
175
|
+
them. A qualify that kept the crowds as they stood, as when a voice over every subject leaves,
|
|
176
|
+
dropped the news, and the row stayed silent: a voice starting at 50 ms on one subject read nothing
|
|
177
|
+
on the lanes while one over every subject faded out then.
|
|
178
|
+
- A crowd voice that starts or begins fading in the same frame as a voice whose change needs the
|
|
179
|
+
lanes qualified again plays on them. Taking the frame's changes one at a time stopped at the
|
|
180
|
+
first that needs a qualify, such as a locus member off the lanes starting, and the qualify that
|
|
181
|
+
followed never heard of the rest: a voice on one subject starting at 50 ms beside a glide in a
|
|
182
|
+
locus starting then read nothing on the lanes for good.
|
|
183
|
+
- A paused mix asks a weight signal again at each sync, on lanes and for a blend, as the general
|
|
184
|
+
path already asked a plain voice's. A sync that moved host time while the mix clock stood still
|
|
185
|
+
was not a new frame: a laned voice kept the weight its lanes were last filled with, and a blend
|
|
186
|
+
the reading it took before, so a signal the host set while paused, such as a `level`, changed
|
|
187
|
+
nothing until the mix ran again. A paused mix on lanes now fills them each frame, as a running
|
|
188
|
+
one does.
|
|
189
|
+
- A subject numbered by a mix, or by a motion patch, is let go once collected even when it was the
|
|
190
|
+
first object numbered and was collected before a second came. Its number, its lane positions and
|
|
191
|
+
its motion state were held for good.
|
|
192
|
+
- A crowd copies its rows' motion stretches as a fill begins, rather than in the loop that reads
|
|
193
|
+
every row. Once voices had faded, V8 spent that loop's inlining on the copy and stopped inlining
|
|
194
|
+
each row's ease, and every mix in the process stayed slower afterwards. With 10k tween voices of
|
|
195
|
+
one subject each read by `pull`, a frame takes 0.24 ms on teitou after every voice has been
|
|
196
|
+
replaced once, from 0.31, and 0.40 ms with one replaced each frame (`churn^`), from 0.44.
|
|
197
|
+
- A crowd compacts its rows once a quarter of them are empty, rather than half: one replacing a
|
|
198
|
+
voice a frame carried as many empty rows as live ones, all walked every fill. The same 10k voices
|
|
199
|
+
replaced once each take 0.31 ms a frame on teitou, from 0.33 (`bench/scatter.mjs replaced`).
|
|
200
|
+
- A voice weighted by a signal on a lane is weighed under the fade its lane already worked out,
|
|
201
|
+
where the general path's weighing worked the fade out again for every subject; a laned `fn` call
|
|
202
|
+
in a mix keeping no history no longer asks the mix for its horizon. On teitou three signal voices
|
|
203
|
+
over 10k subjects take about 0.91 of the time they did, three plain `fn` voices 0.91 and a blend
|
|
204
|
+
of three 0.95.
|
|
205
|
+
- A voice's handle is one object of a class, where each `cue` made a dozen closures: cueing 10k
|
|
206
|
+
tween voices of one subject each takes 7.7–9.7 ms on teitou, from 13.5–15.3, and 10k `fn` voices
|
|
207
|
+
2.8–3.1 ms, from 4.8–7.1.
|
|
208
|
+
- A voice naming its subjects keeps them in a list, with a set only past eight, and a motion patch
|
|
209
|
+
numbering one subject makes no `FinalizationRegistry` until it numbers a second: a tween voice of
|
|
210
|
+
one subject holds about 5.3 KB, from 5.6 (`bench/heapby.mjs`).
|
|
211
|
+
- A voice over every subject coming, starting or going among a crowd no longer qualifies every
|
|
212
|
+
voice again, and a probe meeting it folds it onto the lanes' values where it comes after every
|
|
213
|
+
other laned voice, rather than reading the general path for the frame: with 10k tween voices of
|
|
214
|
+
one subject each and a voice over all of them replaced every frame (`swap`), a frame takes
|
|
215
|
+
6.0–6.1 ms on teitou, from 12.2–12.5. A qualify that would rebuild the crowds and laned channels
|
|
216
|
+
unchanged keeps them as they stand.
|
|
217
|
+
- `atRest` answers from the lanes' values for a subject every voice of which is laned, rather than
|
|
218
|
+
folding a pose to compare: asked of 10k subjects under two voices, a frame takes 0.94–1.07 ms on
|
|
219
|
+
teitou, from 1.41–1.43 (`rest`).
|
|
220
|
+
- A subject fading out of a voice with `handle.fade({ subject })` gets the same weight on a lane
|
|
221
|
+
as on the general path. A lane held the voice's weight to 1 before multiplying in the subject's
|
|
222
|
+
ramp, where the general path holds the product: a voice at weight 1.6 halfway out of a subject
|
|
223
|
+
read 0.5 of its delta on a lane and 0.8 elsewhere. Below weight 1 the two differed only in the
|
|
224
|
+
last bit.
|
|
225
|
+
- A voice weighted by a signal runs on lanes, weighed by the general path's own arithmetic, where
|
|
226
|
+
it always took the general path; a signal that keeps state through `setting.keep` takes its voice
|
|
227
|
+
off lanes from the first call, as a patch that keeps state does. Three such voices over 10k
|
|
228
|
+
subjects take 0.79 of the time they did on teitou. A signal holding state of its own outside
|
|
229
|
+
`setting.keep` is now called for every subject the mix has met, probed or not, as a stateless
|
|
230
|
+
patch on a lane already is.
|
|
231
|
+
- `atRest` reads a voice's weight as the probe it repeats did, holds and stagger delay included.
|
|
232
|
+
It handed a weight signal the voice's time before its holds and the subject's delay, so
|
|
233
|
+
`weightOf` changed after it, and a channel with no rest could switch on or off and stay so.
|
|
234
|
+
- A locus whose members are all `keys` or stateless `fn` voices runs on lanes, so `mix.blend` over
|
|
235
|
+
such patches does: each subject's members are gathered as the general path's locus fold gathers
|
|
236
|
+
them and folded in at its first member's place. A motion member keeps the whole locus on the
|
|
237
|
+
general path. On teitou three voices in a locus over 10k subjects take 0.72 of the time they
|
|
238
|
+
did, and a blend of three weighted by a signal 0.84.
|
|
239
|
+
- A voice in a locus folds only the channels its patch writes, as a voice outside one does; a
|
|
240
|
+
delta's other keys were folded too.
|
|
241
|
+
- A `keys` or stateless `fn` voice naming one subject and writing several channels joins a crowd,
|
|
242
|
+
as one writing a single channel does, where it took the general path. On teitou 10k such voices
|
|
243
|
+
read by probe take 0.79 of the time they did and 1k take 0.65; 100 take about 1.1.
|
|
244
|
+
- A crowd's `keys` rows fold their stops straight into the lanes, through the same segment search
|
|
245
|
+
`readKeys` makes, rather than through a delta per row: 10k `keys` voices of one subject each
|
|
246
|
+
take 0.74 of the time they did read by `pull` and 0.85 by probe on teitou.
|
|
247
|
+
- A crowd more than half empty rows slides its rows down in place, where the mix qualified every
|
|
248
|
+
voice again: with one of 10k tween voices replaced each frame, the worst of 12,000 frames fell
|
|
249
|
+
from about 9 ms to 3 on teitou.
|
|
250
|
+
- A fold with a locus in play allocates nothing per subject: 10k subjects under three voices in a
|
|
251
|
+
locus take 0.83 of the time they did on teitou, with a third of the collections.
|
|
252
|
+
- A read back copies records holding plain data directly, not through `structuredClone`: a
|
|
253
|
+
projection made and probed every frame over 1k subjects and three voices takes 0.61 of the time
|
|
254
|
+
ahead and 0.81 back.
|
|
255
|
+
- A spring works out its time terms once for the subjects released together: one spring voice over
|
|
256
|
+
10k subjects read by `pull` takes about 0.89 of the time, and a spring voice per subject 0.86.
|
|
257
|
+
- A voice shares one record among the subjects it does not reach, where it kept a full one for
|
|
258
|
+
each subject it was asked about. 1000 voices each picking one subject by `target` held 269 MB
|
|
259
|
+
after their first frame and now 37 MB; that first frame takes 77 ms on teitou, from 123, and
|
|
260
|
+
each later frame 0.86 of the time.
|
|
261
|
+
- A voice and its records make their maps, arrays and promises on first use, and a subject's
|
|
262
|
+
number needs no token object. A voice of one subject holds 2.5 KB after its first frame as a
|
|
263
|
+
`fn` (3.4 KB before) and 5.2 KB as a tween (7.3 KB). On teitou cueing 10k tween voices of one
|
|
264
|
+
subject each takes about 18 ms, from 26, and reading their first frame about 31, from 39.
|
|
265
|
+
`setting.keep` is now one function per voice that writes to the record being called for, so one
|
|
266
|
+
kept and called after its call, which `Setting` never allowed, no longer reaches the record it
|
|
267
|
+
came with.
|
|
268
|
+
|
|
269
|
+
## 0.4.0
|
|
270
|
+
|
|
271
|
+
### Breaking
|
|
272
|
+
|
|
273
|
+
- A tween's `MotionSpec` reads `ms: undefined` where its `ms` is a function of the subject.
|
|
274
|
+
- `Handle.state` can read `'held'`. Only a voice cued with `hold` reaches it, so nothing changes at
|
|
275
|
+
run time for one cued without, but a `switch` over every state no longer covers them all.
|
|
276
|
+
|
|
277
|
+
### Added
|
|
278
|
+
|
|
279
|
+
- `mix.inert` is true when another frame would change no pose, so a host's loop may sleep while
|
|
280
|
+
`live` is still true: every voice is done, held after its passes at a plain weight, or a motion
|
|
281
|
+
whose every subject has landed on its target. A retarget, a fade or a cue makes it false again.
|
|
282
|
+
- `handle.fade({ subject, over })` fades one subject out of a voice, which plays on for the rest.
|
|
283
|
+
Once the ramp ends the voice forgets the subject (its record, its lane position and a motion
|
|
284
|
+
patch's state for it) and no longer reaches it; a motion patch's `to` brings it back, met afresh.
|
|
285
|
+
`mix.drop(subject)` clears it too. It keeps the voice on its lane.
|
|
286
|
+
- `tween`'s `ms` may be a function of the subject, asked each time one of its stretches starts.
|
|
287
|
+
- `hold: 'before' | 'after' | 'both'` on a voice. Before, a subject shows the voice's first frame
|
|
288
|
+
while the voice is pending and while the subject waits out its `stagger`; after, a finite loop
|
|
289
|
+
shows its last frame once its passes are done and stays, `held`, until faded. `fade.in` counts
|
|
290
|
+
from the first frame shown. What WAAPI calls `fill`.
|
|
291
|
+
- `handle.played` resolves true when a finite loop's last pass ends, false if the voice leaves
|
|
292
|
+
first. It never rejects.
|
|
293
|
+
|
|
294
|
+
### Fixed
|
|
295
|
+
|
|
296
|
+
- A `keys` or stateless `fn` voice naming one subject and writing one channel fills from its
|
|
297
|
+
channel's crowd, as a motion voice does, where it took the general path. On teitou 10k `fn`
|
|
298
|
+
voices of one subject each read by `pull` take 0.7 of the time they did.
|
|
299
|
+
- A voice naming its subjects that is cued, starts or retires relinks only those subjects, and
|
|
300
|
+
joins or leaves its crowd in place, where it made every subject relink and the lanes qualify
|
|
301
|
+
every voice again: a frame stopping one of 10k such voices and cueing another fell from about
|
|
302
|
+
9 ms to 0.6 by `pull` on teitou. `mix.drop` looks only at the voices over every subject and
|
|
303
|
+
those naming the one dropped, where it walked every voice.
|
|
304
|
+
- `mix.drop` frees a subject from a voice faded off it with `handle.fade({ subject })`, and a mix
|
|
305
|
+
whose last ramp of that kind is dropped can read `inert` again; it also frees the subject from a
|
|
306
|
+
motion patch that another voice plays on after the voice naming the subject left.
|
|
307
|
+
- `pull` reads a list it read before in the same order from the lanes in one run, checking the
|
|
308
|
+
frame once rather than per subject: a tween voice over 10k subjects read by `pull` takes about
|
|
309
|
+
0.83 of the time it did on teitou.
|
|
310
|
+
- A lane gives a `vec(1)` channel an array of one, as the general path does, where it gave a
|
|
311
|
+
bare number; and a motion voice on one folds without making an array per subject per frame.
|
|
312
|
+
- A `tween` divides elapsed by its length in milliseconds, so a point a whole fraction of the way
|
|
313
|
+
in lands exactly: 150 ms into a 200 ms linear tween from 0 to 100 reads 75, not 74.99999999999999.
|
|
314
|
+
- `pull` over a list read again in the same order skips looking each subject up, and copies lane
|
|
315
|
+
values a column at a time; a motion voice's lane fills in one loop. One tween voice over 10k
|
|
316
|
+
subjects read by `pull` takes 0.59–0.66 of the time it did on teitou; a spring 0.68–0.74.
|
|
317
|
+
- Motion voices of one subject each fill from one crowd of flat rows per channel, and `sync` visits
|
|
318
|
+
only voices with something due: 10k tween voices of one subject each take 0.54–0.59 of the time
|
|
319
|
+
they did on teitou, 100k take 0.31–0.33.
|
|
320
|
+
|
|
7
321
|
## 0.3.0
|
|
8
322
|
|
|
9
323
|
### Breaking
|
package/README.md
CHANGED
|
@@ -76,9 +76,10 @@ so the engine keeps no dependencies; its README says how it works.
|
|
|
76
76
|
|
|
77
77
|
## Status
|
|
78
78
|
|
|
79
|
-
v0.
|
|
79
|
+
v0.5.0, with four consumers. magicsmoke runs every fault on it, wod its wheel, tricks and
|
|
80
|
+
transitions, weasel its animator's tweens, and
|
|
80
81
|
[klieg](https://github.com/orochi235/klieg) composes all three of its systems — letter motion,
|
|
81
|
-
part effects, and the environment a sign is lit by — on a mix each
|
|
82
|
+
part effects, and the environment a sign is lit by — on a mix each.
|
|
82
83
|
That port was an extraction rather than a rewrite, so it is also the evidence that the arithmetic
|
|
83
84
|
here is the arithmetic a working renderer already had: klieg's suite of 1974 cases and its
|
|
84
85
|
screenshot baselines came through it unchanged.
|
package/dist/bare.d.ts
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { type Crowd } from './crowd.js';
|
|
2
|
+
import { type Lane } from './lane.js';
|
|
3
|
+
import type { Lanes } from './lanes.js';
|
|
4
|
+
/**
|
|
5
|
+
* `Row.SAMPLED` of a crowd row sampled bare in every fill that ran its crowd since it was set, so
|
|
6
|
+
* its sample is the one `unbare` works out from `hot` at `Crowd.bareNow` rather than a copy.
|
|
7
|
+
*/
|
|
8
|
+
export declare const enum Sampled {
|
|
9
|
+
BARE = -2
|
|
10
|
+
}
|
|
11
|
+
/** A `Flag.FAST` row's voice time at `now`, before its delay. */
|
|
12
|
+
export declare function clock(hot: Float64Array, h: number, now: number): number;
|
|
13
|
+
/**
|
|
14
|
+
* A crowd's rows from `p` on that are tweens with nothing on the voice or the stretch for
|
|
15
|
+
* `runCrowd` to look at, sampled from `hot` and folded straight in, up to voice `id`; returns the
|
|
16
|
+
* first row it leaves to `runCrowd`. A row's sample is not kept: `unbare` works it out again when
|
|
17
|
+
* something asks for it.
|
|
18
|
+
*/
|
|
19
|
+
export declare function bareRows<I, O>(lanes: Lanes<I, O>, c: Crowd<I, O>, p: number, id: number): number;
|
|
20
|
+
/**
|
|
21
|
+
* Keeps the sample a `Sampled.BARE` row was last given, worked out from `hot` as `bareRows` did,
|
|
22
|
+
* before `hot` changes or something reads it; nothing for any other row.
|
|
23
|
+
*/
|
|
24
|
+
export declare function unbare<I, O>(c: Crowd<I, O>, p: number): void;
|
|
25
|
+
/**
|
|
26
|
+
* `unbare` for a lane position: its sample worked out again from its patch's stretch, which tells
|
|
27
|
+
* the lane through `stretchChanged` before it changes.
|
|
28
|
+
*/
|
|
29
|
+
export declare function unbareLane<I, O>(lane: Lane<I, O>, p: number): void;
|
package/dist/bare.js
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { foldNumber } from './channels.js';
|
|
2
|
+
import { bare } from './crowd.js';
|
|
3
|
+
import { between, left, progress } from './tweened.js';
|
|
4
|
+
/** A `Flag.FAST` row's voice time at `now`, before its delay. */
|
|
5
|
+
export function clock(hot, h, now) {
|
|
6
|
+
return (hot[h + 2 /* Hot.ELAPSED */] +
|
|
7
|
+
(now - hot[h + 1 /* Hot.NOW */]) * hot[h + 3 /* Hot.RATE */]);
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* A crowd's rows from `p` on that are tweens with nothing on the voice or the stretch for
|
|
11
|
+
* `runCrowd` to look at, sampled from `hot` and folded straight in, up to voice `id`; returns the
|
|
12
|
+
* first row it leaves to `runCrowd`. A row's sample is not kept: `unbare` works it out again when
|
|
13
|
+
* something asks for it.
|
|
14
|
+
*/
|
|
15
|
+
export function bareRows(lanes, c, p, id) {
|
|
16
|
+
const data = c.data;
|
|
17
|
+
const hot = c.hot;
|
|
18
|
+
const H = c.stride;
|
|
19
|
+
const per = lanes.per;
|
|
20
|
+
const list = c.list;
|
|
21
|
+
const ch = c.chans[0];
|
|
22
|
+
const n = c.axes;
|
|
23
|
+
const fills = lanes.fills;
|
|
24
|
+
const from = lanes.frameProbes;
|
|
25
|
+
const probed = lanes.probes !== from;
|
|
26
|
+
const now = lanes.now;
|
|
27
|
+
const op = ch.op;
|
|
28
|
+
for (; p < list.length; p++) {
|
|
29
|
+
const h = p * H;
|
|
30
|
+
if (hot[h + 7 /* Hot.ID */] >= id || !bare(hot[h + 0 /* Hot.FLAGS */]))
|
|
31
|
+
break;
|
|
32
|
+
const o = p * 8 /* Row.STRIDE */;
|
|
33
|
+
if (data[o + 4 /* Row.MSLOT */] < 0 || data[o + 7 /* Row.MET */] === 0)
|
|
34
|
+
break;
|
|
35
|
+
const slot = list[p];
|
|
36
|
+
const q = slot * 6 /* Per.SLOT */;
|
|
37
|
+
if (probed &&
|
|
38
|
+
(per[q + 3 /* Per.LANE_PROBE */] > from || per[q + 4 /* Per.GENERAL_PROBE */] > from))
|
|
39
|
+
break;
|
|
40
|
+
const elapsed = clock(hot, h, now) - data[o + 0 /* Row.DELAY */];
|
|
41
|
+
if (!(elapsed >= 0))
|
|
42
|
+
break;
|
|
43
|
+
if (per[q + 5 /* Per.LANE_FILL */] === fills - 1)
|
|
44
|
+
data[o + 3 /* Row.PROBED */] = data[o + 2 /* Row.WEIGHT */];
|
|
45
|
+
const w = hot[h + 4 /* Hot.WEIGHT */];
|
|
46
|
+
data[o + 2 /* Row.WEIGHT */] = w;
|
|
47
|
+
if (data[o + 5 /* Row.SAMPLED */] !== -2 /* Sampled.BARE */) {
|
|
48
|
+
data[o + 5 /* Row.SAMPLED */] = -2 /* Sampled.BARE */;
|
|
49
|
+
c.deltas[p] = null;
|
|
50
|
+
}
|
|
51
|
+
const share = left(c.eases[p], progress(elapsed, hot[h + 8 /* Hot.AT */], hot[h + 9 /* Hot.MS */]));
|
|
52
|
+
if (w <= 0)
|
|
53
|
+
continue;
|
|
54
|
+
const values = ch.values;
|
|
55
|
+
const base = slot * n;
|
|
56
|
+
const x = h + 10 /* Hot.X0 */;
|
|
57
|
+
const g = x + 2 * n;
|
|
58
|
+
for (let a = 0; a < n; a++)
|
|
59
|
+
values[base + a] = foldNumber(op, values[base + a], between(hot[x + a], hot[g + a], share), w);
|
|
60
|
+
}
|
|
61
|
+
return p;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Keeps the sample a `Sampled.BARE` row was last given, worked out from `hot` as `bareRows` did,
|
|
65
|
+
* before `hot` changes or something reads it; nothing for any other row.
|
|
66
|
+
*/
|
|
67
|
+
export function unbare(c, p) {
|
|
68
|
+
const o = p * 8 /* Row.STRIDE */;
|
|
69
|
+
const data = c.data;
|
|
70
|
+
if (data[o + 5 /* Row.SAMPLED */] !== -2 /* Sampled.BARE */)
|
|
71
|
+
return;
|
|
72
|
+
const hot = c.hot;
|
|
73
|
+
const h = p * c.stride;
|
|
74
|
+
const n = c.axes;
|
|
75
|
+
const elapsed = clock(hot, h, c.bareNow) - data[o + 0 /* Row.DELAY */];
|
|
76
|
+
const share = left(c.eases[p], progress(elapsed, hot[h + 8 /* Hot.AT */], hot[h + 9 /* Hot.MS */]));
|
|
77
|
+
const x = h + 10 /* Hot.X0 */;
|
|
78
|
+
const g = x + 2 * n;
|
|
79
|
+
for (let a = 0; a < n; a++)
|
|
80
|
+
c.samples[p * n + a] = between(hot[x + a], hot[g + a], share);
|
|
81
|
+
data[o + 5 /* Row.SAMPLED */] = c.bareFill;
|
|
82
|
+
data[o + 6 /* Row.SEEKS */] = hot[h + 5 /* Hot.SEEKS */];
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* `unbare` for a lane position: its sample worked out again from its patch's stretch, which tells
|
|
86
|
+
* the lane through `stretchChanged` before it changes.
|
|
87
|
+
*/
|
|
88
|
+
export function unbareLane(lane, p) {
|
|
89
|
+
const o = p * 8 /* Row.STRIDE */;
|
|
90
|
+
const data = lane.data;
|
|
91
|
+
if (data[o + 5 /* Row.SAMPLED */] !== -2 /* Sampled.BARE */)
|
|
92
|
+
return;
|
|
93
|
+
const run = lane.motion;
|
|
94
|
+
const runs = run.runs;
|
|
95
|
+
const n = run.n;
|
|
96
|
+
const b = run.base(data[o + 4 /* Row.MSLOT */]);
|
|
97
|
+
const elapsed = lane.bareElapsed - data[o + 0 /* Row.DELAY */];
|
|
98
|
+
const share = left(run.ease, progress(elapsed, runs[b], runs[b + 2]));
|
|
99
|
+
const x = b + 3;
|
|
100
|
+
const g = x + 2 * n;
|
|
101
|
+
for (let a = 0; a < n; a++)
|
|
102
|
+
lane.samples[p * n + a] = between(runs[x + a], runs[g + a], share);
|
|
103
|
+
data[o + 5 /* Row.SAMPLED */] = lane.bareFill;
|
|
104
|
+
data[o + 6 /* Row.SEEKS */] = lane.bareSeeks;
|
|
105
|
+
}
|
package/dist/blend.d.ts
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { Mixer } from './mixer.js';
|
|
2
|
+
import { Store } from './store.js';
|
|
3
|
+
import type { Signal } from './types.js';
|
|
4
|
+
import type { Subject, Voice } from './voice.js';
|
|
5
|
+
/** A `mix.blend`'s one signal, read once for all its members. */
|
|
6
|
+
export interface Blend<I, O> {
|
|
7
|
+
by: Signal<I>;
|
|
8
|
+
stops: number;
|
|
9
|
+
members: Voice<I, O>[];
|
|
10
|
+
/** The general path's last read: its frame, its subject, and the value. */
|
|
11
|
+
frame: number;
|
|
12
|
+
subject: I | undefined;
|
|
13
|
+
value: number;
|
|
14
|
+
/** The general path's read of each subject, for one probed again after another in its frame. */
|
|
15
|
+
reads: Store<I, {
|
|
16
|
+
frame: number;
|
|
17
|
+
value: number;
|
|
18
|
+
}>;
|
|
19
|
+
/** Lanes' reads by subject number: the frame each is from, and the value. */
|
|
20
|
+
frames: Float64Array;
|
|
21
|
+
values: Float64Array;
|
|
22
|
+
}
|
|
23
|
+
export declare function blendOf<I, O>(by: Signal<I>, stops: number): Blend<I, O>;
|
|
24
|
+
/** Gives a projection's copies blends of their own, so its reads never overwrite the mix's. */
|
|
25
|
+
export declare function ownBlends<I, O>(voices: Voice<I, O>[]): void;
|
|
26
|
+
/** A blend member's share of its signal's read for this subject this frame, made by the first to ask. */
|
|
27
|
+
export declare function blended<I, O>(this: Mixer<I, O>, voice: Voice<I, O>, subject: I, held: Subject<unknown>, slot: number): number;
|
|
28
|
+
/** A blend member's share of the read lanes made of its signal for subject `slot` this frame; NaN for none. */
|
|
29
|
+
export declare function laneRead<I, O>(this: Mixer<I, O>, voice: Voice<I, O>, slot: number): number;
|
|
30
|
+
/**
|
|
31
|
+
* A blend's signal kept state on one member's record: the others' records for the subject hold
|
|
32
|
+
* the same, so whichever member reads next steps it, and every member leaves its lane.
|
|
33
|
+
*/
|
|
34
|
+
export declare function shareKept<I, O>(this: Mixer<I, O>, of: Blend<I, O>, voice: Voice<I, O>, subject: I, held: Subject<unknown>): void;
|
package/dist/blend.js
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { stateful } from './hosts.js';
|
|
2
|
+
import { reading } from './reading.js';
|
|
3
|
+
import { Store } from './store.js';
|
|
4
|
+
export function blendOf(by, stops) {
|
|
5
|
+
return {
|
|
6
|
+
by,
|
|
7
|
+
stops,
|
|
8
|
+
members: [],
|
|
9
|
+
frame: 0,
|
|
10
|
+
subject: undefined,
|
|
11
|
+
value: 0,
|
|
12
|
+
reads: new Store(),
|
|
13
|
+
frames: new Float64Array(0),
|
|
14
|
+
values: new Float64Array(0),
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
/** Gives a projection's copies blends of their own, so its reads never overwrite the mix's. */
|
|
18
|
+
export function ownBlends(voices) {
|
|
19
|
+
const own = new Map();
|
|
20
|
+
for (const v of voices) {
|
|
21
|
+
if (v.blend === null)
|
|
22
|
+
continue;
|
|
23
|
+
let of = own.get(v.blend.of);
|
|
24
|
+
if (of === undefined) {
|
|
25
|
+
of = blendOf(v.blend.of.by, v.blend.of.stops);
|
|
26
|
+
own.set(v.blend.of, of);
|
|
27
|
+
}
|
|
28
|
+
v.blend = { of, i: v.blend.i };
|
|
29
|
+
of.members.push(v);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
/** A blend member's weight for its signal's read `k`: the members' stops sit evenly along 0..1. */
|
|
33
|
+
function shareOf(k, i, stops) {
|
|
34
|
+
const d = stops === 0 ? 0 : Math.abs(k * stops - i);
|
|
35
|
+
return d >= 1 ? 0 : 1 - d;
|
|
36
|
+
}
|
|
37
|
+
/** A blend member's share of its signal's read for this subject this frame, made by the first to ask. */
|
|
38
|
+
export function blended(voice, subject, held, slot) {
|
|
39
|
+
const { of, i } = voice.blend;
|
|
40
|
+
const frame = this.frame;
|
|
41
|
+
let cell;
|
|
42
|
+
if (slot < 0) {
|
|
43
|
+
if (of.frame === frame && of.subject === subject)
|
|
44
|
+
return shareOf(of.value, i, of.stops);
|
|
45
|
+
cell = of.reads.get(subject);
|
|
46
|
+
if (cell?.frame === frame)
|
|
47
|
+
return shareOf(cell.value, i, of.stops);
|
|
48
|
+
}
|
|
49
|
+
else if (slot < of.frames.length) {
|
|
50
|
+
const read = this.laneRead(voice, slot);
|
|
51
|
+
if (!Number.isNaN(read))
|
|
52
|
+
return read;
|
|
53
|
+
}
|
|
54
|
+
else {
|
|
55
|
+
const size = Math.max(slot + 1, of.frames.length * 2, 64);
|
|
56
|
+
const frames = new Float64Array(size);
|
|
57
|
+
const values = new Float64Array(size);
|
|
58
|
+
frames.set(of.frames);
|
|
59
|
+
values.set(of.values);
|
|
60
|
+
of.frames = frames;
|
|
61
|
+
of.values = values;
|
|
62
|
+
}
|
|
63
|
+
const kept = reading.kept;
|
|
64
|
+
const k = of.by(subject, voice.setting);
|
|
65
|
+
if (reading.kept !== kept && !this.projecting)
|
|
66
|
+
this.shareKept(of, voice, subject, held);
|
|
67
|
+
if (slot < 0) {
|
|
68
|
+
of.frame = frame;
|
|
69
|
+
of.subject = subject;
|
|
70
|
+
of.value = k;
|
|
71
|
+
if (cell === undefined)
|
|
72
|
+
of.reads.set(subject, { frame, value: k });
|
|
73
|
+
else {
|
|
74
|
+
cell.frame = frame;
|
|
75
|
+
cell.value = k;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
else {
|
|
79
|
+
of.frames[slot] = frame;
|
|
80
|
+
of.values[slot] = k;
|
|
81
|
+
}
|
|
82
|
+
return shareOf(k, i, of.stops);
|
|
83
|
+
}
|
|
84
|
+
/** A blend member's share of the read lanes made of its signal for subject `slot` this frame; NaN for none. */
|
|
85
|
+
export function laneRead(voice, slot) {
|
|
86
|
+
const { of, i } = voice.blend;
|
|
87
|
+
return slot < of.frames.length && of.frames[slot] === this.frame
|
|
88
|
+
? shareOf(of.values[slot], i, of.stops)
|
|
89
|
+
: Number.NaN;
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* A blend's signal kept state on one member's record: the others' records for the subject hold
|
|
93
|
+
* the same, so whichever member reads next steps it, and every member leaves its lane.
|
|
94
|
+
*/
|
|
95
|
+
export function shareKept(of, voice, subject, held) {
|
|
96
|
+
for (const m of of.members) {
|
|
97
|
+
const h = m === voice ? undefined : m.subjects.get(subject);
|
|
98
|
+
if (h?.reaches && h.kept === null)
|
|
99
|
+
h.kept = held.kept;
|
|
100
|
+
stateful(this, m);
|
|
101
|
+
}
|
|
102
|
+
}
|