@opendaw/studio-core 0.0.82 → 0.0.84

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 (44) hide show
  1. package/dist/ExternalLib.d.ts +3 -2
  2. package/dist/ExternalLib.d.ts.map +1 -1
  3. package/dist/ExternalLib.js +2 -2
  4. package/dist/Preferences.d.ts +4 -0
  5. package/dist/Preferences.d.ts.map +1 -1
  6. package/dist/Preferences.js +4 -2
  7. package/dist/capture/CaptureDevices.js +1 -1
  8. package/dist/capture/RecordAudio.d.ts.map +1 -1
  9. package/dist/capture/RecordAudio.js +18 -9
  10. package/dist/capture/RecordMidi.d.ts.map +1 -1
  11. package/dist/capture/RecordMidi.js +4 -3
  12. package/dist/dawproject/BuiltinDevices.d.ts +1 -1
  13. package/dist/dawproject/DawProject.d.ts.map +1 -1
  14. package/dist/dawproject/DawProject.js +17 -3
  15. package/dist/dawproject/DawProjectImporter.d.ts.map +1 -1
  16. package/dist/dawproject/DawProjectImporter.js +23 -20
  17. package/dist/midi/MIDILearning.d.ts +4 -16
  18. package/dist/midi/MIDILearning.d.ts.map +1 -1
  19. package/dist/midi/MIDILearning.js +95 -62
  20. package/dist/processors.js +17 -17
  21. package/dist/processors.js.map +4 -4
  22. package/dist/project/Project.d.ts.map +1 -1
  23. package/dist/project/Project.js +1 -0
  24. package/dist/project/ProjectBundle.d.ts.map +1 -1
  25. package/dist/project/ProjectBundle.js +17 -3
  26. package/dist/project/ProjectMigration.d.ts.map +1 -1
  27. package/dist/project/ProjectMigration.js +4 -3
  28. package/dist/project/ProjectValidation.d.ts.map +1 -1
  29. package/dist/project/ProjectValidation.js +15 -13
  30. package/dist/project/audio/AudioContentFactory.d.ts.map +1 -1
  31. package/dist/project/audio/AudioContentFactory.js +3 -3
  32. package/dist/project/audio/AudioContentModifier.d.ts.map +1 -1
  33. package/dist/project/audio/AudioContentModifier.js +0 -1
  34. package/dist/project/audio/AudioFileBoxFactory.d.ts.map +1 -1
  35. package/dist/project/audio/AudioFileBoxFactory.js +5 -0
  36. package/dist/samples/DefaultSampleLoaderManager.d.ts.map +1 -1
  37. package/dist/samples/DefaultSampleLoaderManager.js +4 -1
  38. package/dist/soundfont/DefaultSoundfontLoader.d.ts.map +1 -1
  39. package/dist/soundfont/DefaultSoundfontLoader.js +8 -7
  40. package/dist/soundfont/SoundfontService.d.ts.map +1 -1
  41. package/dist/soundfont/SoundfontService.js +3 -6
  42. package/dist/workers-main.js +1 -1
  43. package/dist/workers-main.js.map +2 -2
  44. package/package.json +15 -15
@@ -1,5 +1,6 @@
1
+ import { Promises } from "@opendaw/lib-runtime";
1
2
  export declare namespace ExternalLib {
2
- const JSZip: () => Promise<import("jszip")>;
3
- const SoundFont2: () => Promise<typeof import("soundfont2").SoundFont2>;
3
+ const JSZip: () => Promise<Promises.RejectedResult | Promises.ResolveResult<import("jszip")>>;
4
+ const SoundFont2: () => Promise<Promises.RejectedResult | Promises.ResolveResult<typeof import("soundfont2").SoundFont2>>;
4
5
  }
5
6
  //# sourceMappingURL=ExternalLib.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"ExternalLib.d.ts","sourceRoot":"","sources":["../src/ExternalLib.ts"],"names":[],"mappings":"AAGA,yBAAiB,WAAW,CAAC;IAMlB,MAAM,KAAK,gCAC8C,CAAA;IAEzD,MAAM,UAAU,uDAC+C,CAAA;CACzE"}
1
+ {"version":3,"file":"ExternalLib.d.ts","sourceRoot":"","sources":["../src/ExternalLib.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,QAAQ,EAAC,MAAM,sBAAsB,CAAA;AAG7C,yBAAiB,WAAW,CAAC;IAMlB,MAAM,KAAK,kFAC+C,CAAA;IAE1D,MAAM,UAAU,yGACgD,CAAA;CAC1E"}
@@ -5,6 +5,6 @@ export var ExternalLib;
5
5
  console.debug(`ExternalLib.importFailure count: ${count}, online: ${navigator.onLine}`, error);
6
6
  return count < 10;
7
7
  };
8
- ExternalLib.JSZip = async () => await Promises.guardedRetry(() => import("jszip").then(({ default: JSZip }) => JSZip), callback);
9
- ExternalLib.SoundFont2 = async () => await Promises.guardedRetry(() => import("soundfont2").then(({ SoundFont2 }) => SoundFont2), callback);
8
+ ExternalLib.JSZip = async () => await Promises.tryCatch(Promises.guardedRetry(() => import("jszip").then(({ default: JSZip }) => JSZip), callback));
9
+ ExternalLib.SoundFont2 = async () => await Promises.tryCatch(Promises.guardedRetry(() => import("soundfont2").then(({ SoundFont2 }) => SoundFont2), callback));
10
10
  })(ExternalLib || (ExternalLib = {}));
@@ -2,6 +2,8 @@ import { z } from "zod";
2
2
  import { Observer, Subscription } from "@opendaw/lib-std";
3
3
  declare const PreferencesSchema: z.ZodObject<{
4
4
  "visible-help-hints": z.ZodDefault<z.ZodBoolean>;
5
+ "note-audition-while-editing": z.ZodDefault<z.ZodBoolean>;
6
+ "modifying-controls-wheel": z.ZodDefault<z.ZodBoolean>;
5
7
  "auto-open-clips": z.ZodDefault<z.ZodBoolean>;
6
8
  "auto-create-output-compressor": z.ZodDefault<z.ZodBoolean>;
7
9
  "footer-show-fps-meter": z.ZodDefault<z.ZodBoolean>;
@@ -13,6 +15,8 @@ export type Preferences = z.infer<typeof PreferencesSchema>;
13
15
  export declare const Preferences: {
14
16
  values: {
15
17
  "visible-help-hints": boolean;
18
+ "note-audition-while-editing": boolean;
19
+ "modifying-controls-wheel": boolean;
16
20
  "auto-open-clips": boolean;
17
21
  "auto-create-output-compressor": boolean;
18
22
  "footer-show-fps-meter": boolean;
@@ -1 +1 @@
1
- {"version":3,"file":"Preferences.d.ts","sourceRoot":"","sources":["../src/Preferences.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,CAAC,EAAC,MAAM,KAAK,CAAA;AACrB,OAAO,EAAsB,QAAQ,EAAE,YAAY,EAAW,MAAM,kBAAkB,CAAA;AAEtF,QAAA,MAAM,iBAAiB;;;;;;;;iBAQrB,CAAA;AAEF,MAAM,MAAM,WAAW,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,iBAAiB,CAAC,CAAA;AAE3D,eAAO,MAAM,WAAW;;;;;;;;;;0BAkCM,GAAG,SAAS,MAAM,WAAW,YACrC,QAAQ,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC,YAAY,GAAG,KAAG,YAAY;CAK1E,CAAA"}
1
+ {"version":3,"file":"Preferences.d.ts","sourceRoot":"","sources":["../src/Preferences.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,CAAC,EAAC,MAAM,KAAK,CAAA;AACrB,OAAO,EAAsB,QAAQ,EAAE,YAAY,EAAW,MAAM,kBAAkB,CAAA;AAEtF,QAAA,MAAM,iBAAiB;;;;;;;;;;iBAUrB,CAAA;AAEF,MAAM,MAAM,WAAW,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,iBAAiB,CAAC,CAAA;AAE3D,eAAO,MAAM,WAAW;;;;;;;;;;;;0BAkCM,GAAG,SAAS,MAAM,WAAW,YACrC,QAAQ,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC,YAAY,GAAG,KAAG,YAAY;CAK1E,CAAA"}
@@ -2,12 +2,14 @@ import { z } from "zod";
2
2
  import { isDefined, Notifier, tryCatch } from "@opendaw/lib-std";
3
3
  const PreferencesSchema = z.object({
4
4
  "visible-help-hints": z.boolean().default(true),
5
- "auto-open-clips": z.boolean().default(false),
5
+ "note-audition-while-editing": z.boolean().default(true),
6
+ "modifying-controls-wheel": z.boolean().default(false),
7
+ "auto-open-clips": z.boolean().default(true),
6
8
  "auto-create-output-compressor": z.boolean().default(true),
7
9
  "footer-show-fps-meter": z.boolean().default(false),
8
10
  "footer-show-build-infos": z.boolean().default(false),
9
11
  "dragging-use-pointer-lock": z.boolean().default(false),
10
- "enable-beta-features": z.boolean().default(false),
12
+ "enable-beta-features": z.boolean().default(false)
11
13
  });
12
14
  export const Preferences = (() => {
13
15
  const STORAGE_KEY = "preferences";
@@ -31,7 +31,7 @@ export class CaptureDevices {
31
31
  },
32
32
  onRemoved: ({ box: { address: { uuid } } }) => {
33
33
  this.#captures.removeByKeyIfExist(uuid)?.terminate();
34
- this.#captureSubscriptions.get(uuid).subscription.terminate();
34
+ this.#captureSubscriptions.removeByKeyIfExist(uuid)?.subscription.terminate();
35
35
  }
36
36
  });
37
37
  }
@@ -1 +1 @@
1
- {"version":3,"file":"RecordAudio.d.ts","sourceRoot":"","sources":["../../src/capture/RecordAudio.ts"],"names":[],"mappings":"AAAA,OAAO,EAA2C,UAAU,EAAmB,MAAM,kBAAkB,CAAA;AAGvG,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;KACjB,CAAA;IAED,MAAM,CAAC,MAAM,KAAK,GACd,0FAAwF,kBAAkB,KACxG,UAwEL,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;KACjB,CAAA;IAED,MAAM,CAAC,MAAM,KAAK,GACd,0FAAwF,kBAAkB,KACxG,UAuFL,CAAA;;CACJ"}
@@ -1,6 +1,6 @@
1
- import { Option, quantizeCeil, quantizeFloor, Terminable, Terminator, UUID } from "@opendaw/lib-std";
2
- import { dbToGain, PPQN } from "@opendaw/lib-dsp";
3
- import { AudioFileBox, AudioRegionBox, ValueEventCollectionBox } from "@opendaw/studio-boxes";
1
+ import { Option, quantizeFloor, Terminable, Terminator, UUID } from "@opendaw/lib-std";
2
+ import { dbToGain, PPQN, TimeBase } from "@opendaw/lib-dsp";
3
+ import { AudioFileBox, AudioPitchStretchBox, AudioRegionBox, ValueEventCollectionBox, WarpMarkerBox } from "@opendaw/studio-boxes";
4
4
  import { ColorCodes, TrackType } from "@opendaw/studio-adapters";
5
5
  import { RecordTrack } from "./RecordTrack";
6
6
  export var RecordAudio;
@@ -31,15 +31,21 @@ export var RecordAudio;
31
31
  const fileName = `Recording-${fileDateString}`;
32
32
  const fileBox = AudioFileBox.create(boxGraph, uuid, box => box.fileName.setValue(fileName));
33
33
  const collectionBox = ValueEventCollectionBox.create(boxGraph, UUID.generate());
34
+ const stretchBox = AudioPitchStretchBox.create(boxGraph, UUID.generate());
35
+ WarpMarkerBox.create(boxGraph, UUID.generate(), box => box.owner.refer(stretchBox.warpMarkers));
36
+ const warpMarkerBox = WarpMarkerBox.create(boxGraph, UUID.generate(), box => box.owner.refer(stretchBox.warpMarkers));
34
37
  const regionBox = AudioRegionBox.create(boxGraph, UUID.generate(), box => {
35
38
  box.file.refer(fileBox);
36
39
  box.events.refer(collectionBox.owners);
37
40
  box.regions.refer(trackBox.regions);
38
41
  box.position.setValue(position);
39
42
  box.hue.setValue(ColorCodes.forTrackType(TrackType.Audio));
43
+ box.timeBase.setValue(TimeBase.Musical);
40
44
  box.label.setValue("Recording");
45
+ box.playMode.refer(stretchBox);
41
46
  });
42
- return { fileBox, regionBox };
47
+ project.selection.select(regionBox);
48
+ return { fileBox, regionBox, warpMarkerBox };
43
49
  });
44
50
  const { tempoMap, env: { audioContext: { sampleRate } } } = project;
45
51
  terminator.ownAll(Terminable.create(() => {
@@ -61,15 +67,18 @@ export var RecordAudio;
61
67
  streamGain.connect(recordingWorklet);
62
68
  recordingData = createRecordingData(quantizeFloor(owner.getValue(), beats));
63
69
  }
64
- const { regionBox } = recordingData.unwrap();
70
+ const { regionBox, warpMarkerBox } = recordingData.unwrap();
65
71
  editing.modify(() => {
66
72
  if (regionBox.isAttached()) {
67
73
  const { duration, loopDuration } = regionBox;
68
- const newDuration = quantizeCeil(engine.position.getValue(), beats) - regionBox.position.getValue();
69
- duration.setValue(newDuration);
70
- loopDuration.setValue(newDuration);
71
- const totalSamples = Math.ceil(tempoMap.intervalToSeconds(0, newDuration) * sampleRate);
74
+ const distanceInPPQN = Math.floor(engine.position.getValue() - regionBox.position.getValue());
75
+ duration.setValue(distanceInPPQN);
76
+ loopDuration.setValue(distanceInPPQN);
77
+ warpMarkerBox.position.setValue(distanceInPPQN);
78
+ const seconds = tempoMap.intervalToSeconds(0, distanceInPPQN);
79
+ const totalSamples = Math.ceil(seconds * sampleRate);
72
80
  recordingWorklet.setFillLength(totalSamples);
81
+ warpMarkerBox.seconds.setValue(seconds);
73
82
  }
74
83
  else {
75
84
  terminator.terminate();
@@ -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,UAuEvE,CAAA;;CACJ"}
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,UAwEvE,CAAA;;CACJ"}
@@ -19,14 +19,15 @@ export var RecordMidi;
19
19
  const writePosition = position.getValue() + latency;
20
20
  editing.modify(() => {
21
21
  const collection = NoteEventCollectionBox.create(boxGraph, UUID.generate());
22
- const region = NoteRegionBox.create(boxGraph, UUID.generate(), box => {
22
+ const regionBox = NoteRegionBox.create(boxGraph, UUID.generate(), box => {
23
23
  box.regions.refer(trackBox.regions);
24
24
  box.events.refer(collection.owners);
25
25
  box.position.setValue(Math.max(quantizeRound(writePosition, beats), 0));
26
26
  box.hue.setValue(ColorCodes.forTrackType(TrackType.Notes));
27
27
  });
28
- engine.ignoreNoteRegion(region.address.uuid);
29
- writing = Option.wrap({ region, collection });
28
+ project.selection.select(regionBox);
29
+ engine.ignoreNoteRegion(regionBox.address.uuid);
30
+ writing = Option.wrap({ region: regionBox, collection });
30
31
  }, false);
31
32
  };
32
33
  terminator.own(position.catchupAndSubscribe(owner => {
@@ -4,6 +4,6 @@ import { EqualizerSchema } from "@opendaw/lib-dawproject";
4
4
  import { Pointers } from "@opendaw/studio-enums";
5
5
  import { RevampDeviceBox } from "@opendaw/studio-boxes";
6
6
  export declare namespace BuiltinDevices {
7
- const equalizer: (boxGraph: BoxGraph, equalizer: EqualizerSchema, field: Field<Pointers.MidiEffectHost> | Field<Pointers.AudioEffectHost>, index: int) => RevampDeviceBox;
7
+ const equalizer: (boxGraph: BoxGraph, equalizer: EqualizerSchema, field: Field<Pointers.MIDIEffectHost> | Field<Pointers.AudioEffectHost>, index: int) => RevampDeviceBox;
8
8
  }
9
9
  //# sourceMappingURL=BuiltinDevices.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"DawProject.d.ts","sourceRoot":"","sources":["../../src/dawproject/DawProject.ts"],"names":[],"mappings":"AAAA,OAAO,EAA8B,IAAI,EAAC,MAAM,kBAAkB,CAAA;AAElE,OAAO,EAAsB,cAAc,EAAE,aAAa,EAAC,MAAM,yBAAyB,CAAA;AAC1F,OAAO,EAAC,eAAe,EAAE,mBAAmB,EAAC,MAAM,0BAA0B,CAAA;AAI7E,yBAAiB,UAAU,CAAC;IACxB,KAAY,QAAQ,GAAG;QAAE,IAAI,EAAE,IAAI,CAAC,KAAK,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,WAAW,CAAA;KAAE,CAAA;IAE5F,UAAiB,gBAAgB;QAC7B,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,QAAQ,CAAA;QAChC,QAAQ,CAAC,IAAI,EAAE,IAAI,CAAC,KAAK,GAAG,QAAQ,CAAA;KACvC;IAEM,MAAM,MAAM,GAAU,QAAQ,WAAW,GAAG,MAAM,CAAC,WAAW,CAAC,KAAG,OAAO,CAAC;QAC7E,QAAQ,EAAE,cAAc,CAAC;QACzB,OAAO,EAAE,aAAa,CAAC;QACvB,SAAS,EAAE,gBAAgB,CAAA;KAC9B,CAyBA,CAAA;IAEM,MAAM,MAAM,GAAU,UAAU,eAAe,EACzB,eAAe,mBAAmB,EAClC,UAAU,cAAc,KAAG,OAAO,CAAC,WAAW,CAiB1E,CAAA;CACJ"}
1
+ {"version":3,"file":"DawProject.d.ts","sourceRoot":"","sources":["../../src/dawproject/DawProject.ts"],"names":[],"mappings":"AAAA,OAAO,EAA+C,IAAI,EAAC,MAAM,kBAAkB,CAAA;AAEnF,OAAO,EAAsB,cAAc,EAAE,aAAa,EAAC,MAAM,yBAAyB,CAAA;AAC1F,OAAO,EAAC,eAAe,EAAE,mBAAmB,EAAC,MAAM,0BAA0B,CAAA;AAI7E,yBAAiB,UAAU,CAAC;IACxB,KAAY,QAAQ,GAAG;QAAE,IAAI,EAAE,IAAI,CAAC,KAAK,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,WAAW,CAAA;KAAE,CAAA;IAE5F,UAAiB,gBAAgB;QAC7B,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,QAAQ,CAAA;QAChC,QAAQ,CAAC,IAAI,EAAE,IAAI,CAAC,KAAK,GAAG,QAAQ,CAAA;KACvC;IAEM,MAAM,MAAM,GAAU,QAAQ,WAAW,GAAG,MAAM,CAAC,WAAW,CAAC,KAAG,OAAO,CAAC;QAC7E,QAAQ,EAAE,cAAc,CAAC;QACzB,OAAO,EAAE,aAAa,CAAC;QACvB,SAAS,EAAE,gBAAgB,CAAA;KAC9B,CAgCA,CAAA;IAEM,MAAM,MAAM,GAAU,UAAU,eAAe,EACzB,eAAe,mBAAmB,EAClC,UAAU,cAAc,KAAG,OAAO,CAAC,WAAW,CAwB1E,CAAA;CACJ"}
@@ -1,4 +1,4 @@
1
- import { asDefined, isDefined, panic, UUID } from "@opendaw/lib-std";
1
+ import { asDefined, isDefined, panic, RuntimeNotifier, UUID } from "@opendaw/lib-std";
2
2
  import { Xml } from "@opendaw/lib-xml";
3
3
  import { FileReferenceSchema, MetaDataSchema, ProjectSchema } from "@opendaw/lib-dawproject";
4
4
  import { DawProjectExporter } from "./DawProjectExporter";
@@ -6,7 +6,14 @@ import { ExternalLib } from "../ExternalLib";
6
6
  export var DawProject;
7
7
  (function (DawProject) {
8
8
  DawProject.decode = async (buffer) => {
9
- const JSZip = await ExternalLib.JSZip();
9
+ const { status, value: JSZip, error } = await ExternalLib.JSZip();
10
+ if (status === "rejected") {
11
+ await RuntimeNotifier.info({
12
+ headline: "Error",
13
+ message: `Could not load JSZip: ${String(error)}`
14
+ });
15
+ return Promise.reject(error);
16
+ }
10
17
  const zip = await JSZip.loadAsync(buffer);
11
18
  const metaDataXml = await zip.file("metadata.xml")?.async("string");
12
19
  const metaData = isDefined(metaDataXml) ? Xml.parse(metaDataXml, MetaDataSchema) : Xml.element({}, MetaDataSchema);
@@ -30,7 +37,14 @@ export var DawProject;
30
37
  };
31
38
  };
32
39
  DawProject.encode = async (skeleton, sampleManager, metaData) => {
33
- const JSZip = await ExternalLib.JSZip();
40
+ const { status, value: JSZip, error } = await ExternalLib.JSZip();
41
+ if (status === "rejected") {
42
+ await RuntimeNotifier.info({
43
+ headline: "Error",
44
+ message: `Could not load JSZip: ${String(error)}`
45
+ });
46
+ return Promise.reject(error);
47
+ }
34
48
  const zip = new JSZip();
35
49
  const projectSchema = DawProjectExporter.write(skeleton, sampleManager, {
36
50
  write: (path, buffer) => {
@@ -1 +1 @@
1
- {"version":3,"file":"DawProjectImporter.d.ts","sourceRoot":"","sources":["../../src/dawproject/DawProjectImporter.ts"],"names":[],"mappings":"AAAA,OAAO,EAiBH,IAAI,EAEP,MAAM,kBAAkB,CAAA;AAGzB,OAAO,EAcH,aAAa,EAOhB,MAAM,yBAAyB,CAAA;AAyBhC,OAAO,EAOH,eAAe,EAElB,MAAM,0BAA0B,CAAA;AACjC,OAAO,EAAC,UAAU,EAAC,MAAM,cAAc,CAAA;AAKvC,yBAAiB,gBAAgB,CAAC;IAW9B,KAAY,MAAM,GAAG;QACjB,QAAQ,EAAE,aAAa,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACpC,QAAQ,EAAE,eAAe,CAAA;KAC5B,CAAA;IAOM,MAAM,IAAI,GAAU,QAAQ,aAAa,EAAE,WAAW,UAAU,CAAC,gBAAgB,KAAG,OAAO,CAAC,MAAM,CAqYxG,CAAA;CAaJ"}
1
+ {"version":3,"file":"DawProjectImporter.d.ts","sourceRoot":"","sources":["../../src/dawproject/DawProjectImporter.ts"],"names":[],"mappings":"AAAA,OAAO,EAiBH,IAAI,EAEP,MAAM,kBAAkB,CAAA;AAGzB,OAAO,EAcH,aAAa,EAOhB,MAAM,yBAAyB,CAAA;AAyBhC,OAAO,EAMH,eAAe,EAElB,MAAM,0BAA0B,CAAA;AACjC,OAAO,EAAC,UAAU,EAAC,MAAM,cAAc,CAAA;AAIvC,yBAAiB,gBAAgB,CAAC;IAW9B,KAAY,MAAM,GAAG;QACjB,QAAQ,EAAE,aAAa,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACpC,QAAQ,EAAE,eAAe,CAAA;KAC5B,CAAA;IAOM,MAAM,IAAI,GAAU,QAAQ,aAAa,EAAE,WAAW,UAAU,CAAC,gBAAgB,KAAG,OAAO,CAAC,MAAM,CAyYxG,CAAA;CAaJ"}
@@ -4,8 +4,7 @@ import { gainToDb, PPQN } from "@opendaw/lib-dsp";
4
4
  import { ChannelRole, ClipsSchema, DeviceRole, EqualizerSchema, LanesSchema, NotesSchema, PointsSchema, SendType, TrackSchema, WarpsSchema } from "@opendaw/lib-dawproject";
5
5
  import { AudioSendRouting, AudioUnitType, IconSymbol } from "@opendaw/studio-enums";
6
6
  import { AudioBusBox, AudioFileBox, AudioPitchStretchBox, AudioRegionBox, AudioUnitBox, AuxSendBox, BoxIO, CaptureAudioBox, CaptureMidiBox, GrooveShuffleBox, NoteEventBox, NoteEventCollectionBox, NoteRegionBox, RootBox, TimelineBox, TrackBox, UnknownAudioEffectDeviceBox, UnknownMidiEffectDeviceBox, UserInterfaceBox, ValueEventCollectionBox } from "@opendaw/studio-boxes";
7
- import { AudioUnitOrdering, ColorCodes, DeviceBoxUtils, InstrumentFactories, TrackType } from "@opendaw/studio-adapters";
8
- import { DeviceIO } from "./DeviceIO";
7
+ import { AudioUnitOrdering, ColorCodes, InstrumentFactories, TrackType } from "@opendaw/studio-adapters";
9
8
  import { BuiltinDevices } from "./BuiltinDevices";
10
9
  import { AudioContentHelpers } from "../project/audio/AudioContentHelpers";
11
10
  export var DawProjectImport;
@@ -61,17 +60,19 @@ export var DawProjectImport;
61
60
  const createEffect = (device, field, index) => {
62
61
  const { deviceRole, deviceVendor, deviceID, deviceName, state } = device;
63
62
  assert(deviceRole === DeviceRole.NOTE_FX || deviceRole === DeviceRole.AUDIO_FX, "Device is not an effect");
64
- if (deviceVendor === "openDAW") {
65
- console.debug(`Recreate openDAW effect device '${deviceName}' with id '${deviceID}'`);
66
- const resource = ifDefined(state?.path, path => resources.fromPath(path));
63
+ /*
64
+ TODO There is a bug in this if branch, which results in an invalid host pointer
65
+ if (deviceVendor === "openDAW") {
66
+ console.debug(`Recreate openDAW effect device '${deviceName}' with id '${deviceID}'`)
67
+ const resource = ifDefined(state?.path, path => resources.fromPath(path))
67
68
  if (isDefined(resource)) {
68
- const device = DeviceIO.importDevice(boxGraph, resource.buffer);
69
- device.host.refer(field);
70
- device.label.setValue(deviceName ?? "");
71
- DeviceBoxUtils.lookupIndexField(device).setValue(index);
72
- return;
69
+ const device = DeviceIO.importDevice(boxGraph, resource.buffer)
70
+ device.host.refer(field)
71
+ device.label.setValue(deviceName ?? "")
72
+ DeviceBoxUtils.lookupIndexField(device).setValue(index)
73
+ return
73
74
  }
74
- }
75
+ }*/
75
76
  if (isInstanceOf(device, EqualizerSchema)) {
76
77
  return BuiltinDevices.equalizer(boxGraph, device, field, index);
77
78
  }
@@ -112,20 +113,22 @@ export var DawProjectImport;
112
113
  });
113
114
  };
114
115
  const createInstrumentBox = (audioUnitBox, track, device) => {
116
+ /*
117
+ TODO There is a bug in this if branch, which results in an invalid host pointer
115
118
  if (isDefined(device)) {
116
- const { deviceName, deviceVendor, deviceID, state } = device;
119
+ const {deviceName, deviceVendor, deviceID, state} = device
117
120
  if (deviceVendor === "openDAW") {
118
- console.debug(`Recreate openDAW instrument device '${deviceName}' with id '${deviceID}'`);
119
- const resource = ifDefined(state?.path, path => resources.fromPath(path));
121
+ console.debug(`Recreate openDAW instrument device '${deviceName}' with id '${deviceID}'`)
122
+ const resource = ifDefined(state?.path, path => resources.fromPath(path))
120
123
  if (isDefined(resource)) {
121
- const device = DeviceIO.importDevice(boxGraph, resource.buffer);
122
- device.host.refer(audioUnitBox.input);
123
- device.label.setValue(deviceName ?? "");
124
- assert(DeviceBoxUtils.isInstrumentDeviceBox(device), `${device.name} is not an instrument`);
125
- return device;
124
+ const device = DeviceIO.importDevice(boxGraph, resource.buffer)
125
+ device.host.refer(audioUnitBox.input)
126
+ device.label.setValue(deviceName ?? "")
127
+ assert(DeviceBoxUtils.isInstrumentDeviceBox(device), `${device.name} is not an instrument`)
128
+ return device as InstrumentBox
126
129
  }
127
130
  }
128
- }
131
+ }*/
129
132
  if (track.contentType === "notes") {
130
133
  return InstrumentFactories.Vaporisateur
131
134
  .create(boxGraph, audioUnitBox.input, track.name ?? "", IconSymbol.Piano);
@@ -1,26 +1,14 @@
1
- import { byte, JSONValue, Provider, Terminable } from "@opendaw/lib-std";
2
- import { Address, AddressJSON, PrimitiveField, PrimitiveValues } from "@opendaw/lib-box";
1
+ import { Terminable } from "@opendaw/lib-std";
2
+ import { Address, Field, PrimitiveField, PrimitiveValues } from "@opendaw/lib-box";
3
3
  import { Pointers } from "@opendaw/studio-enums";
4
4
  import { Project } from "../project";
5
- export type MIDIConnectionJSON = ({
6
- type: "control";
7
- controlId: byte;
8
- }) & {
9
- address: AddressJSON;
10
- channel: byte;
11
- } & JSONValue;
12
- export interface MIDIConnection extends Terminable {
13
- address: Address;
14
- label: Provider<string>;
15
- toJSON(): MIDIConnectionJSON;
16
- }
17
5
  export declare class MIDILearning implements Terminable {
18
6
  #private;
19
7
  constructor(project: Project);
8
+ followUser(field: Field<Pointers.MIDIControllers>): void;
20
9
  hasMidiConnection(address: Address): boolean;
21
10
  forgetMidiConnection(address: Address): void;
22
- learnMIDIControls(field: PrimitiveField<PrimitiveValues, Pointers.MidiControl | Pointers>): Promise<void>;
23
- toJSON(): ReadonlyArray<MIDIConnectionJSON>;
11
+ learnMIDIControls(field: PrimitiveField<PrimitiveValues, Pointers.MIDIControl | Pointers>): Promise<void>;
24
12
  terminate(): void;
25
13
  }
26
14
  //# sourceMappingURL=MIDILearning.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"MIDILearning.d.ts","sourceRoot":"","sources":["../../src/midi/MIDILearning.ts"],"names":[],"mappings":"AAAA,OAAO,EACH,IAAI,EAIJ,SAAS,EAGT,QAAQ,EAGR,UAAU,EAEb,MAAM,kBAAkB,CAAA;AACzB,OAAO,EAAC,OAAO,EAAE,WAAW,EAAE,cAAc,EAAE,eAAe,EAAC,MAAM,kBAAkB,CAAA;AAEtF,OAAO,EAAC,QAAQ,EAAC,MAAM,uBAAuB,CAAA;AAE9C,OAAO,EAAC,OAAO,EAAC,MAAM,YAAY,CAAA;AAIlC,MAAM,MAAM,kBAAkB,GAAG,CAAC;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,SAAS,EAAE,IAAI,CAAA;CAAE,CAAC,GACjE;IAAE,OAAO,EAAE,WAAW,CAAC;IAAC,OAAO,EAAE,IAAI,CAAA;CAAE,GACvC,SAAS,CAAA;AAEf,MAAM,WAAW,cAAe,SAAQ,UAAU;IAC9C,OAAO,EAAE,OAAO,CAAA;IAChB,KAAK,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAA;IACvB,MAAM,IAAI,kBAAkB,CAAA;CAC/B;AAID,qBAAa,YAAa,YAAW,UAAU;;gBAM/B,OAAO,EAAE,OAAO;IAK5B,iBAAiB,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO;IAC5C,oBAAoB,CAAC,OAAO,EAAE,OAAO;IAE/B,iBAAiB,CAAC,KAAK,EAAE,cAAc,CAAC,eAAe,EAAE,QAAQ,CAAC,WAAW,GAAG,QAAQ,CAAC;IAsB/F,MAAM,IAAI,aAAa,CAAC,kBAAkB,CAAC;IAI3C,SAAS,IAAI,IAAI;CAiEpB"}
1
+ {"version":3,"file":"MIDILearning.d.ts","sourceRoot":"","sources":["../../src/midi/MIDILearning.ts"],"names":[],"mappings":"AAAA,OAAO,EAYH,UAAU,EAGb,MAAM,kBAAkB,CAAA;AACzB,OAAO,EAAC,OAAO,EAAE,KAAK,EAAE,cAAc,EAAE,eAAe,EAAC,MAAM,kBAAkB,CAAA;AAEhF,OAAO,EAAC,QAAQ,EAAC,MAAM,uBAAuB,CAAA;AAC9C,OAAO,EAAC,OAAO,EAAC,MAAM,YAAY,CAAA;AAelC,qBAAa,YAAa,YAAW,UAAU;;gBAS/B,OAAO,EAAE,OAAO;IAK5B,UAAU,CAAC,KAAK,EAAE,KAAK,CAAC,QAAQ,CAAC,eAAe,CAAC,GAAG,IAAI;IAkBxD,iBAAiB,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO;IAI5C,oBAAoB,CAAC,OAAO,EAAE,OAAO;IAK/B,iBAAiB,CAAC,KAAK,EAAE,cAAc,CAAC,eAAe,EAAE,QAAQ,CAAC,WAAW,GAAG,QAAQ,CAAC;IAuC/F,SAAS,IAAI,IAAI;CA8DpB"}
@@ -1,107 +1,140 @@
1
- import { Errors, isDefined, isNotNull, RuntimeNotifier, Terminator } from "@opendaw/lib-std";
1
+ import { asDefined, asInstanceOf, EmptyExec, Errors, isNotNull, Option, RuntimeNotifier, Terminator, UUID } from "@opendaw/lib-std";
2
2
  import { Address } from "@opendaw/lib-box";
3
3
  import { MidiData } from "@opendaw/lib-midi";
4
4
  import { MidiDevices } from "./MidiDevices";
5
5
  import { AnimationFrame } from "@opendaw/lib-dom";
6
+ import { MIDIControllerBox } from "@opendaw/studio-boxes";
7
+ // will be part of MIDI preferences. Should not filter for the device-id
8
+ // because it is different for the same hardware in different browsers.
9
+ const respectDeviceID = false;
6
10
  export class MIDILearning {
7
11
  #terminator = new Terminator();
8
12
  #project;
9
13
  #connections;
14
+ #optMIDIContollers = Option.None;
15
+ #optFieldSubscription = Option.None;
10
16
  constructor(project) {
11
17
  this.#project = project;
12
- this.#connections = Address.newSet(connection => connection.address);
18
+ this.#connections = Address.newSet(connection => connection.box.address);
19
+ }
20
+ followUser(field) {
21
+ this.#killAllConnections();
22
+ this.#optFieldSubscription.ifSome(subscription => subscription.terminate());
23
+ this.#optFieldSubscription = Option.None;
24
+ this.#optMIDIContollers = Option.wrap(field);
25
+ this.#optFieldSubscription = Option.wrap(field.pointerHub.catchupAndSubscribe({
26
+ onAdded: ({ box: anyBox }) => {
27
+ if (MidiDevices.get().isEmpty() && MidiDevices.canRequestMidiAccess()) {
28
+ MidiDevices.requestPermission().then(EmptyExec, EmptyExec);
29
+ }
30
+ const box = asInstanceOf(anyBox, MIDIControllerBox);
31
+ const { subscription, handleEvent } = this.#registerMIDIControllerBox(box);
32
+ this.#connections.add({ box, subscription, handleEvent });
33
+ },
34
+ onRemoved: ({ box: { address } }) => this.#connections.removeByKey(address).subscription.terminate()
35
+ }));
36
+ }
37
+ hasMidiConnection(address) {
38
+ return this.#findConnectionByParameterAddress(address).nonEmpty();
39
+ }
40
+ forgetMidiConnection(address) {
41
+ const connection = this.#findConnectionByParameterAddress(address).unwrap("No connection to forget");
42
+ this.#project.editing.modify(() => asDefined(connection).box.delete());
13
43
  }
14
- hasMidiConnection(address) { return this.#connections.hasKey(address); }
15
- forgetMidiConnection(address) { this.#connections.removeByKey(address).terminate(); }
16
44
  async learnMIDIControls(field) {
45
+ if (this.#optMIDIContollers.isEmpty()) {
46
+ return RuntimeNotifier.info({
47
+ headline: "Learn Midi Controller...",
48
+ message: "No user accepting midi controls."
49
+ });
50
+ }
17
51
  if (!MidiDevices.canRequestMidiAccess()) {
18
52
  return;
19
53
  }
20
54
  await MidiDevices.requestPermission();
21
55
  const learnLifecycle = this.#terminator.spawn();
22
56
  const abortController = new AbortController();
57
+ // TODO subscribeMessageEvents is not really suffient, because it does not distinguish between midi devices.
23
58
  learnLifecycle.own(MidiDevices.subscribeMessageEvents((event) => {
24
59
  const data = event.data;
25
60
  if (data === null) {
26
61
  return;
27
62
  }
63
+ const deviceId = event.target instanceof MIDIInput ? event.target.id : "";
28
64
  if (MidiData.isController(data)) {
29
65
  learnLifecycle.terminate();
30
66
  abortController.abort(Errors.AbortError);
31
- return this.#startListeningControl(field, MidiData.readChannel(data), MidiData.readParam1(data), event);
67
+ const midiControllersField = this.#optMIDIContollers.unwrap();
68
+ const { editing } = this.#project;
69
+ const optBox = editing.modify(() => MIDIControllerBox.create(this.#project.boxGraph, UUID.generate(), box => {
70
+ box.controllers.refer(midiControllersField);
71
+ box.parameter.refer(field);
72
+ box.deviceId.setValue(deviceId);
73
+ box.deviceChannel.setValue(MidiData.readChannel(data));
74
+ box.controlId.setValue(MidiData.readParam1(data));
75
+ }));
76
+ this.#connections.get(optBox.unwrap("Could not create MIDIControllerBox").address).handleEvent(event);
32
77
  }
33
78
  }));
34
79
  return RuntimeNotifier.info({
35
- headline: "Learn Midi Keys...",
36
- message: "Hit a key on your midi-device to learn a connection.",
80
+ headline: "Learn Midi Controller...",
81
+ message: "Turn a controller on your midi-device...",
37
82
  okText: "Cancel",
38
83
  abortSignal: abortController.signal
39
84
  }).then(() => learnLifecycle.terminate(), Errors.CatchAbort);
40
85
  }
41
- toJSON() {
42
- return this.#connections.values().map(connection => connection.toJSON());
43
- }
44
86
  terminate() {
45
87
  this.#killAllConnections();
46
88
  this.#terminator.terminate();
47
89
  }
48
- #startListeningControl(field, channel, controlId, event) {
49
- console.debug(`startListeningControl channel: ${channel}, controlId: ${controlId}`);
50
- const { observer, terminate } = this.#createMidiControlObserver(this.#project, this.#project.parameterFieldAdapters.get(field.address), controlId);
51
- if (isDefined(event)) {
52
- observer(event);
53
- }
54
- const subscription = MidiDevices.subscribeMessageEvents(observer, channel);
55
- this.#connections.add({
56
- address: field.address,
57
- toJSON: () => ({
58
- type: "control",
59
- address: field.address.toJSON(),
60
- channel,
61
- controlId
62
- }),
63
- label: () => this.#project.parameterFieldAdapters.get(field.address).name,
64
- terminate: () => {
65
- terminate();
66
- subscription.terminate();
67
- }
68
- });
69
- }
70
- #killAllConnections() {
71
- this.#connections.forEach(({ terminate }) => terminate());
72
- this.#connections.clear();
73
- }
74
- #createMidiControlObserver(project, adapter, controlId) {
90
+ #registerMIDIControllerBox({ parameter: { targetAddress }, controlId, deviceId, deviceChannel }) {
91
+ const address = targetAddress.unwrap("No parameter address");
92
+ const adapter = this.#project.parameterFieldAdapters.get(address);
93
+ const { editing } = this.#project;
75
94
  const registration = adapter.registerMidiControl();
76
95
  let pendingValue = null;
77
- const update = (value) => project.editing.modify(() => adapter.setValue(adapter.valueMapping.y(value)), false);
78
- return {
79
- observer: (event) => {
80
- const data = event.data;
81
- if (data === null) {
82
- return;
96
+ const update = (value) => editing.modify(() => adapter.setUnitValue(value), false);
97
+ const handleEvent = (event) => {
98
+ const data = event.data;
99
+ if (data === null) {
100
+ return;
101
+ }
102
+ const id = event.target instanceof MIDIInput ? event.target.id : "";
103
+ if (MidiData.isController(data)
104
+ && MidiData.readParam1(data) === controlId.getValue() && (!respectDeviceID || id === deviceId.getValue())) {
105
+ const value = MidiData.asValue(data);
106
+ if (pendingValue === null) {
107
+ update(value);
108
+ pendingValue = value;
109
+ AnimationFrame.once(() => {
110
+ if (isNotNull(pendingValue)) {
111
+ update(pendingValue);
112
+ pendingValue = null;
113
+ }
114
+ });
83
115
  }
84
- if (MidiData.isController(data) && MidiData.readParam1(data) === controlId) {
85
- const value = MidiData.asValue(data);
86
- if (pendingValue === null) {
87
- update(value);
88
- pendingValue = value;
89
- AnimationFrame.once(() => {
90
- if (isNotNull(pendingValue)) {
91
- update(pendingValue);
92
- pendingValue = null;
93
- }
94
- });
95
- }
96
- else {
97
- pendingValue = value;
98
- }
116
+ else {
117
+ pendingValue = value;
99
118
  }
100
- },
101
- terminate: () => {
102
- pendingValue = null;
103
- registration.terminate();
104
119
  }
105
120
  };
121
+ const channel = deviceChannel.getValue() === -1 ? undefined : deviceChannel.getValue();
122
+ const subscription = MidiDevices.subscribeMessageEvents(handleEvent, channel);
123
+ return {
124
+ subscription: {
125
+ terminate: () => {
126
+ pendingValue = null;
127
+ subscription.terminate();
128
+ registration.terminate();
129
+ }
130
+ }, handleEvent
131
+ };
132
+ }
133
+ #killAllConnections() {
134
+ this.#connections.forEach(({ subscription }) => subscription.terminate());
135
+ this.#connections.clear();
136
+ }
137
+ #findConnectionByParameterAddress(address) {
138
+ return Option.wrap(this.#connections.values().find(({ box }) => box.parameter.targetAddress.unwrap() === address));
106
139
  }
107
140
  }