@opendaw/studio-core 0.0.19 → 0.0.20

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 (108) hide show
  1. package/dist/AudioInputDevices.d.ts +8 -0
  2. package/dist/AudioInputDevices.d.ts.map +1 -0
  3. package/dist/AudioInputDevices.js +30 -0
  4. package/dist/AudioUnitOrdering.d.ts +3 -0
  5. package/dist/AudioUnitOrdering.d.ts.map +1 -0
  6. package/dist/AudioUnitOrdering.js +7 -0
  7. package/dist/EffectBox.d.ts +2 -2
  8. package/dist/EffectBox.d.ts.map +1 -1
  9. package/dist/Engine.d.ts +31 -10
  10. package/dist/Engine.d.ts.map +1 -1
  11. package/dist/EngineFacade.d.ts +22 -11
  12. package/dist/EngineFacade.d.ts.map +1 -1
  13. package/dist/EngineFacade.js +39 -22
  14. package/dist/EngineWorklet.d.ts +22 -13
  15. package/dist/EngineWorklet.d.ts.map +1 -1
  16. package/dist/EngineWorklet.js +47 -56
  17. package/dist/MeterWorklet.d.ts.map +1 -1
  18. package/dist/MeterWorklet.js +2 -2
  19. package/dist/Project.d.ts +4 -2
  20. package/dist/Project.d.ts.map +1 -1
  21. package/dist/Project.js +6 -3
  22. package/dist/ProjectApi.d.ts +0 -1
  23. package/dist/ProjectApi.d.ts.map +1 -1
  24. package/dist/ProjectApi.js +16 -11
  25. package/dist/ProjectEnv.d.ts +1 -0
  26. package/dist/ProjectEnv.d.ts.map +1 -1
  27. package/dist/ProjectMigration.d.ts.map +1 -1
  28. package/dist/ProjectMigration.js +20 -3
  29. package/dist/RecordingWorklet.d.ts +15 -3
  30. package/dist/RecordingWorklet.d.ts.map +1 -1
  31. package/dist/RecordingWorklet.js +111 -16
  32. package/dist/WorkerAgents.d.ts +2 -2
  33. package/dist/WorkerAgents.d.ts.map +1 -1
  34. package/dist/Worklets.d.ts +1 -1
  35. package/dist/Worklets.d.ts.map +1 -1
  36. package/dist/Worklets.js +2 -2
  37. package/dist/capture/Capture.d.ts +22 -0
  38. package/dist/capture/Capture.d.ts.map +1 -0
  39. package/dist/capture/Capture.js +32 -0
  40. package/dist/capture/CaptureAudio.d.ts +17 -0
  41. package/dist/capture/CaptureAudio.d.ts.map +1 -0
  42. package/dist/capture/CaptureAudio.js +106 -0
  43. package/dist/capture/CaptureManager.d.ts +12 -0
  44. package/dist/capture/CaptureManager.d.ts.map +1 -0
  45. package/dist/capture/CaptureManager.js +38 -0
  46. package/dist/capture/CaptureMidi.d.ts +13 -0
  47. package/dist/capture/CaptureMidi.d.ts.map +1 -0
  48. package/dist/capture/CaptureMidi.js +50 -0
  49. package/dist/capture/RecordAudio.d.ts +21 -0
  50. package/dist/capture/RecordAudio.d.ts.map +1 -0
  51. package/dist/capture/RecordAudio.js +66 -0
  52. package/dist/capture/RecordMidi.d.ts +15 -0
  53. package/dist/capture/RecordMidi.d.ts.map +1 -0
  54. package/dist/capture/RecordMidi.js +85 -0
  55. package/dist/capture/RecordTrack.d.ts +7 -0
  56. package/dist/capture/RecordTrack.d.ts.map +1 -0
  57. package/dist/capture/RecordTrack.js +23 -0
  58. package/dist/capture/Recording.d.ts +9 -0
  59. package/dist/capture/Recording.d.ts.map +1 -0
  60. package/dist/capture/Recording.js +65 -0
  61. package/dist/capture/RecordingContext.d.ts +14 -0
  62. package/dist/capture/RecordingContext.d.ts.map +1 -0
  63. package/dist/capture/RecordingContext.js +1 -0
  64. package/dist/dawproject/AudioUnitExportLayout.d.ts +10 -0
  65. package/dist/dawproject/AudioUnitExportLayout.d.ts.map +1 -0
  66. package/dist/dawproject/AudioUnitExportLayout.js +64 -0
  67. package/dist/dawproject/BuiltinDevices.d.ts +9 -0
  68. package/dist/dawproject/BuiltinDevices.d.ts.map +1 -0
  69. package/dist/dawproject/BuiltinDevices.js +70 -0
  70. package/dist/dawproject/{DawProjectIO.d.ts → DawProject.d.ts} +6 -4
  71. package/dist/dawproject/DawProject.d.ts.map +1 -0
  72. package/dist/dawproject/{DawProjectIO.js → DawProject.js} +23 -5
  73. package/dist/dawproject/DawProjectExporter.d.ts +7 -6
  74. package/dist/dawproject/DawProjectExporter.d.ts.map +1 -1
  75. package/dist/dawproject/DawProjectExporter.js +245 -22
  76. package/dist/dawproject/DawProjectExporter.test.js +39 -6
  77. package/dist/dawproject/DawProjectImport.d.ts +12 -0
  78. package/dist/dawproject/DawProjectImport.d.ts.map +1 -0
  79. package/dist/dawproject/DawProjectImport.js +389 -0
  80. package/dist/dawproject/DawProjectImport.test.js +6 -7
  81. package/dist/dawproject/DeviceIO.d.ts +8 -0
  82. package/dist/dawproject/DeviceIO.d.ts.map +1 -0
  83. package/dist/dawproject/DeviceIO.js +63 -0
  84. package/dist/index.d.ts +11 -3
  85. package/dist/index.d.ts.map +1 -1
  86. package/dist/index.js +11 -3
  87. package/dist/processors.js +3 -3
  88. package/dist/processors.js.map +4 -4
  89. package/dist/samples/MainThreadSampleLoader.d.ts +1 -0
  90. package/dist/samples/MainThreadSampleLoader.d.ts.map +1 -1
  91. package/dist/samples/MainThreadSampleLoader.js +10 -6
  92. package/dist/samples/MainThreadSampleManager.d.ts +5 -5
  93. package/dist/samples/MainThreadSampleManager.d.ts.map +1 -1
  94. package/dist/samples/MainThreadSampleManager.js +1 -0
  95. package/dist/samples/SampleProvider.d.ts +2 -2
  96. package/dist/samples/SampleProvider.d.ts.map +1 -1
  97. package/dist/samples/SampleStorage.d.ts.map +1 -1
  98. package/dist/samples/SampleStorage.js +2 -3
  99. package/dist/workers.js +2 -2
  100. package/dist/workers.js.map +4 -4
  101. package/package.json +15 -15
  102. package/dist/dawproject/DawProjectIO.d.ts.map +0 -1
  103. package/dist/dawproject/DawProjectImporter.d.ts +0 -12
  104. package/dist/dawproject/DawProjectImporter.d.ts.map +0 -1
  105. package/dist/dawproject/DawProjectImporter.js +0 -273
  106. package/dist/samples/SamplePeaks.d.ts +0 -6
  107. package/dist/samples/SamplePeaks.d.ts.map +0 -1
  108. package/dist/samples/SamplePeaks.js +0 -9
@@ -0,0 +1,38 @@
1
+ import { asInstanceOf, isDefined, UUID } from "@opendaw/lib-std";
2
+ import { AudioUnitBox } from "@opendaw/studio-boxes";
3
+ import { CaptureMidi } from "./CaptureMidi";
4
+ import { CaptureAudio } from "./CaptureAudio";
5
+ export class CaptureManager {
6
+ #project;
7
+ #subscription;
8
+ #captures;
9
+ constructor(project) {
10
+ this.#project = project;
11
+ this.#captures = UUID.newSet(unit => unit.uuid);
12
+ this.#subscription = this.#project.rootBox.audioUnits.pointerHub.catchupAndSubscribeTransactual({
13
+ onAdd: ({ box }) => {
14
+ const audioUnitBox = asInstanceOf(box, AudioUnitBox);
15
+ const capture = audioUnitBox.capture.targetVertex
16
+ .ifSome(({ box }) => box.accept({
17
+ visitCaptureMidiBox: (box) => new CaptureMidi(this, audioUnitBox, box),
18
+ visitCaptureAudioBox: (box) => new CaptureAudio(this, audioUnitBox, box)
19
+ }));
20
+ if (isDefined(capture)) {
21
+ this.#captures.add(capture);
22
+ }
23
+ },
24
+ onRemove: ({ box: { address: { uuid } } }) => this.#captures.removeByKeyIfExist(uuid)?.terminate()
25
+ });
26
+ }
27
+ get project() { return this.#project; }
28
+ get(uuid) { return this.#captures.opt(uuid); }
29
+ filterArmed() {
30
+ return this.#captures.values()
31
+ .filter(capture => capture.armed.getValue() && capture.audioUnitBox.input.pointerHub.nonEmpty());
32
+ }
33
+ terminate() {
34
+ this.#subscription.terminate();
35
+ this.#captures.forEach(capture => capture.terminate());
36
+ this.#captures.clear();
37
+ }
38
+ }
@@ -0,0 +1,13 @@
1
+ import { Option, Terminable } from "@opendaw/lib-std";
2
+ import { AudioUnitBox, CaptureMidiBox } from "@opendaw/studio-boxes";
3
+ import { Capture } from "./Capture";
4
+ import { RecordingContext } from "./RecordingContext";
5
+ import { CaptureManager } from "./CaptureManager";
6
+ export declare class CaptureMidi extends Capture<CaptureMidiBox> {
7
+ #private;
8
+ constructor(manager: CaptureManager, audioUnitBox: AudioUnitBox, captureMidiBox: CaptureMidiBox);
9
+ prepareRecording({ requestMIDIAccess }: RecordingContext): Promise<void>;
10
+ startRecording({ project, engine }: RecordingContext): Terminable;
11
+ get deviceLabel(): Option<string>;
12
+ }
13
+ //# sourceMappingURL=CaptureMidi.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"CaptureMidi.d.ts","sourceRoot":"","sources":["../../src/capture/CaptureMidi.ts"],"names":[],"mappings":"AAAA,OAAO,EAAiD,MAAM,EAAS,UAAU,EAAC,MAAM,kBAAkB,CAAA;AAG1G,OAAO,EAAC,YAAY,EAAE,cAAc,EAAC,MAAM,uBAAuB,CAAA;AAClE,OAAO,EAAC,OAAO,EAAC,MAAM,WAAW,CAAA;AAEjC,OAAO,EAAC,gBAAgB,EAAC,MAAM,oBAAoB,CAAA;AACnD,OAAO,EAAC,cAAc,EAAC,MAAM,kBAAkB,CAAA;AAE/C,qBAAa,WAAY,SAAQ,OAAO,CAAC,cAAc,CAAC;;gBAKxC,OAAO,EAAE,cAAc,EAAE,YAAY,EAAE,YAAY,EAAE,cAAc,EAAE,cAAc;IAWzF,gBAAgB,CAAC,EAAC,iBAAiB,EAAC,EAAE,gBAAgB,GAAG,OAAO,CAAC,IAAI,CAAC;IAe5E,cAAc,CAAC,EAAC,OAAO,EAAE,MAAM,EAAC,EAAE,gBAAgB,GAAG,UAAU;IAyB/D,IAAI,WAAW,IAAI,MAAM,CAAC,MAAM,CAAC,CAAqB;CACzD"}
@@ -0,0 +1,50 @@
1
+ import { assert, isDefined, isUndefined, Notifier, Option, panic, Terminable } from "@opendaw/lib-std";
2
+ import { Events } from "@opendaw/lib-dom";
3
+ import { MidiData } from "@opendaw/lib-midi";
4
+ import { Capture } from "./Capture";
5
+ import { RecordMidi } from "./RecordMidi";
6
+ export class CaptureMidi extends Capture {
7
+ #midiAccess = Option.None;
8
+ #filterChannel = Option.None;
9
+ constructor(manager, audioUnitBox, captureMidiBox) {
10
+ super(manager, audioUnitBox, captureMidiBox);
11
+ this.ownAll(captureMidiBox.channel.catchupAndSubscribe(owner => {
12
+ const channel = owner.getValue();
13
+ this.#filterChannel = channel >= 0 ? Option.wrap(channel) : Option.None;
14
+ }));
15
+ }
16
+ async prepareRecording({ requestMIDIAccess }) {
17
+ return requestMIDIAccess()
18
+ .then(midiAccess => {
19
+ const option = this.deviceId.getValue();
20
+ if (option.nonEmpty()) {
21
+ const captureDevices = Array.from(midiAccess.inputs.values());
22
+ const id = option.unwrap();
23
+ if (isUndefined(captureDevices.find(device => id === device.id))) {
24
+ return panic(`Could not find MIDI device with id: '${id}'`);
25
+ }
26
+ }
27
+ this.#midiAccess = Option.wrap(midiAccess);
28
+ });
29
+ }
30
+ startRecording({ project, engine }) {
31
+ assert(this.#midiAccess.nonEmpty(), "Stream not prepared.");
32
+ const midiAccess = this.#midiAccess.unwrap();
33
+ const notifier = new Notifier();
34
+ const captureDevices = Array.from(midiAccess.inputs.values());
35
+ this.deviceId.getValue().ifSome(id => captureDevices.filter(device => id === device.id));
36
+ return Terminable.many(Terminable.many(...captureDevices.map(input => Events.subscribe(input, "midimessage", (event) => {
37
+ const data = event.data;
38
+ if (isDefined(data) &&
39
+ this.#filterChannel.mapOr(channel => MidiData.readChannel(data) === channel, true)) {
40
+ notifier.notify(event);
41
+ }
42
+ }))), RecordMidi.start({
43
+ notifier,
44
+ engine,
45
+ project,
46
+ capture: this
47
+ }));
48
+ }
49
+ get deviceLabel() { return Option.None; }
50
+ }
@@ -0,0 +1,21 @@
1
+ import { Terminable } from "@opendaw/lib-std";
2
+ import { SampleManager } from "@opendaw/studio-adapters";
3
+ import { Engine } from "../Engine";
4
+ import { Project } from "../Project";
5
+ import { Capture } from "./Capture";
6
+ import { RecordingWorklet } from "../RecordingWorklet";
7
+ export declare namespace RecordAudio {
8
+ type RecordAudioContext = {
9
+ recordingWorklet: RecordingWorklet;
10
+ mediaStream: MediaStream;
11
+ sampleManager: SampleManager;
12
+ audioContext: AudioContext;
13
+ engine: Engine;
14
+ project: Project;
15
+ capture: Capture;
16
+ gainDb: number;
17
+ };
18
+ export const start: ({ recordingWorklet, mediaStream, sampleManager, audioContext, engine, project, capture, gainDb }: RecordAudioContext) => Terminable;
19
+ export {};
20
+ }
21
+ //# sourceMappingURL=RecordAudio.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"RecordAudio.d.ts","sourceRoot":"","sources":["../../src/capture/RecordAudio.ts"],"names":[],"mappings":"AAAA,OAAO,EAAwB,UAAU,EAAmB,MAAM,kBAAkB,CAAA;AAGpF,OAAO,EAAC,aAAa,EAAY,MAAM,0BAA0B,CAAA;AACjE,OAAO,EAAC,MAAM,EAAC,MAAM,WAAW,CAAA;AAChC,OAAO,EAAC,OAAO,EAAC,MAAM,YAAY,CAAA;AAClC,OAAO,EAAC,OAAO,EAAC,MAAM,WAAW,CAAA;AAEjC,OAAO,EAAC,gBAAgB,EAAC,MAAM,qBAAqB,CAAA;AAGpD,yBAAiB,WAAW,CAAC;IACzB,KAAK,kBAAkB,GAAG;QACtB,gBAAgB,EAAE,gBAAgB,CAAA;QAClC,WAAW,EAAE,WAAW,CAAA;QACxB,aAAa,EAAE,aAAa,CAAA;QAC5B,YAAY,EAAE,YAAY,CAAA;QAC1B,MAAM,EAAE,MAAM,CAAA;QACd,OAAO,EAAE,OAAO,CAAA;QAChB,OAAO,EAAE,OAAO,CAAA;QAChB,MAAM,EAAE,MAAM,CAAA;KACjB,CAAA;IAED,MAAM,CAAC,MAAM,KAAK,GACd,kGAEG,kBAAkB,KAAG,UA2D3B,CAAA;;CACJ"}
@@ -0,0 +1,66 @@
1
+ import { Option, quantizeFloor, Terminable, Terminator, UUID } from "@opendaw/lib-std";
2
+ import { dbToGain, PPQN } from "@opendaw/lib-dsp";
3
+ import { AudioFileBox, AudioRegionBox } from "@opendaw/studio-boxes";
4
+ import { TrackType } from "@opendaw/studio-adapters";
5
+ import { RecordTrack } from "./RecordTrack";
6
+ import { ColorCodes } from "../ColorCodes";
7
+ export var RecordAudio;
8
+ (function (RecordAudio) {
9
+ RecordAudio.start = ({ recordingWorklet, mediaStream, sampleManager, audioContext, engine, project, capture, gainDb }) => {
10
+ const terminator = new Terminator();
11
+ const beats = PPQN.fromSignature(1, project.timelineBox.signature.denominator.getValue());
12
+ const { editing, boxGraph } = project;
13
+ const trackBox = RecordTrack.findOrCreate(editing, capture.audioUnitBox, TrackType.Audio);
14
+ const uuid = recordingWorklet.uuid;
15
+ sampleManager.record(recordingWorklet);
16
+ const streamSource = audioContext.createMediaStreamSource(mediaStream);
17
+ const streamGain = audioContext.createGain();
18
+ streamGain.gain.value = dbToGain(gainDb);
19
+ streamSource.connect(streamGain);
20
+ let writing = Option.None;
21
+ const resizeRegion = () => {
22
+ if (writing.isEmpty()) {
23
+ return;
24
+ }
25
+ const { regionBox } = writing.unwrap();
26
+ editing.modify(() => {
27
+ if (regionBox.isAttached()) {
28
+ const { duration, loopDuration } = regionBox;
29
+ const newDuration = Math.floor(PPQN.samplesToPulses(recordingWorklet.numberOfFrames, project.timelineBox.bpm.getValue(), audioContext.sampleRate));
30
+ duration.setValue(newDuration);
31
+ loopDuration.setValue(newDuration);
32
+ }
33
+ }, false);
34
+ };
35
+ terminator.ownAll(Terminable.create(() => {
36
+ recordingWorklet.finalize().then();
37
+ streamGain.disconnect();
38
+ streamSource.disconnect();
39
+ }), engine.position.catchupAndSubscribe(owner => {
40
+ if (writing.isEmpty() && engine.isRecording.getValue()) {
41
+ streamGain.connect(recordingWorklet);
42
+ writing = editing.modify(() => {
43
+ const position = quantizeFloor(owner.getValue(), beats);
44
+ const fileDateString = new Date()
45
+ .toISOString()
46
+ .replaceAll("T", "-")
47
+ .replaceAll(".", "-")
48
+ .replaceAll(":", "-")
49
+ .replaceAll("Z", "");
50
+ const fileName = `Recording-${fileDateString}`;
51
+ const fileBox = AudioFileBox.create(boxGraph, uuid, box => box.fileName.setValue(fileName));
52
+ const regionBox = AudioRegionBox.create(boxGraph, UUID.generate(), box => {
53
+ box.file.refer(fileBox);
54
+ box.regions.refer(trackBox.regions);
55
+ box.position.setValue(position);
56
+ box.hue.setValue(ColorCodes.forTrackType(TrackType.Audio));
57
+ box.label.setValue("Recording");
58
+ });
59
+ return { fileBox, regionBox };
60
+ });
61
+ }
62
+ resizeRegion();
63
+ }), Terminable.create(() => resizeRegion()));
64
+ return terminator;
65
+ };
66
+ })(RecordAudio || (RecordAudio = {}));
@@ -0,0 +1,15 @@
1
+ import { Notifier, Terminable } from "@opendaw/lib-std";
2
+ import { Engine } from "../Engine";
3
+ import { Project } from "../Project";
4
+ import { Capture } from "./Capture";
5
+ export declare namespace RecordMidi {
6
+ type RecordMidiContext = {
7
+ notifier: Notifier<MIDIMessageEvent>;
8
+ engine: Engine;
9
+ project: Project;
10
+ capture: Capture;
11
+ };
12
+ export const start: ({ notifier, engine, project, capture }: RecordMidiContext) => Terminable;
13
+ export {};
14
+ }
15
+ //# sourceMappingURL=RecordMidi.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"RecordMidi.d.ts","sourceRoot":"","sources":["../../src/capture/RecordMidi.ts"],"names":[],"mappings":"AAAA,OAAO,EAGH,QAAQ,EAIR,UAAU,EAGb,MAAM,kBAAkB,CAAA;AAKzB,OAAO,EAAC,MAAM,EAAC,MAAM,WAAW,CAAA;AAChC,OAAO,EAAC,OAAO,EAAC,MAAM,YAAY,CAAA;AAClC,OAAO,EAAC,OAAO,EAAC,MAAM,WAAW,CAAA;AAIjC,yBAAiB,UAAU,CAAC;IACxB,KAAK,iBAAiB,GAAG;QACrB,QAAQ,EAAE,QAAQ,CAAC,gBAAgB,CAAC,CAAC;QACrC,MAAM,EAAE,MAAM,CAAC;QACf,OAAO,EAAE,OAAO,CAAC;QACjB,OAAO,EAAE,OAAO,CAAA;KACnB,CAAA;IAED,MAAM,CAAC,MAAM,KAAK,GAAI,wCAAsC,iBAAiB,KAAG,UAiE/E,CAAA;;CACJ"}
@@ -0,0 +1,85 @@
1
+ import { isUndefined, Option, quantizeCeil, quantizeFloor, Terminator, UUID } from "@opendaw/lib-std";
2
+ import { PPQN } from "@opendaw/lib-dsp";
3
+ import { MidiData } from "@opendaw/lib-midi";
4
+ import { NoteEventBox, NoteEventCollectionBox, NoteRegionBox } from "@opendaw/studio-boxes";
5
+ import { TrackType } from "@opendaw/studio-adapters";
6
+ import { RecordTrack } from "./RecordTrack";
7
+ import { ColorCodes } from "../ColorCodes";
8
+ export var RecordMidi;
9
+ (function (RecordMidi) {
10
+ RecordMidi.start = ({ notifier, engine, project, capture }) => {
11
+ console.debug("RecordMidi.start");
12
+ const beats = PPQN.fromSignature(1, project.timelineBox.signature.denominator.getValue());
13
+ const { editing, boxGraph } = project;
14
+ const trackBox = RecordTrack.findOrCreate(editing, capture.audioUnitBox, TrackType.Notes);
15
+ const terminator = new Terminator();
16
+ const activeNotes = new Map();
17
+ let writing = Option.None;
18
+ terminator.own(engine.position.catchupAndSubscribe(owner => {
19
+ if (writing.isEmpty()) {
20
+ return;
21
+ }
22
+ const writePosition = owner.getValue();
23
+ const { region, collection } = writing.unwrap();
24
+ editing.modify(() => {
25
+ if (region.isAttached() && collection.isAttached()) {
26
+ const { position, duration, loopDuration } = region;
27
+ const newDuration = quantizeCeil(writePosition, beats) - position.getValue();
28
+ duration.setValue(newDuration);
29
+ loopDuration.setValue(newDuration);
30
+ for (const event of activeNotes.values()) {
31
+ if (event.isAttached()) {
32
+ event.duration.setValue(writePosition - event.position.getValue());
33
+ }
34
+ else {
35
+ activeNotes.delete(event.pitch.getValue());
36
+ }
37
+ }
38
+ }
39
+ else {
40
+ writing = Option.None;
41
+ }
42
+ }, false);
43
+ }));
44
+ terminator.ownAll(notifier.subscribe((event) => {
45
+ if (!engine.isRecording.getValue()) {
46
+ return;
47
+ }
48
+ const data = event.data;
49
+ if (isUndefined(data)) {
50
+ return;
51
+ }
52
+ const position = engine.position.getValue();
53
+ if (MidiData.isNoteOn(data)) {
54
+ const pitch = MidiData.readParam1(data);
55
+ if (writing.isEmpty()) {
56
+ editing.modify(() => {
57
+ const collection = NoteEventCollectionBox.create(boxGraph, UUID.generate());
58
+ const region = NoteRegionBox.create(boxGraph, UUID.generate(), box => {
59
+ box.regions.refer(trackBox.regions);
60
+ box.events.refer(collection.owners);
61
+ box.position.setValue(quantizeFloor(position, beats));
62
+ box.hue.setValue(ColorCodes.forTrackType(TrackType.Notes));
63
+ });
64
+ writing = Option.wrap({ region, collection });
65
+ }, false);
66
+ }
67
+ const { collection } = writing.unwrap();
68
+ editing.modify(() => {
69
+ activeNotes.set(pitch, NoteEventBox.create(boxGraph, UUID.generate(), box => {
70
+ box.position.setValue(position);
71
+ box.duration.setValue(1.0);
72
+ box.pitch.setValue(pitch);
73
+ box.velocity.setValue(MidiData.readParam2(data) / 127.0);
74
+ box.events.refer(collection.events);
75
+ }));
76
+ }, false);
77
+ }
78
+ else if (MidiData.isNoteOff(data)) {
79
+ const pitch = MidiData.readParam1(data);
80
+ activeNotes.delete(pitch);
81
+ }
82
+ }));
83
+ return terminator;
84
+ };
85
+ })(RecordMidi || (RecordMidi = {}));
@@ -0,0 +1,7 @@
1
+ import { AudioUnitBox, TrackBox } from "@opendaw/studio-boxes";
2
+ import { TrackType } from "@opendaw/studio-adapters";
3
+ import { Editing } from "@opendaw/lib-box";
4
+ export declare namespace RecordTrack {
5
+ const findOrCreate: (editing: Editing, audioUnitBox: AudioUnitBox, type: TrackType) => TrackBox;
6
+ }
7
+ //# sourceMappingURL=RecordTrack.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"RecordTrack.d.ts","sourceRoot":"","sources":["../../src/capture/RecordTrack.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,YAAY,EAAE,QAAQ,EAAC,MAAM,uBAAuB,CAAA;AAE5D,OAAO,EAAC,SAAS,EAAC,MAAM,0BAA0B,CAAA;AAClD,OAAO,EAAC,OAAO,EAAC,MAAM,kBAAkB,CAAA;AAExC,yBAAiB,WAAW,CAAC;IAClB,MAAM,YAAY,GAAI,SAAS,OAAO,EAAE,cAAc,YAAY,EAAE,MAAM,SAAS,KAAG,QAe5F,CAAA;CACJ"}
@@ -0,0 +1,23 @@
1
+ import { TrackBox } from "@opendaw/studio-boxes";
2
+ import { asInstanceOf, UUID } from "@opendaw/lib-std";
3
+ export var RecordTrack;
4
+ (function (RecordTrack) {
5
+ RecordTrack.findOrCreate = (editing, audioUnitBox, type) => {
6
+ let index = 0 | 0;
7
+ for (const trackBox of audioUnitBox.tracks.pointerHub.incoming()
8
+ .map(({ box }) => asInstanceOf(box, TrackBox))) {
9
+ const hasNoRegions = trackBox.regions.pointerHub.isEmpty();
10
+ const acceptsNotes = trackBox.type.getValue() === type;
11
+ if (hasNoRegions && acceptsNotes) {
12
+ return trackBox;
13
+ }
14
+ index = Math.max(index, trackBox.index.getValue());
15
+ }
16
+ return editing.modify(() => TrackBox.create(audioUnitBox.graph, UUID.generate(), box => {
17
+ box.type.setValue(type);
18
+ box.index.setValue(index + 1);
19
+ box.tracks.refer(audioUnitBox.tracks);
20
+ box.target.refer(audioUnitBox);
21
+ })).unwrap("Could not create TrackBox");
22
+ };
23
+ })(RecordTrack || (RecordTrack = {}));
@@ -0,0 +1,9 @@
1
+ import { Terminable } from "@opendaw/lib-std";
2
+ import { RecordingContext } from "./RecordingContext";
3
+ export declare class Recording {
4
+ #private;
5
+ static get isRecording(): boolean;
6
+ static start(context: RecordingContext, countIn: boolean): Promise<Terminable>;
7
+ private constructor();
8
+ }
9
+ //# sourceMappingURL=Recording.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Recording.d.ts","sourceRoot":"","sources":["../../src/capture/Recording.ts"],"names":[],"mappings":"AAAA,OAAO,EAA+B,UAAU,EAAmB,MAAM,kBAAkB,CAAA;AAE3F,OAAO,EAAC,gBAAgB,EAAC,MAAM,oBAAoB,CAAA;AAMnD,qBAAa,SAAS;;IAClB,MAAM,KAAK,WAAW,IAAI,OAAO,CAA2B;WAE/C,KAAK,CAAC,OAAO,EAAE,gBAAgB,EAAE,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,UAAU,CAAC;IA+DpF,OAAO;CACV"}
@@ -0,0 +1,65 @@
1
+ import { asInstanceOf, assert, Option, Terminable, Terminator, warn } from "@opendaw/lib-std";
2
+ import { Promises } from "@opendaw/lib-runtime";
3
+ import { AudioUnitBox } from "@opendaw/studio-boxes";
4
+ import { AudioUnitType } from "@opendaw/studio-enums";
5
+ import { InstrumentFactories } from "../InstrumentFactories";
6
+ export class Recording {
7
+ static get isRecording() { return this.#isRecording; }
8
+ static async start(context, countIn) {
9
+ if (this.#isRecording) {
10
+ return Promise.resolve(Terminable.Empty);
11
+ }
12
+ this.#isRecording = true;
13
+ assert(this.#instance.isEmpty(), "Recording already in progress");
14
+ const { engine, project } = context;
15
+ this.#prepare(project);
16
+ const { captureManager, editing } = project;
17
+ const terminator = new Terminator();
18
+ const captures = captureManager.filterArmed();
19
+ if (captures.length === 0) {
20
+ this.#isRecording = false;
21
+ return warn("No track is armed for Recording");
22
+ }
23
+ const { status, error } = await Promises.tryCatch(Promise.all(captures.map(capture => capture.prepareRecording(context))));
24
+ if (status === "rejected") {
25
+ this.#isRecording = false;
26
+ return warn(`Could not prepare recording: ${error}`);
27
+ }
28
+ terminator.ownAll(...captures.map(capture => capture.startRecording(context)));
29
+ engine.startRecording(countIn);
30
+ const { isRecording, isCountingIn } = engine;
31
+ const stop = () => {
32
+ if (isRecording.getValue() || isCountingIn.getValue()) {
33
+ return;
34
+ }
35
+ editing.mark();
36
+ terminator.terminate();
37
+ this.#isRecording = false;
38
+ };
39
+ terminator.ownAll(engine.isRecording.subscribe(stop), engine.isCountingIn.subscribe(stop), Terminable.create(() => Recording.#instance = Option.None));
40
+ this.#instance = Option.wrap(new Recording());
41
+ return terminator;
42
+ }
43
+ static #prepare({ api, captureManager, editing, rootBox, userEditingManager }) {
44
+ const captures = captureManager.filterArmed();
45
+ const instruments = rootBox.audioUnits.pointerHub.incoming()
46
+ .map(({ box }) => asInstanceOf(box, AudioUnitBox))
47
+ .filter(box => box.type.getValue() === AudioUnitType.Instrument);
48
+ if (instruments.length === 0) {
49
+ const { audioUnitBox } = editing
50
+ .modify(() => api.createInstrument(InstrumentFactories.Tape))
51
+ .unwrap("Could not create Tape");
52
+ captureManager.get(audioUnitBox.address.uuid)
53
+ .unwrap("Could not unwrap capture")
54
+ .armed.setValue(true);
55
+ }
56
+ else if (captures.length === 0) {
57
+ userEditingManager.audioUnit.get()
58
+ .ifSome(({ box: { address: { uuid } } }) => captureManager.get(uuid)
59
+ .ifSome(capture => capture.armed.setValue(true))); // auto arm editing audio-unit
60
+ }
61
+ }
62
+ static #isRecording = false;
63
+ static #instance = Option.None;
64
+ constructor() { }
65
+ }
@@ -0,0 +1,14 @@
1
+ import { Provider } from "@opendaw/lib-std";
2
+ import { SampleManager } from "@opendaw/studio-adapters";
3
+ import { Project } from "../Project";
4
+ import { Engine } from "../Engine";
5
+ import { Worklets } from "../Worklets";
6
+ export interface RecordingContext {
7
+ project: Project;
8
+ worklets: Worklets;
9
+ engine: Engine;
10
+ audioContext: AudioContext;
11
+ sampleManager: SampleManager;
12
+ requestMIDIAccess: Provider<Promise<MIDIAccess>>;
13
+ }
14
+ //# sourceMappingURL=RecordingContext.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"RecordingContext.d.ts","sourceRoot":"","sources":["../../src/capture/RecordingContext.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,QAAQ,EAAC,MAAM,kBAAkB,CAAA;AACzC,OAAO,EAAC,aAAa,EAAC,MAAM,0BAA0B,CAAA;AACtD,OAAO,EAAC,OAAO,EAAC,MAAM,YAAY,CAAA;AAClC,OAAO,EAAC,MAAM,EAAC,MAAM,WAAW,CAAA;AAChC,OAAO,EAAC,QAAQ,EAAC,MAAM,aAAa,CAAA;AAEpC,MAAM,WAAW,gBAAgB;IAC7B,OAAO,EAAE,OAAO,CAAA;IAChB,QAAQ,EAAE,QAAQ,CAAA;IAClB,MAAM,EAAE,MAAM,CAAA;IACd,YAAY,EAAE,YAAY,CAAA;IAC1B,aAAa,EAAE,aAAa,CAAA;IAC5B,iBAAiB,EAAE,QAAQ,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC,CAAA;CACnD"}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,10 @@
1
+ import { AudioUnitBox } from "@opendaw/studio-boxes";
2
+ export declare namespace AudioUnitExportLayout {
3
+ interface Track {
4
+ audioUnit: AudioUnitBox;
5
+ children: Array<Track>;
6
+ }
7
+ const layout: (audioUnits: ReadonlyArray<AudioUnitBox>) => Array<Track>;
8
+ const printTrackStructure: (tracks: ReadonlyArray<Track>, indent?: number) => void;
9
+ }
10
+ //# sourceMappingURL=AudioUnitExportLayout.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AudioUnitExportLayout.d.ts","sourceRoot":"","sources":["../../src/dawproject/AudioUnitExportLayout.ts"],"names":[],"mappings":"AAAA,OAAO,EAAc,YAAY,EAAC,MAAM,uBAAuB,CAAA;AAK/D,yBAAiB,qBAAqB,CAAC;IACnC,UAAiB,KAAK;QAClB,SAAS,EAAE,YAAY,CAAA;QACvB,QAAQ,EAAE,KAAK,CAAC,KAAK,CAAC,CAAA;KACzB;IAEM,MAAM,MAAM,GAAI,YAAY,aAAa,CAAC,YAAY,CAAC,KAAG,KAAK,CAAC,KAAK,CAgC3E,CAAA;IAgBM,MAAM,mBAAmB,GAAI,QAAQ,aAAa,CAAC,KAAK,CAAC,EAAE,eAAU,KAAG,IAU9E,CAAA;CACJ"}
@@ -0,0 +1,64 @@
1
+ import { AudioBusBox, AudioUnitBox } from "@opendaw/studio-boxes";
2
+ import { ArrayMultimap, asInstanceOf, isDefined, isInstanceOf, Option } from "@opendaw/lib-std";
3
+ import { AudioUnitType } from "@opendaw/studio-enums";
4
+ import { DeviceBoxUtils } from "@opendaw/studio-adapters";
5
+ export var AudioUnitExportLayout;
6
+ (function (AudioUnitExportLayout) {
7
+ AudioUnitExportLayout.layout = (audioUnits) => {
8
+ const feedsInto = new ArrayMultimap();
9
+ audioUnits.forEach(unit => {
10
+ unit.output.targetVertex.ifSome(({ box }) => {
11
+ if (isInstanceOf(box, AudioBusBox)) {
12
+ box.output.targetVertex.ifSome(({ box: targetUnit }) => {
13
+ const audioUnit = asInstanceOf(targetUnit, AudioUnitBox);
14
+ if (audioUnit.type.getValue() !== AudioUnitType.Output) {
15
+ feedsInto.add(audioUnit, unit);
16
+ }
17
+ });
18
+ }
19
+ });
20
+ });
21
+ // Roots are:
22
+ // 1. Units with no output
23
+ // 2. Units that connect directly to Output (become independent roots)
24
+ // 3. The Output unit itself (as a standalone root)
25
+ const roots = audioUnits.filter(unit => {
26
+ if (unit.type.getValue() === AudioUnitType.Output) {
27
+ return true;
28
+ }
29
+ if (unit.output.targetVertex.isEmpty()) {
30
+ return true;
31
+ }
32
+ return unit.output.targetVertex
33
+ .flatMap(({ box }) => isInstanceOf(box, AudioBusBox) ? box.output.targetVertex : Option.None)
34
+ .map(({ box }) => asInstanceOf(box, AudioUnitBox).type.getValue() === AudioUnitType.Output)
35
+ .unwrapOrElse(false);
36
+ });
37
+ const visited = new Set();
38
+ return roots
39
+ .map(root => buildTrackRecursive(root, feedsInto, visited))
40
+ .filter(isDefined);
41
+ };
42
+ const buildTrackRecursive = (audioUnit, feedsInto, visited) => {
43
+ if (visited.has(audioUnit)) {
44
+ console.warn(`Cycle detected at AudioUnitBox`, audioUnit);
45
+ return null;
46
+ }
47
+ visited.add(audioUnit);
48
+ const children = feedsInto.get(audioUnit)
49
+ .map(childUnit => buildTrackRecursive(childUnit, feedsInto, visited))
50
+ .filter(isDefined);
51
+ return { audioUnit, children };
52
+ };
53
+ AudioUnitExportLayout.printTrackStructure = (tracks, indent = 0) => {
54
+ const spaces = " ".repeat(indent);
55
+ tracks.forEach(track => {
56
+ const inputBox = track.audioUnit.input.pointerHub.incoming().at(0)?.box;
57
+ const label = DeviceBoxUtils.lookupLabelField(inputBox).getValue();
58
+ console.debug(`${spaces}⌙ ${label} (${track.audioUnit.address.toString()})`);
59
+ if (track.children.length > 0) {
60
+ AudioUnitExportLayout.printTrackStructure(track.children, indent + 2);
61
+ }
62
+ });
63
+ };
64
+ })(AudioUnitExportLayout || (AudioUnitExportLayout = {}));
@@ -0,0 +1,9 @@
1
+ import { EqualizerSchema } from "@opendaw/lib-dawproject";
2
+ import { RevampDeviceBox } from "@opendaw/studio-boxes";
3
+ import { BoxGraph, Field } from "@opendaw/lib-box";
4
+ import { int } from "@opendaw/lib-std";
5
+ import { Pointers } from "@opendaw/studio-enums";
6
+ export declare namespace BuiltinDevices {
7
+ const equalizer: (boxGraph: BoxGraph, equalizer: EqualizerSchema, field: Field<Pointers.MidiEffectHost> | Field<Pointers.AudioEffectHost>, index: int) => RevampDeviceBox;
8
+ }
9
+ //# sourceMappingURL=BuiltinDevices.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"BuiltinDevices.d.ts","sourceRoot":"","sources":["../../src/dawproject/BuiltinDevices.ts"],"names":[],"mappings":"AAAA,OAAO,EAAyB,eAAe,EAAmB,MAAM,yBAAyB,CAAA;AACjG,OAAO,EAAC,eAAe,EAA0B,MAAM,uBAAuB,CAAA;AAC9E,OAAO,EAAC,QAAQ,EAAE,KAAK,EAAC,MAAM,kBAAkB,CAAA;AAChD,OAAO,EAAY,GAAG,EAAO,MAAM,kBAAkB,CAAA;AACrD,OAAO,EAAC,QAAQ,EAAC,MAAM,uBAAuB,CAAA;AAG9C,yBAAiB,cAAc,CAAC;IACrB,MAAM,SAAS,GAAI,UAAU,QAAQ,EAClB,WAAW,eAAe,EAC1B,OAAO,KAAK,CAAC,QAAQ,CAAC,cAAc,CAAC,GAAG,KAAK,CAAC,QAAQ,CAAC,eAAe,CAAC,EACvE,OAAO,GAAG,KAAG,eA4DtC,CAAA;CACJ"}
@@ -0,0 +1,70 @@
1
+ import { EqBandType, ParameterDecoder } from "@opendaw/lib-dawproject";
2
+ import { RevampDeviceBox } from "@opendaw/studio-boxes";
3
+ import { ifDefined, UUID } from "@opendaw/lib-std";
4
+ import { semitoneToHz } from "@opendaw/lib-dsp";
5
+ export var BuiltinDevices;
6
+ (function (BuiltinDevices) {
7
+ BuiltinDevices.equalizer = (boxGraph, equalizer, field, index) => {
8
+ const mapOrder = (order) => {
9
+ switch (order) {
10
+ case 1:
11
+ case 2:
12
+ return 0;
13
+ case 3:
14
+ case 4:
15
+ return 1;
16
+ case 5:
17
+ case 6:
18
+ return 2;
19
+ default:
20
+ return 3;
21
+ }
22
+ };
23
+ const readPass = (schema, pass) => {
24
+ const { order, frequency, q, enabled } = pass;
25
+ order.setValue(mapOrder(schema.order));
26
+ frequency.setValue(semitoneToHz(schema.freq.value));
27
+ ifDefined(schema.Q?.value, value => q.setValue(value));
28
+ ifDefined(schema.enabled?.value, value => enabled.setValue(value));
29
+ };
30
+ const readShelf = (schema, pass) => {
31
+ const { frequency, gain, enabled } = pass;
32
+ frequency.setValue(ParameterDecoder.readValue(schema.freq));
33
+ ifDefined(schema.gain?.value, value => gain.setValue(value));
34
+ ifDefined(schema.enabled?.value, value => enabled.setValue(value));
35
+ };
36
+ return RevampDeviceBox.create(boxGraph, UUID.generate(), box => {
37
+ box.host.refer(field);
38
+ box.index.setValue(index);
39
+ box.label.setValue(equalizer.deviceName ?? "Revamp");
40
+ let bellIndex = 0;
41
+ equalizer.bands.forEach((band) => {
42
+ switch (band.type) {
43
+ case EqBandType.HIGH_PASS:
44
+ return readPass(band, box.highPass);
45
+ case EqBandType.LOW_PASS:
46
+ return readPass(band, box.lowPass);
47
+ case EqBandType.HIGH_SHELF:
48
+ return readShelf(band, box.highShelf);
49
+ case EqBandType.LOW_SHELF:
50
+ return readShelf(band, box.lowShelf);
51
+ case EqBandType.BELL: {
52
+ if (bellIndex === 3) {
53
+ return;
54
+ }
55
+ const bell = [box.lowBell, box.midBell, box.highBell][bellIndex++];
56
+ const { frequency, gain, q, enabled } = bell;
57
+ frequency.setValue(ParameterDecoder.readValue(band.freq));
58
+ ifDefined(band.Q?.value, value => q.setValue(value));
59
+ ifDefined(band.gain?.value, value => gain.setValue(value));
60
+ ifDefined(band.enabled?.value, value => enabled.setValue(value));
61
+ return;
62
+ }
63
+ default:
64
+ console.warn(`Cannot map band type: ${band.type} to Revamp`);
65
+ return;
66
+ }
67
+ });
68
+ });
69
+ };
70
+ })(BuiltinDevices || (BuiltinDevices = {}));
@@ -1,20 +1,22 @@
1
1
  import { UUID } from "@opendaw/lib-std";
2
2
  import { MetaDataSchema, ProjectSchema } from "@opendaw/lib-dawproject";
3
- export declare namespace DawProjectIO {
3
+ import { Project } from "../Project";
4
+ export declare namespace DawProject {
4
5
  type Resource = {
5
6
  uuid: UUID.Format;
6
7
  path: string;
7
8
  name: string;
8
9
  buffer: ArrayBuffer;
9
10
  };
10
- interface Resources {
11
+ interface ResourceProvider {
11
12
  fromPath(path: string): Resource;
12
13
  fromUUID(uuid: UUID.Format): Resource;
13
14
  }
14
15
  const decode: (buffer: ArrayBuffer | NonSharedBuffer) => Promise<{
15
16
  metaData: MetaDataSchema;
16
17
  project: ProjectSchema;
17
- resources: Resources;
18
+ resources: ResourceProvider;
18
19
  }>;
20
+ const encode: (project: Project, metaData: MetaDataSchema) => Promise<ArrayBuffer>;
19
21
  }
20
- //# sourceMappingURL=DawProjectIO.d.ts.map
22
+ //# sourceMappingURL=DawProject.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"DawProject.d.ts","sourceRoot":"","sources":["../../src/dawproject/DawProject.ts"],"names":[],"mappings":"AAEA,OAAO,EAAmB,IAAI,EAAC,MAAM,kBAAkB,CAAA;AACvD,OAAO,EAAsB,cAAc,EAAE,aAAa,EAAC,MAAM,yBAAyB,CAAA;AAC1F,OAAO,EAAC,OAAO,EAAC,MAAM,YAAY,CAAA;AAGlC,yBAAiB,UAAU,CAAC;IACxB,KAAY,QAAQ,GAAG;QAAE,IAAI,EAAE,IAAI,CAAC,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,WAAW,CAAA;KAAE,CAAA;IAE7F,UAAiB,gBAAgB;QAC7B,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,QAAQ,CAAA;QAChC,QAAQ,CAAC,IAAI,EAAE,IAAI,CAAC,MAAM,GAAG,QAAQ,CAAA;KACxC;IAEM,MAAM,MAAM,GAAU,QAAQ,WAAW,GAAG,eAAe,KAAG,OAAO,CAAC;QACzE,QAAQ,EAAE,cAAc,CAAC;QACzB,OAAO,EAAE,aAAa,CAAC;QACvB,SAAS,EAAE,gBAAgB,CAAA;KAC9B,CAyBA,CAAA;IAEM,MAAM,MAAM,GAAI,SAAS,OAAO,EAAE,UAAU,cAAc,KAAG,OAAO,CAAC,WAAW,CAgBtF,CAAA;CACJ"}