@msb235/blits 0.4.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (102) hide show
  1. package/CHANGELOG.md +262 -0
  2. package/README.md +2 -1
  3. package/dist/bare.d.ts +29 -0
  4. package/dist/bare.js +105 -0
  5. package/dist/blend.d.ts +34 -0
  6. package/dist/blend.js +102 -0
  7. package/dist/book.d.ts +53 -0
  8. package/dist/book.js +218 -0
  9. package/dist/chain.d.ts +22 -0
  10. package/dist/chain.js +150 -0
  11. package/dist/channels.d.ts +2 -0
  12. package/dist/channels.js +3 -1
  13. package/dist/clock.d.ts +50 -3
  14. package/dist/clock.js +102 -9
  15. package/dist/clone.d.ts +7 -0
  16. package/dist/clone.js +57 -0
  17. package/dist/columns.d.ts +29 -0
  18. package/dist/columns.js +183 -0
  19. package/dist/crowd.d.ts +128 -0
  20. package/dist/crowd.js +354 -0
  21. package/dist/cue.d.ts +3 -0
  22. package/dist/cue.js +115 -0
  23. package/dist/due.d.ts +12 -0
  24. package/dist/due.js +93 -0
  25. package/dist/fade.d.ts +19 -0
  26. package/dist/fade.js +133 -0
  27. package/dist/fill.d.ts +42 -0
  28. package/dist/fill.js +381 -0
  29. package/dist/fold.d.ts +51 -0
  30. package/dist/fold.js +475 -0
  31. package/dist/gather.d.ts +24 -0
  32. package/dist/gather.js +186 -0
  33. package/dist/handle.d.ts +31 -0
  34. package/dist/handle.js +56 -0
  35. package/dist/held.d.ts +14 -0
  36. package/dist/held.js +117 -0
  37. package/dist/history.d.ts +22 -0
  38. package/dist/history.js +149 -0
  39. package/dist/hosts.d.ts +16 -0
  40. package/dist/hosts.js +178 -0
  41. package/dist/index.d.ts +1 -1
  42. package/dist/keyrows.d.ts +36 -0
  43. package/dist/keyrows.js +138 -0
  44. package/dist/lane.d.ts +210 -0
  45. package/dist/lane.js +187 -0
  46. package/dist/lanes.d.ts +121 -246
  47. package/dist/lanes.js +150 -1578
  48. package/dist/locus.d.ts +40 -0
  49. package/dist/locus.js +89 -0
  50. package/dist/marks.d.ts +12 -0
  51. package/dist/marks.js +80 -0
  52. package/dist/meet.d.ts +16 -0
  53. package/dist/meet.js +113 -0
  54. package/dist/mixer.d.ts +244 -196
  55. package/dist/mixer.js +242 -2220
  56. package/dist/motion.d.ts +66 -21
  57. package/dist/motion.js +325 -165
  58. package/dist/move.d.ts +9 -0
  59. package/dist/move.js +190 -0
  60. package/dist/named.d.ts +12 -0
  61. package/dist/named.js +39 -0
  62. package/dist/numbers.d.ts +18 -6
  63. package/dist/numbers.js +59 -31
  64. package/dist/owed.d.ts +24 -0
  65. package/dist/owed.js +51 -0
  66. package/dist/owner.d.ts +43 -0
  67. package/dist/owner.js +117 -0
  68. package/dist/pace.d.ts +40 -0
  69. package/dist/pace.js +113 -0
  70. package/dist/patch.d.ts +51 -10
  71. package/dist/patch.js +140 -45
  72. package/dist/place.d.ts +20 -0
  73. package/dist/place.js +196 -0
  74. package/dist/project.d.ts +4 -0
  75. package/dist/project.js +233 -0
  76. package/dist/pull.d.ts +5 -0
  77. package/dist/pull.js +175 -0
  78. package/dist/qualify.d.ts +31 -0
  79. package/dist/qualify.js +345 -0
  80. package/dist/relink.d.ts +25 -0
  81. package/dist/relink.js +59 -0
  82. package/dist/rows.d.ts +33 -0
  83. package/dist/rows.js +282 -0
  84. package/dist/sample.d.ts +21 -0
  85. package/dist/sample.js +232 -0
  86. package/dist/signals.d.ts +12 -9
  87. package/dist/signals.js +8 -5
  88. package/dist/store.d.ts +10 -3
  89. package/dist/store.js +44 -9
  90. package/dist/strays.d.ts +21 -0
  91. package/dist/strays.js +84 -0
  92. package/dist/tweened.d.ts +11 -0
  93. package/dist/tweened.js +48 -0
  94. package/dist/types.d.ts +217 -34
  95. package/dist/unreached.d.ts +5 -0
  96. package/dist/unreached.js +24 -0
  97. package/dist/voice.d.ts +240 -0
  98. package/dist/voice.js +322 -0
  99. package/dist/weigh.d.ts +26 -0
  100. package/dist/weigh.js +89 -0
  101. package/docs/schema.html +118 -40
  102. package/package.json +5 -2
package/dist/pace.d.ts ADDED
@@ -0,0 +1,40 @@
1
+ import { type Clock } from './clock.js';
2
+ /**
3
+ * A mix's own rate, as a clock: mix time as a function of host time, which here means the host's
4
+ * timestamp less what `rebase` took out. Each entry is the clock from the host time it took effect,
5
+ * oldest first, and the last is in force; under `history` the entries a read back may reach are
6
+ * kept, and without it only the last. Entries are never changed once logged, so a copy of the log
7
+ * stays as it was.
8
+ */
9
+ export declare class Pace {
10
+ private readonly keeps;
11
+ private log;
12
+ constructor(keeps: boolean, log?: Clock[]);
13
+ private get clock();
14
+ /** Set before the first sync, the clock waits to be anchored there, reading host time until then. */
15
+ private get waiting();
16
+ /** Mix time at host time `u`, anchoring a clock set before the first sync there. */
17
+ sync(u: number): number;
18
+ /** What the mix clock reads at host time `u`, by the clock in force then. */
19
+ reading(u: number): number;
20
+ /**
21
+ * The mix time of something pinned to host time `u`, seen from host time `now`: what the clock
22
+ * will read there, or Infinity where `u` lies ahead and the clock stands still before it, since
23
+ * the moment the mix reaches is then not `u`'s alone.
24
+ */
25
+ at(u: number, now: number): number;
26
+ /** The earliest host time the mix clock reads `t`; Infinity where it never will. */
27
+ timeOf(t: number): number;
28
+ /** The rate at host time `u`; before the first sync, the rate the first frame starts at. */
29
+ rateAt(u: number): number;
30
+ /** Whether the mix clock stands still from host time `u` on, short of another change. */
31
+ stopped(u: number): boolean;
32
+ /**
33
+ * Sets the rate from host time `u`, at once or over `over` host ms, the way a handle sets a
34
+ * voice's. Under history, lets go of the entries a read no longer reaches: those over before mix
35
+ * time `reach`.
36
+ */
37
+ change(u: number, rate: number, over: number, reach: number): void;
38
+ /** A copy holding the clocks that took effect before host time `u`, for a read back to then. */
39
+ until(u: number): Pace;
40
+ }
package/dist/pace.js ADDED
@@ -0,0 +1,113 @@
1
+ import { elapsedWith, rateWith, retime, timeWith } from './clock.js';
2
+ /**
3
+ * A mix's own rate, as a clock: mix time as a function of host time, which here means the host's
4
+ * timestamp less what `rebase` took out. Each entry is the clock from the host time it took effect,
5
+ * oldest first, and the last is in force; under `history` the entries a read back may reach are
6
+ * kept, and without it only the last. Entries are never changed once logged, so a copy of the log
7
+ * stays as it was.
8
+ */
9
+ export class Pace {
10
+ keeps;
11
+ log;
12
+ constructor(keeps, log = [{ anchorNow: 0, anchorElapsed: 0, rate: 1, ramp: null }]) {
13
+ this.keeps = keeps;
14
+ this.log = log;
15
+ }
16
+ get clock() {
17
+ return this.log[this.log.length - 1];
18
+ }
19
+ /** Set before the first sync, the clock waits to be anchored there, reading host time until then. */
20
+ get waiting() {
21
+ return Number.isNaN(this.clock.anchorNow);
22
+ }
23
+ /** Mix time at host time `u`, anchoring a clock set before the first sync there. */
24
+ sync(u) {
25
+ if (this.waiting)
26
+ this.log = [{ ...this.clock, anchorNow: u, anchorElapsed: u }];
27
+ return this.reading(u);
28
+ }
29
+ /** What the mix clock reads at host time `u`, by the clock in force then. */
30
+ reading(u) {
31
+ if (this.waiting)
32
+ return u;
33
+ const log = this.log;
34
+ let i = log.length - 1;
35
+ while (i > 0 && log[i].anchorNow > u)
36
+ i--;
37
+ return elapsedWith(log[i], u);
38
+ }
39
+ /**
40
+ * The mix time of something pinned to host time `u`, seen from host time `now`: what the clock
41
+ * will read there, or Infinity where `u` lies ahead and the clock stands still before it, since
42
+ * the moment the mix reaches is then not `u`'s alone.
43
+ */
44
+ at(u, now) {
45
+ if (!(u > now) || this.waiting)
46
+ return this.reading(u);
47
+ const c = this.clock;
48
+ const r = c.ramp;
49
+ const still = r === null ? c.rate <= 0 : r.to <= 0 && u > c.anchorNow + r.over;
50
+ return still ? Number.POSITIVE_INFINITY : elapsedWith(c, u);
51
+ }
52
+ /** The earliest host time the mix clock reads `t`; Infinity where it never will. */
53
+ timeOf(t) {
54
+ if (this.waiting)
55
+ return t;
56
+ const log = this.log;
57
+ let i = 0;
58
+ while (i + 1 < log.length && t > log[i + 1].anchorElapsed)
59
+ i++;
60
+ return timeWith(log[i], t);
61
+ }
62
+ /** The rate at host time `u`; before the first sync, the rate the first frame starts at. */
63
+ rateAt(u) {
64
+ const c = this.clock;
65
+ if (this.waiting || Number.isNaN(u))
66
+ return c.ramp === null ? c.rate : c.ramp.from;
67
+ return rateWith(c, u);
68
+ }
69
+ /** Whether the mix clock stands still from host time `u` on, short of another change. */
70
+ stopped(u) {
71
+ const c = this.clock;
72
+ const r = c.ramp;
73
+ if (r === null)
74
+ return c.rate === 0;
75
+ return r.to === 0 && !this.waiting && u >= c.anchorNow + r.over;
76
+ }
77
+ /**
78
+ * Sets the rate from host time `u`, at once or over `over` host ms, the way a handle sets a
79
+ * voice's. Under history, lets go of the entries a read no longer reaches: those over before mix
80
+ * time `reach`.
81
+ */
82
+ change(u, rate, over, reach) {
83
+ const was = this.clock;
84
+ const next = { ...was };
85
+ if (Number.isNaN(u) || this.waiting) {
86
+ const from = this.rateAt(Number.NaN);
87
+ next.ramp = over > 0 && rate !== from ? { from, to: rate, over } : null;
88
+ next.rate = rate;
89
+ next.anchorNow = Number.NaN;
90
+ this.log = [next];
91
+ return;
92
+ }
93
+ retime(next, u, rate, over);
94
+ const log = this.log;
95
+ if (this.keeps && was.anchorNow < u)
96
+ log.push(next);
97
+ else
98
+ log[log.length - 1] = next;
99
+ let drop = 0;
100
+ while (drop + 1 < log.length && log[drop + 1].anchorElapsed <= reach)
101
+ drop++;
102
+ if (drop > 0)
103
+ log.splice(0, drop);
104
+ }
105
+ /** A copy holding the clocks that took effect before host time `u`, for a read back to then. */
106
+ until(u) {
107
+ const log = this.log;
108
+ let n = 1;
109
+ while (n < log.length && log[n].anchorNow < u)
110
+ n++;
111
+ return new Pace(this.keeps, log.slice(0, n));
112
+ }
113
+ }
package/dist/patch.d.ts CHANGED
@@ -2,11 +2,11 @@ import { type LerpInto } from './channels.js';
2
2
  import { type Curve } from './easing.js';
3
3
  import type { Channel, Easing, Keyframe, Kit, Patch, Setting } from './types.js';
4
4
  /**
5
- * What `patch` takes besides its period and its function.
5
+ * What `patch` takes besides its duration and its function.
6
6
  *
7
7
  * @category patch
8
8
  */
9
- export interface PatchOptions<I, O, S> {
9
+ export interface PatchOptions<I, O, S, H = unknown> {
10
10
  /** The channels this patch contributes to. Every key `at` sets, and no others. Default: `kit`'s. */
11
11
  writes?: readonly (keyof O)[];
12
12
  /** The channels this patch was written against, which `cue` checks a mix's kit against. */
@@ -14,16 +14,16 @@ export interface PatchOptions<I, O, S> {
14
14
  /** The fields of `setting.host` this patch reads, which `cue` checks the mix's host for. */
15
15
  reads?: readonly string[];
16
16
  state?(subject: I): S;
17
- step?(state: S, dt: number, subject: I, setting: Setting<S>): void;
17
+ step?(state: S, dt: number, subject: I, setting: Setting<S, H>): void;
18
18
  }
19
19
  /**
20
20
  * The procedural form. Flicker, roving, a hash walk, a starter strike.
21
21
  *
22
22
  * @category patch
23
23
  */
24
- export declare function patch<I, O, S = void>(period: number, at: (phase: number, subject: I, setting: Setting<S>) => Partial<O>, opts: PatchOptions<I, O, S>): Patch<I, O, S>;
24
+ export declare function patch<I, O, S = void, H = unknown>(duration: number, at: (phase: number, subject: I, setting: Setting<S, H>) => Partial<O>, opts: PatchOptions<I, O, S, H>): Patch<I, O, S, H>;
25
25
  /**
26
- * What `keys` takes besides its period and its stops.
26
+ * What `keys` takes besides its duration and its stops.
27
27
  *
28
28
  * @category patch
29
29
  */
@@ -32,7 +32,7 @@ export interface KeysOptions<O> {
32
32
  ease?: Easing;
33
33
  /** A curve for one channel, where it differs from the rest. */
34
34
  easeBy?: (channel: keyof O) => Easing | undefined;
35
- /** Milliseconds one channel waits before it starts moving, within the period. */
35
+ /** Milliseconds one channel waits before it starts moving, within the duration. */
36
36
  delayBy?: (channel: keyof O) => number;
37
37
  /**
38
38
  * How one channel interpolates, where it should differ from the channel's own `lerp`. A mix hands
@@ -52,19 +52,58 @@ interface Point {
52
52
  }
53
53
  type Lerp = (a: never, b: never, u: number) => unknown;
54
54
  /** One channel of a stop list, sorted and resolved once, so a read is a binary search. */
55
- interface Track {
55
+ export interface Track {
56
56
  channel: string;
57
57
  /** Every stop carrying this channel, by phase. */
58
58
  all: Point[];
59
59
  /** The same without stops at 0, which a `from: 'current'` base replaces. */
60
60
  tail: Point[];
61
+ /** The phases of `all`, which the search reads, and of `tail`, made on its first search. */
62
+ ats: number[];
63
+ tailAts: number[] | null;
61
64
  delay: number;
62
65
  lerp: ((a: never, b: never, u: number) => unknown) | undefined;
63
66
  }
64
67
  export interface Built {
65
68
  tracks: Track[];
66
- period: number;
69
+ duration: number;
67
70
  }
71
+ /** What `segment` found: nothing to read, one value, or a segment to interpolate along. */
72
+ export declare const NOTHING = 0;
73
+ export declare const AT = 1;
74
+ export declare const BETWEEN = 2;
75
+ /**
76
+ * Where `segment` leaves a track's reading: the value, or the segment's two ends, how far along it
77
+ * is before and after its easing, whether it leaves `base`, its length and its easing. Read it
78
+ * before anything else reads a track.
79
+ */
80
+ export declare const seg: {
81
+ a: unknown;
82
+ b: unknown;
83
+ u: number;
84
+ eased: number;
85
+ first: boolean;
86
+ len: number;
87
+ ease: Curve | undefined;
88
+ };
89
+ /** A track's phase once its channel's own delay applies. */
90
+ export declare function shifted(track: Track, phase: number, duration: number): number;
91
+ /** A phase once a channel's delay of `delay` ms in a duration of `duration` applies. */
92
+ export declare function shift(delay: number, phase: number, duration: number): number;
93
+ /**
94
+ * Where `phase` falls among `n` stop phases `ats[off + k * stride]`, in order: -2 for no stops, -1
95
+ * at or before the first, `n` at or past the last, and otherwise the first stop past it, where the
96
+ * segment from the one before ends. With `segment`'s, the one copy of the search.
97
+ */
98
+ export declare function locate(ats: ArrayLike<number>, off: number, stride: number, n: number, phase: number): number;
99
+ /** How far along a segment from phase `a` to `b` a phase is, before easing. */
100
+ export declare function fraction(phase: number, a: number, b: number): number;
101
+ /**
102
+ * Finds where one track is at a phase, into `seg`: the one copy of the search, which `read` and a
103
+ * lane folding stops straight into a subject's values both make. `base` stands in for the value at
104
+ * phase 0, which is how `from: 'current'` starts a voice wherever the subject already is.
105
+ */
106
+ export declare function segment(track: Track, phase: number, base: unknown): number;
68
107
  /**
69
108
  * Arrays a reader lets keyed reads interpolate into, by track. Each only ever holds what a read made,
70
109
  * never a stop's or a base's, and a delta read through it is good until the next read through it.
@@ -83,14 +122,16 @@ export declare function intosOf(built: Built, channels: readonly Channel<unknown
83
122
  export declare function readKeys(built: Built, phase: number, out: Record<string, unknown>, base?: Record<string, unknown>, lerps?: readonly (Lerp | undefined)[],
84
123
  /** Per channel, how fast a retargeted subject was moving, units per ms. */
85
124
  slopes?: Record<string, unknown>, intos?: readonly (LerpInto | undefined)[], scratch?: Scratch): Record<string, unknown>;
125
+ /** A patch's `duration`, or the deprecated `period` where only that is set. */
126
+ export declare const durationOf: <I, O, S>(p: Patch<I, O, S>) => number;
86
127
  /** The built form of a patch's stops, made on first ask for a patch `keys()` did not build. */
87
128
  export declare function builtOf<I, O, S>(p: Patch<I, O, S>): Built;
88
129
  /** Evaluates a stop list into a fresh delta. */
89
- export declare function evalKeys<O>(stops: readonly Keyframe<O>[], writes: readonly (keyof O)[], phase: number, period: number, opts: KeysOptions<O>, base?: Partial<O>): Partial<O>;
130
+ export declare function evalKeys<O>(stops: readonly Keyframe<O>[], writes: readonly (keyof O)[], phase: number, duration: number, opts: KeysOptions<O>, base?: Partial<O>): Partial<O>;
90
131
  /**
91
132
  * The declarative form. klieg's transition sugar and wod's keyframes.
92
133
  *
93
134
  * @category patch
94
135
  */
95
- export declare function keys<I, O>(period: number, stops: readonly Keyframe<O>[], opts?: KeysOptions<O>): Patch<I, O, void>;
136
+ export declare function keys<I, O>(duration: number, stops: readonly Keyframe<O>[], opts?: KeysOptions<O>): Patch<I, O, void>;
96
137
  export {};
package/dist/patch.js CHANGED
@@ -5,13 +5,14 @@ import { curve } from './easing.js';
5
5
  *
6
6
  * @category patch
7
7
  */
8
- export function patch(period, at, opts) {
8
+ export function patch(duration, at, opts) {
9
9
  const writes = opts.writes ?? (opts.kit ? Object.keys(opts.kit) : undefined);
10
10
  if (writes === undefined)
11
11
  throw new Error('blits: a patch needs writes, or a kit to take them from');
12
12
  return {
13
13
  form: 'fn',
14
- period,
14
+ duration,
15
+ period: duration,
15
16
  writes,
16
17
  kit: opts.kit,
17
18
  reads: opts.reads,
@@ -37,7 +38,7 @@ function interpolate(a, b, u) {
37
38
  }
38
39
  return u < 0.5 ? a : b;
39
40
  }
40
- function build(stops, writes, period, opts) {
41
+ function build(stops, writes, duration, opts) {
41
42
  const fallback = opts.ease === undefined ? undefined : curve(opts.ease);
42
43
  const tracks = [];
43
44
  for (const channel of writes) {
@@ -50,72 +51,165 @@ function build(stops, writes, period, opts) {
50
51
  continue;
51
52
  all.push({
52
53
  at: stop.at,
53
- value,
54
+ // A copy, so every reader sees the stop as it was built: a lane may hold its numbers.
55
+ value: Array.isArray(value)
56
+ ? [...value]
57
+ : ArrayBuffer.isView(value)
58
+ ? value.slice()
59
+ : value,
54
60
  ease: stop.ease === undefined ? trackEase : curve(stop.ease),
55
61
  });
56
62
  }
57
63
  all.sort((x, y) => x.at - y.at);
64
+ const tail = all.filter((pt) => pt.at !== 0);
58
65
  tracks.push({
59
66
  channel: channel,
60
67
  all,
61
- tail: all.filter((pt) => pt.at !== 0),
68
+ tail,
69
+ ats: all.map((pt) => pt.at),
70
+ tailAts: null,
62
71
  delay: opts.delayBy?.(channel) ?? 0,
63
72
  lerp: opts.lerpBy?.(channel) ?? opts.kit?.[channel]?.lerp,
64
73
  });
65
74
  }
66
- return { tracks, period };
75
+ return { tracks, duration };
67
76
  }
77
+ /** What `segment` found: nothing to read, one value, or a segment to interpolate along. */
78
+ export const NOTHING = 0;
79
+ export const AT = 1;
80
+ export const BETWEEN = 2;
68
81
  /**
69
- * Reads one track at a phase. `base` stands in for the value at phase 0, which is how
70
- * `from: 'current'` starts a voice wherever the subject already is.
82
+ * Where `segment` leaves a track's reading: the value, or the segment's two ends, how far along it
83
+ * is before and after its easing, whether it leaves `base`, its length and its easing. Read it
84
+ * before anything else reads a track.
71
85
  */
72
- function read(track, phase, base, lerp,
73
- /** Units per ms, for a retarget's first segment. */
74
- slope, period, into, reuse) {
75
- const pts = base === undefined ? track.all : track.tail;
76
- const o = base === undefined ? 0 : 1;
77
- const n = pts.length + o;
86
+ export const seg = {
87
+ a: undefined,
88
+ b: undefined,
89
+ u: 0,
90
+ eased: 0,
91
+ first: false,
92
+ len: 0,
93
+ ease: undefined,
94
+ };
95
+ /** A track's phase once its channel's own delay applies. */
96
+ export function shifted(track, phase, duration) {
97
+ return shift(track.delay, phase, duration);
98
+ }
99
+ /** A phase once a channel's delay of `delay` ms in a duration of `duration` applies. */
100
+ export function shift(delay, phase, duration) {
101
+ return delay === 0 || duration === 0 ? phase : Math.max(0, (phase * duration - delay) / duration);
102
+ }
103
+ /**
104
+ * Where `phase` falls among `n` stop phases `ats[off + k * stride]`, in order: -2 for no stops, -1
105
+ * at or before the first, `n` at or past the last, and otherwise the first stop past it, where the
106
+ * segment from the one before ends. With `segment`'s, the one copy of the search.
107
+ */
108
+ export function locate(ats, off, stride, n, phase) {
78
109
  if (n === 0)
79
- return undefined;
80
- const firstAt = o === 1 ? 0 : pts[0].at;
81
- if (phase <= firstAt)
82
- return o === 1 ? base : pts[0].value;
83
- const last = pts[pts.length - 1];
84
- if (last === undefined || phase >= last.at)
85
- return last === undefined ? base : last.value;
86
- // The first point at or past phase; the segment ends there.
87
- let lo = 1;
88
- let hi = n - 1;
110
+ return -2;
111
+ if (phase <= ats[off])
112
+ return -1;
113
+ if (phase >= ats[off + (n - 1) * stride])
114
+ return n;
115
+ return firstPast(ats, off, stride, 1, n - 1, phase);
116
+ }
117
+ /** The first of stops `lo` to `hi` whose phase is at or past `phase`, the last known to be. */
118
+ function firstPast(ats, off, stride, lo, hi, phase) {
89
119
  while (lo < hi) {
90
120
  const mid = (lo + hi) >> 1;
91
- if (pts[mid - o].at >= phase)
121
+ if (ats[off + mid * stride] >= phase)
92
122
  hi = mid;
93
123
  else
94
124
  lo = mid + 1;
95
125
  }
126
+ return lo;
127
+ }
128
+ /** How far along a segment from phase `a` to `b` a phase is, before easing. */
129
+ export function fraction(phase, a, b) {
130
+ return (phase - a) / (b - a);
131
+ }
132
+ /**
133
+ * Finds where one track is at a phase, into `seg`: the one copy of the search, which `read` and a
134
+ * lane folding stops straight into a subject's values both make. `base` stands in for the value at
135
+ * phase 0, which is how `from: 'current'` starts a voice wherever the subject already is.
136
+ */
137
+ export function segment(track, phase, base) {
138
+ let lo;
139
+ const o = base === undefined ? 0 : 1;
140
+ const pts = o === 0 ? track.all : track.tail;
141
+ if (o === 0) {
142
+ const n = pts.length;
143
+ lo = locate(track.ats, 0, 1, n, phase);
144
+ if (lo === -2)
145
+ return NOTHING;
146
+ if (lo === -1 || lo === n) {
147
+ seg.a = pts[lo === -1 ? 0 : n - 1].value;
148
+ return AT;
149
+ }
150
+ }
151
+ else {
152
+ // Stop 0 is `base`, at phase 0, and stop k is the tail's k - 1.
153
+ const n = pts.length + 1;
154
+ if (phase <= 0) {
155
+ seg.a = base;
156
+ return AT;
157
+ }
158
+ const last = pts[pts.length - 1];
159
+ if (last === undefined || phase >= last.at) {
160
+ seg.a = last === undefined ? base : last.value;
161
+ return AT;
162
+ }
163
+ track.tailAts ??= pts.map((pt) => pt.at);
164
+ lo = firstPast(track.tailAts, -1, 1, 1, n - 1, phase);
165
+ }
96
166
  const b = pts[lo - o];
97
167
  const aAt = lo - 1 < o ? 0 : pts[lo - 1 - o].at;
98
- const aValue = lo - 1 < o ? base : pts[lo - 1 - o].value;
99
- const u = (phase - aAt) / (b.at - aAt);
168
+ const u = fraction(phase, aAt, b.at);
169
+ // Before any of `seg` is written, so an easing that reads stops itself cannot overwrite it.
100
170
  const eased = b.ease ? b.ease(u) : u;
171
+ seg.a = lo - 1 < o ? base : pts[lo - 1 - o].value;
172
+ seg.b = b.value;
173
+ seg.u = u;
174
+ seg.eased = eased;
175
+ seg.first = lo - 1 < o;
176
+ seg.len = b.at - aAt;
177
+ seg.ease = b.ease;
178
+ return BETWEEN;
179
+ }
180
+ /** Reads one track at a phase; `base` as `segment` takes it. */
181
+ function read(track, phase, base, lerp,
182
+ /** Units per ms, for a retarget's first segment. */
183
+ slope, duration, into, reuse) {
184
+ const found = segment(track, phase, base);
185
+ if (found === NOTHING)
186
+ return undefined;
187
+ if (found === AT)
188
+ return seg.a;
189
+ const aValue = seg.a;
190
+ const bValue = seg.b;
191
+ const u = seg.u;
192
+ const eased = seg.eased;
193
+ const first = seg.first;
194
+ const len = seg.len;
195
+ const ease = seg.ease;
101
196
  let value;
102
197
  if (into !== undefined) {
103
- value = into(reuse, aValue, b.value, eased);
198
+ value = into(reuse, aValue, bValue, eased);
104
199
  lastRead.wrote = value;
105
200
  }
106
201
  else {
107
202
  const by = track.lerp ?? lerp;
108
- value = by ? by(aValue, b.value, eased) : interpolate(aValue, b.value, eased);
203
+ value = by ? by(aValue, bValue, eased) : interpolate(aValue, bValue, eased);
109
204
  }
110
205
  // Leaving a retarget's base, bend the first segment so it starts at the slope the subject had:
111
206
  // add c·u(1−u)², which is 0 at both ends, flat at the end, and fixes the slope at the start.
112
- if (slope === undefined || lo - 1 >= o)
207
+ if (slope === undefined || !first)
113
208
  return value;
114
- const len = b.at - aAt;
115
- const e0 = b.ease ? b.ease(1e-6) / 1e-6 : 1;
116
- const bend = (s, a, z) => (s * period * len - (z - a) * e0) * u * (1 - u) * (1 - u);
209
+ const e0 = ease ? ease(1e-6) / 1e-6 : 1;
210
+ const bend = (s, a, z) => (s * duration * len - (z - a) * e0) * u * (1 - u) * (1 - u);
117
211
  if (typeof value === 'number' && typeof slope === 'number')
118
- return value + bend(slope, aValue, b.value);
212
+ return value + bend(slope, aValue, bValue);
119
213
  if (Array.isArray(value) && Array.isArray(slope)) {
120
214
  const out = value === lastRead.wrote ? value : new Array(value.length);
121
215
  for (let i = 0; i < value.length; i++) {
@@ -123,7 +217,7 @@ slope, period, into, reuse) {
123
217
  const s = slope[i];
124
218
  out[i] =
125
219
  typeof v === 'number' && typeof s === 'number'
126
- ? v + bend(s, aValue[i], b.value[i])
220
+ ? v + bend(s, aValue[i], bValue[i])
127
221
  : v;
128
222
  }
129
223
  return out;
@@ -150,15 +244,13 @@ export function intosOf(built, channels) {
150
244
  export function readKeys(built, phase, out, base, lerps,
151
245
  /** Per channel, how fast a retargeted subject was moving, units per ms. */
152
246
  slopes, intos, scratch) {
153
- const period = built.period;
247
+ const duration = built.duration;
154
248
  for (let i = 0; i < built.tracks.length; i++) {
155
249
  const track = built.tracks[i];
156
- const delay = track.delay;
157
- const shifted = delay === 0 || period === 0 ? phase : Math.max(0, (phase * period - delay) / period);
158
250
  const perMs = slopes?.[track.channel];
159
251
  const into = scratch === undefined ? undefined : intos?.[i];
160
252
  lastRead.wrote = undefined;
161
- const value = read(track, shifted, base?.[track.channel], lerps?.[i], period === 0 ? undefined : perMs, period, into, into === undefined ? undefined : scratch[i]);
253
+ const value = read(track, shifted(track, phase, duration), base?.[track.channel], lerps?.[i], duration === 0 ? undefined : perMs, duration, into, into === undefined ? undefined : scratch[i]);
162
254
  if (into !== undefined && value === lastRead.wrote)
163
255
  scratch[i] = value;
164
256
  if (value !== undefined)
@@ -168,31 +260,34 @@ slopes, intos, scratch) {
168
260
  }
169
261
  return out;
170
262
  }
263
+ /** A patch's `duration`, or the deprecated `period` where only that is set. */
264
+ export const durationOf = (p) => p.duration ?? p.period ?? 0;
171
265
  const cache = new WeakMap();
172
266
  /** The built form of a patch's stops, made on first ask for a patch `keys()` did not build. */
173
267
  export function builtOf(p) {
174
268
  let held = cache.get(p);
175
269
  if (held === undefined) {
176
- held = build(p.keys, p.writes, p.period, keysOptionsOf(p) ?? {});
270
+ held = build(p.keys, p.writes, durationOf(p), keysOptionsOf(p) ?? {});
177
271
  cache.set(p, held);
178
272
  }
179
273
  return held;
180
274
  }
181
275
  /** Evaluates a stop list into a fresh delta. */
182
- export function evalKeys(stops, writes, phase, period, opts, base) {
183
- return readKeys(build(stops, writes, period, opts), phase, {}, base);
276
+ export function evalKeys(stops, writes, phase, duration, opts, base) {
277
+ return readKeys(build(stops, writes, duration, opts), phase, {}, base);
184
278
  }
185
279
  /**
186
280
  * The declarative form. klieg's transition sugar and wod's keyframes.
187
281
  *
188
282
  * @category patch
189
283
  */
190
- export function keys(period, stops, opts = {}) {
284
+ export function keys(duration, stops, opts = {}) {
191
285
  const writes = [...new Set(stops.flatMap((s) => Object.keys(s.delta)))];
192
- const made = build(stops, writes, period, opts);
286
+ const made = build(stops, writes, duration, opts);
193
287
  const p = {
194
288
  form: 'keys',
195
- period,
289
+ duration,
290
+ period: duration,
196
291
  writes,
197
292
  kit: opts.kit,
198
293
  keys: stops,
@@ -0,0 +1,20 @@
1
+ import type { Mixer } from './mixer.js';
2
+ import type { Placement, VoiceSpec } from './types.js';
3
+ import { type Voice } from './voice.js';
4
+ /** What a voice's own owner's clock reads now: the mix clock for a voice no owner holds. */
5
+ export declare function localNow<I, O>(mix: Mixer<I, O>, voice: Voice<I, O>): number;
6
+ /** A voice's start as mix time. */
7
+ export declare function startOf<I, O>(voice: Voice<I, O>): number;
8
+ /** Mix time at host time `u`, for something pinned there; Infinity while the mix may never get there. */
9
+ export declare function mixAt<I, O>(mix: Mixer<I, O>, u: number): number;
10
+ export declare function pin<I, O>(mix: Mixer<I, O>, voice: Voice<I, O>, u: number): void;
11
+ /** Puts each pending voice's start, given in host time, where the mix's rate now puts it. */
12
+ export declare function repin<I, O>(mix: Mixer<I, O>): void;
13
+ /** Refuses a placement that names a mark twice on one side, or that waits on itself. */
14
+ export declare function checkPlacement<I, O>(mix: Mixer<I, O>, spec: VoiceSpec<I, O>, anchor: Placement, owner: Voice<I, O> | null): void;
15
+ /**
16
+ * Fixes every anchored voice's start and out from what its anchors answer now. A start is fixed
17
+ * while the voice waits and an out until its fade begins; a target that has left keeps the time it
18
+ * last gave. Repeated so a chain of anchors settles in one sync.
19
+ */
20
+ export declare function place<I, O>(mix: Mixer<I, O>): void;