laive-mcp 0.1.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 (67) hide show
  1. package/AGENTS.md +48 -0
  2. package/CHANGELOG.md +13 -0
  3. package/LICENSE +674 -0
  4. package/README.md +219 -0
  5. package/bin/laive.mjs +340 -0
  6. package/package.json +66 -0
  7. package/packages/als-parser/src/index.js +2 -0
  8. package/packages/als-parser/src/read.js +16 -0
  9. package/packages/als-parser/src/summarize.js +116 -0
  10. package/packages/common/src/index.js +3 -0
  11. package/packages/common/src/jsonl.js +41 -0
  12. package/packages/common/src/protocol.js +94 -0
  13. package/packages/common/src/validation.js +121 -0
  14. package/packages/live-bridge-remote-script/README.md +22 -0
  15. package/packages/live-bridge-remote-script/python/laive/__init__.py +7 -0
  16. package/packages/live-bridge-remote-script/python/laive/control_surface.py +208 -0
  17. package/packages/live-bridge-remote-script/python/laive/fake_live.py +168 -0
  18. package/packages/live-bridge-remote-script/python/laive/listeners.py +46 -0
  19. package/packages/live-bridge-remote-script/python/laive/live_access.py +272 -0
  20. package/packages/live-bridge-remote-script/python/laive/protocol.py +87 -0
  21. package/packages/live-bridge-remote-script/python/laive/server.py +130 -0
  22. package/packages/live-bridge-remote-script/python/laive/task_queue.py +47 -0
  23. package/packages/live-bridge-remote-script/src/bridge/client.js +113 -0
  24. package/packages/live-bridge-remote-script/src/bridge/server.js +189 -0
  25. package/packages/live-bridge-remote-script/src/cli/client.js +75 -0
  26. package/packages/live-bridge-remote-script/src/cli/server.js +51 -0
  27. package/packages/live-bridge-remote-script/src/fixtures/default-live-set.json +113 -0
  28. package/packages/live-bridge-remote-script/src/index.js +3 -0
  29. package/packages/live-bridge-remote-script/src/runtime/fixture-runtime.js +356 -0
  30. package/packages/live-sidecar-m4l/README.md +45 -0
  31. package/packages/live-sidecar-m4l/device/laive-sidecar.amxd +0 -0
  32. package/packages/live-sidecar-m4l/project/code/laive-sidecar-node.js +149 -0
  33. package/packages/live-sidecar-m4l/project/data/laive-sidecar.manifest.json +8 -0
  34. package/packages/live-sidecar-m4l/project/laive-sidecar.maxproj +36 -0
  35. package/packages/live-sidecar-m4l/project/patchers/laive-sidecar.maxpat +172 -0
  36. package/packages/live-sidecar-m4l/src/contracts.js +35 -0
  37. package/packages/live-sidecar-m4l/src/index.js +19 -0
  38. package/packages/live-sidecar-m4l/src/install-sidecar-device.js +15 -0
  39. package/packages/live-sidecar-m4l/src/package-sidecar.js +5 -0
  40. package/packages/live-sidecar-m4l/src/project.js +132 -0
  41. package/packages/live-sidecar-m4l/src/runtime.js +96 -0
  42. package/packages/live-sidecar-m4l/src/workflows.js +95 -0
  43. package/packages/mcp-server/src/cli.js +113 -0
  44. package/packages/mcp-server/src/default-tools.js +253 -0
  45. package/packages/mcp-server/src/errors.js +24 -0
  46. package/packages/mcp-server/src/index.js +10 -0
  47. package/packages/mcp-server/src/server.js +96 -0
  48. package/packages/mcp-server/src/session.js +475 -0
  49. package/packages/mcp-server/src/tool-registry.js +41 -0
  50. package/packages/state-engine/src/engine.js +566 -0
  51. package/packages/state-engine/src/ids.js +57 -0
  52. package/packages/state-engine/src/index.js +40 -0
  53. package/packages/state-engine/src/normalize.js +357 -0
  54. package/packages/state-engine/src/queries.js +154 -0
  55. package/packages/state-engine/src/replay.js +60 -0
  56. package/packages/ui-automation/src/executor.js +87 -0
  57. package/packages/ui-automation/src/guards.js +21 -0
  58. package/packages/ui-automation/src/helper.js +186 -0
  59. package/packages/ui-automation/src/index.js +20 -0
  60. package/packages/ui-automation/src/macos.js +82 -0
  61. package/packages/ui-automation/src/package-ui-helper.js +5 -0
  62. package/packages/ui-automation/src/workflows.js +72 -0
  63. package/scripts/install-remote-script.py +7 -0
  64. package/scripts/install-ui-helper.mjs +14 -0
  65. package/scripts/package-remote-script.py +7 -0
  66. package/scripts/package-ui-helper.mjs +4 -0
  67. package/scripts/remote_script_tooling.py +253 -0
@@ -0,0 +1,566 @@
1
+ import {
2
+ compareTrackIds,
3
+ makeArrangementClipId,
4
+ makeDeviceId,
5
+ makeParameterId,
6
+ makeSceneId,
7
+ makeSessionClipId,
8
+ makeTrackId
9
+ } from "./ids.js";
10
+ import {
11
+ normalizeApplication,
12
+ normalizeCapabilities,
13
+ normalizeClip,
14
+ normalizeDevice,
15
+ normalizeParameter,
16
+ normalizeScene,
17
+ normalizeSelection,
18
+ normalizeSong,
19
+ normalizeTrack,
20
+ normalizeTrackBundle
21
+ } from "./normalize.js";
22
+ import {
23
+ findTrack,
24
+ getSelectedContext,
25
+ getTrackDetails,
26
+ listPlayingClips,
27
+ searchEntities,
28
+ summarizeProject
29
+ } from "./queries.js";
30
+
31
+ function isoNow(value) {
32
+ if (typeof value === "string" && value.length > 0) {
33
+ return value;
34
+ }
35
+
36
+ return new Date().toISOString();
37
+ }
38
+
39
+ function createEmptyEntityCollections() {
40
+ return {
41
+ tracks: {},
42
+ scenes: {},
43
+ clips: {},
44
+ devices: {},
45
+ parameters: {}
46
+ };
47
+ }
48
+
49
+ export function createInitialState(options = {}) {
50
+ const observedAt = isoNow(options.observedAt);
51
+
52
+ return {
53
+ meta: {
54
+ snapshotVersion: 0,
55
+ lastUpdatedAt: observedAt,
56
+ lastEventAt: null,
57
+ bridgeVersion: null,
58
+ liveVersion: null,
59
+ dirtyPaths: [],
60
+ traceLength: 0
61
+ },
62
+ application: null,
63
+ song: null,
64
+ selection: null,
65
+ capabilities: null,
66
+ trackOrder: [],
67
+ visibleTrackIds: [],
68
+ returnTrackIds: [],
69
+ masterTrackId: null,
70
+ sceneOrder: [],
71
+ ...createEmptyEntityCollections()
72
+ };
73
+ }
74
+
75
+ function sortedUniqueStrings(values) {
76
+ return [...new Set(values.filter(Boolean))].sort();
77
+ }
78
+
79
+ function replaceDirtyPaths(state, dirtyPaths) {
80
+ state.meta.dirtyPaths = sortedUniqueStrings(dirtyPaths);
81
+ }
82
+
83
+ function addDirtyPaths(state, dirtyPaths = []) {
84
+ replaceDirtyPaths(state, [...state.meta.dirtyPaths, ...dirtyPaths]);
85
+ }
86
+
87
+ function clearDirtyPaths(state, dirtyPaths = []) {
88
+ const dirtySet = new Set(state.meta.dirtyPaths);
89
+
90
+ for (const dirtyPath of dirtyPaths) {
91
+ dirtySet.delete(dirtyPath);
92
+ }
93
+
94
+ state.meta.dirtyPaths = [...dirtySet].sort();
95
+ }
96
+
97
+ function updateMetaAfterMutation(state, options = {}) {
98
+ const observedAt = isoNow(options.observedAt);
99
+ state.meta.snapshotVersion += 1;
100
+ state.meta.lastUpdatedAt = observedAt;
101
+ state.meta.traceLength += 1;
102
+
103
+ if (options.lastEventAt) {
104
+ state.meta.lastEventAt = isoNow(options.lastEventAt);
105
+ }
106
+
107
+ if (options.bridgeVersion) {
108
+ state.meta.bridgeVersion = options.bridgeVersion;
109
+ }
110
+
111
+ if (options.liveVersion) {
112
+ state.meta.liveVersion = options.liveVersion;
113
+ }
114
+ }
115
+
116
+ function removeParametersForDevice(state, deviceId) {
117
+ const device = state.devices[deviceId];
118
+
119
+ if (!device) {
120
+ return;
121
+ }
122
+
123
+ for (const parameterId of device.parameterIds) {
124
+ delete state.parameters[parameterId];
125
+ }
126
+ }
127
+
128
+ function removeDevice(state, deviceId) {
129
+ removeParametersForDevice(state, deviceId);
130
+ delete state.devices[deviceId];
131
+ }
132
+
133
+ function removeTrackSubtree(state, trackId) {
134
+ const track = state.tracks[trackId];
135
+
136
+ if (!track) {
137
+ return;
138
+ }
139
+
140
+ for (const clipId of [...track.sessionClipIds, ...track.arrangementClipIds]) {
141
+ delete state.clips[clipId];
142
+ }
143
+
144
+ for (const deviceId of track.deviceIds) {
145
+ removeDevice(state, deviceId);
146
+ }
147
+
148
+ delete state.tracks[trackId];
149
+ state.trackOrder = state.trackOrder.filter((currentTrackId) => currentTrackId !== trackId);
150
+ state.visibleTrackIds = state.visibleTrackIds.filter(
151
+ (currentTrackId) => currentTrackId !== trackId
152
+ );
153
+ state.returnTrackIds = state.returnTrackIds.filter(
154
+ (currentTrackId) => currentTrackId !== trackId
155
+ );
156
+
157
+ if (state.masterTrackId === trackId) {
158
+ state.masterTrackId = null;
159
+ }
160
+ }
161
+
162
+ function upsertTrackBundle(state, track, options = {}) {
163
+ const trackId =
164
+ track.id ??
165
+ makeTrackId(track.section ?? track.kind ?? "visible", track.index ?? track.track_index ?? 0);
166
+ const bundle = normalizeTrackBundle(
167
+ track,
168
+ {
169
+ track: state.tracks[trackId],
170
+ clips: state.clips,
171
+ devices: state.devices,
172
+ parameters: state.parameters
173
+ },
174
+ options
175
+ );
176
+
177
+ const previousTrack = state.tracks[bundle.track.id];
178
+
179
+ if (previousTrack) {
180
+ const previousClipIds = new Set([
181
+ ...previousTrack.sessionClipIds,
182
+ ...previousTrack.arrangementClipIds
183
+ ]);
184
+ const nextClipIds = new Set([
185
+ ...bundle.track.sessionClipIds,
186
+ ...bundle.track.arrangementClipIds
187
+ ]);
188
+
189
+ for (const clipId of previousClipIds) {
190
+ if (!nextClipIds.has(clipId)) {
191
+ delete state.clips[clipId];
192
+ }
193
+ }
194
+
195
+ const previousDeviceIds = new Set(previousTrack.deviceIds);
196
+ const nextDeviceIds = new Set(bundle.track.deviceIds);
197
+
198
+ for (const deviceId of previousDeviceIds) {
199
+ if (!nextDeviceIds.has(deviceId)) {
200
+ removeDevice(state, deviceId);
201
+ }
202
+ }
203
+ }
204
+
205
+ state.tracks[bundle.track.id] = bundle.track;
206
+
207
+ for (const clip of [...bundle.sessionClips, ...bundle.arrangementClips]) {
208
+ state.clips[clip.id] = clip;
209
+ }
210
+
211
+ for (const { device, parameters } of bundle.devices) {
212
+ state.devices[device.id] = device;
213
+
214
+ for (const parameter of parameters) {
215
+ state.parameters[parameter.id] = parameter;
216
+ }
217
+ }
218
+
219
+ const trackOrderSet = new Set(state.trackOrder);
220
+ trackOrderSet.add(bundle.track.id);
221
+ state.trackOrder = [...trackOrderSet].sort(compareTrackIds);
222
+
223
+ if (bundle.track.section === "visible") {
224
+ state.visibleTrackIds = [...new Set([...state.visibleTrackIds, bundle.track.id])].sort(
225
+ compareTrackIds
226
+ );
227
+ } else if (bundle.track.section === "return") {
228
+ state.returnTrackIds = [...new Set([...state.returnTrackIds, bundle.track.id])].sort(
229
+ compareTrackIds
230
+ );
231
+ } else if (bundle.track.section === "master") {
232
+ state.masterTrackId = bundle.track.id;
233
+ }
234
+ }
235
+
236
+ function upsertScene(state, scene, options = {}) {
237
+ const sceneId = scene.id ?? makeSceneId(scene.index ?? scene.scene_index ?? 0);
238
+ const normalizedScene = normalizeScene(scene, state.scenes[sceneId], options);
239
+ state.scenes[normalizedScene.id] = normalizedScene;
240
+ state.sceneOrder = [...new Set([...state.sceneOrder, normalizedScene.id])].sort(
241
+ (left, right) => state.scenes[left].index - state.scenes[right].index
242
+ );
243
+ }
244
+
245
+ function upsertClip(state, clip, options = {}) {
246
+ const clipId =
247
+ clip.id ??
248
+ (clip.location === "arrangement"
249
+ ? makeArrangementClipId(
250
+ clip.track_id,
251
+ clip.index ?? clip.arrangement_index ?? clip.arrangementIndex ?? 0
252
+ )
253
+ : makeSessionClipId(
254
+ clip.track_id,
255
+ clip.slot_index ?? clip.slotIndex ?? clip.scene_index ?? 0
256
+ ));
257
+ const normalizedClip = normalizeClip(
258
+ clip,
259
+ clip.track_id,
260
+ state.clips[clipId],
261
+ options
262
+ );
263
+ state.clips[normalizedClip.id] = normalizedClip;
264
+
265
+ const track = state.tracks[normalizedClip.trackId];
266
+
267
+ if (track) {
268
+ if (normalizedClip.location === "session") {
269
+ track.sessionClipIds = [...new Set([...track.sessionClipIds, normalizedClip.id])];
270
+ } else {
271
+ track.arrangementClipIds = [...new Set([...track.arrangementClipIds, normalizedClip.id])];
272
+ }
273
+ }
274
+ }
275
+
276
+ function upsertDevice(state, device, options = {}) {
277
+ const deviceId =
278
+ device.id ?? makeDeviceId(device.track_id, device.index ?? device.device_index ?? 0);
279
+ const normalizedDevice = normalizeDevice(
280
+ device,
281
+ device.track_id,
282
+ state.devices[deviceId],
283
+ options
284
+ );
285
+ state.devices[normalizedDevice.id] = normalizedDevice;
286
+
287
+ const track = state.tracks[normalizedDevice.trackId];
288
+
289
+ if (track) {
290
+ track.deviceIds = [...new Set([...track.deviceIds, normalizedDevice.id])];
291
+ }
292
+ }
293
+
294
+ function upsertParameter(state, parameter, options = {}) {
295
+ const parameterId =
296
+ parameter.id ??
297
+ makeParameterId(
298
+ parameter.device_id,
299
+ parameter.index ?? parameter.parameter_index ?? parameter.parameterIndex ?? 0
300
+ );
301
+ const normalizedParameter = normalizeParameter(
302
+ parameter,
303
+ parameter.device_id,
304
+ state.parameters[parameterId],
305
+ options
306
+ );
307
+ state.parameters[normalizedParameter.id] = normalizedParameter;
308
+
309
+ const device = state.devices[normalizedParameter.deviceId];
310
+
311
+ if (device) {
312
+ device.parameterIds = [...new Set([...device.parameterIds, normalizedParameter.id])];
313
+ }
314
+ }
315
+
316
+ export function applySnapshot(state, snapshot, options = {}) {
317
+ const nextState = createInitialState({
318
+ observedAt: options.observedAt ?? snapshot.observed_at
319
+ });
320
+ nextState.meta.snapshotVersion = state.meta.snapshotVersion;
321
+ nextState.meta.traceLength = state.meta.traceLength;
322
+ nextState.meta.lastEventAt = state.meta.lastEventAt;
323
+
324
+ if (snapshot.application) {
325
+ nextState.application = normalizeApplication(snapshot.application, null, options);
326
+ }
327
+
328
+ if (snapshot.song) {
329
+ nextState.song = normalizeSong(snapshot.song, null, options);
330
+ }
331
+
332
+ if (snapshot.selection) {
333
+ nextState.selection = normalizeSelection(snapshot.selection, null, options);
334
+ }
335
+
336
+ if (snapshot.capabilities) {
337
+ nextState.capabilities = normalizeCapabilities(snapshot.capabilities, null, options);
338
+ }
339
+
340
+ for (const scene of snapshot.scenes ?? []) {
341
+ upsertScene(nextState, scene, options);
342
+ }
343
+
344
+ for (const track of snapshot.tracks ?? []) {
345
+ upsertTrackBundle(nextState, track, options);
346
+ }
347
+
348
+ updateMetaAfterMutation(nextState, {
349
+ observedAt: options.observedAt ?? snapshot.observed_at,
350
+ bridgeVersion: snapshot.bridge_version ?? options.bridgeVersion,
351
+ liveVersion: snapshot.live_version ?? options.liveVersion
352
+ });
353
+
354
+ replaceDirtyPaths(nextState, []);
355
+ return nextState;
356
+ }
357
+
358
+ function handleTransportChanged(state, payload, options) {
359
+ state.song = normalizeSong({ ...state.song, ...payload }, state.song, options);
360
+ }
361
+
362
+ function handleSelectionChanged(state, payload, options) {
363
+ state.selection = normalizeSelection(payload, state.selection, options);
364
+ }
365
+
366
+ function handleTrackRemoved(state, payload) {
367
+ const trackId = payload.track_id ?? payload.id;
368
+
369
+ if (!trackId) {
370
+ return;
371
+ }
372
+
373
+ removeTrackSubtree(state, trackId);
374
+ }
375
+
376
+ function handleSceneRemoved(state, payload) {
377
+ const sceneId = payload.scene_id ?? payload.id;
378
+
379
+ if (!sceneId) {
380
+ return;
381
+ }
382
+
383
+ delete state.scenes[sceneId];
384
+ state.sceneOrder = state.sceneOrder.filter((currentSceneId) => currentSceneId !== sceneId);
385
+ addDirtyPaths(state, ["song.scenes"]);
386
+ }
387
+
388
+ function handleClipRemoved(state, payload) {
389
+ const clipId = payload.clip_id ?? payload.id;
390
+ const clip = clipId ? state.clips[clipId] : null;
391
+
392
+ if (!clip) {
393
+ return;
394
+ }
395
+
396
+ const track = state.tracks[clip.trackId];
397
+
398
+ if (track) {
399
+ track.sessionClipIds = track.sessionClipIds.filter((currentClipId) => currentClipId !== clipId);
400
+ track.arrangementClipIds = track.arrangementClipIds.filter(
401
+ (currentClipId) => currentClipId !== clipId
402
+ );
403
+ }
404
+
405
+ delete state.clips[clipId];
406
+ }
407
+
408
+ function handleDeviceRemoved(state, payload) {
409
+ const deviceId = payload.device_id ?? payload.id;
410
+
411
+ if (!deviceId) {
412
+ return;
413
+ }
414
+
415
+ const device = state.devices[deviceId];
416
+
417
+ if (device) {
418
+ const track = state.tracks[device.trackId];
419
+
420
+ if (track) {
421
+ track.deviceIds = track.deviceIds.filter((currentDeviceId) => currentDeviceId !== deviceId);
422
+ }
423
+ }
424
+
425
+ removeDevice(state, deviceId);
426
+ }
427
+
428
+ function handleParameterRemoved(state, payload) {
429
+ const parameterId = payload.parameter_id ?? payload.id;
430
+
431
+ if (!parameterId) {
432
+ return;
433
+ }
434
+
435
+ const parameter = state.parameters[parameterId];
436
+
437
+ if (parameter) {
438
+ const device = state.devices[parameter.deviceId];
439
+
440
+ if (device) {
441
+ device.parameterIds = device.parameterIds.filter(
442
+ (currentParameterId) => currentParameterId !== parameterId
443
+ );
444
+ }
445
+ }
446
+
447
+ delete state.parameters[parameterId];
448
+ }
449
+
450
+ const eventHandlers = {
451
+ "transport.changed": (state, payload, options) => handleTransportChanged(state, payload, options),
452
+ "selection.changed": (state, payload, options) => handleSelectionChanged(state, payload, options),
453
+ "track.added": (state, payload, options) => upsertTrackBundle(state, payload.track ?? payload, options),
454
+ "track.updated": (state, payload, options) => upsertTrackBundle(state, payload.track ?? payload, options),
455
+ "track.removed": (state, payload) => handleTrackRemoved(state, payload),
456
+ "scene.added": (state, payload, options) => upsertScene(state, payload.scene ?? payload, options),
457
+ "scene.updated": (state, payload, options) => upsertScene(state, payload.scene ?? payload, options),
458
+ "scene.removed": (state, payload) => handleSceneRemoved(state, payload),
459
+ "clip.updated": (state, payload, options) => upsertClip(state, payload.clip ?? payload, options),
460
+ "clip.removed": (state, payload) => handleClipRemoved(state, payload),
461
+ "device.updated": (state, payload, options) =>
462
+ upsertDevice(state, payload.device ?? payload, options),
463
+ "device.removed": (state, payload) => handleDeviceRemoved(state, payload),
464
+ "device.selected": (state, payload, options) =>
465
+ handleSelectionChanged(
466
+ state,
467
+ { ...state.selection, selected_device_id: payload.device_id ?? payload.id ?? null },
468
+ options
469
+ ),
470
+ "parameter.updated": (state, payload, options) =>
471
+ upsertParameter(state, payload.parameter ?? payload, options),
472
+ "parameter.removed": (state, payload) => handleParameterRemoved(state, payload),
473
+ "state.dirty": (state, payload) => addDirtyPaths(state, payload.paths ?? []),
474
+ "state.resynced": (state, payload) => clearDirtyPaths(state, payload.paths ?? [])
475
+ };
476
+
477
+ export function applyEvent(state, eventEnvelope, options = {}) {
478
+ const nextState = structuredClone(state);
479
+ const eventName = eventEnvelope.event ?? eventEnvelope.name;
480
+ const payload = eventEnvelope.payload ?? {};
481
+ const observedAt = isoNow(options.observedAt ?? eventEnvelope.observed_at);
482
+ const handler = eventHandlers[eventName];
483
+
484
+ if (handler) {
485
+ handler(nextState, payload, { observedAt });
486
+ } else {
487
+ addDirtyPaths(nextState, payload.paths ?? ["unknown"]);
488
+ }
489
+
490
+ updateMetaAfterMutation(nextState, {
491
+ observedAt,
492
+ lastEventAt: observedAt
493
+ });
494
+
495
+ return nextState;
496
+ }
497
+
498
+ export function markDirtyPaths(state, dirtyPaths = [], options = {}) {
499
+ const nextState = structuredClone(state);
500
+ addDirtyPaths(nextState, dirtyPaths);
501
+ updateMetaAfterMutation(nextState, {
502
+ observedAt: options.observedAt
503
+ });
504
+ return nextState;
505
+ }
506
+
507
+ export function reconcileSubtree(state, descriptor, options = {}) {
508
+ const nextState = structuredClone(state);
509
+ const kind = descriptor.kind;
510
+
511
+ if (kind === "track" && descriptor.payload) {
512
+ upsertTrackBundle(nextState, descriptor.payload, options);
513
+ clearDirtyPaths(nextState, [`track:${descriptor.payload.id ?? "unknown"}`]);
514
+ } else if (kind === "scene" && descriptor.payload) {
515
+ upsertScene(nextState, descriptor.payload, options);
516
+ clearDirtyPaths(nextState, ["song.scenes"]);
517
+ } else if (kind === "clip" && descriptor.payload) {
518
+ upsertClip(nextState, descriptor.payload, options);
519
+ } else if (kind === "selection" && descriptor.payload) {
520
+ handleSelectionChanged(nextState, descriptor.payload, options);
521
+ } else if (kind === "transport" && descriptor.payload) {
522
+ handleTransportChanged(nextState, descriptor.payload, options);
523
+ } else if (kind === "snapshot" && descriptor.payload) {
524
+ return applySnapshot(nextState, descriptor.payload, options);
525
+ }
526
+
527
+ updateMetaAfterMutation(nextState, {
528
+ observedAt: options.observedAt
529
+ });
530
+
531
+ return nextState;
532
+ }
533
+
534
+ export function createStateEngine(initialState = createInitialState()) {
535
+ let currentState = structuredClone(initialState);
536
+
537
+ return {
538
+ getState() {
539
+ return structuredClone(currentState);
540
+ },
541
+ applySnapshot(snapshot, options = {}) {
542
+ currentState = applySnapshot(currentState, snapshot, options);
543
+ return this.getState();
544
+ },
545
+ applyEvent(eventEnvelope, options = {}) {
546
+ currentState = applyEvent(currentState, eventEnvelope, options);
547
+ return this.getState();
548
+ },
549
+ markDirtyPaths(dirtyPaths, options = {}) {
550
+ currentState = markDirtyPaths(currentState, dirtyPaths, options);
551
+ return this.getState();
552
+ },
553
+ reconcileSubtree(descriptor, options = {}) {
554
+ currentState = reconcileSubtree(currentState, descriptor, options);
555
+ return this.getState();
556
+ },
557
+ query: {
558
+ summarizeProject: () => summarizeProject(currentState),
559
+ getSelectedContext: () => getSelectedContext(currentState),
560
+ findTrack: (query) => findTrack(currentState, query),
561
+ getTrackDetails: (trackId) => getTrackDetails(currentState, trackId),
562
+ listPlayingClips: () => listPlayingClips(currentState),
563
+ searchEntities: (query) => searchEntities(currentState, query)
564
+ }
565
+ };
566
+ }
@@ -0,0 +1,57 @@
1
+ const trackSectionOrder = {
2
+ visible: 0,
3
+ return: 1,
4
+ master: 2
5
+ };
6
+
7
+ export function makeTrackId(section = "visible", index = 0) {
8
+ return `track:${section}:${index}`;
9
+ }
10
+
11
+ export function makeSceneId(index = 0) {
12
+ return `scene:${index}`;
13
+ }
14
+
15
+ export function makeSessionClipId(trackId, slotIndex = 0) {
16
+ return `clip:session:${trackId}:slot:${slotIndex}`;
17
+ }
18
+
19
+ export function makeArrangementClipId(trackId, index = 0) {
20
+ return `clip:arrangement:${trackId}:index:${index}`;
21
+ }
22
+
23
+ export function makeDeviceId(trackId, index = 0) {
24
+ return `device:${trackId}:index:${index}`;
25
+ }
26
+
27
+ export function makeParameterId(deviceId, index = 0) {
28
+ return `parameter:${deviceId}:param:${index}`;
29
+ }
30
+
31
+ export function compareTrackIds(leftId, rightId) {
32
+ const left = parseTrackId(leftId);
33
+ const right = parseTrackId(rightId);
34
+
35
+ if (left.section !== right.section) {
36
+ return trackSectionOrder[left.section] - trackSectionOrder[right.section];
37
+ }
38
+
39
+ return left.index - right.index;
40
+ }
41
+
42
+ export function parseTrackId(trackId) {
43
+ const [, section, rawIndex] = trackId.split(":");
44
+ return {
45
+ id: trackId,
46
+ section,
47
+ index: Number(rawIndex)
48
+ };
49
+ }
50
+
51
+ export function parseSceneId(sceneId) {
52
+ const [, rawIndex] = sceneId.split(":");
53
+ return {
54
+ id: sceneId,
55
+ index: Number(rawIndex)
56
+ };
57
+ }
@@ -0,0 +1,40 @@
1
+ export {
2
+ applyEvent,
3
+ applySnapshot,
4
+ createInitialState,
5
+ createStateEngine,
6
+ markDirtyPaths,
7
+ reconcileSubtree
8
+ } from "./engine.js";
9
+ export {
10
+ compareTrackIds,
11
+ makeArrangementClipId,
12
+ makeDeviceId,
13
+ makeParameterId,
14
+ makeSceneId,
15
+ makeSessionClipId,
16
+ makeTrackId,
17
+ parseSceneId,
18
+ parseTrackId
19
+ } from "./ids.js";
20
+ export {
21
+ normalizeApplication,
22
+ normalizeCapabilities,
23
+ normalizeClip,
24
+ normalizeDevice,
25
+ normalizeParameter,
26
+ normalizeScene,
27
+ normalizeSelection,
28
+ normalizeSong,
29
+ normalizeTrack,
30
+ normalizeTrackBundle
31
+ } from "./normalize.js";
32
+ export {
33
+ findTrack,
34
+ getSelectedContext,
35
+ getTrackDetails,
36
+ listPlayingClips,
37
+ searchEntities,
38
+ summarizeProject
39
+ } from "./queries.js";
40
+ export { loadTraceFile, parseTraceText, replayTrace } from "./replay.js";