@msb235/blits 0.5.0 → 0.7.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 (99) hide show
  1. package/CHANGELOG.md +163 -0
  2. package/README.md +32 -8
  3. package/dist/bare.js +7 -1
  4. package/dist/book.d.ts +8 -1
  5. package/dist/book.js +35 -4
  6. package/dist/chain.d.ts +4 -3
  7. package/dist/chain.js +10 -6
  8. package/dist/channels.d.ts +12 -19
  9. package/dist/channels.js +18 -118
  10. package/dist/clock.d.ts +12 -5
  11. package/dist/clock.js +13 -5
  12. package/dist/closed.d.ts +15 -0
  13. package/dist/closed.js +144 -0
  14. package/dist/color.d.ts +61 -0
  15. package/dist/color.js +229 -0
  16. package/dist/columns.js +2 -1
  17. package/dist/crowd.d.ts +4 -4
  18. package/dist/crowd.js +11 -8
  19. package/dist/cue.d.ts +2 -0
  20. package/dist/cue.js +78 -27
  21. package/dist/due.js +5 -2
  22. package/dist/fade.d.ts +13 -0
  23. package/dist/fade.js +55 -4
  24. package/dist/fill.d.ts +1 -1
  25. package/dist/fill.js +17 -10
  26. package/dist/fit.d.ts +144 -0
  27. package/dist/fit.js +181 -0
  28. package/dist/fold.d.ts +3 -3
  29. package/dist/fold.js +25 -18
  30. package/dist/gather.d.ts +6 -0
  31. package/dist/gather.js +18 -0
  32. package/dist/handle.d.ts +27 -2
  33. package/dist/handle.js +60 -11
  34. package/dist/held.js +7 -3
  35. package/dist/history.d.ts +15 -2
  36. package/dist/history.js +54 -9
  37. package/dist/hosts.d.ts +2 -2
  38. package/dist/hosts.js +19 -6
  39. package/dist/index.d.ts +10 -2
  40. package/dist/index.js +6 -1
  41. package/dist/lane.d.ts +4 -4
  42. package/dist/lane.js +5 -5
  43. package/dist/lanes.d.ts +32 -9
  44. package/dist/lanes.js +53 -24
  45. package/dist/locus.d.ts +3 -3
  46. package/dist/locus.js +1 -1
  47. package/dist/marks.d.ts +3 -0
  48. package/dist/marks.js +81 -13
  49. package/dist/meet.d.ts +3 -2
  50. package/dist/meet.js +15 -3
  51. package/dist/mixer.d.ts +55 -35
  52. package/dist/mixer.js +173 -83
  53. package/dist/motion.d.ts +2 -199
  54. package/dist/motion.js +2 -652
  55. package/dist/motions.d.ts +266 -0
  56. package/dist/motions.js +724 -0
  57. package/dist/move.d.ts +1 -1
  58. package/dist/move.js +27 -11
  59. package/dist/owner.d.ts +2 -2
  60. package/dist/owner.js +5 -5
  61. package/dist/pace.d.ts +2 -0
  62. package/dist/pace.js +8 -0
  63. package/dist/pack.d.ts +31 -0
  64. package/dist/pack.js +65 -0
  65. package/dist/paging.d.ts +63 -0
  66. package/dist/paging.js +110 -0
  67. package/dist/patch.d.ts +3 -0
  68. package/dist/patch.js +2 -0
  69. package/dist/place.js +74 -25
  70. package/dist/project.d.ts +7 -4
  71. package/dist/project.js +117 -78
  72. package/dist/pull.js +4 -3
  73. package/dist/qualify.js +4 -2
  74. package/dist/revive.d.ts +58 -0
  75. package/dist/revive.js +158 -0
  76. package/dist/sample.d.ts +1 -1
  77. package/dist/sample.js +6 -1
  78. package/dist/seek.d.ts +3 -0
  79. package/dist/seek.js +311 -0
  80. package/dist/signals.d.ts +3 -2
  81. package/dist/signals.js +12 -6
  82. package/dist/spans.d.ts +29 -0
  83. package/dist/spans.js +165 -0
  84. package/dist/strays.d.ts +1 -1
  85. package/dist/strays.js +21 -0
  86. package/dist/tape.d.ts +21 -0
  87. package/dist/tape.js +65 -0
  88. package/dist/ticker.d.ts +73 -0
  89. package/dist/ticker.js +115 -0
  90. package/dist/transport.d.ts +121 -0
  91. package/dist/transport.js +271 -0
  92. package/dist/types.d.ts +427 -50
  93. package/dist/unpage.d.ts +8 -0
  94. package/dist/unpage.js +163 -0
  95. package/dist/voice.d.ts +78 -12
  96. package/dist/voice.js +126 -12
  97. package/dist/weigh.js +2 -2
  98. package/docs/schema.html +140 -48
  99. package/package.json +2 -1
package/dist/project.js CHANGED
@@ -1,85 +1,60 @@
1
1
  import { ownBlends } from './blend.js';
2
2
  import { index } from './chain.js';
3
- import { copyHeld, last } from './history.js';
4
- import { readingAt } from './marks.js';
3
+ import { copyHeld, last, leftAt } from './history.js';
4
+ import { Mixer } from './mixer.js';
5
5
  import { move } from './move.js';
6
6
  import { heldByInput, ownerReading, relink } from './owner.js';
7
7
  import { pin } from './place.js';
8
8
  import { reading } from './reading.js';
9
9
  import { Store } from './store.js';
10
+ import { hostAt } from './tape.js';
11
+ import { Transport } from './transport.js';
12
+ import { cover } from './unpage.js';
10
13
  import { unreached } from './unreached.js';
11
- /** A projection to `timestamp` through `c`, a mixer of its own the mix has just made. */
12
- export function project(mix, c, timestamp) {
13
- const u = timestamp - mix.offset;
14
- const pace = mix.pace;
15
- const t = readingAt(mix, u);
16
- c.projecting = true;
17
- c.pose = mix.pose;
18
- c.offset = mix.offset;
19
- c.u = u;
20
- c.wantsPose = mix.wantsPose;
21
- c.nextId = mix.nextId;
22
- c.reducedNow = mix.reducedNow;
23
- if (Number.isNaN(mix.now) || t >= mix.now) {
24
- c.now = mix.now;
25
- c.pace = pace === null ? null : pace.until(Number.POSITIVE_INFINITY);
26
- c.cued = mix.cued
27
- .filter((v) => v.state !== 'done')
28
- .map((v) => {
29
- const copy = v.copy((subject) => carry(mix, v, subject));
30
- const at = mix.pins?.get(v);
31
- if (at !== undefined)
32
- pin(c, copy, at);
33
- return copy;
34
- });
35
- ownBlends(c.cued);
36
- if (mix.owners !== null)
37
- relink(c.cued);
38
- c.announced = mix.announced.map((a) => ({ ...a }));
39
- count(c);
40
- move(c, t);
41
- }
42
- else {
43
- const history = mix.opts.history;
14
+ /**
15
+ * Every mix on a transport read at mix time `t`, through copies of them on a transport of their
16
+ * own, moved together so an anchor across mixes answers as it would live.
17
+ */
18
+ export function projectAll(transport, t) {
19
+ const ahead = Number.isNaN(transport.now) || t >= transport.now;
20
+ const tape = transport.tape;
21
+ const next = ahead && tape !== undefined ? tape.timestampAt(tape.undoDepth()) : undefined;
22
+ if (next !== undefined && next <= t)
23
+ throw new Error(`blits: the tape holds calls at ${next} that a read ahead to ${t} cannot play; seek there to see them`);
24
+ if (!ahead) {
25
+ const history = transport.history;
44
26
  if (!history)
45
27
  throw new Error('blits: reading back needs a mix made with history');
46
- if (t < mix.now - history.ms)
47
- throw new Error(`blits: ${timestamp} is older than this mix's history reaches`);
48
- c.now = t;
49
- c.pace = pace === null ? null : pace.until(u);
50
- c.backward = true;
51
- const was = last(mix.hostLog, t, true);
52
- const host = mix.opts.host;
53
- const then = was && typeof host === 'object' && host !== null
54
- ? Object.assign(Object.create(host), was.fields)
55
- : undefined;
56
- if (was)
57
- c.hostThen = was;
58
- c.announced = mix.announced.filter((a) => a.made < t).map((a) => ({ ...a }));
59
- c.cued = [...mix.cued, ...mix.gone]
60
- .filter((v) => v.cuedAt <= t && v.doneAt > t)
61
- .sort((a, b) => a.id - b.id)
62
- .map((v) => {
63
- const log = v.log;
64
- const copy = v.copy((subject) => recall(mix, v, subject, t), last(log, t, (e) => e.sync) ?? log[0]);
65
- if (then !== undefined)
66
- copy.setting.host = then;
67
- return copy;
28
+ cover(transport, t);
29
+ }
30
+ const u = hostAt(transport, t);
31
+ const pace = transport.pace;
32
+ const c = new Transport(undefined, true);
33
+ c.offset = transport.offset;
34
+ c.u = u;
35
+ c.nextId = transport.nextId;
36
+ c.now = ahead ? transport.now : t;
37
+ c.pace = pace === null ? null : pace.until(ahead ? Number.POSITIVE_INFINITY : u);
38
+ c.announced = (ahead ? transport.announced : transport.announced.filter((a) => a.made < t)).map((a) => ({ ...a }));
39
+ const copies = new Map();
40
+ for (const mix of transport.members) {
41
+ const copy = new Mixer(mix.kit, {
42
+ ...mix.opts,
43
+ history: undefined,
44
+ lanes: false,
45
+ transport: c,
68
46
  });
69
- ownBlends(c.cued);
70
- if (mix.owners !== null)
71
- relink(c.cued);
72
- for (const copy of c.cued)
73
- copy.state =
74
- (copy.owner === null ? t : ownerReading(copy.owner, t)) < copy.start
75
- ? 'pending'
76
- : copy.out && copy.out.at <= t
77
- ? 'fading'
78
- : copy.holdsAfter && copy.elapsedAt(t) >= copy.span + copy.latest
79
- ? 'held'
80
- : 'live';
81
- count(c);
47
+ copy.slot = mix.slot;
48
+ copy.projecting = true;
49
+ copies.set(mix, copy);
50
+ if (ahead)
51
+ copyAhead(mix, copy);
52
+ else
53
+ copyBack(mix, copy, t);
82
54
  }
55
+ if (ahead)
56
+ for (const copy of copies.values())
57
+ move(copy, t);
83
58
  const read = (f) => {
84
59
  reading.live = false;
85
60
  try {
@@ -90,14 +65,78 @@ export function project(mix, c, timestamp) {
90
65
  }
91
66
  };
92
67
  return {
93
- timestamp,
94
- probe: (subject, out) => read(() => c.fold(subject, out)),
95
- assess: (subject) => read(() => {
96
- c.fold(subject);
97
- return doubts(c, subject);
98
- }),
68
+ timestamp: t,
69
+ of(mix) {
70
+ const copy = copies.get(mix);
71
+ if (copy === undefined)
72
+ throw new Error('blits: that mix is not on this transport');
73
+ return {
74
+ timestamp: t,
75
+ probe: (subject, out) => read(() => copy.fold(subject, out)),
76
+ assess: (subject) => read(() => {
77
+ copy.fold(subject);
78
+ return doubts(copy, subject);
79
+ }),
80
+ };
81
+ },
99
82
  };
100
83
  }
84
+ /** A mix's copy for a read ahead: its voices as they stand, each subject carried from the live one. */
85
+ function copyAhead(mix, c) {
86
+ c.pose = mix.pose;
87
+ c.wantsPose = mix.wantsPose;
88
+ c.reducedNow = mix.reducedNow;
89
+ c.cued = mix.cued
90
+ .filter((v) => v.state !== 'done')
91
+ .map((v) => {
92
+ const copy = v.copy((subject) => carry(mix, v, subject));
93
+ const at = mix.pins?.get(v);
94
+ if (at !== undefined)
95
+ pin(c, copy, at);
96
+ return copy;
97
+ });
98
+ ownBlends(c.cued);
99
+ if (mix.owners !== null)
100
+ relink(c.cued);
101
+ count(c);
102
+ }
103
+ /** A mix's copy for a read back to `t`: its voices then, from the controls and copies kept. */
104
+ function copyBack(mix, c, t) {
105
+ c.pose = mix.pose;
106
+ c.wantsPose = mix.wantsPose;
107
+ c.reducedNow = mix.reducedNow;
108
+ c.backward = true;
109
+ const was = last(mix.hostLog, t, true);
110
+ const host = mix.opts.host;
111
+ const then = was && typeof host === 'object' && host !== null
112
+ ? Object.assign(Object.create(host), was.fields)
113
+ : undefined;
114
+ if (was)
115
+ c.hostThen = was;
116
+ c.cued = [...mix.cued, ...mix.gone]
117
+ .filter((v) => v.cuedAt <= t && v.doneAt > t)
118
+ .sort((a, b) => a.id - b.id)
119
+ .map((v) => {
120
+ const log = v.log;
121
+ const copy = v.copy((subject) => recall(mix, v, subject, t), last(log, t, (e) => e.sync) ?? log[0]);
122
+ if (then !== undefined)
123
+ copy.setting.host = then;
124
+ return copy;
125
+ });
126
+ ownBlends(c.cued);
127
+ if (mix.owners !== null)
128
+ relink(c.cued);
129
+ for (const copy of c.cued)
130
+ copy.state =
131
+ (copy.owner === null ? t : ownerReading(copy.owner, t)) < copy.start
132
+ ? 'pending'
133
+ : copy.out && copy.out.at <= t
134
+ ? 'fading'
135
+ : copy.freezesAfter && copy.elapsedAt(t) >= copy.span + copy.latest
136
+ ? 'frozen'
137
+ : 'live';
138
+ count(c);
139
+ }
101
140
  /** Recounts what the fold's shortcuts depend on, for a projection's freshly copied voices. */
102
141
  function count(mix) {
103
142
  mix.named = new Store();
@@ -132,7 +171,7 @@ function carry(mix, voice, subject) {
132
171
  * voice's start with fresh state, which is exact for a voice stepped at a fixed interval.
133
172
  */
134
173
  function recall(mix, voice, subject, t) {
135
- const live = recordOf(mix, voice, subject);
174
+ const live = leftAt(voice.left, subject, t) ?? recordOf(mix, voice, subject);
136
175
  if (live === undefined)
137
176
  return undefined;
138
177
  const snap = live.snaps && last(live.snaps, t, true);
@@ -192,7 +231,7 @@ function doubts(mix, subject) {
192
231
  for (const slot of voice.slots)
193
232
  out[mix.names[slot]] = 'held';
194
233
  }
195
- for (let held = mix.chain(subject, mix.now); held !== null; held = held.next) {
234
+ for (let held = mix.chain(subject); held !== null; held = held.next) {
196
235
  const voice = held.voice;
197
236
  if (voice === null || voice.state === 'done')
198
237
  continue;
package/dist/pull.js CHANGED
@@ -1,3 +1,4 @@
1
+ import { axesOf } from './channels.js';
1
2
  import { copy } from './clone.js';
2
3
  import { clampRun, flush, pullRun, writeLater } from './columns.js';
3
4
  /** Writes a folded pose's value into subject `n`'s places in a column. */
@@ -128,14 +129,14 @@ function readAll(mix, list, columns, live, current, whole) {
128
129
  const kept = heads[n];
129
130
  const head = was[n] === subject && kept !== undefined && kept.version === version
130
131
  ? kept
131
- : mix.chain(subject, now);
132
+ : mix.chain(subject);
132
133
  was[n] = subject;
133
134
  heads[n] = head;
134
135
  slot = head.slot;
135
136
  slots[n] = slot;
136
137
  }
137
138
  }
138
- const laned = live && lanes?.prepare(slot, subject, now, mix.version, heads[n] ?? null) === true;
139
+ const laned = live && lanes?.prepare(slot, subject, mix.version, heads[n] ?? null) === true;
139
140
  if (laned && whole && lanes.whole && !lanes.owes(slot)) {
140
141
  writeLater(lanes, slot, columns, n);
141
142
  continue;
@@ -160,7 +161,7 @@ function columnsOf(mix, into) {
160
161
  throw new Error(`blits: pull was handed ${key}, which the kit lacks`);
161
162
  const channel = mix.channels[slot];
162
163
  const rest = channel.rest;
163
- const axes = Array.isArray(rest) ? rest.length : 1;
164
+ const axes = axesOf(channel);
164
165
  const bounds = channel.bounds;
165
166
  const at = new Float64Array(axes).fill(Number.NaN);
166
167
  if (typeof rest === 'number')
package/dist/qualify.js CHANGED
@@ -163,11 +163,13 @@ export function requalify(lanes, version) {
163
163
  return;
164
164
  const channel = host.channels[slot];
165
165
  const n = numericOf(channel);
166
- const rest = channel.rest;
166
+ // A rest-less channel's values are NaN until some voice passes the band: absent from the pose.
167
+ const rest = (channel.rest ?? Number.NaN);
168
+ const scalar = typeof rest === 'number' && n.axes === 1;
167
169
  const ch = {
168
170
  name: host.names[slot],
169
171
  op: n.op,
170
- scalar: typeof rest === 'number',
172
+ scalar,
171
173
  rest: typeof rest === 'number' ? rest : rest[0],
172
174
  axes: n.axes,
173
175
  values: new Float64Array(lanes.cap * n.axes),
@@ -0,0 +1,58 @@
1
+ import type { Mixer } from './mixer.js';
2
+ import type { Run } from './motions.js';
3
+ import { type PackedRecord } from './pack.js';
4
+ import { type Key } from './paging.js';
5
+ import type { Described } from './types.js';
6
+ import { type Controls, type Ramp, type Rise, Voice } from './voice.js';
7
+ /** A voice that has left, as plain data: the descriptor it was cued with, its controls and its records. */
8
+ interface PackedVoice {
9
+ as: Described;
10
+ start: number;
11
+ anchorNow: number;
12
+ anchorElapsed: number;
13
+ rate: number;
14
+ ramp: {
15
+ from: number;
16
+ to: number;
17
+ over: number;
18
+ } | null;
19
+ weight: number;
20
+ out: Ramp | null;
21
+ back: Rise | null;
22
+ outAt: number;
23
+ outOver: number;
24
+ outSet: boolean;
25
+ cuedAt: number;
26
+ doneAt: number;
27
+ opened: number;
28
+ pinned: number;
29
+ latest: number;
30
+ keeping: boolean;
31
+ first: Controls | null;
32
+ log: Controls[] | null;
33
+ played: boolean | undefined;
34
+ playedAt: number;
35
+ records: [Key, PackedRecord][];
36
+ runs: [Key, Run][];
37
+ left: [Key, number, PackedRecord][];
38
+ parts: [Key, number, number][];
39
+ parted: [Key, number][];
40
+ }
41
+ /**
42
+ * Voices a history store holds whose handles something still holds, by id: a seek that revives
43
+ * one puts it behind its handle, and needs it back if it left after the moment sought.
44
+ */
45
+ export declare class Outs {
46
+ private readonly out;
47
+ private readonly gone;
48
+ add(id: number, doneAt: number, h: object): void;
49
+ /** Takes a revived voice's handle back, undefined where none is held. */
50
+ take(id: number): object | undefined;
51
+ /** The id of a voice that left after `t` and that `has` lacks; undefined for none. */
52
+ missing(t: number, has: (id: number) => boolean): number | undefined;
53
+ }
54
+ /** Pages out a voice that left before `reach`; false for one that has to stay in memory. */
55
+ export declare function pageVoice<I, O>(mix: Mixer<I, O>, voice: Voice<I, O>, reach: number): boolean;
56
+ /** A voice a history store paged out, rebuilt from its descriptor and put back as it left. */
57
+ export declare function reviveVoice<I, O>(mix: Mixer<I, O>, id: number, d: PackedVoice): Voice<I, O>;
58
+ export type { PackedVoice };
package/dist/revive.js ADDED
@@ -0,0 +1,158 @@
1
+ import { playable } from './cue.js';
2
+ import { VoiceHandle } from './handle.js';
3
+ import { handle } from './hosts.js';
4
+ import { packHeld, unpackHeld } from './pack.js';
5
+ import { pageOut } from './paging.js';
6
+ import { Voice } from './voice.js';
7
+ /**
8
+ * Voices a history store holds whose handles something still holds, by id: a seek that revives
9
+ * one puts it behind its handle, and needs it back if it left after the moment sought.
10
+ */
11
+ export class Outs {
12
+ out = new Map();
13
+ gone = new FinalizationRegistry((id) => {
14
+ if (this.out.get(id)?.handle.deref() === undefined)
15
+ this.out.delete(id);
16
+ });
17
+ add(id, doneAt, h) {
18
+ this.out.set(id, { doneAt, handle: new WeakRef(h) });
19
+ this.gone.register(h, id);
20
+ }
21
+ /** Takes a revived voice's handle back, undefined where none is held. */
22
+ take(id) {
23
+ const e = this.out.get(id);
24
+ this.out.delete(id);
25
+ return e?.handle.deref();
26
+ }
27
+ /** The id of a voice that left after `t` and that `has` lacks; undefined for none. */
28
+ missing(t, has) {
29
+ for (const [id, e] of this.out)
30
+ if (e.doneAt > t && !has(id))
31
+ return id;
32
+ return undefined;
33
+ }
34
+ }
35
+ /** Whether a voice can leave memory: it was cued with `as`, and nothing else in the mix holds it. */
36
+ function pageable(voice) {
37
+ return (voice.spec.as !== undefined &&
38
+ voice.owner === null &&
39
+ voice.holding === null &&
40
+ voice.blend === null &&
41
+ voice.answers === null &&
42
+ voice.fitting === null);
43
+ }
44
+ /** Pages out a voice that left before `reach`; false for one that has to stay in memory. */
45
+ export function pageVoice(mix, voice, reach) {
46
+ if (!pageable(voice))
47
+ return false;
48
+ const keys = mix.keys;
49
+ const data = packVoice(voice, keys);
50
+ pageOut(mix, 'voice', voice.id, undefined, [{ at: voice.doneAt }], () => data, reach);
51
+ const h = voice.handle;
52
+ VoiceHandle.page(h, { weight: voice.weight, played: voice.settled.played === true });
53
+ mix.outs.add(voice.id, voice.doneAt, h);
54
+ mix.parters.delete(voice);
55
+ return true;
56
+ }
57
+ function packVoice(voice, keys) {
58
+ const subjectOf = (key) => keys.subject(key);
59
+ const records = [];
60
+ const runs = [];
61
+ for (const key of voice.keyed ?? []) {
62
+ const subject = subjectOf(key);
63
+ const held = voice.subjects.get(subject);
64
+ if (held?.reaches)
65
+ records.push([key, packHeld(voice, held)]);
66
+ const run = voice.motion?.pack(subject);
67
+ if (run !== undefined)
68
+ runs.push([key, run]);
69
+ }
70
+ const settled = voice.settled;
71
+ return {
72
+ as: voice.spec.as,
73
+ start: voice.start,
74
+ anchorNow: voice.anchorNow,
75
+ anchorElapsed: voice.anchorElapsed,
76
+ rate: voice.rate,
77
+ ramp: voice.ramp,
78
+ weight: voice.weight,
79
+ out: voice.out,
80
+ back: voice.back,
81
+ outAt: voice.outAt,
82
+ outOver: voice.outOver,
83
+ outSet: voice.outSet,
84
+ cuedAt: voice.cuedAt,
85
+ doneAt: voice.doneAt,
86
+ opened: voice.opened,
87
+ pinned: voice.pinned,
88
+ latest: voice.latest,
89
+ keeping: voice.keeping,
90
+ first: voice.first,
91
+ log: voice.log,
92
+ played: settled.played,
93
+ playedAt: settled.at,
94
+ records,
95
+ runs,
96
+ left: (voice.left ?? []).map((e) => [keys.key(e.subject), e.at, packHeld(voice, e.held)]),
97
+ parts: [...(voice.parts ?? [])].map(([s, r]) => [keys.key(s), r.at, r.over]),
98
+ parted: [...(voice.parted ?? [])].map(([s, at]) => [keys.key(s), at]),
99
+ };
100
+ }
101
+ /** A voice a history store paged out, rebuilt from its descriptor and put back as it left. */
102
+ export function reviveVoice(mix, id, d) {
103
+ const revive = mix.opts.history?.revive;
104
+ if (revive === undefined)
105
+ throw new Error('blits: a paged voice needs history.revive');
106
+ const spec = { ...revive(d.as), as: d.as };
107
+ playable(mix, spec);
108
+ const voice = new Voice(id, spec, spec.patch, spec.fade ?? {}, Number.NEGATIVE_INFINITY, d.start, mix.slotOf, mix.channels, mix.opts.host, mix.send, null);
109
+ voice.anchorNow = d.anchorNow;
110
+ voice.anchorElapsed = d.anchorElapsed;
111
+ voice.rate = d.rate;
112
+ voice.ramp = d.ramp;
113
+ voice.weight = d.weight;
114
+ voice.out = d.out;
115
+ voice.back = d.back;
116
+ voice.outAt = d.outAt;
117
+ voice.outOver = d.outOver;
118
+ voice.outSet = d.outSet;
119
+ voice.cuedAt = d.cuedAt;
120
+ voice.doneAt = d.doneAt;
121
+ voice.opened = d.opened;
122
+ voice.pinned = d.pinned;
123
+ voice.latest = d.latest;
124
+ voice.keeping = d.keeping;
125
+ voice.first = d.first;
126
+ voice.log = d.log;
127
+ voice.state = 'done';
128
+ if (d.played !== undefined)
129
+ voice.play(d.played, d.playedAt);
130
+ voice.resolve();
131
+ const keys = mix.keys;
132
+ const subjectOf = (key) => keys.subject(key);
133
+ voice.keyed = new Set();
134
+ for (const [key, p] of d.records) {
135
+ voice.subjects.set(subjectOf(key), unpackHeld(voice, p));
136
+ voice.keyed.add(key);
137
+ }
138
+ for (const [key, run] of d.runs)
139
+ voice.motion?.unpack(subjectOf(key), run);
140
+ if (d.left.length > 0)
141
+ voice.left = d.left.map(([key, at, p]) => ({
142
+ subject: subjectOf(key),
143
+ at,
144
+ held: unpackHeld(voice, p),
145
+ }));
146
+ if (d.parts.length > 0)
147
+ voice.parts = new Map(d.parts.map(([key, at, over]) => [subjectOf(key), { at, over }]));
148
+ if (d.parted.length > 0)
149
+ voice.parted = new Map(d.parted.map(([key, at]) => [subjectOf(key), at]));
150
+ const h = mix.outs?.take(id);
151
+ if (h === undefined)
152
+ voice.handle = handle(mix, voice);
153
+ else {
154
+ VoiceHandle.rehome(h, voice);
155
+ voice.handle = h;
156
+ }
157
+ return voice;
158
+ }
package/dist/sample.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { type Lane, type Positions } from './lane.js';
2
2
  import type { Lanes } from './lanes.js';
3
- import type { Motions } from './motion.js';
3
+ import type { Motions } from './motions.js';
4
4
  import type { Subject } from './voice.js';
5
5
  /**
6
6
  * A motion voice's fill: what `one` and `move` do for each subject, in one loop for the common
package/dist/sample.js CHANGED
@@ -17,6 +17,7 @@ export function runMotion(lanes, lane, run) {
17
17
  const ch = lane.chans[0];
18
18
  const fills = lanes.fills;
19
19
  const from = lanes.frameProbes;
20
+ const lately = lanes.lately;
20
21
  const seeks = voice.seeks;
21
22
  const elapsedNow = voice.elapsedAt(lanes.now);
22
23
  const flat = lane.flat;
@@ -57,6 +58,10 @@ export function runMotion(lanes, lane, run) {
57
58
  const q = slot * 6 /* Per.SLOT */;
58
59
  if (per[q + 5 /* Per.LANE_FILL */] === fills - 1)
59
60
  data[o + 3 /* Row.PROBED */] = data[o + 2 /* Row.WEIGHT */];
61
+ if (lately >= 0 && lanes.unread(slot)) {
62
+ unbareLane(lane, p);
63
+ continue;
64
+ }
60
65
  const delay = data[o + 0 /* Row.DELAY */];
61
66
  const elapsed = elapsedNow - delay;
62
67
  if (elapsed < 0) {
@@ -106,9 +111,9 @@ export function runMotion(lanes, lane, run) {
106
111
  data[o + 5 /* Row.SAMPLED */] = -2 /* Sampled.BARE */;
107
112
  lane.deltas[p] = null;
108
113
  }
109
- const share = left(ease, progress(elapsed, runs[b], runs[b + 2]));
110
114
  if (w <= 0)
111
115
  continue;
116
+ const share = left(ease, progress(elapsed, runs[b], runs[b + 2]));
112
117
  const values = ch.values;
113
118
  const base = slot * n;
114
119
  const x = b + 3;
package/dist/seek.d.ts ADDED
@@ -0,0 +1,3 @@
1
+ import type { Transport } from './transport.js';
2
+ /** Moves every mix on the transport to mix time `t`, as it stood at the end of that frame. */
3
+ export declare function seek(transport: Transport, t: number): void;