@opendaw/studio-core 0.0.6

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 (53) hide show
  1. package/README.md +1 -0
  2. package/dist/Engine.d.ts +21 -0
  3. package/dist/Engine.d.ts.map +1 -0
  4. package/dist/Engine.js +1 -0
  5. package/dist/EngineFacade.d.ts +28 -0
  6. package/dist/EngineFacade.d.ts.map +1 -0
  7. package/dist/EngineFacade.js +50 -0
  8. package/dist/EngineWorklet.d.ts +30 -0
  9. package/dist/EngineWorklet.d.ts.map +1 -0
  10. package/dist/EngineWorklet.js +168 -0
  11. package/dist/MeterWorklet.d.ts +15 -0
  12. package/dist/MeterWorklet.d.ts.map +1 -0
  13. package/dist/MeterWorklet.js +33 -0
  14. package/dist/Mixer.d.ts +13 -0
  15. package/dist/Mixer.d.ts.map +1 -0
  16. package/dist/Mixer.js +110 -0
  17. package/dist/Project.d.ts +41 -0
  18. package/dist/Project.d.ts.map +1 -0
  19. package/dist/Project.js +173 -0
  20. package/dist/ProjectEnv.d.ts +5 -0
  21. package/dist/ProjectEnv.d.ts.map +1 -0
  22. package/dist/ProjectEnv.js +1 -0
  23. package/dist/RecordingWorklet.d.ts +9 -0
  24. package/dist/RecordingWorklet.d.ts.map +1 -0
  25. package/dist/RecordingWorklet.js +42 -0
  26. package/dist/Wav.d.ts +6 -0
  27. package/dist/Wav.d.ts.map +1 -0
  28. package/dist/Wav.js +46 -0
  29. package/dist/WorkletFactory.d.ts +8 -0
  30. package/dist/WorkletFactory.d.ts.map +1 -0
  31. package/dist/WorkletFactory.js +9 -0
  32. package/dist/Worklets.d.ts +22 -0
  33. package/dist/Worklets.d.ts.map +1 -0
  34. package/dist/Worklets.js +36 -0
  35. package/dist/engine-processor.js +6 -0
  36. package/dist/engine-processor.js.map +7 -0
  37. package/dist/index.d.ts +13 -0
  38. package/dist/index.d.ts.map +1 -0
  39. package/dist/index.js +12 -0
  40. package/dist/meter-processor.js +2 -0
  41. package/dist/meter-processor.js.map +7 -0
  42. package/dist/recording-processor.js +4 -0
  43. package/dist/recording-processor.js.map +7 -0
  44. package/dist/sync-log/Commit.d.ts +26 -0
  45. package/dist/sync-log/Commit.d.ts.map +1 -0
  46. package/dist/sync-log/Commit.js +76 -0
  47. package/dist/sync-log/SyncLogReader.d.ts +12 -0
  48. package/dist/sync-log/SyncLogReader.d.ts.map +1 -0
  49. package/dist/sync-log/SyncLogReader.js +51 -0
  50. package/dist/sync-log/SyncLogWriter.d.ts +10 -0
  51. package/dist/sync-log/SyncLogWriter.d.ts.map +1 -0
  52. package/dist/sync-log/SyncLogWriter.js +49 -0
  53. package/package.json +48 -0
package/README.md ADDED
@@ -0,0 +1 @@
1
+ This package is part of the openDAW SDK
@@ -0,0 +1,21 @@
1
+ import { ppqn } from "@opendaw/lib-dsp";
2
+ import { byte, DefaultObservableValue, int, MutableObservableValue, Nullable, ObservableValue, Observer, Subscription, unitValue, UUID } from "@opendaw/lib-std";
3
+ import { ClipNotification } from "@opendaw/studio-adapters";
4
+ export interface Engine {
5
+ position(): ObservableValue<ppqn>;
6
+ isPlaying(): MutableObservableValue<boolean>;
7
+ isRecording(): MutableObservableValue<boolean>;
8
+ metronomeEnabled(): MutableObservableValue<boolean>;
9
+ isReady(): Promise<void>;
10
+ queryLoadingComplete(): Promise<boolean>;
11
+ stop(): void;
12
+ panic(): void;
13
+ noteOn(uuid: UUID.Format, pitch: byte, velocity: unitValue): void;
14
+ noteOff(uuid: UUID.Format, pitch: byte): void;
15
+ scheduleClipPlay(...clipIds: ReadonlyArray<UUID.Format>): void;
16
+ scheduleClipStop(...trackIds: ReadonlyArray<UUID.Format>): void;
17
+ requestPosition(position: ppqn): void;
18
+ subscribeClipNotification(observer: Observer<ClipNotification>): Subscription;
19
+ markerState(): DefaultObservableValue<Nullable<[UUID.Format, int]>>;
20
+ }
21
+ //# sourceMappingURL=Engine.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Engine.d.ts","sourceRoot":"","sources":["../src/Engine.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,IAAI,EAAC,MAAM,kBAAkB,CAAA;AACrC,OAAO,EACH,IAAI,EACJ,sBAAsB,EACtB,GAAG,EACH,sBAAsB,EACtB,QAAQ,EACR,eAAe,EACf,QAAQ,EACR,YAAY,EACZ,SAAS,EACT,IAAI,EACP,MAAM,kBAAkB,CAAA;AACzB,OAAO,EAAC,gBAAgB,EAAC,MAAM,0BAA0B,CAAA;AAEzD,MAAM,WAAW,MAAM;IACnB,QAAQ,IAAI,eAAe,CAAC,IAAI,CAAC,CAAA;IACjC,SAAS,IAAI,sBAAsB,CAAC,OAAO,CAAC,CAAA;IAC5C,WAAW,IAAI,sBAAsB,CAAC,OAAO,CAAC,CAAA;IAC9C,gBAAgB,IAAI,sBAAsB,CAAC,OAAO,CAAC,CAAA;IACnD,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC,CAAA;IACxB,oBAAoB,IAAI,OAAO,CAAC,OAAO,CAAC,CAAA;IACxC,IAAI,IAAI,IAAI,CAAA;IACZ,KAAK,IAAI,IAAI,CAAA;IACb,MAAM,CAAC,IAAI,EAAE,IAAI,CAAC,MAAM,EAAE,KAAK,EAAE,IAAI,EAAE,QAAQ,EAAE,SAAS,GAAG,IAAI,CAAA;IACjE,OAAO,CAAC,IAAI,EAAE,IAAI,CAAC,MAAM,EAAE,KAAK,EAAE,IAAI,GAAG,IAAI,CAAA;IAC7C,gBAAgB,CAAC,GAAG,OAAO,EAAE,aAAa,CAAC,IAAI,CAAC,MAAM,CAAC,GAAG,IAAI,CAAA;IAC9D,gBAAgB,CAAC,GAAG,QAAQ,EAAE,aAAa,CAAC,IAAI,CAAC,MAAM,CAAC,GAAG,IAAI,CAAA;IAC/D,eAAe,CAAC,QAAQ,EAAE,IAAI,GAAG,IAAI,CAAA;IACrC,yBAAyB,CAAC,QAAQ,EAAE,QAAQ,CAAC,gBAAgB,CAAC,GAAG,YAAY,CAAA;IAC7E,WAAW,IAAI,sBAAsB,CAAC,QAAQ,CAAC,CAAC,IAAI,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,CAAC,CAAA;CACtE"}
package/dist/Engine.js ADDED
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,28 @@
1
+ import { byte, DefaultObservableValue, int, MutableObservableValue, Nullable, ObservableValue, Observer, Subscription, unitValue, UUID } from "@opendaw/lib-std";
2
+ import { ppqn } from "@opendaw/lib-dsp";
3
+ import { ClipNotification } from "@opendaw/studio-adapters";
4
+ import { Engine } from "./Engine";
5
+ import { EngineWorklet } from "./EngineWorklet";
6
+ export declare class EngineFacade implements Engine {
7
+ #private;
8
+ setClient(client: EngineWorklet): void;
9
+ playbackTimestamp(): MutableObservableValue<ppqn>;
10
+ position(): ObservableValue<ppqn>;
11
+ isPlaying(): MutableObservableValue<boolean>;
12
+ isRecording(): MutableObservableValue<boolean>;
13
+ metronomeEnabled(): MutableObservableValue<boolean>;
14
+ markerState(): DefaultObservableValue<Nullable<[UUID.Format, int]>>;
15
+ isReady(): Promise<void>;
16
+ queryLoadingComplete(): Promise<boolean>;
17
+ stop(): void;
18
+ panic(): void;
19
+ sampleRate(): number;
20
+ subscribeClipNotification(observer: Observer<ClipNotification>): Subscription;
21
+ noteOn(uuid: UUID.Format, pitch: byte, velocity: unitValue): void;
22
+ noteOff(uuid: UUID.Format, pitch: byte): void;
23
+ scheduleClipPlay(...clipIds: ReadonlyArray<UUID.Format>): void;
24
+ scheduleClipStop(...trackIds: ReadonlyArray<UUID.Format>): void;
25
+ requestPosition(position: ppqn): void;
26
+ terminate(): void;
27
+ }
28
+ //# sourceMappingURL=EngineFacade.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"EngineFacade.d.ts","sourceRoot":"","sources":["../src/EngineFacade.ts"],"names":[],"mappings":"AAAA,OAAO,EACH,IAAI,EACJ,sBAAsB,EACtB,GAAG,EACH,sBAAsB,EACtB,QAAQ,EACR,eAAe,EACf,QAAQ,EAER,YAAY,EAEZ,SAAS,EACT,IAAI,EACP,MAAM,kBAAkB,CAAA;AACzB,OAAO,EAAC,IAAI,EAAC,MAAM,kBAAkB,CAAA;AACrC,OAAO,EAAC,gBAAgB,EAAC,MAAM,0BAA0B,CAAA;AACzD,OAAO,EAAC,MAAM,EAAC,MAAM,UAAU,CAAA;AAC/B,OAAO,EAAC,aAAa,EAAC,MAAM,iBAAiB,CAAA;AAE7C,qBAAa,YAAa,YAAW,MAAM;;IAWvC,SAAS,CAAC,MAAM,EAAE,aAAa;IAuB/B,iBAAiB,IAAI,sBAAsB,CAAC,IAAI,CAAC;IACjD,QAAQ,IAAI,eAAe,CAAC,IAAI,CAAC;IACjC,SAAS,IAAI,sBAAsB,CAAC,OAAO,CAAC;IAC5C,WAAW,IAAI,sBAAsB,CAAC,OAAO,CAAC;IAC9C,gBAAgB,IAAI,sBAAsB,CAAC,OAAO,CAAC;IACnD,WAAW,IAAI,sBAAsB,CAAC,QAAQ,CAAC,CAAC,IAAI,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,CAAC;IACnE,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IACxB,oBAAoB,IAAI,OAAO,CAAC,OAAO,CAAC;IAGxC,IAAI,IAAI,IAAI;IACZ,KAAK,IAAI,IAAI;IACb,UAAU,IAAI,MAAM;IACpB,yBAAyB,CAAC,QAAQ,EAAE,QAAQ,CAAC,gBAAgB,CAAC,GAAG,YAAY;IAG7E,MAAM,CAAC,IAAI,EAAE,IAAI,CAAC,MAAM,EAAE,KAAK,EAAE,IAAI,EAAE,QAAQ,EAAE,SAAS,GAAG,IAAI;IAGjE,OAAO,CAAC,IAAI,EAAE,IAAI,CAAC,MAAM,EAAE,KAAK,EAAE,IAAI,GAAG,IAAI;IAC7C,gBAAgB,CAAC,GAAG,OAAO,EAAE,aAAa,CAAC,IAAI,CAAC,MAAM,CAAC,GAAG,IAAI;IAC9D,gBAAgB,CAAC,GAAG,QAAQ,EAAE,aAAa,CAAC,IAAI,CAAC,MAAM,CAAC,GAAG,IAAI;IAC/D,eAAe,CAAC,QAAQ,EAAE,IAAI,GAAG,IAAI;IAErC,SAAS,IAAI,IAAI;CAKpB"}
@@ -0,0 +1,50 @@
1
+ import { DefaultObservableValue, Option, Terminator } from "@opendaw/lib-std";
2
+ export class EngineFacade {
3
+ #terminator = new Terminator();
4
+ #playbackTimestamp = new DefaultObservableValue(0.0);
5
+ #position = new DefaultObservableValue(0.0);
6
+ #isPlaying = new DefaultObservableValue(false);
7
+ #isRecording = new DefaultObservableValue(false);
8
+ #metronomeEnabled = new DefaultObservableValue(false);
9
+ #markerState = new DefaultObservableValue(null);
10
+ #client = Option.None;
11
+ setClient(client) {
12
+ this.#client = Option.wrap(client);
13
+ this.#terminator.terminate();
14
+ this.#terminator.ownAll(client.playbackTimestamp().subscribe(owner => this.#playbackTimestamp.setValue(owner.getValue())), client.position().subscribe(owner => this.#position.setValue(owner.getValue())), client.isPlaying().subscribe(owner => this.#isPlaying.setValue(owner.getValue())), client.isRecording().subscribe(owner => this.#isRecording.setValue(owner.getValue())), client.metronomeEnabled().subscribe(owner => this.#metronomeEnabled.setValue(owner.getValue())), client.markerState().subscribe(owner => this.#markerState.setValue(owner.getValue())), this.position().subscribe(owner => client.position().setValue(owner.getValue())), this.isPlaying().subscribe(owner => client.isPlaying().setValue(owner.getValue())), this.isRecording().subscribe(owner => client.isRecording().setValue(owner.getValue())), this.metronomeEnabled().subscribe(owner => client.metronomeEnabled().setValue(owner.getValue())));
15
+ this.#playbackTimestamp.setValue(client.playbackTimestamp().getValue());
16
+ this.#position.setValue(client.position().getValue());
17
+ this.#isPlaying.setValue(client.isPlaying().getValue());
18
+ this.#isRecording.setValue(client.isRecording().getValue());
19
+ this.#metronomeEnabled.setValue(client.metronomeEnabled().getValue());
20
+ this.#markerState.setValue(client.markerState().getValue());
21
+ }
22
+ playbackTimestamp() { return this.#playbackTimestamp; }
23
+ position() { return this.#position; }
24
+ isPlaying() { return this.#isPlaying; }
25
+ isRecording() { return this.#isRecording; }
26
+ metronomeEnabled() { return this.#metronomeEnabled; }
27
+ markerState() { return this.#markerState; }
28
+ isReady() { return this.#client.mapOr(client => client.isReady(), Promise.resolve()); }
29
+ queryLoadingComplete() {
30
+ return this.#client.mapOr(client => client.queryLoadingComplete(), Promise.resolve(false));
31
+ }
32
+ stop() { this.#client.ifSome(client => client.stop()); }
33
+ panic() { this.#client.ifSome(client => client.panic()); }
34
+ sampleRate() { return this.#client.isEmpty() ? 44_100 : this.#client.unwrap().context.sampleRate; }
35
+ subscribeClipNotification(observer) {
36
+ return this.#client.unwrap().subscribeClipNotification(observer);
37
+ }
38
+ noteOn(uuid, pitch, velocity) {
39
+ this.#client.unwrap("No engine").noteOn(uuid, pitch, velocity);
40
+ }
41
+ noteOff(uuid, pitch) { this.#client.unwrap("No engine").noteOff(uuid, pitch); }
42
+ scheduleClipPlay(...clipIds) { this.#client.unwrap("No engine").scheduleClipPlay(...clipIds); }
43
+ scheduleClipStop(...trackIds) { this.#client.unwrap("No engine").scheduleClipStop(...trackIds); }
44
+ requestPosition(position) { this.#client.unwrap("No engine").requestPosition(position); }
45
+ terminate() {
46
+ this.#terminator.terminate();
47
+ this.#client.ifSome(client => client.terminate());
48
+ this.#client = Option.None;
49
+ }
50
+ }
@@ -0,0 +1,30 @@
1
+ import { byte, DefaultObservableValue, int, MutableObservableValue, Nullable, Observer, Subscription, unitValue, UUID } from "@opendaw/lib-std";
2
+ import { ppqn } from "@opendaw/lib-dsp";
3
+ import { ClipNotification, ExportStemsConfiguration } from "@opendaw/studio-adapters";
4
+ import { Project } from "./Project";
5
+ import { WorkletFactory } from "./WorkletFactory";
6
+ export declare class EngineWorklet extends AudioWorkletNode {
7
+ #private;
8
+ static bootFactory(context: BaseAudioContext, url: string): Promise<WorkletFactory<EngineWorklet>>;
9
+ static ID: int;
10
+ readonly id: number;
11
+ constructor(context: BaseAudioContext, project: Readonly<Project>, exportConfiguration?: ExportStemsConfiguration);
12
+ stop(): void;
13
+ panic(): void;
14
+ isPlaying(): MutableObservableValue<boolean>;
15
+ isRecording(): MutableObservableValue<boolean>;
16
+ playbackTimestamp(): DefaultObservableValue<number>;
17
+ position(): MutableObservableValue<ppqn>;
18
+ metronomeEnabled(): DefaultObservableValue<boolean>;
19
+ isReady(): Promise<void>;
20
+ queryLoadingComplete(): Promise<boolean>;
21
+ noteOn(uuid: UUID.Format, pitch: byte, velocity: unitValue): void;
22
+ noteOff(uuid: UUID.Format, pitch: byte): void;
23
+ scheduleClipPlay(...clipIds: ReadonlyArray<UUID.Format>): void;
24
+ scheduleClipStop(...trackIds: ReadonlyArray<UUID.Format>): void;
25
+ requestPosition(position: ppqn): void;
26
+ subscribeClipNotification(observer: Observer<ClipNotification>): Subscription;
27
+ markerState(): DefaultObservableValue<Nullable<[UUID.Format, int]>>;
28
+ terminate(): void;
29
+ }
30
+ //# sourceMappingURL=EngineWorklet.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"EngineWorklet.d.ts","sourceRoot":"","sources":["../src/EngineWorklet.ts"],"names":[],"mappings":"AAAA,OAAO,EAEH,IAAI,EACJ,sBAAsB,EACtB,GAAG,EACH,sBAAsB,EAEtB,QAAQ,EACR,QAAQ,EAER,YAAY,EAGZ,SAAS,EACT,IAAI,EACP,MAAM,kBAAkB,CAAA;AACzB,OAAO,EAAC,IAAI,EAAC,MAAM,kBAAkB,CAAA;AACrC,OAAO,EAEH,gBAAgB,EAOhB,wBAAwB,EAC3B,MAAM,0BAA0B,CAAA;AAKjC,OAAO,EAAC,OAAO,EAAC,MAAM,WAAW,CAAA;AACjC,OAAO,EAAC,cAAc,EAAC,MAAM,kBAAkB,CAAA;AAE/C,qBAAa,aAAc,SAAQ,gBAAgB;;IAC/C,MAAM,CAAC,WAAW,CAAC,OAAO,EAAE,gBAAgB,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,CAAC,aAAa,CAAC,CAAC;IAIlG,MAAM,CAAC,EAAE,EAAE,GAAG,CAAQ;IAEtB,QAAQ,CAAC,EAAE,SAAqB;gBAgBpB,OAAO,EAAE,gBAAgB,EACzB,OAAO,EAAE,QAAQ,CAAC,OAAO,CAAC,EAC1B,mBAAmB,CAAC,EAAE,wBAAwB;IA2G1D,IAAI,IAAI,IAAI;IASZ,KAAK,IAAI,IAAI;IACb,SAAS,IAAI,sBAAsB,CAAC,OAAO,CAAC;IAC5C,WAAW,IAAI,sBAAsB,CAAC,OAAO,CAAC;IAC9C,iBAAiB,IAAI,sBAAsB,CAAC,MAAM,CAAC;IACnD,QAAQ,IAAI,sBAAsB,CAAC,IAAI,CAAC;IACxC,gBAAgB,IAAI,sBAAsB,CAAC,OAAO,CAAC;IACnD,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IACxB,oBAAoB,IAAI,OAAO,CAAC,OAAO,CAAC;IACxC,MAAM,CAAC,IAAI,EAAE,IAAI,CAAC,MAAM,EAAE,KAAK,EAAE,IAAI,EAAE,QAAQ,EAAE,SAAS,GAAG,IAAI;IACjE,OAAO,CAAC,IAAI,EAAE,IAAI,CAAC,MAAM,EAAE,KAAK,EAAE,IAAI,GAAG,IAAI;IAC7C,gBAAgB,CAAC,GAAG,OAAO,EAAE,aAAa,CAAC,IAAI,CAAC,MAAM,CAAC,GAAG,IAAI;IAK9D,gBAAgB,CAAC,GAAG,QAAQ,EAAE,aAAa,CAAC,IAAI,CAAC,MAAM,CAAC,GAAG,IAAI;IAG/D,eAAe,CAAC,QAAQ,EAAE,IAAI,GAAG,IAAI;IAIrC,yBAAyB,CAAC,QAAQ,EAAE,QAAQ,CAAC,gBAAgB,CAAC,GAAG,YAAY;IAQ7E,WAAW,IAAI,sBAAsB,CAAC,QAAQ,CAAC,CAAC,IAAI,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,CAAC;IAInE,SAAS,IAAI,IAAI;CAKpB"}
@@ -0,0 +1,168 @@
1
+ import { Arrays, DefaultObservableValue, Notifier, Option, SyncStream, Terminator, UUID } from "@opendaw/lib-std";
2
+ import { EngineStateSchema, ExportStemsConfiguration } from "@opendaw/studio-adapters";
3
+ import { SyncSource } from "@opendaw/lib-box";
4
+ import { AnimationFrame } from "@opendaw/lib-dom";
5
+ import { Communicator, Messenger } from "@opendaw/lib-runtime";
6
+ import { WorkletFactory } from "./WorkletFactory";
7
+ export class EngineWorklet extends AudioWorkletNode {
8
+ static bootFactory(context, url) {
9
+ return WorkletFactory.boot(context, url);
10
+ }
11
+ static ID = 0 | 0;
12
+ id = EngineWorklet.ID++;
13
+ #terminator = new Terminator();
14
+ #playbackTimestamp = new DefaultObservableValue(0.0);
15
+ #position = new DefaultObservableValue(0.0);
16
+ #isPlaying = new DefaultObservableValue(false);
17
+ #isRecording = new DefaultObservableValue(false);
18
+ #metronomeEnabled = new DefaultObservableValue(false);
19
+ #markerState = new DefaultObservableValue(null);
20
+ #notifyClipNotification;
21
+ #playingClips;
22
+ #commands;
23
+ #isReady;
24
+ #ignoreUpdates = false;
25
+ constructor(context, project, exportConfiguration) {
26
+ console.debug("constructor");
27
+ const numberOfChannels = ExportStemsConfiguration.countStems(Option.wrap(exportConfiguration)) * 2;
28
+ const reader = SyncStream.reader(EngineStateSchema(), state => {
29
+ this.#ignoreUpdates = true;
30
+ this.#position.setValue(state.position);
31
+ this.#ignoreUpdates = false;
32
+ });
33
+ super(context, "engine-processor", {
34
+ numberOfInputs: 0,
35
+ numberOfOutputs: 1,
36
+ outputChannelCount: [numberOfChannels],
37
+ processorOptions: {
38
+ sab: reader.buffer,
39
+ project: project.toArrayBuffer(),
40
+ exportConfiguration
41
+ }
42
+ });
43
+ const { resolve, promise } = Promise.withResolvers();
44
+ const messenger = Messenger.for(this.port);
45
+ this.#isReady = promise;
46
+ this.#notifyClipNotification = this.#terminator.own(new Notifier());
47
+ this.#playingClips = [];
48
+ this.#commands = this.#terminator.own(Communicator.sender(messenger.channel("engine-commands"), dispatcher => new class {
49
+ setPlaying(value) { dispatcher.dispatchAndForget(this.setPlaying, value); }
50
+ setRecording(value) { dispatcher.dispatchAndForget(this.setRecording, value); }
51
+ setPosition(position) { dispatcher.dispatchAndForget(this.setPosition, position); }
52
+ setMetronomeEnabled(enabled) { dispatcher.dispatchAndForget(this.setMetronomeEnabled, enabled); }
53
+ stopAndReset() { dispatcher.dispatchAndForget(this.stopAndReset); }
54
+ queryLoadingComplete() {
55
+ return dispatcher.dispatchAndReturn(this.queryLoadingComplete);
56
+ }
57
+ panic() { dispatcher.dispatchAndForget(this.panic); }
58
+ noteOn(uuid, pitch, velocity) {
59
+ dispatcher.dispatchAndForget(this.noteOn, uuid, pitch, velocity);
60
+ }
61
+ noteOff(uuid, pitch) {
62
+ dispatcher.dispatchAndForget(this.noteOff, uuid, pitch);
63
+ }
64
+ scheduleClipPlay(clipIds) {
65
+ dispatcher.dispatchAndForget(this.scheduleClipPlay, clipIds);
66
+ }
67
+ scheduleClipStop(trackIds) {
68
+ dispatcher.dispatchAndForget(this.scheduleClipStop, trackIds);
69
+ }
70
+ terminate() { dispatcher.dispatchAndForget(this.terminate); }
71
+ }));
72
+ Communicator.executor(messenger.channel("engine-to-client"), {
73
+ log: (message) => console.log("WORKLET", message),
74
+ ready: () => resolve(),
75
+ fetchAudio: (uuid) => {
76
+ return new Promise((resolve, reject) => {
77
+ const handler = project.audioManager.getOrCreate(uuid);
78
+ handler.subscribe(state => {
79
+ if (state.type === "error") {
80
+ reject(state.reason);
81
+ }
82
+ else if (state.type === "loaded") {
83
+ resolve(handler.data.unwrap());
84
+ }
85
+ });
86
+ });
87
+ },
88
+ notifyClipSequenceChanges: (changes) => {
89
+ changes.stopped.forEach(uuid => {
90
+ for (let i = 0; i < this.#playingClips.length; i++) {
91
+ if (UUID.equals(this.#playingClips[i], uuid)) {
92
+ this.#playingClips.splice(i, 1);
93
+ break;
94
+ }
95
+ }
96
+ });
97
+ changes.started.forEach(uuid => this.#playingClips.push(uuid));
98
+ this.#notifyClipNotification.notify({ type: "sequencing", changes });
99
+ },
100
+ switchMarkerState: (state) => this.#markerState.setValue(state)
101
+ });
102
+ this.#terminator.ownAll(AnimationFrame.add(() => reader.tryRead()), project.liveStreamReceiver.connect(messenger.channel("engine-live-data")), new SyncSource(project.boxGraph, messenger.channel("engine-sync"), false), this.#isPlaying.catchupAndSubscribe(owner => {
103
+ const isPlaying = owner.getValue();
104
+ if (isPlaying) {
105
+ this.#commands.setPosition(this.#playbackTimestamp.getValue());
106
+ }
107
+ else if (this.#isRecording.getValue()) {
108
+ this.#isRecording.setValue(false);
109
+ }
110
+ this.#commands.setPlaying(isPlaying);
111
+ }), this.#isRecording.subscribe(owner => {
112
+ const willRecord = owner.getValue();
113
+ this.#commands.setPlaying(willRecord);
114
+ this.#commands.setRecording(willRecord);
115
+ }), this.#metronomeEnabled.catchupAndSubscribe(owner => this.#commands.setMetronomeEnabled(owner.getValue())), this.#position.catchupAndSubscribe(owner => {
116
+ if (!this.#ignoreUpdates) {
117
+ this.#commands.setPosition(owner.getValue());
118
+ }
119
+ }));
120
+ }
121
+ stop() {
122
+ if (!this.#isPlaying.getValue() && this.#position.getValue() === 0.0) {
123
+ this.#commands.stopAndReset();
124
+ }
125
+ else {
126
+ this.#isRecording.setValue(false);
127
+ this.#isPlaying.setValue(false);
128
+ this.requestPosition(0.0);
129
+ }
130
+ }
131
+ panic() { this.#commands.panic(); }
132
+ isPlaying() { return this.#isPlaying; }
133
+ isRecording() { return this.#isRecording; }
134
+ playbackTimestamp() { return this.#playbackTimestamp; }
135
+ position() { return this.#position; }
136
+ metronomeEnabled() { return this.#metronomeEnabled; }
137
+ isReady() { return this.#isReady; }
138
+ queryLoadingComplete() { return this.#commands.queryLoadingComplete(); }
139
+ noteOn(uuid, pitch, velocity) { this.#commands.noteOn(uuid, pitch, velocity); }
140
+ noteOff(uuid, pitch) { this.#commands.noteOff(uuid, pitch); }
141
+ scheduleClipPlay(...clipIds) {
142
+ this.#notifyClipNotification.notify({ type: "waiting", clips: clipIds });
143
+ this.#commands.scheduleClipPlay(clipIds);
144
+ this.#isPlaying.setValue(true); // must be second, since they might be executed in different blocks
145
+ }
146
+ scheduleClipStop(...trackIds) {
147
+ this.#commands.scheduleClipStop(trackIds);
148
+ }
149
+ requestPosition(position) {
150
+ this.#playbackTimestamp.setValue(position);
151
+ this.#commands.setPosition(position);
152
+ }
153
+ subscribeClipNotification(observer) {
154
+ observer({
155
+ type: "sequencing",
156
+ changes: { started: this.#playingClips, stopped: Arrays.empty(), obsolete: Arrays.empty() }
157
+ });
158
+ return this.#notifyClipNotification.subscribe(observer);
159
+ }
160
+ markerState() {
161
+ return this.#markerState;
162
+ }
163
+ terminate() {
164
+ console.debug(`terminate EngineClient id: ${this.id}`);
165
+ this.#terminator.terminate();
166
+ this.disconnect();
167
+ }
168
+ }
@@ -0,0 +1,15 @@
1
+ import { int, Observer, Subscription, Terminable } from "@opendaw/lib-std";
2
+ import { WorkletFactory } from "./WorkletFactory";
3
+ export type PeakSchema = {
4
+ peak: Float32Array;
5
+ rms: Float32Array;
6
+ };
7
+ export declare class MeterWorklet extends AudioWorkletNode implements Terminable {
8
+ #private;
9
+ static bootFactory(context: BaseAudioContext, url: string): Promise<WorkletFactory<MeterWorklet>>;
10
+ static create(factory: WorkletFactory<MeterWorklet>, numChannels: int): MeterWorklet;
11
+ private constructor();
12
+ subscribe(observer: Observer<PeakSchema>): Subscription;
13
+ terminate(): void;
14
+ }
15
+ //# sourceMappingURL=MeterWorklet.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MeterWorklet.d.ts","sourceRoot":"","sources":["../src/MeterWorklet.ts"],"names":[],"mappings":"AACA,OAAO,EAAC,GAAG,EAAY,QAAQ,EAAU,YAAY,EAAc,UAAU,EAAa,MAAM,kBAAkB,CAAA;AAElH,OAAO,EAAC,cAAc,EAAC,MAAM,kBAAkB,CAAA;AAE/C,MAAM,MAAM,UAAU,GAAG;IAAE,IAAI,EAAE,YAAY,CAAC;IAAC,GAAG,EAAE,YAAY,CAAA;CAAE,CAAA;AAElE,qBAAa,YAAa,SAAQ,gBAAiB,YAAW,UAAU;;WACvD,WAAW,CAAC,OAAO,EAAE,gBAAgB,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,CAAC,YAAY,CAAC,CAAC;IAIvG,MAAM,CAAC,MAAM,CAAC,OAAO,EAAE,cAAc,CAAC,YAAY,CAAC,EAAE,WAAW,EAAE,GAAG,GAAG,YAAY;IAOpF,OAAO;IAmBP,SAAS,CAAC,QAAQ,EAAE,QAAQ,CAAC,UAAU,CAAC,GAAG,YAAY;IAEvD,SAAS,IAAI,IAAI;CACpB"}
@@ -0,0 +1,33 @@
1
+ import { Notifier, Schema, SyncStream, Terminator } from "@opendaw/lib-std";
2
+ import { AnimationFrame } from "@opendaw/lib-dom";
3
+ import { WorkletFactory } from "./WorkletFactory";
4
+ export class MeterWorklet extends AudioWorkletNode {
5
+ static async bootFactory(context, url) {
6
+ return WorkletFactory.boot(context, url);
7
+ }
8
+ static create(factory, numChannels) {
9
+ return factory.create(context => new MeterWorklet(context, numChannels));
10
+ }
11
+ #terminator = new Terminator();
12
+ #notifier = new Notifier();
13
+ constructor(context, numberOfChannels) {
14
+ const receiver = SyncStream.reader(Schema.createBuilder({
15
+ peak: Schema.floats(numberOfChannels),
16
+ rms: Schema.floats(numberOfChannels)
17
+ })(), (data) => this.#notifier.notify(data));
18
+ super(context, "peak-meter-processor", {
19
+ numberOfInputs: 1,
20
+ channelCount: numberOfChannels,
21
+ channelCountMode: "explicit",
22
+ processorOptions: {
23
+ sab: receiver.buffer,
24
+ numberOfChannels,
25
+ rmsWindowInSeconds: 0.100,
26
+ valueDecay: 0.200
27
+ }
28
+ });
29
+ this.#terminator.own(AnimationFrame.add(() => receiver.tryRead()));
30
+ }
31
+ subscribe(observer) { return this.#notifier.subscribe(observer); }
32
+ terminate() { this.#terminator.terminate(); }
33
+ }
@@ -0,0 +1,13 @@
1
+ import { Terminable } from "@opendaw/lib-std";
2
+ import { Pointers } from "@opendaw/studio-enums";
3
+ import { AudioUnitBoxAdapter, SortedBoxAdapterCollection } from "@opendaw/studio-adapters";
4
+ export interface ChannelStripView {
5
+ silent(value: boolean): void;
6
+ }
7
+ export declare class Mixer implements Terminable {
8
+ #private;
9
+ constructor(audioUnits: SortedBoxAdapterCollection<AudioUnitBoxAdapter, Pointers.AudioUnits>);
10
+ registerChannelStrip({ uuid }: AudioUnitBoxAdapter, view: ChannelStripView): Terminable;
11
+ terminate(): void;
12
+ }
13
+ //# sourceMappingURL=Mixer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Mixer.d.ts","sourceRoot":"","sources":["../src/Mixer.ts"],"names":[],"mappings":"AAAA,OAAO,EAAwD,UAAU,EAAmB,MAAM,kBAAkB,CAAA;AACpH,OAAO,EAAC,QAAQ,EAAC,MAAM,uBAAuB,CAAA;AAE9C,OAAO,EAAC,mBAAmB,EAAE,0BAA0B,EAAC,MAAM,0BAA0B,CAAA;AAIxF,MAAM,WAAW,gBAAgB;IAC7B,MAAM,CAAC,KAAK,EAAE,OAAO,GAAG,IAAI,CAAA;CAC/B;AAQD,qBAAa,KAAM,YAAW,UAAU;;gBAOxB,UAAU,EAAE,0BAA0B,CAAC,mBAAmB,EAAE,QAAQ,CAAC,UAAU,CAAC;IAuC5F,oBAAoB,CAAC,EAAC,IAAI,EAAC,EAAE,mBAAmB,EAAE,IAAI,EAAE,gBAAgB,GAAG,UAAU;IASrF,SAAS,IAAI,IAAI;CAqDpB"}
package/dist/Mixer.js ADDED
@@ -0,0 +1,110 @@
1
+ import { Arrays, asDefined, EmptyExec, Terminable, Terminator, UUID } from "@opendaw/lib-std";
2
+ import { Pointers } from "@opendaw/studio-enums";
3
+ import { deferNextFrame } from "@opendaw/lib-dom";
4
+ export class Mixer {
5
+ #terminator = new Terminator();
6
+ #states;
7
+ #solo;
8
+ #virtualSolo;
9
+ #deferUpdate;
10
+ constructor(audioUnits) {
11
+ this.#states = UUID.newSet(({ adapter: { uuid } }) => uuid);
12
+ this.#solo = new Set();
13
+ this.#virtualSolo = new Set();
14
+ this.#deferUpdate = this.#terminator.own(deferNextFrame(() => this.#updateStates()));
15
+ this.#terminator.own(audioUnits.catchupAndSubscribe({
16
+ onAdd: (adapter) => {
17
+ const { mute, solo } = adapter.namedParameter;
18
+ const views = [];
19
+ this.#states.add({
20
+ adapter,
21
+ views,
22
+ subscription: Terminable.many(mute.catchupAndSubscribe(owner => {
23
+ if (owner.getControlledValue()) {
24
+ views.forEach(view => view.silent(true));
25
+ }
26
+ else {
27
+ this.#deferUpdate.request();
28
+ }
29
+ }), solo.catchupAndSubscribe(owner => {
30
+ if (owner.getControlledValue()) {
31
+ this.#solo.add(adapter);
32
+ }
33
+ else {
34
+ this.#solo.delete(adapter);
35
+ }
36
+ this.#deferUpdate.request();
37
+ }))
38
+ });
39
+ },
40
+ onRemove: (adapter) => {
41
+ this.#solo.delete(adapter);
42
+ this.#states.removeByKey(adapter.uuid).subscription.terminate();
43
+ this.#deferUpdate.request();
44
+ },
45
+ onReorder: EmptyExec
46
+ }));
47
+ }
48
+ registerChannelStrip({ uuid }, view) {
49
+ this.#states.get(uuid).views.push(view);
50
+ this.#deferUpdate.request();
51
+ return Terminable.create(() => {
52
+ this.#states.opt(uuid).ifSome(({ views }) => Arrays.remove(views, view));
53
+ this.#deferUpdate.request();
54
+ });
55
+ }
56
+ terminate() { this.#terminator.terminate(); }
57
+ #updateStates() {
58
+ this.#virtualSolo.clear();
59
+ this.#processChannelStrips();
60
+ this.#updateChannelStripViews();
61
+ }
62
+ #processChannelStrips() {
63
+ const touched = new Set();
64
+ const processUpstreamChannels = (adapter) => {
65
+ if (touched.has(adapter)) {
66
+ return;
67
+ }
68
+ touched.add(adapter);
69
+ adapter.input.getValue().ifSome(input => {
70
+ if (input.type === "bus") {
71
+ input.box.input.pointerHub
72
+ .filter(Pointers.AudioOutput)
73
+ .map(pointer => this.#resolveAdapter(pointer.box))
74
+ .forEach((adapter) => {
75
+ const { namedParameter: { solo } } = adapter;
76
+ if (!solo.getControlledValue()) {
77
+ this.#virtualSolo.add(adapter);
78
+ }
79
+ processUpstreamChannels(adapter);
80
+ });
81
+ }
82
+ });
83
+ };
84
+ this.#states.forEach(({ adapter }) => {
85
+ const { namedParameter: { solo } } = adapter;
86
+ if (solo.getControlledValue()) {
87
+ processUpstreamChannels(adapter);
88
+ }
89
+ });
90
+ }
91
+ #resolveAdapter(box) {
92
+ return asDefined(box.accept({
93
+ visitAudioUnitBox: ({ address: { uuid } }) => this.#states.get(uuid).adapter,
94
+ visitAuxSendBox: ({ audioUnit: { targetVertex } }) => this.#states.get(targetVertex.unwrap().address.uuid).adapter
95
+ }), "Could not resolve entry");
96
+ }
97
+ #updateChannelStripViews() {
98
+ this.#states.forEach(({ adapter, views }) => {
99
+ const { mute, solo } = adapter.namedParameter;
100
+ if (mute.getControlledValue()) {
101
+ views.forEach(view => view.silent(true));
102
+ }
103
+ else {
104
+ const isSolo = solo.getControlledValue() || this.#virtualSolo.has(adapter);
105
+ const value = this.#solo.size > 0 && !isSolo && !adapter.isOutput;
106
+ views.forEach(view => view.silent(value));
107
+ }
108
+ });
109
+ }
110
+ }
@@ -0,0 +1,41 @@
1
+ import { Terminable, TerminableOwner, Terminator } from "@opendaw/lib-std";
2
+ import { BoxGraph, Editing } from "@opendaw/lib-box";
3
+ import { AudioBusBox, AudioUnitBox, BoxIO, RootBox, TimelineBox, UserInterfaceBox } from "@opendaw/studio-boxes";
4
+ import { AudioLoaderManager, BoxAdapters, BoxAdaptersContext, ClipSequencing, ParameterFieldAdapters, RootBoxAdapter, TimelineBoxAdapter, UserEditingManager, VertexSelection } from "@opendaw/studio-adapters";
5
+ import { LiveStreamBroadcaster, LiveStreamReceiver } from "@opendaw/lib-fusion";
6
+ import { ProjectEnv } from "./ProjectEnv";
7
+ import { Mixer } from "./Mixer";
8
+ export declare class Project implements BoxAdaptersContext, Terminable, TerminableOwner {
9
+ #private;
10
+ static new(env: ProjectEnv): Project;
11
+ static load(env: ProjectEnv, arrayBuffer: ArrayBuffer): Project;
12
+ readonly boxGraph: BoxGraph<BoxIO.TypeMap>;
13
+ readonly rootBox: RootBox;
14
+ readonly userInterfaceBox: UserInterfaceBox;
15
+ readonly masterBusBox: AudioBusBox;
16
+ readonly masterAudioUnit: AudioUnitBox;
17
+ readonly timelineBox: TimelineBox;
18
+ readonly editing: Editing;
19
+ readonly selection: VertexSelection;
20
+ readonly boxAdapters: BoxAdapters;
21
+ readonly userEditingManager: UserEditingManager;
22
+ readonly parameterFieldAdapters: ParameterFieldAdapters;
23
+ readonly liveStreamReceiver: LiveStreamReceiver;
24
+ readonly mixer: Mixer;
25
+ private constructor();
26
+ own<T extends Terminable>(terminable: T): T;
27
+ ownAll<T extends Terminable>(...terminables: Array<T>): void;
28
+ spawn(): Terminator;
29
+ get bpm(): number;
30
+ get rootBoxAdapter(): RootBoxAdapter;
31
+ get timelineBoxAdapter(): TimelineBoxAdapter;
32
+ get audioManager(): AudioLoaderManager;
33
+ get clipSequencing(): ClipSequencing;
34
+ get isAudioContext(): boolean;
35
+ get isMainThread(): boolean;
36
+ get liveStreamBroadcaster(): LiveStreamBroadcaster;
37
+ toArrayBuffer(): ArrayBufferLike;
38
+ copy(): Project;
39
+ terminate(): void;
40
+ }
41
+ //# sourceMappingURL=Project.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Project.d.ts","sourceRoot":"","sources":["../src/Project.ts"],"names":[],"mappings":"AAAA,OAAO,EAKH,UAAU,EACV,eAAe,EACf,UAAU,EAEb,MAAM,kBAAkB,CAAA;AACzB,OAAO,EAAC,QAAQ,EAAE,OAAO,EAAC,MAAM,kBAAkB,CAAA;AAClD,OAAO,EACH,WAAW,EACX,YAAY,EACZ,KAAK,EAGL,OAAO,EACP,WAAW,EACX,gBAAgB,EAInB,MAAM,uBAAuB,CAAA;AAC9B,OAAO,EACH,kBAAkB,EAClB,WAAW,EACX,kBAAkB,EAClB,cAAc,EAGd,sBAAsB,EAEtB,cAAc,EACd,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;AAI7B,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;IA8D/D,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,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;IA6BP,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,MAAM,CAAyC;IAC1D,IAAI,cAAc,IAAI,cAAc,CAAmE;IACvG,IAAI,kBAAkB,IAAI,kBAAkB,CAA2E;IACvH,IAAI,YAAY,IAAI,kBAAkB,CAAgC;IACtE,IAAI,cAAc,IAAI,cAAc,CAAkD;IACtF,IAAI,cAAc,IAAI,OAAO,CAAe;IAC5C,IAAI,YAAY,IAAI,OAAO,CAAc;IACzC,IAAI,qBAAqB,IAAI,qBAAqB,CAAkD;IAEpG,aAAa,IAAI,eAAe;IAiBhC,IAAI,IAAI,OAAO;IAEf,SAAS,IAAI,IAAI;CACpB"}