@msb235/blits 0.4.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.
Files changed (102) hide show
  1. package/CHANGELOG.md +262 -0
  2. package/README.md +2 -1
  3. package/dist/bare.d.ts +29 -0
  4. package/dist/bare.js +105 -0
  5. package/dist/blend.d.ts +34 -0
  6. package/dist/blend.js +102 -0
  7. package/dist/book.d.ts +53 -0
  8. package/dist/book.js +218 -0
  9. package/dist/chain.d.ts +22 -0
  10. package/dist/chain.js +150 -0
  11. package/dist/channels.d.ts +2 -0
  12. package/dist/channels.js +3 -1
  13. package/dist/clock.d.ts +50 -3
  14. package/dist/clock.js +102 -9
  15. package/dist/clone.d.ts +7 -0
  16. package/dist/clone.js +57 -0
  17. package/dist/columns.d.ts +29 -0
  18. package/dist/columns.js +183 -0
  19. package/dist/crowd.d.ts +128 -0
  20. package/dist/crowd.js +354 -0
  21. package/dist/cue.d.ts +3 -0
  22. package/dist/cue.js +115 -0
  23. package/dist/due.d.ts +12 -0
  24. package/dist/due.js +93 -0
  25. package/dist/fade.d.ts +19 -0
  26. package/dist/fade.js +133 -0
  27. package/dist/fill.d.ts +42 -0
  28. package/dist/fill.js +381 -0
  29. package/dist/fold.d.ts +51 -0
  30. package/dist/fold.js +475 -0
  31. package/dist/gather.d.ts +24 -0
  32. package/dist/gather.js +186 -0
  33. package/dist/handle.d.ts +31 -0
  34. package/dist/handle.js +56 -0
  35. package/dist/held.d.ts +14 -0
  36. package/dist/held.js +117 -0
  37. package/dist/history.d.ts +22 -0
  38. package/dist/history.js +149 -0
  39. package/dist/hosts.d.ts +16 -0
  40. package/dist/hosts.js +178 -0
  41. package/dist/index.d.ts +1 -1
  42. package/dist/keyrows.d.ts +36 -0
  43. package/dist/keyrows.js +138 -0
  44. package/dist/lane.d.ts +210 -0
  45. package/dist/lane.js +187 -0
  46. package/dist/lanes.d.ts +121 -246
  47. package/dist/lanes.js +150 -1578
  48. package/dist/locus.d.ts +40 -0
  49. package/dist/locus.js +89 -0
  50. package/dist/marks.d.ts +12 -0
  51. package/dist/marks.js +80 -0
  52. package/dist/meet.d.ts +16 -0
  53. package/dist/meet.js +113 -0
  54. package/dist/mixer.d.ts +244 -196
  55. package/dist/mixer.js +242 -2220
  56. package/dist/motion.d.ts +66 -21
  57. package/dist/motion.js +325 -165
  58. package/dist/move.d.ts +9 -0
  59. package/dist/move.js +190 -0
  60. package/dist/named.d.ts +12 -0
  61. package/dist/named.js +39 -0
  62. package/dist/numbers.d.ts +18 -6
  63. package/dist/numbers.js +59 -31
  64. package/dist/owed.d.ts +24 -0
  65. package/dist/owed.js +51 -0
  66. package/dist/owner.d.ts +43 -0
  67. package/dist/owner.js +117 -0
  68. package/dist/pace.d.ts +40 -0
  69. package/dist/pace.js +113 -0
  70. package/dist/patch.d.ts +51 -10
  71. package/dist/patch.js +140 -45
  72. package/dist/place.d.ts +20 -0
  73. package/dist/place.js +196 -0
  74. package/dist/project.d.ts +4 -0
  75. package/dist/project.js +233 -0
  76. package/dist/pull.d.ts +5 -0
  77. package/dist/pull.js +175 -0
  78. package/dist/qualify.d.ts +31 -0
  79. package/dist/qualify.js +345 -0
  80. package/dist/relink.d.ts +25 -0
  81. package/dist/relink.js +59 -0
  82. package/dist/rows.d.ts +33 -0
  83. package/dist/rows.js +282 -0
  84. package/dist/sample.d.ts +21 -0
  85. package/dist/sample.js +232 -0
  86. package/dist/signals.d.ts +12 -9
  87. package/dist/signals.js +8 -5
  88. package/dist/store.d.ts +10 -3
  89. package/dist/store.js +44 -9
  90. package/dist/strays.d.ts +21 -0
  91. package/dist/strays.js +84 -0
  92. package/dist/tweened.d.ts +11 -0
  93. package/dist/tweened.js +48 -0
  94. package/dist/types.d.ts +217 -34
  95. package/dist/unreached.d.ts +5 -0
  96. package/dist/unreached.js +24 -0
  97. package/dist/voice.d.ts +240 -0
  98. package/dist/voice.js +322 -0
  99. package/dist/weigh.d.ts +26 -0
  100. package/dist/weigh.js +89 -0
  101. package/docs/schema.html +118 -40
  102. package/package.json +5 -2
package/CHANGELOG.md CHANGED
@@ -4,6 +4,268 @@ 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
+
7
269
  ## 0.4.0
8
270
 
9
271
  ### Breaking
package/README.md CHANGED
@@ -76,7 +76,8 @@ so the engine keeps no dependencies; its README says how it works.
76
76
 
77
77
  ## Status
78
78
 
79
- v0.4.0, with two consumers. magicsmoke runs every fault on it, and
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
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
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
+ }
@@ -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
+ }
package/dist/book.d.ts ADDED
@@ -0,0 +1,53 @@
1
+ import type { Booker, BookOptions, Hit, Marked } from './types.js';
2
+ import type { Voice } from './voice.js';
3
+ /** A mark as the mix lists it, with what tells it apart from every other for good. */
4
+ export type Listed = Marked & {
5
+ order: number;
6
+ };
7
+ /** What a booker reads of the mix that made it. */
8
+ export interface BookHost<I, O> {
9
+ /** The voices in the mix, pending, live, held or fading. */
10
+ readonly voices: readonly Voice<I, O>[];
11
+ /** The host's timestamp at the last sync. */
12
+ readonly timestamp: number;
13
+ /** The mix clock at the last sync. */
14
+ readonly now: number;
15
+ /** The host timestamp the mix clock reads mix time `t` at; Infinity where it never will. */
16
+ hostOf(t: number): number;
17
+ /** What the mix clock reads at host timestamp `timestamp`. */
18
+ readingAt(timestamp: number): number;
19
+ /** Mix time a voice is gone at, where something has fixed it. */
20
+ endOf(voice: Voice<I, O>): number | undefined;
21
+ marks(from: number, to: number): Listed[];
22
+ /** The booker stopped: forget it. */
23
+ unhook(booker: Book<I, O>): void;
24
+ }
25
+ /** Refuses hits a voice could never play. */
26
+ export declare function checkHits(hits: readonly Hit[], duration: number): void;
27
+ /** One `book` call: maps host time onto the outside clock and takes each item once. */
28
+ export declare class Book<I, O> implements Booker {
29
+ private readonly host;
30
+ private readonly opts;
31
+ /** The outside clock at the last sync, smoothed; NaN until the first. */
32
+ private est;
33
+ private last;
34
+ /** The mix clock at the previous sync, where a voice's clock change counts from. */
35
+ private prev;
36
+ private round;
37
+ private stopped;
38
+ /** Bookings of marks, by `keyOf`, kept while listed so one already taken is not taken late. */
39
+ private readonly marked;
40
+ private readonly tracks;
41
+ private readonly due;
42
+ constructor(host: BookHost<I, O>, opts: BookOptions);
43
+ /** Books what comes within `ahead` of this sync, and retracts what moved. */
44
+ sync(): void;
45
+ stop(): void;
46
+ /** Whether a standing booking now falls at host time `time` within `RETIME_MS`. */
47
+ private stands;
48
+ private fits;
49
+ private marks;
50
+ private hits;
51
+ /** Host time of a hit's pass, or undefined where the voice no longer plays it. */
52
+ private timeOfHit;
53
+ }