@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.
Files changed (153) hide show
  1. package/CHANGELOG.md +180 -0
  2. package/README.md +22 -4
  3. package/dist/book.js +4 -4
  4. package/dist/chain.d.ts +1 -1
  5. package/dist/chain.js +57 -54
  6. package/dist/channels.d.ts +26 -5
  7. package/dist/channels.js +12 -8
  8. package/dist/clock.d.ts +8 -1
  9. package/dist/clock.js +14 -3
  10. package/dist/clone.d.ts +3 -0
  11. package/dist/clone.js +16 -1
  12. package/dist/closed.d.ts +1 -1
  13. package/dist/closed.js +3 -2
  14. package/dist/color.d.ts +0 -11
  15. package/dist/color.js +4 -24
  16. package/dist/columns.js +0 -1
  17. package/dist/copyout.d.ts +6 -0
  18. package/dist/copyout.js +38 -0
  19. package/dist/crowd.d.ts +7 -5
  20. package/dist/crowd.js +32 -18
  21. package/dist/cue.d.ts +3 -1
  22. package/dist/cue.js +31 -20
  23. package/dist/departed.d.ts +34 -0
  24. package/dist/departed.js +73 -0
  25. package/dist/easing.d.ts +5 -0
  26. package/dist/easing.js +31 -2
  27. package/dist/everyone.d.ts +56 -0
  28. package/dist/everyone.js +221 -0
  29. package/dist/fade.d.ts +8 -1
  30. package/dist/fade.js +34 -6
  31. package/dist/fill.d.ts +22 -13
  32. package/dist/fill.js +104 -41
  33. package/dist/fit.js +2 -1
  34. package/dist/fold.d.ts +37 -0
  35. package/dist/fold.js +213 -28
  36. package/dist/gather.d.ts +15 -9
  37. package/dist/gather.js +60 -25
  38. package/dist/gone.js +10 -0
  39. package/dist/handle.d.ts +16 -5
  40. package/dist/handle.js +56 -6
  41. package/dist/held.d.ts +8 -5
  42. package/dist/held.js +56 -58
  43. package/dist/history.d.ts +17 -13
  44. package/dist/history.js +47 -28
  45. package/dist/hosts.js +55 -8
  46. package/dist/index.d.ts +9 -4
  47. package/dist/index.js +5 -2
  48. package/dist/keyfill.d.ts +7 -0
  49. package/dist/keyfill.js +124 -0
  50. package/dist/keyrows.d.ts +1 -0
  51. package/dist/keyrows.js +1 -1
  52. package/dist/lane.d.ts +32 -2
  53. package/dist/lane.js +5 -0
  54. package/dist/lanes.d.ts +32 -8
  55. package/dist/lanes.js +69 -37
  56. package/dist/leavings.d.ts +26 -0
  57. package/dist/leavings.js +70 -0
  58. package/dist/meet.js +14 -8
  59. package/dist/mixer.d.ts +39 -7
  60. package/dist/mixer.js +50 -10
  61. package/dist/motion.d.ts +2 -0
  62. package/dist/motion.js +4 -1
  63. package/dist/motions.d.ts +22 -15
  64. package/dist/motions.js +40 -34
  65. package/dist/move.js +23 -16
  66. package/dist/moved.d.ts +11 -0
  67. package/dist/moved.js +29 -0
  68. package/dist/origin.d.ts +19 -0
  69. package/dist/origin.js +68 -0
  70. package/dist/owed.d.ts +4 -4
  71. package/dist/owed.js +8 -8
  72. package/dist/owner.d.ts +1 -1
  73. package/dist/owner.js +2 -2
  74. package/dist/pace.d.ts +13 -5
  75. package/dist/pace.js +21 -8
  76. package/dist/pack.d.ts +3 -1
  77. package/dist/pack.js +15 -24
  78. package/dist/paging.d.ts +8 -2
  79. package/dist/paging.js +44 -4
  80. package/dist/patch.d.ts +14 -9
  81. package/dist/patch.js +83 -21
  82. package/dist/place.js +18 -3
  83. package/dist/project.d.ts +12 -1
  84. package/dist/project.js +101 -83
  85. package/dist/pull.d.ts +2 -0
  86. package/dist/pull.js +8 -0
  87. package/dist/qualify.js +10 -2
  88. package/dist/reading.d.ts +5 -0
  89. package/dist/reading.js +5 -0
  90. package/dist/record.d.ts +9 -0
  91. package/dist/record.js +42 -0
  92. package/dist/relink.d.ts +11 -5
  93. package/dist/relink.js +7 -5
  94. package/dist/restored.d.ts +41 -0
  95. package/dist/restored.js +91 -0
  96. package/dist/revive.d.ts +10 -3
  97. package/dist/revive.js +31 -23
  98. package/dist/roster.d.ts +11 -0
  99. package/dist/roster.js +46 -0
  100. package/dist/rotation.d.ts +29 -0
  101. package/dist/rotation.js +91 -0
  102. package/dist/rows.d.ts +5 -5
  103. package/dist/rows.js +5 -5
  104. package/dist/sample.d.ts +3 -2
  105. package/dist/sample.js +32 -15
  106. package/dist/seek.js +97 -114
  107. package/dist/shared.d.ts +6 -0
  108. package/dist/shared.js +15 -0
  109. package/dist/signals.d.ts +24 -3
  110. package/dist/signals.js +50 -11
  111. package/dist/spans.js +7 -0
  112. package/dist/stack.d.ts +11 -0
  113. package/dist/stack.js +47 -0
  114. package/dist/standing.d.ts +10 -0
  115. package/dist/standing.js +79 -0
  116. package/dist/store.d.ts +3 -3
  117. package/dist/store.js +3 -3
  118. package/dist/strays.js +3 -0
  119. package/dist/tape.d.ts +8 -5
  120. package/dist/tape.js +21 -13
  121. package/dist/testing.d.ts +35 -0
  122. package/dist/testing.js +116 -0
  123. package/dist/touch.d.ts +6 -0
  124. package/dist/touch.js +28 -0
  125. package/dist/transport.d.ts +49 -6
  126. package/dist/transport.js +89 -19
  127. package/dist/turns.d.ts +18 -0
  128. package/dist/turns.js +72 -0
  129. package/dist/tweened.js +1 -1
  130. package/dist/types/channel.d.ts +46 -0
  131. package/dist/types/channel.js +1 -0
  132. package/dist/types/history.d.ts +139 -0
  133. package/dist/types/history.js +1 -0
  134. package/dist/types/mix.d.ts +336 -0
  135. package/dist/types/mix.js +1 -0
  136. package/dist/types/patch.d.ts +187 -0
  137. package/dist/types/patch.js +1 -0
  138. package/dist/types/place.d.ts +157 -0
  139. package/dist/types/place.js +1 -0
  140. package/dist/types/transport.d.ts +71 -0
  141. package/dist/types/transport.js +1 -0
  142. package/dist/types/voice.d.ts +323 -0
  143. package/dist/types/voice.js +1 -0
  144. package/dist/types.d.ts +7 -1162
  145. package/dist/unpage.d.ts +2 -2
  146. package/dist/unpage.js +53 -25
  147. package/dist/voice.d.ts +90 -22
  148. package/dist/voice.js +105 -43
  149. package/dist/wave.d.ts +42 -0
  150. package/dist/wave.js +62 -0
  151. package/dist/weigh.js +1 -1
  152. package/docs/schema.html +42 -36
  153. package/package.json +8 -3
package/dist/voice.js CHANGED
@@ -3,7 +3,7 @@ import { curve } from './easing.js';
3
3
  import { motionOf } from './motion.js';
4
4
  import { Named } from './named.js';
5
5
  import { childPlayed, Holding, mixTime, ownedElapsed, ownerPatch } from './owner.js';
6
- import { builtOf, durationOf, intosOf } from './patch.js';
6
+ import { builtOf, intosOf } from './patch.js';
7
7
  import { reading } from './reading.js';
8
8
  import { Store } from './store.js';
9
9
  export const none = Object.freeze([]);
@@ -118,17 +118,32 @@ export class Voice {
118
118
  holder = null;
119
119
  /** Whether its channels run as lanes this frame, so the general fold passes it by. */
120
120
  laned = false;
121
- /** How many times it has been sought, so a delta read before a seek is not handed out after it. */
121
+ /**
122
+ * How many times it has been sought or touched, so a delta read before either is not handed out
123
+ * after it.
124
+ */
122
125
  seeks = 0;
126
+ /** Whether the host has written to its handle, so it no longer plays as it was cued. */
127
+ written = false;
128
+ /** How many of `seeks` were the mix's `touch`, which moves no clock. */
129
+ touches = 0;
130
+ /** How many times its clock has jumped, which a booker counts hits again from. */
131
+ get jumps() {
132
+ return this.seeks - this.touches;
133
+ }
134
+ /** Seeks that rebuild state; a record stamped with fewer makes its state again before stepping. */
135
+ rebuilds = 0;
136
+ /** Whether its span's last fit jumped it to its end, which leaves it no time in that fit. */
137
+ skipped = false;
123
138
  /** Whether its patch has kept state on a record through `setting.keep`, which makes it stateful. */
124
139
  keeping = false;
125
140
  /** Subjects fading out of this voice alone, by the ramp each started; null while none are. */
126
141
  parts = null;
127
- /** Subjects faded out of this voice, by the mix time each left at; null while none have. */
142
+ /** Subjects faded out of this voice, by the mix time and frame each left at; null while none have. */
128
143
  parted = null;
129
144
  /**
130
- * Under history, the records of subjects that left this voice, by the mix time each left at,
131
- * while a seek or a read back may reach them; null while none have.
145
+ * Under history, the records of subjects that left this voice, by the mix time and frame each
146
+ * left at, while a seek or a read back may reach them; null while none have.
132
147
  */
133
148
  left = null;
134
149
  ease;
@@ -146,8 +161,13 @@ export class Voice {
146
161
  opened = Number.NaN;
147
162
  /** Which of its entries in the mix's due queue is current; older ones are skipped when popped. */
148
163
  dueToken = 0;
149
- /** Records made while it was pending, whose `since` its start may since have moved. */
164
+ /**
165
+ * Records whose `since` was still ahead when last worked out, which a later start, rate change
166
+ * or seek moves; null while none are.
167
+ */
150
168
  early = null;
169
+ /** The clocks it ran on before its current one, where a subject's origin may lie; null for none. */
170
+ clocks = null;
151
171
  /** Reused for every call this voice makes, so it is valid only during the call. */
152
172
  setting;
153
173
  /** Every subject this voice has been asked about, so a handover knows when it is finished. */
@@ -157,8 +177,12 @@ export class Voice {
157
177
  restedCount = 0;
158
178
  /** The mix time it was cued at; -Infinity before the first sync. */
159
179
  cuedAt = Number.NEGATIVE_INFINITY;
160
- /** The mix time it left at. */
180
+ /** The frame it was cued in. */
181
+ cuedSeq = Number.NEGATIVE_INFINITY;
182
+ /** The mix time it left at, which a fade or seek given a time already passed puts behind now. */
161
183
  doneAt = Number.POSITIVE_INFINITY;
184
+ /** The frame its leaving was decided in, which a seek or read back partitions by. */
185
+ doneSeq = Number.POSITIVE_INFINITY;
162
186
  /** Under `history`, its controls after each change, oldest first. */
163
187
  log = null;
164
188
  /** Under `history`, its controls as `cue` left them, which a voice parked by a seek takes back. */
@@ -187,6 +211,20 @@ export class Voice {
187
211
  unreached = null;
188
212
  /** By subject number, a bit set where `unreached` stands for the subject; null until one is. */
189
213
  unreachedBits = null;
214
+ /** Whether one record stands for every subject: see `shares` in `everyone.ts`. */
215
+ sharing = false;
216
+ /** That record, null until a chain first asks and once the voice shares no more. */
217
+ everyone = null;
218
+ /** While sharing, by subject number: a bit where a chain has asked about the subject. */
219
+ sighted = null;
220
+ /** While sharing, by subject number: the weight last given the subject on the general path. */
221
+ weights = null;
222
+ /**
223
+ * While sharing a patch that is called, by subject number: when it was last called for the
224
+ * subject and at what `seeks`, side by side, and the delta that call gave.
225
+ */
226
+ stamps = null;
227
+ deltas = null;
190
228
  /** The handle `cue` returned, which `voices` hands back too. */
191
229
  handle = null;
192
230
  /** With a history store, the key of every subject it has a record of; null without one. */
@@ -201,8 +239,10 @@ export class Voice {
201
239
  */
202
240
  finished = false;
203
241
  playedAs = undefined;
204
- /** The mix time `played` was settled at, so a seek back to before it can open it again. */
242
+ /** The mix time `played` was settled at. */
205
243
  playedAt = Number.NaN;
244
+ /** The frame `played` was settled in, so a seek back to before it can open it again. */
245
+ playedSeq = Number.NaN;
206
246
  donePromise = null;
207
247
  doneSettle = null;
208
248
  playedPromise = null;
@@ -241,18 +281,19 @@ export class Voice {
241
281
  * Its finite loop ended (true) or it left first (false), whichever comes first, noticed at mix
242
282
  * time `at`: `played` resolves.
243
283
  */
244
- play(played, at) {
284
+ play(played, at, seq) {
245
285
  if (this.quiet || this.playedAs !== undefined)
246
286
  return;
247
287
  this.playedAs = played;
248
288
  this.playedAt = at;
289
+ this.playedSeq = seq;
249
290
  this.playedSettle?.(played);
250
291
  if (played && this.owner !== null)
251
- childPlayed(this.owner, at);
292
+ childPlayed(this.owner, at, seq);
252
293
  }
253
- /** How `played` settled, and the mix time it did; undefined while it has not. */
294
+ /** How `played` settled, and the mix time and frame it did; undefined while it has not. */
254
295
  get settled() {
255
- return { played: this.playedAs, at: this.playedAt };
296
+ return { played: this.playedAs, at: this.playedAt, seq: this.playedSeq };
256
297
  }
257
298
  /** Whether `played` settled true, which its owner counted. */
258
299
  get passed() {
@@ -263,42 +304,50 @@ export class Voice {
263
304
  return this.playedAs === false;
264
305
  }
265
306
  /**
266
- * A seek back to mix time `t`: `done` and `played`, where they settled after it, start over with
307
+ * A seek back to frame `seq`: `done` and `played`, where they settled after it, start over with
267
308
  * fresh promises. True where `played` had settled true, which its owner counted.
268
309
  */
269
- reopen(t) {
310
+ reopen(seq) {
270
311
  let unplayed = false;
271
- if (this.playedAs !== undefined && !(this.playedAt <= t)) {
312
+ if (this.playedAs !== undefined && !(this.playedSeq <= seq)) {
272
313
  unplayed = this.playedAs;
273
314
  this.playedAs = undefined;
274
315
  this.playedPromise = null;
275
316
  this.playedSettle = null;
276
317
  }
277
- if (this.finished && !(this.doneAt <= t)) {
318
+ if (this.finished && !(this.doneSeq <= seq)) {
278
319
  this.finished = false;
279
320
  this.donePromise = null;
280
321
  this.doneSettle = null;
281
322
  }
282
323
  return unplayed;
283
324
  }
284
- /**
285
- * A seek back to before its cue: out of the mix, as `cue` left it, until the mix plays the cue
286
- * again. What it played since is forgotten, and `done` and `played` start over where they had
287
- * settled.
288
- */
289
- park() {
290
- this.reopen(Number.NEGATIVE_INFINITY);
291
- const c = this.first;
325
+ /** Takes on a set of controls: its clock, weight, fades and where its anchors put it. */
326
+ take(c) {
292
327
  this.anchorNow = c.anchorNow;
293
328
  this.anchorElapsed = c.anchorElapsed;
294
329
  this.rate = c.rate;
295
330
  this.ramp = c.ramp;
296
331
  this.weight = c.weight;
297
332
  this.out = c.out;
333
+ this.back = c.back;
298
334
  this.start = c.start;
299
335
  this.outAt = c.outAt;
300
336
  this.outOver = c.outOver;
301
337
  this.outSet = c.outSet;
338
+ this.rebuilds = c.rebuilds;
339
+ const clocks = this.clocks;
340
+ if (clocks !== null && clocks.length !== c.past)
341
+ this.clocks = c.past === 0 ? null : clocks.slice(0, c.past);
342
+ }
343
+ /**
344
+ * A seek back to before its cue: out of the mix, as `cue` left it, until the mix plays the cue
345
+ * again. What it played since is forgotten, and `done` and `played` start over where they had
346
+ * settled.
347
+ */
348
+ park() {
349
+ this.reopen(Number.NEGATIVE_INFINITY);
350
+ this.take(this.first);
302
351
  this.log = null;
303
352
  this.state = 'pending';
304
353
  this.subjects = new Store();
@@ -316,7 +365,9 @@ export class Voice {
316
365
  this.latest = 0;
317
366
  this.restedCount = 0;
318
367
  this.cuedAt = Number.NEGATIVE_INFINITY;
368
+ this.cuedSeq = Number.NEGATIVE_INFINITY;
319
369
  this.doneAt = Number.POSITIVE_INFINITY;
370
+ this.doneSeq = Number.POSITIVE_INFINITY;
320
371
  this.placing = false;
321
372
  this.answers = null;
322
373
  this.folds = null;
@@ -324,6 +375,12 @@ export class Voice {
324
375
  this.ownKept = null;
325
376
  this.unreached = null;
326
377
  this.unreachedBits = null;
378
+ this.sharing = false;
379
+ this.everyone = null;
380
+ this.sighted = null;
381
+ this.weights = null;
382
+ this.stamps = null;
383
+ this.deltas = null;
327
384
  if (this.holding !== null)
328
385
  this.holding = new Holding();
329
386
  }
@@ -346,13 +403,13 @@ export class Voice {
346
403
  : undefined;
347
404
  this.ease = fade.ease === undefined ? undefined : curve(fade.ease);
348
405
  this.passes = passesOf(spec.loop);
349
- this.duration = durationOf(patch);
406
+ this.duration = patch.duration;
350
407
  this.span =
351
408
  this.duration > 0 && Number.isFinite(this.passes)
352
409
  ? this.duration * this.passes
353
410
  : Number.POSITIVE_INFINITY;
354
411
  this.motion = motionOf(patch);
355
- const freezes = this.motion === undefined ? (spec.freeze ?? spec.hold) : undefined;
412
+ const freezes = this.motion === undefined ? spec.freeze : undefined;
356
413
  // A voice with no freeze of its own takes its owner's.
357
414
  const inherits = freezes === undefined && this.motion === undefined && owner !== null;
358
415
  this.freezesBefore = inherits
@@ -375,10 +432,11 @@ export class Voice {
375
432
  const t = timeWith(this, elapsed);
376
433
  return this.owner === null ? t : mixTime(this.owner, t);
377
434
  }
378
- /** Records this voice's controls as they stand, from mix time `at`. */
379
- note(at, sync = false) {
435
+ /** Records this voice's controls as they stand, from mix time `at` in frame `seq`. */
436
+ note(at, seq, sync = false) {
380
437
  this.log?.push({
381
438
  at,
439
+ seq,
382
440
  sync,
383
441
  anchorNow: this.anchorNow,
384
442
  anchorElapsed: this.anchorElapsed,
@@ -391,13 +449,17 @@ export class Voice {
391
449
  outAt: this.outAt,
392
450
  outOver: this.outOver,
393
451
  outSet: this.outSet,
452
+ rebuilds: this.rebuilds,
453
+ past: this.clocks?.length ?? 0,
394
454
  });
395
455
  }
396
456
  /**
397
457
  * A copy for a projection: its own setting, which sends nothing, and its own per-subject records,
398
- * filled from `fill`. With `controls` it takes those, as the voice stood at an earlier time.
458
+ * filled from `fill`. With `controls` it takes those, as the voice stood at an earlier time. With
459
+ * `share`, a voice keeping one record for every subject gives its copy a copy of that one, which
460
+ * a read ahead starts every subject from; `fill` then serves only a copy that shares no more.
399
461
  */
400
- copy(fill, controls) {
462
+ copy(fill, controls, share = false) {
401
463
  // A literal keeps the copy in fast mode, where `Object.assign` left it a dictionary that every
402
464
  // read in a projection looked up by name.
403
465
  const v = { __proto__: Voice.prototype, ...this };
@@ -411,22 +473,22 @@ export class Voice {
411
473
  v.laned = false;
412
474
  v.log = null;
413
475
  v.early = null;
414
- if (controls) {
415
- v.anchorNow = controls.anchorNow;
416
- v.anchorElapsed = controls.anchorElapsed;
417
- v.rate = controls.rate;
418
- v.ramp = controls.ramp;
419
- v.weight = controls.weight;
420
- v.out = controls.out;
421
- v.back = controls.back;
422
- v.start = controls.start;
423
- v.outAt = controls.outAt;
424
- v.outOver = controls.outOver;
425
- v.outSet = controls.outSet;
426
- }
476
+ if (controls)
477
+ v.take(controls);
478
+ if (v.clocks === this.clocks && v.clocks !== null)
479
+ v.clocks = v.clocks.slice();
427
480
  v.quiet = true;
428
481
  v.unreached = null;
429
482
  v.unreachedBits = null;
483
+ const all = share && this.sharing ? this.everyone : null;
484
+ v.sharing = all !== null;
485
+ // Its delta is the live voice's to write into, and a keys voice's is in the live scratch.
486
+ v.everyone =
487
+ all === null ? null : { ...all, delta: null, probed: Number.NaN, from: all.stepped };
488
+ v.sighted = null;
489
+ v.weights = null;
490
+ v.stamps = null;
491
+ v.deltas = null;
430
492
  return v;
431
493
  }
432
494
  rateAt(now) {
package/dist/wave.d.ts ADDED
@@ -0,0 +1,42 @@
1
+ import type { Kit, Patch } from './types.js';
2
+ /**
3
+ * The shape of one cycle of a `wave`.
4
+ *
5
+ * @category patch
6
+ */
7
+ export type WaveShape = 'sine' | 'triangle' | 'saw' | 'square';
8
+ /**
9
+ * What `wave` takes besides its duration.
10
+ *
11
+ * @category patch
12
+ */
13
+ export interface WaveOptions<O> {
14
+ /** Default 'sine'. */
15
+ shape?: WaveShape;
16
+ /** Whole or fractional cycles per pass of the patch's duration. Default 1. */
17
+ cycles?: number;
18
+ /** Where in its cycle the wave starts, 0..1. Default 0. */
19
+ phase?: number;
20
+ /** Peak swing per channel, either side of the channel's rest. Its keys are the patch's writes,
21
+ * and only a channel holding a number can take one. */
22
+ depth: {
23
+ [K in keyof O as NonNullable<O[K]> extends number ? K : never]?: number;
24
+ };
25
+ /** The channels this wave was written against, which `cue` checks a mix's kit against. Each
26
+ * channel swings around its rest here, so a `mul` channel pulses around 1; without it, around 0. */
27
+ kit?: Partial<Kit<O>>;
28
+ }
29
+ /**
30
+ * The unit wave: -1..1 at `x` cycles, starting at 0 and rising (square: +1 for the first half).
31
+ *
32
+ * @category patch
33
+ */
34
+ export declare function waveAt(shape: WaveShape, x: number): number;
35
+ /**
36
+ * A periodic swing on each channel `depth` names, around its rest: an LFO for a bob, a sway or a
37
+ * pulse. An `fn` patch that keeps its options on `patch.wave`, for an engine reading the wave
38
+ * rather than calling it.
39
+ *
40
+ * @category patch
41
+ */
42
+ export declare function wave<I, O>(duration: number, opts: WaveOptions<O>): Patch<I, O, void>;
package/dist/wave.js ADDED
@@ -0,0 +1,62 @@
1
+ import { patch } from './patch.js';
2
+ /**
3
+ * The unit wave: -1..1 at `x` cycles, starting at 0 and rising (square: +1 for the first half).
4
+ *
5
+ * @category patch
6
+ */
7
+ export function waveAt(shape, x) {
8
+ const u = x - Math.floor(x);
9
+ switch (shape) {
10
+ case 'sine':
11
+ return Math.sin(2 * Math.PI * u);
12
+ case 'triangle':
13
+ return u < 0.25 ? 4 * u : u < 0.75 ? 2 - 4 * u : 4 * u - 4;
14
+ case 'saw':
15
+ return u < 0.5 ? 2 * u : 2 * u - 2;
16
+ case 'square':
17
+ return u < 0.5 ? 1 : -1;
18
+ }
19
+ }
20
+ const plans = new WeakMap();
21
+ function planOf(opts) {
22
+ let plan = plans.get(opts);
23
+ if (plan === undefined) {
24
+ const depth = opts.depth;
25
+ const writes = Object.keys(depth);
26
+ plan = {
27
+ shape: opts.shape ?? 'sine',
28
+ cycles: opts.cycles ?? 1,
29
+ start: opts.phase ?? 0,
30
+ writes,
31
+ depths: writes.map((c) => depth[c]),
32
+ rests: writes.map((c) => {
33
+ const rest = opts.kit?.[c]?.rest;
34
+ return typeof rest === 'number' ? rest : 0;
35
+ }),
36
+ };
37
+ plans.set(opts, plan);
38
+ }
39
+ return plan;
40
+ }
41
+ /**
42
+ * A periodic swing on each channel `depth` names, around its rest: an LFO for a bob, a sway or a
43
+ * pulse. An `fn` patch that keeps its options on `patch.wave`, for an engine reading the wave
44
+ * rather than calling it.
45
+ *
46
+ * @category patch
47
+ */
48
+ export function wave(duration, opts) {
49
+ const own = planOf(opts);
50
+ const made = patch(duration,
51
+ // A copy of the patch carries this `at` with a `wave` of its own, which it is read by.
52
+ function at(phase) {
53
+ const plan = this.wave === opts ? own : planOf(this.wave);
54
+ const unit = waveAt(plan.shape, phase * plan.cycles + plan.start);
55
+ const out = {};
56
+ for (let i = 0; i < plan.writes.length; i++)
57
+ out[plan.writes[i]] =
58
+ plan.rests[i] + plan.depths[i] * unit;
59
+ return out;
60
+ }, { writes: own.writes, kit: opts.kit });
61
+ return { ...made, wave: opts };
62
+ }
package/dist/weigh.js CHANGED
@@ -39,7 +39,7 @@ export function ownedBy(voice, subject) {
39
39
  */
40
40
  export function ownerBase(owner, subject) {
41
41
  const now = this.now;
42
- const held = this.held(owner, subject, now, -1);
42
+ const held = this.held(owner, subject, -1);
43
43
  if (held.probed === this.frame)
44
44
  return held.phase;
45
45
  const kept = reading.kept;