@msb235/blits 0.13.0 → 0.15.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 (178) hide show
  1. package/CHANGELOG.md +183 -0
  2. package/README.md +20 -3
  3. package/dist/ahead.d.ts +26 -0
  4. package/dist/ahead.js +264 -0
  5. package/dist/around.d.ts +11 -0
  6. package/dist/around.js +32 -0
  7. package/dist/bare.js +15 -7
  8. package/dist/blank.d.ts +10 -0
  9. package/dist/blank.js +26 -0
  10. package/dist/blend.d.ts +2 -1
  11. package/dist/blend.js +1 -6
  12. package/dist/bound.d.ts +70 -0
  13. package/dist/bound.js +351 -0
  14. package/dist/chain.d.ts +2 -1
  15. package/dist/chain.js +7 -5
  16. package/dist/changes.d.ts +24 -0
  17. package/dist/changes.js +189 -0
  18. package/dist/channels.d.ts +39 -13
  19. package/dist/channels.js +14 -5
  20. package/dist/closed.d.ts +1 -0
  21. package/dist/closed.js +45 -1
  22. package/dist/color.d.ts +12 -10
  23. package/dist/color.js +25 -25
  24. package/dist/columns.js +7 -3
  25. package/dist/contribution.d.ts +30 -0
  26. package/dist/contribution.js +374 -0
  27. package/dist/copyout.d.ts +10 -0
  28. package/dist/copyout.js +69 -24
  29. package/dist/crowd.d.ts +8 -2
  30. package/dist/crowd.js +4 -5
  31. package/dist/cue.d.ts +8 -1
  32. package/dist/cue.js +47 -14
  33. package/dist/cut.d.ts +8 -0
  34. package/dist/cut.js +4 -0
  35. package/dist/drop.d.ts +9 -0
  36. package/dist/drop.js +84 -0
  37. package/dist/due.d.ts +24 -5
  38. package/dist/due.js +113 -33
  39. package/dist/easing.d.ts +4 -4
  40. package/dist/easing.js +3 -3
  41. package/dist/everyone.d.ts +3 -2
  42. package/dist/everyone.js +3 -3
  43. package/dist/fade.d.ts +2 -1
  44. package/dist/fade.js +2 -1
  45. package/dist/fill.d.ts +3 -2
  46. package/dist/fill.js +51 -19
  47. package/dist/fit.d.ts +44 -26
  48. package/dist/fit.js +10 -9
  49. package/dist/fling.d.ts +66 -0
  50. package/dist/fling.js +162 -0
  51. package/dist/fold.d.ts +4 -37
  52. package/dist/fold.js +5 -512
  53. package/dist/gather.d.ts +12 -7
  54. package/dist/gather.js +179 -44
  55. package/dist/gone.js +2 -2
  56. package/dist/grid.d.ts +47 -0
  57. package/dist/grid.js +247 -0
  58. package/dist/handle.d.ts +3 -0
  59. package/dist/handle.js +19 -8
  60. package/dist/held.d.ts +2 -1
  61. package/dist/history.d.ts +6 -5
  62. package/dist/history.js +9 -9
  63. package/dist/hosts.js +9 -15
  64. package/dist/index.d.ts +5 -4
  65. package/dist/keyfill.d.ts +1 -1
  66. package/dist/keyfill.js +5 -3
  67. package/dist/keyrows.d.ts +12 -0
  68. package/dist/keyrows.js +99 -12
  69. package/dist/lane.d.ts +36 -6
  70. package/dist/lane.js +32 -1
  71. package/dist/lanes.d.ts +23 -8
  72. package/dist/lanes.js +29 -8
  73. package/dist/leavings.d.ts +2 -2
  74. package/dist/leavings.js +1 -1
  75. package/dist/lives.d.ts +47 -0
  76. package/dist/lives.js +180 -0
  77. package/dist/meet.d.ts +2 -2
  78. package/dist/meet.js +90 -4
  79. package/dist/mixer.d.ts +61 -35
  80. package/dist/mixer.js +64 -169
  81. package/dist/motion.d.ts +19 -106
  82. package/dist/motion.js +60 -13
  83. package/dist/motions.d.ts +62 -150
  84. package/dist/motions.js +57 -369
  85. package/dist/move.js +24 -1
  86. package/dist/moved.d.ts +1 -1
  87. package/dist/moved.js +16 -7
  88. package/dist/named.d.ts +0 -1
  89. package/dist/named.js +20 -2
  90. package/dist/numbers.d.ts +2 -0
  91. package/dist/numbers.js +4 -0
  92. package/dist/owed.d.ts +2 -0
  93. package/dist/owed.js +4 -0
  94. package/dist/pack.d.ts +3 -1
  95. package/dist/pack.js +4 -3
  96. package/dist/patch.d.ts +7 -3
  97. package/dist/patch.js +6 -2
  98. package/dist/places.d.ts +53 -0
  99. package/dist/places.js +171 -0
  100. package/dist/plot.d.ts +12 -0
  101. package/dist/plot.js +52 -0
  102. package/dist/project.d.ts +4 -4
  103. package/dist/project.js +9 -211
  104. package/dist/pull.js +11 -8
  105. package/dist/qualify.js +84 -21
  106. package/dist/reading.d.ts +6 -0
  107. package/dist/reading.js +5 -0
  108. package/dist/record.d.ts +5 -1
  109. package/dist/record.js +4 -2
  110. package/dist/relink.d.ts +1 -1
  111. package/dist/restored.d.ts +2 -2
  112. package/dist/restored.js +1 -1
  113. package/dist/rests.d.ts +9 -0
  114. package/dist/rests.js +76 -0
  115. package/dist/revive.d.ts +3 -2
  116. package/dist/revive.js +2 -2
  117. package/dist/{locus.d.ts → role.d.ts} +10 -9
  118. package/dist/{locus.js → role.js} +3 -3
  119. package/dist/roles.d.ts +10 -0
  120. package/dist/roles.js +158 -0
  121. package/dist/roster.js +3 -3
  122. package/dist/rotation.d.ts +1 -1
  123. package/dist/rotation.js +1 -1
  124. package/dist/rows.js +1 -1
  125. package/dist/sample.d.ts +2 -2
  126. package/dist/sample.js +33 -7
  127. package/dist/seek.js +13 -5
  128. package/dist/setting.d.ts +27 -0
  129. package/dist/setting.js +55 -0
  130. package/dist/shared.d.ts +1 -1
  131. package/dist/shared.js +1 -1
  132. package/dist/signals.d.ts +64 -32
  133. package/dist/signals.js +20 -18
  134. package/dist/stack.d.ts +5 -5
  135. package/dist/stack.js +5 -5
  136. package/dist/standing.d.ts +2 -1
  137. package/dist/standing.js +3 -2
  138. package/dist/store.d.ts +9 -0
  139. package/dist/store.js +24 -0
  140. package/dist/strays.d.ts +2 -1
  141. package/dist/strays.js +21 -14
  142. package/dist/stretch.d.ts +112 -0
  143. package/dist/stretch.js +9 -0
  144. package/dist/subject.d.ts +142 -0
  145. package/dist/subject.js +1 -0
  146. package/dist/tape.d.ts +9 -5
  147. package/dist/tape.js +5 -1
  148. package/dist/then.d.ts +9 -0
  149. package/dist/then.js +57 -0
  150. package/dist/ticker.d.ts +4 -4
  151. package/dist/transport.d.ts +0 -8
  152. package/dist/transport.js +0 -4
  153. package/dist/turns.d.ts +4 -3
  154. package/dist/turns.js +4 -3
  155. package/dist/types/arc.d.ts +12 -11
  156. package/dist/types/channel.d.ts +12 -8
  157. package/dist/types/history.d.ts +35 -19
  158. package/dist/types/mix.d.ts +222 -161
  159. package/dist/types/motion.d.ts +151 -0
  160. package/dist/types/motion.js +1 -0
  161. package/dist/types/patch.d.ts +69 -34
  162. package/dist/types/place.d.ts +51 -36
  163. package/dist/types/transport.d.ts +20 -18
  164. package/dist/types/voice.d.ts +145 -72
  165. package/dist/types/watch.d.ts +88 -40
  166. package/dist/types.d.ts +1 -0
  167. package/dist/voice.d.ts +9 -141
  168. package/dist/voice.js +30 -79
  169. package/dist/warn.d.ts +2 -0
  170. package/dist/warn.js +1 -1
  171. package/dist/watch.d.ts +2 -2
  172. package/dist/watch.js +5 -3
  173. package/dist/wave.d.ts +4 -3
  174. package/dist/wave.js +3 -2
  175. package/dist/weigh.d.ts +7 -1
  176. package/dist/weigh.js +27 -0
  177. package/docs/schema.html +223 -37
  178. package/package.json +6 -2
package/dist/signals.js CHANGED
@@ -33,9 +33,9 @@ export function shareOf(k, i, stops) {
33
33
  return d >= 1 ? 0 : 1 - d;
34
34
  }
35
35
  /**
36
- * Member `i` of `n`'s share of `by`, as `mix.blend` weighs it: the hat of `by`'s read at the
37
- * member's stop, the stops spaced evenly from 0 to 1. Weights a voice outside a blend as one of
38
- * its members, such as one member played alone.
36
+ * Member `i` of `n`'s share of `by`, as `mix.blend` weighs it. It is the hat of `by`'s read at the
37
+ * member's stop, the stops spaced evenly from 0 to 1. Weights a voice outside a blend as one of its
38
+ * members, such as one member played alone.
39
39
  *
40
40
  * @category signal
41
41
  */
@@ -48,7 +48,7 @@ const touch = () => {
48
48
  reading.inputs++;
49
49
  };
50
50
  /**
51
- * Marks `read` as a signal driven from outside the clock, which a host's own signal has to be for
51
+ * Marks `read` as a signal driven from outside the clock. A host's own signal has to be one for
52
52
  * `assess` and a history's `inputs` to treat it as one. Two uses:
53
53
  *
54
54
  * `input(read)` is an input of the host's own: a pointer, a volume, a value in a store. The host
@@ -69,8 +69,8 @@ export function input(read, of) {
69
69
  return Object.assign(read, { input: true, touch });
70
70
  }
71
71
  /**
72
- * A signal the host writes: `input` over one number, with `set` reporting each change. klieg's
73
- * `level`.
72
+ * A signal the host writes. It is `input` over one number, with `set` reporting each change.
73
+ * klieg's `level`.
74
74
  *
75
75
  * @category signal
76
76
  */
@@ -93,9 +93,9 @@ const firstSight = (target, from, setting) => from === undefined || !Number.isFi
93
93
  ? { value: target, seen: Number.NaN }
94
94
  : { value: from, seen: setting.timestamp };
95
95
  /**
96
- * The package's linear decay primitive: follows its input at a rate limit, no faster than `riseMs` per
97
- * unit climbing or `fallMs` draining. The mix keeps its state per voice and subject, so two voices
98
- * handed the same slew each follow on their own. A subject starts on its input, or at `from`
96
+ * Follows its input at a rate limit, the package's linear decay primitive. No faster than `riseMs`
97
+ * per unit climbing or `fallMs` draining. The mix keeps its state per voice and subject, so two
98
+ * voices handed the same slew each follow on their own. A subject starts on its input, or at `from`
99
99
  * unless `dt` is infinite.
100
100
  *
101
101
  * @category signal
@@ -123,16 +123,17 @@ export function slew(of, { riseMs = 0, fallMs = 0, from }) {
123
123
  return marked(read, [of]);
124
124
  }
125
125
  /**
126
- * The exponential counterpart to `slew`: closes the same fraction of the distance to its input in
127
- * every equal stretch of time, `1 − exp(−gap / ms)`, so it reads the same however the frames that
128
- * sample it are spaced while the input holds still. `riseMs` and `fallMs` are time constants: after
129
- * one, 63% of the way is covered. 0 follows at once. Within `floor` of its input it lands on it, so a
130
- * subject can reach rest; pass 0 to keep the curve exact. The mix keeps its state per voice and subject.
131
- * A subject starts on its input, or at `from` unless `dt` is infinite.
126
+ * Closes the same fraction of the distance to its input in every equal stretch of time. The
127
+ * exponential counterpart to `slew`: the fraction is `1 − exp(−gap / ms)`, so it reads the same
128
+ * however the frames that sample it are spaced while the input holds still. `riseMs` and `fallMs`
129
+ * are time constants: after one, 63% of the way is covered. 0 follows at once. Within `floor` of
130
+ * its input it lands on it, so a subject can reach rest; pass 0 to keep the curve exact. The mix
131
+ * keeps its state per voice and subject. A subject starts on its input, or at `from` unless `dt` is
132
+ * infinite.
132
133
  *
133
134
  * @category signal
134
135
  */
135
- export function lag(of, { riseMs = 0, fallMs = 0, floor = 1e-6, from, }) {
136
+ export function lag(of, { riseMs = 0, fallMs = 0, floor = 1e-6, from }) {
136
137
  const read = (subject, setting) => {
137
138
  const target = of(subject, setting);
138
139
  const held = setting.keep(read, () => firstSight(target, from, setting));
@@ -155,8 +156,9 @@ export function lag(of, { riseMs = 0, fallMs = 0, floor = 1e-6, from, }) {
155
156
  return marked(read, [of]);
156
157
  }
157
158
  /**
158
- * The other half of the pair: a band, not an edge. It drops at `off`, passes at `on`, and between
159
- * the two it holds whatever it last did, so an input resting on a threshold cannot chatter.
159
+ * Passes or drops its input across a band, not an edge. The other half of the pair. It drops at
160
+ * `off`, passes at `on`, and between the two it holds whatever it last did, so an input resting on
161
+ * a threshold cannot chatter.
160
162
  *
161
163
  * @category signal
162
164
  */
package/dist/stack.d.ts CHANGED
@@ -1,10 +1,10 @@
1
1
  import type { Kit } from './types.js';
2
2
  /**
3
- * Folds deltas into one pose by a kit's arithmetic, in order, with no mix and no clock: what a mix
4
- * gives for voices contributing these deltas at these weights. Each channel starts at its rest, a
5
- * weight (default 1, held to 0..1) fades a delta toward rest, and the pose is clamped to each
6
- * channel's bounds. A channel with no rest takes the last delta a mix's default band switches on, any
7
- * weight above 0, and stays undefined where none is.
3
+ * Folds deltas into one pose by a kit's arithmetic, with no mix and no clock. It folds them in
4
+ * order, and gives what a mix gives for voices contributing these deltas at these weights. Each
5
+ * channel starts at its rest, a weight (default 1, held to 0..1) fades a delta toward rest, and the
6
+ * pose is clamped to each channel's bounds. A channel with no rest takes the last delta a mix's
7
+ * default band switches on, any weight above 0, and stays undefined where none is.
8
8
  *
9
9
  * @category channel
10
10
  */
package/dist/stack.js CHANGED
@@ -2,11 +2,11 @@ import { STOCK_BAND } from './channels.js';
2
2
  import { copy, first } from './clone.js';
3
3
  import { bound, merged } from './fold.js';
4
4
  /**
5
- * Folds deltas into one pose by a kit's arithmetic, in order, with no mix and no clock: what a mix
6
- * gives for voices contributing these deltas at these weights. Each channel starts at its rest, a
7
- * weight (default 1, held to 0..1) fades a delta toward rest, and the pose is clamped to each
8
- * channel's bounds. A channel with no rest takes the last delta a mix's default band switches on, any
9
- * weight above 0, and stays undefined where none is.
5
+ * Folds deltas into one pose by a kit's arithmetic, with no mix and no clock. It folds them in
6
+ * order, and gives what a mix gives for voices contributing these deltas at these weights. Each
7
+ * channel starts at its rest, a weight (default 1, held to 0..1) fades a delta toward rest, and the
8
+ * pose is clamped to each channel's bounds. A channel with no rest takes the last delta a mix's
9
+ * default band switches on, any weight above 0, and stays undefined where none is.
10
10
  *
11
11
  * @category channel
12
12
  */
@@ -1,5 +1,6 @@
1
+ import type { Cut } from './cut.js';
1
2
  import type { Mixer } from './mixer.js';
2
- import type { Cut, Transport } from './transport.js';
3
+ import type { Transport } from './transport.js';
3
4
  /**
4
5
  * Throws unless a read back to mix time `t` is exact with no history: every voice plays as it was
5
6
  * cued and reads the same whenever it is asked, and no mix on the transport has changed since `t`.
package/dist/standing.js CHANGED
@@ -1,7 +1,8 @@
1
+ import { mirror } from './ahead.js';
1
2
  import { ownBlends } from './blend.js';
2
3
  import { relink } from './owner.js';
3
4
  import { pin } from './place.js';
4
- import { mirror, recall, stateAt } from './project.js';
5
+ import { recall, stateAt } from './project.js';
5
6
  import { recount } from './roster.js';
6
7
  /**
7
8
  * Why a voice cannot be read at an earlier time from how it stands now, or undefined where it can:
@@ -28,7 +29,7 @@ function unfit(mix, voice) {
28
29
  return 'it plays under an owner';
29
30
  // A rest-less channel switches on across a band, by the weights its voice has passed through.
30
31
  const steady = typeof spec.weight !== 'function' &&
31
- spec.locus === undefined &&
32
+ spec.role === undefined &&
32
33
  !((voice.fade.in ?? 0) > 0) &&
33
34
  !((voice.fade.out ?? 0) > 0);
34
35
  if (!steady && voice.slots.some((s) => mix.channels[s]?.rest === undefined))
package/dist/store.d.ts CHANGED
@@ -33,3 +33,12 @@ export declare class Masked<K, V> extends Store<K, V> {
33
33
  get(key: K): V | undefined;
34
34
  delete(key: K): void;
35
35
  }
36
+ /** A store that fills a missing entry from `make` on first ask. */
37
+ export declare class Filled<K, V> extends Store<K, V> {
38
+ private readonly make;
39
+ /** Keys deleted here, which are made afresh by whoever asks and never again by `make`. */
40
+ private gone;
41
+ constructor(make: (key: K) => V | undefined);
42
+ get(key: K): V | undefined;
43
+ delete(key: K): void;
44
+ }
package/dist/store.js CHANGED
@@ -89,3 +89,27 @@ export class Masked extends Store {
89
89
  const none = Symbol('none');
90
90
  /** A Map's key equality, SameValueZero: NaN finds NaN. */
91
91
  const same = (a, b) => a === b || Object.is(a, b);
92
+ /** A store that fills a missing entry from `make` on first ask. */
93
+ export class Filled extends Store {
94
+ make;
95
+ /** Keys deleted here, which are made afresh by whoever asks and never again by `make`. */
96
+ gone = null;
97
+ constructor(make) {
98
+ super();
99
+ this.make = make;
100
+ }
101
+ get(key) {
102
+ let v = super.get(key);
103
+ if (v === undefined && this.gone?.get(key) !== true) {
104
+ v = this.make(key);
105
+ if (v !== undefined)
106
+ super.set(key, v);
107
+ }
108
+ return v;
109
+ }
110
+ delete(key) {
111
+ super.delete(key);
112
+ this.gone ??= new Store();
113
+ this.gone.set(key, true);
114
+ }
115
+ }
package/dist/strays.d.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import type { Mixer } from './mixer.js';
2
- import type { MotionOwner, Motions } from './motions.js';
2
+ import type { Motions } from './motions.js';
3
+ import type { MotionOwner } from './stretch.js';
3
4
  import type { Voice } from './voice.js';
4
5
  /** Notes a motion patch that may hold state for a subject no voice playing it reaches. */
5
6
  export declare function stray<I, O>(mix: Mixer<I, O>, motion: Motions<I>, subject: I): void;
package/dist/strays.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { unpart } from './fade.js';
2
2
  import { pageOut } from './paging.js';
3
- import { record, unplayable } from './tape.js';
3
+ import { record, taping, unplayable } from './tape.js';
4
4
  /** Notes a motion patch that may hold state for a subject no voice playing it reaches. */
5
5
  export function stray(mix, motion, subject) {
6
6
  const list = mix.strays.get(subject);
@@ -24,7 +24,7 @@ export function unplay(mix, voice) {
24
24
  if (voice.named === null)
25
25
  mix.strayAll.add(motion);
26
26
  else
27
- for (const subject of voice.named)
27
+ for (const subject of voice.named.list)
28
28
  stray(mix, motion, subject);
29
29
  }
30
30
  /**
@@ -45,19 +45,14 @@ export function motionOwner(mix) {
45
45
  seq() {
46
46
  return mix.deref()?.transport.seq ?? Number.NaN;
47
47
  },
48
+ early() {
49
+ const m = mix.deref();
50
+ return m !== undefined && (m.syncing || !m.looked);
51
+ },
48
52
  changed(id, motion, subject, change) {
49
53
  const m = mix.deref();
50
- if (m === undefined)
51
- return;
52
- record(m, 'motion', (stage) => {
53
- const to = stage.mix(m);
54
- if (to === undefined)
55
- return;
56
- if (to !== m)
57
- return changeAhead(to, id, motion, subject, change);
58
- const v = cuedById(m, id) ?? m.gone.find((g) => g.id === id);
59
- (v?.motion ?? motion).change(subject, { ...change });
60
- });
54
+ if (m !== undefined && taping(m))
55
+ recordChange(m, id, motion, subject, change);
61
56
  },
62
57
  page(id, stream, subject, out) {
63
58
  const m = mix.deref();
@@ -84,6 +79,18 @@ export function motionOwner(mix) {
84
79
  },
85
80
  };
86
81
  }
82
+ // Apart from `changed`, whose body would otherwise make this closure's context on every change.
83
+ function recordChange(m, id, motion, subject, change) {
84
+ record(m, 'motion', (stage) => {
85
+ const to = stage.mix(m);
86
+ if (to === undefined)
87
+ return;
88
+ if (to !== m)
89
+ return changeAhead(to, id, motion, subject, change);
90
+ const v = cuedById(m, id) ?? m.gone.find((g) => g.id === id);
91
+ (v?.motion ?? motion).change(subject, { ...change });
92
+ });
93
+ }
87
94
  /**
88
95
  * A retarget or push the tape holds, made for a read ahead: kept on its transport for the patch
89
96
  * to apply as it reads, since the patch's own state is the live mix's.
@@ -117,7 +124,7 @@ function changeAhead(to, id, motion, given, change) {
117
124
  made.set(motion, bySubject);
118
125
  }
119
126
  const list = bySubject.get(subject);
120
- const c = { at, to: change.to, v: change.v };
127
+ const c = { at, to: change.to, x: change.x, v: change.v };
121
128
  if (list === undefined)
122
129
  bySubject.set(subject, [c]);
123
130
  else {
@@ -0,0 +1,112 @@
1
+ import type { Curve } from './easing.js';
2
+ import type { Motions } from './motions.js';
3
+ import type { MotionSpec } from './types.js';
4
+ /**
5
+ * One closed-form stretch: released at `at` (voice ms) from `x0` moving at `v0`, toward `to`, over
6
+ * `ms` for a tween (0 for any other shape).
7
+ */
8
+ export interface Segment {
9
+ at: number;
10
+ ms: number;
11
+ x0: number[];
12
+ v0: number[];
13
+ to: number[];
14
+ /** The change that released it; undefined for a subject's first stretch. */
15
+ change?: Change;
16
+ }
17
+ export interface Change {
18
+ /**
19
+ * Voice ms it takes effect at; absent, until the subject's next read stamps it with that read's
20
+ * time, for one made while no frame had met the subject.
21
+ */
22
+ at?: number;
23
+ to?: number[];
24
+ /** Where it moves from, in place of where its stretch has it then. */
25
+ x?: number[];
26
+ v?: number[];
27
+ /** The mix clock when it was made, NaN where no mix was playing the patch. */
28
+ made?: number;
29
+ /** The frame it was made in, which a rewind partitions by; NaN where no mix was playing the patch. */
30
+ seq?: number;
31
+ /** Whether it was made before its frame was read, so a read of what that frame showed has it. */
32
+ early?: boolean;
33
+ }
34
+ /** A record of a motion patch's history leaving memory, at a mix time and frame. */
35
+ export interface Paging {
36
+ at: number;
37
+ seq: number;
38
+ data: unknown;
39
+ }
40
+ /** A subject's run as plain data: its flags, its stretches oldest first, and changes still to apply. */
41
+ export interface Run {
42
+ flags: number;
43
+ stretches: Segment[];
44
+ pending: Change[] | undefined;
45
+ }
46
+ /** `n` numbers of `from` from `at`, as an array of their own. */
47
+ export declare function numbers(from: Float64Array, at: number, n: number): number[];
48
+ /**
49
+ * A subject's flags: its value is a number; it has changes pending; it has older stretches; the
50
+ * stretch it is playing was found landed, which holds until another replaces it.
51
+ */
52
+ export declare const enum RunFlag {
53
+ SCALAR = 1,
54
+ PENDING = 2,
55
+ OLDER = 4,
56
+ LANDED = 8
57
+ }
58
+ /**
59
+ * The mix playing a patch, asked with its voice's id: an owner and an id, where a pair of closures
60
+ * cost two functions, their contexts and a weak reference per voice.
61
+ */
62
+ export interface MotionOwner {
63
+ /** The subject's voice time at the mix's latest frame; NaN where no frame of the voice met it. */
64
+ frame(id: number, subject: unknown): number;
65
+ /**
66
+ * Brings a subject the voice faded out back to it, so a change is not made for nothing: its
67
+ * voice time now, which a change bringing it back is made at; NaN for any other subject.
68
+ */
69
+ revive(id: number, subject: unknown): number;
70
+ /** The mix clock at its latest frame; NaN before the first. */
71
+ now(): number;
72
+ /** The number of the frame the mix is playing. */
73
+ seq(): number;
74
+ /** Whether a change made now comes before anything read the frame. */
75
+ early(): boolean;
76
+ /**
77
+ * The host made a change, already given its time, which a seek makes again on the patch its
78
+ * voice plays then: a voice a history store paged out comes back with a patch of its own.
79
+ */
80
+ changed<I>(id: number, motion: Motions<I>, subject: I, change: Change): void;
81
+ /** History leaving memory for a subject, which a mix with a history store pages out. */
82
+ page(id: number, stream: 'released' | 'stretch', subject: unknown, out: Paging[]): void;
83
+ }
84
+ /**
85
+ * What a mix keeping copies of a patch's stretches is told, with its id and the subject's number,
86
+ * before one changes.
87
+ */
88
+ export interface Watcher {
89
+ stretchChanged(id: number, s: number): void;
90
+ }
91
+ export interface Shape<I> {
92
+ from: (subject: I) => number[];
93
+ /** How fast each axis starts moving, given where it starts: still unless the options say. */
94
+ velocity: (subject: I, x: number[]) => number[];
95
+ /**
96
+ * Where it heads when released from `x` at `v`, given where it was heading; `put` for a stretch a
97
+ * `set` released.
98
+ */
99
+ aim: (x: number[], v: number[], was: number[] | null, subject: I, put: boolean) => number[];
100
+ /** `[form, settle, k1, k2, k3]`. */
101
+ law: readonly number[];
102
+ /** Whether a subject's value is a number rather than an array. */
103
+ scalar: (subject: I) => boolean;
104
+ /** Whether every subject's value is a number; undefined where that depends on the subject. */
105
+ scalarEvery(): boolean | undefined;
106
+ /** A tween's easing, which its law cannot hold; undefined, but present, on every other shape. */
107
+ ease: Curve | undefined;
108
+ /** A tween's ms for a stretch the subject starts now; undefined, but present, on the rest. */
109
+ ms: ((subject: I) => number) | undefined;
110
+ /** The patch's kind and constants, as `Patch.motion` reports them. */
111
+ spec(): MotionSpec;
112
+ }
@@ -0,0 +1,9 @@
1
+ /** Where `numbers` gathers what it copies, kept an array of doubles so every copy is one too. */
2
+ const gathered = [0.5];
3
+ /** `n` numbers of `from` from `at`, as an array of their own. */
4
+ export function numbers(from, at, n) {
5
+ // `Array.from` walks a typed array by its iterator: about 500 B a call for three axes.
6
+ for (let i = 0; i < n; i++)
7
+ gathered[i] = from[at + i];
8
+ return gathered.slice(0, n);
9
+ }
@@ -0,0 +1,142 @@
1
+ import type { Stamp } from './restored.js';
2
+ /**
3
+ * A subject's record as it left a voice, faded out of it or dropped: the mix time and frame it
4
+ * left at, and `sync` where it shows in its own frame: left in the frame's sync, or before anything
5
+ * read the mix that frame.
6
+ */
7
+ export interface Left<I> {
8
+ subject: I;
9
+ at: number;
10
+ seq: number;
11
+ sync: boolean;
12
+ held: Subject<unknown>;
13
+ /**
14
+ * The voice's fade of the subject as it stood on leaving, which a seek or read back to before
15
+ * puts back: its ramp out, while running, or its having faded out.
16
+ */
17
+ ramp: Parting | null;
18
+ parted: Parted | null;
19
+ }
20
+ /** A subject's ramp out of one voice: when it began, how long it runs, and the frame it was asked in. */
21
+ export interface Parting {
22
+ at: number;
23
+ over: number;
24
+ seq: number;
25
+ }
26
+ export interface Parted {
27
+ at: number;
28
+ seq: number;
29
+ ramp: Parting | undefined;
30
+ }
31
+ /**
32
+ * What a copy of a record keeps for the seeks that put a record back from it: the copy's state
33
+ * stepped on to `ticks`, as it stood near `at`, a moment one of them sought.
34
+ */
35
+ export interface Ahead<S> {
36
+ /** The record the last seek put back, and the moment it sought, until that record steps. */
37
+ put: Subject<S> | null;
38
+ sought: number;
39
+ /** The `since` of the record put back, which the steps in `state` were counted from. */
40
+ since: number;
41
+ at: number;
42
+ ticks: number;
43
+ /** Undefined until a record has stepped on from the copy. */
44
+ state: S | undefined;
45
+ /**
46
+ * The state the record a seek replaced had stepped on to from the copy, at `leftTicks`; undefined
47
+ * until one had.
48
+ */
49
+ leftTicks: number;
50
+ left: S | undefined;
51
+ }
52
+ /** A copy of a record kept under `history`, by the mix time and frame it was taken at. */
53
+ export interface Snap<S> {
54
+ at: number;
55
+ seq: number;
56
+ held: Subject<S>;
57
+ ahead?: Ahead<S>;
58
+ /** Set where another record's list holds it too, so a seek cutting it from one lets go of it. */
59
+ shared?: true;
60
+ }
61
+ /** Everything one voice holds for one subject, chained through the next voice that reaches it. */
62
+ export interface Subject<S> {
63
+ /** The voice's `target` answer, fixed on first sight. */
64
+ reaches: boolean;
65
+ /** The voice's `stagger` answer in voice ms, fixed on first sight. */
66
+ delay: number;
67
+ /** The mix timestamp this subject's delay ran out at, which its step grid counts from. */
68
+ since: number;
69
+ /** The mix timestamp its fade in counts from: `since`, or earlier for a voice freezing before. */
70
+ shown: number;
71
+ /** The weight this voice gave this subject the last frame it was probed, 0 where it gave none. */
72
+ weight: number;
73
+ /**
74
+ * The mix time it was left at rest during a handover, so it stops contributing, and which a seek
75
+ * reads to know whether it had by then; NaN while it has not (`isRested`).
76
+ */
77
+ restedAt: number;
78
+ /**
79
+ * Per written channel, whether a rest-less contribution is on: 0 unknown, 1 on, 2 off; null until
80
+ * a rest-less channel asks.
81
+ */
82
+ bands: Uint8Array | null;
83
+ state: S;
84
+ /** The `now` this subject last caught up to, for this voice. */
85
+ stepped: number;
86
+ /** Under `stepMs`, how many intervals have run since `since`; `stepped` is where that lands. */
87
+ ticks: number;
88
+ /** The `now` this subject was last probed at, so twelve probes in a frame step once. */
89
+ probed: number;
90
+ /** The last delta computed this frame, handed back to a repeat probe unchanged. */
91
+ delta: Record<string, unknown> | null;
92
+ /** The phase `delta` was read at, to read it again once its voice's scratch has moved on. */
93
+ phase: number;
94
+ /** The voice's `seeks` when `delta` was read, so a seek later in the frame reads it again. */
95
+ seeks: number;
96
+ /** The voice's `rebuilds` when its state was last made, so a seek's rebuild reaches it once. */
97
+ rebuilt: number;
98
+ /** Its kept signal state was stepped on in one go from a copy history kept, by a seek. */
99
+ copied: boolean;
100
+ /** Stop 0 for a `from: 'current'` voice, taken the first frame this subject is seen. */
101
+ base?: Record<string, unknown>;
102
+ /** The pose's velocity per channel at that moment, units per ms, so the first segment leaves at it. */
103
+ slope?: Record<string, unknown>;
104
+ /** What `setting.keep` holds for this voice and subject, by owner; null until it holds any. */
105
+ kept: Map<object, unknown> | null;
106
+ /** Kept state a history store gave back, which each owner takes in turn on its first keep. */
107
+ unkept?: unknown[];
108
+ /** Under `history`, copies of this record by the mix time they were taken, oldest first. */
109
+ snaps?: Snap<S>[];
110
+ /** In a projection: where this record started from, and whether nothing known could be. */
111
+ from?: number;
112
+ unknown?: boolean;
113
+ /** Under `history` with `inputs`, what an input weight signal read for this subject, when it changed. */
114
+ inputs?: {
115
+ at: number;
116
+ seq: number;
117
+ value: number;
118
+ }[];
119
+ /** In a projection reading back: that record, to read in place of the signal. */
120
+ replay?: {
121
+ at: number;
122
+ value: number;
123
+ }[];
124
+ /** The voice this is the record of; null on a subject's stub. */
125
+ voice: object | null;
126
+ /** The next voice's record for this subject, in voice order, among those that reach it. */
127
+ next: Subject<unknown> | null;
128
+ /**
129
+ * On the first record, which the mix keeps per subject so a probe makes one lookup: the `version`
130
+ * the chain was linked at, and per role, by channel slot, whether a rest-less contribution is on:
131
+ * 0 never decided, 1 on, 2 off.
132
+ */
133
+ version: number;
134
+ roles: Map<string, Uint8Array> | null;
135
+ /** On the first record: the number lanes index this subject by, -1 without one. */
136
+ slot: number;
137
+ /** A motion voice's number for this subject in its patch, good while `freed` is the patch's own. */
138
+ run: number;
139
+ freed: number;
140
+ /** In a voice's store once a seek has gone back, the stamp of the seek it was last put back or written at. */
141
+ restored: Stamp | null;
142
+ }
@@ -0,0 +1 @@
1
+ export {};
package/dist/tape.d.ts CHANGED
@@ -18,15 +18,18 @@ export declare function unplayable(what: string): never;
18
18
  * at: rate 0 holds mix time still across frames, and host time tells them apart.
19
19
  */
20
20
  export declare function tapeOf(transport: Transport): Tape | undefined;
21
+ interface Recorder {
22
+ readonly tape: Tape | undefined;
23
+ readonly replaying: boolean;
24
+ readonly projecting: boolean;
25
+ }
26
+ /** Whether `record` keeps a call made on `on` now, so a caller can skip making one it would drop. */
27
+ export declare function taping(on: Recorder): boolean;
21
28
  /**
22
29
  * Records a host call, which `again` makes once more when the mix plays past it after a seek, or
23
30
  * when a read ahead does, on the stage it is given.
24
31
  */
25
- export declare function record(on: {
26
- readonly tape: Tape | undefined;
27
- readonly replaying: boolean;
28
- readonly projecting: boolean;
29
- }, label: string, again: (stage: Stage) => void): void;
32
+ export declare function record(on: Recorder, label: string, again: (stage: Stage) => void): void;
30
33
  /** The host time at which the mix clock reads `t`, under the rate it has now. */
31
34
  export declare function hostAt(clock: {
32
35
  readonly pace: Pace | null;
@@ -47,3 +50,4 @@ export declare function hostAhead(clock: {
47
50
  * stage of its copies, it makes them on those, and the caller puts the tape back.
48
51
  */
49
52
  export declare function replay(transport: Transport, due: (u: number) => boolean, onto?: Transport, on?: Stage): number;
53
+ export {};
package/dist/tape.js CHANGED
@@ -24,13 +24,17 @@ export function tapeOf(transport) {
24
24
  coalesceWindowMs: 0,
25
25
  });
26
26
  }
27
+ /** Whether `record` keeps a call made on `on` now, so a caller can skip making one it would drop. */
28
+ export function taping(on) {
29
+ return on.tape !== undefined && !on.replaying && !on.projecting;
30
+ }
27
31
  /**
28
32
  * Records a host call, which `again` makes once more when the mix plays past it after a seek, or
29
33
  * when a read ahead does, on the stage it is given.
30
34
  */
31
35
  export function record(on, label, again) {
32
36
  const tape = on.tape;
33
- if (tape === undefined || on.replaying || on.projecting)
37
+ if (tape === undefined || !taping(on))
34
38
  return;
35
39
  const op = {
36
40
  label,
package/dist/then.d.ts ADDED
@@ -0,0 +1,9 @@
1
+ import { type Cut } from './cut.js';
2
+ import type { Motions } from './motions.js';
3
+ /**
4
+ * `subject`'s position and velocity at voice time `t`, into the patch's `xs` and `vs`, as frame `cut` left it:
5
+ * in the run it had then, with only the changes made by then, as a seek back there plays it. A run
6
+ * with none made since reads as it stands; one with some plays from its earliest stretch kept,
7
+ * the rest applied again in the order they take effect. Commits nothing, and gives the run's flags.
8
+ */
9
+ export declare function sampleThen<I>(motions: Motions<I>, subject: I, t: number, cut: Cut): number;
package/dist/then.js ADDED
@@ -0,0 +1,57 @@
1
+ import { within } from './cut.js';
2
+ /** Whether `c` was made by frame `cut`; one made with no mix playing its patch was. */
3
+ const madeBy = (c, cut) => c.seq === undefined || Number.isNaN(c.seq) || within(cut, c.seq, c.early === true);
4
+ /** The stretch playing at voice time `t` of a run's, oldest first: the latest released by then, else the first. */
5
+ function playingIn(list, t) {
6
+ const last = list[list.length - 1];
7
+ if (t >= last.at || list.length === 1)
8
+ return last;
9
+ if (list[0].at > t)
10
+ return list[0];
11
+ let lo = 0;
12
+ let hi = list.length - 2;
13
+ while (lo < hi) {
14
+ const mid = (lo + hi + 1) >> 1;
15
+ if (list[mid].at <= t)
16
+ lo = mid;
17
+ else
18
+ hi = mid - 1;
19
+ }
20
+ return list[lo];
21
+ }
22
+ /**
23
+ * `subject`'s position and velocity at voice time `t`, into the patch's `xs` and `vs`, as frame `cut` left it:
24
+ * in the run it had then, with only the changes made by then, as a seek back there plays it. A run
25
+ * with none made since reads as it stands; one with some plays from its earliest stretch kept,
26
+ * the rest applied again in the order they take effect. Commits nothing, and gives the run's flags.
27
+ */
28
+ export function sampleThen(motions, subject, t, cut) {
29
+ const run = motions.runThen(subject, cut) ?? motions.freshRun(subject);
30
+ let stretches = run.stretches;
31
+ let pending = run.pending ?? [];
32
+ const kept = [];
33
+ let since = false;
34
+ for (const c of [...stretches.slice(1).map((seg) => seg.change), ...pending])
35
+ if (c === undefined || madeBy(c, cut)) {
36
+ if (c !== undefined)
37
+ kept.push(c);
38
+ }
39
+ else
40
+ since = true;
41
+ if (since) {
42
+ stretches = [stretches[0]];
43
+ pending = kept
44
+ .filter((c) => c.at !== undefined)
45
+ .sort((a, b) => a.at - b.at);
46
+ }
47
+ let seg = playingIn(stretches, t);
48
+ for (const c of pending) {
49
+ const a = c.at;
50
+ if (a === undefined || !(a <= t))
51
+ break;
52
+ if (a >= seg.at)
53
+ seg = motions.applied(subject, seg, c, a);
54
+ }
55
+ motions.evaluateSegment(seg, t, motions.xs, motions.vs);
56
+ return run.flags;
57
+ }