@opendaw/studio-core 0.0.95 → 0.0.96

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.
@@ -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
@@ -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,GACvG,uBAAuB,CAAA"}
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":"AAyBA,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,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"}
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"}
@@ -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
@@ -66,7 +66,7 @@ export class Mixer {
66
66
  return;
67
67
  }
68
68
  touched.add(adapter);
69
- adapter.input.getValue().ifSome(input => {
69
+ adapter.input.adapter().ifSome(input => {
70
70
  if (input.type === "bus") {
71
71
  input.box.input.pointerHub
72
72
  .filter(Pointers.AudioOutput)
@@ -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;;gBAW1C,OAAO,EAAE,cAAc,EAAE,YAAY,EAAE,YAAY,EAAE,eAAe,EAAE,eAAe;IAiCjG,IAAI,YAAY,IAAI,OAAO,CAA4B;IACvD,IAAI,YAAY,CAAC,KAAK,EAAE,OAAO,EAS9B;IACD,IAAI,MAAM,IAAI,MAAM,CAAsB;IAC1C,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;IAEK,gBAAgB,IAAI,OAAO,CAAC,IAAI,CAAC;IAiBvC,cAAc,IAAI,UAAU;CAqF/B"}
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
- #monitor = null;
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.#monitor)) {
24
- this.#monitor.gainNode.gain.value = dbToGain(this.#gainDb);
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.#stream.ifSome(stream => this.#connectMonitoring(stream));
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 streamOption = this.#stream;
84
- if (streamOption.isEmpty()) {
85
- console.warn("No audio stream available for recording.");
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 mediaStream = streamOption.unwrap();
89
- const channelCount = mediaStream.getAudioTracks().at(0)?.getSettings().channelCount ?? 1;
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
- mediaStream,
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.#disconnectMonitoring();
143
+ this.#destroyAudioChain();
143
144
  this.#stream.clear(stream => stream.getAudioTracks().forEach(track => track.stop()));
144
145
  }
145
- #connectMonitoring(stream) {
146
- this.#disconnectMonitoring();
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.connect(audioContext.destination);
153
- this.#monitor = { sourceNode, gainNode };
160
+ this.#audioChain = { sourceNode, gainNode };
161
+ if (wasMonitoring || this.#isMonitoring) {
162
+ this.#connectMonitoring();
163
+ }
154
164
  }
155
- #disconnectMonitoring() {
156
- if (isDefined(this.#monitor)) {
157
- const { sourceNode, gainNode } = this.#monitor;
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
- mediaStream: MediaStream;
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, mediaStream, sampleManager, audioContext, project, capture, gainDb, outputLatency }: RecordAudioContext) => Terminable;
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;AAUvG,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,WAAW,EAAE,WAAW,CAAA;QACxB,aAAa,EAAE,mBAAmB,CAAA;QAClC,YAAY,EAAE,YAAY,CAAA;QAC1B,OAAO,EAAE,OAAO,CAAA;QAChB,OAAO,EAAE,OAAO,CAAA;QAChB,MAAM,EAAE,MAAM,CAAA;QACd,aAAa,EAAE,MAAM,CAAA;KACxB,CAAA;IAQD,MAAM,CAAC,MAAM,KAAK,GACd,yGASG,kBAAkB,KACnB,UAyKL,CAAA;;CACJ"}
1
+ {"version":3,"file":"RecordAudio.d.ts","sourceRoot":"","sources":["../../src/capture/RecordAudio.ts"],"names":[],"mappings":"AAAA,OAAO,EAA2C,UAAU,EAAmB,MAAM,kBAAkB,CAAA;AAUvG,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;IAQD,MAAM,CAAC,MAAM,KAAK,GACd,kFAAgF,kBAAkB,KAChG,UAiKL,CAAA;;CACJ"}
@@ -1,24 +1,16 @@
1
1
  import { asInstanceOf, Option, quantizeFloor, Terminable, Terminator, UUID } from "@opendaw/lib-std";
2
- import { dbToGain, PPQN, TimeBase } from "@opendaw/lib-dsp";
2
+ import { PPQN, TimeBase } from "@opendaw/lib-dsp";
3
3
  import { AudioFileBox, AudioPitchStretchBox, AudioRegionBox, TrackBox, ValueEventCollectionBox, WarpMarkerBox } 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, mediaStream, sampleManager, audioContext, project, capture, gainDb, outputLatency }) => {
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;
@@ -110,6 +102,7 @@ export var RecordAudio;
110
102
  currentTake = Option.wrap(createTakeRegion(position, currentWaveformOffset, true));
111
103
  };
112
104
  terminator.ownAll(Terminable.create(() => {
105
+ sourceNode.disconnect(recordingWorklet);
113
106
  if (recordingWorklet.numberOfFrames === 0 || fileBox.isEmpty()) {
114
107
  console.debug("Abort recording audio.");
115
108
  sampleManager.remove(originalUuid);
@@ -142,7 +135,7 @@ export var RecordAudio;
142
135
  }
143
136
  lastPosition = currentPosition;
144
137
  if (fileBox.isEmpty()) {
145
- streamGain.connect(recordingWorklet);
138
+ sourceNode.connect(recordingWorklet);
146
139
  editing.modify(() => {
147
140
  fileBox = Option.wrap(createFileBox());
148
141
  const position = quantizeFloor(currentPosition, beats);