@msb235/blits 0.7.0 → 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 (156) hide show
  1. package/CHANGELOG.md +196 -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 +33 -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 +36 -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.d.ts +32 -0
  39. package/dist/gone.js +144 -0
  40. package/dist/handle.d.ts +16 -5
  41. package/dist/handle.js +56 -6
  42. package/dist/held.d.ts +8 -5
  43. package/dist/held.js +56 -58
  44. package/dist/history.d.ts +17 -13
  45. package/dist/history.js +49 -28
  46. package/dist/hosts.js +57 -9
  47. package/dist/index.d.ts +9 -4
  48. package/dist/index.js +5 -2
  49. package/dist/keyfill.d.ts +7 -0
  50. package/dist/keyfill.js +124 -0
  51. package/dist/keyrows.d.ts +1 -0
  52. package/dist/keyrows.js +1 -1
  53. package/dist/lane.d.ts +32 -2
  54. package/dist/lane.js +5 -0
  55. package/dist/lanes.d.ts +32 -8
  56. package/dist/lanes.js +69 -37
  57. package/dist/leavings.d.ts +26 -0
  58. package/dist/leavings.js +70 -0
  59. package/dist/meet.js +14 -8
  60. package/dist/mixer.d.ts +43 -7
  61. package/dist/mixer.js +60 -11
  62. package/dist/motion.d.ts +2 -0
  63. package/dist/motion.js +4 -1
  64. package/dist/motions.d.ts +22 -15
  65. package/dist/motions.js +40 -34
  66. package/dist/move.js +31 -20
  67. package/dist/moved.d.ts +11 -0
  68. package/dist/moved.js +29 -0
  69. package/dist/origin.d.ts +19 -0
  70. package/dist/origin.js +68 -0
  71. package/dist/owed.d.ts +4 -4
  72. package/dist/owed.js +8 -8
  73. package/dist/owner.d.ts +1 -1
  74. package/dist/owner.js +2 -2
  75. package/dist/pace.d.ts +13 -5
  76. package/dist/pace.js +21 -8
  77. package/dist/pack.d.ts +3 -1
  78. package/dist/pack.js +15 -24
  79. package/dist/paging.d.ts +8 -2
  80. package/dist/paging.js +44 -4
  81. package/dist/patch.d.ts +14 -9
  82. package/dist/patch.js +83 -21
  83. package/dist/place.js +188 -30
  84. package/dist/project.d.ts +12 -1
  85. package/dist/project.js +101 -83
  86. package/dist/pull.d.ts +2 -0
  87. package/dist/pull.js +8 -0
  88. package/dist/qualify.js +10 -2
  89. package/dist/reading.d.ts +5 -0
  90. package/dist/reading.js +5 -0
  91. package/dist/record.d.ts +9 -0
  92. package/dist/record.js +42 -0
  93. package/dist/relink.d.ts +11 -5
  94. package/dist/relink.js +7 -5
  95. package/dist/restored.d.ts +41 -0
  96. package/dist/restored.js +91 -0
  97. package/dist/revive.d.ts +10 -3
  98. package/dist/revive.js +31 -23
  99. package/dist/roster.d.ts +11 -0
  100. package/dist/roster.js +46 -0
  101. package/dist/rotation.d.ts +29 -0
  102. package/dist/rotation.js +91 -0
  103. package/dist/rows.d.ts +5 -5
  104. package/dist/rows.js +5 -5
  105. package/dist/sample.d.ts +3 -2
  106. package/dist/sample.js +32 -15
  107. package/dist/scored.d.ts +57 -0
  108. package/dist/scored.js +196 -0
  109. package/dist/seek.js +97 -114
  110. package/dist/shared.d.ts +6 -0
  111. package/dist/shared.js +15 -0
  112. package/dist/signals.d.ts +24 -3
  113. package/dist/signals.js +50 -11
  114. package/dist/spans.js +7 -0
  115. package/dist/stack.d.ts +11 -0
  116. package/dist/stack.js +47 -0
  117. package/dist/standing.d.ts +10 -0
  118. package/dist/standing.js +79 -0
  119. package/dist/store.d.ts +3 -3
  120. package/dist/store.js +3 -3
  121. package/dist/strays.js +3 -0
  122. package/dist/tape.d.ts +8 -5
  123. package/dist/tape.js +21 -13
  124. package/dist/testing.d.ts +35 -0
  125. package/dist/testing.js +116 -0
  126. package/dist/touch.d.ts +6 -0
  127. package/dist/touch.js +28 -0
  128. package/dist/transport.d.ts +53 -6
  129. package/dist/transport.js +102 -20
  130. package/dist/turns.d.ts +18 -0
  131. package/dist/turns.js +72 -0
  132. package/dist/tweened.js +1 -1
  133. package/dist/types/channel.d.ts +46 -0
  134. package/dist/types/channel.js +1 -0
  135. package/dist/types/history.d.ts +139 -0
  136. package/dist/types/history.js +1 -0
  137. package/dist/types/mix.d.ts +336 -0
  138. package/dist/types/mix.js +1 -0
  139. package/dist/types/patch.d.ts +187 -0
  140. package/dist/types/patch.js +1 -0
  141. package/dist/types/place.d.ts +157 -0
  142. package/dist/types/place.js +1 -0
  143. package/dist/types/transport.d.ts +71 -0
  144. package/dist/types/transport.js +1 -0
  145. package/dist/types/voice.d.ts +323 -0
  146. package/dist/types/voice.js +1 -0
  147. package/dist/types.d.ts +7 -1162
  148. package/dist/unpage.d.ts +2 -2
  149. package/dist/unpage.js +55 -25
  150. package/dist/voice.d.ts +92 -22
  151. package/dist/voice.js +108 -43
  152. package/dist/wave.d.ts +42 -0
  153. package/dist/wave.js +62 -0
  154. package/dist/weigh.js +1 -1
  155. package/docs/schema.html +42 -36
  156. 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. */
@@ -175,6 +199,8 @@ export class Voice {
175
199
  placing = false;
176
200
  /** What each member of a join last answered, kept for once its target has left. */
177
201
  answers = null;
202
+ /** What each of its anchors' queries has read of each mix's gone voices (`place.ts`'s `Fold`). */
203
+ folds = null;
178
204
  /** The record `setting.keep` writes to: the one its patch or signal is being called for. */
179
205
  keepOn = null;
180
206
  /** What `setting.keep` holds when called before any record is. */
@@ -185,6 +211,20 @@ export class Voice {
185
211
  unreached = null;
186
212
  /** By subject number, a bit set where `unreached` stands for the subject; null until one is. */
187
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;
188
228
  /** The handle `cue` returned, which `voices` hands back too. */
189
229
  handle = null;
190
230
  /** With a history store, the key of every subject it has a record of; null without one. */
@@ -199,8 +239,10 @@ export class Voice {
199
239
  */
200
240
  finished = false;
201
241
  playedAs = undefined;
202
- /** 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. */
203
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;
204
246
  donePromise = null;
205
247
  doneSettle = null;
206
248
  playedPromise = null;
@@ -239,18 +281,19 @@ export class Voice {
239
281
  * Its finite loop ended (true) or it left first (false), whichever comes first, noticed at mix
240
282
  * time `at`: `played` resolves.
241
283
  */
242
- play(played, at) {
284
+ play(played, at, seq) {
243
285
  if (this.quiet || this.playedAs !== undefined)
244
286
  return;
245
287
  this.playedAs = played;
246
288
  this.playedAt = at;
289
+ this.playedSeq = seq;
247
290
  this.playedSettle?.(played);
248
291
  if (played && this.owner !== null)
249
- childPlayed(this.owner, at);
292
+ childPlayed(this.owner, at, seq);
250
293
  }
251
- /** 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. */
252
295
  get settled() {
253
- return { played: this.playedAs, at: this.playedAt };
296
+ return { played: this.playedAs, at: this.playedAt, seq: this.playedSeq };
254
297
  }
255
298
  /** Whether `played` settled true, which its owner counted. */
256
299
  get passed() {
@@ -261,42 +304,50 @@ export class Voice {
261
304
  return this.playedAs === false;
262
305
  }
263
306
  /**
264
- * 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
265
308
  * fresh promises. True where `played` had settled true, which its owner counted.
266
309
  */
267
- reopen(t) {
310
+ reopen(seq) {
268
311
  let unplayed = false;
269
- if (this.playedAs !== undefined && !(this.playedAt <= t)) {
312
+ if (this.playedAs !== undefined && !(this.playedSeq <= seq)) {
270
313
  unplayed = this.playedAs;
271
314
  this.playedAs = undefined;
272
315
  this.playedPromise = null;
273
316
  this.playedSettle = null;
274
317
  }
275
- if (this.finished && !(this.doneAt <= t)) {
318
+ if (this.finished && !(this.doneSeq <= seq)) {
276
319
  this.finished = false;
277
320
  this.donePromise = null;
278
321
  this.doneSettle = null;
279
322
  }
280
323
  return unplayed;
281
324
  }
282
- /**
283
- * A seek back to before its cue: out of the mix, as `cue` left it, until the mix plays the cue
284
- * again. What it played since is forgotten, and `done` and `played` start over where they had
285
- * settled.
286
- */
287
- park() {
288
- this.reopen(Number.NEGATIVE_INFINITY);
289
- const c = this.first;
325
+ /** Takes on a set of controls: its clock, weight, fades and where its anchors put it. */
326
+ take(c) {
290
327
  this.anchorNow = c.anchorNow;
291
328
  this.anchorElapsed = c.anchorElapsed;
292
329
  this.rate = c.rate;
293
330
  this.ramp = c.ramp;
294
331
  this.weight = c.weight;
295
332
  this.out = c.out;
333
+ this.back = c.back;
296
334
  this.start = c.start;
297
335
  this.outAt = c.outAt;
298
336
  this.outOver = c.outOver;
299
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);
300
351
  this.log = null;
301
352
  this.state = 'pending';
302
353
  this.subjects = new Store();
@@ -314,13 +365,22 @@ export class Voice {
314
365
  this.latest = 0;
315
366
  this.restedCount = 0;
316
367
  this.cuedAt = Number.NEGATIVE_INFINITY;
368
+ this.cuedSeq = Number.NEGATIVE_INFINITY;
317
369
  this.doneAt = Number.POSITIVE_INFINITY;
370
+ this.doneSeq = Number.POSITIVE_INFINITY;
318
371
  this.placing = false;
319
372
  this.answers = null;
373
+ this.folds = null;
320
374
  this.keepOn = null;
321
375
  this.ownKept = null;
322
376
  this.unreached = null;
323
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;
324
384
  if (this.holding !== null)
325
385
  this.holding = new Holding();
326
386
  }
@@ -343,13 +403,13 @@ export class Voice {
343
403
  : undefined;
344
404
  this.ease = fade.ease === undefined ? undefined : curve(fade.ease);
345
405
  this.passes = passesOf(spec.loop);
346
- this.duration = durationOf(patch);
406
+ this.duration = patch.duration;
347
407
  this.span =
348
408
  this.duration > 0 && Number.isFinite(this.passes)
349
409
  ? this.duration * this.passes
350
410
  : Number.POSITIVE_INFINITY;
351
411
  this.motion = motionOf(patch);
352
- const freezes = this.motion === undefined ? (spec.freeze ?? spec.hold) : undefined;
412
+ const freezes = this.motion === undefined ? spec.freeze : undefined;
353
413
  // A voice with no freeze of its own takes its owner's.
354
414
  const inherits = freezes === undefined && this.motion === undefined && owner !== null;
355
415
  this.freezesBefore = inherits
@@ -372,10 +432,11 @@ export class Voice {
372
432
  const t = timeWith(this, elapsed);
373
433
  return this.owner === null ? t : mixTime(this.owner, t);
374
434
  }
375
- /** Records this voice's controls as they stand, from mix time `at`. */
376
- 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) {
377
437
  this.log?.push({
378
438
  at,
439
+ seq,
379
440
  sync,
380
441
  anchorNow: this.anchorNow,
381
442
  anchorElapsed: this.anchorElapsed,
@@ -388,13 +449,17 @@ export class Voice {
388
449
  outAt: this.outAt,
389
450
  outOver: this.outOver,
390
451
  outSet: this.outSet,
452
+ rebuilds: this.rebuilds,
453
+ past: this.clocks?.length ?? 0,
391
454
  });
392
455
  }
393
456
  /**
394
457
  * A copy for a projection: its own setting, which sends nothing, and its own per-subject records,
395
- * 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.
396
461
  */
397
- copy(fill, controls) {
462
+ copy(fill, controls, share = false) {
398
463
  // A literal keeps the copy in fast mode, where `Object.assign` left it a dictionary that every
399
464
  // read in a projection looked up by name.
400
465
  const v = { __proto__: Voice.prototype, ...this };
@@ -408,22 +473,22 @@ export class Voice {
408
473
  v.laned = false;
409
474
  v.log = null;
410
475
  v.early = null;
411
- if (controls) {
412
- v.anchorNow = controls.anchorNow;
413
- v.anchorElapsed = controls.anchorElapsed;
414
- v.rate = controls.rate;
415
- v.ramp = controls.ramp;
416
- v.weight = controls.weight;
417
- v.out = controls.out;
418
- v.back = controls.back;
419
- v.start = controls.start;
420
- v.outAt = controls.outAt;
421
- v.outOver = controls.outOver;
422
- v.outSet = controls.outSet;
423
- }
476
+ if (controls)
477
+ v.take(controls);
478
+ if (v.clocks === this.clocks && v.clocks !== null)
479
+ v.clocks = v.clocks.slice();
424
480
  v.quiet = true;
425
481
  v.unreached = null;
426
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;
427
492
  return v;
428
493
  }
429
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;