@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
@@ -1,25 +1,43 @@
1
1
  import type { Mixer } from './mixer.js';
2
2
  import { Pace } from './pace.js';
3
3
  import { Pager } from './paging.js';
4
+ import { type ScoreHolders } from './scored.js';
4
5
  import type { Marked, Mix, MixOptions, Tape, Transport as TransportApi, TransportOptions, TransportProjection } from './types.js';
5
6
  /**
6
7
  * A mark the host announced on a score: `at` in host time, so the rate never moves one, and NaN
7
- * until the next sync for one announced as now before any sync; `made` is the mix time it was
8
- * announced, for a read back to know what was known.
8
+ * until the next sync for one announced as now before any sync.
9
9
  */
10
10
  export interface Announced {
11
11
  name: string;
12
12
  score: string | undefined;
13
13
  tags: readonly string[];
14
14
  at: number;
15
- made: number;
16
15
  order: number;
16
+ /** The frame it was announced in, for a read back to know what was known. */
17
+ seq: number;
17
18
  /** The slot of the mix that announced it, -1 for the transport; only it sees an unnamed score. */
18
19
  slot: number;
19
20
  /** That mix's name, for `Marked.mix`. */
20
21
  mix: string | undefined;
21
22
  }
22
23
  type Member = Mixer<unknown, unknown>;
24
+ /**
25
+ * One frame on the transport: its number, the mix time it stood at and the host time it was made
26
+ * at. Rate 0 holds mix time still across frames, so only the number tells two of them apart.
27
+ */
28
+ export interface Frame {
29
+ seq: number;
30
+ at: number;
31
+ u: number;
32
+ }
33
+ /** Where history is cut for a seek or read: what frame `seq` made, and before it, is kept. */
34
+ export interface Cut {
35
+ seq: number;
36
+ /** For a read of what a frame showed: of that frame's own changes, only those made before it was read. */
37
+ strict: boolean;
38
+ }
39
+ /** Whether a change made in frame `seq` falls inside `cut`; `early` for one made before the frame was read. */
40
+ export declare function within(cut: Cut, seq: number, early?: boolean): boolean;
23
41
  /**
24
42
  * The clock its mixes play on: host time less what `rebase` took out, at the rate its `pace`
25
43
  * sets, moved by `sync` and `seek`, and the tape of calls a seek plays again. A mix made alone
@@ -34,14 +52,23 @@ export declare class Transport implements TransportApi {
34
52
  dropped: {
35
53
  mix: Member;
36
54
  at: number;
55
+ seq: number;
37
56
  }[];
38
57
  /** Numbers the mixes as they join, for their order and for whose unnamed score a mark is on. */
39
58
  slots: number;
59
+ holders: ScoreHolders;
40
60
  private wakeFns;
41
61
  /** Whether it has woken since the last sync, so it wakes once. */
42
62
  private woken;
43
63
  /** The mix clock at the last sync or seek. */
44
64
  now: number;
65
+ /**
66
+ * The number of the frame being played: each sync and seek begins one, and so does each host
67
+ * time the tape plays calls again at.
68
+ */
69
+ seq: number;
70
+ /** Under history, the frames a seek or read back may reach, oldest first. */
71
+ frames: Frame[];
45
72
  /** Host time at the last sync: the host's timestamp less every gap `rebase` took out. */
46
73
  u: number;
47
74
  offset: number;
@@ -51,8 +78,21 @@ export declare class Transport implements TransportApi {
51
78
  floor: number;
52
79
  last: number;
53
80
  rebasing: boolean;
81
+ /** The reach the tape was last pruned to. */
82
+ taped: number;
54
83
  /** The rate; null while it has never been set, when mix time is host time. */
55
84
  pace: Pace | null;
85
+ /** The latest mix time a voice any mix on it let go of from `gone` had left at. */
86
+ forgotTo: number;
87
+ /**
88
+ * The latest mix time a host changed a mix on it, or a voice or a subject left one: without
89
+ * history, the earliest time a read back still finds the mixes as they stand now.
90
+ */
91
+ settled: number;
92
+ /** Records a change or a leaving at the mix time now; before the first sync, nothing. */
93
+ settle(): void;
94
+ /** The frame the rate was first set in, which a seek or read back to before it finds unset. */
95
+ pacedSeq: number;
56
96
  /** True while the tape makes a recorded call again, so it is not recorded twice. */
57
97
  replaying: boolean;
58
98
  announced: Announced[];
@@ -68,11 +108,11 @@ export declare class Transport implements TransportApi {
68
108
  constructor(history: MixOptions['history'], shared: boolean);
69
109
  /** Whether this is a projection's own transport, which records and seeks nothing. */
70
110
  get projecting(): boolean;
71
- /** Takes a mix on, after every mix that joined before it; `slot` keeps a copy's in place. */
111
+ /** Takes a mix on, after every mix that joined before it; `slot` keeps one a seek puts back in place. */
72
112
  join(mix: Member, slot?: number): void;
73
113
  drop<I, O, H>(mix: Mix<I, O, H>): void;
74
- /** A seek back to `t`: every mix dropped after it is back on, as it stood then. */
75
- undrop(t: number): void;
114
+ /** A seek back to frame `seq`: every mix dropped after it is back on, as it stood then. */
115
+ undrop(seq: number): void;
76
116
  seek(time: number): void;
77
117
  prepare(time: number): Promise<void>;
78
118
  announce(name: string, opts: {
@@ -89,6 +129,13 @@ export declare class Transport implements TransportApi {
89
129
  woke(): void;
90
130
  sync(timestamp: number): void;
91
131
  private syncAt;
132
+ /** Begins a frame at mix time `at` and the host time the transport stands at. */
133
+ tick(at: number): void;
134
+ /**
135
+ * The frame a seek or read to mix time `t` lands on: the last one standing at or before `t`,
136
+ * since rate 0 can hold the clock at `t` for many.
137
+ */
138
+ frameAt(t: number): Frame | undefined;
92
139
  rebase(): void;
93
140
  /**
94
141
  * Moves `floor` past what the mix clock standing at `now` has let go of. With a store nothing is
package/dist/transport.js CHANGED
@@ -1,11 +1,18 @@
1
1
  import { listedAll } from './marks.js';
2
2
  import { move, nextFrame, waits } from './move.js';
3
3
  import { Pace } from './pace.js';
4
- import { Pager, prepare } from './paging.js';
4
+ import { Pager, pageTransport, prepare } from './paging.js';
5
5
  import { pin } from './place.js';
6
6
  import { projectAll } from './project.js';
7
+ import { scoredDrop, scoredJoin } from './scored.js';
7
8
  import { seek } from './seek.js';
8
9
  import { record, replay, tapeOf } from './tape.js';
10
+ /** How far the tape's reach moves before it is pruned again, ms of mix time. */
11
+ const TAPE_STEP_MS = 1000;
12
+ /** Whether a change made in frame `seq` falls inside `cut`; `early` for one made before the frame was read. */
13
+ export function within(cut, seq, early = false) {
14
+ return seq < cut.seq || (seq === cut.seq && (!cut.strict || early));
15
+ }
9
16
  /**
10
17
  * The clock its mixes play on: host time less what `rebase` took out, at the rate its `pace`
11
18
  * sets, moved by `sync` and `seek`, and the tape of calls a seek plays again. A mix made alone
@@ -20,11 +27,19 @@ export class Transport {
20
27
  dropped = [];
21
28
  /** Numbers the mixes as they join, for their order and for whose unnamed score a mark is on. */
22
29
  slots = 0;
30
+ holders = new Map();
23
31
  wakeFns = [];
24
32
  /** Whether it has woken since the last sync, so it wakes once. */
25
33
  woken = false;
26
34
  /** The mix clock at the last sync or seek. */
27
35
  now = Number.NaN;
36
+ /**
37
+ * The number of the frame being played: each sync and seek begins one, and so does each host
38
+ * time the tape plays calls again at.
39
+ */
40
+ seq = 0;
41
+ /** Under history, the frames a seek or read back may reach, oldest first. */
42
+ frames = [];
28
43
  /** Host time at the last sync: the host's timestamp less every gap `rebase` took out. */
29
44
  u = Number.NaN;
30
45
  offset = 0;
@@ -34,8 +49,24 @@ export class Transport {
34
49
  floor = Number.NEGATIVE_INFINITY;
35
50
  last = Number.NaN;
36
51
  rebasing = false;
52
+ /** The reach the tape was last pruned to. */
53
+ taped = Number.NEGATIVE_INFINITY;
37
54
  /** The rate; null while it has never been set, when mix time is host time. */
38
55
  pace = null;
56
+ /** The latest mix time a voice any mix on it let go of from `gone` had left at. */
57
+ forgotTo = Number.NEGATIVE_INFINITY;
58
+ /**
59
+ * The latest mix time a host changed a mix on it, or a voice or a subject left one: without
60
+ * history, the earliest time a read back still finds the mixes as they stand now.
61
+ */
62
+ settled = Number.NEGATIVE_INFINITY;
63
+ /** Records a change or a leaving at the mix time now; before the first sync, nothing. */
64
+ settle() {
65
+ if (this.now > this.settled)
66
+ this.settled = this.now;
67
+ }
68
+ /** The frame the rate was first set in, which a seek or read back to before it finds unset. */
69
+ pacedSeq = Number.POSITIVE_INFINITY;
39
70
  /** True while the tape makes a recorded call again, so it is not recorded twice. */
40
71
  replaying = false;
41
72
  announced = [];
@@ -58,7 +89,7 @@ export class Transport {
58
89
  get projecting() {
59
90
  return this.members.some((m) => m.projecting);
60
91
  }
61
- /** Takes a mix on, after every mix that joined before it; `slot` keeps a copy's in place. */
92
+ /** Takes a mix on, after every mix that joined before it; `slot` keeps one a seek puts back in place. */
62
93
  join(mix, slot = this.slots++) {
63
94
  mix.slot = slot;
64
95
  const i = this.members.findIndex((m) => m.slot > slot);
@@ -66,6 +97,7 @@ export class Transport {
66
97
  this.members.push(mix);
67
98
  else
68
99
  this.members.splice(i, 0, mix);
100
+ scoredJoin(mix);
69
101
  }
70
102
  drop(mix) {
71
103
  const m = mix;
@@ -73,18 +105,19 @@ export class Transport {
73
105
  if (i < 0)
74
106
  throw new Error(`blits: ${m.name ?? 'that mix'} is not on this transport`);
75
107
  this.members.splice(i, 1);
108
+ scoredDrop(m);
76
109
  m.dropped = true;
77
110
  if (this.history !== undefined) {
78
- this.dropped.push({ mix: m, at: this.now });
111
+ this.dropped.push({ mix: m, at: this.now, seq: this.seq });
79
112
  record(this, 'drop', () => this.drop(mix));
80
113
  }
81
114
  }
82
- /** A seek back to `t`: every mix dropped after it is back on, as it stood then. */
83
- undrop(t) {
84
- const back = this.dropped.filter((d) => d.at > t);
115
+ /** A seek back to frame `seq`: every mix dropped after it is back on, as it stood then. */
116
+ undrop(seq) {
117
+ const back = this.dropped.filter((d) => d.seq > seq);
85
118
  if (back.length === 0)
86
119
  return;
87
- this.dropped = this.dropped.filter((d) => d.at <= t);
120
+ this.dropped = this.dropped.filter((d) => d.seq <= seq);
88
121
  for (const d of back) {
89
122
  d.mix.dropped = false;
90
123
  this.join(d.mix, d.mix.slot);
@@ -143,6 +176,8 @@ export class Transport {
143
176
  }
144
177
  }
145
178
  syncAt(timestamp) {
179
+ if (Number.isNaN(timestamp))
180
+ throw new RangeError('blits: sync was given NaN; it takes the host clock as a number');
146
181
  if (timestamp < this.last && !this.rebasing)
147
182
  throw new RangeError(`blits: sync went back from ${this.last} to ${timestamp}; the host's clock only goes forward, and seek moves the mix`);
148
183
  if (this.rebasing && !Number.isNaN(this.last))
@@ -159,24 +194,39 @@ export class Transport {
159
194
  this.born = now;
160
195
  // Calls the host made before, played again where a seek went back past them; one may set the
161
196
  // rate, which moves where this sync lands.
162
- if (this.tape !== undefined && now > this.now) {
163
- const reading = () => (this.pace === null ? u : this.pace.reading(u));
164
- replay(this, reading);
197
+ // A call played again at this sync's own host time began its frame, moved there before it.
198
+ let begun = false;
199
+ if (this.tape !== undefined && later) {
200
+ begun = replay(this, (at) => at <= u) === u;
165
201
  pace = this.pace;
166
- now = reading();
202
+ now = pace === null ? u : pace.reading(u);
203
+ }
204
+ if (!begun) {
205
+ this.tick(now);
206
+ // Host time moving while the mix clock stands still lands what waits on host time or the host.
207
+ const moved = now !== this.now;
208
+ for (const m of this.members)
209
+ if (moved || (later && pace !== null && waits(m)))
210
+ move(m, now);
167
211
  }
168
- // Host time moving while the mix clock stands still lands what waits on host time or the host.
169
- const moved = now !== this.now;
170
- for (const m of this.members)
171
- if (moved || (later && pace !== null && waits(m)))
172
- move(m, now);
173
212
  const history = this.history;
174
213
  this.kept();
175
214
  const reach = this.keepsFrom();
176
- if (this.tape !== undefined && history !== undefined)
215
+ // In steps: the tape shifts every entry it keeps to let go of the oldest, and one a second
216
+ // behind reach is never played again.
217
+ if (this.tape !== undefined && history !== undefined && !(reach - this.taped < TAPE_STEP_MS)) {
177
218
  this.tape.prune(reach);
219
+ this.taped = reach;
220
+ }
178
221
  if (history !== undefined && this.dropped.length > 0)
179
222
  this.dropped = this.dropped.filter((d) => d.at >= reach);
223
+ const frames = this.frames;
224
+ let n = 0;
225
+ while (n + 1 < frames.length && frames[n + 1].at <= reach)
226
+ n++;
227
+ if (n > 0)
228
+ frames.splice(0, n);
229
+ pageTransport(this);
180
230
  this.woken = false;
181
231
  // It is a frame too, which asks every weight signal again, so one reading input follows it.
182
232
  if (still)
@@ -191,8 +241,38 @@ export class Transport {
191
241
  b.sync();
192
242
  }
193
243
  }
244
+ /** Begins a frame at mix time `at` and the host time the transport stands at. */
245
+ tick(at) {
246
+ const seq = ++this.seq;
247
+ if (this.history !== undefined)
248
+ this.frames.push({ seq, at, u: this.u });
249
+ for (const m of this.members)
250
+ m.looked = false;
251
+ }
252
+ /**
253
+ * The frame a seek or read to mix time `t` lands on: the last one standing at or before `t`,
254
+ * since rate 0 can hold the clock at `t` for many.
255
+ */
256
+ frameAt(t) {
257
+ const frames = this.frames;
258
+ let lo = 0;
259
+ let hi = frames.length - 1;
260
+ let found;
261
+ while (lo <= hi) {
262
+ const mid = (lo + hi) >> 1;
263
+ const f = frames[mid];
264
+ if (f.at <= t) {
265
+ found = f;
266
+ lo = mid + 1;
267
+ }
268
+ else
269
+ hi = mid - 1;
270
+ }
271
+ return found;
272
+ }
194
273
  rebase() {
195
274
  this.rebasing = true;
275
+ this.settle();
196
276
  }
197
277
  /**
198
278
  * Moves `floor` past what the mix clock standing at `now` has let go of. With a store nothing is
@@ -230,6 +310,7 @@ export class Transport {
230
310
  throw new RangeError(`blits: a mix's rate is a finite number, 0 or more, not ${rate}`);
231
311
  if (this.pace === null) {
232
312
  this.pace = new Pace(this.history !== undefined);
313
+ this.pacedSeq = this.seq;
233
314
  // Until now mix time was host time, so a pending voice's start is the host time it was given.
234
315
  for (const m of this.members)
235
316
  for (const v of m.cued)
@@ -237,7 +318,7 @@ export class Transport {
237
318
  pin(m, v, v.start);
238
319
  }
239
320
  const reach = this.history === undefined ? Number.NEGATIVE_INFINITY : this.keepsFrom();
240
- this.pace.change(this.u, rate, over, reach);
321
+ this.pace.change(this.u, rate, over, reach, this.seq);
241
322
  for (const m of this.members)
242
323
  m.stir();
243
324
  record(this, 'rate', () => this.ramp(rate, over));
@@ -251,14 +332,15 @@ export function announce(transport, name, opts, slot, mix) {
251
332
  score: opts.score,
252
333
  tags: opts.tags ?? [],
253
334
  at,
254
- made: Number.isNaN(transport.now) ? Number.NEGATIVE_INFINITY : transport.now,
255
335
  order: transport.nextId++,
336
+ seq: transport.seq,
256
337
  slot,
257
338
  mix,
258
339
  };
259
340
  transport.announced.push(mark);
341
+ transport.settle();
260
342
  record(transport, 'announce', () => {
261
- transport.announced.push({ ...mark });
343
+ transport.announced.push({ ...mark, seq: transport.seq });
262
344
  });
263
345
  }
264
346
  /**
@@ -0,0 +1,18 @@
1
+ import type { Laned } from './lane.js';
2
+ /** `d` less whole turns, so within half a turn of 0: the short way round a circle of `turn`. */
3
+ export declare function shortWay(d: number, turn: number): number;
4
+ /**
5
+ * Folds a rotation's value into a subject's numbers at `base`, at weight `w`, in the channel's own
6
+ * arithmetic: an angle the short way round and then as a sum, anything else through the channel's
7
+ * `fold`, which takes its axes as one value.
8
+ */
9
+ export declare function turnInto(ch: Laned, values: Float64Array, base: number, value: unknown, w: number): void;
10
+ /** `turnInto` for a value held as numbers at `from` in `source`, as a locus holds what it gathered. */
11
+ export declare function turnFrom(ch: Laned, values: Float64Array, base: number, source: Float64Array, from: number, w: number): void;
12
+ /**
13
+ * A locus member's rotation lerped by `u` into what the members before it left at `base`, through
14
+ * the channel's own `lerp`; the first member's value as it is, where `taken` is 0.
15
+ */
16
+ export declare function turnToward(ch: Laned, values: Float64Array, base: number, value: unknown, taken: number, u: number): void;
17
+ /** Sets every numbered subject's values on a lane to the channel's rest, as a fill starts from. */
18
+ export declare function rested(ch: Laned, size: number): void;
package/dist/turns.js ADDED
@@ -0,0 +1,72 @@
1
+ import { doubles } from './channels.js';
2
+ /** `d` less whole turns, so within half a turn of 0: the short way round a circle of `turn`. */
3
+ export function shortWay(d, turn) {
4
+ return d - turn * Math.round(d / turn);
5
+ }
6
+ let held = doubles(4, 0);
7
+ let other = doubles(4, 0);
8
+ /** A subject's numbers on a lane as an array a channel's own functions take: `to`, or one as long. */
9
+ function lift(values, base, axes, to) {
10
+ const out = to.length === axes ? to : doubles(axes, 0);
11
+ for (let a = 0; a < axes; a++)
12
+ out[a] = values[base + a];
13
+ return out;
14
+ }
15
+ /**
16
+ * Folds a rotation's value into a subject's numbers at `base`, at weight `w`, in the channel's own
17
+ * arithmetic: an angle the short way round and then as a sum, anything else through the channel's
18
+ * `fold`, which takes its axes as one value.
19
+ */
20
+ export function turnInto(ch, values, base, value, w) {
21
+ if (ch.turn > 0) {
22
+ values[base] = values[base] + shortWay(value, ch.turn) * w;
23
+ return;
24
+ }
25
+ const axes = ch.axes;
26
+ held = lift(values, base, axes, held);
27
+ const out = ch.channel.fold(held, value, w);
28
+ for (let a = 0; a < axes; a++)
29
+ values[base + a] = out[a];
30
+ }
31
+ /** `turnInto` for a value held as numbers at `from` in `source`, as a locus holds what it gathered. */
32
+ export function turnFrom(ch, values, base, source, from, w) {
33
+ if (ch.turn > 0) {
34
+ turnInto(ch, values, base, source[from], w);
35
+ return;
36
+ }
37
+ other = lift(source, from, ch.axes, other);
38
+ turnInto(ch, values, base, other, w);
39
+ }
40
+ /**
41
+ * A locus member's rotation lerped by `u` into what the members before it left at `base`, through
42
+ * the channel's own `lerp`; the first member's value as it is, where `taken` is 0.
43
+ */
44
+ export function turnToward(ch, values, base, value, taken, u) {
45
+ if (ch.turn > 0) {
46
+ const was = values[base];
47
+ const v = value;
48
+ values[base] = taken === 0 ? v : was + shortWay(v - was, ch.turn) * u;
49
+ return;
50
+ }
51
+ const axes = ch.axes;
52
+ let out = value;
53
+ if (taken !== 0) {
54
+ held = lift(values, base, axes, held);
55
+ out = ch.channel.lerp(held, value, u);
56
+ }
57
+ for (let a = 0; a < axes; a++)
58
+ values[base + a] = out[a];
59
+ }
60
+ /** Sets every numbered subject's values on a lane to the channel's rest, as a fill starts from. */
61
+ export function rested(ch, size) {
62
+ const values = ch.values;
63
+ const axes = ch.axes;
64
+ if (ch.op !== 'own') {
65
+ values.fill(ch.rest, 0, size * axes);
66
+ return;
67
+ }
68
+ const start = ch.start;
69
+ for (let i = 0; i < size * axes; i += axes)
70
+ for (let a = 0; a < axes; a++)
71
+ values[i + a] = start[a];
72
+ }
package/dist/tweened.js CHANGED
@@ -5,7 +5,7 @@ const SPAN = 1e-4;
5
5
  * `read` asks for it; a mix and a retarget never use it.
6
6
  */
7
7
  let slope = false;
8
- /** The last tween sample's time, curve and length, and the share of the way it had left to go. */
8
+ /** The last tween sample's progress and curve, and the share of the way it had left to go. */
9
9
  const memo = { u: Number.NaN, ease: undefined, left: 0 };
10
10
  /** Turns the velocity `eased` works out on or off. */
11
11
  export function sloped(on) {
@@ -0,0 +1,46 @@
1
+ /**
2
+ * One field of a delta, with its own arithmetic.
3
+ *
4
+ * @category channel
5
+ */
6
+ export interface Channel<V> {
7
+ /**
8
+ * Names this channel's arithmetic, so a patch written against one channel can be checked against
9
+ * another of the same name. Two channels of one kind must fold identically; the stock ones set it,
10
+ * as `'sum'` or `'vec(3, sum)'`. Absent, a channel matches only itself.
11
+ */
12
+ kind?: string;
13
+ /**
14
+ * The range a numeric value means anything in, such as 0..1 for an opacity. The mix clamps the
15
+ * folded value to it, axis by axis for an array, so neither stacked voices nor a retarget that
16
+ * carries speed can push it out; overshoot stops flat at the bound.
17
+ */
18
+ bounds?: readonly [min: number, max: number];
19
+ /** Identity. Absent means the channel has none: it replaces rather than contributes. */
20
+ rest?: V;
21
+ /** Fold two contributions into one. */
22
+ merge(a: V, b: V): V;
23
+ /** Fade toward `rest` by weight 0..1. Required when `rest` is set; absent otherwise. */
24
+ scale?(v: V, w: number): V;
25
+ /** Interpolate, for retargeting, for blending alternatives, and for folding a locus. */
26
+ lerp(a: V, b: V, u: number): V;
27
+ /**
28
+ * Optional: `merge(into, scale(v, w))`, written into `into` and returned, so a channel whose
29
+ * values are objects need not allocate per contribution. The mix only hands it a value it made.
30
+ */
31
+ fold?(into: V, v: V, w: number): V;
32
+ /**
33
+ * Optional: a fresh copy of `v`, which the mix makes of `rest` for `fold` to write into. The mix
34
+ * copies arrays, typed arrays and plain objects itself; a channel whose `rest` is anything else,
35
+ * such as a class instance, and that has `fold`, needs this or `cue` refuses it.
36
+ */
37
+ copy?(v: V): V;
38
+ }
39
+ /**
40
+ * The channel set for one kind of delta.
41
+ *
42
+ * @category channel
43
+ */
44
+ export type Kit<O> = {
45
+ readonly [K in keyof O]-?: Channel<NonNullable<O[K]>>;
46
+ };
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,139 @@
1
+ import type { VoiceSpec } from './voice.js';
2
+ /**
3
+ * What a mix or transport remembers, for `seek` and `project` to read back: see
4
+ * `MixOptions.history`.
5
+ *
6
+ * @category mix
7
+ */
8
+ export interface HistoryOptions {
9
+ /** How much stays in memory, ms of mix time. With a `store`, the rest is paged out, not dropped. */
10
+ ms: number;
11
+ every?: number;
12
+ inputs?: boolean;
13
+ tape?: TapeMaker;
14
+ /**
15
+ * Where history older than `ms` goes instead of being dropped. A `seek` or `project` reaching
16
+ * past memory needs `prepare` first, or throws `HistoryMiss`. With a store, blits never prunes
17
+ * the tape: the host bounds it.
18
+ */
19
+ store?: HistoryStore;
20
+ /** Rebuilds a cued voice's spec from the descriptor it was cued with, so the voice can be paged out. */
21
+ revive?(d: Described): VoiceSpec<any, any>;
22
+ }
23
+ /**
24
+ * A voice as plain data: `kind` names what built it, and `data` survives `structuredClone`.
25
+ *
26
+ * @category mix
27
+ */
28
+ export interface Described {
29
+ kind: string;
30
+ data: unknown;
31
+ }
32
+ /**
33
+ * Where a history `store` keeps what memory let go of. blits decides what is kept and when it is
34
+ * read back; the store decides where it lives.
35
+ *
36
+ * @category mix
37
+ */
38
+ export interface HistoryStore {
39
+ /** Records leaving memory. One brought back by a seek may be paged again: keep one per key and `seq`. */
40
+ page(out: readonly Paged[]): void;
41
+ /**
42
+ * What a restore to mix time `t` needs, for every mix: from each stream, per voice and subject,
43
+ * the latest record at or before `t` and every record after it. Returning everything is fine.
44
+ */
45
+ load(t: number): Promise<readonly Paged[]>;
46
+ /**
47
+ * A seek went back to frame `seq`: every record whose `seq` is greater is from a future the tape
48
+ * makes again. Forget them.
49
+ */
50
+ cut(seq: number): void;
51
+ }
52
+ /**
53
+ * What a history record holds: a voice's `controls`, a stateful record's `snap`, an `input`
54
+ * signal's reading, a subject's record when it `left` a voice, a motion subject's `released` run
55
+ * or older `stretch`, the `host` fields patches read, a whole `voice` that has left, or the
56
+ * transport's own: a `mark` announced on it, a change of its `pace`, and a `frame` it played.
57
+ *
58
+ * @category mix
59
+ */
60
+ export type PagedStream = 'voice' | 'snap' | 'input' | 'left' | 'released' | 'stretch' | 'host' | 'controls' | 'mark' | 'pace' | 'frame';
61
+ /**
62
+ * One history record leaving memory, as plain data.
63
+ *
64
+ * @category mix
65
+ */
66
+ export interface Paged {
67
+ /** The mix's `name`; '' for a mix made alone without one, and for the transport's own records. */
68
+ mix: string;
69
+ stream: PagedStream;
70
+ /** The voice's id, for every stream but `host` and the transport's own. */
71
+ voice?: number;
72
+ /** The subject's key, for a record of one subject; for a `mark`, the number it was announced as. */
73
+ subject?: string | number;
74
+ /** Mix time. */
75
+ at: number;
76
+ /**
77
+ * The transport's frame it was made in. Rate 0 holds mix time still across frames, and a fade
78
+ * given a time already passed is filed behind the frame that decided it, so a cut goes by this.
79
+ */
80
+ seq: number;
81
+ data: unknown;
82
+ }
83
+ /**
84
+ * One host call a mix recorded on its tape: `apply` makes it again, and `invert` does nothing,
85
+ * since a mix goes back by restoring what it kept rather than by undoing calls.
86
+ *
87
+ * @category mix
88
+ */
89
+ export interface TapeOp {
90
+ apply(adapter: unknown): void;
91
+ invert(): TapeOp;
92
+ label?: string;
93
+ }
94
+ /**
95
+ * One future a tape keeps at its current entry: the calls the host made after the mix last went
96
+ * back here, before it made new ones.
97
+ *
98
+ * @category mix
99
+ */
100
+ export interface TapeBranch {
101
+ /** Its first entry's id, which `switchBranch` takes. */
102
+ id: number;
103
+ label: string;
104
+ timestamp: number;
105
+ /** How many entries run along it from here. */
106
+ length: number;
107
+ /** Whether the mix plays it on from here. */
108
+ current: boolean;
109
+ }
110
+ /**
111
+ * Where a mix keeps the host's calls, by host time, for `seek`. weasel-history's `History`
112
+ * (`@weasel-js/history`) has this shape. A host reads `branches` and calls `switchBranch` to pick
113
+ * which recorded future plays on; moving it any other way is the mix's job.
114
+ *
115
+ * @category mix
116
+ */
117
+ export interface Tape {
118
+ recordEntry(ops: TapeOp[], label: string): void;
119
+ undoDepth(): number;
120
+ timestampAt(i: number): number | undefined;
121
+ depthAt(t: number): number;
122
+ goto(n: number): void;
123
+ redo(): void;
124
+ prune(t: number): void;
125
+ branches(): readonly TapeBranch[];
126
+ switchBranch(id: number): void;
127
+ }
128
+ /**
129
+ * Makes a mix's tape: weasel-history's `createHistory` is one. The mix passes the clock it stamps
130
+ * calls with, branching on, so a call made after going back keeps the old future as a branch, and
131
+ * coalescing off.
132
+ *
133
+ * @category mix
134
+ */
135
+ export type TapeMaker = (adapter: unknown, opts: {
136
+ now: () => number;
137
+ branching: boolean;
138
+ coalesceWindowMs: number;
139
+ }) => Tape;
@@ -0,0 +1 @@
1
+ export {};