@opendaw/studio-core 0.0.87 → 0.0.89

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 (41) hide show
  1. package/dist/capture/RecordAudio.d.ts.map +1 -1
  2. package/dist/capture/RecordAudio.js +9 -4
  3. package/dist/capture/RecordMidi.d.ts.map +1 -1
  4. package/dist/capture/RecordMidi.js +90 -55
  5. package/dist/processors.js +1 -1
  6. package/dist/processors.js.map +2 -2
  7. package/dist/ui/generic/ClipboardManager.d.ts +19 -0
  8. package/dist/ui/generic/ClipboardManager.d.ts.map +1 -0
  9. package/dist/ui/generic/ClipboardManager.js +77 -0
  10. package/dist/ui/generic/ContextMenu.d.ts +13 -0
  11. package/dist/ui/generic/ContextMenu.d.ts.map +1 -0
  12. package/dist/ui/generic/ContextMenu.js +61 -0
  13. package/dist/ui/generic/menu-item.d.ts +95 -0
  14. package/dist/ui/generic/menu-item.d.ts.map +1 -0
  15. package/dist/ui/generic/menu-item.js +124 -0
  16. package/dist/ui/index.d.ts +7 -4
  17. package/dist/ui/index.d.ts.map +1 -1
  18. package/dist/ui/index.js +7 -4
  19. package/dist/ui/timeline/RegionClipResolver.d.ts.map +1 -0
  20. package/dist/ui/timeline/RegionClipResolver.test.d.ts.map +1 -0
  21. package/dist/ui/timeline/RegionModifyStrategies.d.ts.map +1 -0
  22. package/dist/ui/timeline/TimeGrid.d.ts.map +1 -0
  23. package/dist/ui/timeline/TimelineRange.d.ts.map +1 -0
  24. package/dist/ysync/YMapper.d.ts.map +1 -1
  25. package/dist/ysync/YMapper.js +13 -5
  26. package/package.json +10 -10
  27. package/dist/ui/RegionClipResolver.d.ts.map +0 -1
  28. package/dist/ui/RegionClipResolver.test.d.ts.map +0 -1
  29. package/dist/ui/RegionModifyStrategies.d.ts.map +0 -1
  30. package/dist/ui/TimeGrid.d.ts.map +0 -1
  31. package/dist/ui/TimelineRange.d.ts.map +0 -1
  32. /package/dist/ui/{RegionClipResolver.d.ts → timeline/RegionClipResolver.d.ts} +0 -0
  33. /package/dist/ui/{RegionClipResolver.js → timeline/RegionClipResolver.js} +0 -0
  34. /package/dist/ui/{RegionClipResolver.test.d.ts → timeline/RegionClipResolver.test.d.ts} +0 -0
  35. /package/dist/ui/{RegionClipResolver.test.js → timeline/RegionClipResolver.test.js} +0 -0
  36. /package/dist/ui/{RegionModifyStrategies.d.ts → timeline/RegionModifyStrategies.d.ts} +0 -0
  37. /package/dist/ui/{RegionModifyStrategies.js → timeline/RegionModifyStrategies.js} +0 -0
  38. /package/dist/ui/{TimeGrid.d.ts → timeline/TimeGrid.d.ts} +0 -0
  39. /package/dist/ui/{TimeGrid.js → timeline/TimeGrid.js} +0 -0
  40. /package/dist/ui/{TimelineRange.d.ts → timeline/TimelineRange.d.ts} +0 -0
  41. /package/dist/ui/{TimelineRange.js → timeline/TimelineRange.js} +0 -0
@@ -1 +1 @@
1
- {"version":3,"file":"RecordAudio.d.ts","sourceRoot":"","sources":["../../src/capture/RecordAudio.ts"],"names":[],"mappings":"AAAA,OAAO,EAA6B,UAAU,EAAmB,MAAM,kBAAkB,CAAA;AAUzF,OAAO,EAAa,mBAAmB,EAAY,MAAM,0BAA0B,CAAA;AACnF,OAAO,EAAC,OAAO,EAAC,MAAM,YAAY,CAAA;AAClC,OAAO,EAAC,gBAAgB,EAAC,MAAM,qBAAqB,CAAA;AACpD,OAAO,EAAC,OAAO,EAAC,MAAM,WAAW,CAAA;AAGjC,yBAAiB,WAAW,CAAC;IACzB,KAAK,kBAAkB,GAAG;QACtB,gBAAgB,EAAE,gBAAgB,CAAA;QAClC,WAAW,EAAE,WAAW,CAAA;QACxB,aAAa,EAAE,mBAAmB,CAAA;QAClC,YAAY,EAAE,YAAY,CAAA;QAC1B,OAAO,EAAE,OAAO,CAAA;QAChB,OAAO,EAAE,OAAO,CAAA;QAChB,MAAM,EAAE,MAAM,CAAA;QACd,aAAa,EAAE,MAAM,CAAA;KACxB,CAAA;IAQD,MAAM,CAAC,MAAM,KAAK,GACd,yGASG,kBAAkB,KACnB,UAsIL,CAAA;;CACJ"}
1
+ {"version":3,"file":"RecordAudio.d.ts","sourceRoot":"","sources":["../../src/capture/RecordAudio.ts"],"names":[],"mappings":"AAAA,OAAO,EAA6B,UAAU,EAAmB,MAAM,kBAAkB,CAAA;AAUzF,OAAO,EAAa,mBAAmB,EAAY,MAAM,0BAA0B,CAAA;AACnF,OAAO,EAAC,OAAO,EAAC,MAAM,YAAY,CAAA;AAClC,OAAO,EAAC,gBAAgB,EAAC,MAAM,qBAAqB,CAAA;AACpD,OAAO,EAAC,OAAO,EAAC,MAAM,WAAW,CAAA;AAGjC,yBAAiB,WAAW,CAAC;IACzB,KAAK,kBAAkB,GAAG;QACtB,gBAAgB,EAAE,gBAAgB,CAAA;QAClC,WAAW,EAAE,WAAW,CAAA;QACxB,aAAa,EAAE,mBAAmB,CAAA;QAClC,YAAY,EAAE,YAAY,CAAA;QAC1B,OAAO,EAAE,OAAO,CAAA;QAChB,OAAO,EAAE,OAAO,CAAA;QAChB,MAAM,EAAE,MAAM,CAAA;QACd,aAAa,EAAE,MAAM,CAAA;KACxB,CAAA;IAQD,MAAM,CAAC,MAAM,KAAK,GACd,yGASG,kBAAkB,KACnB,UA2IL,CAAA;;CACJ"}
@@ -23,6 +23,7 @@ export var RecordAudio;
23
23
  let currentTake = Option.None;
24
24
  let lastPosition = 0;
25
25
  let currentWaveformOffset = outputLatency;
26
+ let takeNumber = 0;
26
27
  const { tempoMap, env: { audioContext: { sampleRate } } } = project;
27
28
  const { loopArea } = timelineBox;
28
29
  const createFileBox = () => {
@@ -36,6 +37,7 @@ export var RecordAudio;
36
37
  return AudioFileBox.create(boxGraph, originalUuid, box => box.fileName.setValue(fileName));
37
38
  };
38
39
  const createTakeRegion = (position, waveformOffset, forceNewTrack) => {
40
+ takeNumber++;
39
41
  const trackBox = RecordTrack.findOrCreate(editing, capture.audioUnitBox, TrackType.Audio, forceNewTrack);
40
42
  const collectionBox = ValueEventCollectionBox.create(boxGraph, UUID.generate());
41
43
  const stretchBox = AudioPitchStretchBox.create(boxGraph, UUID.generate());
@@ -48,7 +50,7 @@ export var RecordAudio;
48
50
  box.position.setValue(position);
49
51
  box.hue.setValue(ColorCodes.forTrackType(TrackType.Audio));
50
52
  box.timeBase.setValue(TimeBase.Musical);
51
- box.label.setValue("Recording");
53
+ box.label.setValue(`Take ${takeNumber}`);
52
54
  box.playMode.refer(stretchBox);
53
55
  box.waveformOffset.setValue(waveformOffset);
54
56
  });
@@ -57,15 +59,17 @@ export var RecordAudio;
57
59
  return { trackBox, regionBox, warpMarkerBox };
58
60
  };
59
61
  const finalizeTake = (take, loopDurationPPQN) => {
60
- const { regionBox, warpMarkerBox } = take;
62
+ const { trackBox, regionBox, warpMarkerBox } = take;
61
63
  if (regionBox.isAttached()) {
62
64
  regionBox.duration.setValue(loopDurationPPQN);
63
65
  regionBox.loopDuration.setValue(loopDurationPPQN);
64
- regionBox.mute.setValue(true);
65
66
  const seconds = tempoMap.intervalToSeconds(0, loopDurationPPQN);
66
67
  warpMarkerBox.position.setValue(loopDurationPPQN);
67
68
  warpMarkerBox.seconds.setValue(seconds);
68
69
  }
70
+ if (trackBox.isAttached()) {
71
+ trackBox.enabled.setValue(false);
72
+ }
69
73
  };
70
74
  const startNewTake = (position) => {
71
75
  currentTake = Option.wrap(createTakeRegion(position, currentWaveformOffset, true));
@@ -112,7 +116,8 @@ export var RecordAudio;
112
116
  editing.modify(() => {
113
117
  if (regionBox.isAttached()) {
114
118
  const { duration, loopDuration } = regionBox;
115
- const distanceInPPQN = Math.floor(currentPosition - regionBox.position.getValue());
119
+ const maxDuration = loopEnabled ? loopTo - regionBox.position.getValue() : Infinity;
120
+ const distanceInPPQN = Math.min(maxDuration, Math.floor(currentPosition - regionBox.position.getValue()));
116
121
  duration.setValue(distanceInPPQN);
117
122
  loopDuration.setValue(distanceInPPQN);
118
123
  warpMarkerBox.position.setValue(distanceInPPQN);
@@ -1 +1 @@
1
- {"version":3,"file":"RecordMidi.d.ts","sourceRoot":"","sources":["../../src/capture/RecordMidi.ts"],"names":[],"mappings":"AAAA,OAAO,EAAO,QAAQ,EAAuC,UAAU,EAAmB,MAAM,kBAAkB,CAAA;AAGlH,OAAO,EAAa,UAAU,EAAY,MAAM,0BAA0B,CAAA;AAC1E,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;IAID,MAAM,CAAC,MAAM,KAAK,GAAI,gCAA8B,iBAAiB,KAAG,UAyEvE,CAAA;;CACJ"}
1
+ {"version":3,"file":"RecordMidi.d.ts","sourceRoot":"","sources":["../../src/capture/RecordMidi.ts"],"names":[],"mappings":"AAAA,OAAO,EAAY,QAAQ,EAAuC,UAAU,EAAmB,MAAM,kBAAkB,CAAA;AAGvH,OAAO,EAAa,UAAU,EAAY,MAAM,0BAA0B,CAAA;AAC1E,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,UAmHvE,CAAA;;CACJ"}
@@ -1,4 +1,4 @@
1
- import { Option, quantizeCeil, quantizeRound, Terminator, UUID } from "@opendaw/lib-std";
1
+ import { Option, quantizeCeil, quantizeFloor, Terminator, UUID } from "@opendaw/lib-std";
2
2
  import { PPQN } from "@opendaw/lib-dsp";
3
3
  import { NoteEventBox, NoteEventCollectionBox, NoteRegionBox } from "@opendaw/studio-boxes";
4
4
  import { ColorCodes, NoteSignal, TrackType } from "@opendaw/studio-adapters";
@@ -8,81 +8,116 @@ export var RecordMidi;
8
8
  const MIN_NOTE_DURATION = PPQN.fromSignature(1, 128);
9
9
  RecordMidi.start = ({ notifier, project, capture }) => {
10
10
  const beats = PPQN.fromSignature(1, project.timelineBox.signature.denominator.getValue());
11
- const { editing, boxGraph, engine, env: { audioContext }, timelineBox: { bpm } } = project;
11
+ const { editing, boxGraph, engine, env: { audioContext }, timelineBox } = project;
12
12
  const { position, isRecording } = engine;
13
- const trackBox = RecordTrack.findOrCreate(editing, capture.audioUnitBox, TrackType.Notes);
13
+ const { loopArea } = timelineBox;
14
14
  const terminator = new Terminator();
15
15
  const activeNotes = new Map();
16
- const latency = PPQN.secondsToPulses(audioContext.outputLatency ?? 10.0, bpm.getValue());
17
- let writing = Option.None;
18
- const createRegion = () => {
19
- const writePosition = position.getValue() + latency;
20
- editing.modify(() => {
21
- const collection = NoteEventCollectionBox.create(boxGraph, UUID.generate());
22
- const regionBox = NoteRegionBox.create(boxGraph, UUID.generate(), box => {
23
- box.regions.refer(trackBox.regions);
24
- box.events.refer(collection.owners);
25
- box.position.setValue(Math.max(quantizeRound(writePosition, beats), 0));
26
- box.hue.setValue(ColorCodes.forTrackType(TrackType.Notes));
27
- });
28
- capture.addRecordedRegion(regionBox);
29
- project.selection.select(regionBox);
30
- engine.ignoreNoteRegion(regionBox.address.uuid);
31
- writing = Option.wrap({ region: regionBox, collection });
32
- }, false);
16
+ const latency = PPQN.secondsToPulses(audioContext.outputLatency ?? 10.0, timelineBox.bpm.getValue());
17
+ let currentTake = Option.None;
18
+ let lastPosition = 0;
19
+ let positionOffset = 0;
20
+ let takeNumber = 0;
21
+ const createTakeRegion = (position, forceNewTrack) => {
22
+ takeNumber++;
23
+ const trackBox = RecordTrack.findOrCreate(editing, capture.audioUnitBox, TrackType.Notes, forceNewTrack);
24
+ const collection = NoteEventCollectionBox.create(boxGraph, UUID.generate());
25
+ const regionBox = NoteRegionBox.create(boxGraph, UUID.generate(), box => {
26
+ box.regions.refer(trackBox.regions);
27
+ box.events.refer(collection.owners);
28
+ box.position.setValue(position);
29
+ box.hue.setValue(ColorCodes.forTrackType(TrackType.Notes));
30
+ box.label.setValue(`Take ${takeNumber}`);
31
+ });
32
+ capture.addRecordedRegion(regionBox);
33
+ project.selection.select(regionBox);
34
+ engine.ignoreNoteRegion(regionBox.address.uuid);
35
+ return { trackBox, regionBox, collection };
36
+ };
37
+ const finalizeTake = (take, loopDurationPPQN) => {
38
+ const { trackBox, regionBox } = take;
39
+ if (regionBox.isAttached()) {
40
+ regionBox.duration.setValue(loopDurationPPQN);
41
+ regionBox.loopDuration.setValue(loopDurationPPQN);
42
+ }
43
+ if (trackBox.isAttached()) {
44
+ trackBox.enabled.setValue(false);
45
+ }
46
+ };
47
+ const startNewTake = (position) => {
48
+ currentTake = Option.wrap(createTakeRegion(position, true));
33
49
  };
34
50
  terminator.own(position.catchupAndSubscribe(owner => {
35
- if (writing.isEmpty()) {
36
- if (isRecording.getValue()) {
37
- createRegion();
38
- }
39
- else {
40
- return;
41
- }
51
+ if (!isRecording.getValue()) {
52
+ return;
42
53
  }
43
- const writePosition = owner.getValue() + latency;
44
- const { region, collection } = writing.unwrap();
45
- editing.modify(() => {
46
- if (region.isAttached() && collection.isAttached()) {
47
- const { position, duration, loopDuration } = region;
48
- const newDuration = quantizeCeil(writePosition, beats) - position.getValue();
49
- duration.setValue(newDuration);
50
- loopDuration.setValue(newDuration);
51
- for (const event of activeNotes.values()) {
52
- if (event.isAttached()) {
53
- event.duration.setValue(Math.max(MIN_NOTE_DURATION, writePosition - region.position.getValue() - event.position.getValue()));
54
- }
55
- else {
56
- activeNotes.delete(event.pitch.getValue());
54
+ const currentPosition = owner.getValue();
55
+ const writePosition = currentPosition + latency;
56
+ const loopEnabled = loopArea.enabled.getValue();
57
+ const loopFrom = loopArea.from.getValue();
58
+ const loopTo = loopArea.to.getValue();
59
+ const loopDurationPPQN = loopTo - loopFrom;
60
+ if (loopEnabled && currentTake.nonEmpty() && currentPosition < lastPosition) {
61
+ positionOffset += loopDurationPPQN;
62
+ editing.modify(() => {
63
+ currentTake.ifSome(take => finalizeTake(take, loopDurationPPQN));
64
+ startNewTake(loopFrom);
65
+ }, false);
66
+ }
67
+ lastPosition = currentPosition;
68
+ if (currentTake.isEmpty()) {
69
+ editing.modify(() => {
70
+ const pos = quantizeFloor(currentPosition, beats);
71
+ currentTake = Option.wrap(createTakeRegion(pos, false));
72
+ }, false);
73
+ }
74
+ currentTake.ifSome(({ regionBox, collection }) => {
75
+ editing.modify(() => {
76
+ if (regionBox.isAttached() && collection.isAttached()) {
77
+ const { position: regionPosition, duration, loopDuration } = regionBox;
78
+ const maxDuration = loopEnabled ? loopTo - regionPosition.getValue() : Infinity;
79
+ const newDuration = Math.min(maxDuration, quantizeCeil(writePosition, beats) - regionPosition.getValue());
80
+ duration.setValue(newDuration);
81
+ loopDuration.setValue(newDuration);
82
+ for (const { event, take, creationOffset } of activeNotes.values()) {
83
+ if (event.isAttached()) {
84
+ const elapsed = (positionOffset + writePosition) - (creationOffset + take.regionBox.position.getValue() + event.position.getValue());
85
+ event.duration.setValue(Math.max(MIN_NOTE_DURATION, elapsed));
86
+ }
87
+ else {
88
+ activeNotes.delete(event.pitch.getValue());
89
+ }
57
90
  }
58
91
  }
59
- }
60
- else {
61
- terminator.terminate();
62
- writing = Option.None;
63
- }
64
- }, false);
92
+ else {
93
+ terminator.terminate();
94
+ currentTake = Option.None;
95
+ }
96
+ }, false);
97
+ });
65
98
  }));
66
99
  terminator.ownAll(notifier.subscribe((signal) => {
67
100
  const writePosition = position.getValue() + latency;
68
101
  if (NoteSignal.isOn(signal)) {
69
102
  const { pitch, velocity } = signal;
70
- if (writing.isEmpty()) {
71
- createRegion();
103
+ if (currentTake.isEmpty()) {
104
+ return;
72
105
  }
73
- const { region, collection } = writing.unwrap();
106
+ const take = currentTake.unwrap();
107
+ const { regionBox, collection } = take;
74
108
  editing.modify(() => {
75
- const position = writePosition - region.position.getValue();
76
- if (position < -PPQN.SemiQuaver) {
109
+ const notePosition = writePosition - regionBox.position.getValue();
110
+ if (notePosition < -PPQN.SemiQuaver) {
77
111
  return;
78
112
  }
79
- activeNotes.set(pitch, NoteEventBox.create(boxGraph, UUID.generate(), box => {
80
- box.position.setValue(Math.max(0, position));
113
+ const event = NoteEventBox.create(boxGraph, UUID.generate(), box => {
114
+ box.position.setValue(Math.max(0, notePosition));
81
115
  box.duration.setValue(MIN_NOTE_DURATION);
82
116
  box.pitch.setValue(pitch);
83
117
  box.velocity.setValue(velocity);
84
118
  box.events.refer(collection.events);
85
- }));
119
+ });
120
+ activeNotes.set(pitch, { event, take, creationOffset: positionOffset });
86
121
  }, false);
87
122
  }
88
123
  else if (NoteSignal.isOff(signal)) {