@opendaw/studio-core 0.0.20 → 0.0.21

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 (42) hide show
  1. package/dist/AudioDevices.d.ts +8 -0
  2. package/dist/AudioDevices.d.ts.map +1 -0
  3. package/dist/AudioDevices.js +34 -0
  4. package/dist/MidiDevices.d.ts +12 -0
  5. package/dist/MidiDevices.d.ts.map +1 -0
  6. package/dist/MidiDevices.js +92 -0
  7. package/dist/Project.d.ts +10 -1
  8. package/dist/Project.d.ts.map +1 -1
  9. package/dist/Project.js +21 -1
  10. package/dist/ProjectEnv.d.ts +0 -1
  11. package/dist/ProjectEnv.d.ts.map +1 -1
  12. package/dist/capture/Capture.d.ts +1 -1
  13. package/dist/capture/Capture.d.ts.map +1 -1
  14. package/dist/capture/CaptureAudio.d.ts +5 -5
  15. package/dist/capture/CaptureAudio.d.ts.map +1 -1
  16. package/dist/capture/CaptureAudio.js +14 -14
  17. package/dist/capture/CaptureMidi.d.ts +2 -2
  18. package/dist/capture/CaptureMidi.d.ts.map +1 -1
  19. package/dist/capture/CaptureMidi.js +86 -30
  20. package/dist/capture/RecordAudio.d.ts +1 -3
  21. package/dist/capture/RecordAudio.d.ts.map +1 -1
  22. package/dist/capture/RecordAudio.js +2 -2
  23. package/dist/capture/RecordMidi.d.ts +1 -3
  24. package/dist/capture/RecordMidi.d.ts.map +1 -1
  25. package/dist/capture/RecordMidi.js +4 -4
  26. package/dist/capture/Recording.js +2 -2
  27. package/dist/capture/RecordingContext.d.ts +0 -4
  28. package/dist/capture/RecordingContext.d.ts.map +1 -1
  29. package/dist/dawproject/DawProject.d.ts.map +1 -1
  30. package/dist/dawproject/DawProject.js +4 -5
  31. package/dist/dawproject/DawProjectExporter.test.js +1 -2
  32. package/dist/index.d.ts +3 -1
  33. package/dist/index.d.ts.map +1 -1
  34. package/dist/index.js +3 -1
  35. package/dist/processors.js +2 -2
  36. package/dist/processors.js.map +4 -4
  37. package/dist/workers.js +2 -2
  38. package/dist/workers.js.map +4 -4
  39. package/package.json +14 -14
  40. package/dist/AudioInputDevices.d.ts +0 -8
  41. package/dist/AudioInputDevices.d.ts.map +0 -1
  42. package/dist/AudioInputDevices.js +0 -30
@@ -0,0 +1,8 @@
1
+ export declare class AudioDevices {
2
+ #private;
3
+ static requestPermission(): Promise<undefined>;
4
+ static requestStream(constraints: MediaTrackConstraints): Promise<MediaStream>;
5
+ static updateInputList(): Promise<undefined>;
6
+ static get inputs(): ReadonlyArray<MediaDeviceInfo>;
7
+ }
8
+ //# sourceMappingURL=AudioDevices.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AudioDevices.d.ts","sourceRoot":"","sources":["../src/AudioDevices.ts"],"names":[],"mappings":"AAIA,qBAAa,YAAY;;WACR,iBAAiB;WAQjB,aAAa,CAAC,WAAW,EAAE,qBAAqB,GAAG,OAAO,CAAC,WAAW,CAAC;WAavE,eAAe;IAY5B,MAAM,KAAK,MAAM,IAAI,aAAa,CAAC,eAAe,CAAC,CAAsB;CAC5E"}
@@ -0,0 +1,34 @@
1
+ import { Promises } from "@opendaw/lib-runtime";
2
+ import { Arrays, isInstanceOf, warn } from "@opendaw/lib-std";
3
+ import { ConstrainDOM } from "@opendaw/lib-dom";
4
+ export class AudioDevices {
5
+ static async requestPermission() {
6
+ const { status, value: stream } = await Promises.tryCatch(navigator.mediaDevices.getUserMedia({ audio: true }));
7
+ if (status === "rejected") {
8
+ return warn("Could not request permission.");
9
+ }
10
+ stream.getTracks().forEach(track => track.stop());
11
+ await this.updateInputList();
12
+ }
13
+ static async requestStream(constraints) {
14
+ const { status, value: stream, error } = await Promises.tryCatch(navigator.mediaDevices.getUserMedia({ audio: constraints }));
15
+ if (status === "rejected") {
16
+ return warn(isInstanceOf(error, OverconstrainedError) ?
17
+ error.constraint === "deviceId"
18
+ ? `Could not find device with id: '${ConstrainDOM.resolveString(constraints.deviceId)}'`
19
+ : error.constraint
20
+ : String(error));
21
+ }
22
+ return stream;
23
+ }
24
+ static async updateInputList() {
25
+ this.#inputs = Arrays.empty();
26
+ const { status, value: devices } = await Promises.tryCatch(navigator.mediaDevices.enumerateDevices());
27
+ if (status === "rejected") {
28
+ return warn("Could not enumerate devices.");
29
+ }
30
+ this.#inputs = devices.filter(device => device.kind === "audioinput" && device.deviceId !== "" && device.groupId !== "");
31
+ }
32
+ static #inputs = Arrays.empty();
33
+ static get inputs() { return this.#inputs; }
34
+ }
@@ -0,0 +1,12 @@
1
+ import { MutableObservableValue, ObservableOption, Option } from "@opendaw/lib-std";
2
+ export declare class MidiDevices {
3
+ #private;
4
+ static canRequestMidiAccess(): boolean;
5
+ static requestPermission(): Promise<undefined>;
6
+ static get(): ObservableOption<MIDIAccess>;
7
+ static inputs(): Option<ReadonlyArray<MIDIInput>>;
8
+ static outputs(): Option<ReadonlyArray<MIDIOutput>>;
9
+ static panic(): void;
10
+ static available(): MutableObservableValue<boolean>;
11
+ }
12
+ //# sourceMappingURL=MidiDevices.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MidiDevices.d.ts","sourceRoot":"","sources":["../src/MidiDevices.ts"],"names":[],"mappings":"AAAA,OAAO,EAGH,sBAAsB,EAEtB,gBAAgB,EAGhB,MAAM,EAGT,MAAM,kBAAkB,CAAA;AAIzB,qBAAa,WAAW;;IACpB,MAAM,CAAC,oBAAoB,IAAI,OAAO;WAEzB,iBAAiB;IAiB9B,MAAM,CAAC,GAAG,IAAI,gBAAgB,CAAC,UAAU,CAAC;IAE1C,MAAM,CAAC,MAAM,IAAI,MAAM,CAAC,aAAa,CAAC,SAAS,CAAC,CAAC;IAIjD,MAAM,CAAC,OAAO,IAAI,MAAM,CAAC,aAAa,CAAC,UAAU,CAAC,CAAC;IAInD,MAAM,CAAC,KAAK,IAAI,IAAI;IAkBpB,MAAM,CAAC,SAAS,IAAI,sBAAsB,CAAC,OAAO,CAAC;CAqCtD"}
@@ -0,0 +1,92 @@
1
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
2
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
3
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
4
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
6
+ };
7
+ var __metadata = (this && this.__metadata) || function (k, v) {
8
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
9
+ };
10
+ import { Lazy, MutableObservableOption, MutableObservableValue, Notifier, warn } from "@opendaw/lib-std";
11
+ import { MidiData } from "@opendaw/lib-midi";
12
+ import { Promises } from "@opendaw/lib-runtime";
13
+ export class MidiDevices {
14
+ static canRequestMidiAccess() { return "requestMIDIAccess" in navigator; }
15
+ static async requestPermission() {
16
+ if (this.canRequestMidiAccess()) {
17
+ const { status, value: midiAccess, error } = await Promises.tryCatch(navigator.requestMIDIAccess({ sysex: false }));
18
+ if (status === "rejected") {
19
+ console.warn(error);
20
+ return warn("Could not request MIDI");
21
+ }
22
+ const numberOfInputs = midiAccess.inputs.size;
23
+ const numberOfOutputs = midiAccess.outputs.size;
24
+ console.debug(`MIDI access granted: ${numberOfInputs} inputs, ${numberOfOutputs} outputs`);
25
+ this.#midiAccess.wrap(midiAccess);
26
+ }
27
+ else {
28
+ return warn("This browser does not support MIDI");
29
+ }
30
+ }
31
+ static get() { return this.#midiAccess; }
32
+ static inputs() {
33
+ return this.get().map(({ inputs }) => Array.from(inputs.values()));
34
+ }
35
+ static outputs() {
36
+ return this.get().map(({ outputs }) => Array.from(outputs.values()));
37
+ }
38
+ static panic() {
39
+ this.get().ifSome((midiAccess) => {
40
+ for (let note = 0; note < 128; note++) {
41
+ for (let channel = 0; channel < 16; channel++) {
42
+ const data = MidiData.noteOff(channel, note);
43
+ const event = new MessageEvent("midimessage", { data });
44
+ for (let input of midiAccess.inputs.values()) {
45
+ input.dispatchEvent(event);
46
+ }
47
+ for (let output of midiAccess.outputs.values()) {
48
+ output.send(data);
49
+ }
50
+ }
51
+ }
52
+ });
53
+ }
54
+ static available() {
55
+ const scope = this;
56
+ return new class {
57
+ #notifier = new Notifier();
58
+ constructor() {
59
+ const subscription = scope.get().subscribe(option => {
60
+ if (option.nonEmpty()) {
61
+ subscription.terminate();
62
+ this.#notifier.notify(this);
63
+ } // MIDIAccess cannot be turned off
64
+ });
65
+ }
66
+ setValue(value) {
67
+ if (!value || scope.#midiAccess.nonEmpty() || scope.#isRequesting) {
68
+ return;
69
+ }
70
+ console.debug("Request MIDI access");
71
+ scope.#isRequesting = true;
72
+ scope.requestPermission().finally(() => scope.#isRequesting = false);
73
+ }
74
+ getValue() { return scope.#midiAccess.nonEmpty(); }
75
+ catchupAndSubscribe(observer) {
76
+ observer(this);
77
+ return this.#notifier.subscribe(observer);
78
+ }
79
+ subscribe(observer) {
80
+ return this.#notifier.subscribe(observer);
81
+ }
82
+ };
83
+ }
84
+ static #isRequesting = false;
85
+ static #midiAccess = new MutableObservableOption();
86
+ }
87
+ __decorate([
88
+ Lazy,
89
+ __metadata("design:type", Function),
90
+ __metadata("design:paramtypes", []),
91
+ __metadata("design:returntype", Object)
92
+ ], MidiDevices, "available", null);
package/dist/Project.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { Terminable, TerminableOwner, Terminator } from "@opendaw/lib-std";
1
+ import { Procedure, Terminable, TerminableOwner, Terminator } from "@opendaw/lib-std";
2
2
  import { BoxGraph, Editing } from "@opendaw/lib-box";
3
3
  import { AudioBusBox, AudioUnitBox, BoxIO, RootBox, TimelineBox, UserInterfaceBox } from "@opendaw/studio-boxes";
4
4
  import { BoxAdapters, BoxAdaptersContext, ClipSequencing, ParameterFieldAdapters, ProjectDecoder, RootBoxAdapter, SampleManager, TimelineBoxAdapter, UserEditingManager, VertexSelection } from "@opendaw/studio-adapters";
@@ -7,6 +7,13 @@ import { ProjectEnv } from "./ProjectEnv";
7
7
  import { Mixer } from "./Mixer";
8
8
  import { ProjectApi } from "./ProjectApi";
9
9
  import { CaptureManager } from "./capture/CaptureManager";
10
+ import { EngineFacade } from "./EngineFacade";
11
+ import { EngineWorklet } from "./EngineWorklet";
12
+ import { Worklets } from "./Worklets";
13
+ export type RestartWorklet = {
14
+ unload: Procedure<unknown>;
15
+ load: Procedure<EngineWorklet>;
16
+ };
10
17
  export declare class Project implements BoxAdaptersContext, Terminable, TerminableOwner {
11
18
  #private;
12
19
  static new(env: ProjectEnv): Project;
@@ -27,7 +34,9 @@ export declare class Project implements BoxAdaptersContext, Terminable, Terminab
27
34
  readonly parameterFieldAdapters: ParameterFieldAdapters;
28
35
  readonly liveStreamReceiver: LiveStreamReceiver;
29
36
  readonly mixer: Mixer;
37
+ readonly engine: EngineFacade;
30
38
  private constructor();
39
+ startAudioWorklet(worklets: Worklets, restart: RestartWorklet): EngineWorklet;
31
40
  own<T extends Terminable>(terminable: T): T;
32
41
  ownAll<T extends Terminable>(...terminables: Array<T>): void;
33
42
  spawn(): Terminator;
@@ -1 +1 @@
1
- {"version":3,"file":"Project.d.ts","sourceRoot":"","sources":["../src/Project.ts"],"names":[],"mappings":"AAAA,OAAO,EAAiC,UAAU,EAAE,eAAe,EAAE,UAAU,EAAO,MAAM,kBAAkB,CAAA;AAC9G,OAAO,EAAC,QAAQ,EAAE,OAAO,EAAC,MAAM,kBAAkB,CAAA;AAClD,OAAO,EACH,WAAW,EACX,YAAY,EACZ,KAAK,EAEL,OAAO,EACP,WAAW,EACX,gBAAgB,EACnB,MAAM,uBAAuB,CAAA;AAC9B,OAAO,EACH,WAAW,EACX,kBAAkB,EAClB,cAAc,EAGd,sBAAsB,EACtB,cAAc,EACd,cAAc,EACd,aAAa,EACb,kBAAkB,EAClB,kBAAkB,EAClB,eAAe,EAClB,MAAM,0BAA0B,CAAA;AACjC,OAAO,EAAC,qBAAqB,EAAE,kBAAkB,EAAC,MAAM,qBAAqB,CAAA;AAE7E,OAAO,EAAC,UAAU,EAAC,MAAM,cAAc,CAAA;AACvC,OAAO,EAAC,KAAK,EAAC,MAAM,SAAS,CAAA;AAC7B,OAAO,EAAC,UAAU,EAAC,MAAM,cAAc,CAAA;AAEvC,OAAO,EAAC,cAAc,EAAC,MAAM,0BAA0B,CAAA;AAIvD,qBAAa,OAAQ,YAAW,kBAAkB,EAAE,UAAU,EAAE,eAAe;;IAC3E,MAAM,CAAC,GAAG,CAAC,GAAG,EAAE,UAAU,GAAG,OAAO;IAuCpC,MAAM,CAAC,IAAI,CAAC,GAAG,EAAE,UAAU,EAAE,WAAW,EAAE,WAAW,GAAG,OAAO;IAM/D,MAAM,CAAC,QAAQ,CAAC,GAAG,EAAE,UAAU,EAAE,QAAQ,EAAE,cAAc,CAAC,QAAQ,GAAG,OAAO;IAQ5E,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAC,KAAK,CAAC,OAAO,CAAC,CAAA;IAE1C,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAA;IACzB,QAAQ,CAAC,gBAAgB,EAAE,gBAAgB,CAAA;IAC3C,QAAQ,CAAC,YAAY,EAAE,WAAW,CAAA;IAClC,QAAQ,CAAC,eAAe,EAAE,YAAY,CAAA;IACtC,QAAQ,CAAC,WAAW,EAAE,WAAW,CAAA;IAEjC,QAAQ,CAAC,GAAG,EAAE,UAAU,CAAA;IACxB,QAAQ,CAAC,cAAc,EAAE,cAAc,CAAA;IACvC,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAA;IACzB,QAAQ,CAAC,SAAS,EAAE,eAAe,CAAA;IACnC,QAAQ,CAAC,WAAW,EAAE,WAAW,CAAA;IACjC,QAAQ,CAAC,kBAAkB,EAAE,kBAAkB,CAAA;IAC/C,QAAQ,CAAC,sBAAsB,EAAE,sBAAsB,CAAA;IACvD,QAAQ,CAAC,kBAAkB,EAAE,kBAAkB,CAAA;IAC/C,QAAQ,CAAC,KAAK,EAAE,KAAK,CAAA;IAErB,OAAO;IA8BP,GAAG,CAAC,CAAC,SAAS,UAAU,EAAE,UAAU,EAAE,CAAC,GAAG,CAAC;IAC3C,MAAM,CAAC,CAAC,SAAS,UAAU,EAAE,GAAG,WAAW,EAAE,KAAK,CAAC,CAAC,CAAC,GAAG,IAAI;IAC5D,KAAK,IAAI,UAAU;IAEnB,IAAI,GAAG,IAAI,UAAU,CAAmB;IACxC,IAAI,GAAG,IAAI,MAAM,CAAyC;IAC1D,IAAI,cAAc,IAAI,cAAc,CAAmE;IACvG,IAAI,kBAAkB,IAAI,kBAAkB,CAA2E;IACvH,IAAI,aAAa,IAAI,aAAa,CAAiC;IACnE,IAAI,cAAc,IAAI,cAAc,CAAkD;IACtF,IAAI,cAAc,IAAI,OAAO,CAAe;IAC5C,IAAI,YAAY,IAAI,OAAO,CAAc;IACzC,IAAI,qBAAqB,IAAI,qBAAqB,CAAkD;IAEpG,IAAI,QAAQ,IAAI,cAAc,CAAC,QAAQ,CAWtC;IAED,aAAa,IAAI,eAAe;IAiBhC,IAAI,IAAI,OAAO;IAEf,SAAS,IAAI,IAAI;CACpB"}
1
+ {"version":3,"file":"Project.d.ts","sourceRoot":"","sources":["../src/Project.ts"],"names":[],"mappings":"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"}
package/dist/Project.js CHANGED
@@ -8,8 +8,8 @@ import { Mixer } from "./Mixer";
8
8
  import { ProjectApi } from "./ProjectApi";
9
9
  import { ProjectMigration } from "./ProjectMigration";
10
10
  import { CaptureManager } from "./capture/CaptureManager";
11
+ import { EngineFacade } from "./EngineFacade";
11
12
  // Main Entry Point for a Project
12
- //
13
13
  export class Project {
14
14
  static new(env) {
15
15
  const boxGraph = new BoxGraph(Option.wrap(BoxIO.create));
@@ -75,6 +75,7 @@ export class Project {
75
75
  parameterFieldAdapters;
76
76
  liveStreamReceiver;
77
77
  mixer;
78
+ engine = new EngineFacade();
78
79
  constructor(env, boxGraph, { rootBox, userInterfaceBox, masterBusBox, masterAudioUnit, timelineBox }) {
79
80
  this.#env = env;
80
81
  this.boxGraph = boxGraph;
@@ -96,6 +97,25 @@ export class Project {
96
97
  this.mixer = new Mixer(this.rootBoxAdapter.audioUnits);
97
98
  console.debug(`Project was created on ${this.rootBoxAdapter.created.toString()}`);
98
99
  }
100
+ startAudioWorklet(worklets, restart) {
101
+ console.debug(`start AudioWorklet`);
102
+ const lifecycle = this.#terminator.spawn();
103
+ const engine = lifecycle.own(worklets.createEngine(this));
104
+ const handler = async (event) => {
105
+ console.warn(event);
106
+ // we will only accept the first error
107
+ engine.removeEventListener("error", handler);
108
+ engine.removeEventListener("processorerror", handler);
109
+ restart.unload(event);
110
+ lifecycle.terminate();
111
+ restart.load(this.startAudioWorklet(worklets, restart));
112
+ };
113
+ engine.addEventListener("error", handler);
114
+ engine.addEventListener("processorerror", handler);
115
+ engine.connect(engine.context.destination);
116
+ this.engine.setClient(engine);
117
+ return engine;
118
+ }
99
119
  own(terminable) { return this.#terminator.own(terminable); }
100
120
  ownAll(...terminables) { return this.#terminator.ownAll(...terminables); }
101
121
  spawn() { return this.#terminator.spawn(); }
@@ -1,6 +1,5 @@
1
1
  import { SampleManager } from "@opendaw/studio-adapters";
2
2
  export interface ProjectEnv {
3
- sampleRate: number;
4
3
  sampleManager: SampleManager;
5
4
  }
6
5
  //# sourceMappingURL=ProjectEnv.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"ProjectEnv.d.ts","sourceRoot":"","sources":["../src/ProjectEnv.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,aAAa,EAAC,MAAM,0BAA0B,CAAA;AAEtD,MAAM,WAAW,UAAU;IACvB,UAAU,EAAE,MAAM,CAAA;IAClB,aAAa,EAAE,aAAa,CAAA;CAC/B"}
1
+ {"version":3,"file":"ProjectEnv.d.ts","sourceRoot":"","sources":["../src/ProjectEnv.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,aAAa,EAAC,MAAM,0BAA0B,CAAA;AAEtD,MAAM,WAAW,UAAU;IACvB,aAAa,EAAE,aAAa,CAAA;CAC/B"}
@@ -6,9 +6,9 @@ import { CaptureManager } from "./CaptureManager";
6
6
  export declare abstract class Capture<BOX extends CaptureBox = CaptureBox> implements Terminable {
7
7
  #private;
8
8
  protected constructor(manager: CaptureManager, audioUnitBox: AudioUnitBox, captureBox: BOX);
9
+ abstract get deviceLabel(): Option<string>;
9
10
  abstract prepareRecording(context: RecordingContext): Promise<void>;
10
11
  abstract startRecording(context: RecordingContext): Terminable;
11
- abstract get deviceLabel(): Option<string>;
12
12
  get uuid(): UUID.Format;
13
13
  get manager(): CaptureManager;
14
14
  get audioUnitBox(): AudioUnitBox;
@@ -1 +1 @@
1
- {"version":3,"file":"Capture.d.ts","sourceRoot":"","sources":["../../src/capture/Capture.ts"],"names":[],"mappings":"AAAA,OAAO,EAGH,sBAAsB,EACtB,MAAM,EACN,UAAU,EAEV,IAAI,EACP,MAAM,kBAAkB,CAAA;AACzB,OAAO,EAAC,YAAY,EAAC,MAAM,uBAAuB,CAAA;AAClD,OAAO,EAAC,UAAU,EAAC,MAAM,0BAA0B,CAAA;AAEnD,OAAO,EAAC,gBAAgB,EAAC,MAAM,oBAAoB,CAAA;AACnD,OAAO,EAAC,cAAc,EAAC,MAAM,kBAAkB,CAAA;AAE/C,8BAAsB,OAAO,CAAC,GAAG,SAAS,UAAU,GAAG,UAAU,CAAE,YAAW,UAAU;;IAUpF,SAAS,aAAa,OAAO,EAAE,cAAc,EAAE,YAAY,EAAE,YAAY,EAAE,UAAU,EAAE,GAAG;IAmB1F,QAAQ,CAAC,gBAAgB,CAAC,OAAO,EAAE,gBAAgB,GAAG,OAAO,CAAC,IAAI,CAAC;IACnE,QAAQ,CAAC,cAAc,CAAC,OAAO,EAAE,gBAAgB,GAAG,UAAU;IAC9D,QAAQ,KAAK,WAAW,IAAI,MAAM,CAAC,MAAM,CAAC,CAAA;IAE1C,IAAI,IAAI,IAAI,IAAI,CAAC,MAAM,CAAyC;IAChE,IAAI,OAAO,IAAI,cAAc,CAAuB;IACpD,IAAI,YAAY,IAAI,YAAY,CAA4B;IAC5D,IAAI,UAAU,IAAI,GAAG,CAA0B;IAC/C,IAAI,KAAK,IAAI,sBAAsB,CAAC,OAAO,CAAC,CAAqB;IACjE,IAAI,QAAQ,IAAI,sBAAsB,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAwB;IAE9E,GAAG,CAAC,CAAC,SAAS,UAAU,EAAE,UAAU,EAAE,CAAC,GAAG,CAAC;IAC3C,MAAM,CAAC,CAAC,SAAS,UAAU,EAAE,GAAG,WAAW,EAAE,aAAa,CAAC,CAAC,CAAC,GAAG,IAAI;IACpE,SAAS,IAAI,IAAI;CACpB"}
1
+ {"version":3,"file":"Capture.d.ts","sourceRoot":"","sources":["../../src/capture/Capture.ts"],"names":[],"mappings":"AAAA,OAAO,EAGH,sBAAsB,EACtB,MAAM,EACN,UAAU,EAEV,IAAI,EACP,MAAM,kBAAkB,CAAA;AACzB,OAAO,EAAC,YAAY,EAAC,MAAM,uBAAuB,CAAA;AAClD,OAAO,EAAC,UAAU,EAAC,MAAM,0BAA0B,CAAA;AAEnD,OAAO,EAAC,gBAAgB,EAAC,MAAM,oBAAoB,CAAA;AACnD,OAAO,EAAC,cAAc,EAAC,MAAM,kBAAkB,CAAA;AAE/C,8BAAsB,OAAO,CAAC,GAAG,SAAS,UAAU,GAAG,UAAU,CAAE,YAAW,UAAU;;IAUpF,SAAS,aAAa,OAAO,EAAE,cAAc,EAAE,YAAY,EAAE,YAAY,EAAE,UAAU,EAAE,GAAG;IAmB1F,QAAQ,KAAK,WAAW,IAAI,MAAM,CAAC,MAAM,CAAC,CAAA;IAC1C,QAAQ,CAAC,gBAAgB,CAAC,OAAO,EAAE,gBAAgB,GAAG,OAAO,CAAC,IAAI,CAAC;IACnE,QAAQ,CAAC,cAAc,CAAC,OAAO,EAAE,gBAAgB,GAAG,UAAU;IAE9D,IAAI,IAAI,IAAI,IAAI,CAAC,MAAM,CAAyC;IAChE,IAAI,OAAO,IAAI,cAAc,CAAuB;IACpD,IAAI,YAAY,IAAI,YAAY,CAA4B;IAC5D,IAAI,UAAU,IAAI,GAAG,CAA0B;IAC/C,IAAI,KAAK,IAAI,sBAAsB,CAAC,OAAO,CAAC,CAAqB;IACjE,IAAI,QAAQ,IAAI,sBAAsB,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAwB;IAE9E,GAAG,CAAC,CAAC,SAAS,UAAU,EAAE,UAAU,EAAE,CAAC,GAAG,CAAC;IAC3C,MAAM,CAAC,CAAC,SAAS,UAAU,EAAE,GAAG,WAAW,EAAE,aAAa,CAAC,CAAC,CAAC,GAAG,IAAI;IACpE,SAAS,IAAI,IAAI;CACpB"}
@@ -1,17 +1,17 @@
1
- import { ObservableOption, Option, Terminable } from "@opendaw/lib-std";
1
+ import { MutableObservableOption, Option, Terminable } from "@opendaw/lib-std";
2
2
  import { AudioUnitBox, CaptureAudioBox } from "@opendaw/studio-boxes";
3
3
  import { Capture } from "./Capture";
4
- import { RecordingContext } from "./RecordingContext";
5
4
  import { CaptureManager } from "./CaptureManager";
5
+ import { RecordingContext } from "./RecordingContext";
6
6
  export declare class CaptureAudio extends Capture<CaptureAudioBox> {
7
7
  #private;
8
- constructor(manager: CaptureManager, audioUnitBox: AudioUnitBox, captureBox: CaptureAudioBox);
8
+ constructor(manager: CaptureManager, audioUnitBox: AudioUnitBox, captureAudioBox: CaptureAudioBox);
9
9
  get gainDb(): number;
10
- get stream(): ObservableOption<MediaStream>;
10
+ get stream(): MutableObservableOption<MediaStream>;
11
11
  get streamDeviceId(): Option<string>;
12
12
  get deviceLabel(): Option<string>;
13
13
  get streamMediaTrack(): Option<MediaStreamTrack>;
14
14
  prepareRecording({}: RecordingContext): Promise<void>;
15
- startRecording({ audioContext, worklets, project, engine, sampleManager }: RecordingContext): Terminable;
15
+ startRecording({ audioContext, worklets, project, sampleManager }: RecordingContext): Terminable;
16
16
  }
17
17
  //# sourceMappingURL=CaptureAudio.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"CaptureAudio.d.ts","sourceRoot":"","sources":["../../src/capture/CaptureAudio.ts"],"names":[],"mappings":"AAAA,OAAO,EAAuC,gBAAgB,EAAE,MAAM,EAAE,UAAU,EAAO,MAAM,kBAAkB,CAAA;AACjH,OAAO,EAAC,YAAY,EAAE,eAAe,EAAC,MAAM,uBAAuB,CAAA;AACnE,OAAO,EAAC,OAAO,EAAC,MAAM,WAAW,CAAA;AAEjC,OAAO,EAAC,gBAAgB,EAAC,MAAM,oBAAoB,CAAA;AACnD,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,UAAU,EAAE,eAAe;IA4B5F,IAAI,MAAM,IAAI,MAAM,CAAsB;IAE1C,IAAI,MAAM,IAAI,gBAAgB,CAAC,WAAW,CAAC,CAAsB;IAEjE,IAAI,cAAc,IAAI,MAAM,CAAC,MAAM,CAAC,CAEnC;IAED,IAAI,WAAW,IAAI,MAAM,CAAC,MAAM,CAAC,CAEhC;IAED,IAAI,gBAAgB,IAAI,MAAM,CAAC,gBAAgB,CAAC,CAE/C;IAEK,gBAAgB,CAAC,EAAE,EAAE,gBAAgB,GAAG,OAAO,CAAC,IAAI,CAAC;IAI3D,cAAc,CAAC,EAAC,YAAY,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,EAAE,aAAa,EAAC,EAAE,gBAAgB,GAAG,UAAU;CA6DzG"}
1
+ {"version":3,"file":"CaptureAudio.d.ts","sourceRoot":"","sources":["../../src/capture/CaptureAudio.ts"],"names":[],"mappings":"AAAA,OAAO,EAAuC,uBAAuB,EAAE,MAAM,EAAE,UAAU,EAAO,MAAM,kBAAkB,CAAA;AAExH,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;AAE/C,OAAO,EAAC,gBAAgB,EAAC,MAAM,oBAAoB,CAAA;AAGnD,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,WAAW,IAAI,MAAM,CAAC,MAAM,CAAC,CAEhC;IAED,IAAI,gBAAgB,IAAI,MAAM,CAAC,gBAAgB,CAAC,CAE/C;IAEK,gBAAgB,CAAC,EAAE,EAAE,gBAAgB,GAAG,OAAO,CAAC,IAAI,CAAC;IAI3D,cAAc,CAAC,EAAC,YAAY,EAAE,QAAQ,EAAE,OAAO,EAAE,aAAa,EAAC,EAAE,gBAAgB,GAAG,UAAU;CA6DjG"}
@@ -1,21 +1,21 @@
1
- import { assert, isDefined, isUndefined, ObservableOption, Option, warn } from "@opendaw/lib-std";
1
+ import { assert, isDefined, isUndefined, MutableObservableOption, Option, warn } from "@opendaw/lib-std";
2
+ import { Promises } from "@opendaw/lib-runtime";
2
3
  import { Capture } from "./Capture";
3
4
  import { RecordAudio } from "./RecordAudio";
4
- import { AudioInputDevices } from "../AudioInputDevices";
5
- import { Promises } from "@opendaw/lib-runtime";
5
+ import { AudioDevices } from "../AudioDevices";
6
6
  export class CaptureAudio extends Capture {
7
7
  #stream;
8
8
  #streamGenerator;
9
9
  #requestChannels = Option.None;
10
10
  #gainDb = 0.0;
11
- constructor(manager, audioUnitBox, captureBox) {
12
- super(manager, audioUnitBox, captureBox);
13
- this.#stream = new ObservableOption();
11
+ constructor(manager, audioUnitBox, captureAudioBox) {
12
+ super(manager, audioUnitBox, captureAudioBox);
13
+ this.#stream = new MutableObservableOption();
14
14
  this.#streamGenerator = Promises.sequential(() => this.#updateStream());
15
- this.ownAll(captureBox.requestChannels.catchupAndSubscribe(owner => {
15
+ this.ownAll(captureAudioBox.requestChannels.catchupAndSubscribe(owner => {
16
16
  const channels = owner.getValue();
17
17
  this.#requestChannels = channels === 1 || channels === 2 ? Option.wrap(channels) : Option.None;
18
- }), captureBox.gainDb.catchupAndSubscribe(owner => this.#gainDb = owner.getValue()), captureBox.deviceId.catchupAndSubscribe(async () => {
18
+ }), captureAudioBox.gainDb.catchupAndSubscribe(owner => this.#gainDb = owner.getValue()), captureAudioBox.deviceId.catchupAndSubscribe(async () => {
19
19
  if (this.armed.getValue()) {
20
20
  await this.#streamGenerator();
21
21
  }
@@ -43,18 +43,18 @@ export class CaptureAudio extends Capture {
43
43
  async prepareRecording({}) {
44
44
  return this.#streamGenerator();
45
45
  }
46
- startRecording({ audioContext, worklets, project, engine, sampleManager }) {
46
+ startRecording({ audioContext, worklets, project, sampleManager }) {
47
47
  const streamOption = this.#stream;
48
48
  assert(streamOption.nonEmpty(), "Stream not prepared.");
49
49
  const mediaStream = streamOption.unwrap();
50
50
  const channelCount = mediaStream.getAudioTracks().at(0)?.getSettings().channelCount ?? 1;
51
51
  const numChunks = 128;
52
+ const recordingWorklet = worklets.createRecording(channelCount, numChunks, audioContext.outputLatency);
52
53
  return RecordAudio.start({
53
- recordingWorklet: worklets.createRecording(channelCount, numChunks, audioContext.outputLatency),
54
+ recordingWorklet,
54
55
  mediaStream,
55
56
  sampleManager,
56
57
  audioContext,
57
- engine,
58
58
  project,
59
59
  capture: this,
60
60
  gainDb: this.#gainDb
@@ -78,14 +78,14 @@ export class CaptureAudio extends Capture {
78
78
  this.#stopStream();
79
79
  const deviceId = this.deviceId.getValue().unwrapOrUndefined();
80
80
  const channelCount = this.#requestChannels.unwrapOrElse(1); // as of today, browsers cap MediaStream audio to stereo.
81
- return AudioInputDevices.requestStream({
81
+ return AudioDevices.requestStream({
82
82
  deviceId: { exact: deviceId },
83
- sampleRate: this.manager.project.env.sampleRate,
83
+ sampleRate: this.manager.project.engine.sampleRate(),
84
84
  sampleSize: 32,
85
85
  echoCancellation: false,
86
86
  noiseSuppression: false,
87
87
  autoGainControl: false,
88
- channelCount: { exact: channelCount }
88
+ channelCount
89
89
  }).then(stream => {
90
90
  const tracks = stream.getAudioTracks();
91
91
  const settings = tracks.at(0)?.getSettings();
@@ -6,8 +6,8 @@ import { CaptureManager } from "./CaptureManager";
6
6
  export declare class CaptureMidi extends Capture<CaptureMidiBox> {
7
7
  #private;
8
8
  constructor(manager: CaptureManager, audioUnitBox: AudioUnitBox, captureMidiBox: CaptureMidiBox);
9
- prepareRecording({ requestMIDIAccess }: RecordingContext): Promise<void>;
10
- startRecording({ project, engine }: RecordingContext): Terminable;
11
9
  get deviceLabel(): Option<string>;
10
+ prepareRecording({}: RecordingContext): Promise<void>;
11
+ startRecording({ project }: RecordingContext): Terminable;
12
12
  }
13
13
  //# sourceMappingURL=CaptureMidi.d.ts.map
@@ -1 +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"}
1
+ {"version":3,"file":"CaptureMidi.d.ts","sourceRoot":"","sources":["../../src/capture/CaptureMidi.ts"],"names":[],"mappings":"AAAA,OAAO,EAQH,MAAM,EAEN,UAAU,EAEb,MAAM,kBAAkB,CAAA;AAGzB,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;AAI/C,qBAAa,WAAY,SAAQ,OAAO,CAAC,cAAc,CAAC;;gBAQxC,OAAO,EAAE,cAAc,EAAE,YAAY,EAAE,YAAY,EAAE,cAAc,EAAE,cAAc;IAsC/F,IAAI,WAAW,IAAI,MAAM,CAAC,MAAM,CAAC,CAA0C;IAErE,gBAAgB,CAAC,EAAE,EAAE,gBAAgB,GAAG,OAAO,CAAC,IAAI,CAAC;IAgB3D,cAAc,CAAC,EAAC,OAAO,EAAC,EAAE,gBAAgB,GAAG,UAAU;CA8C1D"}
@@ -1,50 +1,106 @@
1
- import { assert, isDefined, isUndefined, Notifier, Option, panic, Terminable } from "@opendaw/lib-std";
1
+ import { asDefined, assert, isDefined, isUndefined, Notifier, Option, Terminable, warn } from "@opendaw/lib-std";
2
2
  import { Events } from "@opendaw/lib-dom";
3
3
  import { MidiData } from "@opendaw/lib-midi";
4
4
  import { Capture } from "./Capture";
5
5
  import { RecordMidi } from "./RecordMidi";
6
+ import { MidiDevices } from "../MidiDevices";
7
+ import { Promises } from "@opendaw/lib-runtime";
6
8
  export class CaptureMidi extends Capture {
7
- #midiAccess = Option.None;
9
+ #streamGenerator;
10
+ #notifier = new Notifier();
8
11
  #filterChannel = Option.None;
12
+ #streaming = Option.None;
9
13
  constructor(manager, audioUnitBox, captureMidiBox) {
10
14
  super(manager, audioUnitBox, captureMidiBox);
11
- this.ownAll(captureMidiBox.channel.catchupAndSubscribe(owner => {
15
+ this.#streamGenerator = Promises.sequential(() => this.#updateStream());
16
+ this.ownAll(captureMidiBox.channel.subscribe(async (owner) => {
12
17
  const channel = owner.getValue();
13
18
  this.#filterChannel = channel >= 0 ? Option.wrap(channel) : Option.None;
19
+ await this.#streamGenerator();
20
+ }), captureMidiBox.deviceId.catchupAndSubscribe(async () => {
21
+ if (this.armed.getValue()) {
22
+ await this.#streamGenerator();
23
+ }
24
+ }), this.armed.catchupAndSubscribe(async (owner) => {
25
+ const armed = owner.getValue();
26
+ if (armed) {
27
+ await this.#streamGenerator();
28
+ }
29
+ else {
30
+ this.#stopStream();
31
+ }
32
+ }), this.#notifier.subscribe(event => {
33
+ console.debug(MidiData.debug(event.data));
34
+ const data = asDefined(event.data);
35
+ const engine = manager.project.engine;
36
+ const isNoteOn = MidiData.isNoteOn(data);
37
+ if (MidiData.isNoteOff(data) || (isNoteOn && MidiData.readVelocity(data) === 0)) {
38
+ engine.noteOff(this.uuid, MidiData.readPitch(data));
39
+ }
40
+ else if (isNoteOn) {
41
+ engine.noteOn(this.uuid, MidiData.readPitch(data), MidiData.readVelocity(data));
42
+ }
14
43
  }));
15
44
  }
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
- }
45
+ get deviceLabel() { return Option.wrap("MIDI coming soon."); }
46
+ async prepareRecording({}) {
47
+ const availableMidiDevices = MidiDevices.get();
48
+ if (availableMidiDevices.isEmpty()) {
49
+ return Promise.reject("MIDI is not available");
50
+ }
51
+ const option = this.deviceId.getValue();
52
+ if (option.nonEmpty()) {
53
+ const { inputs } = availableMidiDevices.unwrap();
54
+ const captureDevices = Array.from(inputs.values());
55
+ const deviceId = option.unwrap();
56
+ if (isUndefined(captureDevices.find(device => deviceId === device.id))) {
57
+ return warn(`Could not find MIDI device with id: '${deviceId}'`);
26
58
  }
27
- this.#midiAccess = Option.wrap(midiAccess);
28
- });
59
+ }
60
+ }
61
+ startRecording({ project }) {
62
+ const availableMidiDevices = MidiDevices.inputs();
63
+ assert(availableMidiDevices.nonEmpty(), "No MIDI input devices found");
64
+ return RecordMidi.start({ notifier: this.#notifier, project, capture: this });
29
65
  }
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) => {
66
+ async #updateStream() {
67
+ // TODO Check if the requirements have been changed (are different than the current stream setup)
68
+ if (MidiDevices.get().isEmpty()) {
69
+ await MidiDevices.requestPermission();
70
+ }
71
+ const availableMidiDevices = MidiDevices.inputs();
72
+ const inputs = availableMidiDevices.unwrap();
73
+ const captureDevices = this.deviceId.getValue().match({
74
+ none: () => inputs,
75
+ some: id => inputs.filter(device => id === device.id)
76
+ });
77
+ const activeNotes = new Int8Array(128);
78
+ this.#streaming.ifSome(terminable => terminable.terminate());
79
+ this.#streaming = Option.wrap(Terminable.many(...captureDevices.map(input => Events.subscribe(input, "midimessage", (event) => {
37
80
  const data = event.data;
38
81
  if (isDefined(data) &&
39
82
  this.#filterChannel.mapOr(channel => MidiData.readChannel(data) === channel, true)) {
40
- notifier.notify(event);
83
+ if (MidiData.isNoteOn(data)) {
84
+ activeNotes[MidiData.readPitch(data)]++;
85
+ this.#notifier.notify(event);
86
+ }
87
+ else if (MidiData.isNoteOff(data)) {
88
+ activeNotes[MidiData.readPitch(data)]--;
89
+ this.#notifier.notify(event);
90
+ }
41
91
  }
42
- }))), RecordMidi.start({
43
- notifier,
44
- engine,
45
- project,
46
- capture: this
47
- }));
92
+ })), Terminable.create(() => activeNotes.forEach((count, index) => {
93
+ if (count > 0) {
94
+ // TODO respect channel!
95
+ const event = new MessageEvent("midimessage", { data: MidiData.noteOff(index, count) });
96
+ for (let i = 0; i < count; i++) {
97
+ this.#notifier.notify(event);
98
+ }
99
+ }
100
+ }))));
101
+ }
102
+ #stopStream() {
103
+ this.#streaming.ifSome(terminable => terminable.terminate());
104
+ this.#streaming = Option.None;
48
105
  }
49
- get deviceLabel() { return Option.None; }
50
106
  }
@@ -1,6 +1,5 @@
1
1
  import { Terminable } from "@opendaw/lib-std";
2
2
  import { SampleManager } from "@opendaw/studio-adapters";
3
- import { Engine } from "../Engine";
4
3
  import { Project } from "../Project";
5
4
  import { Capture } from "./Capture";
6
5
  import { RecordingWorklet } from "../RecordingWorklet";
@@ -10,12 +9,11 @@ export declare namespace RecordAudio {
10
9
  mediaStream: MediaStream;
11
10
  sampleManager: SampleManager;
12
11
  audioContext: AudioContext;
13
- engine: Engine;
14
12
  project: Project;
15
13
  capture: Capture;
16
14
  gainDb: number;
17
15
  };
18
- export const start: ({ recordingWorklet, mediaStream, sampleManager, audioContext, engine, project, capture, gainDb }: RecordAudioContext) => Terminable;
16
+ export const start: ({ recordingWorklet, mediaStream, sampleManager, audioContext, project, capture, gainDb }: RecordAudioContext) => Terminable;
19
17
  export {};
20
18
  }
21
19
  //# sourceMappingURL=RecordAudio.d.ts.map
@@ -1 +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"}
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,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,OAAO,EAAE,OAAO,CAAA;QAChB,OAAO,EAAE,OAAO,CAAA;QAChB,MAAM,EAAE,MAAM,CAAA;KACjB,CAAA;IAED,MAAM,CAAC,MAAM,KAAK,GACd,0FAEG,kBAAkB,KAAG,UA2D3B,CAAA;;CACJ"}
@@ -6,10 +6,10 @@ import { RecordTrack } from "./RecordTrack";
6
6
  import { ColorCodes } from "../ColorCodes";
7
7
  export var RecordAudio;
8
8
  (function (RecordAudio) {
9
- RecordAudio.start = ({ recordingWorklet, mediaStream, sampleManager, audioContext, engine, project, capture, gainDb }) => {
9
+ RecordAudio.start = ({ recordingWorklet, mediaStream, sampleManager, audioContext, project, capture, gainDb }) => {
10
10
  const terminator = new Terminator();
11
11
  const beats = PPQN.fromSignature(1, project.timelineBox.signature.denominator.getValue());
12
- const { editing, boxGraph } = project;
12
+ const { editing, engine, boxGraph } = project;
13
13
  const trackBox = RecordTrack.findOrCreate(editing, capture.audioUnitBox, TrackType.Audio);
14
14
  const uuid = recordingWorklet.uuid;
15
15
  sampleManager.record(recordingWorklet);
@@ -1,15 +1,13 @@
1
1
  import { Notifier, Terminable } from "@opendaw/lib-std";
2
- import { Engine } from "../Engine";
3
2
  import { Project } from "../Project";
4
3
  import { Capture } from "./Capture";
5
4
  export declare namespace RecordMidi {
6
5
  type RecordMidiContext = {
7
6
  notifier: Notifier<MIDIMessageEvent>;
8
- engine: Engine;
9
7
  project: Project;
10
8
  capture: Capture;
11
9
  };
12
- export const start: ({ notifier, engine, project, capture }: RecordMidiContext) => Terminable;
10
+ export const start: ({ notifier, project, capture }: RecordMidiContext) => Terminable;
13
11
  export {};
14
12
  }
15
13
  //# sourceMappingURL=RecordMidi.d.ts.map
@@ -1 +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"}
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,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,OAAO,EAAE,OAAO,CAAC;QACjB,OAAO,EAAE,OAAO,CAAA;KACnB,CAAA;IAED,MAAM,CAAC,MAAM,KAAK,GAAI,gCAA8B,iBAAiB,KAAG,UAiEvE,CAAA;;CACJ"}