@opendaw/studio-core 0.0.24 → 0.0.26

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 (55) hide show
  1. package/dist/AudioDevices.js +4 -4
  2. package/dist/AudioOfflineRenderer.d.ts +8 -0
  3. package/dist/AudioOfflineRenderer.d.ts.map +1 -0
  4. package/dist/AudioOfflineRenderer.js +88 -0
  5. package/dist/AudioWorklets.d.ts +1 -1
  6. package/dist/AudioWorklets.d.ts.map +1 -1
  7. package/dist/AudioWorklets.js +3 -2
  8. package/dist/FilePickerAcceptTypes.d.ts +8 -0
  9. package/dist/FilePickerAcceptTypes.d.ts.map +1 -0
  10. package/dist/FilePickerAcceptTypes.js +27 -0
  11. package/dist/WorkerAgents.d.ts +1 -1
  12. package/dist/WorkerAgents.d.ts.map +1 -1
  13. package/dist/WorkerAgents.js +3 -3
  14. package/dist/capture/CaptureAudio.d.ts.map +1 -1
  15. package/dist/capture/CaptureAudio.js +9 -4
  16. package/dist/capture/CaptureMidi.d.ts.map +1 -1
  17. package/dist/capture/CaptureMidi.js +5 -4
  18. package/dist/capture/Recording.d.ts.map +1 -1
  19. package/dist/capture/Recording.js +3 -3
  20. package/dist/index.d.ts +15 -8
  21. package/dist/index.d.ts.map +1 -1
  22. package/dist/index.js +15 -8
  23. package/dist/midi/MIDILearning.d.ts +26 -0
  24. package/dist/midi/MIDILearning.d.ts.map +1 -0
  25. package/dist/midi/MIDILearning.js +88 -0
  26. package/dist/midi/MIDIMessageSubscriber.d.ts +5 -0
  27. package/dist/midi/MIDIMessageSubscriber.d.ts.map +1 -0
  28. package/dist/midi/MIDIMessageSubscriber.js +40 -0
  29. package/dist/{MidiDevices.d.ts → midi/MidiDevices.d.ts} +2 -1
  30. package/dist/midi/MidiDevices.d.ts.map +1 -0
  31. package/dist/{MidiDevices.js → midi/MidiDevices.js} +16 -4
  32. package/dist/processors.js +3 -3
  33. package/dist/processors.js.map +4 -4
  34. package/dist/project/Project.d.ts +8 -1
  35. package/dist/project/Project.d.ts.map +1 -1
  36. package/dist/project/Project.js +14 -3
  37. package/dist/project/ProjectBundle.d.ts +1 -1
  38. package/dist/project/ProjectBundle.d.ts.map +1 -1
  39. package/dist/project/ProjectBundle.js +9 -11
  40. package/dist/project/ProjectEnv.d.ts +0 -7
  41. package/dist/project/ProjectEnv.d.ts.map +1 -1
  42. package/dist/project/ProjectProfile.js +3 -3
  43. package/dist/samples/OpenSampleAPI.d.ts +14 -0
  44. package/dist/samples/OpenSampleAPI.d.ts.map +1 -0
  45. package/dist/samples/OpenSampleAPI.js +109 -0
  46. package/dist/samples/SampleAPI.d.ts +10 -0
  47. package/dist/samples/SampleAPI.d.ts.map +1 -0
  48. package/dist/samples/SampleAPI.js +1 -0
  49. package/dist/samples/SampleImporter.d.ts +11 -0
  50. package/dist/samples/SampleImporter.d.ts.map +1 -0
  51. package/dist/samples/SampleImporter.js +1 -0
  52. package/dist/workers.js +2 -2
  53. package/dist/workers.js.map +4 -4
  54. package/package.json +14 -14
  55. package/dist/MidiDevices.d.ts.map +0 -1
@@ -1,11 +1,11 @@
1
1
  import { Promises } from "@opendaw/lib-runtime";
2
- import { Arrays, isInstanceOf, warn } from "@opendaw/lib-std";
2
+ import { Arrays, Errors } from "@opendaw/lib-std";
3
3
  import { ConstrainDOM } from "@opendaw/lib-dom";
4
4
  export class AudioDevices {
5
5
  static async requestPermission() {
6
6
  const { status, value: stream } = await Promises.tryCatch(navigator.mediaDevices.getUserMedia({ audio: true }));
7
7
  if (status === "rejected") {
8
- return warn("Could not request permission.");
8
+ return Errors.warn("Could not request permission.");
9
9
  }
10
10
  stream.getTracks().forEach(track => track.stop());
11
11
  await this.updateInputList();
@@ -13,7 +13,7 @@ export class AudioDevices {
13
13
  static async requestStream(constraints) {
14
14
  const { status, value: stream, error } = await Promises.tryCatch(navigator.mediaDevices.getUserMedia({ audio: constraints }));
15
15
  if (status === "rejected") {
16
- return warn(isInstanceOf(error, OverconstrainedError) ?
16
+ return Errors.warn(Errors.isOverconstrained(error) ?
17
17
  error.constraint === "deviceId"
18
18
  ? `Could not find device with id: '${ConstrainDOM.resolveString(constraints.deviceId)}'`
19
19
  : error.constraint
@@ -26,7 +26,7 @@ export class AudioDevices {
26
26
  this.#inputs = Arrays.empty();
27
27
  const { status, value: devices } = await Promises.tryCatch(navigator.mediaDevices.enumerateDevices());
28
28
  if (status === "rejected") {
29
- return warn("Could not enumerate devices.");
29
+ return Errors.warn("Could not enumerate devices.");
30
30
  }
31
31
  this.#inputs = devices.filter(device => device.kind === "audioinput" && device.deviceId !== "" && device.groupId !== "");
32
32
  }
@@ -0,0 +1,8 @@
1
+ import { int, Option } from "@opendaw/lib-std";
2
+ import { ExportStemsConfiguration } from "@opendaw/studio-adapters";
3
+ import { Project } from "./project/Project";
4
+ import { ProjectMeta } from "./project/ProjectMeta";
5
+ export declare namespace AudioOfflineRenderer {
6
+ const start: (source: Project, meta: ProjectMeta, optExportConfiguration: Option<ExportStemsConfiguration>, sampleRate?: int) => Promise<void>;
7
+ }
8
+ //# sourceMappingURL=AudioOfflineRenderer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AudioOfflineRenderer.d.ts","sourceRoot":"","sources":["../src/AudioOfflineRenderer.ts"],"names":[],"mappings":"AAAA,OAAO,EAAiC,GAAG,EAAE,MAAM,EAAmC,MAAM,kBAAkB,CAAA;AAI9G,OAAO,EAAC,wBAAwB,EAAC,MAAM,0BAA0B,CAAA;AACjE,OAAO,EAAC,OAAO,EAAC,MAAM,mBAAmB,CAAA;AACzC,OAAO,EAAC,WAAW,EAAC,MAAM,uBAAuB,CAAA;AAIjD,yBAAiB,oBAAoB,CAAC;IAC3B,MAAM,KAAK,GAAU,QAAQ,OAAO,EACf,MAAM,WAAW,EACjB,wBAAwB,MAAM,CAAC,wBAAwB,CAAC,EACxD,aAAY,GAAY,KAAG,OAAO,CAAC,IAAI,CA4BlE,CAAA;CA6CJ"}
@@ -0,0 +1,88 @@
1
+ import { DefaultObservableValue, Errors, panic, RuntimeNotifier, TimeSpan } from "@opendaw/lib-std";
2
+ import { PPQN } from "@opendaw/lib-dsp";
3
+ import { AnimationFrame, Files } from "@opendaw/lib-dom";
4
+ import { Promises, Wait } from "@opendaw/lib-runtime";
5
+ import { ExportStemsConfiguration } from "@opendaw/studio-adapters";
6
+ import { encodeWavFloat } from "./Wav";
7
+ import { AudioWorklets } from "./AudioWorklets";
8
+ export var AudioOfflineRenderer;
9
+ (function (AudioOfflineRenderer) {
10
+ AudioOfflineRenderer.start = async (source, meta, optExportConfiguration, sampleRate = 48_000) => {
11
+ const project = source.copy();
12
+ const numStems = ExportStemsConfiguration.countStems(optExportConfiguration);
13
+ const progress = new DefaultObservableValue(0.0);
14
+ const dialog = RuntimeNotifier.progress({ headline: "Rendering...", progress });
15
+ project.boxGraph.beginTransaction();
16
+ project.timelineBox.loopArea.enabled.setValue(false);
17
+ project.boxGraph.endTransaction();
18
+ const durationInPulses = project.timelineBox.durationInPulses.getValue();
19
+ const numSamples = PPQN.pulsesToSamples(durationInPulses, project.bpm, sampleRate);
20
+ const context = new OfflineAudioContext(numStems * 2, numSamples, sampleRate);
21
+ const durationInSeconds = numSamples / sampleRate;
22
+ const worklets = await AudioWorklets.install(context);
23
+ const engineWorklet = worklets.createEngine(project, optExportConfiguration.unwrapOrUndefined());
24
+ engineWorklet.play();
25
+ engineWorklet.connect(context.destination);
26
+ await engineWorklet.isReady();
27
+ while (!await engineWorklet.queryLoadingComplete()) {
28
+ await Wait.timeSpan(TimeSpan.seconds(1));
29
+ }
30
+ const terminable = AnimationFrame.add(() => progress.setValue(context.currentTime / durationInSeconds));
31
+ const buffer = await context.startRendering();
32
+ terminable.terminate();
33
+ dialog.terminate();
34
+ project.terminate();
35
+ if (optExportConfiguration.isEmpty()) {
36
+ await saveWavFile(buffer, meta);
37
+ }
38
+ else {
39
+ await saveZipFile(buffer, meta, Object.values(optExportConfiguration.unwrap()).map(({ fileName }) => fileName));
40
+ }
41
+ };
42
+ const saveWavFile = async (buffer, meta) => {
43
+ const approved = await RuntimeNotifier.approve({
44
+ headline: "Save Wav-File",
45
+ message: "",
46
+ approveText: "Save"
47
+ });
48
+ if (!approved) {
49
+ return;
50
+ }
51
+ const wavFile = encodeWavFloat(buffer);
52
+ const suggestedName = `${meta.name}.wav`;
53
+ const saveResult = await Promises.tryCatch(Files.save(wavFile, { suggestedName }));
54
+ if (saveResult.status === "rejected" && !Errors.isAbort(saveResult.error)) {
55
+ panic(String(saveResult.error));
56
+ }
57
+ };
58
+ const saveZipFile = async (buffer, meta, trackNames) => {
59
+ const { default: JSZip } = await import("jszip");
60
+ const dialog = RuntimeNotifier.progress({ headline: "Creating Zip File..." });
61
+ const numStems = buffer.numberOfChannels >> 1;
62
+ const zip = new JSZip();
63
+ for (let stemIndex = 0; stemIndex < numStems; stemIndex++) {
64
+ const l = buffer.getChannelData(stemIndex * 2);
65
+ const r = buffer.getChannelData(stemIndex * 2 + 1);
66
+ const file = encodeWavFloat({ channels: [l, r], sampleRate: buffer.sampleRate, numFrames: buffer.length });
67
+ zip.file(`${trackNames[stemIndex]}.wav`, file, { binary: true });
68
+ }
69
+ const arrayBuffer = await zip.generateAsync({
70
+ type: "arraybuffer",
71
+ compression: "DEFLATE",
72
+ compressionOptions: { level: 6 }
73
+ });
74
+ dialog.terminate();
75
+ const approved = await RuntimeNotifier.approve({
76
+ headline: "Save Zip",
77
+ message: `Size: ${arrayBuffer.byteLength >> 20}M`,
78
+ approveText: "Save"
79
+ });
80
+ if (!approved) {
81
+ return;
82
+ }
83
+ const saveResult = await Promises.tryCatch(Files.save(arrayBuffer, { suggestedName: `${meta.name}.zip` }));
84
+ if (saveResult.status === "rejected") {
85
+ panic(String(saveResult.error));
86
+ }
87
+ };
88
+ })(AudioOfflineRenderer || (AudioOfflineRenderer = {}));
@@ -6,7 +6,7 @@ import { MeterWorklet } from "./MeterWorklet";
6
6
  import { RecordingWorklet } from "./RecordingWorklet";
7
7
  export declare class AudioWorklets {
8
8
  #private;
9
- static install(context: BaseAudioContext, workletURL: string): Promise<AudioWorklets>;
9
+ static install(context: BaseAudioContext): Promise<AudioWorklets>;
10
10
  static get(context: BaseAudioContext): AudioWorklets;
11
11
  constructor(context: BaseAudioContext);
12
12
  get context(): BaseAudioContext;
@@ -1 +1 @@
1
- {"version":3,"file":"AudioWorklets.d.ts","sourceRoot":"","sources":["../src/AudioWorklets.ts"],"names":[],"mappings":"AAAA,OAAO,EAAY,GAAG,EAAC,MAAM,kBAAkB,CAAA;AAC/C,OAAO,EAAC,wBAAwB,EAAa,MAAM,0BAA0B,CAAA;AAC7E,OAAO,EAAC,OAAO,EAAC,MAAM,mBAAmB,CAAA;AACzC,OAAO,EAAC,aAAa,EAAC,MAAM,iBAAiB,CAAA;AAC7C,OAAO,EAAC,YAAY,EAAC,MAAM,gBAAgB,CAAA;AAC3C,OAAO,EAAC,gBAAgB,EAAC,MAAM,oBAAoB,CAAA;AAGnD,qBAAa,aAAa;;WACT,OAAO,CAAC,OAAO,EAAE,gBAAgB,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC;IAQ3F,MAAM,CAAC,GAAG,CAAC,OAAO,EAAE,gBAAgB,GAAG,aAAa;gBAMxC,OAAO,EAAE,gBAAgB;IAErC,IAAI,OAAO,IAAI,gBAAgB,CAAuB;IAEtD,WAAW,CAAC,gBAAgB,EAAE,GAAG,GAAG,YAAY;IAIhD,YAAY,CAAC,OAAO,EAAE,OAAO,EAAE,mBAAmB,CAAC,EAAE,wBAAwB,GAAG,aAAa;IAI7F,eAAe,CAAC,gBAAgB,EAAE,GAAG,EAAE,SAAS,EAAE,GAAG,EAAE,aAAa,EAAE,MAAM,GAAG,gBAAgB;CAOlG"}
1
+ {"version":3,"file":"AudioWorklets.d.ts","sourceRoot":"","sources":["../src/AudioWorklets.ts"],"names":[],"mappings":"AAAA,OAAO,EAAY,GAAG,EAAC,MAAM,kBAAkB,CAAA;AAC/C,OAAO,EAAC,wBAAwB,EAAa,MAAM,0BAA0B,CAAA;AAC7E,OAAO,EAAC,OAAO,EAAC,MAAM,mBAAmB,CAAA;AACzC,OAAO,EAAC,aAAa,EAAC,MAAM,iBAAiB,CAAA;AAC7C,OAAO,EAAC,YAAY,EAAC,MAAM,gBAAgB,CAAA;AAC3C,OAAO,EAAC,gBAAgB,EAAC,MAAM,oBAAoB,CAAA;AAKnD,qBAAa,aAAa;;WACT,OAAO,CAAC,OAAO,EAAE,gBAAgB,GAAG,OAAO,CAAC,aAAa,CAAC;IAQvE,MAAM,CAAC,GAAG,CAAC,OAAO,EAAE,gBAAgB,GAAG,aAAa;gBAMxC,OAAO,EAAE,gBAAgB;IAErC,IAAI,OAAO,IAAI,gBAAgB,CAAuB;IAEtD,WAAW,CAAC,gBAAgB,EAAE,GAAG,GAAG,YAAY;IAIhD,YAAY,CAAC,OAAO,EAAE,OAAO,EAAE,mBAAmB,CAAC,EAAE,wBAAwB,GAAG,aAAa;IAI7F,eAAe,CAAC,gBAAgB,EAAE,GAAG,EAAE,SAAS,EAAE,GAAG,EAAE,aAAa,EAAE,MAAM,GAAG,gBAAgB;CAOlG"}
@@ -3,9 +3,10 @@ import { EngineWorklet } from "./EngineWorklet";
3
3
  import { MeterWorklet } from "./MeterWorklet";
4
4
  import { RecordingWorklet } from "./RecordingWorklet";
5
5
  import { RenderQuantum } from "./RenderQuantum";
6
+ const WorkletsUrl = new URL("./processors.js", import.meta.url);
6
7
  export class AudioWorklets {
7
- static async install(context, workletURL) {
8
- return context.audioWorklet.addModule(workletURL).then(() => {
8
+ static async install(context) {
9
+ return context.audioWorklet.addModule(WorkletsUrl).then(() => {
9
10
  const worklets = new AudioWorklets(context);
10
11
  this.#map.set(context, worklets);
11
12
  return worklets;
@@ -0,0 +1,8 @@
1
+ export declare namespace FilePickerAcceptTypes {
2
+ const WavFiles: FilePickerOptions;
3
+ const ProjectSyncLog: FilePickerOptions;
4
+ const ProjectFileType: FilePickerAcceptType;
5
+ const ProjectBundleFileType: FilePickerAcceptType;
6
+ const DawprojectFileType: FilePickerAcceptType;
7
+ }
8
+ //# sourceMappingURL=FilePickerAcceptTypes.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"FilePickerAcceptTypes.d.ts","sourceRoot":"","sources":["../src/FilePickerAcceptTypes.ts"],"names":[],"mappings":"AAAA,yBAAiB,qBAAqB,CAAC;IAC5B,MAAM,QAAQ,EAAE,iBAKtB,CAAA;IACM,MAAM,cAAc,EAAE,iBAK5B,CAAA;IAEM,MAAM,eAAe,EAAE,oBAG7B,CAAA;IAEM,MAAM,qBAAqB,EAAE,oBAGnC,CAAA;IAEM,MAAM,kBAAkB,EAAE,oBAGhC,CAAA;CACJ"}
@@ -0,0 +1,27 @@
1
+ export var FilePickerAcceptTypes;
2
+ (function (FilePickerAcceptTypes) {
3
+ FilePickerAcceptTypes.WavFiles = {
4
+ types: [{
5
+ description: "wav-file",
6
+ accept: { "audio/wav": [".wav"] }
7
+ }]
8
+ };
9
+ FilePickerAcceptTypes.ProjectSyncLog = {
10
+ types: [{
11
+ description: "openDAW sync-log-file",
12
+ accept: { "application/octet-stream": [".odsl"] }
13
+ }]
14
+ };
15
+ FilePickerAcceptTypes.ProjectFileType = {
16
+ description: "openDAW project",
17
+ accept: { "application/octet-stream": [".od"] }
18
+ };
19
+ FilePickerAcceptTypes.ProjectBundleFileType = {
20
+ description: "openDAW project bundle",
21
+ accept: { "application/octet-stream": [".odb"] }
22
+ };
23
+ FilePickerAcceptTypes.DawprojectFileType = {
24
+ description: "dawproject",
25
+ accept: { "application/octet-stream": [".dawproject"] }
26
+ };
27
+ })(FilePickerAcceptTypes || (FilePickerAcceptTypes = {}));
@@ -2,7 +2,7 @@ import { Option } from "@opendaw/lib-std";
2
2
  import type { OpfsProtocol, SamplePeakProtocol } from "@opendaw/lib-fusion";
3
3
  import { Messenger } from "@opendaw/lib-runtime";
4
4
  export declare class WorkerAgents {
5
- static install(workerURL: string): void;
5
+ static install(): void;
6
6
  static messenger: Option<Messenger>;
7
7
  static get Peak(): SamplePeakProtocol;
8
8
  static get Opfs(): OpfsProtocol;
@@ -1 +1 @@
1
- {"version":3,"file":"WorkerAgents.d.ts","sourceRoot":"","sources":["../src/WorkerAgents.ts"],"names":[],"mappings":"AAAA,OAAO,EAAwB,MAAM,EAAY,MAAM,kBAAkB,CAAA;AACzE,OAAO,KAAK,EAAC,YAAY,EAAE,kBAAkB,EAAC,MAAM,qBAAqB,CAAA;AAEzE,OAAO,EAAe,SAAS,EAAC,MAAM,sBAAsB,CAAA;AAE5D,qBAAa,YAAY;IACrB,MAAM,CAAC,OAAO,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI;IAKvC,MAAM,CAAC,SAAS,EAAE,MAAM,CAAC,SAAS,CAAC,CAAc;IAGjD,MAAM,KAAK,IAAI,IAAI,kBAAkB,CAapC;IAGD,MAAM,KAAK,IAAI,IAAI,YAAY,CAS9B;CACJ"}
1
+ {"version":3,"file":"WorkerAgents.d.ts","sourceRoot":"","sources":["../src/WorkerAgents.ts"],"names":[],"mappings":"AAAA,OAAO,EAAwB,MAAM,EAAY,MAAM,kBAAkB,CAAA;AACzE,OAAO,KAAK,EAAC,YAAY,EAAE,kBAAkB,EAAC,MAAM,qBAAqB,CAAA;AAEzE,OAAO,EAAe,SAAS,EAAC,MAAM,sBAAsB,CAAA;AAI5D,qBAAa,YAAY;IACrB,MAAM,CAAC,OAAO,IAAI,IAAI;IAItB,MAAM,CAAC,SAAS,EAAE,MAAM,CAAC,SAAS,CAAC,CAAc;IAGjD,MAAM,KAAK,IAAI,IAAI,kBAAkB,CAapC;IAGD,MAAM,KAAK,IAAI,IAAI,YAAY,CAS9B;CACJ"}
@@ -9,10 +9,10 @@ var __metadata = (this && this.__metadata) || function (k, v) {
9
9
  };
10
10
  import { Lazy, Option } from "@opendaw/lib-std";
11
11
  import { Communicator, Messenger } from "@opendaw/lib-runtime";
12
+ const WorkerUrl = new URL("./workers.js", import.meta.url);
12
13
  export class WorkerAgents {
13
- static install(workerURL) {
14
- console.debug("workerURL", workerURL);
15
- this.messenger = Option.wrap(Messenger.for(new Worker(workerURL, { type: "module" })));
14
+ static install() {
15
+ this.messenger = Option.wrap(Messenger.for(new Worker(WorkerUrl, { type: "module" })));
16
16
  }
17
17
  static messenger = Option.None;
18
18
  static get Peak() {
@@ -1 +1 @@
1
- {"version":3,"file":"CaptureAudio.d.ts","sourceRoot":"","sources":["../../src/capture/CaptureAudio.ts"],"names":[],"mappings":"AAAA,OAAO,EAKH,uBAAuB,EACvB,MAAM,EAEN,UAAU,EAEb,MAAM,kBAAkB,CAAA;AAEzB,OAAO,EAAC,YAAY,EAAE,eAAe,EAAC,MAAM,uBAAuB,CAAA;AACnE,OAAO,EAAC,OAAO,EAAC,MAAM,WAAW,CAAA;AACjC,OAAO,EAAC,cAAc,EAAC,MAAM,kBAAkB,CAAA;AAI/C,qBAAa,YAAa,SAAQ,OAAO,CAAC,eAAe,CAAC;;gBAQ1C,OAAO,EAAE,cAAc,EAAE,YAAY,EAAE,YAAY,EAAE,eAAe,EAAE,eAAe;IA4BjG,IAAI,MAAM,IAAI,MAAM,CAAsB;IAE1C,IAAI,MAAM,IAAI,uBAAuB,CAAC,WAAW,CAAC,CAAsB;IAExE,IAAI,cAAc,IAAI,MAAM,CAAC,MAAM,CAAC,CAEnC;IAED,IAAI,KAAK,IAAI,MAAM,CAAsE;IAEzF,IAAI,WAAW,IAAI,MAAM,CAAC,MAAM,CAAC,CAA+D;IAEhG,IAAI,gBAAgB,IAAI,MAAM,CAAC,gBAAgB,CAAC,CAE/C;IAEK,gBAAgB,IAAI,OAAO,CAAC,IAAI,CAAC;IAcvC,cAAc,IAAI,UAAU;CA+D/B"}
1
+ {"version":3,"file":"CaptureAudio.d.ts","sourceRoot":"","sources":["../../src/capture/CaptureAudio.ts"],"names":[],"mappings":"AAAA,OAAO,EAKH,uBAAuB,EACvB,MAAM,EAEN,UAAU,EACb,MAAM,kBAAkB,CAAA;AAEzB,OAAO,EAAC,YAAY,EAAE,eAAe,EAAC,MAAM,uBAAuB,CAAA;AACnE,OAAO,EAAC,OAAO,EAAC,MAAM,WAAW,CAAA;AACjC,OAAO,EAAC,cAAc,EAAC,MAAM,kBAAkB,CAAA;AAI/C,qBAAa,YAAa,SAAQ,OAAO,CAAC,eAAe,CAAC;;gBAQ1C,OAAO,EAAE,cAAc,EAAE,YAAY,EAAE,YAAY,EAAE,eAAe,EAAE,eAAe;IA4BjG,IAAI,MAAM,IAAI,MAAM,CAAsB;IAE1C,IAAI,MAAM,IAAI,uBAAuB,CAAC,WAAW,CAAC,CAAsB;IAExE,IAAI,cAAc,IAAI,MAAM,CAAC,MAAM,CAAC,CAEnC;IAED,IAAI,KAAK,IAAI,MAAM,CAAsE;IAEzF,IAAI,WAAW,IAAI,MAAM,CAAC,MAAM,CAAC,CAA+D;IAEhG,IAAI,gBAAgB,IAAI,MAAM,CAAC,gBAAgB,CAAC,CAE/C;IAEK,gBAAgB,IAAI,OAAO,CAAC,IAAI,CAAC;IAiBvC,cAAc,IAAI,UAAU;CA+D/B"}
@@ -1,4 +1,4 @@
1
- import { abort, isDefined, isUndefined, MutableObservableOption, Option, safeExecute, Terminable, warn } from "@opendaw/lib-std";
1
+ import { Errors, isDefined, isUndefined, MutableObservableOption, Option, RuntimeNotifier, Terminable } from "@opendaw/lib-std";
2
2
  import { Promises } from "@opendaw/lib-runtime";
3
3
  import { Capture } from "./Capture";
4
4
  import { RecordAudio } from "./RecordAudio";
@@ -43,9 +43,14 @@ export class CaptureAudio extends Capture {
43
43
  const { project } = this.manager;
44
44
  const { env: { audioContext } } = project;
45
45
  if (isUndefined(audioContext.outputLatency)) {
46
- const approved = await safeExecute(project.env.dialogs?.approve, "Warning", "Your browser does not support 'output latency'. This will cause timing issue while recording.", "Ignore", "Cancel");
46
+ const approved = RuntimeNotifier.approve({
47
+ headline: "Warning",
48
+ message: "Your browser does not support 'output latency'. This will cause timing issue while recording.",
49
+ approveText: "Ignore",
50
+ cancelText: "Cancel"
51
+ });
47
52
  if (!approved) {
48
- return abort("Recording cancelled");
53
+ return Promise.reject("Recording cancelled");
49
54
  }
50
55
  }
51
56
  return this.#streamGenerator();
@@ -105,7 +110,7 @@ export class CaptureAudio extends Capture {
105
110
  }
106
111
  else {
107
112
  stream.getAudioTracks().forEach(track => track.stop());
108
- return warn(`Could not find audio device with id: '${deviceId} in ${gotDeviceId}'`);
113
+ return Errors.warn(`Could not find audio device with id: '${deviceId} in ${gotDeviceId}'`);
109
114
  }
110
115
  });
111
116
  }
@@ -1 +1 @@
1
- {"version":3,"file":"CaptureMidi.d.ts","sourceRoot":"","sources":["../../src/capture/CaptureMidi.ts"],"names":[],"mappings":"AAAA,OAAO,EAOH,QAAQ,EACR,MAAM,EACN,YAAY,EACZ,UAAU,EAEb,MAAM,kBAAkB,CAAA;AAIzB,OAAO,EAAC,YAAY,EAAE,cAAc,EAAC,MAAM,uBAAuB,CAAA;AAClE,OAAO,EAAC,UAAU,EAAC,MAAM,0BAA0B,CAAA;AAEnD,OAAO,EAAC,OAAO,EAAC,MAAM,WAAW,CAAA;AACjC,OAAO,EAAC,cAAc,EAAC,MAAM,kBAAkB,CAAA;AAG/C,qBAAa,WAAY,SAAQ,OAAO,CAAC,cAAc,CAAC;;gBAOxC,OAAO,EAAE,cAAc,EAAE,YAAY,EAAE,YAAY,EAAE,cAAc,EAAE,cAAc;IA8B/F,MAAM,CAAC,MAAM,EAAE,UAAU,GAAG,IAAI;IAEhC,cAAc,CAAC,QAAQ,EAAE,QAAQ,CAAC,UAAU,CAAC,GAAG,YAAY;IAE5D,IAAI,KAAK,IAAI,MAAM,CAgBlB;IAED,IAAI,WAAW,IAAI,MAAM,CAAC,MAAM,CAAC,CAIhC;IAEK,gBAAgB,IAAI,OAAO,CAAC,IAAI,CAAC;IAuBvC,cAAc,IAAI,UAAU;CAgD/B"}
1
+ {"version":3,"file":"CaptureMidi.d.ts","sourceRoot":"","sources":["../../src/capture/CaptureMidi.ts"],"names":[],"mappings":"AAAA,OAAO,EAQH,QAAQ,EACR,MAAM,EACN,YAAY,EACZ,UAAU,EACb,MAAM,kBAAkB,CAAA;AAIzB,OAAO,EAAC,YAAY,EAAE,cAAc,EAAC,MAAM,uBAAuB,CAAA;AAClE,OAAO,EAAC,UAAU,EAAC,MAAM,0BAA0B,CAAA;AAEnD,OAAO,EAAC,OAAO,EAAC,MAAM,WAAW,CAAA;AACjC,OAAO,EAAC,cAAc,EAAC,MAAM,kBAAkB,CAAA;AAI/C,qBAAa,WAAY,SAAQ,OAAO,CAAC,cAAc,CAAC;;gBAOxC,OAAO,EAAE,cAAc,EAAE,YAAY,EAAE,YAAY,EAAE,cAAc,EAAE,cAAc;IA8B/F,MAAM,CAAC,MAAM,EAAE,UAAU,GAAG,IAAI;IAEhC,cAAc,CAAC,QAAQ,EAAE,QAAQ,CAAC,UAAU,CAAC,GAAG,YAAY;IAE5D,IAAI,KAAK,IAAI,MAAM,CAgBlB;IAED,IAAI,WAAW,IAAI,MAAM,CAAC,MAAM,CAAC,CAIhC;IAEK,gBAAgB,IAAI,OAAO,CAAC,IAAI,CAAC;IAuBvC,cAAc,IAAI,UAAU;CAgD/B"}
@@ -1,11 +1,12 @@
1
- import { assert, isDefined, isUndefined, Notifier, Option, Terminable, warn } from "@opendaw/lib-std";
1
+ import { assert, Errors, isDefined, isUndefined, Notifier, Option, Terminable } from "@opendaw/lib-std";
2
2
  import { Events } from "@opendaw/lib-dom";
3
3
  import { MidiData } from "@opendaw/lib-midi";
4
4
  import { Promises } from "@opendaw/lib-runtime";
5
5
  import { NoteSignal } from "@opendaw/studio-adapters";
6
- import { MidiDevices } from "../MidiDevices";
6
+ import { MidiDevices } from "../midi/MidiDevices";
7
7
  import { Capture } from "./Capture";
8
8
  import { RecordMidi } from "./RecordMidi";
9
+ var warn = Errors.warn;
9
10
  export class CaptureMidi extends Capture {
10
11
  #streamGenerator;
11
12
  #notifier = new Notifier();
@@ -66,12 +67,12 @@ export class CaptureMidi extends Capture {
66
67
  await MidiDevices.requestPermission();
67
68
  }
68
69
  else {
69
- return warn("MIDI not available");
70
+ return Errors.warn("MIDI not available");
70
71
  }
71
72
  }
72
73
  const optInputs = MidiDevices.inputs();
73
74
  if (optInputs.isEmpty()) {
74
- return warn("MIDI not available");
75
+ return Errors.warn("MIDI not available");
75
76
  }
76
77
  const inputs = optInputs.unwrap();
77
78
  if (inputs.length === 0) {
@@ -1 +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;AAK3F,OAAO,EAAC,OAAO,EAAC,MAAM,oBAAoB,CAAA;AAE1C,qBAAa,SAAS;;IAClB,MAAM,KAAK,WAAW,IAAI,OAAO,CAA2B;WAE/C,KAAK,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,UAAU,CAAC;IA8D3E,OAAO;CACV"}
1
+ {"version":3,"file":"Recording.d.ts","sourceRoot":"","sources":["../../src/capture/Recording.ts"],"names":[],"mappings":"AAAA,OAAO,EAAuC,UAAU,EAAa,MAAM,kBAAkB,CAAA;AAK7F,OAAO,EAAC,OAAO,EAAC,MAAM,oBAAoB,CAAA;AAE1C,qBAAa,SAAS;;IAClB,MAAM,KAAK,WAAW,IAAI,OAAO,CAA2B;WAE/C,KAAK,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,UAAU,CAAC;IA8D3E,OAAO;CACV"}
@@ -1,4 +1,4 @@
1
- import { asInstanceOf, assert, Option, Terminable, Terminator, warn } from "@opendaw/lib-std";
1
+ import { asInstanceOf, assert, Errors, Option, Terminable, Terminator } from "@opendaw/lib-std";
2
2
  import { Promises } from "@opendaw/lib-runtime";
3
3
  import { AudioUnitBox } from "@opendaw/studio-boxes";
4
4
  import { AudioUnitType } from "@opendaw/studio-enums";
@@ -17,12 +17,12 @@ export class Recording {
17
17
  const captures = captureDevices.filterArmed();
18
18
  if (captures.length === 0) {
19
19
  this.#isRecording = false;
20
- return warn("No track is armed for Recording");
20
+ return Errors.warn("No track is armed for Recording");
21
21
  }
22
22
  const { status, error } = await Promises.tryCatch(Promise.all(captures.map(capture => capture.prepareRecording())));
23
23
  if (status === "rejected") {
24
24
  this.#isRecording = false;
25
- return warn(String(error));
25
+ return Errors.warn(String(error));
26
26
  }
27
27
  terminator.ownAll(...captures.map(capture => capture.startRecording()));
28
28
  engine.startRecording(countIn);
package/dist/index.d.ts CHANGED
@@ -1,10 +1,3 @@
1
- export * from "./sync-log/Commit";
2
- export * from "./sync-log/SyncLogReader";
3
- export * from "./sync-log/SyncLogWriter";
4
- export * from "./samples/SampleStorage";
5
- export * from "./samples/MainThreadSampleLoader";
6
- export * from "./samples/MainThreadSampleManager";
7
- export * from "./samples/SampleProvider";
8
1
  export * from "./capture/Capture";
9
2
  export * from "./capture/CaptureAudio";
10
3
  export * from "./capture/CaptureMidi";
@@ -14,6 +7,8 @@ export * from "./dawproject/DawProject";
14
7
  export * from "./dawproject/DawProjectExporter";
15
8
  export * from "./dawproject/DawProjectImport";
16
9
  export * from "./dawproject/DawProjectImport";
10
+ export * from "./midi/MidiDevices";
11
+ export * from "./midi/MIDILearning";
17
12
  export * from "./project/Project";
18
13
  export * from "./project/ProjectApi";
19
14
  export * from "./project/ProjectBundle";
@@ -21,7 +16,18 @@ export * from "./project/ProjectEnv";
21
16
  export * from "./project/ProjectMeta";
22
17
  export * from "./project/ProjectPaths";
23
18
  export * from "./project/ProjectProfile";
19
+ export * from "./samples/SampleStorage";
20
+ export * from "./samples/MainThreadSampleLoader";
21
+ export * from "./samples/MainThreadSampleManager";
22
+ export * from "./samples/OpenSampleAPI";
23
+ export * from "./samples/SampleAPI";
24
+ export * from "./samples/SampleImporter";
25
+ export * from "./samples/SampleProvider";
26
+ export * from "./sync-log/Commit";
27
+ export * from "./sync-log/SyncLogReader";
28
+ export * from "./sync-log/SyncLogWriter";
24
29
  export * from "./AudioDevices";
30
+ export * from "./AudioOfflineRenderer";
25
31
  export * from "./AudioUnitOrdering";
26
32
  export * from "./ColorCodes";
27
33
  export * from "./Colors";
@@ -31,15 +37,16 @@ export * from "./EffectFactories";
31
37
  export * from "./Engine";
32
38
  export * from "./EngineFacade";
33
39
  export * from "./EngineWorklet";
40
+ export * from "./FilePickerAcceptTypes";
34
41
  export * from "./InstrumentBox";
35
42
  export * from "./InstrumentFactories";
36
43
  export * from "./InstrumentFactory";
37
44
  export * from "./InstrumentOptions";
38
45
  export * from "./InstrumentProduct";
39
46
  export * from "./MeterWorklet";
40
- export * from "./MidiDevices";
41
47
  export * from "./Mixer";
42
48
  export * from "./PeaksWriter";
49
+ export * from "./RenderQuantum";
43
50
  export * from "./Wav";
44
51
  export * from "./WorkerAgents";
45
52
  export * from "./AudioWorklets";
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,mBAAmB,CAAA;AACjC,cAAc,0BAA0B,CAAA;AACxC,cAAc,0BAA0B,CAAA;AAExC,cAAc,yBAAyB,CAAA;AACvC,cAAc,kCAAkC,CAAA;AAChD,cAAc,mCAAmC,CAAA;AACjD,cAAc,0BAA0B,CAAA;AAExC,cAAc,mBAAmB,CAAA;AACjC,cAAc,wBAAwB,CAAA;AACtC,cAAc,uBAAuB,CAAA;AACrC,cAAc,0BAA0B,CAAA;AACxC,cAAc,qBAAqB,CAAA;AAEnC,cAAc,yBAAyB,CAAA;AACvC,cAAc,iCAAiC,CAAA;AAC/C,cAAc,+BAA+B,CAAA;AAC7C,cAAc,+BAA+B,CAAA;AAE7C,cAAc,mBAAmB,CAAA;AACjC,cAAc,sBAAsB,CAAA;AACpC,cAAc,yBAAyB,CAAA;AACvC,cAAc,sBAAsB,CAAA;AACpC,cAAc,uBAAuB,CAAA;AACrC,cAAc,wBAAwB,CAAA;AACtC,cAAc,0BAA0B,CAAA;AAExC,cAAc,gBAAgB,CAAA;AAC9B,cAAc,qBAAqB,CAAA;AACnC,cAAc,cAAc,CAAA;AAC5B,cAAc,UAAU,CAAA;AACxB,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,iBAAiB,CAAA;AAC/B,cAAc,uBAAuB,CAAA;AACrC,cAAc,qBAAqB,CAAA;AACnC,cAAc,qBAAqB,CAAA;AACnC,cAAc,qBAAqB,CAAA;AACnC,cAAc,gBAAgB,CAAA;AAC9B,cAAc,eAAe,CAAA;AAC7B,cAAc,SAAS,CAAA;AACvB,cAAc,eAAe,CAAA;AAC7B,cAAc,OAAO,CAAA;AACrB,cAAc,gBAAgB,CAAA;AAC9B,cAAc,iBAAiB,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,mBAAmB,CAAA;AACjC,cAAc,wBAAwB,CAAA;AACtC,cAAc,uBAAuB,CAAA;AACrC,cAAc,0BAA0B,CAAA;AACxC,cAAc,qBAAqB,CAAA;AAEnC,cAAc,yBAAyB,CAAA;AACvC,cAAc,iCAAiC,CAAA;AAC/C,cAAc,+BAA+B,CAAA;AAC7C,cAAc,+BAA+B,CAAA;AAE7C,cAAc,oBAAoB,CAAA;AAClC,cAAc,qBAAqB,CAAA;AAEnC,cAAc,mBAAmB,CAAA;AACjC,cAAc,sBAAsB,CAAA;AACpC,cAAc,yBAAyB,CAAA;AACvC,cAAc,sBAAsB,CAAA;AACpC,cAAc,uBAAuB,CAAA;AACrC,cAAc,wBAAwB,CAAA;AACtC,cAAc,0BAA0B,CAAA;AAExC,cAAc,yBAAyB,CAAA;AACvC,cAAc,kCAAkC,CAAA;AAChD,cAAc,mCAAmC,CAAA;AACjD,cAAc,yBAAyB,CAAA;AACvC,cAAc,qBAAqB,CAAA;AACnC,cAAc,0BAA0B,CAAA;AACxC,cAAc,0BAA0B,CAAA;AAExC,cAAc,mBAAmB,CAAA;AACjC,cAAc,0BAA0B,CAAA;AACxC,cAAc,0BAA0B,CAAA;AAExC,cAAc,gBAAgB,CAAA;AAC9B,cAAc,wBAAwB,CAAA;AACtC,cAAc,qBAAqB,CAAA;AACnC,cAAc,cAAc,CAAA;AAC5B,cAAc,UAAU,CAAA;AACxB,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,yBAAyB,CAAA;AACvC,cAAc,iBAAiB,CAAA;AAC/B,cAAc,uBAAuB,CAAA;AACrC,cAAc,qBAAqB,CAAA;AACnC,cAAc,qBAAqB,CAAA;AACnC,cAAc,qBAAqB,CAAA;AACnC,cAAc,gBAAgB,CAAA;AAC9B,cAAc,SAAS,CAAA;AACvB,cAAc,eAAe,CAAA;AAC7B,cAAc,iBAAiB,CAAA;AAC/B,cAAc,OAAO,CAAA;AACrB,cAAc,gBAAgB,CAAA;AAC9B,cAAc,iBAAiB,CAAA"}
package/dist/index.js CHANGED
@@ -1,10 +1,3 @@
1
- export * from "./sync-log/Commit";
2
- export * from "./sync-log/SyncLogReader";
3
- export * from "./sync-log/SyncLogWriter";
4
- export * from "./samples/SampleStorage";
5
- export * from "./samples/MainThreadSampleLoader";
6
- export * from "./samples/MainThreadSampleManager";
7
- export * from "./samples/SampleProvider";
8
1
  export * from "./capture/Capture";
9
2
  export * from "./capture/CaptureAudio";
10
3
  export * from "./capture/CaptureMidi";
@@ -14,6 +7,8 @@ export * from "./dawproject/DawProject";
14
7
  export * from "./dawproject/DawProjectExporter";
15
8
  export * from "./dawproject/DawProjectImport";
16
9
  export * from "./dawproject/DawProjectImport";
10
+ export * from "./midi/MidiDevices";
11
+ export * from "./midi/MIDILearning";
17
12
  export * from "./project/Project";
18
13
  export * from "./project/ProjectApi";
19
14
  export * from "./project/ProjectBundle";
@@ -21,7 +16,18 @@ export * from "./project/ProjectEnv";
21
16
  export * from "./project/ProjectMeta";
22
17
  export * from "./project/ProjectPaths";
23
18
  export * from "./project/ProjectProfile";
19
+ export * from "./samples/SampleStorage";
20
+ export * from "./samples/MainThreadSampleLoader";
21
+ export * from "./samples/MainThreadSampleManager";
22
+ export * from "./samples/OpenSampleAPI";
23
+ export * from "./samples/SampleAPI";
24
+ export * from "./samples/SampleImporter";
25
+ export * from "./samples/SampleProvider";
26
+ export * from "./sync-log/Commit";
27
+ export * from "./sync-log/SyncLogReader";
28
+ export * from "./sync-log/SyncLogWriter";
24
29
  export * from "./AudioDevices";
30
+ export * from "./AudioOfflineRenderer";
25
31
  export * from "./AudioUnitOrdering";
26
32
  export * from "./ColorCodes";
27
33
  export * from "./Colors";
@@ -31,15 +37,16 @@ export * from "./EffectFactories";
31
37
  export * from "./Engine";
32
38
  export * from "./EngineFacade";
33
39
  export * from "./EngineWorklet";
40
+ export * from "./FilePickerAcceptTypes";
34
41
  export * from "./InstrumentBox";
35
42
  export * from "./InstrumentFactories";
36
43
  export * from "./InstrumentFactory";
37
44
  export * from "./InstrumentOptions";
38
45
  export * from "./InstrumentProduct";
39
46
  export * from "./MeterWorklet";
40
- export * from "./MidiDevices";
41
47
  export * from "./Mixer";
42
48
  export * from "./PeaksWriter";
49
+ export * from "./RenderQuantum";
43
50
  export * from "./Wav";
44
51
  export * from "./WorkerAgents";
45
52
  export * from "./AudioWorklets";
@@ -0,0 +1,26 @@
1
+ import { byte, JSONValue, Provider, Terminable } from "@opendaw/lib-std";
2
+ import { Address, AddressJSON, PrimitiveField, PrimitiveValues } from "@opendaw/lib-box";
3
+ import { Pointers } from "@opendaw/studio-enums";
4
+ import { Project } from "../project/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
+ export declare class MIDILearning implements Terminable {
18
+ #private;
19
+ constructor(project: Project);
20
+ hasMidiConnection(address: Address): boolean;
21
+ forgetMidiConnection(address: Address): void;
22
+ learnMIDIControls(field: PrimitiveField<PrimitiveValues, Pointers.MidiControl | Pointers>): Promise<void>;
23
+ toJSON(): ReadonlyArray<MIDIConnectionJSON>;
24
+ terminate(): void;
25
+ }
26
+ //# sourceMappingURL=MIDILearning.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MIDILearning.d.ts","sourceRoot":"","sources":["../../src/midi/MIDILearning.ts"],"names":[],"mappings":"AAAA,OAAO,EACH,IAAI,EAGJ,SAAS,EAET,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,oBAAoB,CAAA;AAG1C,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;CAgDpB"}
@@ -0,0 +1,88 @@
1
+ import { Errors, isDefined, RuntimeNotifier, Terminator } from "@opendaw/lib-std";
2
+ import { Address } from "@opendaw/lib-box";
3
+ import { MidiData } from "@opendaw/lib-midi";
4
+ import { MidiDevices } from "./MidiDevices";
5
+ export class MIDILearning {
6
+ #terminator = new Terminator();
7
+ #project;
8
+ #connections;
9
+ constructor(project) {
10
+ this.#project = project;
11
+ this.#connections = Address.newSet(connection => connection.address);
12
+ }
13
+ hasMidiConnection(address) { return this.#connections.hasKey(address); }
14
+ forgetMidiConnection(address) { this.#connections.removeByKey(address).terminate(); }
15
+ async learnMIDIControls(field) {
16
+ if (!MidiDevices.canRequestMidiAccess()) {
17
+ return;
18
+ }
19
+ await MidiDevices.requestPermission();
20
+ const learnLifecycle = this.#terminator.spawn();
21
+ const abortController = new AbortController();
22
+ learnLifecycle.own(MidiDevices.subscribeMessageEvents((event) => {
23
+ const data = event.data;
24
+ if (data === null) {
25
+ return;
26
+ }
27
+ if (MidiData.isController(data)) {
28
+ learnLifecycle.terminate();
29
+ abortController.abort(Errors.AbortError);
30
+ return this.#startListeningControl(field, MidiData.readChannel(data), MidiData.readParam1(data), event);
31
+ }
32
+ }));
33
+ return RuntimeNotifier.info({
34
+ headline: "Learn Midi Keys...",
35
+ message: "Hit a key on your midi-device to learn a connection.",
36
+ okText: "Cancel",
37
+ abortSignal: abortController.signal
38
+ }).then(() => learnLifecycle.terminate(), Errors.CatchAbort);
39
+ }
40
+ toJSON() {
41
+ return this.#connections.values().map(connection => connection.toJSON());
42
+ }
43
+ terminate() {
44
+ this.#killAllConnections();
45
+ this.#terminator.terminate();
46
+ }
47
+ #startListeningControl(field, channel, controlId, event) {
48
+ console.debug(`startListeningControl channel: ${channel}, controlId: ${controlId}`);
49
+ const { observer, terminate } = this.#createMidiControlObserver(this.#project, this.#project.parameterFieldAdapters.get(field.address), controlId);
50
+ if (isDefined(event)) {
51
+ observer(event);
52
+ }
53
+ const subscription = MidiDevices.subscribeMessageEvents(observer, channel);
54
+ this.#connections.add({
55
+ address: field.address,
56
+ toJSON: () => ({
57
+ type: "control",
58
+ address: field.address.toJSON(),
59
+ channel,
60
+ controlId
61
+ }),
62
+ label: () => this.#project.parameterFieldAdapters.get(field.address).name,
63
+ terminate: () => {
64
+ terminate();
65
+ subscription.terminate();
66
+ }
67
+ });
68
+ }
69
+ #killAllConnections() {
70
+ this.#connections.forEach(({ terminate }) => terminate());
71
+ this.#connections.clear();
72
+ }
73
+ #createMidiControlObserver(project, adapter, controlId) {
74
+ const registration = adapter.registerMidiControl();
75
+ return {
76
+ observer: (event) => {
77
+ const data = event.data;
78
+ if (data === null) {
79
+ return;
80
+ }
81
+ if (MidiData.isController(data) && MidiData.readParam1(data) === controlId) {
82
+ project.editing.modify(() => adapter.setValue(adapter.valueMapping.y(MidiData.asValue(data))), false);
83
+ }
84
+ },
85
+ terminate: () => registration.terminate()
86
+ };
87
+ }
88
+ }
@@ -0,0 +1,5 @@
1
+ import { byte, Observer, Subscription } from "@opendaw/lib-std";
2
+ export declare class MIDIMessageSubscriber {
3
+ static subscribeMessageEvents(access: MIDIAccess, observer: Observer<MIDIMessageEvent>, channel?: byte): Subscription;
4
+ }
5
+ //# sourceMappingURL=MIDIMessageSubscriber.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MIDIMessageSubscriber.d.ts","sourceRoot":"","sources":["../../src/midi/MIDIMessageSubscriber.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,IAAI,EAAqC,QAAQ,EAAE,YAAY,EAAC,MAAM,kBAAkB,CAAA;AAIhG,qBAAa,qBAAqB;IAC9B,MAAM,CAAC,sBAAsB,CAAC,MAAM,EAAE,UAAU,EAAE,QAAQ,EAAE,QAAQ,CAAC,gBAAgB,CAAC,EAAE,OAAO,CAAC,EAAE,IAAI,GAAG,YAAY;CA+BxH"}
@@ -0,0 +1,40 @@
1
+ import { isDefined, isInstanceOf } from "@opendaw/lib-std";
2
+ import { Events } from "@opendaw/lib-dom";
3
+ import { MidiData } from "@opendaw/lib-midi";
4
+ export class MIDIMessageSubscriber {
5
+ static subscribeMessageEvents(access, observer, channel) {
6
+ const listen = (input) => isDefined(channel)
7
+ ? Events.subscribe(input, "midimessage", (event) => {
8
+ if (event.data === null || MidiData.readChannel(event.data) !== channel) {
9
+ return;
10
+ }
11
+ observer(event);
12
+ }) : Events.subscribe(input, "midimessage", observer);
13
+ const connections = Array.from(access.inputs.values())
14
+ .map(input => ([input, listen(input)]));
15
+ const stateSubscription = Events.subscribe(access, "statechange", (event) => {
16
+ const port = event.port;
17
+ if (!isInstanceOf(port, MIDIInput)) {
18
+ return;
19
+ }
20
+ for (const [input, subscription] of connections) {
21
+ if (input === port) {
22
+ // Well, this is strange, but if you start listening to a midi-input initially,
23
+ // it will change its state to 'connected', so we clean up the first old subscriptions.
24
+ subscription.terminate();
25
+ break;
26
+ }
27
+ }
28
+ if (port.state === "connected") {
29
+ connections.push([port, listen(port)]);
30
+ }
31
+ });
32
+ return {
33
+ terminate: () => {
34
+ stateSubscription.terminate();
35
+ connections.forEach(([_, subscription]) => subscription.terminate());
36
+ connections.length = 0;
37
+ }
38
+ };
39
+ }
40
+ }