@opendaw/studio-core 0.0.126 → 0.0.128

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 (82) hide show
  1. package/dist/AssetService.d.ts.map +1 -1
  2. package/dist/AssetService.js +7 -7
  3. package/dist/AudioConsolidation.d.ts.map +1 -1
  4. package/dist/AudioConsolidation.js +2 -2
  5. package/dist/EffectBox.d.ts +2 -2
  6. package/dist/EffectBox.d.ts.map +1 -1
  7. package/dist/EffectFactories.d.ts +20 -14
  8. package/dist/EffectFactories.d.ts.map +1 -1
  9. package/dist/EffectFactories.js +88 -20
  10. package/dist/EffectFactory.d.ts +1 -0
  11. package/dist/EffectFactory.d.ts.map +1 -1
  12. package/dist/Engine.d.ts +2 -1
  13. package/dist/Engine.d.ts.map +1 -1
  14. package/dist/EngineFacade.d.ts +2 -1
  15. package/dist/EngineFacade.d.ts.map +1 -1
  16. package/dist/EngineFacade.js +3 -0
  17. package/dist/EngineWorklet.d.ts +2 -1
  18. package/dist/EngineWorklet.d.ts.map +1 -1
  19. package/dist/EngineWorklet.js +11 -1
  20. package/dist/Mixer.d.ts.map +1 -1
  21. package/dist/Mixer.js +3 -2
  22. package/dist/OfflineEngineRenderer.d.ts.map +1 -1
  23. package/dist/OfflineEngineRenderer.js +41 -3
  24. package/dist/StudioPreferences.d.ts +1 -1
  25. package/dist/StudioSettings.d.ts +1 -1
  26. package/dist/StudioSettings.js +2 -2
  27. package/dist/capture/RecordAudio.d.ts.map +1 -1
  28. package/dist/capture/RecordAudio.js +48 -18
  29. package/dist/capture/RecordAutomation.d.ts.map +1 -1
  30. package/dist/capture/RecordAutomation.js +219 -198
  31. package/dist/capture/RecordMidi.d.ts.map +1 -1
  32. package/dist/capture/RecordMidi.js +1 -1
  33. package/dist/cloud/CloudBackupSamples.js +1 -1
  34. package/dist/dawproject/DawProjectExporter.js +1 -1
  35. package/dist/dawproject/DawProjectService.d.ts.map +1 -1
  36. package/dist/dawproject/DawProjectService.js +3 -16
  37. package/dist/index.d.ts +0 -1
  38. package/dist/index.d.ts.map +1 -1
  39. package/dist/index.js +0 -1
  40. package/dist/midi/MidiDevices.d.ts.map +1 -1
  41. package/dist/midi/MidiDevices.js +8 -2
  42. package/dist/offline-engine.js +1 -1
  43. package/dist/offline-engine.js.map +3 -3
  44. package/dist/processors.js +37 -25
  45. package/dist/processors.js.map +4 -4
  46. package/dist/project/Project.d.ts.map +1 -1
  47. package/dist/project/Project.js +30 -5
  48. package/dist/project/Recovery.js +1 -1
  49. package/dist/project/migration/MigrateAudioClipBox.d.ts.map +1 -1
  50. package/dist/project/migration/MigrateAudioClipBox.js +7 -0
  51. package/dist/project/migration/MigrateAudioRegionBox.d.ts.map +1 -1
  52. package/dist/project/migration/MigrateAudioRegionBox.js +7 -0
  53. package/dist/samples/OpenSampleAPI.d.ts.map +1 -1
  54. package/dist/samples/OpenSampleAPI.js +1 -1
  55. package/dist/samples/SampleService.js +1 -1
  56. package/dist/samples/SampleStorage.d.ts.map +1 -1
  57. package/dist/samples/SampleStorage.js +1 -1
  58. package/dist/ui/clipboard/ClipboardManager.d.ts.map +1 -1
  59. package/dist/ui/clipboard/ClipboardManager.js +18 -4
  60. package/dist/ui/clipboard/types/AudioUnitsClipboardHandler.d.ts.map +1 -1
  61. package/dist/ui/clipboard/types/AudioUnitsClipboardHandler.js +8 -2
  62. package/dist/ui/clipboard/types/DevicesClipboardHandler.d.ts.map +1 -1
  63. package/dist/ui/clipboard/types/DevicesClipboardHandler.js +77 -10
  64. package/dist/ui/clipboard/types/DevicesClipboardHandler.test.d.ts +2 -0
  65. package/dist/ui/clipboard/types/DevicesClipboardHandler.test.d.ts.map +1 -0
  66. package/dist/ui/clipboard/types/DevicesClipboardHandler.test.js +1154 -0
  67. package/dist/ui/timeline/RegionClipResolver.d.ts.map +1 -1
  68. package/dist/ui/timeline/RegionClipResolver.js +21 -29
  69. package/dist/ui/timeline/TimeGrid.d.ts +2 -0
  70. package/dist/ui/timeline/TimeGrid.d.ts.map +1 -1
  71. package/dist/ui/timeline/TimeGrid.js +13 -1
  72. package/dist/workers-main.js +1 -1
  73. package/dist/workers-main.js.map +3 -3
  74. package/dist/ysync/YService.d.ts +6 -1
  75. package/dist/ysync/YService.d.ts.map +1 -1
  76. package/dist/ysync/YService.js +2 -2
  77. package/dist/ysync/YSync.d.ts.map +1 -1
  78. package/dist/ysync/YSync.js +1 -0
  79. package/package.json +15 -15
  80. package/dist/WavFile.d.ts +0 -7
  81. package/dist/WavFile.d.ts.map +0 -1
  82. package/dist/WavFile.js +0 -120
@@ -2,134 +2,248 @@ import { Option, quantizeCeil, quantizeFloor, Terminable, UUID } from "@opendaw/
2
2
  import { Interpolation, PPQN } from "@opendaw/lib-dsp";
3
3
  import { Address } from "@opendaw/lib-box";
4
4
  import { TrackBox, ValueEventBox, ValueEventCollectionBox, ValueRegionBox } from "@opendaw/studio-boxes";
5
- import { ColorCodes, Devices, InterpolationFieldAdapter, TrackBoxAdapter, TrackType, ValueEventCollectionBoxAdapter } from "@opendaw/studio-adapters";
5
+ import { ColorCodes, InterpolationFieldAdapter, TrackBoxAdapter, TrackType, ValueEventCollectionBoxAdapter } from "@opendaw/studio-adapters";
6
6
  import { RegionClipResolver } from "../ui";
7
7
  export var RecordAutomation;
8
8
  (function (RecordAutomation) {
9
- const Eplison = 0.01;
10
- RecordAutomation.start = (project) => {
11
- const { editing, engine, boxAdapters, parameterFieldAdapters, boxGraph, timelineBox } = project;
12
- const activeRecordings = Address.newSet(state => state.adapter.address);
13
- let lastPosition = engine.position.getValue();
14
- const createRegion = (trackBoxAdapter, adapter, startPos, previousUnitValue, value, floating) => {
15
- const trackBox = trackBoxAdapter.box;
16
- project.selection.deselect(...trackBoxAdapter.regions.collection.asArray()
17
- .filter(region => region.isSelected)
18
- .map(region => region.box));
19
- RegionClipResolver.fromRange(trackBoxAdapter, startPos, startPos + PPQN.SemiQuaver)();
20
- const collectionBox = ValueEventCollectionBox.create(boxGraph, UUID.generate());
21
- const regionBox = ValueRegionBox.create(boxGraph, UUID.generate(), box => {
22
- box.position.setValue(startPos);
23
- box.duration.setValue(PPQN.SemiQuaver);
24
- box.loopDuration.setValue(PPQN.SemiQuaver);
25
- box.hue.setValue(ColorCodes.forTrackType(TrackType.Value));
26
- box.label.setValue(adapter.name);
27
- box.events.refer(collectionBox.owners);
28
- box.regions.refer(trackBox.regions);
9
+ const Epsilon = 0.01;
10
+ const findOrCreateTrack = ({ project, parameterFieldAdapters }, adapter) => {
11
+ const tracksOpt = parameterFieldAdapters.getTracks(adapter.address);
12
+ if (tracksOpt.isEmpty()) {
13
+ console.warn(`Cannot record automation: no tracks registered for '${adapter.name}' (${adapter.address})`);
14
+ return Option.None;
15
+ }
16
+ const tracks = tracksOpt.unwrap();
17
+ const existing = tracks.controls(adapter.field);
18
+ if (existing.nonEmpty()) {
19
+ return Option.wrap(existing.unwrap());
20
+ }
21
+ const trackBox = TrackBox.create(project.boxGraph, UUID.generate(), box => {
22
+ box.index.setValue(tracks.collection.getMinFreeIndex());
23
+ box.type.setValue(TrackType.Value);
24
+ box.tracks.refer(tracks.audioUnitBox.tracks);
25
+ box.target.refer(adapter.field);
26
+ });
27
+ return Option.wrap(project.boxAdapters.adapterFor(trackBox, TrackBoxAdapter));
28
+ };
29
+ const createRegion = ({ project }, trackBoxAdapter, adapter, startPos, previousUnitValue, value, floating) => {
30
+ const { boxGraph } = project;
31
+ const trackBox = trackBoxAdapter.box;
32
+ project.selection.deselect(...trackBoxAdapter.regions.collection.asArray()
33
+ .filter(region => region.isSelected)
34
+ .map(region => region.box));
35
+ RegionClipResolver.fromRange(trackBoxAdapter, startPos, startPos + PPQN.SemiQuaver)();
36
+ const collectionBox = ValueEventCollectionBox.create(boxGraph, UUID.generate());
37
+ const regionBox = ValueRegionBox.create(boxGraph, UUID.generate(), box => {
38
+ box.position.setValue(startPos);
39
+ box.duration.setValue(PPQN.SemiQuaver);
40
+ box.loopDuration.setValue(PPQN.SemiQuaver);
41
+ box.hue.setValue(ColorCodes.forTrackType(TrackType.Value));
42
+ box.label.setValue(adapter.name);
43
+ box.events.refer(collectionBox.owners);
44
+ box.regions.refer(trackBox.regions);
45
+ });
46
+ project.selection.select(regionBox);
47
+ const interpolation = floating ? Interpolation.Linear : Interpolation.None;
48
+ let lastEventBox;
49
+ if (previousUnitValue !== value) {
50
+ ValueEventBox.create(boxGraph, UUID.generate(), box => {
51
+ box.position.setValue(0);
52
+ box.value.setValue(previousUnitValue);
53
+ box.events.refer(collectionBox.events);
29
54
  });
30
- project.selection.select(regionBox);
31
- const interpolation = floating ? Interpolation.Linear : Interpolation.None;
32
- let lastEventBox;
33
- if (previousUnitValue !== value) {
34
- ValueEventBox.create(boxGraph, UUID.generate(), box => {
35
- box.position.setValue(0);
36
- box.value.setValue(previousUnitValue);
37
- box.events.refer(collectionBox.events);
38
- });
39
- lastEventBox = ValueEventBox.create(boxGraph, UUID.generate(), box => {
40
- box.position.setValue(0);
41
- box.index.setValue(1);
42
- box.value.setValue(value);
43
- box.events.refer(collectionBox.events);
44
- });
45
- InterpolationFieldAdapter.write(lastEventBox.interpolation, interpolation);
46
- }
47
- else {
48
- lastEventBox = ValueEventBox.create(boxGraph, UUID.generate(), box => {
49
- box.position.setValue(0);
50
- box.value.setValue(value);
51
- box.events.refer(collectionBox.events);
52
- });
53
- InterpolationFieldAdapter.write(lastEventBox.interpolation, interpolation);
54
- }
55
- return {
56
- adapter, trackBoxAdapter, regionBox, collectionBox,
57
- startPosition: startPos, floating, lastValue: value,
58
- lastRelativePosition: 0, lastEventBox
59
- };
55
+ lastEventBox = ValueEventBox.create(boxGraph, UUID.generate(), box => {
56
+ box.position.setValue(0);
57
+ box.index.setValue(1);
58
+ box.value.setValue(value);
59
+ box.events.refer(collectionBox.events);
60
+ });
61
+ InterpolationFieldAdapter.write(lastEventBox.interpolation, interpolation);
62
+ }
63
+ else {
64
+ lastEventBox = ValueEventBox.create(boxGraph, UUID.generate(), box => {
65
+ box.position.setValue(0);
66
+ box.value.setValue(value);
67
+ box.events.refer(collectionBox.events);
68
+ });
69
+ InterpolationFieldAdapter.write(lastEventBox.interpolation, interpolation);
70
+ }
71
+ return {
72
+ adapter, trackBoxAdapter, regionBox, collectionBox,
73
+ startPosition: startPos, floating, lastValue: value,
74
+ lastRelativePosition: 0, lastEventBox
60
75
  };
61
- const findOrCreateTrack = (adapter) => {
62
- const deviceBox = adapter.field.box;
63
- const deviceAdapterOpt = Option.tryCatch(() => boxAdapters.adapterFor(deviceBox, Devices.isAny));
64
- if (deviceAdapterOpt.isEmpty()) {
65
- console.warn(`Cannot record automation: could not find device adapter for ${deviceBox.name}`);
66
- return Option.None;
67
- }
68
- const deviceAdapter = deviceAdapterOpt.unwrap();
69
- const audioUnitAdapter = deviceAdapter.audioUnitBoxAdapter();
70
- const tracks = audioUnitAdapter.tracks;
71
- const existing = tracks.controls(adapter.field);
72
- if (existing.nonEmpty()) {
73
- return Option.wrap(existing.unwrap());
76
+ };
77
+ const simplifyRecordedEvents = ({ boxAdapters }, state) => {
78
+ if (!state.floating) {
79
+ return;
80
+ }
81
+ const adapter = boxAdapters.adapterFor(state.collectionBox, ValueEventCollectionBoxAdapter);
82
+ const events = [...adapter.events.asArray()];
83
+ const keep = [];
84
+ for (const event of events) {
85
+ while (keep.length >= 2) {
86
+ const a = keep[keep.length - 2];
87
+ const b = keep[keep.length - 1];
88
+ if (a.position === b.position || b.position === event.position) {
89
+ break;
90
+ }
91
+ if (a.interpolation.type !== "linear" || b.interpolation.type !== "linear") {
92
+ break;
93
+ }
94
+ const t = (b.position - a.position) / (event.position - a.position);
95
+ const expected = a.value + t * (event.value - a.value);
96
+ if (Math.abs(b.value - expected) > Epsilon) {
97
+ break;
98
+ }
99
+ keep.pop();
100
+ adapter.events.remove(b);
101
+ b.box.delete();
74
102
  }
75
- const trackBox = TrackBox.create(boxGraph, UUID.generate(), box => {
76
- box.index.setValue(tracks.collection.getMinFreeIndex());
77
- box.type.setValue(TrackType.Value);
78
- box.tracks.refer(audioUnitAdapter.box.tracks);
79
- box.target.refer(adapter.field);
103
+ keep.push(event);
104
+ }
105
+ };
106
+ const handleWriteUpdate = ({ boxGraph }, state, position, value) => {
107
+ if (position < state.startPosition) {
108
+ return;
109
+ }
110
+ const relativePosition = Math.trunc(position - state.startPosition);
111
+ if (relativePosition < state.lastRelativePosition) {
112
+ return;
113
+ }
114
+ if (relativePosition === state.lastRelativePosition) {
115
+ state.lastEventBox.value.setValue(value);
116
+ state.lastValue = value;
117
+ }
118
+ else {
119
+ const interpolation = state.floating ? Interpolation.Linear : Interpolation.None;
120
+ state.lastEventBox = ValueEventBox.create(boxGraph, UUID.generate(), box => {
121
+ box.position.setValue(relativePosition);
122
+ box.value.setValue(value);
123
+ box.events.refer(state.collectionBox.events);
80
124
  });
81
- return Option.wrap(boxAdapters.adapterFor(trackBox, TrackBoxAdapter));
82
- };
125
+ InterpolationFieldAdapter.write(state.lastEventBox.interpolation, interpolation);
126
+ state.lastValue = value;
127
+ state.lastRelativePosition = relativePosition;
128
+ }
129
+ };
130
+ const finalizeState = (project, state, finalPosition) => {
131
+ const { boxGraph } = project;
132
+ if (!state.regionBox.isAttached()) {
133
+ return;
134
+ }
135
+ const finalDuration = Math.max(0, quantizeCeil(finalPosition - state.startPosition, PPQN.SemiQuaver));
136
+ if (finalDuration <= 0) {
137
+ state.regionBox.delete();
138
+ return;
139
+ }
140
+ const oldDuration = state.regionBox.duration.getValue();
141
+ if (finalDuration > oldDuration) {
142
+ RegionClipResolver.fromRange(state.trackBoxAdapter, state.startPosition + oldDuration, state.startPosition + finalDuration)();
143
+ }
144
+ if (finalDuration > state.lastRelativePosition) {
145
+ ValueEventBox.create(boxGraph, UUID.generate(), box => {
146
+ box.position.setValue(finalDuration);
147
+ box.value.setValue(state.lastValue);
148
+ box.events.refer(state.collectionBox.events);
149
+ });
150
+ }
151
+ state.regionBox.duration.setValue(finalDuration);
152
+ state.regionBox.loopDuration.setValue(finalDuration);
153
+ simplifyRecordedEvents(project, state);
154
+ };
155
+ const updateRegionDurations = (project, activeRecordings, currentPosition, loopEnabled, loopTo) => {
156
+ const { editing } = project;
157
+ editing.modify(() => {
158
+ for (const state of activeRecordings.values()) {
159
+ if (!state.regionBox.isAttached()) {
160
+ continue;
161
+ }
162
+ const oldDuration = state.regionBox.duration.getValue();
163
+ const maxDuration = loopEnabled
164
+ ? loopTo - state.startPosition
165
+ : Infinity;
166
+ const newDuration = Math.max(PPQN.SemiQuaver, quantizeCeil(Math.min(maxDuration, currentPosition - state.startPosition), PPQN.SemiQuaver));
167
+ if (newDuration > oldDuration) {
168
+ RegionClipResolver.fromRange(state.trackBoxAdapter, state.startPosition + oldDuration, state.startPosition + newDuration)();
169
+ }
170
+ state.regionBox.duration.setValue(newDuration);
171
+ state.regionBox.loopDuration.setValue(newDuration);
172
+ }
173
+ }, false);
174
+ };
175
+ const handleLoopWrap = (project, ctx, activeRecordings, loopFrom, loopTo) => {
176
+ const { editing, boxGraph } = project;
177
+ editing.modify(() => {
178
+ const snapshot = [...activeRecordings.values()];
179
+ for (const state of snapshot) {
180
+ if (!state.regionBox.isAttached()) {
181
+ continue;
182
+ }
183
+ const finalDuration = Math.max(PPQN.SemiQuaver, quantizeCeil(loopTo - state.startPosition, PPQN.SemiQuaver));
184
+ const oldDuration = state.regionBox.duration.getValue();
185
+ if (finalDuration > oldDuration) {
186
+ RegionClipResolver.fromRange(state.trackBoxAdapter, state.startPosition + oldDuration, state.startPosition + finalDuration)();
187
+ }
188
+ if (finalDuration > state.lastRelativePosition) {
189
+ ValueEventBox.create(boxGraph, UUID.generate(), box => {
190
+ box.position.setValue(finalDuration);
191
+ box.value.setValue(state.lastValue);
192
+ box.events.refer(state.collectionBox.events);
193
+ });
194
+ }
195
+ state.regionBox.duration.setValue(finalDuration);
196
+ state.regionBox.loopDuration.setValue(finalDuration);
197
+ simplifyRecordedEvents(project, state);
198
+ project.selection.deselect(state.regionBox);
199
+ const newStartPos = quantizeFloor(loopFrom, PPQN.SemiQuaver);
200
+ const newState = createRegion(ctx, state.trackBoxAdapter, state.adapter, newStartPos, state.lastValue, state.lastValue, state.floating);
201
+ activeRecordings.removeByKey(state.adapter.address);
202
+ activeRecordings.add(newState);
203
+ }
204
+ }, false);
205
+ };
206
+ RecordAutomation.start = (project) => {
207
+ const { editing, engine, parameterFieldAdapters, timelineBox } = project;
208
+ const ctx = { project, parameterFieldAdapters };
209
+ const activeRecordings = Address.newSet(state => state.adapter.address);
210
+ let lastPosition = engine.position.getValue();
83
211
  const handleWrite = ({ adapter, previousUnitValue }) => {
84
212
  if (!engine.isRecording.getValue()) {
85
213
  return;
86
214
  }
215
+ if (!parameterFieldAdapters.isTouched(adapter.address)) {
216
+ return;
217
+ }
87
218
  const position = engine.position.getValue();
88
219
  const value = adapter.getUnitValue();
89
220
  const existingState = activeRecordings.opt(adapter.address);
90
221
  if (existingState.isEmpty()) {
91
222
  editing.modify(() => {
92
- const trackOpt = findOrCreateTrack(adapter);
223
+ const trackOpt = findOrCreateTrack(ctx, adapter);
93
224
  if (trackOpt.isEmpty()) {
94
225
  return;
95
226
  }
96
- const trackBoxAdapter = trackOpt.unwrap();
97
227
  const startPos = quantizeFloor(position, PPQN.SemiQuaver);
98
228
  const floating = adapter.valueMapping.floating();
99
- const state = createRegion(trackBoxAdapter, adapter, startPos, previousUnitValue, value, floating);
229
+ const state = createRegion(ctx, trackOpt.unwrap(), adapter, startPos, previousUnitValue, value, floating);
100
230
  activeRecordings.add(state);
101
231
  });
102
232
  }
103
233
  else {
104
- const state = existingState.unwrap();
105
- if (position < state.startPosition) {
106
- return;
107
- }
108
- const relativePosition = Math.trunc(position - state.startPosition);
109
- if (relativePosition < state.lastRelativePosition) {
110
- return;
111
- }
112
- if (relativePosition === state.lastRelativePosition) {
113
- editing.modify(() => {
114
- state.lastEventBox.value.setValue(value);
115
- state.lastValue = value;
116
- }, false);
117
- }
118
- else {
119
- editing.modify(() => {
120
- const interpolation = state.floating ? Interpolation.Linear : Interpolation.None;
121
- state.lastEventBox = ValueEventBox.create(boxGraph, UUID.generate(), box => {
122
- box.position.setValue(relativePosition);
123
- box.value.setValue(value);
124
- box.events.refer(state.collectionBox.events);
125
- });
126
- InterpolationFieldAdapter.write(state.lastEventBox.interpolation, interpolation);
127
- state.lastValue = value;
128
- state.lastRelativePosition = relativePosition;
129
- }, false);
130
- }
234
+ editing.modify(() => handleWriteUpdate(project, existingState.unwrap(), position, value), false);
131
235
  }
132
236
  };
237
+ const handleTouchEnd = (address) => {
238
+ const stateOpt = activeRecordings.opt(address);
239
+ if (stateOpt.isEmpty()) {
240
+ return;
241
+ }
242
+ editing.modify(() => {
243
+ finalizeState(project, stateOpt.unwrap(), engine.position.getValue());
244
+ activeRecordings.removeByKey(address);
245
+ });
246
+ };
133
247
  const handlePosition = () => {
134
248
  if (!engine.isRecording.getValue()) {
135
249
  return;
@@ -142,82 +256,10 @@ export var RecordAutomation;
142
256
  const loopFrom = timelineBox.loopArea.from.getValue();
143
257
  const loopTo = timelineBox.loopArea.to.getValue();
144
258
  if (loopEnabled && currentPosition < lastPosition) {
145
- editing.modify(() => {
146
- const snapshot = [...activeRecordings.values()];
147
- for (const state of snapshot) {
148
- if (!state.regionBox.isAttached()) {
149
- continue;
150
- }
151
- const finalDuration = Math.max(PPQN.SemiQuaver, quantizeCeil(loopTo - state.startPosition, PPQN.SemiQuaver));
152
- const oldDuration = state.regionBox.duration.getValue();
153
- if (finalDuration > oldDuration) {
154
- RegionClipResolver.fromRange(state.trackBoxAdapter, state.startPosition + oldDuration, state.startPosition + finalDuration)();
155
- }
156
- if (finalDuration > state.lastRelativePosition) {
157
- ValueEventBox.create(boxGraph, UUID.generate(), box => {
158
- box.position.setValue(finalDuration);
159
- box.value.setValue(state.lastValue);
160
- box.events.refer(state.collectionBox.events);
161
- });
162
- }
163
- state.regionBox.duration.setValue(finalDuration);
164
- state.regionBox.loopDuration.setValue(finalDuration);
165
- simplifyRecordedEvents(state);
166
- project.selection.deselect(state.regionBox);
167
- const newStartPos = quantizeFloor(loopFrom, PPQN.SemiQuaver);
168
- const newState = createRegion(state.trackBoxAdapter, state.adapter, newStartPos, state.lastValue, state.lastValue, state.floating);
169
- activeRecordings.removeByKey(state.adapter.address);
170
- activeRecordings.add(newState);
171
- }
172
- }, false);
259
+ handleLoopWrap(project, ctx, activeRecordings, loopFrom, loopTo);
173
260
  }
174
261
  lastPosition = currentPosition;
175
- editing.modify(() => {
176
- for (const state of activeRecordings.values()) {
177
- if (!state.regionBox.isAttached()) {
178
- continue;
179
- }
180
- const oldDuration = state.regionBox.duration.getValue();
181
- const maxDuration = loopEnabled
182
- ? loopTo - state.startPosition
183
- : Infinity;
184
- const newDuration = Math.max(PPQN.SemiQuaver, quantizeCeil(Math.min(maxDuration, currentPosition - state.startPosition), PPQN.SemiQuaver));
185
- if (newDuration > oldDuration) {
186
- RegionClipResolver.fromRange(state.trackBoxAdapter, state.startPosition + oldDuration, state.startPosition + newDuration)();
187
- }
188
- state.regionBox.duration.setValue(newDuration);
189
- state.regionBox.loopDuration.setValue(newDuration);
190
- }
191
- }, false);
192
- };
193
- const simplifyRecordedEvents = (state) => {
194
- if (!state.floating) {
195
- return;
196
- }
197
- const adapter = boxAdapters.adapterFor(state.collectionBox, ValueEventCollectionBoxAdapter);
198
- const events = [...adapter.events.asArray()];
199
- const keep = [];
200
- for (const event of events) {
201
- while (keep.length >= 2) {
202
- const a = keep[keep.length - 2];
203
- const b = keep[keep.length - 1];
204
- if (a.position === b.position || b.position === event.position) {
205
- break;
206
- }
207
- if (a.interpolation.type !== "linear" || b.interpolation.type !== "linear") {
208
- break;
209
- }
210
- const t = (b.position - a.position) / (event.position - a.position);
211
- const expected = a.value + t * (event.value - a.value);
212
- if (Math.abs(b.value - expected) > Eplison) {
213
- break;
214
- }
215
- keep.pop();
216
- adapter.events.remove(b);
217
- b.box.delete();
218
- }
219
- keep.push(event);
220
- }
262
+ updateRegionDurations(project, activeRecordings, currentPosition, loopEnabled, loopTo);
221
263
  };
222
264
  const handleTermination = () => {
223
265
  if (activeRecordings.size() === 0) {
@@ -226,31 +268,10 @@ export var RecordAutomation;
226
268
  const finalPosition = engine.position.getValue();
227
269
  editing.modify(() => {
228
270
  for (const state of activeRecordings.values()) {
229
- if (!state.regionBox.isAttached()) {
230
- continue;
231
- }
232
- const finalDuration = Math.max(0, quantizeCeil(finalPosition - state.startPosition, PPQN.SemiQuaver));
233
- if (finalDuration <= 0) {
234
- state.regionBox.delete();
235
- continue;
236
- }
237
- const oldDuration = state.regionBox.duration.getValue();
238
- if (finalDuration > oldDuration) {
239
- RegionClipResolver.fromRange(state.trackBoxAdapter, state.startPosition + oldDuration, state.startPosition + finalDuration)();
240
- }
241
- if (finalDuration > state.lastRelativePosition) {
242
- ValueEventBox.create(boxGraph, UUID.generate(), box => {
243
- box.position.setValue(finalDuration);
244
- box.value.setValue(state.lastValue);
245
- box.events.refer(state.collectionBox.events);
246
- });
247
- }
248
- state.regionBox.duration.setValue(finalDuration);
249
- state.regionBox.loopDuration.setValue(finalDuration);
250
- simplifyRecordedEvents(state);
271
+ finalizeState(project, state, finalPosition);
251
272
  }
252
273
  });
253
274
  };
254
- return Terminable.many(parameterFieldAdapters.subscribeWrites(handleWrite), engine.position.subscribe(handlePosition), Terminable.create(handleTermination));
275
+ return Terminable.many(parameterFieldAdapters.subscribeWrites(handleWrite), engine.position.subscribe(handlePosition), parameterFieldAdapters.subscribeTouchEnd(handleTouchEnd), Terminable.create(handleTermination));
255
276
  };
256
277
  })(RecordAutomation || (RecordAutomation = {}));
@@ -1 +1 @@
1
- {"version":3,"file":"RecordMidi.d.ts","sourceRoot":"","sources":["../../src/capture/RecordMidi.ts"],"names":[],"mappings":"AAAA,OAAO,EAIH,QAAQ,EAKR,UAAU,EAGb,MAAM,kBAAkB,CAAA;AAGzB,OAAO,EAAa,UAAU,EAA2B,MAAM,0BAA0B,CAAA;AACzF,OAAO,EAAC,OAAO,EAAC,MAAM,YAAY,CAAA;AAClC,OAAO,EAAC,OAAO,EAAC,MAAM,WAAW,CAAA;AAGjC,yBAAiB,UAAU,CAAC;IACxB,KAAK,iBAAiB,GAAG;QACrB,QAAQ,EAAE,QAAQ,CAAC,UAAU,CAAC,CAAC;QAC/B,OAAO,EAAE,OAAO,CAAC;QACjB,OAAO,EAAE,OAAO,CAAA;KACnB,CAAA;IAgBD,MAAM,CAAC,MAAM,KAAK,GAAI,gCAA8B,iBAAiB,KAAG,UA2KvE,CAAA;;CACJ"}
1
+ {"version":3,"file":"RecordMidi.d.ts","sourceRoot":"","sources":["../../src/capture/RecordMidi.ts"],"names":[],"mappings":"AAAA,OAAO,EAIH,QAAQ,EAKR,UAAU,EAGb,MAAM,kBAAkB,CAAA;AAGzB,OAAO,EAAa,UAAU,EAA2B,MAAM,0BAA0B,CAAA;AACzF,OAAO,EAAC,OAAO,EAAC,MAAM,YAAY,CAAA;AAClC,OAAO,EAAC,OAAO,EAAC,MAAM,WAAW,CAAA;AAGjC,yBAAiB,UAAU,CAAC;IACxB,KAAK,iBAAiB,GAAG;QACrB,QAAQ,EAAE,QAAQ,CAAC,UAAU,CAAC,CAAC;QAC/B,OAAO,EAAE,OAAO,CAAC;QACjB,OAAO,EAAE,OAAO,CAAA;KACnB,CAAA;IAgBD,MAAM,CAAC,MAAM,KAAK,GAAI,gCAA8B,iBAAiB,KAAG,UA4KvE,CAAA;;CACJ"}
@@ -131,7 +131,7 @@ export var RecordMidi;
131
131
  if (regionBox.isAttached() && collection.isAttached()) {
132
132
  const { position: regionPosition, duration, loopDuration } = regionBox;
133
133
  const maxDuration = loopEnabled && allowTakes ? loopTo - regionPosition.getValue() : Infinity;
134
- const newDuration = Math.min(maxDuration, quantizeCeil(writePosition, beats) - regionPosition.getValue());
134
+ const newDuration = Math.max(duration.getValue(), Math.min(maxDuration, quantizeCeil(writePosition, beats) - regionPosition.getValue()));
135
135
  duration.setValue(newDuration);
136
136
  loopDuration.setValue(newDuration);
137
137
  for (const { event, take, creationOffset } of activeNotes.values()) {
@@ -3,7 +3,7 @@ import { network, Promises } from "@opendaw/lib-runtime";
3
3
  import { SamplePeaks } from "@opendaw/lib-fusion";
4
4
  import { OpenSampleAPI, SampleStorage } from "../samples";
5
5
  import { Workers } from "../Workers";
6
- import { WavFile } from "../WavFile";
6
+ import { WavFile } from "@opendaw/lib-dsp";
7
7
  export class CloudBackupSamples {
8
8
  static RemotePath = "samples";
9
9
  static RemoteCatalogPath = `${this.RemotePath}/index.json`;
@@ -9,7 +9,7 @@ import { AudioFileBox, AudioUnitBox, NoteEventBox, NoteEventCollectionBox, Signa
9
9
  import { ColorCodes, DeviceBoxUtils, InterpolationFieldAdapter } from "@opendaw/studio-adapters";
10
10
  import { AudioUnitExportLayout } from "./AudioUnitExportLayout";
11
11
  import { DeviceIO } from "./DeviceIO";
12
- import { WavFile } from "../WavFile";
12
+ import { WavFile } from "@opendaw/lib-dsp";
13
13
  export var DawProjectExporter;
14
14
  (function (DawProjectExporter) {
15
15
  DawProjectExporter.write = (skeleton, sampleManager, resourcePacker) => {
@@ -1 +1 @@
1
- {"version":3,"file":"DawProjectService.d.ts","sourceRoot":"","sources":["../../src/dawproject/DawProjectService.ts"],"names":[],"mappings":"AAAA,OAAO,EAAoB,MAAM,EAAyB,MAAM,kBAAkB,CAAA;AAKlF,OAAO,EAAC,eAAe,EAAC,MAAM,0BAA0B,CAAA;AAIxD,OAAO,EAAC,cAAc,EAAC,MAAM,YAAY,CAAA;AACzC,OAAO,EAAC,aAAa,EAAC,MAAM,YAAY,CAAA;AAExC,qBAAa,iBAAiB;WACb,gBAAgB,CAAC,aAAa,EAAE,aAAa,GAAG,OAAO,CAAC,MAAM,CAAC,eAAe,CAAC,CAAC;WA2BhF,gBAAgB,CAAC,OAAO,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC;CA4BxE"}
1
+ {"version":3,"file":"DawProjectService.d.ts","sourceRoot":"","sources":["../../src/dawproject/DawProjectService.ts"],"names":[],"mappings":"AAAA,OAAO,EAAoB,MAAM,EAAyB,MAAM,kBAAkB,CAAA;AAKlF,OAAO,EAAC,eAAe,EAAC,MAAM,0BAA0B,CAAA;AAIxD,OAAO,EAAC,cAAc,EAAC,MAAM,YAAY,CAAA;AACzC,OAAO,EAAC,aAAa,EAAC,MAAM,YAAY,CAAA;AAExC,qBAAa,iBAAiB;WACb,gBAAgB,CAAC,aAAa,EAAE,aAAa,GAAG,OAAO,CAAC,MAAM,CAAC,eAAe,CAAC,CAAC;WA2BhF,gBAAgB,CAAC,OAAO,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC;CAmBxE"}
@@ -48,22 +48,9 @@ export class DawProjectService {
48
48
  if (status === "rejected") {
49
49
  return RuntimeNotifier.info({ headline: "Export Error", message: String(error) });
50
50
  }
51
- else {
52
- const approved = await RuntimeNotifier.approve({
53
- headline: "Save DawProject?",
54
- message: "",
55
- approveText: "Save"
56
- });
57
- if (!approved) {
58
- return;
59
- }
60
- const { status, error } = await Promises.tryCatch(Files.save(zip, { types: [FilePickerAcceptTypes.DawprojectFileType] }));
61
- if (status === "rejected" && !Errors.isAbort(error)) {
62
- throw error;
63
- }
64
- else {
65
- return;
66
- }
51
+ const saveResult = await Promises.tryCatch(Files.save(zip, { types: [FilePickerAcceptTypes.DawprojectFileType] }));
52
+ if (saveResult.status === "rejected" && !Errors.isAbort(saveResult.error)) {
53
+ throw saveResult.error;
67
54
  }
68
55
  }
69
56
  }
package/dist/index.d.ts CHANGED
@@ -27,7 +27,6 @@ export * from "./Mixer";
27
27
  export * from "./PeaksWriter";
28
28
  export * from "./StudioPreferences";
29
29
  export * from "./RenderQuantum";
30
- export * from "./WavFile";
31
30
  export * from "./Workers";
32
31
  export * from "./AudioWorklets";
33
32
  export * from "./StudioSettings";
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,WAAW,CAAA;AACzB,cAAc,SAAS,CAAA;AACvB,cAAc,cAAc,CAAA;AAC5B,mBAAmB,UAAU,CAAA;AAC7B,cAAc,QAAQ,CAAA;AACtB,cAAc,WAAW,CAAA;AACzB,cAAc,WAAW,CAAA;AACzB,cAAc,aAAa,CAAA;AAC3B,cAAc,YAAY,CAAA;AAC1B,cAAc,MAAM,CAAA;AACpB,cAAc,SAAS,CAAA;AAEvB,cAAc,sBAAsB,CAAA;AACpC,cAAc,gBAAgB,CAAA;AAC9B,cAAc,wBAAwB,CAAA;AACtC,cAAc,yBAAyB,CAAA;AACvC,cAAc,cAAc,CAAA;AAC5B,cAAc,aAAa,CAAA;AAC3B,cAAc,iBAAiB,CAAA;AAC/B,cAAc,mBAAmB,CAAA;AACjC,cAAc,UAAU,CAAA;AACxB,cAAc,gBAAgB,CAAA;AAC9B,cAAc,iBAAiB,CAAA;AAC/B,cAAc,eAAe,CAAA;AAC7B,cAAc,yBAAyB,CAAA;AACvC,cAAc,gBAAgB,CAAA;AAC9B,cAAc,SAAS,CAAA;AACvB,cAAc,eAAe,CAAA;AAC7B,cAAc,qBAAqB,CAAA;AACnC,cAAc,iBAAiB,CAAA;AAC/B,cAAc,WAAW,CAAA;AACzB,cAAc,WAAW,CAAA;AACzB,cAAc,iBAAiB,CAAA;AAC/B,cAAc,kBAAkB,CAAA;AAChC,cAAc,mBAAmB,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,WAAW,CAAA;AACzB,cAAc,SAAS,CAAA;AACvB,cAAc,cAAc,CAAA;AAC5B,mBAAmB,UAAU,CAAA;AAC7B,cAAc,QAAQ,CAAA;AACtB,cAAc,WAAW,CAAA;AACzB,cAAc,WAAW,CAAA;AACzB,cAAc,aAAa,CAAA;AAC3B,cAAc,YAAY,CAAA;AAC1B,cAAc,MAAM,CAAA;AACpB,cAAc,SAAS,CAAA;AAEvB,cAAc,sBAAsB,CAAA;AACpC,cAAc,gBAAgB,CAAA;AAC9B,cAAc,wBAAwB,CAAA;AACtC,cAAc,yBAAyB,CAAA;AACvC,cAAc,cAAc,CAAA;AAC5B,cAAc,aAAa,CAAA;AAC3B,cAAc,iBAAiB,CAAA;AAC/B,cAAc,mBAAmB,CAAA;AACjC,cAAc,UAAU,CAAA;AACxB,cAAc,gBAAgB,CAAA;AAC9B,cAAc,iBAAiB,CAAA;AAC/B,cAAc,eAAe,CAAA;AAC7B,cAAc,yBAAyB,CAAA;AACvC,cAAc,gBAAgB,CAAA;AAC9B,cAAc,SAAS,CAAA;AACvB,cAAc,eAAe,CAAA;AAC7B,cAAc,qBAAqB,CAAA;AACnC,cAAc,iBAAiB,CAAA;AAC/B,cAAc,WAAW,CAAA;AACzB,cAAc,iBAAiB,CAAA;AAC/B,cAAc,kBAAkB,CAAA;AAChC,cAAc,mBAAmB,CAAA"}
package/dist/index.js CHANGED
@@ -26,7 +26,6 @@ export * from "./Mixer";
26
26
  export * from "./PeaksWriter";
27
27
  export * from "./StudioPreferences";
28
28
  export * from "./RenderQuantum";
29
- export * from "./WavFile";
30
29
  export * from "./Workers";
31
30
  export * from "./AudioWorklets";
32
31
  export * from "./StudioSettings";
@@ -1 +1 @@
1
- {"version":3,"file":"MidiDevices.d.ts","sourceRoot":"","sources":["../../src/midi/MidiDevices.ts"],"names":[],"mappings":"AAAA,OAAO,EACH,IAAI,EAIJ,sBAAsB,EAEtB,gBAAgB,EAEhB,QAAQ,EACR,MAAM,EAEN,SAAS,EACT,YAAY,EAGf,MAAM,kBAAkB,CAAA;AAIzB,OAAO,EAAC,iBAAiB,EAAC,MAAM,qBAAqB,CAAA;AAErD,qBAAa,WAAW;;IACpB,MAAM,CAAC,oBAAoB,IAAI,OAAO;IAEtC,MAAM,CAAC,QAAQ,CAAC,iBAAiB,EAAE,iBAAiB,CAA0B;IAI9E,MAAM,CAAC,wBAAwB,CAAC,SAAS,EAAE,SAAS,CAAC,UAAU,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,CAAC,EAAE,MAAM,GAAG,UAAU;WAgC3F,iBAAiB;IAiB9B,MAAM,CAAC,GAAG,IAAI,gBAAgB,CAAC,UAAU,CAAC;IAE1C,MAAM,CAAC,sBAAsB,CAAC,QAAQ,EAAE,QAAQ,CAAC,gBAAgB,CAAC,EAAE,OAAO,CAAC,EAAE,IAAI,GAAG,YAAY;IAWjG,MAAM,CAAC,YAAY,IAAI,aAAa,CAAC,SAAS,CAAC;IAK/C,MAAM,CAAC,aAAa,IAAI,aAAa,CAAC,UAAU,CAAC;IAMjD,MAAM,CAAC,mBAAmB,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,CAAC,SAAS,CAAC;IAIzD,MAAM,CAAC,oBAAoB,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,CAAC,UAAU,CAAC;IAI3D,MAAM,CAAC,oBAAoB,IAAI,MAAM,CAAC,aAAa,CAAC,SAAS,CAAC,CAAC;IAI/D,MAAM,CAAC,qBAAqB,IAAI,MAAM,CAAC,aAAa,CAAC,UAAU,CAAC,CAAC;IAIjE,MAAM,CAAC,KAAK,IAAI,IAAI;IAkBpB,MAAM,CAAC,SAAS,IAAI,sBAAsB,CAAC,OAAO,CAAC;CAqCtD"}
1
+ {"version":3,"file":"MidiDevices.d.ts","sourceRoot":"","sources":["../../src/midi/MidiDevices.ts"],"names":[],"mappings":"AAAA,OAAO,EACH,IAAI,EAKJ,sBAAsB,EAEtB,gBAAgB,EAEhB,QAAQ,EACR,MAAM,EAEN,SAAS,EACT,YAAY,EAGf,MAAM,kBAAkB,CAAA;AAIzB,OAAO,EAAC,iBAAiB,EAAC,MAAM,qBAAqB,CAAA;AAErD,qBAAa,WAAW;;IACpB,MAAM,CAAC,oBAAoB,IAAI,OAAO;IAEtC,MAAM,CAAC,QAAQ,CAAC,iBAAiB,EAAE,iBAAiB,CAA0B;IAI9E,MAAM,CAAC,wBAAwB,CAAC,SAAS,EAAE,SAAS,CAAC,UAAU,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,CAAC,EAAE,MAAM,GAAG,UAAU;WAsC3F,iBAAiB;IAiB9B,MAAM,CAAC,GAAG,IAAI,gBAAgB,CAAC,UAAU,CAAC;IAE1C,MAAM,CAAC,sBAAsB,CAAC,QAAQ,EAAE,QAAQ,CAAC,gBAAgB,CAAC,EAAE,OAAO,CAAC,EAAE,IAAI,GAAG,YAAY;IAWjG,MAAM,CAAC,YAAY,IAAI,aAAa,CAAC,SAAS,CAAC;IAK/C,MAAM,CAAC,aAAa,IAAI,aAAa,CAAC,UAAU,CAAC;IAMjD,MAAM,CAAC,mBAAmB,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,CAAC,SAAS,CAAC;IAIzD,MAAM,CAAC,oBAAoB,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,CAAC,UAAU,CAAC;IAI3D,MAAM,CAAC,oBAAoB,IAAI,MAAM,CAAC,aAAa,CAAC,SAAS,CAAC,CAAC;IAI/D,MAAM,CAAC,qBAAqB,IAAI,MAAM,CAAC,aAAa,CAAC,UAAU,CAAC,CAAC;IAIjE,MAAM,CAAC,KAAK,IAAI,IAAI;IAkBpB,MAAM,CAAC,SAAS,IAAI,sBAAsB,CAAC,OAAO,CAAC;CAqCtD"}
@@ -7,7 +7,7 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
7
7
  var __metadata = (this && this.__metadata) || function (k, v) {
8
8
  if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
9
9
  };
10
- import { Errors, Lazy, MutableObservableOption, MutableObservableValue, Notifier, Option, panic, Terminator, UUID } from "@opendaw/lib-std";
10
+ import { Errors, isAbsent, Lazy, MutableObservableOption, MutableObservableValue, Notifier, Option, panic, Terminator, UUID } from "@opendaw/lib-std";
11
11
  import { MidiData } from "@opendaw/lib-midi";
12
12
  import { Promises } from "@opendaw/lib-runtime";
13
13
  import { MIDIMessageSubscriber } from "./MIDIMessageSubscriber";
@@ -46,7 +46,13 @@ export class MidiDevices {
46
46
  MidiDevices.#softwareMIDIOutputs.set(output.id, output);
47
47
  return output;
48
48
  }
49
- static #memoizedRequest = Promises.memoizeAsync(() => navigator.requestMIDIAccess({ sysex: false }));
49
+ static #memoizedRequest = Promises.memoizeAsync(async () => {
50
+ const result = await navigator.requestMIDIAccess({ sysex: false });
51
+ if (isAbsent(result)) {
52
+ return panic("You browser does not support MIDI.");
53
+ }
54
+ return result;
55
+ });
50
56
  static async requestPermission() {
51
57
  if (this.canRequestMidiAccess()) {
52
58
  const { status, value: midiAccess, error } = await Promises.tryCatch(this.#memoizedRequest());