@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.
@@ -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;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 { dbToGain, PPQN, TimeBase } from "@opendaw/lib-dsp";
3
- import { AudioFileBox, AudioPitchStretchBox, AudioRegionBox, TrackBox, ValueEventCollectionBox, WarpMarkerBox } from "@opendaw/studio-boxes";
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, 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;
25
17
  let currentWaveformOffset = outputLatency;
26
18
  let takeNumber = 0;
27
- const { tempoMap, env: { audioContext: { sampleRate } }, engine: { preferences: { settings: { recording } } } } = project;
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.Musical);
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, warpMarkerBox };
47
+ return { trackBox, regionBox };
60
48
  };
61
- const finalizeTake = (take, loopDurationPPQN) => {
62
- const { trackBox, regionBox, warpMarkerBox } = take;
49
+ const finalizeTake = (take, durationInSeconds) => {
50
+ const { trackBox, regionBox } = take;
63
51
  if (regionBox.isAttached()) {
64
- regionBox.duration.setValue(loopDurationPPQN);
65
- regionBox.loopDuration.setValue(loopDurationPPQN);
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 + tempoMap.intervalToSeconds(0, duration.getValue())) * sampleRate));
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 actualDurationPPQN = take.regionBox.duration.getValue();
137
- finalizeTake(take, actualDurationPPQN);
138
- currentWaveformOffset += tempoMap.intervalToSeconds(0, actualDurationPPQN);
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
- streamGain.connect(recordingWorklet);
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, warpMarkerBox }) => {
137
+ currentTake.ifSome(({ regionBox }) => {
153
138
  editing.modify(() => {
154
139
  if (regionBox.isAttached()) {
155
140
  const { duration, loopDuration } = regionBox;
156
- const maxDuration = loopEnabled && allowTakes ? loopTo - regionBox.position.getValue() : Infinity;
157
- const distanceInPPQN = Math.min(maxDuration, Math.floor(currentPosition - regionBox.position.getValue()));
158
- duration.setValue(distanceInPPQN);
159
- loopDuration.setValue(distanceInPPQN);
160
- warpMarkerBox.position.setValue(distanceInPPQN);
161
- const seconds = tempoMap.intervalToSeconds(0, distanceInPPQN);
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();