@opendaw/studio-core 0.0.19 → 0.0.20
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/AudioInputDevices.d.ts +8 -0
- package/dist/AudioInputDevices.d.ts.map +1 -0
- package/dist/AudioInputDevices.js +30 -0
- package/dist/AudioUnitOrdering.d.ts +3 -0
- package/dist/AudioUnitOrdering.d.ts.map +1 -0
- package/dist/AudioUnitOrdering.js +7 -0
- package/dist/EffectBox.d.ts +2 -2
- package/dist/EffectBox.d.ts.map +1 -1
- package/dist/Engine.d.ts +31 -10
- package/dist/Engine.d.ts.map +1 -1
- package/dist/EngineFacade.d.ts +22 -11
- package/dist/EngineFacade.d.ts.map +1 -1
- package/dist/EngineFacade.js +39 -22
- package/dist/EngineWorklet.d.ts +22 -13
- package/dist/EngineWorklet.d.ts.map +1 -1
- package/dist/EngineWorklet.js +47 -56
- package/dist/MeterWorklet.d.ts.map +1 -1
- package/dist/MeterWorklet.js +2 -2
- package/dist/Project.d.ts +4 -2
- package/dist/Project.d.ts.map +1 -1
- package/dist/Project.js +6 -3
- package/dist/ProjectApi.d.ts +0 -1
- package/dist/ProjectApi.d.ts.map +1 -1
- package/dist/ProjectApi.js +16 -11
- package/dist/ProjectEnv.d.ts +1 -0
- package/dist/ProjectEnv.d.ts.map +1 -1
- package/dist/ProjectMigration.d.ts.map +1 -1
- package/dist/ProjectMigration.js +20 -3
- package/dist/RecordingWorklet.d.ts +15 -3
- package/dist/RecordingWorklet.d.ts.map +1 -1
- package/dist/RecordingWorklet.js +111 -16
- package/dist/WorkerAgents.d.ts +2 -2
- package/dist/WorkerAgents.d.ts.map +1 -1
- package/dist/Worklets.d.ts +1 -1
- package/dist/Worklets.d.ts.map +1 -1
- package/dist/Worklets.js +2 -2
- package/dist/capture/Capture.d.ts +22 -0
- package/dist/capture/Capture.d.ts.map +1 -0
- package/dist/capture/Capture.js +32 -0
- package/dist/capture/CaptureAudio.d.ts +17 -0
- package/dist/capture/CaptureAudio.d.ts.map +1 -0
- package/dist/capture/CaptureAudio.js +106 -0
- package/dist/capture/CaptureManager.d.ts +12 -0
- package/dist/capture/CaptureManager.d.ts.map +1 -0
- package/dist/capture/CaptureManager.js +38 -0
- package/dist/capture/CaptureMidi.d.ts +13 -0
- package/dist/capture/CaptureMidi.d.ts.map +1 -0
- package/dist/capture/CaptureMidi.js +50 -0
- package/dist/capture/RecordAudio.d.ts +21 -0
- package/dist/capture/RecordAudio.d.ts.map +1 -0
- package/dist/capture/RecordAudio.js +66 -0
- package/dist/capture/RecordMidi.d.ts +15 -0
- package/dist/capture/RecordMidi.d.ts.map +1 -0
- package/dist/capture/RecordMidi.js +85 -0
- package/dist/capture/RecordTrack.d.ts +7 -0
- package/dist/capture/RecordTrack.d.ts.map +1 -0
- package/dist/capture/RecordTrack.js +23 -0
- package/dist/capture/Recording.d.ts +9 -0
- package/dist/capture/Recording.d.ts.map +1 -0
- package/dist/capture/Recording.js +65 -0
- package/dist/capture/RecordingContext.d.ts +14 -0
- package/dist/capture/RecordingContext.d.ts.map +1 -0
- package/dist/capture/RecordingContext.js +1 -0
- package/dist/dawproject/AudioUnitExportLayout.d.ts +10 -0
- package/dist/dawproject/AudioUnitExportLayout.d.ts.map +1 -0
- package/dist/dawproject/AudioUnitExportLayout.js +64 -0
- package/dist/dawproject/BuiltinDevices.d.ts +9 -0
- package/dist/dawproject/BuiltinDevices.d.ts.map +1 -0
- package/dist/dawproject/BuiltinDevices.js +70 -0
- package/dist/dawproject/{DawProjectIO.d.ts → DawProject.d.ts} +6 -4
- package/dist/dawproject/DawProject.d.ts.map +1 -0
- package/dist/dawproject/{DawProjectIO.js → DawProject.js} +23 -5
- package/dist/dawproject/DawProjectExporter.d.ts +7 -6
- package/dist/dawproject/DawProjectExporter.d.ts.map +1 -1
- package/dist/dawproject/DawProjectExporter.js +245 -22
- package/dist/dawproject/DawProjectExporter.test.js +39 -6
- package/dist/dawproject/DawProjectImport.d.ts +12 -0
- package/dist/dawproject/DawProjectImport.d.ts.map +1 -0
- package/dist/dawproject/DawProjectImport.js +389 -0
- package/dist/dawproject/DawProjectImport.test.js +6 -7
- package/dist/dawproject/DeviceIO.d.ts +8 -0
- package/dist/dawproject/DeviceIO.d.ts.map +1 -0
- package/dist/dawproject/DeviceIO.js +63 -0
- package/dist/index.d.ts +11 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +11 -3
- package/dist/processors.js +3 -3
- package/dist/processors.js.map +4 -4
- package/dist/samples/MainThreadSampleLoader.d.ts +1 -0
- package/dist/samples/MainThreadSampleLoader.d.ts.map +1 -1
- package/dist/samples/MainThreadSampleLoader.js +10 -6
- package/dist/samples/MainThreadSampleManager.d.ts +5 -5
- package/dist/samples/MainThreadSampleManager.d.ts.map +1 -1
- package/dist/samples/MainThreadSampleManager.js +1 -0
- package/dist/samples/SampleProvider.d.ts +2 -2
- package/dist/samples/SampleProvider.d.ts.map +1 -1
- package/dist/samples/SampleStorage.d.ts.map +1 -1
- package/dist/samples/SampleStorage.js +2 -3
- package/dist/workers.js +2 -2
- package/dist/workers.js.map +4 -4
- package/package.json +15 -15
- package/dist/dawproject/DawProjectIO.d.ts.map +0 -1
- package/dist/dawproject/DawProjectImporter.d.ts +0 -12
- package/dist/dawproject/DawProjectImporter.d.ts.map +0 -1
- package/dist/dawproject/DawProjectImporter.js +0 -273
- package/dist/samples/SamplePeaks.d.ts +0 -6
- package/dist/samples/SamplePeaks.d.ts.map +0 -1
- package/dist/samples/SamplePeaks.js +0 -9
package/dist/Project.js
CHANGED
@@ -7,6 +7,7 @@ import { AudioUnitType } from "@opendaw/studio-enums";
|
|
7
7
|
import { Mixer } from "./Mixer";
|
8
8
|
import { ProjectApi } from "./ProjectApi";
|
9
9
|
import { ProjectMigration } from "./ProjectMigration";
|
10
|
+
import { CaptureManager } from "./capture/CaptureManager";
|
10
11
|
// Main Entry Point for a Project
|
11
12
|
//
|
12
13
|
export class Project {
|
@@ -66,6 +67,7 @@ export class Project {
|
|
66
67
|
masterAudioUnit;
|
67
68
|
timelineBox;
|
68
69
|
api;
|
70
|
+
captureManager;
|
69
71
|
editing;
|
70
72
|
selection;
|
71
73
|
boxAdapters;
|
@@ -81,8 +83,8 @@ export class Project {
|
|
81
83
|
this.masterBusBox = masterBusBox;
|
82
84
|
this.masterAudioUnit = masterAudioUnit;
|
83
85
|
this.timelineBox = timelineBox;
|
84
|
-
this.liveStreamReceiver = this.#terminator.own(new LiveStreamReceiver());
|
85
86
|
this.api = new ProjectApi(this);
|
87
|
+
this.captureManager = this.#terminator.own(new CaptureManager(this));
|
86
88
|
this.editing = new Editing(this.boxGraph);
|
87
89
|
this.selection = new VertexSelection(this.editing, this.boxGraph);
|
88
90
|
this.parameterFieldAdapters = new ParameterFieldAdapters();
|
@@ -90,16 +92,18 @@ export class Project {
|
|
90
92
|
this.userEditingManager = new UserEditingManager(this.editing);
|
91
93
|
this.userEditingManager.follow(this.userInterfaceBox);
|
92
94
|
this.selection.switch(this.userInterfaceBox.selection);
|
95
|
+
this.liveStreamReceiver = this.#terminator.own(new LiveStreamReceiver());
|
93
96
|
this.mixer = new Mixer(this.rootBoxAdapter.audioUnits);
|
94
97
|
console.debug(`Project was created on ${this.rootBoxAdapter.created.toString()}`);
|
95
98
|
}
|
96
99
|
own(terminable) { return this.#terminator.own(terminable); }
|
97
100
|
ownAll(...terminables) { return this.#terminator.ownAll(...terminables); }
|
98
101
|
spawn() { return this.#terminator.spawn(); }
|
102
|
+
get env() { return this.#env; }
|
99
103
|
get bpm() { return this.timelineBox.bpm.getValue(); }
|
100
104
|
get rootBoxAdapter() { return this.boxAdapters.adapterFor(this.rootBox, RootBoxAdapter); }
|
101
105
|
get timelineBoxAdapter() { return this.boxAdapters.adapterFor(this.timelineBox, TimelineBoxAdapter); }
|
102
|
-
get
|
106
|
+
get sampleManager() { return this.#env.sampleManager; }
|
103
107
|
get clipSequencing() { return panic("Only available in audio context"); }
|
104
108
|
get isAudioContext() { return false; }
|
105
109
|
get isMainThread() { return true; }
|
@@ -134,5 +138,4 @@ export class Project {
|
|
134
138
|
}
|
135
139
|
copy() { return Project.load(this.#env, this.toArrayBuffer()); }
|
136
140
|
terminate() { this.#terminator.terminate(); }
|
137
|
-
toDawProject() { return panic("Not implemented"); }
|
138
141
|
}
|
package/dist/ProjectApi.d.ts
CHANGED
@@ -39,7 +39,6 @@ export type NoteRegionParams = {
|
|
39
39
|
};
|
40
40
|
export declare class ProjectApi {
|
41
41
|
#private;
|
42
|
-
static readonly AudioUnitOrdering: Record<string, int>;
|
43
42
|
constructor(project: Project);
|
44
43
|
setBpm(value: number): void;
|
45
44
|
catchupAndSubscribeBpm(observer: Observer<number>): Subscription;
|
package/dist/ProjectApi.d.ts.map
CHANGED
@@ -1 +1 @@
|
|
1
|
-
{"version":3,"file":"ProjectApi.d.ts","sourceRoot":"","sources":["../src/ProjectApi.ts"],"names":[],"mappings":"AAAA,OAAO,EAKH,KAAK,EACL,GAAG,EACH,QAAQ,EACR,MAAM,EAEN,YAAY,EAEf,MAAM,kBAAkB,CAAA;AACzB,OAAO,EAAC,IAAI,EAAO,MAAM,kBAAkB,CAAA;AAC3C,OAAO,
|
1
|
+
{"version":3,"file":"ProjectApi.d.ts","sourceRoot":"","sources":["../src/ProjectApi.ts"],"names":[],"mappings":"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"}
|
package/dist/ProjectApi.js
CHANGED
@@ -2,17 +2,12 @@ import { asDefined, asInstanceOf, assert, clamp, Option, Strings, UUID } from "@
|
|
2
2
|
import { PPQN } from "@opendaw/lib-dsp";
|
3
3
|
import { IndexedBox, StringField } from "@opendaw/lib-box";
|
4
4
|
import { AudioUnitType } from "@opendaw/studio-enums";
|
5
|
-
import { AudioBusBox, AudioUnitBox, NoteClipBox, NoteEventBox, NoteEventCollectionBox, NoteRegionBox, TrackBox, ValueClipBox, ValueEventCollectionBox, ValueRegionBox } from "@opendaw/studio-boxes";
|
5
|
+
import { AudioBusBox, AudioUnitBox, CaptureAudioBox, CaptureMidiBox, NoteClipBox, NoteEventBox, NoteEventCollectionBox, NoteRegionBox, TrackBox, ValueClipBox, ValueEventCollectionBox, ValueRegionBox } from "@opendaw/studio-boxes";
|
6
6
|
import { IconSymbol, TrackType } from "@opendaw/studio-adapters";
|
7
7
|
import { ColorCodes } from "./ColorCodes";
|
8
|
+
import { AudioUnitOrdering } from "./AudioUnitOrdering";
|
8
9
|
// noinspection JSUnusedGlobalSymbols
|
9
10
|
export class ProjectApi {
|
10
|
-
static AudioUnitOrdering = {
|
11
|
-
[AudioUnitType.Instrument]: 0,
|
12
|
-
[AudioUnitType.Aux]: 1,
|
13
|
-
[AudioUnitType.Bus]: 2,
|
14
|
-
[AudioUnitType.Output]: 3
|
15
|
-
};
|
16
11
|
#project;
|
17
12
|
constructor(project) { this.#project = project; }
|
18
13
|
setBpm(value) {
|
@@ -34,7 +29,7 @@ export class ProjectApi {
|
|
34
29
|
const inputBox = asDefined(asInstanceOf(box, AudioUnitBox).input.pointerHub.incoming().at(0)).box;
|
35
30
|
return "label" in inputBox && inputBox.label instanceof StringField ? inputBox.label.getValue() : "N/A";
|
36
31
|
});
|
37
|
-
const audioUnitBox = this.#createAudioUnit(AudioUnitType.Instrument, index);
|
32
|
+
const audioUnitBox = this.#createAudioUnit(AudioUnitType.Instrument, this.#trackTypeToCapture(boxGraph, trackType), index);
|
38
33
|
const uniqueName = Strings.getUniqueName(existingNames, name ?? defaultName);
|
39
34
|
const iconSymbol = icon ?? defaultIcon;
|
40
35
|
const instrumentBox = create(boxGraph, audioUnitBox.input, uniqueName, iconSymbol);
|
@@ -58,7 +53,7 @@ export class ProjectApi {
|
|
58
53
|
box.icon.setValue(IconSymbol.toName(icon));
|
59
54
|
box.color.setValue(color);
|
60
55
|
});
|
61
|
-
const audioUnitBox = this.#createAudioUnit(type);
|
56
|
+
const audioUnitBox = this.#createAudioUnit(type, Option.None);
|
62
57
|
TrackBox.create(boxGraph, UUID.generate(), box => {
|
63
58
|
box.tracks.refer(audioUnitBox.tracks);
|
64
59
|
box.target.refer(audioUnitBox);
|
@@ -182,7 +177,7 @@ export class ProjectApi {
|
|
182
177
|
IndexedBox.removeOrder(rootBox.audioUnits, audioUnitBox.index.getValue());
|
183
178
|
audioUnitBox.delete();
|
184
179
|
}
|
185
|
-
#createAudioUnit(type, index) {
|
180
|
+
#createAudioUnit(type, capture, index) {
|
186
181
|
const { boxGraph, rootBox, masterBusBox } = this.#project;
|
187
182
|
const insertIndex = index ?? this.#sortAudioUnitOrdering(type);
|
188
183
|
console.debug(`createAudioUnit type: ${type}, insertIndex: ${insertIndex}`);
|
@@ -191,6 +186,7 @@ export class ProjectApi {
|
|
191
186
|
box.output.refer(masterBusBox.input);
|
192
187
|
box.index.setValue(insertIndex);
|
193
188
|
box.type.setValue(type);
|
189
|
+
capture.ifSome(capture => box.capture.refer(capture));
|
194
190
|
});
|
195
191
|
}
|
196
192
|
#createTrack({ field, target, trackType, insertIndex }) {
|
@@ -203,7 +199,6 @@ export class ProjectApi {
|
|
203
199
|
});
|
204
200
|
}
|
205
201
|
#sortAudioUnitOrdering(type) {
|
206
|
-
const { AudioUnitOrdering } = ProjectApi;
|
207
202
|
const { rootBox } = this.#project;
|
208
203
|
const boxes = IndexedBox.collectIndexedBoxes(rootBox.audioUnits, AudioUnitBox);
|
209
204
|
const order = AudioUnitOrdering[type];
|
@@ -219,4 +214,14 @@ export class ProjectApi {
|
|
219
214
|
}
|
220
215
|
return insertIndex;
|
221
216
|
}
|
217
|
+
#trackTypeToCapture(boxGraph, trackType) {
|
218
|
+
switch (trackType) {
|
219
|
+
case TrackType.Audio:
|
220
|
+
return Option.wrap(CaptureAudioBox.create(boxGraph, UUID.generate()));
|
221
|
+
case TrackType.Notes:
|
222
|
+
return Option.wrap(CaptureMidiBox.create(boxGraph, UUID.generate()));
|
223
|
+
default:
|
224
|
+
return Option.None;
|
225
|
+
}
|
226
|
+
}
|
222
227
|
}
|
package/dist/ProjectEnv.d.ts
CHANGED
package/dist/ProjectEnv.d.ts.map
CHANGED
@@ -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,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,UAAU,EAAE,MAAM,CAAA;IAClB,aAAa,EAAE,aAAa,CAAA;CAC/B"}
|
@@ -1 +1 @@
|
|
1
|
-
{"version":3,"file":"ProjectMigration.d.ts","sourceRoot":"","sources":["../src/ProjectMigration.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,cAAc,EAAC,MAAM,0BAA0B,CAAA;
|
1
|
+
{"version":3,"file":"ProjectMigration.d.ts","sourceRoot":"","sources":["../src/ProjectMigration.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,cAAc,EAAC,MAAM,0BAA0B,CAAA;AAevD,qBAAa,gBAAgB;IACzB,MAAM,CAAC,OAAO,CAAC,EAAC,QAAQ,EAAE,cAAc,EAAC,EAAE,cAAc,CAAC,QAAQ,GAAG,IAAI;CAoE5E"}
|
package/dist/ProjectMigration.js
CHANGED
@@ -1,5 +1,6 @@
|
|
1
|
-
import { GrooveShuffleBox, ValueEventCurveBox } from "@opendaw/studio-boxes";
|
2
|
-
import { asInstanceOf, UUID } from "@opendaw/lib-std";
|
1
|
+
import { CaptureAudioBox, CaptureMidiBox, GrooveShuffleBox, ValueEventCurveBox } from "@opendaw/studio-boxes";
|
2
|
+
import { asDefined, asInstanceOf, UUID } from "@opendaw/lib-std";
|
3
|
+
import { AudioUnitType } from "@opendaw/studio-enums";
|
3
4
|
export class ProjectMigration {
|
4
5
|
static migrate({ boxGraph, mandatoryBoxes }) {
|
5
6
|
const { rootBox } = mandatoryBoxes;
|
@@ -16,7 +17,8 @@ export class ProjectMigration {
|
|
16
17
|
boxGraph.endTransaction();
|
17
18
|
}
|
18
19
|
// TODO We can remove this when we delete all not-migrated, local(!) project files from my machine
|
19
|
-
|
20
|
+
// We need to run on a copy, because we might add more boxes during the migration
|
21
|
+
boxGraph.boxes().slice().forEach(box => box.accept({
|
20
22
|
visitZeitgeistDeviceBox: (box) => {
|
21
23
|
if (box.groove.targetAddress.isEmpty()) {
|
22
24
|
console.debug("Migrate 'ZeitgeistDeviceBox' to GrooveShuffleBox");
|
@@ -54,6 +56,21 @@ export class ProjectMigration {
|
|
54
56
|
boxGraph.endTransaction();
|
55
57
|
}
|
56
58
|
}
|
59
|
+
},
|
60
|
+
visitAudioUnitBox: (box) => {
|
61
|
+
if (box.type.getValue() !== AudioUnitType.Instrument || box.capture.nonEmpty()) {
|
62
|
+
return;
|
63
|
+
}
|
64
|
+
boxGraph.beginTransaction();
|
65
|
+
const captureBox = asDefined(box.input.pointerHub.incoming().at(0)?.box
|
66
|
+
.accept({
|
67
|
+
visitVaporisateurDeviceBox: () => CaptureMidiBox.create(boxGraph, UUID.generate()),
|
68
|
+
visitNanoDeviceBox: () => CaptureMidiBox.create(boxGraph, UUID.generate()),
|
69
|
+
visitPlayfieldDeviceBox: () => CaptureMidiBox.create(boxGraph, UUID.generate()),
|
70
|
+
visitTapeDeviceBox: () => CaptureAudioBox.create(boxGraph, UUID.generate())
|
71
|
+
}));
|
72
|
+
box.capture.refer(captureBox);
|
73
|
+
boxGraph.endTransaction();
|
57
74
|
}
|
58
75
|
}));
|
59
76
|
}
|
@@ -1,6 +1,18 @@
|
|
1
|
-
import {
|
2
|
-
|
1
|
+
import { int, Observer, Option, Subscription, Terminable, UUID } from "@opendaw/lib-std";
|
2
|
+
import { AudioData, RingBuffer, SampleLoader, SampleLoaderState } from "@opendaw/studio-adapters";
|
3
|
+
import { Peaks } from "@opendaw/lib-fusion";
|
4
|
+
export declare class RecordingWorklet extends AudioWorkletNode implements Terminable, SampleLoader {
|
3
5
|
#private;
|
4
|
-
|
6
|
+
readonly uuid: UUID.Format;
|
7
|
+
constructor(context: BaseAudioContext, config: RingBuffer.Config, outputLatency: number);
|
8
|
+
get numberOfFrames(): int;
|
9
|
+
get data(): Option<AudioData>;
|
10
|
+
get peaks(): Option<Peaks>;
|
11
|
+
get state(): SampleLoaderState;
|
12
|
+
invalidate(): void;
|
13
|
+
subscribe(observer: Observer<SampleLoaderState>): Subscription;
|
14
|
+
finalize(): Promise<void>;
|
15
|
+
terminate(): void;
|
16
|
+
toString(): string;
|
5
17
|
}
|
6
18
|
//# sourceMappingURL=RecordingWorklet.d.ts.map
|
@@ -1 +1 @@
|
|
1
|
-
{"version":3,"file":"RecordingWorklet.d.ts","sourceRoot":"","sources":["../src/RecordingWorklet.ts"],"names":[],"mappings":"
|
1
|
+
{"version":3,"file":"RecordingWorklet.d.ts","sourceRoot":"","sources":["../src/RecordingWorklet.ts"],"names":[],"mappings":"AAAA,OAAO,EAIH,GAAG,EAIH,QAAQ,EACR,MAAM,EAEN,YAAY,EACZ,UAAU,EACV,IAAI,EACP,MAAM,kBAAkB,CAAA;AACzB,OAAO,EACH,SAAS,EAET,UAAU,EACV,YAAY,EACZ,iBAAiB,EAEpB,MAAM,0BAA0B,CAAA;AACjC,OAAO,EAAC,KAAK,EAAgC,MAAM,qBAAqB,CAAA;AA2CxE,qBAAa,gBAAiB,SAAQ,gBAAiB,YAAW,UAAU,EAAE,YAAY;;IACtF,QAAQ,CAAC,IAAI,EAAE,IAAI,CAAC,MAAM,CAAkB;gBAahC,OAAO,EAAE,gBAAgB,EAAE,MAAM,EAAE,UAAU,CAAC,MAAM,EAAE,aAAa,EAAE,MAAM;IAgCvF,IAAI,cAAc,IAAI,GAAG,CAA6C;IACtE,IAAI,IAAI,IAAI,MAAM,CAAC,SAAS,CAAC,CAAoB;IACjD,IAAI,KAAK,IAAI,MAAM,CAAC,KAAK,CAAC,CAAqB;IAC/C,IAAI,KAAK,IAAI,iBAAiB,CAAqB;IAEnD,UAAU,IAAI,IAAI;IAElB,SAAS,CAAC,QAAQ,EAAE,QAAQ,CAAC,iBAAiB,CAAC,GAAG,YAAY;IAQxD,QAAQ;IAyBd,SAAS,IAAI,IAAI;IAKjB,QAAQ,IAAI,MAAM;CAMrB"}
|
package/dist/RecordingWorklet.js
CHANGED
@@ -1,31 +1,126 @@
|
|
1
|
-
import {
|
2
|
-
import { Files } from "@opendaw/lib-dom";
|
1
|
+
import { Arrays, assert, ByteArrayInput, isUndefined, Notifier, Option, Progress, Terminable, UUID } from "@opendaw/lib-std";
|
3
2
|
import { mergeChunkPlanes, RingBuffer } from "@opendaw/studio-adapters";
|
4
|
-
import {
|
3
|
+
import { SamplePeaks, SamplePeakWorker } from "@opendaw/lib-fusion";
|
5
4
|
import { RenderQuantum } from "./RenderQuantum";
|
5
|
+
import { WorkerAgents } from "./WorkerAgents";
|
6
|
+
import { SampleStorage } from "./samples/SampleStorage";
|
7
|
+
import { BPMTools } from "@opendaw/lib-dsp";
|
8
|
+
class PeaksWriter {
|
9
|
+
numChannels;
|
10
|
+
data;
|
11
|
+
stages;
|
12
|
+
dataOffset = 0;
|
13
|
+
shift = 7;
|
14
|
+
dataIndex;
|
15
|
+
numFrames = 0 | 0;
|
16
|
+
constructor(numChannels) {
|
17
|
+
this.numChannels = numChannels;
|
18
|
+
this.data = Arrays.create(() => new Int32Array(1 << 20), numChannels); // TODO auto-resize
|
19
|
+
this.dataIndex = new Int32Array(numChannels);
|
20
|
+
this.stages = [this];
|
21
|
+
}
|
22
|
+
get numPeaks() { return Math.ceil(this.numFrames / (1 << this.shift)); }
|
23
|
+
unitsEachPeak() { return 1 << this.shift; }
|
24
|
+
append(frames) {
|
25
|
+
for (let channel = 0; channel < this.numChannels; ++channel) {
|
26
|
+
const channelFrames = frames[channel];
|
27
|
+
assert(channelFrames.length === RenderQuantum, "Invalid number of frames.");
|
28
|
+
let min = Number.POSITIVE_INFINITY;
|
29
|
+
let max = Number.NEGATIVE_INFINITY;
|
30
|
+
for (let i = 0; i < RenderQuantum; ++i) {
|
31
|
+
const frame = channelFrames[i];
|
32
|
+
min = Math.min(frame, min);
|
33
|
+
max = Math.max(frame, max);
|
34
|
+
}
|
35
|
+
this.data[channel][this.dataIndex[channel]++] = SamplePeakWorker.pack(min, max);
|
36
|
+
}
|
37
|
+
this.numFrames += RenderQuantum;
|
38
|
+
}
|
39
|
+
nearest(_unitsPerPixel) { return this.stages.at(0) ?? null; }
|
40
|
+
}
|
6
41
|
export class RecordingWorklet extends AudioWorkletNode {
|
42
|
+
uuid = UUID.generate();
|
43
|
+
#output;
|
44
|
+
#notifier;
|
7
45
|
#reader;
|
8
|
-
|
46
|
+
#peakWriter;
|
47
|
+
#data = Option.None;
|
48
|
+
#peaks = Option.None;
|
49
|
+
#isRecording = true;
|
50
|
+
#truncateLatency;
|
51
|
+
#state = { type: "record" };
|
52
|
+
constructor(context, config, outputLatency) {
|
9
53
|
super(context, "recording-processor", {
|
10
54
|
numberOfInputs: 1,
|
11
55
|
channelCount: config.numberOfChannels,
|
12
56
|
channelCountMode: "explicit",
|
13
57
|
processorOptions: config
|
14
58
|
});
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
59
|
+
if (isUndefined(outputLatency)) {
|
60
|
+
// TODO Talk to the user
|
61
|
+
console.warn("outputLatency is undefined. Please use Chrome.");
|
62
|
+
}
|
63
|
+
this.#peakWriter = new PeaksWriter(config.numberOfChannels);
|
64
|
+
this.#truncateLatency = Math.floor((outputLatency ?? 0) * this.context.sampleRate / RenderQuantum);
|
65
|
+
this.#output = [];
|
66
|
+
this.#notifier = new Notifier();
|
20
67
|
this.#reader = RingBuffer.reader(config, array => {
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
68
|
+
if (this.#isRecording) {
|
69
|
+
if (this.#truncateLatency === 0) {
|
70
|
+
this.#output.push(array);
|
71
|
+
this.#peakWriter.append(array);
|
72
|
+
}
|
73
|
+
else {
|
74
|
+
if (--this.#truncateLatency === 0) {
|
75
|
+
this.#peaks = Option.wrap(this.#peakWriter);
|
76
|
+
}
|
77
|
+
}
|
28
78
|
}
|
29
79
|
});
|
30
80
|
}
|
81
|
+
get numberOfFrames() { return this.#output.length * RenderQuantum; }
|
82
|
+
get data() { return this.#data; }
|
83
|
+
get peaks() { return this.#peaks; }
|
84
|
+
get state() { return this.#state; }
|
85
|
+
invalidate() { }
|
86
|
+
subscribe(observer) {
|
87
|
+
if (this.#state.type === "loaded") {
|
88
|
+
observer(this.#state);
|
89
|
+
return Terminable.Empty;
|
90
|
+
}
|
91
|
+
return this.#notifier.subscribe(observer);
|
92
|
+
}
|
93
|
+
async finalize() {
|
94
|
+
this.#reader.stop();
|
95
|
+
this.#isRecording = false;
|
96
|
+
const sample_rate = this.context.sampleRate;
|
97
|
+
const numberOfFrames = this.#output.length * RenderQuantum;
|
98
|
+
const numberOfChannels = this.channelCount;
|
99
|
+
const frames = mergeChunkPlanes(this.#output, RenderQuantum, numberOfFrames);
|
100
|
+
const audioData = {
|
101
|
+
sampleRate: sample_rate,
|
102
|
+
numberOfChannels,
|
103
|
+
numberOfFrames,
|
104
|
+
frames
|
105
|
+
};
|
106
|
+
this.#data = Option.wrap(audioData);
|
107
|
+
const shifts = SamplePeaks.findBestFit(numberOfFrames);
|
108
|
+
const peaks = await WorkerAgents
|
109
|
+
.Peak.generateAsync(Progress.Empty, shifts, frames, numberOfFrames, numberOfChannels);
|
110
|
+
this.#peaks = Option.wrap(SamplePeaks.from(new ByteArrayInput(peaks)));
|
111
|
+
const bpm = BPMTools.detect(frames[0], sample_rate);
|
112
|
+
const duration = numberOfFrames / sample_rate;
|
113
|
+
const meta = { name: "Recording", bpm, sample_rate, duration };
|
114
|
+
await SampleStorage.store(this.uuid, audioData, peaks, meta);
|
115
|
+
this.#setState({ type: "loaded" });
|
116
|
+
}
|
117
|
+
terminate() {
|
118
|
+
this.#reader.stop();
|
119
|
+
this.#isRecording = false;
|
120
|
+
}
|
121
|
+
toString() { return `{RecordingWorklet}`; }
|
122
|
+
#setState(value) {
|
123
|
+
this.#state = value;
|
124
|
+
this.#notifier.notify(this.#state);
|
125
|
+
}
|
31
126
|
}
|
package/dist/WorkerAgents.d.ts
CHANGED
@@ -1,10 +1,10 @@
|
|
1
1
|
import { Option } from "@opendaw/lib-std";
|
2
|
-
import type { OpfsProtocol,
|
2
|
+
import type { OpfsProtocol, SamplePeakProtocol } from "@opendaw/lib-fusion";
|
3
3
|
import { Messenger } from "@opendaw/lib-runtime";
|
4
4
|
export declare class WorkerAgents {
|
5
5
|
static install(workerURL: string): void;
|
6
6
|
static messenger: Option<Messenger>;
|
7
|
-
static get Peak():
|
7
|
+
static get Peak(): SamplePeakProtocol;
|
8
8
|
static get Opfs(): OpfsProtocol;
|
9
9
|
}
|
10
10
|
//# sourceMappingURL=WorkerAgents.d.ts.map
|
@@ -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,
|
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"}
|
package/dist/Worklets.d.ts
CHANGED
@@ -11,6 +11,6 @@ export declare class Worklets {
|
|
11
11
|
constructor(context: BaseAudioContext);
|
12
12
|
createMeter(numberOfChannels: int): MeterWorklet;
|
13
13
|
createEngine(project: Project, exportConfiguration?: ExportStemsConfiguration): EngineWorklet;
|
14
|
-
createRecording(numberOfChannels: int, numChunks: int): RecordingWorklet;
|
14
|
+
createRecording(numberOfChannels: int, numChunks: int, outputLatency: number): RecordingWorklet;
|
15
15
|
}
|
16
16
|
//# sourceMappingURL=Worklets.d.ts.map
|
package/dist/Worklets.d.ts.map
CHANGED
@@ -1 +1 @@
|
|
1
|
-
{"version":3,"file":"Worklets.d.ts","sourceRoot":"","sources":["../src/Worklets.ts"],"names":[],"mappings":"AAAA,OAAO,EAAY,GAAG,EAAC,MAAM,kBAAkB,CAAA;AAC/C,OAAO,EAAC,wBAAwB,EAAa,MAAM,0BAA0B,CAAA;AAC7E,OAAO,EAAC,aAAa,EAAC,MAAM,iBAAiB,CAAA;AAC7C,OAAO,EAAC,YAAY,EAAC,MAAM,gBAAgB,CAAA;AAC3C,OAAO,EAAC,gBAAgB,EAAC,MAAM,oBAAoB,CAAA;AACnD,OAAO,EAAC,OAAO,EAAC,MAAM,WAAW,CAAA;AAGjC,qBAAa,QAAQ;;WACJ,OAAO,CAAC,OAAO,EAAE,gBAAgB,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,CAAC;IAQtF,MAAM,CAAC,GAAG,CAAC,OAAO,EAAE,gBAAgB,GAAG,QAAQ;gBAMnC,OAAO,EAAE,gBAAgB;IAErC,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,GAAG,gBAAgB;
|
1
|
+
{"version":3,"file":"Worklets.d.ts","sourceRoot":"","sources":["../src/Worklets.ts"],"names":[],"mappings":"AAAA,OAAO,EAAY,GAAG,EAAC,MAAM,kBAAkB,CAAA;AAC/C,OAAO,EAAC,wBAAwB,EAAa,MAAM,0BAA0B,CAAA;AAC7E,OAAO,EAAC,aAAa,EAAC,MAAM,iBAAiB,CAAA;AAC7C,OAAO,EAAC,YAAY,EAAC,MAAM,gBAAgB,CAAA;AAC3C,OAAO,EAAC,gBAAgB,EAAC,MAAM,oBAAoB,CAAA;AACnD,OAAO,EAAC,OAAO,EAAC,MAAM,WAAW,CAAA;AAGjC,qBAAa,QAAQ;;WACJ,OAAO,CAAC,OAAO,EAAE,gBAAgB,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,CAAC;IAQtF,MAAM,CAAC,GAAG,CAAC,OAAO,EAAE,gBAAgB,GAAG,QAAQ;gBAMnC,OAAO,EAAE,gBAAgB;IAErC,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"}
|
package/dist/Worklets.js
CHANGED
@@ -21,11 +21,11 @@ export class Worklets {
|
|
21
21
|
createEngine(project, exportConfiguration) {
|
22
22
|
return new EngineWorklet(this.#context, project, exportConfiguration);
|
23
23
|
}
|
24
|
-
createRecording(numberOfChannels, numChunks) {
|
24
|
+
createRecording(numberOfChannels, numChunks, outputLatency) {
|
25
25
|
const audioBytes = numberOfChannels * numChunks * RenderQuantum * Float32Array.BYTES_PER_ELEMENT;
|
26
26
|
const pointerBytes = Int32Array.BYTES_PER_ELEMENT * 2;
|
27
27
|
const sab = new SharedArrayBuffer(audioBytes + pointerBytes);
|
28
28
|
const buffer = { sab, numChunks, numberOfChannels, bufferSize: RenderQuantum };
|
29
|
-
return new RecordingWorklet(this.#context, buffer);
|
29
|
+
return new RecordingWorklet(this.#context, buffer, outputLatency);
|
30
30
|
}
|
31
31
|
}
|
@@ -0,0 +1,22 @@
|
|
1
|
+
import { MutableObservableValue, Option, Terminable, UUID } from "@opendaw/lib-std";
|
2
|
+
import { AudioUnitBox } from "@opendaw/studio-boxes";
|
3
|
+
import { CaptureBox } from "@opendaw/studio-adapters";
|
4
|
+
import { RecordingContext } from "./RecordingContext";
|
5
|
+
import { CaptureManager } from "./CaptureManager";
|
6
|
+
export declare abstract class Capture<BOX extends CaptureBox = CaptureBox> implements Terminable {
|
7
|
+
#private;
|
8
|
+
protected constructor(manager: CaptureManager, audioUnitBox: AudioUnitBox, captureBox: BOX);
|
9
|
+
abstract prepareRecording(context: RecordingContext): Promise<void>;
|
10
|
+
abstract startRecording(context: RecordingContext): Terminable;
|
11
|
+
abstract get deviceLabel(): Option<string>;
|
12
|
+
get uuid(): UUID.Format;
|
13
|
+
get manager(): CaptureManager;
|
14
|
+
get audioUnitBox(): AudioUnitBox;
|
15
|
+
get captureBox(): BOX;
|
16
|
+
get armed(): MutableObservableValue<boolean>;
|
17
|
+
get deviceId(): MutableObservableValue<Option<string>>;
|
18
|
+
own<T extends Terminable>(terminable: T): T;
|
19
|
+
ownAll<T extends Terminable>(...terminables: ReadonlyArray<T>): void;
|
20
|
+
terminate(): void;
|
21
|
+
}
|
22
|
+
//# sourceMappingURL=Capture.d.ts.map
|
@@ -0,0 +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"}
|
@@ -0,0 +1,32 @@
|
|
1
|
+
import { DefaultObservableValue, MappedMutableObservableValue, Option, Terminator } from "@opendaw/lib-std";
|
2
|
+
export class Capture {
|
3
|
+
#terminator = new Terminator();
|
4
|
+
#manager;
|
5
|
+
#audioUnitBox;
|
6
|
+
#captureBox;
|
7
|
+
#deviceId;
|
8
|
+
#armed;
|
9
|
+
constructor(manager, audioUnitBox, captureBox) {
|
10
|
+
this.#manager = manager;
|
11
|
+
this.#audioUnitBox = audioUnitBox;
|
12
|
+
this.#captureBox = captureBox;
|
13
|
+
this.#deviceId = new MappedMutableObservableValue(captureBox.deviceId, {
|
14
|
+
fx: x => x.length > 0 ? Option.wrap(x) : Option.None,
|
15
|
+
fy: y => y.match({ none: () => "", some: x => x })
|
16
|
+
});
|
17
|
+
this.#armed = this.#terminator.own(new DefaultObservableValue(false));
|
18
|
+
this.#terminator.ownAll(this.#captureBox.deviceId.catchupAndSubscribe(owner => {
|
19
|
+
const id = owner.getValue();
|
20
|
+
this.#deviceId.setValue(id.length > 0 ? Option.wrap(id) : Option.None);
|
21
|
+
}));
|
22
|
+
}
|
23
|
+
get uuid() { return this.#audioUnitBox.address.uuid; }
|
24
|
+
get manager() { return this.#manager; }
|
25
|
+
get audioUnitBox() { return this.#audioUnitBox; }
|
26
|
+
get captureBox() { return this.#captureBox; }
|
27
|
+
get armed() { return this.#armed; }
|
28
|
+
get deviceId() { return this.#deviceId; }
|
29
|
+
own(terminable) { return this.#terminator.own(terminable); }
|
30
|
+
ownAll(...terminables) { this.#terminator.ownAll(...terminables); }
|
31
|
+
terminate() { this.#terminator.terminate(); }
|
32
|
+
}
|
@@ -0,0 +1,17 @@
|
|
1
|
+
import { ObservableOption, Option, Terminable } from "@opendaw/lib-std";
|
2
|
+
import { AudioUnitBox, CaptureAudioBox } from "@opendaw/studio-boxes";
|
3
|
+
import { Capture } from "./Capture";
|
4
|
+
import { RecordingContext } from "./RecordingContext";
|
5
|
+
import { CaptureManager } from "./CaptureManager";
|
6
|
+
export declare class CaptureAudio extends Capture<CaptureAudioBox> {
|
7
|
+
#private;
|
8
|
+
constructor(manager: CaptureManager, audioUnitBox: AudioUnitBox, captureBox: CaptureAudioBox);
|
9
|
+
get gainDb(): number;
|
10
|
+
get stream(): ObservableOption<MediaStream>;
|
11
|
+
get streamDeviceId(): Option<string>;
|
12
|
+
get deviceLabel(): Option<string>;
|
13
|
+
get streamMediaTrack(): Option<MediaStreamTrack>;
|
14
|
+
prepareRecording({}: RecordingContext): Promise<void>;
|
15
|
+
startRecording({ audioContext, worklets, project, engine, sampleManager }: RecordingContext): Terminable;
|
16
|
+
}
|
17
|
+
//# sourceMappingURL=CaptureAudio.d.ts.map
|
@@ -0,0 +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"}
|
@@ -0,0 +1,106 @@
|
|
1
|
+
import { assert, isDefined, isUndefined, ObservableOption, Option, warn } from "@opendaw/lib-std";
|
2
|
+
import { Capture } from "./Capture";
|
3
|
+
import { RecordAudio } from "./RecordAudio";
|
4
|
+
import { AudioInputDevices } from "../AudioInputDevices";
|
5
|
+
import { Promises } from "@opendaw/lib-runtime";
|
6
|
+
export class CaptureAudio extends Capture {
|
7
|
+
#stream;
|
8
|
+
#streamGenerator;
|
9
|
+
#requestChannels = Option.None;
|
10
|
+
#gainDb = 0.0;
|
11
|
+
constructor(manager, audioUnitBox, captureBox) {
|
12
|
+
super(manager, audioUnitBox, captureBox);
|
13
|
+
this.#stream = new ObservableOption();
|
14
|
+
this.#streamGenerator = Promises.sequential(() => this.#updateStream());
|
15
|
+
this.ownAll(captureBox.requestChannels.catchupAndSubscribe(owner => {
|
16
|
+
const channels = owner.getValue();
|
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 () => {
|
19
|
+
if (this.armed.getValue()) {
|
20
|
+
await this.#streamGenerator();
|
21
|
+
}
|
22
|
+
}), this.armed.catchupAndSubscribe(async (owner) => {
|
23
|
+
const armed = owner.getValue();
|
24
|
+
if (armed) {
|
25
|
+
await this.#streamGenerator();
|
26
|
+
}
|
27
|
+
else {
|
28
|
+
this.#stopStream();
|
29
|
+
}
|
30
|
+
}));
|
31
|
+
}
|
32
|
+
get gainDb() { return this.#gainDb; }
|
33
|
+
get stream() { return this.#stream; }
|
34
|
+
get streamDeviceId() {
|
35
|
+
return this.streamMediaTrack.map(settings => settings.getSettings().deviceId ?? "");
|
36
|
+
}
|
37
|
+
get deviceLabel() {
|
38
|
+
return this.streamMediaTrack.map(track => track.label ?? "");
|
39
|
+
}
|
40
|
+
get streamMediaTrack() {
|
41
|
+
return this.#stream.flatMap(stream => Option.wrap(stream.getAudioTracks().at(0)));
|
42
|
+
}
|
43
|
+
async prepareRecording({}) {
|
44
|
+
return this.#streamGenerator();
|
45
|
+
}
|
46
|
+
startRecording({ audioContext, worklets, project, engine, sampleManager }) {
|
47
|
+
const streamOption = this.#stream;
|
48
|
+
assert(streamOption.nonEmpty(), "Stream not prepared.");
|
49
|
+
const mediaStream = streamOption.unwrap();
|
50
|
+
const channelCount = mediaStream.getAudioTracks().at(0)?.getSettings().channelCount ?? 1;
|
51
|
+
const numChunks = 128;
|
52
|
+
return RecordAudio.start({
|
53
|
+
recordingWorklet: worklets.createRecording(channelCount, numChunks, audioContext.outputLatency),
|
54
|
+
mediaStream,
|
55
|
+
sampleManager,
|
56
|
+
audioContext,
|
57
|
+
engine,
|
58
|
+
project,
|
59
|
+
capture: this,
|
60
|
+
gainDb: this.#gainDb
|
61
|
+
});
|
62
|
+
}
|
63
|
+
async #updateStream() {
|
64
|
+
if (this.#stream.nonEmpty()) {
|
65
|
+
const stream = this.#stream.unwrap();
|
66
|
+
const settings = stream.getAudioTracks().at(0)?.getSettings();
|
67
|
+
console.debug(stream.getAudioTracks());
|
68
|
+
if (isDefined(settings)) {
|
69
|
+
const deviceId = this.deviceId.getValue().unwrapOrUndefined();
|
70
|
+
const channelCount = this.#requestChannels.unwrapOrElse(1);
|
71
|
+
const satisfyChannelCount = settings.channelCount === channelCount;
|
72
|
+
const satisfiedDeviceId = isUndefined(deviceId) || deviceId === settings.deviceId;
|
73
|
+
if (satisfiedDeviceId && satisfyChannelCount) {
|
74
|
+
return Promise.resolve();
|
75
|
+
}
|
76
|
+
}
|
77
|
+
}
|
78
|
+
this.#stopStream();
|
79
|
+
const deviceId = this.deviceId.getValue().unwrapOrUndefined();
|
80
|
+
const channelCount = this.#requestChannels.unwrapOrElse(1); // as of today, browsers cap MediaStream audio to stereo.
|
81
|
+
return AudioInputDevices.requestStream({
|
82
|
+
deviceId: { exact: deviceId },
|
83
|
+
sampleRate: this.manager.project.env.sampleRate,
|
84
|
+
sampleSize: 32,
|
85
|
+
echoCancellation: false,
|
86
|
+
noiseSuppression: false,
|
87
|
+
autoGainControl: false,
|
88
|
+
channelCount: { exact: channelCount }
|
89
|
+
}).then(stream => {
|
90
|
+
const tracks = stream.getAudioTracks();
|
91
|
+
const settings = tracks.at(0)?.getSettings();
|
92
|
+
const gotDeviceId = settings?.deviceId;
|
93
|
+
console.debug(`new stream id: ${stream.id}, device: ${gotDeviceId ?? "Default"}`);
|
94
|
+
if (isUndefined(deviceId) || deviceId === gotDeviceId) {
|
95
|
+
this.#stream.wrap(stream);
|
96
|
+
}
|
97
|
+
else {
|
98
|
+
stream.getAudioTracks().forEach(track => track.stop());
|
99
|
+
return warn(`Could not find audio device with id: '${deviceId} in ${gotDeviceId}'`);
|
100
|
+
}
|
101
|
+
});
|
102
|
+
}
|
103
|
+
#stopStream() {
|
104
|
+
this.#stream.clear(stream => stream.getAudioTracks().forEach(track => track.stop()));
|
105
|
+
}
|
106
|
+
}
|
@@ -0,0 +1,12 @@
|
|
1
|
+
import { Option, Terminable, UUID } from "@opendaw/lib-std";
|
2
|
+
import { Project } from "../Project";
|
3
|
+
import { Capture } from "./Capture";
|
4
|
+
export declare class CaptureManager implements Terminable {
|
5
|
+
#private;
|
6
|
+
constructor(project: Project);
|
7
|
+
get project(): Project;
|
8
|
+
get(uuid: UUID.Format): Option<Capture>;
|
9
|
+
filterArmed(): ReadonlyArray<Capture>;
|
10
|
+
terminate(): void;
|
11
|
+
}
|
12
|
+
//# sourceMappingURL=CaptureManager.d.ts.map
|
@@ -0,0 +1 @@
|
|
1
|
+
{"version":3,"file":"CaptureManager.d.ts","sourceRoot":"","sources":["../../src/capture/CaptureManager.ts"],"names":[],"mappings":"AAAA,OAAO,EAAmC,MAAM,EAA2B,UAAU,EAAE,IAAI,EAAC,MAAM,kBAAkB,CAAA;AAEpH,OAAO,EAAC,OAAO,EAAC,MAAM,YAAY,CAAA;AAClC,OAAO,EAAC,OAAO,EAAC,MAAM,WAAW,CAAA;AAIjC,qBAAa,cAAe,YAAW,UAAU;;gBAKjC,OAAO,EAAE,OAAO;IAiB5B,IAAI,OAAO,IAAI,OAAO,CAAuB;IAE7C,GAAG,CAAC,IAAI,EAAE,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC,OAAO,CAAC;IAEvC,WAAW,IAAI,aAAa,CAAC,OAAO,CAAC;IAKrC,SAAS,IAAI,IAAI;CAKpB"}
|