@opendaw/studio-core 0.0.94 → 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,aAanB,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";
@@ -91,6 +91,7 @@ export var EffectFactories;
91
91
  box.label.setValue("Delay");
92
92
  box.index.setValue(index);
93
93
  box.host.refer(hostField);
94
+ box.version.setValue(1);
94
95
  })
95
96
  };
96
97
  EffectFactories.DattorroReverb = {
@@ -132,6 +133,19 @@ export var EffectFactories;
132
133
  box.host.refer(hostField);
133
134
  })
134
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
+ };
135
149
  EffectFactories.Reverb = {
136
150
  defaultName: "Cheap Reverb",
137
151
  defaultIcon: IconSymbol.Cube,
@@ -239,7 +253,7 @@ export var EffectFactories;
239
253
  };
240
254
  EffectFactories.MidiNamed = { Arpeggio: EffectFactories.Arpeggio, Pitch: EffectFactories.Pitch, Velocity: EffectFactories.Velocity, Zeitgeist: EffectFactories.Zeitgeist };
241
255
  EffectFactories.AudioNamed = {
242
- 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
243
257
  };
244
258
  EffectFactories.MidiList = Object.values(EffectFactories.MidiNamed);
245
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)
@@ -5,12 +5,18 @@ import { CaptureDevices } from "./CaptureDevices";
5
5
  export declare class CaptureAudio extends Capture<CaptureAudioBox> {
6
6
  #private;
7
7
  constructor(manager: CaptureDevices, audioUnitBox: AudioUnitBox, captureAudioBox: CaptureAudioBox);
8
+ get isMonitoring(): boolean;
9
+ set isMonitoring(value: boolean);
8
10
  get gainDb(): number;
11
+ get requestChannels(): Option<1 | 2>;
12
+ set requestChannels(value: 1 | 2);
9
13
  get stream(): MutableObservableOption<MediaStream>;
10
14
  get streamDeviceId(): Option<string>;
11
15
  get label(): string;
12
16
  get deviceLabel(): Option<string>;
13
17
  get streamMediaTrack(): Option<MediaStreamTrack>;
18
+ get outputNode(): Option<AudioNode>;
19
+ get effectiveChannelCount(): number;
14
20
  prepareRecording(): Promise<void>;
15
21
  startRecording(): Terminable;
16
22
  }
@@ -1 +1 @@
1
- {"version":3,"file":"CaptureAudio.d.ts","sourceRoot":"","sources":["../../src/capture/CaptureAudio.ts"],"names":[],"mappings":"AAAA,OAAO,EAKH,uBAAuB,EACvB,MAAM,EAEN,UAAU,EACb,MAAM,kBAAkB,CAAA;AAEzB,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;;gBAQ1C,OAAO,EAAE,cAAc,EAAE,YAAY,EAAE,YAAY,EAAE,eAAe,EAAE,eAAe;IA4BjG,IAAI,MAAM,IAAI,MAAM,CAAsB;IAE1C,IAAI,MAAM,IAAI,uBAAuB,CAAC,WAAW,CAAC,CAAsB;IAExE,IAAI,cAAc,IAAI,MAAM,CAAC,MAAM,CAAC,CAEnC;IAED,IAAI,KAAK,IAAI,MAAM,CAAsE;IAEzF,IAAI,WAAW,IAAI,MAAM,CAAC,MAAM,CAAC,CAA+D;IAEhG,IAAI,gBAAgB,IAAI,MAAM,CAAC,gBAAgB,CAAC,CAE/C;IAEK,gBAAgB,IAAI,OAAO,CAAC,IAAI,CAAC;IAiBvC,cAAc,IAAI,UAAU;CA8D/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"}
@@ -1,4 +1,5 @@
1
1
  import { Errors, isDefined, isUndefined, MutableObservableOption, Option, RuntimeNotifier, Terminable } from "@opendaw/lib-std";
2
+ import { dbToGain } from "@opendaw/lib-dsp";
2
3
  import { Promises } from "@opendaw/lib-runtime";
3
4
  import { Capture } from "./Capture";
4
5
  import { RecordAudio } from "./RecordAudio";
@@ -6,8 +7,10 @@ import { AudioDevices } from "../AudioDevices";
6
7
  export class CaptureAudio extends Capture {
7
8
  #stream;
8
9
  #streamGenerator;
10
+ #isMonitoring = false;
9
11
  #requestChannels = Option.None;
10
12
  #gainDb = 0.0;
13
+ #audioChain = null;
11
14
  constructor(manager, audioUnitBox, captureAudioBox) {
12
15
  super(manager, audioUnitBox, captureAudioBox);
13
16
  this.#stream = new MutableObservableOption();
@@ -15,7 +18,13 @@ export class CaptureAudio extends Capture {
15
18
  this.ownAll(captureAudioBox.requestChannels.catchupAndSubscribe(owner => {
16
19
  const channels = owner.getValue();
17
20
  this.#requestChannels = channels === 1 || channels === 2 ? Option.wrap(channels) : Option.None;
18
- }), captureAudioBox.gainDb.catchupAndSubscribe(owner => this.#gainDb = owner.getValue()), captureAudioBox.deviceId.catchupAndSubscribe(async () => {
21
+ this.#stream.ifSome(stream => this.#rebuildAudioChain(stream));
22
+ }), captureAudioBox.gainDb.catchupAndSubscribe(owner => {
23
+ this.#gainDb = owner.getValue();
24
+ if (isDefined(this.#audioChain)) {
25
+ this.#audioChain.gainNode.gain.value = dbToGain(this.#gainDb);
26
+ }
27
+ }), captureAudioBox.deviceId.catchupAndSubscribe(async () => {
19
28
  if (this.armed.getValue()) {
20
29
  await this.#streamGenerator();
21
30
  }
@@ -29,7 +38,23 @@ export class CaptureAudio extends Capture {
29
38
  }
30
39
  }));
31
40
  }
41
+ get isMonitoring() { return this.#isMonitoring; }
42
+ set isMonitoring(value) {
43
+ if (this.#isMonitoring === value) {
44
+ return;
45
+ }
46
+ this.#isMonitoring = value;
47
+ if (this.#isMonitoring) {
48
+ this.armed.setValue(true);
49
+ this.#connectMonitoring();
50
+ }
51
+ else {
52
+ this.#disconnectMonitoring();
53
+ }
54
+ }
32
55
  get gainDb() { return this.#gainDb; }
56
+ get requestChannels() { return this.#requestChannels; }
57
+ set requestChannels(value) { this.captureBox.requestChannels.setValue(value); }
33
58
  get stream() { return this.#stream; }
34
59
  get streamDeviceId() {
35
60
  return this.streamMediaTrack.map(settings => settings.getSettings().deviceId ?? "");
@@ -39,6 +64,8 @@ export class CaptureAudio extends Capture {
39
64
  get streamMediaTrack() {
40
65
  return this.#stream.flatMap(stream => Option.wrap(stream.getAudioTracks().at(0)));
41
66
  }
67
+ get outputNode() { return Option.wrap(this.#audioChain?.gainNode); }
68
+ get effectiveChannelCount() { return this.#audioChain?.gainNode.channelCount ?? 1; }
42
69
  async prepareRecording() {
43
70
  const { project } = this.manager;
44
71
  const { env: { audioContext } } = project;
@@ -58,23 +85,21 @@ export class CaptureAudio extends Capture {
58
85
  startRecording() {
59
86
  const { project } = this.manager;
60
87
  const { env: { audioContext, audioWorklets, sampleManager } } = project;
61
- const streamOption = this.#stream;
62
- if (streamOption.isEmpty()) {
63
- 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.");
64
91
  return Terminable.Empty;
65
92
  }
66
- const mediaStream = streamOption.unwrap();
67
- const channelCount = mediaStream.getAudioTracks().at(0)?.getSettings().channelCount ?? 1;
93
+ const { gainNode } = audioChain;
94
+ const channelCount = gainNode.channelCount;
68
95
  const numChunks = 128;
69
96
  const recordingWorklet = audioWorklets.createRecording(channelCount, numChunks);
70
97
  return RecordAudio.start({
71
98
  recordingWorklet,
72
- mediaStream,
99
+ sourceNode: gainNode,
73
100
  sampleManager,
74
- audioContext,
75
101
  project,
76
102
  capture: this,
77
- gainDb: this.#gainDb,
78
103
  outputLatency: audioContext.outputLatency ?? 0
79
104
  });
80
105
  }
@@ -105,6 +130,7 @@ export class CaptureAudio extends Capture {
105
130
  const gotDeviceId = settings?.deviceId;
106
131
  console.debug(`new stream. device requested: ${deviceId ?? "default"}, got: ${gotDeviceId ?? "unknown"}. channelCount requested: ${channelCount}, got: ${settings?.channelCount}`);
107
132
  if (isUndefined(deviceId) || deviceId === gotDeviceId) {
133
+ this.#rebuildAudioChain(stream);
108
134
  this.#stream.wrap(stream);
109
135
  }
110
136
  else {
@@ -114,6 +140,46 @@ export class CaptureAudio extends Capture {
114
140
  });
115
141
  }
116
142
  #stopStream() {
143
+ this.#destroyAudioChain();
117
144
  this.#stream.clear(stream => stream.getAudioTracks().forEach(track => track.stop()));
118
145
  }
146
+ #rebuildAudioChain(stream) {
147
+ const wasMonitoring = this.#isMonitoring && isDefined(this.#audioChain);
148
+ this.#destroyAudioChain();
149
+ const { audioContext } = this.manager.project.env;
150
+ const sourceNode = audioContext.createMediaStreamSource(stream);
151
+ const gainNode = audioContext.createGain();
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
+ }
159
+ sourceNode.connect(gainNode);
160
+ this.#audioChain = { sourceNode, gainNode };
161
+ if (wasMonitoring || this.#isMonitoring) {
162
+ this.#connectMonitoring();
163
+ }
164
+ }
165
+ #destroyAudioChain() {
166
+ if (isDefined(this.#audioChain)) {
167
+ const { sourceNode, gainNode } = this.#audioChain;
168
+ sourceNode.disconnect();
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);
183
+ }
184
+ }
119
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);
@@ -1 +1 @@
1
- {"version":3,"file":"RecordMidi.d.ts","sourceRoot":"","sources":["../../src/capture/RecordMidi.ts"],"names":[],"mappings":"AAAA,OAAO,EAA0B,QAAQ,EAAuC,UAAU,EAAmB,MAAM,kBAAkB,CAAA;AAGrI,OAAO,EAAa,UAAU,EAA2B,MAAM,0BAA0B,CAAA;AACzF,OAAO,EAAC,OAAO,EAAC,MAAM,YAAY,CAAA;AAClC,OAAO,EAAC,OAAO,EAAC,MAAM,WAAW,CAAA;AAGjC,yBAAiB,UAAU,CAAC;IACxB,KAAK,iBAAiB,GAAG;QACrB,QAAQ,EAAE,QAAQ,CAAC,UAAU,CAAC,CAAC;QAC/B,OAAO,EAAE,OAAO,CAAC;QACjB,OAAO,EAAE,OAAO,CAAA;KACnB,CAAA;IAgBD,MAAM,CAAC,MAAM,KAAK,GAAI,gCAA8B,iBAAiB,KAAG,UAgJvE,CAAA;;CACJ"}
1
+ {"version":3,"file":"RecordMidi.d.ts","sourceRoot":"","sources":["../../src/capture/RecordMidi.ts"],"names":[],"mappings":"AAAA,OAAO,EAA0B,QAAQ,EAAuC,UAAU,EAAmB,MAAM,kBAAkB,CAAA;AAGrI,OAAO,EAAa,UAAU,EAA2B,MAAM,0BAA0B,CAAA;AACzF,OAAO,EAAC,OAAO,EAAC,MAAM,YAAY,CAAA;AAClC,OAAO,EAAC,OAAO,EAAC,MAAM,WAAW,CAAA;AAGjC,yBAAiB,UAAU,CAAC;IACxB,KAAK,iBAAiB,GAAG;QACrB,QAAQ,EAAE,QAAQ,CAAC,UAAU,CAAC,CAAC;QAC/B,OAAO,EAAE,OAAO,CAAC;QACjB,OAAO,EAAE,OAAO,CAAA;KACnB,CAAA;IAgBD,MAAM,CAAC,MAAM,KAAK,GAAI,gCAA8B,iBAAiB,KAAG,UAkJvE,CAAA;;CACJ"}
@@ -91,12 +91,14 @@ export var RecordMidi;
91
91
  const loopEnabled = loopArea.enabled.getValue();
92
92
  const loopFrom = loopArea.from.getValue();
93
93
  const loopTo = loopArea.to.getValue();
94
- const loopDurationPPQN = loopTo - loopFrom;
95
94
  const allowTakes = project.engine.preferences.settings.recording.allowTakes;
96
95
  if (loopEnabled && allowTakes && currentTake.nonEmpty() && currentPosition < lastPosition) {
97
- positionOffset += loopDurationPPQN;
98
96
  editing.modify(() => {
99
- currentTake.ifSome(take => finalizeTake(take, loopDurationPPQN));
97
+ currentTake.ifSome(take => {
98
+ const actualDurationPPQN = take.regionBox.duration.getValue();
99
+ finalizeTake(take, actualDurationPPQN);
100
+ positionOffset += actualDurationPPQN;
101
+ });
100
102
  startNewTake(loopFrom);
101
103
  }, false);
102
104
  }
@@ -1 +1 @@
1
- {"version":3,"file":"DawProjectExporter.d.ts","sourceRoot":"","sources":["../../src/dawproject/DawProjectExporter.ts"],"names":[],"mappings":"AAKA,OAAO,EAaH,mBAAmB,EAKnB,aAAa,EAUhB,MAAM,yBAAyB,CAAA;AAahC,OAAO,EAA6B,eAAe,EAAE,mBAAmB,EAAC,MAAM,0BAA0B,CAAA;AAKzG,yBAAiB,kBAAkB,CAAC;IAChC,UAAiB,cAAc;QAAE,KAAK,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,eAAe,GAAG,mBAAmB,CAAA;KAAC;IAE5F,MAAM,KAAK,GAAI,UAAU,eAAe,EAAE,eAAe,mBAAmB,EAAE,gBAAgB,cAAc,kBAoPlH,CAAA;CACJ"}
1
+ {"version":3,"file":"DawProjectExporter.d.ts","sourceRoot":"","sources":["../../src/dawproject/DawProjectExporter.ts"],"names":[],"mappings":"AAKA,OAAO,EAcH,mBAAmB,EAMnB,aAAa,EAahB,MAAM,yBAAyB,CAAA;AAgBhC,OAAO,EAAwD,eAAe,EAAE,mBAAmB,EAAC,MAAM,0BAA0B,CAAA;AAKpI,yBAAiB,kBAAkB,CAAC;IAChC,UAAiB,cAAc;QAAE,KAAK,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,eAAe,GAAG,mBAAmB,CAAA;KAAC;IAE5F,MAAM,KAAK,GAAI,UAAU,eAAe,EAAE,eAAe,mBAAmB,EAAE,gBAAgB,cAAc,kBAqTlH,CAAA;CACJ"}
@@ -3,10 +3,10 @@ import { Xml } from "@opendaw/lib-xml";
3
3
  import { dbToGain, PPQN } from "@opendaw/lib-dsp";
4
4
  import { AddressIdEncoder, BooleanField } from "@opendaw/lib-box";
5
5
  import { Html } from "@opendaw/lib-dom";
6
- import { ApplicationSchema, ArrangementSchema, AudioAlgorithm, AudioSchema, BuiltinDeviceSchema, ChannelRole, ChannelSchema, ClipSchema, ClipsSchema, DeviceRole, FileReferenceSchema, LanesSchema, NoteSchema, NotesSchema, ParameterEncoder, ProjectSchema, RealParameterSchema, TimeSignatureParameterSchema, TimeUnit, TrackSchema, TransportSchema, Unit, WarpSchema, WarpsSchema } from "@opendaw/lib-dawproject";
6
+ import { ApplicationSchema, ArrangementSchema, AudioAlgorithm, AudioSchema, AutomationTargetSchema, BuiltinDeviceSchema, ChannelRole, ChannelSchema, ClipSchema, ClipsSchema, DeviceRole, FileReferenceSchema, LanesSchema, NoteSchema, NotesSchema, ParameterEncoder, PointsSchema, ProjectSchema, RealParameterSchema, RealPointSchema, TempoAutomationConverter, TimeSignatureParameterSchema, TimeSignaturePointSchema, TimeUnit, TrackSchema, TransportSchema, Unit, WarpSchema, WarpsSchema } from "@opendaw/lib-dawproject";
7
7
  import { AudioUnitType } from "@opendaw/studio-enums";
8
- import { AudioFileBox, AudioUnitBox, NoteEventBox, NoteEventCollectionBox, TrackBox } from "@opendaw/studio-boxes";
9
- import { ColorCodes, DeviceBoxUtils } from "@opendaw/studio-adapters";
8
+ import { AudioFileBox, AudioUnitBox, NoteEventBox, NoteEventCollectionBox, SignatureEventBox, TrackBox, ValueEventBox, ValueEventCollectionBox } from "@opendaw/studio-boxes";
9
+ import { ColorCodes, DeviceBoxUtils, InterpolationFieldAdapter } from "@opendaw/studio-adapters";
10
10
  import { AudioUnitExportLayout } from "./AudioUnitExportLayout";
11
11
  import { DeviceIO } from "./DeviceIO";
12
12
  import { WavFile } from "../WavFile";
@@ -205,6 +205,77 @@ export var DawProjectExporter;
205
205
  content: []
206
206
  }, ClipSchema)]
207
207
  }, ClipsSchema);
208
+ const writeTempoAutomation = () => {
209
+ const { tempoTrack } = timelineBox;
210
+ if (!tempoTrack.enabled.getValue()) {
211
+ return undefined;
212
+ }
213
+ const targetVertex = tempoTrack.events.targetVertex;
214
+ if (targetVertex.isEmpty()) {
215
+ return undefined;
216
+ }
217
+ const collectionBox = asInstanceOf(targetVertex.unwrap().box, ValueEventCollectionBox);
218
+ const events = collectionBox.events.pointerHub.incoming()
219
+ .map(({ box }) => asInstanceOf(box, ValueEventBox))
220
+ .sort((eventA, eventB) => eventA.position.getValue() - eventB.position.getValue());
221
+ if (events.length === 0) {
222
+ return undefined;
223
+ }
224
+ const minBpm = tempoTrack.minBpm.getValue();
225
+ const maxBpm = tempoTrack.maxBpm.getValue();
226
+ const tempoParameterId = ids.getOrCreate(timelineBox.bpm.address);
227
+ return Xml.element({
228
+ unit: Unit.BPM,
229
+ target: Xml.element({
230
+ parameter: tempoParameterId
231
+ }, AutomationTargetSchema),
232
+ points: events.map(event => {
233
+ const interpolation = InterpolationFieldAdapter.read(event.interpolation);
234
+ return Xml.element({
235
+ time: event.position.getValue() / PPQN.Quarter,
236
+ value: TempoAutomationConverter.normalizedToBpm(event.value.getValue(), minBpm, maxBpm),
237
+ interpolation: TempoAutomationConverter.toDawProjectInterpolation(interpolation)
238
+ }, RealPointSchema);
239
+ })
240
+ }, PointsSchema);
241
+ };
242
+ const writeSignatureAutomation = () => {
243
+ const { signatureTrack, signature } = timelineBox;
244
+ if (!signatureTrack.enabled.getValue()) {
245
+ return undefined;
246
+ }
247
+ const events = signatureTrack.events.pointerHub.incoming()
248
+ .map(({ box }) => asInstanceOf(box, SignatureEventBox))
249
+ .sort((eventA, eventB) => eventA.index.getValue() - eventB.index.getValue());
250
+ if (events.length === 0) {
251
+ return undefined;
252
+ }
253
+ // Calculate absolute positions for each event
254
+ const points = [];
255
+ let accumulatedPpqn = 0;
256
+ let currentNominator = signature.nominator.getValue();
257
+ let currentDenominator = signature.denominator.getValue();
258
+ // Add the initial signature at time 0
259
+ points.push(Xml.element({
260
+ time: 0,
261
+ numerator: currentNominator,
262
+ denominator: currentDenominator
263
+ }, TimeSignaturePointSchema));
264
+ for (const event of events) {
265
+ const barDuration = PPQN.fromSignature(currentNominator, currentDenominator);
266
+ accumulatedPpqn += barDuration * event.relativePosition.getValue();
267
+ currentNominator = event.nominator.getValue();
268
+ currentDenominator = event.denominator.getValue();
269
+ points.push(Xml.element({
270
+ time: accumulatedPpqn / PPQN.Quarter,
271
+ numerator: currentNominator,
272
+ denominator: currentDenominator
273
+ }, TimeSignaturePointSchema));
274
+ }
275
+ return Xml.element({
276
+ points
277
+ }, PointsSchema);
278
+ };
208
279
  const writeLanes = () => {
209
280
  return audioUnits
210
281
  .flatMap(audioUnitBox => audioUnitBox.tracks.pointerHub.incoming()
@@ -230,6 +301,8 @@ export var DawProjectExporter;
230
301
  transport: writeTransport(),
231
302
  structure: writeStructure(),
232
303
  arrangement: Xml.element({
304
+ tempoAutomation: writeTempoAutomation(),
305
+ timeSignatureAutomation: writeSignatureAutomation(),
233
306
  lanes: Xml.element({
234
307
  lanes: writeLanes(),
235
308
  timeUnit: TimeUnit.BEATS
@@ -1 +1 @@
1
- {"version":3,"file":"DawProjectImporter.d.ts","sourceRoot":"","sources":["../../src/dawproject/DawProjectImporter.ts"],"names":[],"mappings":"AAAA,OAAO,EAiBH,IAAI,EAEP,MAAM,kBAAkB,CAAA;AAGzB,OAAO,EAcH,aAAa,EAOhB,MAAM,yBAAyB,CAAA;AAyBhC,OAAO,EAMH,eAAe,EAElB,MAAM,0BAA0B,CAAA;AACjC,OAAO,EAAC,UAAU,EAAC,MAAM,cAAc,CAAA;AAIvC,yBAAiB,gBAAgB,CAAC;IAW9B,KAAY,MAAM,GAAG;QACjB,QAAQ,EAAE,aAAa,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACpC,QAAQ,EAAE,eAAe,CAAA;KAC5B,CAAA;IAOM,MAAM,IAAI,GAAU,QAAQ,aAAa,EAAE,WAAW,UAAU,CAAC,gBAAgB,KAAG,OAAO,CAAC,MAAM,CAyYxG,CAAA;CAaJ"}
1
+ {"version":3,"file":"DawProjectImporter.d.ts","sourceRoot":"","sources":["../../src/dawproject/DawProjectImporter.ts"],"names":[],"mappings":"AAAA,OAAO,EAiBH,IAAI,EAEP,MAAM,kBAAkB,CAAA;AAGzB,OAAO,EAcH,aAAa,EAUhB,MAAM,yBAAyB,CAAA;AA2BhC,OAAO,EAOH,eAAe,EAElB,MAAM,0BAA0B,CAAA;AACjC,OAAO,EAAC,UAAU,EAAC,MAAM,cAAc,CAAA;AAIvC,yBAAiB,gBAAgB,CAAC;IAW9B,KAAY,MAAM,GAAG;QACjB,QAAQ,EAAE,aAAa,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACpC,QAAQ,EAAE,eAAe,CAAA;KAC5B,CAAA;IAOM,MAAM,IAAI,GAAU,QAAQ,aAAa,EAAE,WAAW,UAAU,CAAC,gBAAgB,KAAG,OAAO,CAAC,MAAM,CAycxG,CAAA;CAaJ"}
@@ -1,10 +1,10 @@
1
1
  import { ArrayMultimap, asDefined, asInstanceOf, assert, Color, ifDefined, isDefined, isInstanceOf, isUndefined, NumberComparator, Option, panic, UUID, ValueMapping } from "@opendaw/lib-std";
2
2
  import { BoxGraph } from "@opendaw/lib-box";
3
3
  import { gainToDb, PPQN } from "@opendaw/lib-dsp";
4
- import { ChannelRole, ClipsSchema, DeviceRole, EqualizerSchema, LanesSchema, NotesSchema, PointsSchema, SendType, TrackSchema, WarpsSchema } from "@opendaw/lib-dawproject";
4
+ import { ChannelRole, ClipsSchema, DeviceRole, EqualizerSchema, LanesSchema, NotesSchema, PointsSchema, RealPointSchema, SendType, TempoAutomationConverter, TimeSignaturePointSchema, TrackSchema, WarpsSchema } from "@opendaw/lib-dawproject";
5
5
  import { AudioSendRouting, AudioUnitType, IconSymbol } from "@opendaw/studio-enums";
6
- import { AudioBusBox, AudioFileBox, AudioPitchStretchBox, AudioRegionBox, AudioUnitBox, AuxSendBox, BoxIO, CaptureAudioBox, CaptureMidiBox, GrooveShuffleBox, NoteEventBox, NoteEventCollectionBox, NoteRegionBox, RootBox, TimelineBox, TrackBox, UnknownAudioEffectDeviceBox, UnknownMidiEffectDeviceBox, UserInterfaceBox, ValueEventCollectionBox } from "@opendaw/studio-boxes";
7
- import { AudioUnitOrdering, ColorCodes, InstrumentFactories, TrackType } from "@opendaw/studio-adapters";
6
+ import { AudioBusBox, AudioFileBox, AudioPitchStretchBox, AudioRegionBox, AudioUnitBox, AuxSendBox, BoxIO, CaptureAudioBox, CaptureMidiBox, GrooveShuffleBox, NoteEventBox, NoteEventCollectionBox, NoteRegionBox, RootBox, SignatureEventBox, TimelineBox, TrackBox, UnknownAudioEffectDeviceBox, UnknownMidiEffectDeviceBox, UserInterfaceBox, ValueEventBox, ValueEventCollectionBox } from "@opendaw/studio-boxes";
7
+ import { AudioUnitOrdering, ColorCodes, InstrumentFactories, InterpolationFieldAdapter, TrackType } from "@opendaw/studio-adapters";
8
8
  import { BuiltinDevices } from "./BuiltinDevices";
9
9
  import { AudioContentHelpers } from "../project/audio/AudioContentHelpers";
10
10
  export var DawProjectImport;
@@ -344,6 +344,74 @@ export var DawProjectImport;
344
344
  box.playMode.refer(pitchStretch);
345
345
  });
346
346
  };
347
+ const readTempoAutomation = (tempoAutomation) => {
348
+ const points = tempoAutomation.points;
349
+ if (!isDefined(points) || points.length === 0) {
350
+ return;
351
+ }
352
+ const minBpm = timelineBox.tempoTrack.minBpm.getValue();
353
+ const maxBpm = timelineBox.tempoTrack.maxBpm.getValue();
354
+ const collectionBox = ValueEventCollectionBox.create(boxGraph, UUID.generate());
355
+ timelineBox.tempoTrack.events.refer(collectionBox.owners);
356
+ timelineBox.tempoTrack.enabled.setValue(true);
357
+ points
358
+ .filter((point) => isInstanceOf(point, RealPointSchema))
359
+ .forEach(point => {
360
+ const eventBox = ValueEventBox.create(boxGraph, UUID.generate(), box => {
361
+ box.position.setValue(point.time * PPQN.Quarter);
362
+ box.index.setValue(0);
363
+ box.value.setValue(TempoAutomationConverter.bpmToNormalized(point.value, minBpm, maxBpm));
364
+ box.events.refer(collectionBox.events);
365
+ });
366
+ const interpolation = TempoAutomationConverter.fromDawProjectInterpolation(point.interpolation);
367
+ InterpolationFieldAdapter.write(eventBox.interpolation, interpolation);
368
+ });
369
+ };
370
+ ifDefined(arrangement.tempoAutomation, readTempoAutomation);
371
+ const readSignatureAutomation = (signatureAutomation) => {
372
+ const points = signatureAutomation.points;
373
+ if (!isDefined(points) || points.length === 0) {
374
+ return;
375
+ }
376
+ const signaturePoints = points
377
+ .filter((point) => isInstanceOf(point, TimeSignaturePointSchema))
378
+ .sort((pointA, pointB) => pointA.time - pointB.time);
379
+ if (signaturePoints.length === 0) {
380
+ return;
381
+ }
382
+ // First point at or near time 0 sets the base signature
383
+ const firstPoint = signaturePoints[0];
384
+ const firstPointIsAtStart = Number(firstPoint.time) < 0.001;
385
+ if (firstPointIsAtStart) {
386
+ timelineBox.signature.nominator.setValue(firstPoint.numerator);
387
+ timelineBox.signature.denominator.setValue(firstPoint.denominator);
388
+ }
389
+ // Remaining points become SignatureEventBox instances
390
+ const eventsToCreate = firstPointIsAtStart ? signaturePoints.slice(1) : signaturePoints;
391
+ if (eventsToCreate.length === 0) {
392
+ return;
393
+ }
394
+ timelineBox.signatureTrack.enabled.setValue(true);
395
+ let prevPpqn = 0;
396
+ let prevNominator = timelineBox.signature.nominator.getValue();
397
+ let prevDenominator = timelineBox.signature.denominator.getValue();
398
+ eventsToCreate.forEach((point, index) => {
399
+ const currentPpqn = point.time * PPQN.Quarter;
400
+ const barDuration = PPQN.fromSignature(prevNominator, prevDenominator);
401
+ const relativePosition = Math.max(1, Math.round((currentPpqn - prevPpqn) / barDuration));
402
+ SignatureEventBox.create(boxGraph, UUID.generate(), box => {
403
+ box.index.setValue(index);
404
+ box.relativePosition.setValue(relativePosition);
405
+ box.nominator.setValue(point.numerator);
406
+ box.denominator.setValue(point.denominator);
407
+ box.events.refer(timelineBox.signatureTrack.events);
408
+ });
409
+ prevPpqn = prevPpqn + relativePosition * barDuration;
410
+ prevNominator = point.numerator;
411
+ prevDenominator = point.denominator;
412
+ });
413
+ };
414
+ ifDefined(arrangement.timeSignatureAutomation, readSignatureAutomation);
347
415
  return Promise.all(arrangement?.lanes?.lanes?.filter(timeline => isInstanceOf(timeline, LanesSchema))
348
416
  .map(readLane) ?? []);
349
417
  };