@opendaw/studio-core 0.0.100 → 0.0.102

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 (114) hide show
  1. package/dist/AssetService.js +2 -2
  2. package/dist/EffectFactories.d.ts +2 -0
  3. package/dist/EffectFactories.d.ts.map +1 -1
  4. package/dist/EffectFactories.js +44 -24
  5. package/dist/Engine.d.ts +5 -0
  6. package/dist/Engine.d.ts.map +1 -1
  7. package/dist/EngineFacade.d.ts +5 -0
  8. package/dist/EngineFacade.d.ts.map +1 -1
  9. package/dist/EngineFacade.js +23 -2
  10. package/dist/EngineWorklet.d.ts +5 -0
  11. package/dist/EngineWorklet.d.ts.map +1 -1
  12. package/dist/EngineWorklet.js +107 -11
  13. package/dist/HRClockWorker.d.ts +7 -0
  14. package/dist/HRClockWorker.d.ts.map +1 -0
  15. package/dist/HRClockWorker.js +54 -0
  16. package/dist/OfflineEngineRenderer.d.ts.map +1 -1
  17. package/dist/OfflineEngineRenderer.js +6 -2
  18. package/dist/RecordingWorklet.d.ts +2 -1
  19. package/dist/RecordingWorklet.d.ts.map +1 -1
  20. package/dist/RecordingWorklet.js +9 -1
  21. package/dist/Storage.d.ts.map +1 -1
  22. package/dist/Storage.js +1 -0
  23. package/dist/StudioPreferences.d.ts +9 -0
  24. package/dist/StudioPreferences.d.ts.map +1 -1
  25. package/dist/StudioSettings.d.ts +14 -0
  26. package/dist/StudioSettings.d.ts.map +1 -1
  27. package/dist/StudioSettings.js +19 -2
  28. package/dist/capture/CaptureAudio.d.ts +3 -1
  29. package/dist/capture/CaptureAudio.d.ts.map +1 -1
  30. package/dist/capture/CaptureAudio.js +51 -29
  31. package/dist/capture/MonitoringMode.d.ts +2 -0
  32. package/dist/capture/MonitoringMode.d.ts.map +1 -0
  33. package/dist/capture/MonitoringMode.js +1 -0
  34. package/dist/capture/RecordAudio.d.ts.map +1 -1
  35. package/dist/capture/RecordAudio.js +1 -0
  36. package/dist/capture/index.d.ts +1 -0
  37. package/dist/capture/index.d.ts.map +1 -1
  38. package/dist/capture/index.js +1 -0
  39. package/dist/dawproject/DawProjectImporter.d.ts.map +1 -1
  40. package/dist/dawproject/DawProjectImporter.js +4 -0
  41. package/dist/processors.js +24 -24
  42. package/dist/processors.js.map +4 -4
  43. package/dist/project/Project.d.ts +7 -1
  44. package/dist/project/Project.d.ts.map +1 -1
  45. package/dist/project/Project.js +58 -8
  46. package/dist/project/ProjectApi.d.ts +1 -1
  47. package/dist/project/ProjectApi.d.ts.map +1 -1
  48. package/dist/project/ProjectApi.js +17 -3
  49. package/dist/project/ProjectMigration.d.ts.map +1 -1
  50. package/dist/project/ProjectMigration.js +3 -2
  51. package/dist/project/audio/AudioContentFactory.d.ts +5 -0
  52. package/dist/project/audio/AudioContentFactory.d.ts.map +1 -1
  53. package/dist/project/audio/AudioContentFactory.js +13 -0
  54. package/dist/project/migration/MigrateNeuralAmpDeviceBox.d.ts +4 -0
  55. package/dist/project/migration/MigrateNeuralAmpDeviceBox.d.ts.map +1 -0
  56. package/dist/project/migration/MigrateNeuralAmpDeviceBox.js +29 -0
  57. package/dist/project/migration/index.d.ts +1 -0
  58. package/dist/project/migration/index.d.ts.map +1 -1
  59. package/dist/project/migration/index.js +1 -0
  60. package/dist/ui/{generic → clipboard}/ClipboardManager.d.ts +3 -3
  61. package/dist/ui/clipboard/ClipboardManager.d.ts.map +1 -0
  62. package/dist/ui/clipboard/ClipboardManager.js +147 -0
  63. package/dist/ui/clipboard/ClipboardUtils.d.ts +12 -0
  64. package/dist/ui/clipboard/ClipboardUtils.d.ts.map +1 -0
  65. package/dist/ui/clipboard/ClipboardUtils.js +94 -0
  66. package/dist/ui/{generic → clipboard}/ContextMenu.d.ts +1 -1
  67. package/dist/ui/clipboard/ContextMenu.d.ts.map +1 -0
  68. package/dist/ui/{generic → clipboard}/ContextMenu.js +1 -1
  69. package/dist/ui/clipboard/types/AudioUnitsClipboardHandler.d.ts +18 -0
  70. package/dist/ui/clipboard/types/AudioUnitsClipboardHandler.d.ts.map +1 -0
  71. package/dist/ui/clipboard/types/AudioUnitsClipboardHandler.js +215 -0
  72. package/dist/ui/clipboard/types/DevicesClipboardHandler.d.ts +18 -0
  73. package/dist/ui/clipboard/types/DevicesClipboardHandler.d.ts.map +1 -0
  74. package/dist/ui/clipboard/types/DevicesClipboardHandler.js +188 -0
  75. package/dist/ui/clipboard/types/NotesClipboardHandler.d.ts +21 -0
  76. package/dist/ui/clipboard/types/NotesClipboardHandler.d.ts.map +1 -0
  77. package/dist/ui/clipboard/types/NotesClipboardHandler.js +72 -0
  78. package/dist/ui/clipboard/types/RegionsClipboardHandler.d.ts +24 -0
  79. package/dist/ui/clipboard/types/RegionsClipboardHandler.d.ts.map +1 -0
  80. package/dist/ui/clipboard/types/RegionsClipboardHandler.js +137 -0
  81. package/dist/ui/clipboard/types/ValuesClipboardHandler.d.ts +22 -0
  82. package/dist/ui/clipboard/types/ValuesClipboardHandler.d.ts.map +1 -0
  83. package/dist/ui/clipboard/types/ValuesClipboardHandler.js +135 -0
  84. package/dist/ui/index.d.ts +14 -3
  85. package/dist/ui/index.d.ts.map +1 -1
  86. package/dist/ui/index.js +14 -3
  87. package/dist/ui/menu/MenuItems.d.ts.map +1 -0
  88. package/dist/ui/timeline/RegionClipResolver.d.ts +5 -1
  89. package/dist/ui/timeline/RegionClipResolver.d.ts.map +1 -1
  90. package/dist/ui/timeline/RegionClipResolver.js +5 -1
  91. package/dist/ui/timeline/RegionKeepExistingResolver.d.ts +34 -0
  92. package/dist/ui/timeline/RegionKeepExistingResolver.d.ts.map +1 -0
  93. package/dist/ui/timeline/RegionKeepExistingResolver.js +171 -0
  94. package/dist/ui/timeline/RegionOverlapResolver.d.ts +33 -0
  95. package/dist/ui/timeline/RegionOverlapResolver.d.ts.map +1 -0
  96. package/dist/ui/timeline/RegionOverlapResolver.js +79 -0
  97. package/dist/ui/timeline/RegionPushExistingResolver.d.ts +31 -0
  98. package/dist/ui/timeline/RegionPushExistingResolver.d.ts.map +1 -0
  99. package/dist/ui/timeline/RegionPushExistingResolver.js +159 -0
  100. package/dist/ui/timeline/TimelineFocus.d.ts +14 -0
  101. package/dist/ui/timeline/TimelineFocus.d.ts.map +1 -0
  102. package/dist/ui/timeline/TimelineFocus.js +45 -0
  103. package/dist/ui/timeline/TrackResolver.d.ts +11 -0
  104. package/dist/ui/timeline/TrackResolver.d.ts.map +1 -0
  105. package/dist/ui/timeline/TrackResolver.js +5 -0
  106. package/dist/workers-main.js +1 -1
  107. package/dist/workers-main.js.map +3 -3
  108. package/package.json +62 -62
  109. package/dist/ui/generic/ClipboardManager.d.ts.map +0 -1
  110. package/dist/ui/generic/ClipboardManager.js +0 -77
  111. package/dist/ui/generic/ContextMenu.d.ts.map +0 -1
  112. package/dist/ui/generic/MenuItems.d.ts.map +0 -1
  113. /package/dist/ui/{generic → menu}/MenuItems.d.ts +0 -0
  114. /package/dist/ui/{generic → menu}/MenuItems.js +0 -0
@@ -0,0 +1,171 @@
1
+ import { TrackBoxAdapter, TrackType } from "@opendaw/studio-adapters";
2
+ /**
3
+ * Resolver for "keep-existing" overlap behavior.
4
+ * When overlap is detected, the INCOMING (selected/moved/copied) region moves to a track below.
5
+ * Existing regions stay in place.
6
+ */
7
+ export class RegionKeepExistingResolver {
8
+ /**
9
+ * For selection-based operations (move, resize, copy).
10
+ * Returns:
11
+ * - postProcess: function to call AFTER changes are applied (handles moves)
12
+ * - trackResolver: function to get target track for copies (use BEFORE creating copy)
13
+ *
14
+ * NOTE: Track resolution is lazy - target tracks are computed when trackResolver is called
15
+ * or when postProcess runs. This is necessary because creating new tracks requires being
16
+ * inside a transaction.
17
+ */
18
+ static fromSelection(tracks, adapters, strategy, deltaIndex, projectApi, boxAdapters) {
19
+ const isCopy = strategy.showOrigin();
20
+ // Collect overlap info - don't compute target tracks yet (need to be in transaction)
21
+ const copyOverlaps = [];
22
+ const moveOverlaps = [];
23
+ for (const track of tracks) {
24
+ const selectedStrategy = strategy.selectedModifyStrategy();
25
+ for (const adapter of adapters) {
26
+ const adapterTrackIndex = adapter.trackBoxAdapter.unwrap().listIndex + deltaIndex;
27
+ if (adapterTrackIndex !== track.listIndex) {
28
+ continue;
29
+ }
30
+ const position = selectedStrategy.readPosition(adapter);
31
+ const complete = selectedStrategy.readComplete(adapter);
32
+ let hasOverlap = false;
33
+ for (const region of track.regions.collection.iterateRange(0, complete)) {
34
+ // For moves: skip the adapter itself (it's being moved)
35
+ // For copies: DON'T skip - the copy might overlap with the original
36
+ if (!isCopy && region === adapter) {
37
+ continue;
38
+ }
39
+ // For moves: skip other selected regions (they move together)
40
+ if (!isCopy && region.isSelected) {
41
+ continue;
42
+ }
43
+ if (region.complete <= position) {
44
+ continue;
45
+ }
46
+ if (region.position >= complete) {
47
+ break;
48
+ }
49
+ hasOverlap = true;
50
+ break;
51
+ }
52
+ if (hasOverlap) {
53
+ const info = { track, position, complete, adapter };
54
+ if (isCopy) {
55
+ copyOverlaps.push(info);
56
+ }
57
+ else {
58
+ moveOverlaps.push(info);
59
+ }
60
+ }
61
+ }
62
+ }
63
+ // Cache for lazily computed target tracks
64
+ const targetTrackCache = new Map();
65
+ // Track which tracks were affected by copy redirects (for dispatchChange)
66
+ const copyAffectedTracks = [];
67
+ const getTargetTrack = (info) => {
68
+ let targetTrack = targetTrackCache.get(info.adapter);
69
+ if (targetTrack === undefined) {
70
+ targetTrack = RegionKeepExistingResolver.#findOrCreateTrackBelow(info.track, info.position, info.complete, projectApi, boxAdapters);
71
+ targetTrackCache.set(info.adapter, targetTrack);
72
+ }
73
+ return targetTrack;
74
+ };
75
+ // Lazy track resolver for copies - called inside the transaction
76
+ const trackResolver = (adapter, defaultTrack) => {
77
+ const info = copyOverlaps.find(overlap => overlap.adapter === adapter);
78
+ if (info !== undefined) {
79
+ const targetTrack = getTargetTrack(info);
80
+ // Track affected tracks for dispatchChange in postProcess
81
+ if (!copyAffectedTracks.includes(targetTrack)) {
82
+ copyAffectedTracks.push(targetTrack);
83
+ }
84
+ if (!copyAffectedTracks.includes(info.track)) {
85
+ copyAffectedTracks.push(info.track);
86
+ }
87
+ return targetTrack;
88
+ }
89
+ return defaultTrack;
90
+ };
91
+ // Post-process - handles moves and dispatches changes for copy-affected tracks
92
+ const postProcess = () => {
93
+ const affectedTracks = [...copyAffectedTracks];
94
+ for (const info of moveOverlaps) {
95
+ const targetTrack = getTargetTrack(info);
96
+ if (!affectedTracks.includes(targetTrack)) {
97
+ affectedTracks.push(targetTrack);
98
+ }
99
+ if (!affectedTracks.includes(info.track)) {
100
+ affectedTracks.push(info.track);
101
+ }
102
+ info.adapter.box.regions.refer(targetTrack.box.regions);
103
+ }
104
+ affectedTracks
105
+ .filter(track => track.listIndex >= 0)
106
+ .forEach(track => track.regions.dispatchChange());
107
+ };
108
+ return { postProcess, trackResolver };
109
+ }
110
+ /**
111
+ * For range-based operations (drop, duplicate).
112
+ * Returns the target track to use for creation (may be different from original if overlap exists).
113
+ */
114
+ static resolveTargetTrack(track, position, complete, projectApi, boxAdapters) {
115
+ if (track.type === TrackType.Value) {
116
+ return track;
117
+ }
118
+ // Check for overlap
119
+ for (const region of track.regions.collection.iterateRange(0, complete)) {
120
+ if (region.complete <= position) {
121
+ continue;
122
+ }
123
+ if (region.position >= complete) {
124
+ break;
125
+ }
126
+ // Found overlap, find track below
127
+ return RegionKeepExistingResolver.#findOrCreateTrackBelow(track, position, complete, projectApi, boxAdapters);
128
+ }
129
+ return track;
130
+ }
131
+ static #findOrCreateTrackBelow(sourceTrack, position, complete, projectApi, boxAdapters) {
132
+ const audioUnit = sourceTrack.audioUnit;
133
+ const trackType = sourceTrack.type;
134
+ // Get all tracks of same type in this audio unit, sorted by index
135
+ const siblingTracks = audioUnit.tracks.pointerHub.incoming()
136
+ .map(vertex => vertex.box)
137
+ .filter(trackBox => trackBox.type.getValue() === trackType)
138
+ .sort((boxA, boxB) => boxA.index.getValue() - boxB.index.getValue());
139
+ const sourceIndex = sourceTrack.indexField.getValue();
140
+ // Look for existing track below with space
141
+ for (const trackBox of siblingTracks) {
142
+ if (trackBox.index.getValue() <= sourceIndex) {
143
+ continue;
144
+ }
145
+ if (RegionKeepExistingResolver.#hasSpace(trackBox, position, complete)) {
146
+ return boxAdapters.adapterFor(trackBox, TrackBoxAdapter);
147
+ }
148
+ }
149
+ // No suitable existing track found, create new track below source
150
+ const insertIndex = sourceIndex + 1;
151
+ const newTrackBox = trackType === TrackType.Audio
152
+ ? projectApi.createAudioTrack(audioUnit, insertIndex)
153
+ : projectApi.createNoteTrack(audioUnit, insertIndex);
154
+ return boxAdapters.adapterFor(newTrackBox, TrackBoxAdapter);
155
+ }
156
+ static #hasSpace(trackBox, position, complete) {
157
+ for (const vertex of trackBox.regions.pointerHub.incoming()) {
158
+ const regionPosition = vertex.box.position.getValue();
159
+ const regionDuration = vertex.box.duration.getValue();
160
+ const regionComplete = regionPosition + regionDuration;
161
+ if (regionComplete <= position) {
162
+ continue;
163
+ }
164
+ if (regionPosition >= complete) {
165
+ continue;
166
+ }
167
+ return false;
168
+ }
169
+ return true;
170
+ }
171
+ }
@@ -0,0 +1,33 @@
1
+ import { Exec, int } from "@opendaw/lib-std";
2
+ import { ppqn } from "@opendaw/lib-dsp";
3
+ import { BoxEditing } from "@opendaw/lib-box";
4
+ import { AnyRegionBoxAdapter, BoxAdapters, TrackBoxAdapter } from "@opendaw/studio-adapters";
5
+ import { RegionModifyStrategies } from "./RegionModifyStrategies";
6
+ import { ProjectApi } from "../../project";
7
+ import { TrackResolver } from "./TrackResolver";
8
+ export declare class RegionOverlapResolver {
9
+ #private;
10
+ constructor(editing: BoxEditing, projectApi: ProjectApi, boxAdapters: BoxAdapters);
11
+ /**
12
+ * For selection-based operations (move, resize, copy).
13
+ * @param tracks - The tracks involved in the operation
14
+ * @param adapters - The region adapters being modified
15
+ * @param strategy - The modify strategy (determines if it's a copy, etc.)
16
+ * @param deltaIndex - Track index delta (for cross-track moves)
17
+ * @param changes - Callback that performs the actual changes. Receives a TrackResolver
18
+ * that should be used to determine target tracks for copies.
19
+ */
20
+ apply(tracks: ReadonlyArray<TrackBoxAdapter>, adapters: ReadonlyArray<AnyRegionBoxAdapter>, strategy: RegionModifyStrategies, deltaIndex: int, changes: (trackResolver: TrackResolver) => void): void;
21
+ /**
22
+ * For range-based operations (drop, duplicate).
23
+ * Returns the target track to use (may differ from input track for keep-existing mode).
24
+ */
25
+ resolveTargetTrack(track: TrackBoxAdapter, position: ppqn, complete: ppqn): TrackBoxAdapter;
26
+ /**
27
+ * Creates a resolver function for range-based operations (to be called inside an existing transaction).
28
+ * Call this BEFORE creating the region to capture the "before" state.
29
+ * Then call the returned function AFTER creating the region.
30
+ */
31
+ fromRange(track: TrackBoxAdapter, position: ppqn, complete: ppqn): Exec;
32
+ }
33
+ //# sourceMappingURL=RegionOverlapResolver.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"RegionOverlapResolver.d.ts","sourceRoot":"","sources":["../../../src/ui/timeline/RegionOverlapResolver.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,IAAI,EAAE,GAAG,EAAC,MAAM,kBAAkB,CAAA;AAC1C,OAAO,EAAC,IAAI,EAAC,MAAM,kBAAkB,CAAA;AACrC,OAAO,EAAC,UAAU,EAAC,MAAM,kBAAkB,CAAA;AAC3C,OAAO,EAAC,mBAAmB,EAAE,WAAW,EAAE,eAAe,EAAC,MAAM,0BAA0B,CAAA;AAC1F,OAAO,EAAC,sBAAsB,EAAC,MAAM,0BAA0B,CAAA;AAK/D,OAAO,EAAC,UAAU,EAAC,MAAM,eAAe,CAAA;AACxC,OAAO,EAAC,aAAa,EAAC,MAAM,iBAAiB,CAAA;AAE7C,qBAAa,qBAAqB;;gBAKlB,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,WAAW,EAAE,WAAW;IAMjF;;;;;;;;OAQG;IACH,KAAK,CAAC,MAAM,EAAE,aAAa,CAAC,eAAe,CAAC,EACtC,QAAQ,EAAE,aAAa,CAAC,mBAAmB,CAAC,EAC5C,QAAQ,EAAE,sBAAsB,EAChC,UAAU,EAAE,GAAG,EACf,OAAO,EAAE,CAAC,aAAa,EAAE,aAAa,KAAK,IAAI,GAAG,IAAI;IA2B5D;;;OAGG;IACH,kBAAkB,CAAC,KAAK,EAAE,eAAe,EAAE,QAAQ,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,GAAG,eAAe;IAS3F;;;;OAIG;IACH,SAAS,CAAC,KAAK,EAAE,eAAe,EAAE,QAAQ,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,GAAG,IAAI;CAW1E"}
@@ -0,0 +1,79 @@
1
+ import { RegionClipResolver } from "./RegionClipResolver";
2
+ import { RegionKeepExistingResolver } from "./RegionKeepExistingResolver";
3
+ import { RegionPushExistingResolver } from "./RegionPushExistingResolver";
4
+ import { StudioPreferences } from "../../StudioPreferences";
5
+ export class RegionOverlapResolver {
6
+ #editing;
7
+ #projectApi;
8
+ #boxAdapters;
9
+ constructor(editing, projectApi, boxAdapters) {
10
+ this.#editing = editing;
11
+ this.#projectApi = projectApi;
12
+ this.#boxAdapters = boxAdapters;
13
+ }
14
+ /**
15
+ * For selection-based operations (move, resize, copy).
16
+ * @param tracks - The tracks involved in the operation
17
+ * @param adapters - The region adapters being modified
18
+ * @param strategy - The modify strategy (determines if it's a copy, etc.)
19
+ * @param deltaIndex - Track index delta (for cross-track moves)
20
+ * @param changes - Callback that performs the actual changes. Receives a TrackResolver
21
+ * that should be used to determine target tracks for copies.
22
+ */
23
+ apply(tracks, adapters, strategy, deltaIndex, changes) {
24
+ const behaviour = StudioPreferences.settings.editing["overlapping-regions-behaviour"];
25
+ if (behaviour === "clip") {
26
+ const { postProcess, trackResolver } = RegionClipResolver.fromSelection(tracks, adapters, strategy, deltaIndex);
27
+ this.#editing.modify(() => {
28
+ changes(trackResolver);
29
+ postProcess();
30
+ });
31
+ RegionClipResolver.validateTracks(tracks);
32
+ }
33
+ else if (behaviour === "push-existing") {
34
+ const { postProcess, trackResolver } = RegionPushExistingResolver.fromSelection(tracks, adapters, strategy, deltaIndex, this.#projectApi, this.#boxAdapters);
35
+ this.#editing.modify(() => {
36
+ changes(trackResolver);
37
+ postProcess();
38
+ });
39
+ }
40
+ else {
41
+ // keep-existing
42
+ const { postProcess, trackResolver } = RegionKeepExistingResolver.fromSelection(tracks, adapters, strategy, deltaIndex, this.#projectApi, this.#boxAdapters);
43
+ this.#editing.modify(() => {
44
+ changes(trackResolver);
45
+ postProcess();
46
+ });
47
+ }
48
+ }
49
+ /**
50
+ * For range-based operations (drop, duplicate).
51
+ * Returns the target track to use (may differ from input track for keep-existing mode).
52
+ */
53
+ resolveTargetTrack(track, position, complete) {
54
+ const behaviour = StudioPreferences.settings.editing["overlapping-regions-behaviour"];
55
+ if (behaviour === "keep-existing") {
56
+ return RegionKeepExistingResolver
57
+ .resolveTargetTrack(track, position, complete, this.#projectApi, this.#boxAdapters);
58
+ }
59
+ return track;
60
+ }
61
+ /**
62
+ * Creates a resolver function for range-based operations (to be called inside an existing transaction).
63
+ * Call this BEFORE creating the region to capture the "before" state.
64
+ * Then call the returned function AFTER creating the region.
65
+ */
66
+ fromRange(track, position, complete) {
67
+ const behaviour = StudioPreferences.settings.editing["overlapping-regions-behaviour"];
68
+ if (behaviour === "clip") {
69
+ return RegionClipResolver.fromRange(track, position, complete);
70
+ }
71
+ else if (behaviour === "push-existing") {
72
+ return RegionPushExistingResolver.fromRange(track, position, complete, this.#projectApi, this.#boxAdapters);
73
+ }
74
+ else {
75
+ // keep-existing: nothing to do after creation - caller should use resolveTargetTrack before creating
76
+ return () => { };
77
+ }
78
+ }
79
+ }
@@ -0,0 +1,31 @@
1
+ import { Exec, int } from "@opendaw/lib-std";
2
+ import { ppqn } from "@opendaw/lib-dsp";
3
+ import { AnyRegionBoxAdapter, BoxAdapters, TrackBoxAdapter } from "@opendaw/studio-adapters";
4
+ import { RegionModifyStrategies } from "./RegionModifyStrategies";
5
+ import { ProjectApi } from "../../project";
6
+ import { TrackResolver } from "./TrackResolver";
7
+ /**
8
+ * Resolver for "push-existing" overlap behavior.
9
+ * When overlap is detected, the EXISTING (overlapped) regions move to a track below.
10
+ * Incoming regions stay where they were placed.
11
+ */
12
+ export declare class RegionPushExistingResolver {
13
+ #private;
14
+ /**
15
+ * For selection-based operations (move, resize).
16
+ * Returns:
17
+ * - postProcess: function to call AFTER changes are applied
18
+ * - trackResolver: Identity (incoming regions stay where placed)
19
+ */
20
+ static fromSelection(tracks: ReadonlyArray<TrackBoxAdapter>, adapters: ReadonlyArray<AnyRegionBoxAdapter>, strategy: RegionModifyStrategies, deltaIndex: int, projectApi: ProjectApi, boxAdapters: BoxAdapters): {
21
+ postProcess: Exec;
22
+ trackResolver: TrackResolver;
23
+ };
24
+ /**
25
+ * For range-based operations (drop, duplicate).
26
+ * Returns a function that should be called AFTER the region is created.
27
+ */
28
+ static fromRange(track: TrackBoxAdapter, position: ppqn, complete: ppqn, projectApi: ProjectApi, boxAdapters: BoxAdapters): Exec;
29
+ private constructor();
30
+ }
31
+ //# sourceMappingURL=RegionPushExistingResolver.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"RegionPushExistingResolver.d.ts","sourceRoot":"","sources":["../../../src/ui/timeline/RegionPushExistingResolver.ts"],"names":[],"mappings":"AAAA,OAAO,EAAY,IAAI,EAAE,GAAG,EAAsB,MAAM,kBAAkB,CAAA;AAC1E,OAAO,EAAC,IAAI,EAAC,MAAM,kBAAkB,CAAA;AACrC,OAAO,EAAC,mBAAmB,EAAE,WAAW,EAAE,eAAe,EAAY,MAAM,0BAA0B,CAAA;AAErG,OAAO,EAAC,sBAAsB,EAAC,MAAM,0BAA0B,CAAA;AAC/D,OAAO,EAAC,UAAU,EAAC,MAAM,eAAe,CAAA;AACxC,OAAO,EAAC,aAAa,EAAC,MAAM,iBAAiB,CAAA;AAE7C;;;;GAIG;AACH,qBAAa,0BAA0B;;IACnC;;;;;OAKG;IACH,MAAM,CAAC,aAAa,CAAC,MAAM,EAAE,aAAa,CAAC,eAAe,CAAC,EACtC,QAAQ,EAAE,aAAa,CAAC,mBAAmB,CAAC,EAC5C,QAAQ,EAAE,sBAAsB,EAChC,UAAU,EAAE,GAAG,EACf,UAAU,EAAE,UAAU,EACtB,WAAW,EAAE,WAAW,GAAG;QAAE,WAAW,EAAE,IAAI,CAAC;QAAC,aAAa,EAAE,aAAa,CAAA;KAAE;IA6BnG;;;OAGG;IACH,MAAM,CAAC,SAAS,CAAC,KAAK,EAAE,eAAe,EACtB,QAAQ,EAAE,IAAI,EACd,QAAQ,EAAE,IAAI,EACd,UAAU,EAAE,UAAU,EACtB,WAAW,EAAE,WAAW,GAAG,IAAI;IAqEhD,OAAO;CAsBV"}
@@ -0,0 +1,159 @@
1
+ import { EmptyExec, isDefined } from "@opendaw/lib-std";
2
+ import { TrackBoxAdapter, TrackType } from "@opendaw/studio-adapters";
3
+ import { TrackResolver } from "./TrackResolver";
4
+ /**
5
+ * Resolver for "push-existing" overlap behavior.
6
+ * When overlap is detected, the EXISTING (overlapped) regions move to a track below.
7
+ * Incoming regions stay where they were placed.
8
+ */
9
+ export class RegionPushExistingResolver {
10
+ /**
11
+ * For selection-based operations (move, resize).
12
+ * Returns:
13
+ * - postProcess: function to call AFTER changes are applied
14
+ * - trackResolver: Identity (incoming regions stay where placed)
15
+ */
16
+ static fromSelection(tracks, adapters, strategy, deltaIndex, projectApi, boxAdapters) {
17
+ const resolver = new RegionPushExistingResolver(projectApi, boxAdapters);
18
+ // Capture overlapped regions BEFORE changes
19
+ for (const track of tracks) {
20
+ const selectedStrategy = strategy.selectedModifyStrategy();
21
+ const overlapped = [];
22
+ for (const adapter of adapters) {
23
+ const adapterTrackIndex = adapter.trackBoxAdapter.unwrap().listIndex + deltaIndex;
24
+ if (adapterTrackIndex !== track.listIndex) {
25
+ continue;
26
+ }
27
+ const position = selectedStrategy.readPosition(adapter);
28
+ const complete = selectedStrategy.readComplete(adapter);
29
+ for (const region of track.regions.collection.iterateRange(0, complete)) {
30
+ if (region.complete <= position) {
31
+ continue;
32
+ }
33
+ if (region.position >= complete) {
34
+ break;
35
+ }
36
+ if (region.isSelected && !strategy.showOrigin()) {
37
+ continue;
38
+ }
39
+ if (!overlapped.includes(region)) {
40
+ overlapped.push(region);
41
+ }
42
+ }
43
+ }
44
+ if (overlapped.length > 0) {
45
+ resolver.#overlappedByTrack.set(track, overlapped);
46
+ }
47
+ }
48
+ return {
49
+ postProcess: () => resolver.#solve(),
50
+ trackResolver: TrackResolver.Identity
51
+ };
52
+ }
53
+ /**
54
+ * For range-based operations (drop, duplicate).
55
+ * Returns a function that should be called AFTER the region is created.
56
+ */
57
+ static fromRange(track, position, complete, projectApi, boxAdapters) {
58
+ if (track.type === TrackType.Value) {
59
+ return () => { };
60
+ }
61
+ // Find overlapped regions BEFORE changes
62
+ const overlapped = [];
63
+ for (const region of track.regions.collection.iterateRange(0, complete)) {
64
+ if (region.complete <= position) {
65
+ continue;
66
+ }
67
+ if (region.position >= complete) {
68
+ break;
69
+ }
70
+ overlapped.push(region);
71
+ }
72
+ if (overlapped.length === 0) {
73
+ return EmptyExec;
74
+ }
75
+ return () => {
76
+ const targetTrack = RegionPushExistingResolver.#findOrCreateTrackBelow(track, overlapped, projectApi, boxAdapters);
77
+ if (isDefined(targetTrack)) {
78
+ for (const region of overlapped) {
79
+ region.box.regions.refer(targetTrack.box.regions);
80
+ }
81
+ track.regions.dispatchChange();
82
+ targetTrack.regions.dispatchChange();
83
+ }
84
+ };
85
+ }
86
+ static #findOrCreateTrackBelow(sourceTrack, regionsToPlace, projectApi, boxAdapters) {
87
+ const trackType = sourceTrack.type;
88
+ if (trackType === TrackType.Value) {
89
+ return undefined;
90
+ }
91
+ const minPosition = Math.min(...regionsToPlace.map(region => region.position));
92
+ const maxComplete = Math.max(...regionsToPlace.map(region => region.complete));
93
+ const audioUnit = sourceTrack.audioUnit;
94
+ // Get all tracks of same type in this audio unit, sorted by index
95
+ const siblingTracks = audioUnit.tracks.pointerHub.incoming()
96
+ .map(vertex => vertex.box)
97
+ .filter(trackBox => trackBox.type.getValue() === trackType)
98
+ .sort((boxA, boxB) => boxA.index.getValue() - boxB.index.getValue());
99
+ const sourceIndex = sourceTrack.indexField.getValue();
100
+ // Look for existing track below with space
101
+ for (const trackBox of siblingTracks) {
102
+ if (trackBox.index.getValue() <= sourceIndex) {
103
+ continue;
104
+ }
105
+ if (RegionPushExistingResolver.#hasSpace(trackBox, minPosition, maxComplete)) {
106
+ return boxAdapters.adapterFor(trackBox, TrackBoxAdapter);
107
+ }
108
+ }
109
+ // No suitable existing track found, create new track below source
110
+ const insertIndex = sourceIndex + 1;
111
+ const newTrackBox = trackType === TrackType.Audio
112
+ ? projectApi.createAudioTrack(audioUnit, insertIndex)
113
+ : projectApi.createNoteTrack(audioUnit, insertIndex);
114
+ return boxAdapters.adapterFor(newTrackBox, TrackBoxAdapter);
115
+ }
116
+ static #hasSpace(trackBox, position, complete) {
117
+ for (const vertex of trackBox.regions.pointerHub.incoming()) {
118
+ const regionPosition = vertex.box.position.getValue();
119
+ const regionDuration = vertex.box.duration.getValue();
120
+ const regionComplete = regionPosition + regionDuration;
121
+ if (regionComplete <= position) {
122
+ continue;
123
+ }
124
+ if (regionPosition >= complete) {
125
+ continue;
126
+ }
127
+ return false;
128
+ }
129
+ return true;
130
+ }
131
+ #projectApi;
132
+ #boxAdapters;
133
+ #overlappedByTrack;
134
+ constructor(projectApi, boxAdapters) {
135
+ this.#projectApi = projectApi;
136
+ this.#boxAdapters = boxAdapters;
137
+ this.#overlappedByTrack = new Map();
138
+ }
139
+ #solve() {
140
+ const affectedTracks = [];
141
+ for (const [track, overlapped] of this.#overlappedByTrack) {
142
+ const targetTrack = RegionPushExistingResolver.#findOrCreateTrackBelow(track, overlapped, this.#projectApi, this.#boxAdapters);
143
+ if (!isDefined(targetTrack)) {
144
+ continue;
145
+ }
146
+ if (!affectedTracks.includes(targetTrack)) {
147
+ affectedTracks.push(targetTrack);
148
+ }
149
+ if (!affectedTracks.includes(track)) {
150
+ affectedTracks.push(track);
151
+ }
152
+ for (const region of overlapped) {
153
+ region.box.regions.refer(targetTrack.box.regions);
154
+ }
155
+ }
156
+ // Dispatch change on all affected tracks to ensure UI updates
157
+ affectedTracks.forEach(track => track.regions.dispatchChange());
158
+ }
159
+ }
@@ -0,0 +1,14 @@
1
+ import { MutableObservableOption, Terminable } from "@opendaw/lib-std";
2
+ import { AnyRegionBoxAdapter, TrackBoxAdapter } from "@opendaw/studio-adapters";
3
+ export declare class TimelineFocus implements Terminable {
4
+ #private;
5
+ constructor();
6
+ get track(): MutableObservableOption<TrackBoxAdapter>;
7
+ get region(): MutableObservableOption<AnyRegionBoxAdapter>;
8
+ focusTrack(track: TrackBoxAdapter): void;
9
+ focusRegion(region: AnyRegionBoxAdapter): void;
10
+ clearRegionFocus(): void;
11
+ clear(): void;
12
+ terminate(): void;
13
+ }
14
+ //# sourceMappingURL=TimelineFocus.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TimelineFocus.d.ts","sourceRoot":"","sources":["../../../src/ui/timeline/TimelineFocus.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,uBAAuB,EAAE,UAAU,EAAa,MAAM,kBAAkB,CAAA;AAChF,OAAO,EAAC,mBAAmB,EAAE,eAAe,EAAC,MAAM,0BAA0B,CAAA;AAE7E,qBAAa,aAAc,YAAW,UAAU;;;IAY5C,IAAI,KAAK,IAAI,uBAAuB,CAAC,eAAe,CAAC,CAAqB;IAC1E,IAAI,MAAM,IAAI,uBAAuB,CAAC,mBAAmB,CAAC,CAAsB;IAEhF,UAAU,CAAC,KAAK,EAAE,eAAe,GAAG,IAAI;IAWxC,WAAW,CAAC,MAAM,EAAE,mBAAmB,GAAG,IAAI;IAW9C,gBAAgB,IAAI,IAAI;IAKxB,KAAK,IAAI,IAAI;IAOb,SAAS,IAAI,IAAI;CACpB"}
@@ -0,0 +1,45 @@
1
+ import { MutableObservableOption, Terminator } from "@opendaw/lib-std";
2
+ export class TimelineFocus {
3
+ #terminator = new Terminator();
4
+ #trackSubscription = this.#terminator.own(new Terminator());
5
+ #regionSubscription = this.#terminator.own(new Terminator());
6
+ #track;
7
+ #region;
8
+ constructor() {
9
+ this.#track = this.#terminator.own(new MutableObservableOption());
10
+ this.#region = this.#terminator.own(new MutableObservableOption());
11
+ }
12
+ get track() { return this.#track; }
13
+ get region() { return this.#region; }
14
+ focusTrack(track) {
15
+ this.#trackSubscription.terminate();
16
+ this.#trackSubscription.own(track.box.subscribeDeletion(() => {
17
+ console.debug("TimelineFocus: track deleted");
18
+ this.#trackSubscription.terminate();
19
+ this.#track.clear();
20
+ }));
21
+ this.#track.wrap(track);
22
+ this.clearRegionFocus();
23
+ }
24
+ focusRegion(region) {
25
+ region.trackBoxAdapter.ifSome(track => this.focusTrack(track));
26
+ this.#regionSubscription.terminate();
27
+ this.#regionSubscription.own(region.box.subscribeDeletion(() => {
28
+ console.debug("TimelineFocus: region deleted");
29
+ this.#regionSubscription.terminate();
30
+ this.#region.clear();
31
+ }));
32
+ this.#region.wrap(region);
33
+ }
34
+ clearRegionFocus() {
35
+ this.#regionSubscription.terminate();
36
+ this.#region.clear();
37
+ }
38
+ clear() {
39
+ this.#trackSubscription.terminate();
40
+ this.#regionSubscription.terminate();
41
+ this.#track.clear();
42
+ this.#region.clear();
43
+ }
44
+ terminate() { this.#terminator.terminate(); }
45
+ }
@@ -0,0 +1,11 @@
1
+ import { AnyRegionBoxAdapter, TrackBoxAdapter } from "@opendaw/studio-adapters";
2
+ /**
3
+ * Function to resolve the target track for a region.
4
+ * Used by overlap resolvers to provide track overrides for copy operations.
5
+ */
6
+ export type TrackResolver = (adapter: AnyRegionBoxAdapter, defaultTrack: TrackBoxAdapter) => TrackBoxAdapter;
7
+ export declare namespace TrackResolver {
8
+ /** Identity resolver - always returns the default track */
9
+ const Identity: TrackResolver;
10
+ }
11
+ //# sourceMappingURL=TrackResolver.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TrackResolver.d.ts","sourceRoot":"","sources":["../../../src/ui/timeline/TrackResolver.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,mBAAmB,EAAE,eAAe,EAAC,MAAM,0BAA0B,CAAA;AAE7E;;;GAGG;AACH,MAAM,MAAM,aAAa,GAAG,CAAC,OAAO,EAAE,mBAAmB,EAAE,YAAY,EAAE,eAAe,KAAK,eAAe,CAAA;AAE5G,yBAAiB,aAAa,CAAC;IAC3B,2DAA2D;IACpD,MAAM,QAAQ,EAAE,aAAwD,CAAA;CAClF"}
@@ -0,0 +1,5 @@
1
+ export var TrackResolver;
2
+ (function (TrackResolver) {
3
+ /** Identity resolver - always returns the default track */
4
+ TrackResolver.Identity = (_adapter, defaultTrack) => defaultTrack;
5
+ })(TrackResolver || (TrackResolver = {}));