@opendaw/studio-core 0.0.86 → 0.0.87
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/AudioOfflineRenderer.d.ts.map +1 -1
- package/dist/AudioOfflineRenderer.js +1 -0
- package/dist/AudioWorklets.d.ts +3 -2
- package/dist/AudioWorklets.d.ts.map +1 -1
- package/dist/AudioWorklets.js +5 -2
- package/dist/OfflineEngineRenderer.d.ts +10 -0
- package/dist/OfflineEngineRenderer.d.ts.map +1 -0
- package/dist/OfflineEngineRenderer.js +141 -0
- package/dist/RecordingWorklet.d.ts +1 -1
- package/dist/RecordingWorklet.d.ts.map +1 -1
- package/dist/RecordingWorklet.js +3 -7
- package/dist/capture/CaptureAudio.d.ts.map +1 -1
- package/dist/capture/CaptureAudio.js +3 -2
- package/dist/capture/RecordAudio.d.ts +2 -1
- package/dist/capture/RecordAudio.d.ts.map +1 -1
- package/dist/capture/RecordAudio.js +78 -37
- package/dist/capture/RecordTrack.d.ts +1 -1
- package/dist/capture/RecordTrack.d.ts.map +1 -1
- package/dist/capture/RecordTrack.js +7 -5
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/offline-engine.js +2 -0
- package/dist/offline-engine.js.map +7 -0
- package/dist/processors.js +6 -6
- package/dist/processors.js.map +3 -3
- package/dist/project/index.d.ts +1 -0
- package/dist/project/index.d.ts.map +1 -1
- package/dist/project/index.js +1 -0
- package/dist/workers-main.js +1 -1
- package/dist/workers-main.js.map +3 -3
- package/package.json +18 -16
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AudioOfflineRenderer.d.ts","sourceRoot":"","sources":["../src/AudioOfflineRenderer.ts"],"names":[],"mappings":"AAAA,OAAO,EAAS,GAAG,EAAa,MAAM,EAAS,QAAQ,EAAuB,MAAM,kBAAkB,CAAA;AAGtG,OAAO,EAAC,wBAAwB,EAAC,MAAM,0BAA0B,CAAA;AACjE,OAAO,EAAC,OAAO,EAAC,MAAM,WAAW,CAAA;AAGjC,yBAAiB,oBAAoB,CAAC;
|
|
1
|
+
{"version":3,"file":"AudioOfflineRenderer.d.ts","sourceRoot":"","sources":["../src/AudioOfflineRenderer.ts"],"names":[],"mappings":"AAAA,OAAO,EAAS,GAAG,EAAa,MAAM,EAAS,QAAQ,EAAuB,MAAM,kBAAkB,CAAA;AAGtG,OAAO,EAAC,wBAAwB,EAAC,MAAM,0BAA0B,CAAA;AACjE,OAAO,EAAC,OAAO,EAAC,MAAM,WAAW,CAAA;AAGjC,yBAAiB,oBAAoB,CAAC;IAE3B,MAAM,KAAK,GAAU,QAAQ,OAAO,EACf,wBAAwB,MAAM,CAAC,wBAAwB,CAAC,EACxD,UAAU,QAAQ,CAAC,OAAO,EAC1B,cAAc,WAAW,EACzB,aAAY,GAAY,KAAG,OAAO,CAAC,WAAW,CA+CzE,CAAA;CACJ"}
|
|
@@ -5,6 +5,7 @@ import { ExportStemsConfiguration } from "@opendaw/studio-adapters";
|
|
|
5
5
|
import { AudioWorklets } from "./AudioWorklets";
|
|
6
6
|
export var AudioOfflineRenderer;
|
|
7
7
|
(function (AudioOfflineRenderer) {
|
|
8
|
+
/* Deprecated */
|
|
8
9
|
AudioOfflineRenderer.start = async (source, optExportConfiguration, progress, abortSignal, sampleRate = 48_000) => {
|
|
9
10
|
const numStems = ExportStemsConfiguration.countStems(optExportConfiguration);
|
|
10
11
|
if (numStems === 0) {
|
package/dist/AudioWorklets.d.ts
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import { int } from "@opendaw/lib-std";
|
|
2
2
|
import { ExportStemsConfiguration, ProcessorOptions } from "@opendaw/studio-adapters";
|
|
3
|
-
import { Project } from "./project
|
|
3
|
+
import { Project } from "./project";
|
|
4
4
|
import { EngineWorklet } from "./EngineWorklet";
|
|
5
5
|
import { MeterWorklet } from "./MeterWorklet";
|
|
6
6
|
import { RecordingWorklet } from "./RecordingWorklet";
|
|
7
7
|
export declare class AudioWorklets {
|
|
8
8
|
#private;
|
|
9
9
|
static install(url: string): void;
|
|
10
|
+
static get processorsUrl(): string;
|
|
10
11
|
static createFor(context: BaseAudioContext): Promise<AudioWorklets>;
|
|
11
12
|
static get(context: BaseAudioContext): AudioWorklets;
|
|
12
13
|
constructor(context: BaseAudioContext);
|
|
@@ -17,6 +18,6 @@ export declare class AudioWorklets {
|
|
|
17
18
|
exportConfiguration?: ExportStemsConfiguration;
|
|
18
19
|
options?: ProcessorOptions;
|
|
19
20
|
}): EngineWorklet;
|
|
20
|
-
createRecording(numberOfChannels: int, numChunks: int
|
|
21
|
+
createRecording(numberOfChannels: int, numChunks: int): RecordingWorklet;
|
|
21
22
|
}
|
|
22
23
|
//# sourceMappingURL=AudioWorklets.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AudioWorklets.d.ts","sourceRoot":"","sources":["../src/AudioWorklets.ts"],"names":[],"mappings":"AAAA,OAAO,EAAY,GAAG,EAAS,MAAM,kBAAkB,CAAA;AACvD,OAAO,EAAC,wBAAwB,EAAE,gBAAgB,EAAa,MAAM,0BAA0B,CAAA;AAC/F,OAAO,EAAC,OAAO,EAAC,MAAM,
|
|
1
|
+
{"version":3,"file":"AudioWorklets.d.ts","sourceRoot":"","sources":["../src/AudioWorklets.ts"],"names":[],"mappings":"AAAA,OAAO,EAAY,GAAG,EAAS,MAAM,kBAAkB,CAAA;AACvD,OAAO,EAAC,wBAAwB,EAAE,gBAAgB,EAAa,MAAM,0BAA0B,CAAA;AAC/F,OAAO,EAAC,OAAO,EAAC,MAAM,WAAW,CAAA;AACjC,OAAO,EAAC,aAAa,EAAC,MAAM,iBAAiB,CAAA;AAC7C,OAAO,EAAC,YAAY,EAAC,MAAM,gBAAgB,CAAA;AAC3C,OAAO,EAAC,gBAAgB,EAAC,MAAM,oBAAoB,CAAA;AAGnD,qBAAa,aAAa;;IACtB,MAAM,CAAC,OAAO,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI;IAKjC,MAAM,KAAK,aAAa,IAAI,MAAM,CAEjC;WAIY,SAAS,CAAC,OAAO,EAAE,gBAAgB,GAAG,OAAO,CAAC,aAAa,CAAC;IAQzE,MAAM,CAAC,GAAG,CAAC,OAAO,EAAE,gBAAgB,GAAG,aAAa;gBAMxC,OAAO,EAAE,gBAAgB;IAErC,IAAI,OAAO,IAAI,gBAAgB,CAAuB;IAEtD,WAAW,CAAC,gBAAgB,EAAE,GAAG,GAAG,YAAY;IAIhD,YAAY,CAAC,EAAC,OAAO,EAAE,mBAAmB,EAAE,OAAO,EAAC,EAAE;QAClD,OAAO,EAAE,OAAO,CAAC;QACjB,mBAAmB,CAAC,EAAE,wBAAwB,CAAC;QAC/C,OAAO,CAAC,EAAE,gBAAgB,CAAA;KAC7B,GAAG,aAAa;IAIjB,eAAe,CAAC,gBAAgB,EAAE,GAAG,EAAE,SAAS,EAAE,GAAG,GAAG,gBAAgB;CAO3E"}
|
package/dist/AudioWorklets.js
CHANGED
|
@@ -8,6 +8,9 @@ export class AudioWorklets {
|
|
|
8
8
|
console.debug(`WorkletUrl: '${url}'`);
|
|
9
9
|
this.#workletUrl = Option.wrap(url);
|
|
10
10
|
}
|
|
11
|
+
static get processorsUrl() {
|
|
12
|
+
return this.#workletUrl.unwrap("WorkletUrl is missing (call 'install' first)");
|
|
13
|
+
}
|
|
11
14
|
static #workletUrl = Option.None;
|
|
12
15
|
static async createFor(context) {
|
|
13
16
|
return context.audioWorklet.addModule(this.#workletUrl.unwrap("WorkletUrl is missing (call 'install' first)")).then(() => {
|
|
@@ -27,11 +30,11 @@ export class AudioWorklets {
|
|
|
27
30
|
createEngine({ project, exportConfiguration, options }) {
|
|
28
31
|
return new EngineWorklet(this.#context, project, exportConfiguration, options);
|
|
29
32
|
}
|
|
30
|
-
createRecording(numberOfChannels, numChunks
|
|
33
|
+
createRecording(numberOfChannels, numChunks) {
|
|
31
34
|
const audioBytes = numberOfChannels * numChunks * RenderQuantum * Float32Array.BYTES_PER_ELEMENT;
|
|
32
35
|
const pointerBytes = Int32Array.BYTES_PER_ELEMENT * 2;
|
|
33
36
|
const sab = new SharedArrayBuffer(audioBytes + pointerBytes);
|
|
34
37
|
const buffer = { sab, numChunks, numberOfChannels, bufferSize: RenderQuantum };
|
|
35
|
-
return new RecordingWorklet(this.#context, buffer
|
|
38
|
+
return new RecordingWorklet(this.#context, buffer);
|
|
36
39
|
}
|
|
37
40
|
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { int, Option, Progress } from "@opendaw/lib-std";
|
|
2
|
+
import { AudioData } from "@opendaw/lib-dsp";
|
|
3
|
+
import { ExportStemsConfiguration } from "@opendaw/studio-adapters";
|
|
4
|
+
import { Project } from "./project";
|
|
5
|
+
export declare namespace OfflineEngineRenderer {
|
|
6
|
+
const install: (url: string) => void;
|
|
7
|
+
const getWorkerUrl: () => string;
|
|
8
|
+
const start: (source: Project, optExportConfiguration: Option<ExportStemsConfiguration>, progress: Progress.Handler, abortSignal?: AbortSignal, sampleRate?: int) => Promise<AudioData>;
|
|
9
|
+
}
|
|
10
|
+
//# sourceMappingURL=OfflineEngineRenderer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"OfflineEngineRenderer.d.ts","sourceRoot":"","sources":["../src/OfflineEngineRenderer.ts"],"names":[],"mappings":"AAAA,OAAO,EAAS,GAAG,EAAa,MAAM,EAAS,QAAQ,EAAmB,MAAM,kBAAkB,CAAA;AAClG,OAAO,EAAC,SAAS,EAAO,MAAM,kBAAkB,CAAA;AAEhD,OAAO,EAGH,wBAAwB,EAK3B,MAAM,0BAA0B,CAAA;AACjC,OAAO,EAAC,OAAO,EAAC,MAAM,WAAW,CAAA;AAIjC,yBAAiB,qBAAqB,CAAC;IAG5B,MAAM,OAAO,GAAI,KAAK,MAAM,KAAG,IAGrC,CAAA;IAEM,MAAM,YAAY,QAAO,MAE/B,CAAA;IAEM,MAAM,KAAK,GAAU,QAAQ,OAAO,EACf,wBAAwB,MAAM,CAAC,wBAAwB,CAAC,EACxD,UAAU,QAAQ,CAAC,OAAO,EAC1B,cAAc,WAAW,EACzB,aAAY,GAAY,KAAG,OAAO,CAAC,SAAS,CAuIvE,CAAA;CACJ"}
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import { Errors, isDefined, Option, panic, Terminator } from "@opendaw/lib-std";
|
|
2
|
+
import { AudioData } from "@opendaw/lib-dsp";
|
|
3
|
+
import { Communicator, Messenger } from "@opendaw/lib-runtime";
|
|
4
|
+
import { ExportStemsConfiguration } from "@opendaw/studio-adapters";
|
|
5
|
+
import { AudioWorklets } from "./AudioWorklets";
|
|
6
|
+
export var OfflineEngineRenderer;
|
|
7
|
+
(function (OfflineEngineRenderer) {
|
|
8
|
+
let workerUrl = Option.None;
|
|
9
|
+
OfflineEngineRenderer.install = (url) => {
|
|
10
|
+
console.debug(`OfflineEngineWorkerUrl: '${url}'`);
|
|
11
|
+
workerUrl = Option.wrap(url);
|
|
12
|
+
};
|
|
13
|
+
OfflineEngineRenderer.getWorkerUrl = () => {
|
|
14
|
+
return workerUrl.unwrap("OfflineEngineWorkerUrl is missing (call 'install' first)");
|
|
15
|
+
};
|
|
16
|
+
OfflineEngineRenderer.start = async (source, optExportConfiguration, progress, abortSignal, sampleRate = 48_000) => {
|
|
17
|
+
const numStems = ExportStemsConfiguration.countStems(optExportConfiguration);
|
|
18
|
+
if (numStems === 0) {
|
|
19
|
+
return panic("Nothing to export");
|
|
20
|
+
}
|
|
21
|
+
const numberOfChannels = numStems * 2;
|
|
22
|
+
const { promise, reject, resolve } = Promise.withResolvers();
|
|
23
|
+
const worker = new Worker(OfflineEngineRenderer.getWorkerUrl(), { type: "module" });
|
|
24
|
+
const messenger = Messenger.for(worker);
|
|
25
|
+
const protocol = Communicator.sender(messenger.channel("offline-engine"), dispatcher => new class {
|
|
26
|
+
initialize(enginePort, progressPort, config) {
|
|
27
|
+
return dispatcher.dispatchAndReturn(this.initialize, enginePort, progressPort, config);
|
|
28
|
+
}
|
|
29
|
+
render(config) {
|
|
30
|
+
return dispatcher.dispatchAndReturn(this.render, config);
|
|
31
|
+
}
|
|
32
|
+
step(samples) {
|
|
33
|
+
return dispatcher.dispatchAndReturn(this.step, samples);
|
|
34
|
+
}
|
|
35
|
+
stop() { dispatcher.dispatchAndForget(this.stop); }
|
|
36
|
+
});
|
|
37
|
+
const channel = new MessageChannel();
|
|
38
|
+
const progressChannel = new MessageChannel();
|
|
39
|
+
const syncStreamBuffer = new SharedArrayBuffer(1024);
|
|
40
|
+
const controlFlagsBuffer = new SharedArrayBuffer(4);
|
|
41
|
+
const terminator = new Terminator();
|
|
42
|
+
const projectCopy = source.copy();
|
|
43
|
+
const { timelineBox, boxGraph } = projectCopy;
|
|
44
|
+
boxGraph.beginTransaction();
|
|
45
|
+
timelineBox.loopArea.enabled.setValue(false);
|
|
46
|
+
boxGraph.endTransaction();
|
|
47
|
+
const engineMessenger = Messenger.for(channel.port2);
|
|
48
|
+
Communicator.executor(engineMessenger.channel("engine-to-client"), {
|
|
49
|
+
log: (message) => console.log("OFFLINE-ENGINE", message),
|
|
50
|
+
error: (reason) => console.error("OFFLINE-ENGINE", reason),
|
|
51
|
+
ready: () => { },
|
|
52
|
+
fetchAudio: (uuid) => new Promise((resolve, reject) => {
|
|
53
|
+
const handler = source.sampleManager.getOrCreate(uuid);
|
|
54
|
+
const subscription = handler.subscribe(state => {
|
|
55
|
+
if (state.type === "error") {
|
|
56
|
+
reject(new Error(state.reason));
|
|
57
|
+
subscription.terminate();
|
|
58
|
+
}
|
|
59
|
+
else if (state.type === "loaded") {
|
|
60
|
+
resolve(handler.data.unwrap());
|
|
61
|
+
subscription.terminate();
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
}),
|
|
65
|
+
fetchSoundfont: (uuid) => new Promise((resolve, reject) => {
|
|
66
|
+
const handler = source.soundfontManager.getOrCreate(uuid);
|
|
67
|
+
const subscription = handler.subscribe(state => {
|
|
68
|
+
if (state.type === "error") {
|
|
69
|
+
reject(new Error(state.reason));
|
|
70
|
+
subscription.terminate();
|
|
71
|
+
}
|
|
72
|
+
else if (state.type === "loaded") {
|
|
73
|
+
resolve(handler.soundfont.unwrap());
|
|
74
|
+
subscription.terminate();
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
}),
|
|
78
|
+
notifyClipSequenceChanges: () => { },
|
|
79
|
+
switchMarkerState: () => { }
|
|
80
|
+
});
|
|
81
|
+
const engineCommands = Communicator.sender(engineMessenger.channel("engine-commands"), dispatcher => new class {
|
|
82
|
+
play() { dispatcher.dispatchAndForget(this.play); }
|
|
83
|
+
stop(reset) { dispatcher.dispatchAndForget(this.stop, reset); }
|
|
84
|
+
setPosition(position) { dispatcher.dispatchAndForget(this.setPosition, position); }
|
|
85
|
+
prepareRecordingState(countIn) { dispatcher.dispatchAndForget(this.prepareRecordingState, countIn); }
|
|
86
|
+
stopRecording() { dispatcher.dispatchAndForget(this.stopRecording); }
|
|
87
|
+
queryLoadingComplete() { return dispatcher.dispatchAndReturn(this.queryLoadingComplete); }
|
|
88
|
+
panic() { dispatcher.dispatchAndForget(this.panic); }
|
|
89
|
+
noteSignal(signal) { dispatcher.dispatchAndForget(this.noteSignal, signal); }
|
|
90
|
+
ignoreNoteRegion(uuid) { dispatcher.dispatchAndForget(this.ignoreNoteRegion, uuid); }
|
|
91
|
+
scheduleClipPlay(clipIds) { dispatcher.dispatchAndForget(this.scheduleClipPlay, clipIds); }
|
|
92
|
+
scheduleClipStop(trackIds) { dispatcher.dispatchAndForget(this.scheduleClipStop, trackIds); }
|
|
93
|
+
setupMIDI(port, buffer) { dispatcher.dispatchAndForget(this.setupMIDI, port, buffer); }
|
|
94
|
+
terminate() { dispatcher.dispatchAndForget(this.terminate); }
|
|
95
|
+
});
|
|
96
|
+
channel.port2.start();
|
|
97
|
+
progressChannel.port2.start();
|
|
98
|
+
let cancelled = false;
|
|
99
|
+
if (isDefined(abortSignal)) {
|
|
100
|
+
abortSignal.onabort = () => {
|
|
101
|
+
engineCommands.stop(true);
|
|
102
|
+
protocol.stop();
|
|
103
|
+
terminator.terminate();
|
|
104
|
+
worker.terminate();
|
|
105
|
+
cancelled = true;
|
|
106
|
+
reject(Errors.AbortError);
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
progressChannel.port2.onmessage = (event) => progress(event.data.frames / sampleRate);
|
|
110
|
+
await protocol.initialize(channel.port1, progressChannel.port1, {
|
|
111
|
+
sampleRate,
|
|
112
|
+
numberOfChannels,
|
|
113
|
+
processorsUrl: AudioWorklets.processorsUrl,
|
|
114
|
+
syncStreamBuffer,
|
|
115
|
+
controlFlagsBuffer,
|
|
116
|
+
project: projectCopy.toArrayBuffer(),
|
|
117
|
+
exportConfiguration: optExportConfiguration.unwrapOrUndefined()
|
|
118
|
+
});
|
|
119
|
+
engineCommands.play();
|
|
120
|
+
protocol.render({}).then(channels => {
|
|
121
|
+
if (cancelled) {
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
terminator.terminate();
|
|
125
|
+
worker.terminate();
|
|
126
|
+
const numberOfFrames = channels[0].length;
|
|
127
|
+
const audioData = AudioData.create(sampleRate, numberOfFrames, numberOfChannels);
|
|
128
|
+
for (let channelIndex = 0; channelIndex < numberOfChannels; channelIndex++) {
|
|
129
|
+
audioData.frames[channelIndex].set(channels[channelIndex]);
|
|
130
|
+
}
|
|
131
|
+
resolve(audioData);
|
|
132
|
+
}).catch(reason => {
|
|
133
|
+
if (!cancelled) {
|
|
134
|
+
terminator.terminate();
|
|
135
|
+
worker.terminate();
|
|
136
|
+
reject(reason);
|
|
137
|
+
}
|
|
138
|
+
});
|
|
139
|
+
return promise;
|
|
140
|
+
};
|
|
141
|
+
})(OfflineEngineRenderer || (OfflineEngineRenderer = {}));
|
|
@@ -5,7 +5,7 @@ import { RingBuffer, SampleLoader, SampleLoaderState } from "@opendaw/studio-ada
|
|
|
5
5
|
export declare class RecordingWorklet extends AudioWorkletNode implements Terminable, SampleLoader {
|
|
6
6
|
#private;
|
|
7
7
|
readonly uuid: UUID.Bytes;
|
|
8
|
-
constructor(context: BaseAudioContext, config: RingBuffer.Config
|
|
8
|
+
constructor(context: BaseAudioContext, config: RingBuffer.Config);
|
|
9
9
|
own<T extends Terminable>(terminable: T): T;
|
|
10
10
|
limit(count: int): void;
|
|
11
11
|
setFillLength(value: int): void;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"RecordingWorklet.d.ts","sourceRoot":"","sources":["../src/RecordingWorklet.ts"],"names":[],"mappings":"AAAA,OAAO,EAEH,GAAG,EAEH,QAAQ,EACR,MAAM,EAGN,YAAY,EACZ,UAAU,EAEV,IAAI,EACP,MAAM,kBAAkB,CAAA;AACzB,OAAO,EAAC,SAAS,EAAW,MAAM,kBAAkB,CAAA;AACpD,OAAO,EAAC,KAAK,EAAc,MAAM,qBAAqB,CAAA;AACtD,OAAO,EAEH,UAAU,EACV,YAAY,EACZ,iBAAiB,EAEpB,MAAM,0BAA0B,CAAA;AAMjC,qBAAa,gBAAiB,SAAQ,gBAAiB,YAAW,UAAU,EAAE,YAAY;;IAGtF,QAAQ,CAAC,IAAI,EAAE,IAAI,CAAC,KAAK,CAAkB;gBAa/B,OAAO,EAAE,gBAAgB,EAAE,MAAM,EAAE,UAAU,CAAC,MAAM
|
|
1
|
+
{"version":3,"file":"RecordingWorklet.d.ts","sourceRoot":"","sources":["../src/RecordingWorklet.ts"],"names":[],"mappings":"AAAA,OAAO,EAEH,GAAG,EAEH,QAAQ,EACR,MAAM,EAGN,YAAY,EACZ,UAAU,EAEV,IAAI,EACP,MAAM,kBAAkB,CAAA;AACzB,OAAO,EAAC,SAAS,EAAW,MAAM,kBAAkB,CAAA;AACpD,OAAO,EAAC,KAAK,EAAc,MAAM,qBAAqB,CAAA;AACtD,OAAO,EAEH,UAAU,EACV,YAAY,EACZ,iBAAiB,EAEpB,MAAM,0BAA0B,CAAA;AAMjC,qBAAa,gBAAiB,SAAQ,gBAAiB,YAAW,UAAU,EAAE,YAAY;;IAGtF,QAAQ,CAAC,IAAI,EAAE,IAAI,CAAC,KAAK,CAAkB;gBAa/B,OAAO,EAAE,gBAAgB,EAAE,MAAM,EAAE,UAAU,CAAC,MAAM;IAuBhE,GAAG,CAAC,CAAC,SAAS,UAAU,EAAE,UAAU,EAAE,CAAC,GAAG,CAAC;IAE3C,KAAK,CAAC,KAAK,EAAE,GAAG,GAAG,IAAI;IAEvB,aAAa,CAAC,KAAK,EAAE,GAAG,GAAG,IAAI;IAE/B,IAAI,cAAc,IAAI,GAAG,CAA6C;IACtE,IAAI,IAAI,IAAI,MAAM,CAAC,SAAS,CAAC,CAAoB;IACjD,IAAI,KAAK,IAAI,MAAM,CAAC,KAAK,CAAC,CAA6E;IACvG,IAAI,KAAK,IAAI,iBAAiB,CAAqB;IAEnD,UAAU,IAAI,IAAI;IAElB,SAAS,CAAC,QAAQ,EAAE,QAAQ,CAAC,iBAAiB,CAAC,GAAG,YAAY;IAQ9D,SAAS,IAAI,IAAI;IAOjB,QAAQ,IAAI,MAAM;CAqCrB"}
|
package/dist/RecordingWorklet.js
CHANGED
|
@@ -18,7 +18,7 @@ export class RecordingWorklet extends AudioWorkletNode {
|
|
|
18
18
|
#isRecording = true;
|
|
19
19
|
#limitSamples = Number.POSITIVE_INFINITY;
|
|
20
20
|
#state = { type: "record" };
|
|
21
|
-
constructor(context, config
|
|
21
|
+
constructor(context, config) {
|
|
22
22
|
super(context, "recording-processor", {
|
|
23
23
|
numberOfInputs: 1,
|
|
24
24
|
channelCount: config.numberOfChannels,
|
|
@@ -32,12 +32,8 @@ export class RecordingWorklet extends AudioWorkletNode {
|
|
|
32
32
|
this.#reader = RingBuffer.reader(config, array => {
|
|
33
33
|
if (this.#isRecording) {
|
|
34
34
|
this.#output.push(array);
|
|
35
|
-
|
|
36
|
-
if (this.numberOfFrames >=
|
|
37
|
-
this.#peakWriter.append(array);
|
|
38
|
-
}
|
|
39
|
-
const need = this.numberOfFrames - latencyInSamples;
|
|
40
|
-
if (need >= this.#limitSamples) {
|
|
35
|
+
this.#peakWriter.append(array);
|
|
36
|
+
if (this.numberOfFrames >= this.#limitSamples) {
|
|
41
37
|
this.#finalize().catch(error => console.warn(error));
|
|
42
38
|
}
|
|
43
39
|
}
|
|
@@ -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;
|
|
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"}
|
|
@@ -66,7 +66,7 @@ export class CaptureAudio extends Capture {
|
|
|
66
66
|
const mediaStream = streamOption.unwrap();
|
|
67
67
|
const channelCount = mediaStream.getAudioTracks().at(0)?.getSettings().channelCount ?? 1;
|
|
68
68
|
const numChunks = 128;
|
|
69
|
-
const recordingWorklet = audioWorklets.createRecording(channelCount, numChunks
|
|
69
|
+
const recordingWorklet = audioWorklets.createRecording(channelCount, numChunks);
|
|
70
70
|
return RecordAudio.start({
|
|
71
71
|
recordingWorklet,
|
|
72
72
|
mediaStream,
|
|
@@ -74,7 +74,8 @@ export class CaptureAudio extends Capture {
|
|
|
74
74
|
audioContext,
|
|
75
75
|
project,
|
|
76
76
|
capture: this,
|
|
77
|
-
gainDb: this.#gainDb
|
|
77
|
+
gainDb: this.#gainDb,
|
|
78
|
+
outputLatency: audioContext.outputLatency ?? 0
|
|
78
79
|
});
|
|
79
80
|
}
|
|
80
81
|
async #updateStream() {
|
|
@@ -12,8 +12,9 @@ export declare namespace RecordAudio {
|
|
|
12
12
|
project: Project;
|
|
13
13
|
capture: Capture;
|
|
14
14
|
gainDb: number;
|
|
15
|
+
outputLatency: number;
|
|
15
16
|
};
|
|
16
|
-
export const start: ({ recordingWorklet, mediaStream, sampleManager, audioContext, project, capture, gainDb }: RecordAudioContext) => Terminable;
|
|
17
|
+
export const start: ({ recordingWorklet, mediaStream, sampleManager, audioContext, project, capture, gainDb, outputLatency }: RecordAudioContext) => Terminable;
|
|
17
18
|
export {};
|
|
18
19
|
}
|
|
19
20
|
//# sourceMappingURL=RecordAudio.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"RecordAudio.d.ts","sourceRoot":"","sources":["../../src/capture/RecordAudio.ts"],"names":[],"mappings":"AAAA,OAAO,EAA6B,UAAU,EAAmB,MAAM,kBAAkB,CAAA;AAUzF,OAAO,EAAa,mBAAmB,EAAY,MAAM,0BAA0B,CAAA;AACnF,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;
|
|
1
|
+
{"version":3,"file":"RecordAudio.d.ts","sourceRoot":"","sources":["../../src/capture/RecordAudio.ts"],"names":[],"mappings":"AAAA,OAAO,EAA6B,UAAU,EAAmB,MAAM,kBAAkB,CAAA;AAUzF,OAAO,EAAa,mBAAmB,EAAY,MAAM,0BAA0B,CAAA;AACnF,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,UAsIL,CAAA;;CACJ"}
|
|
@@ -5,12 +5,11 @@ import { ColorCodes, TrackType } 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 }) => {
|
|
8
|
+
RecordAudio.start = ({ recordingWorklet, mediaStream, sampleManager, audioContext, project, capture, gainDb, outputLatency }) => {
|
|
9
9
|
const terminator = new Terminator();
|
|
10
10
|
const beats = PPQN.fromSignature(1, project.timelineBox.signature.denominator.getValue());
|
|
11
|
-
const { editing, engine, boxGraph } = project;
|
|
12
|
-
const
|
|
13
|
-
const uuid = recordingWorklet.uuid;
|
|
11
|
+
const { editing, engine, boxGraph, timelineBox } = project;
|
|
12
|
+
const originalUuid = recordingWorklet.uuid;
|
|
14
13
|
sampleManager.record(recordingWorklet);
|
|
15
14
|
const streamSource = audioContext.createMediaStreamSource(mediaStream);
|
|
16
15
|
const streamGain = audioContext.createGain();
|
|
@@ -20,8 +19,13 @@ export var RecordAudio;
|
|
|
20
19
|
streamGain.disconnect();
|
|
21
20
|
streamSource.disconnect();
|
|
22
21
|
}));
|
|
23
|
-
let
|
|
24
|
-
|
|
22
|
+
let fileBox = Option.None;
|
|
23
|
+
let currentTake = Option.None;
|
|
24
|
+
let lastPosition = 0;
|
|
25
|
+
let currentWaveformOffset = outputLatency;
|
|
26
|
+
const { tempoMap, env: { audioContext: { sampleRate } } } = project;
|
|
27
|
+
const { loopArea } = timelineBox;
|
|
28
|
+
const createFileBox = () => {
|
|
25
29
|
const fileDateString = new Date()
|
|
26
30
|
.toISOString()
|
|
27
31
|
.replaceAll("T", "-")
|
|
@@ -29,13 +33,16 @@ export var RecordAudio;
|
|
|
29
33
|
.replaceAll(":", "-")
|
|
30
34
|
.replaceAll("Z", "");
|
|
31
35
|
const fileName = `Recording-${fileDateString}`;
|
|
32
|
-
|
|
36
|
+
return AudioFileBox.create(boxGraph, originalUuid, box => box.fileName.setValue(fileName));
|
|
37
|
+
};
|
|
38
|
+
const createTakeRegion = (position, waveformOffset, forceNewTrack) => {
|
|
39
|
+
const trackBox = RecordTrack.findOrCreate(editing, capture.audioUnitBox, TrackType.Audio, forceNewTrack);
|
|
33
40
|
const collectionBox = ValueEventCollectionBox.create(boxGraph, UUID.generate());
|
|
34
41
|
const stretchBox = AudioPitchStretchBox.create(boxGraph, UUID.generate());
|
|
35
42
|
WarpMarkerBox.create(boxGraph, UUID.generate(), box => box.owner.refer(stretchBox.warpMarkers));
|
|
36
43
|
const warpMarkerBox = WarpMarkerBox.create(boxGraph, UUID.generate(), box => box.owner.refer(stretchBox.warpMarkers));
|
|
37
44
|
const regionBox = AudioRegionBox.create(boxGraph, UUID.generate(), box => {
|
|
38
|
-
box.file.refer(fileBox);
|
|
45
|
+
box.file.refer(fileBox.unwrap());
|
|
39
46
|
box.events.refer(collectionBox.owners);
|
|
40
47
|
box.regions.refer(trackBox.regions);
|
|
41
48
|
box.position.setValue(position);
|
|
@@ -43,50 +50,84 @@ export var RecordAudio;
|
|
|
43
50
|
box.timeBase.setValue(TimeBase.Musical);
|
|
44
51
|
box.label.setValue("Recording");
|
|
45
52
|
box.playMode.refer(stretchBox);
|
|
53
|
+
box.waveformOffset.setValue(waveformOffset);
|
|
46
54
|
});
|
|
47
55
|
capture.addRecordedRegion(regionBox);
|
|
48
56
|
project.selection.select(regionBox);
|
|
49
|
-
return {
|
|
50
|
-
}
|
|
51
|
-
const
|
|
57
|
+
return { trackBox, regionBox, warpMarkerBox };
|
|
58
|
+
};
|
|
59
|
+
const finalizeTake = (take, loopDurationPPQN) => {
|
|
60
|
+
const { regionBox, warpMarkerBox } = take;
|
|
61
|
+
if (regionBox.isAttached()) {
|
|
62
|
+
regionBox.duration.setValue(loopDurationPPQN);
|
|
63
|
+
regionBox.loopDuration.setValue(loopDurationPPQN);
|
|
64
|
+
regionBox.mute.setValue(true);
|
|
65
|
+
const seconds = tempoMap.intervalToSeconds(0, loopDurationPPQN);
|
|
66
|
+
warpMarkerBox.position.setValue(loopDurationPPQN);
|
|
67
|
+
warpMarkerBox.seconds.setValue(seconds);
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
const startNewTake = (position) => {
|
|
71
|
+
currentTake = Option.wrap(createTakeRegion(position, currentWaveformOffset, true));
|
|
72
|
+
};
|
|
52
73
|
terminator.ownAll(Terminable.create(() => {
|
|
53
|
-
if (recordingWorklet.numberOfFrames === 0 ||
|
|
74
|
+
if (recordingWorklet.numberOfFrames === 0 || fileBox.isEmpty()) {
|
|
54
75
|
console.debug("Abort recording audio.");
|
|
55
|
-
sampleManager.remove(
|
|
76
|
+
sampleManager.remove(originalUuid);
|
|
56
77
|
recordingWorklet.terminate();
|
|
57
78
|
}
|
|
58
79
|
else {
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
80
|
+
currentTake.ifSome(({ regionBox: { duration } }) => {
|
|
81
|
+
recordingWorklet.limit(Math.ceil((currentWaveformOffset + tempoMap.intervalToSeconds(0, duration.getValue())) * sampleRate));
|
|
82
|
+
});
|
|
83
|
+
fileBox.ifSome(fb => fb.endInSeconds.setValue(recordingWorklet.numberOfFrames / sampleRate));
|
|
62
84
|
}
|
|
63
85
|
}), engine.position.catchupAndSubscribe(owner => {
|
|
64
86
|
if (!engine.isRecording.getValue()) {
|
|
65
87
|
return;
|
|
66
88
|
}
|
|
67
|
-
|
|
89
|
+
const currentPosition = owner.getValue();
|
|
90
|
+
const loopEnabled = loopArea.enabled.getValue();
|
|
91
|
+
const loopFrom = loopArea.from.getValue();
|
|
92
|
+
const loopTo = loopArea.to.getValue();
|
|
93
|
+
const loopDurationPPQN = loopTo - loopFrom;
|
|
94
|
+
const loopDurationSeconds = tempoMap.intervalToSeconds(loopFrom, loopTo);
|
|
95
|
+
if (loopEnabled && currentTake.nonEmpty() && currentPosition < lastPosition) {
|
|
96
|
+
editing.modify(() => {
|
|
97
|
+
currentTake.ifSome(take => finalizeTake(take, loopDurationPPQN));
|
|
98
|
+
currentWaveformOffset += loopDurationSeconds;
|
|
99
|
+
startNewTake(loopFrom);
|
|
100
|
+
}, false);
|
|
101
|
+
}
|
|
102
|
+
lastPosition = currentPosition;
|
|
103
|
+
if (fileBox.isEmpty()) {
|
|
68
104
|
streamGain.connect(recordingWorklet);
|
|
69
|
-
|
|
105
|
+
editing.modify(() => {
|
|
106
|
+
fileBox = Option.wrap(createFileBox());
|
|
107
|
+
const position = quantizeFloor(currentPosition, beats);
|
|
108
|
+
currentTake = Option.wrap(createTakeRegion(position, currentWaveformOffset, false));
|
|
109
|
+
}, false);
|
|
70
110
|
}
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
111
|
+
currentTake.ifSome(({ regionBox, warpMarkerBox }) => {
|
|
112
|
+
editing.modify(() => {
|
|
113
|
+
if (regionBox.isAttached()) {
|
|
114
|
+
const { duration, loopDuration } = regionBox;
|
|
115
|
+
const distanceInPPQN = Math.floor(currentPosition - regionBox.position.getValue());
|
|
116
|
+
duration.setValue(distanceInPPQN);
|
|
117
|
+
loopDuration.setValue(distanceInPPQN);
|
|
118
|
+
warpMarkerBox.position.setValue(distanceInPPQN);
|
|
119
|
+
const seconds = tempoMap.intervalToSeconds(0, distanceInPPQN);
|
|
120
|
+
const totalSamples = Math.ceil((currentWaveformOffset + seconds) * sampleRate);
|
|
121
|
+
recordingWorklet.setFillLength(totalSamples);
|
|
122
|
+
fileBox.ifSome(fb => fb.endInSeconds.setValue(totalSamples / sampleRate));
|
|
123
|
+
warpMarkerBox.seconds.setValue(seconds);
|
|
124
|
+
}
|
|
125
|
+
else {
|
|
126
|
+
terminator.terminate();
|
|
127
|
+
currentTake = Option.None;
|
|
128
|
+
}
|
|
129
|
+
}, false);
|
|
130
|
+
});
|
|
90
131
|
}));
|
|
91
132
|
return terminator;
|
|
92
133
|
};
|
|
@@ -2,6 +2,6 @@ import { AudioUnitBox, TrackBox } from "@opendaw/studio-boxes";
|
|
|
2
2
|
import { TrackType } from "@opendaw/studio-adapters";
|
|
3
3
|
import { BoxEditing } from "@opendaw/lib-box";
|
|
4
4
|
export declare namespace RecordTrack {
|
|
5
|
-
const findOrCreate: (editing: BoxEditing, audioUnitBox: AudioUnitBox, type: TrackType) => TrackBox;
|
|
5
|
+
const findOrCreate: (editing: BoxEditing, audioUnitBox: AudioUnitBox, type: TrackType, forceCreate?: boolean) => TrackBox;
|
|
6
6
|
}
|
|
7
7
|
//# sourceMappingURL=RecordTrack.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"RecordTrack.d.ts","sourceRoot":"","sources":["../../src/capture/RecordTrack.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,YAAY,EAAE,QAAQ,EAAC,MAAM,uBAAuB,CAAA;AAE5D,OAAO,EAAC,SAAS,EAAC,MAAM,0BAA0B,CAAA;AAClD,OAAO,EAAC,UAAU,EAAC,MAAM,kBAAkB,CAAA;AAE3C,yBAAiB,WAAW,CAAC;IAClB,MAAM,YAAY,GAAI,SAAS,UAAU,EAAE,cAAc,YAAY,EAAE,MAAM,SAAS,KAAG,
|
|
1
|
+
{"version":3,"file":"RecordTrack.d.ts","sourceRoot":"","sources":["../../src/capture/RecordTrack.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,YAAY,EAAE,QAAQ,EAAC,MAAM,uBAAuB,CAAA;AAE5D,OAAO,EAAC,SAAS,EAAC,MAAM,0BAA0B,CAAA;AAClD,OAAO,EAAC,UAAU,EAAC,MAAM,kBAAkB,CAAA;AAE3C,yBAAiB,WAAW,CAAC;IAClB,MAAM,YAAY,GAAI,SAAS,UAAU,EAAE,cAAc,YAAY,EAAE,MAAM,SAAS,EAAE,cAAa,OAAe,KAAG,QAiB7H,CAAA;CACJ"}
|
|
@@ -2,14 +2,16 @@ import { TrackBox } from "@opendaw/studio-boxes";
|
|
|
2
2
|
import { asInstanceOf, UUID } from "@opendaw/lib-std";
|
|
3
3
|
export var RecordTrack;
|
|
4
4
|
(function (RecordTrack) {
|
|
5
|
-
RecordTrack.findOrCreate = (editing, audioUnitBox, type) => {
|
|
5
|
+
RecordTrack.findOrCreate = (editing, audioUnitBox, type, forceCreate = false) => {
|
|
6
6
|
let index = 0 | 0;
|
|
7
7
|
for (const trackBox of audioUnitBox.tracks.pointerHub.incoming()
|
|
8
8
|
.map(({ box }) => asInstanceOf(box, TrackBox))) {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
9
|
+
if (!forceCreate) {
|
|
10
|
+
const hasNoRegions = trackBox.regions.pointerHub.isEmpty();
|
|
11
|
+
const matchesType = trackBox.type.getValue() === type;
|
|
12
|
+
if (hasNoRegions && matchesType) {
|
|
13
|
+
return trackBox;
|
|
14
|
+
}
|
|
13
15
|
}
|
|
14
16
|
index = Math.max(index, trackBox.index.getValue());
|
|
15
17
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -11,6 +11,7 @@ export * from "./ui";
|
|
|
11
11
|
export * from "./ysync";
|
|
12
12
|
export * from "./AudioDevices";
|
|
13
13
|
export * from "./AudioOfflineRenderer";
|
|
14
|
+
export * from "./OfflineEngineRenderer";
|
|
14
15
|
export * from "./AudioUtils";
|
|
15
16
|
export * from "./EffectBox";
|
|
16
17
|
export * from "./EffectFactory";
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,WAAW,CAAA;AACzB,cAAc,SAAS,CAAA;AACvB,cAAc,cAAc,CAAA;AAC5B,mBAAmB,UAAU,CAAA;AAC7B,cAAc,QAAQ,CAAA;AACtB,cAAc,WAAW,CAAA;AACzB,cAAc,WAAW,CAAA;AACzB,cAAc,aAAa,CAAA;AAC3B,cAAc,YAAY,CAAA;AAC1B,cAAc,MAAM,CAAA;AACpB,cAAc,SAAS,CAAA;AAEvB,cAAc,gBAAgB,CAAA;AAC9B,cAAc,wBAAwB,CAAA;AACtC,cAAc,cAAc,CAAA;AAC5B,cAAc,aAAa,CAAA;AAC3B,cAAc,iBAAiB,CAAA;AAC/B,cAAc,mBAAmB,CAAA;AACjC,cAAc,UAAU,CAAA;AACxB,cAAc,gBAAgB,CAAA;AAC9B,cAAc,iBAAiB,CAAA;AAC/B,cAAc,eAAe,CAAA;AAC7B,cAAc,yBAAyB,CAAA;AACvC,cAAc,gBAAgB,CAAA;AAC9B,cAAc,SAAS,CAAA;AACvB,cAAc,eAAe,CAAA;AAC7B,cAAc,qBAAqB,CAAA;AACnC,cAAc,iBAAiB,CAAA;AAC/B,cAAc,WAAW,CAAA;AACzB,cAAc,WAAW,CAAA;AACzB,cAAc,iBAAiB,CAAA;AAC/B,cAAc,kBAAkB,CAAA"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,WAAW,CAAA;AACzB,cAAc,SAAS,CAAA;AACvB,cAAc,cAAc,CAAA;AAC5B,mBAAmB,UAAU,CAAA;AAC7B,cAAc,QAAQ,CAAA;AACtB,cAAc,WAAW,CAAA;AACzB,cAAc,WAAW,CAAA;AACzB,cAAc,aAAa,CAAA;AAC3B,cAAc,YAAY,CAAA;AAC1B,cAAc,MAAM,CAAA;AACpB,cAAc,SAAS,CAAA;AAEvB,cAAc,gBAAgB,CAAA;AAC9B,cAAc,wBAAwB,CAAA;AACtC,cAAc,yBAAyB,CAAA;AACvC,cAAc,cAAc,CAAA;AAC5B,cAAc,aAAa,CAAA;AAC3B,cAAc,iBAAiB,CAAA;AAC/B,cAAc,mBAAmB,CAAA;AACjC,cAAc,UAAU,CAAA;AACxB,cAAc,gBAAgB,CAAA;AAC9B,cAAc,iBAAiB,CAAA;AAC/B,cAAc,eAAe,CAAA;AAC7B,cAAc,yBAAyB,CAAA;AACvC,cAAc,gBAAgB,CAAA;AAC9B,cAAc,SAAS,CAAA;AACvB,cAAc,eAAe,CAAA;AAC7B,cAAc,qBAAqB,CAAA;AACnC,cAAc,iBAAiB,CAAA;AAC/B,cAAc,WAAW,CAAA;AACzB,cAAc,WAAW,CAAA;AACzB,cAAc,iBAAiB,CAAA;AAC/B,cAAc,kBAAkB,CAAA"}
|
package/dist/index.js
CHANGED
|
@@ -10,6 +10,7 @@ export * from "./ui";
|
|
|
10
10
|
export * from "./ysync";
|
|
11
11
|
export * from "./AudioDevices";
|
|
12
12
|
export * from "./AudioOfflineRenderer";
|
|
13
|
+
export * from "./OfflineEngineRenderer";
|
|
13
14
|
export * from "./AudioUtils";
|
|
14
15
|
export * from "./EffectBox";
|
|
15
16
|
export * from "./EffectFactory";
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
var h=n=>n!=null;var x=(n,e="asDefined failed")=>n??p(U(e));var U=n=>n instanceof Function?n():n;var z=n=>{throw new Error(`Unhandled ${n}`)},p=n=>{throw typeof n=="string"?new Error(n):n},q=(n,e)=>n?void 0:p(U(e));var G=n=>new Proxy({},{get(){return p(n)}});var F=class{value;status="success";constructor(e){this.value=e}error=G("Cannot access error when succeeded")},_=class{error;status="failure";constructor(e){this.error=e}value=G("Cannot access value when failed")},V=n=>{try{return new F(n())}catch(e){return new _(e)}};var Y=()=>{};var N;(function(n){n[n.Ascending=1]="Ascending",n[n.Descending=-1]="Descending"})(N||(N={}));var b=class{static#e=Object.freeze(new Array(0));static empty=()=>this.#e;static clear=e=>{e.length=0};static replace=(e,t)=>{e.length=0,e.push(...t)};static consume=(e,t)=>{for(let s=0;s<e.length;)t(e[s])?e.splice(s,1):s++};static peekFirst=e=>e.at(0)??null;static peekLast=e=>e.at(-1)??null;static getFirst=(e,t)=>x(e.at(0),t);static getLast=(e,t)=>x(e.at(-1),t);static getPrev=(e,t)=>{let s=e.indexOf(t);return s===-1?p(`${t} not found in ${e}`):x(e.at((s-1)%e.length),"Internal Error")};static getNext=(e,t)=>{let s=e.indexOf(t);return s===-1?p(`${t} not found in ${e}`):x(e.at((s+1)%e.length),"Internal Error")};static removeLast=(e,t)=>x(e.pop(),t);static create=(e,t)=>{let s=new Array(t);for(let r=0;r<t;r++)s[r]=e(r);return s};static equals=(e,t)=>{if(e.length!==t.length)return!1;for(let s=0;s<e.length;s++)if(e[s]!==t[s])return!1;return!0};static satisfy=(e,t)=>{if(e.length<2)return!0;let s=e[0];for(let r=1;r<e.length;r++)if(!t(s,e[r]))return!1;return!0};static remove=(e,t)=>{let s=e.indexOf(t);if(s===-1)return p(`${t} not found in ${e}`);e.splice(s,1)};static removeIf=(e,t)=>{for(let s=e.length-1;s>=0;s--)t(e[s])&&e.splice(s,1)};static removeOpt=(e,t)=>{let s=e.indexOf(t);return s===-1?!1:(e.splice(s,1),!0)};static hasDuplicates=e=>new Set(e).size<e.length;static removeDuplicates=e=>{let t=0,s=new Set;for(let r of e)s.has(r)||(s.add(r),e[t++]=r);return e.length=t,e};static removeDuplicateKeys=(e,t)=>{let s=0,r=new Set;for(let o of e){let i=o[t];r.has(i)||(r.add(i),e[s++]=o)}return e.length=s,e};static subtract(e,t,s){return e.filter(r=>!t.some(o=>s(r,o)))}static intersect(e,t,s){return e.filter(r=>t.some(o=>s(r,o)))}static merge(e,t,s){return[...e.filter(r=>!t.some(o=>s(r,o))),...t]}static*iterate(e){for(let t=0;t<e.length;t++)yield e[t]}static*iterateReverse(e){for(let t=e.length-1;t>=0;t--)yield e[t]}static*iterateStateFull(e){let t=e.length-1;for(let s=0;s<=t;s++)yield{value:e[s],isFirst:s===0,isLast:s===t}}static*iterateAdjacent(e){if(!(e.length<=1))for(let t=1,s=e[0];t<e.length;t++){let r=e[t];yield[s,r],s=r}}static isSorted(e,t=N.Ascending){if(e.length<2)return!0;let s=e[0];for(let r=1;r<e.length;r++){let o=e[r];if(Math.sign(s-o)===t)return!1;s=o}return!0}static toRecord(e,t){return e.reduce((s,r)=>(s[t(r)]=r,s),{})}static concatArrayBuffers(e,t){let s=new ArrayBuffer(e.byteLength+t.byteLength),r=new Uint8Array(s);return r.set(new Uint8Array(e),0),r.set(new Uint8Array(t),e.byteLength),s}};var I=class{static*empty(){}static one(e){return[e]}static count(e){let t=0;for(let s of e)t++;return t}static some(e,t){for(let s of e)if(t(s))return!0;return!1}static every(e,t){for(let s of e)if(!t(s))return!1;return!0}static reduce(e,t,s){let r=s,o=0;for(let i of e)r=t(r,i,o++);return r}static includes(e,t){for(let s of e)if(s===t)return!0;return!1}static forEach(e,t){for(let s of e)t(s)}static*map(e,t){let s=0;for(let r of e)yield t(r,s++)}static*take(e,t){let s=0;for(let r of e){if(s++>=t)return;yield r}}static filter(e,t){let s=[];for(let r of e)t(r)&&s.push(r);return s}static filterMap(e,t){let s=[];for(let r of e){let o=t(r);h(o)&&s.push(o)}return s}static reverse(e){let t=[];for(let s of e)t.push(s);return t.reverse()}static*pairWise(e){let t=e[Symbol.iterator](),{done:s,value:r}=t.next(),o=r;if(s!==!0)for(;;){let{done:i,value:a}=t.next();if(i===!0){yield[o,null];return}yield[o,a],o=a}}};var K=Object.freeze({Empty:{terminate:Y},create:n=>({terminate:n}),many:(...n)=>({terminate:()=>{for(;n.length>0;)n.pop().terminate()}})});var E=class{static subscribeMany(e,...t){return K.many(...t.map(s=>s.subscribe(()=>e(s))))}#e=new Set;subscribe(e){return this.#e.add(e),{terminate:()=>this.#e.delete(e)}}isEmpty(){return this.#e.size===0}notify(e){this.#e.forEach(t=>t(e))}observers(){return this.#e}terminate(){this.#e.clear()}};var T=class n{static POSITIVE_INFINITY=new n(Number.POSITIVE_INFINITY);static millis=e=>new n(e);static seconds=e=>new n(e*n.#e);static minutes=e=>new n(e*n.#t);static hours=e=>new n(e*n.#r);static days=e=>new n(e*n.#o);static toHHMMSS=e=>((e/3600|0)+100).toString().slice(1)+":"+((e/60|0)%60+100).toString().slice(1)+":"+(e%60+100).toString().slice(1);static#e=1e3;static#t=6e4;static#r=36e5;static#o=864e5;#s;constructor(e){this.#s=e}millis(){return this.#s}absSeconds(){return Math.abs(this.#s)/n.#e}absMinutes(){return Math.abs(this.#s)/n.#t}absHours(){return Math.abs(this.#s)/n.#r}absDays(){return Math.abs(this.#s)/n.#o}split(){return{d:Math.floor(this.absDays()),h:Math.floor(this.absHours())%24,m:Math.floor(this.absMinutes())%60,s:Math.floor(this.absSeconds())%60}}isNow(){return this.#s===0}isPast(){return this.#s<0}isFuture(){return this.#s>0}toUnitString(){let e,t,s=Math.floor(Math.abs(this.#s)/1e3),r=Math.floor(s/60),o=Math.floor(r/60),i=Math.floor(o/24);return s<60?(e=s,t="second"):r<60?(e=r,t="minute"):o<24?(e=o,t="hour"):(e=i,t="day"),new Intl.RelativeTimeFormat("en",{numeric:"auto",style:"long"}).format(e*Math.sign(this.#s),t)}toString(){if(isNaN(this.#s))return"NaN";if(!isFinite(this.#s))return"\u221E";let{d:e,h:t,m:s,s:r}=this.split();return e>0?[n.#n("d",e),n.#n("h",t),n.#n("m",s),n.#n("s",r)].join(", "):t>0?[n.#n("h",t),n.#n("m",s),n.#n("s",r)].join(", "):s>0?[n.#n("m",s),n.#n("s",r)].join(", "):r>0?n.#n("s",r):"now"}static#n=(e,t)=>{switch(e){case"d":return`${t} ${t<2?"day":"days"}`;case"h":return`${t} ${t<2?"hour":"hours"}`;case"m":return`${t} ${t<2?"minute":"minutes"}`;case"s":return`${t} ${t<2?"second":"seconds"}`;default:return z(e)}}};var $;(function(n){n.sender=(r,o)=>o(new t(r)),n.executor=(r,o)=>new s(r,o);let e=r=>{let o=[];for(let i of r)i instanceof MessagePort&&o.push(i),typeof ImageBitmap<"u"&&i instanceof ImageBitmap&&o.push(i),typeof OffscreenCanvas<"u"&&i instanceof OffscreenCanvas&&o.push(i);return o};class t{#e;#t=new Map;#r;#o=0;constructor(o){this.#e=o,this.#r=o.subscribe(this.#s)}terminate(){this.#r.terminate()}dispatchAndForget=(o,...i)=>{let a=e(i);this.#e.send({type:"send",returnId:!1,func:o.name,args:Array.from(I.map(i,c=>({value:c})))},a)};dispatchAndReturn=(o,...i)=>new Promise((a,c)=>{let l=I.reduce(i,(u,v,d)=>(typeof v=="function"&&u.push([d,v]),u),[]);this.#t.set(this.#o,{executorTuple:{resolve:a,reject:c},callbacks:new Map(l)});let f=e(i);this.#e.send({type:"send",returnId:this.#o,func:o.name,args:Array.from(I.map(i,(u,v)=>typeof u=="function"?{callback:v}:{value:u}))},f),this.#o++});#s=o=>{let i=this.#t.get(o.returnId);h(i)?o.type==="resolve"?(i.executorTuple.resolve(o.resolve),this.#t.delete(o.returnId)):o.type==="reject"?(i.executorTuple.reject(o.reject),this.#t.delete(o.returnId)):o.type==="callback"&&i.callbacks?.get(o.funcAt).apply(this,o.args):p(`Promise has already been resolved. ${JSON.stringify(o)}`)}}class s{#e;#t;#r;constructor(o,i){this.#e=o,this.#t=i,this.#r=o.subscribe(this.#o)}terminate(){this.#r.terminate()}#o=o=>{q(o.type==="send",()=>"Message type must be 'send'");let i=Object.getPrototypeOf(this.#t)===Object.getPrototypeOf({})?this.#t:Object.getPrototypeOf(this.#t),a=x(i[o.func],`${o.func.toString()} does not exists on ${this.#t}`),c=o.returnId;if(c===!1)a.apply(this.#t,o.args.map(l=>"value"in l?l.value:p(`${o.func.toString()} has no promise.`)));else try{a.apply(this.#t,o.args.map(f=>"callback"in f?(...u)=>this.#i(c,f.callback,u):f.value)).then(f=>{try{this.#s(c,f)}catch(u){this.#n(c,u)}},f=>this.#n(c,f))}catch(l){this.#n(c,l)}};#s=(o,i)=>this.#e.send({type:"resolve",returnId:o,resolve:i});#n=(o,i)=>this.#e.send({type:"reject",returnId:o,reject:i});#i=(o,i,a)=>this.#e.send({type:"callback",returnId:o,funcAt:i,args:a})}n.Executor=s})($||($={}));var W={for:n=>new D(n)},re=[],D=class{#e;#t=new E;constructor(e){if(this.#e=e,h(e.onmessage)||h(e.onmessageerror))throw console.error(e),new Error(`${e} is already wrapped.`);e.onmessage=t=>this.#t.notify(t.data),e.onmessageerror=t=>{throw new Error(t.type)}}send(e,t){this.#e.postMessage(e,t??re)}channel(e){return new k(this,e)}subscribe(e){return this.#t.subscribe(e)}terminate(){this.#t.terminate(),this.#e.onmessage=null,this.#e.onmessageerror=null}},k=class n{#e;#t;#r=new E;#o;constructor(e,t){this.#e=e,this.#t=t,this.#o=e.subscribe(s=>{"__id__"in s&&s.__id__==="42"&&"message"in s&&"channel"in s&&s.channel===t&&this.#r.notify(s.message)})}send(e,t){this.#e.send({__id__:"42",channel:this.#t,message:e},t)}channel(e){return new n(this,e)}subscribe(e){return this.#r.subscribe(e)}terminate(){this.#o.terminate(),this.#r.terminate()}};var A;(function(n){n.frame=()=>new Promise(e=>requestAnimationFrame(()=>e())),n.frames=e=>new Promise(t=>{let s=e,r=()=>{--s<=0?t():requestAnimationFrame(r)};requestAnimationFrame(r)}),n.timeSpan=(e,...t)=>new Promise(s=>setTimeout(s,e.millis(),...t)),n.event=(e,t)=>new Promise(s=>e.addEventListener(t,s,{once:!0})),n.observable=e=>new Promise(t=>{let s=e.subscribe(()=>{s.terminate(),t()})}),n.complete=e=>new Promise((t,s)=>{let r=setInterval(()=>{let{status:o,value:i,error:a}=V(()=>e.next());if(o==="success"){let{done:c,value:l}=i;c&&(clearInterval(r),t(l))}else clearInterval(r),s(a)},0)})})(A||(A={}));var C=(n,e)=>Math.floor(3840/e)*n,Z=(n,e=4,t=4)=>{let s=C(1,t),r=Math.floor(n/s),o=Math.floor(r/e),a=(Math.floor(n)-C(o*e,t))%s,c=Math.floor(a/240),l=a%240;return{bars:o,beats:r-o*e,semiquavers:c,ticks:l}},J=(n,e)=>n*e/60*960,X=(n,e)=>n*60/960/e,oe=(n,e)=>e*60/960/n,ie=(n,e,t)=>J(n/t,e),ae=(n,e,t)=>X(n,e)*t,ee={Bar:3840,Quarter:960,SemiQuaver:240,fromSignature:C,toParts:Z,secondsToPulses:J,pulsesToSeconds:X,secondsToBpm:oe,samplesToPulses:ie,pulsesToSamples:ae,toString:(n,e=4,t=4)=>{let{bars:s,beats:r,semiquavers:o,ticks:i}=Z(n|0,e,t);return`${s+1}.${r+1}.${o+1}:${i}`}};var P=128,Te=ee.fromSignature(1,48);var ce=Math.log(10)/20;var te=n=>Math.exp(n*ce);var L=class{port;constructor(){this.port=globalThis.__workletPort__}};function se(n){let e=globalThis;e.sampleRate=n.sampleRate,e.currentFrame=0,e.currentTime=0,e.AudioWorkletProcessor=L,e.registerProcessor=(t,s)=>{e.__registeredProcessors__=e.__registeredProcessors__||{},e.__registeredProcessors__[t]=s}}function R(n,e){let t=globalThis;t.currentFrame=n,t.currentTime=n/e}var B=null,Q=null,y=48e3,w=2,j=!1,m=0;$.executor(W.for(self).channel("offline-engine"),{async initialize(n,e,t){y=t.sampleRate,w=t.numberOfChannels,Q=e,m=0,se({sampleRate:y}),globalThis.__workletPort__=n,await import(t.processorsUrl);let s=globalThis.__registeredProcessors__["engine-processor"];B=new s({processorOptions:{syncStreamBuffer:t.syncStreamBuffer,controlFlagsBuffer:t.controlFlagsBuffer,project:t.project,exportConfiguration:t.exportConfiguration}})},async step(n){let e=b.create(()=>new Float32Array(n),w),t=b.create(()=>new Float32Array(P),w),s=0;for(;s<n;){let r=[t];R(m,y),B.process([[]],r),m+=P;let o=n-s,i=Math.min(o,P);for(let a=0;a<w;a++)e[a].set(r[0][a].subarray(0,i),s);s+=i}return e},async render(n){let{silenceThresholdDb:e,silenceDurationSeconds:t,maxDurationSeconds:s}=n,r=te(e??-72),o=Math.ceil((t??10)*y),i=h(s)?Math.ceil(s*y):1/0,a=b.create(()=>[],w),c=0,l=!1,f=0;for(j=!0,await A.timeSpan(T.seconds(0));j&&m<i;){let d=[b.create(()=>new Float32Array(P),w)];R(m,y);let S=B.process([[]],d),M=0;for(let g of d[0])for(let ne of g){let H=Math.abs(ne);H>M&&(M=H)}let O=M<=r;if(M>r&&(l=!0),O&&l){if(c+=P,c>=o)break}else c=0;for(let g=0;g<w;g++)a[g].push(d[0][g].slice());if(m+=P,!S)break;m-f>=y&&(f=m,h(Q)&&Q.postMessage({frames:m}),await new Promise(g=>setTimeout(g,0)))}let u=m-c+Math.min(y/4,c);return b.create(v=>{let d=new Float32Array(u),S=0;for(let M of a[v]){if(S>=u)break;let O=Math.min(M.length,u-S);d.set(M.subarray(0,O),S),S+=O}return d},w)},stop(){j=!1}});
|
|
2
|
+
//# sourceMappingURL=offline-engine.js.map
|