@opendaw/studio-core 0.0.95 → 0.0.97
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/EffectBox.d.ts +2 -2
- package/dist/EffectBox.d.ts.map +1 -1
- package/dist/EffectFactories.d.ts +3 -0
- package/dist/EffectFactories.d.ts.map +1 -1
- package/dist/EffectFactories.js +15 -2
- package/dist/Mixer.js +1 -1
- package/dist/capture/CaptureAudio.d.ts +4 -0
- package/dist/capture/CaptureAudio.d.ts.map +1 -1
- package/dist/capture/CaptureAudio.js +46 -23
- package/dist/capture/RecordAudio.d.ts +2 -4
- package/dist/capture/RecordAudio.d.ts.map +1 -1
- package/dist/capture/RecordAudio.js +23 -42
- package/dist/processors.js +8 -8
- package/dist/processors.js.map +4 -4
- package/package.json +13 -13
package/dist/EffectBox.d.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import { ArpeggioDeviceBox, MaximizerDeviceBox, CompressorDeviceBox, CrusherDeviceBox, DattorroReverbDeviceBox, DelayDeviceBox, FoldDeviceBox, ModularDeviceBox, PitchDeviceBox, RevampDeviceBox, ReverbDeviceBox, StereoToolDeviceBox, TidalDeviceBox, UnknownAudioEffectDeviceBox, UnknownMidiEffectDeviceBox, VelocityDeviceBox, ZeitgeistDeviceBox } from "@opendaw/studio-boxes";
|
|
2
|
-
export type EffectBox = ArpeggioDeviceBox | PitchDeviceBox | VelocityDeviceBox | ZeitgeistDeviceBox | UnknownMidiEffectDeviceBox | MaximizerDeviceBox | DelayDeviceBox | ReverbDeviceBox | RevampDeviceBox | StereoToolDeviceBox | TidalDeviceBox | ModularDeviceBox | UnknownAudioEffectDeviceBox | CompressorDeviceBox | CrusherDeviceBox | FoldDeviceBox | DattorroReverbDeviceBox;
|
|
1
|
+
import { ArpeggioDeviceBox, MaximizerDeviceBox, CompressorDeviceBox, CrusherDeviceBox, DattorroReverbDeviceBox, DelayDeviceBox, FoldDeviceBox, GateDeviceBox, ModularDeviceBox, PitchDeviceBox, RevampDeviceBox, ReverbDeviceBox, StereoToolDeviceBox, TidalDeviceBox, UnknownAudioEffectDeviceBox, UnknownMidiEffectDeviceBox, VelocityDeviceBox, ZeitgeistDeviceBox } from "@opendaw/studio-boxes";
|
|
2
|
+
export type EffectBox = ArpeggioDeviceBox | PitchDeviceBox | VelocityDeviceBox | ZeitgeistDeviceBox | UnknownMidiEffectDeviceBox | MaximizerDeviceBox | DelayDeviceBox | ReverbDeviceBox | RevampDeviceBox | StereoToolDeviceBox | TidalDeviceBox | ModularDeviceBox | UnknownAudioEffectDeviceBox | CompressorDeviceBox | GateDeviceBox | CrusherDeviceBox | FoldDeviceBox | DattorroReverbDeviceBox;
|
|
3
3
|
//# sourceMappingURL=EffectBox.d.ts.map
|
package/dist/EffectBox.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"EffectBox.d.ts","sourceRoot":"","sources":["../src/EffectBox.ts"],"names":[],"mappings":"AAAA,OAAO,EACH,iBAAiB,EACjB,kBAAkB,EAClB,mBAAmB,EACnB,gBAAgB,EAChB,uBAAuB,EACvB,cAAc,EACd,aAAa,EACb,gBAAgB,EAChB,cAAc,EACd,eAAe,EACf,eAAe,EACf,mBAAmB,EACnB,cAAc,EACd,2BAA2B,EAC3B,0BAA0B,EAC1B,iBAAiB,EACjB,kBAAkB,EACrB,MAAM,uBAAuB,CAAA;AAE9B,MAAM,MAAM,SAAS,GACf,iBAAiB,GAAG,cAAc,GAAG,iBAAiB,GAAG,kBAAkB,GAAG,0BAA0B,GACxG,kBAAkB,GAAG,cAAc,GAAG,eAAe,GAAG,eAAe,GAAG,mBAAmB,GAAG,cAAc,GAC9G,gBAAgB,GAAG,2BAA2B,GAAG,mBAAmB,GAAG,gBAAgB,GAAG,aAAa,
|
|
1
|
+
{"version":3,"file":"EffectBox.d.ts","sourceRoot":"","sources":["../src/EffectBox.ts"],"names":[],"mappings":"AAAA,OAAO,EACH,iBAAiB,EACjB,kBAAkB,EAClB,mBAAmB,EACnB,gBAAgB,EAChB,uBAAuB,EACvB,cAAc,EACd,aAAa,EACb,aAAa,EACb,gBAAgB,EAChB,cAAc,EACd,eAAe,EACf,eAAe,EACf,mBAAmB,EACnB,cAAc,EACd,2BAA2B,EAC3B,0BAA0B,EAC1B,iBAAiB,EACjB,kBAAkB,EACrB,MAAM,uBAAuB,CAAA;AAE9B,MAAM,MAAM,SAAS,GACf,iBAAiB,GAAG,cAAc,GAAG,iBAAiB,GAAG,kBAAkB,GAAG,0BAA0B,GACxG,kBAAkB,GAAG,cAAc,GAAG,eAAe,GAAG,eAAe,GAAG,mBAAmB,GAAG,cAAc,GAC9G,gBAAgB,GAAG,2BAA2B,GAAG,mBAAmB,GAAG,aAAa,GACpF,gBAAgB,GAAG,aAAa,GAAG,uBAAuB,CAAA"}
|
|
@@ -9,6 +9,7 @@ export declare namespace EffectFactories {
|
|
|
9
9
|
const DattorroReverb: EffectFactory;
|
|
10
10
|
const Maximizer: EffectFactory;
|
|
11
11
|
const Compressor: EffectFactory;
|
|
12
|
+
const Gate: EffectFactory;
|
|
12
13
|
const Reverb: EffectFactory;
|
|
13
14
|
const Crusher: EffectFactory;
|
|
14
15
|
const Fold: EffectFactory;
|
|
@@ -24,6 +25,7 @@ export declare namespace EffectFactories {
|
|
|
24
25
|
const AudioNamed: {
|
|
25
26
|
StereoTool: EffectFactory;
|
|
26
27
|
Compressor: EffectFactory;
|
|
28
|
+
Gate: EffectFactory;
|
|
27
29
|
Delay: EffectFactory;
|
|
28
30
|
Reverb: EffectFactory;
|
|
29
31
|
DattorroReverb: EffectFactory;
|
|
@@ -38,6 +40,7 @@ export declare namespace EffectFactories {
|
|
|
38
40
|
const MergedNamed: {
|
|
39
41
|
StereoTool: EffectFactory;
|
|
40
42
|
Compressor: EffectFactory;
|
|
43
|
+
Gate: EffectFactory;
|
|
41
44
|
Delay: EffectFactory;
|
|
42
45
|
Reverb: EffectFactory;
|
|
43
46
|
DattorroReverb: EffectFactory;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"EffectFactories.d.ts","sourceRoot":"","sources":["../src/EffectFactories.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"EffectFactories.d.ts","sourceRoot":"","sources":["../src/EffectFactories.ts"],"names":[],"mappings":"AA0BA,OAAO,EAAC,aAAa,EAAC,MAAM,iBAAiB,CAAA;AAG7C,yBAAiB,eAAe,CAAC;IACtB,MAAM,QAAQ,EAAE,aAYtB,CAAA;IAEM,MAAM,KAAK,EAAE,aAYnB,CAAA;IAEM,MAAM,QAAQ,EAAE,aAYtB,CAAA;IAEM,MAAM,SAAS,EAAE,aAsBvB,CAAA;IAEM,MAAM,UAAU,EAAE,aAaxB,CAAA;IAEM,MAAM,KAAK,EAAE,aAcnB,CAAA;IAEM,MAAM,cAAc,EAAE,aAa5B,CAAA;IAEM,MAAM,SAAS,EAAE,aAavB,CAAA;IAEM,MAAM,UAAU,EAAE,aAaxB,CAAA;IAEM,MAAM,IAAI,EAAE,aAalB,CAAA;IAEM,MAAM,MAAM,EAAE,aAcpB,CAAA;IAEM,MAAM,OAAO,EAAE,aAarB,CAAA;IAEM,MAAM,IAAI,EAAE,aAalB,CAAA;IAEM,MAAM,KAAK,EAAE,aAcnB,CAAA;IAEM,MAAM,MAAM,EAAE,aAapB,CAAA;IAEM,MAAM,OAAO,EAAE,aAqCrB,CAAA;IAEM,MAAM,SAAS;;;;;KAAyC,CAAA;IACxD,MAAM,UAAU;;;;;;;;;;;;KAEtB,CAAA;IACM,MAAM,QAAQ,EAAE,aAAa,CAAC,QAAQ,CAAC,aAAa,CAAC,CAA4B,CAAA;IACjF,MAAM,SAAS,EAAE,aAAa,CAAC,QAAQ,CAAC,aAAa,CAAC,CAA6B,CAAA;IACnF,MAAM,WAAW;;;;;;;;;;;;;;;;KAAgC,CAAA;IACxD,KAAY,cAAc,GAAG,MAAM,OAAO,SAAS,CAAA;IACnD,KAAY,eAAe,GAAG,MAAM,OAAO,UAAU,CAAA;CACxD"}
|
package/dist/EffectFactories.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { UUID } from "@opendaw/lib-std";
|
|
2
|
-
import { ArpeggioDeviceBox, CompressorDeviceBox, CrusherDeviceBox, DattorroReverbDeviceBox, DelayDeviceBox, FoldDeviceBox, GrooveShuffleBox, MaximizerDeviceBox, ModularAudioInputBox, ModularAudioOutputBox, ModularBox, ModularDeviceBox, ModuleConnectionBox, PitchDeviceBox, RevampDeviceBox, ReverbDeviceBox, StereoToolDeviceBox, TidalDeviceBox, VelocityDeviceBox, ZeitgeistDeviceBox } from "@opendaw/studio-boxes";
|
|
2
|
+
import { ArpeggioDeviceBox, CompressorDeviceBox, CrusherDeviceBox, DattorroReverbDeviceBox, DelayDeviceBox, FoldDeviceBox, GateDeviceBox, GrooveShuffleBox, MaximizerDeviceBox, ModularAudioInputBox, ModularAudioOutputBox, ModularBox, ModularDeviceBox, ModuleConnectionBox, PitchDeviceBox, RevampDeviceBox, ReverbDeviceBox, StereoToolDeviceBox, TidalDeviceBox, VelocityDeviceBox, ZeitgeistDeviceBox } from "@opendaw/studio-boxes";
|
|
3
3
|
import { IconSymbol } from "@opendaw/studio-enums";
|
|
4
4
|
import { DeviceManualUrls } from "@opendaw/studio-adapters";
|
|
5
5
|
import { EffectParameterDefaults } from "./EffectParameterDefaults";
|
|
@@ -133,6 +133,19 @@ export var EffectFactories;
|
|
|
133
133
|
box.host.refer(hostField);
|
|
134
134
|
})
|
|
135
135
|
};
|
|
136
|
+
EffectFactories.Gate = {
|
|
137
|
+
defaultName: "Gate",
|
|
138
|
+
defaultIcon: IconSymbol.Gate,
|
|
139
|
+
description: "Attenuates signals below a threshold to reduce noise",
|
|
140
|
+
manualPage: DeviceManualUrls.Gate,
|
|
141
|
+
separatorBefore: false,
|
|
142
|
+
type: "audio",
|
|
143
|
+
create: ({ boxGraph }, hostField, index) => GateDeviceBox.create(boxGraph, UUID.generate(), box => {
|
|
144
|
+
box.label.setValue("Gate");
|
|
145
|
+
box.index.setValue(index);
|
|
146
|
+
box.host.refer(hostField);
|
|
147
|
+
})
|
|
148
|
+
};
|
|
136
149
|
EffectFactories.Reverb = {
|
|
137
150
|
defaultName: "Cheap Reverb",
|
|
138
151
|
defaultIcon: IconSymbol.Cube,
|
|
@@ -240,7 +253,7 @@ export var EffectFactories;
|
|
|
240
253
|
};
|
|
241
254
|
EffectFactories.MidiNamed = { Arpeggio: EffectFactories.Arpeggio, Pitch: EffectFactories.Pitch, Velocity: EffectFactories.Velocity, Zeitgeist: EffectFactories.Zeitgeist };
|
|
242
255
|
EffectFactories.AudioNamed = {
|
|
243
|
-
StereoTool: EffectFactories.StereoTool, Compressor: EffectFactories.Compressor, Delay: EffectFactories.Delay, Reverb: EffectFactories.Reverb, DattorroReverb: EffectFactories.DattorroReverb, Revamp: EffectFactories.Revamp, Crusher: EffectFactories.Crusher, Fold: EffectFactories.Fold, Tidal: EffectFactories.Tidal, Maximizer: EffectFactories.Maximizer
|
|
256
|
+
StereoTool: EffectFactories.StereoTool, Compressor: EffectFactories.Compressor, Gate: EffectFactories.Gate, Delay: EffectFactories.Delay, Reverb: EffectFactories.Reverb, DattorroReverb: EffectFactories.DattorroReverb, Revamp: EffectFactories.Revamp, Crusher: EffectFactories.Crusher, Fold: EffectFactories.Fold, Tidal: EffectFactories.Tidal, Maximizer: EffectFactories.Maximizer
|
|
244
257
|
};
|
|
245
258
|
EffectFactories.MidiList = Object.values(EffectFactories.MidiNamed);
|
|
246
259
|
EffectFactories.AudioList = Object.values(EffectFactories.AudioNamed);
|
package/dist/Mixer.js
CHANGED
|
@@ -8,11 +8,15 @@ export declare class CaptureAudio extends Capture<CaptureAudioBox> {
|
|
|
8
8
|
get isMonitoring(): boolean;
|
|
9
9
|
set isMonitoring(value: boolean);
|
|
10
10
|
get gainDb(): number;
|
|
11
|
+
get requestChannels(): Option<1 | 2>;
|
|
12
|
+
set requestChannels(value: 1 | 2);
|
|
11
13
|
get stream(): MutableObservableOption<MediaStream>;
|
|
12
14
|
get streamDeviceId(): Option<string>;
|
|
13
15
|
get label(): string;
|
|
14
16
|
get deviceLabel(): Option<string>;
|
|
15
17
|
get streamMediaTrack(): Option<MediaStreamTrack>;
|
|
18
|
+
get outputNode(): Option<AudioNode>;
|
|
19
|
+
get effectiveChannelCount(): number;
|
|
16
20
|
prepareRecording(): Promise<void>;
|
|
17
21
|
startRecording(): Terminable;
|
|
18
22
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CaptureAudio.d.ts","sourceRoot":"","sources":["../../src/capture/CaptureAudio.ts"],"names":[],"mappings":"AAAA,OAAO,EAKH,uBAAuB,EAEvB,MAAM,EAEN,UAAU,EACb,MAAM,kBAAkB,CAAA;AAGzB,OAAO,EAAC,YAAY,EAAE,eAAe,EAAC,MAAM,uBAAuB,CAAA;AACnE,OAAO,EAAC,OAAO,EAAC,MAAM,WAAW,CAAA;AACjC,OAAO,EAAC,cAAc,EAAC,MAAM,kBAAkB,CAAA;AAI/C,qBAAa,YAAa,SAAQ,OAAO,CAAC,eAAe,CAAC;;
|
|
1
|
+
{"version":3,"file":"CaptureAudio.d.ts","sourceRoot":"","sources":["../../src/capture/CaptureAudio.ts"],"names":[],"mappings":"AAAA,OAAO,EAKH,uBAAuB,EAEvB,MAAM,EAEN,UAAU,EACb,MAAM,kBAAkB,CAAA;AAGzB,OAAO,EAAC,YAAY,EAAE,eAAe,EAAC,MAAM,uBAAuB,CAAA;AACnE,OAAO,EAAC,OAAO,EAAC,MAAM,WAAW,CAAA;AACjC,OAAO,EAAC,cAAc,EAAC,MAAM,kBAAkB,CAAA;AAI/C,qBAAa,YAAa,SAAQ,OAAO,CAAC,eAAe,CAAC;;gBAa1C,OAAO,EAAE,cAAc,EAAE,YAAY,EAAE,YAAY,EAAE,eAAe,EAAE,eAAe;IAkCjG,IAAI,YAAY,IAAI,OAAO,CAA4B;IACvD,IAAI,YAAY,CAAC,KAAK,EAAE,OAAO,EAS9B;IACD,IAAI,MAAM,IAAI,MAAM,CAAsB;IAC1C,IAAI,eAAe,IAAI,MAAM,CAAC,CAAC,GAAG,CAAC,CAAC,CAA+B;IACnE,IAAI,eAAe,CAAC,KAAK,EAAE,CAAC,GAAG,CAAC,EAAmD;IACnF,IAAI,MAAM,IAAI,uBAAuB,CAAC,WAAW,CAAC,CAAsB;IACxE,IAAI,cAAc,IAAI,MAAM,CAAC,MAAM,CAAC,CAEnC;IACD,IAAI,KAAK,IAAI,MAAM,CAAsE;IACzF,IAAI,WAAW,IAAI,MAAM,CAAC,MAAM,CAAC,CAA+D;IAChG,IAAI,gBAAgB,IAAI,MAAM,CAAC,gBAAgB,CAAC,CAE/C;IACD,IAAI,UAAU,IAAI,MAAM,CAAC,SAAS,CAAC,CAAiD;IACpF,IAAI,qBAAqB,IAAI,MAAM,CAAsD;IAEnF,gBAAgB,IAAI,OAAO,CAAC,IAAI,CAAC;IAiBvC,cAAc,IAAI,UAAU;CAyG/B"}
|
|
@@ -10,7 +10,7 @@ export class CaptureAudio extends Capture {
|
|
|
10
10
|
#isMonitoring = false;
|
|
11
11
|
#requestChannels = Option.None;
|
|
12
12
|
#gainDb = 0.0;
|
|
13
|
-
#
|
|
13
|
+
#audioChain = null;
|
|
14
14
|
constructor(manager, audioUnitBox, captureAudioBox) {
|
|
15
15
|
super(manager, audioUnitBox, captureAudioBox);
|
|
16
16
|
this.#stream = new MutableObservableOption();
|
|
@@ -18,10 +18,11 @@ export class CaptureAudio extends Capture {
|
|
|
18
18
|
this.ownAll(captureAudioBox.requestChannels.catchupAndSubscribe(owner => {
|
|
19
19
|
const channels = owner.getValue();
|
|
20
20
|
this.#requestChannels = channels === 1 || channels === 2 ? Option.wrap(channels) : Option.None;
|
|
21
|
+
this.#stream.ifSome(stream => this.#rebuildAudioChain(stream));
|
|
21
22
|
}), captureAudioBox.gainDb.catchupAndSubscribe(owner => {
|
|
22
23
|
this.#gainDb = owner.getValue();
|
|
23
|
-
if (isDefined(this.#
|
|
24
|
-
this.#
|
|
24
|
+
if (isDefined(this.#audioChain)) {
|
|
25
|
+
this.#audioChain.gainNode.gain.value = dbToGain(this.#gainDb);
|
|
25
26
|
}
|
|
26
27
|
}), captureAudioBox.deviceId.catchupAndSubscribe(async () => {
|
|
27
28
|
if (this.armed.getValue()) {
|
|
@@ -45,13 +46,15 @@ export class CaptureAudio extends Capture {
|
|
|
45
46
|
this.#isMonitoring = value;
|
|
46
47
|
if (this.#isMonitoring) {
|
|
47
48
|
this.armed.setValue(true);
|
|
48
|
-
this.#
|
|
49
|
+
this.#connectMonitoring();
|
|
49
50
|
}
|
|
50
51
|
else {
|
|
51
52
|
this.#disconnectMonitoring();
|
|
52
53
|
}
|
|
53
54
|
}
|
|
54
55
|
get gainDb() { return this.#gainDb; }
|
|
56
|
+
get requestChannels() { return this.#requestChannels; }
|
|
57
|
+
set requestChannels(value) { this.captureBox.requestChannels.setValue(value); }
|
|
55
58
|
get stream() { return this.#stream; }
|
|
56
59
|
get streamDeviceId() {
|
|
57
60
|
return this.streamMediaTrack.map(settings => settings.getSettings().deviceId ?? "");
|
|
@@ -61,6 +64,8 @@ export class CaptureAudio extends Capture {
|
|
|
61
64
|
get streamMediaTrack() {
|
|
62
65
|
return this.#stream.flatMap(stream => Option.wrap(stream.getAudioTracks().at(0)));
|
|
63
66
|
}
|
|
67
|
+
get outputNode() { return Option.wrap(this.#audioChain?.gainNode); }
|
|
68
|
+
get effectiveChannelCount() { return this.#audioChain?.gainNode.channelCount ?? 1; }
|
|
64
69
|
async prepareRecording() {
|
|
65
70
|
const { project } = this.manager;
|
|
66
71
|
const { env: { audioContext } } = project;
|
|
@@ -80,23 +85,21 @@ export class CaptureAudio extends Capture {
|
|
|
80
85
|
startRecording() {
|
|
81
86
|
const { project } = this.manager;
|
|
82
87
|
const { env: { audioContext, audioWorklets, sampleManager } } = project;
|
|
83
|
-
const
|
|
84
|
-
if (
|
|
85
|
-
console.warn("No audio
|
|
88
|
+
const audioChain = this.#audioChain;
|
|
89
|
+
if (!isDefined(audioChain)) {
|
|
90
|
+
console.warn("No audio chain available for recording.");
|
|
86
91
|
return Terminable.Empty;
|
|
87
92
|
}
|
|
88
|
-
const
|
|
89
|
-
const channelCount =
|
|
93
|
+
const { gainNode } = audioChain;
|
|
94
|
+
const channelCount = gainNode.channelCount;
|
|
90
95
|
const numChunks = 128;
|
|
91
96
|
const recordingWorklet = audioWorklets.createRecording(channelCount, numChunks);
|
|
92
97
|
return RecordAudio.start({
|
|
93
98
|
recordingWorklet,
|
|
94
|
-
|
|
99
|
+
sourceNode: gainNode,
|
|
95
100
|
sampleManager,
|
|
96
|
-
audioContext,
|
|
97
101
|
project,
|
|
98
102
|
capture: this,
|
|
99
|
-
gainDb: this.#gainDb,
|
|
100
103
|
outputLatency: audioContext.outputLatency ?? 0
|
|
101
104
|
});
|
|
102
105
|
}
|
|
@@ -127,10 +130,8 @@ export class CaptureAudio extends Capture {
|
|
|
127
130
|
const gotDeviceId = settings?.deviceId;
|
|
128
131
|
console.debug(`new stream. device requested: ${deviceId ?? "default"}, got: ${gotDeviceId ?? "unknown"}. channelCount requested: ${channelCount}, got: ${settings?.channelCount}`);
|
|
129
132
|
if (isUndefined(deviceId) || deviceId === gotDeviceId) {
|
|
133
|
+
this.#rebuildAudioChain(stream);
|
|
130
134
|
this.#stream.wrap(stream);
|
|
131
|
-
if (this.#isMonitoring) {
|
|
132
|
-
this.#connectMonitoring(stream);
|
|
133
|
-
}
|
|
134
135
|
}
|
|
135
136
|
else {
|
|
136
137
|
stream.getAudioTracks().forEach(track => track.stop());
|
|
@@ -139,24 +140,46 @@ export class CaptureAudio extends Capture {
|
|
|
139
140
|
});
|
|
140
141
|
}
|
|
141
142
|
#stopStream() {
|
|
142
|
-
this.#
|
|
143
|
+
this.#destroyAudioChain();
|
|
143
144
|
this.#stream.clear(stream => stream.getAudioTracks().forEach(track => track.stop()));
|
|
144
145
|
}
|
|
145
|
-
#
|
|
146
|
-
this.#
|
|
146
|
+
#rebuildAudioChain(stream) {
|
|
147
|
+
const wasMonitoring = this.#isMonitoring && isDefined(this.#audioChain);
|
|
148
|
+
this.#destroyAudioChain();
|
|
147
149
|
const { audioContext } = this.manager.project.env;
|
|
148
150
|
const sourceNode = audioContext.createMediaStreamSource(stream);
|
|
149
151
|
const gainNode = audioContext.createGain();
|
|
150
152
|
gainNode.gain.value = dbToGain(this.#gainDb);
|
|
153
|
+
const streamChannelCount = stream.getAudioTracks().at(0)?.getSettings().channelCount ?? 1;
|
|
154
|
+
const requestMono = this.#requestChannels.mapOr(channels => channels === 1, false);
|
|
155
|
+
if (requestMono && streamChannelCount === 2) {
|
|
156
|
+
gainNode.channelCount = 1;
|
|
157
|
+
gainNode.channelCountMode = "explicit";
|
|
158
|
+
}
|
|
151
159
|
sourceNode.connect(gainNode);
|
|
152
|
-
gainNode
|
|
153
|
-
this.#
|
|
160
|
+
this.#audioChain = { sourceNode, gainNode };
|
|
161
|
+
if (wasMonitoring || this.#isMonitoring) {
|
|
162
|
+
this.#connectMonitoring();
|
|
163
|
+
}
|
|
154
164
|
}
|
|
155
|
-
#
|
|
156
|
-
if (isDefined(this.#
|
|
157
|
-
const { sourceNode, gainNode } = this.#
|
|
165
|
+
#destroyAudioChain() {
|
|
166
|
+
if (isDefined(this.#audioChain)) {
|
|
167
|
+
const { sourceNode, gainNode } = this.#audioChain;
|
|
158
168
|
sourceNode.disconnect();
|
|
159
169
|
gainNode.disconnect();
|
|
170
|
+
this.#audioChain = null;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
#connectMonitoring() {
|
|
174
|
+
if (isDefined(this.#audioChain)) {
|
|
175
|
+
const { audioContext } = this.manager.project.env;
|
|
176
|
+
this.#audioChain.gainNode.connect(audioContext.destination);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
#disconnectMonitoring() {
|
|
180
|
+
if (isDefined(this.#audioChain)) {
|
|
181
|
+
const { audioContext } = this.manager.project.env;
|
|
182
|
+
this.#audioChain.gainNode.disconnect(audioContext.destination);
|
|
160
183
|
}
|
|
161
184
|
}
|
|
162
185
|
}
|
|
@@ -6,15 +6,13 @@ import { Capture } from "./Capture";
|
|
|
6
6
|
export declare namespace RecordAudio {
|
|
7
7
|
type RecordAudioContext = {
|
|
8
8
|
recordingWorklet: RecordingWorklet;
|
|
9
|
-
|
|
9
|
+
sourceNode: AudioNode;
|
|
10
10
|
sampleManager: SampleLoaderManager;
|
|
11
|
-
audioContext: AudioContext;
|
|
12
11
|
project: Project;
|
|
13
12
|
capture: Capture;
|
|
14
|
-
gainDb: number;
|
|
15
13
|
outputLatency: number;
|
|
16
14
|
};
|
|
17
|
-
export const start: ({ recordingWorklet,
|
|
15
|
+
export const start: ({ recordingWorklet, sourceNode, sampleManager, project, capture, outputLatency }: RecordAudioContext) => Terminable;
|
|
18
16
|
export {};
|
|
19
17
|
}
|
|
20
18
|
//# sourceMappingURL=RecordAudio.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"RecordAudio.d.ts","sourceRoot":"","sources":["../../src/capture/RecordAudio.ts"],"names":[],"mappings":"AAAA,OAAO,EAA2C,UAAU,EAAmB,MAAM,kBAAkB,CAAA;
|
|
1
|
+
{"version":3,"file":"RecordAudio.d.ts","sourceRoot":"","sources":["../../src/capture/RecordAudio.ts"],"names":[],"mappings":"AAAA,OAAO,EAA2C,UAAU,EAAmB,MAAM,kBAAkB,CAAA;AAGvG,OAAO,EAAa,mBAAmB,EAA2B,MAAM,0BAA0B,CAAA;AAClG,OAAO,EAAC,OAAO,EAAC,MAAM,YAAY,CAAA;AAClC,OAAO,EAAC,gBAAgB,EAAC,MAAM,qBAAqB,CAAA;AACpD,OAAO,EAAC,OAAO,EAAC,MAAM,WAAW,CAAA;AAGjC,yBAAiB,WAAW,CAAC;IACzB,KAAK,kBAAkB,GAAG;QACtB,gBAAgB,EAAE,gBAAgB,CAAA;QAClC,UAAU,EAAE,SAAS,CAAA;QACrB,aAAa,EAAE,mBAAmB,CAAA;QAClC,OAAO,EAAE,OAAO,CAAA;QAChB,OAAO,EAAE,OAAO,CAAA;QAChB,aAAa,EAAE,MAAM,CAAA;KACxB,CAAA;IAOD,MAAM,CAAC,MAAM,KAAK,GACd,kFAAgF,kBAAkB,KAChG,UAkJL,CAAA;;CACJ"}
|
|
@@ -1,30 +1,22 @@
|
|
|
1
1
|
import { asInstanceOf, Option, quantizeFloor, Terminable, Terminator, UUID } from "@opendaw/lib-std";
|
|
2
|
-
import {
|
|
3
|
-
import { AudioFileBox,
|
|
2
|
+
import { PPQN, TimeBase } from "@opendaw/lib-dsp";
|
|
3
|
+
import { AudioFileBox, AudioRegionBox, TrackBox, ValueEventCollectionBox } from "@opendaw/studio-boxes";
|
|
4
4
|
import { ColorCodes, TrackType, UnionBoxTypes } from "@opendaw/studio-adapters";
|
|
5
5
|
import { RecordTrack } from "./RecordTrack";
|
|
6
6
|
export var RecordAudio;
|
|
7
7
|
(function (RecordAudio) {
|
|
8
|
-
RecordAudio.start = ({ recordingWorklet,
|
|
8
|
+
RecordAudio.start = ({ recordingWorklet, sourceNode, sampleManager, project, capture, outputLatency }) => {
|
|
9
9
|
const terminator = new Terminator();
|
|
10
10
|
const beats = PPQN.fromSignature(1, project.timelineBox.signature.denominator.getValue());
|
|
11
11
|
const { editing, engine, boxGraph, timelineBox } = project;
|
|
12
12
|
const originalUuid = recordingWorklet.uuid;
|
|
13
13
|
sampleManager.record(recordingWorklet);
|
|
14
|
-
const streamSource = audioContext.createMediaStreamSource(mediaStream);
|
|
15
|
-
const streamGain = audioContext.createGain();
|
|
16
|
-
streamGain.gain.value = dbToGain(gainDb);
|
|
17
|
-
streamSource.connect(streamGain);
|
|
18
|
-
recordingWorklet.own(Terminable.create(() => {
|
|
19
|
-
streamGain.disconnect();
|
|
20
|
-
streamSource.disconnect();
|
|
21
|
-
}));
|
|
22
14
|
let fileBox = Option.None;
|
|
23
15
|
let currentTake = Option.None;
|
|
24
16
|
let lastPosition = 0;
|
|
25
17
|
let currentWaveformOffset = outputLatency;
|
|
26
18
|
let takeNumber = 0;
|
|
27
|
-
const {
|
|
19
|
+
const { env: { audioContext: { sampleRate } }, engine: { preferences: { settings: { recording } } } } = project;
|
|
28
20
|
const { loopArea } = timelineBox;
|
|
29
21
|
const createFileBox = () => {
|
|
30
22
|
const fileDateString = new Date()
|
|
@@ -40,32 +32,25 @@ export var RecordAudio;
|
|
|
40
32
|
takeNumber++;
|
|
41
33
|
const trackBox = RecordTrack.findOrCreate(editing, capture.audioUnitBox, TrackType.Audio, forceNewTrack);
|
|
42
34
|
const collectionBox = ValueEventCollectionBox.create(boxGraph, UUID.generate());
|
|
43
|
-
const stretchBox = AudioPitchStretchBox.create(boxGraph, UUID.generate());
|
|
44
|
-
WarpMarkerBox.create(boxGraph, UUID.generate(), box => box.owner.refer(stretchBox.warpMarkers));
|
|
45
|
-
const warpMarkerBox = WarpMarkerBox.create(boxGraph, UUID.generate(), box => box.owner.refer(stretchBox.warpMarkers));
|
|
46
35
|
const regionBox = AudioRegionBox.create(boxGraph, UUID.generate(), box => {
|
|
47
36
|
box.file.refer(fileBox.unwrap());
|
|
48
37
|
box.events.refer(collectionBox.owners);
|
|
49
38
|
box.regions.refer(trackBox.regions);
|
|
50
39
|
box.position.setValue(position);
|
|
51
40
|
box.hue.setValue(ColorCodes.forTrackType(TrackType.Audio));
|
|
52
|
-
box.timeBase.setValue(TimeBase.
|
|
41
|
+
box.timeBase.setValue(TimeBase.Seconds);
|
|
53
42
|
box.label.setValue(`Take ${takeNumber}`);
|
|
54
|
-
box.playMode.refer(stretchBox);
|
|
55
43
|
box.waveformOffset.setValue(waveformOffset);
|
|
56
44
|
});
|
|
57
45
|
capture.addRecordedRegion(regionBox);
|
|
58
46
|
project.selection.select(regionBox);
|
|
59
|
-
return { trackBox, regionBox
|
|
47
|
+
return { trackBox, regionBox };
|
|
60
48
|
};
|
|
61
|
-
const finalizeTake = (take,
|
|
62
|
-
const { trackBox, regionBox
|
|
49
|
+
const finalizeTake = (take, durationInSeconds) => {
|
|
50
|
+
const { trackBox, regionBox } = take;
|
|
63
51
|
if (regionBox.isAttached()) {
|
|
64
|
-
regionBox.duration.setValue(
|
|
65
|
-
regionBox.loopDuration.setValue(
|
|
66
|
-
const seconds = tempoMap.intervalToSeconds(0, loopDurationPPQN);
|
|
67
|
-
warpMarkerBox.position.setValue(loopDurationPPQN);
|
|
68
|
-
warpMarkerBox.seconds.setValue(seconds);
|
|
52
|
+
regionBox.duration.setValue(durationInSeconds);
|
|
53
|
+
regionBox.loopDuration.setValue(durationInSeconds);
|
|
69
54
|
}
|
|
70
55
|
const { olderTakeAction, olderTakeScope } = recording;
|
|
71
56
|
if (olderTakeScope === "all") {
|
|
@@ -110,6 +95,7 @@ export var RecordAudio;
|
|
|
110
95
|
currentTake = Option.wrap(createTakeRegion(position, currentWaveformOffset, true));
|
|
111
96
|
};
|
|
112
97
|
terminator.ownAll(Terminable.create(() => {
|
|
98
|
+
sourceNode.disconnect(recordingWorklet);
|
|
113
99
|
if (recordingWorklet.numberOfFrames === 0 || fileBox.isEmpty()) {
|
|
114
100
|
console.debug("Abort recording audio.");
|
|
115
101
|
sampleManager.remove(originalUuid);
|
|
@@ -117,7 +103,7 @@ export var RecordAudio;
|
|
|
117
103
|
}
|
|
118
104
|
else {
|
|
119
105
|
currentTake.ifSome(({ regionBox: { duration } }) => {
|
|
120
|
-
recordingWorklet.limit(Math.ceil((currentWaveformOffset +
|
|
106
|
+
recordingWorklet.limit(Math.ceil((currentWaveformOffset + duration.getValue()) * sampleRate));
|
|
121
107
|
});
|
|
122
108
|
fileBox.ifSome(fb => fb.endInSeconds.setValue(recordingWorklet.numberOfFrames / sampleRate));
|
|
123
109
|
}
|
|
@@ -128,41 +114,36 @@ export var RecordAudio;
|
|
|
128
114
|
const currentPosition = owner.getValue();
|
|
129
115
|
const loopEnabled = loopArea.enabled.getValue();
|
|
130
116
|
const loopFrom = loopArea.from.getValue();
|
|
131
|
-
const loopTo = loopArea.to.getValue();
|
|
132
117
|
const allowTakes = project.engine.preferences.settings.recording.allowTakes;
|
|
133
118
|
if (loopEnabled && allowTakes && currentTake.nonEmpty() && currentPosition < lastPosition) {
|
|
134
119
|
editing.modify(() => {
|
|
135
120
|
currentTake.ifSome(take => {
|
|
136
|
-
const
|
|
137
|
-
finalizeTake(take,
|
|
138
|
-
currentWaveformOffset +=
|
|
121
|
+
const actualDurationInSeconds = take.regionBox.duration.getValue();
|
|
122
|
+
finalizeTake(take, actualDurationInSeconds);
|
|
123
|
+
currentWaveformOffset += actualDurationInSeconds;
|
|
139
124
|
});
|
|
140
125
|
startNewTake(loopFrom);
|
|
141
126
|
}, false);
|
|
142
127
|
}
|
|
143
128
|
lastPosition = currentPosition;
|
|
144
129
|
if (fileBox.isEmpty()) {
|
|
145
|
-
|
|
130
|
+
sourceNode.connect(recordingWorklet);
|
|
146
131
|
editing.modify(() => {
|
|
147
132
|
fileBox = Option.wrap(createFileBox());
|
|
148
133
|
const position = quantizeFloor(currentPosition, beats);
|
|
149
134
|
currentTake = Option.wrap(createTakeRegion(position, currentWaveformOffset, false));
|
|
150
135
|
}, false);
|
|
151
136
|
}
|
|
152
|
-
currentTake.ifSome(({ regionBox
|
|
137
|
+
currentTake.ifSome(({ regionBox }) => {
|
|
153
138
|
editing.modify(() => {
|
|
154
139
|
if (regionBox.isAttached()) {
|
|
155
140
|
const { duration, loopDuration } = regionBox;
|
|
156
|
-
const
|
|
157
|
-
const
|
|
158
|
-
duration.setValue(
|
|
159
|
-
loopDuration.setValue(
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
const totalSamples = Math.ceil((currentWaveformOffset + seconds) * sampleRate);
|
|
163
|
-
recordingWorklet.setFillLength(totalSamples);
|
|
164
|
-
fileBox.ifSome(fb => fb.endInSeconds.setValue(totalSamples / sampleRate));
|
|
165
|
-
warpMarkerBox.seconds.setValue(seconds);
|
|
141
|
+
const totalSeconds = recordingWorklet.numberOfFrames / sampleRate;
|
|
142
|
+
const takeSeconds = totalSeconds - currentWaveformOffset;
|
|
143
|
+
duration.setValue(takeSeconds);
|
|
144
|
+
loopDuration.setValue(takeSeconds);
|
|
145
|
+
recordingWorklet.setFillLength(recordingWorklet.numberOfFrames);
|
|
146
|
+
fileBox.ifSome(fb => fb.endInSeconds.setValue(totalSeconds));
|
|
166
147
|
}
|
|
167
148
|
else {
|
|
168
149
|
terminator.terminate();
|